LINUX.ORG.RU

Как ресайзить картинки с прозрачностью, чтобе не возникало артефактов на границе?

 


2

1

https://github.com/nodeca/pica/issues/60

Есть PNG, у которого по периметру рамка со 100% прозрачности. Ресайзится ланцешем, RGBA, каждый канал независимо.

Понятно, что 100% альфа позоляет в RGB положить что угодно. В частности, в картинки часто кладут #000000. Из-за этого при резкой смене альфы вылезают артефакты в виде серых ореолов.

Как правильно победить эту бяку? Желательно с не очень дорогими вычислениями.

★★★★★

Идеологически правильно, мне кажется — уменьшать удельный вес пикселя в апертуре FIR-фильтра в соответствии с его прозрачностью.

Но просто домножить цвет на уровень прозрачности будет мало (прозрачные пиксели будут эквивалентны чёрным), надо отмасштабировать выходной результат, чтобы FIR-фильтр понимал, что он считает не по апертуре, скажем, в 9 пикселей, а по «апертуре» в 8.5 пикслелей. Это можно сделать домножением результата на (площадь апертуры)/(сумма альфа-коэффициентов в апертуре).

По-моему, выбирать надо именно правильную идеологию, даже если это затратнее по вычислениям. В сабже проблема видна в наихудшем случае (резкая смена альфы, резкая смена цвета), но она существует всегда, просто меньше бросается в глаза, если переходы более плавные. Добавлением костыля для обхода этого конкретного бага в ОП проблема полностью не решится.

По вычислениям получается так:

  • Предварительная обработка (умножить RGB на A) — O(n)
  • Вычислить матрицу коэффициентов для постобработки методом скользящего среднего на основе IIR — для прямоугольной апертуры очень легко сделать O(n), но для более сложной формы, видимо, проще сделать O(n*m), n — размер изображения, m — высота апертуры (или ширина, неважно).
  • Для постобработки (домножить результат на вычисленную матрицу) — O(n)

Ещё из плюсов — если считать альфу за границами изображения равной нулю, то это автоматически решает проблему с фильтрацией возле границ, когда апертура выезжает за пределы картинки.

anonymous
()

Посмотрел код, очень удивлён, что фильтр (написано, что вроде бы типа как Ланцоша) там применяется отдельно по горизонтали и вертикали, как будто он разделяемый.

Но насколько я знаю, единственный разделяемый центральносимметричный фильтр — это Гаусс и только Гаусс.

anonymous
()
Ответ на: комментарий от cdshines

Ты не то лечить пытаешься. Любой фильтр так или иначе расколбасит резкую границу, и победить это не реально. Проблема в том, что начинает размываться неправильное значение цвета, а не то что фильтр кривой.

Vit ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Спорить не буду, т.к математику порядком подзабыл. Но ланцоша разделяют абсолютно все. А вот его модификации из imagemagick дейсвительно не разделяемые (Lanczos EWA и т.п.).

Vit ★★★★★
() автор топика

В код не смотрел, но что мешает на этапе смешения пикселей в один делать примерно так:

# src_pixels = {pixel : amount, ...}
pixels = {pixel : amount*(pixel.alpha/MAX_ALPHA)}
r = sum([pixel.r*amount for pixel, amount in pixels])/sum(pixels.values())

Таким образом, совсем прозрачные пиксели учитываться не будут вообще.

// да, я знаю, что там у вас жабоскрипт, но думаю и так понятно

anonymous
()
Ответ на: комментарий от Vit

Он все правильно описал. Я долго возился с этой проблемой, но с рабоче-крестьянской позиции, предварительно нормализуя пиксели. Здесь же дано более правильное определение.

ruzisufaka
()
Ответ на: комментарий от anonymous

Пока нет уверенности, что результирующий фильтр можно будет продолжать называть ланцешом.

Плюс есть специфичные яваскриптопроблемы, что плавучка и деление убивают оптимизации.

Vit ★★★★★
() автор топика
Ответ на: комментарий от Vit

Нет, всё норм, в википедии пишут, правда без ссылки, что это перемножение двух одномерных фильтров. Я просто грешным делом думал, что Ланцош — это идеальный ФНЧ с sinc-окном, тогда его ИХ будет не sinc(x)*sinc(x/3)*sinc(y)*sinc(y/3), а sinc(sqrt(x^2+y^2))*sinc(sqrt(x^2+y^2)/3), и в таком виде он уже не разделяемый.

( https://en.wikipedia.org/wiki/Lanczos_resampling#Multidimensional_interpolation )

anonymous
()
Ответ на: комментарий от Vit

Проверить нечем, теории обработки сигналов с дырками я что-то не припомню. Но этот подход проходит все sanity check, которые можно прикинуть на коленке — он не зависит от цвета прозрачных пикселей, он превращается в стандартный FIR для непрозрачной картинки, он превращается в стандартный FIR для картинки с равномерным уровнем прозрачности, он оставляет белую картинку с дырками белой, а чёрную с дырками чёрной. Плюс немного интуиции, которая подсказывает, что линейные системы лучше всего реализуются линейными методами, типа суммирования и масштабирования.

anonymous
()
Ответ на: комментарий от anonymous

Сложно получается, не взлетит на яваскрипте. Надо что-то другое. Например, перед ресайзом пересчитать все точки где alpha = 0.

Vit ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Там есть еще такой момент - alpha может быть premultiplied, а может и не быть. В яваскрипте она premultiplied, когда с канвасом работаешь. А вот в Skia, откуда алгоритмы тыри^Wтворчески переосмысливали - не premultiplied. Правда я не уверен, что в Skia с подобными картинками все хорошо, и я знаю как минимум одну ошибку округления, которую пришлось фиксить.

Vit ★★★★★
() автор топика
Ответ на: комментарий от Vit

Ты ж понимаешь, что это костыль. Если в твоём примере прозрачную область сделать не 0/255, а 1/255, у тебя будут появляться ровно такие же чёрные ореолы. Это свидетельство более широкой проблемы. К тому же это не просто костыль, но ещё и костыль, которой потребует больше ресурсов, чем нормальное решение, если честно красить точки по ближайшему ненулевому соседу.

anonymous
()
Ответ на: комментарий от anonymous

Во-во. Чёрная полоска - это лишь наиболее наглядная демонстрация более широкой проблемы кривой работы с неравномерной прозрачностью.

anonymous
()
Ответ на: комментарий от anonymous

Обсуждавшееся нормальное решение, к сожалению, не юзабельно по скорости. Если уйти от предрасчитанных фильтров для конвольвера, или добавить деление, всё сразу грустно станет.

Vit ★★★★★
() автор топика

Проблема в том, что RGB краевых пикселей размывается с этими #000000, да.
Вариант - предварительно размазать цвет краевых пикселей наружу (на место тех самых #000000), за край альфы, не трогая саму альфу. А затем уже ресайзить.

blexey ★★★★★
()
Ответ на: комментарий от Vit

Серьёзно, сильно тормозит деление? На яваскрипте? На сколько падает производительность, если сдвиги в исходниках делениями заменить?

anonymous
()
Ответ на: комментарий от Vit

Не, пиксельную магию забросил где-то в 90-х, во времена появления в наших краях TrueColor.
В голове - её остатки, позволяющие понимать базовые вещи и причинно-следственные связи действий в графических редакторах.

blexey ★★★★★
()
Ответ на: комментарий от anonymous

Там не само деление тормозит, там деоптимизация инлайновых кешей происходит. Разница раза в 4-5. Ну и плюс более сложная математика. Вполне реально на порядок просесть. С плавучкой похожая фигня.

Если нужно быстро молотить циферьки, в критичных цепях надо вписываться в int32 и без деления.

Vit ★★★★★
() автор топика
Ответ на: комментарий от anonymous

http://nodeca.github.io/glur/demo/

Вот там уже плавучка есть, хоть и на fixed point 32. IIR-фильтр рекурсивный.

Так себе скорость. Спасает только то, что в USM подобное применяется уже после ресайза, когда картинка уменьшена.

Vit ★★★★★
() автор топика
Ответ на: комментарий от trashymichael

а если серьезно, костыль в виде: сделать маску по прозрачности, заблурить, наложить прозрачность поверху не подойдет?

trashymichael ★★★
()
Ответ на: комментарий от Vit

С такими суровыми требованиями тебе даже костылей нормальных не предложат. Но можно попробовать делать все по очереди - сначала фильтровать все, потом делить все, чтобы кэш не портился. Нахождение суммы альфа-коэффициентов - это практически та же фильтрация, но без умножений вообще (все коэффициенты фильтра равны 1).

anonymous
()
Ответ на: комментарий от anonymous

Ну скоро будет SIMD и WebAssembly, но пока надо работать с тем что есть. Лично мне PNG ресайзить не надо, так что я в любом случае в домике. Просто хотелось бы добить ради понтов.

Нашел обсуждение точно такого же косяка в ImageMagick. Они там сразу написали, что делать развесовку цвета по альфе в лоб - слишком дорого. Правда пока не понял как зафиксили.

- http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=17926&amp...
- http://www.imagemagick.org/Usage/bugs/resize_halo/

Vit ★★★★★
() автор топика

Может быть, тебе nearest-neighbor interpolation подойдет? Там такой проблемы не будет вовсе (будут другие, лол =).

Waterlaz ★★★★★
()
Ответ на: комментарий от Vit

Единственный метод, который будет давать стабильный результат всегда. Если ты придумаешь какой-то хак для ресайза в этом юзкейз, в другом он будет давать плохие результаты. Мне так кажется =)

Waterlaz ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.