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