Точность и правила округления внутренних элементов холста по математике в основном не определены, поэтому трудно точно сказать, что здесь происходит. Все, что мы действительно знаем, это то, что пиксели являются байтами без знака, а альфа предварительно умножается.
Однако мы можем получить некоторую информацию, используя getImageData
для проверки пикселей при рисовании штампа, например:
var px = 75;
var py = 100;
var stamp = new Image;
stamp.onload = function() {
for (var i = 0; i < 100; ++i) {
imageData = context.getImageData(px, py, 1, 1);
console.log(Array.prototype.slice.call(imageData.data, 0, 4));
context.drawImage(stamp, 0, 0);
}
};
stamp.src = 'stamp.png';
Образец в px = 75
, py = 100
находится прямо в середине серого шарика. После нанесения штампа на белый холст, журнал гласит:
[254, 254, 254, 255]
При px = 120
, py = 150
образец находится в середине красной области. После нанесения штампа один раз журнал гласит:
[254, 253, 253, 255]
Итак, похоже, что холст был изменен на (-1, -1, -1) для серого пикселя и (-1, -2, -2) для красного пикселя.
Выборка этих же пикселей в изображении штампа с использованием RMagick дает:
[167, 22, 22, 1] // x = 75, y = 100
[167, 22, 22, 2] // x = 120, y = 150
Работая по математике, используя стандартное уравнение альфа-смешивания, вы можете проверить каждое из значений цвета:
function blend(dst, src) {
var a = src[3] / 255.0
return [
(1.0 - a) * dst[0] + a * src[0],
(1.0 - a) * dst[1] + a * src[1],
(1.0 - a) * dst[2] + a * src[2]
];
}
console.log(blend([255, 255, 255], [167, 22, 22, 1]));
// output: [254.6549..., 254.0862..., 254.0862...]
console.log(blend([255, 255, 255], [167, 22, 22, 2]));
// output: [254.3098..., 253.1725..., 253.1725...]
Исходя из этого, мы можем догадаться, что код наложения холста фактически представляет результаты, а не округляет их. Это даст вам результат [254, 254, 254]
и [254, 253, 253]
, как мы видели на холсте. Скорее всего, они вообще не выполняют никакого округления, и оно неявно сбрасывается при возвращении к неподписанному байту.
Именно поэтому в другом посте рекомендуется хранить данные изображения в виде массива с плавающей точкой, самостоятельно выполнять математические вычисления, а затем обновлять холст с результатом. Таким образом вы получаете больше точности и можете управлять такими вещами, как округление.
Редактировать: На самом деле, эта функция blend()
не совсем правильная, даже когда результаты получены, поскольку значения пикселей холста для 120, 150
стабилизируются на [127, 0, 0]
, и эта функция стабилизируется на [167, 22, 22]
. Точно так же, когда я рисовал изображение только один раз в прозрачном холсте, getImageData
на пикселе в 120, 150
было [127, 0, 0, 2]
. Что?!
Оказывается, это вызвано предварительным умножением, которое, похоже, применяется к загруженным элементам изображения. См. this jsFiddle для примера.
Предварительно умноженные пиксели сохраняются как:
// r, g, b are 0 to 255
// a is 0 to 1
// dst is all 0 to 255
dst.r = Math.floor(r * a);
dst.g = Math.floor(g * a);
dst.b = Math.floor(b * a);
dst.a = a * 255;
Они распаковываются позже как:
inv = 1.0 / (a / 255);
r = Math.floor(dst.r * inv);
g = Math.floor(dst.g * inv);
b = Math.floor(dst.b * inv);
Запуск этого пакета / распаковки против [167, 22, 22, 2]
показывает:
a = 2 / 255; // 0.00784
inv = 1.0 / (2 / 255); // 127.5
r = Math.floor(Math.floor(167 * a) * inv); // 127
g = Math.floor(Math.floor(22 * a) * inv); // 0
b = Math.floor(Math.floor(22 * a) * inv); // 0