huespace 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []