HTML5 Canvas получить матрицу преобразования? - PullRequest
27 голосов
/ 13 сентября 2011

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

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

Ответы [ 4 ]

30 голосов
/ 13 сентября 2011

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

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

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

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

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

12 голосов
/ 13 сентября 2011

EDIT (27.06.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'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;  
};

РЕДАКТИРОВАТЬ: атрибут currentTransform был добавлен в спецификацию ;сообщается, что он поддерживается в Firefox и Opera.Я проверил Firefox и нашел его с префиксом mozCurrentTransform.Предположительно, его можно использовать как для получения, так и для установки матрицы преобразования.

OLDER STUFF, STILL MOSTLY TRUE:

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

2 голосов
/ 13 сентября 2011

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

0 голосов
/ 11 января 2019

По мотивам этого ответа я обновил ответ @ ellisbben, чтобы использовать Proxy вместо наследования прототипа (что у меня не сработало). Код , указанный в комментариях к ответу @ ellisbben overriding 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...