Резюме
При многократном рисовании чего-либо (видимо, с низким значением альфа) на холсте, независимо от того, имеет ли он функцию drawImage()
или fill
, полученные цвета значительно неточны во всех проверенных мной браузерах. Здесь образец результатов, которые я получаю с конкретной операцией смешивания:
Демонстрация проблем
Для примера и некоторого кода для игры, проверьте этот jsFiddle, который я обработал:
Верхний набор данных является результатом теста, который вы можете настроить, отредактировав 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 из вашего видимого холста каждый раз, когда его необходимо обновить. Поддержание двух копий данных, с одной в виде текстуры в памяти и одной в виде видимого холста (который обновляется путем штамповки текстуры по мере необходимости), должен решить эту проблему.