color_diff 0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d4856b49dfb2ca4f6fc555def253b3aa586cdc01
4
+ data.tar.gz: 1f38316a2e62d067e0edf4772f03d787bc057be2
5
+ SHA512:
6
+ metadata.gz: 6d9d78fb545168a91a75f303a8e5f3fa36bd32bac2057b27621ccd992954ebf97d3ddc8a800d4575e439bab248910859f0f662113f38155716254b09d881356d
7
+ data.tar.gz: faceec60200cf5b91ad725db8d9400d329c6ba6a55aeb426f0f4a3b6f3f022af6381bc0eb5426da03e0df9460be88ed6da5146b3fab49dae2447777924f63ec4
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Daniel Hanson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18
+ SOFTWARE.
@@ -0,0 +1,41 @@
1
+ # Color Diff
2
+
3
+ Calculating differences between colors using the CIEDE2000 algorithm
4
+
5
+ ## Install
6
+
7
+ gem install color_diff
8
+
9
+ ## Usage
10
+
11
+ Difference between two colors:
12
+
13
+ yellow = ColorDiff::Color::RGB.new 255, 255, 0
14
+ gold = ColorDiff::Color::RGB.new 255, 215, 0
15
+
16
+ ColorDiff.between yellow, gold
17
+ # => 11.584521223499245
18
+
19
+ Picking the most similar color from a list:
20
+
21
+ red = ColorDiff::Color::RGB.new 255, 0, 0
22
+ blue = ColorDiff::Color::RGB.new 0, 0, 255
23
+ yellow = ColorDiff::Color::RGB.new 255, 255, 0
24
+ gold = ColorDiff::Color::RGB.new 255, 215, 0
25
+
26
+ list = ColorDiff::List.new [red, blue, yellow]
27
+ list.closest_to(gold) == yellow
28
+ # => true
29
+
30
+ Building a map of nearest match colors from a finite palette:
31
+
32
+ red = ColorDiff::Color::RGB.new 255, 0, 0
33
+ black = ColorDiff::Color::RGB.new 0, 0, 0
34
+ dark_gray = ColorDiff::Color::RGB.new 80, 80, 80
35
+ maroon = ColorDiff::Color::RGB.new 128, 0, 0
36
+
37
+ list = ColorDiff::List.new [red, black]
38
+ palette = [dark_gray, maroon]
39
+
40
+ list.closest_to_map(palette)
41
+ # => { 'R255G0B0' => maroon, 'R0B0G0' => dark_gray }
@@ -0,0 +1,35 @@
1
+ require 'color_diff/color/rgb'
2
+ require 'color_diff/color/xyz'
3
+ require 'color_diff/color/lab'
4
+ require 'color_diff/ciede2000'
5
+ require 'color_diff/list'
6
+
7
+ module ColorDiff
8
+ # = Color Diff
9
+ #
10
+ # Color Diff contains a number of methods to assist in measuring how similar
11
+ # or dissimilar colors are from one another
12
+ #
13
+ # == Simple Comparison
14
+ #
15
+ # Two color objects can be compared like so:
16
+ #
17
+ # yellow = ColorDiff::Color::RGB.new(255, 255, 0)
18
+ # gold = ColorDiff::Color::RGB.new(255, 215, 0)
19
+ # ColorDiff.between(yellow, gold) => 11.584521223499245
20
+ #
21
+ # The higher the result the more dissimilar the colors, e.g.
22
+ #
23
+ # blue = ColorDiff::Color::RGB.new(0, 0, 255)
24
+ # red = ColorDiff::Color::RGB.new(255, 0, 0)
25
+ # ColorDiff.between(blue, red) => 52.87867414046132
26
+ #
27
+ # See ColorDiff::List for example of more complex comparisons
28
+
29
+ def self.between(lab1, lab2)
30
+ lab1 = lab1.to_lab unless lab1.is_a? Color::Lab
31
+ lab2 = lab2.to_lab unless lab2.is_a? Color::Lab
32
+
33
+ ColorDiff::Ciede2000.diff(lab1, lab2)
34
+ end
35
+ end
@@ -0,0 +1,115 @@
1
+ module ColorDiff
2
+ class Ciede2000
3
+ def self.diff(c1, c2)
4
+ # get lab values for color 1
5
+ l1 = c1.l
6
+ a1 = c1.a
7
+ b1 = c1.b
8
+
9
+ # get lab values for color 2
10
+ l2 = c2.l
11
+ a2 = c2.a
12
+ b2 = c2.b
13
+
14
+ # weight factors
15
+ kl = 1
16
+ kc = 1
17
+ kh = 1
18
+
19
+ # Step 1: Calculate c1p, c2p, h1p, h2p
20
+ #
21
+ c1 = Math.sqrt((a1 ** 2) + (b1 ** 2))
22
+ c2 = Math.sqrt((a2 ** 2) + (b2 ** 2))
23
+
24
+ a_c1_c2 = (c1 + c2) / 2.0
25
+
26
+ g = 0.5 * (1 - Math.sqrt((a_c1_c2 ** 7.0) / ((a_c1_c2 ** 7.0) + (25.0 ** 7.0))))
27
+
28
+ a1p = (1.0 + g) * a1
29
+ a2p = (1.0 + g) * a2
30
+ c1p = Math.sqrt((a1p ** 2) + (b1 ** 2))
31
+ c2p = Math.sqrt((a2p ** 2) + (b2 ** 2))
32
+ h1p = hp_f(b1, a1p)
33
+ h2p = hp_f(b2, a2p)
34
+
35
+ # Step 2: calculate dlp, dcp, dhp
36
+ #
37
+ dlp = l2 - l1
38
+ dcp = c2p - c1p
39
+ dhp_tmp = dhp_f(c1, c2, h1p, h2p)
40
+ dhp = 2 * Math.sqrt(c1p * c2p) * Math.sin(radians(dhp_tmp) / 2.0)
41
+
42
+ # Step 3: calculate CIEDE2000 Color-Difference
43
+ #
44
+ a_l = (l1 + l2) / 2.0
45
+ a_cp = (c1p + c2p) / 2.0
46
+ a_hp = a_hp_f(c1, c2, h1p, h2p)
47
+ t = 1 -
48
+ 0.17 * Math.cos(radians(a_hp - 30)) +
49
+ 0.24 * Math.cos(radians(2 * a_hp)) +
50
+ 0.32 * Math.cos(radians(3 * a_hp + 6)) -
51
+ 0.20 * Math.cos(radians(4 * a_hp - 63))
52
+ d_ro = 30 * Math.exp(-(((a_hp - 275) / 25) ** 2))
53
+ rc = Math.sqrt((a_cp ** 7.0) / ((a_cp ** 7.0) + (25.0 ** 7.0)))
54
+ sl = 1 + ((0.015 * ((a_l - 50) ** 2)) / Math.sqrt(20 + ((a_l - 50) ** 2.0)))
55
+ sc = 1 + 0.045 * a_cp
56
+ sh = 1 + 0.015 * a_cp * t
57
+ rt = -2 * rc * Math.sin(radians(2 * d_ro))
58
+ de = Math.sqrt(
59
+ ((dlp / (sl * kl)) ** 2) +
60
+ ((dcp / (sc * kc)) ** 2) +
61
+ ((dhp / (sh * kh)) ** 2) +
62
+ rt * (dcp / (sc * kc)) * (dhp / (sh * kh)) )
63
+
64
+ de
65
+ end
66
+
67
+ def self.degrees(n)
68
+ 180 / Math::PI * n
69
+ end
70
+
71
+ def self.radians(n)
72
+ Math::PI / 180 * n
73
+ end
74
+
75
+ def self.hp_f(x, y)
76
+ 0 if x.zero? && y.zero?
77
+
78
+ tmphp = degrees Math.atan2(x, y)
79
+ if tmphp >= 0
80
+ tmphp
81
+ else
82
+ tmphp + 360
83
+ end
84
+ end
85
+
86
+ def self.dhp_f(c1, c2, h1p, h2p)
87
+ diff = h2p - h1p
88
+
89
+ if c1 * c2 == 0
90
+ 0
91
+ elsif diff.abs <= 180
92
+ diff
93
+ elsif diff > 180
94
+ diff - 360
95
+ elsif diff < -180
96
+ diff + 360
97
+ end
98
+ end
99
+
100
+ def self.a_hp_f(c1, c2, h1p, h2p)
101
+ sum = h1p + h2p
102
+ diff = h1p - h2p
103
+
104
+ if c1 * c2 == 0
105
+ sum
106
+ elsif diff.abs <= 180
107
+ sum / 2.0
108
+ elsif diff.abs > 180 && sum < 360
109
+ (sum + 360) / 2.0
110
+ elsif diff.abs > 180 && sum >= 360
111
+ (sum - 360) / 2.0
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,46 @@
1
+ module ColorDiff
2
+ module Color
3
+ class Lab
4
+ attr_reader :l, :a, :b
5
+
6
+ PRECISION = 3
7
+
8
+ def initialize(l = 0, a = 0, b = 0)
9
+ @l = l
10
+ @a = a
11
+ @b = b
12
+ end
13
+
14
+ def self.from_xyz(xyz)
15
+ ref_x = 95.047
16
+ ref_y = 100.000
17
+ ref_z = 108.883
18
+ x = xyz.x / ref_x
19
+ y = xyz.y / ref_y
20
+ z = xyz.z / ref_z
21
+
22
+ x = normalize_xyz_component(x)
23
+ y = normalize_xyz_component(y)
24
+ z = normalize_xyz_component(z)
25
+
26
+ l = (116 * y) - 16
27
+ a = 500 * (x - y)
28
+ b = 200 * (y - z)
29
+
30
+ new(l, a, b)
31
+ end
32
+
33
+ def self.normalize_xyz_component(c)
34
+ if c > 0.008856
35
+ c ** (1 / 3.0)
36
+ else
37
+ (c * 7.787) + (16 / 116.0)
38
+ end
39
+ end
40
+
41
+ def to_s
42
+ "L#{@l.round(PRECISION)}A#{@a.round(PRECISION)}B#{@b.round(PRECISION)}"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ module ColorDiff
2
+ module Color
3
+ class RGB
4
+ attr_reader :r, :g, :b
5
+
6
+ def initialize(r = 0, g = 0, b = 0)
7
+ @r = r
8
+ @g = g
9
+ @b = b
10
+ end
11
+
12
+ def to_xyz
13
+ Xyz.from_rgb(self)
14
+ end
15
+
16
+ def to_lab
17
+ to_xyz.to_lab
18
+ end
19
+
20
+ def to_s
21
+ "R#{@r}G#{@g}B#{@b}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ module ColorDiff
2
+ module Color
3
+ class Xyz
4
+ attr_reader :x, :y, :z
5
+
6
+ def initialize(x = 0, y = 0, z = 0)
7
+ @x = x
8
+ @y = y
9
+ @z = z
10
+ end
11
+
12
+ def to_lab
13
+ Lab.from_xyz(self)
14
+ end
15
+
16
+ def self.from_rgb(rgb)
17
+ r = normalize_rgb_component rgb.r
18
+ g = normalize_rgb_component rgb.g
19
+ b = normalize_rgb_component rgb.b
20
+
21
+ x = r * 0.4124 + g * 0.3576 + b * 0.1805
22
+ y = r * 0.2126 + g * 0.7152 + b * 0.0722
23
+ z = r * 0.0193 + g * 0.1192 + b * 0.9505
24
+
25
+ new(x, y, z)
26
+ end
27
+
28
+ def self.normalize_rgb_component(c)
29
+ c = c / 255.0
30
+
31
+ if c > 0.04045
32
+ c = ((c + 0.055) / 1.055) ** 2.4
33
+ else
34
+ c = c / 12.92
35
+ end
36
+
37
+ c * 100
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,67 @@
1
+ module ColorDiff
2
+ class List
3
+ # +items+ An array of ColorDiff::Colors
4
+ def initialize(items)
5
+ @items = items
6
+ end
7
+
8
+ # Compares all +items+ in the list to the supplied +color+ and returns
9
+ # the most similar
10
+ #
11
+ # +color+ ColorDiff::Color to compare against the list
12
+ #
13
+ # == Example
14
+ #
15
+ # red = ColorDiff::Color::RGB.new(255, 0, 0)
16
+ # blue = ColorDiff::Color::RGB.new(0, 0, 255)
17
+ # yellow = ColorDiff::Color::RGB.new(255, 255, 0)
18
+ # gold = ColorDiff::Color::RGB.new(255, 215, 0)
19
+ #
20
+ # list = ColorDiff::List.new [red, blue, yellow]
21
+ # list.closest_to(gold) == yellow => true
22
+ #
23
+ def closest_to(color)
24
+ closest_color = nil
25
+ closest_diff = 100
26
+
27
+ @items.each do |item|
28
+ diff = ColorDiff.between(color, item)
29
+ if diff < closest_diff
30
+ closest_diff = diff
31
+ closest_color = item
32
+ end
33
+ end
34
+
35
+ closest_color
36
+ end
37
+
38
+ # Compares all +items+ in the list against the supplied +palette+ and
39
+ # maps each item to the closest palette color. The resulting hash is
40
+ # returned
41
+ #
42
+ # +palette+ Array of ColorDiff::Colors to compare against the list
43
+ #
44
+ # == Example
45
+ #
46
+ # red = ColorDiff::Color::RGB.new(255, 0, 0)
47
+ # black = ColorDiff::Color::RGB.new(0, 0, 0)
48
+ # dark_gray = ColorDiff::Color::RGB.new(80, 80, 80)
49
+ # maroon = ColorDiff::Color::RGB.new(128, 0, 0)
50
+ #
51
+ # list = ColorDiff::List.new [red, black]
52
+ # palette = [dark_gray, maroon]
53
+ #
54
+ # list.closest_to_map(palette) =>
55
+ # { 'R255G0B0' => maroon, 'R0B0G0' => dark_gray }
56
+ #
57
+ def closest_to_map(palette)
58
+ map = {}
59
+ options = List.new(palette)
60
+ @items.each do |item|
61
+ map[item.to_s] = options.closest_to item
62
+ end
63
+
64
+ map
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module ColorDiff
2
+ VERSION = '0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: color_diff
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Hanson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ description: Calculate RGB color distances using CIEDE2000 formula
28
+ email: hansondr@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - LICENSE
34
+ - README.md
35
+ - lib/color_diff.rb
36
+ - lib/color_diff/ciede2000.rb
37
+ - lib/color_diff/color/lab.rb
38
+ - lib/color_diff/color/rgb.rb
39
+ - lib/color_diff/color/xyz.rb
40
+ - lib/color_diff/list.rb
41
+ - lib/color_diff/version.rb
42
+ homepage: https://github.com/hansondr/color_diff
43
+ licenses:
44
+ - MIT
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.6.8
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Calculate rgb color distances using CIEDE2000 formula
66
+ test_files: []