respira/src/utils/brotherColors.ts
Jan-Henrik Bruhn 69eb7c9986 feature: Add RGB color matching for Brother thread identification
Enhances the Brother color mapping to support exact RGB color matching in
addition to catalog number matching. This follows the logic from the
BrotherColor.FromColor method in the App codebase.

Changes:
- Added optional RGB matching to enhanceThreadWithBrotherColor function
- RGB matching is enabled by default but can be disabled via options
- Enhanced logging to show breakdown of matches (catalog vs RGB)
- Matching priority: catalog number first, then RGB (both exact match only)

Example: A thread with RGB(255,255,255) will now match Brother color "WHITE"
(code 001) even if the catalog number is missing or doesn't match.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-21 20:52:32 +01:00

301 lines
8.5 KiB
TypeScript

/**
* Brother Color Mapping Utilities
*
* This module provides utilities for mapping thread colors to official Brother
* embroidery thread colors with their proper names and chart codes.
*
* Based on the Brother Embroidery thread catalog, this mapping ensures accurate
* color identification for embroidery patterns.
*
* The mapping logic follows the implementation in Asura.Core.Models.EmbroideryUtil.GetThreadColorListFromPesx
*/
import brotherColorData from "../data/BrotherColor.json";
/**
* Brother thread color data structure
*/
export interface BrotherColor {
/** RGB red value (0-255) */
R: number;
/** RGB green value (0-255) */
G: number;
/** RGB blue value (0-255) */
B: number;
/** Brother thread chart code (e.g., "001", "843") */
ColorCode: string;
/** Color name (e.g., "WHITE", "BEIGE") */
ColorName: string;
/** Brand code (13 for Brother Embroidery) */
BrandCode: number;
/** Brand name */
brandName?: string;
}
/**
* Thread color information with Brother mapping
*/
export interface ThreadColorInfo {
/** RGB color as hex string */
hex: string;
/** Brand name (e.g., "Brother Embroidery") */
brand: string | null;
/** Color catalog/chart code (e.g., "001", "843") */
catalogNumber: string | null;
/** Color description/name (e.g., "WHITE", "BEIGE") */
description: string | null;
/** Chart code (same as catalogNumber for Brother) */
chart: string | null;
/** RGB values */
rgb: {
r: number;
g: number;
b: number;
};
}
// Type-safe Brother color data
const brotherColors = brotherColorData as BrotherColor[];
/**
* Convert RGB values to hex color string
*/
function rgbToHex(r: number, g: number, b: number): string {
return `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase()}`;
}
/**
* Pad a color code to 3 digits with leading zeros
* e.g., "5" -> "005", "43" -> "043", "900" -> "900"
*
* This replicates the logic:
* ```csharp
* if (int.TryParse(threadCode, NumberStyles.Number, CultureInfo.InvariantCulture, out result))
* threadCode = $"{result:000}";
* ```
*/
function padColorCode(code: string | number): string {
const codeStr = typeof code === "number" ? code.toString() : code;
const parsed = parseInt(codeStr, 10);
if (!isNaN(parsed)) {
return parsed.toString().padStart(3, "0");
}
return codeStr;
}
/**
* Find Brother color by color code (exact match only)
*
* This replicates:
* ```csharp
* IEnumerable<BrotherColor> source2 = BrotherColor.Colors.Where<BrotherColor>((c => c.ColorCode == threadCode));
* ```
*
* @param colorCode - Brother thread code (e.g., "001", "5", 843)
* @returns Brother color data or undefined if not found
*/
export function findBrotherColorByCode(
colorCode: string | number,
): BrotherColor | undefined {
const paddedCode = padColorCode(colorCode);
return brotherColors.find((c) => c.ColorCode === paddedCode);
}
/**
* Find Brother color by RGB values (exact match only)
*
* @param r - Red value (0-255)
* @param g - Green value (0-255)
* @param b - Blue value (0-255)
* @returns Brother color data or undefined if no exact match found
*/
export function findBrotherColorByRGB(
r: number,
g: number,
b: number,
): BrotherColor | undefined {
return brotherColors.find((c) => c.R === r && c.G === g && c.B === b);
}
/**
* Convert Brother color to ThreadColorInfo
*
* This replicates:
* ```csharp
* threadColor1.BrandName = brotherColor.BrandName;
* threadColor1.BrandCode = brotherColor.BrandCode;
* threadColor1.ColorName = brotherColor.ColorName;
* threadColor1.ColorCode = brotherColor.ColorCode;
* threadColor1.R = brotherColor.R;
* threadColor1.G = brotherColor.G;
* threadColor1.B = brotherColor.B;
* ```
*
* @param brotherColor - Brother color data
* @returns Thread color information
*/
export function brotherColorToThreadInfo(
brotherColor: BrotherColor,
): ThreadColorInfo {
return {
hex: rgbToHex(brotherColor.R, brotherColor.G, brotherColor.B),
brand: brotherColor.brandName || "Brother Embroidery",
catalogNumber: brotherColor.ColorCode,
description: brotherColor.ColorName,
chart: brotherColor.ColorCode,
rgb: {
r: brotherColor.R,
g: brotherColor.G,
b: brotherColor.B,
},
};
}
/**
* Map a thread color code to full Brother thread information
*
* This is the main mapping function that replicates the logic from
* EmbroideryUtil.GetThreadColorListFromPesx in the C# app.
*
* Returns null if the color code doesn't match any Brother color (exact match only).
*
* @param threadCode - Thread code from pattern file (can be string or number)
* @returns Thread color information with Brother mapping, or null if code doesn't match
*/
export function mapThreadCode(
threadCode: string | number,
): ThreadColorInfo | null {
// Parse and pad the thread code
const code = padColorCode(threadCode);
// Look up the Brother color (exact match only)
const brotherColor = findBrotherColorByCode(code);
if (!brotherColor) {
return null;
}
return brotherColorToThreadInfo(brotherColor);
}
/**
* Create a custom thread color (for non-Brother colors)
*
* This replicates the custom color handling for chartcodes 250-253:
* ```csharp
* if ("000".Equals(threadCode) && num >= 250 && num <= 253) {
* Color colorLong = EmbroideryUtil.GetColorLong(element);
* threadColor1.BrandName = "";
* threadColor1.BrandCode = num;
* threadColor1.ColorName = "";
* threadColor1.ColorCode = "0";
* threadColor1.R = colorLong.R;
* threadColor1.G = colorLong.G;
* threadColor1.B = colorLong.B;
* }
* ```
*
* @param r - Red value (0-255)
* @param g - Green value (0-255)
* @param b - Blue value (0-255)
* @param chartCode - Chart code (250-253 for custom colors)
* @returns Thread color information for custom color
*/
export function createCustomThreadColor(
r: number,
g: number,
b: number,
chartCode?: number,
): ThreadColorInfo {
return {
hex: rgbToHex(r, g, b),
brand: chartCode !== undefined ? "" : null,
catalogNumber: chartCode !== undefined ? "0" : null,
description: chartCode !== undefined ? "" : null,
chart: chartCode !== undefined ? chartCode.toString() : null,
rgb: { r, g, b },
};
}
/**
* Get all available Brother colors
*
* @returns Array of all Brother thread colors
*/
export function getAllBrotherColors(): BrotherColor[] {
return [...brotherColors];
}
/**
* Get all Brother colors as ThreadColorInfo
*
* @returns Array of all Brother thread colors as ThreadColorInfo
*/
export function getAllBrotherThreads(): ThreadColorInfo[] {
return brotherColors.map(brotherColorToThreadInfo);
}
/**
* Enhance thread data with Brother color mapping
*
* Takes thread data from a PES file and enhances it with Brother color information
* using exact matching by catalog number or RGB values.
*
* This follows the logic from BrotherColor.FromColor and EmbroideryUtil.GetThreadColorListFromPesx:
* 1. If catalogNumber matches a Brother color code, use Brother data
* 2. Optionally, try exact RGB color matching
* 3. If no match, preserve the original thread data
*
* @param thread - Thread data from PES file
* @param options - Enhancement options
* @param options.matchByRGB - Enable exact RGB color matching (default: true)
* @returns Enhanced thread data with Brother mapping if applicable
*/
export function enhanceThreadWithBrotherColor(
thread: {
color: number;
hex: string;
brand: string | null;
catalogNumber: string | null;
description: string | null;
chart: string | null;
},
options: {
matchByRGB?: boolean;
} = {},
): ThreadColorInfo {
const { matchByRGB = true } = options;
// First, try to match by catalog number
if (thread.catalogNumber) {
const brotherInfo = mapThreadCode(thread.catalogNumber);
if (brotherInfo) {
// Found a Brother color match by catalog number - use Brother data
return brotherInfo;
}
}
// Second, try exact RGB matching if enabled (replicates BrotherColor.FromColor logic)
const cleanHex = thread.hex.replace("#", "");
const r = parseInt(cleanHex.slice(0, 2), 16);
const g = parseInt(cleanHex.slice(2, 4), 16);
const b = parseInt(cleanHex.slice(4, 6), 16);
if (matchByRGB) {
const brotherColor = findBrotherColorByRGB(r, g, b);
if (brotherColor) {
// Found exact RGB match - use Brother data
return brotherColorToThreadInfo(brotherColor);
}
}
// No Brother match - return thread data as-is
return {
hex: thread.hex,
brand: thread.brand,
catalogNumber: thread.catalogNumber,
description: thread.description,
chart: thread.chart,
rgb: { r, g, b },
};
}