Ok, I did this back in 2014 in java but since I reimplemented the and the algorithm in javascript this time. I thought of writing about how I found a nice and simple algorithm to extract prominent colors out of an image. app Problem: Given an image we want to extract 6 prominent colors from it. Definitions: Lab color is designed to approximate human vision. Euclidean distance in LAB color space approximates perceivable distance of human vision. LAB color space: It is unsupervised clustering algorithm that groups the unlabeled dataset into k different clusters. K-means: HSB(Hue Saturation Brightness) color space Algorithm: 1. Resize the image to a manageable size (100 x 100) 2. Convert individual pixels of colors to LAB color space 3. Calculate 6 * 4 = 24 k means clusters from pixels in LAB color space 4. Convert these 24 centroids to HSB color space and sort them by hue. 5. Divide them into groups of 4 and for each group pick 2 most saturated colors 6. From 2 colors pick the most bright color Here is the full code. kmeans ; Jimp ; Color ; { getProminentColors(image) { image.resize(Jimp.AUTO, ); data = ColorPicker._prepareDataForKmeans(image); time = .now(); ans = kmeans(data, , { : , : }); ans.centroids = ans.centroids.sort( c2.size - c1.size); kmeansColors = ans.centroids.map( { Color( ._labToHex(centroid.centroid)); }); ._getFinalColors(kmeansColors).map( { { : c.tohex() }; }); } _getFinalColors(kmeansColors) { kmeansColors.sort( ._toArray(c1.tohsv())[ ] < ._toArray(c2.tohsv())[ ] ); filteredColors = []; ( i = ; i < kmeansColors.length; i += ) { colorList = []; ( j = ; j < ; j++) { colorList.push(kmeansColors[i + j]); } colorList.sort( ._toArray(c1.tohsv())[ ] < ._toArray(c2.tohsv())[ ] ); filteredColors.push(colorList[colorList.length - ]); filteredColors.push(colorList[colorList.length - ]); } finalColors = []; ( i = ; i < filteredColors.length; i += ) { ( ._toArray(filteredColors[i].tohsv())[ ] > ._toArray(filteredColors[i + ].tohsv())[ ] ) { finalColors.push(filteredColors[i]); } { finalColors.push(filteredColors[i + ]); } } finalColors; } _labToHex(lab) { color = Color( + lab[ ] + + lab[ ] + + lab[ ] + ); color.tohex(); } _prepareDataForKmeans(image) { data = []; ( i = ; i < image.bitmap.width; i++) { ( j = ; j < image.bitmap.height; j++) { intColor = image.getPixelColor(i, j); hex = ._toHexColor(intColor); color = Color(hex); xyz = color.tolab(); xyz = xyz .substr( , xyz.length - ) .split( ) .map( (v)); data.push(xyz); } } data; } _toHexColor(intColor) { rgba = Jimp.intToRGBA(intColor); color = Color( + rgba.r + + rgba.g + + rgba.b + ); color.tohex(); } _toArray(color) { index = color.indexOf( ); color = color.substr(index + , color.length - index); color.split( ).map( (c)); } } import from "ml-kmeans" import from "jimp" import from "pigment/full" export default class ColorPicker static /* Jimp.RESIZE_NEAREST_NEIGHBOR; Jimp.RESIZE_BILINEAR; Jimp.RESIZE_BICUBIC; Jimp.RESIZE_HERMITE; Jimp.RESIZE_BEZIER; These does not work with first params. */ 100 let let Date let 24 initialization "random" maxIterations 20 ( ) => c1, c2 let => centroid return new this return this => c return color // original implementation in java: https://github.com/kamalkishor1991/croma/blob/master/src/main/java/org/numixproject/colorextractor/image/KMeansColorPicker.java static ( ) => c1, c2 this 0 this 0 let for let 0 4 let for let 0 4 ( ) => c1, c2 this 1 this 1 1 2 let for let 0 2 if this 2 this 1 2 else 1 return static let new "lab(" 0 ", " 1 ", " 2 ")" return static let for let 0 for let 0 let let this let new let // format: "xyz(19.78527130484015, 8.600439447528947, 95.19796416837329)" to double array of xyz 4 5 ", " => v parseFloat return static let // Need to optimize this once everything else starts working. TODO: let new "rgb(" ", " ", " ")" return static let "(" 1 return ", " => c parseFloat Javascript implementation Java implementation Try it out here - or on the Google https://croma.app Play Store Process and Learnings: It turns out that this gives us really good results and off-course I tried a lot of other methods before reaching to this algorithm. Here are some of the key properties which make this algorithm so good. 1. Converting to LAB color space - . I tried k-means with other color spaces like RGB but they did not yield good results as their Euclidean distance does not make much sense to human eye. This divides the color space of the image into an evenly distributed space based on human perception of colors 2. Resizing the image to avoid performance issues with k-means as it is a n^2 algorithm so reducing the number of points to execute it fast on mobile device is critical. 3. Getting 24 points and further sorting them based on HSB. I had fun developing this back is 2014 and learned a lot about color spaces and developing simple algorithm to solve problems. My biggest learning while developing this was Elegant solutions require a deep understanding of the problem space. Thanks for reading and happy coding. The app is so please consider contributing and making it better. open source