/
ColorThief.java
118 lines (108 loc) · 4.09 KB
/
ColorThief.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package mezz.jei.color;
/*
* Java Color Thief
* by Sven Woltmann, Fonpit AG
*
* http://www.androidpit.com
* http://www.androidpit.de
*
* License
* -------
* Creative Commons Attribution 2.5 License:
* http://creativecommons.org/licenses/by/2.5/
*
* Thanks
* ------
* Lokesh Dhakar - for the original Color Thief JavaScript version
* available at http://lokeshdhakar.com/projects/color-thief/
*/
import javax.annotation.Nullable;
import java.util.Arrays;
import net.minecraft.client.renderer.texture.NativeImage;
public class ColorThief {
/**
* Use the median cut algorithm to cluster similar colors.
*
* @param sourceImage the source image
* @param colorCount the size of the palette; the number of colors returned
* @param quality 0 is the highest quality settings. 10 is the default. There is
* a trade-off between quality and speed. The bigger the number,
* the faster the palette generation but the greater the
* likelihood that colors will be missed.
* @param ignoreWhite if <code>true</code>, white pixels are ignored
* @return the palette as array of RGB arrays
*/
@Nullable
public static int[][] getPalette(NativeImage sourceImage, int colorCount, int quality, boolean ignoreWhite) {
MMCQ.CMap cmap = getColorMap(sourceImage, colorCount, quality, ignoreWhite);
if (cmap == null) {
return null;
}
return cmap.palette();
}
/**
* Use the median cut algorithm to cluster similar colors.
*
* @param sourceImage the source image
* @param colorCount the size of the palette; the number of colors returned
* @param quality 0 is the highest quality settings. 10 is the default. There is
* a trade-off between quality and speed. The bigger the number,
* the faster the palette generation but the greater the
* likelihood that colors will be missed.
* @param ignoreWhite if <code>true</code>, white pixels are ignored
* @return the color map
*/
@Nullable
public static MMCQ.CMap getColorMap(NativeImage sourceImage, int colorCount, int quality, boolean ignoreWhite) {
if (sourceImage.format() == NativeImage.PixelFormat.RGBA) {
int[][] pixelArray = getPixels(sourceImage, quality, ignoreWhite);
// Send array to quantize function which clusters values using median
// cut algorithm
return MMCQ.quantize(pixelArray, colorCount);
}
return null;
}
/**
* Gets the image's pixels via BufferedImage.getRaster().getDataBuffer().
* Fast, but doesn't work for all color models.
*
* @param sourceImage the source image
* @param quality 1 is the highest quality settings. 10 is the default. There is
* a trade-off between quality and speed. The bigger the number,
* the faster the palette generation but the greater the
* likelihood that colors will be missed.
* @param ignoreWhite if <code>true</code>, white pixels are ignored
* @return an array of pixels (each an RGB int array)
*/
private static int[][] getPixels(NativeImage sourceImage, int quality, boolean ignoreWhite) {
int width = sourceImage.getWidth();
int height = sourceImage.getHeight();
int pixelCount = width * height;
// Store the RGB values in an array format suitable for quantize function
// numRegardedPixels must be rounded up to avoid an
// ArrayIndexOutOfBoundsException if all pixels are good.
int numRegardedPixels = (pixelCount + quality - 1) / quality;
int numUsedPixels = 0;
int[][] pixelArray = new int[numRegardedPixels][];
int i = 0;
while (i < pixelCount) {
int x = i % width;
int y = i / width;
int rgba = sourceImage.getPixelRGBA(x, y);
int a = rgba >> 24 & 255;
int b = rgba >> 16 & 255;
int g = rgba >> 8 & 255;
int r = rgba & 255;
// If pixel is mostly opaque and not white
if (a >= 125 && !(ignoreWhite && r > 250 && g > 250 && b > 250)) {
pixelArray[numUsedPixels] = new int[]{r, g, b};
numUsedPixels++;
i += quality;
} else {
i++;
}
}
// trim the array
return Arrays.copyOfRange(pixelArray, 0, numUsedPixels);
}
}