Search code examples
javascriptimagehtml5-canvashtml5-videohref

Saving Image in Javascript


I have an HTML page where I get video feed from device camera. I draw it to a canvas with a setTimeOut loop and take a picture to an image. Then I apply a filter to that image (sepia, grayscale...) and save it. The image is saved without the filter. Why isn't the filtered image saved but a "raw" one? I'm not that good at Javascript so please be gentle.

<video id = "video" autoplay></video>
<button onclick = "snap()">Snap</button>
<canvas id = "canvas" width = "400" height = "300"></canvas>
<img id = "photo" src = "pic.png" alt = "Photo">
<button onclick = "save()">Save</button>
<button onclick = "sepia()">Sepia</button>//Added in edit
//--------------
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var photo = document.getElementById('photo');

navigator.getMedia({ video: true, audio: false }, success, failure);
function success(stream) {
    video.src = vendorUrl.createObjectURL(stream);
}
function failure(error) {}

video.addEventListener('play', sendToCanvas, false);
function sendToCanvas() {
    draw(this, context, canvas.width, canvas.height);
}
function draw(video, context, width, height) {
    context.drawImage(video, 0, 0, width, height); 
    setTimeout(draw, 10, video, context, width, height); 
}
function snap() {
    photo.setAttribute('src', canvas.toDataURL("image/png"));
}
function save() {
    var currentdate = new Date().toLocaleString();
    var link = document.createElement('a');
    link.download = currentdate + ".png";
    link.href = photo.src;
    link.click();
}
function sepia() {//Added in edit
    photo.style["-webkit-filter"] = "sepia(100%)";
}

Solution

  • You are using CSS filters, which are not really drawn into the canvas, but just a decoration from the page.

    Hence, canvas' methods to extract its content won't keep these filters.

    It's like if you do set a background-color to the canvas element without drawing anything, or some borders, you won't get these styles once you do use the toDataURL method, only the drawing made onto the canvas' context itself are parts of the canvas' content :

    var ctx = c.getContext('2d');
    //only this black rectangle is drawn onto the canvas
    ctx.fillRect(10,10,20,20);
    
    expImg.src = c.toDataURL();
    /* these styles are only applied on the document,
       not onto the canvas itself
    */
    canvas{
      background-color: lightgreen;
      border: 10px solid red;
      }
    
    img{
      border: 1px solid green;
      }
    <canvas id="c"></canvas>
    <img id="expImg"/>

    You have to understand the canvas is just like an image. When you do apply the CSS filters on it, you're not really modifying the file, but only its display in the document.

    Now, you can process the filters directly onto the canvas' pixels, and be able to keep them.

    Here is a small example showing you how to modify canvas pixels.

    var draw = function() {
    
      c.width = img.width;
      c.height = img.height;
    
      // first draw your image once
      ctx.drawImage(img, 0, 0);
      //then get its data
      var imgData = ctx.getImageData(0, 0, c.width, c.height);
      // get the array of pixels
      var arr = imgData.data;
      //loop through each pixels, 4 per loop because pixels are [R,G,B,A,R,G,B,A...]
      for (var i = 0; i < arr.length; i += 4) {
        // store the red value
        var r = arr[i];
        //replace the green value with the red one
        arr[i] = arr[i + 1];
        arr[i + 1] = r;
      }
      // put the modified imageData back to your canvas
      ctx.putImageData(imgData, 0, 0);
    };
    
    var ctx = c.getContext('2d');
    var img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = draw;
    
    img.src = 'https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png';
    <canvas id="c"></canvas>

    Ps : it seems we will soon be able to directly set a context's filter property, currently only available on FF, under a flag, but that will make things way easier !