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

色を組み立て直す

チャンネルミキサーで新しい色の数学を発明したわけではありません。既知のRGB加重和を、既存の調整レイヤー、出力選択、定数、保存設定にAIと接続した過程です。

PHOTOSHOT DEVELOPMENT JOURNAL / 11

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

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

ソースから始める。

色を混ぜる計算はすでにあった

元画像のRGBが各出力にどれだけ寄与するかを決める計算は、すでに知られていました。設定保存、マスク、不透明度の合成も既存の基盤です。AIと計算・操作画面・文書構造を同じ機能としてつなぎました。

Adobeの文書は出力チャンネルとモノクロの意味を確認する資料です。以下はPhotoshotの公開実装からの抜粋であり、動作の参考資料とコードの出典を区別しています。

出力と入力のチャンネルを分ける

赤出力の選択は、元画像の赤だけを変える意味ではありません。出力ごとに入力R・G・Bの比率と定数を指定します。三つの出力で数値設定は合計12個です。

0〜1のRGBに各比率を掛け、定数を加えて100で割り、0〜1に制限します。合計表示は案内であり、比率を自動で100%に正規化しません。

src/adjustments.ts · v0.16.0
L166–184
Photoshotの実装コード ↗
  mixer: {
    name: '채널 혼합',
    description: '출력 채널을 원본 RGB 채널의 비율로 만듭니다.',
    controls: ['r', 'g', 'b']
      .flatMap((c) =>
        ['R', 'G', 'B', 'Constant'].map((v, i) =>
          control(
            c + v,
            ['빨강', '초록', '파랑', '상수'][i],
            -200,
            200,
            c === v.toLowerCase() ? 100 : 0,
            1,
            c,
          ),
        ),
      )
      .concat(control('mono', '단색', 0, 1)),
  },
Photoshot無料Photoshop代替の開発記 / 11IMPLEMENTATION

計算の
内側へ。

定数と不透明度は別の段階

定数+10は成分に0.1を加えるため、明るい値は上限に達する場合があります。不透明度は計算後の画像と元画像を合成する段階で働きます。

説明用RGB 170・130・110の赤と青を交換すると110・130・170。70%で合成すると丸め前は128・130・152です。写真から採取した測定値ではありません。

同じ構造でモノクロも扱う

モノクロでは、すべての出力が赤出力の設定を読みます。同じ加重和をR・G・Bに入れるのでグレーになります。

9回の白黒調整は色相を六つの範囲に分ける方式でした。チャンネルミキサーのモノクロはRGB成分を重み付けする方式です。似た見た目でも操作する基準が違います。

PHOTOSHOT / SOURCE NOTES11

コードを読む。

src/adjustments.ts · v0.16.0
L720–727
Photoshotの実装コード ↗
    if (t === 'mixer') {
      for (let c = 0; c < 3; c++) {
        const key = v.mono ? 'r' : 'rgb'[c];
        out[c] = clamp(
          (r * v[key + 'R'] + g * v[key + 'G'] + b * v[key + 'B'] + v[key + 'Constant']) / 100,
        );
      }
    }
src/adjustments.ts · v0.16.0
L836–853
Photoshotの実装コード ↗
    beforeRGB: RGB = [0, 0, 0],
    mixed: RGB = [0, 0, 0];
  for (let i = start; i < end; i += 4) {
    if (!data[i + 3]) continue;
    let amount = opacity * (mask ? mask[i + 3] / 255 : 1);
    if (blend === 'dissolve') amount = pixelNoise(i / 4) < amount ? 1 : 0;
    if (!amount) continue;
    compiled(data[i], data[i + 1], data[i + 2], out);
    for (let c = 0; c < 3; c++) beforeRGB[c] = data[i + c] / 255;
    if (a.values.dither && (a.type === 'lookup' || a.type === 'gradient'))
      for (let c = 0; c < 3; c++) out[c] = clamp(out[c] + (pixelNoise(i / 4) - 0.5) / 255);
    blendRGB(beforeRGB, out, blend, mixed);
    for (let c = 0; c < 3; c++)
      data[i + c] = Math.round(clamp(beforeRGB[c] + (mixed[c] - beforeRGB[c]) * amount) * 255);
  }
}
src/adjustments.ts · v0.16.0
L253–265
Photoshotの実装コード ↗
export function isIdentityAdjustment(a: Adjustment): boolean {
  if (a.type === 'lookup') return !a.cube;
  if (a.type === 'curves') return channels.every((c) => a.curves![c].every((p) => p.x === p.y));
  if (a.type === 'photo') return a.values.density === 0;
  if (['blackwhite', 'gradient', 'invert', 'posterize', 'threshold'].includes(a.type)) return false;
  if (a.bands?.some((b) => b.hue || b.saturation || b.lightness)) return false;
  return definitions[a.type].controls.every(
    (c) =>
      ['preserve', 'relative', 'skin', 'legacy', 'colorizeHue', 'colorizeSaturation'].includes(
        c.key,
      ) || (a.values[c.key] ?? c.initial) === c.initial,
  );
}
Photoshot無料Photoshop代替の開発記 / 11IN PRACTICE

エディターで見る。

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

写真とコードを照合する

保存プロジェクトと実画面はMonochromeオン、Red 21・Green 72・Blue 7・Constant 0、不透明度100%です。AI生成の人物画像を実際のPhotoshotで編集し、書き出しました。

本日、公開コードの16調整について恒等変換、アルファ、保存往復、合成の一致を再確認しました。Photoshop全体との同等性を示す検査ではありません。次回は外部の色変換表を読むカラールックアップです。

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

写真で見る結果。

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

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