Почему в выходных данных SVG feTurbulence есть тонкие темные линии?

avatar
Alan De Smet
9 августа 2021 в 04:57
144
2
4

Экспериментируя с примитивом фильтра feTurbulence, я получаю тонкие темные линии везде, где я их не ожидал. Они наиболее заметны, когда numOctaves="1". Почему они там?

Допустим, я начну со ссылочного кода из https://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement (исправлю его, чтобы он компилировался). Я называю это как

turbulence(
            0,        /* color channel */,
            point,    /* {x,y} */
            1.0, 1.0, /* fBaseFreqX and Y */
            1,        /* numOctaves */
            0,        /* bFractalSum */
            0,        /* bDoStitching */
            0.0, 0.0, /* fTileX and Y */
            0.0, 0.0, /* fTileWidth and Height */
            )

(Мой полный исходный код доступен по адресу https://gitlab.com/AlanDeSmet/svg-1.1-feturbulence)

Итерация x и y от 0,0 до 10,0, взятие 300 выборок и умножение каждой выборки на 256 создает изображение в оттенках серого 300x300:

enter image description here

Это то, что я ожидаю увидеть. Это похоже на турбулентность Перлина, создаваемую такими программами, как

.

Adobe Flash (источник): enter image description here

3ds Max (источник): enter image description here

Но если я создаю SVG с помощью feTurbulence и просматриваю его в Firefox, Chromium или Inkscape (которые, как мне кажется, являются тремя независимыми реализациями), я получаю следующее:

enter image description here

Источник:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg height="10" width="10" version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter color-interpolation-filters="sRGB"
     id="test-turbulence" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" numOctaves="1" baseFrequency="1" />
    <feColorMatrix
       values="1 0 0 0 0
               1 0 0 0 0
               1 0 0 0 0
               0 0 0 0 1 " />
  </filter>
  <rect width="10" height="10" style="filter:url(#test-turbulence)" x="0" y="0" />
</svg>

(Я использую color-interpolation-filters="sRGB", чтобы более точно соответствовать выходным данным моей простой программы. Это не меняет структуру, а просто "затемняет" изображение.

На изображении есть тонкие темные линии по всему изображению, чего я не ожидал. Вот параллельное сравнение; мое использование стандартной эталонной реализации слева (или выше) и вывод Chromium (который выглядит идентично Firefox и Inkscape) справа (или ниже).

enter image description here enter image description here

Похоже, что это соответствует стандарту, так как три разных средства визуализации согласуются друг с другом, но я считаю, что это не то, что делает стандартная эталонная реализация, и не то, что делают некоторые другие программы.

Почему существует разница между моей попыткой использовать эталонную реализацию стандарта и тем, что делают Firefox, Chrome и Inkscape? Предполагается ли, что стандарт отличается от других программ, реализованных для турбулентности Перлина? Если да, то какая разница?

Источник
Robert Longson
9 августа 2021 в 20:12
0

gitlab.com/AlanDeSmet/svg-1.1-feturbulence/-/blob/master/… должен выполнять цикл до тех пор, пока один из градиентов не станет ненулевым. В противном случае s будет 0, и вы будете делить на 0

Alan De Smet
18 августа 2021 в 16:10
0

Этот проект gitlab существует исключительно для экспериментов со спецификацией SVG feTurbulence, поэтому я использую эталонную реализацию стандарта с минимальными изменениями. Это включает в себя опасность всех нулевых градиентов. Я должен добавить несколько заметок, чтобы любой, кто наткнулся на него, знал, что он не подходит для использования в производстве.

Robert Longson
18 августа 2021 в 16:22
1

В стандарте где-то есть опечатки, которые указывают на это.

Robert Longson
18 августа 2021 в 16:39
1

lists.w3.org/Archives/Public/www-svg/2015Jan/0014.html

Ответы (2)

avatar
Alan De Smet
11 августа 2021 в 18:03
4

Сводка

Это ожидаемое поведение. Этого можно избежать, используя альфа-канал. канал для турбулентности, а не любой из цветовых каналов.

Вы можете использовать feColorMatrix для создания турбулентности в оттенках серого из альфа-канала:

<filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
  <feTurbulence type="turbulence" baseFrequency="0.02" />
  <feColorMatrix
     values="0 0 0 1 0
             0 0 0 1 0
             0 0 0 1 0
             0 0 0 0 1 " />
</filter>

Но почему?

Поведение удивительное, но оно соответствует спецификации SVG и может подходит для определенных целей.

Неожиданные строки из альфа-канала, несмотря на то, что он был отброшен! Вот, например, все четыре канала. Обратите внимание, что потоки охватывают все цветовых каналов и совпадает с почти нулевыми участками альфа-канал.

enter image description here

(SVG, использованный для создания этого, приведен ниже в разделе "Альфа-сравнение SVG".)

В спецификации SVG (резервная ссылка) говорится:

Если не указано иное, все фильтры изображения работают с предварительным умножением RGBA. образцы. Фильтры, которые более естественно работают с данными без предварительного умножения (feColorMatrix и feComponentTransfer) временно отменяет и повторяет действия. предварительное умножение, как указано.

«Предварительное умножение» в данном случае говорит о предварительно умноженном альфа-канале, где цветовые каналы регулируются альфой. Предварительное умножение хорошо, потому что оно позволяет компоновке и фильтрации работать правильно. Это происходит за сцены, и вы можете игнорировать это... если вы не измените альфа-канал.

Проблема в том, что при предварительном умножении теряются данные. И когда альфа-значения приблизиться к 0 (полностью прозрачный), потеря данных особенно серьезна. Когда feColorMatrix или feComponentTransfer «временно отменить и повторить предварительное умножение", операция отмены является лишь приближением. Эти данные потеря проявляется в виде неожиданных линий по всему изображению.

Например, для входного изображения, цветовые каналы которого

enter image description here

и чей альфа-канал

enter image description here

предварительно умноженная версия цветовых каналов будет

enter image description here

Попытка отменить предварительное умножение дает следующее:

enter image description here

Имеются повреждения по всему изображению (чуть меньше 50 % несоответствия пикселей), но разница с оригиналом наиболее разительна, когда альфа близка к нулю.

(Эти изображения были созданы с помощью приведенного ниже кода Python в разделе «Генератор изображений для сравнения». premul_alpha и unpremul_alpha основаны на
реализации Inkscape<776972972>3531>2)<77139731>2)

А как насчет type="fractalNoise"?

Все вышеперечисленное относится к <feTurbulence type="fractalNoise">, так почему это не проблема?

Потому что <feTurbulence type="fractalNoise" numOctaves="1"> — это необработанный двухмерный шум Перлина, а шум Перлина находится в диапазоне от −0,707 до 0,707 (backup link). Он рассматривается как диапазон от −1 до 1. Переназначив этот диапазон от 0 до 255, все значения окажутся между 37 и 217. Повреждение присутствует, но поскольку альфа никогда не бывает достаточно близкой к 0, вы его не видите.

Это становится видимым с type="turbulence", потому что турбулентность Перлина использует абсолютное значение необработанного шума. Таким образом, диапазон становится от 0,000 до 0,707, в конечном счете, в диапазоне от 0 до 217. Вот почему fractalNoise не имеет чистого черного цвета, а turbulence имеет (и почему ни один из них не имеет чисто белого цвета).

enter image description here enter image description here

(Источник для этого находится в разделе "Турбулентность и шум" ниже.)

Сноски

Альфа-сравнение SVG

Это SVG, сравнивающий четыре канала, испускаемые feTurbulence.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   height="230"
   width="800"
   version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter id="turbulence-red" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="1 0 0 0 0
               1 0 0 0 0
               1 0 0 0 0
               0 0 0 0 1 " />
  </filter>
  <filter id="turbulence-green" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 1 0 0 0
               0 1 0 0 0
               0 1 0 0 0
               0 0 0 0 1 " />
  </filter>
  <filter id="turbulence-blue" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 1 0 0
               0 0 1 0 0
               0 0 1 0 0
               0 0 0 0 1 " />
  </filter>
  <filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 0 1 0
               0 0 0 1 0
               0 0 0 1 0
               0 0 0 0 1 " />
  </filter>
  <text x="100" y="220" text-anchor="middle">Red Channel</text>
  <rect x="0" y="0" width="200" height="200"
     style="filter:url(#turbulence-red)" />

  <text x="300" y="220" text-anchor="middle">Green Channel</text>
  <rect x="200" y="0" width="200" height="200"
     style="filter:url(#turbulence-green)" />

  <text x="500" y="220" text-anchor="middle">Blue Channel</text>
  <rect x="400" y="0" width="200" height="200"
     style="filter:url(#turbulence-blue)" />

  <text x="700" y="220" text-anchor="middle">Alpha Channel</text>
  <rect x="600" y="0" width="200" height="200"
     style="filter:url(#turbulence-alpha)" />
</svg>

Генератор сравнительных изображений

Этот код сгенерировал четыре квадратных примера изображений выше.

#! /usr/bin/python3

from PIL import Image

def premul_alpha(color,alpha):
    temp = alpha * color + 128
    res = (temp + (temp >> 8)) >> 8
    return res

def unpremul_alpha(color, alpha):
    if alpha == 0: return color # Nonsensical operation
    res = int((255 * color + alpha/2) / alpha)
    return res

originalimg = Image.new("L",(256,256))
original_px = originalimg.load()
alphaimg = Image.new("L",(256,256))
alpha_px = alphaimg.load()
premulimg = Image.new("L",(256,256))
premul_px = premulimg.load()
restoredimg = Image.new("L",(256,256))
restored_px = restoredimg.load()
damagedimg = Image.new("L",(256,256),0)
damaged_px = damagedimg.load()


total = 0
dmg_count =0
for color in range(256):
    for alpha in range(0,256):
        original_px[color,alpha] = color;
        alpha_px[color,alpha] = alpha;
        during  = premul_alpha(color,alpha)
        premul_px[color,alpha] = during
        restored = unpremul_alpha(during,alpha)
        restored_px[color,alpha] = restored
        total += 1
        if restored != color:
            dmg_count += 1
            damaged_px[color,alpha] = 255
print(f"{dmg_count}/{total} -> {dmg_count/total}")

originalimg.save("original.png")
alphaimg.save("alpha.png")
premulimg.save("premul.png")
restoredimg.save("restored.png")
damagedimg.save("damaged.png")

Турбулентность и шум

Шум:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   height="200"
   width="200"
   version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
    <feTurbulence type="fractalNoise" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 0 1 0
               0 0 0 1 0
               0 0 0 1 0
               0 0 0 0 1 " />
  </filter>
  <rect x="0" y="0" width="200" height="200"
     style="filter:url(#turbulence-alpha)" />
</svg>

Турбулентность:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   height="200"
   width="200"
   version="1.1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <filter id="turbulence-alpha" x="0" y="0" width="1" height="1">
    <feTurbulence type="turbulence" baseFrequency="0.02" />
    <feColorMatrix
       values="0 0 0 1 0
               0 0 0 1 0
               0 0 0 1 0
               0 0 0 0 1 " />
  </filter>
  <rect x="0" y="0" width="200" height="200"
     style="filter:url(#turbulence-alpha)" />
</svg>

Michael Mullany
12 августа 2021 в 17:48
1

Это фантастическая детективная работа, Алан - отличная работа!

Alan De Smet
18 августа 2021 в 15:39
0

Майкл: Предполагая, что предложенная вами награда была предназначена для этого ответа, если я правильно понимаю, она не автоматически применяется к этому ответу, потому что ответ предшествует награде. Возможно, вам нужно явно что-то сделать? (Я не очень хорошо знаком с наградами, поэтому могу ошибаться.)

avatar
Michael Mullany
9 августа 2021 в 11:38
1

Это не совсем та математика, которая используется. Это соответствующий комментарий из исходного кода Chromium:

/** О типах шума: разница между первыми двумя заключается в незначительных изменениях в алгоритм, это не два совершенно разных шума. Вывод выглядит иначе, но как только шум генерируется в диапазоне [1, -1], вывод возвращается в диапазон [0, 1], выполнив :

 *  kFractalNoise_Type : noise * 0.5 + 0.5

 *  kTurbulence_Type   : abs(noise)

Очень мало различий между двумя типами, хотя вы можете заметить разницу визуально.

https://source.chromium.org/chromium/chromium/src/+/main: Third_Party/skia/src/shaders/SkPerlinNoiseShader.cpp?q=SkPerlinNoiseShader&ss=chromium

Ну, я не знаю, полезно ли это, но эти нити представляют собой области с нулевой или почти нулевой непрозрачностью, которые преобразуются в полностью непрозрачные, когда вы везде устанавливаете непрозрачность на 1. Вы можете увидеть это, добавив зеленый фон и умножив альфа на 128.

svg{
  background: green;
}
<svg height="600px" width="800px" viewBox="0 0 800 600">
  <filter color-interpolation-filters="sRGB"
     id="test-turbulence" x="0%" y="0%" width="100%" height="100%">
    <feTurbulence type="turbulence" numOctaves="1" baseFrequency=".02" />
    <feColorMatrix
       values="1 0 0 0 0
               1 0 0 0 0
               1 0 0 0 0
               0 0 0 128 0 " />
  </filter>
  <rect width="800" height="600" filter="url(#test-turbulence)" x="0" y="0" />
</svg>
Alan De Smet
9 августа 2021 в 19:36
0

Ваш ответ не доходит до того, что я пытаюсь понять. Я думаю, что мой первоначальный вопрос был не так ясен, как мог бы быть; Я надеюсь, что мой капитальный ремонт прояснит, что я ищу. Я перешел от своего грубого хака к коду, основанному на эталонной реализации стандарта SVG, который имеет такое же ожидаемое поведение.

Alan De Smet
9 августа 2021 в 21:53
0

«Эти нити имеют нулевую или почти нулевую непрозрачность» Очень многообещающая подсказка, спасибо! Это невероятно подозрительное совпадение. Я посмотрю, смогу ли я понять, почему альфа влияет на результат, несмотря на мои попытки отказаться от нее.

Alan De Smet
9 августа 2021 в 22:42
0

Я думаю, что он у меня есть! По стандарту операции фильтрации работают с предварительно умноженным альфа-каналом RGB (0–255). Это сделано для того, чтобы исправить некоторые проблемы смешивания. При этом теряется информация в каналах RGB, но это нормально, потому что это не очевидно с соответствующей альфой. Тем не менее, feColorMattrix и feComponentTransfer временно отменяют предварительное умножение, чтобы выполнить свою математику, но опять же, это нормально, поскольку это не очевидно с соответствующей альфой ... если только кто-то просто жестко не устанавливает альфу на 1,0 ... тогда потерянные данные становятся этими строками. Решение: если вам нужен 1 канал турбулентности, используйте A, а не R, G или B.

Alan De Smet
9 августа 2021 в 22:43
0

Я могу попытаться ответить на свой вопрос более правильно позже (особенно после того, как трижды проверю свое понимание), но если вы хотите что-то написать, я был бы рад принять такой ответ. Спасибо вам за помощь!

Alan De Smet
9 августа 2021 в 22:44
0

(Это не объясняет, почему у fractalNoise нет той же проблемы, но это может стать очевидным, если я повнимательнее рассмотрю алгоритм. Но сейчас не время.)

Michael Mullany
10 августа 2021 в 10:58
0

Привет, Алан - не беспокойтесь, если вы напишете свой собственный ответ - я даже проголосую за него :)