SVG feComposite: in, out, xor 결과 비교하기
SVG 필터의 in, out, xor 입력을 비교하고 피연산자 순서와 부분 알파를 시험합니다. 시각적인 구멍이 편집 가능한 형상이 아닐 때 원본 경로를 보관하세요.
목차
SVG의 feComposite는 렌더링된 두 입력을 합성합니다. 완전히 불투명한 입력에서 in은 겹친 부분 안에 첫 번째 입력을 남기고, out은 겹친 부분 밖에 남깁니다. xor는 두 입력이 공유하는 겹친 부분을 제외한 영역을 합칩니다. 이 연산은 렌더링 시점의 칠을 바꿉니다. 원본 경로를 편집 가능한 교집합이나 차집합으로 다시 쓰지는 않습니다.
겹친 부분이 구멍처럼 보이지만 전달 파일에는 실제 윤곽이 필요할 때 이 구분이 중요합니다. 아래 예제는 자체 제작한 비대칭 도형 두 개를 비교하고, 입력을 뒤집고, 부분 투명도를 확인합니다. 필터 결과와 함께 원본 아트워크를 보관하세요.
연산자를 고르기 전에 두 입력에 이름 붙이기
MDN feComposite 참조 문서는 이미지 공간에서 픽셀별로 수행하는 연산으로 설명합니다. in은 첫 번째 입력, in2는 두 번째 입력의 이름을 지정합니다. 필터 체인이 바뀌었을 때 피연산자가 눈치채지 못한 사이 달라지지 않도록 중간 결과에 명시적인 이름을 붙이세요.
내부가 불투명하면 다음 세 연산을 쉽게 비교할 수 있습니다.
| 연산 | 첫 번째 입력 A, 두 번째 입력 B | 입력을 뒤집으면 달라지는 점 |
|---|---|---|
in | 겹친 부분의 A 칠 | 겹친 부분에 대신 B의 칠이 남음 |
out | B 바깥의 A 칠 | A 바깥의 B 칠은 다른 영역임 |
xor | 공유하는 불투명 겹침 영역을 제외한 두 입력 | 동일한 비겹침 영역이 남음 |
같은 operator 속성은 다른 필터 기본 요소에도 다른 값으로 사용됩니다. 합성이라는 맥락을 명확히 하세요. MDN operator 참조 문서는 형태학 연산과의 차이를 설명합니다.

두 번째 아트워크 명시적으로 정렬하기
첫 번째 피연산자 A는 청록색 계단형 블록입니다. B는 황토색의 비뚤어진 연 모양입니다. 둘 다 같은 0 0 240 160 viewBox를 사용합니다. B는 A의 로컬 바운딩 박스를 벗어나므로 A의 범위를 필터 영역으로 가정하는 것은 좋은 출발점이 아닙니다.
필터는 filterUnits="userSpaceOnUse"를 사용하고 영역을 x=0, y=0, width=240, height=160으로 명시합니다. MDN filterUnits 참조 문서는 이 좌표 선택을 설명합니다. 또한 primitiveUnits="userSpaceOnUse"를 설정하고 두 번째 입력에도 같은 전체 프레임 좌표를 부여합니다.
B는 feImage를 통해 전달하는 독립적인 SVG 데이터 URL이며 결과 이름은 operandB입니다. 이는 B를 이미지 입력으로 필터에 넣는 방식입니다. B의 경로를 필터가 적용된 아트워크의 새로운 직접 자식 요소로 삽입하지는 않습니다. MDN feImage 참조 문서는 이미지 입력 동작을 설명합니다.
필터는 명시적으로 sRGB를 선택합니다. feComposite 참조 문서에 설명된 것처럼 이 필터 기본 요소의 기본값은 linearRGB입니다. 그 기본값이나 연산자 이름이 경로 편집 기능을 제공하지는 않습니다.
자체 제작한 전체 비교 실행하기
이 내용을 starter.html로 저장하고 폴더를 로컬 HTTP로 제공하세요. 예를 들어 python3 -m http.server 8766 --bind 127.0.0.1을 사용할 수 있습니다. http://127.0.0.1:8766/starter.html을 엽니다. 필요하면 다른 빈 포트를 사용하세요.
페이지는 명시한 두 크기마다 원본 두 개와 필터 사례 일곱 개를 만듭니다. 필터 ID마다 사례와 너비를 넣어 같은 문서 안의 여러 인라인 SVG가 고유한 참조를 갖게 합니다. 각 SVG 저장 링크는 독립적인 필터와 인코딩된 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>연산자의 입력을 뒤집어도 코드는 같은 직접 A 경로를 유지합니다. 예를 들어 b-in-a는 feComposite를 in="operandB"와 in2="SourceGraphic"로 바꿉니다. 필터가 적용되는 원본 요소를 B로 대체하지 않습니다. 따라서 A의 기본 형상을 바꾸지 않고 피연산자 순서를 눈으로 확인할 수 있습니다.
여기서 픽셀을 검사할 때는 생성한 각 SVG로 그린 캔버스를 읽습니다. 다운로드하는 전달 파일은 여전히 필터가 적용된 SVG입니다. 새 불리언 경로나 PerfectVector 변환 결과가 아닙니다.
칠과 형상을 별도로 비교하기
이 페이지를 그대로 한 Chrome 환경에서 120 × 80과 240 × 160으로 실행하고, SVG 18개를 모두 저장한 뒤 같은 브라우저에서 이미지로 다시 열었습니다. 표시 크기는 명시적인 입력이며 서로 다른 물리 기기 두 대에 대한 주장이 아닙니다.
내부 표본 위치 세 곳은 원래 좌표계를 사용합니다. A만 있는 곳은 (40,40), B만 있는 곳은 (200,84), 겹친 곳은 (120,84)입니다. 불투명한 피연산자에서는 두 크기 모두 다음 알파 값이 관찰되었습니다.
| 필터 입력과 연산자 | A만 있는 곳의 알파 | B만 있는 곳의 알파 | 겹친 곳의 알파 |
|---|---|---|---|
A in B | 0 | 0 | 255 |
A out B | 255 | 0 | 0 |
A xor B | 255 | 255 | 0 |
B in A | 0 | 0 | 255 |
B out A | 0 | 255 | 0 |
B xor A | 255 | 255 | 0 |
in 행은 같은 불투명 겹침 영역을 보이지만 색상은 다릅니다. A가 먼저면 청록색 (23,107,91,255), B가 먼저면 황토색 (212,165,58,255)이었습니다. 바깥쪽 표본 (230,150)은 계속 투명했습니다. 선택한 내부 픽셀의 결과이며 전체 가장자리 커버리지나 호환성 테스트가 아닙니다.
필터가 적용된 모든 파일은 A의 정확한 원본 d 문자열과 같은 SHA-256을 유지했습니다. 로컬 기하학적 getBBox()도 x=24, y=24, width=168, height=112 그대로였습니다. 이 박스는 원본 경로를 나타내며 보이는 필터 픽셀의 바운딩 박스가 아닙니다. 인코딩된 B 경로도 바뀌지 않았습니다. 따라서 직접 만든 두 윤곽을 그대로 유지하면서도 필터 결과는 다르게 보일 수 있습니다.
저장한 원본에는 직접 경로 하나가 있고 필터 이미지는 없습니다. 각 필터 파일에는 직접 A 경로 하나와 B의 별도 한 경로 SVG를 담은 feImage 하나가 있습니다. 일반적인 래스터 image 요소는 추가되지 않습니다. 렌더링 시의 이미지 공간 연산과 벡터 원본 피연산자는 함께 존재할 수 있습니다. 원본 요소 목록만으로 결과가 편집 가능한 결합 형상이 되었다고 볼 수는 없습니다.
부분 불투명도는 구멍의 의미를 바꿉니다
마지막 사례에서는 A를 불투명하게 유지하면서 B를 절반만 불투명하게 만든 다음 A out B를 적용합니다. 겹친 부분은 비지 않았습니다. 두 크기에서 관찰된 RGBA는 (22,106,90,127)이었습니다. 렌더링과 읽기 과정의 반올림이 포함된, 부분적으로 투명한 청록색입니다.
out의 출력 알파는 첫 번째 알파에 1에서 두 번째 알파를 뺀 값을 곱한 것입니다. in은 두 알파의 곱입니다. Filter Effects 합성 정의는 사전 곱셈 색상과 알파로 이 연산들을 정의합니다. 불투명한 B는 A의 겹친 칠을 제거할 수 있지만, 부분적으로 투명한 B는 일부를 남깁니다.
실제 배경 위에서 결과를 확인하세요. 흰색 위의 옅은 영역은 완전히 투명한 구멍이 아니라 반투명한 칠일 수 있습니다. 안티앨리어싱된 가장자리 픽셀도 눈으로 확인해야 합니다. 내부 표본이 모든 경계 픽셀의 알파가 0이라고 보장하지는 않습니다. 불투명도를 재매핑하는 별도 작업은 SVG 알파 임계값 가이드를 참고하세요.
필터 효과를 전달할지 실제 윤곽을 만들지 결정하기
받는 쪽 렌더러가 시각적 합성을 재현해야 한다면 이 필터를 유지하세요. 원본 A와 B 파일을 보관하고 필터 SVG를 실제 사용처에서 다시 열어 확인합니다. 브라우저에서 다시 열기에 성공했다고 다른 렌더러나 생산 장비까지 보장되지는 않습니다.
선택 가능한 결합 윤곽이나 커팅 준비가 된 구멍을 전달해야 한다면 예정된 작업 흐름에서 실제 경로 편집 또는 불리언 연산을 수행하고 확인하세요. 여기서 바뀐 시각적인 칠은 그 형상을 만들지 않습니다. clipPath와 mask 비교 가이드는 관련된 가시성 차이를 설명합니다. SVG 그룹 해제 가이드는 화면의 아트워크가 예상한 편집 가능 객체가 아닐 때 도움이 됩니다.
클립아트가 픽셀로만 존재하고 편집 가능한 원본 도형이 필요하다면 PerfectVector로 클립아트 SVG를 준비하고 윤곽과 구멍을 확인한 뒤 마스터를 보관하세요. 원하는 필터나 경로 연산은 별도로 적용하고 검증합니다. 벡터 변환이 이 필터의 시각적인 겹침을 검증된 제조용 윤곽으로 바꾸지는 않습니다.
자주 묻는 질문
feComposite out은 겹친 경로 형상을 삭제하나요? 아닙니다. 두 번째 입력의 알파를 사용해 렌더링된 칠을 바꿉니다. 이 필터 파일에서는 겹친 칠이 투명해진 곳에서도 원본 A의 경로 데이터와 로컬 형상 범위가 바뀌지 않았습니다.
in과 in2를 바꾸면 결과가 달라지는 이유는 무엇인가요? 첫 번째와 두 번째 입력을 지정하기 때문입니다. In은 두 번째 입력의 불투명도 안에 첫 번째 입력의 칠을 남기고, out은 그 바깥에 첫 번째 입력을 남깁니다. 뒤집은 in 사례는 같은 불투명 겹침 영역을 보이면서도 색상이 다르고, 뒤집은 out 사례는 서로 다른 영역을 남깁니다.
xor는 항상 겹친 부분을 완전히 비우나요? 완전히 불투명한 내부 겹침은 이 xor 사례에서 비어 있었습니다. 부분 알파와 안티앨리어싱된 가장자리는 별도로 확인해야 합니다. 불투명 도형의 집합 도식은 모든 반투명 픽셀을 설명하지 않습니다.
필터로 만든 구멍을 편집 가능한 커팅 경로로 바로 전달해도 되나요? 시각적인 필터 결과는 새로 만든 불리언 형상이 아닙니다. 원본 도형을 보관하고, 예정된 경로 작업에서 필요한 윤곽을 만들고 확인한 다음, 받는 애플리케이션과 생산 요구 사항을 별도로 검증하세요.
출처
- MDN: feComposite — 이미지 공간 입력의 역할, 연산자 설명, 기본 필터 색 공간.
- MDN: operator — 합성 연산자 값과 별도의 형태학 맥락.
- MDN: filterUnits — 필터 영역 좌표계.
- MDN: feImage — 필터 파이프라인으로 전달하는 이미지 입력.
- W3C: Filter Effects 합성 — 사전 곱셈 색상과 알파의 합성 정의.
자신의 래스터 클립아트에서 시작하나요? 편집 가능한 SVG 마스터를 만들고 윤곽과 구멍을 확인하세요. 그 마스터를 보관하면서 선택한 시각적 합성이나 별도로 만든 경로 결과를 실제 사용처에서 확인합니다.


