From 748504cf9198f20b2b4313397a7ee9bd314660ab Mon Sep 17 00:00:00 2001 From: Hugo Nogueira Date: Fri, 19 Aug 2022 13:09:51 +0100 Subject: [PATCH] feat: Add pickHexColorContrast (#10) * feat: Add pickHexColorContrast * chore: Export pickHexColorContrast * Update publish-rc.yml * Create thin-dolls-shout.md --- .changeset/thin-dolls-shout.md | 5 +++++ .github/workflows/publish-rc.yml | 4 ++-- src/getSRGBLuminanceFromHex.ts | 13 +++++++---- src/index.ts | 1 + src/pickHexColorContrast.ts | 33 +++++++++++++++++++++++++++ src/tests/pickColorContrast.test.ts | 35 +++++++++++++++++++++++++++++ src/types.ts | 2 ++ src/util/index.ts | 5 +++-- 8 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 .changeset/thin-dolls-shout.md create mode 100644 src/pickHexColorContrast.ts create mode 100644 src/tests/pickColorContrast.test.ts diff --git a/.changeset/thin-dolls-shout.md b/.changeset/thin-dolls-shout.md new file mode 100644 index 0000000..7cc1626 --- /dev/null +++ b/.changeset/thin-dolls-shout.md @@ -0,0 +1,5 @@ +--- +"@sardine/colour": minor +--- + +feat: Add `pickHexColorContrast` function diff --git a/.github/workflows/publish-rc.yml b/.github/workflows/publish-rc.yml index 8cc9885..a5add94 100644 --- a/.github/workflows/publish-rc.yml +++ b/.github/workflows/publish-rc.yml @@ -44,7 +44,7 @@ jobs: - name: Publish Skroll Release Candidate run: | - npm version prerelease --preid rc.${{ github.event.number }}.${{ github.run_id }} --tag rc --no-git-tag-version - npm publish + npm version prerelease --preid rc.${{ github.event.number }}.${{ github.run_id }} --git-tag-version=false + npm publish --tag rc env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/src/getSRGBLuminanceFromHex.ts b/src/getSRGBLuminanceFromHex.ts index e3558bf..f797815 100644 --- a/src/getSRGBLuminanceFromHex.ts +++ b/src/getSRGBLuminanceFromHex.ts @@ -1,5 +1,6 @@ import { convertHextoRGB } from "./converters.js"; import { linearRGB } from "./util/index.js"; +import type { WCAG } from "./types"; /** * Returns the relative luminance of a colour in the sRGB space. @@ -12,11 +13,15 @@ import { linearRGB } from "./util/index.js"; * https://www.w3.org/WAI/GL/wiki/Relative_luminance * @param colour an hexadecimal colour */ -export const getSRGBLuminanceFromHex = (colour: string) => { +export const getSRGBLuminanceFromHex = ( + colour: string, + standard?: WCAG +) => { + const isWCAG21 = standard === "WCAG2.1"; const rgbColor = convertHextoRGB(colour); - const r = linearRGB(rgbColor.R); - const g = linearRGB(rgbColor.G); - const b = linearRGB(rgbColor.B); + const r = linearRGB(rgbColor.R, isWCAG21); + const g = linearRGB(rgbColor.G, isWCAG21); + const b = linearRGB(rgbColor.B, isWCAG21); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; diff --git a/src/index.ts b/src/index.ts index 50b69da..8b727b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,3 +7,4 @@ export { } from "./converters.js"; export { RGBdistance } from "./RGBdistance.js"; export { getSRGBLuminanceFromHex } from "./getSRGBLuminanceFromHex.js"; +export { pickHexColorContrast } from "./pickHexColorContrast.js"; diff --git a/src/pickHexColorContrast.ts b/src/pickHexColorContrast.ts new file mode 100644 index 0000000..a3cf886 --- /dev/null +++ b/src/pickHexColorContrast.ts @@ -0,0 +1,33 @@ +import { getSRGBLuminanceFromHex } from "./getSRGBLuminanceFromHex.js"; +import type { WCAG } from "./types"; + +type ColorArgs = { + backgroundColour: string; + optionOneColour: string; + optionTwoColour: string; +} + +const relativeContrast = (firstColour: number, secondColour: number) => { + if (firstColour > secondColour) { + return firstColour / secondColour; + } + return secondColour / firstColour; +} + +export const pickHexColorContrast = ( + {backgroundColour, optionOneColour, optionTwoColour}: ColorArgs, + standard: WCAG +): string => { + const backgroundColourLuminance = getSRGBLuminanceFromHex(backgroundColour, standard) + 0.05; + const optionOneColourLuminance = getSRGBLuminanceFromHex(optionOneColour, standard) + 0.05; + const optionTwoColourLuminance = getSRGBLuminanceFromHex(optionTwoColour, standard) + 0.05; + + const optionOneContrast = relativeContrast(optionOneColourLuminance, backgroundColourLuminance); + const optionTwoContrast = relativeContrast(optionTwoColourLuminance, backgroundColourLuminance); + + if (optionOneContrast > optionTwoContrast) { + return optionOneColour; + } + return optionTwoColour; + +}; diff --git a/src/tests/pickColorContrast.test.ts b/src/tests/pickColorContrast.test.ts new file mode 100644 index 0000000..cbc9f69 --- /dev/null +++ b/src/tests/pickColorContrast.test.ts @@ -0,0 +1,35 @@ +import test from "ava"; +import { pickHexColorContrast } from "../pickHexColorContrast.js"; + +test("should return #FFFFFF as the best colour for a #333333 background", ({ + is, +}) => { + const colours = { + backgroundColour: "#333333", + optionOneColour: "#FFFFFF", + optionTwoColour: "#000000", + }; + is(pickHexColorContrast(colours, "WCAG2.1"), "#FFFFFF"); +}); + +test("should return #000000 as the best colour for a #BED background", ({ + is, +}) => { + const colours = { + backgroundColour: "#BED", + optionOneColour: "#FFFFFF", + optionTwoColour: "#000000", + }; + is(pickHexColorContrast(colours, "WCAG2.1"), "#000000"); +}); + +test("should return #000000 as the best colour for a #DD337F background", ({ + is, +}) => { + const colours = { + backgroundColour: "#DD337F", + optionOneColour: "#FFFFFF", + optionTwoColour: "#000000", + }; + is(pickHexColorContrast(colours, "WCAG2.1"), "#000000"); +}); diff --git a/src/types.ts b/src/types.ts index d9bd69e..59f6b0e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -48,3 +48,5 @@ export interface HueHelper { } export type ColourSpace = "sRGB"; + +export type WCAG = "WCAG2.1" | "WCAG3.0"; diff --git a/src/util/index.ts b/src/util/index.ts index 8748918..85e7cfd 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -78,11 +78,12 @@ export const bigSquare = (n: number): number => * It takes a RGB channel in the range [0 - 255] and returns a value between 0 and 1 * @param rgbValue number to be normalised */ -export function linearRGB(rgbValue: number) { +export function linearRGB(rgbValue: number, WCAG21?: boolean) { const rgbRatio = rgbValue / 255; + const threshold = WCAG21 ? 0.03928 : 0.04045; let linearValue: number; - if (rgbRatio > 0.04045) { + if (rgbRatio > threshold) { linearValue = Math.pow((rgbRatio + 0.055) / 1.055, 2.4); } else { linearValue = rgbRatio / 12.92;