UNPKG

colorparsley

Version:

colorParsley() • a lightweight yet versatile color parsing function with no dependencies. Takes various color strings, numbers, or objects, and turns them into simple arrays. Bonus utilities colorToHex() and colorToRGB() included

377 lines (299 loc) 16.1 kB
/////////////////////////////////////////////////////////////////////////////// /** @preserve ///// CoLoR PaRsLeY a simple set of color parsing thingies! ///// Beta 0.1.8 Revision date: June 04, 2022 ///// ///// Functions to parse color values and return array ///// Copyright (c) 2019-2022 by Andrew Somers. All Rights Reserved. ///// LICENSE: AGPL 3 ///// CONTACT: Please use the ISSUES or DISCUSSIONS tab at: ///// https://github.com/Myndex/colorparsley/ ///// /////////////////////////////////////////////////////////////////////////////// ///// ///// IMPORT: ///// import { colorParsley } from 'colorparsley'; ///// ///// let rgbaArray = colorParsley('#abcdef'); ///// ///// Output as array: [r,g,b,a,isValid,colorspace] ///// Example: [123,123,123,1.0,true,'sRGB'] // */ /////////////////////////////////////////////////////////////////////////////// // ==ClosureCompiler== // @compilation_level SIMPLE_OPTIMIZATIONS // @output_file_name colorparsley.min.js // @code_url https://raw.githubusercontent.com/Myndex/colorparsley/master/src/colorparsley.js // ==/ClosureCompiler== // //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///// BEGIN COLOR PARSLEY 0.1.8 BLOCK \///////////////////////////////////// //// \/////////////////////////////////// /// \///////////////////////////////// ///// ƒ colorParsley() /////////////////////////////////////////////////// export function colorParsley (colorIn) { if (typeof colorIn === 'string') { return parseString(colorIn); } else if (typeof colorIn === 'number') { return [(colorIn & 0xFF0000) >> 16, (colorIn & 0x00FF00) >> 8, (colorIn & 0x0000FF), 1, true, 'unknown']; } else if (typeof colorIn === 'object') { if (Array.isArray(colorIn)) { return colorIn; } else if (!isNaN(colorIn.r) || !isNaN(colorIn.red)){ // validate object & return array let objArray = [0,0,0,0,false,'unknown']; // takes object with r g b or red green blue etc... objArray[0] = (colorIn.r)?colorIn.r:(colorIn.red)? colorIn.red:false; objArray[1] = (colorIn.g)?colorIn.g:(colorIn.green)? colorIn.green:false; objArray[2] = (colorIn.b)?colorIn.b:(colorIn.blue)? colorIn.blue:false; objArray[3] = (colorIn.a)?colorIn.a:(colorIn.alpha)? colorIn.alpha:1; objArray[4] = (objArray[0]&&objArray[1]&&objArray[2]) ? true : false; objArray[5] = (colorIn.space)?colorIn.space: (colorIn.colorSpace)?colorIn.colorSpace: (colorIn.colorspace)?colorIn.colorspace: 'unknown'; return objArray; } }; // End if statement console.log('colorParsley error: invalid input') return [0,0,0,0,false,'inputError'] // throw 'InvalidInput' // return error }; ///// ƒ parseString() /////////////////////////////////////////////////// // private function parseString (colorString = '#abcdef') { // strip junk and make a clean string (replace unmatched) // This retains all alphanumeric and , . # % ( ) / colorString = colorString.replace(/[^\w,.#%()\/ -]/g,''); colorString = colorString.toLowerCase(); // set lowercase let isValid = false; // validation flag, in array element [4] let type = 'sRGB'; // Default colorspace flag in element [5] let retArray = [0,0,0,0,isValid,type]; // init the return array // test for named color before iterating array if (colorString.match(/^(?:(?!rgb|l.h|hs|col|\d|#).{0,4})(?=[g-z])/)) { ///// CSS4 NAMED COLORS plus a bonus set of GREYS and GRAYS ////////// // If name is matched, parse and return the color values let namedColors = { gray0:'000000',gray1:'111111',gray2:'222222',gray3:'333333',gray4:'444444',gray5:'555555',gray6:'666666',gray7:'777777',gray8:'888888',gray9:'999999',graya:'aaaaaa',grayb:'bbbbbb',grayc:'cccccc',grayd:'dddddd',graye:'eeeeee',grayf:'ffffff',midgray:'a0a0a0', grey0:'000000',grey1:'111111',grey2:'222222',grey3:'333333',grey4:'444444',grey5:'555555',grey6:'666666',grey7:'777777',grey8:'888888',grey9:'999999',greya:'aaaaaa',greyb:'bbbbbb',greyc:'cccccc',greyd:'dddddd',greye:'eeeeee',greyf:'ffffff',midgrey:'a0a0a0', aliceblue:'f0f8ff',antiquewhite:'faebd7',aqua:'00ffff',aquamarine:'7fffd4',azure:'f0ffff', beige:'f5f5dc',bisque:'ffe4c4',black:'000000',blanchedalmond:'ffebcd',blue:'0000ff',blueviolet:'8a2be2',brown:'a52a2a',burlywood:'deb887', cadetblue:'5f9ea0',chartreuse:'7fff00',chocolate:'d2691e',coral:'ff7f50',cornflowerblue:'6495ed',cornsilk:'fff8dc',crimson:'dc143c',cyan:'00ffff', darkblue:'00008b',darkcyan:'008b8b',darkgoldenrod:'b8860b',darkgray:'a9a9a9',darkgreen:'006400',darkgrey:'a9a9a9',darkkhaki:'bdb76b',darkmagenta:'8b008b',darkolivegreen:'556b2f',darkorange:'ff8c00',darkorchid:'9932cc',darkred:'8b0000',darksalmon:'e9967a',darkseagreen:'8fbc8f',darkslateblue:'483d8b',darkslategray:'2f4f4f',darkslategrey:'2f4f4f',darkturquoise:'00ced1',darkviolet:'9400d3',deeppink:'ff1493',deepskyblue:'00bfff',dimgray:'696969',dimgrey:'696969',dodgerblue:'1e90ff', firebrick:'b22222',floralwhite:'fffaf0',forestgreen:'228b22',fuchsia:'ff00ff', gainsboro:'dcdcdc',ghostwhite:'f8f8ff',gold:'ffd700',goldenrod:'daa520',gray:'808080',green:'008000',greenyellow:'adff2f',grey:'808080', honeydew:'f0fff0',hotpink:'ff69b4', indianred:'cd5c5c',indigo:'4b0082',ivory:'fffff0', khaki:'f0e68c', lavender:'e6e6fa',lavenderblush:'fff0f5',lawngreen:'7cfc00',lemonchiffon:'fffacd',lightblue:'add8e6',lightcoral:'f08080',lightcyan:'e0ffff',lightgoldenrodyellow:'fafad2',lightgray:'d3d3d3',lightgreen:'90ee90',lightgrey:'d3d3d3',lightpink:'ffb6c1',lightsalmon:'ffa07a',lightseagreen:'20b2aa',lightskyblue:'87cefa',lightslategray:'778899',lightslategrey:'778899',lightsteelblue:'b0c4de',lightyellow:'ffffe0',lime:'00ff00',limegreen:'32cd32',linen:'faf0e6', magenta:'ff00ff',maroon:'800000',mediumaquamarine:'66cdaa',mediumblue:'0000cd',mediumorchid:'ba55d3',mediumpurple:'9370db',mediumseagreen:'3cb371',mediumslateblue:'7b68ee',mediumspringgreen:'00fa9a',mediumturquoise:'48d1cc',mediumvioletred:'c71585',midnightblue:'191970',mintcream:'f5fffa',mistyrose:'ffe4e1',moccasin:'ffe4b5', navajowhite:'ffdead',navy:'000080', oldlace:'fdf5e6',olive:'808000',olivedrab:'6b8e23',orange:'ffa500',orangered:'ff4500',orchid:'da70d6', palegoldenrod:'eee8aa',palegreen:'98fb98',paleturquoise:'afeeee',palevioletred:'db7093',papayawhip:'ffefd5',peachpuff:'ffdab9',peru:'cd853f',pink:'ffc0cb',plum:'dda0dd',powderblue:'b0e0e6',purple:'800080', rebeccapurple:'663399',red:'ff0000',rosybrown:'bc8f8f',royalblue:'4169e1', saddlebrown:'8b4513',salmon:'fa8072',sandybrown:'f4a460',seagreen:'2e8b57',seashell:'fff5ee',sienna:'a0522d',silver:'c0c0c0',skyblue:'87ceeb',slateblue:'6a5acd',slategray:'708090',slategrey:'708090',snow:'fffafa',springgreen:'00ff7f',steelblue:'4682b4', tan:'d2b48c',teal:'008080',thistle:'d8bfd8',tomato:'ff6347',turquoise:'40e0d0', violet:'ee82ee', wheat:'f5deb3',white:'ffffff',whitesmoke:'f5f5f5', yellow:'ffff00',yellowgreen:'9acd32' }; for (let key in namedColors) { if (colorString == key) { let hexRex = { rex: /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/, sprig: function (slices) { for (let i = 0; i < 3; i++) { retArray[i] = parseInt(slices[i+1],16); } retArray[3] = 1; return true; } }; let hexProc = hexRex.rex.exec(namedColors[key]); retArray[4] = isValid = hexRex.sprig(hexProc); return retArray; } } }; // end of named colors section // NEW regex 0.1.6 - still current for use with with 0.1.8+ // See docs for breakdown of regex pattern let colorRex = { rex: /(?:^(?:#|0x|)(?:(?:([\da-f])([\da-f])([\da-f])([\da-f])?)(?!\S)|(?:([\da-f]{2})(?:([\da-f]{2})([\da-f]{2})([\da-f]{2})?)?))|(?:(?:^(?:rgba?|)\(? ?(?:(?:(?:(255|(?:25[0-4]|2[0-4]\d|1?\d{1,2})(?:\.\d{1,24})?)))(?:,[^\S]*$|(?:(?:, ?| )(255|(?:25[0-4]|2[0-4]\d|1?\d{1,2})(?:\.\d{1,24})?)(?:, ?| )(255|(?:25[0-4]|2[0-4]\d|1?\d{1,2})(?:\.\d{1,24})?)))|(100%|\d{1,2}(?:\.\d{1,24})?%)(?:,?[^\S]*$|(?:(?:, ?| )(?:(100%|\d{1,2}(?:\.\d{1,24})?%)(?:, ?| )(100%|\d{1,2}(?:\.\d{1,24})?%)))))|^(?:color\((srgb|srgb-linear|display-p3|a98-rgb|prophoto-rgb|rec2020|xyz|xyz-d50|xyz-d65) (?:(100%|\d{1,2}(?:\.\d{1,24})?%|[0 ]\.\d{1,24}|[01])) (?:(100%|\d{1,2}(?:\.\d{1,24})?%|[0 ]\.\d{1,24}|[01])) (?:(100%|\d{1,2}(?:\.\d{1,24})?%|[0 ]\.\d{1,24}|[01])))|^(?:((?:r(?!gb)|c(?!olor)|[abd-qs-z])[a-z]{2,5})\( ?((?:\d{0,3}\.|)\d{1,24}%?)(?:, ?| )((?:\d{0,3}\.|)\d{1,24}%?)(?:, ?| )((?:\d{0,3}\.|)\d{1,24}%?))))(?:(?:,| \/| ) ?(?:(100%|\d{1,2}(?:\.\d{1,24})?%|[0 ]\.\d{1,24}|[01])))?(?:\)| |))[^\S]*$/, parsley: function (slices) { let slicePos = 0; let sliceLast = 0; let base = 10; let divisor = 100.0; let convertPct = 2.55; let alpha = '1'; if (slices[23]) { alpha = slices[23]; delete slices[23]; } // Set alpha before anything else retArray[3] = (alpha.match(/%/g)) ? parseFloat(alpha) / divisor : parseFloat(alpha); // determine first and last element for (let k=1; k < slices.length; k++) { if (slices[k]) { slicePos = (slicePos) ? slicePos : k; sliceLast = k; } } switch (sliceLast) { case 4: // This is the 3-4 digit hex parsing base = 16; divisor = 15.0; retArray[3] = parseInt(slices[sliceLast],base) / divisor ; case 3: base = 16; for (let i = 0; i < 3; i++) { retArray[i] = parseInt(slices[slicePos+i] + slices[slicePos+i],base); } break; case 5: // allows two digit hex to become grey base = 16; case 9: // allows 1-3 digit INT with comma to become grey retArray[0] = retArray[1] = retArray[2] = (base == 10) ? parseFloat(slices[sliceLast]) : parseInt(slices[sliceLast],base); break; case 12: // allows single percentage to become grey retArray[0] = retArray[1] = retArray[2] = parseFloat(slices[sliceLast]) * convertPct; break; case 8: // These are the main parsings for hex and rgb() base = 16; divisor = 255.0; retArray[3] = parseInt(slices[8],base) / divisor ; case 7: base = 16; case 11: for (let i = 0; i < 3; i++) { retArray[i] = (base == 10) ? parseFloat(slices[slicePos+i]) : parseInt(slices[slicePos+i],base); } break; case 14: // rgb() percentage for (let i = 0; i < 3; i++) { retArray[i] = parseFloat(slices[slicePos+i]) * convertPct; } break; case 18: // This is for color() CSS 4 retArray[5] = slices[15]; for (let i = 0; i < 3; i++) { // color() is converted to 0.0-255.0 slicePos++; retArray[i] = (slices[slicePos].match(/%/g)) ? parseFloat(slices[slicePos]) * 2.55: parseFloat(slices[slicePos]) * 255; } break; case 22: // This is the "wild west" section retArray[5] = slices[slicePos]; for (let i = 0; i < 3; i++ ) { slicePos++; retArray[i] = (slices[slicePos]) ? (slices[slicePos].match(/%/g)) ? parseFloat(slices[slicePos]) / divisor : parseFloat(slices[slicePos]) : 0.0 ; } // Process for HSL and HWB if (retArray[5].match(/^(?:hsla?|hwba?)/i) ) { let sat,light,white,black,hwbFact; let hue = retArray[0] % 360.0; if (hue < 0) { hue += 360.0; } if (retArray[5].match(/^hsla?/i) ) { sat = retArray[1]; light = retArray[2]; white = 0; hwbFact = 1; } else if (retArray[5].match(/^hwba?/i) ) { white = retArray[1]; black = retArray[2]; if (white + black >= 1) { retArray[0] = retArray[1] = retArray[2] = white / (white + black); retArray[5] = 'sRGB'; break; } sat = 1.0; light = 0.5; hwbFact = (1.0 - white - black); } function f(n) { // from CSS reference implementation let k = (n + hue/30) % 12; let a = sat * Math.min(light, 1 - light); return light - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); } retArray[0] = Math.round(255 * (f(0) * hwbFact + white)); retArray[1] = Math.round(255 * (f(8) * hwbFact + white)); retArray[2] = Math.round(255 * (f(4) * hwbFact + white)); retArray[5] = 'sRGB'; } break; } return true; } // close parsley sub-function }; // close colorRex obj // The main call let slicesProc = colorRex.rex.exec(colorString); if (slicesProc) { // Error catch retArray[4] = isValid = colorRex.parsley(slicesProc); // set the isValid flag return retArray; } else { isValid = false; console.log('colorParsley error: unable to parse string') return [0,0,0,0,isValid,'parsleyError'] // throw 'InvalidString' } }; //////////////////////////////////////////////////////////////////////////////// ///// BONUS STRING FORMATTING UTILITIES \//////////////////////////////////// ///// ƒ colorToHex() /////////////////////////////////////////////////// // returns hex string, 3,4,6, or 8 chars if that was entered, no # // If alpha is 1 or empty, no alpha is returned i.e. abcf returns abc export function colorToHex (rgba = [0,0,0,''], allow3 = true) { let R = Math.round(rgba[0]).toString(16).padStart(2, '0'); let G = Math.round(rgba[1]).toString(16).padStart(2, '0'); let B = Math.round(rgba[2]).toString(16).padStart(2, '0'); let A = (rgba[3] == '' || rgba[3] == 1) ? '' : Math.round(rgba[3] * 255).toString(16).padStart(2, '0') ; // this if returns a 3 character hex if possible - aabbcc becomes abc if ( allow3 && parseInt(A, 16) % 17 == 0 && parseInt(A, 16) % 17 == 0 && parseInt(A, 16) % 17 == 0 && (parseInt(A, 16) % 17 == 0 || A == '') ) { return R.charAt(0) + G.charAt(0) + B.charAt(0) + A.charAt(0); } else { return R + G + B + A; } } ///// ƒ colorToRGB() /////////////////////////////////////////////////// // RGBAstr — returns rgb() or rgba() INT value string (0-255) no spaces // If alpha is 1 or empty, no alpha is returned export function colorToRGB (rgba = [0,0,0,''], round = true) { if (round) { for (let i=0; i < 3; i++) { rgba[i] = Math.round(rgba[i]); } // while RGB tuples round to int, A needs additional precision rgba[3] = ( rgba[3] == '' || rgba[3] == 1) ? 1 : Math.trunc(rgba[3]*1000)*0.001; } return ( rgba[3] == '' || rgba[3] == 1) ? 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')' : 'rgba(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ',' + rgba[3] + ')'; } /////\ END UTILITIES /////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// ///\ ////////////////////////////////////// ////\ ////////////////////////////////////// /////\ END COLOR PARSLEY 0.1.8 BLOCK ////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////