Skip to content

Latest commit

 

History

History
390 lines (280 loc) · 17.8 KB

File metadata and controls

390 lines (280 loc) · 17.8 KB

Pixel Bead Studio: Image-to-Pixel-Bead Conversion Pipeline

A technical note for developers, AI agents, and contributors. This document describes the implementation currently present in the Pixel Bead Studio repository. It is intentionally implementation-oriented and does not describe unimplemented machine-learning features.

Project links

Abstract

Pixel Bead Studio converts a raster image into a structured, editable pixel-bead project. The output is not a decorative approximation of beads and not a single flattened preview. It is a versioned grid whose cells reference a physical bead palette.

The current pipeline is deterministic and browser-based:

image file
  -> file validation and browser decode
  -> contain/crop framing
  -> preview rasterization
  -> brightness/contrast/saturation adjustment
  -> optional edge-connected background removal
  -> resize to target grid
  -> active-palette selection
  -> CIEDE2000 nearest-color matching
  -> optional Floyd–Steinberg-style error diffusion
  -> PixelBeadProject
  -> editing, material counts, PNG/PDF export

No ML model is required for the conversion itself. The product UI, authentication, persistence, and cloud storage are separate layers around the image-conversion core.

1. Output contract: PixelBeadProject

The conversion result is designed to remain useful after the first preview. It can be edited, validated, persisted, transferred to the Creator, counted by material, migrated between project versions, and exported.

The core types are defined in lib/beads-core/types.ts.

type PaletteColor = {
  id: string;
  name: string;
  hex: string;
};

type PixelBeadProject = {
  version: 4;
  revision: number;
  id: string;
  title: string;
  sourceType: "image" | "manual" | "template";
  sourceImage?: string;
  templateId?: string;
  width: number;
  height: number;
  cells: number[];
  palette: PaletteColor[];
  paletteId: string;
  paletteVersion: string;
  settings?: PatternSettings;
  status: "draft" | "ready";
  createdAt: string;
  updatedAt: string;
};

The cell array is row-major:

cellIndex = row * width + column

Each non-negative cell is an index into palette. -1 represents an empty cell, normally produced from transparent pixels. The project invariant is:

cells.length === width * height

Project creation and migration are implemented in lib/beads-core/project.ts. The validator requires a non-empty palette, positive integer dimensions, valid cell indexes, and no more than 60,000 cells.

2. Input validation and browser decode

The browser entry point is generatePatternFromFile.

Supported file types:

  • image/jpeg
  • image/png
  • image/webp

The current maximum input size is 10 MB. The adapter validates MIME type and file size, creates an Object URL, decodes the image through the browser Image API, and reads pixels through Canvas.

The function accepts an AbortSignal. The UI uses this to cancel stale conversions when the user changes grid size, palette, framing, or image settings before the previous conversion completes.

The browser adapter is intentionally separate from the pure grid and color algorithms. A future open-source package can provide Node, browser, or Web Worker decoders without coupling the core to a specific runtime.

3. Grid dimensions and framing

The core accepts explicit width and height. It also supports legacy size presets for project compatibility:

Preset Width in cells
small 29
medium 58
large 87
xlarge 116
huge 145

Two framing modes are implemented:

  • contain: preserve the entire source image, scale it into the grid, and center it. Empty margins may remain;
  • crop: compute a centered source rectangle with the target grid aspect ratio and fill the grid.

The geometry functions are in lib/beads-core/pattern-engine.ts: resolveGridDimensions, letterboxRect, and centerCropRect.

The framing decision is explicit. The converter does not perform semantic subject detection or automatically decide which crop is aesthetically correct.

4. Preview rasterization and image adjustments

The converter first creates a preview Canvas for the selected source rectangle. The preview long edge is at least large enough for the target grid and is capped at 1024 pixels. Canvas image smoothing is enabled at high quality.

Adjustments are applied to RGBA pixels before quantization:

gray     = 0.299R + 0.587G + 0.114B
satColor = gray + (channel - gray) * saturation
contrast = (satColor - 128) * contrast + 128
result   = clamp(contrast + brightness, 0, 255)

The implementation uses brightness, contrast, and saturation values from the project settings. Values are clamped to the byte range after adjustment. The adjusted preview is also retained for comparison in the UI.

Relevant implementation: applyImageAdjustments in lib/beads-core/pattern-engine.ts.

5. Optional background removal

Background removal uses a color tolerance plus edge-connected flood fill. It does not globally remove every pixel close to the selected color.

Algorithm:

  1. Parse the user-selected six-digit HEX color.
  2. Seed the queue with matching pixels on the four image borders.
  3. Compare RGB distance against the normalized tolerance.
  4. Traverse four-connected neighbors only.
  5. Set Alpha to zero for every visited pixel.
  6. Leave disconnected interior regions unchanged.

This behavior avoids removing an interior area merely because it has the same color as the background. When transparentAsEmpty is enabled, pixels with Alpha below the transparency threshold become -1 cells during quantization.

Implementation: applyBackgroundRemoval in lib/beads-core/pattern-engine.ts.

6. Palette registry

Palette definitions are registered in lib/beads-core/palette.ts. The current registry contains:

  • MARD 221
  • Perler
  • Hama
  • Artkal S
  • COCO

Each palette definition contains color IDs, display names, HEX values, a version, and bead-pitch metadata. The default palette is MARD 221.

Palette data is a versioned input to conversion. A project stores both paletteId and paletteVersion, so later palette changes do not silently redefine an existing project.

7. Active palette selection

Using every color in a large brand palette for every image usually produces noisy material lists. The implementation therefore selects an active subset before assigning final cells.

The process is:

  1. For each non-transparent source pixel, find its nearest color in the working palette.
  2. Count the resulting palette indexes.
  3. Sort by frequency, with palette index as the deterministic tie-breaker.
  4. Keep the first maxColors colors.
  5. Match each target grid pixel against this active subset.

The default color limits are defined by detail preset and grid size:

Detail small medium large xlarge huge
easy 12 18 18 24 24
balanced 24 40 61 80 96
high 32 56 80 112 128

maxColors can be overridden. If the user enables inventory filtering, allowedColorIds restricts the working palette before active-palette selection and nearest-color matching. A non-empty allowed set is honored; an empty set falls back to the full palette.

8. Perceptual color matching with CIEDE2000

The converter does not use RGB Euclidean distance. Each palette HEX is converted to Lab, and the nearest candidate is selected using CIEDE2000.

paletteIndex = argmin(i, CIEDE2000(Lab(source), Lab(palette[i])))

The implementation is in lib/beads-core/color-distance.ts. Palette Lab values are cached so the same palette is not repeatedly converted for every pixel.

This choice improves perceptual ordering for colors that are numerically close in RGB but visibly different, especially around skin tones, muted colors, and hue transitions. It does not model lighting, material gloss, camera response, or the physical appearance of a particular bead batch.

9. Optional error diffusion

The quantizer supports false, true, and "auto" for dithering.

When enabled, the algorithm scans the grid row by row. After assigning a palette color, it computes the RGB residual and diffuses it to future neighbors using these weights:

       7/16
3/16   5/16   1/16

This is Floyd–Steinberg-style error diffusion. It changes the spatial distribution of existing palette colors; it never creates a new palette entry.

"auto" estimates source complexity by counting 4-bit RGB bins. Dithering is enabled when the number of observed bins exceeds 38. The heuristic is deterministic and intentionally simple enough to test.

The quantizer is implemented as quantizeRgba in lib/beads-core/pattern-engine.ts.

10. Conversion order and important implementation detail

The effective order is:

decode
  -> select source rectangle
  -> resize to preview
  -> apply image adjustments
  -> capture adjusted preview
  -> remove connected background, if enabled
  -> resize preview to target grid
  -> quantize with neutral adjustments

The last step uses neutral adjustment values because brightness, contrast, and saturation have already been applied to the preview. Reapplying them during quantization would apply the same transform twice.

11. Editing and material accounting

Once the cells are generated, the UI loads them into the same project model used by manually created and imported patterns.

Core operations include:

  • paint, erase, fill, and eyedropper editing;
  • revision tracking after valid cell changes;
  • material counting with calculateMaterials;
  • deterministic suggestions for isolated single-cell cleanup;
  • one-shot transfer from the converter to the Creator using pixel-bead.transfer.v1;
  • HEX-to-brand palette mapping for imported templates.

The transfer and mapping adapter is lib/pattern-transfer.ts. It uses exact HEX matches first and CIEDE2000 nearest-color matching for non-exact colors.

12. Export model

The same project can be exported as:

  • PNG with grid lines, color codes, coordinates, and a material legend;
  • PDF with an overview, material list, and board pages.

Large patterns are split into physical 29 × 29 board sections. Actual-size PDF rendering uses the palette's bead-pitch metadata.

Implementations:

13. Minimal browser-side usage

The complete file-to-pattern entry point requires a browser Image, Canvas, and File implementation.

import {
  DEFAULT_BRAND,
  generatePatternFromFile,
  getPalette,
} from "./lib/beads-core";

const generated = await generatePatternFromFile(
  file,
  {
    width: 58,
    height: 58,
    detail: "balanced",
    framing: "contain",
    adjustments: {
      brightness: 0,
      contrast: 100,
      saturation: 100,
    },
    maxColors: 40,
    dither: false,
    transparentAsEmpty: true,
  },
  {
    paletteDefinition: getPalette(DEFAULT_BRAND),
  },
);

console.log(generated.width, generated.height);
console.log(generated.cells.length); // width * height
console.log(generated.paletteId, generated.paletteVersion);

For a standalone package, the recommended boundary is to keep this browser adapter separate from a pure function accepting RGBA bytes, dimensions, palette data, and conversion settings.

14. Complexity and limits

Let P = width × height be the target grid area and K be the number of active palette colors.

  • image adjustment: linear in preview pixel count;
  • background flood fill: O(P_preview) in the worst case;
  • active-palette discovery: linear in sampled pixels multiplied by candidate palette size;
  • final quantization: O(P × K) because each grid cell compares against the active palette;
  • material accounting: O(P);
  • memory for the final project: O(P + paletteSize).

The current core rejects grids larger than 60,000 cells. The browser adapter rejects unsupported MIME types and files above 10 MB. These limits protect the UI from unbounded Canvas and quantization work; they are implementation safeguards, not claims about the algorithm's theoretical maximum.

15. Tests and verified behaviors

The repository tests cover the following implementation contracts:

  • CIEDE2000 identity and perceptual ordering;
  • registration and lookup of all five palettes;
  • background-removal tolerance, connectivity, disabled behavior, and invalid input;
  • isolated-cell cleanup suggestions;
  • project validation, migration, and HEX-cell import;
  • PNG layout and material legend geometry;
  • PDF generation and pagination;
  • physical 29 × 29 board-section splitting;
  • converter and export browser workflows.

The test suite is indexed in tests/. Focused tests are preferable when extracting the core into a separate repository because they document algorithm invariants more directly than UI snapshots.

16. Current boundaries

The current implementation should not be described as:

  • semantic image understanding;
  • automatic subject segmentation;
  • an image-generation model;
  • a guarantee that digital HEX values equal the appearance of physical beads under every light source;
  • a copyright, privacy, or licensing solution for user-provided images.

It is a deterministic raster-to-grid quantizer with palette constraints, optional connected background removal, perceptual color matching, and editable project output. Complex images may still require manual cleanup after conversion.

17. Suggested standalone open-source repository

pixel-bead-core/
├── README.md
├── LICENSE
├── packages/
│   ├── core/              # project model, grid validation, palettes, Lab/CIEDE2000, quantizer
│   ├── browser-adapter/   # File, Image, Canvas, preview, and background removal
│   └── exporters/         # PNG and PDF renderers
├── palettes/              # versioned palette data, provenance, and licenses
├── examples/              # input settings, RGBA fixtures, and output projects
└── tests/                 # algorithm, migration, adapter, and exporter tests

Recommended API boundaries:

  1. Keep the quantizer deterministic and side-effect free.
  2. Keep authentication, database access, storage, and UI outside the core package.
  3. Accept raw RGBA buffers in the core so Node, browser, and Web Worker adapters can share the algorithm.
  4. Version palette data and record provenance for every palette release.
  5. Include fixture images, expected cell arrays, and expected material counts.
  6. Document invalid-input behavior and size limits as part of the public API.
  7. Make the README, type definitions, examples, and tests sufficient for an AI agent to understand the system without reading the entire product application.

Source index

Concern Current implementation
Types and project format lib/beads-core/types.ts
File decode and conversion entry lib/beads-core/browser/image.ts
Grid geometry, adjustments, masking, quantization lib/beads-core/pattern-engine.ts
Lab and CIEDE2000 lib/beads-core/color-distance.ts
Palette registry lib/beads-core/palette.ts
Project creation, validation, migration, materials lib/beads-core/project.ts
PNG export lib/beads-core/browser/png.ts
PDF export lib/beads-core/pdf.ts
Converter UI workflow components/image-converter.tsx
Tests tests/

Summary

The central engineering decision is to represent the conversion result as a versioned, palette-aware, editable grid rather than a flattened image. That single contract connects image processing, manual editing, inventory counts, persistence, project transfer, and printable export.

Pixel Bead Studio currently implements this contract with browser-local raster processing, explicit framing, optional connected background removal, active-palette reduction, CIEDE2000 matching, optional error diffusion, and validated PixelBeadProject output.