first commit
This commit is contained in:
commit
89c3ddb84b
14 changed files with 1886 additions and 0 deletions
763
apca-w3.js
Normal file
763
apca-w3.js
Normal file
|
@ -0,0 +1,763 @@
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
/** @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 ////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////
|
Loading…
Add table
Add a link
Reference in a new issue