@jjlmoya/utils-games-development 1.55.0 → 1.57.0

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +5 -1
  3. package/src/index.ts +1 -0
  4. package/src/tests/contrast_legibility.test.ts +56 -10
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/runtime_registry.test.ts +27 -0
  7. package/src/tests/tool_validation.test.ts +2 -2
  8. package/src/tool/localizationSanitizer/localization-sanitizer.css +4 -2
  9. package/src/tool/pixelArtPaletteSwapper/app-client.ts +234 -0
  10. package/src/tool/pixelArtPaletteSwapper/bibliography.astro +6 -0
  11. package/src/tool/pixelArtPaletteSwapper/bibliography.ts +16 -0
  12. package/src/tool/pixelArtPaletteSwapper/component.astro +153 -0
  13. package/src/tool/pixelArtPaletteSwapper/entry.ts +28 -0
  14. package/src/tool/pixelArtPaletteSwapper/i18n/de.ts +233 -0
  15. package/src/tool/pixelArtPaletteSwapper/i18n/en.ts +233 -0
  16. package/src/tool/pixelArtPaletteSwapper/i18n/es.ts +233 -0
  17. package/src/tool/pixelArtPaletteSwapper/i18n/fr.ts +233 -0
  18. package/src/tool/pixelArtPaletteSwapper/i18n/id.ts +233 -0
  19. package/src/tool/pixelArtPaletteSwapper/i18n/it.ts +233 -0
  20. package/src/tool/pixelArtPaletteSwapper/i18n/ja.ts +225 -0
  21. package/src/tool/pixelArtPaletteSwapper/i18n/ko.ts +225 -0
  22. package/src/tool/pixelArtPaletteSwapper/i18n/nl.ts +233 -0
  23. package/src/tool/pixelArtPaletteSwapper/i18n/pl.ts +233 -0
  24. package/src/tool/pixelArtPaletteSwapper/i18n/pt.ts +233 -0
  25. package/src/tool/pixelArtPaletteSwapper/i18n/ru.ts +233 -0
  26. package/src/tool/pixelArtPaletteSwapper/i18n/sv.ts +233 -0
  27. package/src/tool/pixelArtPaletteSwapper/i18n/tr.ts +233 -0
  28. package/src/tool/pixelArtPaletteSwapper/i18n/zh.ts +225 -0
  29. package/src/tool/pixelArtPaletteSwapper/index.ts +12 -0
  30. package/src/tool/pixelArtPaletteSwapper/logic.test.ts +43 -0
  31. package/src/tool/pixelArtPaletteSwapper/logic.ts +201 -0
  32. package/src/tool/pixelArtPaletteSwapper/pixel-art-palette-swapper.css +728 -0
  33. package/src/tool/pixelArtPaletteSwapper/seo.astro +15 -0
  34. package/src/tool/pixelArtPaletteSwapper/ui.ts +35 -0
  35. package/src/tool/retroSfxGenerator/client.ts +9 -2
  36. package/src/tool/retroSfxGenerator/logic.test.ts +21 -0
  37. package/src/tool/retroSfxGenerator/logic.ts +1 -1
  38. package/src/tool/retroSfxGenerator/retro-sfx-generator.css +10 -4
  39. package/src/tools.ts +2 -0
@@ -0,0 +1,233 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { PixelArtPaletteSwapperUI } from '../ui';
3
+
4
+ export const content: ToolLocaleContent<PixelArtPaletteSwapperUI> = {
5
+ slug: 'piksel-sanati-palet-degistirici',
6
+ title: 'Piksel Sanatı Palet Değiştirici',
7
+ description: 'Sprite ve sprite sayfalarındaki renkleri klasik konsol paletlerine veya özel hex renklerine doğrudan tarayıcınızda dönüştürün.',
8
+ ui: {
9
+ uploadTitle: 'Bir sprite veya sprite sayfası bırakın',
10
+ uploadHint: 'PNG, JPEG veya WebP dosyaları doğrudan cihazınızda işlenir',
11
+ chooseImage: 'Görsel seç',
12
+ replaceImage: 'Görseli değiştir',
13
+ paletteTitle: 'Palet seçin',
14
+ gameBoyPalette: 'Game Boy',
15
+ nesPalette: 'NES esintili',
16
+ pico8Palette: 'PICO-8',
17
+ commodore64Palette: 'Commodore 64',
18
+ dawnBringerPalette: 'DawnBringer 16',
19
+ customPalette: 'Özel renkler',
20
+ customPaletteHint: 'Hex değerlerini virgül, boşluk veya yeni satır ile ayırın.',
21
+ applyCustomPalette: 'Paleti uygula',
22
+ resetCustomPalette: 'Sıfırla',
23
+ sourcePreview: 'Orijinal',
24
+ resultPreview: 'Dönüştürülmüş sonuç',
25
+ waitingForImage: 'Bir görsel bekleniyor',
26
+ uploadToPreview: 'Önizlemek için bir görsel yükleyin',
27
+ resultEmpty: 'Orijinal ve palet uygulanmış sürümler yan yana görünecektir.',
28
+ downloadPng: 'PNG İndir',
29
+ downloadDisabled: 'Dışa aktarmak için bir görsel yükleyin.',
30
+ colorCount: 'Kaynak renkler',
31
+ mappedCount: 'Kullanılan renkler',
32
+ imageSize: 'Görsel boyutu',
33
+ paletteCount: 'palet renkleri',
34
+ preserveAlpha: 'Şeffaflığı koru',
35
+ zoomLabel: 'Yakınlaştırma',
36
+ processing: 'Pikseller eşleştiriliyor',
37
+ invalidPalette: 'En az bir geçerli hex rengi ekleyin',
38
+ invalidImage: 'Geçerli bir PNG, JPEG veya WebP görseli seçin',
39
+ readyStatus: 'Hazır',
40
+ dropActive: 'Yüklemek için bırakın',
41
+ mappedSummary: '{source} kaynak renk {mapped} palet rengine eşlendi',
42
+ },
43
+ seo: [
44
+ {
45
+ type: 'title',
46
+ level: 2,
47
+ text: 'Tam Renkli Sprite Görsellerini Planlı Retro Paletlere Dönüştürün',
48
+ },
49
+ {
50
+ type: 'paragraph',
51
+ html: 'Sınırlı bir palet, teknik bir kısıtlamadan daha fazlasıdır. Sprite görsellerinize tutarlı bir renk dili kazandırır, sahnelerin birbirine ait görünmesini sağlar ve belirli bir konsolun veya hayali donanımın görsel karakterini yansıtır. Tarayıcı tabanlı bu palet değiştirici, Game Boy, NES esintili, PICO-8, Commodore 64, DawnBringer 16 ve özel paletleri denerken orijinal görsel ile indirgenmiş sürümü karşılaştırmanıza olanak tanır.',
52
+ },
53
+ {
54
+ type: 'title',
55
+ level: 2,
56
+ text: 'En Yakın Renk Eşleme Nasıl Çalışır',
57
+ },
58
+ {
59
+ type: 'paragraph',
60
+ html: 'Araç, görünür her pikselin kırmızı, yeşil ve mavi kanallarını okur ve bu rengi seçilen paletteki her renkle karşılaştırır. En küçük karesel RGB mesafesine sahip palet girdisini seçer ve yeni bir tuval arabelleğine yazar. Şeffaflığı koru seçeneği etkinleştirildiğinde, şeffaf pikseller şeffaf kalır ve yarı şeffaf kenarlar orijinal opaklığını korur.',
61
+ },
62
+ {
63
+ type: 'comparative',
64
+ columns: 2,
65
+ items: [
66
+ {
67
+ title: 'Palet Sınırlama',
68
+ description: 'Her kaynak renk, mevcut en yakın renk örneğiyle değiştirilir.',
69
+ points: [
70
+ 'Sprite, simge, karo ve arayüz çizimleri için hızlı ve öngörülebilir',
71
+ 'Orijinal boyutları ve piksel konumlarını korur',
72
+ 'Belirli bir renk bütçesini gözden geçirmeyi kolaylaştırır',
73
+ ],
74
+ },
75
+ {
76
+ title: 'Palet Değiştirme (Palette Swapping)',
77
+ description: 'Aynı çizim, özenle seçilmiş başka bir renk kümesine yeniden eşlenebilir.',
78
+ points: [
79
+ 'Alternatif kostümler, biyomlar ve hasar durumları için kullanışlıdır',
80
+ 'Özel hex listeleri mevcut sanat yönünüze uymanızı sağlar',
81
+ 'İndirilen PNG düzenleyicinize aktarılmaya hazırdır',
82
+ ],
83
+ },
84
+ ],
85
+ },
86
+ {
87
+ type: 'title',
88
+ level: 2,
89
+ text: 'Piksel Sanatı İçin Palet Seçimi',
90
+ },
91
+ {
92
+ type: 'table',
93
+ headers: ['Palet', 'Renkler', 'Uygunluk', 'Dikkat Edilmesi Gerekenler'],
94
+ rows: [
95
+ ['Game Boy', '4', 'Monokrom el konsolu hissi ve güçlü değer çalışmaları', 'Dar değer aralığı yakın malzemeleri birleştirebilir'],
96
+ ['NES esintili', '16', 'Hacimli arcade sprite çizimleri, karakterler ve karolar', 'Çok parlak renkler küçük detayları bastırabilir'],
97
+ ['PICO-8', '16', 'Doygun vurgulara sahip modern piksel sanatı', 'Yüksek doygunluktaki tonlar bilinçli kontrast gerektirir'],
98
+ ['Commodore 64', '16', 'Yumuşak retro sahneler ve bilgisayar oyunu estetiği', 'Düşük kontrast net siluetlerden faydalanır'],
99
+ ['DawnBringer 16', '16', 'Genel piksel sanatı için esnek özel palet', 'Rampa renkleri yine de planlı ışık yönü gerektirir'],
100
+ ],
101
+ },
102
+ {
103
+ type: 'title',
104
+ level: 2,
105
+ text: 'Sprite Sayfaları İçin Pratik İş Akışı',
106
+ },
107
+ {
108
+ type: 'paragraph',
109
+ html: 'Rahatça düzenleyebileceğiniz en büyük kaynak çizimle başlayın, ardından dışa aktarılan sprite veya sprite sayfasını buraya yükleyin. Bir yön belirlemek için bir hazır ayar seçin veya palet kitaplığından özel bir liste yapıştırın. İki tuvali daha yüksek yakınlaştırmada inceleyin; kaybolan yüz özelliklerini, birleşmiş hatları ve ana renkten ayrılmayan vurguları arayın. Sonuç mat görünüyorsa, daha güçlü bir değer adımına sahip bir palet deneyin veya özel listeye bilinçli bir vurgu rengi ekleyin.',
110
+ },
111
+ {
112
+ type: 'tip',
113
+ title: 'Paleti Bilinçli Tutun',
114
+ html: 'Daha büyük bir liste otomatik olarak daha iyi değildir. Dört ila on altı renkle başlayın, her renge bir görev atayın ve en parlak değerleri odak noktaları veya okunabilir vurgular için saklayın. En yakın renk algoritması konumları korur ancak çiziminizin görsel hiyerarşisini hangi renklerin taşıyacağına karar veremez.',
115
+ },
116
+ {
117
+ type: 'title',
118
+ level: 2,
119
+ text: 'Piksel Sanatı Dışa Aktarım Kontrol Listesi',
120
+ },
121
+ {
122
+ type: 'diagnostic',
123
+ variant: 'info',
124
+ title: 'İndirgenmiş PNG Görselini İçe Aktarmadan Önce',
125
+ html: 'Sonucu yüzde 100 ölçekte ve oyun içi nihai ölçekte kontrol edin, şeffaf kenarların temiz olduğunu doğrulayın, önemli siluetlerin okunabilir kaldığından emin olun ve sıfırdan başlamadan paleti revize edebilmek için orijinal kaynak dosyayı dışa aktarılan dosyanın yanında saklayın.',
126
+ },
127
+ {
128
+ type: 'glossary',
129
+ items: [
130
+ {
131
+ term: 'Renk Kuantizasyonu',
132
+ definition: 'Geniş bir kaynak renk kümesini daha küçük ve tanımlı bir kümeye indirgeme işlemi.',
133
+ },
134
+ {
135
+ term: 'Palet Rampası',
136
+ definition: 'Bir malzemeyi veya yüzeyi gölgelendirmek için kullanılan koyu, orta ve açık renklerin ilişkili dizisi.',
137
+ },
138
+ {
139
+ term: 'İndeksli Palet',
140
+ definition: 'Görüntü piksellerinin tam renkleri tekrar tekrar saklamak yerine ortak bir listedeki girdilere başvurduğu kompakt renk tablosu.',
141
+ },
142
+ ],
143
+ },
144
+ ],
145
+ faq: [
146
+ {
147
+ question: 'Palet değiştirici görselimi bir sunucuya yüklüyor mu?',
148
+ answer: 'Hayır. Görsel tarayıcınızda bir tuvale dönüştürülür, JavaScript ile yerel olarak eşlenir ve doğrudan PNG olarak dışa aktarılır. Aracın yükleme adımı yoktur.',
149
+ },
150
+ {
151
+ question: 'Kendi paletimi kullanabilir miyim?',
152
+ answer: 'Evet. Özel renkler alanına virgül, boşluk veya yeni satırla ayrılmış altı haneli veya üç haneli hex renklerini yapıştırın, ardından Paleti uygula butonunu seçin.',
153
+ },
154
+ {
155
+ question: 'Sprite veya sprite sayfamın boyutunu değiştirir mi?',
156
+ answer: 'Hayır. Çıktı, Şeffaflığı koru seçeneği etkinleştirildiğinde kaynak genişliği, yüksekliği, piksel konumlarını ve alfa değerlerini korur.',
157
+ },
158
+ {
159
+ question: 'Hangi algoritmayı kullanıyor?',
160
+ answer: 'Görünür her piksel, RGB uzayında karesel Öklid mesafesi kullanılarak seçilen paletteki en yakın renge atanır. Hızlı, kararlı ve önizlemesi kolaydır ancak tarama (dithering) veya algısal Lab renk düzeltmesi uygulamaz.',
161
+ },
162
+ ],
163
+ howTo: [
164
+ {
165
+ name: 'Sprite Yükleyin',
166
+ text: 'Çalışma alanına bir PNG, JPEG veya WebP sprite veya sprite sayfası bırakın ya da Görsel seç butonunu kullanın.',
167
+ },
168
+ {
169
+ name: 'Palet Seçin',
170
+ text: 'Klasik bir hazır ayar seçin veya kendi hex renklerinizi girin. Palet uygulandığında sonuç hemen güncellenir.',
171
+ },
172
+ {
173
+ name: 'Karşılaştırın ve Dışa Aktarın',
174
+ text: 'Orijinal ve indirgenmiş tuvalleri inceleyin, önizleme yakınlaştırmasını ayarlayın ve sonucu PNG olarak indirin.',
175
+ },
176
+ ],
177
+ schemas: [
178
+ {
179
+ '@context': 'https://schema.org',
180
+ '@type': 'SoftwareApplication',
181
+ name: 'Piksel Sanatı Palet Değiştirici',
182
+ applicationCategory: 'DesignApplication',
183
+ operatingSystem: 'Any',
184
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
185
+ },
186
+ {
187
+ '@context': 'https://schema.org',
188
+ '@type': 'FAQPage',
189
+ mainEntity: [
190
+ {
191
+ '@type': 'Question',
192
+ name: 'Palet değiştirici görselimi bir sunucuya yüklüyor mu?',
193
+ acceptedAnswer: {
194
+ '@type': 'Answer',
195
+ text: 'Hayır. Görsel tarayıcıda işlenir ve doğrudan PNG olarak dışa aktarılır.',
196
+ },
197
+ },
198
+ {
199
+ '@type': 'Question',
200
+ name: 'Kendi paletimi kullanabilir miyim?',
201
+ acceptedAnswer: {
202
+ '@type': 'Answer',
203
+ text: 'Evet. Özel renkler alanına hex renklerini yapıştırıp paleti uygulayabilirsiniz.',
204
+ },
205
+ },
206
+ ],
207
+ },
208
+ {
209
+ '@context': 'https://schema.org',
210
+ '@type': 'HowTo',
211
+ name: 'Sprite Görsellerini Retro Palete Dönüştürme',
212
+ step: [
213
+ { '@type': 'HowToStep', name: 'Sprite Yükleyin', text: 'Çalışma alanına bir görsel bırakın.' },
214
+ { '@type': 'HowToStep', name: 'Palet Seçin', text: 'Bir hazır ayar seçin veya hex renklerinizi girin.' },
215
+ { '@type': 'HowToStep', name: 'Karşılaştırın ve Dışa Aktarın', text: 'Sonucu inceleyin ve PNG olarak indirin.' },
216
+ ],
217
+ },
218
+ ],
219
+ bibliography: [
220
+ {
221
+ name: 'MDN Canvas API',
222
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API',
223
+ },
224
+ {
225
+ name: 'Lospec Palette List',
226
+ url: 'https://lospec.com/palette-list',
227
+ },
228
+ {
229
+ name: 'W3C Portable Network Graphics Specification',
230
+ url: 'https://www.w3.org/TR/png-3/',
231
+ },
232
+ ],
233
+ };
@@ -0,0 +1,225 @@
1
+ import type { ToolLocaleContent } from '../../../types';
2
+ import type { PixelArtPaletteSwapperUI } from '../ui';
3
+
4
+ export const content: ToolLocaleContent<PixelArtPaletteSwapperUI> = {
5
+ slug: 'pixel-art-palette-swapper',
6
+ title: '像素艺术调色板调换工具',
7
+ description: '在浏览器中将精灵图和精灵图集快速转换为复古主机调色板或自定义 Hex 颜色。',
8
+ ui: {
9
+ uploadTitle: '拖放精灵图或精灵图集',
10
+ uploadHint: 'PNG、JPEG 或 WebP 文件均在本地处理',
11
+ chooseImage: '选择图片',
12
+ replaceImage: '替换图片',
13
+ paletteTitle: '选择调色板',
14
+ gameBoyPalette: 'Game Boy',
15
+ nesPalette: 'NES 风格',
16
+ pico8Palette: 'PICO-8',
17
+ commodore64Palette: 'Commodore 64',
18
+ dawnBringerPalette: 'DawnBringer 16',
19
+ customPalette: '自定义颜色',
20
+ customPaletteHint: '使用逗号、空格或换行分隔 Hex 颜色代码。',
21
+ applyCustomPalette: '应用调色板',
22
+ resetCustomPalette: '重置',
23
+ sourcePreview: '原图',
24
+ resultPreview: '转换结果',
25
+ waitingForImage: '等待加载图片',
26
+ uploadToPreview: '上传图片以预览效果',
27
+ resultEmpty: '原图与调色板转换后的效果将并排显示。',
28
+ downloadPng: '下载 PNG',
29
+ downloadDisabled: '请先上传图片以启用导出。',
30
+ colorCount: '原图颜色数',
31
+ mappedCount: '已用颜色数',
32
+ imageSize: '图片尺寸',
33
+ paletteCount: '调色板颜色数',
34
+ preserveAlpha: '保留透明度',
35
+ zoomLabel: '缩放',
36
+ processing: '正在映射像素',
37
+ invalidPalette: '请至少添加一个有效的 Hex 颜色代码',
38
+ invalidImage: '请选择 PNG、JPEG 或 WebP 格式图片',
39
+ readyStatus: '就绪',
40
+ dropActive: '松开即可加载',
41
+ mappedSummary: '已将 {source} 种原图颜色映射至 {mapped} 种调色板颜色',
42
+ },
43
+ seo: [
44
+ {
45
+ type: 'title',
46
+ level: 2,
47
+ text: '将全彩像素图转换为经典的复古主机配色',
48
+ },
49
+ {
50
+ type: 'paragraph',
51
+ html: '限制色彩数量能赋予像素艺术独特的复古风格。本工具支持转换为 Game Boy、NES、PICO-8 等经典主机的调色板。',
52
+ },
53
+ {
54
+ type: 'title',
55
+ level: 2,
56
+ text: '颜色映射计算原理',
57
+ },
58
+ {
59
+ type: 'paragraph',
60
+ html: '工具会计算每个像素 RGB 值的距离,并将其替换为目标调色板中最接近的颜色,同时完好保留透明通道。',
61
+ },
62
+ {
63
+ type: 'comparative',
64
+ columns: 2,
65
+ items: [
66
+ {
67
+ title: '调色板限制',
68
+ description: '将原有颜色替换为最接近的色块。',
69
+ points: [
70
+ '适合像素角色、图标及图块集',
71
+ '保持原始尺寸与像素位置不变',
72
+ '便于精确掌控色彩预算',
73
+ ],
74
+ },
75
+ {
76
+ title: '调色板调换(Palette Swapping)',
77
+ description: '快速生成不同配色的角色或场景。',
78
+ points: [
79
+ '常用于换装、变色或状态变化',
80
+ '支持自定义 Hex 列表导入',
81
+ '可直接下载 PNG 导入引擎',
82
+ ],
83
+ },
84
+ ],
85
+ },
86
+ {
87
+ type: 'title',
88
+ level: 2,
89
+ text: '复古调色板对比',
90
+ },
91
+ {
92
+ type: 'table',
93
+ headers: ['调色板', '颜色数', '适用场景', '注意事项'],
94
+ rows: [
95
+ ['Game Boy', '4', '黑白掌机复古风格', '对比度较低时细节易混淆'],
96
+ ['NES 风格', '16', '经典街机与平台动作游戏', '高饱和度颜色需注意搭配'],
97
+ ['PICO-8', '16', '现代鲜艳像素风格', '注意控制高饱和色彩的对比'],
98
+ ['Commodore 64', '16', '复古电脑端艺术风格', '清晰的轮廓有助于提升识别度'],
99
+ ['DawnBringer 16', '16', '通用性极高的手绘调色板', '需要明确的光影方向'],
100
+ ],
101
+ },
102
+ {
103
+ type: 'title',
104
+ level: 2,
105
+ text: '精灵图集工作流建议',
106
+ },
107
+ {
108
+ type: 'paragraph',
109
+ html: '上传导出好的精灵图集,利用放大功能仔细检查面部细节和轮廓线是否保持清晰。',
110
+ },
111
+ {
112
+ type: 'tip',
113
+ title: '控制色彩数量',
114
+ html: '将颜色控制在 4 到 16 色之间通常能获得最干练清晰的效果。',
115
+ },
116
+ {
117
+ type: 'title',
118
+ level: 2,
119
+ text: '导出前检查清单',
120
+ },
121
+ {
122
+ type: 'diagnostic',
123
+ variant: 'info',
124
+ title: '导入游戏前的准备',
125
+ html: '在 100% 比例下检查画面,确保透明边缘干净无杂色。',
126
+ },
127
+ {
128
+ type: 'glossary',
129
+ items: [
130
+ {
131
+ term: '色彩量化(Quantization)',
132
+ definition: '将大量颜色缩减为较小固定颜色集合的过程。',
133
+ },
134
+ {
135
+ term: '色彩阶梯(Ramp)',
136
+ definition: '用于表现明暗过渡和材质体感的一组同系颜色。',
137
+ },
138
+ {
139
+ term: '索引调色板',
140
+ definition: '像素通过引用色表序号来确定颜色的存储形式。',
141
+ },
142
+ ],
143
+ },
144
+ ],
145
+ faq: [
146
+ {
147
+ question: '我的图片会被上传到服务器吗?',
148
+ answer: '不会。所有图像处理均在您的浏览器本地进行。',
149
+ },
150
+ {
151
+ question: '支持使用我自己的调色板吗?',
152
+ answer: '支持。在自定义颜色框中输入 Hex 代码并点击应用调色板即可。',
153
+ },
154
+ ],
155
+ howTo: [
156
+ {
157
+ name: '加载图片',
158
+ text: '拖放 PNG、JPEG 或 WebP 图片到工作区。',
159
+ },
160
+ {
161
+ name: '选择调色板',
162
+ text: '选择预设或输入自定义 Hex 代码。',
163
+ },
164
+ {
165
+ name: '导出结果',
166
+ text: '下载转换后的 PNG 格式图片。',
167
+ },
168
+ ],
169
+ schemas: [
170
+ {
171
+ '@context': 'https://schema.org',
172
+ '@type': 'SoftwareApplication',
173
+ name: '像素艺术调色板调换工具',
174
+ applicationCategory: 'DesignApplication',
175
+ operatingSystem: 'Any',
176
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
177
+ },
178
+ {
179
+ '@context': 'https://schema.org',
180
+ '@type': 'FAQPage',
181
+ mainEntity: [
182
+ {
183
+ '@type': 'Question',
184
+ name: '我的图片会被上传到服务器吗?',
185
+ acceptedAnswer: {
186
+ '@type': 'Answer',
187
+ text: '不会。所有图像处理均在您的浏览器本地进行。',
188
+ },
189
+ },
190
+ {
191
+ '@type': 'Question',
192
+ name: '支持使用我自己的调色板吗?',
193
+ acceptedAnswer: {
194
+ '@type': 'Answer',
195
+ text: '支持。在自定义颜色框中输入 Hex 代码并点击应用调色板即可。',
196
+ },
197
+ },
198
+ ],
199
+ },
200
+ {
201
+ '@context': 'https://schema.org',
202
+ '@type': 'HowTo',
203
+ name: '如何将像素图转换至复古调色板',
204
+ step: [
205
+ { '@type': 'HowToStep', name: '加载图片', text: '拖放图片到工作区。' },
206
+ { '@type': 'HowToStep', name: '选择调色板', text: '选择预设或输入自定义 Hex 代码。' },
207
+ { '@type': 'HowToStep', name: '导出结果', text: '下载转换后的 PNG 格式图片。' },
208
+ ],
209
+ },
210
+ ],
211
+ bibliography: [
212
+ {
213
+ name: 'MDN Canvas API',
214
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API',
215
+ },
216
+ {
217
+ name: 'Lospec Palette List',
218
+ url: 'https://lospec.com/palette-list',
219
+ },
220
+ {
221
+ name: 'W3C Portable Network Graphics Specification',
222
+ url: 'https://www.w3.org/TR/png-3/',
223
+ },
224
+ ],
225
+ };
@@ -0,0 +1,12 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { pixelArtPaletteSwapper } from './entry';
3
+
4
+ export * from './entry';
5
+ export * from './logic';
6
+
7
+ export const PIXEL_ART_PALETTE_SWAPPER_TOOL: ToolDefinition = {
8
+ entry: pixelArtPaletteSwapper,
9
+ Component: () => import('./component.astro'),
10
+ SEOComponent: () => import('./seo.astro'),
11
+ BibliographyComponent: () => import('./bibliography.astro'),
12
+ };
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ PRESET_PALETTES,
4
+ findNearestColor,
5
+ hexToRgb,
6
+ parsePaletteInput,
7
+ quantizeImageData,
8
+ rgbToHex,
9
+ } from './logic';
10
+
11
+ describe('pixel art palette swapper logic', () => {
12
+ it('parses short and full hex colors into normalized values', () => {
13
+ expect(hexToRgb('#0f8')).toEqual({ r: 0, g: 255, b: 136 });
14
+ expect(rgbToHex({ r: 18, g: 52, b: 86 })).toBe('#123456');
15
+ });
16
+
17
+ it('removes invalid and duplicate custom colors', () => {
18
+ expect(parsePaletteInput('#000, #ffffff, nope, #000000')).toEqual([
19
+ { r: 0, g: 0, b: 0, hex: '#000000' },
20
+ { r: 255, g: 255, b: 255, hex: '#ffffff' },
21
+ ]);
22
+ });
23
+
24
+ it('chooses the closest palette color in RGB space', () => {
25
+ const palette = parsePaletteInput('#000000 #ffffff');
26
+ expect(findNearestColor({ r: 220, g: 220, b: 220 }, palette)?.hex).toBe('#ffffff');
27
+ expect(findNearestColor({ r: 30, g: 30, b: 30 }, palette)?.hex).toBe('#000000');
28
+ });
29
+
30
+ it('maps opaque pixels while preserving transparent pixels and alpha', () => {
31
+ const source = new Uint8ClampedArray([
32
+ 240, 20, 20, 255,
33
+ 10, 10, 10, 128,
34
+ 255, 255, 255, 0,
35
+ ]);
36
+ const result = quantizeImageData(source, PRESET_PALETTES.pico8);
37
+ expect(Array.from(result.data.slice(0, 4))).toEqual([255, 0, 77, 255]);
38
+ expect(result.data[7]).toBe(128);
39
+ expect(Array.from(result.data.slice(8, 12))).toEqual([255, 255, 255, 0]);
40
+ expect(result.sourceColors).toBe(2);
41
+ expect(result.mappedColors).toBe(2);
42
+ });
43
+ });