color_sort 1.0.0 → 1.1.0
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 +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
|