automatic-colour-scheme/apca-w3.js

764 lines
29 KiB
JavaScript
Raw Permalink Normal View History

2025-02-09 20:19:13 +00:00
///////////////////////////////////////////////////////////////////////////////
/** @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[0]||
Math.max(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)+'<sub>'+polCat+'</sub>';
} 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 ////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////