AI生成の動作例用写真 / 編集前
DEVELOPMENT JOURNAL / 12
Photoshot
GITHUB × AI
無料Photoshop代替の開発記

ファイルから写真の色へ

LUTの解析、補間、ICC色管理エンジンはすでに存在しました。異なる入力を検証し、同じ調整レイヤーにつなぐことが開発の仕事です。参考資料と実際に組み込んだ構成要素を区別します。

PHOTOSHOT DEVELOPMENT JOURNAL / 12

AI生成の動作例用写真。編集画面と出力画像は実際のPhotoshotで作成しました。

Photoshot無料Photoshop代替の開発記 / 12FOUNDATION

ソースから始める。

色の表と色管理エンジン

色変換表を読むだけでは完了しません。解析、データ検証、座標計算、補間、合成をつなぐ必要があります。AIと各段階をPhotoshotの入出力に合わせて実装しました。

OpenColorIOの資料はLUT構造の参考です。ICCにはlcms-wasm 1.0.5とLittleCMS 2.16を実際に組み込みました。文書の参照とソフトウェアの導入は異なる再利用です。

ファイルを共通の格子に変える

CUBE・3DL・LOOK・CSPは格子サイズ、入力範囲、RGB配列を持つ共通Cubeに変換します。先行する1D入力曲線はshaperとして保持します。

上限は32 MiB、3D格子は各軸65です。配列はsize³×3個の値が必要で、無限大や不正な入力範囲などを拒否します。同じ拡張子のあらゆる変種を読めるわけではありません。

src/lut.ts · v0.16.0
L19–38
Photoshotの実装コード ↗
export function validateCube(c: Cube) {
  if (
    !c ||
    !Number.isInteger(c.size) ||
    c.size < 2 ||
    c.size > MAX_SIZE ||
    typeof c.name !== 'string' ||
    c.name.length > 200 ||
    !Array.isArray(c.min) ||
    !Array.isArray(c.max) ||
    c.min.length !== 3 ||
    c.max.length !== 3 ||
    c.min.some((x, i) => !num(x) || !num(c.max[i]) || x >= c.max[i]) ||
    !Array.isArray(c.data) ||
    c.data.length !== c.size ** 3 * 3 ||
    c.data.some((x) => !num(x) || Math.abs(x) > 65536)
  )
    fail();
  if (c.only1d !== undefined && typeof c.only1d !== 'boolean') fail();
  if (c.shaper) {
Photoshot無料Photoshop代替の開発記 / 12IMPLEMENTATION

計算の
内側へ。

格子の間の色を求める

入力曲線を適用した後、範囲を正規化して格子座標を求めます。整数部がセル、小数部がセル内の位置です。三線形補間は8頂点、四面体補間は選択した4頂点を使います。

補間法は結果に影響します。本日、1Dと3Dの順序、軸の順序、二つの補間経路の既存検査を再実行しました。きれいな作例だけではパーサーの正しさを判定できません。

ICCには実際の外部エンジンを組み込む

ICCではヘッダーとタグ範囲を検査し、LittleCMSで変換を構成してsRGB格子65³点をサンプリングします。その後は共通LUT経路で補間します。

対象はLab/XYZ AbstractとRGB→RGB Device Linkです。一般のモニタープロファイルやCMYK Device Linkは受け付けません。各画素で元のICC変換を直接実行する方式とも異なります。

PHOTOSHOT / SOURCE NOTES12

コードを読む。

src/adjustments.ts · v0.16.0
L777–790
Photoshotの実装コード ↗
    if (t === 'lookup' && cube) {
      const cr = shaperValue(cube, 0, r),
        cg = shaperValue(cube, 1, g),
        cb = shaperValue(cube, 2, b);
      const size = cube.size,
        rx = clamp((cr - cube.min[0]) / (cube.max[0] - cube.min[0])) * (size - 1),
        gy = clamp((cg - cube.min[1]) / (cube.max[1] - cube.min[1])) * (size - 1),
        bz = clamp((cb - cube.min[2]) / (cube.max[2] - cube.min[2])) * (size - 1),
        lr = Math.floor(rx),
        lg = Math.floor(gy),
        lb = Math.floor(bz),
        fr = rx - lr,
        fg = gy - lg,
        fb = bz - lb;
src/icc.ts · v0.16.0
L32–37
Photoshotの実装コード ↗
  if (kind === 'link' && (input !== 'RGB ' || output !== 'RGB '))
    throw Error('현재 RGB → RGB Device Link만 지원합니다.');
  if (kind === 'abst' && (!['Lab ', 'XYZ '].includes(input) || !['Lab ', 'XYZ '].includes(output)))
    throw Error('Lab/XYZ Abstract 프로파일만 지원합니다.');
  engine ??= instantiate(
    wasmBinary
src/icc.ts · v0.16.0
L65–77
Photoshotの実装コード ↗
      transform(p, input === 'Lab ' ? LAB : XYZ, 0, output === 'Lab ' ? LAB : XYZ);
      transform(last, output === 'Lab ' ? LAB : XYZ, srgb, RGB);
    }
    const size = 65,
      total = size ** 3,
      data: number[] = [];
    for (let start = 0; start < total; start += 4096) {
      const count = Math.min(4096, total - start);
      let values = new Float32Array(count * 3);
      for (let i = 0; i < count; i++) {
        const index = start + i;
        values[i * 3] = (index % size) / (size - 1);
        values[i * 3 + 1] = (Math.floor(index / size) % size) / (size - 1);
Photoshot無料Photoshop代替の開発記 / 12IN PRACTICE

エディターで見る。

実際のPhotoshot作業画面。設定と写真を一緒に確認できます。
実際のPhotoshot作業画面。設定と写真を一緒に確認できます。

作例と検査の範囲を分ける

保存作例はPhotoshot / Soft Film、四面体補間、ディザリングオフ、不透明度100%です。内蔵プリセットの例であり、ICC読み込み画面ではありません。

本日19グループのアップグレード検査に加え、恒等Link・反転Link・恒等Abstract、不正なICCタグ範囲の拒否を確認しました。既存エンジンと独自の解析・操作画面をつないだ例です。次回は短い反転式を編集構造に接続します。

Photoshot無料Photoshop代替の開発記 / 12BEFORE / AFTER

写真で見る結果。

AI生成の動作例用写真 / 編集前
01 / AI生成の動作例用写真 / 編集前
Photoshotの出力 / 編集結果
02 / Photoshotの出力 / 編集結果

AI生成の動作例用写真。編集画面と出力画像は実際のPhotoshotで作成しました。