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.
- checksums.yaml +7 -0
- data/LICENSE +18 -0
- data/README.md +41 -0
- data/lib/color_diff.rb +35 -0
- data/lib/color_diff/ciede2000.rb +115 -0
- data/lib/color_diff/color/lab.rb +46 -0
- data/lib/color_diff/color/rgb.rb +25 -0
- data/lib/color_diff/color/xyz.rb +41 -0
- data/lib/color_diff/list.rb +67 -0
- data/lib/color_diff/version.rb +3 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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 }
|
data/lib/color_diff.rb
ADDED
@@ -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
|
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: []
|