color_sort 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -0
- data/lib/color_sort.rb +7 -0
- data/lib/color_sort/closest_pair_finder.rb +35 -0
- data/lib/color_sort/group_sorter.rb +33 -0
- data/lib/color_sort/grouped.rb +10 -0
- data/lib/color_sort/grouped/base_group.rb +30 -0
- data/lib/color_sort/grouped/empty_group.rb +9 -0
- data/lib/color_sort/grouped/group_list.rb +30 -0
- data/lib/color_sort/grouped/multiple_item_group.rb +51 -0
- data/lib/color_sort/grouped/single_item_group.rb +17 -0
- data/lib/color_sort/ordering.rb +17 -4
- data/lib/color_sort/version.rb +1 -1
- data/spec/lib/closest_pair_finder_spec.rb +11 -0
- data/spec/lib/group_list_spec.rb +29 -0
- data/spec/lib/ordering_spec.rb +32 -23
- data/spec/spec_helper.rb +35 -0
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9304b816bb21e888326ea5ece5445f87d3ed7d93
|
4
|
+
data.tar.gz: 96d2388ce66d707a4d7fb5fda94f3d0e6a714e87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 015c771e85c643b3e14568fe98701a4eb140e8d3f4831ca57d1d5a660957eb3b0798885883e8399941539ac65568abd2fab74ac25ecf06c1ad5e722d1b2da8ec
|
7
|
+
data.tar.gz: 4bdf11e523a9bdbc6bb38f9e006d5be39e5cafd0cf94ba49e3346e7b69fa68d21460ca648446b82f5204e3954be4c31b58312b776d425c282028de6056ef023b
|
data/README.md
CHANGED
@@ -33,6 +33,19 @@ sorted_colors = ColorSort.sort(unsorted_colors)
|
|
33
33
|
# => ["94ec1e", "93e482", "39ae1e", "35c047", "5f9d4c", "7f40ed"]
|
34
34
|
```
|
35
35
|
|
36
|
+
You can also sort multiple groups of colors with
|
37
|
+
`ColorSort.sort_groups`. This will leave the groups in the order
|
38
|
+
originally given, but will sort the colors within the groups. It will
|
39
|
+
also order them such that the last color in a group and the first color in
|
40
|
+
the next group are as close as possible.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
unsorted_colors = [["35c047", "7f40ed"], ["39ae1e"], ["5f9d4c", "94ec1e", "93e482"]]
|
44
|
+
|
45
|
+
sorted_colors = ColorSort.sort_groups(unsorted_colors)
|
46
|
+
# => [["7f40ed", "35c047"], ["39ae1e"], ["5f9d4c", "93e482", "94ec1e"]]
|
47
|
+
```
|
48
|
+
|
36
49
|
## How it works
|
37
50
|
|
38
51
|
Often when dealing with colors on computers, we're working with the [RGB color model](http://en.wikipedia.org/wiki/Color_model#RGB_color_model). As each color has three components, we can think of it as a point in 3D space. This allows us to use the [Euclidean distance](http://en.wikipedia.org/wiki/Euclidean_distance) to see how far apart two colors are.
|
data/lib/color_sort.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require "color_sort/version"
|
2
2
|
require "color_sort/ordering"
|
3
|
+
require "color_sort/closest_pair_finder"
|
4
|
+
require "color_sort/grouped"
|
5
|
+
require "color_sort/group_sorter"
|
3
6
|
require "color_sort/distance"
|
4
7
|
require "color_sort/color_space_converter"
|
5
8
|
require "color_sort/sorter"
|
@@ -8,4 +11,8 @@ module ColorSort
|
|
8
11
|
def self.sort(colors)
|
9
12
|
Sorter.new(colors).sorted
|
10
13
|
end
|
14
|
+
|
15
|
+
def self.sort_groups(groups)
|
16
|
+
GroupSorter.new(groups).sorted
|
17
|
+
end
|
11
18
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ColorSort
|
2
|
+
class ClosestPairFinder
|
3
|
+
def initialize(group_a, group_b)
|
4
|
+
@group_a = group_a
|
5
|
+
@group_b = group_b
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.pair(*args)
|
9
|
+
new(*args).pair
|
10
|
+
end
|
11
|
+
|
12
|
+
def pair
|
13
|
+
shortest_distance = 1_000_000
|
14
|
+
closest_pair = nil
|
15
|
+
|
16
|
+
group_a.each do |color_a|
|
17
|
+
group_b.each do |color_b|
|
18
|
+
if (current_distance = distance(color_a, color_b)) < shortest_distance
|
19
|
+
shortest_distance = current_distance
|
20
|
+
closest_pair = [color_a, color_b]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
closest_pair
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
attr_reader :group_a, :group_b
|
30
|
+
|
31
|
+
def distance(color_a, color_b)
|
32
|
+
ColorSort::Distance.ciede2000(color_a, color_b)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ColorSort
|
2
|
+
class GroupSorter
|
3
|
+
def initialize(groups)
|
4
|
+
@groups = groups
|
5
|
+
end
|
6
|
+
|
7
|
+
def sorted
|
8
|
+
Grouped::GroupList.new(lab_groups).sorted.map { |group|
|
9
|
+
group.map { |lab_color|
|
10
|
+
color_map[lab_color]
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
attr_reader :groups
|
17
|
+
|
18
|
+
def color_map
|
19
|
+
@color_map ||= groups.flatten.each_with_object({}) { |hex_rgb_color, hash|
|
20
|
+
lab_color = ColorSort::ColorSpaceConverter.hex_rgb_to_lab(hex_rgb_color)
|
21
|
+
hash[lab_color] = hex_rgb_color
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def lab_groups
|
26
|
+
@lab_groups ||= groups.map { |group|
|
27
|
+
group.map { |hex_rgb_color|
|
28
|
+
ColorSort::ColorSpaceConverter.hex_rgb_to_lab(hex_rgb_color)
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require "color_sort/grouped/base_group"
|
2
|
+
require "color_sort/grouped/empty_group"
|
3
|
+
require "color_sort/grouped/single_item_group"
|
4
|
+
require "color_sort/grouped/multiple_item_group"
|
5
|
+
require "color_sort/grouped/group_list"
|
6
|
+
|
7
|
+
module ColorSort
|
8
|
+
module Grouped
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ColorSort
|
2
|
+
module Grouped
|
3
|
+
class BaseGroup
|
4
|
+
attr_accessor :previous_group, :next_group
|
5
|
+
|
6
|
+
def initialize(items)
|
7
|
+
@items = items
|
8
|
+
end
|
9
|
+
|
10
|
+
def sorted
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_color
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def end_color
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
items.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
attr_reader :items
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ColorSort
|
2
|
+
module Grouped
|
3
|
+
class GroupList
|
4
|
+
def initialize(initial_groups)
|
5
|
+
@groups = initial_groups.map { |items| initialize_group(items) }
|
6
|
+
groups.reject(&:empty?).each_cons(2) do |group_a, group_b|
|
7
|
+
group_a.next_group = group_b
|
8
|
+
group_b.previous_group = group_a
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def sorted
|
13
|
+
groups.map(&:sorted)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_reader :groups
|
18
|
+
|
19
|
+
def initialize_group(items)
|
20
|
+
if items.size == 0
|
21
|
+
EmptyGroup.new(items)
|
22
|
+
elsif items.size == 1
|
23
|
+
SingleItemGroup.new(items)
|
24
|
+
else
|
25
|
+
MultipleItemGroup.new(items)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ColorSort
|
2
|
+
module Grouped
|
3
|
+
class MultipleItemGroup < BaseGroup
|
4
|
+
def sorted
|
5
|
+
ordering = Ordering.new(start: start_color, finish: end_color)
|
6
|
+
items_except(start_color, end_color).each do |color|
|
7
|
+
ordering.add_lab_color(color)
|
8
|
+
end
|
9
|
+
ordering.colors
|
10
|
+
end
|
11
|
+
|
12
|
+
def start_color
|
13
|
+
@start_color ||= calculate_start_color
|
14
|
+
end
|
15
|
+
|
16
|
+
def end_color
|
17
|
+
@end_color ||= calculate_end_color
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def calculate_end_color
|
22
|
+
if next_group.nil?
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
closest_color(items_except(start_color), next_group.items)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def calculate_start_color
|
30
|
+
if previous_group.nil? || previous_group.end_color.nil?
|
31
|
+
nil
|
32
|
+
else
|
33
|
+
closest_color(items, [previous_group.end_color])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def closest_color(my_colors, other_colors)
|
38
|
+
color, _ = *ClosestPairFinder.pair(my_colors, other_colors)
|
39
|
+
color
|
40
|
+
end
|
41
|
+
|
42
|
+
def items_except(*ignored_items)
|
43
|
+
remaining = items.dup
|
44
|
+
ignored_items.each do |ignored_item|
|
45
|
+
remaining.delete_at(remaining.index(ignored_item)) unless ignored_item.nil?
|
46
|
+
end
|
47
|
+
remaining
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/color_sort/ordering.rb
CHANGED
@@ -4,16 +4,27 @@ module ColorSort
|
|
4
4
|
class Ordering
|
5
5
|
attr_reader :colors
|
6
6
|
|
7
|
-
def initialize
|
7
|
+
def initialize(start: nil, finish: nil)
|
8
8
|
@colors = []
|
9
|
+
@start = start
|
10
|
+
@finish = finish
|
11
|
+
colors << start unless start.nil?
|
12
|
+
colors << finish unless finish.nil?
|
9
13
|
end
|
10
14
|
|
11
15
|
def add_lab_color(color)
|
12
16
|
if colors.size < 2
|
13
|
-
|
17
|
+
if finish.nil?
|
18
|
+
colors << color
|
19
|
+
else
|
20
|
+
colors.unshift(color)
|
21
|
+
end
|
14
22
|
else
|
15
|
-
min_delta = distance(color, colors.first)
|
16
23
|
min_delta_index = 0
|
24
|
+
min_delta = 1_000_000
|
25
|
+
if start.nil?
|
26
|
+
min_delta = distance(color, colors.first)
|
27
|
+
end
|
17
28
|
|
18
29
|
(1...colors.size).each do |i|
|
19
30
|
remove_distance = distance(colors[i-1], colors[i])
|
@@ -25,7 +36,7 @@ module ColorSort
|
|
25
36
|
end
|
26
37
|
end
|
27
38
|
|
28
|
-
if distance(colors.last, color) < min_delta
|
39
|
+
if finish.nil? && distance(colors.last, color) < min_delta
|
29
40
|
min_delta_index = colors.size
|
30
41
|
end
|
31
42
|
|
@@ -34,6 +45,8 @@ module ColorSort
|
|
34
45
|
end
|
35
46
|
|
36
47
|
private
|
48
|
+
attr_reader :start, :finish
|
49
|
+
|
37
50
|
def distance(color_a, color_b)
|
38
51
|
ColorSort::Distance.ciede2000(color_a, color_b)
|
39
52
|
end
|
data/lib/color_sort/version.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ColorSort::ClosestPairFinder, :color_doubles do
|
4
|
+
describe ".pair" do
|
5
|
+
subject { ColorSort::ClosestPairFinder }
|
6
|
+
|
7
|
+
it "returns the pair of colors (one from each group) that are closest" do
|
8
|
+
expect(subject.pair([red, green], [blue])).to eq([red, blue])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ColorSort::Grouped::GroupList, :color_doubles do
|
4
|
+
describe "#sorted" do
|
5
|
+
let(:group_a) { [red, green, blue, yellow] }
|
6
|
+
let(:group_b) { [yellow] }
|
7
|
+
let(:group_c) { [red, blue, green] }
|
8
|
+
let(:group_d) { [] }
|
9
|
+
let(:group_e) { [red, blue, yellow] }
|
10
|
+
let(:group_f) { [] }
|
11
|
+
|
12
|
+
let(:expected_a) { [green, blue, red, yellow] }
|
13
|
+
let(:expected_b) { [yellow] }
|
14
|
+
let(:expected_c) { [red, green, blue] }
|
15
|
+
let(:expected_d) { [] }
|
16
|
+
let(:expected_e) { [blue, red, yellow] }
|
17
|
+
let(:expected_f) { [] }
|
18
|
+
|
19
|
+
it "sorts the colors within their groups" do
|
20
|
+
grouped_sort = ColorSort::Grouped::GroupList.new(
|
21
|
+
[group_a, group_b, group_c, group_d, group_e, group_f]
|
22
|
+
)
|
23
|
+
|
24
|
+
expect(grouped_sort.sorted).to eq(
|
25
|
+
[expected_a, expected_b, expected_c, expected_d, expected_e, expected_f]
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/lib/ordering_spec.rb
CHANGED
@@ -1,20 +1,6 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe ColorSort::Ordering do
|
4
|
-
let(:red) { double(:red) }
|
5
|
-
let(:green) { double(:green) }
|
6
|
-
let(:blue) { double(:blue) }
|
7
|
-
|
8
|
-
before do
|
9
|
-
set_distance(red, red, 0)
|
10
|
-
set_distance(green, green, 0)
|
11
|
-
set_distance(blue, blue, 0)
|
12
|
-
|
13
|
-
set_distance(red, blue, 1)
|
14
|
-
set_distance(blue, green, 2)
|
15
|
-
set_distance(red, green, 3)
|
16
|
-
end
|
17
|
-
|
3
|
+
describe ColorSort::Ordering, :color_doubles do
|
18
4
|
describe "#add_lab_color" do
|
19
5
|
context "with an empty ordering" do
|
20
6
|
it "adds the color to the end of the array" do
|
@@ -64,14 +50,37 @@ describe ColorSort::Ordering do
|
|
64
50
|
end
|
65
51
|
end
|
66
52
|
end
|
67
|
-
end
|
68
53
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
54
|
+
context "with a fixed start color" do
|
55
|
+
subject { ColorSort::Ordering.new(start: blue) }
|
56
|
+
|
57
|
+
it "doesn't add color at the start, even if it minimises total distance" do
|
58
|
+
subject.add_lab_color(green)
|
59
|
+
subject.add_lab_color(red)
|
60
|
+
|
61
|
+
expect(subject.colors).to eq([blue, red, green])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with a fixed finish color" do
|
66
|
+
subject { ColorSort::Ordering.new(finish: blue) }
|
67
|
+
|
68
|
+
it "doesn't add color at end, even if it minimises total distance" do
|
69
|
+
subject.add_lab_color(green)
|
70
|
+
subject.add_lab_color(red)
|
71
|
+
|
72
|
+
expect(subject.colors).to eq([green, red, blue])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "with fixed start and finish colors" do
|
77
|
+
subject { ColorSort::Ordering.new(start: red, finish: blue) }
|
78
|
+
|
79
|
+
it "doesn't add color at start or end, even if it minimises total distance" do
|
80
|
+
subject.add_lab_color(green)
|
81
|
+
|
82
|
+
expect(subject.colors).to eq([red, green, blue])
|
83
|
+
end
|
84
|
+
end
|
76
85
|
end
|
77
86
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,38 @@
|
|
1
1
|
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
2
|
require 'color_sort'
|
3
3
|
require "rspec/radar"
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
|
8
|
+
shared_context "with color doubles", :color_doubles do
|
9
|
+
let(:red) { double(:red) }
|
10
|
+
let(:green) { double(:green) }
|
11
|
+
let(:blue) { double(:blue) }
|
12
|
+
let(:yellow) { double(:yellow) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
set_distance(red, red, 0)
|
16
|
+
set_distance(green, green, 0)
|
17
|
+
set_distance(blue, blue, 0)
|
18
|
+
set_distance(yellow, yellow, 0)
|
19
|
+
|
20
|
+
set_distance(red, blue, 1)
|
21
|
+
set_distance(blue, green, 2)
|
22
|
+
set_distance(red, green, 3)
|
23
|
+
|
24
|
+
set_distance(yellow, red, 4)
|
25
|
+
set_distance(yellow, green, 5)
|
26
|
+
set_distance(yellow, blue, 6)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_distance(color_a, color_b, distance)
|
30
|
+
allow(ColorSort::Distance).to receive(:ciede2000)
|
31
|
+
.with(color_a, color_b)
|
32
|
+
.and_return(distance)
|
33
|
+
allow(ColorSort::Distance).to receive(:ciede2000)
|
34
|
+
.with(color_b, color_a)
|
35
|
+
.and_return(distance)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: color_sort
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pip Taylor
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorscore
|
@@ -109,14 +109,24 @@ files:
|
|
109
109
|
- Rakefile
|
110
110
|
- color_sort.gemspec
|
111
111
|
- lib/color_sort.rb
|
112
|
+
- lib/color_sort/closest_pair_finder.rb
|
112
113
|
- lib/color_sort/color_space_converter.rb
|
113
114
|
- lib/color_sort/distance.rb
|
115
|
+
- lib/color_sort/group_sorter.rb
|
116
|
+
- lib/color_sort/grouped.rb
|
117
|
+
- lib/color_sort/grouped/base_group.rb
|
118
|
+
- lib/color_sort/grouped/empty_group.rb
|
119
|
+
- lib/color_sort/grouped/group_list.rb
|
120
|
+
- lib/color_sort/grouped/multiple_item_group.rb
|
121
|
+
- lib/color_sort/grouped/single_item_group.rb
|
114
122
|
- lib/color_sort/ordering.rb
|
115
123
|
- lib/color_sort/sorter.rb
|
116
124
|
- lib/color_sort/version.rb
|
117
125
|
- spec/color_sort_spec.rb
|
126
|
+
- spec/lib/closest_pair_finder_spec.rb
|
118
127
|
- spec/lib/color_space_converter_spec.rb
|
119
128
|
- spec/lib/distance_spec.rb
|
129
|
+
- spec/lib/group_list_spec.rb
|
120
130
|
- spec/lib/ordering_spec.rb
|
121
131
|
- spec/lib/sorter_spec.rb
|
122
132
|
- spec/spec_helper.rb
|
@@ -146,8 +156,10 @@ specification_version: 4
|
|
146
156
|
summary: Sorts colors perceptually
|
147
157
|
test_files:
|
148
158
|
- spec/color_sort_spec.rb
|
159
|
+
- spec/lib/closest_pair_finder_spec.rb
|
149
160
|
- spec/lib/color_space_converter_spec.rb
|
150
161
|
- spec/lib/distance_spec.rb
|
162
|
+
- spec/lib/group_list_spec.rb
|
151
163
|
- spec/lib/ordering_spec.rb
|
152
164
|
- spec/lib/sorter_spec.rb
|
153
165
|
- spec/spec_helper.rb
|