有没有办法获得画布的当前转换矩阵?有一个context.setTransform()函数,但据我所知,似乎没有getTransform()等价。
更具体地说,我想获得矩阵的当前比例和平移元素。谷歌一直对此毫无帮助。
答案 0 :(得分:30)
不,根本就没有。 :(
大多数canavs库(例如cake.js)已经实现了自己的矩阵类来跟踪当前的转换矩阵。
cake.js的创建者认为没有办法获得当前的矩阵是荒谬的,足以保证bug report about it.不幸的是,这是在2007年,并且没有努力在规范中包含getCurrentTransform
编辑:我创建了一个简单的Transformation类,可以让您轻松制作自己的getCanvas()
或者并排跟踪Canvas的矩阵。 Here it is.我希望有所帮助!
编辑2012年6月:新规范确实包含获取当前转换矩阵的方法! context.currentTransform
可用于获取或设置当前转换矩阵。遗憾的是,虽然Firefox确实在其上下文中具有特定于供应商的mozCurrentTransform
属性,但尚未实现它。所以你还不能使用它,但很快就会出现在规范中!
答案 1 :(得分:12)
编辑(2016年6月27日):WHATWG规范现在有一个函数getTransform()
而不是currentTransform
,从语义上看,getTransform()
创建副本变换矩阵的em>。看起来主浏览器仍然缺少它。
再次编辑:
这是一个粗略的实现:
//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;
};
编辑:
属性currentTransform
has been added to the spec;据报道,Firefox和Opera支持它。我检查了Firefox,发现它以供应商为前缀为mozCurrentTransform
。据推测,它可用于获取和设置变换矩阵。
OLDER STUFF,仍然是最好的:
如果您想获得当前的转换矩阵,您必须自己跟踪它。一种方法是使用Javascript的原型继承来添加getMatrix()
方法并扩充修改矩阵的方法:
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();
要真正做到正确,您需要在使用上下文的save()
和restore()
方法时跟踪多个矩阵。
答案 2 :(得分:2)
正如@ellisbben提到的那样,你能做到这一点的唯一方法就是自己跟踪它。您可以找到一个解决方案here。它将上下文包装在一个包装器中,然后在那里处理令人讨厌的位。
答案 3 :(得分:0)
受this answer的启发,我更新了@ellisbben的答案,使其使用代理而不是原型继承(这对我不起作用)。覆盖CanvasRenderingContext2D.prototype
的代码linked in the comments of @ellisbben's answer也不起作用。 (See related question。)
// in theory, SVGMatrix will be used by the Canvas API in the future;
// in practice, we can borrow an SVG matrix today!
function createMatrix() {
const 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.currentTransform` should always correspond to its
// current transformation matrix.
// Call `enhanceContext` on a freshly-fetched 2d canvas context for best
// results.
function enhanceContext(context) {
// The main property we are enhancing the context to track
let currentTransform = createMatrix();
// the stack of saved matrices
const savedTransforms = [currentTransform];
const enhanced = {
currentTransform,
savedTransforms,
// helper for manually forcing the canvas transformation matrix to
// match the stored matrix.
_setMatrix() {
const m = enhanced.currentTransform;
context.setTransform(m.a, m.b, m.c, m.d, m.e, m.f);
},
save() {
enhanced.savedTransforms.push(enhanced.currentTransform);
context.save();
},
// 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() {
if (enhanced.savedTransforms.length == 0) return;
context.restore();
enhanced.currentTransform = enhanced.savedTransforms.pop();
enhanced._setMatrix();
},
scale(x, y) {
enhanced.currentTransform = enhanced.currentTransform.scaleNonUniform(
x,
y
);
context.scale(x, y);
},
rotate(theta) {
// canvas `rotate` uses radians, SVGMatrix uses degrees.
enhanced.currentTransform = enhanced.currentTransform.rotate(
(theta * 180) / Math.PI
);
context.rotate(theta);
},
translate(x, y) {
enhanced.currentTransform = enhanced.currentTransform.translate(x, y);
context.translate(x, y);
},
transform(a, b, c, d, e, f) {
const 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;
enhanced.currentTransform = enhanced.currentTransform.multiply(rhs);
context.transform(a, b, c, d, e, f);
},
// Warning: `resetTransform` is not implemented in at least some browsers
// and this is _not_ a shim.
resetTransform() {
enhanced.currentTransform = createMatrix();
context.resetTransform();
},
};
const handler = {
get: (target, key) => {
const value =
key in enhanced
? enhanced[key]
: key in target
? target[key]
: undefined;
if (value === undefined) {
return value;
}
return typeof value === 'function'
? (...args) => value.apply(target, args)
: value;
},
set: (target, key, value) => {
if (key in target) {
target[key] = value;
}
return value;
},
};
return new Proxy(context, handler);
}
function testIt() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const enhanced = enhanceContext(ctx);
const log = (msg) => {
const { a, b, c, d, e, f } = enhanced.currentTransform;
console.log(msg, { a, b, c, d, e, f });
};
window.enhanced = enhanced;
log('initial');
enhanced.save();
enhanced.scale(1, 2);
log('scale(1,2)');
enhanced.restore();
enhanced.save();
enhanced.translate(10, 20);
log('translate(10,20)');
enhanced.restore();
enhanced.save();
enhanced.rotate(30);
log('rotate(30)');
enhanced.restore();
enhanced.save();
enhanced.scale(1, 2);
enhanced.translate(10, 20);
log('scale(1,2) translate(10,20)');
enhanced.restore();
}
testIt();