camalian 0.0.1 → 0.0.2
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 +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
|