abachrome 0.1.5 → 0.2.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 +4 -4
- data/abachrome.gemspec +1 -0
- data/devenv.nix +1 -1
- data/lib/abachrome/abc_decimal.rb +42 -35
- data/lib/abachrome/color.rb +61 -10
- data/lib/abachrome/color_mixins/blend.rb +7 -5
- data/lib/abachrome/color_mixins/harmonies.rb +187 -0
- data/lib/abachrome/color_mixins/lighten.rb +8 -6
- data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
- data/lib/abachrome/color_mixins/to_colorspace.rb +10 -8
- data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
- data/lib/abachrome/color_mixins/to_lms.rb +106 -0
- data/lib/abachrome/color_mixins/to_lrgb.rb +14 -12
- data/lib/abachrome/color_mixins/to_oklab.rb +16 -14
- data/lib/abachrome/color_mixins/to_oklch.rb +12 -10
- data/lib/abachrome/color_mixins/to_srgb.rb +17 -15
- data/lib/abachrome/color_mixins/to_xyz.rb +106 -0
- data/lib/abachrome/color_mixins/wcag.rb +126 -0
- data/lib/abachrome/color_models/cmyk.rb +160 -0
- data/lib/abachrome/color_models/hsv.rb +5 -3
- data/lib/abachrome/color_models/lms.rb +3 -1
- data/lib/abachrome/color_models/oklab.rb +3 -1
- data/lib/abachrome/color_models/oklch.rb +6 -4
- data/lib/abachrome/color_models/rgb.rb +4 -2
- data/lib/abachrome/color_models/xyz.rb +11 -1
- data/lib/abachrome/color_models/yiq.rb +37 -0
- data/lib/abachrome/color_space.rb +28 -14
- data/lib/abachrome/converter.rb +10 -8
- data/lib/abachrome/converters/base.rb +13 -11
- data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
- data/lib/abachrome/converters/lms_to_lrgb.rb +5 -3
- data/lib/abachrome/converters/lms_to_oklab.rb +28 -0
- data/lib/abachrome/converters/lms_to_srgb.rb +6 -4
- data/lib/abachrome/converters/lms_to_xyz.rb +5 -3
- data/lib/abachrome/converters/lrgb_to_lms.rb +29 -1
- data/lib/abachrome/converters/lrgb_to_oklab.rb +5 -3
- data/lib/abachrome/converters/lrgb_to_srgb.rb +6 -4
- data/lib/abachrome/converters/lrgb_to_xyz.rb +5 -3
- data/lib/abachrome/converters/oklab_to_lms.rb +9 -7
- data/lib/abachrome/converters/oklab_to_lrgb.rb +7 -7
- data/lib/abachrome/converters/oklab_to_oklch.rb +4 -2
- data/lib/abachrome/converters/oklab_to_srgb.rb +4 -2
- data/lib/abachrome/converters/oklab_to_xyz.rb +29 -0
- data/lib/abachrome/converters/oklch_to_lms.rb +28 -0
- data/lib/abachrome/converters/oklch_to_lrgb.rb +5 -3
- data/lib/abachrome/converters/oklch_to_oklab.rb +5 -3
- data/lib/abachrome/converters/oklch_to_srgb.rb +6 -4
- data/lib/abachrome/converters/oklch_to_xyz.rb +6 -4
- data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
- data/lib/abachrome/converters/srgb_to_lms.rb +29 -0
- data/lib/abachrome/converters/srgb_to_lrgb.rb +5 -3
- data/lib/abachrome/converters/srgb_to_oklab.rb +4 -2
- data/lib/abachrome/converters/srgb_to_oklch.rb +5 -3
- data/lib/abachrome/converters/srgb_to_xyz.rb +30 -0
- data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
- data/lib/abachrome/converters/xyz_to_lms.rb +5 -3
- data/lib/abachrome/converters/xyz_to_lrgb.rb +33 -0
- data/lib/abachrome/converters/xyz_to_oklab.rb +5 -3
- data/lib/abachrome/converters/xyz_to_srgb.rb +30 -0
- data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
- data/lib/abachrome/floatify.rb +282 -0
- data/lib/abachrome/gamut/base.rb +3 -3
- data/lib/abachrome/gamut/p3.rb +3 -3
- data/lib/abachrome/gamut/rec2020.rb +2 -2
- data/lib/abachrome/gamut/srgb.rb +4 -2
- data/lib/abachrome/illuminants/base.rb +2 -2
- data/lib/abachrome/illuminants/d50.rb +2 -2
- data/lib/abachrome/illuminants/d55.rb +2 -2
- data/lib/abachrome/illuminants/d65.rb +2 -2
- data/lib/abachrome/illuminants/d75.rb +2 -2
- data/lib/abachrome/named/css.rb +149 -149
- data/lib/abachrome/named/tailwind.rb +265 -265
- data/lib/abachrome/outputs/css.rb +2 -2
- data/lib/abachrome/palette.rb +26 -25
- data/lib/abachrome/palette_mixins/interpolate.rb +3 -1
- data/lib/abachrome/palette_mixins/resample.rb +2 -2
- data/lib/abachrome/palette_mixins/stretch_luminance.rb +2 -2
- data/lib/abachrome/parsers/css.rb +86 -71
- data/lib/abachrome/parsers/hex.rb +2 -2
- data/lib/abachrome/parsers/tailwind.rb +8 -8
- data/lib/abachrome/spectral.rb +277 -0
- data/lib/abachrome/to_abcd.rb +4 -4
- data/lib/abachrome/version.rb +2 -2
- data/lib/abachrome.rb +66 -10
- metadata +41 -3
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::ColorMixins::ToSrgb - sRGB color space conversion functionality
|
|
2
4
|
#
|
|
3
5
|
# This mixin provides methods for converting colors to the sRGB color space, which is the
|
|
@@ -23,11 +25,11 @@ module Abachrome
|
|
|
23
25
|
module ColorMixins
|
|
24
26
|
module ToSrgb
|
|
25
27
|
# Converts the current color to the sRGB color space.
|
|
26
|
-
#
|
|
28
|
+
#
|
|
27
29
|
# If the color is already in the sRGB color space, returns the color instance
|
|
28
30
|
# unchanged. Otherwise, performs a color space conversion from the current
|
|
29
31
|
# color space to sRGB.
|
|
30
|
-
#
|
|
32
|
+
#
|
|
31
33
|
# @return [Abachrome::Color] A new Color instance in the sRGB color space,
|
|
32
34
|
# or self if already in sRGB
|
|
33
35
|
def to_srgb
|
|
@@ -37,7 +39,7 @@ module Abachrome
|
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# Alias for #to_srgb method.
|
|
40
|
-
#
|
|
42
|
+
#
|
|
41
43
|
# @return [Abachrome::Color] The color converted to sRGB color space
|
|
42
44
|
def to_rgb
|
|
43
45
|
# assume they mean srgb
|
|
@@ -59,7 +61,7 @@ module Abachrome
|
|
|
59
61
|
# Converts the current color to sRGB color space in place.
|
|
60
62
|
# This is an alias for {#to_srgb!} as RGB commonly refers to sRGB
|
|
61
63
|
# in web and design contexts.
|
|
62
|
-
#
|
|
64
|
+
#
|
|
63
65
|
# @return [self] Returns self after converting to sRGB
|
|
64
66
|
def to_rgb!
|
|
65
67
|
# assume they mean srgb
|
|
@@ -67,7 +69,7 @@ module Abachrome
|
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
# Returns the red component of the color in the sRGB color space.
|
|
70
|
-
#
|
|
72
|
+
#
|
|
71
73
|
# @return [AbcDecimal] The red component value in the sRGB color space,
|
|
72
74
|
# normalized between 0 and 1.
|
|
73
75
|
def red
|
|
@@ -75,27 +77,27 @@ module Abachrome
|
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
# Returns the green component of the color in sRGB space.
|
|
78
|
-
#
|
|
80
|
+
#
|
|
79
81
|
# This method converts the current color to sRGB color space if needed,
|
|
80
82
|
# then extracts the green component (second coordinate).
|
|
81
|
-
#
|
|
83
|
+
#
|
|
82
84
|
# @return [AbcDecimal] The green component value in the sRGB color space, typically in the range 0-1
|
|
83
85
|
def green
|
|
84
86
|
to_srgb.coordinates[1]
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
# Returns the blue component of the color in sRGB color space.
|
|
88
|
-
#
|
|
90
|
+
#
|
|
89
91
|
# This method converts the current color to sRGB if needed and
|
|
90
92
|
# extracts the third coordinate value (blue).
|
|
91
|
-
#
|
|
93
|
+
#
|
|
92
94
|
# @return [AbcDecimal] The blue component value in sRGB space, typically in range 0-1
|
|
93
95
|
def blue
|
|
94
96
|
to_srgb.coordinates[2]
|
|
95
97
|
end
|
|
96
98
|
|
|
97
99
|
# Returns the RGB color values in the sRGB color space.
|
|
98
|
-
#
|
|
100
|
+
#
|
|
99
101
|
# @return [Array<AbcDecimal>] An array of three AbcDecimal values representing
|
|
100
102
|
# the red, green, and blue color components in the sRGB color space.
|
|
101
103
|
def srgb_values
|
|
@@ -103,18 +105,18 @@ module Abachrome
|
|
|
103
105
|
end
|
|
104
106
|
|
|
105
107
|
# Returns the RGB values of the color as coordinates in the sRGB color space.
|
|
106
|
-
#
|
|
108
|
+
#
|
|
107
109
|
# @return [Array<Abachrome::AbcDecimal>] The RGB coordinates (red, green, blue) in sRGB color space
|
|
108
110
|
def rgb_values
|
|
109
111
|
to_srgb.coordinates
|
|
110
112
|
end
|
|
111
113
|
|
|
112
114
|
# Returns an array of RGB values (0-255) for this color.
|
|
113
|
-
#
|
|
115
|
+
#
|
|
114
116
|
# This method converts the color to sRGB, then scales the component values
|
|
115
117
|
# from the 0-1 range to the 0-255 range commonly used in RGB color codes.
|
|
116
118
|
# Values are rounded to the nearest integer and clamped between 0 and 255.
|
|
117
|
-
#
|
|
119
|
+
#
|
|
118
120
|
# @return [Array<Integer>] An array of three integers representing the [R, G, B]
|
|
119
121
|
# values in the 0-255 range
|
|
120
122
|
def rgb_array
|
|
@@ -123,7 +125,7 @@ module Abachrome
|
|
|
123
125
|
|
|
124
126
|
# Returns a hexadecimal representation of this color in sRGB color space.
|
|
125
127
|
# Converts the color to sRGB, then formats it as a hexadecimal string.
|
|
126
|
-
#
|
|
128
|
+
#
|
|
127
129
|
# @return [String] A string in the format "#RRGGBB" where RR, GG, and BB are
|
|
128
130
|
# the hexadecimal representations of the red, green, and blue components,
|
|
129
131
|
# each ranging from 00 to FF.
|
|
@@ -137,4 +139,4 @@ module Abachrome
|
|
|
137
139
|
end
|
|
138
140
|
end
|
|
139
141
|
|
|
140
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
142
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::ColorMixins::ToXyz - XYZ color space conversion functionality
|
|
4
|
+
#
|
|
5
|
+
# This mixin provides methods for converting colors to the XYZ color space, which is the
|
|
6
|
+
# CIE 1931 color space that forms the basis for most other color space definitions and
|
|
7
|
+
# serves as a device-independent reference color space. XYZ represents colors using
|
|
8
|
+
# tristimulus values that correspond to the response of the human visual system to light.
|
|
9
|
+
#
|
|
10
|
+
# Key features:
|
|
11
|
+
# - Convert colors to XYZ with automatic converter lookup
|
|
12
|
+
# - Both non-destructive (to_xyz) and destructive (to_xyz!) conversion methods
|
|
13
|
+
# - Direct access to XYZ components (x, y, z)
|
|
14
|
+
# - Utility methods for XYZ array and value extraction
|
|
15
|
+
# - Optimized to return the same object when no conversion is needed
|
|
16
|
+
# - High-precision decimal arithmetic for accurate color science calculations
|
|
17
|
+
#
|
|
18
|
+
# The XYZ color space uses three components: X, Y, and Z tristimulus values, providing
|
|
19
|
+
# a device-independent representation of colors that serves as the foundation for defining
|
|
20
|
+
# other color spaces, making it essential for accurate color transformations.
|
|
21
|
+
|
|
22
|
+
require_relative "../converter"
|
|
23
|
+
|
|
24
|
+
module Abachrome
|
|
25
|
+
module ColorMixins
|
|
26
|
+
module ToXyz
|
|
27
|
+
# Converts the current color to the XYZ color space.
|
|
28
|
+
#
|
|
29
|
+
# If the color is already in XYZ, it returns the color unchanged.
|
|
30
|
+
# Otherwise, it uses the Converter to transform the color to XYZ.
|
|
31
|
+
#
|
|
32
|
+
# @return [Abachrome::Color] A new Color object in the XYZ color space
|
|
33
|
+
def to_xyz
|
|
34
|
+
return self if color_space.name == :xyz
|
|
35
|
+
|
|
36
|
+
Converter.convert(self, :xyz)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Converts the color to the XYZ color space in place.
|
|
40
|
+
# This method transforms the current color into XYZ space,
|
|
41
|
+
# modifying the original object by updating its color space
|
|
42
|
+
# and coordinates if not already in XYZ.
|
|
43
|
+
#
|
|
44
|
+
# @example
|
|
45
|
+
# color = Abachrome::Color.from_hex("#ff5500")
|
|
46
|
+
# color.to_xyz! # Color now uses XYZ color space
|
|
47
|
+
#
|
|
48
|
+
# @return [Abachrome::Color] self, with updated color space and coordinates
|
|
49
|
+
def to_xyz!
|
|
50
|
+
unless color_space.name == :xyz
|
|
51
|
+
xyz_color = to_xyz
|
|
52
|
+
@color_space = xyz_color.color_space
|
|
53
|
+
@coordinates = xyz_color.coordinates
|
|
54
|
+
end
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the X component from the XYZ color space.
|
|
59
|
+
#
|
|
60
|
+
# The X component represents the mix of cone response curves chosen to be
|
|
61
|
+
# nonnegative and represents a scale of the CIE RGB red primary.
|
|
62
|
+
#
|
|
63
|
+
# @return [AbcDecimal] The X tristimulus value from the XYZ color space
|
|
64
|
+
def x
|
|
65
|
+
to_xyz.coordinates[0]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns the Y component from the XYZ color space.
|
|
69
|
+
#
|
|
70
|
+
# The Y component represents luminance, which closely matches human perception
|
|
71
|
+
# of brightness. It corresponds to the CIE RGB green primary.
|
|
72
|
+
#
|
|
73
|
+
# @return [AbcDecimal] The Y tristimulus value from the XYZ color space
|
|
74
|
+
def y
|
|
75
|
+
to_xyz.coordinates[1]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns the Z component from the XYZ color space.
|
|
79
|
+
#
|
|
80
|
+
# The Z component represents the CIE RGB blue primary and is roughly equal
|
|
81
|
+
# to blue stimulation.
|
|
82
|
+
#
|
|
83
|
+
# @return [AbcDecimal] The Z tristimulus value from the XYZ color space
|
|
84
|
+
def z
|
|
85
|
+
to_xyz.coordinates[2]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Returns the XYZ color space coordinates for this color.
|
|
89
|
+
#
|
|
90
|
+
# @return [Array<AbcDecimal>] An array of XYZ coordinates [X, Y, Z] representing the color in XYZ color space
|
|
91
|
+
def xyz_values
|
|
92
|
+
to_xyz.coordinates
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns an array representation of the color's coordinates in the XYZ color space.
|
|
96
|
+
#
|
|
97
|
+
# @return [Array<AbcDecimal>] An array containing the coordinates of the color
|
|
98
|
+
# in the XYZ color space in the order [X, Y, Z]
|
|
99
|
+
def xyz_array
|
|
100
|
+
to_xyz.coordinates
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::ColorMixins::Wcag - WCAG Accessibility Module
|
|
4
|
+
#
|
|
5
|
+
# This module provides methods for calculating color contrast ratios and checking
|
|
6
|
+
# WCAG 2.0/2.1 compliance for accessibility. It implements the relative luminance
|
|
7
|
+
# calculation with proper sRGB linearization according to WCAG specifications.
|
|
8
|
+
#
|
|
9
|
+
# Key features:
|
|
10
|
+
# - Calculate relative luminance using WCAG formula: Y = 0.2126R + 0.7152G + 0.0722B
|
|
11
|
+
# - Compute contrast ratio between two colors
|
|
12
|
+
# - Check WCAG 2.0 Level AA and AAA compliance for normal and large text
|
|
13
|
+
# - Check WCAG 2.1 non-text contrast compliance (3:1 minimum)
|
|
14
|
+
# - Predicate methods for easy accessibility checks
|
|
15
|
+
#
|
|
16
|
+
# References:
|
|
17
|
+
# - WCAG 2.0: https://www.w3.org/TR/WCAG20/
|
|
18
|
+
# - WCAG 2.1: https://www.w3.org/TR/WCAG21/
|
|
19
|
+
# - Relative luminance: https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
20
|
+
# - Contrast ratio: https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
|
21
|
+
|
|
22
|
+
module Abachrome
|
|
23
|
+
module ColorMixins
|
|
24
|
+
module Wcag
|
|
25
|
+
# Calculates the relative luminance of the color according to WCAG specifications.
|
|
26
|
+
# Uses the formula: Y = 0.2126R + 0.7152G + 0.0722B
|
|
27
|
+
# where R, G, B are linearized sRGB values.
|
|
28
|
+
#
|
|
29
|
+
# The linearization follows the sRGB specification:
|
|
30
|
+
# - For values <= 0.03928: linear_value = value / 12.92
|
|
31
|
+
# - For values > 0.03928: linear_value = ((value + 0.055) / 1.055) ^ 2.4
|
|
32
|
+
#
|
|
33
|
+
# @return [AbcDecimal] The relative luminance value between 0.0 (darkest) and 1.0 (lightest)
|
|
34
|
+
def relative_luminance
|
|
35
|
+
rgb = to_srgb
|
|
36
|
+
r, g, b = rgb.coordinates
|
|
37
|
+
|
|
38
|
+
# Linearize sRGB values
|
|
39
|
+
r_linear = linearize_srgb_component(r)
|
|
40
|
+
g_linear = linearize_srgb_component(g)
|
|
41
|
+
b_linear = linearize_srgb_component(b)
|
|
42
|
+
|
|
43
|
+
# Apply WCAG luminance coefficients (Rec. 709)
|
|
44
|
+
AbcDecimal("0.2126") * r_linear +
|
|
45
|
+
AbcDecimal("0.7152") * g_linear +
|
|
46
|
+
AbcDecimal("0.0722") * b_linear
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Calculates the contrast ratio between this color and another color.
|
|
50
|
+
# The contrast ratio is calculated according to WCAG:
|
|
51
|
+
# (L1 + 0.05) / (L2 + 0.05)
|
|
52
|
+
# where L1 is the relative luminance of the lighter color and
|
|
53
|
+
# L2 is the relative luminance of the darker color.
|
|
54
|
+
#
|
|
55
|
+
# @param other [Abachrome::Color] The color to compare against
|
|
56
|
+
# @return [AbcDecimal] The contrast ratio, ranging from 1:1 (no contrast) to 21:1 (maximum contrast)
|
|
57
|
+
def contrast_ratio(other)
|
|
58
|
+
l1 = relative_luminance
|
|
59
|
+
l2 = other.relative_luminance
|
|
60
|
+
|
|
61
|
+
# Ensure L1 is the lighter color
|
|
62
|
+
l1, l2 = l2, l1 if l1 < l2
|
|
63
|
+
|
|
64
|
+
(l1 + AbcDecimal("0.05")) / (l2 + AbcDecimal("0.05"))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Checks if the contrast ratio with another color meets WCAG 2.0 Level AA standards.
|
|
68
|
+
# AA requirements:
|
|
69
|
+
# - Normal text (< 18pt or < 14pt bold): minimum 4.5:1 contrast ratio
|
|
70
|
+
# - Large text (≥ 18pt or ≥ 14pt bold): minimum 3:1 contrast ratio
|
|
71
|
+
#
|
|
72
|
+
# @param other [Abachrome::Color] The color to compare against
|
|
73
|
+
# @param large_text [Boolean] Whether the text is considered large (default: false)
|
|
74
|
+
# @return [Boolean] true if the contrast meets AA standards
|
|
75
|
+
def meets_wcag_aa?(other, large_text: false)
|
|
76
|
+
ratio = contrast_ratio(other)
|
|
77
|
+
minimum = large_text ? AbcDecimal("3.0") : AbcDecimal("4.5")
|
|
78
|
+
ratio >= minimum
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Checks if the contrast ratio with another color meets WCAG 2.0 Level AAA standards.
|
|
82
|
+
# AAA requirements:
|
|
83
|
+
# - Normal text (< 18pt or < 14pt bold): minimum 7:1 contrast ratio
|
|
84
|
+
# - Large text (≥ 18pt or ≥ 14pt bold): minimum 4.5:1 contrast ratio
|
|
85
|
+
#
|
|
86
|
+
# @param other [Abachrome::Color] The color to compare against
|
|
87
|
+
# @param large_text [Boolean] Whether the text is considered large (default: false)
|
|
88
|
+
# @return [Boolean] true if the contrast meets AAA standards
|
|
89
|
+
def meets_wcag_aaa?(other, large_text: false)
|
|
90
|
+
ratio = contrast_ratio(other)
|
|
91
|
+
minimum = large_text ? AbcDecimal("4.5") : AbcDecimal("7.0")
|
|
92
|
+
ratio >= minimum
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Checks if the contrast ratio with another color meets WCAG 2.1 non-text contrast requirements.
|
|
96
|
+
# Non-text contrast applies to:
|
|
97
|
+
# - Graphical objects (icons, graphs, infographics)
|
|
98
|
+
# - User interface components (buttons, form inputs, focus indicators)
|
|
99
|
+
# Requirement: minimum 3:1 contrast ratio
|
|
100
|
+
#
|
|
101
|
+
# @param other [Abachrome::Color] The color to compare against
|
|
102
|
+
# @return [Boolean] true if the contrast meets the 3:1 minimum for non-text content
|
|
103
|
+
def meets_wcag_non_text?(other)
|
|
104
|
+
ratio = contrast_ratio(other)
|
|
105
|
+
ratio >= AbcDecimal("3.0")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# Linearizes a single sRGB component value according to the sRGB specification.
|
|
111
|
+
# This is required before applying the luminance coefficients.
|
|
112
|
+
#
|
|
113
|
+
# @param component [AbcDecimal] The sRGB component value (0.0 to 1.0)
|
|
114
|
+
# @return [AbcDecimal] The linearized component value
|
|
115
|
+
def linearize_srgb_component(component)
|
|
116
|
+
if component <= AbcDecimal("0.03928")
|
|
117
|
+
component / AbcDecimal("12.92")
|
|
118
|
+
else
|
|
119
|
+
((component + AbcDecimal("0.055")) / AbcDecimal("1.055"))**AbcDecimal("2.4")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::ColorModels::CMYK - CMYK color space model utilities
|
|
4
|
+
#
|
|
5
|
+
# This module provides utility methods for the CMYK (Cyan, Magenta, Yellow, Key/Black)
|
|
6
|
+
# color model within the Abachrome color manipulation library. CMYK represents colors
|
|
7
|
+
# using the subtractive color synthesis model used in printing and physical media.
|
|
8
|
+
#
|
|
9
|
+
# Key features:
|
|
10
|
+
# - Subtractive color model (ink on paper, not light)
|
|
11
|
+
# - Four channels: Cyan, Magenta, Yellow, and Key (Black)
|
|
12
|
+
# - Supports Undercolor Removal (UCR) for ink savings
|
|
13
|
+
# - Supports Gray Component Replacement (GCR) for richer blacks
|
|
14
|
+
# - High-precision BigDecimal arithmetic for exact ink percentages
|
|
15
|
+
# - Essential for print media, PDF generation, and pre-press workflows
|
|
16
|
+
#
|
|
17
|
+
# The CMYK model is fundamental for professional printing, where the fourth channel
|
|
18
|
+
# (Black/Key) is added to achieve crisp text and deep shadows that cannot be produced
|
|
19
|
+
# by combining cyan, magenta, and yellow inks alone.
|
|
20
|
+
|
|
21
|
+
require_relative "../abc_decimal"
|
|
22
|
+
|
|
23
|
+
module Abachrome
|
|
24
|
+
module ColorModels
|
|
25
|
+
class CMYK
|
|
26
|
+
include Abachrome::ToAbcd
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
# Normalizes CMYK color component values to the [0,1] range.
|
|
30
|
+
#
|
|
31
|
+
# @param c [Numeric] Cyan component (0-1 or 0-100 for percentages)
|
|
32
|
+
# @param m [Numeric] Magenta component (0-1 or 0-100 for percentages)
|
|
33
|
+
# @param y [Numeric] Yellow component (0-1 or 0-100 for percentages)
|
|
34
|
+
# @param k [Numeric] Key/Black component (0-1 or 0-100 for percentages)
|
|
35
|
+
# @return [Array<AbcDecimal>] Array of four normalized components as AbcDecimal objects
|
|
36
|
+
def normalize(c, m, y, k)
|
|
37
|
+
[c, m, y, k].map do |value|
|
|
38
|
+
case value
|
|
39
|
+
when String
|
|
40
|
+
if value.end_with?("%")
|
|
41
|
+
value_without_percent = value.chomp("%")
|
|
42
|
+
AbcDecimal(value_without_percent) / AbcDecimal(100)
|
|
43
|
+
else
|
|
44
|
+
AbcDecimal(value)
|
|
45
|
+
end
|
|
46
|
+
when Numeric
|
|
47
|
+
if value > 1
|
|
48
|
+
AbcDecimal(value) / AbcDecimal(100)
|
|
49
|
+
else
|
|
50
|
+
AbcDecimal(value)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Converts RGB values to CMYK using the standard naive conversion.
|
|
57
|
+
# This produces high ink coverage and is generally not recommended for production.
|
|
58
|
+
#
|
|
59
|
+
# @param r [Numeric] Red component (0-1)
|
|
60
|
+
# @param g [Numeric] Green component (0-1)
|
|
61
|
+
# @param b [Numeric] Blue component (0-1)
|
|
62
|
+
# @return [Array<AbcDecimal>] Array of [c, m, y, k] values
|
|
63
|
+
def from_rgb_naive(r, g, b)
|
|
64
|
+
r = AbcDecimal(r)
|
|
65
|
+
g = AbcDecimal(g)
|
|
66
|
+
b = AbcDecimal(b)
|
|
67
|
+
|
|
68
|
+
# Simple complementary conversion
|
|
69
|
+
c = AbcDecimal(1) - r
|
|
70
|
+
m = AbcDecimal(1) - g
|
|
71
|
+
y = AbcDecimal(1) - b
|
|
72
|
+
k = AbcDecimal(0)
|
|
73
|
+
|
|
74
|
+
[c, m, y, k]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Converts RGB values to CMYK using Undercolor Removal (UCR).
|
|
78
|
+
# This extracts the gray component to the black (K) channel, reducing ink usage.
|
|
79
|
+
#
|
|
80
|
+
# @param r [Numeric] Red component (0-1)
|
|
81
|
+
# @param g [Numeric] Green component (0-1)
|
|
82
|
+
# @param b [Numeric] Blue component (0-1)
|
|
83
|
+
# @param gcr_amount [Numeric] Gray Component Replacement amount (0-1), default 1.0 for full UCR
|
|
84
|
+
# @return [Array<AbcDecimal>] Array of [c, m, y, k] values
|
|
85
|
+
def from_rgb_ucr(r, g, b, gcr_amount = 1.0)
|
|
86
|
+
r = AbcDecimal(r)
|
|
87
|
+
g = AbcDecimal(g)
|
|
88
|
+
b = AbcDecimal(b)
|
|
89
|
+
gcr = AbcDecimal(gcr_amount)
|
|
90
|
+
|
|
91
|
+
# Calculate complementary CMY values
|
|
92
|
+
c = AbcDecimal(1) - r
|
|
93
|
+
m = AbcDecimal(1) - g
|
|
94
|
+
y = AbcDecimal(1) - b
|
|
95
|
+
|
|
96
|
+
# Find the minimum (gray component)
|
|
97
|
+
k = [c, m, y].min * gcr
|
|
98
|
+
|
|
99
|
+
# Remove the gray component from CMY if k > 0
|
|
100
|
+
if k.positive?
|
|
101
|
+
c -= k
|
|
102
|
+
m -= k
|
|
103
|
+
y -= k
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
[c, m, y, k]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Alias for from_rgb_ucr with full GCR (the standard approach).
|
|
110
|
+
# GCR is essentially the same as UCR but allows control over how much
|
|
111
|
+
# of the gray component to extract.
|
|
112
|
+
#
|
|
113
|
+
# @param r [Numeric] Red component (0-1)
|
|
114
|
+
# @param g [Numeric] Green component (0-1)
|
|
115
|
+
# @param b [Numeric] Blue component (0-1)
|
|
116
|
+
# @param amount [Numeric] Amount of GCR to apply (0-1), default 1.0
|
|
117
|
+
# @return [Array<AbcDecimal>] Array of [c, m, y, k] values
|
|
118
|
+
def from_rgb_gcr(r, g, b, amount = 1.0)
|
|
119
|
+
from_rgb_ucr(r, g, b, amount)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Converts CMYK values back to RGB.
|
|
123
|
+
# This is a straightforward inverse of the naive RGB→CMY conversion
|
|
124
|
+
# plus the addition of the black channel.
|
|
125
|
+
#
|
|
126
|
+
# @param c [Numeric] Cyan component (0-1)
|
|
127
|
+
# @param m [Numeric] Magenta component (0-1)
|
|
128
|
+
# @param y [Numeric] Yellow component (0-1)
|
|
129
|
+
# @param k [Numeric] Key/Black component (0-1)
|
|
130
|
+
# @return [Array<AbcDecimal>] Array of [r, g, b] values
|
|
131
|
+
def to_rgb(c, m, y, k)
|
|
132
|
+
c = AbcDecimal(c)
|
|
133
|
+
m = AbcDecimal(m)
|
|
134
|
+
y = AbcDecimal(y)
|
|
135
|
+
k = AbcDecimal(k)
|
|
136
|
+
|
|
137
|
+
# Standard CMYK to RGB conversion
|
|
138
|
+
r = (AbcDecimal(1) - c) * (AbcDecimal(1) - k)
|
|
139
|
+
g = (AbcDecimal(1) - m) * (AbcDecimal(1) - k)
|
|
140
|
+
b = (AbcDecimal(1) - y) * (AbcDecimal(1) - k)
|
|
141
|
+
|
|
142
|
+
[r, g, b]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Calculates the Total Area Coverage (TAC) for CMYK values.
|
|
146
|
+
# TAC is the sum of all four ink percentages and is critical in print workflows
|
|
147
|
+
# to prevent excessive ink that can cause smearing or paper damage.
|
|
148
|
+
#
|
|
149
|
+
# @param c [Numeric] Cyan component (0-1)
|
|
150
|
+
# @param m [Numeric] Magenta component (0-1)
|
|
151
|
+
# @param y [Numeric] Yellow component (0-1)
|
|
152
|
+
# @param k [Numeric] Key/Black component (0-1)
|
|
153
|
+
# @return [AbcDecimal] Total ink coverage as a decimal (0-4, often expressed as 0-400%)
|
|
154
|
+
def total_area_coverage(c, m, y, k)
|
|
155
|
+
AbcDecimal(c) + AbcDecimal(m) + AbcDecimal(y) + AbcDecimal(k)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::ColorModels::HSV - HSV color space model definition
|
|
2
4
|
#
|
|
3
5
|
# This module defines the HSV (Hue, Saturation, Value) color model within the Abachrome
|
|
@@ -30,13 +32,13 @@ module Abachrome
|
|
|
30
32
|
|
|
31
33
|
# Validates whether the coordinates are valid for the HSV color model.
|
|
32
34
|
# Each component (hue, saturation, value) must be in the range [0, 1].
|
|
33
|
-
#
|
|
35
|
+
#
|
|
34
36
|
# @param coordinates [Array<Numeric>] An array of three values representing
|
|
35
37
|
# hue (h), saturation (s), and value (v) in the range [0, 1]
|
|
36
38
|
# @return [Boolean] true if all coordinates are within valid ranges, false otherwise
|
|
37
39
|
def valid_coordinates?(coordinates)
|
|
38
40
|
h, s, v = coordinates
|
|
39
|
-
h
|
|
41
|
+
h.between?(0, 1.0) &&
|
|
40
42
|
s >= 0 && s <= 1.0 &&
|
|
41
43
|
v >= 0 && v <= 1.0
|
|
42
44
|
end
|
|
@@ -44,4 +46,4 @@ module Abachrome
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
49
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::ColorModels::Lms - LMS color space model definition
|
|
2
4
|
#
|
|
3
5
|
# This module defines the LMS color model within the Abachrome color manipulation library.
|
|
@@ -33,4 +35,4 @@ ColorSpace.register(
|
|
|
33
35
|
[]
|
|
34
36
|
)
|
|
35
37
|
|
|
36
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
38
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::ColorModels::Oklab - OKLAB color space model definition
|
|
2
4
|
#
|
|
3
5
|
# This module defines the OKLAB color model within the Abachrome color manipulation library.
|
|
@@ -32,4 +34,4 @@ ColorSpace.register(
|
|
|
32
34
|
["ok-lab"]
|
|
33
35
|
)
|
|
34
36
|
|
|
35
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
37
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::ColorModels::Oklch - OKLCH color space model definition
|
|
2
4
|
#
|
|
3
5
|
# This module defines the OKLCH color model within the Abachrome color manipulation library.
|
|
@@ -23,7 +25,7 @@ module Abachrome
|
|
|
23
25
|
module ColorModels
|
|
24
26
|
class Oklch
|
|
25
27
|
# Normalizes OKLCH color values to their standard ranges.
|
|
26
|
-
#
|
|
28
|
+
#
|
|
27
29
|
# @param l [Numeric] The lightness component, will be clamped to range 0-1
|
|
28
30
|
# @param c [Numeric] The chroma component, will be clamped to range 0-1
|
|
29
31
|
# @param h [Numeric] The hue component in degrees, will be normalized to range 0-360
|
|
@@ -45,7 +47,7 @@ module Abachrome
|
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
# Converts OKLCH color coordinates to OKLab color coordinates.
|
|
48
|
-
#
|
|
50
|
+
#
|
|
49
51
|
# @param l [Numeric] The lightness value in the OKLCH color space
|
|
50
52
|
# @param c [Numeric] The chroma value in the OKLCH color space
|
|
51
53
|
# @param h [Numeric] The hue value in degrees in the OKLCH color space
|
|
@@ -59,7 +61,7 @@ module Abachrome
|
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
# Converts OKLab color coordinates to OKLCH color coordinates.
|
|
62
|
-
#
|
|
64
|
+
#
|
|
63
65
|
# @param l [Numeric] The lightness component from OKLab.
|
|
64
66
|
# @param a [Numeric] The green-red component from OKLab.
|
|
65
67
|
# @param b [Numeric] The blue-yellow component from OKLab.
|
|
@@ -86,4 +88,4 @@ ColorSpace.register(
|
|
|
86
88
|
["ok-lch"]
|
|
87
89
|
)
|
|
88
90
|
|
|
89
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
91
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::ColorModels::RGB - RGB color space model utilities
|
|
2
4
|
#
|
|
3
5
|
# This module provides utility methods for the RGB color model within the Abachrome
|
|
@@ -22,7 +24,7 @@ module Abachrome
|
|
|
22
24
|
class RGB
|
|
23
25
|
class << self
|
|
24
26
|
# Normalizes RGB color component values to the [0,1] range.
|
|
25
|
-
#
|
|
27
|
+
#
|
|
26
28
|
# @param r [String, Numeric] Red component. If string with % suffix, interpreted as percentage;
|
|
27
29
|
# if string without suffix or numeric > 1, interpreted as 0-255 range value;
|
|
28
30
|
# if numeric ≤ 1, used directly.
|
|
@@ -53,4 +55,4 @@ module Abachrome
|
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
58
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::ColorModels::Xyz - XYZ color space model definition
|
|
2
4
|
#
|
|
3
5
|
# This module defines the XYZ color model within the Abachrome color manipulation library.
|
|
@@ -26,4 +28,12 @@ module Abachrome
|
|
|
26
28
|
end
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
ColorSpace.register(
|
|
32
|
+
:xyz,
|
|
33
|
+
"XYZ",
|
|
34
|
+
%w[x y z],
|
|
35
|
+
nil,
|
|
36
|
+
[]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::ColorModels::YIQ - YIQ color space model utilities
|
|
4
|
+
#
|
|
5
|
+
# This module provides utility methods for the YIQ color model within the Abachrome
|
|
6
|
+
# color manipulation library. YIQ is a color space historically used by the NTSC
|
|
7
|
+
# television standard, separating luminance (Y) from chrominance (I and Q).
|
|
8
|
+
#
|
|
9
|
+
# Key features:
|
|
10
|
+
# - Separates brightness (luma) from color information
|
|
11
|
+
# - Y (luma): Weighted sum of RGB based on human eye sensitivity
|
|
12
|
+
# - I (In-phase): Orange-to-blue axis
|
|
13
|
+
# - Q (Quadrature): Purple-to-green axis
|
|
14
|
+
# - Supports legacy broadcast standards and computer vision applications
|
|
15
|
+
# - Uses Rec. 601 coefficients for luma calculation
|
|
16
|
+
#
|
|
17
|
+
# The YIQ model is essential for image processing algorithms that need to separate
|
|
18
|
+
# luminance from chrominance, including JPEG compression, grayscale conversion, and
|
|
19
|
+
# edge detection in computer vision applications.
|
|
20
|
+
|
|
21
|
+
module Abachrome
|
|
22
|
+
module ColorModels
|
|
23
|
+
class YIQ
|
|
24
|
+
class << self
|
|
25
|
+
# Normalizes YIQ color component values to their standard ranges.
|
|
26
|
+
#
|
|
27
|
+
# @param y [Numeric] Luma component (brightness), typically in range [0,1]
|
|
28
|
+
# @param i [Numeric] In-phase component (orange-blue), typically in range [-0.5957, 0.5957]
|
|
29
|
+
# @param q [Numeric] Quadrature component (purple-green), typically in range [-0.5226, 0.5226]
|
|
30
|
+
# @return [Array<AbcDecimal>] Array of three normalized components as AbcDecimal objects
|
|
31
|
+
def normalize(y, i, q)
|
|
32
|
+
[y, i, q].map { |value| AbcDecimal(value) }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|