EDIT(1/10/2020): MDN now indicates that getTransform() is supported in most major browsers; the code below may still have value as a part of implementing a polyfill for Internet Explorer, Edge, and Android Firefox.
EDIT(6/27/2016): The WHATWG spec now has a function getTransform() instead of currentTransform and it appears semantically clear that getTransform() creates a copy of the transformation matrix. Looks like it is still missing from major browsers.
EDIT, again:
Here's a rough implementation:
//in theory, SVGMatrix will be used by the Canvas API in the future;
//in practice, we can borrow an SVG matrix today!
var createMatrix = function() {
var svgNamespace = "http://www.w3.org/2000/svg";
return document.createElementNS(svgNamespace, "g").getCTM();
}
//`enhanceContext` takes a 2d canvas context and wraps its matrix-changing
//functions so that `context._matrix` should always correspond to its
//current transformation matrix.
//Call `enhanceContext` on a freshly-fetched 2d canvas context for best
//results.
var enhanceContext = function(context) {
var m = createMatrix();
context._matrix = m;
//the stack of saved matrices
context._savedMatrices = [m];
var super_ = context.__proto__;
context.__proto__ = ({
//helper for manually forcing the canvas transformation matrix to
//match the stored matrix.
_setMatrix: function() {
var m = this._matrix;
super_.setTransform.call(this, m.a, m.b, m.c, m.d, m.e, m.f);
},
save: function() {
this._savedMatrices.push(this._matrix);
super_.save.call(this);
},
//if the stack of matrices we're managing doesn't have a saved matrix,
//we won't even call the context's original `restore` method.
restore: function() {
if(this._savedMatrices.length == 0)
return;
super_.restore.call(this);
this._matrix = this._savedMatrices.pop();
this._setMatrix();
},
scale: function(x, y) {
this._matrix = this._matrix.scaleNonUniform(x, y);
super_.scale.call(this, x, y);
},
rotate: function(theta) {
//canvas `rotate` uses radians, SVGMatrix uses degrees.
this._matrix = this._matrix.rotate(theta * 180 / Math.PI);
super_.rotate.call(this, theta);
},
translate: function(x, y) {
this._matrix = this._matrix.translate(x, y);
super_.translate.call(this, x, y);
},
transform: function(a, b, c, d, e, f) {
var rhs = createMatrix();
//2x2 scale-skew matrix
rhs.a = a; rhs.b = b;
rhs.c = c; rhs.d = d;
//translation vector
rhs.e = e; rhs.f = f;
this._matrix = this._matrix.multiply(rhs);
super_.transform.call(this, a, b, c, d, e, f);
},
//warning: `resetTransform` is not implemented in at least some browsers
//and this is _not_ a shim.
resetTransform: function() {
this._matrix = createMatrix();
super_.resetTransform.call(this);
},
__proto__: super_
});
return context;
};
EDIT:
The attribute currentTransform has been added to the spec; it is reported to be supported in Firefox and Opera. I checked on Firefox and found it vendor-prefixed as mozCurrentTransform. Presumably it can be used to both get and set the transform matrix.
OLDER STUFF, STILL MOSTLY TRUE:
If you want to get the current transformation matrix, you'll have to keep track of it yourself. One way of doing this would be to use Javascript's prototypical inheritance to add a getMatrix() method and augment the methods which modify the matrix:
var context = canvas.getContext("2d");
var super = context.__proto__;
context.__proto__ = ({
__proto__: super, //"inherit" default behavior
getMatrix: function() { return this.matrix; },
scale: function(x, y) {
//assuming the matrix manipulations are already defined...
var newMatrix = scaleMatrix(x, y, this.getMatrix());
this.matrix = newMatrix;
return super.scale.call(this, x, y);
},
/* similar things for rotate, translate, transform, setTransform */
/* ... */
});
context.matrix = makeDefaultMatrix();
To really get it right, you'd need to track multiple matrices when the save() and restore() methods of the context are used.