Гладкие неровные пиксели
Я создал пинч-фильтр / эффект на холсте, используя следующий алгоритм:
// iterate pixels
for (var i = 0; i < originalPixels.data.length; i+= 4) {
// calculate a pixel's position, distance, and angle
var pixel = new Pixel(affectedPixels, i, origin);
// check if the pixel is in the effect area
if (pixel.dist < effectRadius) {
// initial method (flawed)
// iterate original pixels and calculate the new position of the current pixel in the affected pixels
if (method.value == "org2aff") {
var targetDist = ( pixel.dist - (1 - pixel.dist / effectRadius) * (effectStrength * effectRadius) ).clamp(0, effectRadius);
var targetPos = calcPos(origin, pixel.angle, targetDist);
setPixel(affectedPixels, targetPos.x, targetPos.y, getPixel(originalPixels, pixel.pos.x, pixel.pos.y));
} else {
// alternative method (better)
// iterate affected pixels and calculate the original position of the current pixel in the original pixels
var originalDist = (pixel.dist + (effectStrength * effectRadius)) / (1 + effectStrength);
var originalPos = calcPos(origin, pixel.angle, originalDist);
setPixel(affectedPixels, pixel.pos.x, pixel.pos.y, getPixel(originalPixels, originalPos.x, originalPos.y));
}
} else {
// copy unaffected pixels from original to new image
setPixel(affectedPixels, pixel.pos.x, pixel.pos.y, getPixel(originalPixels, pixel.pos.x, pixel.pos.y));
}
}
Я изо всех сил пытался достичь этого, и я очень доволен результатом. Тем не менее, у меня есть небольшая проблема; неровные пиксели. Сравните щепотку JS с Gimp:
Я не знаю, что мне не хватает. Нужно ли применять другой фильтр после фактического фильтра? Или мой алгоритм вообще неверен?
Я не могу добавить полный код здесь (как фрагмент кода SO), потому что он содержит 4 base64 изображения / текстуры (всего 65 тыс. Символов). Вместо этого вот JSFiddle.
2 ответа
Один из способов убрать результат - суперсэмплинг. Вот простой пример: https://jsfiddle.net/Lawmo4q8/
По сути, вместо того, чтобы вычислять одно значение для одного пикселя, вы берете несколько выборок значений в пределах / вокруг пикселя...
let color =
calcColor(x - 0.25, y - 0.25) + calcColor(x + 0.25, y - 0.25) +
calcColor(x - 0.25, y + 0.25) + calcColor(x + 0.25, y + 0.25);
... и каким-то образом объединить результаты.
color /= 4;
Вместо применения последовательного фильтра лучше всего выполнять передискретизацию непосредственно в алгоритме сжатия. (вместо расчета одного пикселя сделайте это для более слегка смещенных пикселей и усредните)
Другой вариант — повысить масштаб исходного изображения, запустить алгоритм и уменьшить изображение, интерполируя пиксели.