Pumpkin_M
Pumpkin_M

Reputation: 161

Save canvas in grayscale

I was wondering if it is possible to have a coloured canvas in HTML5 and then save it as a black and white (grayscale) PNG, while you click on the save button. I managed to save the canvas as a PNG already, which is great! But I'd like to add the grayscale feature without having to change it myself with a program as Illustrator etc. The canvas has moving particles btw. (I'm not sure if that has any effect though)

Thanks~!

function download(){
    var dt = c.toDataURL('image/png');
    this.href = dt; 
}; 
downloadlink.addEventListener('click', download, false); 

Upvotes: 5

Views: 6533

Answers (3)

user1693593
user1693593

Reputation:

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>

Upvotes: 11

aznoqmous
aznoqmous

Reputation: 121

Or you could use ctx.globalCompositeOperation = 'luminosity' before drawing your image on a white filled canvas, not as fun but same result (:

Upvotes: 6

Bitzu
Bitzu

Reputation: 133

I suggest calculating luma like this:

var luma = (11 * obj.data[i] + 16 * obj.data[i + 1] +  5 * obj.data[i + 2]) >> 5;

Upvotes: -1

Related Questions