huespace 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6422aedbcf443c23efb849ff2543a1872f120a82d2e68d7a8fc34b0ee4f518f3
4
+ data.tar.gz: 2353233ed144e5283f833b8e9c7512999a1655c70881d6bac7d13f8ce33743ea
5
+ SHA512:
6
+ metadata.gz: 50c80164bc380eea51cd27f0798930988334333e327da22abcc639b1711d2eeb89cf40e1033f1da2fcaa90ea01341b9fc0f7e587e54175332c02d34a401a9053
7
+ data.tar.gz: 726acd864d746d4d64176f07f8ead52ac69c2d647fab10340803e095ba42ab917ac807e3c4d869d735d8acc337375efb57f81416f3f31b10a6c4c6061faf7c47
@@ -0,0 +1,80 @@
1
+ module Huespace
2
+ class MedianCut
3
+ SplitInfo = Struct.new(:range, :group_index, :color_index, keyword_init: true)
4
+
5
+ def self.process(colors, count, hist)
6
+ colors = colors.uniq # Remove duplicate colors
7
+ groups = [colors]
8
+ limit = [count, colors.size].min
9
+
10
+ loop do
11
+ break if groups.size >= limit
12
+
13
+ split_info = determine_split(groups)
14
+ group1, group2 = split_group(groups[split_info.group_index], split_info)
15
+ groups.delete_at(split_info.group_index) # Remove group that we split by
16
+ groups << group1 unless group1.empty?
17
+ groups << group2 unless group2.empty?
18
+ end
19
+
20
+ palette = []
21
+ groups.sort_by! { |group| -calc_sort_score(group, hist) }
22
+ groups.each do |group|
23
+ palette << average_color(group)
24
+ end
25
+
26
+ palette[0...count]
27
+ end
28
+
29
+ private
30
+
31
+ def self.determine_split(groups)
32
+ stats = []
33
+
34
+ groups.each_with_index do |group, index|
35
+ reds = group.map { |el| el[0] }
36
+ greens = group.map { |el| el[1] }
37
+ blues = group.map { |el| el[2] }
38
+
39
+ ranges = []
40
+ ranges << SplitInfo.new(group_index: index, range: reds.max - reds.min, color_index: 0)
41
+ ranges << SplitInfo.new(group_index: index, range: greens.max - greens.min, color_index: 1)
42
+ ranges << SplitInfo.new(group_index: index, range: blues.max - blues.min, color_index: 2)
43
+
44
+ stats << ranges.max_by(&:range)
45
+ end
46
+
47
+ stats.max_by(&:range)
48
+ end
49
+
50
+ def self.split_group(group, split_info)
51
+ colors = group.sort_by { |pixel| pixel[split_info.color_index] }
52
+
53
+ median_index = colors.size / 2
54
+
55
+ group1 = colors[0..(median_index - 1)]
56
+ group2 = colors[median_index..-1]
57
+
58
+ [group1, group2]
59
+ end
60
+
61
+ # Score for sorting colors by dominance
62
+ # For each group we calculate the sum of how many of each pixel from the group was in the original image
63
+ def self.calc_sort_score(group, hist)
64
+ score = 0
65
+ group.each do |pixel|
66
+ score += hist[Huespace.get_pixel_index(pixel)]
67
+ end
68
+
69
+ score
70
+ end
71
+
72
+ def self.average_color(colors)
73
+ average_r = colors.map { |pixel| pixel[0]}.sum() / colors.size()
74
+ average_g = colors.map { |pixel| pixel[1]}.sum() / colors.size()
75
+ average_b = colors.map { |pixel| pixel[2]}.sum() / colors.size()
76
+
77
+ [average_r, average_g, average_b]
78
+ end
79
+ end
80
+ end
data/lib/huespace.rb ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Huespace
4
+ require "mini_magick"
5
+ require "huespace/median_cut.rb"
6
+
7
+ # Returns a palette of representative colors
8
+ def Huespace.get_palette(image_source, n_colors)
9
+ pixels = Huespace.load_image(image_source)
10
+
11
+ sampled_pixels, hist = Huespace.sample_pixels(pixels)
12
+ Huespace::MedianCut.process(sampled_pixels, n_colors, hist)
13
+ end
14
+
15
+ # Returns most colorful color
16
+ # This is achieved by sorting the palette using the following formula:
17
+ # (max + min) * (max - min)) / max
18
+ # max and min represent one of the rgb values
19
+ # More about this here: http://changingminds.org/explanations/perception/visual/colourfulness.htm
20
+ def Huespace.get_most_colorful_color(image_source)
21
+ colors = Huespace.get_palette(image_source, 6)
22
+
23
+ return colors.sort_by { |color| ((color.max + color.min) * (color.max - color.min)) / color.max }.last()
24
+ end
25
+
26
+ # Returns the dominant color
27
+ def Huespace.get_dominant_color(image_source)
28
+ colors = Huespace.get_palette(image_source, 5)
29
+
30
+ return colors[0]
31
+ end
32
+
33
+ private
34
+ def Huespace.load_image(image_source)
35
+ begin
36
+ image = MiniMagick::Image.open(image_source)
37
+ rescue TypeError
38
+ # Extract from stream
39
+ image = MiniMagick::Image.read(image_source) #
40
+ rescue StandardError => e
41
+ raise "Invalid URL!"
42
+ end
43
+
44
+ image.get_pixels.flatten(1)
45
+ end
46
+
47
+ # Quality determines the step between chosen pixels
48
+ # Higher number = Lower quality
49
+ def Huespace.sample_pixels(pixels, quality=10)
50
+ sampled_pixels = [];
51
+ hist = Hash.new(0)
52
+
53
+ (0..pixels.length - 1).step(quality).each do |i|
54
+ sampled_pixels << pixels[i] unless (pixels[i][0] > 250 && pixels[i][1] > 250 && pixels[i][2] > 250) # Skip white pixels
55
+ hist[get_pixel_index(pixels[i])] += 1
56
+ end
57
+
58
+ [sampled_pixels, hist]
59
+ end
60
+
61
+ # Determines the index for the histogram
62
+ def Huespace.get_pixel_index(pixel)
63
+ return pixel[0] << 10 + pixel[1] << 5 + pixel[2]
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: huespace
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Dino Tognon, Ivan Božić
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mini_magick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.11.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.11.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.2'
41
+ description: "Huespace can extract a color palette from local images, url-s or images
42
+ as byte-streams. \n Besides palettes, it can also extract the
43
+ most dominant color and the most colorful color!"
44
+ email: dino.tognon@arsfutura.co
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - lib/huespace.rb
50
+ - lib/huespace/median_cut.rb
51
+ homepage:
52
+ licenses:
53
+ - MIT
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 2.5.7
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubygems_version: 3.0.9
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Extract a color palette from any image
74
+ test_files: []