@versatiles/style 5.5.1 → 5.6.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 +6 -8
- package/dist/index.d.ts +97 -0
- package/dist/index.js +137 -46
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
- package/src/color/abstract.ts +4 -0
- package/src/color/rgb.test.ts +40 -72
- package/src/color/rgb.ts +11 -0
- package/src/color/utils.ts +10 -4
- package/src/guess_style/guess_style.test.ts +1 -1
- package/src/shortbread/layers.ts +30 -5
- package/src/style_builder/recolor.test.ts +46 -0
- package/src/style_builder/recolor.ts +161 -34
- package/src/style_builder/style_builder.test.ts +2 -0
- package/src/styles/colorful.ts +36 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versatiles/style",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"description": "Generate StyleJSON for MapLibre",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -41,27 +41,27 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@maplibre/maplibre-gl-native": "^6.0.0",
|
|
43
43
|
"@maplibre/maplibre-gl-style-spec": "^23.1.0",
|
|
44
|
-
"@rollup/plugin-commonjs": "^28.0.
|
|
45
|
-
"@rollup/plugin-node-resolve": "^16.0.
|
|
44
|
+
"@rollup/plugin-commonjs": "^28.0.3",
|
|
45
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
46
46
|
"@rollup/plugin-terser": "^0.4.4",
|
|
47
47
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
48
48
|
"@types/bin-pack": "^1.0.3",
|
|
49
49
|
"@types/brace-expansion": "^1.1.2",
|
|
50
50
|
"@types/inquirer": "^9.0.7",
|
|
51
51
|
"@types/jest": "^29.5.14",
|
|
52
|
-
"@types/node": "^22.13.
|
|
52
|
+
"@types/node": "^22.13.10",
|
|
53
53
|
"@types/tar-stream": "^3.1.3",
|
|
54
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
55
|
-
"@typescript-eslint/parser": "^8.
|
|
56
|
-
"@versatiles/release-tool": "^2.
|
|
54
|
+
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
|
55
|
+
"@typescript-eslint/parser": "^8.26.1",
|
|
56
|
+
"@versatiles/release-tool": "^2.4.1",
|
|
57
57
|
"bin-pack": "^1.0.2",
|
|
58
|
-
"esbuild": "^0.25.
|
|
59
|
-
"eslint": "^9.
|
|
60
|
-
"inquirer": "^12.4.
|
|
58
|
+
"esbuild": "^0.25.1",
|
|
59
|
+
"eslint": "^9.22.0",
|
|
60
|
+
"inquirer": "^12.4.3",
|
|
61
61
|
"jest": "^29.7.0",
|
|
62
62
|
"jest-environment-jsdom": "^29.7.0",
|
|
63
63
|
"jest-ts-webcompat-resolver": "^1.0.0",
|
|
64
|
-
"rollup": "^4.
|
|
64
|
+
"rollup": "^4.35.0",
|
|
65
65
|
"rollup-plugin-dts": "^6.1.1",
|
|
66
66
|
"rollup-plugin-sourcemaps2": "^0.5.0",
|
|
67
67
|
"sharp": "^0.33.5",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"ts-jest": "^29.2.6",
|
|
70
70
|
"ts-node": "^10.9.2",
|
|
71
71
|
"tsx": "^4.19.3",
|
|
72
|
-
"typescript": "^5.
|
|
73
|
-
"typescript-eslint": "^8.
|
|
72
|
+
"typescript": "^5.8.2",
|
|
73
|
+
"typescript-eslint": "^8.26.1"
|
|
74
74
|
}
|
|
75
75
|
}
|
package/src/color/abstract.ts
CHANGED
|
@@ -75,6 +75,10 @@ export abstract class Color {
|
|
|
75
75
|
return this.toRGB().tint(value, tintColor);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
blend(value: number, blendColor: Color): RGB {
|
|
79
|
+
return this.toRGB().blend(value, blendColor);
|
|
80
|
+
}
|
|
81
|
+
|
|
78
82
|
setHue(value: number): HSV {
|
|
79
83
|
return this.toHSV().setHue(value);
|
|
80
84
|
}
|
package/src/color/rgb.test.ts
CHANGED
|
@@ -134,94 +134,62 @@ describe('RGB Class', () => {
|
|
|
134
134
|
return cb(new RGB(50, 150, 200, 0.8)).round().asArray();
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
test('clamps extreme gamma values', () => {
|
|
143
|
-
expect(pc(c => c.gamma(0.001))).toStrictEqual([255, 255, 255, 0.8]);
|
|
144
|
-
expect(pc(c => c.gamma(1000))).toStrictEqual([0, 0, 0, 0.8]);
|
|
145
|
-
});
|
|
137
|
+
test('adjusts gamma correctly', () => {
|
|
138
|
+
expect(pc(c => c.gamma(2.2))).toStrictEqual([7, 79, 149, 0.8]);
|
|
139
|
+
expect(pc(c => c.gamma(0.001))).toStrictEqual([255, 255, 255, 0.8]);
|
|
140
|
+
expect(pc(c => c.gamma(1000))).toStrictEqual([0, 0, 0, 0.8]);
|
|
146
141
|
});
|
|
147
142
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
expect(pc(c => c.invert())).toStrictEqual([205, 105, 55, 0.8]);
|
|
151
|
-
});
|
|
143
|
+
test('inverts RGB values correctly', () => {
|
|
144
|
+
expect(pc(c => c.invert())).toStrictEqual([205, 105, 55, 0.8]);
|
|
152
145
|
});
|
|
153
146
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
test('clamps extreme contrast values', () => {
|
|
160
|
-
expect(pc(c => c.contrast(1e6))).toStrictEqual([0, 255, 255, 0.8]);
|
|
161
|
-
expect(pc(c => c.contrast(0))).toStrictEqual([128, 128, 128, 0.8]);
|
|
162
|
-
});
|
|
147
|
+
test('adjusts contrast correctly', () => {
|
|
148
|
+
expect(pc(c => c.contrast(1.5))).toStrictEqual([11, 161, 236, 0.8]);
|
|
149
|
+
expect(pc(c => c.contrast(1e6))).toStrictEqual([0, 255, 255, 0.8]);
|
|
150
|
+
expect(pc(c => c.contrast(0))).toStrictEqual([128, 128, 128, 0.8]);
|
|
163
151
|
});
|
|
164
152
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
test('decreases brightness correctly', () => {
|
|
171
|
-
expect(pc(c => c.brightness(-0.5))).toStrictEqual([25, 75, 100, 0.8]);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('clamps brightness values', () => {
|
|
175
|
-
expect(pc(c => c.brightness(2))).toStrictEqual([255, 255, 255, 0.8]);
|
|
176
|
-
expect(pc(c => c.brightness(-2))).toStrictEqual([0, 0, 0, 0.8]);
|
|
177
|
-
});
|
|
153
|
+
test('increases brightness correctly', () => {
|
|
154
|
+
expect(pc(c => c.brightness(0.5))).toStrictEqual([153, 203, 228, 0.8]);
|
|
155
|
+
expect(pc(c => c.brightness(-0.5))).toStrictEqual([25, 75, 100, 0.8]);
|
|
156
|
+
expect(pc(c => c.brightness(2))).toStrictEqual([255, 255, 255, 0.8]);
|
|
157
|
+
expect(pc(c => c.brightness(-2))).toStrictEqual([0, 0, 0, 0.8]);
|
|
178
158
|
});
|
|
179
159
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
test('handles extreme tint values', () => {
|
|
187
|
-
const tintColor = new RGB(255, 0, 0);
|
|
188
|
-
expect(pc(c => c.tint(1, tintColor))).toStrictEqual([200, 50, 50, 0.8]);
|
|
189
|
-
expect(pc(c => c.tint(0, tintColor))).toStrictEqual([50, 150, 200, 0.8]);
|
|
190
|
-
});
|
|
160
|
+
test('tints color correctly', () => {
|
|
161
|
+
const tintColor = new RGB(255, 0, 0);
|
|
162
|
+
expect(pc(c => c.tint(0.5, tintColor))).toStrictEqual([125, 100, 125, 0.8]);
|
|
163
|
+
expect(pc(c => c.tint(1, tintColor))).toStrictEqual([200, 50, 50, 0.8]);
|
|
164
|
+
expect(pc(c => c.tint(0, tintColor))).toStrictEqual([50, 150, 200, 0.8]);
|
|
191
165
|
});
|
|
192
166
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
});
|
|
167
|
+
test('blends color correctly', () => {
|
|
168
|
+
const blendColor = new RGB(255, 0, 0);
|
|
169
|
+
expect(pc(c => c.blend(0.2, blendColor))).toStrictEqual([91, 120, 160, 0.8]);
|
|
170
|
+
expect(pc(c => c.blend(0.5, blendColor))).toStrictEqual([153, 75, 100, 0.8]);
|
|
171
|
+
expect(pc(c => c.blend(0.8, blendColor))).toStrictEqual([214, 30, 40, 0.8]);
|
|
172
|
+
expect(pc(c => c.blend(1, blendColor))).toStrictEqual([255, 0, 0, 0.8]);
|
|
173
|
+
expect(pc(c => c.blend(0, blendColor))).toStrictEqual([50, 150, 200, 0.8]);
|
|
201
174
|
});
|
|
202
175
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('clamps darken ratio', () => {
|
|
209
|
-
expect(pc(c => c.darken(1))).toStrictEqual([0, 0, 0, 0.8]);
|
|
210
|
-
expect(pc(c => c.darken(2))).toStrictEqual([0, 0, 0, 0.8]);
|
|
211
|
-
});
|
|
176
|
+
test('lightens the color correctly', () => {
|
|
177
|
+
expect(pc(c => c.lighten(0.5))).toStrictEqual([153, 203, 228, 0.8]);
|
|
178
|
+
expect(pc(c => c.lighten(2))).toStrictEqual([255, 255, 255, 0.8]);
|
|
212
179
|
});
|
|
213
180
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
181
|
+
test('darkens the color correctly', () => {
|
|
182
|
+
expect(pc(c => c.darken(0.5))).toStrictEqual([25, 75, 100, 0.8]);
|
|
183
|
+
expect(pc(c => c.darken(1))).toStrictEqual([0, 0, 0, 0.8]);
|
|
184
|
+
expect(pc(c => c.darken(2))).toStrictEqual([0, 0, 0, 0.8]);
|
|
185
|
+
});
|
|
218
186
|
|
|
219
|
-
|
|
220
|
-
|
|
187
|
+
test('fades color correctly', () => {
|
|
188
|
+
expect(pc(c => c.fade(0.5))).toStrictEqual([50, 150, 200, 0.4]);
|
|
189
|
+
expect(pc(c => c.fade(1))).toStrictEqual([50, 150, 200, 0]);
|
|
221
190
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
});
|
|
191
|
+
const fullyOpaque = new RGB(50, 150, 200, 1);
|
|
192
|
+
expect(fullyOpaque.fade(0).asArray()).toStrictEqual([50, 150, 200, 1]);
|
|
225
193
|
});
|
|
226
194
|
});
|
|
227
195
|
});
|
package/src/color/rgb.ts
CHANGED
|
@@ -224,6 +224,17 @@ export class RGB extends Color {
|
|
|
224
224
|
)
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
blend(value: number, blendColor: Color): RGB {
|
|
228
|
+
value = clamp(value ?? 0, 0, 1);
|
|
229
|
+
const rgbNew = blendColor.toRGB();
|
|
230
|
+
return new RGB(
|
|
231
|
+
this.r * (1 - value) + value * rgbNew.r,
|
|
232
|
+
this.g * (1 - value) + value * rgbNew.g,
|
|
233
|
+
this.b * (1 - value) + value * rgbNew.b,
|
|
234
|
+
this.a
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
227
238
|
lighten(ratio: number): RGB {
|
|
228
239
|
return new RGB(
|
|
229
240
|
clamp(255 - (255 - this.r) * (1 - ratio), 0, 255),
|
package/src/color/utils.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
export function clamp(
|
|
4
|
-
|
|
3
|
+
export function clamp(value: number, min: number, max: number): number {
|
|
4
|
+
if (value == null || isNaN(value)) return min;
|
|
5
|
+
if (value < min) return min;
|
|
6
|
+
if (value > max) return max;
|
|
7
|
+
return value;
|
|
5
8
|
}
|
|
6
9
|
|
|
7
|
-
export function mod(
|
|
8
|
-
|
|
10
|
+
export function mod(value: number, max: number): number {
|
|
11
|
+
value = value % max;
|
|
12
|
+
if (value < 0) value += max;
|
|
13
|
+
if (value == 0) return 0;
|
|
14
|
+
return value;
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
export function formatFloat(num: number, precision: number): string {
|
|
@@ -61,7 +61,7 @@ describe('guessStyle', () => {
|
|
|
61
61
|
it('should build shortbread vector styles', () => {
|
|
62
62
|
const style = guessStyle({ tiles, vector_layers: vectorLayersShortbread }, { baseUrl: 'http://example.com' });
|
|
63
63
|
|
|
64
|
-
expect(style.layers.length).toBe(
|
|
64
|
+
expect(style.layers.length).toBe(309);
|
|
65
65
|
style.layers = [];
|
|
66
66
|
|
|
67
67
|
expect(style).toStrictEqual({
|
package/src/shortbread/layers.ts
CHANGED
|
@@ -193,7 +193,6 @@ export function getShortbreadLayers(option: { readonly language: Language }): Ma
|
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
// no links
|
|
196
|
-
const noDrivewayExpression: LegacyFilterSpecification = ['!=', 'service', 'driveway'];
|
|
197
196
|
['track', 'pedestrian', 'service', 'living_street', 'residential', 'unclassified'].forEach(t => {
|
|
198
197
|
results.push({
|
|
199
198
|
id: prefix + 'street-' + t.replace(/_/g, '') + suffix,
|
|
@@ -202,7 +201,6 @@ export function getShortbreadLayers(option: { readonly language: Language }): Ma
|
|
|
202
201
|
filter: ['all',
|
|
203
202
|
['==', 'kind', t],
|
|
204
203
|
...filter,
|
|
205
|
-
...(t === 'service') ? [noDrivewayExpression] : [], // ignore driveways
|
|
206
204
|
],
|
|
207
205
|
});
|
|
208
206
|
});
|
|
@@ -217,7 +215,6 @@ export function getShortbreadLayers(option: { readonly language: Language }): Ma
|
|
|
217
215
|
['==', 'kind', t],
|
|
218
216
|
['==', 'bicycle', 'designated'],
|
|
219
217
|
...filter,
|
|
220
|
-
...(t === 'service') ? [noDrivewayExpression] : [], // ignore driveways
|
|
221
218
|
],
|
|
222
219
|
});
|
|
223
220
|
});
|
|
@@ -254,8 +251,11 @@ export function getShortbreadLayers(option: { readonly language: Language }): Ma
|
|
|
254
251
|
|
|
255
252
|
// separate outline for trains
|
|
256
253
|
[':outline', ''].forEach(suffix => {
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
|
|
255
|
+
// with service distinction
|
|
256
|
+
['rail', 'light_rail', 'subway', 'narrow_gauge', 'tram'].reverse().forEach((t) => {
|
|
257
|
+
|
|
258
|
+
// main rail
|
|
259
259
|
results.push({
|
|
260
260
|
id: prefix + 'transport-' + t.replace(/_/g, '') + suffix,
|
|
261
261
|
type: 'line',
|
|
@@ -266,6 +266,31 @@ export function getShortbreadLayers(option: { readonly language: Language }): Ma
|
|
|
266
266
|
...filter,
|
|
267
267
|
],
|
|
268
268
|
});
|
|
269
|
+
|
|
270
|
+
// service rail (crossover, siding, spur, yard)
|
|
271
|
+
results.push({
|
|
272
|
+
id: prefix + 'transport-' + t.replace(/_/g, '') + '-service' + suffix,
|
|
273
|
+
type: 'line',
|
|
274
|
+
'source-layer': 'streets',
|
|
275
|
+
filter: ['all',
|
|
276
|
+
['in', 'kind', t],
|
|
277
|
+
['has', 'service'],
|
|
278
|
+
...filter,
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// other transport
|
|
284
|
+
['funicular', 'monorail', 'bus_guideway', 'busway'].reverse().forEach((t) => {
|
|
285
|
+
results.push({
|
|
286
|
+
id: prefix + 'transport-' + t.replace(/_/g, '') + suffix,
|
|
287
|
+
type: 'line',
|
|
288
|
+
'source-layer': 'streets',
|
|
289
|
+
filter: ['all',
|
|
290
|
+
['in', 'kind', t],
|
|
291
|
+
...filter,
|
|
292
|
+
],
|
|
293
|
+
});
|
|
269
294
|
});
|
|
270
295
|
|
|
271
296
|
if (c === 'street') {
|
|
@@ -15,6 +15,8 @@ describe('recolor', () => {
|
|
|
15
15
|
brightness: 0,
|
|
16
16
|
tint: 0,
|
|
17
17
|
tintColor: '#FF0000',
|
|
18
|
+
blend: 0,
|
|
19
|
+
blendColor: '#000000',
|
|
18
20
|
});
|
|
19
21
|
});
|
|
20
22
|
});
|
|
@@ -188,6 +190,50 @@ describe('recolor', () => {
|
|
|
188
190
|
expect(colors2string(colors)).toBe('7766DD00,0033EE55,1100FFAA,221188,665C99');
|
|
189
191
|
});
|
|
190
192
|
});
|
|
193
|
+
|
|
194
|
+
describe('blend', () => {
|
|
195
|
+
it('should not blend at all', () => {
|
|
196
|
+
const colors = getTestColors();
|
|
197
|
+
recolorArray(colors, { blend: 0, blendColor: '#F00' });
|
|
198
|
+
expect(colors2string(colors)).toBe('FFAA5500,00FFAA55,5500FFAA,AA5500,AA7755');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should blend a little bit red', () => {
|
|
202
|
+
const colors = getTestColors();
|
|
203
|
+
recolorArray(colors, { blend: 0.5, blendColor: '#F00' });
|
|
204
|
+
expect(colors2string(colors)).toBe('FF552B00,80805555,AA0080AA,D52B00,D53C2B');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should blend a little bit yellow', () => {
|
|
208
|
+
const colors = getTestColors();
|
|
209
|
+
recolorArray(colors, { blend: 0.2, blendColor: '#FF0' });
|
|
210
|
+
expect(colors2string(colors)).toBe('FFBB4400,33FF8855,7733CCAA,BB7700,BB9244');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should blend a little bit green', () => {
|
|
214
|
+
const colors = getTestColors();
|
|
215
|
+
recolorArray(colors, { blend: 0.2, blendColor: '#0F0' });
|
|
216
|
+
expect(colors2string(colors)).toBe('CCBB4400,00FF8855,4433CCAA,887700,889244');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should blend a little bit blue', () => {
|
|
220
|
+
const colors = getTestColors();
|
|
221
|
+
recolorArray(colors, { blend: 0.2, blendColor: '#00F' });
|
|
222
|
+
expect(colors2string(colors)).toBe('CC887700,00CCBB55,4400FFAA,884433,885F77');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should blend strongly orange', () => {
|
|
226
|
+
const colors = getTestColors();
|
|
227
|
+
recolorArray(colors, { blend: 0.8, blendColor: '#F80' });
|
|
228
|
+
expect(colors2string(colors)).toBe('FF8F1100,CCA02255,DD6D33AA,EE7E00,EE8511');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should blend a strongly blue', () => {
|
|
232
|
+
const colors = getTestColors();
|
|
233
|
+
recolorArray(colors, { blend: 0.8, blendColor: '#00F' });
|
|
234
|
+
expect(colors2string(colors)).toBe('3322DD00,0033EE55,1100FFAA,2211CC,2218DD');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
191
237
|
});
|
|
192
238
|
|
|
193
239
|
describe('recolorObject', () => {
|
|
@@ -1,31 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module for applying various color transformations such as hue rotation, saturation, contrast, brightness,
|
|
3
|
+
* tinting, and blending. These transformations are defined through the `RecolorOptions` interface.
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { Color } from '../color/index.js';
|
|
2
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for recoloring all map colors.
|
|
10
|
+
*
|
|
11
|
+
* The transformations (if specified) are done in the following order:
|
|
12
|
+
* 1. [Invert brightness](#invertbrightness)
|
|
13
|
+
* 2. [Rotate hue](#rotate)
|
|
14
|
+
* 3. [Saturate](#saturate)
|
|
15
|
+
* 4. [Gamma correction](#gamma)
|
|
16
|
+
* 5. [Contrast adjustment](#contrast)
|
|
17
|
+
* 6. [Brightness adjustment](#brightness)
|
|
18
|
+
* 7. [Tinting](#tint)
|
|
19
|
+
* 8. [Blending](#blend)
|
|
20
|
+
*
|
|
21
|
+
* Usage Examples
|
|
22
|
+
*
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const style = VersaTilesStyle.colorful({
|
|
25
|
+
* recolor: {
|
|
26
|
+
* rotate: 180,
|
|
27
|
+
* saturate: 0.5,
|
|
28
|
+
* brightness: 0.2,
|
|
29
|
+
* }
|
|
30
|
+
* };
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* If you want do make you map simply brighter or darker, you can use the `blend` option:
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const style = VersaTilesStyle.colorful({
|
|
36
|
+
* recolor: {
|
|
37
|
+
* blend: 0.5,
|
|
38
|
+
* blendColor: '#000000', // to blend all colors with black
|
|
39
|
+
* // or blendColor: '#FFFFFF', // to blend all colors with white
|
|
40
|
+
* }
|
|
41
|
+
* };
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
*/
|
|
45
|
+
|
|
3
46
|
export interface RecolorOptions {
|
|
4
|
-
|
|
47
|
+
/**
|
|
48
|
+
* If true, inverts all colors' brightness.
|
|
49
|
+
* See also {@link HSL.invertLuminosity}
|
|
50
|
+
*/
|
|
5
51
|
invertBrightness?: boolean;
|
|
6
52
|
|
|
7
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Rotate the hue of all colors in degrees (0-360).
|
|
55
|
+
* See also {@link HSL.rotateHue}
|
|
56
|
+
*/
|
|
8
57
|
rotate?: number;
|
|
9
58
|
|
|
10
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Adjust the saturation level. Positive values increase, negative values decrease saturation.
|
|
61
|
+
* |value|effect |
|
|
62
|
+
* |----:|-----------------|
|
|
63
|
+
* | -1|grayscale |
|
|
64
|
+
* | 0|no effect |
|
|
65
|
+
* | 1|double saturation|
|
|
66
|
+
*
|
|
67
|
+
* See also {@link HSL.saturate}
|
|
68
|
+
*/
|
|
11
69
|
saturate?: number;
|
|
12
70
|
|
|
13
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Adjust the gamma (non-linear brightness adjustment).
|
|
73
|
+
* Defaults to 1.
|
|
74
|
+
* See also {@link RGB.gamma}
|
|
75
|
+
*/
|
|
14
76
|
gamma?: number;
|
|
15
77
|
|
|
16
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Adjust the contrast level.
|
|
80
|
+
* Values > 1 increase contrast, values < 1 decrease it.
|
|
81
|
+
* Defaults to 1.
|
|
82
|
+
* See also {@link RGB.contrast}
|
|
83
|
+
*/
|
|
17
84
|
contrast?: number;
|
|
18
85
|
|
|
19
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Adjust the brightness level.
|
|
88
|
+
* Positive values make it brighter, negative values make it darker.
|
|
89
|
+
* Defaults to 0.
|
|
90
|
+
* See also {@link RGB.brightness}
|
|
91
|
+
*/
|
|
20
92
|
brightness?: number;
|
|
21
93
|
|
|
22
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Intensity of the tinting effect (0 = none, 1 = full effect).
|
|
96
|
+
* See also {@link RGB.tint}
|
|
97
|
+
*/
|
|
23
98
|
tint?: number;
|
|
24
99
|
|
|
25
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The tinting color in hex format (default: '#FF0000').
|
|
102
|
+
* See also {@link RGB.tint}
|
|
103
|
+
*/
|
|
26
104
|
tintColor?: string;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Intensity of the blending effect (0 = none, 1 = full effect).
|
|
108
|
+
* See also {@link RGB.blend}
|
|
109
|
+
*/
|
|
110
|
+
blend?: number;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The blending color in hex format (default: '#000000').
|
|
114
|
+
* See also {@link RGB.blend}
|
|
115
|
+
*/
|
|
116
|
+
blendColor?: string;
|
|
27
117
|
}
|
|
28
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Returns the default recolor settings.
|
|
121
|
+
*/
|
|
29
122
|
export function getDefaultRecolorFlags(): RecolorOptions {
|
|
30
123
|
return {
|
|
31
124
|
invertBrightness: false,
|
|
@@ -36,30 +129,49 @@ export function getDefaultRecolorFlags(): RecolorOptions {
|
|
|
36
129
|
brightness: 0,
|
|
37
130
|
tint: 0,
|
|
38
131
|
tintColor: '#FF0000',
|
|
132
|
+
blend: 0,
|
|
133
|
+
blendColor: '#000000',
|
|
39
134
|
};
|
|
40
135
|
}
|
|
41
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Checks if the given options object contains any active recolor transformations.
|
|
139
|
+
* @param opt The recolor options to validate.
|
|
140
|
+
*/
|
|
42
141
|
function isValidRecolorOptions(opt?: RecolorOptions): opt is RecolorOptions {
|
|
43
142
|
if (!opt) return false;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
143
|
+
return (
|
|
144
|
+
opt.invertBrightness ||
|
|
145
|
+
(opt.rotate != null && opt.rotate !== 0) ||
|
|
146
|
+
(opt.saturate != null && opt.saturate !== 0) ||
|
|
147
|
+
(opt.gamma != null && opt.gamma !== 1) ||
|
|
148
|
+
(opt.contrast != null && opt.contrast !== 1) ||
|
|
149
|
+
(opt.brightness != null && opt.brightness !== 0) ||
|
|
150
|
+
(opt.tint != null && opt.tint !== 0) ||
|
|
151
|
+
(opt.tintColor != null && opt.tintColor !== '#FF0000') ||
|
|
152
|
+
(opt.blend != null && opt.blend !== 0) ||
|
|
153
|
+
(opt.blendColor != null && opt.blendColor !== '#000000')
|
|
154
|
+
);
|
|
53
155
|
}
|
|
54
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Applies recoloring transformations to a record of colors.
|
|
159
|
+
* @param colors A record of color names to `Color` instances.
|
|
160
|
+
* @param opt Optional recolor options.
|
|
161
|
+
*/
|
|
55
162
|
export function recolorObject(colors: Record<string, Color>, opt?: RecolorOptions): void {
|
|
56
163
|
if (!isValidRecolorOptions(opt)) return;
|
|
57
164
|
|
|
58
|
-
for (const [
|
|
59
|
-
colors[
|
|
165
|
+
for (const [key, color] of Object.entries(colors)) {
|
|
166
|
+
colors[key] = recolor(color, opt);
|
|
60
167
|
}
|
|
61
168
|
}
|
|
62
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Applies recoloring transformations to an array of colors.
|
|
172
|
+
* @param colors An array of `Color` instances.
|
|
173
|
+
* @param opt Optional recolor options.
|
|
174
|
+
*/
|
|
63
175
|
export function recolorArray(colors: Color[], opt?: RecolorOptions): void {
|
|
64
176
|
if (!isValidRecolorOptions(opt)) return;
|
|
65
177
|
|
|
@@ -68,43 +180,58 @@ export function recolorArray(colors: Color[], opt?: RecolorOptions): void {
|
|
|
68
180
|
}
|
|
69
181
|
}
|
|
70
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Caches recolored colors to optimize performance.
|
|
185
|
+
*/
|
|
71
186
|
export class CachedRecolor {
|
|
72
187
|
private readonly skip: boolean;
|
|
73
|
-
|
|
74
188
|
private readonly opt?: RecolorOptions;
|
|
75
|
-
|
|
76
189
|
private readonly cache: Map<string, Color>;
|
|
77
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Creates a cached recolor instance.
|
|
193
|
+
* @param opt Optional recolor options.
|
|
194
|
+
*/
|
|
78
195
|
public constructor(opt?: RecolorOptions) {
|
|
79
196
|
this.skip = !isValidRecolorOptions(opt);
|
|
80
197
|
this.cache = new Map();
|
|
81
198
|
this.opt = opt;
|
|
82
199
|
}
|
|
83
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Applies cached recoloring to a given color.
|
|
203
|
+
* @param color The color to recolor.
|
|
204
|
+
* @returns The recolored color, either from cache or newly computed.
|
|
205
|
+
*/
|
|
84
206
|
public do(color: Color): Color {
|
|
85
207
|
if (this.skip) return color;
|
|
86
208
|
|
|
87
209
|
const key = color.asHex();
|
|
210
|
+
if (this.cache.has(key)) return this.cache.get(key)!;
|
|
88
211
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
color = recolor(color, this.opt);
|
|
93
|
-
this.cache.set(key, color);
|
|
94
|
-
return color;
|
|
212
|
+
const recolored = recolor(color, this.opt);
|
|
213
|
+
this.cache.set(key, recolored);
|
|
214
|
+
return recolored;
|
|
95
215
|
}
|
|
96
216
|
}
|
|
97
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Applies the specified recoloring transformations to a single color.
|
|
220
|
+
* @param color The original color.
|
|
221
|
+
* @param opt Optional recolor options.
|
|
222
|
+
* @returns A new `Color` instance with applied transformations.
|
|
223
|
+
*/
|
|
98
224
|
export function recolor(color: Color, opt?: RecolorOptions): Color {
|
|
99
225
|
if (!isValidRecolorOptions(opt)) return color;
|
|
100
226
|
|
|
101
|
-
if (opt.invertBrightness
|
|
102
|
-
if (
|
|
103
|
-
if (
|
|
104
|
-
if (
|
|
105
|
-
if (
|
|
106
|
-
if (
|
|
107
|
-
if (
|
|
227
|
+
if (opt.invertBrightness) color = color.invertLuminosity();
|
|
228
|
+
if (opt.rotate) color = color.rotateHue(opt.rotate);
|
|
229
|
+
if (opt.saturate) color = color.saturate(opt.saturate);
|
|
230
|
+
if (opt.gamma != null && opt.gamma != 1) color = color.gamma(opt.gamma);
|
|
231
|
+
if (opt.contrast != null && opt.contrast != 1) color = color.contrast(opt.contrast);
|
|
232
|
+
if (opt.brightness) color = color.brightness(opt.brightness);
|
|
233
|
+
if (opt.tint && opt.tintColor != null) color = color.tint(opt.tint, Color.parse(opt.tintColor));
|
|
234
|
+
if (opt.blend && opt.blendColor != null) color = color.blend(opt.blend, Color.parse(opt.blendColor));
|
|
108
235
|
|
|
109
236
|
return color;
|
|
110
237
|
}
|