почему размер текста, используемого в качестве источника в фильтре SVG feComposite, зависит от размера элемента svg?

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

<svg width="100" height="100">

  <defs>
    <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
    <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
    
    <filter id="myfilter" width="120%">
      <feImage xlink:href="#circ" result="lay1"/>
      <feImage xlink:href="#A" result="lay2"/>
      <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
    </filter>
  </defs>

  <g filter="url(#myfilter)" >
    <use href="#circ"/>
    <use href="#A"/>
  </g>

</svg> 

Это дает мне этот результат, как и ожидалось:

Текст вырезает заливку круга с помощью feComposite

Но потом мне захотелось сделать все покрупнее. Итак, мне нужно было увеличить ширину и высоту элемента svg. Однако, когда я это делаю, текст становится меньше. Вот измененный SVG, только увеличивший атрибут высоты в элементе svg:

<svg width="100" height="150">

  <defs>
    <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
    <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
    
    <filter id="myfilter" width="120%">
      <feImage xlink:href="#circ" result="lay1"/>
      <feImage xlink:href="#A" result="lay2"/>
      <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
    </filter>
  </defs>

  <g filter="url(#myfilter)" >
    <use href="#circ"/>
    <use href="#A"/>
  </g>

</svg> 

Это привело к уменьшению масштаба текстового содержимого по вертикали.

Текстовое содержимое feComposite уменьшено по вертикали после увеличения высоты корневого элемента svg

Если я увеличу ширину элемента svg, текст будет меньше масштабироваться по горизонтали:

<svg width="150" height="150">

  <defs>
    <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
    <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
    
    <filter id="myfilter" width="120%">
      <feImage xlink:href="#circ" result="lay1"/>
      <feImage xlink:href="#A" result="lay2"/>
      <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
    </filter>
  </defs>

  <g filter="url(#myfilter)" >
    <use href="#circ"/>
    <use href="#A"/>
  </g>

</svg> 

введите здесь описание изображения

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

Это происходит только для текста, используемого в качестве источника фильтра. Если я использую тот же текстовый элемент без фильтра, на него не влияют изменения ширины/высоты корневого элемента svg. Например, в следующем примере я изменил предыдущий пример, добавив элемент <use>, чтобы добавить еще один экземпляр текста (обернутый в <g> с переводом ниже на странице):

<svg width="150" height="150">

  <defs>
    <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
    <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
    
    <filter id="myfilter" width="120%">
      <feImage xlink:href="#circ" result="lay1"/>
      <feImage xlink:href="#A" result="lay2"/>
      <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
    </filter>
  </defs>

  <rect x="0" y="40" width="100" height="20" fill="none"/>
  
  <g filter="url(#myfilter)" >
    <use href="#circ"/>
    <use href="#A"/>
  </g>
  
  <g transform="translate(0,70)">
    <use href="#A"/>
  </g>

</svg> 

введите здесь описание изображения

Что здесь происходит? Почему текст, являющийся источником feComposite, масштабируется в зависимости от ширины/высоты svg?


person Peter Constable    schedule 03.12.2020    source источник
comment
Кажется, это ошибка Chrome. Соответствующим поведением было бы не изменять размер, как это можно наблюдать при рендеринге с помощью librsvg или Inkscape. Обратите внимание, что Firefox вообще не отображает фильтр, я думаю, потому что ссылки на внутренние идентификаторы не поддерживаются.   -  person ccprog    schedule 03.12.2020


Ответы (1)


Как правило, фильтры SVG склонны к ошибкам, когда размеры и единицы измерения не указаны. В этом случае элемент g передает фильтру неверные размеры. Например, если вы добавите координату y к первому элементу использования (кружку) внутри элемента g и отрегулируете ее значение, текст будет уменьшаться и расширяться.

Все работает нормально, если вы явно добавляете размеры ко всему и указываете через saveAspectRatio, хотите ли вы, чтобы соотношение сторон ввода сохранялось или нет. И если вы хотите, чтобы он был сохранен, хотите ли вы, чтобы он соответствовал большему (встреча) или меньшему (срез) измерению ввода.

Ваш фильтр фактически отбрасывает содержимое, с которым он был вызван (его SourceGraphic), поэтому на самом деле не имеет значения, что у вас есть в элементе g - он использует содержимое только для определения размера области фильтра (как оказалось, непоследовательно). Таким образом, вы можете просто применить фильтр к прямоугольному элементу 100%/100%, размер которого вы должны указать явно.

Итак, если вы явно указываете размеры - этот фильтр работает просто отлично. Я не знаю, какого поведения вы пытаетесь добиться. Если вы хотите, чтобы содержимое оставалось фиксированным при увеличении ширины/высоты элемента SVG, вам нужен именно этот фильтр.

<svg x="0" y="0" width="100px" height="150px" >

  <defs>
    <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
    <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
    
    <filter id="myfilter" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox" x="0" y="0" width="100" height="150">
      <feImage x="0" y="0" height="1" width="1"  xlink:href="#circ" result="lay1"/>
      <feImage x="0" y="0" height="1" width="1"  xlink:href="#A" result="lay2"/>
      <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
    </filter>
  </defs>

  <rect filter="url(#myfilter)" x="0%" y="0%" width="100%" height="100%"/>

</svg> 

    <svg x="0" y="0" width="200px" height="250px" >

      <defs>
        <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
        <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
        
        <filter id="myfilter" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox" x="0" y="0" width="100" height="150">
          <feImage x="0" y="0" height="1" width="1"  xlink:href="#circ" result="lay1"/>
          <feImage x="0" y="0" height="1" width="1"  xlink:href="#A" result="lay2"/>
          <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
        </filter>
      </defs>

      <rect filter="url(#myfilter)" x="0%" y="0%" width="100%" height="100%"/>

    </svg> 

С другой стороны, если вы хотите, чтобы контент масштабировался, но сохранял соотношение сторон, вам понадобится такой фильтр.

<svg x="0" y="0" width="100px" height="100px" viewBox="0 0 100 100" >

  <defs>
    <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
    <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
    
    <filter id="myfilter"  primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
      <feImage x="0" y="0" height="1" width="1"  xlink:href="#circ" result="lay1" preserveAspectRatio="xMidYMid slice"/>
      <feImage x="0" y="0" height=".65 " width="1"  xlink:href="#A" result="lay2" preserveAspectRatio="xMidYMid slice"/>
      <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
    </filter>
  </defs>

  <rect filter="url(#myfilter)" x="0" y="0" width="100" height="150"/>

</svg> 

    <svg x="0" y="0" width="200px" height="250px" viewBox="0 0 100 100" >

      <defs>
        <circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
        <text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
        
        <filter id="myfilter"  primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
          <feImage x="0" y="0" height="1" width="1"  xlink:href="#circ" result="lay1" preserveAspectRatio="xMidYMid slice"/>
          <feImage x="0" y="0" height=".65 " width="1"  xlink:href="#A" result="lay2" preserveAspectRatio="xMidYMid slice"/>
          <feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
        </filter>
      </defs>

      <rect filter="url(#myfilter)" x="0" y="0" width="100" height="150"/>

    </svg> 

person Michael Mullany    schedule 04.12.2020
comment
Спасибо за исправления, которые работают. Я до сих пор не до конца понимаю, что происходит, и мне нужно больше узнать о единицах измерения/размерах в SVG. Вы сказали, плохие размеры; это только в смысле неожиданных входных данных, которые приводят к недетерминированным результатам (т. е. подлежат различной обработке соответствующими реализациями среды выполнения SVG)? Или плохо в том смысле, что наблюдаемое поведение было детерминированным и ожидаемым в соответствии со спецификацией SVG с учетом входных данных, но не тем, что я хотел? - person Peter Constable; 04.12.2020
comment
Плохие размеры = ошибка. В фильтрах SVG много ошибок. Браузерные команды исправляют их очень медленно. Есть некоторые ошибки фильтра, которые были обнаружены в течение 6+ лет. Текст SVG также имеет множество незавершенных функций и ошибок. Итак, фильтры очень мощны, но нужно везде все тестировать и остерегаться багов. И в вашем случае фильтр вообще не будет работать в firefox, потому что он не поддерживает ввод фрагментов в feImage - вам нужно встраивать вещи через data uri. - person Michael Mullany; 04.12.2020
comment
Есть ли альтернатива feImage для получения входных данных для feComposite? Я знаю, что могу использовать SourceImage для одного, но не придумал, как еще получить второй. - person Peter Constable; 06.12.2020
comment
SourceGraphic - не SourceImage :) Есть и другие методы - поместите обе формы рядом в SourceGraphic и используйте feOffset для их наложения и feFlood, размер которого соответствует результату, который вы хотите выбрать через feComposite/in. Другой метод, который работает, когда у вас есть одноцветные фигуры — вы создаете SourceGraphic, помещая разные фигуры в красный, зеленый и синий каналы, а затем используете feColorMatrix, чтобы разделить их на отдельные результаты перед дальнейшей обработкой. - person Michael Mullany; 07.12.2020
comment
Верно, SourceGraphic. (Писал по памяти.) Фильтр вообще не будет работать в firefox, потому что он не поддерживает ввод фрагментов в feImage - вам нужно встраивать вещи через data uri Как это делается? - person Peter Constable; 07.12.2020
comment
Хорошее место для начала: css-tricks.com/lodge/svg. /09-svg-data-uris. - person Michael Mullany; 08.12.2020
comment
Другой способ - для вашего конкретного случая. Просто нарисуйте восьмерку поверх круга, как хотите, но сделайте это красным, а затем примените к этой группе фильтр с feColorMatrix/matrix = 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -1 0 0 1 0 Это преобразует альфа-канал всего, что на 100% красное, в прозрачный. - person Michael Mullany; 08.12.2020