Skip to content

Commit

Permalink
feat: Add function to calculate triadic colors
Browse files Browse the repository at this point in the history
This commit adds a function to calculate the triadic colors of a given color across all supported formats (hex, rgb, hsl, etc.). Accompanying unit tests are also included to validate the correctness of these calculations. The new function has been exported through the project's main entry point for use in creating color schemes.
  • Loading branch information
mallikcheripally committed Jun 10, 2024
1 parent 8c3552a commit 4369ab6
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
125 changes: 125 additions & 0 deletions src/harmony/triadicColors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { detectColorFormat } from '@/parser/detectColorFormat';
import { decomposeColor } from '@/parser/decomposeColor';
import { rgbToHsl } from '@/conversions/rgbToHsl';
import { hslToRgb } from '@/conversions/hslToRgb';
import { hslaToRgba } from '@/conversions/hslaToRgba';
import { hexToHsl } from '@/conversions/hexToHsl';
import { hexAlphaToHsla } from '@/conversions/hexAlphaToHsla';
import { labToRgb } from '@/conversions/labToRgb';
import { lchToRgb } from '@/conversions/lchToRgb';
import { parseColorToRgba } from '@/parser/parseColorToRgba';
import { rebuildColorFromRgba } from '@/parser/rebuildColorFromRgba';
import { ColorFormat, ColorFormats } from '@/utils/colorFormats';
import {rgbToLab} from "@/conversions/rgbToLab";
import {recomposeColor} from "@/parser/recomposeColor";
import {rgbToLch} from "@/conversions/rgbToLch";

/**
* Finds the triadic colors of a given color.
*
* @param {string} color - The input color string in any supported format (hex, rgb, hsl, etc.).
* @returns {string[]} - The triadic colors in the original format.
* @throws {Error} - Throws an error if the input color format is invalid.
*/
export function triadicColors(color: string): string[] {
const format: ColorFormat = detectColorFormat(color);
const decomposed = decomposeColor(color);

let triadicColors: { r: number; g: number; b: number; a?: number }[] = [];

switch (format) {
case ColorFormats.HSL: {
const { h, s, l } = decomposed;
const h1 = (h + 120) % 360;
const h2 = (h + 240) % 360;
triadicColors = [hslToRgb(h1, s, l, false), hslToRgb(h2, s, l, false)];
break;
}

case ColorFormats.HSLA: {
const { h, s, l, a } = decomposed;
const h1 = (h + 120) % 360;
const h2 = (h + 240) % 360;
triadicColors = [hslaToRgba(h1, s, l, a, false), hslaToRgba(h2, s, l, a, false)];
break;
}

case ColorFormats.RGB: {
const hsl = rgbToHsl(decomposed.r, decomposed.g, decomposed.b, false);
const h1 = (hsl.h + 120) % 360;
const h2 = (hsl.h + 240) % 360;
return [
hslToRgb(h1, hsl.s, hsl.l),
hslToRgb(h2, hsl.s, hsl.l),
];
}

case ColorFormats.RGBA: {
const hsl = rgbToHsl(decomposed.r, decomposed.g, decomposed.b, false);
const h1 = (hsl.h + 120) % 360;
const h2 = (hsl.h + 240) % 360;
triadicColors = [
// @ts-ignore
hslToRgb(h1, hsl.s, hsl.l, false),
// @ts-ignore
hslToRgb(h2, hsl.s, hsl.l, false),
];
triadicColors.forEach((color) => (color.a = decomposed.a));
break;
}
case ColorFormats.HEX: {
const hsl = hexToHsl(color, false);
const h1 = (hsl.h + 120) % 360;
const h2 = (hsl.h + 240) % 360;
triadicColors = [
// @ts-ignore
hslToRgb(h1, hsl.s, hsl.l, false),
// @ts-ignore
hslToRgb(h2, hsl.s, hsl.l, false),
];
break;
}
case ColorFormats.HEX_ALPHA: {
const hsla = hexAlphaToHsla(color, false);
const h1 = (hsla.h + 120) % 360;
const h2 = (hsla.h + 240) % 360;
triadicColors = [
hslaToRgba(h1, hsla.s, hsla.l, hsla.a, false),
hslaToRgba(h2, hsla.s, hsla.l, hsla.a, false),
];
break;
}
case ColorFormats.LAB: {
const { l, a, b } = decomposed;
const triadicLabColors = [
{ l, a: -a, b: -b },
{ l, a: -a, b: -b + 120 },
];
return triadicLabColors.map(lab => recomposeColor(color, lab));
}
case ColorFormats.LCH: {
const { l, c, h } = decomposed;
const triadicLchColors = [
{ l, c, h: (h + 120) % 360 },
{ l, c, h: (h + 240) % 360 },
];
return triadicLchColors.map(lch => recomposeColor(color, lch));
}
case ColorFormats.NAMED: {
const rgba = parseColorToRgba(color);
const hsl = rgbToHsl(rgba.r, rgba.g, rgba.b, false);
const h1 = (hsl.h + 120) % 360;
const h2 = (hsl.h + 240) % 360;
triadicColors = [
hslToRgb(h1, hsl.s, hsl.l, false),
hslToRgb(h2, hsl.s, hsl.l, false),
];
break;
}
default:
throw new Error(`Unsupported color format ${color} for triadic color calculation`);
}

// @ts-ignore
return triadicColors.map((triadicColor) => rebuildColorFromRgba(color, triadicColor));
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export { xyzToRgb } from '@/conversions/xyzToRgb';

// Harmony
export { complementaryColor } from '@/harmony/complementaryColor';
export { triadicColors } from '@/harmony/triadicColors';


// Validations
Expand Down
48 changes: 48 additions & 0 deletions tests/harmony/triadicColors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { triadicColors } from '@/harmony/triadicColors';

describe('triadicColors', () => {
test('triadic colors of rgb(255, 0, 0) are rgb(0, 255, 0) and rgb(0, 0, 255)', () => {
const result = triadicColors('rgb(255, 0, 0)');
expect(result).toEqual(['rgb(0, 255, 0)', 'rgb(0, 0, 255)']);
});

test('triadic colors of rgba(255, 0, 0, 1) are rgba(0, 255, 0, 1) and rgba(0, 0, 255, 1)', () => {
const result = triadicColors('rgba(255, 0, 0, 1)');
expect(result).toEqual(['rgba(0, 255, 0, 1)', 'rgba(0, 0, 255, 1)']);
});

test('triadic colors of hsl(0, 100%, 50%) are hsl(120, 100%, 50%) and hsl(240, 100%, 50%)', () => {
const result = triadicColors('hsl(0, 100%, 50%)');
expect(result).toEqual(['hsl(120, 100%, 50%)', 'hsl(240, 100%, 50%)']);
});

test('triadic colors of hsla(0, 100%, 50%, 1) are hsla(120, 100%, 50%, 1) and hsla(240, 100%, 50%, 1)', () => {
const result = triadicColors('hsla(0, 100%, 50%, 1)');
expect(result).toEqual(['hsla(120, 100%, 50%, 1)', 'hsla(240, 100%, 50%, 1)']);
});

test('triadic colors of #ff0000 are #00ff00 and #0000ff', () => {
const result = triadicColors('#ff0000');
expect(result).toEqual(['#00ff00', '#0000ff']);
});

test('triadic colors of #ff0000ff are #00ff00ff and #0000ffff', () => {
const result = triadicColors('#ff0000ff');
expect(result).toEqual(['#00ff00ff', '#0000ffff']);
});

test('triadic colors of lab(50 40 30) are lab(50 -40 -30) and lab(50 -40 90)', () => {
const result = triadicColors('lab(50 40 30)');
expect(result).toEqual(['lab(50 -40 -30)', 'lab(50 -40 90)']);
});

test('triadic colors of lch(50 30 100) are lch(50 30 -20) and lch(50 30 220)', () => {
const result = triadicColors('lch(50 30 100)');
expect(result).toEqual(['lch(50 30 220)', 'lch(50 30 340)']);
});

test('triadic colors of red (named color) are lime and blue', () => {
const result = triadicColors('red');
expect(result).toEqual(['rgba(0, 255, 0, 1)', 'rgba(0, 0, 255, 1)']);
});
});

0 comments on commit 4369ab6

Please sign in to comment.