It's possible by extracting the RGB data from the canvas and calculate their luma values.
There are a couple of requisites that needs to be fulfilled though, like CORS (which does not seem to be a problem in this case as you can already save out an image) and if you wish to keep the original data after save you can either copy the current image on canvas to a temporary one, or keep a backup of the pixel data that needs to be extracted.
RGB to Luma
The formula for converting the RGB data into a luma value (grey) is as follows based on the REC 709 (or BT.709) formula:
luma = Red x 0.2126 + Green x 0.7152 + Blue x 0.0722
Alternatively you could use the REC 601 (BT.601) formula instead (which is more common for SD video footage):
luma = Red x 0.299 + Green x 0.587 + Blue x 0.114
To use this, simply iterate over all pixels, obtain the luma value using one of these formulas and replace all channels in target with the resulting luma value.
Temporary data
For temporary data we can do either (when save button is clicked):
Temporary canvas
- Create a temporary canvas
- Set the size equal to source canvas
- Draw in source canvas using
drawImage()
- Extract target canvas' pixel data using
getImageData()
- Iterate over the pixels, convert using the formula, put data back
- Save out image then discard temporary canvas
or
ImageData backup
- Extract image data using
getImageData()
- this will act as our backup
- Create new
ImageData
the same size using createImageData()
- Iterate over the pixel from the backup, but put the result in luma into the created
ImageData
- Put back the created
ImageData
, save out image
- Put back the backup data
If the latter step is necessary depends really on how you update the canvas. If you are animating a loop there may not be need to put back the backup (or keep one) if everything gets updated anyways, but if so and you see flash of grey or some areas left grey, then this step would be needed.
Example using the ImageData
approach
var ctx = c.getContext("2d"), img = new Image;
img.onload = setup; img.crossOrigin = "";
img.src = "//i.imgur.com/OrYVGI8.jpg";
function setup() {
c.width = this.naturalWidth; c.height = this.naturalHeight;
ctx.drawImage(this, 0, 0); btn.disabled = false
}
// Main code for demo
btn.onclick = function() {
var idataSrc = ctx.getImageData(0, 0, c.width, c.height), // original
idataTrg = ctx.createImageData(c.width, c.height), // empty data
dataSrc = idataSrc.data, // reference the data itself
dataTrg = idataTrg.data,
len = dataSrc.length, i = 0, luma;
// convert by iterating over each pixel each representing RGBA
for(; i < len; i += 4) {
// calculate luma, here using Rec 709
luma = dataSrc[i] * 0.2126 + dataSrc[i+1] * 0.7152 + dataSrc[i+2] * 0.0722;
// update target's RGB using the same luma value for all channels
dataTrg[i] = dataTrg[i+1] = dataTrg[i+2] = luma;
dataTrg[i+3] = dataSrc[i+3]; // copy alpha
}
// put back luma data so we can save it as image
ctx.putImageData(idataTrg, 0, 0);
demo.src = c.toDataURL(); // set demo result's src url
// restore backup data
ctx.putImageData(idataSrc, 0, 0);
};
<button disabled id=btn>SAVE TO GREY</button> (click then scroll down to see result)<br>
<canvas id=c></canvas><br><img id=demo>