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.