PerfectVector
Автор: Irene Kim8 мин чтения

Canvas Path2D SVG: контуры в правильном размере

Используйте данные контуров SVG на canvas с верным преобразованием viewBox, цветами, отверстиями и плотностью пикселей. Разберите авторский пример и сохраняйте редактируемый оригинал SVG.

На этой странице

Передайте атрибут d контура в new Path2D(d), затем нарисуйте его контекстом canvas. Для совпадения с SVG также перенесите отображение координат, оформление и правило заливки. Конструктор принимает данные контура; он не импортирует документ SVG и не применяет его viewBox, CSS или преобразования групп (конструктор Path2D на MDN).

Это важно, когда собственная иконка правильно выглядит в SVG, но на canvas становится маленькой, смещённой, чёрной или теряет отверстие. Пример ниже исправляет эти различия двумя контурами и явным преобразованием вписывания. Начните с существующей векторной геометрии, если она есть. Векторизация полезна лишь тогда, когда нужная для повторного использования графика всё ещё существует в пикселях.

Что содержит объект Path2D

Считайте данные контура описанием очертания. Другие инструкции SVG находятся вне этой строки, и код рисования canvas должен предоставить необходимые из них.

Исходные данные SVGЗадача canvas
Значение d контураПостроить геометрию Path2D
viewBox и размер отображенияВычислить преобразование рисования
fill и fill-ruleЗадать fillStyle и передать правило в fill()
transform родителя или контураОтдельно применить эквивалентное преобразование
CSS, градиенты, маски, текст и изображенияРеализовать нужную отрисовку или выбрать другой способ

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

Сравните исходник с ненастроенным canvas

Эта собственная эмблема имеет округлое бирюзовое тело, оранжевый выступ и квадратное отверстие. Её viewBox начинается в (20, 10), а не (0, 0). Внутренний подконтур идёт в том же направлении, что внешняя граница, поэтому объявленное правило evenodd важно.

Поместите этот SVG и canvas в HTML-страницу:

<svg id="source" xmlns="http://www.w3.org/2000/svg"
     viewBox="20 10 120 80" width="240" height="160">
  <path fill="#126d70" fill-rule="evenodd"
        d="M40 20 H90 Q110 20 110 40 V60 Q110 80 90 80 H40 Q30 80 30 70 V30 Q30 20 40 20 Z M50 35 H80 V60 H50 Z"/>
  <path fill="#f19b38" d="M115 25 L135 50 L115 75 Z"/>
</svg>
<canvas id="result"></canvas>

Если перебрать два значения d и вызвать ctx.fill(new Path2D(d)) без остальных настроек, геометрия использует координаты canvas и оформление и правило заливки по умолчанию. Она не приобретает автоматически внешний вид SVG. Canvas fill() принимает и контур, и явное правило evenodd или nonzero; по умолчанию используется nonzero (fill() на MDN).

Браузерное сравнение SVG-эмблемы, небольшой чёрной необработанной отрисовки Path2D с заполненным отверстием и правильно вписанной двухцветной отрисовки canvas
Одни контуры, написанные вручную, в браузере: эталон SVG, необработанный Path2D и явное вписывание с восстановленными заливками и evenodd. Таблица фиксирует размеры буфера для четырёх сочетаний размера и DPR.

Метод: мы отрисовали этот написанный вручную SVG в браузере, нарисовали два его контура обоими способами canvas и заново выполнили опубликованный код на отдельной HTML-странице. Скриншот — реальный вывод браузера. Он демонстрирует обработку координат и оформления, а не преобразование PerfectVector.

Отобразите viewBox в canvas

По умолчанию SVG сохраняет пропорции, вписывая viewBox в область просмотра и центрируя его. Спецификация системы координат SVG определяет это отображение. Для данного плоского примера воспроизведите xMidYMid meet одним равномерным масштабом и переносом:

scale = min(cssWidth / viewBoxWidth, cssHeight / viewBoxHeight)
tx = (cssWidth - viewBoxWidth * scale) / 2 - viewBoxX * scale
ty = (cssHeight - viewBoxHeight * scale) / 2 - viewBoxY * scale

Последние слагаемые убирают ненулевое начало. При 240 × 160 масштаб равен 2, tx равен -40, а ty — -20. При 300 × 120 масштаб становится 1.5; вписанный viewBox шириной 180 пикселей получает по 60 пикселей горизонтального пространства с каждой стороны. Поэтому перенос равен (30, -15) после учёта исходного начала.

Используйте следующий скрипт после HTML выше. Он обрабатывает прямые атрибуты контуров этого образца, перерисовывает весь canvas и разделяет CSS-размер и размер растрового буфера:

const source = document.querySelector("#source");
const vb = source.viewBox.baseVal;
const layers = [...source.querySelectorAll("path")].map(el => ({
  path: new Path2D(el.getAttribute("d")),
  fill: el.getAttribute("fill"),
  rule: el.getAttribute("fill-rule") || "nonzero"
}));
 
function draw(canvas, cssWidth, cssHeight, dpr = window.devicePixelRatio || 1) {
  canvas.style.width = `${cssWidth}px`;
  canvas.style.height = `${cssHeight}px`;
  canvas.width = Math.round(cssWidth * dpr);
  canvas.height = Math.round(cssHeight * dpr);
  const ctx = canvas.getContext("2d");
  const scale = Math.min(cssWidth / vb.width, cssHeight / vb.height);
  const tx = (cssWidth - vb.width * scale) / 2 - vb.x * scale;
  const ty = (cssHeight - vb.height * scale) / 2 - vb.y * scale;
  const px = canvas.width / cssWidth;
  const py = canvas.height / cssHeight;
  ctx.setTransform(px * scale, 0, 0, py * scale, px * tx, py * ty);
  for (const layer of layers) {
    ctx.fillStyle = layer.fill;
    ctx.fill(layer.path, layer.rule);
  }
}
 
draw(document.querySelector("#result"), 240, 160);
 

Задание canvas.width и canvas.height очищает и сбрасывает контекст. Затем функция задаёт полное преобразование и снова рисует каждый слой, поэтому повторные вызовы не накапливают масштаб. Она предполагает корректный viewBox с положительной шириной и высотой и положительные запрошенные размеры.

px и py используют реальные округлённые размеры буфера. Это сохраняет отображение CSS-координат на выделенные пиксели даже тогда, когда запрошенная плотность даёт дробные размеры. Пример MDN с devicePixelRatio объясняет, почему размер отображения canvas и размеры в пикселях нужно обрабатывать отдельно.

Проверяйте размер и плотность пикселей отдельно

Мы явно передали значения DPR 1 и 2 в одну функцию при двух CSS-размерах. Это контролируемые входные значения плотности, а не утверждение о проверке двух физических устройств. Сообщаемый браузером коэффициент пикселей устройства для вызова по умолчанию был 2.

CSS-размерАргумент DPRНаблюдаемый растровый буфер canvas
240 × 1601240 × 160
240 × 1602480 × 320
300 × 1201300 × 120
300 × 1202600 × 240

Центр отверстия оставался прозрачным во всех четырёх случаях. Повышение плотности меняет разрешение буфера; оно не исправляет неверное преобразование и не восстанавливает пропавшее правило заливки. Перерисовывайте при изменении нужных CSS-размеров или коэффициента пикселей устройства. Увеличение одним CSS растягивает уже имеющийся на canvas растр.

Если исходник неверен ещё до canvas, используйте проверки размера SVG и viewBox. Если ошибается только canvas, сначала сравните его преобразование и оформление с рабочим эталоном SVG.

Сохраните исходник и выберите подходящий способ импорта

Храните SVG-оригинал и его атрибуты вместе с производными данными контуров. Процедура рисования Path2D полезна, когда вы управляете небольшим набором контуров и их оформлением. Для сложного SVG, от которого нужен лишь браузерный внешний вид, загрузка как изображения и рисование этого изображения может избавить от воспроизведения каждой возможности. Этот способ всё равно рисует в растровый буфер canvas.

Сохранение вывода canvas в PNG создаёт растровое изображение, даже если рисование началось с векторных контуров. API экспорта canvas создаёт blob изображения; поддержка PNG обязательна (toBlob() на MDN). Храните SVG для последующего редактирования контуров. Аналогично, SVG-файл может содержать растровое изображение вместо полезных данных контуров; диагностика встроенных изображений помогает различать эти случаи.

PerfectVector подходит, когда остался только PNG или JPG собственной иконки и нужны повторно используемые контуры. Преобразуйте изображение в SVG, затем проверьте силуэт, отдельные цвета и небольшие отверстия перед извлечением контуров для canvas. Качественный SVG-оригинал должен сразу переходить к подготовке. Трассировка не реализует отображение viewBox, не копирует CSS и не делает PNG canvas редактируемым как вектор.

Для целого семейства иконок установите общую сетку UI-кита и правила оформления перед подключением каждой процедуры рисования. Другие примеры подготовки SVG есть в блоге.

Перед использованием рисунка в интерфейсе

  • Проверьте эталон SVG при тех же размерах отображения, что canvas.
  • Подтвердите начало viewBox вместе с его шириной и высотой.
  • Перенесите оформление и нужное правило заливки; проверьте каждое отверстие.
  • Учтите преобразования групп вместо копирования одного d.
  • Прочитайте реальные canvas.width и canvas.height, затем проверьте самый большой предполагаемый размер отображения.
  • Сохраните SVG-оригинал и задайте имя для вспомогательных технологий или окружающий текст, подходящий интерфейсу.

Частые вопросы

Может ли Path2D импортировать целый SVG-файл? Нет. Его строковый вход — данные контура SVG, например значение атрибута d. Структура документа, отображение viewBox, стили и преобразования групп требуют отдельной обработки.

Почему отверстие моего SVG заполнилось на canvas? Проверьте правило заливки и направления подконтуров. Этот пример требует evenodd, тогда как canvas fill по умолчанию использует nonzero. Передавайте нужное правило при заливке объекта Path2D.

Сохраняет ли рисование контуров SVG на canvas векторный экспорт? Контуры остаются повторно используемыми инструкциями рисования в программе, но экспорт canvas в PNG создаёт пиксели. Сохраните исходный SVG для редактирования и векторной передачи.

Источники

  1. MDN — Path2D constructor — Определяет строку данных контура, принимаемую конструктором.
  2. W3C — SVG coordinate systems — Определяет начало viewBox, отображение в область просмотра и выравнивание с сохранением пропорций.
  3. MDN — Canvas fill() — Описывает заливку контуров и правила evenodd и nonzero.
  4. MDN — devicePixelRatio — Объясняет CSS-размер отображения и разрешение буфера canvas.
  5. MDN — Canvas toBlob() — Описывает экспорт растрового изображения из canvas.

Есть только растровая иконка? Подготовьте её SVG с PerfectVector, проверьте контур и отверстия, затем сравните рисунок canvas с этим SVG при реальных размерах интерфейса.

Ещё из блога

Начните с более чистого SVG,
который проще редактировать