├── .editorconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── index.js
├── package.json
├── test.js
└── test
├── car.jpg
├── css
├── deep
│ └── sample.css
├── sample.css
└── url.css
├── img
└── girl.png
└── white.jpg
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.{json,yml}]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 |
3 | node_modules/
4 | npm-debug.log
5 |
6 | .travis.yml
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - stable
5 | - "8"
6 | - "6"
7 | - "4"
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## v0.2.0, v.0.2.1 - 20170929
6 |
7 | - Improve warning message when color is missing
8 | - Upgrade to PostCSS v6.0.11
9 | - Upgrade to AVA v0.22.0
10 | - Fix image path to be relative [#2](https://github.com/ismamz/postcss-get-color/issues/2)
11 | - Fix some typo issues
12 | - Add `test` folder and compress sample images
13 | - Break v0.12 of NodeJS
14 |
15 | ## v0.1.0 - 20160412
16 |
17 | - Initial release
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright 2017 Ismael Martínez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PostCSS Get Color [![Build Status][ci-img]][ci]
2 |
3 |
4 |
5 | > [PostCSS] plugin to get the prominent colors from an image
6 |
7 | The plugin uses [Vibrant.js] and the [node port](https://github.com/akfish/node-vibrant) (node-vibrant). [Vibrant.js] is a javascript port of the awesome [Palette class](https://developer.android.com/reference/android/support/v7/graphics/Palette.html) in the Android support library.
8 |
9 | [Vibrant.js]: https://github.com/jariz/vibrant.js/
10 | [PostCSS]: https://github.com/postcss/postcss
11 | [ci-img]: https://travis-ci.org/ismamz/postcss-get-color.svg
12 | [ci]: https://travis-ci.org/ismamz/postcss-get-color
13 |
14 | ## Material Design Example
15 |
16 |
17 |
18 |
19 | > Based on the [standards of material design](https://material.io/guidelines/style/color.html), the palette library extracts commonly used color profiles from an image. Each profile is defined by a Target, and colors extracted from the bitmap image are scored against each profile based on saturation, luminance, and population (number of pixels in the bitmap represented by the color). For each profile, the color with the best score defines that color profile for the given image.
20 |
21 | Source: [Android Developers - Extract Color Profiles](https://developer.android.com/training/material/palette-colors.html#extract-color-profiles)
22 |
23 | | Input | Output |
24 | |:-------------:|:-------------:|
25 | |
|
|
26 | | `color: get-color("../img/girl.png", Vibrant);` | `color: #e8ba3c;` |
27 |
28 |
29 | ---
30 |
31 |
32 | ### CSS Input
33 |
34 | ```css
35 | .foo {
36 | background-color: get-color("path/to/image.jpg", LightVibrant) url("path/to/image.jpg) no-repeat;
37 | }
38 |
39 | .bar {
40 | color: get-color("path/to/image.png");
41 | }
42 | ```
43 |
44 | ### CSS Output
45 |
46 | ```css
47 | .foo {
48 | background-color: #b9911b url("path/to/image.jpg") no-repeat;
49 | }
50 |
51 | .bar {
52 | color: #b9911b;
53 | }
54 | ```
55 |
56 | ## Features
57 |
58 | ```
59 | get-color(, [, ])
60 | ```
61 |
62 | **image-path** `string`: path to image relative to the CSS file (with quotes).
63 |
64 | **color-name** `string`: name (case sensitive) from the palette ([see available names](#vibrant-palette)).
_Default:_ first available color in the palette.
65 |
66 | **text-color** `[title|body]`: get the compatible foreground color.
67 |
68 |
69 | Use color format in `hex`, `rgb` or `rgba` ([see Options](#options)).
70 |
71 |
72 | ## Vibrant Palette
73 |
74 | See examples in [Vibrant.js Page](http://jariz.github.io/vibrant.js/).
75 |
76 | - Vibrant
77 | - DarkVibrant
78 | - LightVibrant
79 | - Muted
80 | - DarkMuted
81 | - LightMuted
82 |
83 | **Note:** colors are writing in `PascalCase`.
84 |
85 | You can get the title text color that works best with any **'title'** text that is used over this swatch's color, and the body text color that works best with any **'body'** text that is used over this swatch's color.
86 |
87 | #### After
88 |
89 | ```css
90 | .foo {
91 | color: get-color("path/to/image.jpg", LightVibrant, text);
92 | }
93 | ```
94 |
95 | #### Before
96 |
97 | ```css
98 | .foo {
99 | color: #000; /* or #fff */
100 | }
101 | ```
102 |
103 | ## Usage
104 |
105 | ### Quick usage
106 |
107 | Using [PostCSS CLI](https://github.com/postcss/postcss-cli) you can do the following:
108 |
109 | First, install `postcss-cli` and the plugin on your project folder:
110 |
111 | ```
112 | $ npm install postcss-cli postcss-get-color --save-dev
113 | ```
114 |
115 | And finally add this script to your `package.json`:
116 |
117 | ```json
118 | "scripts": {
119 | "postcss": "postcss input.css -u postcss-get-color -o output.css -w"
120 | }
121 | ```
122 |
123 | After this you can run `npm run postcss` and transform your `input.css` into `output.css`. Note that `-w` is for observe file system changes and recompile as source files change.
124 |
125 | ### For tasks runners and others enviroments
126 |
127 | ```js
128 | postcss([ require('postcss-get-color')({ /* options*/ }) ])
129 | ```
130 |
131 | See [PostCSS] docs for examples of your environment.
132 |
133 | ## Options
134 |
135 | ##### `format`
136 |
137 | Type: `string`
138 |
139 | Default: `hex`
140 |
141 | Select the color format between: `hex`, `rgb`, `rgba`.
142 |
143 | ## Contributing
144 |
145 | If you want to improve the plugin, [send a pull request](https://github.com/ismamz/postcss-get-color/pull/new/master) ;-)
146 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var postcss = require('postcss');
3 | var parser = require('postcss-value-parser');
4 | var vibrant = require('node-vibrant');
5 |
6 | var colorNames = [
7 | 'Vibrant',
8 | 'Muted',
9 | 'DarkVibrant',
10 | 'DarkMuted',
11 | 'LightVibrant',
12 | 'LightMuted'
13 | ];
14 |
15 | function isUrl(string) {
16 | var matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/;
17 | return matcher.test(string);
18 | }
19 |
20 | function getAvailable(palette) {
21 | var i = 0;
22 | var availableColors = [];
23 | while (i < colorNames.length) {
24 | if (palette[colorNames[i]] !== null) {
25 | availableColors.push({
26 | colorName: colorNames[i],
27 | colorValue: palette[colorNames[i]].getHex()
28 | });
29 | }
30 | i++;
31 | }
32 | return availableColors;
33 | }
34 |
35 | function formatColor(format, palette, name) {
36 | var color, colorRgb;
37 | switch (format) {
38 | case 'hex':
39 | color = palette[name].getHex();
40 | break;
41 | case 'rgb':
42 | colorRgb = palette[name].getRgb();
43 | color = 'rgb(' + colorRgb[0] + ', ' +
44 | colorRgb[1] + ', ' + colorRgb[2] + ')';
45 | break;
46 | case 'rgba':
47 | colorRgb = palette[name].getRgb();
48 | color = 'rgba(' + colorRgb[0] + ', ' +
49 | colorRgb[1] + ', ' + colorRgb[2] + ', 1)';
50 | break;
51 | default:
52 | color = palette[name].getHex();
53 | break;
54 | }
55 | return color;
56 | }
57 |
58 | function getTextColor(palette, name, text) {
59 | var color;
60 | switch (text) {
61 | case 'title':
62 | color = palette[name].getTitleTextColor();
63 | break;
64 | case 'body':
65 | color = palette[name].getBodyTextColor();
66 | break;
67 | default:
68 | return false;
69 | }
70 | return color;
71 | }
72 |
73 | function procDecl(image, name, decl, nodes, text, format, result) {
74 | return new Promise(function (resolve, reject) {
75 | var imagePath;
76 |
77 | if (isUrl(image) || path.isAbsolute(image) || !decl.source.input.file) {
78 | imagePath = image;
79 | } else {
80 | imagePath = path.resolve(path.dirname(decl.source.input.file), image);
81 | }
82 |
83 | vibrant.from(imagePath).getPalette(function (err, palette) {
84 | var color;
85 |
86 | if (!err) {
87 | if (!(name in palette) || palette[name] !== null ) {
88 |
89 | if (text === 'title' || text === 'body') {
90 | color = getTextColor(palette, name, text);
91 | } else {
92 | color = formatColor(format, palette, name);
93 | }
94 |
95 | } else {
96 | var availableColors = getAvailable(palette);
97 | var newName = availableColors[0].colorName;
98 |
99 | if (text === 'title' || text === 'body') {
100 | color = getTextColor(palette, newName, text);
101 | } else {
102 | color = formatColor(format, palette, newName);
103 | }
104 |
105 | // Warning: color is not in the palette
106 | var availableColorsNames = availableColors.map(function (c) {
107 | return c.colorName;
108 | });
109 |
110 | decl.warn(
111 | result, name + ' isn\'t available. ' +
112 | 'First available color was used: ' +
113 | availableColorsNames.join(', ')
114 | );
115 | }
116 |
117 | nodes.walk(function (node) {
118 | if (node.type === 'function' &&
119 | node.value === 'get-color') {
120 | node.type = 'word';
121 | node.value = color;
122 | node.nodes = null;
123 | }
124 | });
125 |
126 | decl.value = nodes.toString();
127 | resolve(color);
128 | } else {
129 | reject(err);
130 | throw decl.error('Problem with "' + image +
131 | '" file.', { plugin: 'postcss-get-color' });
132 | }
133 | });
134 | });
135 | }
136 |
137 | module.exports = postcss.plugin('postcss-get-color', function (opts) {
138 | return function (css, result) {
139 | opts = opts || {};
140 |
141 | var format = 'format' in opts ? opts.format : 'hex'; // rgb or rgba
142 |
143 | const promises = [];
144 |
145 | css.walkRules(function (rule) {
146 | rule.walkDecls(function (decl) {
147 | var value = decl.value;
148 |
149 | var name, image, text;
150 |
151 | var str = parser(value);
152 | str.walk(function (node) {
153 | if (node.type === 'function' &&
154 | node.value === 'get-color') {
155 |
156 | // Get the image path
157 | if (node.nodes[0].type === 'string') {
158 | image = node.nodes[0].value;
159 | } else {
160 | throw decl.error('Missed quotes in first argument');
161 | }
162 |
163 | // Check if has 2 args and get the nameColor
164 | if (node.nodes.length > 2 &&
165 | node.nodes[2].type === 'word') {
166 | name = node.nodes[2].value;
167 | } else {
168 | name = 'Vibrant';
169 | }
170 |
171 | // Text Color as third arg
172 | if (node.nodes.length === 5 &&
173 | node.nodes[4].type === 'word') {
174 |
175 | text = node.nodes[4].value;
176 |
177 | if (text !== 'title' && text !== 'body') {
178 | throw decl.error('Invalid text color ' + name);
179 | }
180 | } else {
181 | text = false;
182 | }
183 |
184 | // Check if is a valid name color before process
185 | if (colorNames.indexOf(name) > -1) {
186 | promises.push(
187 | procDecl(image, name, decl, str, text, format, result)
188 | );
189 | } else {
190 | throw decl.error('Unknown name color: ' + name);
191 | }
192 |
193 | }
194 | });
195 | });
196 | });
197 |
198 | return Promise.all(promises).then(
199 | function (response) {
200 | console.log('\n\npostcss-get-color processed ' + response.length + ' images\n');
201 | },
202 | function (err) {
203 | throw err;
204 | }
205 | );
206 | };
207 | });
208 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postcss-get-color",
3 | "version": "0.2.0",
4 | "description": "PostCSS plugin to get the prominent colors from an image",
5 | "keywords": [
6 | "postcss",
7 | "css",
8 | "postcss-plugin",
9 | "color",
10 | "dominant",
11 | "background-color"
12 | ],
13 | "author": "ismamz ",
14 | "license": "MIT",
15 | "repository": "ismamz/postcss-get-color",
16 | "bugs": {
17 | "url": "https://github.com/ismamz/postcss-get-color/issues"
18 | },
19 | "homepage": "https://github.com/ismamz/postcss-get-color",
20 | "dependencies": {
21 | "node-vibrant": "^2.1.2",
22 | "path": "^0.12.7",
23 | "postcss": "^6.0.11",
24 | "postcss-value-parser": "^3.3.0"
25 | },
26 | "devDependencies": {
27 | "ava": "^0.22.0",
28 | "eslint": "^1.10.3",
29 | "eslint-config-postcss": "^1.0.0",
30 | "fs": "0.0.1-security"
31 | },
32 | "scripts": {
33 | "test": "ava && eslint *.js"
34 | },
35 | "eslintConfig": {
36 | "extends": "postcss/es5",
37 | "rules": {
38 | "max-len": [
39 | 2,
40 | 100
41 | ]
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss';
2 | import test from 'ava';
3 | import fs from 'fs';
4 |
5 | import plugin from './';
6 |
7 | function run(t, input, output, opts = { }) {
8 | return postcss([ plugin(opts) ]).process(input)
9 | .then( result => {
10 | t.deepEqual(result.css, output);
11 | t.deepEqual(result.warnings().length, 0);
12 | });
13 | }
14 |
15 | function runFile(t, input, output, opts = { }) {
16 | return postcss([ plugin(opts) ]).process(fs.readFileSync(input), { from: input })
17 | .then( result => {
18 | t.deepEqual(result.css, output);
19 | t.deepEqual(result.warnings().length, 0);
20 | });
21 | }
22 |
23 | function runWithWarn(t, input, output, opts = { }) {
24 | return postcss([ plugin(opts) ]).process(input)
25 | .then( result => {
26 | t.deepEqual(result.css, output);
27 | t.deepEqual(result.warnings().length, 1);
28 | });
29 | }
30 |
31 | test('Always pass', t => {
32 | return run(t, 'a { }',
33 | 'a { }', { });
34 | });
35 |
36 | test('LightMuted twice', t => {
37 | return run(t, 'a { background: get-color("test/car.jpg", LightMuted); ' +
38 | 'color: get-color("test/white.jpg", LightMuted); }',
39 | 'a { background: #e1e1e1; color: #fcfcfc; }', { });
40 | });
41 |
42 | test('Vibrant by arg', t => {
43 | return run(t, 'a { background: get-color("test/img/girl.png", Vibrant); }',
44 | 'a { background: #e8ba3c; }', { });
45 | });
46 |
47 | var url = 'http://jariz.github.io/vibrant.js/examples/2.jpg';
48 | test('Vibrant by Url', t => {
49 | return run(t, 'a { background: get-color("' + url + '", Vibrant); }',
50 | 'a { background: #b9911b; }', { });
51 | });
52 |
53 | test('Title color', t => {
54 | return run(t, 'a { background: get-color("test/car.jpg", Vibrant, title); }',
55 | 'a { background: #fff; }', { });
56 | });
57 |
58 | test('Body color with warning', t => {
59 | return runWithWarn(t, 'a { background: get-color("test/white.jpg", LightVibrant, body); }',
60 | 'a { background: #000; }', { });
61 | });
62 |
63 | test('Body color without warning', t => {
64 | return run(t, 'a { background: get-color("test/white.jpg", LightMuted, body); }',
65 | 'a { background: #000; }', { });
66 | });
67 |
68 | test('Vibrant by default', t => {
69 | return run(t, 'a { background: get-color("test/car.jpg"); }',
70 | 'a { background: #b79022; }', { });
71 | });
72 |
73 | test('Works with single quotes', t => {
74 | return run(t, 'a { background: get-color(\'test/car.jpg\'); }',
75 | 'a { background: #b79022; }', { });
76 | });
77 |
78 | test('White image take LighMuted by default', t => {
79 | return runWithWarn(t, 'a { background: get-color("test/white.jpg")' +
80 | ' url("car.jpg") no-repeat; }',
81 | 'a { background: #fcfcfc url("car.jpg") no-repeat; }', { });
82 | });
83 |
84 | test('Multiple value', t => {
85 | return runWithWarn(t, 'a { background: get-color("test/white.jpg")' +
86 | ' url("car.jpg") no-repeat; }',
87 | 'a { background: #fcfcfc url("car.jpg") no-repeat; }', { });
88 | });
89 |
90 | test('Relative path in file', t => {
91 | return runFile(t, './test/css/sample.css',
92 | '.bg { background: url("../img/girl.jpg") #e8ba3c; }\n', { });
93 | });
94 |
95 | test('Url in file', t => {
96 | return runFile(t, './test/css/url.css',
97 | '.url { color: #b9911b; }\n', { });
98 | });
99 |
100 | test('Relative path for deep file', t => {
101 | return runFile(t, './test/css/deep/sample.css',
102 | '.bg { background: url("../../img/girl.jpg") #e8ba3c; }\n', { });
103 | });
104 |
105 |
--------------------------------------------------------------------------------
/test/car.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ismamz/postcss-get-color/89149e1b7a3d0d346c5f46678c7324e2adaba8df/test/car.jpg
--------------------------------------------------------------------------------
/test/css/deep/sample.css:
--------------------------------------------------------------------------------
1 | .bg { background: url("../../img/girl.jpg") get-color("../../img/girl.png", Vibrant); }
2 |
--------------------------------------------------------------------------------
/test/css/sample.css:
--------------------------------------------------------------------------------
1 | .bg { background: url("../img/girl.jpg") get-color("../img/girl.png", Vibrant); }
2 |
--------------------------------------------------------------------------------
/test/css/url.css:
--------------------------------------------------------------------------------
1 | .url { color: get-color("http://jariz.github.io/vibrant.js/examples/2.jpg"); }
2 |
--------------------------------------------------------------------------------
/test/img/girl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ismamz/postcss-get-color/89149e1b7a3d0d346c5f46678c7324e2adaba8df/test/img/girl.png
--------------------------------------------------------------------------------
/test/white.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ismamz/postcss-get-color/89149e1b7a3d0d346c5f46678c7324e2adaba8df/test/white.jpg
--------------------------------------------------------------------------------