PageSourceSearch

https://klimamap.de/ol10/color.js

js klimamap.de collected 2026-10-05 02:46:08 UTC 8,822 bytes, 333 lines download raw bytes

vendor: 8,822 bytes, lines 1-333
1/**
2 * @module ol/color
3 */
4import {createCanvasContext2D} from './dom.js';
5import {clamp, toFixed} from './math.js';
6
7/**
8 * A color represented as a short array [red, green, blue, alpha].
9 * red, green, and blue should be integers in the range 0..255 inclusive.
10 * alpha should be a float in the range 0..1 inclusive. If no alpha value is
11 * given then `1` will be used.
12 * @typedef {Array<number>} Color
13 * @api
14 */
15
16/**
17 * Color to indicate that no color should be rendered. This is meant to be used for per-reference
18 * comparisons only.
19 * @type {Color}
20 */
21export const NO_COLOR = [NaN, NaN, NaN, 0];
22
23let colorParseContext;
24/**
25 * @return {CanvasRenderingContext2D} The color parse context
26 */
27function getColorParseContext() {
28  if (!colorParseContext) {
29    colorParseContext = createCanvasContext2D(1, 1, undefined, {
30      willReadFrequently: true,
31      desynchronized: true,
32    });
33  }
34  return colorParseContext;
35}
36
37const rgbModernRegEx =
38  /^rgba?\(\s*(\d+%?)\s+(\d+%?)\s+(\d+%?)(?:\s*\/\s*(\d+%|\d*\.\d+|[01]))?\s*\)$/i;
39const rgbLegacyAbsoluteRegEx =
40  /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*(\d+%|\d*\.\d+|[01]))?\s*\)$/i;
41const rgbLegacyPercentageRegEx =
42  /^rgba?\(\s*(\d+%)\s*,\s*(\d+%)\s*,\s*(\d+%)(?:\s*,\s*(\d+%|\d*\.\d+|[01]))?\s*\)$/i;
43const hexRegEx = /^#([\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;
44
45/**
46 * @param {string} s Color component as number or percentage.
47 * @param {number} divider Divider for percentage.
48 * @return {number} Color component.
49 */
50function toColorComponent(s, divider) {
51  return s.endsWith('%')
52    ? Number(s.substring(0, s.length - 1)) / divider
53    : Number(s);
54}
55
56/**
57 * @param {string} color Color string.
58 */
59function throwInvalidColor(color) {
60  throw new Error('failed to parse "' + color + '" as color');
61}
62
63/**
64 * @param {string} color Color string.
65 * @return {Color} RGBa color array.
66 */
67function parseRgba(color) {
68  // Fast lane for rgb(a) colors
69  if (color.toLowerCase().startsWith('rgb')) {
70    const rgb =
71      color.match(rgbLegacyAbsoluteRegEx) ||
72      color.match(rgbModernRegEx) ||
73      color.match(rgbLegacyPercentageRegEx);
74    if (rgb) {
75      const alpha = rgb[4];
76      const rgbDivider = 100 / 255;
77      return [
78        clamp((toColorComponent(rgb[1], rgbDivider) + 0.5) | 0, 0, 255),
79        clamp((toColorComponent(rgb[2], rgbDivider) + 0.5) | 0, 0, 255),
80        clamp((toColorComponent(rgb[3], rgbDivider) + 0.5) | 0, 0, 255),
81        alpha !== undefined ? clamp(toColorComponent(alpha, 100), 0, 1) : 1,
82      ];
83    }
84    throwInvalidColor(color);
85  }
86  // Fast lane for hex colors (also with alpha)
87  if (color.startsWith('#')) {
88    if (hexRegEx.test(color)) {
89      const hex = color.substring(1);
90      const step = hex.length <= 4 ? 1 : 2;
91      const colorFromHex = [0, 0, 0, 255];
92      for (let i = 0, ii = hex.length; i < ii; i += step) {
93        let colorComponent = parseInt(hex.substring(i, i + step), 16);
94        if (step === 1) {
95          colorComponent += colorComponent << 4;
96        }
97        colorFromHex[i / step] = colorComponent;
98      }
99      colorFromHex[3] = colorFromHex[3] / 255;
100      return colorFromHex;
101    }
102    throwInvalidColor(color);
103  }
104  // Use canvas color serialization to parse the color into hex or rgba
105  // See https://www.w3.org/TR/2021/SPSD-2dcontext-20210128/#serialization-of-a-color
106  const context = getColorParseContext();
107  context.fillStyle = '#abcdef';
108  let invalidCheckFillStyle = context.fillStyle;
109  context.fillStyle = color;
110  if (context.fillStyle === invalidCheckFillStyle) {
111    context.fillStyle = '#fedcba';
112    invalidCheckFillStyle = context.fillStyle;
113    context.fillStyle = color;
114    if (context.fillStyle === invalidCheckFillStyle) {
115      throwInvalidColor(color);
116    }
117  }
118  const colorString = context.fillStyle;
119  if (colorString.startsWith('#') || colorString.startsWith('rgba')) {
120    return parseRgba(colorString);
121  }
122  context.clearRect(0, 0, 1, 1);
123  context.fillRect(0, 0, 1, 1);
124  const colorFromImage = Array.from(context.getImageData(0, 0, 1, 1).data);
125  colorFromImage[3] = toFixed(colorFromImage[3] / 255, 3);
126  return colorFromImage;
127}
128
129/**
130 * Return the color as an rgba string.
131 * @param {Color|string} color Color.
132 * @return {string} Rgba string.
133 * @api
134 */
135export function asString(color) {
136  if (typeof color === 'string') {
137    return color;
138  }
139  return toString(color);
140}
141
142/**
143 * @type {number}
144 */
145const MAX_CACHE_SIZE = 1024;
146
147/**
148 * We maintain a small cache of parsed strings.  Whenever the cache grows too large,
149 * we delete an arbitrary set of the entries.
150 *
151 * @type {Object<string, Color>}
152 */
153const cache = {};
154
155/**
156 * @type {number}
157 */
158let cacheSize = 0;
159
160/**
161 * @param {Color} color A color that may or may not have an alpha channel.
162 * @return {Color} The input color with an alpha channel.  If the input color has
163 * an alpha channel, the input color will be returned unchanged.  Otherwise, a new
164 * array will be returned with the input color and an alpha channel of 1.
165 */
166export function withAlpha(color) {
167  if (color.length === 4) {
168    return color;
169  }
170  const output = color.slice();
171  output[3] = 1;
172  return output;
173}
174
175// The functions b1, b2, a1, a2, rgbaToLcha and lchaToRgba below are adapted from
176// https://stackoverflow.com/a/67219995/2389327
177
178/**
179 * @param {number} v Input value.
180 * @return {number} Output value.
181 */
182function b1(v) {
183  return v > 0.0031308 ? Math.pow(v, 1 / 2.4) * 269.025 - 14.025 : v * 3294.6;
184}
185
186/**
187 * @param {number} v Input value.
188 * @return {number} Output value.
189 */
190function b2(v) {
191  return v > 0.2068965 ? Math.pow(v, 3) : (v - 4 / 29) * (108 / 841);
192}
193
194/**
195 * @param {number} v Input value.
196 * @return {number} Output value.
197 */
198function a1(v) {
199  return v > 10.314724 ? Math.pow((v + 14.025) / 269.025, 2.4) : v / 3294.6;
200}
201
202/**
203 * @param {number} v Input value.
204 * @return {number} Output value.
205 */
206function a2(v) {
207  return v > 0.0088564 ? Math.pow(v, 1 / 3) : v / (108 / 841) + 4 / 29;
208}
209
210/**
211 * @param {Color} color RGBA color.
212 * @return {Color} LCHuv color with alpha.
213 */
214export function rgbaToLcha(color) {
215  const r = a1(color[0]);
216  const g = a1(color[1]);
217  const b = a1(color[2]);
218  const y = a2(r * 0.222488403 + g * 0.716873169 + b * 0.06060791);
219  const l = 500 * (a2(r * 0.452247074 + g * 0.399439023 + b * 0.148375274) - y);
220  const q = 200 * (y - a2(r * 0.016863605 + g * 0.117638439 + b * 0.865350722));
221  const h = Math.atan2(q, l) * (180 / Math.PI);
222  return [
223    116 * y - 16,
224    Math.sqrt(l * l + q * q),
225    h < 0 ? h + 360 : h,
226    color[3],
227  ];
228}
229
230/**
231 * @param {Color} color LCHuv color with alpha.
232 * @return {Color} RGBA color.
233 */
234export function lchaToRgba(color) {
235  const l = (color[0] + 16) / 116;
236  const c = color[1];
237  const h = (color[2] * Math.PI) / 180;
238  const y = b2(l);
239  const x = b2(l + (c / 500) * Math.cos(h));
240  const z = b2(l - (c / 200) * Math.sin(h));
241  const r = b1(x * 3.021973625 - y * 1.617392459 - z * 0.404875592);
242  const g = b1(x * -0.943766287 + y * 1.916279586 + z * 0.027607165);
243  const b = b1(x * 0.069407491 - y * 0.22898585 + z * 1.159737864);
244  return [
245    clamp((r + 0.5) | 0, 0, 255),
246    clamp((g + 0.5) | 0, 0, 255),
247    clamp((b + 0.5) | 0, 0, 255),
248    color[3],
249  ];
250}
251
252/**
253 * @param {string} s String.
254 * @return {Color} Color.
255 */
256export function fromString(s) {
257  if (s === 'none') {
258    return NO_COLOR;
259  }
260  if (cache.hasOwnProperty(s)) {
261    return cache[s];
262  }
263  if (cacheSize >= MAX_CACHE_SIZE) {
264    let i = 0;
265    for (const key in cache) {
266      if ((i++ & 3) === 0) {
267        delete cache[key];
268        --cacheSize;
269      }
270    }
271  }
272
273  const color = parseRgba(s);
274  if (color.length !== 4) {
275    throwInvalidColor(s);
276  }
277  for (const c of color) {
278    if (isNaN(c)) {
279      throwInvalidColor(s);
280    }
281  }
282  cache[s] = color;
283  ++cacheSize;
284  return color;
285}
286
287/**
288 * Return the color as an array. This function maintains a cache of calculated
289 * arrays which means the result should not be modified.
290 * @param {Color|string} color Color.
291 * @return {Color} Color.
292 * @api
293 */
294export function asArray(color) {
295  if (Array.isArray(color)) {
296    return color;
297  }
298  return fromString(color);
299}
300
301/**
302 * @param {Color} color Color.
303 * @return {string} String.
304 */
305export function toString(color) {
306  let r = color[0];
307  if (r != (r | 0)) {
308    r = (r + 0.5) | 0;
309  }
310  let g = color[1];
311  if (g != (g | 0)) {
312    g = (g + 0.5) | 0;
313  }
314  let b = color[2];
315  if (b != (b | 0)) {
316    b = (b + 0.5) | 0;
317  }
318  const a = color[3] === undefined ? 1 : Math.round(color[3] * 1000) / 1000;
319  return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
320}
321
322/**
323 * @param {string} s String.
324 * @return {boolean} Whether the string is actually a valid color
325 */
326export function isStringColor(s) {
327  try {
328    fromString(s);
329    return true;
330  } catch {
331    return false;
332  }
333}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.