Неверные результаты смешивания во всех браузерах с HTML5 canvas - PullRequest
19 голосов
/ 08 октября 2011

Резюме

При многократном рисовании чего-либо (очевидно с низким альфа-значением) на холсте, независимо от того, используется ли функция drawImage() или fill, результирующие цвета значительно неточны во всех протестированных браузерах. Вот пример результатов, которые я получаю с определенной операцией смешивания:

Invalid blending results

Проблема демонстрации

Для примера и кода, с которым можно поиграть, посмотрите этот jsFiddle, который я разработал:

http://jsfiddle.net/jMjFh/2/

Верхний набор данных является результатом теста, который можно настроить, отредактировав iters и rgba в верхней части кода JS. Он берет любой цвет, который вы укажете, RGBA all in range [0, 255], и печатает его на абсолютно чистом, прозрачном холсте iters раз. В то же время он продолжает вычисление того, что стандартная функция смешивания source-over-dest Porter-Duff будет производить для той же процедуры (т. Е. Что браузеры должны запускать и предлагать).

Нижний набор данных представляет граничный случай, который я обнаружил, когда смешивание [127, 0, 0, 2] поверх [127, 0, 0, 63] дает неточный результат. Интересно, что смешивание [127, 0, 0, 2] поверх [127, 0, 0, 62] и всех [127, 0, 0, x] цветов дает x <= 62 ожидаемый результат. Обратите внимание, что этот граничный случай допустим только в Firefox и IE в Windows. В любом другом браузере в любой другой операционной системе, которую я тестировал, результаты намного хуже.

Кроме того, если вы запустите тест в Firefox в Windows и Chrome в Windows, вы заметите значительную разницу в результатах смешивания. К сожалению, мы не говорим об одном или двух значениях - это гораздо хуже.

Справочная информация

Мне известно о том, что спецификация HTML5 canvas указывает на то, что выполнение drawImage() или putImageData(), а затем getImageData() может показать слегка изменяющиеся результаты из-за ошибок округления. В этом случае, и исходя из всего, с чем я столкнулся до сих пор, мы говорим о чем-то незначительном, например, о том, что красный канал 122 вместо 121.

В качестве справочного материала Я задал этот вопрос некоторое время назад, когда @NathanOstgard провел отличное исследование и пришел к выводу, что виновато предварительное умножение альфа. Хотя это, безусловно, может сработать здесь, я думаю, что основная проблема заключается в чем-то большем.

Вопрос

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

Спасибо!


Редактировать: Я просто добавил своего рода трассировку стека, которая выводит каждое промежуточное значение цвета и ожидаемое значение для этой стадии процесса.


Редактировать: После небольшого исследования, я думаю, что я добился небольшого прогресса.

Обоснование Chrome14 на Win7

В Chrome14 на Win7 результирующий цвет после операции наложения - серый, что на много значений отличается от ожидаемого и желаемого красноватого результата. Это заставило меня поверить, что у Chrome нет проблемы с округлением и точностью, потому что разница настолько велика. Вместо этого кажется, что Chrome хранит все значения пикселей с предварительно умноженным альфа.

Эту идею можно продемонстрировать, нарисовав холст одним цветом. В Chrome, если вы примените цвет [127, 0, 0, 2] один раз к холсту, вы получите [127, 0, 0, 2], когда прочитаете его обратно. Однако, если вы дважды примените [127, 0, 0, 2] к холсту, Chrome даст вам [85, 0, 0, 3], если вы проверите полученный цвет. Это действительно имеет некоторый смысл, если учесть, что предварительно умноженный эквивалент [127, 0, 0, 2] равен [1, 0, 0, 2].

По-видимому, когда Chrome14 на Win7 выполняет операцию смешивания, он ссылается на предварительно умноженное значение цвета [1, 0, 0, 2] для компонента dest и предварительно не умноженное значение цвета [127, 0, 0, 2] для компонента source. Когда эти два цвета смешаны вместе, мы получим [85, 0, 0, 3], используя подход Porter-Duff source-over-dest, как и ожидалось.

Итак, похоже, что Chrome14 на Win7 непоследовательно ссылается на значения пикселей при работе с ними. Информация хранится в предварительно умноженном состоянии для внутреннего использования, представляется обратно в неумноженной форме и обрабатывается с использованием значений обеих форм.

Я думаю, что можно было бы обойти это, сделав несколько дополнительных вызовов к getImageData() и / или putImageData(), но влияние на производительность не кажется таким уж большим. Более того, похоже, что это не та проблема, которую демонстрирует Firefox7 на Win7, поэтому необходимо провести дополнительные исследования в этой области.


Редактировать: Ниже приведен один из возможных подходов, который, вероятно, сработает.

Мысли о решении

Я еще не вернулся к работе над этим, но один из подходов WebGL, который я недавно придумал, - это запускать все операции рисования с помощью простого пиксельного шейдера, который выполняет смешивание Porter-Duff source-over-dest самостоятельно. , Кажется, что проблема здесь возникает только при интерполяции значений для целей смешивания. Таким образом, если шейдер считывает два пиксельных значения, вычисляет результат и записывает (не смешивает) значение в место назначения, это должно смягчить проблему. По крайней мере, это не должно составлять. Тем не менее, определенно все еще будут небольшие неточности округления.

Я знаю, что в своем первоначальном вопросе я упоминал, что ручное смешивание пикселей не будет жизнеспособным. Для реализации 2D-холста приложения, которое требует обратной связи в реальном времени, я не вижу способа это исправить. Все смешивание будет выполняться на ЦП и будет препятствовать выполнению другого кода JS. Однако с WebGL ваш пиксельный шейдер работает на графическом процессоре, поэтому я почти уверен, что не должно быть никакого падения производительности.

Сложная часть заключается в возможности подачи холста dest в качестве текстуры в шейдер, потому что вам также нужно визуализировать его на холсте для просмотра. В конечном итоге вы хотите избежать необходимости генерировать объект текстуры WebGL из видимого холста каждый раз, когда его нужно обновить. Решение этой проблемы должно быть выполнено путем сохранения двух копий данных, одной из которых является текстура в памяти, а другой - видимый холст (который обновляется путем надписи на текстуре по мере необходимости).

Ответы [ 2 ]

3 голосов
/ 08 октября 2011

О Боже.Я думаю, что мы только что вошли в зону сумерек здесь.Боюсь, у меня нет ответа, но я могу предоставить как минимум дополнительную информацию.Ваша догадка верна, что по крайней мере что-то здесь больше.

Я бы предпочел, чтобы пример был значительно более простым (и, следовательно, менее подверженным любым видам ошибок - я боюсь, что у вас могут быть проблемы с 0-255 где-то вместо 0-1, но не выглядел полностью), чем у вас, поэтому я взбил что-то крошечноеТо, что я нашел, не было приятным:

<canvas id="canvas1" width="128" height="128"></canvas>
<canvas id="canvas2" width="127" height="127"></canvas>

+

var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var can2 = document.getElementById('canvas2');
var ctx2 = can2.getContext('2d');

var alpha = 2/255.0;

ctx.fillStyle = 'rgba(127, 0, 0, ' + alpha + ')';
ctx2.fillStyle = 'rgba(127, 0, 0, ' + alpha + ')'; // this is grey
//ctx2.fillStyle = 'rgba(171, 0, 0, ' + alpha + ')'; // this works

for (var i = 0; i < 32; i++) {
      ctx.fillRect(0,0,500,500);
      ctx2.fillRect(0,0,500,500);
}

Дает это:

enter image description here

Убедитесь сами здесь:

http://jsfiddle.net/jjAMX/

Кажется, что если ваша площадь поверхности слишком мала (не область fillRect, а сама область холста), то операция рисования будет стиркой.

В IE9 и FF7 вроде нормально работает.Я представил это как ошибка в Chromium.(Посмотрим, что они скажут, но их типичный МО пока игнорирует большинство отчетов об ошибках, если только они не являются проблемами безопасности).

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

ВВ конце концов, Chrome, вероятно, что-то делает для оптимизации холстов с площадью поверхности меньше 128x128, что портит все.

0 голосов
/ 17 октября 2011

Установка globalCompositeOperation в "lighter" (операция, при которой значения перекрывающихся пикселей добавляются вместе, это не операция по умолчанию для многих браузеров) дает одинаковый цвет и приводит к ie9, chrome 14 и последнему авроре firefox:

Expected: [126.99999999999999, 0, 0, 63.51372549019608]
Result: [127, 0, 0, 64]

Expected: [126.99999999999999, 0, 0, 63.51372549019608]
Result: [127, 0, 0, 64]

http://jsfiddle.net/jMjFh/11/

изменить: вы можете узнать, что означают различные составные операции здесь:

https://developer.mozilla.org/en/Canvas_tutorial/Compositing#globalCompositeOperation

например, "copy" былполезно для меня, когда мне нужно сделать анимацию затухания в определенных частях холста

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...