Page 1 of 3 You can use Canvas to draw new graphics or you can load existing images and process them at the pixel level. In this extract from a chapter in my new book on JavaScript Graphics we look at how it works.
Now available as a paperback or ebook from Amazon.
JavaScript Bitmap Graphics With Canvas
Contents
- JavaScript Graphics
- Getting Started With Canvas
- Drawing Paths
Extract: Basic Paths Extract: SVG Paths Extract: Bezier Curves
- Stroke and Fill
Extract: Stroke Properties Extract: Fill and Holes Extract: Gradient & Pattern Fills
- Transformations
Extract: Transformations Extract: Custom Coordinates Extract Graphics State
- Text
Extract: Text, Typography & SVG Extract: Unicode
- Clipping, Compositing and Effects
Extract: Clipping & Basic Compositing
- Generating Bitmaps
Extract: Introduction To Bitmaps Extract : Animation
- WebWorkers & OffscreenCanvas
Extract: Web Workers Extract: OffscreenCanvas
- Bit Manipulation In JavaScript
Extract: Bit Manipulation
- Typed Arrays
Extract: Typed Arrays
- Files, blobs, URLs & Fetch
Extract: Blobs & Files Extract: Read/Writing Local Files Extract: Fetch API **NEW!
- Image Processing
Extract: ImageData Extract:The Filter API
- 3D WebGL
Extract: WebGL 3D
- 2D WebGL
Extract: WebGL Convolutions
<ASIN:B07XJQDS4Z>
<ASIN:1871962579>
<ASIN:1871962560>
In the early part of this book the emphasis was on creating graphics by drawing on the canvas. By contrast the latter part of the book is about loading graphics files into the canvas. Why would you want to do this as opposed to simply loading files into an Image object? If you only want to display the bitmaps then you should use an image object. The advantage of a canvas object is that you can manipulate the pixel values to change what is displayed. This is image processing and it is the subject of this chapter.
Getting at the Pixels
The drawImage method allows you to make the connection between a bitmap and the canvas object, but what about getting at the pixels of a bitmap?
You can do this quite easily with the help of the ImageData object.
There are two methods for creating an ImageData object:
-
ctx.createImageData(w,h) creates an ImageData object with width w and height h
-
ctx.createImageData(IData) creates an ImageData object the same size as the ImageData object specified by IData.
In both cases all pixels are set to transparent black, i.e. R=0, G=0, B=0 and A=0.
These two methods correspond to constructors which can be used in a Worker where no canvas is available:
-
new ImageData(w,h)
-
new ImageDate(IData)
A third method creates an ImageData object from the pixels in a specified area of a canvas object:
-
ctx.getImageData(x,y,w,h) creates an ImageData object from the pixels in the rectangle with top left corner at x,y and width w and height h.
To manipulate the pixels in the ImageData object you make use of its data property which is a Uint8ClampedArray (see Chapter11) of pixel values in the order RGBA for each pixel.
The first element of the array i.e. data[0] is the R value for the pixel in the top left corner. The pixels are stored in the array in row order with four elements to each pixel.
A few moments thought should convince you that the R value for the pixel at x,y in the rectangle of pixels is stored at:
data[(x+y*w)*4]
We can use this to write a method that allows direct access to the color information for the pixel at x,y. We can then use these methods to manipulate the pixel data and then write the result to the canvas using:
ctx.putImageData(ImageData,x,y);
which renders the ImageData object with its top left corner at x,y.
There is another more sophisticated putImageData method:
ctx.putImageData(ImageData,x,y,sx,sy,sw,sh)
which only transfers data from the ImageData object within the rectangle with top left corner at sx,sy and width sw and height sh.
This is more or less all we need to create and modify graphics working at the pixel level. Of course, it would be a good idea to implement some slightly higher-level methods and while this is easy it does raise the question of how best to package them to make them easy to use. One approach is simply to add them as ad-hoc methods to the ImageData object that you create. For example:
var ImDat=ctx.createImageData(100,100);
ImDat.getPixel=function(x,y){
var i=(x+y*this.width)*4;
return {R:this.data[i],
G:this.data[i+1],
B:this.data[i+2],
A:this.data[i+3]
}
}
adds the getPixel method to the ImDat object to return an object with the properties R, G, B and A for the pixel at x,y.
You can add a similar setPixel method:
ImDat.setPixel=function(x,y,c){
var i=(x+y*this.width)*4;
this.data[i]=c.R;
this.data[i+1]=c.G;
this.data[i+2]=c.B;
this.data[i+3]=c.A;
}
to set the pixel at x,y to the color specified by the RGBA properties of the c object. Of course, in a production system you would need to add checks that the parameters were of the correct type and that the values were all in the range 0 to 255. You could also write other methods to work with color defined in other ways - CSS colors, color in the range 0 to 1 and so on.
|