camalian 0.0.2 → 0.2.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
- SHA1:
3
- metadata.gz: 6d00dedccb6e595bdc20ada99a24660c6ae5dd3d
4
- data.tar.gz: 5c0dbce2351031926ae80fd502d96f14899c83c6
2
+ SHA256:
3
+ metadata.gz: df3efad40b55f5cc45e321d93c23077296b7367de0ea54dfb663b666c6c47b2f
4
+ data.tar.gz: d1d99748916c16c4b13e247427ae54a166623893cdfdd9322978fa16d8ab85f4
5
5
  SHA512:
6
- metadata.gz: e7cf381751a54732d7a08474333accca30b0b5284c0916373f88c739e1951f4557469c5c5611d719200e29d5c1c5403aa87d7143395fbf3e96b311d55d66a9d2
7
- data.tar.gz: bf78a507dcec731e88d8fe13c0f6e5e962529bf644d5fb49b9d760518ebf1352c25108a4f1e3ee4fab726c663b773bdcb0ff3aa6a975b359c4f9771a4dbad57c
6
+ metadata.gz: a3e0d48d911fdd7fe75a5fb5bcb2b7963e509eb7297f924be57ecd806ff36db462d347e2d1159d4d28031c077e5cb39a1e0df1716614e00311fb4f192f694722
7
+ data.tar.gz: '08f12c2b89f583d56fc327fe20f6d283a042d0bd435152fe29cec944b1e5ca187b56daa2f4601cdb88da2627fa41c445516c02db819cbe14b2ea408d036a76fa'
@@ -0,0 +1,30 @@
1
+ name: Github Packages Push
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - '*'
8
+ jobs:
9
+ build:
10
+ name: Build + Publish
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby 2.6
16
+ uses: actions/setup-ruby@v1
17
+ with:
18
+ ruby-version: 2.6.x
19
+
20
+ - name: Publish to GPR
21
+ run: |
22
+ mkdir -p $HOME/.gem
23
+ touch $HOME/.gem/credentials
24
+ chmod 0600 $HOME/.gem/credentials
25
+ printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
26
+ gem build *.gemspec
27
+ gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
28
+ env:
29
+ GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
30
+ OWNER: ${{ github.repository_owner }}
@@ -0,0 +1,32 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: build
9
+
10
+ on:
11
+ push:
12
+ branches: [master]
13
+ pull_request:
14
+ branches: [master]
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ ruby: ["2.5", "2.6", "2.7"]
22
+ name: Ruby ${{ matrix.ruby }}
23
+ steps:
24
+ - uses: actions/checkout@v2
25
+ - name: Set up Ruby ${{ matrix.ruby }}
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby }}
29
+ - name: Install dependencies
30
+ run: bundle install
31
+ - name: Run tests
32
+ run: bundle exec rake
@@ -0,0 +1,29 @@
1
+ name: Ruby Gems Push
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - '*'
8
+ jobs:
9
+ build:
10
+ name: Build + Publish
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby 2.6
16
+ uses: actions/setup-ruby@v1
17
+ with:
18
+ ruby-version: 2.6.x
19
+
20
+ - name: Publish to RubyGems
21
+ run: |
22
+ mkdir -p $HOME/.gem
23
+ touch $HOME/.gem/credentials
24
+ chmod 0600 $HOME/.gem/credentials
25
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
26
+ gem build *.gemspec
27
+ gem push *.gem
28
+ env:
29
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
data/.gitignore CHANGED
@@ -15,4 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- .idea/
18
+ .idea/
19
+ coverage/
data/.overcommit.yml ADDED
@@ -0,0 +1,33 @@
1
+ # Use this file to configure the Overcommit hooks you wish to use. This will
2
+ # extend the default configuration defined in:
3
+ # https://github.com/sds/overcommit/blob/master/config/default.yml
4
+ #
5
+ # At the topmost level of this YAML file is a key representing type of hook
6
+ # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
7
+ # customize each hook, such as whether to only run it on certain files (via
8
+ # `include`), whether to only display output if it fails (via `quiet`), etc.
9
+ #
10
+ # For a complete list of hooks, see:
11
+ # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
12
+ #
13
+ # For a complete list of options that you can use to customize hooks, see:
14
+ # https://github.com/sds/overcommit#configuration
15
+ #
16
+ # Uncomment the following lines to make the configuration take effect.
17
+
18
+ PreCommit:
19
+ RuboCop:
20
+ enabled: true
21
+ on_warn: fail # Treat all warnings as failures
22
+
23
+ # TrailingWhitespace:
24
+ # enabled: true
25
+ # exclude:
26
+ # - '**/db/structure.sql' # Ignore trailing whitespace in generated files
27
+ #
28
+ #PostCheckout:
29
+ # ALL: # Special hook name that customizes all hooks of this type
30
+ # quiet: true # Change all post-checkout hooks to only display output on failure
31
+ #
32
+ # IndexTags:
33
+ # enabled: true # Generate a tags file with `ctags` each time HEAD changes
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
2
+ # configuration file. It makes it possible to enable/disable
3
+ # certain cops (checks) and to alter their behavior if they accept
4
+ # any parameters. The file can be placed either in your home
5
+ # directory or in some project directory.
6
+ #
7
+ # RuboCop will start looking for the configuration file in the directory
8
+ # where the inspected file is and continue its way up to the root directory.
9
+ #
10
+ # See https://docs.rubocop.org/rubocop/configuration
11
+
12
+ AllCops:
13
+ NewCops: enable
14
+
15
+ Naming/MethodParameterName:
16
+ AllowedNames:
17
+ - r
18
+ - g
19
+ - b
20
+
21
+ Metrics/MethodLength:
22
+ Max: 50
23
+
24
+ Metrics/PerceivedComplexity:
25
+ Max: 25
26
+
27
+ Metrics/CyclomaticComplexity:
28
+ Max: 15
29
+
30
+ Metrics/BlockLength:
31
+ Exclude:
32
+ - test/**/*
33
+
34
+ Metrics/AbcSize:
35
+ Max: 30
36
+ IgnoredMethods:
37
+ - build_components
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 2.7.3
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.7
4
+ - 2.6
5
+ - 2.5
6
+ - 2.4
7
+
8
+ before_script:
9
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
10
+ - chmod +x ./cc-test-reporter
11
+ - ./cc-test-reporter before-build
12
+
13
+ script:
14
+ - bundle exec rake test
15
+
16
+ after_script:
17
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
data/Gemfile CHANGED
@@ -1,4 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in camalian.gemspec
4
6
  gemspec
7
+
8
+ gem 'rubocop', '~> 1.3', require: false
9
+ gem 'rubocop-daemon', require: false
10
+ gem 'simplecov', require: false
data/README.md CHANGED
@@ -1,12 +1,16 @@
1
1
  # Camalian
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/camalian.svg)](https://badge.fury.io/rb/camalian)
4
+ ![Ruby](https://github.com/nazarhussain/camalian/workflows/build/badge.svg?branch=master)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/5495a2c122469d81b6c5/maintainability)](https://codeclimate.com/github/nazarhussain/camalian/maintainability)
6
+
3
7
  Ruby gem to extract color palettes from images and play with their saturation
4
8
 
5
9
  ## Installation
6
10
 
7
11
  Add this line to your application's Gemfile:
8
12
 
9
- gem 'camalian', '~> 0.0.2'
13
+ gem 'camalian', '~> 0.2.1'
10
14
 
11
15
  And then execute:
12
16
 
@@ -18,10 +22,46 @@ Or install it yourself as:
18
22
 
19
23
  ## Usage
20
24
 
21
- image = Camalian::load('file_path')
22
- colors = image.prominent_colors(15)
23
- colors = colors.sort_similar_colors
24
- 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
+ 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.
33
+
34
+ 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.
35
+
36
+ ## Quantization Algorithms
37
+
38
+ Currently following algorithms are implemented.
39
+
40
+ ### Histogram
41
+
42
+ 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.
43
+
44
+ ### K Means
45
+
46
+ 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.
47
+
48
+ ### Median Cut
49
+
50
+ This algorithm uses color highest color range to determine the median and split colors to groups. The output consists of average color of such color groups. Since these algorithm don't use actual colors and instead average, so you will may not exact matching pixel in the image. This algorithm is nice to be used with image compression, where similarity and compression is important than having same pixel colors. You can learn more about this [technique here](https://tpgit.github.io/UnOfficialLeptDocs/leptonica/color-quantization.html) . It can be accessed by `Camalian::QUANTIZATION_MEDIAN_CUT` constant.
51
+
52
+
53
+ You can set default quantization method globally as:
54
+
55
+ ```ruby
56
+ Camalian.options[:quantization] = Camalian::QUANTIZATION_K_MEANS
57
+ ```
58
+
59
+ or you can set at the time of extracting colors by.
60
+
61
+ ```ruby
62
+ image = Camalian::load('file_path')
63
+ colors = image.prominent_colors(15, quantization: Camalian::QUANTIZATION_K_MEANS)
64
+ ```
25
65
 
26
66
  ## Contributing
27
67
 
data/Rakefile CHANGED
@@ -1,10 +1,13 @@
1
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
2
4
 
3
5
  require 'rake/testtask'
4
6
 
5
7
  Rake::TestTask.new do |t|
6
8
  t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
7
10
  end
8
11
 
9
- desc "Run tests"
10
- task :default => :test
12
+ desc 'Run tests'
13
+ task default: :test
data/camalian.gemspec CHANGED
@@ -1,27 +1,28 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'camalian/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "camalian"
8
+ spec.name = 'camalian'
8
9
  spec.version = Camalian::VERSION
9
- spec.authors = ["Nazar Hussain"]
10
- spec.email = ["nazarhussain@gmail.com"]
11
- spec.description = %q{Library used to deal with colors and images}
12
- spec.summary = %q{Library used to deal with colors and images. You can extract colors from images.}
13
- spec.homepage = "https://github.com/nazarhussain/camalian"
14
- spec.license = "MIT"
10
+ spec.authors = ['Nazar Hussain']
11
+ spec.email = ['nazarhussain@gmail.com']
12
+ spec.description = 'Library used to deal with colors and images'
13
+ spec.summary = 'Library used to deal with colors and images. You can extract colors from images.'
14
+ spec.homepage = 'https://github.com/nazarhussain/camalian'
15
+ spec.license = 'MIT'
15
16
 
16
- spec.files = `git ls-files`.split($/)
17
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.requirements = 'ImageMagick'
22
+ spec.add_dependency 'chunky_png', '~> 1.3', '>= 1.3.14'
22
23
 
23
- spec.add_dependency "cocaine"
24
- spec.add_dependency "rmagick", "~> 2.15.4"
25
- spec.add_dependency "oily_png", "~> 1.2.0"
24
+ spec.add_development_dependency 'minitest', '~> 5.14', '>= 5.14.2'
25
+ spec.add_development_dependency 'rake', '~> 13.0', '>= 13.0.1'
26
26
 
27
+ spec.required_ruby_version = '>= 2.5'
27
28
  end
data/lib/camalian.rb CHANGED
@@ -1,25 +1,31 @@
1
- require "oily_png"
2
- require "tempfile"
3
- require "open-uri"
4
- require "cocaine"
5
- require "chunky_png_patch/color"
1
+ # frozen_string_literal: true
6
2
 
7
- require "camalian/version"
8
- require "camalian/color"
9
- require "camalian/palette"
10
- require "camalian/image"
3
+ require 'chunky_png'
4
+ require 'tempfile'
5
+ require 'open-uri'
6
+
7
+ require 'camalian/version'
8
+ require 'camalian/color'
9
+ require 'camalian/palette'
10
+ require 'camalian/image'
11
+ require 'camalian/quantization/histogram'
12
+ require 'camalian/quantization/k_means'
13
+ require 'camalian/quantization/median_cut'
14
+
15
+ module Camalian # :nodoc:
16
+ QUANTIZATION_HISTOGRAM = Camalian::Quantization::Histogram
17
+ QUANTIZATION_K_MEANS = Camalian::Quantization::KMeans
18
+ QUANTIZATION_MEDIAN_CUT = Camalian::Quantization::MedianCut
11
19
 
12
- module Camalian
13
20
  class << self
14
21
  def options
15
- convert = `which convert`.strip
16
22
  @options ||= {
17
- :image_magick_path => convert.length > 0 ? convert : '/usr/bin/convert',
18
- :color_count => 8,
23
+ color_count: 8,
24
+ quantization: Camalian::QUANTIZATION_HISTOGRAM
19
25
  }
20
26
  end
21
27
 
22
- def load(image_path)
28
+ def load(image_path)
23
29
  Image.new(image_path)
24
30
  end
25
31
  end
@@ -1,15 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Camalian
4
+ # Camalian color object
2
5
  class Color
3
-
4
6
  attr_reader :r, :g, :b, :h, :s, :l, :hsv
5
7
 
6
- def initialize(*value)
7
- if value.size == 1
8
- rgb = extract_rgb(value.first)
9
- build_components(rgb[0], rgb[1], rgb[2])
10
- elsif value.size == 3
11
- build_components(value[0], value[1], value[2])
12
- end
8
+ def initialize(r, g, b)
9
+ build_components(r, g, b)
10
+ end
11
+
12
+ def self.from_hex(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
+
19
+ Color.new(r, g, b)
13
20
  end
14
21
 
15
22
  def to_s
@@ -20,22 +27,28 @@ module Camalian
20
27
  "##{r.to_s(16).rjust(2, '0')}#{g.to_s(16).rjust(2, '0')}#{b.to_s(16).rjust(2, '0')}"
21
28
  end
22
29
 
23
- def distance(color)
24
- [(self.h - color.h) % 360, (color.h - self.h) % 360].min
30
+ # Used for array uniqueness
31
+ def hash
32
+ "#{r}#{g}#{b}".to_i
25
33
  end
26
34
 
27
- def extract_rgb(color_hash)
28
- color_hash = color_hash[0..6]
29
- color_hash = color_hash[1..6] if color_hash[0] == '#'
30
- r = color_hash[0..1].to_i(16)
31
- g = color_hash[2..3].to_i(16)
32
- b = color_hash[4..5].to_i(16)
33
- [r, g, b]
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)
42
+ [(h - color.h) % 360, (color.h - h) % 360].min
43
+ end
44
+
45
+ def rgb_distance(color)
46
+ Math.sqrt(((r - color.r)**2) + ((g - color.g)**2) + ((b - color.b)**2))
34
47
  end
35
48
 
36
49
  private
37
50
 
38
- def build_components(r,g,b)
51
+ def build_components(r, g, b)
39
52
  @r = r
40
53
  @g = g
41
54
  @b = b
@@ -50,21 +63,21 @@ module Camalian
50
63
 
51
64
  @l = (cmax + cmin) / 2.0
52
65
 
53
- if delta == 0
66
+ if delta.zero?
54
67
  @h = 0
55
68
  elsif cmax == ri
56
69
  @h = 60 * (((gi - bi) / delta) % 6)
57
70
  elsif cmax == gi
58
- @h = 60 * (((bi - ri)/ delta) + 2)
71
+ @h = 60 * (((bi - ri) / delta) + 2)
59
72
  elsif cmax == bi
60
- @h = 60 * (((ri - gi)/ delta) + 4)
73
+ @h = 60 * (((ri - gi) / delta) + 4)
61
74
  end
62
75
 
63
- if (delta == 0)
64
- @s = 0
65
- else
66
- @s = delta / ( 1 - (2*@l -1).abs )
67
- end
76
+ @s = if delta.zero?
77
+ 0
78
+ else
79
+ delta / (1 - (2 * @l - 1).abs)
80
+ end
68
81
 
69
82
  @h = @h.round(2)
70
83
  @s = (@s * 100).round(2)
@@ -72,22 +85,22 @@ module Camalian
72
85
 
73
86
  # HSV Calculation
74
87
  # Hue calculation
75
- if delta == 0
88
+ if delta.zero?
76
89
  @hsv = [0]
77
90
  elsif cmax == ri
78
91
  @hsv = [60 * (((gi - bi) / delta) % 6)]
79
92
  elsif cmax == gi
80
- @hsv = [60 * (((bi - ri)/ delta) + 2)]
93
+ @hsv = [60 * (((bi - ri) / delta) + 2)]
81
94
  elsif cmax == bi
82
- @hsv = [60 * (((ri - gi)/ delta) + 4)]
95
+ @hsv = [60 * (((ri - gi) / delta) + 4)]
83
96
  end
84
97
 
85
98
  # Saturation calculation
86
- if (cmax == 0)
87
- @hsv << 0
88
- else
89
- @hsv << delta / cmax
90
- end
99
+ @hsv << if cmax.zero?
100
+ 0
101
+ else
102
+ delta / cmax
103
+ end
91
104
 
92
105
  # Value calculation
93
106
  @hsv << cmax