diff --git a/package-lock.json b/package-lock.json index 05ee586..31ffd46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "advanced-color-utils", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "advanced-color-utils", - "version": "1.0.2", + "version": "1.0.3", "license": "MIT", "dependencies": { "chroma-js": "^2.4.2", diff --git a/package.json b/package.json index 6daf49f..2594aff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "advanced-color-utils", - "version": "1.0.2", + "version": "1.0.3", "description": "Unleash the full potential of color manipulation with the ColorUtils library! Designed for developers who need precise control over color processing", "main": "dist/ColorUtils.js", "types": "dist/ColorUtils.d.ts", diff --git a/src/ColorUtils.js b/src/ColorUtils.js deleted file mode 100644 index 0dbfbd8..0000000 --- a/src/ColorUtils.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ColorUtils = void 0; -const chroma_js_1 = __importDefault(require("chroma-js")); -const color_diff_1 = require("color-diff"); -class ColorUtils { - /** - * Convert a hex color to an RGB object. - * @param {string} hex - The hex color string (e.g., '#ff0000'). - * @returns {RGBColor} The RGB color object. - */ - static hexToRgb(hex) { - const rgb = (0, chroma_js_1.default)(hex).rgb(); - return { R: rgb[0], G: rgb[1], B: rgb[2] }; - } - /** - * Convert a hex color to a LAB object. - * @param {string} hex - The hex color string (e.g., '#ff0000'). - * @returns {LabColor} The LAB color object. - */ - static hexToLab(hex) { - const [L, a, b] = (0, chroma_js_1.default)(hex).lab(); - return { L, a, b }; - } - /** - * Get a specified number of distinct colors from a list based on a similarity threshold. - * @param {string[]} hexColors - The list of hex color strings. - * @param {number} numColors - The number of distinct colors to retrieve. - * @param {number} threshold - The threshold for color similarity (lower values mean more similar). - * @returns {string[]} The list of distinct hex color strings. - */ - static getDistinctColors(hexColors, numColors, threshold) { - const distinctColors = []; - for (let i = 0; i < hexColors.length && distinctColors.length < numColors; i++) { - const hex = hexColors[i]; - const labColor = this.hexToLab(hex); - const isDistinct = distinctColors.every(distinctHex => { - const distinctLab = this.hexToLab(distinctHex); - return (0, color_diff_1.diff)(labColor, distinctLab) > threshold; - }); - if (isDistinct) { - distinctColors.push(hex); - } - } - return distinctColors; - } -} -exports.ColorUtils = ColorUtils; diff --git a/src/ColorUtils.ts b/src/ColorUtils.ts index ca49071..450b1a4 100644 --- a/src/ColorUtils.ts +++ b/src/ColorUtils.ts @@ -4,6 +4,37 @@ import { LogPerf } from "@color-utils/decorators/LogPerf"; export class ColorUtils { + /** + * Generate a complementary color for a given hex color. + * @param {string} hex - The hex color string (e.g., '#ff0000'). + * @returns {string} The complementary hex color string. + */ + public static getComplementaryColor(hex: string): string { + const complementary = chroma(hex).set('hsl.h', (chroma(hex).get('hsl.h') + 180) % 360); + return complementary.hex(); + } + + /** + * Generate a color palette based on a given hex color and the desired number of colors. + * @param {string} hex - The hex color string (e.g., '#ff0000'). + * @param {number} numColors - The number of colors in the palette. + * @returns {string[]} The array of hex color strings in the palette. + */ + public static generateColorPalette(hex: string, numColors: number): string[] { + return chroma.scale([hex, chroma(hex).set('hsl.h', (chroma(hex).get('hsl.h') + 180) % 360)]) + .mode('lab') + .colors(numColors); + } + + /** + * Convert an RGB color to a hex color string. + * @param {RGBColor} rgb - The RGB color object. + * @returns {string} The hex color string. + */ + public static rgbToHex(rgb: RGBColor): string { + return chroma(rgb.R, rgb.G, rgb.B).hex(); + } + /** * Convert a hex color to an RGB object. * @param {string} hex - The hex color string (e.g., "#ff0000"). @@ -14,6 +45,15 @@ export class ColorUtils { return { R: rgb[0], G: rgb[1], B: rgb[2] }; } + /** + * Convert a LAB color to a hex color string. + * @param {LabColor} lab - The LAB color object. + * @returns {string} The hex color string. + */ + public static labToHex(lab: LabColor): string { + return chroma.lab(lab.L, lab.a, lab.b).hex(); + } + /** * Convert a hex color to a LAB object. * @param {string} hex - The hex color string (e.g., "#ff0000"). @@ -25,12 +65,12 @@ export class ColorUtils { } /** - * Get a specified number of distinct colors from a list based on a similarity threshold. - * @param {string[]} hexColors - The list of hex color strings. - * @param {number} numColors - The number of distinct colors to retrieve. - * @param {number} threshold - The threshold for color similarity (lower values mean more similar). - * @returns {string[]} The list of distinct hex color strings. - */ + * Get a specified number of distinct colors from a list based on a similarity threshold. + * @param {string[]} hexColors - The list of hex color strings. + * @param {number} numColors - The number of distinct colors to retrieve. + * @param {number} threshold - The threshold for color similarity (lower values mean more similar). + * @returns {string[]} The list of distinct hex color strings. + */ @LogPerf("info") public static getDistinctColors(hexColors: string[], numColors: number, threshold: number): string[] { if (numColors <= 0) return []; @@ -38,18 +78,15 @@ export class ColorUtils { const distinctColors: string[] = []; const labColors = hexColors.map(hex => this.hexToLab(hex)); - outerLoop: for (let i = 0; i < labColors.length; i++) { + for (let i = 0; i < labColors.length && distinctColors.length < numColors; i++) { const labColor = labColors[i]; - for (let j = 0; j < distinctColors.length; j++) { - const distinctLab = this.hexToLab(distinctColors[j]); - if (diff(labColor, distinctLab) <= threshold) { - continue outerLoop; - } - } - if (distinctColors.length < numColors) { + const isDistinct = distinctColors.every(distinctHex => { + const distinctLab = this.hexToLab(distinctHex); + return diff(labColor, distinctLab) > threshold; + }); + + if (isDistinct) { distinctColors.push(hexColors[i]); - } else { - break; } } diff --git a/src/__tests__/ColorUtils.test.ts b/src/__tests__/ColorUtils.test.ts index c6ff3a7..67a6be5 100644 --- a/src/__tests__/ColorUtils.test.ts +++ b/src/__tests__/ColorUtils.test.ts @@ -1,5 +1,5 @@ -import { ColorUtils } from "@color-utils/ColorUtils"; -import { generateRandomColor } from "@color-utils/helper/generateColor"; +import { ColorUtils } from "../ColorUtils"; +import { generateRandomColor } from "../helper/generateColor"; describe("getDistinctColors",()=>{ it("should return the correct number of distinct colors", () => { const colors = ["#ff0000", "#ff1100", "#00ff00", "#0000ff", "#00ffff", "#ff00ff", "#ffff00", "#000000", "#ffffff"]; @@ -18,6 +18,22 @@ describe("getDistinctColors",()=>{ const filteredColors = ColorUtils.getDistinctColors(colors, numberOfColors, threshold); expect(filteredColors).toEqual(expect.arrayContaining(["#ff0000", "#00ff00"])); }); + it("should return empty array when empty colors are given", () => { + const colors:Array = []; + const numberOfColors = 2; + const threshold = 2; + + const filteredColors = ColorUtils.getDistinctColors(colors, numberOfColors, threshold); + expect(filteredColors).toStrictEqual([]); + }); + it("should return empty array numberOfColors is 0", () => { + const colors:Array = ["#ff0000", "#ff0001", "#ff0002", "#00ff00", "#0000ff"]; + const numberOfColors = 0; + const threshold = 2; + + const filteredColors = ColorUtils.getDistinctColors(colors, numberOfColors, threshold); + expect(filteredColors).toStrictEqual([]); + }); }); describe('ColorUtils Performance', () => { @@ -37,4 +53,69 @@ describe('ColorUtils Performance', () => { expect(distinctColors).toBeInstanceOf(Array); expect(distinctColors.length).toBe(numColorsToRetrieve); }); +}); + +describe('ColorUtils Features', () => { + it('should generate a complementary color', () => { + const complementaryColor = ColorUtils.getComplementaryColor('#ff0000'); + expect(complementaryColor).toBe('#00ffff'); + }); + + it('should generate a color palette', () => { + const palette = ColorUtils.generateColorPalette('#ff0000', 5); + expect(palette.length).toBe(5); + palette.forEach(color => expect(color).toMatch(/^#[0-9a-fA-F]{6}$/)); + }); + + it('should convert RGB to Hex', () => { + const rgb = { R: 255, G: 0, B: 0 }; + const hex = ColorUtils.rgbToHex(rgb); + expect(hex).toBe('#ff0000'); + }); + + it('should convert LAB to Hex', () => { + const lab = { L: 53.23288, a: 80.10933, b: 67.22006 }; + const hex = ColorUtils.labToHex(lab); + expect(hex).toBe('#ff0000'); + }); + + it('should get distinct colors', () => { + const colors = ['#ff0000', '#00ff00', '#0000ff', '#ff00ff']; + const numColorsToRetrieve = 2; + const threshold = 20; + const distinctColors = ColorUtils.getDistinctColors(colors, numColorsToRetrieve, threshold); + expect(distinctColors.length).toBe(2); + distinctColors.forEach(color => expect(color).toMatch(/^#[0-9a-fA-F]{6}$/)); + }); + + // Unit tests for hexToRgb + it('should convert hex to RGB correctly', () => { + const hex = '#ff0000'; + const rgb = ColorUtils.hexToRgb(hex); + expect(rgb).toEqual({ R: 255, G: 0, B: 0 }); + }); + + it('should handle shorthand hex codes', () => { + const hex = '#0f0'; + const rgb = ColorUtils.hexToRgb(hex); + expect(rgb).toEqual({ R: 0, G: 255, B: 0 }); + }); + + it('should handle case-insensitive hex codes', () => { + const hex = '#00FF00'; + const rgb = ColorUtils.hexToRgb(hex); + expect(rgb).toEqual({ R: 0, G: 255, B: 0 }); + }); + + it('should convert hex to RGB correctly for black', () => { + const hex = '#000000'; + const rgb = ColorUtils.hexToRgb(hex); + expect(rgb).toEqual({ R: 0, G: 0, B: 0 }); + }); + + it('should convert hex to RGB correctly for white', () => { + const hex = '#ffffff'; + const rgb = ColorUtils.hexToRgb(hex); + expect(rgb).toEqual({ R: 255, G: 255, B: 255 }); + }); }); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 7e2483b..6737c8d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,5 @@ } }, "include": ["src/**/*"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "src/**/__tests__/*"] }