PerfectVector
Autor: Irene Kim

SVG feComposite: porównaj grafikę in, out i xor

Porównaj wejścia filtrów SVG przez in, out i xor, zbadaj kolejność i częściowe alfa oraz zachowaj ścieżki, gdy widoczny otwór nie jest edytowalną geometrią.

10 min czytania
Na tej stronie

SVG feComposite łączy dwa renderowane wejścia. Dla w pełni nieprzezroczystych wejść in zachowuje pierwsze w części wspólnej, out zachowuje je poza częścią wspólną, a xor łączy oba poza wspólnym obszarem nakładania. Operacje zmieniają malowanie podczas renderowania. Nie przepisują ścieżek źródłowych na edytowalną część wspólną lub różnicę.

Różnica ma znaczenie, gdy nakładanie wygląda jak otwór, lecz wynik wymaga rzeczywistych konturów. Poniższy przykład porównuje dwa oryginalne asymetryczne kształty, odwraca wejścia i sprawdza częściową przezroczystość. Zachowaj grafikę źródłową obok filtrowanego wyniku.

Nazwij oba wejścia przed wyborem operatora

Opis feComposite MDN przedstawia operację piksel po pikselu w przestrzeni obrazu. in nazywa pierwsze wejście, a in2 drugie. Nadaj wynikom pośrednim jawne nazwy, aby zmiana łańcucha filtrów nie zmieniała po cichu operandów.

Dla nieprzezroczystych wnętrz trzy operacje łatwo porównać:

OperacjaPierwsze wejście A, drugie BCo zmienia odwrócenie wejść
inMalowanie A w części wspólnejCzęść wspólna otrzymuje zamiast tego malowanie B
outMalowanie A poza BMalowanie B poza A to inny obszar
xorOba wejścia poza ich nieprzezroczystą częścią wspólnąPozostają te same nienakładające się obszary

Ten sam atrybut operator pojawia się w innych prymitywach filtrów z innymi wartościami. Zachowaj jawny kontekst kompozycji; opis operator MDN odróżnia go od operacji morfologicznych.

Zmień widoczne malowanie
Koncepcyjne porównanie nakładającej się kolorowej grafiki i kompozycji z niepomalowaną częścią wspólną
Ilustracja: nakładające się wejścia mogą pozostawić obszar niepomalowany. Dekoracyjne sylwetki i strzałki są koncepcyjne; dokładne operatory, próbki alfa i niezmienione ścieżki poniżej pochodzą z wykonanego oryginalnego testu przeglądarkowego.

Jawnie wyrównaj drugą grafikę

Nasz pierwszy operand A to morskozielony schodkowy blok. B to ochrowy przekrzywiony latawiec. Oba używają tego samego viewBox 0 0 240 160. B wychodzi poza lokalną obwiednię A, więc przyjęcie granic A jako obszaru filtra byłoby złym założeniem początkowym.

Filtr używa filterUnits="userSpaceOnUse" dla jawnego obszaru x=0, y=0, width=240, height=160. Opis filterUnits MDN wyjaśnia wybór współrzędnych. Ustawiamy też primitiveUnits="userSpaceOnUse" i nadajemy drugiemu wejściu te same współrzędne pełnego kadru.

B jest samowystarczalnym adresem danych SVG przekazanym przez feImage, z nazwą wyniku operandB. Wprowadza go do filtra jako wejście obrazu; nie wstawia ścieżki B jako nowego bezpośredniego potomka filtrowanej grafiki. Opis feImage MDN przedstawia to zachowanie wejścia.

Filtr jawnie wybiera sRGB. Domyślna wartość tych prymitywów to linearRGB, zgodnie z opisem feComposite. Ani wartość domyślna, ani nazwa operatora nie dostarczają operacji edycji ścieżek.

Uruchom kompletne oryginalne porównanie

Zapisz kod jako starter.html i udostępnij folder przez lokalne HTTP, na przykład python3 -m http.server 8766 --bind 127.0.0.1. Otwórz http://127.0.0.1:8766/starter.html; w razie potrzeby użyj innego wolnego portu.

Strona buduje dwa oryginały i siedem filtrowanych przypadków w każdym z dwóch jawnych rozmiarów. Każdy identyfikator filtra zawiera przypadek i szerokość, więc wiele SVG inline w jednym dokumencie ma unikalne odwołania. Każdy link Save SVG zachowuje samowystarczalny filtr i zakodowane wejście B.

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Original SVG composite artwork</title>
<style>
  body { margin: 24px; font: 16px system-ui; background: #f6f2e8; }
  main { display: grid; grid-template-columns: repeat(3, 260px); gap: 18px; }
  figure { margin: 0; }
  svg { display: block; background: white; border: 1px solid #bbb; }
  figcaption { margin: 6px 0; }
  pre { white-space: pre-wrap; font-size: 12px; }
</style>
<h1>Original operands and rendered composites</h1>
<main id="cases"></main>
<pre id="report"></pre>
<script>
  const A = 'M24 24 H132 V56 H192 V136 H24 Z';
  const B = 'M92 8 L216 84 L92 152 L60 84 Z';
  const svgNS = 'http://www.w3.org/2000/svg';
  const cases = [
    { id: 'a' }, { id: 'b' },
    { id: 'a-in-b', operator: 'in' },
    { id: 'a-out-b', operator: 'out' },
    { id: 'a-xor-b', operator: 'xor' },
    { id: 'b-in-a', operator: 'in', reverse: true },
    { id: 'b-out-a', operator: 'out', reverse: true },
    { id: 'b-xor-a', operator: 'xor', reverse: true },
    { id: 'a-out-half-b', operator: 'out', opacity: 0.5 },
  ];
  function documentSVG(test, width) {
    const height = width * 2 / 3;
    const id = test.id + '-' + width;
    const second = '<svg xmlns="' + svgNS + '" width="240" height="160" viewBox="0 0 240 160">'
      + '<path d="' + B + '" fill="#d4a53a" opacity="' + (test.opacity ?? 1) + '"/></svg>';
    const href = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(second);
    const first = test.reverse ? 'operandB' : 'SourceGraphic';
    const other = test.reverse ? 'SourceGraphic' : 'operandB';
    const defs = test.operator ? '<defs><filter id="' + id + '" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse" x="0" y="0" width="240" height="160" color-interpolation-filters="sRGB">'
      + '<feImage href="' + href + '" x="0" y="0" width="240" height="160" preserveAspectRatio="none" result="operandB"/>'
      + '<feComposite in="' + first + '" in2="' + other + '" operator="' + test.operator + '"/></filter></defs>' : '';
    return '<svg xmlns="' + svgNS + '" width="' + width + '" height="' + height + '" viewBox="0 0 240 160">'
      + defs + '<path d="' + (test.id === 'b' ? B : A) + '" fill="' + (test.id === 'b' ? '#d4a53a' : '#176b5b')
      + '"' + (test.operator ? ' filter="url(#' + id + ')"' : '') + '/></svg>';
  }
  async function run() {
    const results = [];
    for (const width of [120, 240]) {
      for (const test of cases) {
        const source = documentSVG(test, width);
        const svg = new DOMParser().parseFromString(source, 'image/svg+xml').documentElement;
        const figure = document.createElement('figure');
        const caption = document.createElement('figcaption');
        caption.textContent = test.id + ', ' + width + ' × ' + width * 2 / 3;
        figure.append(document.importNode(svg, true), caption);
        document.getElementById('cases').append(figure);
        const path = figure.querySelector('path');
        const bbox = path.getBBox();
        const image = new Image();
        image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
        await image.decode();
        const canvas = document.createElement('canvas');
        canvas.width = width;
        canvas.height = width * 2 / 3;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0);
        const sample = (x, y) => [...ctx.getImageData(Math.floor(x * width / 240), Math.floor(y * width / 240), 1, 1).data];
        const hash = [...new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(path.getAttribute('d'))))]
          .map(v => v.toString(16).padStart(2, '0')).join('');
        const download = document.createElement('a');
        download.href = image.src;
        download.download = test.id + '-' + width + '.svg';
        download.textContent = 'Save SVG';
        figure.append(download);
        results.push({ id: test.id, width, height: canvas.height, d: path.getAttribute('d'), dSHA256: hash,
          bbox: { x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height },
          directPaths: svg.querySelectorAll('path').length, filterImages: svg.querySelectorAll('feImage').length,
          aOnly: sample(40, 40), bOnly: sample(200, 84), overlap: sample(120, 84), outside: sample(230, 150) });
      }
    }
    document.getElementById('report').textContent = JSON.stringify({ A, B, results }, null, 2);
  }
  run();
</script>
</html>

Kod zachowuje tę samą bezpośrednią ścieżkę A nawet po odwróceniu wejść operatora. Na przykład b-in-a zmienia feComposite na in="operandB" i in2="SourceGraphic"; nie zastępuje filtrowanego elementu źródłowego przez B. Uwidacznia kolejność operandów bez zmiany bazowej geometrii A.

Kontrola pikseli odczytuje canvas pomalowany z każdego wygenerowanego SVG. Wynikiem do pobrania nadal jest filtrowany SVG. Nie jest to nowa ścieżka logiczna ani konwersja PerfectVector.

Porównaj malowanie i geometrię oddzielnie

Uruchomiliśmy dokładnie tę stronę w jednym środowisku Chrome przy 120 × 80 i 240 × 160, zapisaliśmy wszystkie 18 SVG i otworzyliśmy je ponownie jako obrazy w tej samej przeglądarce. Rozmiary wyświetlania są jawnymi wejściami, a nie twierdzeniem o dwóch fizycznych urządzeniach.

Trzy pozycje próbek wewnętrznych używają oryginalnego układu współrzędnych: tylko A (40,40), tylko B (200,84) i część wspólna (120,84). Przy nieprzezroczystych operandach zaobserwowano te wartości alfa w obu rozmiarach:

Wejścia filtra i operatorAlfa tylko AAlfa tylko BAlfa części wspólnej
A in B00255
A out B25500
A xor B2552550
B in A00255
B out A02550
B xor A2552550

Wiersze in dzielą nieprzezroczystą część wspólną, lecz różnią się kolorem: A jako pierwsze dało morskozielony (23,107,91,255), a B jako pierwsze ochrowy (212,165,58,255). Próbka zewnętrzna (230,150) pozostawała przezroczysta. To wybrane piksele wnętrza, a nie pełny test pokrycia krawędzi lub zgodności.

Każdy filtrowany plik nadal zawierał dokładny oryginalny ciąg d A i zgodny SHA-256. Lokalny geometryczny getBBox() pozostał x=24, y=24, width=168, height=112. Prostokąt opisuje ścieżkę źródłową, a nie obwiednię widocznych pikseli filtra. Zakodowana ścieżka B także nie zmieniła się. Wynik filtra może więc wyglądać inaczej, podczas gdy oba utworzone kontury pozostają nienaruszone.

Zapisane oryginały mają jedną bezpośrednią ścieżkę i brak obrazu filtra. Każdy filtrowany plik ma jedną bezpośrednią ścieżkę A oraz jeden feImage przenoszący oddzielny jednościeżkowy SVG B. Nie wprowadzono zwykłego rastrowego elementu image. Renderowana operacja przestrzeni obrazu i wektorowe operandy źródłowe mogą współistnieć; sam inwentarz źródła nie oznacza, że wynik stał się edytowalną połączoną geometrią.

Częściowa nieprzezroczystość zmienia znaczenie otworu

Ostatni przypadek nadaje B połowę nieprzezroczystości, pozostawia A nieprzezroczyste, a potem stosuje A out B. Część wspólna nie stała się pusta. W obu rozmiarach obserwowane RGBA wynosiło (22,106,90,127): częściowo przezroczysty morskozielony, z zaokrągleniami procesu renderowania i odczytu.

Dla out alfa wyjściowe to pierwsze alfa pomnożone przez jeden minus drugie alfa. Dla in to ich iloczyn. Definicja kompozycji Filter Effects opisuje te operacje przez kolory premultiplikowane i alfa. Nieprzezroczyste B może usunąć nakładające się malowanie A; częściowo przezroczyste B pozostawia jego część.

Sprawdź wynik na rzeczywistym tle. Jasny obszar na bieli może być półprzezroczystym malowaniem, a nie całkowicie przezroczystym otworem. Wygładzone piksele krawędzi też wymagają oględzin; próbka wnętrza nie obiecuje alfa zero dla każdego piksela granicy. Oddzielne przemapowanie nieprzezroczystości opisuje poradnik progów alfa SVG.

Przekaż efekt filtra lub utwórz prawdziwe kontury

Zachowaj filtr, gdy renderer odbiorcy ma odtworzyć widoczną kompozycję. Zachowaj oryginalne pliki A i B i sprawdź ponownie otwarty filtrowany SVG w rzeczywistym miejscu docelowym. Nasze udane otwarcie w przeglądarce nie certyfikuje innych rendererów lub maszyn produkcyjnych.

Jeśli przekazanie wymaga zaznaczalnych połączonych konturów lub otworu gotowego do cięcia, wykonaj i sprawdź prawdziwą edycję ścieżek albo operację logiczną w zamierzonym procesie. Zmienione widoczne malowanie nie tworzy tej geometrii. Poradnik clipPath a maska omawia pokrewną różnicę widoczności; poradnik rozgrupowania SVG pomaga, gdy wyświetlana grafika nie jest oczekiwanym edytowalnym obiektem.

Jeśli clipart istnieje tylko jako piksele i potrzebujesz edytowalnych kształtów źródła, przygotuj SVG clipartu w PerfectVector, sprawdź kontury i otwory oraz zachowaj wzorzec. Zastosuj i zweryfikuj zamierzony filtr lub operację ścieżki oddzielnie. Wektoryzacja nie zmienia wizualnego nakładania tego filtra w zweryfikowany kontur produkcyjny.

Najczęstsze pytania

Czy feComposite out usuwa nakładającą się geometrię ścieżki? Nie. Zmienia renderowane malowanie przez alfa drugiego wejścia. W naszych filtrowanych plikach oryginalne dane ścieżki A i lokalne granice geometrii pozostały bez zmian nawet tam, gdzie nakładające malowanie stało się przezroczyste.

Dlaczego zamiana in i in2 zmienia wynik? Oznaczają pierwsze i drugie wejście. In zachowuje malowanie pierwszego w nieprzezroczystości drugiego; out zachowuje pierwsze poza nim. Odwrócone przypadki in dzielą nieprzezroczystą część wspólną, lecz pokazują inne kolory, a odwrócone out zachowują inne obszary.

Czy xor zawsze tworzy całkowicie pustą część wspólną? Całkowicie nieprzezroczyste wnętrze części wspólnej było puste w naszych przypadkach xor. Częściowe alfa i wygładzone krawędzie wymagają własnej kontroli; diagramy zbiorów nieprzezroczystych kształtów nie opisują każdego półprzezroczystego piksela.

Czy mogę wysłać filtrowany otwór bezpośrednio jako edytowalną ścieżkę cięcia? Wizualny wynik filtra nie jest nowo utworzoną geometrią logiczną. Zachowaj oryginalne kształty, utwórz i sprawdź wymagane kontury w docelowym procesie ścieżek, a osobno zweryfikuj aplikację odbiorcy i wymagania produkcyjne.

Źródła

  1. MDN: feComposite — Role wejść w przestrzeni obrazu, opisy operatorów i domyślna przestrzeń kolorów filtra.
  2. MDN: operator — Wartości operatorów kompozycji i osobny kontekst morfologii.
  3. MDN: filterUnits — Układ współrzędnych obszaru filtra.
  4. MDN: feImage — Wejście obrazu do potoku filtrów.
  5. W3C: Kompozycja Filter Effects — Definicje kompozycji premultiplikowanego koloru i alfa.

Zaczynasz od własnego rastrowego clipartu? Utwórz edytowalny wzorzec SVG, sprawdź kontury i otwory, a potem zachowaj go podczas kontroli wybranej wizualnej kompozycji lub osobno utworzonego wyniku ścieżek w miejscu docelowym.

Więcej z bloga

PerfectVector

Zacznij od czystszego SVG,
który łatwiej edytować

Bez karty kredytowej