abachrome 0.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 +7 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +5 -0
- data/README.md +99 -0
- data/demos/ncurses/plasma.rb +124 -0
- data/devenv.lock +100 -0
- data/devenv.nix +51 -0
- data/devenv.yaml +15 -0
- data/lib/abachrome/abc_decimal.rb +161 -0
- data/lib/abachrome/color.rb +74 -0
- data/lib/abachrome/color_mixins/blend.rb +45 -0
- data/lib/abachrome/color_mixins/lighten.rb +39 -0
- data/lib/abachrome/color_mixins/to_colorspace.rb +38 -0
- data/lib/abachrome/color_mixins/to_lrgb.rb +49 -0
- data/lib/abachrome/color_mixins/to_oklab.rb +48 -0
- data/lib/abachrome/color_mixins/to_oklch.rb +48 -0
- data/lib/abachrome/color_mixins/to_srgb.rb +63 -0
- data/lib/abachrome/color_models/hsv.rb +22 -0
- data/lib/abachrome/color_models/oklab.rb +16 -0
- data/lib/abachrome/color_models/oklch.rb +47 -0
- data/lib/abachrome/color_models/rgb.rb +28 -0
- data/lib/abachrome/color_space.rb +97 -0
- data/lib/abachrome/converter.rb +59 -0
- data/lib/abachrome/converters/base.rb +57 -0
- data/lib/abachrome/converters/lrgb_to_oklab.rb +27 -0
- data/lib/abachrome/converters/lrgb_to_srgb.rb +30 -0
- data/lib/abachrome/converters/oklab_to_lrgb.rb +42 -0
- data/lib/abachrome/converters/oklab_to_oklch.rb +23 -0
- data/lib/abachrome/converters/oklab_to_srgb.rb +17 -0
- data/lib/abachrome/converters/oklch_to_lrgb.rb +15 -0
- data/lib/abachrome/converters/oklch_to_oklab.rb +23 -0
- data/lib/abachrome/converters/oklch_to_srgb.rb +18 -0
- data/lib/abachrome/converters/srgb_to_lrgb.rb +27 -0
- data/lib/abachrome/converters/srgb_to_oklab.rb +15 -0
- data/lib/abachrome/converters/srgb_to_oklch.rb +18 -0
- data/lib/abachrome/gamut/base.rb +72 -0
- data/lib/abachrome/gamut/p3.rb +25 -0
- data/lib/abachrome/gamut/rec2020.rb +23 -0
- data/lib/abachrome/gamut/srgb.rb +27 -0
- data/lib/abachrome/illuminants/base.rb +33 -0
- data/lib/abachrome/illuminants/d50.rb +31 -0
- data/lib/abachrome/illuminants/d55.rb +27 -0
- data/lib/abachrome/illuminants/d65.rb +35 -0
- data/lib/abachrome/illuminants/d75.rb +27 -0
- data/lib/abachrome/named/css.rb +164 -0
- data/lib/abachrome/outputs/css.rb +117 -0
- data/lib/abachrome/palette.rb +131 -0
- data/lib/abachrome/palette_mixins/interpolate.rb +31 -0
- data/lib/abachrome/palette_mixins/resample.rb +59 -0
- data/lib/abachrome/palette_mixins/stretch_luminance.rb +70 -0
- data/lib/abachrome/parsers/hex.rb +50 -0
- data/lib/abachrome/to_abcd.rb +13 -0
- data/lib/abachrome/version.rb +5 -0
- data/lib/abachrome.rb +99 -0
- metadata +172 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../color"
|
4
|
+
require_relative "../color_space"
|
5
|
+
|
6
|
+
module Abachrome
|
7
|
+
module Outputs
|
8
|
+
class CSS
|
9
|
+
def self.format(color, gamut: nil, companding: nil)
|
10
|
+
rgb_color = color.to_rgb
|
11
|
+
r, g, b = rgb_color.coordinates
|
12
|
+
a = rgb_color.alpha
|
13
|
+
|
14
|
+
# Apply gamut mapping if provided
|
15
|
+
r, g, b = gamut.map([r, g, b]) if gamut
|
16
|
+
|
17
|
+
# Apply companding if provided
|
18
|
+
if companding
|
19
|
+
r = companding.call(r)
|
20
|
+
g = companding.call(g)
|
21
|
+
b = companding.call(b)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert to 8-bit values
|
25
|
+
r = (r * 255).round
|
26
|
+
g = (g * 255).round
|
27
|
+
b = (b * 255).round
|
28
|
+
|
29
|
+
# Format based on alpha value
|
30
|
+
return Kernel.format("rgba(%d, %d, %d, %.3f)", r, g, b, a) unless a == AbcDecimal.new("1.0")
|
31
|
+
return Kernel.format("#%02x%02x%02x", r, g, b) unless r == g && g == b
|
32
|
+
|
33
|
+
# Use shortened hex format for grayscale
|
34
|
+
hex = Kernel.format("%02x", r)
|
35
|
+
"##{hex}#{hex}#{hex}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.format_hex(color, gamut: nil, companding: nil)
|
39
|
+
rgb_color = color.to_rgb
|
40
|
+
r, g, b = rgb_color.coordinates
|
41
|
+
a = rgb_color.alpha
|
42
|
+
|
43
|
+
# Apply gamut mapping if provided
|
44
|
+
r, g, b = gamut.map([r, g, b]) if gamut
|
45
|
+
|
46
|
+
# Apply companding if provided
|
47
|
+
if companding
|
48
|
+
r = companding.call(r)
|
49
|
+
g = companding.call(g)
|
50
|
+
b = companding.call(b)
|
51
|
+
end
|
52
|
+
|
53
|
+
r = (r * 255).round
|
54
|
+
g = (g * 255).round
|
55
|
+
b = (b * 255).round
|
56
|
+
|
57
|
+
if a == AbcDecimal.new("1.0")
|
58
|
+
Kernel.format("#%02x%02x%02x", r, g, b)
|
59
|
+
else
|
60
|
+
a = (a * 255).round
|
61
|
+
Kernel.format("#%02x%02x%02x%02x", r, g, b, a)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.format_rgb(color, gamut: nil, companding: nil)
|
66
|
+
rgb_color = color.to_rgb
|
67
|
+
r, g, b = rgb_color.coordinates
|
68
|
+
a = rgb_color.alpha
|
69
|
+
|
70
|
+
# Apply gamut mapping if provided
|
71
|
+
r, g, b = gamut.map([r, g, b]) if gamut
|
72
|
+
|
73
|
+
# Apply companding if provided
|
74
|
+
if companding
|
75
|
+
r = companding.call(r)
|
76
|
+
g = companding.call(g)
|
77
|
+
b = companding.call(b)
|
78
|
+
end
|
79
|
+
|
80
|
+
r = (r * 255).round
|
81
|
+
g = (g * 255).round
|
82
|
+
b = (b * 255).round
|
83
|
+
|
84
|
+
if a == AbcDecimal.new("1.0")
|
85
|
+
Kernel.format("rgb(%d, %d, %d)", r, g, b)
|
86
|
+
else
|
87
|
+
Kernel.format("rgba(%d, %d, %d, %.3f)", r, g, b, a)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.format_oklab(color, gamut: nil, companding: nil, precision: 3)
|
92
|
+
oklab_color = color.to_oklab
|
93
|
+
l, a, b = oklab_color.coordinates
|
94
|
+
alpha = oklab_color.alpha
|
95
|
+
|
96
|
+
# Apply gamut mapping if provided
|
97
|
+
l, a, b = gamut.map([l, a, b]) if gamut
|
98
|
+
|
99
|
+
# Apply companding if provided
|
100
|
+
if companding
|
101
|
+
l = companding.call(l)
|
102
|
+
a = companding.call(a)
|
103
|
+
b = companding.call(b)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Format with appropriate precision
|
107
|
+
format_string = "%.#{precision}f %.#{precision}f %.#{precision}f"
|
108
|
+
|
109
|
+
if alpha == AbcDecimal.new("1.0")
|
110
|
+
Kernel.format("oklab(#{format_string})", l, a, b)
|
111
|
+
else
|
112
|
+
Kernel.format("oklab(#{format_string} / %.#{precision}f)", l, a, b, alpha)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abachrome
|
4
|
+
class Palette
|
5
|
+
attr_reader :colors
|
6
|
+
|
7
|
+
def initialize(colors = [])
|
8
|
+
@colors = colors.map { |c| c.is_a?(Color) ? c : Color.from_hex(c.to_s) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(color)
|
12
|
+
color = Color.from_hex(color.to_s) unless color.is_a?(Color)
|
13
|
+
@colors << color
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
alias << add
|
18
|
+
|
19
|
+
def remove(color)
|
20
|
+
@colors.delete(color)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@colors.clear
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def size
|
30
|
+
@colors.size
|
31
|
+
end
|
32
|
+
|
33
|
+
def empty?
|
34
|
+
@colors.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def each(&block)
|
38
|
+
@colors.each(&block)
|
39
|
+
end
|
40
|
+
def each_with_index(&block)
|
41
|
+
@colors.each_with_index(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def map(&block)
|
45
|
+
self.class.new(@colors.map(&block))
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_a
|
49
|
+
@colors.dup
|
50
|
+
end
|
51
|
+
|
52
|
+
def [](index)
|
53
|
+
@colors[index]
|
54
|
+
end
|
55
|
+
|
56
|
+
def slice(start, length = nil)
|
57
|
+
new_colors = length ? @colors[start, length] : @colors[start]
|
58
|
+
self.class.new(new_colors)
|
59
|
+
end
|
60
|
+
|
61
|
+
def first
|
62
|
+
@colors.first
|
63
|
+
end
|
64
|
+
|
65
|
+
def last
|
66
|
+
@colors.last
|
67
|
+
end
|
68
|
+
|
69
|
+
def sort_by_lightness
|
70
|
+
self.class.new(@colors.sort_by(&:lightness))
|
71
|
+
end
|
72
|
+
|
73
|
+
def sort_by_saturation
|
74
|
+
self.class.new(@colors.sort_by { |c| c.to_oklab.coordinates[1] })
|
75
|
+
end
|
76
|
+
|
77
|
+
def blend_all(amount = 0.5)
|
78
|
+
return nil if empty?
|
79
|
+
|
80
|
+
result = first
|
81
|
+
@colors[1..].each do |color|
|
82
|
+
result = result.blend(color, amount)
|
83
|
+
end
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
def average
|
88
|
+
return nil if empty?
|
89
|
+
|
90
|
+
oklab_coords = @colors.map(&:to_oklab).map(&:coordinates)
|
91
|
+
avg_coords = oklab_coords.reduce([0, 0, 0]) do |sum, coords|
|
92
|
+
[sum[0] + coords[0], sum[1] + coords[1], sum[2] + coords[2]]
|
93
|
+
end
|
94
|
+
avg_coords.map! { |c| c / size }
|
95
|
+
|
96
|
+
Color.new(
|
97
|
+
ColorSpace.find(:oklab),
|
98
|
+
avg_coords,
|
99
|
+
@colors.map(&:alpha).sum / size
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_css(format: :hex)
|
104
|
+
to_a.map do |color|
|
105
|
+
case format
|
106
|
+
when :hex
|
107
|
+
Outputs::CSS.format_hex(color)
|
108
|
+
when :rgb
|
109
|
+
Outputs::CSS.format_rgb(color)
|
110
|
+
when :oklab
|
111
|
+
Outputs::CSS.format_oklab(color)
|
112
|
+
else
|
113
|
+
Outputs::CSS.format(color)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def inspect
|
119
|
+
"#<#{self.class} colors=#{@colors.map(&:to_s)}>"
|
120
|
+
end
|
121
|
+
|
122
|
+
mixins_path = File.join(__dir__, "palette_mixins", "*.rb")
|
123
|
+
Dir[mixins_path].each do |file|
|
124
|
+
require file
|
125
|
+
mixin_name = File.basename(file, ".rb")
|
126
|
+
inflector = Dry::Inflector.new
|
127
|
+
mixin_module = Abachrome::PaletteMixins.const_get(inflector.camelize(mixin_name))
|
128
|
+
include mixin_module
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abachrome
|
4
|
+
module PaletteMixins
|
5
|
+
module Interpolate
|
6
|
+
def interpolate(count_between = 1)
|
7
|
+
return self if count_between < 1 || size < 2
|
8
|
+
|
9
|
+
new_colors = []
|
10
|
+
@colors.each_cons(2) do |color1, color2|
|
11
|
+
new_colors << color1
|
12
|
+
step = AbcDecimal("1.0") / AbcDecimal(count_between + 1)
|
13
|
+
|
14
|
+
(1..count_between).each do |i|
|
15
|
+
amount = step * i
|
16
|
+
new_colors << color1.blend(color2, amount)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
new_colors << last
|
20
|
+
|
21
|
+
self.class.new(new_colors)
|
22
|
+
end
|
23
|
+
|
24
|
+
def interpolate!(count_between = 1)
|
25
|
+
interpolated = interpolate(count_between)
|
26
|
+
@colors = interpolated.colors
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abachrome
|
4
|
+
module PaletteMixins
|
5
|
+
module Resample
|
6
|
+
def resample(new_size)
|
7
|
+
return self if new_size == size || empty?
|
8
|
+
return self.class.new([@colors.first]) if new_size == 1
|
9
|
+
|
10
|
+
step = (size - 1).to_f / (new_size - 1)
|
11
|
+
|
12
|
+
self.class.new(
|
13
|
+
(0...new_size).map do |i|
|
14
|
+
index = i * step
|
15
|
+
lower_index = index.floor
|
16
|
+
upper_index = [lower_index + 1, size - 1].min
|
17
|
+
|
18
|
+
if lower_index == upper_index
|
19
|
+
@colors[lower_index]
|
20
|
+
else
|
21
|
+
fraction = index - lower_index
|
22
|
+
@colors[lower_index].blend(@colors[upper_index], fraction)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def resample!(new_size)
|
29
|
+
resampled = resample(new_size)
|
30
|
+
@colors = resampled.colors
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def expand(new_size)
|
35
|
+
raise ArgumentError, "New size must be larger than current size" if new_size <= size
|
36
|
+
|
37
|
+
resample(new_size)
|
38
|
+
end
|
39
|
+
|
40
|
+
def expand!(new_size)
|
41
|
+
raise ArgumentError, "New size must be larger than current size" if new_size <= size
|
42
|
+
|
43
|
+
resample!(new_size)
|
44
|
+
end
|
45
|
+
|
46
|
+
def reduce(new_size)
|
47
|
+
raise ArgumentError, "New size must be smaller than current size" if new_size >= size
|
48
|
+
|
49
|
+
resample(new_size)
|
50
|
+
end
|
51
|
+
|
52
|
+
def reduce!(new_size)
|
53
|
+
raise ArgumentError, "New size must be smaller than current size" if new_size >= size
|
54
|
+
|
55
|
+
resample!(new_size)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abachrome
|
4
|
+
module PaletteMixins
|
5
|
+
module StretchLuminance
|
6
|
+
def stretch_luminance(new_min: 0.0, new_max: 1.0)
|
7
|
+
return self if empty?
|
8
|
+
|
9
|
+
new_min = AbcDecimal(new_min)
|
10
|
+
new_max = AbcDecimal(new_max)
|
11
|
+
|
12
|
+
oklab_colors = @colors.map(&:to_oklab)
|
13
|
+
current_min = oklab_colors.map { |c| c.coordinates[0] }.min
|
14
|
+
current_max = oklab_colors.map { |c| c.coordinates[0] }.max
|
15
|
+
|
16
|
+
range = current_max - current_min
|
17
|
+
new_range = new_max - new_min
|
18
|
+
|
19
|
+
self.class.new(
|
20
|
+
oklab_colors.map do |color|
|
21
|
+
l, a, b = color.coordinates
|
22
|
+
scaled_l = if range.zero?
|
23
|
+
new_min
|
24
|
+
else
|
25
|
+
new_min + ((l - current_min) * new_range / range)
|
26
|
+
end
|
27
|
+
|
28
|
+
Color.new(
|
29
|
+
ColorSpace.find(:oklab),
|
30
|
+
[scaled_l, a, b],
|
31
|
+
color.alpha
|
32
|
+
)
|
33
|
+
end
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def stretch_luminance!(new_min: 0.0, new_max: 1.0)
|
38
|
+
stretched = stretch_luminance(new_min: new_min, new_max: new_max)
|
39
|
+
@colors = stretched.colors
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def normalize_luminance
|
44
|
+
stretch_luminance(new_min: 0.0, new_max: 1.0)
|
45
|
+
end
|
46
|
+
|
47
|
+
def normalize_luminance!
|
48
|
+
stretch_luminance!(new_min: 0.0, new_max: 1.0)
|
49
|
+
end
|
50
|
+
|
51
|
+
def compress_luminance(amount = 0.5)
|
52
|
+
amount = AbcDecimal(amount)
|
53
|
+
mid_point = AbcDecimal("0.5")
|
54
|
+
stretch_luminance(
|
55
|
+
new_min: mid_point - (mid_point * amount),
|
56
|
+
new_max: mid_point + (mid_point * amount)
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def compress_luminance!(amount = 0.5)
|
61
|
+
amount = AbcDecimal(amount)
|
62
|
+
mid_point = AbcDecimal("0.5")
|
63
|
+
stretch_luminance!(
|
64
|
+
new_min: mid_point - (mid_point * amount),
|
65
|
+
new_max: mid_point + (mid_point * amount)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Abachrome
|
4
|
+
module Parsers
|
5
|
+
class Hex
|
6
|
+
HEX_PATTERN = /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{8})$/
|
7
|
+
|
8
|
+
def self.parse(input)
|
9
|
+
hex = input.gsub(/^#/, "")
|
10
|
+
return nil unless hex.match?(HEX_PATTERN)
|
11
|
+
|
12
|
+
case hex.length
|
13
|
+
when 3
|
14
|
+
parse_short_hex(hex)
|
15
|
+
when 4
|
16
|
+
parse_short_hex_with_alpha(hex)
|
17
|
+
when 6
|
18
|
+
parse_full_hex(hex)
|
19
|
+
when 8
|
20
|
+
parse_full_hex_with_alpha(hex)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.parse_short_hex(hex)
|
25
|
+
r, g, b = hex.chars.map { |c| (c + c).to_i(16) }
|
26
|
+
Color.from_rgb(r / 255.0, g / 255.0, b / 255.0)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parse_short_hex_with_alpha(hex)
|
30
|
+
r, g, b, a = hex.chars.map { |c| (c + c).to_i(16) }
|
31
|
+
Color.from_rgb(r / 255.0, g / 255.0, b / 255.0, a / 255.0)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.parse_full_hex(hex)
|
35
|
+
r = hex[0, 2].to_i(16)
|
36
|
+
g = hex[2, 2].to_i(16)
|
37
|
+
b = hex[4, 2].to_i(16)
|
38
|
+
Color.from_rgb(r / 255.0, g / 255.0, b / 255.0)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.parse_full_hex_with_alpha(hex)
|
42
|
+
r = hex[0, 2].to_i(16)
|
43
|
+
g = hex[2, 2].to_i(16)
|
44
|
+
b = hex[4, 2].to_i(16)
|
45
|
+
a = hex[6, 2].to_i(16)
|
46
|
+
Color.from_rgb(r / 255.0, g / 255.0, b / 255.0, a / 255.0)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/abachrome.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abachrome/to_abcd"
|
4
|
+
|
5
|
+
module Abachrome
|
6
|
+
module_function
|
7
|
+
|
8
|
+
autoload :AbcDecimal, "abachrome/abc_decimal"
|
9
|
+
autoload :Color, "abachrome/color"
|
10
|
+
autoload :Palette, "abachrome/palette"
|
11
|
+
autoload :ColorSpace, "abachrome/color_space"
|
12
|
+
autoload :Converter, "abachrome/converter"
|
13
|
+
autoload :Gamut, "abachrome/gamut/base"
|
14
|
+
autoload :ToAbcd, "abachrome/to_abcd"
|
15
|
+
autoload :VERSION, "abachrome/version"
|
16
|
+
|
17
|
+
module ColorModels
|
18
|
+
autoload :HSV, "abachrome/color_models/hsv"
|
19
|
+
autoload :Oklab, "abachrome/color_models/oklab"
|
20
|
+
autoload :RGB, "abachrome/color_models/rgb"
|
21
|
+
end
|
22
|
+
|
23
|
+
module ColorMixins
|
24
|
+
autoload :ToLrgb, "abachrome/color_mixins/to_lrgb"
|
25
|
+
autoload :ToOklab, "abachrome/color_mixins/to_oklab"
|
26
|
+
end
|
27
|
+
|
28
|
+
module Converters
|
29
|
+
autoload :Base, "abachrome/converters/base"
|
30
|
+
autoload :LrgbToOklab, "abachrome/converters/lrgb_to_oklab"
|
31
|
+
autoload :OklabToLrgb, "abachrome/converters/oklab_to_lrgb"
|
32
|
+
end
|
33
|
+
|
34
|
+
module Gamut
|
35
|
+
autoload :P3, "abachrome/gamut/p3"
|
36
|
+
autoload :Rec2020, "abachrome/gamut/rec2020"
|
37
|
+
autoload :SRGB, "abachrome/gamut/srgb"
|
38
|
+
end
|
39
|
+
|
40
|
+
module Illuminants
|
41
|
+
autoload :Base, "abachrome/illuminants/base"
|
42
|
+
autoload :D50, "abachrome/illuminants/d50"
|
43
|
+
autoload :D55, "abachrome/illuminants/d55"
|
44
|
+
autoload :D65, "abachrome/illuminants/d65"
|
45
|
+
autoload :D75, "abachrome/illuminants/d75"
|
46
|
+
end
|
47
|
+
|
48
|
+
module Named
|
49
|
+
autoload :CSS, "abachrome/named/css"
|
50
|
+
end
|
51
|
+
|
52
|
+
module Outputs
|
53
|
+
autoload :CSS, "abachrome/outputs/css"
|
54
|
+
end
|
55
|
+
|
56
|
+
module Parsers
|
57
|
+
autoload :Hex, "abachrome/parsers/hex"
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_color(space_name, *coordinates, alpha: 1.0)
|
61
|
+
space = ColorSpace.find(space_name)
|
62
|
+
Color.new(space, coordinates, alpha)
|
63
|
+
end
|
64
|
+
|
65
|
+
def from_rgb(r, g, b, alpha = 1.0)
|
66
|
+
Color.from_rgb(r, g, b, alpha)
|
67
|
+
end
|
68
|
+
|
69
|
+
def from_oklab(l, a, b, alpha = 1.0)
|
70
|
+
Color.from_oklab(l, a, b, alpha)
|
71
|
+
end
|
72
|
+
|
73
|
+
def from_oklch(l, a, b, alpha = 1.0)
|
74
|
+
Color.from_oklch(l, a, b, alpha)
|
75
|
+
end
|
76
|
+
|
77
|
+
def from_hex(hex_str)
|
78
|
+
Parsers::Hex.parse(hex_str)
|
79
|
+
end
|
80
|
+
|
81
|
+
def from_name(color_name)
|
82
|
+
rgb_values = Named::CSS::ColorNames[color_name.downcase]
|
83
|
+
return nil unless rgb_values
|
84
|
+
|
85
|
+
from_rgb(*rgb_values.map { |v| v / 255.0 })
|
86
|
+
end
|
87
|
+
|
88
|
+
def convert(color, to_space)
|
89
|
+
Converter.convert(color, to_space)
|
90
|
+
end
|
91
|
+
|
92
|
+
def register_color_space(name, &block)
|
93
|
+
ColorSpace.register(name, &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def register_converter(from_space, to_space, converter)
|
97
|
+
Converter.register(from_space, to_space, converter)
|
98
|
+
end
|
99
|
+
end
|