Canvas

For general information about using <canvas> see the canvas topic page.

Code usable from Web content

Getting the number of pixels of a certain color in a canvas

The following function will return the number of pixels in a canvas that have the RGB color of r, g and b. This can be very useful to compare for example if a user has painted over another area as explained in this blog post.

function getpixelamount(canvas, r, g, b) {
  var cx = canvas.getContext('2d');
  var pixels = cx.getImageData(0, 0, c.width, c.height);
  var all = pixels.data.length;
  var amount = 0;
  for (i = 0; i < all; i += 4) {
    if (pixels.data[i] === r &&
        pixels.data[i + 1] === g &&
        pixels.data[i + 2] === b) {
      amount++;
    }
  }
  return amount;
};

Getting the color of a pixel in a canvas

This following snippet returns an object with the RGBA values of the pixel at position x and y of the canvas. This can be used to determine if the mouse cursor is inside a certain shape or not.

function getpixelcolour(canvas, x, y) {
  var cx = canvas.getContext('2d');
  var pixel = cx.getImageData(x, y, 1, 1);
  return {
    r: pixel.data[0],
    g: pixel.data[1],
    b: pixel.data[2],
    a: pixel.data[3]
  };
}

Chaining methods

This class provides jQuery-style chained access to 2D context methods and properties.

<html xmlns="http://www.w3.org/1999/xhtml">
<head><title></title></head>
<body>
<canvas id="canvas" width="1800" height="1800"></canvas>

<script type="text/javascript">

function Canvas2DContext(c) {
  if (typeof c === 'string') {
    c = document.getElementById(c);
  }
  if (!(this instanceof Canvas2DContext)) {
    return new Canvas2DContext(c);
  }
  this.context = this.ctx = c.getContext('2d');
  if (!Canvas2DContext.prototype.arc) {
    Canvas2DContext.setup.call(this, this.ctx);
  }
}
Canvas2DContext.setup = function () {
  var methods = ['arc','arcTo','beginPath','bezierCurveTo','clearRect','clip',
    'closePath','drawImage','fill','fillRect','fillText','lineTo','moveTo',
    'quadraticCurveTo','rect','restore','rotate','save','scale','setTransform',
    'stroke','strokeRect','strokeText','transform','translate'];

  var getterMethods = ['createPattern','drawFocusRing','isPointInPath','measureText', // drawFocusRing not currently supported
    // The following might instead be wrapped to be able to chain their child objects
    'createImageData','createLinearGradient',
    'createRadialGradient', 'getImageData','putImageData'
  ];

  var props = ['canvas','fillStyle','font','globalAlpha','globalCompositeOperation',
  'lineCap','lineJoin','lineWidth','miterLimit','shadowOffsetX','shadowOffsetY',
  'shadowBlur','shadowColor','strokeStyle','textAlign','textBaseline'];

  var gmethl, propl;
  for (var i = 0, methl = methods.length; i < methl; i++) {
    var m = methods[i];
    Canvas2DContext.prototype[m] = (function (m) {return function () {
      this.ctx[m].apply(this.ctx, arguments);
      return this;
    };}(m));
  }

  for (i = 0, gmethl = getterMethods.length; i < gmethl; i++) {
    var gm = getterMethods[i];
    Canvas2DContext.prototype[gm] = (function (gm) {return function () {
      return this.ctx[gm].apply(this.ctx, arguments);
    };}(gm));
  }

  for (i = 0, propl = props.length; i < propl; i++) {
    var p = props[i];
    Canvas2DContext.prototype[p] = (function (p) {return function (value) {
      if (typeof value === 'undefined') {
        return this.ctx[p];
      }
      this.ctx[p] = value;
      return this;
    };}(p));
  }
};

var c = document.getElementById('canvas');

var ctx = Canvas2DContext(c).strokeStyle("rgb(30,110,210)").
             transform(10, 3, 4, 5, 1, 0).strokeRect(2, 10, 15, 20).context; // Use context to get access to underlying context
//alert(ctx);

var strokeStyle = Canvas2DContext(c).strokeStyle("rgb(50,110,210)").strokeStyle(); // Use property name as a function (but without arguments) to get the value
// alert(strokeStyle);

</script>
</body>
</html>

Firefox 1.5 quadraticCurveTo() bug workaround

There is a bug in the Firefox 1.5 implementation of quadatricCurveTo(). It does not draw a quadratic curve, as it is just calling the same cubic curve function bezierCurveTo() calls, and repeating the single quadratic control point (x,y) coordinate twice. For this reason quadraticCurveTo() will yield incorrect results. If you require the use of quadraticCurveTo() you must convert your quadratic Bézier curve to a cubic Bézier curve yourself, so you can use the working bezierCurveTo() method.

var currentX, currentY;  // set to last x,y sent to lineTo/moveTo/bezierCurveTo or quadraticCurveToFixed()

function quadraticCurveToFixed( cpx, cpy, x, y ) {
  /*
   For the equations below the following variable name prefixes are used:
     qp0 is the quadratic curve starting point (you must keep this from your last point sent to moveTo(), lineTo(), or bezierCurveTo() ).
     qp1 is the quadratic curve control point (this is the cpx,cpy you would have sent to quadraticCurveTo() ).
     qp2 is the quadratic curve ending point (this is the x,y arguments you would have sent to quadraticCurveTo() ).
   We will convert these points to compute the two needed cubic control points (the starting/ending points are the same for both
   the quadratic and cubic curves.

   The exact equations for the two cubic control points are:
     cp0 = qp0 and cp3 = qp2
     cp1 = qp0 + (qp1 - qp0) * ratio
     cp2 = cp1 + (qp2 - qp0) * (1 - ratio)
     where ratio = (sqrt(2) - 1) * 4 / 3 exactly (approx. 0.5522847498307933984022516322796)
                   if the quadratic is an approximation of an elliptic arc, and the cubic must approximate the same arc, or
           ratio = 2.0 / 3.0 for keeping the same quadratic curve.

   In the code below, we must compute both the x and y terms for each point separately.

    cp1x = qp0x + (qp1x - qp0x) * ratio;
    cp1y = qp0y + (qp1y - qp0y) * ratio;
    cp2x = cp1x + (qp2x - qp0x) * (1 - ratio);
    cp2y = cp1y + (qp2y - qp0y) * (1 - ratio);

   We will now 
     a) replace the qp0x and qp0y variables with currentX and currentY (which *you* must store for each moveTo/lineTo/bezierCurveTo)
     b) replace the qp1x and qp1y variables with cpx and cpy (which we would have passed to quadraticCurveTo)
     c) replace the qp2x and qp2y variables with x and y.
   which leaves us with: 
  */
  var ratio = 2.0 / 3.0; // 0.5522847498307933984022516322796 if the Bezier is approximating an elliptic arc with best fitting
  var cp1x = currentX + (cpx - currentX) * ratio;
  var cp1y = currentY + (cpy - currentY) * ratio;
  var cp2x = cp1x + (x - currentX) * (1 - ratio);
  var cp2y = cp1y + (y - currentY) * (1 - ratio);

  // and now call cubic Bezier curve to function 
  bezierCurveTo( cp1x, cp1y, cp2x, cp2y, x, y );

  currentX = x;
  currentY = y;
}

Code usable only from privileged code

These snippets are only useful from privileged code, such as extensions or privileged apps.

Saving a canvas image to a file

The following function accepts a canvas object and a destination file path string. The canvas is converted to a PNG file and saved to the specified location. The function displays a download progress dialog, but the dialog can be removed.

function saveCanvas(canvas, destFile) {
  // convert string filepath to an nsIFile
  var file = Components.classes["@mozilla.org/file/local;1"]
                       .createInstance(Components.interfaces.nsILocalFile);
  file.initWithPath(destFile);

  // create a data url from the canvas and then create URIs of the source and targets  
  var io = Components.classes["@mozilla.org/network/io-service;1"]
                     .getService(Components.interfaces.nsIIOService);
  var source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
  var target = io.newFileURI(file)
    
  // prepare to save the canvas data
  var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
                          .createInstance(Components.interfaces.nsIWebBrowserPersist);
  
  persist.persistFlags = Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
  persist.persistFlags |= Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
  
  // displays a download dialog (remove these 3 lines for silent download)
  var xfer = Components.classes["@mozilla.org/transfer;1"]
                       .createInstance(Components.interfaces.nsITransfer);
  xfer.init(source, target, "", null, null, null, persist);
  persist.progressListener = xfer;
  
  // save the canvas data to the file
  persist.saveURI(source, null, null, null, null, file);
}

Loading a remote page onto a canvas element

The following class first creates a hidden iframe element and attaches a listener to the frame's load event. Once the remote page has loaded, the remotePageLoaded method fires. This method gets a reference to the iframe's window and draws this window to a canvas object.

Note that this only works if you are running the page from chrome. If you try running the code as a plain webpage, you will get a 'Security error" code: "1000' error.

RemoteCanvas = function() {
    this.url = "http://developer.mozilla.org";
};

RemoteCanvas.CANVAS_WIDTH = 300;
RemoteCanvas.CANVAS_HEIGHT = 300;

RemoteCanvas.prototype.load = function() {
    var windowWidth = window.innerWidth - 25;
    var iframe;
    iframe = document.createElement("iframe");
    iframe.id = "test-iframe";
    iframe.height = "10px";
    iframe.width = windowWidth + "px";
    iframe.style.visibility = "hidden";
    iframe.src = this.url;
    // Here is where the magic happens... add a listener to the
    // frame's onload event
    iframe.addEventListener("load", this.remotePageLoaded, true);
    //append to the end of the page
    window.document.body.appendChild(iframe);
    return;    
};

RemoteCanvas.prototype.remotePageLoaded = function() {
    // Look back up the iframe by id
    var ldrFrame = document.getElementById("test-iframe");
    // Get a reference to the window object you need for the canvas
    // drawWindow method
    var remoteWindow = ldrFrame.contentWindow;

    //Draw canvas
    var canvas = document.createElement("canvas");
    canvas.style.width = RemoteCanvas.CANVAS_WIDTH + "px";
    canvas.style.height = RemoteCanvas.CANVAS_HEIGHT + "px";
    canvas.width = RemoteCanvas.CANVAS_WIDTH;
    canvas.height = RemoteCanvas.CANVAS_HEIGHT;
    var windowWidth = window.innerWidth - 25;
    var windowHeight = window.innerHeight;

    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0,
                  RemoteCanvas.CANVAS_WIDTH,
                  RemoteCanvas.CANVAS_HEIGHT);
    ctx.save();
    ctx.scale(RemoteCanvas.CANVAS_WIDTH / windowWidth,
              RemoteCanvas.CANVAS_HEIGHT / windowHeight);
    ctx.drawWindow(remoteWindow,
                   0, 0,
                   windowWidth, windowHeight,
                   "rgb(255,255,255)");
    ctx.restore();
};

Usage:

var remoteCanvas = new RemoteCanvas();
remoteCanvas.load();

Convert image files to base64 strings

The following code gets a remote image and converts its content to Data URI scheme.

var oCanvas = document.createElement("canvas"), oCtx = oCanvas.getContext("2d");
function loadImageFile (sURL, fCallback) {
  var oImage = new Image();
  oImage.src = sURL;
  oImage.onload = function () {
    oCanvas.width = this.width;
    oCanvas.height = this.height;
    oCtx.clearRect(0, 0, this.width, this.height);
    oCtx.drawImage(this, 0, 0);
    fCallback.call(this, oCanvas.toDataURL());
  };
}

Usage:

loadImageFile("myimage.jpg", function(string64) { alert(string64); });

If you want to get instead the base64 content of a local file using the file <input> element, you must use the FileReader object.

Document Tags and Contributors

Last updated by: wbamberg,