Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 Canvas get transform matrix?

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.

More specifically, I want to get the current scale and translation elements of the matrix. Google has been very unhelpful with this.

like image 617
tnecniv Avatar asked Sep 13 '11 01:09

tnecniv


3 Answers

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.

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.

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!

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!

like image 132
Simon Sarris Avatar answered Sep 24 '22 14:09

Simon Sarris


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.

like image 29
ellisbben Avatar answered Sep 24 '22 14:09

ellisbben


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.

like image 2
Juho Vepsäläinen Avatar answered Sep 22 '22 14:09

Juho Vepsäläinen