DEV Community

hirosys
hirosys

Posted on

I Built a Fuse Bead Pattern Generator Using Kiro and Claude Opus, Deployed on AWS Amplify

Hello everyone!

I built a web application that automatically generates fuse bead (Perler/Nano beads) patterns from any uploaded image using Kiro and Claude Opus. I love making fuse bead crafts, but creating custom patterns manually is always a hassle—so I decided to automate it!

Application Screenshot

Development Environment

Kiro + Claude Opus 4.8

Kiro is an AI-powered IDE provided by AWS. For this project, I used Anthropic's Claude Opus 4.8 as the backend.

Kiro features an excellent workflow called Spec-Driven Development. It allows you to build software systematically by solidifying documents in order: Requirements (requirements.md) → Technical Design (design.md) → Task List (tasks.md). Because the "what to build" phase is completely locked in early on, your specifications rarely drift during implementation.

My workflow was the same as usual: I brainstormed ideas and discussed them with Kiro, and Kiro handled the code implementation.


What I Built

  • Image Upload: Supports JPEG, PNG, GIF, and WebP up to 10MB, with drag-and-drop.
  • Auto-Quantization: Converts images into Perler Bead (100 colors) or Nano Bead (55 colors) palettes.
  • Pegboard Configuration: Customizable grid configurations from 1×1 up to 10×10 boards, featuring an "optimal size recommendation" engine.
  • Advanced Adjustments: Background removal, color limiting/reduction, resizing method selection, and fit-mode options.
  • Manual Editor: Click-and-drag continuous drawing to manually fix individual pixels.
  • Color Inventory: Lists the exact bead count per color and supports PNG pattern exporting.

Tech Stack

Layer Technology
Language Vanilla JavaScript (ES Modules)
Build Tool Vite
Rendering HTML5 Canvas API
Testing Vitest + fast-check (Property-Based Testing)
Hosting AWS Amplify Hosting

Everything runs entirely on the client side inside the browser!


Architectural Deep Dive & Key Features

1. Nearest Neighbor Color Matching via CIE76 Color Difference

This is the core engine of the pattern generator. It translates every pixel of an uploaded image into the "closest matching color" available in the actual physical bead palette.

Measuring distance using standard Euclidean distance in the RGB space often fails because colors that look completely distinct to human eyes (e.g., deep blue vs. purple) can be mathematically classified as "close". To fix this, the application transforms the RGB values into the CIE Lab color space before calculating the distance, ensuring visually accurate matching.

While I considered CIEDE2000 (which offers higher precision), CIE76 provided practically perfect visual results for a ~100-color palette mapping a few thousand pixels. Plus, the implementation is significantly simpler and much less error-prone.

// RGB → sRGB Linearization → XYZ → Lab (D65 Reference White)
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
  );
}

Enter fullscreen mode Exit fullscreen mode

To optimize performance, the Lab values of all official palette colors are cached at application startup using initializePalette. This means during image processing, we only compute the conversion for the incoming image pixels. Mapping thousands of pixels against 100 colors completes instantly.

2. Processing Pipeline Design

Image processing flows inside LocalConversionStrategy using the following precise execution order:

Fit/Resize 
  → Transparent Pixel Detection (alpha < 128 → Unplaced 'null')
  → White Background Blending (128 ≤ alpha < 255 translucent blending)
  → Color Reduction (Only if max color limit is specified)
  → Palette Nearest-Neighbor Matching (CIE76)
  → Background Removal (If toggled ON)

Enter fullscreen mode Exit fullscreen mode

This specific sequence is intentional. If you do not isolate transparent pixels first, transparent regions get filled with arbitrary nearest matches like solid white or black. Furthermore, background removal runs after palette matching because the engine detects backgrounds by matching "which bead color represents the background" within the standardized palette space. Swapping these steps would mix raw pixel color spaces with palette spaces, introducing visual artifacts.

3. Decoupling with the Strategy Pattern

Initially, I wanted to leverage Amazon Bedrock for image-to-pattern processing. The idea was to send images to a multimodal LLM and have it directly return a structured JSON grid of bead color IDs.

However, considering API latency, cost, and strict pixel-perfect precision requirements, I opted to deliver a bulletproof client-side local pipeline first. To keep the Bedrock implementation open as a future enhancement, I abstracted the execution engine using the Strategy Pattern.

// ConversionStrategy.js — Interface Definition
/**
 * @typedef {Object} ConversionStrategy
 * @property {function(HTMLImageElement, ConversionOptions): PatternGrid} convert
 */

Enter fullscreen mode Exit fullscreen mode

Thanks to this decoupling, introducing a BedrockConversionStrategy in the future won't require modifying any of the core UI components or orchestrating code.

LocalConversionStrategy (Current)
  ↓
BedrockConversionStrategy (Future Extension)
  ↓
Alternative Custom Algorithms

Enter fullscreen mode Exit fullscreen mode

4. Replicating the Official Palette

The application includes data structures for 100 Perler Bead colors and 55 Nano Bead colors. While the color names strictly align with official Kawada color charts, exact RGB values are not published. I generated closely approximated RGB definitions based on physical reference charts.

export const PARLER_PALETTE = [
  { id: 'P01', name: 'White',  r: 241, g: 241, b: 241 },
  { id: 'P02', name: 'Cream',  r: 255, g: 246, b: 207 },
  // ... 100 colors
];

Enter fullscreen mode Exit fullscreen mode

Each color is modeled as a flat record { id, name, r, g, b }. If more accurate spectroradiometer measurements become available, I can simply update the RGB fields without breaking the application's underlying structural identifiers.

5. Property-Based Testing

For functions dealing with pure mathematical properties—such as color distance and coordinate mappings—I implemented Property-Based Testing using fast-check.

Unlike traditional unit tests that assert "Given input A, assert output B", property-based testing validates that a given invariant property holds true for an infinite set of randomized inputs. It is incredible at catching edge cases you would easily overlook manually, such as negative indices, out-of-bound coordinates, or zero-alpha pixels.

I defined 23 different mathematical invariants, including the non-negativity and symmetry properties of deltaE, the bounds-guarantees of findClosestColor, and grid dimension constraints.


Development Experience with Kiro

When you explain an idea to Kiro, it translates your vision into requirements.md, followed by design.md, and finally tasks.md. You review and approve the documents at each milestone before any implementation begins.

For this project, we solidified 13 concrete requirements in requirements.md and settled the entire color space conversion strategy and processing pipeline hierarchy inside design.md. Because the constraints were ironed out beforehand, the AI didn't encounter any mid-development dead-ends.

The generated tasks.md listed items divided into architectural waves based on dependency trees. Kiro can run independent tasks in parallel, automatically writing implementation code and driving the project forward until all 264 unit and property tests successfully passed.

The primary difference between Kiro and standard conversational chat AI models is that architectural intent remains perfectly documented. If you ever look at a function down the road and wonder why it was constructed a certain way, you can just open design.md to see the exact reasoning behind it.


Deploying to AWS Amplify Hosting

Monorepo Workaround

The project repository uses a monorepo setup where the web application lives inside a subfolder named bead-pattern-maker/. To make this work seamlessly on AWS Amplify, I placed a custom amplify.yml at the root of the repository to specify the 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/**/*

Enter fullscreen mode Exit fullscreen mode

Note: Although the AWS Amplify Console features a checkbox for "My app is a monorepo," if you already explicitly provide an appRoot inside your custom amplify.yml, you do not need to toggle it on. Amplify discovers and parses it automatically.

Configuring Security Headers

To ensure enterprise-grade security for this static deployment, I configured custom security headers directly in the Amplify Console. This includes tuning Content Security Policy (CSP), HTTP Strict Transport Security (HSTS), and X-Frame-Options.

Keep in mind that within monorepo setups, headers defined inside text configurations like customHttp.yml might occasionally be skipped depending on your build override parameters. Applying them via the Amplify Console UI is the most robust method. The app currently scores a perfect A+ ranking on securityheaders.com!


Key Takeaways

  1. Lab space calculations are vastly superior to RGB for human vision. Using direct RGB calculations often leads to awkward mismatches, whereas shifting to CIE76 instantly aligns colors with human perception.
  2. Lock down your data pipeline early. Deciding the sequence of transformations (especially executing background isolation after palette mapping) saved a massive amount of refactoring effort later on.
  3. Use the Strategy Pattern for future-proofing. It allows you to build a reliable local processing engine right now while leaving the doors wide open for seamless integration with heavy-duty LLM microservices (like Amazon Bedrock) later.
  4. Amplify monorepos are effortless with amplify.yml. Explicitly setting the appRoot keeps console configurations clean. Just remember that it only respects the .yml file extension!
  5. Spec-Driven Development reduces engineering friction. Having a permanent architectural reference point (requirements.md / design.md) to look back on keeps both human developers and generative AI engines completely aligned throughout a project.

Disclaimer

This application is an unofficial, personal fan-made project. It is entirely unaffiliated with Kawada Co., Ltd. or any of its subsidiaries. "Perler Beads" and "Nano Beads" are registered trademarks of Kawada Co., Ltd. Please do not contact official support lines regarding this application.

All other company names, product names, and logos are trademarks or registered trademarks of their respective owners.

Reference Links

Top comments (0)