Adventures with <canvas>

acumen_asset_viewer
Acumen's Asset Viewer

Acumen is at 1.0 beta, and we finally settled on a hybrid image viewer. Google’s excanvas.js is used to fake a canvas in IE (getting it to work in IE8 was the trickiest part) but because excanvas.js runs pretty slowly and doesn’t support text (there are third-party text implementations of text in excanvas, but they’re also slow and don’t use standard fonts) I ended up using canvas just to render images. All main UI elements are implemented in HTML/CSS using jQuery.

Based on my googling, successfully creating a canvas that transparently works in either decent browsers or IE is not common knowledge, so I thought I’d share the method I came up with. I originally wrote this code for the pure canvas asset viewer (which forced IE users to install Chrome Frame); when I decided to reuse the code, I tried to simplify it, and everything I did to make it simpler broke it. I guess the me who wrote the original code wasn’t as big an idiot as the me who borrowed it thinks he was.

var c; // will contain a working canvas when we're done
var d = $('#div_that_will_contain_canvas');
var ie_version = some_expression_that_is_truthy_for_ie();
if( ie_version ){
	c = document.createElement('canvas');
	c.id = viewer_id;
	c.style.width = d.width() + 'px';
	c.style.height = d.height() + 'px';
	d.append( c );
	G_vmlCanvasManager.initElement( c );
} else {
	d.html('<canvas id="' + viewer_id + '" width="' + d.width() + '" height="' + d.height() + '"/>');
	c = document.getElementById( viewer_id );
}

The main takeaway is that creating the canvas using a blob of html does not work for IE (because it doesn’t recognize the canvas element, and thus ignores its attributes) and using the standard DOM methods does not work for proper canvas elements (because using styles to resize a real canvas stretches the canvas).

If you have a cleaner way of achieving the same result please let me know—I’m not proud of this code, it just happens to work.

HTML5 Canvas

I’ve finally gotten around to learning how to drive the new canvas widget (at least in 2d). It’s not as easy to learn as it should be owing to some fairly lame documentation (the best I’ve found is Mozilla’s).

My observations thus far are:

  • It’s pretty depressing how the Canvas offers a better and easier to learn 2d drawing environment than most development tools I’ve used in my life.
  • It works pretty darn well in Safari 4, almost as well in Firefox 3.5 and Opera 9.64, and tolerably in IE6-8 (using Google’s expcanvas).
  • Performance seems to be inversely proportional to rendering quality. So IE/expcanvas renders quickest (but doesn’t implement a whole bunch of stuff), while Opera is significantly faster than Firefox but has some stupid bugs, and Firefox is faster than Safari but does some things wrong. Safari renders perfectly — so far as I’ve seen — but is the slowest.
  • Safari doesn’t scale canvases properly though, which is a bummer.

Canvas API Documentation

My version is now redundant, thanks to the HTML WG. I’ve replaced my incomplete and sometimes incorrect documentation with copy-and-paste.

interface CanvasRenderingContext2D {

  // back-reference to the canvas
  readonly attribute HTMLCanvasElement canvas;

  // state
  void save(); // push state on state stack
  void restore(); // pop state stack and restore state

  // transformations (default transform is the identity matrix)
  void scale(in float x, in float y);
  void rotate(in float angle);
  void translate(in float x, in float y);
  void transform(in float m11, in float m12, in float m21, in float m22, in float dx, in float dy);
  void setTransform(in float m11, in float m12, in float m21, in float m22, in float dx, in float dy);

  // compositing
           attribute float globalAlpha; // (default 1.0)
           attribute DOMString globalCompositeOperation; // (default source-over)

  // colors and styles
           attribute any strokeStyle; // (default black)
           attribute any fillStyle; // (default black)
  CanvasGradient createLinearGradient(in float x0, in float y0, in float x1, in float y1);
  CanvasGradient createRadialGradient(in float x0, in float y0, in float r0, in float x1, in float y1, in float r1);
  CanvasPattern createPattern(in HTMLImageElement image, in DOMString repetition);
  CanvasPattern createPattern(in HTMLCanvasElement image, in DOMString repetition);
  CanvasPattern createPattern(in HTMLVideoElement image, in DOMString repetition);

  // line caps/joins
           attribute float lineWidth; // (default 1)
           attribute DOMString lineCap; // "butt", "round", "square" (default "butt")
           attribute DOMString lineJoin; // "round", "bevel", "miter" (default "miter")
           attribute float miterLimit; // (default 10)

  // shadows
           attribute float shadowOffsetX; // (default 0)
           attribute float shadowOffsetY; // (default 0)
           attribute float shadowBlur; // (default 0)
           attribute DOMString shadowColor; // (default transparent black)

  // rects
  void clearRect(in float x, in float y, in float w, in float h);
  void fillRect(in float x, in float y, in float w, in float h);
  void strokeRect(in float x, in float y, in float w, in float h);

  // path API
  void beginPath();
  void closePath();
  void moveTo(in float x, in float y);
  void lineTo(in float x, in float y);
  void quadraticCurveTo(in float cpx, in float cpy, in float x, in float y);
  void bezierCurveTo(in float cp1x, in float cp1y, in float cp2x, in float cp2y, in float x, in float y);
  void arcTo(in float x1, in float y1, in float x2, in float y2, in float radius);
  void rect(in float x, in float y, in float w, in float h);
  void arc(in float x, in float y, in float radius, in float startAngle, in float endAngle, in boolean anticlockwise);
  void fill();
  void stroke();
  void clip();
  boolean isPointInPath(in float x, in float y);

  // focus management
  boolean drawFocusRing(in Element element, in float xCaret, in float yCaret, in optional boolean canDrawCustom);

  // text
           attribute DOMString font; // (default 10px sans-serif)
           attribute DOMString textAlign; // "start", "end", "left", "right", "center" (default: "start")
           attribute DOMString textBaseline; // "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" (default: "alphabetic")
  void fillText(in DOMString text, in float x, in float y, in optional float maxWidth);
  void strokeText(in DOMString text, in float x, in float y, in optional float maxWidth);
  TextMetrics measureText(in DOMString text);

  // drawing images
  void drawImage(in HTMLImageElement image, in float dx, in float dy, in optional float dw, in float dh);
  void drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
  void drawImage(in HTMLCanvasElement image, in float dx, in float dy, in optional float dw, in float dh);
  void drawImage(in HTMLCanvasElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);
  void drawImage(in HTMLVideoElement image, in float dx, in float dy, in optional float dw, in float dh);
  void drawImage(in HTMLVideoElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);

  // pixel manipulation
  ImageData createImageData(in float sw, in float sh);
  ImageData createImageData(in ImageData imagedata);
  ImageData getImageData(in float sx, in float sy, in float sw, in float sh);
  void putImageData(in ImageData imagedata, in float dx, in float dy, in optional float dirtyX, in float dirtyY, in float dirtyWidth, in float dirtyHeight);
};

interface CanvasGradient {
  // opaque object
  void addColorStop(in float offset, in DOMString color);
};

interface CanvasPattern {
  // opaque object
};

interface TextMetrics {
  readonly attribute float width;
};

interface ImageData {
  readonly attribute unsigned long width;
  readonly attribute unsigned long height;
  readonly attribute CanvasPixelArray data;
};

interface CanvasPixelArray {
  readonly attribute unsigned long length;
  getter octet (in unsigned long index);
  setter void (in unsigned long index, in octet value);
};