Skip to content

Instantly share code, notes, and snippets.

@abicky
Created July 23, 2012 18:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abicky/3165385 to your computer and use it in GitHub Desktop.
Save abicky/3165385 to your computer and use it in GitHub Desktop.
Simple implementation of CanvasRenderingContext2D#getTransform (getTransform for HTML5 Canvas API)
/**
* Copyright 2012- Takeshi Arabiki
* License: MIT License (http://opensource.org/licenses/MIT)
*/
(function() {
CanvasRenderingContext2D.prototype._transform = [1, 0, 0, 1, 0, 0];
CanvasRenderingContext2D.prototype._transforms = [];
CanvasRenderingContext2D.prototype.getTransform = function() {
return this._transform;
};
var restore = CanvasRenderingContext2D.prototype.restore;
CanvasRenderingContext2D.prototype.restore = function() {
this._transform = this._transforms.pop() || [1, 0, 0, 1, 0, 0];
restore.apply(this);
};
// | | | | | |
// | x'| | cos(angle) -sin(angle) 0 | | x |
// | | | | | |
// | y'| = | sin(angle) cos(angle) 0 | | y |
// | | | | | |
// | 1 | | 0 0 1 | | 1 |
// | | | | | |
var rotate = CanvasRenderingContext2D.prototype.rotate;
CanvasRenderingContext2D.prototype.rotate = function(angle) {
var t = [Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0, 0];
this._transform = multiplyTransform(this._transform, t);
rotate.apply(this, arguments);
};
var save = CanvasRenderingContext2D.prototype.save;
CanvasRenderingContext2D.prototype.save = function() {
this._transforms.push(this._transform.slice());
save.apply(this);
};
// | | | | | |
// | x'| | sx 0 0 | | x |
// | | | | | |
// | y'| = | 0 sy 0 | | y |
// | | | | | |
// | 1 | | 0 0 1 | | 1 |
// | | | | | |
var scale = CanvasRenderingContext2D.prototype.scale;
CanvasRenderingContext2D.prototype.scale = function(sx, sy) {
this._transform = multiplyTransform(this._transform, [sx, 0, 0, sy, 0, 0]);
scale.apply(this, arguments);
};
var setTransform = CanvasRenderingContext2D.prototype.setTransform;
CanvasRenderingContext2D.prototype.setTransform = function(a, b, c, d, e, f) {
this._transform = Array.prototype.slice.apply(arguments);
setTransform.apply(this, arguments);
};
// | | | | | |
// | x'| | 1 0 tx | | x |
// | | | | | |
// | y'| = | 0 1 ty | | y |
// | | | | | |
// | 1 | | 0 0 1 | | 1 |
// | | | | | |
var translate = CanvasRenderingContext2D.prototype.translate;
CanvasRenderingContext2D.prototype.translate = function(tx, ty) {
this._transform = multiplyTransform(this._transform, [1, 0, 0, 1, tx, ty]);
translate.apply(this, arguments);
};
// | | | | | |
// | x'| | a c e | | x |
// | | | | | |
// | y'| = | b d f | | y |
// | | | | | |
// | 1 | | 0 0 1 | | 1 |
// | | | | | |
var transform = CanvasRenderingContext2D.prototype.transform;
CanvasRenderingContext2D.prototype.transform = function(a, b, c, d, e, f) {
this._transform = multiplyTransform.call(this, this._transform, arguments);
transform.apply(this, arguments);
};
// ctx.transform.apply(ctx, t1)
// ctx.transform.apply(ctx, t2)
// => ctx.transform.apply(ctx, multiplyTransform(t1, t2))
var multiplyTransform = function(t1, t2) {
return [
t1[0] * t2[0] + t1[2] * t2[1],
t1[1] * t2[0] + t1[3] * t2[1],
t1[0] * t2[2] + t1[2] * t2[3],
t1[1] * t2[2] + t1[3] * t2[3],
t1[0] * t2[4] + t1[2] * t2[5] + t1[4],
t1[1] * t2[4] + t1[3] * t2[5] + t1[5]
];
};
})();
var arrayEqual = function(expected, actual) {
var sign = 10;
var factor = Math.pow(10, sign);
for (var i = 0; i < expected.length; i++) {
if (Math.round(expected[i] * factor) != Math.round(actual[i] * factor)) {
var msg = "not ok\n"
+ "\texpected[" + i + "]: " + expected[i] + "\n"
+ "\tactual[" + i + "]: " + actual[i];
console.error(msg);
return false;
}
}
console.log("ok");
return true;
};
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var I = [1, 0, 0, 1, 0, 0];
var baseTransform = [1, 2, 3, 4, 5, 6];
var testFuncs = {
testSetTransform: function() {
ctx.setTransform.apply(ctx, baseTransform);
arrayEqual(baseTransform, ctx.getTransform());
},
testScale: function() {
ctx.setTransform.apply(ctx, baseTransform);
var sx = 10;
var sy = 20;
ctx.scale(sx, sy);
var t = baseTransform.slice();
arrayEqual([t[0] * sx, t[1] * sy, t[2] * sx, t[3] * sy, t[4] * sx, t[5] * sy], ctx.getTransform());
},
testTransform: function() {
ctx.setTransform.apply(ctx, baseTransform);
ctx.transform(-2, 1, 1.5, -0.5, 1, -2); // inverse matrix
arrayEqual(I, ctx.getTransform());
},
testTranslate: function() {
ctx.setTransform.apply(ctx, baseTransform);
var tx = 10;
var ty = 20;
ctx.translate(tx, ty);
var t = baseTransform.slice();
t[4] = t[4] + tx;
t[5] = t[5] + ty;
arrayEqual(t, ctx.getTransform());
},
testRotate: function() {
ctx.setTransform.apply(ctx, I);
var angle = Math.PI / 6;
ctx.rotate(angle); // inverse matrix
var sin = Math.sin(angle);
var cos = Math.cos(angle);
var t = [cos, sin, -sin, cos, 0, 0];
arrayEqual(t, ctx.getTransform());
ctx.rotate(angle);
sin = Math.sin(angle * 2);
cos = Math.cos(angle * 2);
arrayEqual([cos, sin, -sin, cos, 0, 0], ctx.getTransform());
},
testSaveAndRestore: function() {
var transforms = [];
ctx.setTransform.apply(ctx, I);
arrayEqual(I, ctx.getTransform());
ctx.restore();
arrayEqual(I, ctx.getTransform());
transforms.push(ctx.getTransform());
ctx.save();
ctx.setTransform.apply(ctx, baseTransform);
transforms.push(ctx.getTransform());
ctx.save();
ctx.transform(2, 0, 0, 1, 0, 0);
transforms.push(ctx.getTransform());
ctx.save();
while (transforms.length > 0) {
ctx.restore();
arrayEqual(transforms.pop(), ctx.getTransform());
}
}
};
// run test
for (var name in testFuncs) {
console.log(name);
testFuncs[name]();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment