camalian 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/README.md +38 -5
- data/lib/camalian.rb +3 -1
- data/lib/camalian/color.rb +20 -9
- data/lib/camalian/image.rb +1 -1
- data/lib/camalian/palette.rb +4 -0
- data/lib/camalian/quantization/histogram.rb +1 -1
- data/lib/camalian/quantization/k_means.rb +59 -0
- data/lib/camalian/version.rb +1 -1
- data/test/test_helper.rb +4 -0
- data/test/test_quantization.rb +47 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01c8d4bae81e85284d554a538c52e90741f0db58ddba1d6d9e921761cf041d91
|
4
|
+
data.tar.gz: c5a301ed8505a08e31ed1c65cc4e5c8b0b1ccc832fad6e8279cb279f4750dadd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0418f17dfb07d45db856e30e9b333b532be7a38045aa4be3aae32192e62de4c803837a6730df08a797f3c662bd9a3fc0f21a5e780edfcf8545b3c085440af2d0'
|
7
|
+
data.tar.gz: 0e1144ff5d5d9a8faef915c23518759b98bebd6efdb6a4b39ea7a11ecd67d63e07847b05982853a20b0a719a855d16a1dcba45f28ac97383478a319ef9281488
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -10,7 +10,7 @@ Ruby gem to extract color palettes from images and play with their saturation
|
|
10
10
|
|
11
11
|
Add this line to your application's Gemfile:
|
12
12
|
|
13
|
-
gem 'camalian', '~> 0.1.
|
13
|
+
gem 'camalian', '~> 0.1.1'
|
14
14
|
|
15
15
|
And then execute:
|
16
16
|
|
@@ -22,10 +22,43 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
```ruby
|
26
|
+
image = Camalian::load('file_path')
|
27
|
+
colors = image.prominent_colors(15)
|
28
|
+
colors = colors.sort_similar_colors
|
29
|
+
colors.light_colors(0, 40)
|
30
|
+
```
|
31
|
+
|
32
|
+
### Quantization Algorithms
|
33
|
+
Currently following algorithms are implemented.
|
34
|
+
|
35
|
+
**Histogram**
|
36
|
+
|
37
|
+
Its a most common algorithm for color quantization and used different bucket technique to group the colors together. You can read more about this technique here https://en.wikipedia.org/wiki/Color_histogram. It can be accessed by `Camalian::QUANTIZATION_HISTOGRAM` constant. This is used as default method as well.
|
38
|
+
|
39
|
+
|
40
|
+
**K Means**
|
41
|
+
|
42
|
+
This algorithm uses color distancing in RGB space to group the similar colors. You can learn more about this technique here https://en.wikipedia.org/wiki/K-means_clustering. It can be accessed by `Camalian::QUANTIZATION_K_MEANS` constant.
|
43
|
+
|
44
|
+
|
45
|
+
You can set default quantization method globally as:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
Camalian.options[:quantization] = Camalian::QUANTIZATION_K_MEANS
|
49
|
+
```
|
50
|
+
|
51
|
+
or you can set at the time of extracting colors by.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
image = Camalian::load('file_path')
|
55
|
+
colors = image.prominent_colors(15, quantization: Camalian::QUANTIZATION_K_MEANS)
|
56
|
+
```
|
57
|
+
|
58
|
+
## Usage
|
59
|
+
You can find a working example with detail explanation and reference code here on [this link](https://basicdrift.com/color-extraction-library-build-color-search-engine-fdf369678d5a). Here we will build a functional color based image search engine in Ruby on Rails.
|
60
|
+
|
61
|
+
NOTE: Since its a compute intensive operation so for production use its suggested to use under a background job and not within a request/response cycle.
|
29
62
|
|
30
63
|
## Contributing
|
31
64
|
|
data/lib/camalian.rb
CHANGED
@@ -9,9 +9,11 @@ require 'camalian/color'
|
|
9
9
|
require 'camalian/palette'
|
10
10
|
require 'camalian/image'
|
11
11
|
require 'camalian/quantization/histogram'
|
12
|
+
require 'camalian/quantization/k_means'
|
12
13
|
|
13
14
|
module Camalian # :nodoc:
|
14
|
-
QUANTIZATION_HISTOGRAM =
|
15
|
+
QUANTIZATION_HISTOGRAM = Camalian::Quantization::Histogram
|
16
|
+
QUANTIZATION_K_MEANS = Camalian::Quantization::KMeans
|
15
17
|
|
16
18
|
class << self
|
17
19
|
def options
|
data/lib/camalian/color.rb
CHANGED
@@ -10,7 +10,12 @@ module Camalian
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.from_hex(hex_value)
|
13
|
-
|
13
|
+
color_hash = hex_value[0..6]
|
14
|
+
color_hash = color_hash[1..6] if color_hash[0] == '#'
|
15
|
+
r = color_hash[0..1].to_i(16)
|
16
|
+
g = color_hash[2..3].to_i(16)
|
17
|
+
b = color_hash[4..5].to_i(16)
|
18
|
+
|
14
19
|
Color.new(r, g, b)
|
15
20
|
end
|
16
21
|
|
@@ -22,17 +27,23 @@ module Camalian
|
|
22
27
|
"##{r.to_s(16).rjust(2, '0')}#{g.to_s(16).rjust(2, '0')}#{b.to_s(16).rjust(2, '0')}"
|
23
28
|
end
|
24
29
|
|
25
|
-
|
30
|
+
# Used for array uniqueness
|
31
|
+
def hash
|
32
|
+
"#{r}#{g}#{b}".to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
# Used for object comparison
|
36
|
+
def ==(other)
|
37
|
+
r == other.r && g == other.g && b == other.b
|
38
|
+
end
|
39
|
+
alias eql? ==
|
40
|
+
|
41
|
+
def hue_distance(color)
|
26
42
|
[(h - color.h) % 360, (color.h - h) % 360].min
|
27
43
|
end
|
28
44
|
|
29
|
-
def
|
30
|
-
|
31
|
-
color_hash = color_hash[1..6] if color_hash[0] == '#'
|
32
|
-
r = color_hash[0..1].to_i(16)
|
33
|
-
g = color_hash[2..3].to_i(16)
|
34
|
-
b = color_hash[4..5].to_i(16)
|
35
|
-
[r, g, b]
|
45
|
+
def rgb_distance(color)
|
46
|
+
Math.sqrt(((r - color.r)**2) + ((g - color.g)**2) + ((b - color.b)**2))
|
36
47
|
end
|
37
48
|
|
38
49
|
private
|
data/lib/camalian/image.rb
CHANGED
data/lib/camalian/palette.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Camalian
|
4
|
+
module Quantization
|
5
|
+
class KMeans # :nodoc:
|
6
|
+
INIT_TRIES = 10
|
7
|
+
|
8
|
+
def process(colors, count)
|
9
|
+
# Its faster to extract unique colors once
|
10
|
+
colors = colors.uniq
|
11
|
+
means = initial_means(colors, count)
|
12
|
+
|
13
|
+
done = false
|
14
|
+
|
15
|
+
until done
|
16
|
+
groups = group_by_means(colors, means)
|
17
|
+
new_means = groups.map do |group|
|
18
|
+
Palette.new(group).average_color
|
19
|
+
end
|
20
|
+
common = means & new_means
|
21
|
+
|
22
|
+
done = common.size == means.size
|
23
|
+
means = new_means
|
24
|
+
end
|
25
|
+
|
26
|
+
Palette.new(means)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def initial_means(colors, count)
|
32
|
+
count = colors.size if count > colors.size
|
33
|
+
means = []
|
34
|
+
tries = 0
|
35
|
+
while means.size != count && tries < INIT_TRIES
|
36
|
+
(count - means.size).times { means << colors[rand(colors.size)] }
|
37
|
+
means.uniq!
|
38
|
+
tries += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
means
|
42
|
+
end
|
43
|
+
|
44
|
+
def group_by_means(pixels, means)
|
45
|
+
groups = {}
|
46
|
+
|
47
|
+
pixels.each do |p|
|
48
|
+
distances = means.map { |m| p.rgb_distance(m) }
|
49
|
+
min_distance_index = distances.index(distances.min)
|
50
|
+
|
51
|
+
groups[min_distance_index] ||= []
|
52
|
+
groups[min_distance_index] << p
|
53
|
+
end
|
54
|
+
|
55
|
+
groups.values
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/camalian/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
describe Camalian::Quantization do
|
6
|
+
[Camalian::Quantization::KMeans, Camalian::Quantization::Histogram].each do |klass|
|
7
|
+
describe klass.name.to_s do
|
8
|
+
it 'should extract distinct colors' do
|
9
|
+
colors = %w[#FF0000 #00FF00 #0000FF].map { |c| Camalian::Color.from_hex(c) }
|
10
|
+
result = klass.new.process(colors, 3)
|
11
|
+
|
12
|
+
_(result.size).must_equal 3
|
13
|
+
_(result).must_equal colors
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should extract distinct colors lesser than pixels' do
|
17
|
+
colors = %w[#FF0000 #00FF00 #0000FF].map { |c| Camalian::Color.from_hex(c) }
|
18
|
+
result = klass.new.process(colors, 2)
|
19
|
+
|
20
|
+
_(result.size).must_equal 2
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should extract distinct colors not more than pixels' do
|
24
|
+
colors = %w[#FF0000 #00FF00 #0000FF].map { |c| Camalian::Color.from_hex(c) }
|
25
|
+
result = klass.new.process(colors, 4)
|
26
|
+
|
27
|
+
_(result.size).must_equal 3
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should extract same color' do
|
31
|
+
colors = %w[#FF0000 #FF0000 #FF0000].map { |c| Camalian::Color.from_hex(c) }
|
32
|
+
result = klass.new.process(colors, 3)
|
33
|
+
|
34
|
+
_(result.size).must_equal 1
|
35
|
+
_(result.to_hex).must_equal ['#ff0000']
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should extract multiple colors' do
|
39
|
+
colors = %w[#FF0000 #FF0000 #00FF00 #0000FF].map { |c| Camalian::Color.from_hex(c) }
|
40
|
+
result = klass.new.process(colors, 3)
|
41
|
+
|
42
|
+
_(result.size).must_equal 3
|
43
|
+
_(result.to_hex).must_equal %w[#ff0000 #00ff00 #0000ff]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: camalian
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nazar Hussain
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chunky_png
|
@@ -95,11 +95,13 @@ files:
|
|
95
95
|
- lib/camalian/image.rb
|
96
96
|
- lib/camalian/palette.rb
|
97
97
|
- lib/camalian/quantization/histogram.rb
|
98
|
+
- lib/camalian/quantization/k_means.rb
|
98
99
|
- lib/camalian/version.rb
|
99
100
|
- test/assets/palette.png
|
100
101
|
- test/test_color.rb
|
101
102
|
- test/test_helper.rb
|
102
103
|
- test/test_palette.rb
|
104
|
+
- test/test_quantization.rb
|
103
105
|
homepage: https://github.com/nazarhussain/camalian
|
104
106
|
licenses:
|
105
107
|
- MIT
|
@@ -129,3 +131,4 @@ test_files:
|
|
129
131
|
- test/test_color.rb
|
130
132
|
- test/test_helper.rb
|
131
133
|
- test/test_palette.rb
|
134
|
+
- test/test_quantization.rb
|