google-chromecanvashtml5-canvas

canvas unable to generate bmp image dataurl in chrome


I have code to draw image from video. Here is the code

<script type="text/javascript">

function capture() {

    var video  = document.getElementById("videoId");
    var canvas = capture(video, 1);
    var dataURL = canvas.toDataURL("image/bmp", 1.0);
    console.log("dataurl: "+dataURL);

}

</script>


<body>
 <input type="button" value="Capture" onClick="capture()"/>
 <video id="videoId" width="640" height="480"/>
</body>

on console the dataurl is shown as "data:image/png;base64,..."

Question?

why dataurl is generated in png format??

Note: This has happened in chrome browser[41.0.2272.89]. In firefox url is generated in bmp format.


Solution

  • Support

    BMP is not supported in all browsers. There is no requirement to support other formats than PNG (my emphasis):

    User agents must support PNG ("image/png"). User agents may support other types.

    Any format that isn't recognized will be saved as default PNG.

    The first argument, if provided, controls the type of the image to be returned (e.g. PNG or JPEG). The default is image/png; that type is also used if the given type isn't supported.

    To check if a format is supported check the first part of the returned data-uri:

     var wantType = "image/bmp";
     var dataUri = canvas.toDataURL(wantType);
     if (dataUri.indexOf(wantType) < 0) {  // or use substr etc. data: + mime
          // Format NOT supported - provide workaround/inform user
          // See update below for workaround (or replacement)
     }
    

    Workaround: Manual low-level BMP generation

    To save as BMP you will have to extract the pixel data from canvas, format a file header, append data in the correct format, create a Blob and serve that to the user via objectURL.

    Usage:

    var bmpDataUri = CanvasToBMP.toDataURL(canvas);     // returns an data-URI
    

    There is also the option to get BMP image as raw ArrayBuffer:

    var bmpBuffer = CanvasToBMP.toArrayBuffer(canvas);
    

    and Blob:

    var bmpBlob = CanvasToBMP.toBlob(canvas);
    var url = URL.createObjectURL(bmpBlob);             // example objectURL
    

    Blobs can of course be used with createObjectURL() which can be used as image source as well as download target, and tends to be faster than using data-URIs as they don't need to encode/decode to/from Base-64.

    It writes 32-bits BMP files with support for alpha (Firefox currently ignores the alpha channel in BMP files).

    In any case, here is -

    Demo and source

    /*! canvas-to-bmp version 1.0 ALPHA
        (c) 2015 Ken "Epistemex" Fyrstenberg
        MIT License (this header required)
    */
    
    var CanvasToBMP = {
    
      /**
       * Convert a canvas element to ArrayBuffer containing a BMP file
       * with support for 32-bit (alpha).
       *
       * Note that CORS requirement must be fulfilled.
       *
       * @param {HTMLCanvasElement} canvas - the canvas element to convert
       * @return {ArrayBuffer}
       */
      toArrayBuffer: function(canvas) {
    
        var w = canvas.width,
            h = canvas.height,
            w4 = w * 4,
            idata = canvas.getContext("2d").getImageData(0, 0, w, h),
            data32 = new Uint32Array(idata.data.buffer), // 32-bit representation of canvas
    
            stride = Math.floor((32 * w + 31) / 32) * 4, // row length incl. padding
            pixelArraySize = stride * h,                 // total bitmap size
            fileLength = 122 + pixelArraySize,           // header size is known + bitmap
    
            file = new ArrayBuffer(fileLength),          // raw byte buffer (returned)
            view = new DataView(file),                   // handle endian, reg. width etc.
            pos = 0, x, y = 0, p, s = 0, a, v;
    
        // write file header
        setU16(0x4d42);          // BM
        setU32(fileLength);      // total length
        pos += 4;                // skip unused fields
        setU32(0x7a);            // offset to pixels
    
        // DIB header
        setU32(108);             // header size
        setU32(w);
        setU32(-h >>> 0);        // negative = top-to-bottom
        setU16(1);               // 1 plane
        setU16(32);              // 32-bits (RGBA)
        setU32(3);               // no compression (BI_BITFIELDS, 3)
        setU32(pixelArraySize);  // bitmap size incl. padding (stride x height)
        setU32(2835);            // pixels/meter h (~72 DPI x 39.3701 inch/m)
        setU32(2835);            // pixels/meter v
        pos += 8;                // skip color/important colors
        setU32(0xff0000);        // red channel mask
        setU32(0xff00);          // green channel mask
        setU32(0xff);            // blue channel mask
        setU32(0xff000000);      // alpha channel mask
        setU32(0x57696e20);      // " win" color space
    
        // bitmap data, change order of ABGR to BGRA
        while (y < h) {
          p = 0x7a + y * stride; // offset + stride x height
          x = 0;
          while (x < w4) {
            v = data32[s++];                     // get ABGR
            a = v >>> 24;                        // alpha channel
            view.setUint32(p + x, (v << 8) | a); // set BGRA
            x += 4;
          }
          y++
        }
    
        return file;
    
        // helper method to move current buffer position
        function setU16(data) {view.setUint16(pos, data, true); pos += 2}
        function setU32(data) {view.setUint32(pos, data, true); pos += 4}
      },
    
      /**
       * Converts a canvas to BMP file, returns a Blob representing the
       * file. This can be used with URL.createObjectURL().
       * Note that CORS requirement must be fulfilled.
       *
       * @param {HTMLCanvasElement} canvas - the canvas element to convert
       * @return {Blob}
       */
      toBlob: function(canvas) {
        return new Blob([this.toArrayBuffer(canvas)], {
          type: "image/bmp"
        });
      },
    
      /**
       * Converts the canvas to a data-URI representing a BMP file.
       * Note that CORS requirement must be fulfilled.
       *
       * @param canvas
       * @return {string}
       */
      toDataURL: function(canvas) {
        var buffer = new Uint8Array(this.toArrayBuffer(canvas)),
            bs = "", i = 0, l = buffer.length;
        while (i < l) bs += String.fromCharCode(buffer[i++]);
        return "data:image/bmp;base64," + btoa(bs);
      }
    };
    
    
    // -------- DEMO CODE -------------
    
    var canvas = document.querySelector("canvas"),
      w = canvas.width,
      h = canvas.height,
      ctx = canvas.getContext("2d"),
      gr = ctx.createLinearGradient(0, 0, w, h),
      img = new Image();
    
    gr.addColorStop(0, "hsl(" + (Math.random() * 360) + ", 90%, 70%)"); 
    gr.addColorStop(1, "hsl(" + (Math.random() * 360) + ", 100%, 30%)"); 
    ctx.fillStyle = gr;
    ctx.fillRect(0, 0, w, h);
    
    // append image from the data-uri returned by the CanvasToBMP code below:
    img.src = CanvasToBMP.toDataURL(canvas);
    document.body.appendChild(img);
    <h2>Canvas left, BMP from canvas as image right</h2>
    <canvas width="299" height="200"></canvas>