Canvas toDataURL() возвращает пустое изображение только в Firefox

Я использую glfx.js для редактирования своего изображения, но когда я пытаюсь получить данные этого изображения с помощью функции toDataURL(), я получаю пустое изображение (ширина того же размера, что и исходное изображение).

Странно, что в Chrome script работает отлично.

Я хочу упомянуть, что изображение загружается в canvas с помощью события onload:

           img.onload = function(){

                try {
                    canvas = fx.canvas();
                } catch (e) {
                    alert(e);
                    return;
                }

                // convert the image to a texture
                texture = canvas.texture(img);

                // draw and update canvas
                canvas.draw(texture).update();

                // replace the image with the canvas
                img.parentNode.insertBefore(canvas, img);
                img.parentNode.removeChild(img);

            }

Также мой путь изображения находится в том же домене;

Проблема (в Firefox) - это когда я нажимаю кнопку сохранения. Chrome возвращает ожидаемый результат, но Firefox возвращает это:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA7YAAAIWCAYAAABjkRHCAAAHxklEQVR4nO3BMQEAAADCoPVPbQZ/oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
... [ lots of A s ] ... 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAzwD6aAABkwvPRgAAAABJRU5ErkJggg==

Что может вызвать этот результат и как его исправить?

Ответ 1

Скорее всего, существует какое-то асинхронное событие между временем рисования на холсте и временем, когда вы вызываете toDataURL. По умолчанию холст очищается после каждого композита. Либо предотвратите очистку холста, создав контекст WebGL с помощью preserveDrawingBuffer: true как в

var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true});

или убедитесь, что toDataURL вызывается перед выходом из любого события, которое вы используете для рендеринга. Например, если вы делаете это

function render() {
  drawScene(); 
  requestAnimationFrame(render);
}
render();

И где-то еще сделать это

someElement.addEventListener('click', function() {
  var data = someCanvas.toDataURL();
}, false);

Эти 2 события, animation frame и click не синхронизированы, и холст может быть очищен между вызовами. Примечание. Холст не будет очищен, так как он имеет двойную буферизацию, но буфер toDataURL и другие команды, влияющие на просмотр буфера, очищаются.

Решением является либо использование preserveDrawingBuffer либо обращение к toDataURL внутри того же события, что и рендеринг. Например

var captureFrame = false;

function render() {
  drawScene(); 

  if (captureFrame) {
    captureFrame = false;
    var data = someCanvas.toDataURL();
    ...
  }

  requestAnimationFrame(render);
}
render();

someElement.addEventListener('click', function() {
  captureFrame = true;
}, false);

Какой смысл preserveDrawingBuffer: false который используется по умолчанию? Это может быть значительно быстрее, особенно на мобильных устройствах, чтобы не было необходимости сохранять буфер рисования. Еще один способ взглянуть на это - браузеру нужны 2 копии вашего холста. Тот, к которому вы рисуете, и тот, который он отображает. У этого есть 2 способа иметь дело с этими 2 буферами. (А) двойной буфер. Позвольте вам рисовать один, отображать другой, менять местами буферы, когда вы закончите рендеринг, который выводится из выхода из любого события, которое выдает команды рисования (B) Скопируйте содержимое буфера, который вы рисуете, чтобы сделать буфер, который отображается, Обмен намного быстрее, чем копирование. Таким образом, замена по умолчанию. Это до браузера, что на самом деле происходит. Единственное требование состоит в том, что если preserveDrawingBuffer имеет значение false, буфер чертежа очищается после композита (который является еще одним асинхронным событием и поэтому непредсказуемым), если preserveDrawingBuffer имеет значение true то он должен копироваться, чтобы содержимое чертежа буфера сохранялось.

Обратите внимание, что если у холста есть контекст, у него всегда будет один и тот же контекст. Другими словами, допустим, вы меняете код, который инициализирует контекст WebGL, но вы все равно хотите установить preserveDrawingBuffer: true

Есть как минимум 2 способа.

сначала найдите холст, получите контекст на нем

так как код позже будет в конечном итоге с тем же контекстом.

<script>
document.querySelector('#somecanvasid').getContext(
    'webgl', {preserveDrawingBuffer: true});
</script>
<script src="script/that/will/use/somecanvasid.js"></script>

Поскольку вы уже создали контекст для этого холста, любой скрипт, который последует за ним, получит тот же контекст.

увеличить getContext

<script>
HTMLCanvasElement.prototype.getContext = function(origFn) {
  return function(type, attributes) {
    if (type === 'webgl') {
      attributes = Object.assign({}, attributes, {
        preserveDrawingBuffer: true,
      });
    }
    return origFn.call(this, type, attributes);
  };
}(HTMLCanvasElement.prototype.getContext);
</script>
<script src="script/that/will/use/webgl.js"></script>

В этом случае любой контекст webgl, созданный после увеличения getContext будет иметь для preserveDrawingBuffer значение true.