Comments 83
Там на чёрном фоне переход полностью прозрачного цвета в белый. Казалось бы, должен быть градиент между чёрным и белым. А вот фиг! Прозрачный задан как rgba(255,0,0,1), и в результате посередине явственно виден красный. Я уже сделал патч и описание, хотел отправить разрабам cairo… А потом посмотрел ту же картинку в Хроме. и увидел то же самое. И в последнем Эксплорере. Так что это уже не баг, а фича, и чинить это, к сожалению, никто не будет.
<linearGradient xmlns="http://www.w3.org/2000/svg" id="MyGradient">
<stop offset="0%" stop-color="rgba(255,0,0,0)"/>
<stop offset="100%" stop-color="white"/>
</linearGradient>
цвет должен быть только этот конечный цвет, и никакой другой
Кто вам такое сказал? А если я хочу выйти из прозрачного красного и в прозрачный белый? Я вот так часто в Юнити в частичках делаю. Там даже настраивается альфа-канал — отдельно, а все остальное — отдельно.
Вы мыслите поверхностно (вот у меня есть задача и пусть именно под эту задачу все подстраиваются). А задачи бывают совершенно разные и текущее решение — позволяет решать их все, а не только некоторое подмножество.
Об этом, собственно, и статья
Статья, конечно, близка, но не об этом.
А если я хочу выйти из прозрачного красного и в прозрачный белый? Я вот так часто в Юнити в частичках делаю. Там даже настраивается альфа-канал — отдельно, а все остальное — отдельно.
Прозрачный красный — это как? Полностью прозрачный он один, у него нет цвета. Если же имеется в виду «из прозрачного через полупрозрачный красный и в белый», то запросто:
<stop offset="0%" stop-color="rgba(0,0,0,0)"/>
<stop offset="50%" stop-color="rgba(255,0,0,0.5)"/>
<stop offset="100%" stop-color="white"/>
Прозрачный красный — это абстрактное понятие. Вас же не смущает мнимая единица, которая в квадрате внезапно превращается в -1? Почему же вас смущает прозрачный красный, который при изменении прозрачности внезапно становится красным, а не белым / чёрным?
К слову, какой цвет вы предлагаете на роль «настоящего прозрачного»? Чёрный прозрачный или белый прозрачный? Или, может быть, серый прозрачный?
Прозрачный красный — это как?
это красный + альфаканал. Ваш кэп.
используйте Premultiplied Alpha
Это вообще бредовый совет. Вот у меня есть красный цвет. Я ходу сделать красный с полупрозрачность 25%. Так вот — по формуле, данной в статье у меня вместо «rgba(255,255,255,0.25)» будет «rgba(51,51,51,0.2)». То есть теперь этот цвет будет мало того, что прозрачным, так еще и чернить и он будет некорректно орисовываться поверх белых поверхностей! Premultimlied alpha можно использовать только если рисовать более светлое поверх более темного, а не всегда. Посмотрите сами на его картинку-пример — эта картинка испорчена!
До:
После:
А поверх белого фона это будет еще более заметно.
То есть теперь этот цвет будет мало того, что прозрачным, так еще и чернить и он будет некорректно орисовываться поверх белых поверхностей!
Это неправда. Premultiplied Alpha нужна для вычислений, а не для вывода на экран. Для вывода на альфу нужно обратно разделить. Когда вы после вычислений получите rgba(51,51,51,0.2) и разделите на альфу, вы получите обратно rgba(255,255,255,0.2).
Зачем такой выбор, какой в нем смысл? О потере какой информации идет речь и при чем тут запись в файл, если мы говорим об обработке и выводе на экран?
Давайте вернемся к вашему исходному утверждению: «теперь этот цвет будет мало того, что прозрачным, так еще и чернить и он будет некорректно орисовываться поверх белых поверхностей». В браузерах кроме Сафари background-градиенты строятся именно с помощью приумноженного альфаканала. Сделав градиент, например, от красного непрозрачного, к зеленому прозрачному, вы можете убедиться, что никакого черного там нет.
Идея очень проста: вместо хранения текстуры как [RGBα] нужно хранить её как [α∗Rα∗Gα∗Bα]
О выводе не говорится. Говорится о хранении. Если хранить картинки в таком виде, то в большом количестве целевых устройств будет именно черный цвет. Именно этот эффект мы видим на картинке «после».
Обычный блендинг это:
dectColor * (1 — srcAlpha) + srcColor * srcAlpha
То есть чтобы смешать 2 картинки видеокарта потом все равно делает это умножение на альфу (srcColor * srcAlpha). Поэтому режим смешивания для premultiplied делают таким, чтобы этого умножения не было. Если использовать OpenGL, то вместо glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) просто делают glBlendFunc(GL_SRC_ONE, GL_ONE_MINUS_SRC_ALPHA).
Так что чернить оно не будет.
Но есть другой неприятный момент, смещение цвета при семплинге. Я об этом написал чуть ниже в комментарии. Он имеет место быть для картинок, которые сильно растягиваются, например текстуры GUI. На выходе можем получить не совсем то, что задумывал дизайнер.
Хранить можно и в памати. Но даже если для оптимизации загрузки хранить в файлах в таком же виде, то ничего страшного не случится: вы же будете знать, что это формат приумноженной альфы и будете выводить его правильно. Конечно, другие просмоторщики не будут этого знать и будут открывать так как у автора на рисунке, с черным. Если это например ресурсы игры, то это не страшно.
Возмем 2 RGBA пикселя:
{1, 0, 0, 0.1} {0, 0, 1, 0.9}
Допустим мы семплим ровно между пикселей, это будет что-то типа lerp(c1, c2, 0.5), и пиксель после семплинга будет:
{0.5, 0, 0.5, 0.5}
А теперь делаем Premultiply для этих пикселей:
{0.1, 0, 0, 0.1} {0, 0, 0.9, 0.9}
Аналогично семплим, и получаем другой результат:
{0.05, 0, 0.45, 0.5}
Уже как бы видно, что результат другой, если разделим на 0.5 то получим:
{0.1, 0, 0.9, 0.5}, согласитесь, это значительно отличается от {0.5, 0, 0.5, 0.5}
Соглашусь, значительно отличается. Не понимаю, что тут испорчено, это верный результат.
Предлагаете потом художнику доказывать, что он не прав, и у нас в рендере результат верный?
Понял о чем вы. Растягивать текстуру 2*2 — это грубо говоря наложить градиент. И дизайнеру может хотеться, чтобы градиент шел из красного непрозрачного в зеленый прозрачный. При умножении такого трудно добиться. Но это не значит, что такое поведение без умножения — один из вариантов нормы. Нет, правильный скейлинг и субпиксельный рендеринг может быть только с умножением. А то, что вам иногда нужно другое поведение — это изобразительный прием. Изобразительные приемы бывают разные и вы можете реализовать их через шейдеры. К скейлингу это не будет иметь отношения.
Другими словами, Premultiply нужно делать там, где художник не проверял результат интерполяции между соседними пикселями?
Посмотрите сами на его картинку-пример — эта картинка испорчена!
Картинка тут конечно зря приведена, она только запутывает
Неважно, какие там RGB, если прозрачность 100%.А если прозрачность 90%? А если 99%? А если у нас формат float'ы использует и прозрачность 99.999%?
В какой момент и почему вдруг становится «неважно какие там RGB»?
Хочется просто сказать: «Ты хочешь 0, но принципиально пишешь 255, а потом винишь компьютер, что он не угадал твоё желание — не надо так!»
Не подскажете как перевести ARGB->RGB.
Никак. Оно не «переводится». Вы можете наложить ARGB изображение на что-то (сплошной цвет или другое сплошное изображение), после чего альфа везде будет равна единице и тогда вы её сможете отбросить.
цвет канала OR (альфа-канал XOR цвет подложки).
OR — побитовое ИЛИ
XOR — побитовое исключающее ИЛИ
В коде будет что-то типа:
background = 0xFF # Белая подложка
new_red = red | (alpha ^ background)
Вот например была такая текстура:
А вот она без альфаканала. Вверху до, внизу — обработанная моей тулзой:
Вот тут исходный код тулзы:
https://github.com/MrShoor/AvalancheProject/tree/master/Tools/PNGEdger
А вот я загрузил собранную версию (если кому-то понадобится): http://www.gamedev.ru/files/?id=125762
Если пиксели строго прозрачные или строго непрозрачные (как в примере с плюсом), можно особо не заморачиваться: достаточно сделать всё прозрачное "прозрачным чёрным" и выбрать подходящую glBlendFunc. (В некоторых графических редакторах "прозрачный чёрный" сам по себе появляется безо всяких усилий со стороны редактирующего).
В случае с полупрозрачными пикселями на краях так не прокатит, придётся делать как в статье.
но ведь вроде для корректного смешивания надо возвести в квадрат, смешать, и извлечь корень?
https://www.youtube.com/watch?v=LKnqECcg6Gw
http://stsievert.com/blog/2015/04/23/image-sqrt/
В играх текстуры вполне могут быть сразу в линейном sRGB. Не делать же гамма-коррекцию для каждой операции, когда разумнее загрузить в линейном виде, сделать фильтрацию, и уже на выводе финального изображения откалибровать для монитора.
Современные игры учитывают гамму, претензии скорее нужно направлять к софту: графическим редакторам, браузерам.
https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch24.html
Не делать же гамма-коррекцию для каждой операции, когда разумнее загрузить в линейном виде, сделать фильтрацию, и уже на выводе финального изображения откалибровать для монитора.Делать. Точность в sRGB будет выше. Иначе зачем бы заморачиваться с sRGB вообще. Сегодня такое конвертирование крайне дешевое, потому что делается аппаратно на видеокарте отдельными блоками.
Вместо:
Загрузить две картинки
Возвести значения цветовых каналов изображений в квадрат
Наложить изображения
Найти корень
Опять возвести в квадрат
Сделать тонирование
Опять найти корень
Вывести финальное изображение
Так:
Загрузить изображения
Возвести значения в корень
Наложить изображения
Тонировать
Найти корень
Вывести результат
То есть, я хочу сказать, что гамма-коррекция не относится к интерполяции или другой обработке, и не следует её пихать в каждый фильтр, а делать непосредственно при операциях ввода-вывода.
и не следует её пихать в каждый фильтр, а делать непосредственно при операциях ввода-вывода.Если промежуточные вычисления хранятся в большей размености, то я с вами соглашусь. Если промежуточные вычисления хранятся в byte, то придется каждый раз при отправке в byte и извлечении обратно конвертировать.
В википедии есть статья на эту тему
https://en.m.wikipedia.org/wiki/Alpha_compositing
Добрый день!
Почему то сразу вспомнил сайт UFO. Метод "вставки чёрных кадров" ( https://www.testufo.com/#test=blackframes ) — это то же, что описано в статье?
Не могу согласиться с вами и назвать баг принципом работы. Это недоработка, которая тянется из игнорирования альфа-канала при субпиксельном рендеринге.
Получается, это баг видеокарт.
Что вы называете видеокартой? Видеокарта — это набор железа. Еще есть драйвера, графические API, ваш код шейдеров. Все это заставляет видеокарту работать так или иначе. Видеокарта делает то, что ей скажут. Статья о том, как заставить работать правильно.
Ведь понятно, что усреднять пиксели надо принимая во внимание все слои. В статье же описывается, как не принимать во внимание слой фона, на котором рисуется картинка со сглаживанием и субпиксельным отображением. Но в чём проблема принимать во внимание все слои? Отпадёт необходимость городить кучу описанных костылей.
В указанном в статье примере субпиксели какого-то лешего учитывают соседние пиксели только основного слоя, которые должны быть прозрачны под альфа-слоем. Хотя соседние пиксели надо брать из сочетания фона и основного слоя (в примере фон белый, учитывая что прозрачность полная, то совершенно не важно что там в основном слое).
При чем тут вообще субпиксели? И как вы собираетесь брать сочетание фона и основного слоя — если именно это сочетание и строится неправильно?
Ошибка в построении вызвана тем, что граничащие пиксели берутся из слоя, который не видно вместо того чтобы браться из фона. Основной слой становится прозрачным через маску альфа-слоя.
Не понимаю, как вы не понимаете, что баг легко поправить, просто учитывая фон, а не городить огород.
Предложите формулу, раз это так легко сделать.
Берём красный крест 255,0,0 с синим ключевым цветом 0,0,255 (что не важно, учитывая альфа канал, который превращает его в цвет фона)
Подсчёт пикселей на 50% перекрытии на белом фоне 255,255,255:
255,0,0 + 255,255,255 => 255,128,128, то есть розовый. Никакого синего или фиолетового.
Это для однородного фона так все просто. А если на фоне уже что-то нарисовано?
Разница в том, что прежде чем "просто учитывать цвет фона в формуле", надо понять какой из пикселей фона нужно брать. Вы это забыли сказать.
Что такое "левый пиксель фона" и "правый пиксель фона"?
На этом изображении указанный пиксель перекрывает 2 пикселя фона. Один неизбежно становится левым, второй правым.
Подождите, но вы что и на чем рисуете? Мне почему-то на этой картинке виден один пиксель фона, перекрытый двумя пикселями изображения...
То есть вы предлагаете фактически сначала отрисовать фон на изображении — а потом изображение уже без альфа-канала — на фоне?
При таком способе отрисовки любое изображение будет немного размывать фон — а это неправильно.
Правильно делать как написано в этом посте. Смотреть на синий ключевой цвет вам никто и не предлагает.
Как мы видим, проблема возникает, когда положение текстуры не соответствует попиксельно экранным пикселям. Это можно объяснить билинейной фильтрацией, которую видеопроцессор выполняет при рендеринге спрайта на экране: при сэмплировании текстуры видеопроцессор усредняет значения цвета ближайших соседних пикселей с запрошенными координатами в вертикальном и горизонтальном направлениях.
В статье предполагается, что сэмплирование происходит перед наложением на фон. Поэтому фон считается неопределённым и возникает артифакт. Я же предлагаю объединить эти процессы. При семплировании брать ближайшие пиксели уже с фона. Откройте photoshop или gimp. Там нет таких проблем. Прозрачный фон считается прозрачным.
Нет, в статье предлагается два способа решения проблемы, один из которых — костыль, а второй нормальный.
Вы предлагаете более сложный способ.
Да никто вам не мешает объеденить две операции, если у вас две операции. Просто в статье речь об одной операции, той, которая создает пролемы. В статье общий способ, вы даже сказали что формула та же. Вы же говорите «я знаю, как можно проще» и предлагаете частный.
Формула та же, ничего не меняется. Просто учитывается фон.Нифига ж себе «просто». А ничего что понятие «фона» в общем случае неопределено?
Рассмотрите простой пример: возьмите два ваших креста и расположите их друг над другом. Сдвиньте один из них на 1/3 пикселя, а второй — на 2/3. И? Что тут будет с фоном?
Подсчёт пикселей на 50% перекрытии на белом фоне 255,255,255:Если бы в природе был только белый фон и не было бы никакого другого — то никому нафиг бы не сдалась вся эта конструкция. Но что делать, если у вас кроме обьекта и белого фона есть ещё что-нибудь? Другие обьекты, в частности, полупрозрачные.
Вы уж, пожалуйста, изобретите что-нибудь работающее не только для одного частного случая, а?
Расположите объекты наложения слоями и подсчитывайте по-очереди, считая фоном результат подсчёта.А если обьекты того, отказываются «слоями» располагаться? Мы тут про игру, в общем-то говорим.
Но во время построения изображения фоном будет самый нижний слой.Это если он существует. А если у вас обьекты расположены так, что нельзя сказать какой из них «ниже»?
И всё же что вы говорите — это те самые условия, которых я предлагаю избегать.
Ещё во времена экранного режима 0x13h, когда вся видеопамять умещалась в 64к не было необходимости делать всё как у всех, было интересно экспериментировать, можно было с интересом написать свой хитрый движок рендеринга и даже свою поделку (игрой назвать сложно) на этом движке. И вот тогда мои слова про то, что подготовку текстур можно делать непосредственно во время рендеринга сцены, вместе с сэмплированием и фильтрациями были бы уместны.
Но теперь многие функции положены на видеокарты, что стало стандартом и проще не изобретать велосипед, а идти по проверенному пути, в котором одни грабли обходятся заранее подготовленными костылями и все так делают, о чём собственно статья. Да, в ней всё верно написано. Но с учётом того частного случая, который стал повсеместно употребляться. Статья как раз о том, как готовить текстуры, шлифуя костыль до блеска.
Кстати, а ведь маленькие текстуры до сих пор используются. Где? Да везде! Mip-mapping. )) А когда поверх текстуры еще и DXT-сжатие прошлось, баги множатся и расцветают во всей красе.
Вот как мы боролись с этим в Half-Life; по сути, точно такое же заполнение, как рекомендуют здесь в комментариях, но с упрощенным алгоритмом, без distance field.
Опасайтесь прозрачных пикселей