javascript HTML5 Canvas 获取变换矩阵?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7395813/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
HTML5 Canvas get transform matrix?
提问by tnecniv
Is there a way to get the current transformation matrix for a canvas? There is a context.setTransform() function, but there does not seem to be a getTransform() equivalent as far as I can tell.
有没有办法获得画布的当前变换矩阵?有一个 context.setTransform() 函数,但据我所知似乎没有 getTransform() 等价物。
More specifically, I want to get the current scale and translation elements of the matrix. Google has been very unhelpful with this.
更具体地说,我想获取矩阵的当前比例和平移元素。谷歌对此非常无助。
回答by Simon Sarris
No, there simply isn't. :(
不,根本没有。:(
Most canavs libraries (such as cake.js) have instead implemented their own matrix class to keep track of the current transformation matrix.
大多数 canavs 库(例如 cake.js)已经实现了自己的矩阵类来跟踪当前的转换矩阵。
The creator of cake.js thought that there being no way to get the current matrix was ridiculous enough to warrant a bug report about it.Unfortunately that was back in 2007 and there has been no effort to include a getCurrentTransform in the spec.
cake.js 的创建者认为没有办法获得当前矩阵是荒谬的,足以证明有关它的错误报告。不幸的是,那是在 2007 年,并且没有努力在规范中包含 getCurrentTransform。
Edit:I have created a simple Transformation class that will allow you to easily make your own getCanvas()
or keep track of the Canvas's matrix side-by-side. Here it is.I hope that helps!
编辑:我创建了一个简单的 Transformation 类,它可以让您轻松制作自己的getCanvas()
或并排跟踪 Canvas 的矩阵。这里是。我希望这有帮助!
Edit June 2012:The new specification does include a way to get the current transformation matrix! context.currentTransform
can be used to get or set the current transformation matrix. Unfortunately no browsers have implemented it yet, though Firefox does have the vendor-specific mozCurrentTransform
property on its context. So you can't use it just yet, but it is in the spec, so soon!
2012 年 6 月编辑:新规范确实包含一种获取当前转换矩阵的方法!context.currentTransform
可用于获取或设置当前的变换矩阵。不幸的是,还没有浏览器实现它,尽管 Firefox 确实mozCurrentTransform
在其上下文中具有特定于供应商的属性。所以你现在还不能使用它,但它在规范中,这么快!
回答by ellisbben
EDIT(1/10/2020): MDN now indicatesthat 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.
编辑(2020年1月10日):MDN现在显示的是getTransform()
在大多数主流浏览器的支持; 作为为 Internet Explorer、Edge 和 Android Firefox 实现 polyfill 的一部分,下面的代码可能仍然具有价值。
EDIT(6/27/2016): The WHATWG spec now has a function getTransform()
instead of currentTransform
and it appears semantically clear that getTransform()
creates a copyof the transformation matrix. Looks like it is still missing from major browsers.
编辑(2016 年 6 月 27 日):WHATWG 规范现在有一个函数getTransform()
而不是currentTransform
它,它在语义上看起来很清晰,getTransform()
可以创建转换矩阵的副本。看起来主要浏览器仍然缺少它。
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.
编辑:该属性currentTransform
已添加到规范中;据报道,Firefox 和 Opera 支持它。我检查了 Firefox,发现它的供应商前缀为mozCurrentTransform
. 据推测,它可以用于获取和设置变换矩阵。
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:
如果你想获得当前的变换矩阵,你必须自己跟踪它。这样做的一种方法是使用 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();
To really get it right, you'd need to track multiple matrices when the save()
and restore()
methods of the context are used.
要真正做到正确,您需要在使用上下文的save()
和restore()
方法时跟踪多个矩阵。
回答by Juho Veps?l?inen
回答by spenceryue
Motivated by this answer, I updated @ellisbben's answer to use a Proxy instead of prototype inheritance (which didn't work for me). The code linked in the comments of @ellisbben's answeroverriding CanvasRenderingContext2D.prototype
also didn't work. (See related question.)
通过启发这个答案,我更新@ ellisbben的答案使用代理服务器,而非原型继承(这并没有对我来说有效)。@ellisbben 的答案覆盖评论中链接的代码CanvasRenderingContext2D.prototype
也不起作用。(见相关问题。)
// 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();