Stipple patterns on an HTML5 canvas
Avishkar Autar · Aug 25 2012 · Graphics and Rendering
I wanted to play around a bit with stipple patterns after seeing stippling done with photos on the LinkedIn news feed. However, what I’m going to present is not what LinkedIn does. LinkedIn applies the stipple pattern as a background-image on a DOM element above a <img> element with a (fairly low resolution) JPEG – the stippling may help to alleviate the negative visual impact of the low-resolution image. What I’m going to show is how to do stippling on an HTML5 canvas, which allows for a much greater degree of freedom in terms of what’s possible, but is also slower and requires a modern browser.
I’m going to make use of the GraphicsCore and FXController classes in a previous post, Gaussian blur on an HTML5 canvas. In that post I presented the concept of writing shaders as plug-in to the FXController class to apply different per-pixel effects. What I’m going to present are shaders for a few simple stipple patterns. Applying the shader is simply a matter of passing it into the constructor for the FXController class, e.g.
var theShader = Shader.crossStippleShader;
var fxCtrlr = new FXController(ctxSource, ctxDest, theShader, width, height, 100, 1);
fxCtrlr.init();
Checkerboard Stipple
This shader has the effect of creating a checkerboard pattern.
The source pixel is preserved if (x+y) % 2 == 0, otherwise the pixel’s alpha is reduced to 66.
Shader.checkerboardStippleShader = function (imageData, bufWrite, index, x, y, r, g, b, a, passNum, frameNum, maxFrames)
{
if( (x+y)%2 == 0) {
GraphicsCore.setPixel(bufWrite, index, r, g, b, 255);
}
else {
GraphicsCore.setPixel(bufWrite, index, r, g, b, 66);
}
}
Shader.checkerboardStippleShader.numPassesRequired = 1;
Dot Stipple
This shader blends a white pixel into the source image where x%2 == 0 && y%2 == 0, in effect creating a dotted grid pattern.
The alpha blending code is a straightforward implementation of alpha compositing, but since we’re blending with white (where r=1.0, g=1.0, and b=1.0) the equation is simplified and there is no second color value; we’re just biasing the source color by the alpha value. Also note that this is different than simply changing the alpha of the source pixel (as was done in the Checkerboard Stipple shader), here we’re always blending with white, in the previous shader we’re blending with whatever is the background of the DOM element.
Shader.dotStippleShader = function (imageData, bufWrite, index, x, y, r, g, b, a, passNum, frameNum, maxFrames)
{
var alpha = 0.8;
var r1 = r / 255.0;
var rF = Math.floor((alpha*r1 + (1.0-alpha)) * 255.0);
var g1 = g / 255.0;
var gF = Math.floor((alpha*g1 + (1.0-alpha)) * 255.0);
var b1 = b / 255.0;
var bF = Math.floor((alpha*b1 + (1.0-alpha)) * 255.0);
if( x%2 == 0 && y%2 == 0) {
GraphicsCore.setPixel(bufWrite, index, rF, gF, bF, 255);
} else {
GraphicsCore.setPixel(bufWrite, index, r, g, b, 255);
}
}
Shader.dotStippleShader.numPassesRequired = 1;
Quincunx Stipple
With this shader we blend in a white pixel at every 4 pixels (x%4 == 0 && y%4 == 0, the target pixel) and also at the 4 orthogonally adjacent pixels around the target, creating a quincunx pattern.
Shader.quincunxStippleShader = function (imageData, bufWrite, index, x, y, r, g, b, a, passNum, frameNum, maxFrames)
{
var alpha = 0.78;
var r1 = r / 255.0;
var rF = Math.floor((alpha*r1 + (1.0-alpha)) * 255.0);
var g1 = g / 255.0;
var gF = Math.floor((alpha*g1 + (1.0-alpha)) * 255.0);
var b1 = b / 255.0;
var bF = Math.floor((alpha*b1 + (1.0-alpha)) * 255.0);
if( (x%4 == 0 && y%4 == 0) ||
((x+1)%4 == 0 && y%4 == 0) ||
((x-1)%4 == 0 && y%4 == 0) ||
(x%4 == 0 && (y+1)%4 == 0) ||
(x%4 == 0 && (y-1)%4 == 0) )
{
GraphicsCore.setPixel(bufWrite, index, rF, gF, bF, 255);
}
else
{
GraphicsCore.setPixel(bufWrite, index, r, g, b, 255);
}
}
Shader.quincunxStippleShader.numPassesRequired = 1;
Cross Stipple
Similar to the quincunx stipple, but we blend in a white pixel at every 6 pixels (x%6 == 0 && y%6 == 0, the target pixel), the 4 orthogonally adjacent pixels around the target, and 4 additional pixels extending beyond the orthogonals, creating a cross (“+”) pattern.
Shader.crossStippleShader = function (imageData, bufWrite, index, x, y, r, g, b, a, passNum, frameNum, maxFrames)
{
var alpha = 0.78;
var r1 = r / 255.0;
var rF = Math.floor((alpha*r1 + (1.0-alpha)) * 255.0);
var g1 = g / 255.0;
var gF = Math.floor((alpha*g1 + (1.0-alpha)) * 255.0);
var b1 = b / 255.0;
var bF = Math.floor((alpha*b1 + (1.0-alpha)) * 255.0);
if( (x%6 == 0 && y%6 == 0) ||
((x+1)%6 == 0 && y%6 == 0) ||
((x-1)%6 == 0 && y%6 == 0) ||
((x+2)%6 == 0 && y%6 == 0) ||
((x-2)%6 == 0 && y%6 == 0) ||
(x%6 == 0 && (y+1)%6 == 0) ||
(x%6 == 0 && (y-1)%6 == 0) ||
(x%6 == 0 && (y+2)%6 == 0) ||
(x%6 == 0 && (y-2)%6 == 0)
)
{
GraphicsCore.setPixel(bufWrite, index, rF, gF, bF, 255);
}
else
{
GraphicsCore.setPixel(bufWrite, index, r, g, b, 255);
}
}
Shader.crossStippleShader.numPassesRequired = 1;
That’s all for now. There’s tons of variations possible with only minor code changes to alter blending, color, and the shape of the stipple patten.