最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do you find the color contrast using APCA (Advanced Perpetual Contrast Algorithm)? - Stack Overflow

programmeradmin1浏览0评论

So I wanted to write a color contrast calculator for a given pair of foregroundRGBA and backgroundRGBA colors, using the Advanced Perpetual Contrast Algorithm (APCA) (read Google WebDev's mini-article on APCA).

While the aforementioned site has some code samples to do the same, they are Invalid when either colors have transparency (0 ≤ α < 1)

Point no. 1 is the biggest problem here, since foreground and/or background colors may be semi-transparent. The problem arises in such cases. And I can't really do anything about it. For example, the RGBColor.toY function on that site only works for an sRGB color, and not for RGB colors with alpha transparency.

This is where I need help.

How do I write a color contrast calculator using APCA for a given pair of foregroundRGBA and backgroundRGBA RGBA color objects?

I have copied the constants used in the site and have written some code for some functions, and the RGBA color class (whose objects are to be passed as parameters for the to-be-written APCA color contrast calculator, which can use the RGBA objects and their attributes for easier calculations and readability).

Constants given and used in the site

const sRGBtrc = 2.4;    // Gamma for sRGB linearization. 2.223 could be used instead
                        // 2.218 sets unity with the piecewise sRGB at #777

const Rco = 0.2126;     // sRGB Red Coefficient
const Gco = 0.7156;     // sRGB Green Coefficient
const Bco = 0.0722;     // sRGB Blue Coefficient

const scaleBoW = 1.14;  // Scaling for dark text on light z
const scaleWoB = 1.14;  // Scaling for light text on dark — same as BoW, but
                        // this is separate for possible future use.
const scaleOffset = 0.027;  // Offset

const normBGExp = 0.56;     // Constants for Power Curve Exponents.
const normTXTExp = 0.57;    // One pair for normal text,and one for REVERSE
const revBGExp = 0.62;      // FUTURE: These will eventually be dynamic
const revTXTExp = 0.65;     // as a function of light adaptation and context

const blkThrs = 0.022;  // Level that triggers the soft black clamp
const blkClmp = 1.414;  // Exponent for the soft black clamp curve

Misc. functions

// Clamps a value between a given range [minimum, maximum]
const clamp = (value, minimum=0, maximum=1) => {
  if (value < minimum) return minimum;
  if (value > maximum) return maximum;
  return value;
}

// Modified clamp to clamp an RGBA color value in [0, 255]
const clampColor = (value=0) => clamp(Math.round(value), 0, 255);

The RGBA color class

// An RGBA color class to be used by `foregroundRGBA` and `backgroundRGBA`
class RGBA {
  constructor(red=0, green=0, blue=0, alpha=1) {
    // Clamp the given r, g, b arguments between 0 and 255
    this._red = clampColor(red);
    this._green = clampColor(green);
    this._blue = clampColor(blue);

    // Clamp a between 0 and 1 since it is alpha
    this._alpha = clamp(alpha);
  }

  get r() {
    return this._red;
  }

  set r(value) {
    this._red = clampColor(value);
  }

  get linearizedR() {
    return (this._red / 255) ^ sRGBtrc;
  }

  get g() {
    return this._green;
  }

  set g(value) {
    this._green = clampColor(value);
  }

  get linearizedG() {
    return (this._green / 255) ^ sRGBtrc;
  }

  get b() {
    return this._blue;
  }

  set b(value) {
    this._blue = clampColor(value);
  }

  get linearizedB() {
    return (this._blue / 255) ^ sRGBtrc;
  }

  get a() {
    return this._alpha;
  }

  set a(value) {
    this._alpha = clamp(value);
  }

  // Not given in the site (since the site is using RGB and not RGBA)
  // Written based on assumption (since this._alpha is already betwen 0 and 1 unlike the others)
  get linearizedA() {
    return this._alpha ^ sRGBtrc;
  }

  // Relative Luminance (Y) property of a given RGBA color
  // Works only for RGB and not RGBA (since the formula makes no use of this._alpha and purely returns the value based on RGB and not RGBA)
  get Y() {
    return (
      Math.pow(this._red/255, sRGBtrc) * Rco
    + Math.pow(this._green/255, sRGBtrc) * Gco
    + Math.pow(this._blue/255, sRGBtrc) * Bco

    // Assumption again
    // Also, if there exists an `Aco`, its value is unknown (part of my question)
    // + Math.pow(this._alpha, sRGBtrc) * Aco
    );
  }

  // Modified `toString` to return a CSS-patible RGBA color
  toString() {
    return `rgba(${this._red},${this._green},${this._blue},${this._alpha})`;
  }
}

The APCA-based color contrast calculator to be written

const getAPCAColorContrast = (foregroundRGBA=new RGBA(), backgroundRGBA=new RGBA(255, 255, 255)) => {
  // Code to be written according to the algorithm using passed RGBA objects and their properties
  // WHICH MUST BE COMPATIBLE WITH RGBA COLORS AND NOT JUST RGB COLORS

  return colorContrast;  // Calculated APCA-based color contrast
}

I tried looking for an APCA that can calculate for RGBA colors everywhere but failed.

I will try my best to answer any questions you have to help you give a better answer.
Please let me know if I need to make any edits to make a better question.

I would be thankful to you if you find a way to calculate contrast using APCA for RGBA (not just RGB) colors.

Edit 1: Please note that I do not need code to fill the function (getAPCAColorContrast). All I need is an APCA algorithm patible with not just RGB but RGBA. I can write the code myself, once I know and understand the modified APCA algorithm for RGBA foreground and background colors. (This disclaimer has been written here so that the question doesn't get taken down for breaking rules, if any).

So I wanted to write a color contrast calculator for a given pair of foregroundRGBA and backgroundRGBA colors, using the Advanced Perpetual Contrast Algorithm (APCA) (read Google WebDev's mini-article on APCA).

While the aforementioned site has some code samples to do the same, they are Invalid when either colors have transparency (0 ≤ α < 1)

Point no. 1 is the biggest problem here, since foreground and/or background colors may be semi-transparent. The problem arises in such cases. And I can't really do anything about it. For example, the RGBColor.toY function on that site only works for an sRGB color, and not for RGB colors with alpha transparency.

This is where I need help.

How do I write a color contrast calculator using APCA for a given pair of foregroundRGBA and backgroundRGBA RGBA color objects?

I have copied the constants used in the site and have written some code for some functions, and the RGBA color class (whose objects are to be passed as parameters for the to-be-written APCA color contrast calculator, which can use the RGBA objects and their attributes for easier calculations and readability).

Constants given and used in the site

const sRGBtrc = 2.4;    // Gamma for sRGB linearization. 2.223 could be used instead
                        // 2.218 sets unity with the piecewise sRGB at #777

const Rco = 0.2126;     // sRGB Red Coefficient
const Gco = 0.7156;     // sRGB Green Coefficient
const Bco = 0.0722;     // sRGB Blue Coefficient

const scaleBoW = 1.14;  // Scaling for dark text on light z
const scaleWoB = 1.14;  // Scaling for light text on dark — same as BoW, but
                        // this is separate for possible future use.
const scaleOffset = 0.027;  // Offset

const normBGExp = 0.56;     // Constants for Power Curve Exponents.
const normTXTExp = 0.57;    // One pair for normal text,and one for REVERSE
const revBGExp = 0.62;      // FUTURE: These will eventually be dynamic
const revTXTExp = 0.65;     // as a function of light adaptation and context

const blkThrs = 0.022;  // Level that triggers the soft black clamp
const blkClmp = 1.414;  // Exponent for the soft black clamp curve

Misc. functions

// Clamps a value between a given range [minimum, maximum]
const clamp = (value, minimum=0, maximum=1) => {
  if (value < minimum) return minimum;
  if (value > maximum) return maximum;
  return value;
}

// Modified clamp to clamp an RGBA color value in [0, 255]
const clampColor = (value=0) => clamp(Math.round(value), 0, 255);

The RGBA color class

// An RGBA color class to be used by `foregroundRGBA` and `backgroundRGBA`
class RGBA {
  constructor(red=0, green=0, blue=0, alpha=1) {
    // Clamp the given r, g, b arguments between 0 and 255
    this._red = clampColor(red);
    this._green = clampColor(green);
    this._blue = clampColor(blue);

    // Clamp a between 0 and 1 since it is alpha
    this._alpha = clamp(alpha);
  }

  get r() {
    return this._red;
  }

  set r(value) {
    this._red = clampColor(value);
  }

  get linearizedR() {
    return (this._red / 255) ^ sRGBtrc;
  }

  get g() {
    return this._green;
  }

  set g(value) {
    this._green = clampColor(value);
  }

  get linearizedG() {
    return (this._green / 255) ^ sRGBtrc;
  }

  get b() {
    return this._blue;
  }

  set b(value) {
    this._blue = clampColor(value);
  }

  get linearizedB() {
    return (this._blue / 255) ^ sRGBtrc;
  }

  get a() {
    return this._alpha;
  }

  set a(value) {
    this._alpha = clamp(value);
  }

  // Not given in the site (since the site is using RGB and not RGBA)
  // Written based on assumption (since this._alpha is already betwen 0 and 1 unlike the others)
  get linearizedA() {
    return this._alpha ^ sRGBtrc;
  }

  // Relative Luminance (Y) property of a given RGBA color
  // Works only for RGB and not RGBA (since the formula makes no use of this._alpha and purely returns the value based on RGB and not RGBA)
  get Y() {
    return (
      Math.pow(this._red/255, sRGBtrc) * Rco
    + Math.pow(this._green/255, sRGBtrc) * Gco
    + Math.pow(this._blue/255, sRGBtrc) * Bco

    // Assumption again
    // Also, if there exists an `Aco`, its value is unknown (part of my question)
    // + Math.pow(this._alpha, sRGBtrc) * Aco
    );
  }

  // Modified `toString` to return a CSS-patible RGBA color
  toString() {
    return `rgba(${this._red},${this._green},${this._blue},${this._alpha})`;
  }
}

The APCA-based color contrast calculator to be written

const getAPCAColorContrast = (foregroundRGBA=new RGBA(), backgroundRGBA=new RGBA(255, 255, 255)) => {
  // Code to be written according to the algorithm using passed RGBA objects and their properties
  // WHICH MUST BE COMPATIBLE WITH RGBA COLORS AND NOT JUST RGB COLORS

  return colorContrast;  // Calculated APCA-based color contrast
}

I tried looking for an APCA that can calculate for RGBA colors everywhere but failed.

I will try my best to answer any questions you have to help you give a better answer.
Please let me know if I need to make any edits to make a better question.

I would be thankful to you if you find a way to calculate contrast using APCA for RGBA (not just RGB) colors.

Edit 1: Please note that I do not need code to fill the function (getAPCAColorContrast). All I need is an APCA algorithm patible with not just RGB but RGBA. I can write the code myself, once I know and understand the modified APCA algorithm for RGBA foreground and background colors. (This disclaimer has been written here so that the question doesn't get taken down for breaking rules, if any).

Share Improve this question edited Nov 18, 2021 at 17:15 Myndex 5,4341 gold badge14 silver badges27 bronze badges asked Mar 10, 2021 at 15:03 SFM61319SFM61319 1623 silver badges14 bronze badges 4
  • You may want to file an issue on the GitHub repository for the Web Content Accessibility Guidelines: github./w3c/wcag/issues – Peter O. Commented Mar 10, 2021 at 17:56
  • @PeterO. I was planning on doing that. But I wasn't sure if I should. Now I will do that. Thank you for letting me know! – SFM61319 Commented Mar 10, 2021 at 21:43
  • Note that the link to APCA in this question is an obsolete reference. Please see the canonical GitHub repo: github./Myndex/SAPC-APCA – Myndex Commented Oct 11, 2021 at 18:11
  • NEW: ` colorParsley() `, the parsing utility that es with APCA, now has alpha for text available. – Myndex Commented Feb 5, 2022 at 17:43
Add a ment  | 

2 Answers 2

Reset to default 5

sorry for any confusion. Markov00's answer is essentailly correct, but as I'm the one behind APCA, I thought I should provide an "official" answer. I just stumbled on this question today.

First, yea sorry about the state the code is/was in — this is very early beta, and my focus has been on the supporting research, not so much on the code.

To be honest I wasn't expecting the amount of interest in this and the number of early adopters. So, I am working on a more plete release, and I do plan to have a cleaned up version and npm project by the end of the year.

Resources

There is a GitHib repo for APCA, and I do respond to issues there usually within a day. https://github./Myndex/SAPC-APCA

I hope to have the latest code and docs up there soon. The "bleeding edge" is the SAPC research tool at https://www.myndex./SAPC/

I do see that you got the code from the W3 GitHub, and unfortunately that was a very early draft and generally should not be used as the constants are different and will give very different results.

The current version October 1, 2021, is the APCA 0.98 G-4g (The key is the G-4g constants, as those rule the results.)

Regarding the use of Transparency

The APCA function needs to see the colors as they will be rendered to the screen. It does not have transparency as part of the algorithm because all positing or blending operations MUST be pleted in the given colorspace BEFORE processing by APCA.

The transparency or blending has nothing to do with APCA, this is defined by the user agent and CSS rules, and there are many different ways to posite or handle transparency. Without knowing the way multiple layers of color or image are going to be blended or rasterized to the screen, it's impossible to predict the final sRGB values.

CSS4 Color will be adding linear and other colorspaces for instance, plus new gradient and other color tools. Ultimately what is important is how the colors will appear on screen, so it's left to the user agent/browser/app to do the blend/posite, and then send the resulting sRGB value to the APCA function.

And it is important that the BACKGROUND and the STIMULUS (text) be sent to the correct Text and BG inputs to APCA, as APCA is polarity dependent.

Alpha

That said, there are actually alpha inputs to the color object, and there are a lot of pending features for future release. Some as I mentioned are anticipating the CSS4 color specs.

Regarding alpha: alpha does not have a gamma, though the A channel may need adjustment depending on the desired result. But typically (and prior to CSS 4), browsers do NOT linearize the colors before a blend operation, so that must be done in the correct colorspace FIRST (i.e. the same the browser will use).

THEN you can calculate the correct RGB value to calculate contrast. But what you NEED is the color as rendered to screen, and that means you must plete the positing with the underlying colors first.

If you do not know the underlying colors then you can not calculate contrast, simply because it is ambiguous and undefined.

G-4G CONSTANTS

For your convenience here is the simplest version of the code, none of the extras of the color object, just the essentials to take an sRGB text color and sRGB BG color, convert them to luminance (Y) and then return the contrast value as a numeric Lc.

///////////////////////////////////////////////////////////////////////////////
/////
/////    APCA - Advanced Perceptual Contrast Algorithm - Beta 0.98G-4g
/////     
/////    Function to parse color values and determine SAPC/APCA contrast
/////    Copyright © 2019-2021 by Andrew Somers. All Rights Reserved.
/////    LICENSE: APCA version to be licensed under W3 cooperative agrmnt.
/////    CONTACT: For SAPC/APCA Please use the ISSUES tab at:
/////    https://github./Myndex/SAPC-APCA/
/////
///////////////////////////////////////////////////////////////////////////////
/////
/////    USAGE:
/////        Use sRGBtoY(color) to convert sRGB to Luminance (Y)
/////        Then send Y-text and Y-background to APCAcontrast(Text, BG)
/////
/////    Lc = APCAcontrast( sRGBtoY(TEXTcolor) , sRGBtoY(BACKGNDcolor) );
/////
/////    Live Demonstrator at https://www.myndex./APCA/
/////
///////////////////////////////////////////////////////////////////////////////


//////////   APCA G - 4g Constants   //////////////////////////////////////


const mainTRC = 2.4; // 2.4 exponent emulates actual monitor perception
    
const sRco = 0.2126729, 
      sGco = 0.7151522, 
      sBco = 0.0721750; // sRGB coefficients

const normBG = 0.56, 
      normTXT = 0.57,
      revTXT = 0.62,
      revBG = 0.65;  // G-4g constants for use with 2.4 exponent

const blkThrs = 0.022,
      blkClmp = 1.414, 
      scaleBoW = 1.14,
      scaleWoB = 1.14,
      loBoWthresh = loWoBthresh = 0.035991,
      loBoWfactor = loWoBfactor = 27.7847239587675,
      loBoWoffset = loWoBoffset = 0.027,
      loClip = 0.001,
      deltaYmin = 0.0005;


////////// ƒ sRGBtoY()   ///////////////////////////////////////////////

function sRGBtoY (sRGBcolor) {
                  // send 8 bit-per-channel integer sRGB (0xFFFFFF)

  let r = (sRGBcolor & 0xFF0000) >> 16,
      g = (sRGBcolor & 0x00FF00) >> 8,
      b = (sRGBcolor & 0x0000FF);
    
  function simpleExp (chan) { return Math.pow(chan/255.0, mainTRC); }
 
         // linearize r, g, or b then apply coefficients
        // and sum then return the resulting luminance
    
   return sRco * simpleExp(r) + sGco * simpleExp(g) + sBco * simpleExp(b);
}


////////// ƒ APCAcontrast()   //////////////////////////////////////////

function APCAcontrast (txtY,bgY) {
                         // send linear Y (luminance) for text and background.
                        // IMPORTANT: Do not swap, polarity is important.
        
  var SAPC = 0.0;            // For raw SAPC values
  var outputContrast = 0.0; // For weighted final values
  
  // 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 > blkThrs) ? txtY :
                            txtY + Math.pow(blkThrs - txtY, blkClmp);
  bgY = (bgY > blkThrs) ? bgY :
                          bgY + Math.pow(blkThrs - bgY, blkClmp);

       ///// Return 0 Early for extremely low ∆Y
  if ( Math.abs(bgY - txtY) < deltaYmin ) { return 0.0; }


//////////   APCA/SAPC CONTRAST   /////////////////////////////////////////

  if ( bgY > txtY ) {  // For normal polarity, black text on white (BoW)

           // Calculate the SAPC contrast value and scale
      
    SAPC = ( Math.pow(bgY, normBG) - Math.pow(txtY, normTXT) ) * scaleBoW;

            // Low Contrast smooth rollout to prevent polarity reversal
           // and also a low-clip for very low contrasts
    outputContrast = (SAPC < loClip) ? 0.0 :
                     (SAPC < loBoWthresh) ?
                      SAPC - SAPC * loBoWfactor * loBoWoffset :
                      SAPC - loBoWoffset;

  } else {  // For reverse polarity, light text on dark (WoB)
           // WoB should always return negative value.

    SAPC = ( Math.pow(bgY, revBG) - Math.pow(txtY, revTXT) ) * scaleWoB;

    outputContrast = (SAPC > -loClip) ? 0.0 :
                     (SAPC > -loWoBthresh) ?
                      SAPC - SAPC * loWoBfactor * loWoBoffset :
                      SAPC + loWoBoffset;
  }

         // return Lc (lightness contrast) as a signed numeric value 
        // It is permissible to round to the nearest whole number.
       
  return  outputContrast * 100.0;
}

////////////////////////////////////////////////////////////////////////////////
/////
/////                 SAPC Method and APCA Algorithm
/////
/////   Thanks To: 
/////   • This project references the research and work of Dr.Legge, Dr.Arditi,
/////     Dr.Lovie-Kitchin, M.Fairchild, R.Hunt, M.Stone, Dr.Poynton, L.Arend, &
/////     many others — see refs at https://www.myndex./WEB/WCAG_CE17polarity
/////   • Stoyan Stefanov for his input parsing idea, Twitter @stoyanstefanov
/////   • Bruce Bailey of USAccessBoard for his encouragement, ideas, & feedback
/////   • Chris Loiselle of Oracle for getting us back on track in a pandemic
/////
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/////
/////   *****  SAPC BLOCK  *****
/////
/////   For Evaluations, this is referred to as: SAPC-8, 0.98 G-series constants
/////                S-LUV Advanced Perceptual Contrast
/////   Copyright © 2019-2021 by Andrew Somers. All Rights Reserved.
/////   SIMPLE VERSION — Only the basic APCA contrast predictor.
/////
/////   Included Extensions & Model Features in this file:
/////       • SAPC-8 Core Contrast (Base APCA) 
/////       • G series constants, group "G-4g" using a 2.4 monitor exponent
/////       • sRGB to Y, parses numeric sRGB color to luminance
/////       • SmoothScale™ scaling technique (non-clinical use only)
/////       • SoftToe black level soft clamp and flare pensation.
/////
/////
////////////////////////////////////////////////////////////////////////////////
/////
/////                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.
/////
/////
////////////////////////////////////////////////////////////////////////////////

Lookup Tables

The above code is just to determine contrast of a pair of colors. There are lookup tables for determining the appropriate minimum font size. See https://www.myndex./APCA/ for the simplest implementation.

And please let me know if you have any questions.

Thank you,

Andy

A possible workaround is to first blend the provided RGBA background with the actual opaque background (e.g. if your background represent the color of a button and is a semi-transparent blue, you have to blend that color with the actual background behind the button, like the page color). The blend color should basically represent the perceived opaque color. The same needs to be done for the foreground, blending it with the perceived background. You can then apply the APCA algorithm to pute the contrast.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论