gauguin 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 +15 -0
- data/.rspec +3 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +13 -0
- data/gauguin.gemspec +30 -0
- data/lib/gauguin/color.rb +76 -0
- data/lib/gauguin/color_space/lab_vector.rb +6 -0
- data/lib/gauguin/color_space/rgb_vector.rb +36 -0
- data/lib/gauguin/color_space/xyz_vector.rb +33 -0
- data/lib/gauguin/color_space.rb +4 -0
- data/lib/gauguin/colors_clusterer.rb +63 -0
- data/lib/gauguin/colors_limiter.rb +14 -0
- data/lib/gauguin/colors_retriever.rb +33 -0
- data/lib/gauguin/image.rb +55 -0
- data/lib/gauguin/image_recolorer.rb +29 -0
- data/lib/gauguin/image_repository.rb +7 -0
- data/lib/gauguin/noise_reducer.rb +26 -0
- data/lib/gauguin/painting.rb +29 -0
- data/lib/gauguin/palette_serializer.rb +22 -0
- data/lib/gauguin/version.rb +3 -0
- data/lib/gauguin.rb +43 -0
- data/spec/integration/painting_spec.rb +79 -0
- data/spec/integration/samples_spec.rb +43 -0
- data/spec/lib/gauguin/color_space/rgb_vector_spec.rb +15 -0
- data/spec/lib/gauguin/color_space/xyz_vector_spec.rb +15 -0
- data/spec/lib/gauguin/color_spec.rb +125 -0
- data/spec/lib/gauguin/colors_clusterer_spec.rb +158 -0
- data/spec/lib/gauguin/colors_limiter_spec.rb +27 -0
- data/spec/lib/gauguin/colors_retriever_spec.rb +85 -0
- data/spec/lib/gauguin/image_recolorer_spec.rb +94 -0
- data/spec/lib/gauguin/image_repository_spec.rb +15 -0
- data/spec/lib/gauguin/image_spec.rb +90 -0
- data/spec/lib/gauguin/noise_reducer_spec.rb +51 -0
- data/spec/lib/gauguin/painting_spec.rb +55 -0
- data/spec/lib/gauguin/palette_serializer_spec.rb +24 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/support/pictures/10_colors.png +0 -0
- data/spec/support/pictures/12_colors.png +0 -0
- data/spec/support/pictures/gauguin.png +0 -0
- data/spec/support/pictures/gray_and_black.png +0 -0
- data/spec/support/pictures/not_unique_colors.png +0 -0
- data/spec/support/pictures/samples/sample1.png +0 -0
- data/spec/support/pictures/samples/sample10.png +0 -0
- data/spec/support/pictures/samples/sample11.png +0 -0
- data/spec/support/pictures/samples/sample2.png +0 -0
- data/spec/support/pictures/samples/sample3.png +0 -0
- data/spec/support/pictures/samples/sample4.png +0 -0
- data/spec/support/pictures/samples/sample5.png +0 -0
- data/spec/support/pictures/samples/sample6.png +0 -0
- data/spec/support/pictures/samples/sample7.png +0 -0
- data/spec/support/pictures/samples/sample8.png +0 -0
- data/spec/support/pictures/samples/sample9.png +0 -0
- data/spec/support/pictures/too_many_colors.png +0 -0
- data/spec/support/pictures/transparent_background.png +0 -0
- data/spec/support/pictures/unique_colors.png +0 -0
- metadata +251 -0
data/lib/gauguin.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "gauguin/version"
|
2
|
+
require "gauguin/color"
|
3
|
+
require "gauguin/color_space"
|
4
|
+
require "gauguin/colors_retriever"
|
5
|
+
require "gauguin/colors_limiter"
|
6
|
+
require "gauguin/colors_clusterer"
|
7
|
+
require "gauguin/noise_reducer"
|
8
|
+
require "gauguin/image_recolorer"
|
9
|
+
require "gauguin/painting"
|
10
|
+
require "gauguin/image"
|
11
|
+
require "gauguin/image_repository"
|
12
|
+
require "gauguin/palette_serializer"
|
13
|
+
|
14
|
+
module Gauguin
|
15
|
+
class << self
|
16
|
+
attr_accessor :configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.configure
|
20
|
+
self.configuration ||= Configuration.new
|
21
|
+
yield(configuration) if block_given?
|
22
|
+
end
|
23
|
+
|
24
|
+
class Configuration
|
25
|
+
DEFAULT_MAX_COLORS_COUNT = 10
|
26
|
+
DEFAULT_COLORS_LIMIT = 10000
|
27
|
+
DEFAULT_MIN_PERCENTAGE_SUM = 0.981
|
28
|
+
DEFAULT_COLOR_SIMILARITY_THRESHOLD = 25
|
29
|
+
|
30
|
+
attr_accessor :max_colors_count, :colors_limit,
|
31
|
+
:min_percentage_sum, :color_similarity_threshold
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@max_colors_count = DEFAULT_MAX_COLORS_COUNT
|
35
|
+
@colors_limit = DEFAULT_COLORS_LIMIT
|
36
|
+
@min_percentage_sum = DEFAULT_MIN_PERCENTAGE_SUM
|
37
|
+
@color_similarity_threshold = DEFAULT_COLOR_SIMILARITY_THRESHOLD
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Gauguin.configure
|
43
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin
|
4
|
+
describe Painting do
|
5
|
+
let(:path) do
|
6
|
+
File.join("spec", "support", "pictures", file_name)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:gray) { Color.new(204, 204, 204) }
|
10
|
+
let(:black) { Color.new(0, 0, 0) }
|
11
|
+
let(:white) { Color.new(255, 255, 255) }
|
12
|
+
|
13
|
+
let(:painting) { Painting.new(path) }
|
14
|
+
|
15
|
+
describe "#palette" do
|
16
|
+
shared_examples_for "retrieves unique colors" do
|
17
|
+
it { expect(subject.count).to eq 5 }
|
18
|
+
it do
|
19
|
+
expect(subject.keys).to include(white)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
subject { painting.palette }
|
24
|
+
|
25
|
+
context "unique colors in the picture" do
|
26
|
+
let(:file_name) { "unique_colors.png" }
|
27
|
+
|
28
|
+
it_behaves_like "retrieves unique colors"
|
29
|
+
end
|
30
|
+
|
31
|
+
context "not unique colors in the picture" do
|
32
|
+
let(:file_name) { "not_unique_colors.png" }
|
33
|
+
|
34
|
+
it_behaves_like "retrieves unique colors"
|
35
|
+
end
|
36
|
+
|
37
|
+
context "image has two colors but with different gradients
|
38
|
+
so actually 1942 unique colors" do
|
39
|
+
let(:file_name) { "gray_and_black.png" }
|
40
|
+
let(:values) { subject.values.flatten }
|
41
|
+
|
42
|
+
it { expect(subject.count).to eq 2 }
|
43
|
+
it { expect(values.include?(black)).to be true }
|
44
|
+
it { expect(values.include?(gray)).to be true }
|
45
|
+
end
|
46
|
+
|
47
|
+
context "transparent background" do
|
48
|
+
let(:file_name) { "transparent_background.png" }
|
49
|
+
|
50
|
+
it { expect(subject.count).to eq 1 }
|
51
|
+
it do
|
52
|
+
expect(subject.keys).to eq [Color.new(2, 0, 0)]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "image with 10 colors" do
|
57
|
+
let(:file_name) { "10_colors.png" }
|
58
|
+
|
59
|
+
it { expect(subject.count).to eq 10 }
|
60
|
+
end
|
61
|
+
|
62
|
+
context "image with over than max_colors_count colors" do
|
63
|
+
let(:file_name) { "12_colors.png" }
|
64
|
+
|
65
|
+
it { expect(subject.count).to eq 10 }
|
66
|
+
|
67
|
+
context "image with over than colors_limit colors" do
|
68
|
+
configure(:colors_limit, 9)
|
69
|
+
configure(:max_colors_count, 12)
|
70
|
+
|
71
|
+
it "returns colors_limit colors" do
|
72
|
+
expect(subject.count).to eq 9
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin
|
4
|
+
describe "samples" do
|
5
|
+
def self.picture_path(file_name)
|
6
|
+
File.join("spec", "support", "pictures", file_name)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:painting) { Painting.new(picture_path(file_name)) }
|
10
|
+
|
11
|
+
def self.paths
|
12
|
+
(1..11).map { |i| picture_path(File.join("samples", "sample#{i}.png")) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.expected_results
|
16
|
+
[
|
17
|
+
["rgb(219, 12, 38)", "rgb(255, 255, 255)"],
|
18
|
+
["rgb(168, 36, 40)", "rgb(255, 255, 255)"],
|
19
|
+
["rgb(0, 0, 0)", "rgb(204, 204, 204)"],
|
20
|
+
["rgb(154, 79, 54)", "rgb(187, 196, 201)", "rgb(236, 112, 48)", "rgb(28, 28, 64)", "rgb(92, 54, 59)"],
|
21
|
+
["rgb(254, 254, 254)", "rgb(255, 195, 13)", "rgb(60, 4, 67)"],
|
22
|
+
["rgb(2, 0, 0)"],
|
23
|
+
["rgb(148, 158, 149)", "rgb(198, 64, 63)"],
|
24
|
+
["rgb(109, 207, 246)", "rgb(237, 28, 36)", "rgb(255, 255, 255)"],
|
25
|
+
["rgb(255, 255, 255)", "rgb(87, 196, 15)"],
|
26
|
+
["rgb(240, 110, 170)", "rgb(255, 255, 255)"],
|
27
|
+
["rgb(0, 165, 19)", "rgb(0, 71, 241)", "rgb(230, 27, 49)", "rgb(249, 166, 0)", "rgb(255, 255, 255)"]
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.samples
|
32
|
+
Hash[paths.zip(expected_results)]
|
33
|
+
end
|
34
|
+
|
35
|
+
samples.each do |sample_path, expected_result|
|
36
|
+
it "returns expected result for #{sample_path}" do
|
37
|
+
painting = Painting.new(sample_path)
|
38
|
+
expect(painting.palette.keys.map(&:to_s).sort).to eq(expected_result.sort)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin::ColorSpace
|
4
|
+
describe RgbVector do
|
5
|
+
describe "#to_xyz" do
|
6
|
+
let(:red) { RgbVector[255, 0, 0] }
|
7
|
+
|
8
|
+
it "converts to lab space" do
|
9
|
+
expect(red.to_xyz).to eq(
|
10
|
+
XyzVector[41.24, 21.26, 1.9300000000000002])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin::ColorSpace
|
4
|
+
describe XyzVector do
|
5
|
+
describe "#to_lab" do
|
6
|
+
let(:red) { XyzVector[41.24, 21.26, 1.9300000000000002] }
|
7
|
+
|
8
|
+
it "converts to lab space" do
|
9
|
+
expect(red.to_lab).to eq(
|
10
|
+
LabVector[53.23288178584245, 80.10930952982204, 67.22006831026425])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin
|
4
|
+
describe Color do
|
5
|
+
let(:black) { Color.new(0, 0, 0) }
|
6
|
+
let(:red) { Color.new(255, 0, 0) }
|
7
|
+
|
8
|
+
describe "initialize" do
|
9
|
+
let(:red) { 1 }
|
10
|
+
let(:green) { 2 }
|
11
|
+
let(:blue) { 3 }
|
12
|
+
let(:percentage) { 0.5 }
|
13
|
+
|
14
|
+
subject { Color.new(red, green, blue, percentage) }
|
15
|
+
|
16
|
+
it { expect(subject.red).to eq red }
|
17
|
+
it { expect(subject.green).to eq green }
|
18
|
+
it { expect(subject.blue).to eq blue }
|
19
|
+
it { expect(subject.percentage).to eq percentage }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#==" do
|
23
|
+
it "returns true for colors with the same key values" do
|
24
|
+
expect(black == Color.new(0, 0, 0)).to be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns false if any key value is different" do
|
28
|
+
expect(black == Color.new(0, 0, 1)).to be false
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns false for objects with different classes" do
|
32
|
+
expect(black == "black").to be false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#similar?" do
|
37
|
+
context "similar colors" do
|
38
|
+
it { expect(black.similar?(Color.new(0, 0, 1))).to be true }
|
39
|
+
end
|
40
|
+
|
41
|
+
context "different colors" do
|
42
|
+
it { expect(black.similar?(red)).to be false }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#distance' do
|
47
|
+
it 'returns circa 178.36 between black & red' do
|
48
|
+
expect(black.distance(red)).to be_within(0.01).of(117.34)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#to_lab" do
|
53
|
+
let(:red) { 1 }
|
54
|
+
let(:green) { 2 }
|
55
|
+
let(:blue) { 3 }
|
56
|
+
|
57
|
+
subject { Color.new(red, green, blue).to_lab }
|
58
|
+
|
59
|
+
it "returns lab vector" do
|
60
|
+
rgb_vector = double
|
61
|
+
xyz_vector = double
|
62
|
+
expect(ColorSpace::RgbVector).to receive(:[]).with(red, green, blue).and_return(rgb_vector)
|
63
|
+
expect(rgb_vector).to receive(:to_xyz).and_return(xyz_vector)
|
64
|
+
expect(xyz_vector).to receive(:to_lab)
|
65
|
+
|
66
|
+
subject
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#to_s" do
|
71
|
+
subject { black.to_s }
|
72
|
+
|
73
|
+
it { expect(subject).to eq("rgb(0, 0, 0)") }
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:color) { Color.new(1, 2, 3, 0.4, true) }
|
77
|
+
|
78
|
+
describe "#to_rgb" do
|
79
|
+
subject { color.to_rgb }
|
80
|
+
|
81
|
+
it { expect(subject).to eq([1, 2, 3]) }
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#to_key" do
|
85
|
+
subject { color.to_key }
|
86
|
+
|
87
|
+
it { expect(subject).to eq([1, 2, 3, true]) }
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#to_a" do
|
91
|
+
subject { color.to_a }
|
92
|
+
|
93
|
+
it { expect(subject).to eq([1, 2, 3, 0.4, true]) }
|
94
|
+
end
|
95
|
+
|
96
|
+
describe ".from_a" do
|
97
|
+
subject { Color.from_a([1, 2, 3, 0.4, true]) }
|
98
|
+
|
99
|
+
it { expect(subject.red).to eq(1) }
|
100
|
+
it { expect(subject.green).to eq(2) }
|
101
|
+
it { expect(subject.blue).to eq(3) }
|
102
|
+
it { expect(subject.percentage).to eq(0.4) }
|
103
|
+
it { expect(subject.transparent).to be true }
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#transparent?" do
|
107
|
+
subject { color.transparent? }
|
108
|
+
|
109
|
+
it { expect(subject).to be true }
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#hash" do
|
113
|
+
it "can be used as keys in the hash" do
|
114
|
+
hash = { Color.new(255, 255, 255) => 777 }
|
115
|
+
expect(hash[Color.new(255, 255, 255)]).to eq(777)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#inspect" do
|
120
|
+
subject { color.inspect }
|
121
|
+
|
122
|
+
it { expect(subject).to eq("rgb(1, 2, 3)[0.4][transparent]")}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin
|
4
|
+
describe ColorsClusterer do
|
5
|
+
let(:black) { Color.new(0, 0, 0, 0.597) }
|
6
|
+
let(:white) { Color.new(255, 255, 255, 0.4) }
|
7
|
+
|
8
|
+
let(:clusterer) { ColorsClusterer.new }
|
9
|
+
|
10
|
+
describe "call" do
|
11
|
+
subject { clusterer.call(colors) }
|
12
|
+
|
13
|
+
context "colors is empty" do
|
14
|
+
let(:colors) { [] }
|
15
|
+
|
16
|
+
it { expect(subject).to eq({}) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "colors includes similar colors" do
|
20
|
+
let(:pseudo_black) { Color.new(4, 0, 0, 0.001) }
|
21
|
+
let(:other_pseudo_black) { Color.new(5, 0, 0, 0.001) }
|
22
|
+
let(:another_pseudo_black) { Color.new(6, 0, 0, 0.001) }
|
23
|
+
|
24
|
+
let(:colors) do
|
25
|
+
[black, white, pseudo_black, other_pseudo_black,
|
26
|
+
another_pseudo_black]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "make separate groups for them" do
|
30
|
+
expect(subject).to eq({
|
31
|
+
white => [white],
|
32
|
+
black => [black, pseudo_black, other_pseudo_black,
|
33
|
+
another_pseudo_black]
|
34
|
+
})
|
35
|
+
end
|
36
|
+
|
37
|
+
context do
|
38
|
+
let(:white) { Color.new(255, 255, 255, 0.3) }
|
39
|
+
let(:transparent_white) do
|
40
|
+
Color.new(255, 255, 255, 0.1, Image::Pixel::MAX_TRANSPARENCY)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "make separate groups for fully transparent colors" do
|
44
|
+
colors << transparent_white
|
45
|
+
|
46
|
+
expect(subject).to eq({
|
47
|
+
white => [white],
|
48
|
+
transparent_white => [transparent_white],
|
49
|
+
black => [black, pseudo_black, other_pseudo_black,
|
50
|
+
another_pseudo_black]
|
51
|
+
})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "updates percentage of leader of each group" do
|
56
|
+
subject
|
57
|
+
expect(white.percentage).to eq(0.4)
|
58
|
+
expect(black.percentage).to eq(0.6)
|
59
|
+
end
|
60
|
+
|
61
|
+
context "there is color with bigger percentage
|
62
|
+
than pivot in the group" do
|
63
|
+
before do
|
64
|
+
black.percentage = 0.001
|
65
|
+
other_pseudo_black.percentage = 0.597
|
66
|
+
end
|
67
|
+
|
68
|
+
it "chooses it as pivot" do
|
69
|
+
expect(subject).to eq({
|
70
|
+
white => [white],
|
71
|
+
other_pseudo_black => [black, pseudo_black,
|
72
|
+
other_pseudo_black,
|
73
|
+
another_pseudo_black]
|
74
|
+
})
|
75
|
+
end
|
76
|
+
|
77
|
+
context "pivots are similar" do
|
78
|
+
before do
|
79
|
+
other_pseudo_black.red = 30
|
80
|
+
another_pseudo_black.red = 60
|
81
|
+
end
|
82
|
+
|
83
|
+
it "merge their groups" do
|
84
|
+
expect(subject).to eq({
|
85
|
+
white => [white],
|
86
|
+
other_pseudo_black => [black, pseudo_black,
|
87
|
+
other_pseudo_black,
|
88
|
+
another_pseudo_black]
|
89
|
+
})
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "colors includes different colors" do
|
96
|
+
let(:colors) do
|
97
|
+
[black, white]
|
98
|
+
end
|
99
|
+
|
100
|
+
before do
|
101
|
+
expect(white).to receive(:similar?).
|
102
|
+
with(black).and_return(false)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "make separate groups for them" do
|
106
|
+
expect(subject).to eq({
|
107
|
+
black => [black],
|
108
|
+
white => [white]
|
109
|
+
})
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#clusters" do
|
115
|
+
let(:red) { Color.new(255, 0, 0, 0.1) }
|
116
|
+
let(:colors) { [black, red, white] }
|
117
|
+
|
118
|
+
subject { clusterer.clusters(colors) }
|
119
|
+
|
120
|
+
configure(:max_colors_count, 2)
|
121
|
+
|
122
|
+
before do
|
123
|
+
expect(clusterer).to receive(:call).and_return({
|
124
|
+
black => [black],
|
125
|
+
red => [red],
|
126
|
+
white => [white]
|
127
|
+
})
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns max_colors_count most common colors" do
|
131
|
+
expect(subject).to eq({
|
132
|
+
white => [white],
|
133
|
+
black => [black]
|
134
|
+
})
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "#reversed_clusters" do
|
139
|
+
let(:gray) { Color.new(0, 0, 10, 0.4) }
|
140
|
+
let(:clusters) do
|
141
|
+
{
|
142
|
+
white => [white],
|
143
|
+
black => [black, gray]
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
subject { clusterer.reversed_clusters(clusters) }
|
148
|
+
|
149
|
+
it "returns reversed clusters" do
|
150
|
+
expect(subject).to eq({
|
151
|
+
white => white,
|
152
|
+
black => black,
|
153
|
+
gray => black
|
154
|
+
})
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin
|
4
|
+
describe ColorsLimiter do
|
5
|
+
describe "#limit" do
|
6
|
+
let(:limiter) { ColorsLimiter.new }
|
7
|
+
let(:colors) { [black, red, white] }
|
8
|
+
let(:white) { Color.new(255, 255, 255, 0.01) }
|
9
|
+
let(:red) { Color.new(255, 0, 0, 0.02) }
|
10
|
+
let(:black) { Color.new(0, 0, 0, 0.97) }
|
11
|
+
|
12
|
+
subject { limiter.call(colors) }
|
13
|
+
|
14
|
+
it "returns all colors" do
|
15
|
+
expect(subject).to eq([black, red, white])
|
16
|
+
end
|
17
|
+
|
18
|
+
context "colors count is greater than colors_limit" do
|
19
|
+
configure(:colors_limit, 2)
|
20
|
+
|
21
|
+
it "reduces colors to colors_limit" do
|
22
|
+
expect(subject).to eq([black, red])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin
|
4
|
+
describe ColorsRetriever do
|
5
|
+
let(:retriever) { ColorsRetriever.new(image) }
|
6
|
+
let(:image) do
|
7
|
+
fake = FakeImage.new
|
8
|
+
|
9
|
+
fake.magic_black_pixel = magic_black_pixel
|
10
|
+
fake.magic_white_pixel = magic_white_pixel
|
11
|
+
fake.magic_red_pixel = magic_red_pixel
|
12
|
+
fake.magic_red_little_transparent_pixel = magic_red_little_transparent_pixel
|
13
|
+
|
14
|
+
fake.pixels_repository = {
|
15
|
+
magic_white_pixel => FakeImage::Pixel.new(magic_white_pixel),
|
16
|
+
magic_red_pixel => FakeImage::Pixel.new(magic_red_pixel),
|
17
|
+
magic_black_pixel => FakeImage::Pixel.new(magic_black_pixel),
|
18
|
+
magic_red_little_transparent_pixel => FakeImage::Pixel.new(
|
19
|
+
magic_red_little_transparent_pixel)
|
20
|
+
}
|
21
|
+
|
22
|
+
fake.color_histogram = {
|
23
|
+
magic_white_pixel => 20,
|
24
|
+
magic_black_pixel => 30,
|
25
|
+
magic_red_pixel => 10
|
26
|
+
}
|
27
|
+
|
28
|
+
fake.rows = 10
|
29
|
+
fake.columns = 10
|
30
|
+
|
31
|
+
fake
|
32
|
+
end
|
33
|
+
|
34
|
+
def magic_pixel(rgb, opacity)
|
35
|
+
double(rgb: rgb, opacity: opacity)
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:magic_black_pixel) { magic_pixel([0, 0, 0], 0) }
|
39
|
+
let(:magic_white_pixel) { magic_pixel([255, 255, 255], 0) }
|
40
|
+
let(:magic_red_pixel) { magic_pixel([255, 0, 0], 0) }
|
41
|
+
let(:magic_red_little_transparent_pixel) { magic_pixel([255, 0, 0], 50) }
|
42
|
+
|
43
|
+
describe "#colors" do
|
44
|
+
subject { retriever.colors.sort_by(&:percentage) }
|
45
|
+
|
46
|
+
it "returns array with colors with percentages" do
|
47
|
+
expect(subject).to eq([
|
48
|
+
Color.new(255, 0, 0, 0.1),
|
49
|
+
Color.new(255, 255, 255, 0.2),
|
50
|
+
Color.new(0, 0, 0, 0.3)
|
51
|
+
])
|
52
|
+
end
|
53
|
+
|
54
|
+
context "histogram contains different magic pixels
|
55
|
+
for the same color with different opacity" do
|
56
|
+
before do
|
57
|
+
image.color_histogram[magic_red_little_transparent_pixel] = 40
|
58
|
+
end
|
59
|
+
|
60
|
+
it "sums percentage" do
|
61
|
+
expect(subject).to eq([
|
62
|
+
Color.new(255, 255, 255, 0.2),
|
63
|
+
Color.new(0, 0, 0, 0.3),
|
64
|
+
Color.new(255, 0, 0, 0.5)
|
65
|
+
])
|
66
|
+
end
|
67
|
+
|
68
|
+
context "fully transparent colors" do
|
69
|
+
let(:magic_red_little_transparent_pixel) do
|
70
|
+
magic_pixel([255, 0, 0], Image::Pixel::MAX_TRANSPARENCY)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be treated separately" do
|
74
|
+
expect(subject).to eq([
|
75
|
+
Color.new(255, 0, 0, 0.1),
|
76
|
+
Color.new(255, 255, 255, 0.2),
|
77
|
+
Color.new(0, 0, 0, 0.3),
|
78
|
+
Color.new(255, 0, 0, 0.4, true)
|
79
|
+
])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Gauguin
|
4
|
+
describe ImageRecolorer do
|
5
|
+
describe "#recolor" do
|
6
|
+
let(:image) do
|
7
|
+
fake = FakeImage.new
|
8
|
+
|
9
|
+
fake.pixels = pixels
|
10
|
+
fake.rows = pixels.count
|
11
|
+
fake.columns = pixels.first.count
|
12
|
+
fake.colors_to_pixels = {
|
13
|
+
white.to_s => white_pixel,
|
14
|
+
red.to_s => red_pixel,
|
15
|
+
black.to_s => black_pixel
|
16
|
+
}
|
17
|
+
|
18
|
+
fake
|
19
|
+
end
|
20
|
+
let(:pixels) do
|
21
|
+
[
|
22
|
+
[black_pixel, white_pixel, white_pixel],
|
23
|
+
[black_pixel, black_pixel, white_pixel],
|
24
|
+
[black_pixel, black_pixel, black_pixel]
|
25
|
+
]
|
26
|
+
end
|
27
|
+
let(:image_recolorer) { ImageRecolorer.new(image) }
|
28
|
+
let(:white_pixel) do
|
29
|
+
double('white', to_rgb: [255, 255, 255], transparent?: false)
|
30
|
+
end
|
31
|
+
let(:black_pixel) do
|
32
|
+
double('black', to_rgb: [0, 0, 0], transparent?: false)
|
33
|
+
end
|
34
|
+
let(:red_pixel) do
|
35
|
+
double('red', to_rgb: [255, 0, 0], transparent?: false)
|
36
|
+
end
|
37
|
+
let(:white) { Color.new(255, 255, 255) }
|
38
|
+
let(:black) { Color.new(0, 0, 0) }
|
39
|
+
let(:red) { Color.new(255, 0, 0) }
|
40
|
+
let(:new_colors) do
|
41
|
+
{
|
42
|
+
black => white,
|
43
|
+
white => black
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
subject { image_recolorer.recolor(new_colors) }
|
48
|
+
|
49
|
+
before do
|
50
|
+
allow(Image).to receive(:blank).and_return(image)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "recolors image based on new_colors" do
|
54
|
+
expect(subject.pixels).to eq([
|
55
|
+
[white_pixel, black_pixel, black_pixel],
|
56
|
+
[white_pixel, white_pixel, black_pixel],
|
57
|
+
[white_pixel, white_pixel, white_pixel]
|
58
|
+
])
|
59
|
+
end
|
60
|
+
|
61
|
+
context "transparent pixel" do
|
62
|
+
let(:black_pixel) do
|
63
|
+
double('black', to_rgb: [0, 0, 0], transparent?: true)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "stays the same" do
|
67
|
+
expect(subject.pixels).to eq([
|
68
|
+
[black_pixel, black_pixel, black_pixel],
|
69
|
+
[black_pixel, black_pixel, black_pixel],
|
70
|
+
[black_pixel, black_pixel, black_pixel]
|
71
|
+
])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "color not present in new_colors" do
|
76
|
+
let(:pixels) do
|
77
|
+
[
|
78
|
+
[red_pixel, white_pixel, white_pixel],
|
79
|
+
[red_pixel, red_pixel, white_pixel],
|
80
|
+
[red_pixel, red_pixel, red_pixel]
|
81
|
+
]
|
82
|
+
end
|
83
|
+
|
84
|
+
it "stays the same" do
|
85
|
+
expect(subject.pixels).to eq([
|
86
|
+
[red_pixel, black_pixel, black_pixel],
|
87
|
+
[red_pixel, red_pixel, black_pixel],
|
88
|
+
[red_pixel, red_pixel, red_pixel]
|
89
|
+
])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|