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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 23:58:17  来源:igfitidea点击:

HTML5 Canvas get transform matrix?

javascripthtmlcanvas

提问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.currentTransformcan be used to get or set the current transformation matrix. Unfortunately no browsers have implemented it yet, though Firefox does have the vendor-specific mozCurrentTransformproperty 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 currentTransformand 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 currentTransformhas 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

As @ellisbben mentioned pretty much the only way you can do this is to keep track of it yourself. You can find one solution here. It wraps the context within a wrapper and then handles the nasty bits there.

正如@ellisbben 提到的,您可以做到这一点的唯一方法就是自己跟踪它。您可以在此处找到一种解决方案。它将上下文包装在一个包装器中,然后在那里处理令人讨厌的位。

回答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.prototypealso 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();