Недопустимые результаты смешивания во всех браузерах с холстом HTML5.
Резюме
При многократном рисовании чего-либо (видимо, с низким значением альфа) на холсте, независимо от того, имеет ли он функцию drawImage()
или fill
, полученные цвета значительно неточны во всех проверенных мной браузерах. Здесь образец результатов, которые я получаю с конкретной операцией смешивания:
![Invalid blending results]()
Демонстрация проблем
Для примера и некоторого кода для игры, проверьте этот jsFiddle, который я обработал:
http://jsfiddle.net/jMjFh/2/
Верхний набор данных является результатом теста, который вы можете настроить, отредактировав iters
и rgba
в верхней части кода JS. Он принимает любой цвет, указанный вами, RGBA all in range [0, 255]
, и печатает его на полностью чистом, прозрачном холсте iters
количество раз. В то же время, он ведет текущий расчет того, что стандартная функция Porter-Duff source-over-dest будет производить для той же процедуры (то есть, что браузеры должны запускать и придумывать).
Нижний набор данных представляет собой граничный случай, который я нашел, когда смешение [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, вы заметите разницу значительная в результатах смешивания. К сожалению, мы не говорим об одном или двух значениях - это намного хуже.
Фоновая информация
Мне известно о том, что спецификация canvas HTML5 указывает, что выполнение 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-источник-dest-dest, как и ожидалось.
Итак, похоже, что Chrome14 на Win7 непоследовательно ссылается на значения пикселей при работе с ними. Информация хранится в предварительно умноженном состоянии внутри, представлена вам в непремущественно умноженной форме и управляется с использованием значений обеих форм.
Я думаю, что можно обойти это, сделав некоторые дополнительные вызовы getImageData()
и/или putImageData()
, но последствия производительности кажутся такими большими. Кроме того, это, похоже, не та же проблема, что и Firefox7 на Win7, поэтому для этого потребуется сделать еще несколько исследований.
Изменить: Ниже приведен один возможный подход, который, вероятно, будет работать.
Мысли о решении
Я еще не вернулся к работе над этим, но один подход WebGL, с которым я недавно пришел, состоял в том, чтобы запускать все операции рисования с помощью простого пиксельного шейдера, который выполняет комбинацию Porter-Duff-source-over-dest, Кажется, проблема здесь возникает только при интерполяции значений для целей смешивания. Итак, если шейдер считывает два значения пикселей, вычисляет результат и записывает (не смешивает) значение в пункт назначения, он должен смягчать проблему. По крайней мере, это не должно сочетаться. Тем не менее, все равно будут незначительные погрешности округления.
Я знаю, что в своем первоначальном вопросе я упомянул, что ручное смешивание пикселей не будет жизнеспособным. Для реализации 2D-холста приложения, которое требует обратной связи в реальном времени, я не вижу способа исправить это. Все смешения будут выполняться на процессоре и будут препятствовать выполнению другого кода JS. Однако с помощью WebGL ваш пиксельный шейдер работает на графическом процессоре, поэтому я уверен, что не должно быть никакого повышения производительности.
Сложная часть состоит в том, что можно кормить холст dest
в виде текстуры в шейдере, потому что вам также нужно отображать его на холсте для просмотра. В конечном счете, вы хотите избежать необходимости генерировать объект текстуры WebGL из вашего видимого холста каждый раз, когда его необходимо обновить. Поддержание двух копий данных, с одной в виде текстуры в памяти и одной в виде видимого холста (который обновляется путем штамповки текстуры по мере необходимости), должен решить эту проблему.
Ответы
Ответ 1
О, боже. Я думаю, мы просто вошли в зону сумерек здесь. Боюсь, у меня нет ответа, но я могу предоставить хотя бы немного информации. Ваша догадка верна, что, по крайней мере, здесь что-то больше.
Я бы предпочел, чтобы пример был более простым (и, следовательно, менее подвержен какой-либо ошибке), я боюсь, что у вас может быть проблема с 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 как ошибка. (Мы посмотрим, что они скажут, но их типичный M.O. пока что игнорирует большинство отчетов об ошибках, если они не являются проблемами безопасности).
Я бы попросил вас проверить ваш пример - я думаю, что у вас может быть проблема с 0-255 до 0-1. Код, который у меня здесь, кажется, работает просто dandy, за исключением, возможно, ожидаемого красноватого цвета, но - согласованный итоговый цвет во всех браузерах, за исключением этой проблемы Chrome.
В конце концов, Chrome, вероятно, делает что-то для оптимизации холстов с площадью поверхности, меньшей 128х128, которая загрязняет вещи.
Ответ 2
Настройка 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/
edit: вы можете узнать, что здесь означают различные составные операции:
https://developer.mozilla.org/en/Canvas_tutorial/Compositing#globalCompositeOperation
например, "copy"
был полезен для меня, когда мне нужно было сделать анимацию затухания в определенных частях холста