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 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