Skip to content

Instantly share code, notes, and snippets.

@waffleau
Last active March 27, 2024 10:22
Show Gist options
  • Save waffleau/6e074573d4bece3f7a007ed8536512b5 to your computer and use it in GitHub Desktop.
Save waffleau/6e074573d4bece3f7a007ed8536512b5 to your computer and use it in GitHub Desktop.
Extract dominant colours from an image in Ruby using MiniMagick
def self.extract_dominant_colors(image_path, quantity=5, threshold=0.01)
image = MiniMagick::Image.open(image_path)
# Get image histogram
result = image.run_command('convert', image_path, '-format', '%c', '-colors', quantity, '-depth', 8, 'histogram:info:')
# Extract colors and frequencies from result
frequencies = result.scan(/([0-9]+)\:/).flatten.map { |m| m.to_f }
hex_values = result.scan(/(\#[0-9ABCDEF]{6,8})/).flatten
total_frequencies = frequencies.reduce(:+).to_f
# Create frequency/color pairs [frequency, hex],
# sort by frequency,
# ignore fully transparent colours
# select items over frequency threshold (1% by default),
# extract hex values,
# return desired quantity
frequencies
.map.with_index { |f, i| [f / total_frequencies, hex_values[i]] }
.sort { |r| r[0] }
.reject { |r| r[1].end_with?('FF') }
.select { |r| r[0] > threshold }
.map { |r| r[1][0..6] }
.slice(0, quantity)
end
@chriszo111
Copy link

You can change line 8 from

frequencies = result.scan(/([0-9]+)\:/).flatten.map { |m| m.to_f }

to

frequencies = result.scan(/([0-9]+)\:/).flatten.map(&:to_f)

@tylerpaige
Copy link

tylerpaige commented Dec 2, 2022

Thanks for this!

L20 seems to not sort correctly for me. I think it should be .sort { |a, b| b[0] - a[0] }

Also, if the image does not have any transparency, L21 would needlessly reject any color with a 100% blue channel. I think that line should be .reject { |r| r[1].size == 9 && r[1].ends_with?('FF') }

@chridubois
Copy link

Hello,

is it possible for you to explain how your line :

image.run_command('convert', image_path, '-format', '%c', '-colors', quantity, '-depth', 8, 'histogram:info:')

is working ?

I've used your amazing code for an app which work on images. But, your script is not grouping pixels which are almost same colors. Is it possible to change a config do to that ?

Thanks in advance !

@tylerpaige
Copy link

hey @chridubois,

That line creates a histogram, which is a way of analyzing the colors present in an image. You can read more about it here, but the short explanation of that line is that it returns a list that describes how many pixels in the image are a particular color.

The colors argument should do what you're asking for. If you ask for fewer colors, it will group pixels that are similar. If you ask for more colors, it will give a more detailed breakdown.

From my testing, it's unfortunately a bit hard to get things exactly right. For example, you might ask for 4 colors, and it will only return 3 — even though there are indeed 4 colors present in the image. I don't know enough about ImageMagick to understand why that would be. However I suspect there are ways to get around this issue — maybe you can posterize the image before getting a histogram.

Anyway, the tl;dr of it all is: adjust the quantity argument to be a smaller number.

@alexeyr-ci
Copy link

Shouldn't it reject values ending in 00 instead of FF?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment