* fix(draw-image): use density 72 for 1024x1024 PNG output * test(draw-image): assert real PNG dimensions via IHDR decode * chore(assets): regenerate cover.png at 1024x1024 * docs(handoff): add handoff and ADR for density fix * docs(handoff): set PR number --------- Co-authored-by: opencode-agent <agent@opencode.local>
2.2 KiB
ADR-068: Use sharp density 72 for pixel-perfect 1024×1024 PNG output
Статус
Accepted (2026-07-30)
Контекст
render.mjs рендерил SVG-шаблон cover (1024×1024 по viewBox) в PNG 1365×1365 вместо ожидаемых 1024×1024. Root cause: sharp(Buffer.from(svg), { density: 96 }) — density в DPI, sharp интерпретирует viewBox 1024 (в user units, по умолчанию 1 unit = 1pt = 1/72in) и рендерит 1024pt × 96 DPI / 72 = 1365px. Тесты assertPng1024 проверяли только PNG magic bytes (0x89 0x50 0x4e 0x47), не размеры — баг маскировался. meta.size: 1024 в cli.ts:78 был контрактом, но фактически нарушался.
Рассматривались варианты:
- density: 72 → 1024 × 72/72 = 1024px (точное совпадение).
- density: 96 + resize до 1024 → лишний resample, потеря качества.
- Явный
.resize(1024, 1024)после density → double-pass, медленнее.
Решение
Установить density: 72 в sharp() вызове (render.mjs:36). 72 DPI = canonical SVG default (1pt = 1/72in), даёт pixel-perfect 1:1 маппинг viewBox → px. Дополнительно: assertions в тестах декодируют PNG IHDR chunk (width bytes 16-19, height bytes 20-23, big-endian uint32) и проверяют === 1024 — регрессия на размер теперь детектируется.
Альтернативы
- density 96 + explicit resize(1024): отвергнут — лишний resample снижает качество текста (subpixel), double-pass рендер.
- density 96 + viewBox в px вместо pt: потребовало бы правки всех SVG-шаблонов (
cover.svgetc.), больший scope, риск regressions в layout. - assertions через
sharp().metadata()вместо IHDR decode: добавил бы зависимость от sharp в test-слой (сейчас тесты читают bytes напрямую, без dep). IHDR decode — zero-dependency, быстрее.