As makers, random serendipity is very precious to us, as it represents an excellent opportunity to broaden our horizons by trying stuff that otherwise would not have crossed our minds.

This project was spawned by a chance encounter with an art museum curator during my stint in Edinburgh. The idea was to create a tool to generate Secret Portraits , also known as Anamorphosis , to make some cool merch that would support the operations of the museum.

The Tool Itself

WIP, use with caution!!! (mostly because it is very slow and eats up a large amount of RAM)


Dev Notes

This is an implementation of the algorithm described in this paper: Cylindrical and Conical Mirror Anamorphosis for Image Display

The sources for everything can be found in this GitHub repository .

Proof of Concept

As always when evaluating the feasibility of something, Python is your best friend 😉.

The script did generate usable images, but it was painfully SLOW. So much so that debugging it using images that were larger than 300px-ish was almost impossible.

In order to evaluate these prototypes without having a physical cylindrrical mirror, I came up with a pretty silly/genius idea (that honestly I was pretty proud of at the time):

Blender.

Input image

Input image

Output from the Python script

Output from the Python script

Blender prototype

Blender prototype

It’s not perfect, but it works!

It was clear from the start that I needed something faster, what a great excuse to finally give some new programming languages a go!

GO!!!

When the museum curator first proposed this collaboration with me, I already had an image of how this app is going to look like.

It’s gonna be an Electron app, with the UI built on something like Svelte for ease of deployability.

I have also been meaning to try WASM for a really long time, and from some preliminary searching it seems like two of the programming languages on my radar supported it: Go and Rust.

Both have pretty verbose syntax, but after some searching around it seemed like Rust was a whole nother cow with its super strict type system and extensive use of special characters.

Go it is.

Go and Floating Point Math

I have to say, having not written much C or any strongly typed language for a while, I now understand why some people look down on Python developers (at least in my uni that’s totally a thing). Python just takes care of so much for you under the hood that when I ported the code over to Go, things downright broke.

The issue was that instead of a continuous sector, the code was spitting out “discrete radii”.

Expected output

Expectation

Actual output

Actual output

Please don’t mind the transparency difference, it will be fixed eventually.

Anyway, this was super frustrating to debug, since the main transformation code was a one to one reproduction of the Python implentation.

A lot of fmt.println() statements later, I tracked the issue to be a type conversion discrepancy.

Basically, Python automagically sets variables that should be floats to become floats. Of course, Go isn’t nearly as luxurious, even though it does have some type inference available.

Here is the culprit:

1
var a float64 = float64(theta) * float64(3.14159265358) * float64((r+height-y)/(180*width))

Something fishy is happening in that last float64(), somehow the insides of that aren’t being interpreted as floating point numbers, so a lot of rounding was going on and well… it’s not great.

Brute forcing everything to become float64() did the trick, but now the code looks really ugly.

1
var a float64 = float64(theta) * float64(3.1415) * (float64(r+height-y) / float64(180*width)) //TODO: investigate floating point error here

float64() is probably overkill here, but I wanted to match the Python outputs, and it did seem like Python uses higher precision floats so I went with that.

Speed Issues

Using Go for this was fast, I saw a 10x speed increase over the original Python implementation, which is great!

At least when running it locally.

After compilation to WASM, all of a sudden things were much slower again. It was better than the PoC Python script running natively, but the speed advantage is greatly diminsished.

Also, the browser basically hangs when the image is processing. So the bigger the input image, the more disastrous the user experience is.

For some reason, the WASM program also uses a ton of RAM, and I mean a ton. Processing a 1000px image used on the order of 3GB in Firefox.

Microsoft Edge is much faster, so I’m not sure if what is happening with Firefox. Either way, there is a lot that can still be done do optimise the code, which is something to look into if this were to be taken to production.

On a side note, the way I pass data to and from the WASM binary is by serialising the data to base64, which can be directly applied as the src of html image tags. Initially, I thought of using Javascript to handle this, but as it turns out, passing the base64 string to Javascript and letting Javascript set the src was disastrously slow.

To fix that, I called the requisite getters and setters directly in the Go code, which made it a little annoying as it meant I had to go back and forth between the HTML and Go sources. However, the speedup was definitely worth it.

Going forward

This project is definitely in the “quick and dirty” stage at this point, and there are a lot of changes that I would eventually like to look into.

  • Use WebWorkers to try to eliminate the website hanging problem
  • Parallelization
  • Figure out how to make the image size more consistent, right now it’s really hacky, and different images will produuce very dimensionally different arcs
  • Slider interface to tweak the cylinder radius and sector angles interactively (which would require a massive speedup to not be unusable)
  • Integrate all the Go compilation stuff into Hugo Pipes itself for ease of maintainability

We’ll see how it goes with me starting my semester literally 2 days from writing this.

Until next time, happy tinkering!