/////////////////////////////////////////////////////////////////////////////// /** @preserve ///// SAPC APCA - Advanced Perceptual Contrast Algorithm ///// Beta 0.1.9 W3 • contrast function only ///// DIST: W3 • Revision date: July 3, 2022 ///// Function to parse color values and determine Lc contrast ///// Copyright © 2019-2022 by Andrew Somers. All Rights Reserved. ///// LICENSE: W3 LICENSE ///// CONTACT: Please use the ISSUES or DISCUSSIONS tab at: ///// https://github.com/Myndex/SAPC-APCA/ ///// ///// ------------------------------------------------------ ///// Modified 2024-08-30 by Christian Lawson-Perfect ///// christian@lawson-perfect.uk ///// to use culori.js instead of colorparsley ///// ------------------------------------------------------ ///// /////////////////////////////////////////////////////////////////////////////// ///// ///// MINIMAL IMPORTS: ///// import { APCAcontrast, sRGBtoY, displayP3toY, ///// calcAPCA, fontLookupAPCA } from 'apca-w3'; ///// import { colorParsley } from 'colorparsley'; ///// ///// FORWARD CONTRAST USAGE: ///// Lc = APCAcontrast( sRGBtoY( TEXTcolor ) , sRGBtoY( BACKGNDcolor ) ); ///// Where the colors are sent as an rgba array [255,255,255,1] ///// ///// Retrieving an array of font sizes for the contrast: ///// fontArray = fontLookupAPCA(Lc); ///// ///// Live Demonstrator at https://www.myndex.com/APCA/ // */ /////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///// ///// SAPC Method and APCA Algorithm ///// W3 Licensed Version: https://github.com/Myndex/apca-w3 ///// GITHUB MAIN REPO: https://github.com/Myndex/SAPC-APCA ///// DEVELOPER SITE: https://git.myndex.com/ ///// ///// Acknowledgments and Thanks To: ///// • This project references the research & work of M.Fairchild, R.W.Hunt, ///// Drs. Bailey/Lovie-Kitchin, G.Legge, A.Arditi, M.Stone, C.Poynton, ///// L.Arend, M.Luo, E.Burns, R.Blackwell, P.Barton, M.Brettel, and many ///// others — see refs at https://www.myndex.com/WEB/WCAG_CE17polarity ///// • Bruce Bailey of USAccessBoard for his encouragement, ideas, & feedback ///// • Chris Lilly of W3C for continued review, examination, & oversight ///// • Chris Loiselle of Oracle for getting us back on track in a pandemic ///// • The many volunteer test subjects for participating in the studies. ///// • The many early adopters, beta testers, and code/issue contributors ///// • Principal research conducted at Myndex by A.Somers. ///// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///// ///// ***** SAPC BLOCK ***** ///// ///// For Evaluations, refer to this as: SAPC-8, 0.0.98G-series constant 4g ///// SAPC • S-LUV Advanced Predictive Color ///// ///// SIMPLE VERSION — Only the basic APCA contrast predictor. ///// ///// Included Extensions & Model Features in this file: ///// • SAPC-8 Core Contrast (Base APCA, non-clinical use only) ///// • G series constants, group "G-4g" using a 2.4 monitor exponent ///// • sRGB to Y, parses numeric sRGB color to luminance ///// • SoftToe black level soft clamp and flare compensation. ///// ///// //////////////////////////////////////////////////////////////////////////////// ///// ///// DISCLAIMER AND LIMITATIONS OF USE ///// APCA is an embodiment of certain suprathreshold contrast ///// prediction technologies and it is licensed to the W3 on a ///// limited basis for use in certain specific accessibility ///// guidelines for web content only. APCA may be used for ///// predicting colors for web content use without royalty. ///// ///// However, Any such license excludes other use cases ///// not related to web content. Prohibited uses include ///// medical, clinical evaluation, human safety related, ///// aerospace, transportation, military applications, ///// and uses which are not specific to web based content ///// presented on self-illuminated displays or devices. ///// //////////////////////////////////////////////////////////////////////////////// ////////// APCA 0.1.9 G 4g USAGE /////////////////////////////////////////// /// /// The API for "APCA 0.1.9" is trivially simple. /// Send text and background sRGB numeric values to the sRGBtoY() function, /// and send the resulting text-Y and background-Y to the APCAcontrast function /// it returns a signed float with the numeric Lc contrast result. /// /// The two inputs are TEXT color and BACKGROUND color in that order. /// Each must be a numeric NOT a string, as this simple version has /// no string parsing utilities. EXAMPLE: /// ________________________________________________________________________ /// /// txtColor = colorParsley(0x123456); // color of the text /// bgColor = colorParsley(0xabcdef); // color for the background /// /// contrastLc = APCAcontrast( sRGBtoY(txtColor) , sRGBtoY(bgColor) ); /// ________________________________________________________________________ /// /// ********** QUICK START ********** /// /// Each color must be a 24bit color (8 bit per channel) as a single integer /// (or 0x) sRGB encoded color, i.e. White is either the integer 16777216 or /// the hex 0xffffff. A float is returned with a positive or negative value. /// Negative values mean light text and a dark background, positive values /// mean dark text and a light background. 60.0, or -60.0 is a contrast /// "sort of like" the old WCAG 2's 4.5:1. NOTE: the total range is now less /// than ± 110, so output can be rounded to a signed INT but DO NOT output /// an absolute value - light text on dark BG should return a negative number. /// /// ***** IMPORTANT: Do Not Mix Up Text and Background inputs. ***** /// **************** APCA is polarity sensitive! ***************** /// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// ///// BEGIN APCA 0.1.9 BLOCK \///////////////////////////////////// //// \/////////////////////////////////// /// \///////////////////////////////// // \/////////////////////////////// ///// DEPENDENCIES ///// // The following imports are not needed for the main APCA function, // but are needed for the shortcut/alias calcAPCA(), and for the // future invertAPCA function, which examines hue. //// (add slash to line start for local test mode, remove before push) /* //// LOCAL TESTING SWITCH for using test.html import{colorParsley}from'../node_modules/colorparsley/src/colorparsley.js'; /*/ //// TOGGLE import * as culori from './culori.mjs'; const convertToRGB = culori.converter('rgb'); function parse_rgb(def) { const col = culori.parse(def); const {r, g, b, alpha} = convertToRGB(col); return [255*r, 255*g, 255*b, alpha == undefined ? 1 : alpha]; } // */ //// END LOCAL TESTING SWITCH ///// Module Scope Object Containing Constants ///// ///// APCA 0.0.98G - 4g - W3 Compatible Constants ///// 𝒦 SA98G /////////////////////////////////// const SA98G = { mainTRC: 2.4, // 2.4 exponent for emulating actual monitor perception // For reverseAPCA get mainTRCencode() { return 1 / this.mainTRC }, // sRGB coefficients sRco: 0.2126729, sGco: 0.7151522, sBco: 0.0721750, // G-4g constants for use with 2.4 exponent normBG: 0.56, normTXT: 0.57, revTXT: 0.62, revBG: 0.65, // G-4g Clamps and Scalers blkThrs: 0.022, blkClmp: 1.414, scaleBoW: 1.14, scaleWoB: 1.14, loBoWoffset: 0.027, loWoBoffset: 0.027, deltaYmin: 0.0005, loClip: 0.1, ///// MAGIC NUMBERS for UNCLAMP, for use with 0.022 & 1.414 ///// // Magic Numbers for reverseAPCA mFactor: 1.94685544331710, get mFactInv() { return 1 / this.mFactor}, mOffsetIn: 0.03873938165714010, mExpAdj: 0.2833433964208690, get mExp() { return this.mExpAdj / this.blkClmp}, mOffsetOut: 0.3128657958707580, } ////////////////////////////////////////////////////////////////////////////// ////////// APCA CALCULATION FUNCTIONS \///////////////////////////////////// ////////// ƒ APCAcontrast() //////////////////////////////////////////// export function APCAcontrast (txtY,bgY,places = -1) { // send linear Y (luminance) for text and background. // txtY and bgY must be between 0.0-1.0 // IMPORTANT: Do not swap, polarity is important. const icp = [0.0,1.1]; // input range clamp / input error check if(isNaN(txtY)||isNaN(bgY)||Math.min(txtY,bgY)icp[1]){ return 0.0; // return zero on error // return 'error'; // optional string return for error }; ////////// SAPC LOCAL VARS ///////////////////////////////////////// let SAPC = 0.0; // For raw SAPC values let outputContrast = 0.0; // For weighted final values let polCat = 'BoW'; // Alternate Polarity Indicator. N normal R reverse // TUTORIAL // Use Y for text and BG, and soft clamp black, // return 0 for very close luminances, determine // polarity, and calculate SAPC raw contrast // Then scale for easy to remember levels. // Note that reverse contrast (white text on black) // intentionally returns a negative number // Proper polarity is important! ////////// BLACK SOFT CLAMP //////////////////////////////////////// // Soft clamps Y for either color if it is near black. txtY = (txtY > SA98G.blkThrs) ? txtY : txtY + Math.pow(SA98G.blkThrs - txtY, SA98G.blkClmp); bgY = (bgY > SA98G.blkThrs) ? bgY : bgY + Math.pow(SA98G.blkThrs - bgY, SA98G.blkClmp); ///// Return 0 Early for extremely low ∆Y if ( Math.abs(bgY - txtY) < SA98G.deltaYmin ) { return 0.0; } ////////// APCA/SAPC CONTRAST - LOW CLIP (W3 LICENSE) /////////////// if ( bgY > txtY ) { // For normal polarity, black text on white (BoW) // Calculate the SAPC contrast value and scale SAPC = ( Math.pow(bgY, SA98G.normBG) - Math.pow(txtY, SA98G.normTXT) ) * SA98G.scaleBoW; // Low Contrast smooth rollout to prevent polarity reversal // and also a low-clip for very low contrasts outputContrast = (SAPC < SA98G.loClip) ? 0.0 : SAPC - SA98G.loBoWoffset; } else { // For reverse polarity, light text on dark (WoB) // WoB should always return negative value. polCat = 'WoB'; SAPC = ( Math.pow(bgY, SA98G.revBG) - Math.pow(txtY, SA98G.revTXT) ) * SA98G.scaleWoB; outputContrast = (SAPC > -SA98G.loClip) ? 0.0 : SAPC + SA98G.loWoBoffset; } // return Lc (lightness contrast) as a signed numeric value // Round to the nearest whole number as string is optional. // Rounded can be a signed INT as output will be within ± 127 // places = -1 returns signed float, 1 or more set that many places // 0 returns rounded string, uses BoW or WoB instead of minus sign if(places < 0 ){ // Default (-1) number out, all others are strings return outputContrast * 100.0; } else if(places == 0 ){ return Math.round(Math.abs(outputContrast)*100.0)+''+polCat+''; } else if(Number.isInteger(places)){ return (outputContrast * 100.0).toFixed(places); } else { return 0.0 } } // End APCAcontrast() /* SWITCH -- WORK IN PROGRESS DO NOT USE ////////// ƒ invertAPCA() ////////////////////////////////////////////////// export function invertAPCA ( {knownColor: [128,128,128], knownType: 'bg', targetCnst: 75, returnAs: 'object', unknownType: 'txt', hueInvert: false, hueRange: 5, preserveSat: false }) { //if (Math.abs(targetCnst) < 15) { return false }; // abs contrast must be > 15 let knownY = sRGBtoY (knownColor); let unknownY = knownY, knownExp, unknownExp; let min,max,knownLs,isBG = true; if (knownType == 'bg' || knownType == 'background') { knownLs = Math.pow(knownY, ); black = APCAcontrast(0,knownY); white = APCAcontrast(1,knownY); } else if (knownType == 'txt' || knownType == 'text') { isBG = false; black = APCAcontrast(knownY,0); white = APCAcontrast(knownY,1); } else { return false } // return false on error ///// APCA 0.0.98G - 4g - W3 Compatible Constants //////////////////// const scale = contrast > 0 ? SA98G.scaleBoW : SA98G.scaleWoB; const offset = contrast > 0 ? SA98G.loBoWoffset : -SA98G.loWoBoffset; targetCnst = ( parseFloat(targetCnst) * 0.01 + offset ) / scale; // Soft clamps Y if it is near black. knownY = (knownY > SA98G.blkThrs) ? knownY : knownY + Math.pow(SA98G.blkThrs - knownY, SA98G.blkClmp); // set the known and unknown exponents if (isBG) { knownExp = targetCnst > 0 ? SA98G.normBG : SA98G.revBG; unknownExp = targetCnst > 0 ? SA98G.normTXT : SA98G.revTXT; unknownY = Math.pow( Math.pow(knownY,knownExp) - targetCnst, 1/unknownExp ); if (isNaN(unknownY)) return false; } else if (!isBG) { knownExp = targetCnst > 0 ? SA98G.normTXT : SA98G.revTXT; unknownExp = targetCnst > 0 ? SA98G.normBG : SA98G.revBG; unknownY = Math.pow(targetCnst + Math.pow(knownY,knownExp), 1/unknownExp ); if (isNaN(unknownY)) return false; } //return contrast +'----'+unknownY; if (unknownY > 1.06 || unknownY < 0) { return false } // return false on overflow // if (unknownY < 0) { return false } // return false on underflow //unknownY = Math.max(unknownY,0.0); // unclamp unknownY = (unknownY > SA98G.blkThrs) ? unknownY : (Math.pow(((unknownY + SA98G.mOffsetIn)m* SA98G.mFactor), SA98G.mExp) * SA98G.mFactInv) - SA98G.mOffsetOut; // unknownY - 0.22 * Math.pow(unknownY*0.5, 1/blkClmp); unknownY = Math.max(Math.min(unknownY,1.0),0.0); let testedCnst = (isBG) ? APCAcontrast(unknownY,knownY) : APCAcontrast(knownY,unknownY); if (returnAs === 'object') { let hexB = ( Math.round(Math.pow(unknownY,SA98G.mainTRCencode) * 255) ).toString(16).padStart(2,'0'); hexB = '#' + hexB + hexB + hexB; return {color: hexB, Lc: testedCnst, whiteLc: white, blackLc: black}; } else if (returnAs === 'hex') { let hexB = ( Math.round(Math.pow(unknownY,SA98G.mainTRCencode) * 255) ).toString(16).padStart(2,'0'); return '#' + hexB + hexB + hexB; } else if (returnAs === 'array') { let colorB = Math.round(Math.pow(unknownY,SA98G.mainTRCencode) * 255); let retUse = (knownType == 'bg') ? 'txtColor' : 'bgColor' return [colorB,colorB,colorB,1,retUse]; } else if (returnAs === 'Y' || returnAs === 'y') { return Math.max(0.0,unknownY); } else { return false } // return knownY on error } // */ // END SWITCH ////////// ƒ reverseAPCA() DEPRECATED SOON /////////////////////////////// export function reverseAPCA (contrast = 0,knownY = 1.0, knownType = 'bg',returnAs = 'hex') { if (Math.abs(contrast) < 9) { return false }; // abs contrast must be > 9 let unknownY = knownY, knownExp, unknownExp; ///// APCA 0.0.98G - 4g - W3 Compatible Constants //////////////////// const scale = contrast > 0 ? SA98G.scaleBoW : SA98G.scaleWoB; const offset = contrast > 0 ? SA98G.loBoWoffset : -SA98G.loWoBoffset; contrast = ( parseFloat(contrast) * 0.01 + offset ) / scale; // Soft clamps Y if it is near black. knownY = (knownY > SA98G.blkThrs) ? knownY : knownY + Math.pow(SA98G.blkThrs - knownY, SA98G.blkClmp); // set the known and unknown exponents if (knownType == 'bg' || knownType == 'background') { knownExp = contrast > 0 ? SA98G.normBG : SA98G.revBG; unknownExp = contrast > 0 ? SA98G.normTXT : SA98G.revTXT; unknownY = Math.pow( Math.pow(knownY,knownExp) - contrast, 1/unknownExp ); if (isNaN(unknownY)) return false; } else if (knownType == 'txt' || knownType == 'text') { knownExp = contrast > 0 ? SA98G.normTXT : SA98G.revTXT; unknownExp = contrast > 0 ? SA98G.normBG : SA98G.revBG; unknownY = Math.pow(contrast + Math.pow(knownY,knownExp), 1/unknownExp ); if (isNaN(unknownY)) return false; } else { return false } // return false on error //return contrast +'----'+unknownY; if (unknownY > 1.06 || unknownY < 0) { return false } // return false on overflow // if (unknownY < 0) { return false } // return false on underflow //unknownY = Math.max(unknownY,0.0); // unclamp unknownY = (unknownY > SA98G.blkThrs) ? unknownY : (Math.pow(((unknownY + SA98G.mOffsetIn) * SA98G.mFactor), SA98G.mExp) * SA98G.mFactInv) - SA98G.mOffsetOut; // unknownY - 0.22 * Math.pow(unknownY*0.5, 1/blkClmp); unknownY = Math.max(Math.min(unknownY,1.0),0.0); if (returnAs === 'hex') { let hexB = ( Math.round(Math.pow(unknownY,SA98G.mainTRCencode) * 255) ).toString(16).padStart(2,'0'); return '#' + hexB + hexB + hexB; } else if (returnAs === 'color') { let colorB = Math.round(Math.pow(unknownY,SA98G.mainTRCencode) * 255); let retUse = (knownType == 'bg') ? 'txtColor' : 'bgColor' return [colorB,colorB,colorB,1,retUse]; } else if (returnAs === 'Y' || returnAs === 'y') { return Math.max(0.0,unknownY); } else { return false } // return knownY on error } ////////// ƒ calcAPCA() ///////////////////////////////////////////// export function calcAPCA (textColor, bgColor, places = -1, round = true) { // Note that this function requires culori !! let bgClr = parse_rgb(bgColor); let txClr = parse_rgb(textColor); let hasAlpha = (txClr[3] == '' || txClr[3] == 1) ? false : true ; if (hasAlpha) { txClr = alphaBlend( txClr, bgClr, round); }; return APCAcontrast( sRGBtoY(txClr), sRGBtoY(bgClr), places) } // End calcAPCA() ////////////////////////////////////////////////////////////////////////////// ////////// ƒ fontLookupAPCA() 0.1.7 (G) \//////////////////////////////// ///////// \////////////////////////////// export function fontLookupAPCA (contrast,places=2) { //////////////////////////////////////////////////////////////////////////// ///// CONTRAST * FONT WEIGHT & SIZE ///////////////////////////////////// // Font size interpolations. Here the chart was re-ordered to put // the main contrast levels each on one line, instead of font size per line. // First column is LC value, then each following column is font size by weight // G G G G G G Public Beta 0.1.7 (G) • MAY 28 2022 // Lc values under 70 should have Lc 15 ADDED if used for body text // All font sizes are in px and reference font is Barlow // 999: prohibited - too low contrast // 777: NON TEXT at this minimum weight stroke // 666 - this is for spot text, not fluent-Things like copyright or placeholder. // 5xx - minimum font at this weight for content, 5xx % 500 for font-size // 4xx - minimum font at this weight for any purpose], 4xx % 400 for font-size // MAIN FONT SIZE LOOKUP //// ASCENDING SORTED Public Beta 0.1.7 (G) • MAY 28 2022 //// //// Lc 45 * 0.2 = 9 which is the index for the row for Lc 45 // MAIN FONT LOOKUP May 28 2022 EXPANDED // Sorted by Lc Value // First row is standard weights 100-900 // First column is font size in px // All other values are the Lc contrast // 999 = too low. 777 = non-text and spot text only const fontMatrixAscend = [ ['Lc',100,200,300,400,500,600,700,800,900], [0,999,999,999,999,999,999,999,999,999], [10,999,999,999,999,999,999,999,999,999], [15,777,777,777,777,777,777,777,777,777], [20,777,777,777,777,777,777,777,777,777], [25,777,777,777,120,120,108,96,96,96], [30,777,777,120,108,108,96,72,72,72], [35,777,120,108,96,72,60,48,48,48], [40,120,108,96,60,48,42,32,32,32], [45,108,96,72,42,32,28,24,24,24], [50,96,72,60,32,28,24,21,21,21], [55,80,60,48,28,24,21,18,18,18], [60,72,48,42,24,21,18,16,16,18], [65,68,46,32,21.75,19,17,15,16,18], [70,64,44,28,19.5,18,16,14.5,16,18], [75,60,42,24,18,16,15,14,16,18], [80,56,38.25,23,17.25,15.81,14.81,14,16,18], [85,52,34.5,22,16.5,15.625,14.625,14,16,18], [90,48,32,21,16,15.5,14.5,14,16,18], [95,45,28,19.5,15.5,15,14,13.5,16,18], [100,42,26.5,18.5,15,14.5,13.5,13,16,18], [105,39,25,18,14.5,14,13,12,16,18], [110,36,24,18,14,13,12,11,16,18], [115,34.5,22.5,17.25,12.5,11.875,11.25,10.625,14.5,16.5], [120,33,21,16.5,11,10.75,10.5,10.25,13,15], [125,32,20,16,10,10,10,10,12,14], ]; // ASCENDING SORTED Public Beta 0.1.7 (G) • MAY 28 2022 //// // DELTA - MAIN FONT LOOKUP May 28 2022 EXPANDED // EXPANDED Sorted by Lc Value •• DELTA // The pre-calculated deltas of the above array const fontDeltaAscend = [ ['∆Lc',100,200,300,400,500,600,700,800,900], [0,0,0,0,0,0,0,0,0,0], [10,0,0,0,0,0,0,0,0,0], [15,0,0,0,0,0,0,0,0,0], [20,0,0,0,0,0,0,0,0,0], [25,0,0,0,12,12,12,24,24,24], [30,0,0,12,12,36,36,24,24,24], [35,0,12,12,36,24,18,16,16,16], [40,12,12,24,18,16,14,8,8,8], [45,12,24,12,10,4,4,3,3,3], [50,16,12,12,4,4,3,3,3,3], [55,8,12,6,4,3,3,2,2,0], [60,4,2,10,2.25,2,1,1,0,0], [65,4,2,4,2.25,1,1,0.5,0,0], [70,4,2,4,1.5,2,1,0.5,0,0], [75,4,3.75,1,0.75,0.188,0.188,0,0,0], [80,4,3.75,1,0.75,0.188,0.188,0,0,0], [85,4,2.5,1,0.5,0.125,0.125,0,0,0], [90,3,4,1.5,0.5,0.5,0.5,0.5,0,0], [95,3,1.5,1,0.5,0.5,0.5,0.5,0,0], [100,3,1.5,0.5,0.5,0.5,0.5,1,0,0], [105,3,1,0,0.5,1,1,1,0,0], [110,1.5,1.5,0.75,1.5,1.125,0.75,0.375,1.5,1.5], [115,1.5,1.5,0.75,1.5,1.125,0.75,0.375,1.5,1.5], [120,1,1,0.5,1,0.75,0.5,0.25,1,1], [125,0,0,0,0,0,0,0,0,0], ]; // APCA CONTRAST FONT LOOKUP TABLES // Copyright © 2022 by Myndex Research and Andrew Somers. All Rights Reserved // Public Beta 0.1.7 (G) • MAY 28 2022 // For the following arrays, the Y axis is contrastArrayLen // The two x axis are weightArrayLen and scoreArrayLen // MAY 28 2022 const weightArray = [0,100,200,300,400,500,600,700,800,900]; const weightArrayLen = weightArray.length; // X axis let returnArray = [contrast.toFixed(places),0,0,0,0,0,0,0,0,0,]; const returnArrayLen = returnArray.length; // X axis const contrastArrayAscend = ['lc',0,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100,105,110,115,120,125,]; const contrastArrayLenAsc = contrastArrayAscend.length; // Y azis //// Lc 45 * 0.2 = 9, and 9 is the index for the row for Lc 45 let tempFont = 777; contrast = Math.abs(contrast); // Polarity unneeded for LUT const factor = 0.2; // 1/5 as LUT is in increments of 5 const index = (contrast == 0) ? 1 : (contrast * factor) | 0 ; // LUT row... n|0 is bw floor let w = 0; // scoreAdj interpolates the needed font side per the Lc let scoreAdj = (contrast - fontMatrixAscend[index][w]) * factor; w++; // determines column in font matrix LUT ///////// Font and Score Interpolation \///////////////////////////////// // populate returnArray with interpolated values for (; w < weightArrayLen; w++) { tempFont = fontMatrixAscend[index][w]; if (tempFont > 400) { // declares a specific minimum for the weight. returnArray[w] = tempFont; } else if (contrast < 14.5 ) { returnArray[w] = 999; // 999 = do not use for anything } else if (contrast < 29.5 ) { returnArray[w] = 777; // 777 = non-text only } else { // INTERPOLATION OF FONT SIZE // sets level for 0.5px size increments of smaller fonts // Note bitwise (n|0) instead of floor (tempFont > 24) ? returnArray[w] = Math.round(tempFont - (fontDeltaAscend[index][w] * scoreAdj)) : returnArray[w] = tempFont - ((2.0 * fontDeltaAscend[index][w] * scoreAdj) | 0) * 0.5; // (n|0) is bitwise floor } } ///////// End Interpolation //////////////////////////////////////////// return returnArray } // end fontLookupAPCA /////////\ ///////////////////////////\ //////////\ END fontLookupAPCA() 0.1.7 (G) /////////////////////////////\ /////////////////////////////////////////////////////////////////////////////\ ////////////////////////////////////////////////////////////////////////////// ////////// LUMINANCE CONVERTERS |////////////////////////////////////////// ////////// ƒ sRGBtoY() ////////////////////////////////////////////////// export function sRGBtoY (rgb = [0,0,0]) { // send sRGB 8bpc (0xFFFFFF) or string // NOTE: Currently expects 0-255 ///// APCA 0.0.98G - 4g - W3 Compatible Constants //////////////////// /* const mainTRC = 2.4; // 2.4 exponent emulates actual monitor perception const sRco = 0.2126729, sGco = 0.7151522, sBco = 0.0721750; // sRGB coefficients */ // Future: // 0.2126478133913640 0.7151791475336150 0.0721730390750208 // Derived from: // xW yW K xR yR xG yG xB yB // 0.312720 0.329030 6504 0.640 0.330 0.300 0.600 0.150 0.060 // linearize r, g, or b then apply coefficients // and sum then return the resulting luminance function simpleExp (chan) { return Math.pow(chan/255.0, SA98G.mainTRC); }; return SA98G.sRco * simpleExp(rgb[0]) + SA98G.sGco * simpleExp(rgb[1]) + SA98G.sBco * simpleExp(rgb[2]); } // End sRGBtoY() ////////// ƒ displayP3toY() ///////////////////////////////////////////// export function displayP3toY (rgb = [0,0,0]) { // send rgba array // NOTE: Currently Apple has the tuple as 0.0 to 1.0, NOT 255 ///// APCA 0.0.98G - 4g - W3 Compatible Constants //////////////////// const mainTRC = 2.4; // 2.4 exponent emulates actual monitor perception // Pending evaluation, because, Apple... const sRco = 0.2289829594805780, sGco = 0.6917492625852380, sBco = 0.0792677779341829; // displayP3 coefficients // Derived from: // xW yW K xR yR xG yG xB yB // 0.312720 0.329030 6504 0.680 0.320 0.265 0.690 0.150 0.060 // linearize r, g, or b then apply coefficients // and sum then return the resulting luminance function simpleExp (chan) { return Math.pow(chan, mainTRC); }; return sRco * simpleExp(rgb[0]) + sGco * simpleExp(rgb[1]) + sBco * simpleExp(rgb[2]); } // End displayP3toY() ////////// ƒ adobeRGBtoY() ///////////////////////////////////////////// export function adobeRGBtoY (rgb = [0,0,0]) { // send rgba array // NOTE: Currently expects 0-255 ///// APCA 0.0.98G - 4g - W3 Compatible Constants //////////////////// const mainTRC = 2.35; // 2.35 exponent emulates actual monitor perception // Pending evaluation... const sRco = 0.2973550227113810, sGco = 0.6273727497145280, sBco = 0.0752722275740913; // adobeRGB coefficients // Derived from: // xW yW K xR yR xG yG xB yB // 0.312720 0.329030 6504 0.640 0.330 0.210 0.710 0.150 0.060 // linearize r, g, or b then apply coefficients // and sum then return the resulting luminance function simpleExp (chan) { return Math.pow(chan/255.0, mainTRC); }; return sRco * simpleExp(rgb[0]) + sGco * simpleExp(rgb[1]) + sBco * simpleExp(rgb[2]); } // End displayP3toY() //////////////////////////////////////////////////////////////////////////// ////////// UTILITIES \/////////////////////////////////////////////////// ////////// ƒ alphaBlend() ///////////////////////////////////////////// // send rgba array for text/icon, rgb for background. // Only foreground allows alpha of 0.0 to 1.0 // This blends using gamma encoded space (standard) // rounded 0-255 or set round=false for number 0.0-255.0 export function alphaBlend (rgbaFG=[0,0,0,1.0], rgbBG=[0,0,0], round = true ) { rgbaFG[3] = Math.max(Math.min(rgbaFG[3], 1.0), 0.0); // clamp alpha 0-1 let compBlend = 1.0 - rgbaFG[3]; let rgbOut = [0,0,0,1,true]; // or just use rgbBG to retain other elements? for (let i=0;i<3;i++) { rgbOut[i] = rgbBG[i] * compBlend + rgbaFG[i] * rgbaFG[3]; if (round) rgbOut[i] = Math.min(Math.round(rgbOut[i]),255); }; return rgbOut; } // End alphaBlend() //\ //////////////////////////////////////// ///\ //////////////////////////////////////// ////\ //////////////////////////////////////// /////\ END APCA 0.1.9 G-4g BLOCK //////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////