SVG feComposite: сравните графику in, out и xor
Сравните входы фильтров SVG с in, out и xor, проверьте порядок операндов и частичный альфа-канал, сохраните исходные контуры, когда видимое отверстие не является редактируемой геометрией.
На этой странице
SVG feComposite объединяет два отрисованных входа. Для полностью непрозрачных входов in сохраняет окрашивание первого в пересечении, out — вне пересечения, а xor объединяет оба входа вне их общего пересечения. Эти операции меняют окрашивание при отрисовке. Они не переписывают исходные контуры в редактируемое пересечение или разность.
Это различие важно, когда перекрытие выглядит как отверстие, но результату передачи нужны настоящие контуры. Пример ниже сравнивает две оригинальные асимметричные фигуры, меняет входы местами и проверяет частичную прозрачность. Храните исходную графику рядом с результатом фильтра.
Назовите оба входа до выбора оператора
Справочник MDN по feComposite описывает попиксельную операцию в пространстве изображения. in задаёт первый вход, in2 — второй. Давайте промежуточным результатам явные имена, чтобы изменение цепочки фильтра не меняло операнды незаметно.
Для непрозрачных внутренних областей эти три операции легко сравнить:
| Операция | Первый вход A, второй B | Что меняет перестановка входов |
|---|---|---|
in | Окрашивание A в пересечении | Пересечение несёт окрашивание B |
out | Окрашивание A вне B | Окрашивание B вне A — другая область |
xor | Оба входа вне общего непрозрачного пересечения | Сохраняются те же неперекрывающиеся области |
Тот же атрибут operator встречается у других примитивов фильтров с другими значениями. Явно учитывайте контекст композиции: справочник MDN по operator отличает его от морфологических операций.

Явно совместите вторую графику
Наш первый операнд A — бирюзовый ступенчатый блок. B — охристый перекошенный воздушный змей. Оба используют один viewBox 0 0 240 160. B выходит за локальный ограничивающий прямоугольник A, поэтому использовать границы A как область фильтра было бы плохим исходным допущением.
Фильтр использует filterUnits="userSpaceOnUse" для явной области x=0, y=0, width=240, height=160. Справочник MDN по filterUnits объясняет выбор координат. Мы также задаём primitiveUnits="userSpaceOnUse" и даём второму входу те же координаты полного кадра.
B — самодостаточный data URL SVG, переданный через feImage с именем результата operandB. Это вводит B в фильтр как входное изображение, а не вставляет контур B как новый прямой дочерний элемент фильтруемой графики. Справочник MDN по feImage описывает поведение входного изображения.
Фильтр явно выбирает sRGB. По умолчанию эти примитивы используют linearRGB, как описано в справочнике feComposite. Ни значение по умолчанию, ни имя оператора не дают операции правки контуров.
Запустите полное оригинальное сравнение
Сохраните это как 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.
Проверка пикселей здесь читает canvas, нарисованный из каждого созданного SVG. Загружаемый результат всё ещё фильтрованный SVG. Он не является новым булевым контуром или преобразованием PerfectVector.
Сравнивайте окрашивание и геометрию отдельно
Мы выполнили эту точную страницу в одном окружении Chrome при 120 × 80 и 240 × 160, сохранили все 18 SVG и снова открыли их как изображения в том же браузере. Показанные размеры — явные входные значения, а не утверждение о двух физических устройствах.
Три внутренние позиции образцов используют исходную систему координат: только 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) оставался прозрачным всё время. Это выбранные внутренние пиксели, а не полный тест покрытия краёв или совместимости.
Каждый фильтрованный файл всё ещё имел точную исходную строку d A и совпадающий SHA-256. Его локальный геометрический getBBox() оставался x=24, y=24, width=168, height=112. Этот прямоугольник описывает исходный контур, а не видимые границы пикселей фильтра. Закодированный контур B тоже не менялся. Поэтому результат фильтра может выглядеть иначе, хотя оба созданных контура остаются целыми.
Сохранённые оригиналы имеют один прямой контур и не имеют изображения фильтра. В каждом фильтрованном файле один прямой контур A плюс один feImage, несущий отдельный SVG B из одного контура. Обычный растровый элемент image не добавляется. Отрисованная операция в пространстве изображения и векторные исходные операнды могут сосуществовать; один перечень исходных элементов не означает, что результат стал редактируемой объединённой геометрией.
Частичная непрозрачность меняет смысл отверстия
Последний случай делает B наполовину непрозрачным, оставляя A непрозрачным, затем применяет A out B. Пересечение не стало пустым. В обоих размерах наблюдаемый RGBA был (22,106,90,127): частично прозрачный бирюзовый с округлением конвейера отрисовки/чтения.
Для out выходной альфа-канал равен первому альфа-каналу, умноженному на единицу минус второй. Для in — их произведению. Определение композиции Filter Effects определяет операции через предварительно умноженные цвета и альфа-канал. Непрозрачный B может удалить перекрывающееся окрашивание A, а частично прозрачный B оставляет его часть.
Проверяйте результат на настоящем фоне. Бледная область поверх белого может быть полупрозрачной краской, а не полностью прозрачным отверстием. Сглаженные краевые пиксели тоже требуют визуальной проверки; внутренний образец не обещает нулевого альфа-канала у каждого граничного пикселя. Для отдельной задачи переназначения непрозрачности см. руководство по альфа-порогам SVG.
Передавайте эффект фильтра или создавайте настоящие контуры
Сохраняйте фильтр, если принимающее средство отрисовки должно воспроизвести визуальную композицию. Сохраняйте исходные файлы A и B и проверяйте заново открытый фильтрованный SVG в реальной целевой среде. Наше успешное открытие в браузере не сертифицирует другие средства отрисовки или производственные машины.
Если передаче нужны выделяемые объединённые контуры или отверстие для резки, выполните и проверьте настоящую правку контуров или булеву операцию в нужном процессе. Изменённое видимое окрашивание здесь не создаёт этой геометрии. Руководство clipPath и mask поясняет связанное различие видимости; руководство по разгруппировке SVG помогает, когда показанная графика не является ожидаемым редактируемым объектом.
Если клипарт существует только в пикселях, а нужны редактируемые исходные фигуры, подготовьте SVG-клипарт с PerfectVector, проверьте контуры и отверстия и сохраните мастер. Применяйте и проверяйте нужный фильтр или операцию с контурами отдельно. Векторизация не превращает визуальное пересечение этого фильтра в проверенный производственный контур.
Частые вопросы
Удаляет ли feComposite out геометрию перекрывающегося контура? Нет. Он меняет отрисованное окрашивание по альфа-каналу второго входа. В наших фильтрованных файлах исходные данные контура A и локальные геометрические границы не менялись даже там, где перекрывающееся окрашивание стало прозрачным.
Почему перестановка in и in2 меняет результат? Они задают первый и второй входы. In сохраняет окрашивание первого в пределах непрозрачности второго; out — вне неё. Обратные случаи in имеют общее непрозрачное пересечение, но разные цвета, а обратные out сохраняют разные области.
Всегда ли xor делает пересечение полностью пустым? Полностью непрозрачное внутреннее пересечение было пустым в наших случаях xor. Частичный альфа-канал и сглаженные края требуют отдельной проверки; диаграммы множеств непрозрачных фигур не описывают каждый полупрозрачный пиксель.
Можно ли передать фильтрованное отверстие прямо как редактируемый контур резки? Визуальный результат фильтра не является заново созданной булевой геометрией. Сохраните исходные фигуры, создайте и проверьте нужные контуры в целевом процессе работы с ними и отдельно проверьте принимающее приложение и производственные требования.
Источники
- MDN: feComposite — Роли входов пространства изображения, описания операторов и стандартное цветовое пространство фильтра.
- MDN: operator — Значения операторов композиции и отдельный контекст морфологии.
- MDN: filterUnits — Система координат области фильтра.
- MDN: feImage — Изображение на входе конвейера фильтра.
- W3C: Композиция Filter Effects — Определения композиции предварительно умноженных цвета и альфа-канала.
Начинаете со своего растрового клипарта? Создайте редактируемый мастер SVG, проверьте контуры и отверстия, затем сохраняйте его, проверяя выбранную визуальную композицию или отдельно созданный результат контуров в целевой среде.
Ещё из блога

Обрежьте SVG окончательно, без скрытых контуров
Обрежьте залитые контуры SVG реальными пересечениями, сохраните цвета и отверстие, затем проверьте файл на геометрию, которую отсечение только скрывает.

Inkscape: чем отличаются Object to Path и Stroke to Path
Сравните Object to Path и Stroke to Path в Inkscape на одном круге. Узнайте, где остаётся редактируемая обводка, а где появляется залитая полоса, и что проверить.
