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

フォトフィルターの光と色

色変換の数学、フィルターの考え方、レイヤー合成のコードはすでにありました。必要だったのは、一つの文書と設定へつなぐことです。AIとフォトフィルターを修正した過程から、10回にわたる再利用と接続が代替エディターの機能として積み重なる様子をまとめます。

PHOTOSHOT DEVELOPMENT JOURNAL / 10

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

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

ソースから始める。

部品を持つことと製品をつくることの間

フォトフィルターには既知の色変換と既存の調整エンジンを利用できました。ただし濃度、レイヤーの不透明度、マスクの役割は分ける必要があります。AIと書いたコードが、それらを同じ文書内で正しい順番に実行するよう接続しました。

Adobeの文書はフィルター色・濃度・輝度維持オプションの意味を確認する資料です。ただし、この計算を特定の外部GitHubリポジトリから導入した記録は確認できませんでした。ここで示すのはポトショット自身の公開実装です。

設定の保存とマスク・不透明度の合成は既存処理を再利用し、フィルター色を画素変化へ変換する計算をAIと作成・修正しました。出所の説明はリポジトリのリンクを付けるだけでは足りません。

初期版は元の色と直接混ぜた

初期実装はamountを濃度÷100とし、元の色×(1−amount)+フィルター色×amountで混ぜました。読みやすい出発点で、維持オプションを有効にすると後から輝度補正を呼びます。

公開コードでは、この式をrevision 2以外のプロジェクト用に残しています。新しい計算を追加しても、古いファイルの処理を黙って置き換えないためです。

最初の計算をつなげられたのは、設定保存とレイヤー合成がすでにあったからです。その上で計算を修正し、以前の文書の経路を残せました。再利用できる構造は、AIと書いたコードを追加するときにも修正するときにも役立ちます。

src/adjustments.ts · v0.16.0
L702–706
Photoshotの実装コード ↗
if (t === 'photo' && a.revision !== 2) {
  const amount = v.density / 100;
  for (let c = 0; c < 3; c++) out[c] = out[c] * (1 - amount) + colors![0][c] * amount;
  if (v.preserve) preserveLuma(out, luma(r, g, b));
}
Photoshot無料Photoshop代替の開発記 / 10IMPLEMENTATION

計算の
内側へ。

線形RGBで行列を適用する

revision 2ではRGBを線形値へ変換します。フィルター色と濃度からgainを作り、コード内のRGB↔XYZ行列と組み合わせてphotoMatrixを準備します。準備は画素ごとではなく、調整処理を構成する段階で行います。

各画素では行列の各行と線形RGBの積和を求め、範囲を制限してsRGBへ戻します。単にフィルター色を重ねる初期版から、チャンネルを変換する計算へ変わった部分です。

濃度とレイヤー不透明度は違う

濃度はフィルターの色変換自体を構成する値です。不透明度は計算結果を元の画像へどれだけ合成するかを決め、マスクや合成モードもこの段階で働きます。文書エンジンは調整値、layer.opacity、mask、blendを別々にadjustPixelsへ渡します。

濃度22%と不透明度50%を使った結果が、一般に濃度11%と同じとは言えません。線形化・行列・輝度維持・合成が間にあるため、別の操作として残しています。

PHOTOSHOT / SOURCE NOTES10

コードを読む。

src/adjustments.ts · v0.16.0
L581–586
Photoshotの実装コード ↗
const photoMatrix =
  t === 'photo'
    ? inv.map((row) =>
        [0, 1, 2].map((c) => row.reduce((sum, x, i) => sum + x * gain[i] * xyz[i][c], 0)),
      )
    : [];
src/adjustments.ts · v0.16.0
L707–718
Photoshotの実装コード ↗
if (t === 'photo' && a.revision === 2) {
  scratch[0] = linear(r);
  scratch[1] = linear(g);
  scratch[2] = linear(b);
  for (let c = 0; c < 3; c++)
    out[c] = srgb(clamp(photoMatrix[c].reduce((sum, x, i) => sum + x * scratch[i], 0)));
  if (v.preserve) {
    scratch[0] = r;
    scratch[1] = g;
    scratch[2] = b;
    blendRGB(scratch, out, 'color', out);
  }
src/document.ts · v0.16.0
L284–293
Photoshotの実装コード ↗
adjustPixels(
  pixels.data,
  layer.adjustment,
  layer.opacity,
  mask,
  layer.blend,
  start,
  Math.min(start + chunk, pixels.data.length),
  compiled,
);
Photoshot無料Photoshop代替の開発記 / 10IN PRACTICE

エディターで見る。

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

実例とここまでの開発のつながり

写真例はCooling (80)、フィルター色#006dff、濃度22%、輝度維持オンです。AI生成原画像を実際の編集画面で処理して出力しました。改善後の維持経路はフィルター後にcolor合成を適用します。厳密な輝度保存やAdobeとの全面的な一致を保証するものではありません。

10回を通じた論点は、技術がなかったところへAIがすべてを発明したということではありません。多くの機能を実装するライブラリ、公開例、既知の計算はすでにあり、AIはそれらを読み、つなぎ、不足部分を書いて修正する作業を助けました。Photoshotはその組み合わせで複数のPhotoshop型の編集機能を実装した記録です。製品全体や全出力の一致、開発速度の定量比較を示すものではありません。

6〜10回は異なる計算を使いますが、原画像を残し、設定を保存し、マスクと合成を扱う構造は共通です。GitHubで読んだ例、既存コード、AIと新しく書いた計算をそこへ接続しました。次回はチャンネルミキサーで、RGB各成分が出力チャンネルへ寄与する割合を調整します。

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

写真で見る結果。

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

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