こんにちは。
Kiro と Claude Opus だけで、画像からアイロンビーズの図案を生成する Web アプリを作りました。アイロンビーズ(パーラービーズ)の図案作りが面倒で、自分で作ることにしたやつです。
デモ: https://main.d3jfjapbrozdy8.amplifyapp.com/
コード: https://github.com/hiro-sys/iron-beads-designer
開発環境
Kiro + Claude Opus 4.8
Kiro は AWS が提供する AI 搭載 IDE です。今回は Anthropic の Claude Opus 4.8 をバックエンドに使いました。
Spec 駆動開発という仕組みがあって、要件定義(requirements.md)→ 技術設計(design.md)→ タスク一覧(tasks.md)の順に文書を固めながら開発を進められます。「何を作るか」が先に確定しているので、実装中に仕様が揺れるということが起きにくいです。
今回も自分でアイデアを出して相談し、Kiro がコードを書く、といういつもどおりのスタイルで進めました。
作ったもの
- 画像アップロード(JPEG/PNG/GIF/WebP、最大 10MB、ドラッグ&ドロップ対応)
- パーラービーズ(100色)/ ナノビーズ(55色)への自動変換
- プレート構成の指定(1×1 〜 10×10)とおすすめサイズ提案
- 背景除外、使用色の制限・減色、リサイズ方式とフィットモードの選択
- 図案の手動編集(ドラッグによる連続塗り対応)
- 使用色一覧の表示と PNG エクスポート
技術スタック:
| 領域 | 内容 |
|---|---|
| 言語 | Vanilla JavaScript(ES モジュール) |
| ビルド | Vite |
| レンダリング | HTML5 Canvas API |
| テスト | Vitest + fast-check(プロパティベーステスト) |
| ホスティング | AWS Amplify Hosting |
すべてブラウザで完結します。
実装でこだわったところ
CIE76 色差計算による最近傍色マッチング
図案生成の核になる部分です。各ピクセルを「パレットの中で一番近い色」に変換します。
RGB のままユークリッド距離を測ると、人間の目には違う色に見えるのに「近い」と判定されやすいケースが出ます(濃い青と紫など)。CIE Lab 色空間に変換してから距離を計算することで、視覚的に近い色へのマッチングになります。
CIE76 より精度の高い CIEDE2000 も検討しましたが、100色程度のパレットで数千ピクセルを変換する用途では、CIE76 でも目で見て十分実用的な結果が得られ、実装がシンプルな分バグも潜りにくいので採用しました。
// RGB → sRGB リニア化 → XYZ → Lab(D65 白色点基準)
export function rgbToLab(r, g, b) {
const toLinear = (c) => {
const s = c / 255;
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
};
const lr = toLinear(r), lg = toLinear(g), lb = toLinear(b);
const x = lr * 0.4124564 + lg * 0.3575761 + lb * 0.1804375;
const y = lr * 0.2126729 + lg * 0.7151522 + lb * 0.0721750;
const z = lr * 0.0193339 + lg * 0.1191920 + lb * 0.9503041;
const f = (t) => t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
const fx = f(x / 0.95047), fy = f(y), fz = f(z / 1.08883);
return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
}
export function deltaE(lab1, lab2) {
return Math.sqrt(
(lab1.L - lab2.L) ** 2 + (lab1.a - lab2.a) ** 2 + (lab1.b - lab2.b) ** 2
);
}
Lab 値は起動時に initializePalette で全色分キャッシュします。変換時に毎回計算するのは対象ピクセル側だけになるので、100色 × 数千ピクセルでも十分実用的な速度です。
変換パイプラインの設計
変換処理は LocalConversionStrategy の中でこの順序で流れます。
フィット/リサイズ
→ 透明ピクセル判定(alpha < 128 → 未配置 null)
→ 白背景合成(128 ≤ alpha < 255 の半透明)
→ 減色(最大色数指定時のみ)
→ パレット最近傍色マッチング(CIE76)
→ 背景除外(ON の場合)
この順序にはいくつか理由があります。透明ピクセルの判定を最初に行わないと、透明部分が白や黒に変換されます。背景除外をパレットマッチングの後に置いているのは、「どのビーズ色が背景に近いか」をパレット色空間で判定するためです。逆にすると生ピクセル色とパレット色の色空間が混在してズレが起きます。
Strategy パターンで変換エンジンを差し替え可能にした
当初、Amazon Bedrock による変換も検討していました。画像をマルチモーダルで投げ、AI に直接「ビーズ色のグリッド」を JSON で出力させようとしました。
API コストやレスポンス速度、1 マス単位の精度の観点から、まずは確実に動くローカルパイプラインで完成させることにしました。Bedrock の利用は「将来の課題」として、変換エンジンを Strategy パターンで抽象化しています。
// ConversionStrategy.js — インターフェース定義
/**
* @typedef {Object} ConversionStrategy
* @property {function(HTMLImageElement, ConversionOptions): PatternGrid} convert
*/
将来 BedrockConversionStrategy を追加しても、呼び出し元のコードは変えなくていい構造です。
LocalConversionStrategy(現在)
↓
BedrockConversionStrategy(将来)
↓
その他のアルゴリズム
カラーパレット 100色 / 55色
パーラービーズは 100色、ナノビーズは 55色を定義しています。カワダの公式カラーリストに色名を準拠させていますが、RGB 値は数値で公開されていないため、カラーチャートの見た目から近似値を作っています。
export const PARLER_PALETTE = [
{ id: 'P01', name: 'しろ', r: 241, g: 241, b: 241 },
{ id: 'P02', name: 'クリーム', r: 255, g: 246, b: 207 },
// ... 100色
];
各色は { id, name, r, g, b } の独立したレコードです。将来 RGB 値だけ実測値に差し替えても、id と色名はそのまま維持できる構造にしています。Lab 値はここには書かず、実行時に initializePalette で付与します。
プロパティベーステスト
色差計算や座標変換など「数学的な性質を持つ関数」は、fast-check でプロパティテストを書きました。通常のテストが「この入力に対してこの出力」を検証するのに対し、プロパティテストは「任意の入力に対してこの性質が常に成り立つ」を検証します。手書きのテストケースでは気づきにくいエッジケース(座標が負のケース、alpha=0 のピクセルなど)も自動的に試してくれます。
deltaE の非負性・対称性、findClosestColor の最小保証、グリッドサイズの計算など、23 個のプロパティを定義しています。
Kiro を使った開発体験
Spec 駆動開発の流れ
「何を作りたいか」を話すと、Kiro が requirements.md → design.md → tasks.md の順に文書を作ります。各フェーズでレビューして確認してから次へ進む流れです。
今回は requirements.md で 13 要件を確定させ、design.md でパイプラインの順序や色空間整合性の設計を固めました。「背景除外はパレットマッチングの後」という実装の制約も、設計段階で決まっていたので実装中に迷うことがなかったようです。
tasks.md には依存関係に基づいて Wave に分割されたタスクが並んでいます。独立したものは並列実行してくれます。今回は合計 264 テストが全パスする状態まで自動実装が進みました。
通常のチャット型 AI との違いは、「なぜそう設計するか」が文書として残る点だと思います。実装を進めながら「この関数はなんのためにあるんだっけ」となったときに、design.md を見れば理由が書いてあります。
AWS Amplify へのデプロイ
モノレポ構成での落とし穴
リポジトリルートに bead-pattern-maker/ というサブフォルダがあるモノレポ構成です。ルートに amplify.yml を置いて appRoot を指定することで対応しています。
version: 1
applications:
- appRoot: bead-pattern-maker
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: dist
files:
- '**/*'
cache:
paths:
- node_modules/**/*
Amplify コンソールには「私のアプリはモノレポです」というチェックボックスがありますが、amplify.yml に appRoot が書いてある場合はチェック不要で、自動で読み込まれます。
セキュリティヘッダー
静的サイトでもセキュリティヘッダーはちゃんと設定したかったので、Amplify コンソールのカスタムヘッダーで CSP、HSTS、X-Frame-Options などを設定しました。
モノレポ構成では amplify.yml や customHttp.yml にヘッダーを書いても効かない制約があります。コンソールから設定するのが確実です。ちなみに、securityheaders.com で A+ 評価でした。
学び
- Lab 色空間での距離計算は体感で違いがわかる。RGB のままだと「なんか色がズレる」が起きやすく、CIE76 に切り替えると整う
- 変換パイプラインの順序は設計段階で決めておくと楽。特に「背景除外はパレットマッチングの後」という制約は、後から変えると影響範囲が広い
- Strategy パターンは「将来の拡張」を保留するのに便利。Bedrock 統合を先送りしつつ、インターフェースだけ定義しておける
-
AWS Amplify のモノレポ対応は
amplify.ymlで完結する。appRootを書いておけばコンソールの設定は最小限でいい。ただし拡張子は.ymlのみ - Spec 駆動開発は、設計上の判断が文書として残る。「なぜこの順序か」「なぜこのアーキテクチャか」が requirements.md / design.md に書かれていて、実装中に戻れる場所がある。チャット型の AI 開発とはここが一番異なる
免責事項
本アプリはカワダ株式会社および関係各社とは一切関係のない、個人制作の非公式ファンメイドツールです。「パーラービーズ」「ナノビーズ」はカワダ株式会社の登録商標です。本アプリについての問い合わせをカワダ公式窓口へ行うことは絶対にしないでください。
記載されている会社名、製品名、サービス名、ロゴ等は各社の商標または登録商標です。
