Skip to content
126 changes: 125 additions & 1 deletion src/color/p5.Color.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { RGB, RGBHDR, HSL, HSB, HWB, LAB, LCH, OKLAB, OKLCH } from './creating_reading';


import {
ColorSpace,
to,
Expand All @@ -25,7 +26,8 @@ import {

OKLab,
OKLCH as OKLCHSpace,

contrastWCAG21,
contrastAPCA,
P3
} from 'colorjs.io/fn';
import HSBSpace from './color_spaces/hsb.js';
Expand All @@ -41,6 +43,9 @@ const map = (n, start1, stop1, start2, stop2, clamp) => {

const serializationMap = {};




class Color {
// Reference to underlying color object depending on implementation
// Not meant to be used publicly unless the implementation is known for sure
Expand Down Expand Up @@ -326,6 +331,125 @@ class Color {
return colorString;
}

/**
* Checks the contrast between two colors. This method returns a boolean
* value to indicate if the two color has enough contrast. `true` means that
* the colors has enough contrast to be used as background color and body
* text color. `false` means there is not enough contrast.
*
* A second argument can be passed to the method, `options` , which defines
* the algorithm to be used. The algorithms currently supported are
* WCAG 2.1 (`'WCAG21'`) or APCA (`'APCA'`). The default is WCAG 2.1. If a
* value of `'all'` is passed to the `options` argument, an object containing
* more details is returned. The details object will include the calculated
* contrast value of the colors and different passing criteria.
*
* For more details about color contrast, you can checkout this page from
* color.js. The WebAIM color contrast checker is a good tool to check out as
* well.
*
* @param {Color} other
* @returns {boolean|object}
* @example
* <div>
* <code>
* let bgColor, fg1Color, fg2Color, msg1, msg2;
* function setup() {
* createCanvas(100, 100);
* bgColor = color(0);
* fg1Color = color(100);
* fg2Color = color(220);
*
* if(bgColor.contrast(fg1Color)){
* msg1 = 'good';
* }else{
* msg1 = 'bad';
* }
*
* if(bgColor.contrast(fg2Color)){
* msg2 = 'good';
* }else{
* msg2 = 'bad';
* }
*
* describe('A black canvas with a faint grey word saying "bad" at the top left and a brighter light grey word saying "good" in the middle of the canvas.');
* }
*
* function draw(){
* background(bgColor);
*
* textSize(18);
*
* fill(fg1Color);
* text(msg1, 10, 30);
*
* fill(fg2Color);
* text(msg2, 10, 60);
* }
* </code>
* </div>
*
* <div>
* <code>
* let bgColor, fgColor, contrast;
* function setup() {
* createCanvas(100, 100);
* bgColor = color(0);
* fgColor = color(200);
* contrast = bgColor.contrast(fgColor, 'all');
*
* describe('A black canvas with four short lines of grey text that respectively says: "WCAG 2.1", "12.55", "APCA", and "-73.30".');
* }
*
* function draw(){
* background(bgColor);
*
* textSize(14);
*
* fill(fgColor);
* text('WCAG 2.1', 10, 25);
* text(nf(contrast.WCAG21.value, 0, 2), 10, 40);
*
* text('APCA', 10, 70);
* text(nf(contrast.APCA.value, 0, 2), 10, 85);
* }
* </code>
* </div>
*/
contrast(other_color, options='WCAG21') {
if(options !== 'all'){
let contrastVal, minimum;
switch(options){
case 'WCAG21':
contrastVal = contrastWCAG21(this._color, other_color._color);
minimum = 4.5;
break;
case 'APCA':
contrastVal = Math.abs(contrastAPCA(this._color, other_color._color));
minimum = 75;
break;
default:
return null;
}

return contrastVal >= minimum;
}else{
const wcag21Value = contrastWCAG21(this._color, other_color._color);
const apcaValue = contrastAPCA(this._color, other_color._color);
return {
WCAG21: {
value: wcag21Value,
passedMinimum: wcag21Value >= 4.5,
passedAAA: wcag21Value >= 7
},
APCA: {
value: apcaValue,
passedMinimum: Math.abs(apcaValue) >= 75
}
};
}
};

/**
* Sets the red component of a color.
*
Expand Down
Loading