
無料Photoshop代替の開発記
赤を選んで変える
無料の画像編集ソフトを作るPhotoshot開発記です。今回は全体の色を回すのではなく、赤や緑などの系統ごとに成分を変える選択カラー。実装の重みと相対値の計算を読み、新しい人物写真の服・肌・背景を比較しました。
AI生成の動作例用写真。編集画面と出力画像は実際のPhotoshotで作成しました。
ソースから始める。
色の選択は物体の選択ではない
赤い服を変えるために赤系統を選んでも、編集ソフトが服を認識するわけではありません。唇や肌の赤成分も同じ計算の対象になります。服だけに限定するには、調整レイヤーのマスクで空間的な範囲も指定する必要があります。
選択カラーは画素の色系統に重みを付け、シアン・マゼンタ・イエロー・ブラックの値をRGBの変化に結び付けます。CMYKという名前の調節値を使っても、印刷用CMYKプロファイルへ文書を変換する機能ではありません。
九つの系統を重みで表す
公開コードには赤・黄・緑・シアン・青・マゼンタ・白・中間色・黒があります。RGBの最大値hi、最小値lo、中間値midを求め、赤の重みはRが最大ならhi−mid、そうでなければ0です。他のチャンネルとの差によって適用量が変わります。
白は最小チャンネルが0.5を超える程度、黒は最大チャンネルが0.5を下回る程度を使います。中間色は1−|hi+lo−1|です。これらは排他的な分類ではなく、複数系統の調整が同じ画素で加算される場合があります。
if (t === 'selective') {
const hi = Math.max(r, g, b),
lo = Math.min(r, g, b),
mid = r + g + b - hi - lo;
weights[0] = r === hi ? hi - mid : 0;
weights[1] = b === lo ? mid - lo : 0;
weights[2] = g === hi ? hi - mid : 0;
weights[3] = r === lo ? mid - lo : 0;
weights[4] = b === hi ? hi - mid : 0;
weights[5] = g === lo ? mid - lo : 0;
weights[6] = clamp((lo - 0.5) * 2);
weights[7] = 1 - Math.abs(hi + lo - 1);
weights[8] = clamp((0.5 - hi) * 2);
const input = scratch;計算の
内側へ。
相対値と絶対値では補正量が異なる
各RGBチャンネルには対応するC・M・YとKの値が働きます。相対値では色成分に1−入力チャンネル値、黒成分に入力チャンネル値を掛けます。相対値をオフにすると両方の倍率が1になります。どちらも色系統の重みを使い、出力を0〜1に制限します。
たとえばマゼンタを増やすと緑チャンネルを減らす方向に作用します。絶対値でも全画素が同じだけ変わるわけではなく、系統の重みは残ります。これは引用したPhotoshot実装の説明であり、Adobe Photoshopとの数値的一致を示しません。
赤いニットと緑の葉を比較する
2026年10月22日、新しいAI人物写真を公開中のPhotoshotで開きました。赤系統だけをC 0、M +35、Y −25、K +10に設定。他の八系統は0、相対値オン、Normal、不透明度100%、空間的なマスク制限なしで保存しました。
原画像と結果は同じ1536×1024です。服がマゼンタ寄りになり、唇や肌にも変化が見えます。シアンの背景と緑の葉は比較的維持されます。人物はAI生成モデルであり、芸能人の実写真や推薦ではありません。実際の画面、出力PNG、再編集用プロジェクトを保存しました。
コードを読む。
let delta = 0;
for (let k = 0; k < 9; k++) {
const ink = v[k + 'CMY'[c]] / 100,
black = v[k + 'K'] / 100;
delta +=
weights[k] *
(ink * (v.relative ? 1 - input[c] : 1) + black * (v.relative ? input[c] : 1));
}
out[c] = clamp(input[c] - delta);
}
}
if (t === 'lookup' && cube) {
const cr = shaperValue(cube, 0, r), selective: {
name: '선택 색상',
description: '색상 계열별로 청록·자홍·노랑·검정 성분을 조절합니다.',
controls: colorNames
.flatMap((_, i) =>
['C', 'M', 'Y', 'K'].map((c, j) =>
control(i + c, ['청록', '자홍', '노랑', '검정'][j], -100, 100, 0, 1, String(i)),
),
)
.concat(control('relative', '상대값', 0, 1, 1)),
},check('selective color targets one color family', () => {
const a = A.makeAdjustment('selective');
a.values['0M'] = 100;
const p = Uint8ClampedArray.from([220, 90, 90, 255, 90, 220, 90, 255]);
A.adjustPixels(p, a);
assert(p[1] < 90);
close(Array.from(p.slice(4)), [90, 220, 90, 255]);
});エディターで見る。

検証範囲と次の開発段階
公開ソースの基準はコミットd08d365です。2026年10月22日にadjustment-renderの22検査グループが通過しました。選択カラー専用検査では赤い画素のマゼンタを増やし、緑チャンネルの低下と別の緑画素の維持を確認します。共通検査にはアルファ、調整マスク、合成、プロジェクト互換性も含まれます。
公開画面から保存したプロジェクトでも赤の四値、relative=1、他系統の0、不透明度1を照合しました。すべての写真の色境界や印刷処理を保証するものではありません。18回からはメニューと機能検証表を扱い、公開リポジトリーと運用実装の範囲を明確に分けます。
写真で見る結果。


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