@xivdyetools/core 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +400 -0
  3. package/dist/constants/index.d.ts +56 -0
  4. package/dist/constants/index.d.ts.map +1 -0
  5. package/dist/constants/index.js +103 -0
  6. package/dist/constants/index.js.map +1 -0
  7. package/dist/data/colors_xiv.json +3130 -0
  8. package/dist/data/locales/de.json +231 -0
  9. package/dist/data/locales/en.json +231 -0
  10. package/dist/data/locales/fr.json +231 -0
  11. package/dist/data/locales/ja.json +231 -0
  12. package/dist/data/locales/ko.json +233 -0
  13. package/dist/data/locales/zh.json +233 -0
  14. package/dist/data/presets.json +390 -0
  15. package/dist/index.d.ts +16 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +18 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/services/APIService.d.ts +246 -0
  20. package/dist/services/APIService.d.ts.map +1 -0
  21. package/dist/services/APIService.js +499 -0
  22. package/dist/services/APIService.js.map +1 -0
  23. package/dist/services/ColorService.d.ts +146 -0
  24. package/dist/services/ColorService.d.ts.map +1 -0
  25. package/dist/services/ColorService.js +209 -0
  26. package/dist/services/ColorService.js.map +1 -0
  27. package/dist/services/DyeService.d.ts +230 -0
  28. package/dist/services/DyeService.d.ts.map +1 -0
  29. package/dist/services/DyeService.js +326 -0
  30. package/dist/services/DyeService.js.map +1 -0
  31. package/dist/services/LocalizationService.d.ts +338 -0
  32. package/dist/services/LocalizationService.d.ts.map +1 -0
  33. package/dist/services/LocalizationService.js +449 -0
  34. package/dist/services/LocalizationService.js.map +1 -0
  35. package/dist/services/PaletteService.d.ts +137 -0
  36. package/dist/services/PaletteService.d.ts.map +1 -0
  37. package/dist/services/PaletteService.js +349 -0
  38. package/dist/services/PaletteService.js.map +1 -0
  39. package/dist/services/PresetService.d.ts +196 -0
  40. package/dist/services/PresetService.d.ts.map +1 -0
  41. package/dist/services/PresetService.js +261 -0
  42. package/dist/services/PresetService.js.map +1 -0
  43. package/dist/services/color/ColorAccessibility.d.ts +39 -0
  44. package/dist/services/color/ColorAccessibility.d.ts.map +1 -0
  45. package/dist/services/color/ColorAccessibility.js +71 -0
  46. package/dist/services/color/ColorAccessibility.js.map +1 -0
  47. package/dist/services/color/ColorConverter.d.ts +146 -0
  48. package/dist/services/color/ColorConverter.d.ts.map +1 -0
  49. package/dist/services/color/ColorConverter.js +393 -0
  50. package/dist/services/color/ColorConverter.js.map +1 -0
  51. package/dist/services/color/ColorManipulator.d.ts +36 -0
  52. package/dist/services/color/ColorManipulator.d.ts.map +1 -0
  53. package/dist/services/color/ColorManipulator.js +56 -0
  54. package/dist/services/color/ColorManipulator.js.map +1 -0
  55. package/dist/services/color/ColorblindnessSimulator.d.ts +35 -0
  56. package/dist/services/color/ColorblindnessSimulator.d.ts.map +1 -0
  57. package/dist/services/color/ColorblindnessSimulator.js +110 -0
  58. package/dist/services/color/ColorblindnessSimulator.js.map +1 -0
  59. package/dist/services/dye/DyeDatabase.d.ts +131 -0
  60. package/dist/services/dye/DyeDatabase.d.ts.map +1 -0
  61. package/dist/services/dye/DyeDatabase.js +367 -0
  62. package/dist/services/dye/DyeDatabase.js.map +1 -0
  63. package/dist/services/dye/DyeSearch.d.ts +55 -0
  64. package/dist/services/dye/DyeSearch.d.ts.map +1 -0
  65. package/dist/services/dye/DyeSearch.js +196 -0
  66. package/dist/services/dye/DyeSearch.js.map +1 -0
  67. package/dist/services/dye/HarmonyGenerator.d.ts +110 -0
  68. package/dist/services/dye/HarmonyGenerator.d.ts.map +1 -0
  69. package/dist/services/dye/HarmonyGenerator.js +221 -0
  70. package/dist/services/dye/HarmonyGenerator.js.map +1 -0
  71. package/dist/services/localization/LocaleLoader.d.ts +38 -0
  72. package/dist/services/localization/LocaleLoader.d.ts.map +1 -0
  73. package/dist/services/localization/LocaleLoader.js +83 -0
  74. package/dist/services/localization/LocaleLoader.js.map +1 -0
  75. package/dist/services/localization/LocaleRegistry.d.ts +73 -0
  76. package/dist/services/localization/LocaleRegistry.d.ts.map +1 -0
  77. package/dist/services/localization/LocaleRegistry.js +84 -0
  78. package/dist/services/localization/LocaleRegistry.js.map +1 -0
  79. package/dist/services/localization/TranslationProvider.d.ts +157 -0
  80. package/dist/services/localization/TranslationProvider.d.ts.map +1 -0
  81. package/dist/services/localization/TranslationProvider.js +289 -0
  82. package/dist/services/localization/TranslationProvider.js.map +1 -0
  83. package/dist/types/index.d.ts +409 -0
  84. package/dist/types/index.d.ts.map +1 -0
  85. package/dist/types/index.js +87 -0
  86. package/dist/types/index.js.map +1 -0
  87. package/dist/types/logger.d.ts +84 -0
  88. package/dist/types/logger.d.ts.map +1 -0
  89. package/dist/types/logger.js +54 -0
  90. package/dist/types/logger.js.map +1 -0
  91. package/dist/utils/index.d.ts +441 -0
  92. package/dist/utils/index.d.ts.map +1 -0
  93. package/dist/utils/index.js +577 -0
  94. package/dist/utils/index.js.map +1 -0
  95. package/dist/utils/kd-tree.d.ts +76 -0
  96. package/dist/utils/kd-tree.d.ts.map +1 -0
  97. package/dist/utils/kd-tree.js +195 -0
  98. package/dist/utils/kd-tree.js.map +1 -0
  99. package/dist/version.d.ts +11 -0
  100. package/dist/version.d.ts.map +1 -0
  101. package/dist/version.js +11 -0
  102. package/dist/version.js.map +1 -0
  103. package/package.json +84 -0
@@ -0,0 +1,449 @@
1
+ /**
2
+ * LocalizationService - Facade for localization functionality
3
+ *
4
+ * Per R-4: Facade pattern - delegates to focused service classes
5
+ * Provides multi-language support with lazy loading and fallback chains
6
+ *
7
+ * @module services
8
+ */
9
+ import { LocaleLoader } from './localization/LocaleLoader.js';
10
+ import { LocaleRegistry } from './localization/LocaleRegistry.js';
11
+ import { TranslationProvider } from './localization/TranslationProvider.js';
12
+ // ============================================================================
13
+ // Pure Utility Functions (Extracted for testability)
14
+ // ============================================================================
15
+ /**
16
+ * List of supported locale codes
17
+ */
18
+ export const SUPPORTED_LOCALES = [
19
+ 'en',
20
+ 'ja',
21
+ 'de',
22
+ 'fr',
23
+ 'ko',
24
+ 'zh',
25
+ ];
26
+ /**
27
+ * Extract 2-letter locale code from longer locale strings
28
+ * Pure function - no side effects, easy to test
29
+ *
30
+ * @param locale - Locale string (e.g., 'en-US', 'ja', 'de-DE')
31
+ * @returns 2-letter locale code or null
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * extractLocaleCode('en-US') // 'en'
36
+ * extractLocaleCode('ja') // 'ja'
37
+ * extractLocaleCode('zh-CN') // null (not supported)
38
+ * ```
39
+ */
40
+ export function extractLocaleCode(locale) {
41
+ const code = locale.split('-')[0].toLowerCase();
42
+ return SUPPORTED_LOCALES.includes(code) ? code : null;
43
+ }
44
+ /**
45
+ * Resolve locale from preference chain
46
+ * Pure function - determines locale based on priority without side effects
47
+ *
48
+ * Priority: explicit > guild > system > fallback
49
+ *
50
+ * @param preference - Locale preference with fallback chain
51
+ * @returns Resolved locale code
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * resolveLocaleFromPreference({
56
+ * explicit: 'de',
57
+ * guild: 'ja',
58
+ * system: 'en-US',
59
+ * fallback: 'en'
60
+ * }); // Returns 'de' (highest priority)
61
+ *
62
+ * resolveLocaleFromPreference({
63
+ * explicit: null,
64
+ * guild: 'ja-JP',
65
+ * system: 'en',
66
+ * fallback: 'en'
67
+ * }); // Returns 'ja' (extracted from guild)
68
+ * ```
69
+ */
70
+ export function resolveLocaleFromPreference(preference) {
71
+ // 1. Try explicit user selection (highest priority)
72
+ if (preference.explicit && SUPPORTED_LOCALES.includes(preference.explicit)) {
73
+ return preference.explicit;
74
+ }
75
+ // 2. Try guild/server preference
76
+ if (preference.guild) {
77
+ const guildLocale = extractLocaleCode(preference.guild);
78
+ if (guildLocale && SUPPORTED_LOCALES.includes(guildLocale)) {
79
+ return guildLocale;
80
+ }
81
+ }
82
+ // 3. Try user's system language
83
+ if (preference.system) {
84
+ const systemLocale = extractLocaleCode(preference.system);
85
+ if (systemLocale && SUPPORTED_LOCALES.includes(systemLocale)) {
86
+ return systemLocale;
87
+ }
88
+ }
89
+ // 4. Fallback
90
+ return preference.fallback;
91
+ }
92
+ /**
93
+ * LocalizationService - Main entry point for localization features
94
+ *
95
+ * Refactored for testability: Supports dependency injection of loader, registry, and translator
96
+ *
97
+ * @example Basic usage (static API)
98
+ * ```typescript
99
+ * import { LocalizationService } from 'xivdyetools-core';
100
+ *
101
+ * // Set locale (lazy loads locale data)
102
+ * await LocalizationService.setLocale('ja');
103
+ *
104
+ * // Get localized label
105
+ * const label = LocalizationService.getLabel('dye'); // "カララント:"
106
+ *
107
+ * // Get localized dye name
108
+ * const dyeName = LocalizationService.getDyeName(5729); // "スノウホワイト"
109
+ * ```
110
+ *
111
+ * @example Instance-based usage for testing
112
+ * ```typescript
113
+ * const mockLoader = new MockLocaleLoader();
114
+ * const service = new LocalizationService({ loader: mockLoader });
115
+ * await service.setLocale('ja');
116
+ * ```
117
+ *
118
+ * @example Discord bot locale resolution
119
+ * ```typescript
120
+ * const preference: LocalePreference = {
121
+ * explicit: interaction.options.getString('language'),
122
+ * guild: interaction.guildLocale,
123
+ * system: interaction.locale,
124
+ * fallback: 'en'
125
+ * };
126
+ * await LocalizationService.setLocaleFromPreference(preference);
127
+ * ```
128
+ */
129
+ export class LocalizationService {
130
+ /**
131
+ * Constructor with optional dependency injection
132
+ * @param config Configuration with optional loader, registry, and translator
133
+ */
134
+ constructor(config = {}) {
135
+ this.currentLocale = 'en';
136
+ this.isInitialized = false;
137
+ this.loader = config.loader || new LocaleLoader();
138
+ this.registry = config.registry || new LocaleRegistry();
139
+ this.translator = config.translator || new TranslationProvider(this.registry);
140
+ }
141
+ /**
142
+ * Get the default singleton instance
143
+ * Per Issue #6: Returns eagerly-initialized instance to avoid race conditions
144
+ */
145
+ static getDefault() {
146
+ return this.defaultInstance;
147
+ }
148
+ /**
149
+ * Set active locale (loads locale file if not cached)
150
+ *
151
+ * @param locale - Locale code ('en', 'ja', 'de', 'fr')
152
+ * @throws {AppError} If locale file fails to load
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * await service.setLocale('ja');
157
+ * console.log(service.getCurrentLocale()); // 'ja'
158
+ * ```
159
+ */
160
+ async setLocale(locale) {
161
+ // Check if already loaded
162
+ if (this.registry.hasLocale(locale)) {
163
+ this.currentLocale = locale;
164
+ this.isInitialized = true;
165
+ return;
166
+ }
167
+ // Load locale data (synchronous as of v1.1.3, async signature kept for API compatibility)
168
+ await Promise.resolve(); // Satisfy async requirement while maintaining sync behavior
169
+ const localeData = this.loader.loadLocale(locale);
170
+ this.registry.registerLocale(localeData);
171
+ this.currentLocale = locale;
172
+ this.isInitialized = true;
173
+ }
174
+ /**
175
+ * Static method: Set locale using default instance
176
+ */
177
+ static async setLocale(locale) {
178
+ return this.getDefault().setLocale(locale);
179
+ }
180
+ /**
181
+ * Set locale from preference object with priority chain
182
+ * Uses pure function resolveLocaleFromPreference for resolution logic
183
+ *
184
+ * Priority: explicit > guild > system > fallback
185
+ *
186
+ * @param preference - Locale preference with fallback chain
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * // Discord bot usage
191
+ * await service.setLocaleFromPreference({
192
+ * explicit: 'de', // User selected German
193
+ * guild: 'ja', // Server is Japanese
194
+ * system: 'en-US', // User's system is English
195
+ * fallback: 'en'
196
+ * });
197
+ * // Sets locale to 'de' (highest priority)
198
+ * ```
199
+ */
200
+ async setLocaleFromPreference(preference) {
201
+ const locale = resolveLocaleFromPreference(preference);
202
+ await this.setLocale(locale);
203
+ }
204
+ /**
205
+ * Static method: Set locale from preference using default instance
206
+ */
207
+ static async setLocaleFromPreference(preference) {
208
+ return this.getDefault().setLocaleFromPreference(preference);
209
+ }
210
+ /**
211
+ * Get current active locale
212
+ *
213
+ * @returns Current locale code
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * console.log(service.getCurrentLocale()); // 'ja'
218
+ * ```
219
+ */
220
+ getCurrentLocale() {
221
+ return this.currentLocale;
222
+ }
223
+ /**
224
+ * Static method: Get current locale from default instance
225
+ */
226
+ static getCurrentLocale() {
227
+ return this.getDefault().getCurrentLocale();
228
+ }
229
+ /**
230
+ * Check if service is initialized with a locale
231
+ *
232
+ * @returns true if locale data is loaded
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * if (!service.isLocaleLoaded()) {
237
+ * await service.setLocale('en');
238
+ * }
239
+ * ```
240
+ */
241
+ isLocaleLoaded() {
242
+ return this.isInitialized;
243
+ }
244
+ /**
245
+ * Static method: Check if locale is loaded in default instance
246
+ */
247
+ static isLocaleLoaded() {
248
+ return this.getDefault().isLocaleLoaded();
249
+ }
250
+ /**
251
+ * Get localized UI label (with fallback to English)
252
+ *
253
+ * @param key - Translation key
254
+ * @returns Localized label
255
+ */
256
+ getLabel(key) {
257
+ return this.translator.getLabel(key, this.currentLocale);
258
+ }
259
+ /**
260
+ * Static method: Get localized label using default instance
261
+ */
262
+ static getLabel(key) {
263
+ return this.getDefault().getLabel(key);
264
+ }
265
+ /**
266
+ * Get localized dye name by itemID
267
+ *
268
+ * @param itemID - Dye item ID (5729-48227)
269
+ * @returns Localized name or null if not found
270
+ */
271
+ getDyeName(itemID) {
272
+ return this.translator.getDyeName(itemID, this.currentLocale);
273
+ }
274
+ /**
275
+ * Static method: Get localized dye name using default instance
276
+ */
277
+ static getDyeName(itemID) {
278
+ return this.getDefault().getDyeName(itemID);
279
+ }
280
+ /**
281
+ * Get localized category name
282
+ *
283
+ * @param category - Category key (e.g., "Reds", "Blues")
284
+ * @returns Localized category name
285
+ */
286
+ getCategory(category) {
287
+ return this.translator.getCategory(category, this.currentLocale);
288
+ }
289
+ /**
290
+ * Static method: Get localized category using default instance
291
+ */
292
+ static getCategory(category) {
293
+ return this.getDefault().getCategory(category);
294
+ }
295
+ /**
296
+ * Get localized acquisition method
297
+ *
298
+ * @param acquisition - Acquisition key (e.g., "Dye Vendor", "Crafting")
299
+ * @returns Localized acquisition method
300
+ */
301
+ getAcquisition(acquisition) {
302
+ return this.translator.getAcquisition(acquisition, this.currentLocale);
303
+ }
304
+ /**
305
+ * Static method: Get localized acquisition using default instance
306
+ */
307
+ static getAcquisition(acquisition) {
308
+ return this.getDefault().getAcquisition(acquisition);
309
+ }
310
+ /**
311
+ * Get all metallic dye IDs (for exclusion filtering)
312
+ *
313
+ * @returns Array of metallic dye item IDs
314
+ */
315
+ getMetallicDyeIds() {
316
+ return this.translator.getMetallicDyeIds(this.currentLocale);
317
+ }
318
+ /**
319
+ * Static method: Get metallic dye IDs using default instance
320
+ */
321
+ static getMetallicDyeIds() {
322
+ return this.getDefault().getMetallicDyeIds();
323
+ }
324
+ /**
325
+ * Get localized harmony type name
326
+ *
327
+ * @param key - Harmony type key
328
+ * @returns Localized harmony type
329
+ */
330
+ getHarmonyType(key) {
331
+ return this.translator.getHarmonyType(key, this.currentLocale);
332
+ }
333
+ /**
334
+ * Static method: Get localized harmony type using default instance
335
+ */
336
+ static getHarmonyType(key) {
337
+ return this.getDefault().getHarmonyType(key);
338
+ }
339
+ /**
340
+ * Get localized vision type name
341
+ *
342
+ * @param key - Vision type key
343
+ * @returns Localized vision type
344
+ */
345
+ getVisionType(key) {
346
+ return this.translator.getVisionType(key, this.currentLocale);
347
+ }
348
+ /**
349
+ * Static method: Get localized vision type using default instance
350
+ */
351
+ static getVisionType(key) {
352
+ return this.getDefault().getVisionType(key);
353
+ }
354
+ /**
355
+ * Get localized job name
356
+ *
357
+ * @param key - Job key
358
+ * @returns Localized job name
359
+ */
360
+ getJobName(key) {
361
+ return this.translator.getJobName(key, this.currentLocale);
362
+ }
363
+ /**
364
+ * Static method: Get localized job name using default instance
365
+ */
366
+ static getJobName(key) {
367
+ return this.getDefault().getJobName(key);
368
+ }
369
+ /**
370
+ * Get localized Grand Company name
371
+ *
372
+ * @param key - Grand Company key
373
+ * @returns Localized Grand Company name
374
+ */
375
+ getGrandCompanyName(key) {
376
+ return this.translator.getGrandCompanyName(key, this.currentLocale);
377
+ }
378
+ /**
379
+ * Static method: Get localized Grand Company name using default instance
380
+ */
381
+ static getGrandCompanyName(key) {
382
+ return this.getDefault().getGrandCompanyName(key);
383
+ }
384
+ /**
385
+ * Get all available locale codes
386
+ *
387
+ * @returns Array of supported locale codes
388
+ */
389
+ getAvailableLocales() {
390
+ return [...SUPPORTED_LOCALES];
391
+ }
392
+ /**
393
+ * Static method: Get available locales
394
+ */
395
+ static getAvailableLocales() {
396
+ return [...SUPPORTED_LOCALES];
397
+ }
398
+ /**
399
+ * Preload multiple locales for instant switching
400
+ * Useful for apps that need to switch locales without delay
401
+ *
402
+ * @param locales - Array of locale codes to preload
403
+ */
404
+ async preloadLocales(locales) {
405
+ await Promise.all(locales.map((locale) => this.setLocale(locale)));
406
+ }
407
+ /**
408
+ * Static method: Preload locales using default instance
409
+ */
410
+ static async preloadLocales(locales) {
411
+ return this.getDefault().preloadLocales(locales);
412
+ }
413
+ /**
414
+ * Clear all loaded locales from cache
415
+ * Useful for testing or memory management
416
+ */
417
+ clear() {
418
+ this.registry.clear();
419
+ this.currentLocale = 'en';
420
+ this.isInitialized = false;
421
+ }
422
+ /**
423
+ * Static method: Clear cache of default instance
424
+ */
425
+ static clear() {
426
+ this.getDefault().clear();
427
+ }
428
+ /**
429
+ * Reset the static singleton instance
430
+ * Useful for testing to prevent test pollution between test suites
431
+ * Per Issue #6: Creates a fresh instance since we use eager initialization
432
+ *
433
+ * @example
434
+ * ```typescript
435
+ * // In test cleanup
436
+ * afterEach(() => {
437
+ * LocalizationService.resetInstance();
438
+ * });
439
+ * ```
440
+ */
441
+ static resetInstance() {
442
+ this.defaultInstance.clear();
443
+ this.defaultInstance = new LocalizationService();
444
+ }
445
+ }
446
+ // Default singleton instance for static API compatibility
447
+ // Per Issue #6: Eager initialization to avoid race conditions in concurrent scenarios
448
+ LocalizationService.defaultInstance = new LocalizationService();
449
+ //# sourceMappingURL=LocalizationService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalizationService.js","sourceRoot":"","sources":["../../src/services/LocalizationService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @xivdyetools/core - Palette Extraction Service
3
+ *
4
+ * Extracts dominant colors from pixel data using K-means clustering.
5
+ * Matches extracted colors to closest FFXIV dyes.
6
+ *
7
+ * @module services/PaletteService
8
+ * @example
9
+ * ```typescript
10
+ * import { PaletteService, DyeService, dyeDatabase } from '@xivdyetools/core';
11
+ *
12
+ * const paletteService = new PaletteService();
13
+ * const dyeService = new DyeService(dyeDatabase);
14
+ *
15
+ * // Extract 4 dominant colors from pixel data
16
+ * const palette = paletteService.extractPalette(pixels, 4);
17
+ *
18
+ * // Extract and match to dyes in one step
19
+ * const matches = paletteService.extractAndMatchPalette(pixels, 4, dyeService);
20
+ * ```
21
+ */
22
+ import type { RGB, Dye, Logger } from '../types/index.js';
23
+ import type { DyeService } from './DyeService.js';
24
+ /**
25
+ * Options for palette extraction
26
+ */
27
+ export interface PaletteExtractionOptions {
28
+ /** Number of colors to extract (3-5, default: 4) */
29
+ colorCount?: number;
30
+ /** Maximum K-means iterations (default: 25) */
31
+ maxIterations?: number;
32
+ /** Convergence threshold in RGB distance (default: 1.0) */
33
+ convergenceThreshold?: number;
34
+ /** Maximum pixels to sample (default: 10000) */
35
+ maxSamples?: number;
36
+ }
37
+ /**
38
+ * An extracted color with its dominance (cluster size)
39
+ */
40
+ export interface ExtractedColor {
41
+ /** The extracted RGB color (cluster centroid) */
42
+ color: RGB;
43
+ /** Percentage of pixels in this cluster (0-100) */
44
+ dominance: number;
45
+ /** Number of pixels in this cluster */
46
+ pixelCount: number;
47
+ }
48
+ /**
49
+ * A matched palette entry with extracted and matched dye
50
+ */
51
+ export interface PaletteMatch {
52
+ /** The extracted RGB color */
53
+ extracted: RGB;
54
+ /** The closest matching FFXIV dye */
55
+ matchedDye: Dye;
56
+ /** Color distance between extracted and matched (lower is better) */
57
+ distance: number;
58
+ /** Percentage of pixels in this cluster (0-100) */
59
+ dominance: number;
60
+ }
61
+ /**
62
+ * Configuration options for PaletteService
63
+ */
64
+ export interface PaletteServiceOptions {
65
+ /** Logger for service operations (defaults to NoOpLogger) */
66
+ logger?: Logger;
67
+ }
68
+ /**
69
+ * Service for extracting color palettes from images
70
+ *
71
+ * Uses K-means clustering to find dominant colors, then matches
72
+ * them to the closest FFXIV dyes.
73
+ */
74
+ export declare class PaletteService {
75
+ private logger;
76
+ /** Default extraction options */
77
+ private static readonly DEFAULT_OPTIONS;
78
+ /**
79
+ * Create a new PaletteService
80
+ * @param options - Optional configuration including logger
81
+ */
82
+ constructor(options?: PaletteServiceOptions);
83
+ /**
84
+ * Sample pixels from an array if it exceeds maxSamples
85
+ * Uses uniform sampling to maintain color distribution
86
+ * CORE-PERF-004: Fixed potential out-of-bounds access on last iteration
87
+ */
88
+ private samplePixels;
89
+ /**
90
+ * Extract dominant colors from pixel data
91
+ *
92
+ * @param pixels - Array of RGB pixel values
93
+ * @param options - Extraction options (colorCount, maxIterations, etc.)
94
+ * @returns Array of extracted colors sorted by dominance (most dominant first)
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const pixels = [{ r: 255, g: 0, b: 0 }, { r: 254, g: 1, b: 1 }, ...];
99
+ * const palette = paletteService.extractPalette(pixels, { colorCount: 4 });
100
+ * // Returns: [{ color: { r: 255, g: 0, b: 0 }, dominance: 45, pixelCount: 4500 }, ...]
101
+ * ```
102
+ */
103
+ extractPalette(pixels: RGB[], options?: PaletteExtractionOptions): ExtractedColor[];
104
+ /**
105
+ * Extract colors from pixel data and match each to the closest FFXIV dye
106
+ *
107
+ * @param pixels - Array of RGB pixel values
108
+ * @param dyeService - DyeService instance for matching
109
+ * @param options - Extraction options
110
+ * @returns Array of palette matches sorted by dominance
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const matches = paletteService.extractAndMatchPalette(pixels, dyeService, { colorCount: 4 });
115
+ * // Returns: [{ extracted: {...}, matchedDye: {...}, distance: 12.3, dominance: 45 }, ...]
116
+ * ```
117
+ */
118
+ extractAndMatchPalette(pixels: RGB[], dyeService: DyeService, options?: PaletteExtractionOptions): PaletteMatch[];
119
+ /**
120
+ * Convert flat pixel array (Uint8ClampedArray from canvas) to RGB array
121
+ * Skips alpha channel
122
+ *
123
+ * @param data - Flat array of RGBA values [r, g, b, a, r, g, b, a, ...]
124
+ * @returns Array of RGB objects
125
+ */
126
+ static pixelDataToRGB(data: Uint8ClampedArray | number[]): RGB[];
127
+ /**
128
+ * Filter out near-transparent pixels from RGBA data
129
+ * Useful for images with transparent backgrounds
130
+ *
131
+ * @param data - Flat array of RGBA values
132
+ * @param alphaThreshold - Minimum alpha to include (0-255, default: 128)
133
+ * @returns Array of RGB objects for non-transparent pixels
134
+ */
135
+ static pixelDataToRGBFiltered(data: Uint8ClampedArray | number[], alphaThreshold?: number): RGB[];
136
+ }
137
+ //# sourceMappingURL=PaletteService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaletteService.d.ts","sourceRoot":"","sources":["../../src/services/PaletteService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMlD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,KAAK,EAAE,GAAG,CAAC;IACX,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,SAAS,EAAE,GAAG,CAAC;IACf,qCAAqC;IACrC,UAAU,EAAE,GAAG,CAAC;IAChB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAmND;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IAEvB,iCAAiC;IACjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAKrC;IAEF;;;OAGG;gBACS,OAAO,GAAE,qBAA0B;IAI/C;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAiBpB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,cAAc,EAAE;IA6CvF;;;;;;;;;;;;;OAaG;IACH,sBAAsB,CACpB,MAAM,EAAE,GAAG,EAAE,EACb,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,wBAA6B,GACrC,YAAY,EAAE;IA4BjB;;;;;;OAMG;IACH,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,EAAE,GAAG,GAAG,EAAE;IAehE;;;;;;;OAOG;IACH,MAAM,CAAC,sBAAsB,CAC3B,IAAI,EAAE,iBAAiB,GAAG,MAAM,EAAE,EAClC,cAAc,GAAE,MAAY,GAC3B,GAAG,EAAE;CAgBT"}