
無料Photoshop代替の開発記
ファイルから写真の色へ
LUTの解析、補間、ICC色管理エンジンはすでに存在しました。異なる入力を検証し、同じ調整レイヤーにつなぐことが開発の仕事です。参考資料と実際に組み込んだ構成要素を区別します。
AI生成の動作例用写真。編集画面と出力画像は実際のPhotoshotで作成しました。
ソースから始める。
色の表と色管理エンジン
色変換表を読むだけでは完了しません。解析、データ検証、座標計算、補間、合成をつなぐ必要があります。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個の値が必要で、無限大や不正な入力範囲などを拒否します。同じ拡張子のあらゆる変種を読めるわけではありません。
L19–38Photoshotの実装コード ↗
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) {計算の
内側へ。
格子の間の色を求める
入力曲線を適用した後、範囲を正規化して格子座標を求めます。整数部がセル、小数部がセル内の位置です。三線形補間は8頂点、四面体補間は選択した4頂点を使います。
補間法は結果に影響します。本日、1Dと3Dの順序、軸の順序、二つの補間経路の既存検査を再実行しました。きれいな作例だけではパーサーの正しさを判定できません。
ICCには実際の外部エンジンを組み込む
ICCではヘッダーとタグ範囲を検査し、LittleCMSで変換を構成してsRGB格子65³点をサンプリングします。その後は共通LUT経路で補間します。
対象はLab/XYZ AbstractとRGB→RGB Device Linkです。一般のモニタープロファイルやCMYK Device Linkは受け付けません。各画素で元のICC変換を直接実行する方式とも異なります。
コードを読む。
L777–790Photoshotの実装コード ↗
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;L32–37Photoshotの実装コード ↗
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(
wasmBinaryL65–77Photoshotの実装コード ↗
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 / Soft Film、四面体補間、ディザリングオフ、不透明度100%です。内蔵プリセットの例であり、ICC読み込み画面ではありません。
本日19グループのアップグレード検査に加え、恒等Link・反転Link・恒等Abstract、不正なICCタグ範囲の拒否を確認しました。既存エンジンと独自の解析・操作画面をつないだ例です。次回は短い反転式を編集構造に接続します。
写真で見る結果。


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