@al8b/palette 0.1.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.
- package/README.md +22 -0
- package/dist/core/palette.d.mts +95 -0
- package/dist/core/palette.d.ts +95 -0
- package/dist/core/palette.js +278 -0
- package/dist/core/palette.js.map +1 -0
- package/dist/core/palette.mjs +255 -0
- package/dist/core/palette.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +280 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +255 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types/index.d.mts +17 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @al8b/palette
|
|
2
|
+
|
|
3
|
+
Palette and color conversion utilities for L8B. This package centralizes indexed palette management and the types shared by rendering layers.
|
|
4
|
+
|
|
5
|
+
## Public API
|
|
6
|
+
|
|
7
|
+
- `Palette`
|
|
8
|
+
- Type: `PaletteOptions`
|
|
9
|
+
- Types: `ColorHex`, `ColorRGB`, `PaletteData`
|
|
10
|
+
|
|
11
|
+
## Notes
|
|
12
|
+
|
|
13
|
+
- Used by runtime and script-facing rendering APIs.
|
|
14
|
+
- Keep palette-level color logic here instead of duplicating conversions in screen or image code.
|
|
15
|
+
|
|
16
|
+
## Scripts
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun run build
|
|
20
|
+
bun run test
|
|
21
|
+
bun run clean
|
|
22
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ColorHex, PaletteData, ColorRGB } from '../types/index.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Palette - Color palette management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface PaletteOptions {
|
|
8
|
+
colors?: ColorHex[];
|
|
9
|
+
name?: string;
|
|
10
|
+
}
|
|
11
|
+
declare class Palette {
|
|
12
|
+
private colors;
|
|
13
|
+
private name;
|
|
14
|
+
private rgbCache;
|
|
15
|
+
private runtime?;
|
|
16
|
+
constructor(options?: PaletteOptions | PaletteData, runtime?: any);
|
|
17
|
+
/**
|
|
18
|
+
* Validate palette format
|
|
19
|
+
*/
|
|
20
|
+
private validatePaletteFormat;
|
|
21
|
+
/**
|
|
22
|
+
* Get color by index
|
|
23
|
+
*/
|
|
24
|
+
get(index: number): ColorHex;
|
|
25
|
+
/**
|
|
26
|
+
* Get color as RGB object
|
|
27
|
+
*/
|
|
28
|
+
getRGB(index: number): ColorRGB;
|
|
29
|
+
/**
|
|
30
|
+
* Get all colors
|
|
31
|
+
*/
|
|
32
|
+
getAll(): ColorHex[];
|
|
33
|
+
/**
|
|
34
|
+
* Get palette size
|
|
35
|
+
*/
|
|
36
|
+
get size(): number;
|
|
37
|
+
/**
|
|
38
|
+
* Get palette name
|
|
39
|
+
*/
|
|
40
|
+
get paletteName(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Set color at index (expands palette if needed)
|
|
43
|
+
*/
|
|
44
|
+
set(index: number, color: ColorHex): void;
|
|
45
|
+
/**
|
|
46
|
+
* Add color to palette
|
|
47
|
+
*/
|
|
48
|
+
add(color: ColorHex): number;
|
|
49
|
+
/**
|
|
50
|
+
* Remove color at index
|
|
51
|
+
*/
|
|
52
|
+
remove(index: number): void;
|
|
53
|
+
/**
|
|
54
|
+
* Replace entire palette
|
|
55
|
+
*/
|
|
56
|
+
setPalette(colors: ColorHex[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Reset to original colors
|
|
59
|
+
*/
|
|
60
|
+
reset(paletteData?: PaletteData): void;
|
|
61
|
+
/**
|
|
62
|
+
* Convert hex to RGB
|
|
63
|
+
*/
|
|
64
|
+
private hexToRGB;
|
|
65
|
+
/**
|
|
66
|
+
* Convert RGB to hex
|
|
67
|
+
*/
|
|
68
|
+
static rgbToHex(r: number, g: number, b: number): ColorHex;
|
|
69
|
+
/**
|
|
70
|
+
* Find closest color in palette
|
|
71
|
+
*/
|
|
72
|
+
findClosest(targetHex: ColorHex): number;
|
|
73
|
+
/**
|
|
74
|
+
* Create a gradient between two palette colors
|
|
75
|
+
*/
|
|
76
|
+
gradient(startIndex: number, endIndex: number, steps: number): ColorHex[];
|
|
77
|
+
/**
|
|
78
|
+
* Lighten a color
|
|
79
|
+
*/
|
|
80
|
+
lighten(index: number, amount?: number): ColorHex;
|
|
81
|
+
/**
|
|
82
|
+
* Darken a color
|
|
83
|
+
*/
|
|
84
|
+
darken(index: number, amount?: number): ColorHex;
|
|
85
|
+
/**
|
|
86
|
+
* Mix two colors
|
|
87
|
+
*/
|
|
88
|
+
mix(index1: number, index2: number, ratio?: number): ColorHex;
|
|
89
|
+
/**
|
|
90
|
+
* Export palette data
|
|
91
|
+
*/
|
|
92
|
+
toData(): PaletteData;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { Palette, type PaletteOptions };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ColorHex, PaletteData, ColorRGB } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Palette - Color palette management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface PaletteOptions {
|
|
8
|
+
colors?: ColorHex[];
|
|
9
|
+
name?: string;
|
|
10
|
+
}
|
|
11
|
+
declare class Palette {
|
|
12
|
+
private colors;
|
|
13
|
+
private name;
|
|
14
|
+
private rgbCache;
|
|
15
|
+
private runtime?;
|
|
16
|
+
constructor(options?: PaletteOptions | PaletteData, runtime?: any);
|
|
17
|
+
/**
|
|
18
|
+
* Validate palette format
|
|
19
|
+
*/
|
|
20
|
+
private validatePaletteFormat;
|
|
21
|
+
/**
|
|
22
|
+
* Get color by index
|
|
23
|
+
*/
|
|
24
|
+
get(index: number): ColorHex;
|
|
25
|
+
/**
|
|
26
|
+
* Get color as RGB object
|
|
27
|
+
*/
|
|
28
|
+
getRGB(index: number): ColorRGB;
|
|
29
|
+
/**
|
|
30
|
+
* Get all colors
|
|
31
|
+
*/
|
|
32
|
+
getAll(): ColorHex[];
|
|
33
|
+
/**
|
|
34
|
+
* Get palette size
|
|
35
|
+
*/
|
|
36
|
+
get size(): number;
|
|
37
|
+
/**
|
|
38
|
+
* Get palette name
|
|
39
|
+
*/
|
|
40
|
+
get paletteName(): string;
|
|
41
|
+
/**
|
|
42
|
+
* Set color at index (expands palette if needed)
|
|
43
|
+
*/
|
|
44
|
+
set(index: number, color: ColorHex): void;
|
|
45
|
+
/**
|
|
46
|
+
* Add color to palette
|
|
47
|
+
*/
|
|
48
|
+
add(color: ColorHex): number;
|
|
49
|
+
/**
|
|
50
|
+
* Remove color at index
|
|
51
|
+
*/
|
|
52
|
+
remove(index: number): void;
|
|
53
|
+
/**
|
|
54
|
+
* Replace entire palette
|
|
55
|
+
*/
|
|
56
|
+
setPalette(colors: ColorHex[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Reset to original colors
|
|
59
|
+
*/
|
|
60
|
+
reset(paletteData?: PaletteData): void;
|
|
61
|
+
/**
|
|
62
|
+
* Convert hex to RGB
|
|
63
|
+
*/
|
|
64
|
+
private hexToRGB;
|
|
65
|
+
/**
|
|
66
|
+
* Convert RGB to hex
|
|
67
|
+
*/
|
|
68
|
+
static rgbToHex(r: number, g: number, b: number): ColorHex;
|
|
69
|
+
/**
|
|
70
|
+
* Find closest color in palette
|
|
71
|
+
*/
|
|
72
|
+
findClosest(targetHex: ColorHex): number;
|
|
73
|
+
/**
|
|
74
|
+
* Create a gradient between two palette colors
|
|
75
|
+
*/
|
|
76
|
+
gradient(startIndex: number, endIndex: number, steps: number): ColorHex[];
|
|
77
|
+
/**
|
|
78
|
+
* Lighten a color
|
|
79
|
+
*/
|
|
80
|
+
lighten(index: number, amount?: number): ColorHex;
|
|
81
|
+
/**
|
|
82
|
+
* Darken a color
|
|
83
|
+
*/
|
|
84
|
+
darken(index: number, amount?: number): ColorHex;
|
|
85
|
+
/**
|
|
86
|
+
* Mix two colors
|
|
87
|
+
*/
|
|
88
|
+
mix(index1: number, index2: number, ratio?: number): ColorHex;
|
|
89
|
+
/**
|
|
90
|
+
* Export palette data
|
|
91
|
+
*/
|
|
92
|
+
toData(): PaletteData;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { Palette, type PaletteOptions };
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/core/palette.ts
|
|
22
|
+
var palette_exports = {};
|
|
23
|
+
__export(palette_exports, {
|
|
24
|
+
Palette: () => Palette
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(palette_exports);
|
|
27
|
+
var import_diagnostics = require("@al8b/diagnostics");
|
|
28
|
+
var HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
|
|
29
|
+
var Palette = class _Palette {
|
|
30
|
+
static {
|
|
31
|
+
__name(this, "Palette");
|
|
32
|
+
}
|
|
33
|
+
colors;
|
|
34
|
+
name;
|
|
35
|
+
rgbCache;
|
|
36
|
+
runtime;
|
|
37
|
+
constructor(options = {}, runtime) {
|
|
38
|
+
this.runtime = runtime;
|
|
39
|
+
if ("colors" in options && Array.isArray(options.colors)) {
|
|
40
|
+
if (!this.validatePaletteFormat(options.colors)) {
|
|
41
|
+
(0, import_diagnostics.reportRuntimeError)(this.runtime?.listener, import_diagnostics.APIErrorCode.E7072, {
|
|
42
|
+
format: "invalid color array"
|
|
43
|
+
});
|
|
44
|
+
this.colors = [];
|
|
45
|
+
this.name = "Invalid";
|
|
46
|
+
} else {
|
|
47
|
+
this.colors = [
|
|
48
|
+
...options.colors
|
|
49
|
+
];
|
|
50
|
+
this.name = options.name || "Custom";
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
this.colors = [];
|
|
54
|
+
this.name = "Empty";
|
|
55
|
+
}
|
|
56
|
+
this.rgbCache = /* @__PURE__ */ new Map();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validate palette format
|
|
60
|
+
*/
|
|
61
|
+
validatePaletteFormat(colors) {
|
|
62
|
+
if (!Array.isArray(colors)) return false;
|
|
63
|
+
return colors.every((color) => typeof color === "string" && HEX_COLOR_REGEX.test(color));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get color by index
|
|
67
|
+
*/
|
|
68
|
+
get(index) {
|
|
69
|
+
if (!isFinite(index) || index < 0) {
|
|
70
|
+
(0, import_diagnostics.reportRuntimeError)(this.runtime?.listener, import_diagnostics.APIErrorCode.E7073, {
|
|
71
|
+
index,
|
|
72
|
+
maxIndex: this.colors.length - 1
|
|
73
|
+
});
|
|
74
|
+
return "#000000";
|
|
75
|
+
}
|
|
76
|
+
if (this.colors.length === 0) {
|
|
77
|
+
return "#000000";
|
|
78
|
+
}
|
|
79
|
+
return this.colors[index % this.colors.length] || "#000000";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get color as RGB object
|
|
83
|
+
*/
|
|
84
|
+
getRGB(index) {
|
|
85
|
+
if (this.rgbCache.has(index)) {
|
|
86
|
+
return this.rgbCache.get(index);
|
|
87
|
+
}
|
|
88
|
+
const hex = this.get(index);
|
|
89
|
+
const rgb = this.hexToRGB(hex);
|
|
90
|
+
this.rgbCache.set(index, rgb);
|
|
91
|
+
return rgb;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get all colors
|
|
95
|
+
*/
|
|
96
|
+
getAll() {
|
|
97
|
+
return [
|
|
98
|
+
...this.colors
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get palette size
|
|
103
|
+
*/
|
|
104
|
+
get size() {
|
|
105
|
+
return this.colors.length;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get palette name
|
|
109
|
+
*/
|
|
110
|
+
get paletteName() {
|
|
111
|
+
return this.name;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Set color at index (expands palette if needed)
|
|
115
|
+
*/
|
|
116
|
+
set(index, color) {
|
|
117
|
+
if (!isFinite(index) || index < 0) {
|
|
118
|
+
(0, import_diagnostics.reportRuntimeError)(this.runtime?.listener, import_diagnostics.APIErrorCode.E7073, {
|
|
119
|
+
index,
|
|
120
|
+
maxIndex: this.colors.length - 1
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!HEX_COLOR_REGEX.test(color)) {
|
|
125
|
+
(0, import_diagnostics.reportRuntimeError)(this.runtime?.listener, import_diagnostics.APIErrorCode.E7072, {
|
|
126
|
+
format: color
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
while (this.colors.length <= index) {
|
|
131
|
+
this.colors.push("#000000");
|
|
132
|
+
}
|
|
133
|
+
this.colors[index] = color;
|
|
134
|
+
this.rgbCache.delete(index);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Add color to palette
|
|
138
|
+
*/
|
|
139
|
+
add(color) {
|
|
140
|
+
this.colors.push(color);
|
|
141
|
+
return this.colors.length - 1;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Remove color at index
|
|
145
|
+
*/
|
|
146
|
+
remove(index) {
|
|
147
|
+
if (index >= 0 && index < this.colors.length) {
|
|
148
|
+
this.colors.splice(index, 1);
|
|
149
|
+
this.rgbCache.clear();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Replace entire palette
|
|
154
|
+
*/
|
|
155
|
+
setPalette(colors) {
|
|
156
|
+
if (!this.validatePaletteFormat(colors)) {
|
|
157
|
+
(0, import_diagnostics.reportRuntimeError)(this.runtime?.listener, import_diagnostics.APIErrorCode.E7072, {
|
|
158
|
+
format: "invalid color array"
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.colors = [
|
|
163
|
+
...colors
|
|
164
|
+
];
|
|
165
|
+
this.rgbCache.clear();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Reset to original colors
|
|
169
|
+
*/
|
|
170
|
+
reset(paletteData) {
|
|
171
|
+
if (paletteData) {
|
|
172
|
+
this.colors = [
|
|
173
|
+
...paletteData.colors
|
|
174
|
+
];
|
|
175
|
+
this.name = paletteData.name;
|
|
176
|
+
}
|
|
177
|
+
this.rgbCache.clear();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Convert hex to RGB
|
|
181
|
+
*/
|
|
182
|
+
hexToRGB(hex) {
|
|
183
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
184
|
+
return result ? {
|
|
185
|
+
r: Number.parseInt(result[1], 16),
|
|
186
|
+
g: Number.parseInt(result[2], 16),
|
|
187
|
+
b: Number.parseInt(result[3], 16)
|
|
188
|
+
} : {
|
|
189
|
+
r: 0,
|
|
190
|
+
g: 0,
|
|
191
|
+
b: 0
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Convert RGB to hex
|
|
196
|
+
*/
|
|
197
|
+
static rgbToHex(r, g, b) {
|
|
198
|
+
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Find closest color in palette
|
|
202
|
+
*/
|
|
203
|
+
findClosest(targetHex) {
|
|
204
|
+
const target = this.hexToRGB(targetHex);
|
|
205
|
+
let closestIndex = 0;
|
|
206
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
207
|
+
for (let i = 0; i < this.colors.length; i++) {
|
|
208
|
+
const color = this.getRGB(i);
|
|
209
|
+
const distance = (target.r - color.r) ** 2 + (target.g - color.g) ** 2 + (target.b - color.b) ** 2;
|
|
210
|
+
if (distance < closestDistance) {
|
|
211
|
+
closestDistance = distance;
|
|
212
|
+
closestIndex = i;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return closestIndex;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Create a gradient between two palette colors
|
|
219
|
+
*/
|
|
220
|
+
gradient(startIndex, endIndex, steps) {
|
|
221
|
+
const start = this.getRGB(startIndex);
|
|
222
|
+
const end = this.getRGB(endIndex);
|
|
223
|
+
const gradient = [];
|
|
224
|
+
for (let i = 0; i < steps; i++) {
|
|
225
|
+
const t = i / (steps - 1);
|
|
226
|
+
const r = Math.round(start.r + (end.r - start.r) * t);
|
|
227
|
+
const g = Math.round(start.g + (end.g - start.g) * t);
|
|
228
|
+
const b = Math.round(start.b + (end.b - start.b) * t);
|
|
229
|
+
gradient.push(_Palette.rgbToHex(r, g, b));
|
|
230
|
+
}
|
|
231
|
+
return gradient;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Lighten a color
|
|
235
|
+
*/
|
|
236
|
+
lighten(index, amount = 0.2) {
|
|
237
|
+
const color = this.getRGB(index);
|
|
238
|
+
const r = Math.min(255, Math.round(color.r + (255 - color.r) * amount));
|
|
239
|
+
const g = Math.min(255, Math.round(color.g + (255 - color.g) * amount));
|
|
240
|
+
const b = Math.min(255, Math.round(color.b + (255 - color.b) * amount));
|
|
241
|
+
return _Palette.rgbToHex(r, g, b);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Darken a color
|
|
245
|
+
*/
|
|
246
|
+
darken(index, amount = 0.2) {
|
|
247
|
+
const color = this.getRGB(index);
|
|
248
|
+
const r = Math.max(0, Math.round(color.r * (1 - amount)));
|
|
249
|
+
const g = Math.max(0, Math.round(color.g * (1 - amount)));
|
|
250
|
+
const b = Math.max(0, Math.round(color.b * (1 - amount)));
|
|
251
|
+
return _Palette.rgbToHex(r, g, b);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Mix two colors
|
|
255
|
+
*/
|
|
256
|
+
mix(index1, index2, ratio = 0.5) {
|
|
257
|
+
const color1 = this.getRGB(index1);
|
|
258
|
+
const color2 = this.getRGB(index2);
|
|
259
|
+
const r = Math.round(color1.r + (color2.r - color1.r) * ratio);
|
|
260
|
+
const g = Math.round(color1.g + (color2.g - color1.g) * ratio);
|
|
261
|
+
const b = Math.round(color1.b + (color2.b - color1.b) * ratio);
|
|
262
|
+
return _Palette.rgbToHex(r, g, b);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Export palette data
|
|
266
|
+
*/
|
|
267
|
+
toData() {
|
|
268
|
+
return {
|
|
269
|
+
name: this.name,
|
|
270
|
+
colors: this.getAll()
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
275
|
+
0 && (module.exports = {
|
|
276
|
+
Palette
|
|
277
|
+
});
|
|
278
|
+
//# sourceMappingURL=palette.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/palette.ts"],"sourcesContent":["/**\n * Palette - Color palette management\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\nimport type { ColorHex, ColorRGB, PaletteData } from \"../types\";\n\n/** Matches a valid 6-digit hex color string, e.g. \"#1A2B3C\" */\nconst HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;\n\nexport interface PaletteOptions {\n\tcolors?: ColorHex[];\n\tname?: string;\n}\n\nexport class Palette {\n\tprivate colors: ColorHex[];\n\tprivate name: string;\n\tprivate rgbCache: Map<number, ColorRGB>;\n\tprivate runtime?: any;\n\n\tconstructor(options: PaletteOptions | PaletteData = {}, runtime?: any) {\n\t\tthis.runtime = runtime;\n\n\t\tif (\"colors\" in options && Array.isArray(options.colors)) {\n\t\t\t// Validate palette format to ensure all colors are valid hex strings\n\t\t\tif (!this.validatePaletteFormat(options.colors)) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7072, { format: \"invalid color array\" });\n\t\t\t\tthis.colors = [];\n\t\t\t\tthis.name = \"Invalid\";\n\t\t\t} else {\n\t\t\t\tthis.colors = [...options.colors];\n\t\t\t\tthis.name = options.name || \"Custom\";\n\t\t\t}\n\t\t} else {\n\t\t\t// Initialize with empty palette when no valid colors provided\n\t\t\tthis.colors = [];\n\t\t\tthis.name = \"Empty\";\n\t\t}\n\n\t\tthis.rgbCache = new Map();\n\t}\n\n\t/**\n\t * Validate palette format\n\t */\n\tprivate validatePaletteFormat(colors: any[]): boolean {\n\t\tif (!Array.isArray(colors)) return false;\n\t\treturn colors.every((color) => typeof color === \"string\" && HEX_COLOR_REGEX.test(color));\n\t}\n\n\t/**\n\t * Get color by index\n\t */\n\tget(index: number): ColorHex {\n\t\t// Validate color index is a finite, non-negative number\n\t\tif (!isFinite(index) || index < 0) {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7073, { index, maxIndex: this.colors.length - 1 });\n\t\t\treturn \"#000000\";\n\t\t}\n\n\t\tif (this.colors.length === 0) {\n\t\t\treturn \"#000000\";\n\t\t}\n\n\t\treturn this.colors[index % this.colors.length] || \"#000000\";\n\t}\n\n\t/**\n\t * Get color as RGB object\n\t */\n\tgetRGB(index: number): ColorRGB {\n\t\tif (this.rgbCache.has(index)) {\n\t\t\treturn this.rgbCache.get(index)!;\n\t\t}\n\n\t\tconst hex = this.get(index);\n\t\tconst rgb = this.hexToRGB(hex);\n\t\tthis.rgbCache.set(index, rgb);\n\t\treturn rgb;\n\t}\n\n\t/**\n\t * Get all colors\n\t */\n\tgetAll(): ColorHex[] {\n\t\treturn [...this.colors];\n\t}\n\n\t/**\n\t * Get palette size\n\t */\n\tget size(): number {\n\t\treturn this.colors.length;\n\t}\n\n\t/**\n\t * Get palette name\n\t */\n\tget paletteName(): string {\n\t\treturn this.name;\n\t}\n\n\t/**\n\t * Set color at index (expands palette if needed)\n\t */\n\tset(index: number, color: ColorHex): void {\n\t\t// Validate color index is a finite, non-negative number\n\t\tif (!isFinite(index) || index < 0) {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7073, { index, maxIndex: this.colors.length - 1 });\n\t\t\treturn;\n\t\t}\n\n\t\t// Validate color format matches hex pattern (#RRGGBB)\n\t\tif (!HEX_COLOR_REGEX.test(color)) {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7072, { format: color });\n\t\t\treturn;\n\t\t}\n\n\t\t// Expand palette with black (#000000) if index exceeds current size\n\t\twhile (this.colors.length <= index) {\n\t\t\tthis.colors.push(\"#000000\");\n\t\t}\n\t\tthis.colors[index] = color;\n\t\tthis.rgbCache.delete(index);\n\t}\n\n\t/**\n\t * Add color to palette\n\t */\n\tadd(color: ColorHex): number {\n\t\tthis.colors.push(color);\n\t\treturn this.colors.length - 1;\n\t}\n\n\t/**\n\t * Remove color at index\n\t */\n\tremove(index: number): void {\n\t\tif (index >= 0 && index < this.colors.length) {\n\t\t\tthis.colors.splice(index, 1);\n\t\t\tthis.rgbCache.clear();\n\t\t}\n\t}\n\n\t/**\n\t * Replace entire palette\n\t */\n\tsetPalette(colors: ColorHex[]): void {\n\t\t// Validate palette format to ensure all colors are valid hex strings\n\t\tif (!this.validatePaletteFormat(colors)) {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7072, { format: \"invalid color array\" });\n\t\t\treturn;\n\t\t}\n\n\t\tthis.colors = [...colors];\n\t\tthis.rgbCache.clear();\n\t}\n\n\t/**\n\t * Reset to original colors\n\t */\n\treset(paletteData?: PaletteData): void {\n\t\tif (paletteData) {\n\t\t\tthis.colors = [...paletteData.colors];\n\t\t\tthis.name = paletteData.name;\n\t\t}\n\t\tthis.rgbCache.clear();\n\t}\n\n\t/**\n\t * Convert hex to RGB\n\t */\n\tprivate hexToRGB(hex: ColorHex): ColorRGB {\n\t\tconst result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n\t\treturn result\n\t\t\t? {\n\t\t\t\t\tr: Number.parseInt(result[1], 16),\n\t\t\t\t\tg: Number.parseInt(result[2], 16),\n\t\t\t\t\tb: Number.parseInt(result[3], 16),\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\tr: 0,\n\t\t\t\t\tg: 0,\n\t\t\t\t\tb: 0,\n\t\t\t\t};\n\t}\n\n\t/**\n\t * Convert RGB to hex\n\t */\n\tstatic rgbToHex(r: number, g: number, b: number): ColorHex {\n\t\treturn \"#\" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();\n\t}\n\n\t/**\n\t * Find closest color in palette\n\t */\n\tfindClosest(targetHex: ColorHex): number {\n\t\tconst target = this.hexToRGB(targetHex);\n\t\tlet closestIndex = 0;\n\t\tlet closestDistance = Number.POSITIVE_INFINITY;\n\n\t\tfor (let i = 0; i < this.colors.length; i++) {\n\t\t\tconst color = this.getRGB(i);\n\t\t\tconst distance = (target.r - color.r) ** 2 + (target.g - color.g) ** 2 + (target.b - color.b) ** 2;\n\n\t\t\tif (distance < closestDistance) {\n\t\t\t\tclosestDistance = distance;\n\t\t\t\tclosestIndex = i;\n\t\t\t}\n\t\t}\n\n\t\treturn closestIndex;\n\t}\n\n\t/**\n\t * Create a gradient between two palette colors\n\t */\n\tgradient(startIndex: number, endIndex: number, steps: number): ColorHex[] {\n\t\tconst start = this.getRGB(startIndex);\n\t\tconst end = this.getRGB(endIndex);\n\t\tconst gradient: ColorHex[] = [];\n\n\t\tfor (let i = 0; i < steps; i++) {\n\t\t\tconst t = i / (steps - 1);\n\t\t\tconst r = Math.round(start.r + (end.r - start.r) * t);\n\t\t\tconst g = Math.round(start.g + (end.g - start.g) * t);\n\t\t\tconst b = Math.round(start.b + (end.b - start.b) * t);\n\t\t\tgradient.push(Palette.rgbToHex(r, g, b));\n\t\t}\n\n\t\treturn gradient;\n\t}\n\n\t/**\n\t * Lighten a color\n\t */\n\tlighten(index: number, amount: number = 0.2): ColorHex {\n\t\tconst color = this.getRGB(index);\n\t\tconst r = Math.min(255, Math.round(color.r + (255 - color.r) * amount));\n\t\tconst g = Math.min(255, Math.round(color.g + (255 - color.g) * amount));\n\t\tconst b = Math.min(255, Math.round(color.b + (255 - color.b) * amount));\n\t\treturn Palette.rgbToHex(r, g, b);\n\t}\n\n\t/**\n\t * Darken a color\n\t */\n\tdarken(index: number, amount: number = 0.2): ColorHex {\n\t\tconst color = this.getRGB(index);\n\t\tconst r = Math.max(0, Math.round(color.r * (1 - amount)));\n\t\tconst g = Math.max(0, Math.round(color.g * (1 - amount)));\n\t\tconst b = Math.max(0, Math.round(color.b * (1 - amount)));\n\t\treturn Palette.rgbToHex(r, g, b);\n\t}\n\n\t/**\n\t * Mix two colors\n\t */\n\tmix(index1: number, index2: number, ratio: number = 0.5): ColorHex {\n\t\tconst color1 = this.getRGB(index1);\n\t\tconst color2 = this.getRGB(index2);\n\t\tconst r = Math.round(color1.r + (color2.r - color1.r) * ratio);\n\t\tconst g = Math.round(color1.g + (color2.g - color1.g) * ratio);\n\t\tconst b = Math.round(color1.b + (color2.b - color1.b) * ratio);\n\t\treturn Palette.rgbToHex(r, g, b);\n\t}\n\n\t/**\n\t * Export palette data\n\t */\n\ttoData(): PaletteData {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcolors: this.getAll(),\n\t\t};\n\t}\n}\n"],"mappings":"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","names":["HEX_COLOR_REGEX","Palette","colors","name","rgbCache","runtime","options","Array","isArray","validatePaletteFormat","reportRuntimeError","listener","APIErrorCode","E7072","format","Map","every","color","test","get","index","isFinite","E7073","maxIndex","length","getRGB","has","hex","rgb","hexToRGB","set","getAll","size","paletteName","push","delete","add","remove","splice","clear","setPalette","reset","paletteData","result","exec","r","Number","parseInt","g","b","rgbToHex","toString","slice","toUpperCase","findClosest","targetHex","target","closestIndex","closestDistance","POSITIVE_INFINITY","i","distance","gradient","startIndex","endIndex","steps","start","end","t","Math","round","lighten","amount","min","darken","max","mix","index1","index2","ratio","color1","color2","toData"]}
|