PerfectVector
Por Irene Kim

SVG feComposite: compare arte com in, out e xor

Compare entradas de filtro SVG com in, out e xor, teste ordem dos operandos e alfa parcial e preserve os caminhos quando uma abertura visual não é geometria editável.

11 min de leitura
Nesta página

O SVG feComposite combina duas entradas renderizadas. Para entradas totalmente opacas, in mantém a primeira na sobreposição, out a mantém fora da sobreposição e xor combina ambas fora da sobreposição compartilhada. Essas operações mudam a pintura na renderização. Não reescrevem os caminhos de origem em uma interseção ou diferença editável.

Essa distinção importa quando uma sobreposição parece uma abertura, mas a entrega precisa de contornos reais. O exemplo abaixo compara duas formas assimétricas originais, inverte as entradas e verifica transparência parcial. Mantenha a arte de origem junto do resultado filtrado.

Nomeie as duas entradas antes de escolher o operador

A referência de feComposite da MDN descreve uma operação por pixel no espaço da imagem. in nomeia a primeira entrada; in2, a segunda. Dê nomes explícitos aos resultados intermediários para que uma mudança na cadeia de filtros não altere os operandos silenciosamente.

Para interiores opacos, estas três operações são fáceis de comparar:

OperaçãoPrimeira entrada A, segunda BO que muda ao inverter
inPintura de A na sobreposiçãoA sobreposição passa a ter a pintura de B
outPintura de A fora de BA pintura de B fora de A é outra região
xorAs duas entradas fora da sobreposição opaca compartilhadaPermanecem as mesmas regiões sem sobreposição

O mesmo atributo operator também aparece em outras primitivas de filtro com valores diferentes. Explicite o contexto de composição; a referência de operator da MDN o distingue das operações de morfologia.

Mude a pintura visível
Comparação conceitual de arte colorida sobreposta e uma composição com sobreposição sem pintura
Ilustração: entradas sobrepostas podem deixar uma região sem pintura. Estas silhuetas e setas decorativas são conceituais; operadores exatos, amostras alfa e caminhos inalterados abaixo vêm de um teste original executado no navegador.

Alinhe a segunda arte explicitamente

Nosso primeiro operando A é um bloco em degraus verde-petróleo. B é uma pipa inclinada ocre. Ambos usam o mesmo viewBox 0 0 240 160. B ultrapassa a caixa delimitadora local de A, portanto usar os limites de A como região do filtro seria uma suposição inicial ruim.

O filtro usa filterUnits="userSpaceOnUse" para a região explícita x=0, y=0, width=240, height=160. A referência de filterUnits da MDN explica essa escolha de coordenadas. Também definimos primitiveUnits="userSpaceOnUse" e damos à segunda entrada as mesmas coordenadas do quadro inteiro.

B é uma URL de dados SVG independente passada por feImage, com o nome de resultado operandB. Isso o traz ao filtro como entrada de imagem; não insere o caminho de B como novo filho direto da arte filtrada. A referência de feImage da MDN descreve esse comportamento de entrada de imagem.

O filtro escolhe explicitamente sRGB. O padrão dessas primitivas é linearRGB, como documenta a referência de feComposite. Nem esse padrão nem o nome do operador fornecem uma operação de edição de caminhos.

Execute a comparação original completa

Salve como starter.html e sirva a pasta por HTTP local, por exemplo com python3 -m http.server 8766 --bind 127.0.0.1. Abra http://127.0.0.1:8766/starter.html; use outra porta livre se necessário.

A página cria dois originais mais sete casos filtrados em cada um de dois tamanhos explícitos. Cada ID de filtro inclui o caso e a largura, para que vários SVGs inline no mesmo documento tenham referências únicas. Cada link Save SVG mantém o filtro independente e sua entrada B codificada.

<!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>

O código mantém o mesmo caminho A direto mesmo quando as entradas do operador são invertidas. Por exemplo, b-in-a muda feComposite para in="operandB" e in2="SourceGraphic"; não substitui o elemento de origem filtrado por B. Isso torna visível a ordem dos operandos sem alterar a geometria A subjacente.

A inspeção de pixels aqui lê um canvas pintado a partir de cada SVG gerado. A entrega disponível para download continua sendo o SVG filtrado. Não é um novo caminho booleano nem uma conversão PerfectVector.

Compare pintura e geometria separadamente

Executamos exatamente essa página em um ambiente Chrome a 120 × 80 e 240 × 160, salvamos os 18 SVGs e os reabrimos como imagens no mesmo navegador. Os tamanhos exibidos são entradas explícitas, não uma afirmação sobre dois dispositivos físicos.

Três posições de amostras internas usam o sistema de coordenadas original: só A (40,40), só B (200,84) e sobreposição (120,84). Observamos os seguintes valores alfa nos dois tamanhos com operandos opacos:

Entradas de filtro e operadorAlfa só AAlfa só BAlfa da sobreposição
A in B00255
A out B25500
A xor B2552550
B in A00255
B out A02550
B xor A2552550

As linhas in compartilham uma sobreposição opaca, mas diferem na cor: A primeiro ficou verde-petróleo (23,107,91,255); B primeiro ficou ocre (212,165,58,255). A amostra externa (230,150) permaneceu transparente em todos os casos. São pixels internos selecionados, não um teste completo de cobertura de borda ou compatibilidade.

Cada arquivo filtrado ainda tinha a string d original exata de A e SHA-256 correspondente. Seu getBBox() geométrico local continuou x=24, y=24, width=168, height=112. Essa caixa descreve o caminho de origem; não é a caixa delimitadora dos pixels visíveis do filtro. O caminho B codificado também permaneceu inalterado. Portanto, o resultado de um filtro pode parecer diferente enquanto os dois contornos criados continuam intactos.

Os originais salvos têm um caminho direto e nenhuma imagem de filtro. Cada arquivo filtrado tem um caminho A direto mais um feImage que carrega o SVG separado de B, de um caminho. Nenhum elemento raster image comum é introduzido. Uma operação renderizada no espaço da imagem e operandos vetoriais de origem podem coexistir; só o inventário da origem não significa que o resultado se tornou geometria combinada editável.

Opacidade parcial muda o significado de uma abertura

O último caso torna B meio opaco, mantém A opaco e aplica A out B. A sobreposição não ficou vazia. Nos dois tamanhos, o RGBA observado foi (22,106,90,127): verde-petróleo parcialmente transparente, com arredondamento do processo de renderização e leitura.

Para out, o alfa de saída é o primeiro alfa multiplicado por um menos o segundo alfa. Para in, é o produto deles. A definição de composição Filter Effects define essas operações com cores pré-multiplicadas e alfa. B opaco pode remover a pintura sobreposta de A; B parcialmente transparente deixa parte dela.

Examine o resultado sobre o fundo real. Uma região clara sobre branco pode ser pintura translúcida em vez de um furo totalmente transparente. Pixels de borda com antisserrilhamento também precisam de inspeção visual; a amostra interna não promete que todo pixel de limite tenha alfa zero. Para uma tarefa separada de remapeamento de opacidade, veja o guia de limiar alfa SVG.

Entregue um efeito de filtro ou crie contornos reais

Mantenha esse filtro quando o renderizador receptor deve reproduzir a composição visual. Preserve os arquivos A e B originais e examine o SVG filtrado reaberto no destino real. Nossa reabertura bem-sucedida no navegador não certifica outros renderizadores ou máquinas de produção.

Se a entrega exige contornos combinados selecionáveis ou uma abertura pronta para corte, execute e examine uma edição real de caminhos ou operação booleana no fluxo pretendido. A pintura visível alterada aqui não cria essa geometria. O guia de clipPath versus máscara faz uma distinção relacionada de visibilidade; o guia de desagrupamento SVG ajuda quando a arte exibida não é o objeto editável esperado.

Se seu clipart existe só como pixels e você precisa de formas de origem editáveis, prepare um SVG de clipart com o PerfectVector, examine contornos e aberturas e mantenha o original mestre. Aplique e verifique o filtro ou a operação de caminhos pretendidos separadamente. A vetorização não transforma a sobreposição visual deste filtro em um contorno de fabricação verificado.

Perguntas frequentes

feComposite out apaga a geometria do caminho sobreposto? Não. Ele muda a pintura renderizada usando o alfa da segunda entrada. Nos nossos arquivos filtrados, os dados originais do caminho A e os limites geométricos locais permaneceram inalterados mesmo onde a pintura sobreposta ficou transparente.

Por que trocar in e in2 muda o resultado? Eles identificam a primeira e a segunda entradas. In mantém a pintura da primeira dentro da opacidade da segunda; out mantém a primeira fora dela. Nossos casos in invertidos compartilham a sobreposição opaca, mas mostram cores diferentes; os out invertidos mantêm regiões diferentes.

xor sempre cria uma sobreposição completamente vazia? A sobreposição interna totalmente opaca ficou vazia nos nossos casos xor. Alfa parcial e bordas com antisserrilhamento exigem inspeção própria; diagramas de conjuntos de formas opacas não descrevem todo pixel translúcido.

Posso enviar a abertura filtrada diretamente como caminho de corte editável? Um resultado visual de filtro não é uma nova geometria booleana criada. Mantenha as formas originais, crie e examine os contornos exigidos no fluxo de caminhos pretendido e verifique separadamente a aplicação receptora e os requisitos de produção.

Fontes

  1. MDN: feComposite — Papéis das entradas no espaço da imagem, operadores e espaço de cor padrão do filtro.
  2. MDN: operator — Valores de operadores de composição e o contexto separado de morfologia.
  3. MDN: filterUnits — Sistema de coordenadas da região do filtro.
  4. MDN: feImage — Entrada de imagem no processo de filtros.
  5. W3C: Composição Filter Effects — Definições de composição de cor pré-multiplicada e alfa.

Começando com seu próprio clipart raster? Crie um original SVG editável, examine contornos e aberturas e preserve-o enquanto confere no destino a composição visual escolhida ou o resultado de caminhos criado separadamente.

Mais do blog

PerfectVector

Recomece com um SVG mais limpo
e fácil de editar

Sem cartão de crédito