camalian 0.1.0 → 0.1.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 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