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

#svg #svg-filters

#svg #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> элемент для добавления другого экземпляра текста (заключенного в a <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?

Комментарии:

1. Похоже, это ошибка Chrome. Соответствующим поведением было бы не изменять размер, как это можно наблюдать при рендеринге с помощью librsvg или Inkscape. Обратите внимание, что Firefox вообще не отображает фильтр, я думаю, потому что ссылки на внутренние идентификаторы не поддерживаются.

Ответ №1:

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

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

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

Итак, если вы явно указываете размеры — этот фильтр работает просто отлично. Я не знаю, какого поведения вы пытаетесь добиться. Если вы хотите, чтобы содержимое оставалось фиксированным при расширении ширины / высоты элемента 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>  

Комментарии:

1. Спасибо за исправленные изменения, которые работают. Я все еще не до конца понимаю, что происходит, и мне нужно узнать больше о единицах измерения / размерах в SVG. Вы сказали «плохие размеры»; это просто в смысле неожиданных входных данных, которые приводят к недетерминированным результатам (т. Е. При условии различной обработки соответствующими реализациями среды выполнения SVG)? Или плохо в том смысле, что наблюдаемое поведение было детерминированным и ожидаемым в соответствии со спецификацией SVG, учитывая входные данные, но не то, что я хотел?

2. Плохие размеры = ошибка. В SVG-фильтрах много ошибок. Команды браузеров исправляют их очень медленно. Есть несколько ошибок в фильтрах, которые были замечены более 6 лет назад. SVG text также имеет много незавершенных функций и ошибок. Итак, фильтры очень мощные, но вы должны тестировать все везде и быть осторожными с ошибками. И в вашем случае фильтр вообще не будет работать в firefox, потому что он не поддерживает ввод фрагментов в feImage — вы должны встроить данные через uri данных.

3. Есть ли альтернатива feImage для получения входных данных для feComposite? Я знаю, что могу использовать sourceImage для одного, но не понял, как еще получить второй.

4. «SourceGraphic» — не «sourceImage» 🙂 Есть и другие методы — поместите обе фигуры рядом в вашем SourceGraphic и используйте feOffset для наложения их и feFlood, размер которого соответствует результату, который вы хотите выбрать конечный результат через feComposite / in. Другой метод, который работает, когда у вас есть одноцветные фигуры — вы создаете SourceGraphic, помещая разные фигуры в красный, зеленый и синий каналы, затем используете feColorMatrix, чтобы разделить их на отдельные «результаты» перед дальнейшей обработкой.

5. Правильно, «SourceGraphic». (Записывал из памяти.) » фильтр вообще не будет работать в firefox, потому что он не поддерживает ввод фрагментов в feImage — вам нужно вводить данные через uri данных» Как это делается?