PerfectVector
著者: Irene Kim1 分で読める

SharpでSVGをPNGに変換:サイズ、密度、鮮明な書き出し

SharpでSVGを最終ピクセルサイズの透過PNGに変換します。再現可能な1x・2xテスト、入力密度が必要な場合、PNG拡大がぼやける理由を解説します。

目次

現在のSharpの処理では、書き出すたびにSVGを元にして、resize()で実際に必要なピクセル幅を指定します。2x素材が必要なら、出力ピクセル数を2倍にします。小さなPNGを先に作ってから、そのファイルを拡大しないでください。また、古いdensityによる対処法をすべての処理に流用する必要はありません。Sharp 0.34.4でのテストでは、入力密度を高くしてもしなくても同じピクセルが描画されました。

要点
  1. 元のSVGをマスターデータとして保持します。
  2. 各サイズのPNGを、そのSVGからSharpで直接描画します。
  3. 出力サイズとアルファチャンネルを確認し、実際の表示サイズでファイルを見ます。
  4. それでも元データと結果が異なる場合は、SVGを修正するかレンダラーを調べます。密度設定では、埋め込みビットマップの細部は増やせません。

SVGから1xと2xのPNGを描画する

SharpのコンストラクターはSVGファイルやバッファーを受け取り、resize APIで出力サイズを設定します。64単位の正方形アイコンを256 CSSピクセルで表示する場合、1x用には256ピクセルのPNG、2x用には512ピクセルのPNGを書き出します。

import sharp from "sharp";
 
const source = "icon.svg";
 
await Promise.all([
  sharp(source).resize({ width: 256 }).png().toFile("icon-1x.png"),
  sharp(source).resize({ width: 512 }).png().toFile("icon-2x.png"),
]);

高さを省略すると、Sharpの通常のリサイズ動作では元の縦横比が維持されます。用途に合わせて実際の出力幅を選んでください。「2x」は想定するCSS表示サイズに対するピクセル寸法を指し、SVG属性でも、あらゆる画面での品質を保証する言葉でもありません。256 CSSピクセルで表示するなら、256ピクセルと512ピクセルのPNGをその表示サイズで比較し、大きいファイルにする価値があるか判断します。

テスト用の元アイコンにはwidth="64" height="64" viewBox="0 0 64 64"を指定しました。リポジトリで固定しているSharp 0.34.4で、64、512、1,024ピクセルに描画しました。512ピクセルと1,024ピクセルの出力はSVGから生成され、隅は透明、塗りのある中央は不透明で、報告された寸法も指定どおりでした。これはこの素材とバージョンでの観察結果であり、すべてのSVG作品を対象としたベンチマークではありません。

Sharp 0.34.4での独自描画テスト
Sharpの描画テスト:SVGを直接512ピクセルで描画した結果と密度を調整した結果は同じに見えますが、中間の64ピクセルPNGを拡大するとぼやけます
同じ64 × 64のSVGアイコンを直接512ピクセルで描画する方法、入力密度576で描画して512ピクセルにリサイズする方法、まず64ピクセルでラスター化してから拡大する方法を比較しました。既存のピクセル格子を拡大するのは最後の方法だけです。

図では、問題が生じる仕組みを具体的に示しています。最初の出力が64ピクセルのPNGになると、その後のリサイズは既存のピクセルを補間します。再描画できるパスは残っていません。このテストでは、直接描画した512ピクセルの結果と密度を調整した結果の生のRGBAチャンネルは完全に一致しました。元SVG、描画スクリプト、結果のメタデータは、この記事の調査資料に保存しています。

Sharpでdensityは必要ですか?

Sharpのコンストラクターのdensityオプションは、SVGやPDF入力を読み込む際のDPIを制御し、文書上の既定値は72です。resize()で最終幅を指定する操作とは異なります。拡大によるぼやけを避けるため、入力密度を上げる古いSharpの例を見かけることがあります。それらは過去に実際に報告された症状を扱っています。Sharpのissue #1421では、2018年に32ピクセルのSVGを512ピクセルに拡大するとピクセルが目立つ問題が報告されました。ただし、現在の環境でもその回避策が必要だという根拠にはなりません。

Sharp 0.34.4で、次の2つの処理をそのまま比較しました。

const direct = await sharp("icon.svg")
  .resize({ width: 512 })
  .png()
  .toBuffer();
 
const densityFirst = await sharp("icon.svg", { density: 576 })
  .resize({ width: 512 })
  .png()
  .toBuffer();

64ピクセルのベクターアイコンでは、どちらも同じ512 × 512のRGBAピクセルを出力しました。特定のファイルとインストール済みのSharpバージョンで必要性が確認できたときにだけ入力密度を設定し、更新後も比較を繰り返してください。高いDPI値を万能の鮮明化スイッチとして扱わないでください。まず確認すべきなのは、Sharpが最終PNGを描画する時点で、ベクター形状がまだ残っているかどうかです。

PDFも扱う場合は、その入力を別途テストしてください。このSVGの実験ではPDFの挙動は確認できません。同様に、新しいSharpドキュメントのwithDensity()は出力のDPIメタデータを扱います。これはコンストラクターで指定するSVG入力の描画密度とは別の操作です。

寸法、透明度、元データの内容を確認する

プレビューの拡大表示から推測するのではなく、出力に対してSharpのメタデータAPIを使います。

const oneX = await sharp("icon-1x.png").metadata();
const twoX = await sharp("icon-2x.png").metadata();
 
console.log({
  oneX: [oneX.width, oneX.height, oneX.hasAlpha],
  twoX: [twoX.width, twoX.height, twoX.hasAlpha],
});

今回の正方形の素材では、想定寸法は256 × 256と512 × 512です。hasAlphaはアルファチャンネルがあることを示しますが、特定の隅が透明であることまでは保証しません。納品時に透明度が重要なら、ファイルを確認するかピクセルを調べてください。今回テストしたすべての出力では、隅のアルファ値は0、中央は255でした。SVG内に白い矩形がある場合は、PNG形式であってもすべての出力にその矩形が描画されます。

大きなサイズで新しく描画してもピクセルが目立つ場合は、SVGの内容を確認します。SVG標準では、ラスター画像を埋め込んだり参照したりする<image>要素を使用できます。そのため、拡張子が.svgでも、中身の画像は低解像度PNGのままという場合があります。SVG内のラスター画像を見つけるガイドでは、オブジェクトとXMLの確認方法を説明しています。Sharpの出力幅を増やしても、埋め込まれたピクセルはパスにはなりません。

表示先も確認してください。512ピクセルのファイルを1,024ピクセルで表示すると、ぼやけることがあります。書き出しが正しくても、ビューアーで100%を大きく超えて拡大するとPNGの格子が見えます。実際の描画サイズと背景で比較し、より大きなPNGを渡すか、SVG自体を使うか判断します。Webでは、インラインSVGと画像要素内のSVGで組み込み方が異なります。表示先がSVGに対応していれば、どちらもPNGは不要です。

PerfectVectorを使う場面

SharpはSVGをラスター化します。PNGから編集可能なパスを復元するツールではありません。マスター画像がラスターのアイコンやロゴだけなら、この書き出し処理の前にSVGを作成して確認します。UIキット用PNGアイコンをSVGに変換するガイドでは、一式の形状をそろえる方法と納品時の確認事項を説明しています。PerfectVectorの画像をベクターに変換するワークフローで、ラスター画像からSVGの候補を作れます。結果を開き、輪郭、小さな細部、実際にパスが含まれているかを確認してから、承認したSVGから1xと2xのPNGを描画してください。きれいな元SVGがすでにある場合は、それを保持してSharpで直接処理します。

よくある質問

SharpでSVGをPNGにすると、なぜぼやけますか? 処理の途中で小さなPNGを作り、それを拡大していないか確認してください。次に、SVG内の埋め込みラスター画像を調べ、納品するPNGを実際の表示サイズで比較します。現在のSharpでは、入力密度による回避策を追加する前に、SVGから最終サイズへ直接描画する方法をテストしてください。

8倍に拡大するにはdensity: 576が必要ですか? Sharp 0.34.4で64ピクセルから512ピクセルに変換した今回のSVG素材では不要でした。直接リサイズした結果と密度を調整した結果は、同じRGBAピクセルでした。SVGやインストール済みのレンダラーによって挙動が異なる可能性があるので、その値を決まりとして採用せず、実際の入力でテストしてください。

透過の2x PNGを作るにはどうすればよいですか? 想定するCSS寸法の2倍でSVGを描画し、PNGとして書き出します。結果の幅と高さを確認し、塗りのない部分が透明か調べてください。キャンバスが透明でも、SVG内にすでにある背景形状は消えません。

参考資料

  1. Sharp — コンストラクター — SVG入力への対応と入力のdensityオプション。
  2. Sharp — 画像のリサイズ — 例で使用した出力サイズの挙動。
  3. Sharp — 入力メタデータ — 寸法とアルファのメタデータ確認。
  4. Sharp GitHub — SVGからPNGへの変換でピクセルが目立つissue #1421 — 固定した現行バージョンのテストとは明確に区別した、過去の報告。
  5. Sharp — 出力オプション — 新しい出力密度のメタデータと入力描画密度の違い。
  6. W3C — SVG 2の埋め込みコンテンツ — SVGにラスターピクセルを含められる理由。

マスターがラスター画像だけの場合は、PerfectVectorでSVGの候補を作成し、パスを確認したうえで、承認したファイルをSharpで必要な各PNGサイズに描画してください。

ブログのその他の記事

画像ファイルを
編集可能なSVGに変換