borealis 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/LICENSE.txt +2 -2
- data/README.md +83 -2
- data/borealis.gemspec +5 -2
- data/lib/borealis.rb +20 -14
- data/lib/borealis/cluster.rb +31 -0
- data/lib/borealis/color.rb +76 -8
- data/lib/borealis/image_converter.rb +33 -0
- data/lib/borealis/kmeans.rb +53 -0
- data/lib/borealis/version.rb +1 -1
- data/spec/borealis/borealis_spec.rb +4 -23
- data/spec/borealis/cluster_spec.rb +35 -0
- data/spec/borealis/color_spec.rb +39 -11
- data/spec/borealis/image_converter_spec.rb +28 -0
- data/spec/borealis/kmeans_spec.rb +32 -0
- data/spec/fixtures/aurora_borealis.jpg +0 -0
- data/spec/spec_helper.rb +3 -0
- metadata +40 -23
- data/lib/borealis/image_analyzer.rb +0 -22
- data/spec/borealis/image_analyzer_spec.rb +0 -27
- data/spec/fixtures/winter.jpg +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7dc6e8b735da4b63a7edf43f7b2cc0964a67eff1
|
4
|
+
data.tar.gz: a98909c6d6398b291e8cabecb0ed7eb3a7d1e584
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 806fc46d3831cf1425d2a52832185af7cbadda09c60b0fe19fc457a92b490e32f05d57ca62544164f6066cb607b819010629fcd68c7645adce461592ccc72990
|
7
|
+
data.tar.gz: 4279ff2b8a4a7830f967f5b22edf9832267cbb51b571265f6c1d282b639a6d32d21c247896e69ab2ebcd89eae376c92836b37964624cbc670ccad2f1c9465e78
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2013 Josh Steiner
|
1
|
+
Copyright (c) 2013 Josh Steiner and thoughtbot, inc.
|
2
2
|
|
3
3
|
MIT License
|
4
4
|
|
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
21
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Borealis
|
2
2
|
|
3
|
-
|
3
|
+
Borealis finds the most prominent colors in an image, attempting to find colors that are distinct from one another.
|
4
|
+
|
5
|
+
It provides defaults which favor speed, but the defaults are easily changed if you prefer accuracy. For most purposes, the default settings should be accurate enough.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -18,7 +20,82 @@ Or install it yourself as:
|
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
|
23
|
+
Find the three prominent colors in an image:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
Borealis.new('image/path.jpg').colors # Returns 3 colors with hex and rgb attributes
|
27
|
+
Borealis.new('image/path.jpg').hexes # Returns 3 hex values
|
28
|
+
Borealis.new('image/path.jpg').rgbs # Returns 3 rgb values
|
29
|
+
```
|
30
|
+
|
31
|
+
Easily get color values out of a color:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
color = Borealis.new(file).colors.first
|
35
|
+
color.rgb # "(222, 2, 15)"
|
36
|
+
color.hex # "#DE020F"
|
37
|
+
```
|
38
|
+
|
39
|
+
Measure distance between colors:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
orange.rgb_distance_to red #=> 127.0
|
43
|
+
orange.lab_distance_to red #=> 39.79252198414681
|
44
|
+
|
45
|
+
maroon.rgb_distance_to dark_red #=> 11.0
|
46
|
+
maroon.lab_distance_to dark_read #=> 5.071593528474779
|
47
|
+
```
|
48
|
+
|
49
|
+
Calculate CIE-L\*a\*b\* value of an sRGB color:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
cyan = Borealis::Color.new(0, 255, 255)
|
53
|
+
cyan.to_lab #=> [91.11652110946342, -48.079618466228716, -14.138127754846131]
|
54
|
+
```
|
55
|
+
|
56
|
+
Change the defaults:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
borealis = Borealis.new('image/path.jpg',
|
60
|
+
size: '150x150',
|
61
|
+
colors: 3,
|
62
|
+
delta: 0.001,
|
63
|
+
static: false
|
64
|
+
)
|
65
|
+
```
|
66
|
+
|
67
|
+
### Size
|
68
|
+
|
69
|
+
_Default: '50x50'_
|
70
|
+
|
71
|
+
Size takes any [Image Geometry](http://www.imagemagick.org/script/command-line-processing.php#geometry) that imagemagick handles.
|
72
|
+
|
73
|
+
For more accurate results, use a larger image. However, this comes at a severe time cost.
|
74
|
+
|
75
|
+
### Colors
|
76
|
+
|
77
|
+
_Default: 3_
|
78
|
+
|
79
|
+
The number of colors you want returned.
|
80
|
+
|
81
|
+
### Iterations
|
82
|
+
|
83
|
+
_Default: 3_
|
84
|
+
|
85
|
+
### Static
|
86
|
+
|
87
|
+
_Default: true_
|
88
|
+
|
89
|
+
Static determines whether the color output will be the same each time given the same image. Use false if you want the colors to change each run.
|
90
|
+
|
91
|
+
## How does it work?
|
92
|
+
|
93
|
+
Borealis uses the [k-means](http://en.wikipedia.org/wiki/K-means_clustering) clustering algorithm, which in this case:
|
94
|
+
|
95
|
+
1. Takes _n_ colors from the image as cluster centers
|
96
|
+
2. Adds the rest of the colors to the cluster which they are closest to
|
97
|
+
3. Finds the average color in each cluster, setting the results as new cluster centers
|
98
|
+
4. Empties each cluster, except for the new center, and repeats steps 2-3 until convergance has been reached
|
22
99
|
|
23
100
|
## Contributing
|
24
101
|
|
@@ -27,3 +104,7 @@ TODO: Write usage instructions here
|
|
27
104
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
105
|
4. Push to the branch (`git push origin my-new-feature`)
|
29
106
|
5. Create new Pull Request
|
107
|
+
|
108
|
+
## License
|
109
|
+
|
110
|
+
Borealis is copyright 2013 Josh Steiner and thoughtbot, inc., and may be redistributed under the terms specified in the LICENSE file.
|
data/borealis.gemspec
CHANGED
@@ -8,15 +8,18 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Borealis::VERSION
|
9
9
|
gem.authors = ["Josh Steiner"]
|
10
10
|
gem.email = ["josh@jsteiner.me"]
|
11
|
-
gem.description = %q{Finds
|
12
|
-
gem.summary = %q{Finds
|
11
|
+
gem.description = %q{Finds most prominent colors in a given image.}
|
12
|
+
gem.summary = %q{Finds most prominent colors in a given image.}
|
13
13
|
gem.homepage = ""
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
16
|
gem.test_files = gem.files.grep("spec")
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
|
19
|
+
gem.requirements << 'ImageMagick'
|
20
|
+
|
19
21
|
gem.add_development_dependency 'rspec'
|
22
|
+
gem.add_development_dependency 'bourne'
|
20
23
|
|
21
24
|
gem.add_dependency 'cocaine'
|
22
25
|
end
|
data/lib/borealis.rb
CHANGED
@@ -1,27 +1,33 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require 'borealis/cluster'
|
2
|
+
require 'borealis/color'
|
3
|
+
require 'borealis/image_converter'
|
4
|
+
require 'borealis/kmeans'
|
5
|
+
require 'borealis/version'
|
5
6
|
|
6
7
|
class Borealis
|
7
8
|
attr_reader :colors
|
8
9
|
|
9
|
-
def initialize(file,
|
10
|
-
|
11
|
-
|
12
|
-
colors.
|
10
|
+
def initialize(file, options = {})
|
11
|
+
image_colors = ImageConverter.new(file, options[:size]).to_colors
|
12
|
+
clusters = KMeans.new(image_colors, parse_options(options)).run
|
13
|
+
@colors = clusters.map(&:center)
|
13
14
|
end
|
14
15
|
|
15
16
|
def hexes
|
16
|
-
colors.map(&:hex)
|
17
|
+
@colors.map(&:hex)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rgbs
|
21
|
+
@colors.map(&:rgb)
|
17
22
|
end
|
18
23
|
|
19
24
|
private
|
20
25
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
def parse_options(options)
|
27
|
+
{
|
28
|
+
number_of_clusters: options[:colors],
|
29
|
+
delta: options[:delta],
|
30
|
+
static: options[:static]
|
31
|
+
}.reject{ |_,v| v.nil? }
|
26
32
|
end
|
27
33
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Borealis
|
2
|
+
class Cluster
|
3
|
+
attr_reader :center, :colors
|
4
|
+
|
5
|
+
def initialize(center)
|
6
|
+
@center = center
|
7
|
+
@colors = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def recenter!
|
11
|
+
red_sum = green_sum = blue_sum = 0
|
12
|
+
|
13
|
+
colors.each do |color|
|
14
|
+
red_sum += color.red
|
15
|
+
green_sum += color.green
|
16
|
+
blue_sum += color.blue
|
17
|
+
end
|
18
|
+
|
19
|
+
old_center = @center
|
20
|
+
@center = Color.new(
|
21
|
+
red_sum / colors.length,
|
22
|
+
green_sum / colors.length,
|
23
|
+
blue_sum / colors.length
|
24
|
+
)
|
25
|
+
|
26
|
+
@colors.clear
|
27
|
+
|
28
|
+
old_center.rgb_distance_to @center
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/borealis/color.rb
CHANGED
@@ -1,18 +1,86 @@
|
|
1
1
|
class Borealis
|
2
2
|
class Color
|
3
|
-
|
3
|
+
include Math
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
attr_reader :red, :green, :blue
|
6
|
+
|
7
|
+
def initialize(red, green, blue)
|
8
|
+
@red = red
|
9
|
+
@green = green
|
10
|
+
@blue = blue
|
11
|
+
end
|
12
|
+
|
13
|
+
def rgb_distance_to(other)
|
14
|
+
Math.sqrt((red - other.red) ** 2 + (green - other.green) ** 2 + (blue - other.blue) ** 2)
|
15
|
+
end
|
16
|
+
|
17
|
+
def lab_distance_to(other)
|
18
|
+
l, a, b = to_lab
|
19
|
+
other_l, other_a, other_b = other.to_lab
|
20
|
+
Math.sqrt((l - other_l) ** 2 + (a - other_a) ** 2 + (b - other_b) ** 2)
|
21
|
+
end
|
22
|
+
|
23
|
+
def rgb
|
24
|
+
"(#{red}, #{green}, #{blue})"
|
25
|
+
end
|
26
|
+
|
27
|
+
def hex
|
28
|
+
"##{hex_segment red}#{hex_segment green}#{hex_segment blue}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
red == other.red && green == other.green && blue == other.blue
|
33
|
+
end
|
34
|
+
|
35
|
+
# http://www.easyrgb.com/index.php?X=MATH&H=07#text7
|
36
|
+
def to_lab
|
37
|
+
reference_white_X = 95.047
|
38
|
+
reference_white_Y = 100.000
|
39
|
+
reference_white_Z = 108.883
|
40
|
+
|
41
|
+
x, y, z = to_xyz
|
42
|
+
x = x / reference_white_X
|
43
|
+
y = y / reference_white_Y
|
44
|
+
z = z / reference_white_Z
|
45
|
+
|
46
|
+
x, y, z = [x, y, z].map do |value|
|
47
|
+
if value > 0.008856
|
48
|
+
value ** (1.0 / 3.0)
|
49
|
+
else
|
50
|
+
7.787 * value + 16.0 / 116.0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
cie_L = 116 * y - 16
|
55
|
+
cie_a = 500 * (x - y)
|
56
|
+
cie_b = 200 * (y - z)
|
57
|
+
|
58
|
+
[cie_L, cie_a, cie_b]
|
8
59
|
end
|
9
60
|
|
10
|
-
|
11
|
-
|
61
|
+
private
|
62
|
+
|
63
|
+
def hex_segment(primary_color)
|
64
|
+
primary_color.to_s(16).rjust(2, '0').upcase
|
12
65
|
end
|
13
66
|
|
14
|
-
|
15
|
-
|
67
|
+
# http://www.easyrgb.com/index.php?X=MATH&H=02#text2
|
68
|
+
def to_xyz
|
69
|
+
r, g, b = [@red, @green, @blue].map do |value|
|
70
|
+
value = value / 255.0
|
71
|
+
if value > 0.04045
|
72
|
+
value = ((value + 0.055) / 1.055) ** 2.4
|
73
|
+
else
|
74
|
+
value = value / 12.92
|
75
|
+
end
|
76
|
+
value * 100.0
|
77
|
+
end
|
78
|
+
|
79
|
+
x = r * 0.4124 + g * 0.3576 + b * 0.1805
|
80
|
+
y = r * 0.2126 + g * 0.7152 + b * 0.0722
|
81
|
+
z = r * 0.0193 + g * 0.1192 + b * 0.9505
|
82
|
+
|
83
|
+
[x, y, z]
|
16
84
|
end
|
17
85
|
end
|
18
86
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'cocaine'
|
2
|
+
|
3
|
+
class Borealis
|
4
|
+
class ImageConverter
|
5
|
+
def initialize(image, size)
|
6
|
+
@image = image
|
7
|
+
@size = size || '50x50'
|
8
|
+
end
|
9
|
+
|
10
|
+
def read_image
|
11
|
+
Cocaine::CommandLine.new(
|
12
|
+
'convert',
|
13
|
+
"#{@image} -resize #{@size} txt:-"
|
14
|
+
).run
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_colors
|
18
|
+
colors_for(read_image)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def colors_for(data)
|
24
|
+
lines = data.split("\n").drop(1)
|
25
|
+
lines.map do |line|
|
26
|
+
line = line.scan(/\(([\d+\s+,]+)\)/).flatten
|
27
|
+
rgb_values = line.first.split(',').map { |value| value.to_i }
|
28
|
+
|
29
|
+
Color.new(*rgb_values)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Borealis
|
2
|
+
class KMeans
|
3
|
+
def initialize(colors, options = {})
|
4
|
+
@colors = colors
|
5
|
+
@number_of_clusters = options[:number_of_clusters] || 3
|
6
|
+
@delta = options[:delta] || 0.01
|
7
|
+
@static = options.has_key?(:static) ? options[:static] : true
|
8
|
+
|
9
|
+
if @number_of_clusters > @colors.length
|
10
|
+
raise 'You may not have more clusters than colors.'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
if @colors.length == @number_of_clusters
|
16
|
+
@colors.map { |color| Cluster.new(color) }
|
17
|
+
else
|
18
|
+
max_delta = 0
|
19
|
+
while max_delta <= @delta do
|
20
|
+
@colors.each { |color| add_to_closest_cluster(color) }
|
21
|
+
max_delta = clusters.map { |cluster| cluster.recenter! }.max
|
22
|
+
end
|
23
|
+
|
24
|
+
clusters
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def clusters
|
31
|
+
@clusters ||= @number_of_clusters.times.map do |index|
|
32
|
+
if @static
|
33
|
+
# Find spaced out colors that are always the same
|
34
|
+
color_index = @colors.length * index / @number_of_clusters
|
35
|
+
color = @colors[color_index]
|
36
|
+
else
|
37
|
+
color = @colors.sample
|
38
|
+
@colors.delete color
|
39
|
+
end
|
40
|
+
|
41
|
+
Cluster.new(color)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_to_closest_cluster(color)
|
46
|
+
closest_cluster = clusters.min do |a, b|
|
47
|
+
color.rgb_distance_to(a.center) <=> color.rgb_distance_to(b.center)
|
48
|
+
end
|
49
|
+
|
50
|
+
closest_cluster.colors << color
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/borealis/version.rb
CHANGED
@@ -1,29 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Borealis, '#colors' do
|
4
|
-
it 'returns the
|
5
|
-
image = 'spec/fixtures/
|
6
|
-
|
7
|
-
expect(histogram.colors).to eq histogram.colors.sort
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
describe Borealis, '#hexes' do
|
12
|
-
it 'returns the correct hexes for the data provided' do
|
13
|
-
image = 'spec/fixtures/winter.jpg'
|
14
|
-
histogram = Borealis.new(image, FakeAnalyzer)
|
15
|
-
expect(histogram.hexes).to eq %w(#2A5E9F #2C2D48)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class FakeAnalyzer
|
20
|
-
def initialize(file)
|
21
|
-
end
|
4
|
+
it 'returns the number of colors requested' do
|
5
|
+
image = 'spec/fixtures/aurora_borealis.jpg'
|
6
|
+
borealis = Borealis.new(image, colors: 6)
|
22
7
|
|
23
|
-
|
24
|
-
<<-eos
|
25
|
-
51076: ( 1, 2,3) #2A5E9F srgb(1,2,3)
|
26
|
-
47529: ( 4, 5, 6) #2C2D48 srgb(4,5,6)
|
27
|
-
eos
|
8
|
+
expect(borealis.colors.length).to eq 6
|
28
9
|
end
|
29
10
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Borealis::Cluster, '#recenter!' do
|
4
|
+
it 'returns the distance to the old center' do
|
5
|
+
center = Borealis::Color.new(1, 2, 3)
|
6
|
+
cluster = Borealis::Cluster.new(center)
|
7
|
+
cluster.colors << Borealis::Color.new(2, 4, 6)
|
8
|
+
cluster.colors << Borealis::Color.new(4, 6, 8)
|
9
|
+
|
10
|
+
cluster.recenter!
|
11
|
+
|
12
|
+
expect(cluster.center).to eq Borealis::Color.new(3, 5, 7)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'creates a new color in the center of existing colors' do
|
16
|
+
center = stub('center', :rgb_distance_to)
|
17
|
+
cluster = Borealis::Cluster.new(center)
|
18
|
+
cluster.colors << Borealis::Color.new(2, 4, 6)
|
19
|
+
cluster.colors << Borealis::Color.new(4, 6, 8)
|
20
|
+
|
21
|
+
cluster.recenter!
|
22
|
+
|
23
|
+
expect(cluster.center).to eq Borealis::Color.new(3, 5, 7)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'empties its colors' do
|
27
|
+
center = stub('center', :rgb_distance_to)
|
28
|
+
cluster = Borealis::Cluster.new(center)
|
29
|
+
cluster.colors << Borealis::Color.new(1, 2, 3)
|
30
|
+
|
31
|
+
cluster.recenter!
|
32
|
+
|
33
|
+
expect(cluster.colors).to be_empty
|
34
|
+
end
|
35
|
+
end
|
data/spec/borealis/color_spec.rb
CHANGED
@@ -1,20 +1,48 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Borealis::Color, '#
|
4
|
-
it 'calculates the distance correctly' do
|
5
|
-
color = Borealis::Color.new(
|
6
|
-
|
3
|
+
describe Borealis::Color, '#distance_to' do
|
4
|
+
it 'calculates the RGB distance correctly' do
|
5
|
+
color = Borealis::Color.new(1, 2, 3)
|
6
|
+
other_color = Borealis::Color.new(2, 3, 4)
|
7
|
+
distance = Math.sqrt((1 - 2) ** 2 + (2 - 3) ** 2 + (3 - 4) ** 2)
|
8
|
+
|
9
|
+
expect(color.rgb_distance_to(other_color)).to eq distance
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Borealis::Color, '#rgb' do
|
14
|
+
it 'returns a string with the correct rgb value' do
|
15
|
+
color = Borealis::Color.new(1, 2, 3)
|
16
|
+
expect(color.rgb).to eq '(1, 2, 3)'
|
7
17
|
end
|
8
18
|
end
|
9
19
|
|
10
|
-
describe Borealis::Color, '
|
11
|
-
it '
|
12
|
-
|
13
|
-
|
14
|
-
|
20
|
+
describe Borealis::Color, '#hex' do
|
21
|
+
it 'returns a string with the correct hex value' do
|
22
|
+
color = Borealis::Color.new(222, 2, 15)
|
23
|
+
expect(color.hex).to eq '#DE020F'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Borealis::Color, 'equality' do
|
28
|
+
it 'returns true if the colors have the same rgb' do
|
29
|
+
color = Borealis::Color.new(1, 2, 3)
|
30
|
+
expect(color).to eq Borealis::Color.new(1, 2, 3)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns false if the colors have different rgbs' do
|
34
|
+
color = Borealis::Color.new(1, 2, 3)
|
35
|
+
expect(color).not_to eq Borealis::Color.new(2, 2, 3)
|
36
|
+
end
|
37
|
+
end
|
15
38
|
|
16
|
-
|
39
|
+
describe Borealis::Color, '#to_lab' do
|
40
|
+
it 'converts rgb to lab correctly' do
|
41
|
+
color = Borealis::Color.new(255, 204, 0)
|
17
42
|
|
18
|
-
|
43
|
+
# http://www.easyrgb.com/index.php?X=CALC#Result
|
44
|
+
expect(color.to_lab[0]).to be_within(0.01).of(84.197)
|
45
|
+
expect(color.to_lab[1]).to be_within(0.01).of(3.680)
|
46
|
+
expect(color.to_lab[2]).to be_within(0.01).of(85.223)
|
19
47
|
end
|
20
48
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Borealis::ImageConverter, '#to_colors' do
|
4
|
+
it 'returns as many colors as there are pixels' do
|
5
|
+
image = 'spec/fixtures/aurora_borealis.jpg'
|
6
|
+
colors = Borealis::ImageConverter.new(image, '10x10!').to_colors
|
7
|
+
|
8
|
+
expect(colors.length).to eq 100
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Borealis::ImageConverter, '#read_image' do
|
13
|
+
it 'returns colors in the correct format' do
|
14
|
+
image = 'spec/fixtures/aurora_borealis.jpg'
|
15
|
+
image_data = Borealis::ImageConverter.new(image, '10x10').read_image
|
16
|
+
|
17
|
+
expect(image_data).to match(/\(([\s+\d+,]+)\).+([\#][0-9,A-F,a-f]{6}).+(rgb)/)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'raises Cocaine::ExitStatusError when the image is not present' do
|
21
|
+
image = 'nonexistent.jpg'
|
22
|
+
image_converter = Borealis::ImageConverter.new(image, 'dimensions')
|
23
|
+
|
24
|
+
silence_stderr do
|
25
|
+
expect { image_converter.read_image }.to raise_error(Cocaine::ExitStatusError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Borealis::KMeans do
|
4
|
+
it 'raises if there are more clusters than colors' do
|
5
|
+
colors = []
|
6
|
+
3.times { colors << Borealis::Color.new(1, 2, 3) }
|
7
|
+
|
8
|
+
expect do
|
9
|
+
Borealis::KMeans.new(colors, number_of_clusters: 4)
|
10
|
+
end.to raise_error 'You may not have more clusters than colors.'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Borealis::KMeans, '#run' do
|
15
|
+
it 'sets the colors as cluster centers if there are as many colors as clusters' do
|
16
|
+
colors = []
|
17
|
+
3.times { colors << stub('color') }
|
18
|
+
|
19
|
+
clusters = Borealis::KMeans.new(colors, number_of_clusters: 3).run
|
20
|
+
|
21
|
+
expect(clusters.map(&:center)).to eq colors
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns number_of_clusters clusters' do
|
25
|
+
colors = []
|
26
|
+
20.times { |index| colors << Borealis::Color.new(index, index * 2, index * 3) }
|
27
|
+
|
28
|
+
clusters = Borealis::KMeans.new(colors, number_of_clusters: 8).run
|
29
|
+
|
30
|
+
expect(clusters.length).to eq 8
|
31
|
+
end
|
32
|
+
end
|
Binary file
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,49 +1,58 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: borealis
|
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
|
- Josh Steiner
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-10-11 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: rspec
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bourne
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
28
39
|
- !ruby/object:Gem::Version
|
29
40
|
version: '0'
|
30
41
|
- !ruby/object:Gem::Dependency
|
31
42
|
name: cocaine
|
32
43
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
44
|
requirements:
|
35
|
-
- -
|
45
|
+
- - '>='
|
36
46
|
- !ruby/object:Gem::Version
|
37
47
|
version: '0'
|
38
48
|
type: :runtime
|
39
49
|
prerelease: false
|
40
50
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
51
|
requirements:
|
43
|
-
- -
|
52
|
+
- - '>='
|
44
53
|
- !ruby/object:Gem::Version
|
45
54
|
version: '0'
|
46
|
-
description: Finds
|
55
|
+
description: Finds most prominent colors in a given image.
|
47
56
|
email:
|
48
57
|
- josh@jsteiner.me
|
49
58
|
executables: []
|
@@ -56,40 +65,48 @@ files:
|
|
56
65
|
- LICENSE.txt
|
57
66
|
- README.md
|
58
67
|
- Rakefile
|
68
|
+
- bin/stubs/autospec
|
69
|
+
- bin/stubs/htmldiff
|
70
|
+
- bin/stubs/ldiff
|
71
|
+
- bin/stubs/rspec
|
59
72
|
- borealis.gemspec
|
60
73
|
- lib/borealis.rb
|
74
|
+
- lib/borealis/cluster.rb
|
61
75
|
- lib/borealis/color.rb
|
62
|
-
- lib/borealis/
|
76
|
+
- lib/borealis/image_converter.rb
|
77
|
+
- lib/borealis/kmeans.rb
|
63
78
|
- lib/borealis/version.rb
|
64
79
|
- spec/borealis/borealis_spec.rb
|
80
|
+
- spec/borealis/cluster_spec.rb
|
65
81
|
- spec/borealis/color_spec.rb
|
66
|
-
- spec/borealis/
|
82
|
+
- spec/borealis/image_converter_spec.rb
|
83
|
+
- spec/borealis/kmeans_spec.rb
|
67
84
|
- spec/borealis/version_spec.rb
|
68
|
-
- spec/fixtures/
|
85
|
+
- spec/fixtures/aurora_borealis.jpg
|
69
86
|
- spec/spec_helper.rb
|
70
87
|
- spec/support/silence_stderr.rb
|
71
88
|
homepage: ''
|
72
89
|
licenses: []
|
90
|
+
metadata: {}
|
73
91
|
post_install_message:
|
74
92
|
rdoc_options: []
|
75
93
|
require_paths:
|
76
94
|
- lib
|
77
95
|
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
-
none: false
|
79
96
|
requirements:
|
80
|
-
- -
|
97
|
+
- - '>='
|
81
98
|
- !ruby/object:Gem::Version
|
82
99
|
version: '0'
|
83
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
-
none: false
|
85
101
|
requirements:
|
86
|
-
- -
|
102
|
+
- - '>='
|
87
103
|
- !ruby/object:Gem::Version
|
88
104
|
version: '0'
|
89
|
-
requirements:
|
105
|
+
requirements:
|
106
|
+
- ImageMagick
|
90
107
|
rubyforge_project:
|
91
|
-
rubygems_version:
|
108
|
+
rubygems_version: 2.0.3
|
92
109
|
signing_key:
|
93
|
-
specification_version:
|
94
|
-
summary: Finds
|
110
|
+
specification_version: 4
|
111
|
+
summary: Finds most prominent colors in a given image.
|
95
112
|
test_files: []
|
@@ -1,22 +0,0 @@
|
|
1
|
-
class Borealis
|
2
|
-
class ImageAnalyzer
|
3
|
-
def initialize(image)
|
4
|
-
@image = image
|
5
|
-
end
|
6
|
-
|
7
|
-
def process
|
8
|
-
line = Cocaine::CommandLine.new(histogram_command)
|
9
|
-
line.run.strip
|
10
|
-
end
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def histogram_command
|
15
|
-
quantize = "convert #{@image} +dither -colors 10 gif:-"
|
16
|
-
histogramize = "convert gif:- -define histogram:unique-colors=true \
|
17
|
-
-format %c histogram:info:-"
|
18
|
-
|
19
|
-
"#{quantize} | #{histogramize}"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Borealis::ImageAnalyzer, '#process' do
|
4
|
-
it 'returns data in the correct format' do
|
5
|
-
image = 'spec/fixtures/winter.jpg'
|
6
|
-
analysis = Borealis::ImageAnalyzer.new(image).process
|
7
|
-
|
8
|
-
expect(analysis).to match(/\(([\s+\d+,]+)\).+([\#][0-9,A-F,a-f]{6})/)
|
9
|
-
end
|
10
|
-
|
11
|
-
it 'returns 10 lines' do
|
12
|
-
image = 'spec/fixtures/winter.jpg'
|
13
|
-
analysis = Borealis::ImageAnalyzer.new(image).process
|
14
|
-
|
15
|
-
expect(analysis.lines.count).to eq 10
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'raises Cocaine::ExitStatusError when the image is not present' do
|
19
|
-
image = 'spec/fixtures/nonexistent.jpg'
|
20
|
-
|
21
|
-
silence_stderr do
|
22
|
-
expect {
|
23
|
-
Borealis::ImageAnalyzer.new(image).process
|
24
|
-
}.to raise_error(Cocaine::ExitStatusError)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
data/spec/fixtures/winter.jpg
DELETED
Binary file
|