From 689726f26f9219212307679ef474bc5b355a6840 Mon Sep 17 00:00:00 2001 From: Arvind Date: Sun, 19 Jul 2020 10:00:17 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Implemented=20Complementary=20palet?= =?UTF-8?q?te=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ Implemented Complementary palette generation --- README.md | 23 ++++++++++++--- examples/index.js | 22 ++++++++++++++ package.json | 2 +- src/color.utils.js | 73 ++++++++++++++++++++++++++++++++++++++++++++-- src/index.js | 44 ++++++++++++++++++++++++---- 5 files changed, 150 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8bd6337..a0e34d4 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,8 @@ The following methods are available as of version **1.0.0**. | Method | Type | What it returns | |-|-|-| | `.palette()` | `Getter` | A Material Palette Object with Text Contrast Values for the given color | -| `.shades()` | `Getter` | Light, Main and Dark shades for the given color | -| `.accents()` | `Getter` | Accent Color Palette for the given color | +| `.shades(paletteType)` | `Getter` | Light, Main and Dark shades for a selected palette type | +| `.accents(paletteType)` | `Getter` | Accent Color Palette for a selected palette type | #### 🏗️ Palette Object Structure Logging the palette output for the `PurplePalette` color by @@ -100,7 +100,20 @@ we get an output that follows the following structure. A200 : [Object], A400 : [Object], A700 : [Object], - } // ...More coming soon + } + secondary : { // type of palette + 50 : [Object] + 100 : [Object], + 200 : [Object], + ... + 900 : [Object], // darkest color in palette + // Accents + A100 : [Object], + A200 : [Object], + A400 : [Object], + A700 : [Object], + } + // ...More coming soon } ``` > All the other functions are simply helpers to access specific parts of the complete palette. @@ -287,6 +300,8 @@ which, for the given color, generates the output: } } ``` +Similar outputs are generated for the **secondary** palette type. + These outputs can also be used in conjunction with [Material UI's](https://material-ui.com/) [**createMuiTheme**](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme) to configure custom palettes. For a better understanding on how to use, you can checkout the demo. @@ -300,7 +315,7 @@ These outputs can also be used in conjunction with [Material UI's](https://mater #### Next Possible Stable Release - - [ ] Generate Complementary Palette + - [x] Generate Complementary Palette - [ ] Generate Analogous Palette - [ ] Generate Triadic Palette diff --git a/examples/index.js b/examples/index.js index f2628b9..086bf6b 100644 --- a/examples/index.js +++ b/examples/index.js @@ -23,6 +23,21 @@ console.log (JSON.stringify (PurplePalette.palette (), null, 2)); A200: Object A400: Object A700: Object + secondary : Object + 50: Object + 100: Object + 200: Object + 300: Object + 400: Object + 500: Object + 600: Object + 700: Object + 800: Object + 900: Object + A100: Object + A200: Object + A400: Object + A700: Object */ console.log ('\n\nPRIMARY PALETTE SHADES\n\n'); @@ -31,3 +46,10 @@ console.log (JSON.stringify (PurplePalette.shades ('primary'), null, 2)); console.log ('\n\nPRIMARY PALETTE ACCENTS\n\n'); console.log (JSON.stringify (PurplePalette.accents ('primary'), null, 2)); // Object {A100: Object, A200: Object, A400: Object, A700: Object} + +console.log ('\n\nSECONDARY PALETTE SHADES\n\n'); +console.log (JSON.stringify (PurplePalette.shades ('secondary'), null, 2)); +// Object {light: Object, main: Object, dark: Object} +console.log ('\n\nSECONDARY PALETTE ACCENTS\n\n'); +console.log (JSON.stringify (PurplePalette.accents ('secondary'), null, 2)); +// Object {A100: Object, A200: Object, A400: Object, A700: Object} diff --git a/package.json b/package.json index 568d2a9..3b58988 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matercolors" , - "version": "1.0.4", + "version": "1.1.0", "description": "A tiny, zero-dependency libary for building harmonious material palettes for any color.", "main": "./src/index.js", "type": "module", diff --git a/src/color.utils.js b/src/color.utils.js index ca583fc..e549ffb 100644 --- a/src/color.utils.js +++ b/src/color.utils.js @@ -1,3 +1,4 @@ +/* eslint-disable no-bitwise */ export function RGBToHSL(rgb) { const r = rgb.r / 255; const g = rgb.g / 255; @@ -15,11 +16,14 @@ export function RGBToHSL(rgb) { h = Math.round(h * 60); if (h < 0) h += 360; l = (cmax + cmin) / 2; - s = (delta === 0) ? 0 : (delta / (1 - Math.abs(2 * l - 1))); + s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); s = +(s * 100).toFixed(1); l = +(l * 100).toFixed(1); return { - h, s, l, string: `hsl(${h},${s}%,${l}%)` + h, + s, + l, + string: `hsl(${h},${s}%,${l}%)`, }; } export function RGBToCYMK(rgb) { @@ -33,7 +37,11 @@ export function RGBToCYMK(rgb) { k *= 100; k = parseFloat(k.toFixed(4)); return { - c, y, m, k, string: `cymk(${c}%,${y}%,${m}%,${k}%)` + c, + y, + m, + k, + string: `cymk(${c}%,${y}%,${m}%,${k}%)`, }; } export function hexToRgba(hex) { @@ -65,3 +73,62 @@ export function getContrastText({ r, g, b }, threshold) { const contrast = (Math.round(r * 299) + Math.round(g * 587) + Math.round(b * 114)) / 1000; return contrast >= threshold ? 'black' : 'white'; } + +export function getComplementary(hex) { + const rgb = hexToRgba(hex); + let r = rgb.r / 255.0; + let g = rgb.g / 255.0; + let b = rgb.b / 255.0; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + let h; + let s; + const l = (max + min) / 2.0; + if (max === min) { + h = 0; s = 0; // achromatic + } else { + const d = max - min; + s = l > 0.5 ? d / (2.0 - max - min) : d / (max + min); + if (max === r && g >= b) { + h = 1.0472 * (g - b) / d; + } else if (max === r && g < b) { + h = 1.0472 * (g - b) / d + 6.2832; + } else if (max === g) { + h = 1.0472 * (b - r) / d + 2.0944; + } else if (max === b) { + h = 1.0472 * (r - g) / d + 4.1888; + } + } + h = h / 6.2832 * 360.0 + 0; + // Shift hue to opposite side of wheel and convert to [0-1] value + h += 180; + if (h > 360) { + h -= 360; + } + h /= 360; + if (s === 0) { + r = l; g = l; b = l; // achromatic + } else { + const hue2rgb = function hue2rgb(p, q, T) { + let t = T; + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + r = Math.round(r * 255); + g = Math.round(g * 255); + b = Math.round(b * 255); + // Convert r b and g values to hex + const final = b | (g << 8) | (r << 16); + return `#${(0x1000000 | final).toString(16).substring(1)}`; +} diff --git a/src/index.js b/src/index.js index 347c8be..1f077c4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,14 @@ import { keys, defaultOptions } from './global.constants.js'; import { - normalizeRGB, rgbToHex, hexToRgba, RGBToCYMK, RGBToHSL, getContrastText + normalizeRGB, rgbToHex, hexToRgba, RGBToCYMK, RGBToHSL, getContrastText, getComplementary } from './color.utils.js'; import { createPallete } from './goldenPalettes.js'; export default class Matercolor { constructor(color, options) { this.color = color; + const complementary = getComplementary(color); + this.complementary = () => complementary; this.options = options ? Object.assign(defaultOptions, options) : defaultOptions; @@ -34,14 +36,15 @@ export default class Matercolor { getPalette() { this.palette().primary = {}; - const palette = createPallete( + this.palette().secondary = {}; + const primaryPalette = createPallete( normalizeRGB(hexToRgba(this.color)) ).map(u => rgbToHex( Math.round(u.red * 255), Math.round(u.green * 255), Math.round(u.blue * 255) )); - const accents = createPallete( + const primaryAccents = createPallete( normalizeRGB(hexToRgba(this.color)), true ).map(u => rgbToHex( @@ -49,11 +52,26 @@ export default class Matercolor { Math.round(u.green * 255), Math.round(u.blue * 255) )); - palette.push(...accents); + const secondaryPalette = createPallete( + normalizeRGB(hexToRgba(this.complementary())) + ).map(u => rgbToHex( + Math.round(u.red * 255), + Math.round(u.green * 255), + Math.round(u.blue * 255) + )); + const secondaryAccents = createPallete( + normalizeRGB(hexToRgba(this.complementary())), + true + ).map(u => rgbToHex( + Math.round(u.red * 255), + Math.round(u.green * 255), + Math.round(u.blue * 255) + )); + primaryPalette.push(...primaryAccents); for (let i = 0; i < keys.length; i += 1) { const colorObject = {}; - colorObject.hex = palette[i]; - colorObject.rgb = hexToRgba(palette[i]); + colorObject.hex = primaryPalette[i]; + colorObject.rgb = hexToRgba(primaryPalette[i]); colorObject.rgb.string = `rgb(${colorObject.rgb.r},${colorObject.rgb.g},${colorObject.rgb.b})`; colorObject.hsl = RGBToHSL(colorObject.rgb); colorObject.cymk = RGBToCYMK(colorObject.rgb); @@ -63,5 +81,19 @@ export default class Matercolor { ); this.palette().primary[keys[i]] = colorObject; } + secondaryPalette.push(...secondaryAccents); + for (let i = 0; i < keys.length; i += 1) { + const colorObject = {}; + colorObject.hex = secondaryPalette[i]; + colorObject.rgb = hexToRgba(secondaryPalette[i]); + colorObject.rgb.string = `rgb(${colorObject.rgb.r},${colorObject.rgb.g},${colorObject.rgb.b})`; + colorObject.hsl = RGBToHSL(colorObject.rgb); + colorObject.cymk = RGBToCYMK(colorObject.rgb); + colorObject.contrastText = getContrastText( + colorObject.rgb, + this.options.threshold + ); + this.palette().secondary[keys[i]] = colorObject; + } } }