Site menu Dodging Photoshophobia

Dodging Photoshophobia

I have a bigotry-level aversion to GUI tools, be it an IDE or an image editor. I like to express ideas and to communicate in textual form. On top of that, I have no artistic abilities and it is an ordeal to use a graphical tool like Photoshop or Inkscape to do anything.

For example, the HP-1xC emulators in my Web site used to have a design that was not mine. A pityful user collaborated the design for one flavor. I patched it to create the design for the remaining flavors — changing colors, labels, cropping here and there. Even a bigot can do that using GIMP, after all.

But it was painstaking work. In particular, the mobile versions ask for many tailored sizes (e.g. iPhone 5, iPhone 4 and iPads have all different screen proportions), and design is a maintenance headache over time.

Since the design of a calculator is not exactly fine art, it should be perfectly possible to generate it by automated means. My objective was: a) to be able to generate images from scratch, in every size needed, and b) not having to cope with image editors anymore. Images should be built by calling make, in the same fashion of a software build.

The current results were good enough to be used in a new version of the calculator: the 12C Platinum emulator. I will describe the workflow that might be useful for similar purposes.

SVG with PySVG

The images are generated in SVG, which is a XML-based vector graphics description language. I don't deal with XML directly (that would be worse than using Photoshop); I use the Python language and the PySVG module.

PySVG is quite crude, the documentation is lacking, but it was good enough for my purpose. Using a high-level language like Python allowed me to express the actual structure of the final image as classes: a calculator body contains a display and 39 keys, each key possesses up to 3 labels, and so on.

Subclasses allow some element to be reused by different calculators, each subclass expressing only the differences. Of course I put some extra effort not to repeat myself — much more than the stricly necessary to achieve the Platinum image — so I can attain maximum reusability when generating other "Voyager" images like the upcoming HP-15C.

SVG itself is quite powerful, the famed Inkscape tool is based around the SVG format. Using SVG keeps the door open for a semi-automatic approach: a script can generate the bulk of some image and then the artist uses Inkscape to add the final touches.

All math symbols are "rendered" by using Unicode fonts and some adjustments in size and/or positioning.

Initial rendering to PNG

In theory, most browsers support SVG, but each one renders the same given image differently, and/or may render it differently in some future update. Freezing the image in a bitmap-based image is safer.

I use Inkscape to do the render to bitmap. Fortunately, it accepts command-line instructions:

$INKSCAPE_PATH --export-png $2.png -w 2800 $1.svg

This command produces a "Git-friendly" PNG, meaning that PNG only changes when the actual image changes, so it can be stored in version control. More about this later on.

Note that this PNG is quite big (2800 pixels wide), but this is not the PNG that is shipped along with my software.

Shrinking and cropping to final PNG

In this step, I use ImageMagick to resize the image to the desired size. Some cropping and manipulation are executed on this step as well. One example:

IMP="-define png:exclude-chunks=date"

# simple resize
convert hp12cpv.png -adaptive-resize 984x1611! hp12cpv_ios_4.png

# crop
convert hp12cpv_ios_4.png -crop 984x1541+0+70! hp12cpv_ios_4.png

# Eat away some of the height between display and keys
convert hp12cpv_ios_4.png \( -clone 0 -crop 984x1471+0+274! \
	-repage \+0+203 \) \
	-flatten hp12cpv_ios_4.png
convert hp12cpv_ios_4.png -crop 984x1468+0+0! $IMP hp12cpv_ios_4.png

# Make it Git-friendly
optipng hp12cpv_ios_4.png

I employ OptiPNG to reduce PNG size and, more important, to keep the PNG "Git-friendly". ImageMagick adds some metadata and the PNG file changes every time it is generated. OptiPNG removes this metadata. This guarantees that the SHA-1 of a rebuilt PNG is always the same, provided that its "source" (the Python scripts) are unchanged. UPDATE: the final PNG can change due to software updates. See the next section for details.

ImageMagick is a wonderful tool, powerful and faithful to the spirit of my efforts — to manipulate images without having to use an image editor. Cropping and pasting operations get tricky fast, but I found my way after many cycles of trail and error.

In a certain sense, I mimicked my original workflow i.e. rendering the "classical" calculator in high-resolution and then cropping it to desired propotion. I acknowledge that it is somewhat baroque. Ideally the Python script itself should render different SVGs which are resized directly to final form. But it works as it stands; refactoring comes later.

Update: checking subjective changes in PNGs

One issue that cropped up: PNG images sometimes did change even though the Python scripts were untouched.

The problem is, PNG rendering changes when a) Inkscape is updated, b) the operating system is updated, probably because the font rendering changes, c) rendering are not the same in different machines, even if both run the same OS.

In general, the changes are not destructive; for example, font rendering tends to improve at every major OS upgrade. But, when a PNG changes SHA-1 unexpectedly, I need to check. Comparing old and new images side-by-side is not reliable. Some sort of "image diff" software is the tool for the job.

ImageMagick does have an image comparison function, but it is not ideal: it is all-or-nothing, creating a monocromatic white-red bitmap of changed pixels. I needed a "nuanced" diff, that painted diff pixels in a proportional fashion. For example, considering a white background for the "diff map image", barely different colors should be light gray.

Also, the diff map should use colors: if a pixel that used to be red is now rendered purple, the diff map would signal this by a blue pixel (because red plus blue equals purple).

Not having found a tool like this, I wrote my own: pictdiff. Afterwards, I found some similar tools, some even have similar names. In retrospect, it was a waste of time to write it, but it was fun, and even more fun to rewrite it in a number of modern languages like Rust. (Actually, the very best thing would be to collaborate a new diff command to ImageMagick.)

Besides the diff PNG for visual inspection, this tool returns a "diff metric" and exits with non-zero code when images are found different. This is used to stop the build system when the PNG image changes, forcing me to inspect it. If the new image is good, it is copied onto the archive of "old" images that is the basis of pictdiff comparisons.