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 +7 -0
- data/lib/huespace/median_cut.rb +80 -0
- data/lib/huespace.rb +65 -0
- metadata +74 -0
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: []
|