Подтвердить что ты не робот

HTML5 Canvas получить преобразование матрицы?

Есть ли способ получить текущую матрицу преобразования для холста? Существует функция context.setTransform(), но, насколько я знаю, похоже, что нет эквивалента getTransform().

В частности, я хочу получить текущий масштаб и элементы перевода матрицы. Google был очень бесполезен с этим.

4b9b3361

Ответ 1

Нет, просто нет.: (

В большинстве библиотек canavs (например, cake.js) вместо этого реализован собственный матричный класс для отслеживания текущей матрицы преобразования.

Создатель cake.js подумал, что отсутствие способа получить текущую матрицу достаточно смехотворно, чтобы гарантировать отчет об ошибке . К сожалению, это было в 2007 году, и не было никаких усилий, чтобы включить getCurrentTransform в спецификацию.

Изменить: Я создал простой класс Transformation, который позволит вам легко сделать свой собственный getCanvas() или отслеживать матрицу Canvas рядом друг с другом. Вот он. Я надеюсь, что это поможет!

Изменить июнь 2012: Новая спецификация включает способ получения текущей матрицы преобразования! context.currentTransform может использоваться для получения или установки текущей матрицы преобразования. К сожалению, ни один браузер не реализовал его, хотя Firefox имеет специфическое для поставщика свойство mozCurrentTransform в своем контексте. Таким образом, вы еще не можете использовать его, но он находится в спецификации, так скоро!

Ответ 2

EDIT (6/27/2016): спецификация WHATWG теперь имеет функцию getTransform() вместо currentTransform и появляется семантически ясно, что getTransform() создает копию матрицы преобразования. Похоже, он по-прежнему отсутствует в основных браузерах.

ИЗМЕНИТЬ, еще раз:

Здесь грубая реализация:

//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 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: Атрибут currentTransform был добавлен в спецификацию; он, как сообщается, поддерживается в Firefox и Opera. Я проверил Firefox и обнаружил, что он имеет префикс поставщика mozCurrentTransform. Предположительно, он может использоваться как для получения, так и для установки матрицы преобразования.

СТАРТ ОДЕЖДЫ, ВСЕ ЕЩЕ ИСТИННО:

Если вы хотите получить текущую матрицу трансформации, вам придется ее отслеживать самостоятельно. Одним из способов сделать это было бы использование прототипного наследования 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() контекста.

Ответ 3

Как уже упоминал @ellisbben, единственный способ сделать это - следить за ним самостоятельно. Вы можете найти одно решение здесь. Он обертывает контекст в оболочке, а затем обрабатывает неприятные биты.

Ответ 4

Воодушевленный этим ответом, я обновил ответ @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 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();