Skip to content

Commit a4fe327

Browse files
authored
Merge pull request #1 from jwagner/color-extraction-experiment
Improved getPalette
2 parents 79b7f1d + 4c3e2d7 commit a4fe327

40 files changed

+3896
-292
lines changed

.eslintrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
},
88
"rules": {
99
"no-console": "off",
10+
"no-continue": "off",
1011
"no-plusplus": "off",
1112
"no-bitwise": "off",
1213
"no-use-before-define": "off",
@@ -24,6 +25,12 @@
2425
"rules": {
2526
"import/no-extraneous-dependencies": "off"
2627
}
28+
},
29+
{
30+
"files": "src/test-helpers.ts",
31+
"rules": {
32+
"import/no-extraneous-dependencies": "off"
33+
}
2734
}
2835
]
2936
}

README.md

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,19 @@ Dont-crop is a [small](#performance), dependency free javascript library to fit
88

99
It can be used to pad images instead of cropping them, for a very compact [blur up](https://engineering.fb.com/2015/08/06/android/the-technology-behind-preview-photos/) and what ever else you can come up with.
1010

11-
![lead image](docs/lead-lossless.webp)
11+
## Examples
12+
13+
### fitGradient()
14+
![fitGradient](docs/fitGradient.webp)
1215
Photo by [Abed Ismail](https://unsplash.com/photos/fZXZ1-hbFrY)
1316

17+
### getPalette()
18+
![getPalette](docs/getPalette.webp)
19+
20+
### More Examples
21+
22+
View the [demo page](https://29a.ch/sandbox/2021/dont-crop/) to see more examples and experiment with your own images.
23+
1424
## Installation
1525
```
1626
npm install -S dont-crop
@@ -68,29 +78,48 @@ It has been tested in:
6878
The code is reasonably compact and built with tree shaking in mind.
6979
So your bundles will only include the features you actually use.
7080

71-
When using `fitGradient` only and bundling your code using webpack 5 dont-crop will add about **1.2 kb** to your bundle size.
72-
`getPalette` will cost you a bit more than **1.4 kb**.
73-
You can use both for about **2.1 kb**.
81+
When using `fitGradient` only and bundling your code using webpack 5 dont-crop will add about **1.2 kb** (0.7 gzipped) to your bundle size.
82+
`getPalette` will cost you a bit more than **3.2 kb** (1.7 gzipped).
83+
You can use both for about **4 kb** (2 gzipped).
84+
85+
```
86+
3925 dist/both.js
87+
1911 dist/both.js.gz
88+
1264 dist/fitGradient.js
89+
710 dist/fitGradient.js.gz
90+
3261 dist/getPalette.js
91+
1656 dist/getPalette.js.gz
92+
```
7493

7594
Runtime performance is also fast enough not to worry about.
7695

7796
```
7897
# on a AMD Ryzen 9 5950X
79-
fitGradientToImageData x 43,559 ops/sec ±0.40% (97 runs sampled)
80-
getPaletteFromImageData x 5,420 ops/sec ±0.21% (92 runs sampled)
98+
fitGradientToImageData x 19,813 ops/sec ±0.96% (97 runs sampled)
99+
getPaletteFromImageData(fast=false) x 156 ops/sec ±0.66% (83 runs sampled)
100+
getPaletteFromImageData(fast=true) x 645 ops/sec ±0.17% (97 runs sampled)
81101
```
82102

83103
The versions of the functions operating on images rather than the already downscaled image data are slower.
84-
Their performance depends on the exact browser and device in question as well but it should generally be in the ballpark of few milliseconds for reasonably sized images.
104+
Their performance depends on the exact browser and device in question as well but it should generally be in the ballpark of a few milliseconds for reasonably sized images.
85105

86106
## Test Coverage
87107

88108
The code is well covered in tests. The examples are used as end to end tests in both node and a browser (chrome via puppeteer).
89109

110+
90111
## Algorithms
91112

92113
Glad you asked. `fitGradient()` is using simple [linear regression](https://en.wikipedia.org/wiki/Linear_regression).
93-
`getPallete()` is based on [k-means](https://en.wikipedia.org/wiki/K-means_clustering).
114+
115+
`getPalete()` is based on [k-means](https://en.wikipedia.org/wiki/K-means_clustering).
116+
The initial clusters are chosen using a histogram.
117+
Similar clusters in the result are merged in a post processing step.
118+
This is necessary because k-means tends to return equally sized clusters
119+
whereas getPalette is supposed to return distinct clusters.
120+
The merging is tuned to preserve different hues and colors rather than returning the most prominent shades of color (which might all share a similar hue).
121+
The processing happens in the CIE Lab color space using CIE76 ΔE*.
122+
94123

95124
## Alternatives
96125

@@ -107,6 +136,11 @@ Provides similar functionality to getPalette.
107136
It weighs in at about 6.4k (version 2.3.2).
108137
It's been widely used since 2019 so it is definitely more battle proofen.
109138
From a quick looks it seems to be using median-cut which will likely yield a bit better results than the simplistic k-means used here.
139+
140+
### [fast-average-color](https://github.com/fast-average-color/fast-average-color)
141+
142+
Returns a single average or dominant color color.
143+
110144

111145
### [smartcrop.js](https://github.com/jwagner/smartcrop.js)
112146

@@ -117,13 +151,11 @@ to find smarter crops.
117151

118152
There are plenty of interesting ways to improve this library further.
119153

120-
* Adding creative controls over colors (max saturation, max lightness, min lightness)
154+
* Grouping of colors (saturated, muted, light, dark, warm, cold)
155+
* Tuning of the variables involved in palette extraction potentially allowing some degree of tweaking by the user of the library
121156
* Weighting the linear-regression and k-means to focus on the center or edges
122157
* Using a more robust regression variation like Theil-Senn
123158
* Gamma corrected linear gradients by manually interpolating the stops
124-
* Something better than straight k-means for palette extraction
125-
126-
All of these would of course add complexity (and size) as well, so for now it's the simplest thing that could possibly work.
127159

128160
## License
129161

docs/fitGradient.webp

33 KB
Loading

docs/getPalette.webp

80.7 KB
Loading
Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,92 @@
11
[
22
[
3-
"#928f82",
4-
"#c0cccf",
5-
"#30313a",
6-
"#57645f"
3+
"#a5977d",
4+
"#cad5d9",
5+
"#55695e",
6+
"#594447"
77
],
88
[
9-
"#1a160e",
10-
"#4f3621",
11-
"#90542a",
12-
"#a77f60"
9+
"#aa693a",
10+
"#663415",
11+
"#504937",
12+
"#888173"
1313
],
1414
[
15-
"#825b35",
16-
"#a07a4a",
17-
"#aca699",
18-
"#72858c"
15+
"#a37c49",
16+
"#ae977a",
17+
"#a2afb5",
18+
"#547c8e"
1919
],
2020
[
21-
"#4d7595",
22-
"#6c8aa4",
23-
"#92a0b2",
24-
"#ac9e90"
21+
"#4e7696",
22+
"#93a1b3",
23+
"#ad9780",
24+
"#e0dacc"
2525
],
2626
[
27-
"#0b1c3a",
28-
"#547683",
29-
"#4c4f53",
30-
"#95a6a6"
27+
"#0d2954",
28+
"#488a90",
29+
"#93826a",
30+
"#38281f"
3131
],
3232
[
33-
"#13120a",
34-
"#2e2617",
35-
"#564226",
36-
"#85622e"
33+
"#90672a",
34+
"#5a4322",
35+
"#6b5a4e",
36+
"#13110a"
3737
],
3838
[
39-
"#9a8050",
40-
"#b89c74",
41-
"#716e32",
42-
"#464b26"
39+
"#a79749",
40+
"#5a6e22",
41+
"#bda487",
42+
"#343022"
4343
],
4444
[
45+
"#bf8a66",
46+
"#a47453",
4547
"#ebeae9",
46-
"#ad7b59",
47-
"#c28f6e",
48-
"#9a6d4d"
48+
"#b49580"
4949
],
5050
[
51-
"#141619",
52-
"#443f3b",
53-
"#7d7363",
54-
"#c9cec2"
51+
"#b07945",
52+
"#dde4d9",
53+
"#c1a784",
54+
"#111519"
5555
],
5656
[
57-
"#d8c7d7",
58-
"#9595b4",
59-
"#116a99",
60-
"#312d2c"
57+
"#136ea0",
58+
"#c5a3be",
59+
"#eae3ec",
60+
"#463c37"
6161
],
6262
[
63-
"#8ab0b4",
64-
"#222517",
65-
"#41432c",
66-
"#64766c"
63+
"#8eb4b9",
64+
"#484931",
65+
"#998d77",
66+
"#59726c"
6767
],
6868
[
69-
"#131416",
70-
"#313741",
71-
"#697083",
72-
"#a0a7be"
69+
"#a1a7bf",
70+
"#747c90",
71+
"#303442",
72+
"#111114"
7373
],
7474
[
75-
"#a9acad",
76-
"#522215",
77-
"#8e6b53",
78-
"#3f4b52"
75+
"#b2783f",
76+
"#a9b0b3",
77+
"#436377",
78+
"#04151b"
7979
],
8080
[
81-
"#cbc3c3",
82-
"#e6e2e3",
83-
"#423c39",
84-
"#91847f"
81+
"#ad6536",
82+
"#d7d1d2",
83+
"#b99c8f",
84+
"#433937"
8585
],
8686
[
87-
"#573a0e",
88-
"#88a3a7",
89-
"#ad7c20",
90-
"#707a6d"
87+
"#e89e10",
88+
"#6b640c",
89+
"#7badb8",
90+
"#4e664b"
9191
]
9292
]

examples/testsuite/index.css

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,35 @@ input, select {
5959
height: 100%;
6060
}
6161

62+
.visualization {
63+
position: absolute;
64+
z-index: 10;
65+
top: 0em;
66+
right: 0;
67+
/* transform: scale(0.5); */
68+
/* transform-origin: 100% 0%; */
69+
transition: opacity 0.2s ease-out;
70+
opacity: 0;
71+
}
72+
73+
.frame:hover .visualization {
74+
opacity: 1.0;
75+
}
76+
6277
.colors {
6378
display: inline-block;
6479
margin-right: 1ex;
65-
border: 1px solid white;
66-
height: 1em;
80+
height: 2em;
81+
vertical-align: bottom;
6782
}
6883

6984
.color {
7085
display: inline-block;
71-
width: 1em;
72-
height: 1em;
86+
width: 2em;
87+
height: 2em;
88+
border: 2px solid white;
89+
border-radius: 100%;
90+
margin-right: 1ex;
7391
}
7492

7593
.dropzone {
@@ -95,6 +113,9 @@ input, select {
95113
box-sizing: border-box;
96114
display: flex;
97115
align-items: center;
98-
justify-content: center;
99-
116+
justify-content: center;
117+
}
118+
119+
.info {
120+
margin: 1em 0;
100121
}

examples/testsuite/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ <h2>Test it with our own image</h2>
2222
<span class="note">Drop an image onto this frame or click it to select one.</span>
2323
<input class="dropzone" type="file" />
2424
</div>
25-
<p class="info">
26-
<div class="colors"></div>
27-
</p>
25+
<div class="info">
26+
<div class="colors"></div>
27+
</div>
2828
</div>
2929
<h2>Or a few test images</h2>
3030
<%= htmlWebpackPlugin.tags.bodyTags %>

0 commit comments

Comments
 (0)