Immediate color fill with HTML Canvas – SOS
TLDR: Demo is at https://shaneosullivan.github.io/example-canvas-fill/ , code is at https://github.com/shaneosullivan/example-canvas-fill .
The Downside
When constructing a web site or app utilizing HTML Canvas, it’s usually a requirement to help a flood fill. That’s, when the consumer chooses a color and clicks on a pixel, fill all the encompassing pixels that match the color of the clicked pixel with the consumer’s chosen color.
To take action you’ll be able to write a reasonably easy algorithm to step via the pixels one after the other, examine them to the clicked pixel and both change their color or not. For those who redraw the canvas whereas doing this, in order to offer the consumer with visible suggestions, it may well appear to be this.
This works however is sluggish and ugly. It’s potential to enormously pace this up, in order that it’s primarily prompt, and appears like this
To realize this we pre-process the supply picture and use the output to immediately apply a colored masks to the HTML Canvas.
Why did I work on this?
I’ve constructed an online based mostly app referred to as Kidz Fun Art for my two younger daughters, optimised to be used on a pill. The concept was to construct one thing enjoyable that by no means exhibits adverts to them or methods them into sneaky purchases by “accident”. I noticed them get irritated by the sluggish fill algorithm I first wrote, so my private pleasure compelled me to go resolve this drawback! Right here’s what the ultimate implementation of the answer to this drawback appears like on the app.
The Resolution
Begin with a picture that has a lot of enclosed areas, every with a uniform color inside these areas. On this instance, we’ll use a picture with 4 enclosed areas, numbered 1 via 4.
Now create an online employee, which is JavaScript that runs on a separate thread to the browser thread, so it doesn’t lock up the consumer interface when processing a variety of information.
let employee = new Employee("./src/employee.js");
The employee.js
file comprises the code to execute the fill algorithm. Within the browser UI code, ship the picture pixels to the employee by drawing the picture to a Canvas ingredient and calling the getImageData
operate. Be aware that you just ship an ImageBuffer object to the employee, not the ImageData itself
const canvas = doc.getElementById('mycanvas');
const context = canvas.getContext('second');
const dimensions = { top: canvas.top, width: canvas.width };
const img = new Picture();
img.onload = () => {
context.drawImage(img, 0, 0);
const imageData =
canvas.getImageData(0, 0, dimensions.width, dimensions.top);
employee.postMessage({
motion: "course of",
dimensions,
buffer: imageData.information.buffer,
},
[imageData.data.buffer]
);
};
The employee script then asynchronously inspects each pixel within the picture. It begins by setting the alpha (transparency) worth of every pixel to zero, which marks the pixel as unprocessed. When it finds a pixel with a zero alpha worth, it executes a FILL operation from that pixel, the place each surrounding pixel is given an incremental alpha worth. That’s, the primary time a fill is executed, all surrounding pixels are given an alpha model of 1, the second time an alpha worth of two is assigned, and so forth.
Every time a FILL completes, the employee shops an standalone picture of simply the realm utilized by the FILL (saved as an array of numbers). When it has inspected all pixels within the supply picture, it should ship again to the UI thread all the person picture ‘masks’ it has calculated, in addition to a single picture with all the alpha values set numbers between 1 and 255. Which means that utilizing this system, we will help a most of 255 distinct areas to instant-fill, which needs to be wonderful, as we will fall again to a sluggish fill if a given pixel has not been pre-processed.
You see within the totally processed picture above that every one pixels within the supply picture are assigned an alpha worth. The numeric worth corresponds to one of many masks, as proven under.
For this picture, it will generate 4 masks as within the picture above. The purple areas are the pixels with non-zero alpha values, and the white are the pixels with alpha values of zero.
When the consumer clicks on a pixel of the HTML Canvas node, the UI code checks the alpha worth within the picture returned from the employee. If the worth is 2, it selects the second merchandise within the array of masks it acquired.
Now it’s time to use some HTML Canvas magic, by means of the globalCompositeOperation
property. This property allows all kinds of enjoyable and attention-grabbing operations to be carried out with Canvas, however for our functions we have an interest within the source-in
worth. This makes it in order that calling fillRect()
on the Canvas context will solely fill the non-transparent pixels, and go away the others unchanged.
const pixelMaskContext = pixelMaskCanvasNode.getContext('second');
const pixelMaskImageData = new ImageData(
pixelMaskInfo.width,
pixelMaskInfo.top
);
pixelMaskImageData.information.set(
new Uint8ClampedArray(pixelMaskInfo.pixels)
);
pixelMaskContext.putImageData(pixelMaskImageData, 0, 0);
// Here is the canvas magic that makes it simply draw the non
// clear pixels onto our foremost canvas
pixelMaskContext.globalCompositeOperation = "source-in";
pixelMaskContext.fillStyle = color;
pixelMaskContext.fillRect(
0, 0, pixelMaskInfo.width, pixelMaskInfo.top
);
Now you’ve crammed the masks with a color, on this instance purple, you then simply have to attract that onto the canvas seen to the consumer on the prime left location of the masks, and also you’re achieved!
context.drawImage(
pixelMaskCanvasNode,
pixelMaskInfo.x,
pixelMaskInfo.y
);
It ought to appear to be the picture under when achieved
All of the code for that is accessible on Github at https://github.com/shaneosullivan/example-canvas-fill
You may see the demo operating at https://shaneosullivan.github.io/example-canvas-fill/
One caveat is that for those who do this code in your native laptop by simply opening the index.html
file, it is not going to work as browser safety is not going to let the Employee be registered. You want run a localhost server and run it from there.
P.S.
Because of the Excalidraw group for making it really easy to create these diagrams, what a implausible app!
Associated
Revealed by Shane O’Sullivan
I’m a software program engineer and supervisor from Eire. I spent 7 years working in Eire from 2003 – 2010, then ten years in Silicon Valley from 2010 to 2020.
In California I spent about 6.5 years at Fb Engineering, the final three of which I used to be an engineering supervisor within the Advertisements organisation specializing in buyer dealing with merchandise for creating and managing advertisements.
At Stripe I constructed the Developer Productiveness organisation, with groups that had been answerable for using the Ruby language, testing infrastructure, documentation, developer tooling (e.g. IDE integrations) and extra.
At Promise, I used to be Head of Engineering from 2018 – 2020, answerable for constructing the primary few iterations of our merchandise, hiring for all product roles, assembly with purchasers and buyers, and the rest wanted to get a tiny startup bootstrapped and profitable.
Now I’m again in Eire, engaged on my subsequent firm. Coming quickly (as of early 2023!).
This weblog comprises my varied musings on all issues technical/attention-grabbing on the interweb and past.
View all posts by Shane O’Sullivan
Revealed