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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdc6f4d61421eac9f0017baf9e6f290c37e47b633ee4533e18d00883fd162b54
4
- data.tar.gz: 5cb477be826b4c0706a9385ce31699a34aab8b27c66c5c57140d1818650ca60e
3
+ metadata.gz: 01c8d4bae81e85284d554a538c52e90741f0db58ddba1d6d9e921761cf041d91
4
+ data.tar.gz: c5a301ed8505a08e31ed1c65cc4e5c8b0b1ccc832fad6e8279cb279f4750dadd
5
5
  SHA512:
6
- metadata.gz: 912bcad8d3db3362d2997877fe42b9d2df01c806b9bbfe965463d5df8e530dab55f716820f414e44910e5818ff86ac1ac256f8c9d1a2d760ca75d38c69492dcc
7
- data.tar.gz: fe6989ee96c21f12482857c26aed89f7db69e6d1a41616c9aaed3a11732ccf76c8376c6711b49dc375cbb8d29adfef1ff09ba3ea3c5780c4f70cdce0431041eb
6
+ metadata.gz: '0418f17dfb07d45db856e30e9b333b532be7a38045aa4be3aae32192e62de4c803837a6730df08a797f3c662bd9a3fc0f21a5e780edfcf8545b3c085440af2d0'
7
+ data.tar.gz: 0e1144ff5d5d9a8faef915c23518759b98bebd6efdb6a4b39ea7a11ecd67d63e07847b05982853a20b0a719a855d16a1dcba45f28ac97383478a319ef9281488
@@ -27,6 +27,10 @@ Metrics/PerceivedComplexity:
27
27
  Metrics/CyclomaticComplexity:
28
28
  Max: 15
29
29
 
30
+ Metrics/BlockLength:
31
+ Exclude:
32
+ - test/**/*
33
+
30
34
  Metrics/AbcSize:
31
35
  Max: 20
32
36
  IgnoredMethods:
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.0'
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
- image = Camalian::load('file_path')
26
- colors = image.prominent_colors(15)
27
- colors = colors.sort_similar_colors
28
- colors.light_colors(0, 40)
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
 
@@ -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 = 'histogram'
15
+ QUANTIZATION_HISTOGRAM = Camalian::Quantization::Histogram
16
+ QUANTIZATION_K_MEANS = Camalian::Quantization::KMeans
15
17
 
16
18
  class << self
17
19
  def options
@@ -10,7 +10,12 @@ module Camalian
10
10
  end
11
11
 
12
12
  def self.from_hex(hex_value)
13
- r, g, b = extract_rgb(hex_value)
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
- def distance(color)
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 extract_rgb(color_hash)
30
- color_hash = color_hash[0..6]
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
@@ -22,7 +22,7 @@ module Camalian
22
22
  )
23
23
  end
24
24
 
25
- quantize = Object.const_get("Camalian::Quantization::#{quantization.capitalize}").new
25
+ quantize = quantization.new
26
26
 
27
27
  palette = quantize.process(colors, count)
28
28
 
@@ -34,5 +34,9 @@ module Camalian
34
34
  table = dup
35
35
  Palette.new(table.delete_if { |color| color.l > max or color.l < min })
36
36
  end
37
+
38
+ def to_hex
39
+ map(&:to_hex)
40
+ end
37
41
  end
38
42
  end
@@ -13,7 +13,7 @@ module Camalian
13
13
  buckets[key].push(color)
14
14
  end
15
15
 
16
- Palette.new(buckets.map { |_, value| value.average_color })
16
+ Palette.new(buckets.map { |_, value| value.average_color }[0...count])
17
17
  end
18
18
 
19
19
  private
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Camalian
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
@@ -2,3 +2,7 @@
2
2
 
3
3
  require 'simplecov'
4
4
  SimpleCov.start
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest/spec'
8
+ require 'camalian'
@@ -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.0
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-21 00:00:00.000000000 Z
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