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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 81e0d767639e5bb6b4ffd6915f0b4607ce65b05d
4
- data.tar.gz: b399a98a8bcede7671af2d587e08bc5593a93bc7
3
+ metadata.gz: 9304b816bb21e888326ea5ece5445f87d3ed7d93
4
+ data.tar.gz: 96d2388ce66d707a4d7fb5fda94f3d0e6a714e87
5
5
  SHA512:
6
- metadata.gz: 0c090a1934e79feb4dc86f608b0fc61da779f30de431fc5973aaa3eaafb2b0fd639505c9158e67577cefd79d3e20d67b2dba64f3e7a22d05a725bf6462bd6f82
7
- data.tar.gz: 0ad0740c0cdca320c4f10c10c24abf10f5db10f20623437b47ba082c000a3d1d5031dca9cbb8c0fedefb4726bd9970b7cd939c65b4e197f6bf68b7c1e4406e7b
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.
@@ -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,9 @@
1
+ module ColorSort
2
+ module Grouped
3
+ class EmptyGroup < BaseGroup
4
+ def sorted
5
+ []
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,17 @@
1
+ module ColorSort
2
+ module Grouped
3
+ class SingleItemGroup < BaseGroup
4
+ def sorted
5
+ items
6
+ end
7
+
8
+ def start_color
9
+ @start_color ||= items.first
10
+ end
11
+
12
+ def end_color
13
+ @end_color ||= items.first
14
+ end
15
+ end
16
+ end
17
+ end
@@ -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
- colors << color
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
@@ -1,3 +1,3 @@
1
1
  module ColorSort
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -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
@@ -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
- def set_distance(color_a, color_b, distance)
70
- allow(ColorSort::Distance).to receive(:ciede2000)
71
- .with(color_a, color_b)
72
- .and_return(distance)
73
- allow(ColorSort::Distance).to receive(:ciede2000)
74
- .with(color_b, color_a)
75
- .and_return(distance)
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
@@ -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.0.0
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-04-29 00:00:00.000000000 Z
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