Mandelbrot Renderer Update

Since my recent blog post on my Mandelbrot set renderer using JavaScript and Canvas I’ve been made aware of an extremely useful method to add image data to a canvas context; the putImageData() method. Thanks to Kevin Pickell for pointing this out, he made use of it in his own Mandelbrot renderer.

So I decided to update my renderer to use putImageData as it’s much quicker than drawing each pixel straight onto the canvas. Usage is simple:

//Grab our canvas and context
var canvasID = document.getElementById("canvasID");
var context = canvasID.getContext('2d');

//Create an empty image data object
var imageDataObject = context.createImageData(width, height);
//Add our image data to the object = imageData;
//Draw our image
context.putImageData(imageDataObject, 0, 0);

I found the way the imageDataObject expected data quite surprising at first; I think I was expecting an array of objects or an array of arrays, one for each pixel, but it expects a single large array with the red, green, blue and alpha value of each pixel in the canvas:

//Empty array
var imageData = [];
//For each pixel (top left to bottom right moving horizontally)
imageData.push(red, green, blue, alpha);
//Result: [r1, g1, b1, a1, r2, g2, b2, a2, r3....]

The advantage this method has is you calculate all your image data first, then pass it to the canvas. Before I was calculating a whole vertical row and drawing onto the canvas, before moving onto the next. Not at all ideal when you are repeating the process 600+ times.

Updating the non Web Workers version improved the render time by around 20 – 25 seconds! See the v2 version here. Unfortunately there were issues rotating the canvas after using putImageData(), no matter what I tried, the rotate() method seemed to be ignored, so I removed it. Maybe it’s a limitation of the method, but i’m still looking for a solution.

I had another surprise when adding the new draw method to the version using Web Workers (not fully functional). Since the worker simply needs to process the image data then pass it back to the window I assumed there wouldn’t be an issue; but that wasn’t to be. When letting the worker thread do the calculations it was taking around 8 seconds (initial render) to do this, where as in the main window it was taking less than 1 second. The time increased even more as you zoomed in:

  • Initial render: ~8000ms
  • +1 zoom: ~12000ms
  • +2 zoom: ~20000ms
  • +3 zoom: didn’t render

I have yet to find a solution to this issue; I’m guessing it may be some sort of worker thread priority or the way worker threads handle nested loops with calculations. Maybe a calculation rounding issue? If anyone has any ideas leave a comment.

Joe on May 15 10 / 134 Permalink

In regards to:

“Unfortunately there were issues rotating the canvas after using putImageData(), no matter what I tried, the rotate() method seemed to be ignored”

I tried to do the same thing, but the canvas specification has this to say:

“The current path, transformation matrix, shadow attributes, global alpha, the clipping region, and global composition operator must not affect the getImageData() and putImageData() methods.”

manu on June 11 10 / 161 Permalink

i ran into the same performance issue when trying to modify image data by a Web Worker. a web search lead me to your blog entry.

some profiling showed that the huge amount of extra time is spent for following two operations:

- posting back the pixel data Array from the worker, and
- assigning that Array to the property

where the former is responsible for slightly less than half of the extra time and the latter for the remainder.

i dont know your code in detail, but my implementation benefited from using only ‘integer’ values in the Array (Math.floor()), but although 3-4 times faster, the two aforementioned operations still are way to expensive to post large messages, like bitmaps. i even tend to say the message posting performance is ridiculous.

in conclusion i believe that Web Workers are completely useless for manipulating images. (not that i was happy about their implementation before…)

note: your code could be speed up by posting back an Array with the iteration counts only, and do the translation into colors outside the worker (the message size would be signifficantly smaller).

note2: maybe a custom serialization of the iteration counts would further reduce the message size. (AFAIK implicite JSON (de)serialization is used in your implementation (IIRC a gecko exclusive feature atm), which presumably produces some ammount of unnecessary overhead). maybe use RLE.

note3: when you zoomed in, did the colored pixels increase? this could explain the performance loss there, as a value of 0 takes less space than e.g. 255 when serialized.

note4: i still believe that Web Workers are not suitable for bitmap manipulation :/

Leave a Comment

Your email will not be published. Required fields are marked *.