├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docs └── CN.md ├── package-lock.json ├── package.json ├── public └── index.html └── src ├── App.css ├── App.js ├── App.test.js ├── components └── input.js ├── index.css ├── index.js └── utils ├── antd-color-palette.js ├── avg-color.js ├── brightness.js ├── gradientor.js ├── hex-to-rgb.js ├── hsv-to-rgb.js ├── index.js ├── rgb-to-hex.js ├── rgb-to-hsv.js └── tint-shade.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 renjie1996 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help 2 | default: help 3 | 4 | .PHONY: install 5 | install: 6 | @npm install 7 | 8 | .PHONY: dev 9 | dev: 10 | @npm run start 11 | 12 | .PHONY: test 13 | test: 14 | @npm run test 15 | 16 | help: 17 | @echo " \033[35mmake\033[0m \033[1m命令使用说明\033[0m" 18 | @echo " \033[35mmake install\033[0m\t\033[0m\t--- 安装依赖" 19 | @echo " \033[35mmake dev\033[0m\t\033[0m\t--- 开发模式" 20 | @echo " \033[35mmake test\033[0m\t\033[0m\t--- 运行测试" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

color-design-helper

2 | 3 |

4 |   5 |

6 | 7 | 8 | **Just a toy, which can help color designers to generate color plates like ant-design.** 9 | 10 | Achieved `average color`、`tint-shade color`、`@antd-v3 color` three models 11 | 12 | [Online address](http://zerolty.com/color-design-helper/) 13 | 14 | [中文文档](https://github.com/renjie1996/color-design-helper/blob/master/docs/CN.md) 15 | 16 | [Article](https://github.com/renjie1996/Maple-FrontEnd-Blog/issues/17) 17 | 18 | ### average color 19 | > only average cut two colors 20 | 21 | ![](https://user-images.githubusercontent.com/25033420/50901149-4b04f280-1452-11e9-9a37-9d2d59817302.png) 22 | 23 | ### tint-shade color 24 | > like antd v1.0. The main color is mixed with pure white (#fff), and the main color and the pure white are divided into 100 parts, and the positions of 20/40/60/80 are respectively divided to obtain the color of 4/3/2/1; 25 | > The main color is mixed with pure black (#000), and the main color and the pure black are divided into 100 parts, and the positions of 20/40/60/80 are respectively divided to obtain the color of 6/7/8/9; 26 | 27 | ![](https://user-images.githubusercontent.com/25033420/50901164-4e987980-1452-11e9-9f04-2751dbe805d7.png) 28 | 29 | ### @antd-v3 color 30 | > Like the current antd, Decrement/increment with the value of the HSV model to get a complete gradient swatch 31 | 32 | ![](https://user-images.githubusercontent.com/25033420/50903156-e5b40000-1457-11e9-8b5a-d3781a4f89ac.png) 33 | 34 | # Usage 35 | `Just a static resource running locally` 36 | 37 | > [antd-refs: https://ant.design/docs/spec/colors-cn](https://ant.design/docs/spec/colors-cn) 38 | 39 | ![](https://user-images.githubusercontent.com/25033420/50901576-a4215600-1453-11e9-9210-e7324273e386.png) 40 | 41 | 42 | > run by MakeFile 43 | 44 | - `make install` 45 | - `make dev` 46 | - `make test` 47 | > you also can run by these ways 48 | 49 | - `npm run start` 50 | - `npm run build` 51 | - `npm run test` 52 | 53 | # License 54 | 55 | MIT 56 | 57 | Copyright (c) 2019 @renjie1996 @Shadowless @无影er 58 | -------------------------------------------------------------------------------- /docs/CN.md: -------------------------------------------------------------------------------- 1 | # color-design-helper 2 | 3 | **一个帮助设计师的调色板的小玩具** 4 | 5 | [线上地址](https://renjie1996.github.io) 6 | 7 | 完成了 `average color`、`tint-shade color`、`@antd-v3 color` 三种模式 8 | 9 | ### average color 10 | > 仅仅平均切割两种颜色 11 | 12 | ![](https://user-images.githubusercontent.com/25033420/50901149-4b04f280-1452-11e9-9a37-9d2d59817302.png) 13 | 14 | ### tint-shade color 15 | > andv第一版. 将主色与纯白色(#fff)混合,主色与纯白色之间分成 100 份, 20/40/60/80 的位置分别分割,得到 4/3/2/1 号色; 16 | > 将主色与纯黑色(#000)混合,主色与纯黑色之间分成 100 份, 20/40/60/80 的位置分别分割,得到 6/7/8/9 号色。 17 | 18 | ![](https://user-images.githubusercontent.com/25033420/50901164-4e987980-1452-11e9-9f04-2751dbe805d7.png) 19 | 20 | ### @antd-v3 color 21 | > 目前antd的色彩生成版本,用 HSV 模型的值进行递减/递增得到完整渐变色板 22 | 23 | ![](https://user-images.githubusercontent.com/25033420/50901171-522c0080-1452-11e9-99ee-79ed1676a1cc.png) 24 | 25 | # Usage 26 | `仅仅是运行在本地的静态资源` 27 | 28 | > 使用MakeFile运行 29 | 30 | - `make install` 31 | - `make dev` 32 | - `make test` 33 | 34 | > 使用npm运行 35 | 36 | - `npm run start` 37 | - `npm run build` 38 | - `npm run test` 39 | 40 | 41 | > [参考antd: https://ant.design/docs/spec/colors-cn](https://ant.design/docs/spec/colors-cn) 42 | 43 | ![](https://user-images.githubusercontent.com/25033420/50901576-a4215600-1453-11e9-9210-e7324273e386.png) 44 | 45 | # License 46 | 47 | MIT 48 | 49 | Copyright (c) 2019 @renjie1996 @Shadowless @无影er 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color-design-helper", 3 | "version": "1.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@ctrl/tinycolor": "^2.2.1", 7 | "react": "16.7.0-alpha.2", 8 | "react-dom": "16.7.0-alpha.2", 9 | "react-scripts": "2.1.3" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "homepage": "./", 18 | "eslintConfig": { 19 | "extends": "react-app" 20 | }, 21 | "browserslist": [ 22 | ">0.2%", 23 | "not dead", 24 | "not ie <= 11", 25 | "not op_mini all" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | Color-Design-Helper 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | justify-content: space-around; 4 | align-items: center; 5 | } 6 | 7 | .controller { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | min-height: 800px; 12 | } 13 | 14 | 15 | .color-picker { 16 | width: 300px; 17 | display: flex; 18 | background-color: var(--fg); 19 | padding: 6px; 20 | border-radius: 30px; 21 | font-weight: 300; 22 | } 23 | 24 | .range { 25 | height: 380px; 26 | position: relative; 27 | } 28 | 29 | .range p { 30 | font-size: 42px; 31 | color: var(--fg); 32 | position: absolute; 33 | right: -80px; 34 | top: 100px; 35 | } 36 | 37 | .color-picker input { 38 | display: inline-block; 39 | outline: none; 40 | border: none; 41 | box-sizing: border-box; 42 | height: 52px; 43 | width: 52px; 44 | background-color: var(--fg); 45 | border-radius: 30px; 46 | color: var(--fc); 47 | } 48 | 49 | .color-picker input[type="color"]::-webkit-color-swatch { 50 | border: none; 51 | border-radius: 50%; 52 | } 53 | 54 | .color-picker input[type="color"][disabled] { 55 | opacity: .2; 56 | cursor: not-allowed; 57 | } 58 | .color-picker input[type="text"][disabled] { 59 | opacity: .2; 60 | cursor: not-allowed; 61 | } 62 | 63 | .color-picker input[type="text"] { 64 | width: 200px; 65 | padding: 6px 16px; 66 | font-size: 32px; 67 | } 68 | 69 | .controller input[type="range"] { 70 | margin-top: 30px; 71 | width: 320px; 72 | height: 320px; 73 | transform: rotate(-90deg); 74 | } 75 | 76 | .controller input[type="range"]::-webkit-slider-thumb { 77 | appearance: none; 78 | border: none; 79 | border-radius: 30px; 80 | box-shadow: 0 0 0 .3em var(--bg); 81 | background: var(--fg); 82 | transform: scale(1.5); 83 | transition: transform .3s ease-out; 84 | } 85 | 86 | .controller input[type="range"]::-webkit-slider-thumb:focus, 87 | .controller input[type="range"]::-webkit-slider-thumb:active { 88 | appearance: none; 89 | transform: scale(.8); 90 | } 91 | 92 | .color-list { 93 | height: 800px; 94 | } 95 | 96 | .color-list .color-item{ 97 | width: 200px; 98 | text-align: center; 99 | list-style: none; 100 | } 101 | 102 | .radios { 103 | display: block; 104 | position: relative; 105 | margin: 40px auto; 106 | height: auto; 107 | padding: 20px; 108 | } 109 | 110 | .radios ul{ 111 | margin: 0; 112 | padding: 0; 113 | overflow: auto; 114 | display: flex; 115 | } 116 | 117 | .radios ul li{ 118 | color: #AAAAAA; 119 | display: block; 120 | position: relative; 121 | float: left; 122 | width: 100%; 123 | height: 100px; 124 | border-bottom: 1px solid #333; 125 | } 126 | 127 | .radios ul li input[type=radio]{ 128 | position: absolute; 129 | visibility: hidden; 130 | } 131 | 132 | .radios ul li label{ 133 | display: block; 134 | position: relative; 135 | font-weight: 300; 136 | font-size: 1.35em; 137 | padding: 25px 25px 25px 80px; 138 | margin: 10px auto; 139 | height: 30px; 140 | z-index: 9; 141 | cursor: pointer; 142 | -webkit-transition: all 0.25s linear; 143 | } 144 | 145 | .radios ul li:hover label{ 146 | color: var(--fg); 147 | } 148 | 149 | .radios .check{ 150 | display: block; 151 | position: absolute; 152 | border: 5px solid #AAAAAA; 153 | border-radius: 100%; 154 | height: 25px; 155 | width: 25px; 156 | top: 30px; 157 | left: 20px; 158 | z-index: 5; 159 | transition: border .25s linear; 160 | -webkit-transition: border .25s linear; 161 | } 162 | 163 | .radios ul li:hover .check { 164 | border: 5px solid var(--fg); 165 | } 166 | 167 | ul li .check::before { 168 | display: block; 169 | position: absolute; 170 | content: ''; 171 | border-radius: 100%; 172 | height: 15px; 173 | width: 15px; 174 | top: 5px; 175 | left: 5px; 176 | margin: auto; 177 | transition: background 0.25s linear; 178 | -webkit-transition: background 0.25s linear; 179 | } 180 | 181 | input[type=radio]:checked ~ .check { 182 | border: 5px solid var(--radc); 183 | } 184 | 185 | input[type=radio]:checked ~ .check::before{ 186 | background: var(--radc); 187 | } 188 | 189 | input[type=radio]:checked ~ label{ 190 | color: var(--radc); 191 | } 192 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import './App.css'; 3 | import useInputValue from './components/input'; 4 | import ColorHelper from './utils/index'; 5 | 6 | const ViEWHEIGHT = 800; 7 | const MAX = 25; 8 | const MIN = 1; 9 | 10 | function App() { 11 | const startInputHook = useInputValue('#ffffff', true); 12 | const endInputHook = useInputValue('#1890ff'); 13 | const stepInputHook = useInputValue(1); 14 | const radiosHook = useInputValue('avg'); 15 | const [colors, setColors] = useState([]); 16 | 17 | const {value, onChange, disabled} = startInputHook; 18 | 19 | const filterStartInputHook = { 20 | // 过滤掉非原生的props 21 | value, 22 | onChange, 23 | disabled, 24 | }; 25 | 26 | const isColor = color => { 27 | return color !== '000000' 28 | && color !== '#000000' 29 | && /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/.test(color); 30 | } 31 | 32 | useEffect(() => { 33 | if(isColor(startInputHook.value) && isColor(endInputHook.value)) { 34 | setColors(ColorHelper.gradientor(startInputHook.value, endInputHook.value, stepInputHook.value, radiosHook.value)); 35 | } 36 | }, [startInputHook.value, endInputHook.value, stepInputHook.value, radiosHook.value]); 37 | 38 | useEffect(() => { 39 | if(radiosHook.value !== 'avg') { 40 | startInputHook.setValue('#ffffff'); 41 | startInputHook.setDisabled(true); 42 | } else { 43 | startInputHook.setDisabled(false); 44 | } 45 | }, [radiosHook.value]) 46 | 47 | 48 | const styles = c => ({ 49 | backgroundColor: `rgb(${c})`, 50 | color: ColorHelper.brightness(c), 51 | height: ViEWHEIGHT/colors.length, 52 | lineHeight: `${ViEWHEIGHT/colors.length}px` 53 | }) 54 | 55 | const radios = ['avg', 'tintshade', 'antd']; 56 | 57 | 58 | return ( 59 |
60 |
61 |
62 |
    63 | { 64 | radios.map(r => ( 65 |
  • 66 | 67 | 68 |
    69 |
  • 70 | )) 71 | } 72 |
73 |
74 | 75 | 76 |
77 | 78 | 79 |
80 |
81 | 82 |

Step:{stepInputHook.value}

83 |
84 | 85 | 86 |
87 | 88 | 89 |
90 |
91 | 100 |
101 | ); 102 | 103 | } 104 | 105 | export default App; 106 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/input.js: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react"; 2 | 3 | export default function useInputValue(initiateValue, setOr) { 4 | const [value, setValue] = useState(initiateValue); 5 | const [disabled, setDisabled] = useState(false); 6 | let onChange = useCallback(e => setValue(e.target.value), []); 7 | if(setOr) return { value, onChange, disabled, setDisabled, setValue }; 8 | return { value, onChange, disabled }; 9 | 10 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | background-color: #000000; 5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 6 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | code { 13 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 14 | monospace; 15 | } 16 | 17 | :root { 18 | --fg: #fff; 19 | --bg: #000; 20 | --fc: #0b0e0f; 21 | --radc: #0DFF92; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | -------------------------------------------------------------------------------- /src/utils/antd-color-palette.js: -------------------------------------------------------------------------------- 1 | import { TinyColor } from '@ctrl/tinycolor'; 2 | 3 | const hueStep = 2; 4 | const saturationStep = 16; 5 | const saturationStep2 = 5; 6 | const brightnessStep1 = 5; 7 | const brightnessStep2 = 15; 8 | const lightColorCount = 5; 9 | const darkColorCount = 4; 10 | 11 | const getHue = function (hsv, i, isLight) { 12 | let hue; 13 | if (hsv.h >= 60 && hsv.h <= 240) { 14 | hue = isLight ? hsv.h - hueStep * i : hsv.h + hueStep * i; 15 | } else { 16 | hue = isLight ? hsv.h + hueStep * i : hsv.h - hueStep * i; 17 | } 18 | if (hue < 0) { 19 | hue += 360; 20 | } else if (hue >= 360) { 21 | hue -= 360; 22 | } 23 | return Math.round(hue); 24 | }; 25 | const getSaturation = function (hsv, i, isLight) { 26 | let saturation; 27 | if (isLight) { 28 | saturation = Math.round(hsv.s * 100) - saturationStep * i; 29 | } else if (i === darkColorCount) { 30 | saturation = Math.round(hsv.s * 100) + saturationStep; 31 | } else { 32 | saturation = Math.round(hsv.s * 100) + saturationStep2 * i; 33 | } 34 | if (saturation > 100) { 35 | saturation = 100; 36 | } 37 | if (isLight && i === lightColorCount && saturation > 10) { 38 | saturation = 10; 39 | } 40 | if (saturation < 6) { 41 | saturation = 6; 42 | } 43 | return Math.round(saturation); 44 | }; 45 | const getValue = function (hsv, i, isLight) { 46 | if (isLight) { 47 | return Math.round(hsv.v * 100) + brightnessStep1 * i; 48 | } 49 | return Math.round(hsv.v * 100) - brightnessStep2 * i; 50 | }; 51 | 52 | function colorPalette(color, index, num) { 53 | const isLight = index <= num; 54 | const hsv = new TinyColor({ r: color[0], g: color[1], b: color[2] }).toHsv(); 55 | const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1; 56 | const result = { 57 | h: getHue(hsv, i, isLight), 58 | s: getSaturation(hsv, i, isLight), 59 | v: getValue(hsv, i, isLight) 60 | }; 61 | const rgb = new TinyColor(result).toRgb(); 62 | return [rgb.r, rgb.g, rgb.b]; 63 | 64 | }; 65 | 66 | function multiDimensionalUnique(arr) { 67 | const uniques = []; 68 | const itemsFound = {}; 69 | for(let i = 0, l = arr.length; i < l; i++) { 70 | const stringified = JSON.stringify(arr[i]); 71 | if(itemsFound[stringified]) { continue; } 72 | uniques.push(arr[i]); 73 | itemsFound[stringified] = true; 74 | } 75 | return uniques; 76 | } 77 | 78 | function antdColorPatette(color, granularity) { 79 | let colors = []; 80 | for(let i = 1, len = granularity; i <= len; i++) { 81 | colors.push( 82 | colorPalette(color, i, Math.round((granularity)*0.6)) 83 | ); 84 | } 85 | return multiDimensionalUnique(colors); 86 | } 87 | 88 | export default antdColorPatette; -------------------------------------------------------------------------------- /src/utils/avg-color.js: -------------------------------------------------------------------------------- 1 | function hexFromRgbArray(startArr, endArr, factor) { 2 | return [ 3 | Math.round((startArr[0] + ((endArr[0] - startArr[0]) * factor))), 4 | Math.round((startArr[1] + ((endArr[1] - startArr[1]) * factor))), 5 | Math.round((startArr[2] + ((endArr[2] - startArr[2]) * factor))) 6 | ] 7 | } 8 | function avgMix (startRgbArr, endRgbArr, granularity) { 9 | const colors = []; 10 | const factor = 1 / (granularity + 1); 11 | for(let i = 0, len = granularity + 2;i < len; i++) { 12 | colors.push( 13 | hexFromRgbArray(startRgbArr, endRgbArr, factor * i) 14 | ); 15 | } 16 | return colors; 17 | } 18 | export default avgMix; 19 | 20 | -------------------------------------------------------------------------------- /src/utils/brightness.js: -------------------------------------------------------------------------------- 1 | export default function brightness(c) { 2 | const color = ((c[0] * 299 + c[1] * 587 + c[2] * 114) / 1000) < 154 ? '#ffffff' : '#000000'; 3 | return color; 4 | } -------------------------------------------------------------------------------- /src/utils/gradientor.js: -------------------------------------------------------------------------------- 1 | import rgbArrayFromHex from './hex-to-rgb'; 2 | import avgMix from './avg-color'; 3 | import tintShade from './tint-shade'; 4 | import antdColorPatette from './antd-color-palette'; 5 | 6 | 7 | export default function gradientor(start_color, end_color, granularity, type='avg') { 8 | const startRgbArr = rgbArrayFromHex(start_color).map(Number); 9 | const endRgbArr = rgbArrayFromHex(end_color).map(Number); 10 | granularity = Number(granularity); 11 | let res; 12 | switch(type) { 13 | case'avg': 14 | res = avgMix(startRgbArr, endRgbArr, granularity); 15 | break; 16 | case 'antd': 17 | res = antdColorPatette(endRgbArr, granularity); 18 | break; 19 | case 'tintshade': 20 | res = tintShade(endRgbArr, granularity); 21 | break; 22 | default: break; 23 | } 24 | return res; 25 | } -------------------------------------------------------------------------------- /src/utils/hex-to-rgb.js: -------------------------------------------------------------------------------- 1 | const hexArray = hex => /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 2 | const rgbObjectFromHex = hex => { 3 | var result = hexArray(hex) 4 | return result ? { 5 | r: parseInt(result[1], 16), 6 | g: parseInt(result[2], 16), 7 | b: parseInt(result[3], 16), 8 | } : null; 9 | } 10 | const rgbArrayFromHex = hex => { 11 | const rgb = rgbObjectFromHex(hex); 12 | return [rgb.r, rgb.g, rgb.b]; 13 | } 14 | 15 | export default rgbArrayFromHex; -------------------------------------------------------------------------------- /src/utils/hsv-to-rgb.js: -------------------------------------------------------------------------------- 1 | function HSVtoRGB({h, s, v}) { 2 | let r, g, b; 3 | let i; 4 | let f, p, q, t; 5 | h = Math.max(0, Math.min(360, h)); 6 | s = Math.max(0, Math.min(100, s)); 7 | v = Math.max(0, Math.min(100, v)); 8 | 9 | s /= 100; 10 | v /= 100; 11 | 12 | if (s === 0) { 13 | r = g = b = v; 14 | return [ 15 | Math.round(r * 255), 16 | Math.round(g * 255), 17 | Math.round(b * 255) 18 | ]; 19 | } 20 | 21 | h /= 60; 22 | i = Math.floor(h); 23 | f = h - i; 24 | p = v * (1 - s); 25 | q = v * (1 - s * f); 26 | t = v * (1 - s * (1 - f)); 27 | 28 | switch (i) { 29 | case 0: 30 | r = v; 31 | g = t; 32 | b = p; 33 | break; 34 | 35 | case 1: 36 | r = q; 37 | g = v; 38 | b = p; 39 | break; 40 | 41 | case 2: 42 | r = p; 43 | g = v; 44 | b = t; 45 | break; 46 | 47 | case 3: 48 | r = p; 49 | g = q; 50 | b = v; 51 | break; 52 | 53 | case 4: 54 | r = t; 55 | g = p; 56 | b = v; 57 | break; 58 | 59 | default: // case 5: 60 | r = v; 61 | g = p; 62 | b = q; 63 | } 64 | 65 | return [ 66 | Math.round(r * 255), 67 | Math.round(g * 255), 68 | Math.round(b * 255) 69 | ]; 70 | } 71 | 72 | export default HSVtoRGB; -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import toHex from './rgb-to-hex'; 2 | import gradientor from './gradientor'; 3 | import brightness from './brightness'; 4 | 5 | export default { 6 | toHex, 7 | gradientor, 8 | brightness 9 | } -------------------------------------------------------------------------------- /src/utils/rgb-to-hex.js: -------------------------------------------------------------------------------- 1 | function rgbChannelToHex (channel) { 2 | const hex = channel.toString(16); 3 | return hex.length === 1 ? `0${hex}` : hex; 4 | } 5 | 6 | function rgbToHex (r, g, b) { 7 | return `#${rgbChannelToHex(r)}${rgbChannelToHex(g)}${rgbChannelToHex(b)}`; 8 | } 9 | 10 | function rgbArrayToHex (color) { 11 | return rgbToHex(color[0], color[1], color[2]); 12 | } 13 | 14 | export default function toHex(color) { 15 | return rgbArrayToHex(color); 16 | } -------------------------------------------------------------------------------- /src/utils/rgb-to-hsv.js: -------------------------------------------------------------------------------- 1 | function hslObjectFromhexArr(hexArr) { 2 | let r = hexArr[0]/ 255; 3 | let g = hexArr[1]/ 255; 4 | let b = hexArr[2]/ 255; 5 | let rr, gg, bb, 6 | h, s, v = Math.max(r, g, b), 7 | diff = v - Math.min(r, g, b), 8 | diffc = function(c){ 9 | return (v - c) / 6 / diff + 1 / 2; 10 | }; 11 | 12 | if (diff === 0) { 13 | h = s = 0; 14 | } else { 15 | s = diff / v; 16 | rr = diffc(r); 17 | gg = diffc(g); 18 | bb = diffc(b); 19 | 20 | if (r === v) { 21 | h = bb - gg; 22 | }else if (g === v) { 23 | h = (1 / 3) + rr - bb; 24 | }else if (b === v) { 25 | h = (2 / 3) + gg - rr; 26 | } 27 | if (h < 0) { 28 | h += 1; 29 | } else if (h > 1) { 30 | h -= 1; 31 | } 32 | } 33 | return { 34 | h: Math.round(h * 360), 35 | s: Math.round(s), 36 | v: Math.round(v) 37 | }; 38 | 39 | } 40 | 41 | export default hslObjectFromhexArr; -------------------------------------------------------------------------------- /src/utils/tint-shade.js: -------------------------------------------------------------------------------- 1 | import avgMix from './avg-color'; 2 | function tintShade(color, granularity) { 3 | granularity = Number(granularity); 4 | const mid = Math.round(granularity/2); 5 | const mixWithWhite = granularity % 2 === 0 6 | ? avgMix([255, 255, 255], color, mid).slice(1, mid+1) 7 | : avgMix([255, 255, 255], color, mid).slice(1, mid); 8 | const mixWithBlack = granularity % 2 === 0 9 | ? avgMix(color, [0, 0, 0], mid).slice(1, mid+1) 10 | : avgMix(color, [0, 0, 0], mid).slice(0, mid); 11 | 12 | return [...mixWithWhite, ...mixWithBlack]; 13 | 14 | } 15 | 16 | export default tintShade; --------------------------------------------------------------------------------