@xivdyetools/core
v1.5.4
Published
Core color algorithms and dye database for XIV Dye Tools
Maintainers
Readme
xivdyetools-core
Core color algorithms and dye database for XIV Dye Tools - Environment-agnostic TypeScript library for FFXIV dye color matching, harmony generation, and accessibility checking.
Features
✨ Color Conversion - RGB ↔ HSV ↔ Hex with full validation 🎨 136 FFXIV Dyes - Complete database with RGB/HSV/metadata 🎯 Dye Matching - Find closest dyes to any color 🌈 Color Harmonies - Triadic, complementary, analogous, and more 🖼️ Palette Extraction - K-means++ clustering for multi-color extraction from images ♿ Accessibility - Colorblindness simulation (Brettel 1997) 📡 Universalis API - Price data integration with caching 🔌 Pluggable Cache - Memory, localStorage, Redis support 🌍 Environment Agnostic - Works in Node.js, browsers, edge runtimes 🗣️ 6 Languages - English, Japanese, German, French, Korean, Chinese
Installation
npm install xivdyetools-coreQuick Start
Browser (with bundler)
import { ColorService, DyeService, dyeDatabase } from 'xivdyetools-core';
// Initialize services
const dyeService = new DyeService(dyeDatabase);
// Find closest dye to a color
const closestDye = dyeService.findClosestDye('#FF6B6B');
console.log(closestDye.name); // "Coral Pink"
// Generate color harmonies
const triadicDyes = dyeService.findTriadicDyes('#FF6B6B');
console.log(triadicDyes.map(d => d.name)); // ["Turquoise Green", "Grape Purple"]
// Color conversions
const rgb = ColorService.hexToRgb('#FF6B6B');
const hsv = ColorService.rgbToHsv(rgb.r, rgb.g, rgb.b);
console.log(hsv); // { h: 0, s: 58.04, v: 100 }Node.js (Discord bot, API, CLI)
import {
DyeService,
APIService,
dyeDatabase
} from 'xivdyetools-core';
import Redis from 'ioredis';
// Initialize with Redis cache (for Discord bots)
const redis = new Redis();
const cacheBackend = new RedisCacheBackend(redis);
const apiService = new APIService(cacheBackend);
const dyeService = new DyeService(dyeDatabase);
// Fetch live market prices
const priceData = await apiService.getPriceData(5752); // Jet Black Dye
console.log(`${priceData.currentMinPrice} Gil`);
// Find harmony with pricing
const baseDye = dyeService.findClosestDye('#000000');
const harmonyDyes = dyeService.findComplementaryPair(baseDye.hex);Core Services
ColorService
Pure color conversion and manipulation algorithms.
Memory Note: ColorService uses LRU caches (5 caches × 1000 entries each = up to 5000 cached entries) for performance optimization. For long-running applications or memory-constrained environments, call
ColorService.clearCaches()periodically to free memory. Each cache entry is approximately 50-100 bytes, so maximum memory usage is ~500KB.
import { ColorService } from 'xivdyetools-core';
// Hex ↔ RGB
const rgb = ColorService.hexToRgb('#FF6B6B');
const hex = ColorService.rgbToHex(255, 107, 107);
// RGB ↔ HSV
const hsv = ColorService.rgbToHsv(255, 107, 107);
const rgbFromHsv = ColorService.hsvToRgb(0, 58.04, 100);
// Colorblindness simulation
const simulated = ColorService.simulateColorblindness(
{ r: 255, g: 0, b: 0 },
'deuteranopia'
);
// Color distance (Euclidean in RGB space)
const distance = ColorService.getColorDistance('#FF0000', '#00FF00');
// Color inversion
const inverted = ColorService.invert('#FF6B6B');
// Cache management (for memory-constrained environments)
ColorService.clearCaches();
const cacheStats = ColorService.getCacheStats();DyeService
FFXIV dye database management and color matching.
import { DyeService, dyeDatabase } from 'xivdyetools-core';
const dyeService = new DyeService(dyeDatabase);
// Database access
const allDyes = dyeService.getAllDyes(); // 136 dyes
const dyeById = dyeService.getDyeById(5752); // Jet Black Dye
const categories = dyeService.getCategories(); // ['Neutral', 'Red', 'Blue', ...]
// Color matching
const closest = dyeService.findClosestDye('#FF6B6B');
const nearby = dyeService.findDyesWithinDistance('#FF6B6B', 50, 5);
// Harmony generation
const triadic = dyeService.findTriadicDyes('#FF6B6B');
const complementary = dyeService.findComplementaryPair('#FF6B6B');
const analogous = dyeService.findAnalogousDyes('#FF6B6B', 30);
const monochromatic = dyeService.findMonochromaticDyes('#FF6B6B', 6);
const splitComplementary = dyeService.findSplitComplementaryDyes('#FF6B6B');
// Filtering
const redDyes = dyeService.searchByCategory('Red');
const searchResults = dyeService.searchByName('black');
const filtered = dyeService.filterDyes({
category: 'Special',
excludeIds: [5752, 5753],
minPrice: 0,
maxPrice: 10000
});PaletteService
Multi-color palette extraction from images using K-means++ clustering.
import { PaletteService, DyeService, dyeDatabase } from 'xivdyetools-core';
const paletteService = new PaletteService();
const dyeService = new DyeService(dyeDatabase);
// Extract from Canvas ImageData
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = PaletteService.pixelDataToRGBFiltered(imageData.data);
// Extract dominant colors only
const palette = paletteService.extractPalette(pixels, { colorCount: 4 });
// Returns: Array<{ color: RGB, dominance: number }>
// Extract and match to FFXIV dyes
const matches = paletteService.extractAndMatchPalette(pixels, dyeService, {
colorCount: 4,
maxIterations: 25,
convergenceThreshold: 1.0,
maxSamples: 10000
});
// Returns: Array<{ extracted: RGB, matchedDye: Dye, distance: number, dominance: number }>
// Helper: Convert raw pixel buffer (RGB, 3 bytes per pixel)
const pixelsFromBuffer = PaletteService.pixelDataToRGB(buffer);
// Helper: Convert RGBA ImageData, filtering transparent pixels
const pixelsFromCanvas = PaletteService.pixelDataToRGBFiltered(imageData.data);APIService
Universalis API integration with pluggable cache backends.
import { APIService, MemoryCacheBackend } from 'xivdyetools-core';
// With memory cache (default)
const apiService = new APIService();
// With custom cache backend
const cache = new MemoryCacheBackend();
const apiService = new APIService(cache);
// Fetch price data
const priceData = await apiService.getPriceData(5752); // itemID
const pricesWithDC = await apiService.getPriceData(5752, undefined, 'Aether');
// Batch operations
const prices = await apiService.getPricesForItems([5752, 5753, 5754]);
// Cache management
await apiService.clearCache();
const stats = await apiService.getCacheStats();
// API health check
const { available, latency } = await apiService.getAPIStatus();
// Utility methods
const formatted = APIService.formatPrice(123456); // "123,456G"
const trend = APIService.getPriceTrend(100, 80); // { trend: 'up', ... }Custom Cache Backends
Implement the ICacheBackend interface for custom storage:
import { ICacheBackend, CachedData, PriceData } from 'xivdyetools-core';
import Redis from 'ioredis';
class RedisCacheBackend implements ICacheBackend {
constructor(private redis: Redis) {}
async get(key: string): Promise<CachedData<PriceData> | null> {
const data = await this.redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: CachedData<PriceData>): Promise<void> {
await this.redis.set(key, JSON.stringify(value), 'EX', value.ttl / 1000);
}
async delete(key: string): Promise<void> {
await this.redis.del(key);
}
async clear(): Promise<void> {
await this.redis.flushdb();
}
async keys(): Promise<string[]> {
return await this.redis.keys('*');
}
}
// Use with APIService
const redis = new Redis();
const cache = new RedisCacheBackend(redis);
const apiService = new APIService(cache);TypeScript Types
All services are fully typed with TypeScript:
import type {
Dye,
RGB,
HSV,
HexColor,
PriceData,
CachedData,
VisionType,
ErrorSeverity,
ICacheBackend
} from 'xivdyetools-core';Constants
Access color theory and API configuration constants:
import {
RGB_MAX,
HUE_MAX,
VISION_TYPES,
BRETTEL_MATRICES,
UNIVERSALIS_API_BASE,
API_CACHE_TTL
} from 'xivdyetools-core';Utilities
Helper functions for common tasks:
import {
clamp,
lerp,
isValidHexColor,
isValidRGB,
retry,
sleep,
generateChecksum
} from 'xivdyetools-core';
// Validation
const isValid = isValidHexColor('#FF6B6B'); // true
// Math
const clamped = clamp(150, 0, 100); // 100
const interpolated = lerp(0, 100, 0.5); // 50
// Async utilities
await sleep(1000); // Wait 1 second
const result = await retry(() => fetchData(), 3, 1000); // Retry with backoffUse Cases
Discord Bot
// Implement /harmony command
import { DyeService, dyeDatabase } from 'xivdyetools-core';
const dyeService = new DyeService(dyeDatabase);
const baseDye = dyeService.findClosestDye(userColor);
const harmonyDyes = dyeService.findTriadicDyes(userColor);
// Render color wheel, send Discord embedWeb App
// Color matcher tool
import { DyeService, dyeDatabase } from 'xivdyetools-core';
const dyeService = new DyeService(dyeDatabase);
const matchingDyes = dyeService.findDyesWithinDistance(imageColor, 50, 10);
// Display results in UICLI Tool
// Color conversion utility
import { ColorService } from 'xivdyetools-core';
const hex = process.argv[2];
const rgb = ColorService.hexToRgb(hex);
console.log(`RGB: ${rgb.r}, ${rgb.g}, ${rgb.b}`);Requirements
- Node.js 18.0.0 or higher
- TypeScript 5.3 or higher (for development)
Browser Compatibility
Works in all modern browsers with ES6 module support:
- Chrome/Edge 89+
- Firefox 88+
- Safari 15+
License
MIT © 2025 Flash Galatine
See LICENSE for full details.
Legal Notice
This is a fan-made tool and is not affiliated with or endorsed by Square Enix Co., Ltd. FINAL FANTASY is a registered trademark of Square Enix Holdings Co., Ltd.
Coming Soon
Budget-Aware Dye Suggestions - Find affordable alternatives to expensive dyes based on current market prices. See specification for details.
Related Projects
- XIV Dye Tools Web App - Interactive color tools for FFXIV
- XIV Dye Tools Discord Worker - Cloudflare Worker Discord bot using this package
Support
- Issues: GitHub Issues
- NPM Package: xivdyetools-core
- Documentation: Full Docs
Connect With Me
Flash Galatine | Balmung (Crystal)
🎮 FFXIV: Lodestone Character
📝 Blog: Project Galatine
💻 GitHub: @FlashGalatine
🐦 X / Twitter: @AsheJunius
📺 Twitch: flashgalatine
🌐 BlueSky: projectgalatine.com
❤️ Patreon: ProjectGalatine
💬 Discord: Join Server
Made with ❤️ for the FFXIV community
