Как получить данные пикселей из холста, которым я не владею? - PullRequest
0 голосов
/ 05 января 2019

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

Я пытаюсь сделать это с помощью холста игры Shakes and Fidget . Я использую метод readPixels из контекста .

Вот что я попробовал:

var example = document.getElementById('#canvas');
var context = example.getContext('webgl2');      // Also doesn't work with: ', {preserveDrawingBuffer: true}'
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4); 
context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);

Но все пиксели, очевидно, черные (что, очевидно, неверно).

Редактировать: Кроме того, я хочу прочитать пиксели несколько раз. Спасибо всем за ваши ответы. Ответ от @ Kaiido отлично сработал для меня:)

Ответы [ 2 ]

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

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

Для второго переопределения getContext

HTMLCanvasElement.prototype.getContext = function(origFn) {
  const typesWeCareAbout = {
    "webgl": true,
    "webgl2": true,
    "experimental-webgl": true,
  };
  return function(type, attributes = {}) {
    if (typesWeCareAbout[type]) {
      attributes.preserveDrawingBuffer = true;
    }
    return origFn.call(this, type, attributes);
  };
}(HTMLCanvasElement.prototype.getContext);

Поместите это в верхнюю часть файла перед игрой Unity ИЛИ поместите в отдельный файл сценария и включите его перед игрой Unity.

Теперь вы сможете получить контекст на любом холсте, созданном Unity, и вызывать gl.readPixels в любое время.

Для другого метода, получая пиксели в том же событии, вы вместо этого оберните requestAnimationFrame, чтобы вы могли вставить свой gl.readPixels после использования Unity requestAnimationFrame

window.requestAnimationFrame = function(origFn) {
  return function(callback) {
    return origFn(this, function(time) {
      callback(time);
      gl.readPixels(...);
    };
  };
}(window.requestAnimationFrame);

Другим решением было бы использование виртуального контекста webgl. Эта библиотека показывает пример реализации виртуального контекста webgl и показывает пример постобработки вывода Unity

обратите внимание, что в какой-то момент Unity, вероятно, переключится на использование OffscreenCanvas. В этот момент, скорее всего, потребуются другие решения, чем указанные выше.

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

Вы можете запросить контекст холста только один раз. Все последующие запросы будут либо возвращать null, либо тот же контекст, который был создан ранее, если вы передали те же параметры в getContext().

Теперь одна страница, на которую вы ссылались, не прошла опцию preserveDrawingBuffer при создании их контекста, что означает, что для того, чтобы иметь возможность получать информацию о пикселях оттуда, вам придется подключиться в том же цикле событий как игровой цикл.
К счастью, в этой точной игре используется простой цикл requestAnimationFrame, поэтому для подключения к одному и тому же циклу событий все, что нам нужно, - это также обернуть наш код в вызов requestAnimationFrame.

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

Теперь я понимаю, что это может быть неочевидно, поэтому я попытаюсь объяснить, что делает requestAnimationFrame , и как мы можем быть уверены, что наш обратный вызов будет вызван после Unity.

requestAnimationFrame(fn) помещает fn обратный вызов в стек обратных вызовов, которые будут вызываться одновременно в порядке «первым пришел - первым вышел», незадолго до того, как браузер выполнит рисование на экране операции. Это происходит время от времени (обычно 60 раз в секунду), в конце ближайшего цикла событий.
Это можно понимать как разновидность setTimeout(fn , time_remaining_until_next_paint), с основным отличием в том, что гарантируется, что requestAnimationFrame исполнитель обратного вызова будет вызван в конце цикла события и, следовательно, после другого js выполнения этого события цикл. * +1025 * Так что, если бы мы вызывали requestAnimationFrame(fn) в том же цикле событий, что и тот, куда будут вызываться обратные вызовы, наша фальшивая time_remaining_until_next_paint будет 0, а fn окажется в нижней части нашего стека (последний в последний раз)
И при вызове requestAnimationFrame(fn) изнутри самого исполнителя обратных вызовов, time_remaining_until_next_paint будет что-то около 16, и fn будет вызван среди первых в следующем кадре.

Таким образом, любые вызовы requestAnimationFrame(fn), сделанные извне исполнителя обратных вызовов requestAnimationFrame , гарантированно будут вызываться в том же цикле событий, что и активный цикл запроса requestAnimationFrame , и позвать после.

Таким образом, все, что нам нужно, чтобы захватить эти пиксели, это заключить вызов в readPixels в вызове requestAnimationFrame и вызвать его после цикла Unity, запущенного .

var example = document.getElementById('#canvas');
var context = example.getContext('webgl2') || example.getContext('webgl');
var pixels = new Uint8Array(context.drawingBufferWidth * context.drawingBufferHeight * 4);
requestAnimationFrame(() => {
  context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
  // here `pixels` has the correct data
});
...