React + Twine

Twine is a neat ‘open-source tool for telling interactive, nonlinear stories’, as its blurb says.  It lets you write stories using variables, conditionals, and other simple programming primitives.  It supports three different languages, but I’ll focus only on the default, Harlowe.  Some example code:

Hello $name. (if: $isMale)[You're male!](else:)[You're female!].

This works pretty well, but it can be really cumbersome to do anything complicated.  I was curious to see how well it could be integrated into React.  Harlowe purposefully does not expose any of its internals, so our solution is going to need to be pretty hacky.

The solution that I came up with was to add some javascript to a startup tag that Harlowe will run, and attach the necessary private variable to the global window variable.  Then to load and run the Harlowe engine in the react componentWillMount function.  Like so:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  componentWillMount() {
    const script = document.createElement("script");
    script.src = "harlowe-min.js";
    script.async = true;
    document.body.appendChild(script);
  }

  render() {
    return (
<div className="App">
        <header id="header">My header

</header>
<div id="container">
          <main id="center" className="column">
            <article>
              <tw-story dangerouslySetInnerHTML={{__html: ""}}></tw-story>
              <tw-storydata startnode={1} style={{display:"none"}}>
                <script role="script" id="twine-user-script" type="text/twine-javascript">{`
                  if (!window.harlowe){ window.harlowe = State; }
                `}</script>
                <tw-passagedata pid={1} name="Start">**Success!**(set: $foo to 1) Foo is $foo</tw-passagedata>
              </tw-storydata>
            </article>
          </main>

          <nav id="left" className="column">
<h3>Left heading</h3>
<ul>
	<li><a href="#">Some Link</a></li>
</ul>
</nav>
<div id="right" className="column">
<h3>Right heading</h3>
</div>
</div>
<div id="footer-wrapper">
          <footer id="footer">&nbsp;

</footer></div>
</div>
);
  }
}

export default App;

It seemed to work just fine without

dangerouslySetInnerHTML={{__html: ""}}

But I added it to make it clear to react that we don’t want react to manage this.

Unfortunately I couldn’t proceed further than this with trying to make them nicely integrate. I couldn’t see a way to hook into the engine to know when a variable is updated via, say, a user clicking on a (link:..)[].

There is a VarRef object that would allow exactly this with jQuery, by doing something like:

var setState = this.setState.bind(this);
VarRef.on('set', (obj, name, value) => {
  if (obj === State.variables) {
    setState({[name]: value})
  }
})

Unfortunately the VarRef is not exposed in any scope that I can find. The source code is available, so the VarRef can be exposed with a fairly simple change, but not without doing that, as far I can tell.

Advertisements

Changing the color of image in HTML with an SVG feColorMatrix filter

In a previous post, I changed the color of a simple image (of a pair of eyes) by converting it to an SVG first, and then changing the SVG colors.

But what if you want to a take a complex image and change the color?

 

In Photoshop/Gimp this can be achieved by creating a new layer on top of the image and filling it with a solid color, and then setting its Mode to ‘Multiply’.  But how can we reproduce this on the web?

There are various solutions using svg filters (feFlood and feBlend) but these are not very well supported in browsers.  So I’ve come up with a solution that is very well supported in all modern browsers, including IE.

<svg height="100%" width="100%" >
<defs>
<filter id="f_skintint"  x="0%" y="0%" width="100%" height="100%">
<feColorMatrix
type="matrix"
values="0.5 0   0   0   0
0   0.5 0   0   0
0   0   0.5 0   0
0   0   0   1   0 "/>
</filter>
</defs>
<g filter="url(#f_skintint)" >
<image xlinkHref="girl-head.png" x="507" y="239" width="597" height="491" />
...
</g>
</svg>

Replace the numbers 0.5 with the rgb values of the color that you want.  For example, in react:


hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : {r: 255, g: 255, b: 255};
}

skinColorDef(colorAsString) {
const hex = hexToRgb(colorAsString); /* <-- See next for an improvement*/
const r = hex.r/255;
const g = hex.g/255;
const b = hex.b/255;
return (
<defs>
<filter id="f_skintint"  x="0%" y="0%" width="100%" height="100%">
<feColorMatrix
type="matrix"
values={r +"   0   0   0   0 " +
"0   "+g+"   0   0   0 " +
"0   0   "+b+"   0   0 " +
"0   0   0   1   0 "}/>
</filter>
</defs>
);
}

 

We can now tint our image with a color like skinColorDef(“#e7b48f”).

But let’s make small improvement.  It’s not obvious what the final color is going to be because the tint color is multiplied by the color in the image.  So let’s make it more intuitive by first looking at the main color in the image (e.g. using the color picker in gimp/photoshop) and then dividing (i.e ‘un-multiplying’) the colorAsString by that color.

For example, the skin color in that girl image is #fff2f2 which is (255,242,228).  So:


divideByImageSkinColor(rgb) {
return {r: rgb.r * (255/255), g: rgb.g * (255/242), b: rgb.b * (255/242)}
}

and modify the skinColorDef like:


skinColorDef(colorAsString) {
const hex = divideByImageSkinColor(hexToRgb(colorAsString));

Now we can just chose the colors directly.  For skin, the Fitzpatrick Scale is a nice place to start:

fitzpatrick-color-chart

We can now use these RGB values directly in our skinColorDef function.  Here’s an example html combobox to select the color: (The onChange function is left to you to implement)


<select name="Skin color" onChange={...}>
<option value="#f4d0b1">Light</option>
<option value="#e7b48f">Fair</option>
<option value="#d29e7c">Medium</option>
<option value="#ba7750">Olive</option>
<option value="#a55d2b">Brown</option>
<option value="#3c201d">Black</option>
</select>

And that’s it!

Sidenote: Many years ago, I wrote the graphics drivers (when I worked at Imagination Technologies) to accelerate this sort of multiply operation using shaders.  That driver is used in the iPhone, iPad, TomTom, and many other small devices.

Photoshop/gimp layers to SVG

Ever wanted to export multiple layers in a Gimp or Photoshop image, with each layer as its own PNG, but the whole thing then wrapped up as an SVG?

The usefulness is that an artist can create an image of, say, a person, with eyes of various different colours in multiple layers.  Then we can create an SVG file that we can embed in an html page, and then change the color of the eyes through Javascript.

So take this example.  In this image we have a face made up of various layers, and the layers are further grouped in GroupLayers.

So imagine having this image, then in Javascript on your page being able to swap out just the eye image.  Or just the mouth image.

To achieve this, I had to modify an existing gimp python script from 5 years ago that has since bitrotted.  Back when it was written, there was no such thing as group layers, so the script doesn’t work now.  A bit of hacking, and I get:

#!/usr/bin/env python
# -*- coding: <utf-8> -*-
# Author: Erdem Guven <zuencap@yahoo.com>
# Copyright 2016 John Tapsell
# Copyright 2010 Erdem Guven
# Copyright 2009 Chris Mohler
# "Only Visible" and filename formatting introduced by mh
# License: GPL v3+
# Version 0.2
# GIMP plugin to export as SVG

# Save this to ~/.gimp-*/plug-ins/export_svg.py

from gimpfu import *
import os, re

gettext.install("gimp20-python", gimp.locale_directory, unicode=True)

def format_filename(imagename, layer):
	layername = layer.name.decode('utf-8')
	regex = re.compile("[^-\w]", re.UNICODE)
	filename = imagename + '-' + regex.sub('_', layername) + '.png'
	return filename

def export_layers(dupe, layers, imagename, path, only_visible, inkscape_layers):
	images = ""
	for layer in layers:
		if not only_visible or layer.visible:
			style=""
			if layer.opacity != 100.0:
				style="opacity:"+str(layer.opacity/100.0)+";"
			if not layer.visible:
				style+="display:none"
			if style != "":
				style = 'style="'+style+'"'

			if hasattr(layer,"layers"):
				image = '<g inkscape:groupmode="layer" inkscape:label="%s" %s>' % (layer.name.decode('utf-8'),style)
				image += export_layers(dupe, layer.layers, imagename, path, only_visible, inkscape_layers)
				image += '</g>'
				images = image + images
			else:
				filename = format_filename(imagename, layer)
				fullpath = os.path.join(path, filename);
				pdb.file_png_save_defaults(dupe, layer, fullpath, filename)

				image = ""
				if inkscape_layers:
					image = '<g inkscape:groupmode="layer" inkscape:label="%s" %s>' % (layer.name.decode('utf-8'),style)
					style = ""
				image += ('<image xlink:href="%s" x="%d" y="%d" width="%d" height="%d" %s/>\n' %
					(filename,layer.offsets[0],layer.offsets[1],layer.width,layer.height,style))
				if inkscape_layers:
					image += '</g>'
				images = image + images
		dupe.remove_layer(layer)
	return images

def export_as_svg(img, drw, imagename, path, only_visible=False, inkscape_layers=True):
	dupe = img.duplicate()

	images = export_layers(dupe, dupe.layers, imagename, path, only_visible, inkscape_layers)

	svgpath = os.path.join(path, imagename+".svg");
	svgfile = open(svgpath, "w")
	svgfile.write("""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: GIMP export as svg plugin -->

<svg xmlns:xlink="http://www.w3.org/1999/xlink" """) 	if inkscape_layers: 		svgfile.write('xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" ') 	svgfile.write('width="%d" height="%d">' % (img.width, img.height));
	svgfile.write(images);
	svgfile.write("</svg>");

register(
	proc_name=("python-fu-export-as-svg"),
	blurb=("Export as SVG"),
	help=("Export an svg file and an individual PNG file per layer."),
	author=("Erdem Guven <zuencap@yahoo.com>"),
	copyright=("Erdem Guven"),
	date=("2016"),
	label=("Export as SVG"),
	imagetypes=("*"),
	params=[
		(PF_IMAGE, "img", "Image", None),
		(PF_DRAWABLE, "drw", "Drawable", None),
		(PF_STRING, "imagename", "File prefix for images", "img"),
		(PF_DIRNAME, "path", "Save PNGs here", os.getcwd()),
		(PF_BOOL, "only_visible", "Only Visible Layers?", False),
		(PF_BOOL, "inkscape_layers", "Create Inkscape Layers?", True),
		   ],
	results=[],
	function=(export_as_svg),
	menu=("<Image>/File"),
	domain=("gimp20-python", gimp.locale_directory)
	)

main()

(Note that if you get an error ‘cannot pickle GroupLayers’, this is a bug in gimp. It can be fixed by editing

    350: gimpshelf.shelf[key] = defaults

)

Which when run, produces:

girl-Layer12.png
girl-Layer9.png
girl-Layer11.png
girl-Layer14.png
girl.svg

(I later renamed the layers to something more sensible 🙂 )

The (abbreviated) svg file looks like:


<g inkscape:groupmode="layer" inkscape:label="Expression" >

<g inkscape:groupmode="layer" inkscape:label="Eyes" >

<g inkscape:groupmode="layer" inkscape:label="Layer10" ><image xlink:href="girl-Layer10.png" x="594" y="479" width="311" height="86" /></g>

<g inkscape:groupmode="layer" inkscape:label="Layer14" ><image xlink:href="girl-Layer14.png" x="664" y="470" width="176" height="22" /></g>

<g inkscape:groupmode="layer" inkscape:label="Layer11" ><image xlink:href="girl-Layer11.png" x="614" y="483" width="268" height="85" /></g>

<g inkscape:groupmode="layer" inkscape:label="Layer9" ><image xlink:href="girl-Layer9.png" x="578" y="474" width="339" height="96" /></g>

<g inkscape:groupmode="layer" inkscape:label="Layer12" ><image xlink:href="girl-Layer12.png" x="626" y="514" width="252" height="30" /></g>

</g>

</g>

We can now paste the contents of that SVG directly into our html file, add an id to the groups or image tag, and use CSS or Javascript to set the style to show and hide different layers as needed.

CSS Styling

This all works as-is, but I wanted to go a bit further.  I didn’t actually have different colors of the eyes.  I also wanted to be able to easily change the color.  I use the Inkscape’s Trace Bitmap to turn the layer with the eyes into a vector, like this:

girl-eyecolor

Unfortunately, WordPress.com won’t let me actually use SVG images, so this is a PNG of an SVG created from a PNG….

I used as few colors as possible in the SVG, resulting in just 4 colors used in 4 paths.  I manually edited the SVG, and moved the color style to its own tag, like so:


<defs>
<style type="text/css"><![CDATA[ #eyecolor_darkest { fill:#34435a; } #eyecolor_dark { fill:#5670a1; } #eyecolor_light { fill:#6c8abb; } #eyecolor_lightest { fill:#b4dae5; } ]]></style>

</defs>

<path id="eyecolor_darkest" ..../>

The result is that I now have an svg of a pair of eyes that can be colored through css.  For example, green:

girl-eyecolor_green

Which can now be used directly in the head svg in an html, and styled through normal css:

head_green

Colors

For the sake of completeness, I wanted to let the user change the colors, but not have to make them specify each color individually. I have 4 colors used for the eye, but they are obviously related. Looking at the blue colors in HSL space we get:

RGB:#34435a =  hsl(216, 27%, 28%)
RGB:#5670a1 =  hsl(219, 30%, 48%)
RGB:#6c8abb =  hsl(217, 37%, 58%)
RGB:#b4dae5 =  hsl(193, 49%, 80%)

Annoyingly, the lightest color has a different hue. I viewed this color in gimp, change the hue to 216, then tried to find the closest saturation and value that matched it. 216, 85%, 87% seemed the best fit.

So, armed with this, we now have a way to set the color of the eye with a single hue:

#eyecolor_darkest  =  hsl(hue, 27%, 28%)
#eyecolor_dark     =  hsl(hue, 30%, 48%)
#eyecolor_light    =  hsl(hue, 37%, 58%)
#eyecolor_lightest =  hsl(hue, 85%, 87%)

Or in code:

function setEyeColorHue(hue) {
    document.getElementById("eyecolor_darkest").style.fill = "hsl("+hue+", 27%, 28%)";
    document.getElementById("eyecolor_dark").style.fill = "hsl("+hue+", 30%, 48%)";
    document.getElementById("eyecolor_light").style.fill = "hsl("+hue+", 37%, 58%)";
    document.getElementById("eyecolor_lightest").style.fill = "hsl("+hue+", 85%, 87%)";
}
<label for="hue">Color:</label>
<input type="range" id="hue" min="0" value="216" max="359" step="1" oninput="setEyeColorHue(this.value)" onchange="setEyeColorHue(this.value)"/>

Tinting a more complex image

But what if the image is more complex, and you don’t want to convert it to an SVG?  E.g.

The solution is to apply a filter to multiply the layer by another color.

See my follow up post: Changing the color of image in HTML with an SVG feColorMatrix filter