From 689726f26f9219212307679ef474bc5b355a6840 Mon Sep 17 00:00:00 2001
From: Arvind <arvind@cheenu.net>
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;
+    }
   }
 }