camalian 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +6 -3
- data/Rakefile +9 -0
- data/camalian.gemspec +7 -1
- data/lib/camalian.rb +22 -1
- data/lib/camalian/color.rb +98 -0
- data/lib/camalian/image.rb +36 -0
- data/lib/camalian/palette.rb +27 -0
- data/lib/camalian/version.rb +1 -1
- data/lib/chunky_png_patch/color.rb +14 -0
- data/test/assets/palette.png +0 -0
- data/test/test_color.rb +29 -0
- data/test/test_palette.rb +28 -0
- metadata +65 -14
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6d00dedccb6e595bdc20ada99a24660c6ae5dd3d
|
4
|
+
data.tar.gz: 5c0dbce2351031926ae80fd502d96f14899c83c6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e7cf381751a54732d7a08474333accca30b0b5284c0916373f88c739e1951f4557469c5c5611d719200e29d5c1c5403aa87d7143395fbf3e96b311d55d66a9d2
|
7
|
+
data.tar.gz: bf78a507dcec731e88d8fe13c0f6e5e962529bf644d5fb49b9d760518ebf1352c25108a4f1e3ee4fab726c663b773bdcb0ff3aa6a975b359c4f9771a4dbad57c
|
data/.gitignore
CHANGED
File without changes
|
data/Gemfile
CHANGED
File without changes
|
data/LICENSE.txt
CHANGED
File without changes
|
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Camalian
|
2
2
|
|
3
|
-
|
3
|
+
Ruby gem to extract color palettes from images and play with their saturation
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
7
|
Add this line to your application's Gemfile:
|
8
8
|
|
9
|
-
gem 'camalian'
|
9
|
+
gem 'camalian', '~> 0.0.2'
|
10
10
|
|
11
11
|
And then execute:
|
12
12
|
|
@@ -18,7 +18,10 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
|
21
|
+
image = Camalian::load('file_path')
|
22
|
+
colors = image.prominent_colors(15)
|
23
|
+
colors = colors.sort_similar_colors
|
24
|
+
colors.light_colors(0, 40)
|
22
25
|
|
23
26
|
## Contributing
|
24
27
|
|
data/Rakefile
CHANGED
data/camalian.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["nazarhussain@gmail.com"]
|
11
11
|
spec.description = %q{Library used to deal with colors and images}
|
12
12
|
spec.summary = %q{Library used to deal with colors and images. You can extract colors from images.}
|
13
|
-
spec.homepage = "
|
13
|
+
spec.homepage = "https://github.com/nazarhussain/camalian"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -18,4 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.requirements = 'ImageMagick'
|
22
|
+
|
23
|
+
spec.add_dependency "cocaine"
|
24
|
+
spec.add_dependency "rmagick", "~> 2.15.4"
|
25
|
+
spec.add_dependency "oily_png", "~> 1.2.0"
|
26
|
+
|
21
27
|
end
|
data/lib/camalian.rb
CHANGED
@@ -1,5 +1,26 @@
|
|
1
|
+
require "oily_png"
|
2
|
+
require "tempfile"
|
3
|
+
require "open-uri"
|
4
|
+
require "cocaine"
|
5
|
+
require "chunky_png_patch/color"
|
6
|
+
|
1
7
|
require "camalian/version"
|
8
|
+
require "camalian/color"
|
9
|
+
require "camalian/palette"
|
10
|
+
require "camalian/image"
|
2
11
|
|
3
12
|
module Camalian
|
4
|
-
|
13
|
+
class << self
|
14
|
+
def options
|
15
|
+
convert = `which convert`.strip
|
16
|
+
@options ||= {
|
17
|
+
:image_magick_path => convert.length > 0 ? convert : '/usr/bin/convert',
|
18
|
+
:color_count => 8,
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def load(image_path)
|
23
|
+
Image.new(image_path)
|
24
|
+
end
|
25
|
+
end
|
5
26
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Camalian
|
2
|
+
class Color
|
3
|
+
|
4
|
+
attr_reader :r, :g, :b, :h, :s, :l, :hsv
|
5
|
+
|
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
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"red=#{r} green=#{g} blue=#{b} hue=#{h} saturation=#{s} lightness=#{l}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hex
|
20
|
+
"##{r.to_s(16).rjust(2, '0')}#{g.to_s(16).rjust(2, '0')}#{b.to_s(16).rjust(2, '0')}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def distance(color)
|
24
|
+
[(self.h - color.h) % 360, (color.h - self.h) % 360].min
|
25
|
+
end
|
26
|
+
|
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]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_components(r,g,b)
|
39
|
+
@r = r
|
40
|
+
@g = g
|
41
|
+
@b = b
|
42
|
+
|
43
|
+
ri = @r / 255.0
|
44
|
+
gi = @g / 255.0
|
45
|
+
bi = @b / 255.0
|
46
|
+
|
47
|
+
cmax = [ri, gi, bi].max
|
48
|
+
cmin = [ri, gi, bi].min
|
49
|
+
delta = cmax - cmin
|
50
|
+
|
51
|
+
@l = (cmax + cmin) / 2.0
|
52
|
+
|
53
|
+
if delta == 0
|
54
|
+
@h = 0
|
55
|
+
elsif cmax == ri
|
56
|
+
@h = 60 * (((gi - bi) / delta) % 6)
|
57
|
+
elsif cmax == gi
|
58
|
+
@h = 60 * (((bi - ri)/ delta) + 2)
|
59
|
+
elsif cmax == bi
|
60
|
+
@h = 60 * (((ri - gi)/ delta) + 4)
|
61
|
+
end
|
62
|
+
|
63
|
+
if (delta == 0)
|
64
|
+
@s = 0
|
65
|
+
else
|
66
|
+
@s = delta / ( 1 - (2*@l -1).abs )
|
67
|
+
end
|
68
|
+
|
69
|
+
@h = @h.round(2)
|
70
|
+
@s = (@s * 100).round(2)
|
71
|
+
@l = (@l * 100).round(2)
|
72
|
+
|
73
|
+
# HSV Calculation
|
74
|
+
# Hue calculation
|
75
|
+
if delta == 0
|
76
|
+
@hsv = [0]
|
77
|
+
elsif cmax == ri
|
78
|
+
@hsv = [60 * (((gi - bi) / delta) % 6)]
|
79
|
+
elsif cmax == gi
|
80
|
+
@hsv = [60 * (((bi - ri)/ delta) + 2)]
|
81
|
+
elsif cmax == bi
|
82
|
+
@hsv = [60 * (((ri - gi)/ delta) + 4)]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Saturation calculation
|
86
|
+
if (cmax == 0)
|
87
|
+
@hsv << 0
|
88
|
+
else
|
89
|
+
@hsv << delta / cmax
|
90
|
+
end
|
91
|
+
|
92
|
+
# Value calculation
|
93
|
+
@hsv << cmax
|
94
|
+
|
95
|
+
@hsv = [@hsv[0].round(2), (@hsv[1] * 100).round(2), (@hsv[2] * 100).round(2)]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rmagick'
|
2
|
+
|
3
|
+
module Camalian
|
4
|
+
class Image
|
5
|
+
attr_accessor :src_file_path
|
6
|
+
|
7
|
+
def initialize(file_path)
|
8
|
+
@src_file_path = file_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def prominent_colors(count=Camalian.options[:color_count])
|
12
|
+
image = ::Magick::Image.read(@src_file_path)[0]
|
13
|
+
image.resize!(100,100)
|
14
|
+
colors = Palette.new
|
15
|
+
initial_count = count
|
16
|
+
q = nil
|
17
|
+
loop do
|
18
|
+
q = image.quantize(initial_count, Magick::RGBColorspace)
|
19
|
+
break if q.color_histogram.size > count
|
20
|
+
initial_count = initial_count + 10
|
21
|
+
end
|
22
|
+
palette = q.color_histogram.sort {|a, b| b[1] <=> a[1]}
|
23
|
+
(0..[count, palette.count].min - 1).each do |i|
|
24
|
+
c = palette[i].first.to_s.split(',').map {|x| x[/\d+/]}
|
25
|
+
c.pop
|
26
|
+
c[0], c[1], c[2] = [c[0], c[1], c[2]].map { |s|
|
27
|
+
s = s.to_i
|
28
|
+
s = s / 255 if s / 255 > 0 # not all ImageMagicks are created equal....
|
29
|
+
s
|
30
|
+
}
|
31
|
+
colors << Color.new(c[0],c[1],c[2])
|
32
|
+
end
|
33
|
+
return colors.uniq(&:to_hex)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Camalian
|
2
|
+
class Palette < Array
|
3
|
+
|
4
|
+
def sort_by_lightness
|
5
|
+
Palette.new(self.sort_by { |a| a.l }.reverse)
|
6
|
+
end
|
7
|
+
|
8
|
+
def sort_by_hue
|
9
|
+
Palette.new(self.sort_by { |a| a.h }.reverse)
|
10
|
+
end
|
11
|
+
|
12
|
+
def sort_similar_colors
|
13
|
+
Palette.new(self.sort_by { |a| a.hsv })
|
14
|
+
end
|
15
|
+
|
16
|
+
def sort_by_saturation
|
17
|
+
Palette.new(self.sort_by { |a| a.s }.reverse)
|
18
|
+
end
|
19
|
+
|
20
|
+
def light_colors(limit1, limit2)
|
21
|
+
min = [limit1, limit2].min
|
22
|
+
max = [limit1, limit2].max
|
23
|
+
table = self.dup
|
24
|
+
Palette.new(table.delete_if { |color| color.l > max or color.l < min })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/camalian/version.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
module ChunkyPNG::Color
|
2
|
+
# See http://en.wikipedia.org/wiki/Hue#Computing_hue_from_RGB
|
3
|
+
def self.hue(pixel)
|
4
|
+
r, g, b = r(pixel), g(pixel), b(pixel)
|
5
|
+
return 0 if r == b and b == g
|
6
|
+
((180 / Math::PI * Math.atan2((2 * r) - g - b, Math.sqrt(3) * (g - b))) - 90) % 360
|
7
|
+
end
|
8
|
+
|
9
|
+
# The modular distance, as the hue is circular
|
10
|
+
def self.distance(pixel, poxel)
|
11
|
+
hue_pixel, hue_poxel = hue(pixel), hue(poxel)
|
12
|
+
[(hue_pixel - hue_poxel) % 360, (hue_poxel - hue_pixel) % 360].min
|
13
|
+
end
|
14
|
+
end
|
Binary file
|
data/test/test_color.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/spec'
|
3
|
+
require 'camalian'
|
4
|
+
|
5
|
+
describe Camalian::Color do
|
6
|
+
before do
|
7
|
+
@color = Camalian::Color.new(120,255,30)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Color initialized with 120, 255, 30 rgb values" do
|
11
|
+
it "hex value must be #78ff1e" do
|
12
|
+
@color.to_hex.must_equal "#78ff1e"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "hsl color components must " do
|
16
|
+
[@color.h.to_i, @color.s.to_i, @color.l.to_i].must_equal [96, 100, 55]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "hsv color components must " do
|
20
|
+
@color.hsv.map(&:to_i).must_equal [96, 88, 100]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "initialized with 1 integer rgb value" do
|
25
|
+
it "must have leading zero" do
|
26
|
+
Camalian::Color.new(7, 7, 7).to_hex.must_equal "#070707"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/spec'
|
3
|
+
require 'camalian'
|
4
|
+
|
5
|
+
describe Camalian::Image do
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Camalian::Palette do
|
10
|
+
before do
|
11
|
+
@image = Camalian::load( File.join( File.dirname(__FILE__), 'assets/palette.png'))
|
12
|
+
@palette = @image.prominent_colors(15)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "palette with 15 colors extracted" do
|
16
|
+
it "must have 15 colors" do
|
17
|
+
@palette.size.must_equal 15
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sort similar colors in order" do
|
21
|
+
@palette.sort_similar_colors.map(&:to_hex).must_equal %W(#4dda15 #45c131 #41b53f #3da94d #3da84e #359169 #318578 #2d7986 #296d94 #2560a3 #2154b1 #1d48bf #193dcd #193cce #1530dc)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "color with intensity 0-40 works well" do
|
25
|
+
@palette.light_colors(0, 40).map(&:to_hex).must_equal %W(#318578 #2560a3 #359169 #296d94 #2d7986)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
CHANGED
@@ -1,16 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: camalian
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Nazar Hussain
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
11
|
+
date: 2015-10-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cocaine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rmagick
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.15.4
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.15.4
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: oily_png
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.2.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.2.0
|
14
55
|
description: Library used to deal with colors and images
|
15
56
|
email:
|
16
57
|
- nazarhussain@gmail.com
|
@@ -18,38 +59,48 @@ executables: []
|
|
18
59
|
extensions: []
|
19
60
|
extra_rdoc_files: []
|
20
61
|
files:
|
21
|
-
- .gitignore
|
62
|
+
- ".gitignore"
|
22
63
|
- Gemfile
|
23
64
|
- LICENSE.txt
|
24
65
|
- README.md
|
25
66
|
- Rakefile
|
26
67
|
- camalian.gemspec
|
27
68
|
- lib/camalian.rb
|
69
|
+
- lib/camalian/color.rb
|
70
|
+
- lib/camalian/image.rb
|
71
|
+
- lib/camalian/palette.rb
|
28
72
|
- lib/camalian/version.rb
|
29
|
-
|
73
|
+
- lib/chunky_png_patch/color.rb
|
74
|
+
- test/assets/palette.png
|
75
|
+
- test/test_color.rb
|
76
|
+
- test/test_palette.rb
|
77
|
+
homepage: https://github.com/nazarhussain/camalian
|
30
78
|
licenses:
|
31
79
|
- MIT
|
80
|
+
metadata: {}
|
32
81
|
post_install_message:
|
33
82
|
rdoc_options: []
|
34
83
|
require_paths:
|
35
84
|
- lib
|
36
85
|
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
-
none: false
|
38
86
|
requirements:
|
39
|
-
- -
|
87
|
+
- - ">="
|
40
88
|
- !ruby/object:Gem::Version
|
41
89
|
version: '0'
|
42
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
-
none: false
|
44
91
|
requirements:
|
45
|
-
- -
|
92
|
+
- - ">="
|
46
93
|
- !ruby/object:Gem::Version
|
47
94
|
version: '0'
|
48
|
-
requirements:
|
95
|
+
requirements:
|
96
|
+
- ImageMagick
|
49
97
|
rubyforge_project:
|
50
|
-
rubygems_version:
|
98
|
+
rubygems_version: 2.4.5
|
51
99
|
signing_key:
|
52
|
-
specification_version:
|
100
|
+
specification_version: 4
|
53
101
|
summary: Library used to deal with colors and images. You can extract colors from
|
54
102
|
images.
|
55
|
-
test_files:
|
103
|
+
test_files:
|
104
|
+
- test/assets/palette.png
|
105
|
+
- test/test_color.rb
|
106
|
+
- test/test_palette.rb
|