# bead-core
**Repository Path**: wangdandan810012/bead-core
## Basic Information
- **Project Name**: bead-core
- **Description**: No description available
- **Primary Language**: TypeScript
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-06-30
- **Last Updated**: 2026-09-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# @wangdandan810012/bead-core
蛋蛋拼豆核心算法库 — 将 RGBA 像素图转换为拼豆色号网格。
**重点:图片转图纸,不是把原图按像素一格一格缩小再贴色。** 本库走的是「识图 → 预处理 / Prep 中间图 → 保边降采样 → CIEDE2000 感知配色 → 区域合并 / 限色 / 背景识别」的完整流水线,目标是在较低格数下尽量保住主体轮廓与关键颜色,生成结果更适合真正拿去做拼豆。
- **零运行时依赖**,不绑定 UI 框架
- 可在 **Node.js ≥ 18** 或 **浏览器** 中使用
- 本包为 **ESM**:`package.json` 请设置 `"type": "module"`,或在 TypeScript 中使用 `"module": "ESNext"`
- 在线体验:
- GitHub:
- npm:
## 目录
1. [安装](#安装)
2. [图片转图纸:不只是像素缩放](#图片转图纸不只是像素缩放)
3. [核心概念](#核心概念)
4. [快速开始(端到端)](#快速开始端到端)
5. [读取图片](#读取图片)
6. [runPipeline 参数](#runpipeline-参数)
7. [分步调用](#分步调用)
8. [图像预处理](#图像预处理)
9. [拼豆专用 Prep](#拼豆专用-prep)
10. [编辑与统计](#编辑与统计)
11. [API 一览](#api-一览)
12. [与 Pindou 网页的关系](#与-pindou-网页的关系)
13. [版本与更新](#版本与更新)
14. [FAQ](#faq)
## 安装
```bash
npm install @wangdandan810012/bead-core
```
本地与 [pindou-web](https://github.com/whr810012/pindou) 联调时可用:
```bash
npm install file:../bead-core
```
---
## 图片转图纸:不只是像素缩放
很多人第一反应是「把图缩小到 N×N,每个格子取一个像素的颜色」——这在本库里**不是**默认路径,也通常不是好结果。
### 和「简单缩放贴色」差在哪?
| 简单做法 | bead-core 做法 |
|----------|----------------|
| 整图缩小到目标格数,每格取一个 RGB | **保边降采样**:按格心采样,关闭平滑,避免边缘被区域平均抹糊 |
| RGB 直接找最近色 | **CIEDE2000 感知配色**:按人眼色差匹配色板,不是欧氏距离 |
| 格数跟源图像素 1:1 或随意拉 | **拼板分档建议**(29 / 58 / 87 / 116),按内容类型与手工拼板对齐 |
| 照片直接降格 | 可先走 **拼豆专用 Prep** / **像素风 Prep**,做成大色块、硬边缘中间图再出图 |
| 卡通细线容易被吃掉 | **dominant 模式**下先膨胀细线/五官再降采样,避免灰边贴成肤色把眼睛吃掉 |
| 只出一张图 | 流水线后还可 **合并相似区域**、**限色**、**标记外部背景**、**排除色重映射** |
### 典型路径(由调用方选择,非库内自动)
```
普通照片
→ prepareSourcePixels(亮度/对比/降噪/锐化)
→ createBeadPrepPixels(拼豆专用大色块中间图)
→ runPipeline(flatTile: true,一像素一豆铺格 + 色板匹配)
→ mergeSimilarRegions / limitGridColors / markExternalBackground …
拼豆原图 / 已是像素稿
→ convertImageToPattern(flatTile: true,最近邻 + 感知配色)
或 runPipeline 常规路径(保边采样 + 合并)
卡通插画(追求还原)
→ convertImageToPattern(dominant + 细线膨胀 + fringe 吸附)
→ 可选 mergeSimilarRegions
```
即使 `flatTile: true`(平铺、不做区域合并),仍会做 **色板量化**(CIEDE2000),不是原图像素原样拷贝。
算法细节见 [docs/algorithms](./docs/algorithms/README.md)。
---
## 核心概念
### 色板 `PaletteEntry[]`
库**不内置色板**,由调用方传入。每个色号含唯一 ID、显示色值,以及各品牌豆号:
```typescript
import type { PaletteEntry } from '@wangdandan810012/bead-core'
const palette: PaletteEntry[] = [
{
id: 'red-01',
hex: '#E74C3C',
codes: { MARD: 'A1', COCO: 'A1', MANMAN: 'A1', PANPAN: 'A1', MIXIAOWO: 'A1' },
},
{
id: 'blue-01',
hex: '#3498DB',
codes: { MARD: 'B1', COCO: 'B1', MANMAN: 'B1', PANPAN: 'B1', MIXIAOWO: 'B1' },
},
{
id: 'neutral-001',
hex: '#FFFFFF',
codes: { MARD: 'H1', COCO: 'H1', MANMAN: 'H1', PANPAN: 'H1', MIXIAOWO: 'H1' },
},
]
```
`codes` 的键为 `BrandSystem`:`MARD` | `COCO` | `MANMAN` | `PANPAN` | `MIXIAOWO`。不使用某品牌时填空字符串即可。
### 像素数据
所有图像输入均为 **RGBA** 的 `Uint8ClampedArray`,长度 = `width × height × 4`,按行优先排列(与 Canvas `ImageData.data` 一致)。
### 输出网格 `MappedGrid`
二维数组 `MappedCell[][]`:
| 字段 | 说明 |
|------|------|
| `paletteId` | 匹配到的色板 ID |
| `hex` | 显示用十六进制色值 |
| `isExternal` | 可选;为 `true` 时表示外部背景,不计入拼豆数量,编辑时会跳过 |
工具函数:`cloneGrid(grid)`、`gridDimensions(grid)`。
---
## 快速开始(端到端)
完整流程:**准备迷你色板 → 构造/读取像素 → 预处理 → 生成图纸 → 裁边 → 统计**。
```typescript
import {
prepareSourcePixels,
runPipeline,
computeColorStats,
countTotalBeads,
trimGrid,
type PaletteEntry,
type BrandSystem,
} from '@wangdandan810012/bead-core'
const palette: PaletteEntry[] = [
{
id: 'red-01',
hex: '#E74C3C',
codes: { MARD: 'A1', COCO: 'A1', MANMAN: 'A1', PANPAN: 'A1', MIXIAOWO: 'A1' },
},
{
id: 'neutral-001',
hex: '#FFFFFF',
codes: { MARD: 'H1', COCO: 'H1', MANMAN: 'H1', PANPAN: 'H1', MIXIAOWO: 'H1' },
},
]
// 示例:2×2 纯红块(实际项目请用 Canvas / sharp 读图,见下文)
const width = 2
const height = 2
const pixels = new Uint8ClampedArray([
231, 76, 60, 255, 231, 76, 60, 255,
231, 76, 60, 255, 231, 76, 60, 255,
])
const adjusted = prepareSourcePixels(
pixels,
width,
height,
{ brightness: 0, contrast: 12, saturation: 0 },
{ denoise: false, sharpen: true },
)
const { grid, width: gridW, height: gridH } = runPipeline(adjusted, width, height, {
gridWidth: 2,
mode: 'average',
mergeThreshold: 0,
maxColors: 0,
palette,
backgroundPaletteIds: ['neutral-001'],
excludedPaletteIds: [],
})
const trimmed = trimGrid(grid)
const brand: BrandSystem = 'MARD'
const stats = computeColorStats(trimmed, brand, (paletteId, b) => {
const entry = palette.find((p) => p.id === paletteId)
return entry?.codes[b] ?? paletteId
})
console.log(`网格 ${gridW}×${gridH},共 ${countTotalBeads(trimmed)} 颗豆`)
console.log(stats)
```
---
## 读取图片
### 浏览器:Canvas
```typescript
import { runPipeline, type PaletteEntry } from '@wangdandan810012/bead-core'
async function imageToGrid(imageUrl: string, palette: PaletteEntry[]) {
const img = new Image()
img.crossOrigin = 'anonymous'
img.src = imageUrl
await img.decode()
const canvas = document.createElement('canvas')
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
const ctx = canvas.getContext('2d')!
ctx.drawImage(img, 0, 0)
const { data, width, height } = ctx.getImageData(0, 0, canvas.width, canvas.height)
return runPipeline(data, width, height, {
gridWidth: 64,
mode: 'average',
mergeThreshold: 0,
maxColors: 0,
palette,
backgroundPaletteIds: [],
excludedPaletteIds: [],
})
}
```
### Node.js:sharp
Node 本身不解码图像,需自行安装 `sharp`(**不是**本库依赖):
```bash
npm install sharp
```
```typescript
import sharp from 'sharp'
import { runPipeline, type PaletteEntry } from '@wangdandan810012/bead-core'
async function fileToGrid(filePath: string, palette: PaletteEntry[]) {
const { data, info } = await sharp(filePath)
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true })
return runPipeline(new Uint8ClampedArray(data), info.width, info.height, {
gridWidth: 64,
mode: 'average',
mergeThreshold: 0,
maxColors: 0,
palette,
backgroundPaletteIds: [],
excludedPaletteIds: [],
})
}
```
---
## runPipeline 参数
`runPipeline` 按以下顺序执行(`flatTile: true` 时跳过合并):
```
convertImageToPattern
→ (!flatTile) mergeSimilarRegions
→ (maxColors > 0) limitGridColors
→ markExternalBackground
→ remapExcludedColors
```
| 参数 | 类型 | 说明 |
|------|------|------|
| `gridWidth` | `number` | 输出网格列数;行数 = `round(gridWidth × 原图高 / 原图宽)` |
| `mode` | `'average' \| 'dominant'` | 照片用 `average`;卡通/像素风用 `dominant` |
| `mergeThreshold` | `number` | CIEDE2000 色差阈值,合并相邻相似色块;`0` 关闭 |
| `maxColors` | `number` | 限制最终颜色种类;`0` 不限 |
| `palette` | `PaletteEntry[]` | 可用色板,不能为空 |
| `backgroundPaletteIds` | `string[]` | 从四边洪泛,标记为 `isExternal: true` |
| `excludedPaletteIds` | `string[]` | 排除色号,重映射到色板中最近色 |
| `flatTile` | `boolean?` | 为 `true` 时:转换阶段走平铺路径(最近邻采样 + 色板匹配),且跳过 `mergeSimilarRegions`;适合 prep 中间图 |
| `despeckle` | `boolean?` | 照片 `average` 模式下去除孤立杂点,默认 `false`(可能抹细线) |
返回 `{ grid, width, height }`。
格宽自动建议(不由 `runPipeline` 自动调用,供上层 UI 使用):
```typescript
import { suggestCraftGridWidth, BEAD_BOARD_SIZE } from '@wangdandan810012/bead-core'
// 29 孔拼板 BEAD_BOARD_SIZE = 29
// 小图 29、一般卡通 58、照片 87、线稿/五官 116;自动建议上限 116,滑条可由调用方放到更大
const gridWidth = suggestCraftGridWidth(imgW, imgH, 256, isPhotoLike, detailSensitive)
```
配色时库内部使用 CIEDE2000;对外可用 `colorDistance(rgb1, rgb2)` 与 `findClosestPaletteEntry(rgb, palette)`。
---
## 分步调用
不需要完整流水线时,可单独调用:
```typescript
import {
convertImageToPattern,
mergeSimilarRegions,
markExternalBackground,
limitGridColors,
remapExcludedColors,
} from '@wangdandan810012/bead-core'
let grid = convertImageToPattern(pixels, width, height, {
gridWidth: 64,
mode: 'dominant',
palette,
excludedPaletteIds: [],
despeckle: false, // 去除孤立杂点(仅 average 模式)
flatTile: false,
})
grid = mergeSimilarRegions(grid, 5)
grid = limitGridColors(grid, palette, 20)
grid = markExternalBackground(grid, ['neutral-001'])
grid = remapExcludedColors(grid, palette, ['old-color-id'])
```
`mapImageToGrid` 是 `convertImageToPattern` 的别名。
---
## 图像预处理
在进流水线前可选调节源图:
```typescript
import {
prepareSourcePixels,
applyImageAdjustments,
applyPhotoOptimize,
DEFAULT_IMAGE_ADJUST,
DEFAULT_PHOTO_OPTIMIZE,
} from '@wangdandan810012/bead-core'
const adjusted = prepareSourcePixels(
pixels,
width,
height,
{ ...DEFAULT_IMAGE_ADJUST, contrast: 12 },
{ ...DEFAULT_PHOTO_OPTIMIZE, sharpen: true },
)
// 也可分步:
const step1 = applyImageAdjustments(pixels, width, height, DEFAULT_IMAGE_ADJUST)
const step2 = applyPhotoOptimize(step1, width, height, DEFAULT_PHOTO_OPTIMIZE)
```
| `ImageAdjust` | 含义 |
|---------------|------|
| `brightness` | 亮度偏移 |
| `contrast` | 对比度 |
| `saturation` | 饱和度 |
| `PhotoOptimize` | 含义 |
|-----------------|------|
| `denoise` | 降噪 |
| `sharpen` | 锐化 |
---
## 拼豆专用 Prep
将照片转为「大色块、硬边缘」的**中间图**(不是最终图纸),再按 **一像素一豆** 进流水线(通常配合 `flatTile: true`)。这一步把「照片」变成「更像拼豆图」的素材,而不是简单缩小原图:
```typescript
import {
createBeadPrepPixels,
createPixelArtPrepPixels,
runPipeline,
type PaletteEntry,
} from '@wangdandan810012/bead-core'
// 拼豆专用图(推荐日常照片)
const prep = createBeadPrepPixels(pixels, width, height, 80)
// prep.pixels / prep.width / prep.height / prep.gridWidth / prep.gridHeight
const { grid } = runPipeline(prep.pixels, prep.width, prep.height, {
gridWidth: prep.gridWidth,
mode: 'dominant',
mergeThreshold: 0,
maxColors: 0,
palette,
backgroundPaletteIds: [],
excludedPaletteIds: [],
flatTile: true,
})
// 经典像素风中间图
const pixelArt = createPixelArtPrepPixels(pixels, width, height, 64)
```
辅助分析与建议(可选):
- `analyzeImageContent` / `isDetailSensitiveCartoon` / `countDistinctColors`
- `suggestCraftGridWidth` / `BEAD_BOARD_SIZE` / `AUTO_SUGGEST_MAX_GRID` — 按 29 孔拼板分档建议格宽
- `computePrepTargetDimensions` / `suggestPrepColorCount` / `suggestGridWidthForPrepImage` / `suggestPrepMergeThreshold`
算法细节见 [docs/algorithms](./docs/algorithms/README.md)。
---
## 编辑与统计
所有编辑函数返回**新网格**(不可变),不修改入参;起点为 `isExternal` 或区域内 external 格会被跳过。
```typescript
import {
fillRegion,
paintRect,
trimGrid,
flipGridHorizontal,
flipGridVertical,
cloneGrid,
computeColorStats,
countTotalBeads,
countCompleted,
getConnectedRegions,
} from '@wangdandan810012/bead-core'
grid = fillRegion(grid, row, col, 'red-01', '#E74C3C')
grid = paintRect(grid, row0, col0, row1, col1, 'blue-01', '#3498DB')
grid = trimGrid(grid)
grid = flipGridHorizontal(grid)
grid = flipGridVertical(grid)
const copy = cloneGrid(grid)
const stats = computeColorStats(grid, 'MARD', codeLookup)
const total = countTotalBeads(grid)
const done = countCompleted(grid, new Set(['0,0', '1,2']))
const regions = getConnectedRegions(grid, 'red-01')
```
---
## API 一览
与 `src/index.ts` 公开导出对齐:
| 分类 | 导出 | 说明 |
|------|------|------|
| 流水线 | `runPipeline` | 完整生成流程 |
| 转换 | `convertImageToPattern` / `mapImageToGrid` | 图片 → 网格 |
| 预处理 | `prepareSourcePixels` / `applyImageAdjustments` / `applyPhotoOptimize` | 源图调节 |
| 预处理 | `DEFAULT_IMAGE_ADJUST` / `DEFAULT_PHOTO_OPTIMIZE` | 默认参数 |
| Prep | `createBeadPrepPixels` / `createPixelArtPrepPixels` | 拼豆 / 像素风中间图 |
| Prep | `analyzeImageContent` 等 suggest 系列 | 内容分析与尺寸建议 |
| Prep | `suggestCraftGridWidth` / `BEAD_BOARD_SIZE` | 拼板分档格宽建议(29/58/87/116) |
| 合并 | `mergeSimilarRegions` | 相似色区域合并 |
| 背景 | `markExternalBackground` | 外部背景洪泛标记 |
| 限色 | `limitGridColors` | 限制最大颜色数 |
| 重映射 | `remapExcludedColors` | 排除色重映射 |
| 编辑 | `fillRegion` / `paintRect` / `normalizeRect` | 填充 / 矩形上色 |
| 编辑 | `trimGrid` / `flipGridHorizontal` / `flipGridVertical` | 裁边 / 翻转 |
| 工具 | `cloneGrid` / `gridDimensions` | 克隆 / 尺寸 |
| 统计 | `computeColorStats` / `countTotalBeads` / `countCompleted` / `getConnectedRegions` | 统计与分区 |
| 色彩 | `colorDistance` / `findClosestPaletteEntry` / `filterActivePalette` | CIEDE2000 色差与匹配 |
| 色彩 | `hexToRgb` / `rgbToHex` | 颜色转换 |
| 类型 | `PaletteEntry`、`MappedGrid`、`PipelineOptions`、`BeadPrepResult` 等 | TypeScript 类型 |
完整算法说明:[docs/algorithms](https://github.com/whr810012/bead-core/blob/main/docs/algorithms/README.md)。
---
## 与 Pindou 网页的关系
- [pindou-web](https://github.com/whr810012/pindou-web)(在线:)在浏览器中调用同一套算法完成「上传 → 识图选路径 → 生成 → 换色 / 并色精修 → 导出」。
- 色板、UI、项目存储、原图 IndexedDB、Web Worker 管线由网页层负责;核心只处理像素与网格。
- 智能参数与导入模式建议见 pindou-web 的 `packages/app-shared`(`suggestParams.ts`、`suggestImportMode.ts`)。
- 许可证:MIT。
---
## 版本与更新
### 0.4.0(当前)
- **转换引擎**:`convertImageToPattern` 支持 `flatTile`(平铺出图)与 `despeckle`(去杂点)
- **卡通保细节**:dominant 模式下降采样前膨胀细线/五官;`snapAchromaticFringe` 减少灰边误配色
- **拼板分档**:`suggestCraftGridWidth` 按 29 / 58 / 87 / 116 建议格宽,不再默认跟源图 1:1
- **Prep 参数**:`computePrepTargetDimensions` 等与手工拼板分档对齐
- 文档强调:**图片转图纸 ≠ 像素缩放贴色**
### 0.3.x
- CIEDE2000 感知配色、保边降采样、拼豆专用 Prep / 像素风 Prep
- `runPipeline` 完整流水线:合并、限色、背景洪泛、排除色重映射
完整变更见 GitHub Releases / commit 历史。
---
## FAQ
**是不是把图片缩小再一格一色?**
不是。默认会经过预处理或 Prep、保边采样、感知配色,并可叠加区域合并与背景识别。只有「已是拼豆原图 / prep 中间图」时才常用 `flatTile` 平铺,但仍会做色板匹配。
**适合什么场景?**
需要把图片转成拼豆色号网格的 Web、小程序、Node 批处理;库不含 UI。
**有没有内置色板?**
没有。请自备 `PaletteEntry[]`(可用自有品牌色号或开源色板数据)。
**如何安装?**
`npm install @wangdandan810012/bead-core`。
**算法细节在哪?**
仓库 [`docs/algorithms`](./docs/algorithms/README.md)。
**许可证?**
MIT — 见 [LICENSE](./LICENSE)。
---
## 开发
```bash
git clone https://github.com/whr810012/bead-core.git
cd bead-core
npm install
npm test # 运行测试
npm run build # 编译到 dist/
```
## License
MIT — Copyright (c) 2026 蛋蛋 — 见 [LICENSE](./LICENSE)。
## 致谢
算法思路受以下开源项目启发(本库为独立 TypeScript 实现):
- [Zippland/perler-beads](https://github.com/Zippland/perler-beads)
- [liangdabiao/perler-beads-ai](https://github.com/liangdabiao/perler-beads-ai)