├── .czrc ├── assets └── color.png ├── Picture2color.xmind ├── examples ├── assets │ ├── web.jpg │ ├── color.png │ ├── pic9.jpeg │ └── style.css ├── index.html ├── dev-demo.html └── demo │ ├── color-drage.html │ ├── color-picker.html │ ├── color-deep.html │ ├── color-point.html │ ├── color-similar.html │ └── color-analyse.html ├── .gitignore ├── .editorconfig ├── src ├── index.js ├── types │ └── index.js ├── utils │ ├── paramsFilter.js │ └── index.js └── lib │ ├── Count.js │ ├── Point.js │ ├── Color.js │ ├── Analyse.js │ ├── ColorGroup.js │ └── main.js ├── .babel.config.js ├── LICENSE ├── dist ├── worker.js └── index.js ├── package.json ├── CHANGELOG.md └── README.md /.czrc: -------------------------------------------------------------------------------- 1 | { "path": "cz-conventional-changelog" } -------------------------------------------------------------------------------- /assets/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanxiaodong404/picture2color/HEAD/assets/color.png -------------------------------------------------------------------------------- /Picture2color.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanxiaodong404/picture2color/HEAD/Picture2color.xmind -------------------------------------------------------------------------------- /examples/assets/web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanxiaodong404/picture2color/HEAD/examples/assets/web.jpg -------------------------------------------------------------------------------- /examples/assets/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanxiaodong404/picture2color/HEAD/examples/assets/color.png -------------------------------------------------------------------------------- /examples/assets/pic9.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanxiaodong404/picture2color/HEAD/examples/assets/pic9.jpeg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | .vscode 4 | .DS_Store 5 | package-lock.json 6 | yarn.lock 7 | yarn-error.log 8 | */**/.DS_Store 9 | **/.DS_Store 10 | /.DS_Store 11 | node_modules 12 | 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = false 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 4 10 | # end_of_line = crlf 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-05-12 17:35:04 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-09-08 17:58:34 6 | * @Description: 7 | */ 8 | 9 | const Picture2color = require('./lib/main') 10 | 11 | module.exports = Picture2color -------------------------------------------------------------------------------- /src/types/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-11-30 14:25:18 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2020-12-14 15:50:37 6 | * @Description: 7 | */ 8 | 9 | /** 10 | * 颜色值输出类型 11 | */ 12 | const COLORTYPE = { 13 | HEXCOLOR: 'HEX', 14 | RGBACOLOR: 'RGBA' 15 | } 16 | 17 | 18 | module.exports = { 19 | ...COLORTYPE 20 | } -------------------------------------------------------------------------------- /.babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-09-18 17:51:04 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2020-09-18 18:08:52 6 | * @Description: 7 | */ 8 | module.exports = { 9 | sourceType: 'unambiguous', 10 | presets: ['@babel/preset-env'], 11 | plugins: [ 12 | '@babel/transform-runtime', 13 | ['@babel/plugin-proposal-class-properties', {"loose": false}], 14 | '@babel/plugin-syntax-class-properties' 15 | ] 16 | } -------------------------------------------------------------------------------- /src/utils/paramsFilter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-11-30 14:45:57 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-01-26 12:15:18 6 | * @Description: 处理各个类的参数 7 | */ 8 | 9 | 10 | function analy(params) { 11 | return Object.assign({}, params) 12 | } 13 | function color(params) { 14 | return Object.assign({}, params) 15 | } 16 | function group(params) { 17 | return Object.assign({}, params) 18 | } 19 | function count(params) { 20 | return Object.assign({}, params) 21 | } 22 | function point(params) { 23 | return Object.assign({}, params) 24 | } 25 | function main(params) { 26 | return Object.assign({}, params) 27 | } 28 | module.exports = { 29 | analy, 30 | color, 31 | group, 32 | count, 33 | point, 34 | main 35 | } -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Picture2color 9 | 10 | 11 | 12 | 13 |
14 | 20 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /src/lib/Count.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-11-26 16:55:46 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2020-12-23 15:52:56 6 | * @Description: 用于统计count 7 | */ 8 | 9 | class Count { 10 | constructor(base = 0) { 11 | this.__count = base; 12 | this.__children = []; 13 | this.__parent = null; 14 | } 15 | 16 | resetCount(count = 0) { 17 | this.__count = count 18 | } 19 | 20 | append(item) { 21 | this.__children.push(item) 22 | item.__parent = this; 23 | this.plus(item.__count) 24 | } 25 | 26 | plus(count = 1) { 27 | this.__count += count 28 | if (this.__parent) { 29 | this.__parent.plus(count) 30 | } 31 | } 32 | 33 | get count() { 34 | return this.__count 35 | } 36 | 37 | get percent() { 38 | if (this.__parent) { 39 | return this.__count / this.__parent.__count * 100 40 | } else { 41 | return 100 42 | } 43 | } 44 | 45 | } 46 | 47 | module.exports = Count -------------------------------------------------------------------------------- /src/lib/Point.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-11-27 13:35:10 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-01-26 11:12:51 6 | * @Description: 7 | */ 8 | const utils = require("../utils") 9 | const paramsFilter = require('../utils/paramsFilter') 10 | const defaultOption = { 11 | index: 0, 12 | width: 0, 13 | height: 0, 14 | name: undefined 15 | } 16 | class Point { 17 | constructor(data, option = {}) { 18 | this.option = paramsFilter.point(Object.assign({}, defaultOption, option)); 19 | let {index, width, height, name} = this.option 20 | this.data = data 21 | this.index = index 22 | this.width = width 23 | this.height = height 24 | this.__position = null 25 | this.__colorName = name || null 26 | } 27 | get position() { 28 | return this.__position || (this.__position = utils.index2position(this.index, this.width)) 29 | } 30 | get colorName() { 31 | return this.__colorName || (this.__colorName = utils.data2color(this.data)) 32 | } 33 | } 34 | 35 | module.exports = Point -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 wanxiaodong 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 | -------------------------------------------------------------------------------- /dist/worker.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Picture2color",[],t):"object"==typeof exports?exports.Picture2color=t():e.Picture2color=t()}(this,(function(){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(o,n,function(t){return e[t]}.bind(null,n));return o},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=11)}({11:function(e,t){console.log("worker")}})})); -------------------------------------------------------------------------------- /src/lib/Color.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-10-19 16:25:49 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-09-08 18:02:21 6 | * @Description: 7 | * @Focus: 注意:如果是需要新增属性和方法,请确认是否需要在ColorGroup的代理中进行设置 8 | */ 9 | 10 | const Count = require('./Count'); 11 | const utils = require('../utils') 12 | const types = require('../types') 13 | const paramsFilter = require('../utils/paramsFilter') 14 | const defaultOptionColor = { 15 | deepStep: 120, 16 | value: undefined, // 优化字符串处理速度可传入提前处理好的name utils.data2color(data) 17 | type: types.RGBACOLOR 18 | } 19 | const groupMap = new Map() 20 | class Color extends Count { 21 | constructor(data, option = {}) { 22 | super(1) // count = 1 23 | if (data instanceof Color) return data; 24 | this.option = paramsFilter.color(Object.assign({}, defaultOptionColor, option)); 25 | this.data = data; 26 | this.__colorName = option.value || null; // 颜色名 27 | } 28 | /** 29 | * 颜色深浅 30 | */ 31 | get isDeep() { 32 | return utils.isDeep(this, this.option.deepStep) 33 | } 34 | /** 35 | * 获取颜色rgba名称,有缓存以及懒加载作用 36 | */ 37 | get value() { 38 | return this.__colorName || (this.__colorName = this.toColorString()) 39 | } 40 | /** 41 | * 转换不同的color string 42 | * @param {*} type 43 | */ 44 | toColorString(type = types.RGBACOLOR) { 45 | return utils.data2color(this.data, this.option.type || type) 46 | } 47 | /** 48 | * 克隆color对象 49 | * @param {*} color 50 | * @param {*} option 51 | * @param {*} gid 52 | */ 53 | static clone(color, option) { 54 | let _color = new Color(color.data, utils.paramsFilter.color({...color.option, value: color.__colorName, ...option})) 55 | _color.resetCount(color.__count) 56 | return _color 57 | } 58 | } 59 | 60 | module.exports = Color -------------------------------------------------------------------------------- /examples/assets/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-10-26 11:38:20 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2020-12-14 17:35:06 6 | * @Description: 7 | */ 8 | 9 | 10 | * { 11 | box-sizing: border-box; 12 | } 13 | .container { 14 | display: block; 15 | width: 100%; 16 | padding: 0 15px; 17 | } 18 | img { 19 | display: block; 20 | max-width: 100%; 21 | margin: 0 auto; 22 | } 23 | 24 | iframe { 25 | display: block; 26 | width: 100%; 27 | min-height: 80vh; 28 | border: 1px solid #eee; 29 | border-radius: 4px; 30 | padding: 25px 0; 31 | } 32 | .menu { 33 | display: flex; 34 | justify-content: flex-start; 35 | align-items: center; 36 | flex-wrap: wrap; 37 | } 38 | .menu-item { 39 | display: block; 40 | width: 50%; 41 | color: #666; 42 | font-size: 16px; 43 | font-weight: 600; 44 | transition: all .2s linear; 45 | line-height: 32px; 46 | border-radius: 2px; 47 | padding: 5px 15px; 48 | } 49 | .menu-item:hover { 50 | background-color: #eee; 51 | color: #333; 52 | } 53 | 54 | .demo-block { 55 | background-color: #efefef; 56 | border-radius: 4px; 57 | padding: 15px; 58 | margin: 15px 0; 59 | } 60 | .demo-block img { 61 | max-height: 300px; 62 | } 63 | .demo-block pre { 64 | background-color: #fff; 65 | border-radius: 4px; 66 | padding: 10px; 67 | overflow-x: auto; 68 | } 69 | 70 | .demo-block .button { 71 | width: auto; 72 | display: block; 73 | margin: 10px auto 0 auto; 74 | text-align: center; 75 | font-size: 14px; 76 | color: #333; 77 | padding: 0 15px; 78 | border: 1px solid #999; 79 | border-radius: 3px; 80 | -webkit-user-select: auto; 81 | cursor: pointer; 82 | } 83 | 84 | .demo-title { 85 | color: #333; 86 | font-size: 18px; 87 | font-weight: bold; 88 | line-height: 24px; 89 | } 90 | .demo-sub-title { 91 | color: #666; 92 | font-size: 14px; 93 | font-weight: 500; 94 | line-height: 20px; 95 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "picture2color", 3 | "version": "0.1.06", 4 | "description": "图片颜色获取, 颜色识别,颜色分析,取色", 5 | "keywords": [ 6 | "图片", 7 | "颜色", 8 | "颜色识别", 9 | "颜色提取", 10 | "图片颜色", 11 | "rgba", 12 | "rgb", 13 | "picture", 14 | "color", 15 | "color picker", 16 | "picture2color" 17 | ], 18 | "main": "./src/index.js", 19 | "scripts": { 20 | "build": "webpack --config ./build/webpack.build.prod.js", 21 | "dev": "webpack-dev-server --config ./build/webpack.build.dev.js -w", 22 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0 -s", 23 | "test": "echo \"Error: no test specified\" && exit 1" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/sheldonWan/picture2color.git" 28 | }, 29 | "config": { 30 | "commitizen": { 31 | "extends": [ 32 | "cz-conventional-changelog" 33 | ] 34 | } 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 39 | } 40 | }, 41 | "author": "wanxiaodong", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/sheldonWan/picture2color/issues" 45 | }, 46 | "homepage": "https://github.com/sheldonWan/picture2color#readme", 47 | "dependencies": { 48 | "events": "^3.2.0" 49 | }, 50 | "devDependencies": { 51 | "@babel/core": "^7.11.6", 52 | "@babel/plugin-proposal-class-properties": "^7.10.4", 53 | "@babel/plugin-syntax-class-properties": "^7.10.4", 54 | "@babel/plugin-transform-runtime": "^7.11.5", 55 | "@babel/preset-env": "^7.11.5", 56 | "babel-loader": "^8.1.0", 57 | "babel-runtime": "^6.26.0", 58 | "cz-conventional-changelog": "^3.3.0", 59 | "html-webpack-plugin": "^4.4.1", 60 | "husky": "^7.0.2", 61 | "webpack": "^4.43.0", 62 | "webpack-cli": "^3.3.12", 63 | "webpack-dev-server": "^3.11.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/dev-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Picture2color demo 7 | 8 | 24 | 25 | 26 |
27 |
28 |

主颜色分析

29 |

分析整张图片主要颜色占比

30 |
31 | 32 |
33 |
切换 colorProxy <==> Color
34 |
35 | 36 | 48 |

49 |         
50 |
51 | 62 | 63 | -------------------------------------------------------------------------------- /examples/demo/color-drage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Picture2color 9 | 10 | 11 | 27 | 28 | 29 |
30 |
31 |

获取部分边框颜色配置背景色

32 |

分析整张图片边框向内部分颜色主要占比

33 |
34 | 50 |

51 |     
52 |
53 | 64 | 65 | -------------------------------------------------------------------------------- /examples/demo/color-picker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Picture2color 9 | 10 | 11 | 27 | 28 | 29 |
30 |
31 |

颜色吸取

32 |

颜色提取工具

33 |
34 | 50 |

51 |     
52 |
53 | 64 | 65 | -------------------------------------------------------------------------------- /examples/demo/color-deep.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Picture2color 9 | 10 | 11 | 27 | 28 | 29 |
30 |
31 |

深浅色判定

32 |

判断颜色是否是深色

33 |
34 |
切换
35 | 52 |

53 |     
54 |
55 | 66 | 67 | -------------------------------------------------------------------------------- /examples/demo/color-point.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Picture2color 9 | 10 | 11 | 27 | 28 | 29 |
30 |
31 |

查询颜色坐标(bate功能)

32 |

查询颜色坐标

33 |
34 |
35 | 36 |
37 | 56 |

57 |     
58 |
59 | 70 | 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.6](https://github.com/sheldonWan/picture2color/compare/v0.1.3...v0.1.6) (2021-09-09) 2 | 3 | 4 | ### Features 5 | 6 | * 静态方法优化 ([e4edb5d](https://github.com/sheldonWan/picture2color/commit/e4edb5da8c174c292042bff2c0552af9b7a7005f)) 7 | * build ([91cb7c7](https://github.com/sheldonWan/picture2color/commit/91cb7c72d106f4a4d938ab44eadc5c5912302d2e)) 8 | * new version ([da94a96](https://github.com/sheldonWan/picture2color/commit/da94a96ce09c16fe1ed3028ca28b147af4210a1f)) 9 | 10 | 11 | 12 | ## [0.1.3](https://github.com/sheldonWan/picture2color/compare/v0.1.2...v0.1.3) (2021-01-26) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * import ([7240d0c](https://github.com/sheldonWan/picture2color/commit/7240d0c3b1fdd61955116e1d5829e5ffe5ceb4ae)) 18 | 19 | 20 | 21 | ## [0.1.2](https://github.com/sheldonWan/picture2color/compare/v0.1.1...v0.1.2) (2021-01-26) 22 | 23 | 24 | 25 | ## [0.1.1](https://github.com/sheldonWan/picture2color/compare/v0.1.0...v0.1.1) (2020-12-23) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * fixed count bug ([88fb927](https://github.com/sheldonWan/picture2color/commit/88fb927fd34cd2f165dcebbe962b7327bee49b6b)) 31 | * fixed count bug ([829c1dc](https://github.com/sheldonWan/picture2color/commit/829c1dcce3be8aa91811da0e325bc2fc78878147)) 32 | 33 | 34 | ### Features 35 | 36 | * keyword ([a117dbe](https://github.com/sheldonWan/picture2color/commit/a117dbe84f522205b30ad0dad0cd3bcafa08ec6d)) 37 | 38 | 39 | 40 | # [0.1.0](https://github.com/sheldonWan/picture2color/compare/v0.0.10...v0.1.0) (2020-12-23) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * hex string bug fixed ([152ae7c](https://github.com/sheldonWan/picture2color/commit/152ae7cc80bb28a63d12248fa0263e8bc28ccd8e)) 46 | 47 | 48 | ### Features 49 | 50 | * 添加types和paramsFilter ([1540475](https://github.com/sheldonWan/picture2color/commit/1540475e64fbf679e28c6c7165a1d224e2b796f9)) 51 | * 重新规划0.1.0版本 ([2c7861f](https://github.com/sheldonWan/picture2color/commit/2c7861f87e89baec82dc5e189ddc4f581967488c)) 52 | * 重新设计color proxy ([7581427](https://github.com/sheldonWan/picture2color/commit/758142795585a103984f94dbfcc82e76a485ad54)) 53 | 54 | 55 | 56 | ## [0.0.10](https://github.com/sheldonWan/picture2color/compare/v0.0.9...v0.0.10) (2020-11-27) 57 | 58 | 59 | ### Features 60 | 61 | * 添加changelog ([fa7aa18](https://github.com/sheldonWan/picture2color/commit/fa7aa18b8c21810e5fd9c80e898591332ab93094)) 62 | * 新添加point类 ([e45f673](https://github.com/sheldonWan/picture2color/commit/e45f673712c75b4c5e2b1638c412a2fd35b74283)) 63 | * 新增异步执行参数以及changlog调整 ([a416ee9](https://github.com/sheldonWan/picture2color/commit/a416ee9429c9b9db8fd4cfd2fe37d95275fd1534)) 64 | * 新增demo ([6a89dc3](https://github.com/sheldonWan/picture2color/commit/6a89dc3afcb71bd24ad2b56e47f5c993d8c542d9)) 65 | 66 | 67 | 68 | ## [0.0.9](https://github.com/sheldonWan/picture2color/compare/v0.0.7...v0.0.9) (2020-11-26) 69 | 70 | 71 | 72 | ## [0.0.7](https://github.com/sheldonWan/picture2color/compare/v0.0.6...v0.0.7) (2020-10-26) 73 | 74 | 75 | 76 | ## [0.0.6](https://github.com/sheldonWan/picture2color/compare/v0.0.5...v0.0.6) (2020-10-26) 77 | 78 | 79 | 80 | ## [0.0.5](https://github.com/sheldonWan/picture2color/compare/0.0.4...v0.0.5) (2020-10-22) 81 | 82 | 83 | 84 | ## [0.0.4](https://github.com/sheldonWan/picture2color/compare/0.0.3...0.0.4) (2020-10-22) 85 | 86 | 87 | 88 | ## 0.0.3 (2020-10-22) 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-10-19 16:38:20 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-09-08 18:47:17 6 | * @Description: 7 | */ 8 | 9 | const types = require('../types') 10 | const paramsFilter = require('./paramsFilter') 11 | const Color = require('../lib/Color') 12 | 13 | module.exports = { 14 | /** 15 | * 颜色是否相似 16 | * @param {Color} color1 17 | * @param {Color} color2 18 | * @param {number} step 19 | */ 20 | isSimilarColor(color1, color2, step = 10) { 21 | color1 = typeof color1 === 'string' ? dataInputFormat(color1) : color1; 22 | color2 = typeof color2 === 'string' ? dataInputFormat(color2) : color2; 23 | let [r1, g1, b1, a1] = Array.isArray(color1) ? color1 : color1.data 24 | let [r2, g2, b2, a2] = Array.isArray(color2) ? color2 : color2.data 25 | a1 = a1 / 255; 26 | a2 = a2 / 255 27 | return Math.sqrt(Math.pow((r1 * a1 - r2 * a2), 2) + Math.pow((g1 * a1 - g2 * a2), 2) + Math.pow((b1 * a1 - b2 * a2), 2)) < step 28 | }, 29 | /** 30 | * 是否属于深色 31 | * @param {Color} color 32 | * @param {number} deepStep 0-255 33 | */ 34 | isDeep(color, deepStep = 192) { 35 | color = typeof color === 'string' ? dataInputFormat(color) : color; 36 | let [r, g, b, a] = color.data 37 | a = a / 255 38 | return r * a * 0.299 + g * a * 0.587 + b * a * 0.114 < deepStep 39 | }, 40 | /** 41 | * 生成唯一id 42 | */ 43 | getUniqueId(len = 10) { 44 | return Number(Math.random().toString().split('.')[1].substring(0, len)).toString(32) 45 | }, 46 | /** 47 | * color数据转rgba 48 | * @param {*} data 49 | */ 50 | data2color(data, type = types.RGBACOLOR) { 51 | try { 52 | let color; 53 | switch (type) { 54 | case types.HEXCOLOR: { 55 | let [r, g, b, a] = data || []; 56 | a = a / 255 57 | r = (r * a).toString(16); 58 | g = (g * a).toString(16); 59 | b = (b * a).toString(16); 60 | r = r.length > 1 ? r : `0${r}`; 61 | g = g.length > 1 ? g : `0${g}`; 62 | b = b.length > 1 ? b : `0${b}`; 63 | let str = ['#', r, g, b] 64 | color = str.join(''); 65 | break 66 | } 67 | default: { 68 | // types.RGBACOLOR 69 | let [r, g, b, a] = data || []; 70 | color = `rgba(${r},${g},${b},${a / 255})`; 71 | break 72 | } 73 | } 74 | return color; 75 | } catch (e) { 76 | return '' 77 | } 78 | }, 79 | /** 80 | * 通过色值转rgba数组 81 | * @param {*} color 82 | */ 83 | color2data: color2data, 84 | 85 | index2position(index, width = 1) { 86 | return [index % width, Math.ceil(index / width)] 87 | }, 88 | 89 | paramsFilter 90 | } 91 | 92 | 93 | 94 | /** 95 | * 颜色输入格式化 96 | */ 97 | function dataInputFormat(color) { 98 | if (typeof color === 'object') { 99 | return color 100 | } else { 101 | return color2data(color); 102 | } 103 | } 104 | 105 | /** 106 | * 字符串转color data 107 | * @param {*} color 108 | * @returns 109 | */ 110 | function color2data(color) { 111 | try { 112 | if (/^rgba?\(.+\)/i.test(color)) { 113 | let data = color.match(/([\d.]+)/g); 114 | if (!data || data.length < 3) throw Error('请传入正确的值') 115 | if (data.length === 3) data.push('1'); 116 | let opacity = data.pop(); 117 | data.push(opacity * 255); 118 | return data.slice(0, 4).map(item => Number(item)); 119 | } else if (/^#(\w+)/ig) { 120 | let data = color.match(/^#(\w+)/); 121 | data = data ? data[1] : ''; 122 | if (!data) throw Error('请传入正确的值'); 123 | if (data.length === 3) { 124 | return [parseInt(`0x${data[0].repeat(2)}`), parseInt(`0x${data[1].repeat(2)}`), parseInt(`0x${data[2].repeat(2)}`), 255] 125 | } else if (data.length >= 6) { 126 | data = data.substring(0, 6) 127 | return [parseInt(`0x${data.substring(0, 2)}`), parseInt(`0x${data.substring(2, 4)}`), parseInt(`0x${data.substring(4, 6)}`), 255] 128 | } else { 129 | if (!data) throw Error('请传入正确的值') 130 | } 131 | } 132 | } catch (e) { 133 | console.warn('请传入正确的色值') 134 | } 135 | } -------------------------------------------------------------------------------- /src/lib/Analyse.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-10-19 16:27:03 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-01-26 11:11:40 6 | * @Description: 色值分析 7 | */ 8 | 9 | const Color = require("./Color"); 10 | const ColorGroup = require("./ColorGroup"); 11 | const Point = require('./Point') 12 | const utils = require("../utils"); 13 | const paramsFilter = require('../utils/paramsFilter') 14 | const types = require('../types') 15 | 16 | 17 | const defaultOptionColorAnalyse = {} 18 | class ColorAnalyse { 19 | constructor(colorData, option = {}) { 20 | this.option = paramsFilter.analy(Object.assign({}, defaultOptionColorAnalyse, option)) 21 | this.originData = colorData; 22 | let {width, height} = colorData; 23 | this.width = width 24 | this.height = height 25 | let {colorGroup, translateData} = this.analyse(colorData); 26 | this.colorGroup = colorGroup; 27 | this.translateData = translateData; 28 | } 29 | /** 30 | * 颜色数据分析 31 | * @param {*} data 32 | */ 33 | analyse(data) { 34 | let {width, height, data: _data} = data; 35 | data = this.colorFormat(_data, width, height); 36 | return { 37 | translateData: {width, height, data}, 38 | colorGroup: this.createColorGroup(data) 39 | } 40 | }/** 41 | * 格式化颜色数据 42 | * @param {*} data 43 | */ 44 | colorFormat(data, width, height) { 45 | let list = [], 46 | index = 0; 47 | data = Array.from(data) 48 | while (index * 4 <= data.length - 4) { 49 | let _data = data.slice(index * 4, (index + 1) * 4); 50 | list.push(new Point(_data, {index, width, height})) 51 | index++ 52 | } 53 | return list 54 | } 55 | /** 56 | * 创建一个颜色组 57 | * @param {*} data 58 | */ 59 | createColorGroup(data) { 60 | let group = new ColorGroup(); 61 | data.forEach(item => { 62 | group.concat(item.data, utils.paramsFilter.group({...this.option, value: item.colorName})) 63 | }) 64 | return group 65 | } 66 | /** 67 | * 以边框向内方向获取范围主要色值 68 | * @param {*} option 69 | * @param {*} colorAnalyse 70 | */ 71 | getFrameColorGroup(option = {}, colorAnalyse) { 72 | let {width, height, translateData} = colorAnalyse || this; 73 | let {size = 0.2} = Object.assign({}, this.option, option) 74 | let leftX = size * width, 75 | rightX = (1 - size) * width, 76 | topY = size * height, 77 | bottomY = (1 - size) * height; 78 | let _data = translateData.data.filter((item, index) => { 79 | let _x = index % width, 80 | _y = Math.ceil(index / width); 81 | return (_x <= leftX || _x >= rightX) && (_y >= bottomY || _y <= topY) 82 | }); 83 | return this.createColorGroup(_data) 84 | } 85 | /** 86 | * 根据坐标获取颜色 87 | * @param {*} x 88 | * @param {*} y 89 | * @param {*} colorAnalyse 90 | */ 91 | getPositionColor(x, y, colorAnalyse) { 92 | let {width, translateData} = colorAnalyse || this; 93 | let data = translateData.data[y * width + x] 94 | return this.colorGroup.get(data.colorName) 95 | } 96 | /** 97 | * 颜色筛选 性能问题 暂时不对外开放 98 | * @param {Color | [rgba]} color 99 | */ 100 | colorFilter_bate(color) { 101 | let {translateData, colorGroup} = this; 102 | let {width, data} = translateData 103 | let {colorType} = this.option 104 | if (color instanceof Color) { 105 | return data.filter((item) => { 106 | return item.colorName === color.value 107 | }) 108 | } else if(color.isGroup) { 109 | let list = color.__children.map(item => this.colorFilter_bate(item)) 110 | return list.reduce((list, item) => { 111 | return list.concat(...item) 112 | }, []) 113 | } else if (Array.isArray(color)) { 114 | return data.filter((item) => { 115 | return item.colorName === utils.data2color(color, colorType) 116 | }) 117 | } else { 118 | // rgba(0,0,0,1) 119 | return data.filter((item) => { 120 | return item.colorName === color 121 | }) 122 | } 123 | } 124 | /** 125 | * 销毁 126 | */ 127 | destory() { 128 | this.option = null; 129 | this.originData = null; 130 | this.colorGroup = null; 131 | this.mainColor = null; 132 | this.width = null; 133 | this.height = null; 134 | } 135 | } 136 | 137 | module.exports = ColorAnalyse -------------------------------------------------------------------------------- /src/lib/ColorGroup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-11-26 14:50:42 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-01-26 13:59:18 6 | * @Description: 7 | */ 8 | const Color = require('./Color'); 9 | const Count = require('./Count'); 10 | const utils = require('../utils'); 11 | const paramsFilter = require('../utils/paramsFilter') 12 | class ColorGroup extends Count { 13 | constructor(child) { 14 | super(); 15 | this.gid = utils.getUniqueId(); 16 | this.isGroup = true; // 用于判定color和group 17 | this.__proxy = child || null; // 如果是颜色范围的话,可设置一个颜色代表用以代表这个颜色范围 18 | this.__pool = new Map(); 19 | if (child) { 20 | this.concat(child) 21 | // 代理一些Color类上的方法和属性,注意方法是否需要bind 22 | let proxyList = new Set(['value', 'data', 'isDeep', 'toColorString']) 23 | return new Proxy(this, { 24 | get(target, key) { 25 | if (proxyList.has(key)) { 26 | return child[key] 27 | } else { 28 | return target[key] 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | /** 35 | * 追加颜色到色池 36 | * @param {Color} color 37 | */ 38 | pushPool(color) { 39 | if (color instanceof Color) { 40 | this.__pool.set(color.value, color); 41 | this.append(color) 42 | } else if (color.isGroup){ 43 | this.__pool.set(color.__proxy.value, color); 44 | this.append(color) 45 | } else { 46 | throw('数据错误', color) 47 | } 48 | } 49 | /** 50 | * 向色组内追加颜色 51 | * @param {Group || Color || [rgba]} color 52 | * @return {Color} 53 | */ 54 | concat(color, option) { 55 | let pool = this.__pool 56 | if (color instanceof Color) { 57 | let data = pool.get(color.value) 58 | if (data) { 59 | data.plus(color.__count) 60 | } else { 61 | this.pushPool(color) 62 | } 63 | } else if (color.isGroup) { 64 | let data = pool.get(color.__proxy.value) 65 | if (data) { 66 | data.plus(color.__count) 67 | } else { 68 | this.pushPool(color) 69 | } 70 | } else { 71 | let colorString = utils.data2color(color); 72 | let data = pool.get(colorString) 73 | if (data) { 74 | data.plus() 75 | } else { 76 | color = new Color(color, paramsFilter.color({ 77 | ...option, 78 | value: colorString 79 | }), this) 80 | this.pushPool(color) 81 | } 82 | } 83 | return color 84 | } 85 | /** 86 | * 判断是否包含此颜色 87 | * @param {*} colorName 88 | */ 89 | hasChild(colorName) { 90 | return this.__pool.has(colorName) 91 | } 92 | 93 | get(color) { 94 | if (color instanceof Color) { 95 | return this.__pool.get(color.value) 96 | } else if (Array.isArray(color)) { 97 | // rgba 98 | return this.__pool.get(utils.data2color(color)) 99 | } else { 100 | return this.__pool.get(color) 101 | } 102 | } 103 | /** 104 | * 创建范围颜色代理 105 | * @param {*} option 106 | * @param {*} colorGroup 107 | */ 108 | createColorProxy(option = {}, colorGroup) { 109 | colorGroup = colorGroup || this; 110 | if (colorGroup.__proxy) return this; 111 | let {colorStep = 100} = option; 112 | let map = new Set(); 113 | colorGroup.sortList.forEach(item => { 114 | let hasNoColor = true; 115 | map.forEach((value, key) => { 116 | if (utils.isSimilarColor(value.__proxy, item, colorStep) && hasNoColor) { 117 | // 需要注意避免被重复判定为相似颜色 118 | hasNoColor = false 119 | item = Color.clone(item) 120 | value.concat(item, value); 121 | } 122 | }) 123 | if (hasNoColor) { 124 | item = Color.clone(item) 125 | map.add(new ColorGroup(item)) 126 | } 127 | }) 128 | let _group = new ColorGroup(); 129 | map.forEach((group) => { 130 | _group.concat(group) 131 | }) 132 | return _group 133 | } 134 | 135 | get proxy() { 136 | return this.__proxy 137 | } 138 | 139 | get list() { 140 | return Array.from(this.__pool.values()) 141 | } 142 | 143 | get sortList() { 144 | return this.list.sort((item1, item2) => item2.__count - item1.__count) 145 | } 146 | } 147 | 148 | module.exports = ColorGroup -------------------------------------------------------------------------------- /examples/demo/color-similar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Picture2color 9 | 10 | 11 | 27 | 38 | 39 | 40 |
41 |
42 |

颜色范围判定

43 |

判定两个颜色是否在特定参数范围内属于相同颜色范围, mainColor和borderColor都是返回的近似颜色值

44 |
45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 | 53 | 54 | 55 | 56 |
57 |
58 |
59 | 63 |
64 |
65 | 对比结果: 66 |
67 | 90 |

 91 |     
92 |
93 | 104 | 105 | -------------------------------------------------------------------------------- /examples/demo/color-analyse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Picture2color 9 | 10 | 11 | 27 | 28 | 29 |
30 |
31 |

主颜色分析

32 |

分析整张图片主要颜色占比

33 |
34 | 35 |
36 |
切换 colorProxy <==> Color
37 |
38 | 95 |

 96 |         
97 |
98 | 109 | 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [picture2color](./) 2 | 3 | [![npm version][npm-version-img]][npm-url] 4 | [![npm download][npm-download-img]][npm-download] 5 | [![MIT License][license-image]][license-url] 6 | [![issues-img]][issues] 7 | [![pr-img]][pr] 8 | [![size-img]][size-img] 9 | [![file-img]][file-img] 10 | 11 | ## 简介 12 | 此模块旨在解决前端在图片颜色识别上的问题,有问题可以联系 13 | [我][e-mail]或者提[issues][issues] 14 | 15 | 1、能够分析图片的主要颜色 16 | 17 | 2、根据坐标(像素/百分比)获取对应颜色 18 | 19 | 3、判定单个或者多个颜色深浅判定 20 | 21 | 4、对比两个颜色是否在参数程度范围内是否相似 22 | 23 | 5、获取颜色(或范围颜色)于颜色数据的占比 24 | 25 | 6、获取图片的全部颜色数据 26 | 27 | 7、按图片边框由外到内0-0.5占比的颜色数据 28 | 29 | 8、根据颜色数据创建范围颜色代理colorProxy 30 | 31 | ![img][demo-img] 32 | 33 | ## Demo 34 | 35 | [链接][demo] 36 | 37 | ## install 38 | ``` bash 39 | npm install --save picture2color 40 | ``` 41 | 42 | ## import 43 | 44 | ```javascript 45 | import Picture2color from 'Picture2color' 46 | ``` 47 | 48 | ## static API 49 | 50 | 1、是否是深色 51 | ```javascript 52 | /** 53 | * pa 54 | * @param {Color || ColorProxy} color 55 | * @param {Number} colorStep 值越小说明深色判定范围越小 默认192 56 | * @return Boolean 57 | */ 58 | static isDeep(color, deepStep) 59 | ``` 60 | 2、获取坐标位置传入图片的Color instance 61 | ```javascript 62 | /** 63 | * 获取图片坐标内色值 64 | * @param {*} x 65 | * @param {*} y 66 | * @param {*} image 67 | * @param {*} isPiex 68 | * @return Color 69 | */ 70 | static getImageColor(x, y, image, isPiex) 71 | ``` 72 | 3、判定 73 | ```javascript 74 | /** 75 | * 颜色是否在对应的范围内色值相似 76 | * @param {Color | [r,g,b,a] | colorProxy} color1 77 | * @param {Color | [r,g,b,a] | colorProxy} color2 78 | * @param {number 1++} colorStep 值越小范围越小,默认100 79 | */ 80 | static isSimilarColor(color1, color2, colorStep) 81 | ``` 82 | 4、颜色类(传入[r,g,b,a])实例化color对象 83 | ```javascript 84 | static Color 85 | ``` 86 | ## 实例化 87 | 88 | 1、实例化模块 89 | ```javascript 90 | const image = new Image() || "http://url" 91 | const options = { 92 | async: false, // 是否异步化执行 传入图片为字符串链接也会默认转化为async执行 93 | event: ['click'], // 绑定事件获取颜色信息 然后通过emit=>color向外反馈 94 | deepStep: 192 // 判定深浅色程度,值越大深浅灵敏度越小 --Color 95 | } 96 | let demo = new Picture2color(image, options); 97 | demo instanceof Picture2color // true 98 | demo.on('color', () => {}); 99 | ``` 100 | 101 | ## 实例化API 102 | 103 | 1、获取当前图片主要色值(数据量大的时候计算时间会比较长) 104 | 105 | ```javascript 106 | /** 107 | * 获取图片占比主要颜色列表 108 | */ 109 | instance.getColorGroup() // 返回ColorGroup实例 110 | ``` 111 | 2、以边框为界限向内获取主要颜色列表(数据量大的时候计算时间会比较长) 112 | ```javascript 113 | /** 114 | * 以边框为界限向内获取0-0.5范围主要颜色列表 115 | * @param {*} option {size: 0-0.5} 116 | */ 117 | instance.getFrameColorGroup() // 返回ColorGroup实例 118 | ``` 119 | 120 | ## Color 121 | 122 | 1、实例化 123 | ```javascript 124 | /** 125 | * @params {[r,g,b,a]} rgba色值数组 126 | * @params {*} {deepStep: 0-255} 深色值 值越小说明深色判定范围越小 127 | */ 128 | let colorInstance = new Picture2color.Color(data, option) 129 | ``` 130 | 2、实例化API 131 | 132 | ```javascript 133 | colorInstance.value // rgba 色值 134 | colorInstance.isDeep // Boolean 135 | colorInstance.count // Number 136 | colorInstance.percent // Number 如果是按组生成的颜色可查看百分比 137 | ``` 138 | ## ColorGroup 139 | 140 | 1、实例化 141 | ```javascript 142 | /** 143 | * @params {color} 参数传入color时会将color设置为group的proxy 将color属性代理到颜色组,为空时则纯作为颜色组 144 | * 所谓colorProxy就是将颜色差距极小的颜色归类到一组,然后取组内占比最大的的颜色属性作为代表属性,既包含colorGroup特性也包含color属性 145 | */ 146 | let groupInstance = new Picture2color.ColorGroup() 147 | ``` 148 | 2、实例化API 149 | 150 | ```javascript 151 | groupInstance.list // Array 颜色组包含的所有颜色列表 152 | groupInstance.sortList // Array 颜色组包含的所有颜色列表-降序排列 153 | groupInstance.count // Number 154 | groupInstance.percent // Number 如果是按组生成的颜色可查看百分比 155 | 156 | const option = {colorStep: 100} 157 | groupInstance.createColorProxy(option) // 将colorGroup生成一个新colorGrou,colorGroup内包的的是ColorProxy 158 | /****如果是colorProxy将额外获得Color实例的主要属性****/ 159 | 160 | ``` 161 | ## Count(Color和ColorGroup 都继承的类) 162 | 163 | 1、实例化 164 | ```javascript 165 | /** 166 | */ 167 | let countInstance = new Count() 168 | ``` 169 | 2、实例化API 170 | 171 | ```javascript 172 | countInstance.count // number 数量 173 | countInstance.percent // Number 百分比 174 | ``` 175 | 176 | ## 小更新 177 | 1. isSimilarColor等静态方法支持字符串#fff、rgba(255,255,255) 178 | 179 | ## 未来可期 180 | 181 | 1. 同颜色坐标查找 182 | 2. 颜色范围坐标查找 183 | 3. serviceWorker解决性能问题 184 | 185 | [demo-img]: ./assets/color.png 186 | [demo]: https://wanxiaodong404.github.io/picture2color/examples/ 187 | 188 | [npm-version-img]: https://img.shields.io/npm/v/picture2color 189 | [npm-url]: https://www.npmjs.com/package/picture2color 190 | 191 | [npm-download-img]: https://img.shields.io/npm/dw/picture2color.svg?style=flat 192 | [npm-download]: https://npmcharts.com/compare/picture2color?minimal=true 193 | 194 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat 195 | [license-url]: LICENSE 196 | 197 | [e-mail]: mailto://729779978@qq.com 198 | 199 | [issues-img]: https://img.shields.io/bitbucket/issues-raw/wanxiaodong404/picture2color 200 | [issues]: https://github.com/wanxiaodong404/picture2color/issues 201 | 202 | 203 | [size-img]: https://img.shields.io/badge/minified%20size-16%20kB-informational 204 | [file-img]: https://img.shields.io/badge/files-36-blue 205 | [pr-img]: https://img.shields.io/bitbucket/pr-raw/wanxiaodong404/picture2color 206 | [pr]: https://github.com/wanxiaodong404/picture2color/pr 207 | -------------------------------------------------------------------------------- /src/lib/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: wanxiaodong 3 | * @Date: 2020-10-19 16:36:09 4 | * @Last Modified by: wanxiaodong 5 | * @Last Modified time: 2021-09-09 10:12:16 6 | * @Description: 7 | */ 8 | const events = require('events') 9 | const ColorAnalyse = require('./Analyse') 10 | const Color = require('./Color') 11 | const ColorGroup = require('./ColorGroup') 12 | const utils = require('../utils') 13 | const paramsFilter = require('../utils/paramsFilter') 14 | const types = require('../types') 15 | const defaultOption = { 16 | async: false, // 是否异步化执行 传入图片为字符串链接也会默认转化为async执行 17 | event: ['click'], // 绑定事件获取颜色信息 然后通过emit=>color向外反馈 18 | deepStep: 192 // 判定深浅色程度,值越大深浅灵敏度越小 --Color 19 | } 20 | class Picture2color extends events { 21 | constructor(image, option) { 22 | super(); 23 | this.originColorData = null 24 | this.colorData = null; 25 | this.option = paramsFilter.main(Object.assign({}, defaultOption, (option || {}))); 26 | this.__el = null; 27 | this.__cache = {}; 28 | // this.utils = utils 不对外暴露 29 | if (this.option.async || typeof image === 'string') { 30 | return this.asyncInit(image) 31 | } else { 32 | if (image.complete) { 33 | this.init(image) 34 | } else { 35 | throw Error('图片尚未加载完成') 36 | } 37 | } 38 | } 39 | /** 40 | * 异步初始化 41 | * @param {*} image 42 | */ 43 | asyncInit(image) { 44 | let that = this; 45 | return new Promise(function(resolve, reject) { 46 | if (typeof image === 'string') { 47 | let url = image; 48 | image = new Image(); 49 | image.setAttribute('crossOrigin', 'anonymous'); 50 | image.onload = () => { 51 | that.init(image) 52 | resolve(that) 53 | } 54 | image.onerror = () => { 55 | reject(new Error('图片加载失败')) 56 | } 57 | image.src = url 58 | } else { 59 | that.init(image) 60 | resolve(that) 61 | } 62 | }) 63 | } 64 | init(image) { 65 | this.__el = image 66 | this.getImageColor = Picture2color.getImageColor; 67 | this.getColorData(image) 68 | this.destory = Picture2color.destory; 69 | this.option.event && this.bindEvent(this.option.event); 70 | } 71 | /** 72 | * 绑定事件 73 | * @{Array} 事件类型 74 | */ 75 | bindEvent(eventType = []) { 76 | let that = this; 77 | let event = eventType.map((type) => { 78 | let callback = function(event) { 79 | var {offsetX, offsetY} = event; 80 | let color = that.getImageColor(offsetX, offsetY, that.__el) 81 | that.emit('color', { 82 | type, 83 | eventPosition: [offsetX, offsetY], 84 | color: color 85 | }) 86 | }; 87 | this.__el.addEventListener(type, callback) 88 | return { 89 | type, 90 | callback 91 | } 92 | }); 93 | this.__cache.event = event 94 | } 95 | /** 96 | * 解绑事件 97 | */ 98 | unbindEvent() { 99 | let that = this; 100 | if (this.__cache.event) { 101 | this.__cache.event.forEach(({type, callback}) => { 102 | that.__el.removeEventListener(type, callback) 103 | }) 104 | } 105 | } 106 | /** 107 | * 获取图片的颜色数据 108 | * @param {*} image 109 | */ 110 | getColorData(image) { 111 | let canvas = document.createElement('canvas'); 112 | let {width, height} = image; 113 | canvas.width = width 114 | canvas.height = height 115 | this.__canvas = canvas 116 | let ctx = canvas.getContext('2d') 117 | ctx.drawImage(image, 0, 0); 118 | let data = ctx.getImageData(0, 0, width, height); 119 | this.originColorData = data; 120 | this.analyData = new ColorAnalyse(data, utils.paramsFilter.analy(this.option)); 121 | } 122 | /** 123 | * 颜色筛选 性能问题 暂时不对外开放 124 | * @param {Color | [rgba]} color 125 | */ 126 | colorFilter_bate(color) { 127 | return this.analyData.colorFilter_bate(color) 128 | } 129 | /** 130 | * 获取图片主要颜色列表 131 | */ 132 | getColorGroup() { 133 | return this.analyData.colorGroup 134 | } 135 | /** 136 | * 以边框为界限向内获取0-0.5范围主要颜色列表 137 | * @param {*} option {size: 0-0.5} 138 | */ 139 | getFrameColorGroup(option) { 140 | return this.analyData.getFrameColorGroup(option); 141 | } 142 | /** 143 | * 判断颜色或者颜色列表是否是深色 144 | * @param {Color || ColorList} colorList 145 | * @param {Number} deepStep 146 | */ 147 | static isDeep(colorList, deepStep) { 148 | if (colorList instanceof Color) { 149 | return deepStep ? utils.isDeep(colorList, deepStep) : colorList.isDeep; 150 | } 151 | return colorList.reduce((percent, color) => { 152 | let isDeep = deepStep ? utils.isDeep(color, deepStep) : color.isDeep; 153 | return percent + (isDeep ? color.percent : 0) 154 | }, 0) > 50 155 | } 156 | /** 157 | * 销毁实例 158 | * @param {*} instance 159 | */ 160 | static destory(instance) { 161 | instance = instance || this 162 | instance.unbindEvent() 163 | Object.keys(instance.__cache).forEach(key => { 164 | delete instance.__cache[key] 165 | }) 166 | instance.__cache = null; 167 | instance.originColorData = null; 168 | instance.option = null; 169 | instance.__el = null; 170 | instance.off('.inner'); 171 | instance.analyData && instance.colorData.destory(); 172 | instance.colorData = null; 173 | } 174 | /** 175 | * 获取图片坐标内色值 176 | * @param {*} x 177 | * @param {*} y 178 | * @param {*} image 179 | * @param {*} isPiex 180 | */ 181 | static getImageColor(x = 0, y = 0, image, isPiex = true) { 182 | try { 183 | if (this instanceof Picture2color) { 184 | let {width, height, naturalHeight, naturalWidth} = image; 185 | if (!isPiex) { 186 | // 百分比模式 187 | x = Math.round(x * width * 0.01); 188 | y = Math.round(y * height * 0.01); 189 | } else { 190 | x = Math.round(x / width * naturalWidth); 191 | y = Math.round(y / height * naturalHeight); 192 | } 193 | return this.analyData.getPositionColor(x, y) 194 | } else { 195 | let {width, height, naturalHeight, naturalWidth} = image; 196 | let canvas = document.createElement('canvas'); 197 | canvas.width = width 198 | canvas.height = height 199 | let ctx = canvas.getContext('2d') 200 | ctx.drawImage(image, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height); 201 | if (!isPiex) { 202 | // 百分比模式 203 | x = Math.round(x * width * 0.01); 204 | y = Math.round(y * height * 0.01); 205 | } 206 | let data = ctx.getImageData(x, y, 1, 1); 207 | let color = new Color(data.data) 208 | return color 209 | } 210 | } catch (e) { 211 | console.warn('获取数据失败') 212 | return {} 213 | } 214 | } 215 | /** 216 | * 颜色是否在对应的范围内色值相似 217 | * @param {Color | [r,g,b,a] | string} color1 218 | * @param {Color | [r,g,b,a] | string} color2 219 | * @param {number 1-255} step 220 | */ 221 | static isSimilarColor(color1, color2) { 222 | return utils.isSimilarColor(...arguments) 223 | } 224 | } 225 | 226 | Picture2color.Color = Color 227 | Picture2color.ColorGroup = ColorGroup 228 | 229 | module.exports = Picture2color -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("Picture2color",[],e):"object"==typeof exports?exports.Picture2color=e():t.Picture2color=e()}(this,(function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=6)}([function(t,e){t.exports={analy:function(t){return Object.assign({},t)},color:function(t){return Object.assign({},t)},group:function(t){return Object.assign({},t)},count:function(t){return Object.assign({},t)},point:function(t){return Object.assign({},t)},main:function(t){return Object.assign({},t)}}},function(t,e,r){const n=r(3),o=r(0);r(2);function i(t){return"object"==typeof t?t:s(t)}function s(t){try{if(/^rgba?\(.+\)/i.test(t)){let e=t.match(/([\d.]+)/g);if(!e||e.length<3)throw Error("请传入正确的值");3===e.length&&e.push("1");let r=e.pop();return e.push(255*r),e.slice(0,4).map(t=>Number(t))}{let e=t.match(/^#(\w+)/);if(e=e?e[1]:"",!e)throw Error("请传入正确的值");if(3===e.length)return[parseInt("0x"+e[0].repeat(2)),parseInt("0x"+e[1].repeat(2)),parseInt("0x"+e[2].repeat(2)),255];if(e.length>=6)return e=e.substring(0,6),[parseInt("0x"+e.substring(0,2)),parseInt("0x"+e.substring(2,4)),parseInt("0x"+e.substring(4,6)),255];if(!e)throw Error("请传入正确的值")}}catch(t){console.warn("请传入正确的色值")}}t.exports={isSimilarColor(t,e,r=10){t="string"==typeof t?i(t):t,e="string"==typeof e?i(e):e;let[n,o,s,a]=Array.isArray(t)?t:t.data,[l,u,c,h]=Array.isArray(e)?e:e.data;return a/=255,h/=255,Math.sqrt(Math.pow(n*a-l*h,2)+Math.pow(o*a-u*h,2)+Math.pow(s*a-c*h,2))Number(Math.random().toString().split(".")[1].substring(0,t)).toString(32),data2color(t,e=n.RGBACOLOR){try{let r;switch(e){case n.HEXCOLOR:{let[e,n,o,i]=t||[];i/=255,e=(e*i).toString(16),n=(n*i).toString(16),o=(o*i).toString(16),e=e.length>1?e:"0"+e,n=n.length>1?n:"0"+n,o=o.length>1?o:"0"+o,r=["#",e,n,o].join("");break}default:{let[e,n,o,i]=t||[];r=`rgba(${e},${n},${o},${i/255})`;break}}return r}catch(t){return""}},color2data:s,index2position:(t,e=1)=>[t%e,Math.ceil(t/e)],paramsFilter:o}},function(t,e,r){const n=r(4),o=r(1),i=r(3),s=r(0),a={deepStep:120,value:void 0,type:i.RGBACOLOR};new Map;class l extends n{constructor(t,e={}){if(super(1),t instanceof l)return t;this.option=s.color(Object.assign({},a,e)),this.data=t,this.__colorName=e.value||null}get isDeep(){return o.isDeep(this,this.option.deepStep)}get value(){return this.__colorName||(this.__colorName=this.toColorString())}toColorString(t=i.RGBACOLOR){return o.data2color(this.data,this.option.type||t)}static clone(t,e){let r=new l(t.data,o.paramsFilter.color({...t.option,value:t.__colorName,...e}));return r.resetCount(t.__count),r}}t.exports=l},function(t,e){t.exports={HEXCOLOR:"HEX",RGBACOLOR:"RGBA"}},function(t,e){t.exports=class{constructor(t=0){this.__count=t,this.__children=[],this.__parent=null}resetCount(t=0){this.__count=t}append(t){this.__children.push(t),t.__parent=this,this.plus(t.__count)}plus(t=1){this.__count+=t,this.__parent&&this.__parent.plus(t)}get count(){return this.__count}get percent(){return this.__parent?this.__count/this.__parent.__count*100:100}}},function(t,e,r){const n=r(2),o=r(4),i=r(1),s=r(0);class a extends o{constructor(t){if(super(),this.gid=i.getUniqueId(),this.isGroup=!0,this.__proxy=t||null,this.__pool=new Map,t){this.concat(t);let e=new Set(["value","data","isDeep","toColorString"]);return new Proxy(this,{get:(r,n)=>e.has(n)?t[n]:r[n]})}}pushPool(t){if(t instanceof n)this.__pool.set(t.value,t),this.append(t);else{if(!t.isGroup)throw t;this.__pool.set(t.__proxy.value,t),this.append(t)}}concat(t,e){let r=this.__pool;if(t instanceof n){let e=r.get(t.value);e?e.plus(t.__count):this.pushPool(t)}else if(t.isGroup){let e=r.get(t.__proxy.value);e?e.plus(t.__count):this.pushPool(t)}else{let o=i.data2color(t),a=r.get(o);a?a.plus():(t=new n(t,s.color({...e,value:o}),this),this.pushPool(t))}return t}hasChild(t){return this.__pool.has(t)}get(t){return t instanceof n?this.__pool.get(t.value):Array.isArray(t)?this.__pool.get(i.data2color(t)):this.__pool.get(t)}createColorProxy(t={},e){if((e=e||this).__proxy)return this;let{colorStep:r=100}=t,o=new Set;e.sortList.forEach(t=>{let e=!0;o.forEach((o,s)=>{i.isSimilarColor(o.__proxy,t,r)&&e&&(e=!1,t=n.clone(t),o.concat(t,o))}),e&&(t=n.clone(t),o.add(new a(t)))});let s=new a;return o.forEach(t=>{s.concat(t)}),s}get proxy(){return this.__proxy}get list(){return Array.from(this.__pool.values())}get sortList(){return this.list.sort((t,e)=>e.__count-t.__count)}}t.exports=a},function(t,e,r){const n=r(7);t.exports=n},function(t,e,r){const n=r(8),o=r(9),i=r(2),s=r(5),a=r(1),l=r(0),u=(r(3),{async:!1,event:["click"],deepStep:192});class c extends n{constructor(t,e){if(super(),this.originColorData=null,this.colorData=null,this.option=l.main(Object.assign({},u,e||{})),this.__el=null,this.__cache={},this.option.async||"string"==typeof t)return this.asyncInit(t);if(!t.complete)throw Error("图片尚未加载完成");this.init(t)}asyncInit(t){let e=this;return new Promise((function(r,n){if("string"==typeof t){let o=t;(t=new Image).setAttribute("crossOrigin","anonymous"),t.onload=()=>{e.init(t),r(e)},t.onerror=()=>{n(new Error("图片加载失败"))},t.src=o}else e.init(t),r(e)}))}init(t){this.__el=t,this.getImageColor=c.getImageColor,this.getColorData(t),this.destory=c.destory,this.option.event&&this.bindEvent(this.option.event)}bindEvent(t=[]){let e=this,r=t.map(t=>{let r=function(r){var{offsetX:n,offsetY:o}=r;let i=e.getImageColor(n,o,e.__el);e.emit("color",{type:t,eventPosition:[n,o],color:i})};return this.__el.addEventListener(t,r),{type:t,callback:r}});this.__cache.event=r}unbindEvent(){let t=this;this.__cache.event&&this.__cache.event.forEach(({type:e,callback:r})=>{t.__el.removeEventListener(e,r)})}getColorData(t){let e=document.createElement("canvas"),{width:r,height:n}=t;e.width=r,e.height=n,this.__canvas=e;let i=e.getContext("2d");i.drawImage(t,0,0);let s=i.getImageData(0,0,r,n);this.originColorData=s,this.analyData=new o(s,a.paramsFilter.analy(this.option))}colorFilter_bate(t){return this.analyData.colorFilter_bate(t)}getColorGroup(){return this.analyData.colorGroup}getFrameColorGroup(t){return this.analyData.getFrameColorGroup(t)}static isDeep(t,e){return t instanceof i?e?a.isDeep(t,e):t.isDeep:t.reduce((t,r)=>t+((e?a.isDeep(r,e):r.isDeep)?r.percent:0),0)>50}static destory(t){(t=t||this).unbindEvent(),Object.keys(t.__cache).forEach(e=>{delete t.__cache[e]}),t.__cache=null,t.originColorData=null,t.option=null,t.__el=null,t.off(".inner"),t.analyData&&t.colorData.destory(),t.colorData=null}static getImageColor(t=0,e=0,r,n=!0){try{if(this instanceof c){let{width:o,height:i,naturalHeight:s,naturalWidth:a}=r;return n?(t=Math.round(t/o*a),e=Math.round(e/i*s)):(t=Math.round(t*o*.01),e=Math.round(e*i*.01)),this.analyData.getPositionColor(t,e)}{let{width:o,height:s,naturalHeight:a,naturalWidth:l}=r,u=document.createElement("canvas");u.width=o,u.height=s;let c=u.getContext("2d");c.drawImage(r,0,0,l,a,0,0,o,s),n||(t=Math.round(t*o*.01),e=Math.round(e*s*.01));let h=c.getImageData(t,e,1,1);return new i(h.data)}}catch(t){return console.warn("获取数据失败"),{}}}static isSimilarColor(t,e){return a.isSimilarColor(...arguments)}}c.Color=i,c.ColorGroup=s,t.exports=c},function(t,e,r){"use strict";var n,o="object"==typeof Reflect?Reflect:null,i=o&&"function"==typeof o.apply?o.apply:function(t,e,r){return Function.prototype.apply.call(t,e,r)};n=o&&"function"==typeof o.ownKeys?o.ownKeys:Object.getOwnPropertySymbols?function(t){return Object.getOwnPropertyNames(t).concat(Object.getOwnPropertySymbols(t))}:function(t){return Object.getOwnPropertyNames(t)};var s=Number.isNaN||function(t){return t!=t};function a(){a.init.call(this)}t.exports=a,t.exports.once=function(t,e){return new Promise((function(r,n){function o(){void 0!==i&&t.removeListener("error",i),r([].slice.call(arguments))}var i;"error"!==e&&(i=function(r){t.removeListener(e,o),n(r)},t.once("error",i)),t.once(e,o)}))},a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var l=10;function u(t){if("function"!=typeof t)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof t)}function c(t){return void 0===t._maxListeners?a.defaultMaxListeners:t._maxListeners}function h(t,e,r,n){var o,i,s,a;if(u(r),void 0===(i=t._events)?(i=t._events=Object.create(null),t._eventsCount=0):(void 0!==i.newListener&&(t.emit("newListener",e,r.listener?r.listener:r),i=t._events),s=i[e]),void 0===s)s=i[e]=r,++t._eventsCount;else if("function"==typeof s?s=i[e]=n?[r,s]:[s,r]:n?s.unshift(r):s.push(r),(o=c(t))>0&&s.length>o&&!s.warned){s.warned=!0;var l=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");l.name="MaxListenersExceededWarning",l.emitter=t,l.type=e,l.count=s.length,a=l,console&&console.warn&&console.warn(a)}return t}function p(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function f(t,e,r){var n={fired:!1,wrapFn:void 0,target:t,type:e,listener:r},o=p.bind(n);return o.listener=r,n.wrapFn=o,o}function d(t,e,r){var n=t._events;if(void 0===n)return[];var o=n[e];return void 0===o?[]:"function"==typeof o?r?[o.listener||o]:[o]:r?function(t){for(var e=new Array(t.length),r=0;r0&&(s=e[0]),s instanceof Error)throw s;var a=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw a.context=s,a}var l=o[t];if(void 0===l)return!1;if("function"==typeof l)i(l,this,e);else{var u=l.length,c=g(l,u);for(r=0;r=0;i--)if(r[i]===e||r[i].listener===e){s=r[i].listener,o=i;break}if(o<0)return this;0===o?r.shift():function(t,e){for(;e+1=0;n--)this.removeListener(t,e[n]);return this},a.prototype.listeners=function(t){return d(this,t,!0)},a.prototype.rawListeners=function(t){return d(this,t,!1)},a.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):_.call(t,e)},a.prototype.listenerCount=_,a.prototype.eventNames=function(){return this._eventsCount>0?n(this._events):[]}},function(t,e,r){const n=r(2),o=r(5),i=r(10),s=r(1),a=r(0),l=(r(3),{});t.exports=class{constructor(t,e={}){this.option=a.analy(Object.assign({},l,e)),this.originData=t;let{width:r,height:n}=t;this.width=r,this.height=n;let{colorGroup:o,translateData:i}=this.analyse(t);this.colorGroup=o,this.translateData=i}analyse(t){let{width:e,height:r,data:n}=t;return{translateData:{width:e,height:r,data:t=this.colorFormat(n,e,r)},colorGroup:this.createColorGroup(t)}}colorFormat(t,e,r){let n=[],o=0;for(t=Array.from(t);4*o<=t.length-4;){let s=t.slice(4*o,4*(o+1));n.push(new i(s,{index:o,width:e,height:r})),o++}return n}createColorGroup(t){let e=new o;return t.forEach(t=>{e.concat(t.data,s.paramsFilter.group({...this.option,value:t.colorName}))}),e}getFrameColorGroup(t={},e){let{width:r,height:n,translateData:o}=e||this,{size:i=.2}=Object.assign({},this.option,t),s=i*r,a=(1-i)*r,l=i*n,u=(1-i)*n,c=o.data.filter((t,e)=>{let n=e%r,o=Math.ceil(e/r);return(n<=s||n>=a)&&(o>=u||o<=l)});return this.createColorGroup(c)}getPositionColor(t,e,r){let{width:n,translateData:o}=r||this,i=o.data[e*n+t];return this.colorGroup.get(i.colorName)}colorFilter_bate(t){let{translateData:e,colorGroup:r}=this,{width:o,data:i}=e,{colorType:a}=this.option;if(t instanceof n)return i.filter(e=>e.colorName===t.value);if(t.isGroup){return t.__children.map(t=>this.colorFilter_bate(t)).reduce((t,e)=>t.concat(...e),[])}return Array.isArray(t)?i.filter(e=>e.colorName===s.data2color(t,a)):i.filter(e=>e.colorName===t)}destory(){this.option=null,this.originData=null,this.colorGroup=null,this.mainColor=null,this.width=null,this.height=null}}},function(t,e,r){const n=r(1),o=r(0),i={index:0,width:0,height:0,name:void 0};t.exports=class{constructor(t,e={}){this.option=o.point(Object.assign({},i,e));let{index:r,width:n,height:s,name:a}=this.option;this.data=t,this.index=r,this.width=n,this.height=s,this.__position=null,this.__colorName=a||null}get position(){return this.__position||(this.__position=n.index2position(this.index,this.width))}get colorName(){return this.__colorName||(this.__colorName=n.data2color(this.data))}}}])})); --------------------------------------------------------------------------------