デザイナーから届いたPSDを「ロゴだけPNGで欲しい」「このレイヤーだけ抜き出したい」という要望は、非デザイナーの現場で意外と多いです。ブラウザでPSDを読み込んでPNGに書き出す機能を作ったときのメモです。ライブラリは ag-psd を使いました。
基本の読み込み
import { readPsd } from 'ag-psd';
const buf = await file.arrayBuffer();
const psd = readPsd(buf); // デフォルトで canvas が生成される
psd.canvas; // 合成済み画像
psd.children; // レイヤーツリー
ここまでは簡単ですが、実際のファイルを流すといろいろ起きます。
1. 合成画像(composite)が入っていないPSD
Photoshopの「互換性を優先」設定がオフで保存されたPSDには、合成済みのプレビュー画像が含まれていないことがあります。この場合 psd.canvas は空か真っ白です。レイヤーを自前で重ねる必要がありますが、後述のブレンドモードの問題が出ます。まず合成画像の有無をチェックして、ない場合はユーザーに「レイヤー単位で書き出してください」と伝えるのが現実的でした。
2. ブレンドモードと調整レイヤー
レイヤーを drawImage で順に重ねるだけだと、乗算・スクリーン・オーバーレイなどが再現されません。Canvasの globalCompositeOperation に multiply や screen があるので主要なものは対応できますが、調整レイヤー(トーンカーブ、色相・彩度)やレイヤースタイル(ドロップシャドウ)は再現できません。完全再現は諦めて、合成画像を優先するのが正解でした。
3. 16bit / 32bit のPSD
写真系のPSDは16bitのことがあります。ag-psdは読み込めますが、Canvasは8bitなので変換で階調が落ちます。PNG書き出しの用途なら問題ないケースがほとんどですが、「元データとして保存したい」という人にはPSDのまま残すよう案内します。
4. メモリ
PSDは非圧縮に近い形でピクセルを持っていることが多く、100MB超も普通です。全レイヤーのcanvasを一度に生成すると、レイヤー数×幅×高さ×4バイト分のメモリを使います。
const psd = readPsd(buf, { skipLayerImageData: true }); // まずツリーだけ
まずツリーだけ読んでレイヤー名一覧を出し、ユーザーが選んだレイヤーだけ改めて読む、という2段階にしたら、スマホでもクラッシュしなくなりました。重い処理はWorkerで実行します(Workerでは OffscreenCanvas を使う設定が必要です)。
5. レイヤー名の文字化け
古いPSDや一部のツールで書き出したファイルは、レイヤー名がUnicodeで格納されておらず、日本語が化けることがあります。ag-psdは Unicode レイヤー名(luni ブロック)があればそちらを使うので、多くの場合は問題ありませんが、ない場合はフォールバック表示(「レイヤー 3」など)を用意しておくと親切です。
まとめ
PSDを「完全に」再現するのはPhotoshop以外では難しいですが、「合成画像を取り出す」「特定レイヤーを透過PNGで抜く」なら、ブラウザだけで十分実用になります。
完成形のイメージをつかみたい場合は、ファイルをアップロードせずブラウザ内でPSDをPNGに書き出せるNibboのPSD→PNG変換で、手元のPSDがどう見えるか試してみてください。
— Vitalii