web-dev-qa-db-fra.com

HTML5 Canvas obtenir la matrice de transformation?

Existe-t-il un moyen d'obtenir la matrice de transformation en cours pour un canevas? Il existe une fonction context.setTransform (), mais il ne semble pas y avoir d'équivalent getTransform () pour autant que je sache.

Plus précisément, je souhaite obtenir l’échelle actuelle et les éléments de traduction de la matrice. Google a été très inutile avec cela.

27
tnecniv

Non, il n'y en a tout simplement pas. :(

La plupart des bibliothèques canavs (telles que cake.js) ont à la place implémenté leur propre classe de matrice pour garder une trace de la matrice de transformation actuelle.

Le créateur de cake.js a estimé qu’il n’y avait aucun moyen d’obtenir la matrice actuelle assez ridicule pour justifier un rapport de bug à ce sujet. Malheureusement, cela remonte à 2007 et aucun effort n’a été fait pour inclure un getCurrentTransform dans spec.

Éditer: J'ai créé une classe de transformation simple qui vous permettra de créer facilement votre propre getCanvas() ou de suivre côte à côte la matrice de Canvas. La voici. J'espère que ça aide!

Edit June 2012: La nouvelle spécification inclut un moyen d'obtenir la matrice de transformation actuelle! context.currentTransform peut être utilisé pour obtenir ou définir la matrice de transformation actuelle. Malheureusement, aucun navigateur ne l’a encore implémenté, bien que Firefox ait la propriété mozCurrentTransform spécifique au vendeur dans son contexte. Donc, vous ne pouvez pas l'utiliser pour l'instant, mais c'est dans les spécifications, si tôt!

30
Simon Sarris

EDIT (27/06/2016): La spécification WHATWG a maintenant une fonction getTransform() au lieu de currentTransform et il apparaît clairement que getTransform() crée un copy de la matrice de transformation. On dirait qu'il manque encore des principaux navigateurs.

EDIT, encore une fois:

Voici une mise en œuvre approximative:

//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: L'attribut currentTransforma été ajouté à la spécification ; il est rapporté être pris en charge dans Firefox et Opera. J'ai vérifié sur Firefox et l'ai trouvé préfixé par le vendeur avec mozCurrentTransform. Vraisemblablement, il peut être utilisé pour obtenir et définir la matrice de transformation.

Plus vieux, toujours la plupart du temps vrai:

Si vous voulez obtenir la matrice de transformation actuelle, vous devrez la garder vous-même. Une solution consiste à utiliser l'héritage prototypique de Javascript pour ajouter une méthode getMatrix() et augmenter les méthodes modifiant la matrice:

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();

Pour bien faire les choses, vous devez suivre plusieurs matrices lorsque les méthodes save() et restore() du contexte sont utilisées.

12
ellisbben

Comme @ellisbben l'a mentionné, le seul moyen de le faire est de le garder vous-même. Vous pouvez trouver une solution ici . Il encapsule le contexte dans un wrapper, puis gère les éléments malveillants.

2
Juho Vepsäläinen

Motivé par cette réponse , j'ai mis à jour la réponse de @ ellisbben pour qu'elle utilise un proxy au lieu d'un héritage de prototype (ce qui ne fonctionnait pas pour moi). Le code lié dans les commentaires de la réponse de @ ellisbben override CanvasRenderingContext2D.prototype ne fonctionnait pas non plus. ( Voir question connexe .)

// 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();

0
spenceryue