abachrome-float 0.1.6
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/.envrc +3 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +21 -0
- data/CLA.md +45 -0
- data/CODE-OF-CONDUCT.md +9 -0
- data/LICENSE +19 -0
- data/README.md +315 -0
- data/Rakefile +15 -0
- data/SECURITY.md +94 -0
- data/abachrome-float.gemspec +36 -0
- data/demos/ncurses/plasma.rb +124 -0
- data/devenv.lock +171 -0
- data/devenv.nix +52 -0
- data/devenv.yaml +8 -0
- data/lib/abachrome/color.rb +197 -0
- data/lib/abachrome/color_mixins/blend.rb +100 -0
- data/lib/abachrome/color_mixins/lighten.rb +90 -0
- data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
- data/lib/abachrome/color_mixins/to_colorspace.rb +107 -0
- data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
- data/lib/abachrome/color_mixins/to_lrgb.rb +121 -0
- data/lib/abachrome/color_mixins/to_oklab.rb +117 -0
- data/lib/abachrome/color_mixins/to_oklch.rb +110 -0
- data/lib/abachrome/color_mixins/to_srgb.rb +142 -0
- data/lib/abachrome/color_models/cmyk.rb +159 -0
- data/lib/abachrome/color_models/hsv.rb +49 -0
- data/lib/abachrome/color_models/lms.rb +38 -0
- data/lib/abachrome/color_models/oklab.rb +37 -0
- data/lib/abachrome/color_models/oklch.rb +91 -0
- data/lib/abachrome/color_models/rgb.rb +58 -0
- data/lib/abachrome/color_models/xyz.rb +31 -0
- data/lib/abachrome/color_models/yiq.rb +37 -0
- data/lib/abachrome/color_space.rb +199 -0
- data/lib/abachrome/converter.rb +117 -0
- data/lib/abachrome/converters/base.rb +128 -0
- data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
- data/lib/abachrome/converters/lms_to_lrgb.rb +40 -0
- data/lib/abachrome/converters/lms_to_srgb.rb +27 -0
- data/lib/abachrome/converters/lms_to_xyz.rb +34 -0
- data/lib/abachrome/converters/lrgb_to_lms.rb +3 -0
- data/lib/abachrome/converters/lrgb_to_oklab.rb +57 -0
- data/lib/abachrome/converters/lrgb_to_srgb.rb +59 -0
- data/lib/abachrome/converters/lrgb_to_xyz.rb +33 -0
- data/lib/abachrome/converters/oklab_to_lms.rb +44 -0
- data/lib/abachrome/converters/oklab_to_lrgb.rb +71 -0
- data/lib/abachrome/converters/oklab_to_oklch.rb +56 -0
- data/lib/abachrome/converters/oklab_to_srgb.rb +46 -0
- data/lib/abachrome/converters/oklch_to_lrgb.rb +79 -0
- data/lib/abachrome/converters/oklch_to_oklab.rb +52 -0
- data/lib/abachrome/converters/oklch_to_srgb.rb +46 -0
- data/lib/abachrome/converters/oklch_to_xyz.rb +70 -0
- data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
- data/lib/abachrome/converters/srgb_to_lrgb.rb +55 -0
- data/lib/abachrome/converters/srgb_to_oklab.rb +45 -0
- data/lib/abachrome/converters/srgb_to_oklch.rb +47 -0
- data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
- data/lib/abachrome/converters/xyz_to_lms.rb +34 -0
- data/lib/abachrome/converters/xyz_to_oklab.rb +42 -0
- data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
- data/lib/abachrome/gamut/base.rb +74 -0
- data/lib/abachrome/gamut/p3.rb +27 -0
- data/lib/abachrome/gamut/rec2020.rb +25 -0
- data/lib/abachrome/gamut/srgb.rb +49 -0
- data/lib/abachrome/illuminants/base.rb +35 -0
- data/lib/abachrome/illuminants/d50.rb +33 -0
- data/lib/abachrome/illuminants/d55.rb +29 -0
- data/lib/abachrome/illuminants/d65.rb +37 -0
- data/lib/abachrome/illuminants/d75.rb +29 -0
- data/lib/abachrome/named/css.rb +157 -0
- data/lib/abachrome/named/tailwind.rb +301 -0
- data/lib/abachrome/outputs/css.rb +119 -0
- data/lib/abachrome/palette.rb +244 -0
- data/lib/abachrome/palette_mixins/interpolate.rb +53 -0
- data/lib/abachrome/palette_mixins/resample.rb +61 -0
- data/lib/abachrome/palette_mixins/stretch_luminance.rb +72 -0
- data/lib/abachrome/parsers/css.rb +452 -0
- data/lib/abachrome/parsers/hex.rb +52 -0
- data/lib/abachrome/parsers/tailwind.rb +45 -0
- data/lib/abachrome/spectral.rb +276 -0
- data/lib/abachrome/to_abcd.rb +23 -0
- data/lib/abachrome/version.rb +7 -0
- data/lib/abachrome.rb +242 -0
- data/logo.png +0 -0
- data/logo.webp +0 -0
- data/security/assesments/2025-10-12-SECURITY_ASSESSMENT.md +53 -0
- data/security/vex.json +21 -0
- metadata +146 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::Converters::SrgbToCmyk - sRGB to CMYK color space converter
|
|
4
|
+
#
|
|
5
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the CMYK
|
|
6
|
+
# (Cyan, Magenta, Yellow, Key/Black) color space using Gray Component Replacement (GCR)
|
|
7
|
+
# for optimal ink usage in print production.
|
|
8
|
+
#
|
|
9
|
+
# Key features:
|
|
10
|
+
# - Implements GCR/UCR for efficient ink usage and better print quality
|
|
11
|
+
# - Converts from additive (light) to subtractive (ink) color model
|
|
12
|
+
# - Configurable GCR amount for different printing requirements
|
|
13
|
+
# - Uses BigDecimal for precise ink percentage calculations
|
|
14
|
+
# - Maintains alpha channel transparency values during conversion
|
|
15
|
+
# - Produces professional-quality CMYK separations
|
|
16
|
+
#
|
|
17
|
+
# The conversion uses Gray Component Replacement by default, which extracts the neutral
|
|
18
|
+
# gray component from CMY inks and assigns it to the cheaper and more stable Black (K) ink.
|
|
19
|
+
# This reduces Total Area Coverage (TAC) and improves print quality.
|
|
20
|
+
|
|
21
|
+
module Abachrome
|
|
22
|
+
module Converters
|
|
23
|
+
class SrgbToCmyk
|
|
24
|
+
# Converts a color from sRGB color space to CMYK color space.
|
|
25
|
+
# Uses full GCR (Gray Component Replacement) by default for optimal ink usage.
|
|
26
|
+
#
|
|
27
|
+
# @param srgb_color [Abachrome::Color] A color object in the sRGB color space
|
|
28
|
+
# @param gcr_amount [Numeric] The amount of GCR to apply (0.0 to 1.0), default 1.0 for full GCR
|
|
29
|
+
# @return [Abachrome::Color] A new color object in the CMYK color space
|
|
30
|
+
# with the same alpha value as the input color
|
|
31
|
+
def self.convert(srgb_color, gcr_amount: 1.0)
|
|
32
|
+
r, g, b = srgb_color.coordinates.map { |c| c.to_f }
|
|
33
|
+
|
|
34
|
+
# Use GCR conversion from the CMYK color model
|
|
35
|
+
c, m, y, k = ColorModels::CMYK.from_rgb_gcr(r, g, b, gcr_amount)
|
|
36
|
+
|
|
37
|
+
Color.new(
|
|
38
|
+
ColorSpace.find(:cmyk),
|
|
39
|
+
[c, m, y, k],
|
|
40
|
+
srgb_color.alpha
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Converts a color from sRGB to CMYK using the naive method (no UCR/GCR).
|
|
45
|
+
# This produces higher ink coverage but simpler conversion.
|
|
46
|
+
# Generally not recommended for production printing.
|
|
47
|
+
#
|
|
48
|
+
# @param srgb_color [Abachrome::Color] A color object in the sRGB color space
|
|
49
|
+
# @return [Abachrome::Color] A new color object in the CMYK color space
|
|
50
|
+
def self.convert_naive(srgb_color)
|
|
51
|
+
r, g, b = srgb_color.coordinates.map { |c| c.to_f }
|
|
52
|
+
|
|
53
|
+
# Use naive conversion from the CMYK color model
|
|
54
|
+
c, m, y, k = ColorModels::CMYK.from_rgb_naive(r, g, b)
|
|
55
|
+
|
|
56
|
+
Color.new(
|
|
57
|
+
ColorSpace.find(:cmyk),
|
|
58
|
+
[c, m, y, k],
|
|
59
|
+
srgb_color.alpha
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::Converters::SrgbToLrgb - sRGB to Linear RGB color space converter
|
|
4
|
+
#
|
|
5
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the linear RGB (LRGB) color space by removing gamma correction. The conversion process applies the inverse sRGB transfer function which uses different formulas for small and large values to convert from the gamma-corrected sRGB representation to linear light intensity values.
|
|
6
|
+
#
|
|
7
|
+
# Key features:
|
|
8
|
+
# - Implements the standard sRGB to linear RGB conversion algorithm with precise threshold handling
|
|
9
|
+
# - Converts gamma-corrected sRGB values to linear RGB values for accurate color calculations
|
|
10
|
+
# - Applies different transformation functions based on value magnitude (linear vs power function)
|
|
11
|
+
# - Maintains alpha channel transparency values during conversion
|
|
12
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
|
13
|
+
# - Validates input color space to ensure proper sRGB source data
|
|
14
|
+
#
|
|
15
|
+
# The linear RGB color space provides a linear relationship between stored numeric values and actual light intensity, making it essential for accurate color calculations and serving as an intermediate color space for many color transformations, particularly when converting between different color models.
|
|
16
|
+
|
|
17
|
+
module Abachrome
|
|
18
|
+
module Converters
|
|
19
|
+
class SrgbToLrgb
|
|
20
|
+
# Converts a color from sRGB color space to linear RGB color space.
|
|
21
|
+
# This method performs gamma correction by linearizing each sRGB coordinate.
|
|
22
|
+
#
|
|
23
|
+
# @param srgb_color [Abachrome::Color] A color object in the sRGB color space
|
|
24
|
+
# @return [Abachrome::Color] A new color object in the linear RGB (LRGB) color space
|
|
25
|
+
# with the same alpha value as the input color
|
|
26
|
+
def self.convert(srgb_color)
|
|
27
|
+
r, g, b = srgb_color.coordinates.map { |c| to_linear(c.to_f) }
|
|
28
|
+
|
|
29
|
+
Color.new(
|
|
30
|
+
ColorSpace.find(:lrgb),
|
|
31
|
+
[r, g, b],
|
|
32
|
+
srgb_color.alpha
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Converts a sRGB component to its linear RGB equivalent.
|
|
37
|
+
# This conversion applies the appropriate gamma correction to transform an sRGB value
|
|
38
|
+
# into a linear RGB value.
|
|
39
|
+
#
|
|
40
|
+
# @param v [AbcDecimal, Numeric] The sRGB component value to convert (typically in range 0-1)
|
|
41
|
+
# @return [AbcDecimal] The corresponding linear RGB component value
|
|
42
|
+
def self.to_linear(v)
|
|
43
|
+
v_abs = v.abs
|
|
44
|
+
v_sign = v.negative? ? -1 : 1
|
|
45
|
+
if v_abs <= AD("0.04045")
|
|
46
|
+
v / AD("12.92")
|
|
47
|
+
else
|
|
48
|
+
v_sign * (((v_abs + AD("0.055")) / AD("1.055"))**AD("2.4"))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::Converters::SrgbToOklab - sRGB to OKLAB color space converter
|
|
4
|
+
#
|
|
5
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the OKLAB color space
|
|
6
|
+
# through a two-step conversion process. The transformation first converts sRGB gamma-corrected
|
|
7
|
+
# values to linear RGB as an intermediate step, then applies the standard OKLAB transformation
|
|
8
|
+
# matrices to produce the final perceptually uniform OKLAB coordinates.
|
|
9
|
+
#
|
|
10
|
+
# Key features:
|
|
11
|
+
# - Two-stage conversion pipeline: sRGB → Linear RGB → OKLAB
|
|
12
|
+
# - Leverages existing SrgbToLrgb and LrgbToOklab converters for modular transformation
|
|
13
|
+
# - Removes gamma correction and applies perceptual uniformity transformations
|
|
14
|
+
# - Maintains alpha channel transparency values during conversion
|
|
15
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
|
16
|
+
# - Validates input color space to ensure proper sRGB source data
|
|
17
|
+
#
|
|
18
|
+
# The OKLAB color space provides better perceptual uniformity compared to traditional RGB spaces,
|
|
19
|
+
# making it ideal for color manipulation operations like blending, lightness adjustments, and
|
|
20
|
+
# gamut mapping where human visual perception accuracy is important. This converter enables
|
|
21
|
+
# seamless transformation from display-ready sRGB values to the scientifically accurate OKLAB
|
|
22
|
+
# representation for advanced color processing workflows.
|
|
23
|
+
|
|
24
|
+
module Abachrome
|
|
25
|
+
module Converters
|
|
26
|
+
class SrgbToOklab
|
|
27
|
+
# Converts a color from sRGB color space to Oklab color space.
|
|
28
|
+
# The conversion happens in two steps:
|
|
29
|
+
# 1. sRGB is first converted to linear RGB
|
|
30
|
+
# 2. Linear RGB is then converted to Oklab
|
|
31
|
+
#
|
|
32
|
+
# @param srgb_color [Abachrome::Color] The color in sRGB color space to convert
|
|
33
|
+
# @return [Abachrome::Color] The converted color in Oklab color space
|
|
34
|
+
def self.convert(srgb_color)
|
|
35
|
+
# First convert sRGB to linear RGB
|
|
36
|
+
lrgb_color = SrgbToLrgb.convert(srgb_color)
|
|
37
|
+
|
|
38
|
+
# Then convert linear RGB to Oklab
|
|
39
|
+
LrgbToOklab.convert(lrgb_color)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::Converters::SrgbToOklch - sRGB to OKLCH color space converter
|
|
4
|
+
#
|
|
5
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the OKLCH color space
|
|
6
|
+
# through a two-step conversion process. The transformation first converts sRGB gamma-corrected
|
|
7
|
+
# values to OKLAB rectangular coordinates as an intermediate step, then applies cylindrical coordinate
|
|
8
|
+
# conversion to produce the final OKLCH values with lightness, chroma, and hue components.
|
|
9
|
+
#
|
|
10
|
+
# Key features:
|
|
11
|
+
# - Two-stage conversion pipeline: sRGB → OKLAB → OKLCH
|
|
12
|
+
# - Leverages existing SrgbToOklab and OklabToOklch converters for modular transformation
|
|
13
|
+
# - Converts display-ready RGB values to perceptually uniform cylindrical coordinates
|
|
14
|
+
# - Maintains alpha channel transparency values during conversion
|
|
15
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
|
16
|
+
# - Validates input color space to ensure proper sRGB source data
|
|
17
|
+
#
|
|
18
|
+
# The OKLCH color space provides an intuitive interface for color manipulation through its
|
|
19
|
+
# cylindrical coordinate system, making it ideal for hue adjustments, saturation modifications,
|
|
20
|
+
# and other color operations that benefit from polar coordinates while maintaining perceptual
|
|
21
|
+
# uniformity for natural-looking color transformations.
|
|
22
|
+
|
|
23
|
+
require_relative "srgb_to_oklab"
|
|
24
|
+
require_relative "oklab_to_oklch"
|
|
25
|
+
|
|
26
|
+
module Abachrome
|
|
27
|
+
module Converters
|
|
28
|
+
class SrgbToOklch < Abachrome::Converters::Base
|
|
29
|
+
# Converts an sRGB color to OKLCH color space
|
|
30
|
+
#
|
|
31
|
+
# @param srgb_color [Abachrome::Color] The color in sRGB color space to convert
|
|
32
|
+
# @return [Abachrome::Color] The converted color in OKLCH color space
|
|
33
|
+
# @note This is a two-step conversion process: first from sRGB to OKLab, then from OKLab to OKLCH
|
|
34
|
+
# @see SrgbToOklab
|
|
35
|
+
# @see OklabToOklch
|
|
36
|
+
def self.convert(srgb_color)
|
|
37
|
+
# First convert sRGB to OKLab
|
|
38
|
+
oklab_color = SrgbToOklab.convert(srgb_color)
|
|
39
|
+
|
|
40
|
+
# Then convert OKLab to OKLCh
|
|
41
|
+
OklabToOklch.convert(oklab_color)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::Converters::SrgbToYiq - sRGB to YIQ color space converter
|
|
4
|
+
#
|
|
5
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the YIQ
|
|
6
|
+
# color space using the NTSC standard transformation matrix. The conversion applies the
|
|
7
|
+
# Rec. 601 luma coefficients to separate luminance (Y) from chrominance (I and Q).
|
|
8
|
+
#
|
|
9
|
+
# Key features:
|
|
10
|
+
# - Implements the standard NTSC RGB to YIQ conversion using matrix transformation
|
|
11
|
+
# - Separates brightness (Y) from color information (I, Q)
|
|
12
|
+
# - Uses Rec. 601 luma coefficients: Y = 0.299R + 0.587G + 0.114B
|
|
13
|
+
# - I axis represents orange-to-blue chrominance
|
|
14
|
+
# - Q axis represents purple-to-green chrominance
|
|
15
|
+
# - Maintains alpha channel transparency values during conversion
|
|
16
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
|
17
|
+
#
|
|
18
|
+
# The YIQ color space is historically significant for broadcast television and remains
|
|
19
|
+
# relevant for image processing tasks that require luminance/chrominance separation.
|
|
20
|
+
|
|
21
|
+
module Abachrome
|
|
22
|
+
module Converters
|
|
23
|
+
class SrgbToYiq
|
|
24
|
+
# Converts a color from sRGB color space to YIQ color space.
|
|
25
|
+
# This method applies the NTSC standard transformation matrix.
|
|
26
|
+
#
|
|
27
|
+
# @param srgb_color [Abachrome::Color] A color object in the sRGB color space
|
|
28
|
+
# @return [Abachrome::Color] A new color object in the YIQ color space
|
|
29
|
+
# with the same alpha value as the input color
|
|
30
|
+
def self.convert(srgb_color)
|
|
31
|
+
r, g, b = srgb_color.coordinates.map { |c| c.to_f }
|
|
32
|
+
|
|
33
|
+
# NTSC RGB to YIQ transformation matrix
|
|
34
|
+
# Y = 0.299R + 0.587G + 0.114B (Rec. 601 luma)
|
|
35
|
+
# I = 0.596R - 0.275G - 0.321B (In-phase: orange-blue)
|
|
36
|
+
# Q = 0.212R - 0.523G + 0.311B (Quadrature: purple-green)
|
|
37
|
+
y = (AD("0.299") * r) + (AD("0.587") * g) + (AD("0.114") * b)
|
|
38
|
+
i = (AD("0.5959") * r) - (AD("0.2746") * g) - (AD("0.3213") * b)
|
|
39
|
+
q = (AD("0.2115") * r) - (AD("0.5227") * g) + (AD("0.3112") * b)
|
|
40
|
+
|
|
41
|
+
Color.new(
|
|
42
|
+
ColorSpace.find(:yiq),
|
|
43
|
+
[y, i, q],
|
|
44
|
+
srgb_color.alpha
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Abachrome
|
|
4
|
+
module Converters
|
|
5
|
+
class XyzToLms < Abachrome::Converters::Base
|
|
6
|
+
# Converts a color from XYZ color space to LMS color space.
|
|
7
|
+
#
|
|
8
|
+
# This method implements the XYZ to LMS transformation using the standard
|
|
9
|
+
# transformation matrix. The LMS color space represents the response of
|
|
10
|
+
# the three types of cone cells in the human eye (Long, Medium, Short),
|
|
11
|
+
# while XYZ is the CIE 1931 color space that forms the basis for most
|
|
12
|
+
# other color space definitions.
|
|
13
|
+
#
|
|
14
|
+
# @param xyz_color [Abachrome::Color] The color in XYZ color space
|
|
15
|
+
# @raise [ArgumentError] If the input color is not in XYZ color space
|
|
16
|
+
# @return [Abachrome::Color] The resulting color in LMS color space with
|
|
17
|
+
# the same alpha as the input color
|
|
18
|
+
def self.convert(xyz_color)
|
|
19
|
+
raise_unless xyz_color, :xyz
|
|
20
|
+
|
|
21
|
+
x, y, z = xyz_color.coordinates.map { |_| _.to_f }
|
|
22
|
+
|
|
23
|
+
# XYZ to LMS transformation matrix
|
|
24
|
+
l = (x * AD("0.8189330101")) + (y * AD("0.3618667424")) - (z * AD("0.1288597137"))
|
|
25
|
+
m = (x * AD("0.0329845436")) + (y * AD("0.9293118715")) + (z * AD("0.0361456387"))
|
|
26
|
+
s = (x * AD("0.0482003018")) + (y * AD("0.2643662691")) + (z * AD("0.6338517070"))
|
|
27
|
+
|
|
28
|
+
Color.new(ColorSpace.find(:lms), [l, m, s], xyz_color.alpha)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Abachrome
|
|
4
|
+
module Converters
|
|
5
|
+
class XyzToOklab < Abachrome::Converters::Base
|
|
6
|
+
# Converts a color from XYZ color space to OKLAB color space.
|
|
7
|
+
#
|
|
8
|
+
# This method implements the XYZ to OKLAB transformation by first
|
|
9
|
+
# converting XYZ coordinates to the intermediate LMS (Long, Medium, Short)
|
|
10
|
+
# color space, then applying the LMS to OKLAB transformation matrix.
|
|
11
|
+
#
|
|
12
|
+
# @param xyz_color [Abachrome::Color] The color in XYZ color space
|
|
13
|
+
# @raise [ArgumentError] If the input color is not in XYZ color space
|
|
14
|
+
# @return [Abachrome::Color] The resulting color in OKLAB color space with
|
|
15
|
+
# the same alpha as the input color
|
|
16
|
+
def self.convert(xyz_color)
|
|
17
|
+
raise_unless xyz_color, :xyz
|
|
18
|
+
|
|
19
|
+
x, y, z = xyz_color.coordinates.map { |_| _.to_f }
|
|
20
|
+
|
|
21
|
+
# XYZ to LMS transformation matrix
|
|
22
|
+
l = (x * AD("0.8189330101")) + (y * AD("0.3618667424")) - (z * AD("0.1288597137"))
|
|
23
|
+
m = (x * AD("0.0329845436")) + (y * AD("0.9293118715")) + (z * AD("0.0361456387"))
|
|
24
|
+
s = (x * AD("0.0482003018")) + (y * AD("0.2643662691")) + (z * AD("0.6338517070"))
|
|
25
|
+
|
|
26
|
+
# Apply cube root transformation
|
|
27
|
+
l_ = l.to_f**Rational(1, 3)
|
|
28
|
+
m_ = m.to_f**Rational(1, 3)
|
|
29
|
+
s_ = s.to_f**Rational(1, 3)
|
|
30
|
+
|
|
31
|
+
# LMS to OKLAB transformation matrix
|
|
32
|
+
lightness = (AD("0.2104542553") * l_) + (AD("0.793617785") * m_) - (AD("0.0040720468") * s_)
|
|
33
|
+
a = (AD("1.9779984951") * l_) - (AD("2.4285922050") * m_) + (AD("0.4505937099") * s_)
|
|
34
|
+
b = (AD("0.0259040371") * l_) + (AD("0.7827717662") * m_) - (AD("0.8086757660") * s_)
|
|
35
|
+
|
|
36
|
+
Color.new(ColorSpace.find(:oklab), [lightness, a, b], xyz_color.alpha)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::Converters::YiqToSrgb - YIQ to sRGB color space converter
|
|
4
|
+
#
|
|
5
|
+
# This converter transforms colors from the YIQ color space back to the standard RGB
|
|
6
|
+
# (sRGB) color space using the inverse NTSC transformation matrix. The conversion
|
|
7
|
+
# recombines luminance (Y) and chrominance (I, Q) into RGB components.
|
|
8
|
+
#
|
|
9
|
+
# Key features:
|
|
10
|
+
# - Implements the inverse NTSC YIQ to RGB conversion using matrix transformation
|
|
11
|
+
# - Recombines brightness (Y) and color information (I, Q) into RGB
|
|
12
|
+
# - Maintains alpha channel transparency values during conversion
|
|
13
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
|
14
|
+
# - May produce RGB values outside [0,1] range for out-of-gamut YIQ values
|
|
15
|
+
#
|
|
16
|
+
# The inverse transformation allows colors manipulated in YIQ space (e.g., brightness
|
|
17
|
+
# adjustments on the Y channel) to be converted back to RGB for display.
|
|
18
|
+
|
|
19
|
+
module Abachrome
|
|
20
|
+
module Converters
|
|
21
|
+
class YiqToSrgb
|
|
22
|
+
# Converts a color from YIQ color space to sRGB color space.
|
|
23
|
+
# This method applies the inverse NTSC transformation matrix.
|
|
24
|
+
#
|
|
25
|
+
# @param yiq_color [Abachrome::Color] A color object in the YIQ color space
|
|
26
|
+
# @return [Abachrome::Color] A new color object in the sRGB color space
|
|
27
|
+
# with the same alpha value as the input color
|
|
28
|
+
def self.convert(yiq_color)
|
|
29
|
+
y, i, q = yiq_color.coordinates.map { |c| c.to_f }
|
|
30
|
+
|
|
31
|
+
# Inverse NTSC YIQ to RGB transformation matrix
|
|
32
|
+
# R = Y + 0.956I + 0.619Q
|
|
33
|
+
# G = Y - 0.272I - 0.647Q
|
|
34
|
+
# B = Y - 1.106I + 1.703Q
|
|
35
|
+
r = y + (AD("0.9563") * i) + (AD("0.6210") * q)
|
|
36
|
+
g = y - (AD("0.2721") * i) - (AD("0.6474") * q)
|
|
37
|
+
b = y - (AD("1.1070") * i) + (AD("1.7046") * q)
|
|
38
|
+
|
|
39
|
+
Color.new(
|
|
40
|
+
ColorSpace.find(:srgb),
|
|
41
|
+
[r, g, b],
|
|
42
|
+
yiq_color.alpha
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Abachrome
|
|
4
|
+
module Gamut
|
|
5
|
+
def self.register(gamut)
|
|
6
|
+
@gamuts ||= {}
|
|
7
|
+
@gamuts[gamut.name] = gamut
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.find(name)
|
|
11
|
+
@gamuts ||= {}
|
|
12
|
+
@gamuts[name.to_sym] or raise ArgumentError, "Unknown gamut: #{name}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.gamuts
|
|
16
|
+
@gamuts ||= {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Base
|
|
20
|
+
attr_reader :name, :primaries, :white_point
|
|
21
|
+
|
|
22
|
+
def initialize(name, primaries, white_point)
|
|
23
|
+
@name = name
|
|
24
|
+
@primaries = primaries
|
|
25
|
+
@white_point = white_point
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# TODO: - make this work properly
|
|
29
|
+
def contains?(coordinates)
|
|
30
|
+
x, y, z = coordinates
|
|
31
|
+
x.between?(0, 1) &&
|
|
32
|
+
y >= 0 && y <= 1 &&
|
|
33
|
+
z >= 0 && z <= 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def map(coordinates, method: :clip)
|
|
37
|
+
case method
|
|
38
|
+
when :clip
|
|
39
|
+
clip(coordinates)
|
|
40
|
+
when :scale
|
|
41
|
+
scale(coordinates)
|
|
42
|
+
else
|
|
43
|
+
raise ArgumentError, "Unknown mapping method: #{method}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def clip(coordinates)
|
|
50
|
+
coordinates.map do |c|
|
|
51
|
+
c.clamp(0, 1)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def scale(coordinates, channels = nil, min = nil, max = nil)
|
|
56
|
+
min ||= coordinates.min
|
|
57
|
+
max ||= coordinates.max
|
|
58
|
+
channels ||= (0..(coordinates.length - 1)).to_a
|
|
59
|
+
|
|
60
|
+
scale_factor = if max > 1
|
|
61
|
+
1.0 / max
|
|
62
|
+
elsif min.negative?
|
|
63
|
+
1.0 / (1 - min)
|
|
64
|
+
else
|
|
65
|
+
1.0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
coordinates.each_with_index.map { |c, channel_index| channels.include?(channel_index) ? c * scale_factor : c }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Abachrome
|
|
4
|
+
module Gamut
|
|
5
|
+
class P3 < Base
|
|
6
|
+
def initialize
|
|
7
|
+
primaries = {
|
|
8
|
+
red: [0.680, 0.320],
|
|
9
|
+
green: [0.265, 0.690],
|
|
10
|
+
blue: [0.150, 0.060]
|
|
11
|
+
}
|
|
12
|
+
super(:p3, primaries, :D65)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def contains?(coordinates)
|
|
16
|
+
r, g, b = coordinates
|
|
17
|
+
r.between?(0, 1) &&
|
|
18
|
+
g >= 0 && g <= 1 &&
|
|
19
|
+
b >= 0 && b <= 1
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
register(P3.new)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib
|
|
4
|
+
abachrome
|
|
5
|
+
gamut
|
|
6
|
+
rec2020.rb
|
|
7
|
+
|
|
8
|
+
module Abachrome
|
|
9
|
+
module Gamut
|
|
10
|
+
class Rec2020 < Base
|
|
11
|
+
def initialize
|
|
12
|
+
primaries = {
|
|
13
|
+
red: [0.708, 0.292],
|
|
14
|
+
green: [0.170, 0.797],
|
|
15
|
+
blue: [0.131, 0.046]
|
|
16
|
+
}
|
|
17
|
+
super(:rec2020, primaries, :D65)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
register(Rec2020.new)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Abachrome::Gamut::SRGB - sRGB color gamut definition and validation
|
|
4
|
+
#
|
|
5
|
+
# This module defines the sRGB color gamut within the Abachrome color manipulation library.
|
|
6
|
+
# The sRGB gamut represents the range of colors that can be displayed on standard monitors
|
|
7
|
+
# and is the default color space for web content and most consumer displays. It uses the
|
|
8
|
+
# D65 white point and specific primary color coordinates that define the boundaries of
|
|
9
|
+
# reproducible colors in the sRGB color space.
|
|
10
|
+
#
|
|
11
|
+
# Key features:
|
|
12
|
+
# - Defines sRGB primary color coordinates for red, green, and blue
|
|
13
|
+
# - Uses D65 illuminant as the white point reference for proper color reproduction
|
|
14
|
+
# - Provides gamut boundary validation to ensure colors fall within displayable ranges
|
|
15
|
+
# - Implements color containment checking for RGB coordinate validation
|
|
16
|
+
# - Automatically registers with the global gamut registry for system-wide access
|
|
17
|
+
# - Serves as the foundation for sRGB color space conversions and display output
|
|
18
|
+
#
|
|
19
|
+
# The sRGB gamut is essential for ensuring colors are properly represented on standard
|
|
20
|
+
# displays and provides the color boundary definitions needed for gamut mapping operations
|
|
21
|
+
# when converting between different color spaces or preparing colors for web and print output.
|
|
22
|
+
|
|
23
|
+
require_relative "base"
|
|
24
|
+
|
|
25
|
+
module Abachrome
|
|
26
|
+
module Gamut
|
|
27
|
+
class SRGB < Base
|
|
28
|
+
def initialize
|
|
29
|
+
primaries = {
|
|
30
|
+
red: [0.6400, 0.3300],
|
|
31
|
+
green: [0.3000, 0.6000],
|
|
32
|
+
blue: [0.1500, 0.0600]
|
|
33
|
+
}
|
|
34
|
+
super(:srgb, primaries, :D65)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def contains?(coordinates)
|
|
38
|
+
r, g, b = coordinates
|
|
39
|
+
r.between?(0, 1) &&
|
|
40
|
+
g >= 0 && g <= 1 &&
|
|
41
|
+
b >= 0 && b <= 1
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
register(SRGB.new)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Abachrome
|
|
4
|
+
module Illuminants
|
|
5
|
+
class Base
|
|
6
|
+
class << self
|
|
7
|
+
def whitepoint
|
|
8
|
+
raise NotImplementedError, "#{name}#whitepoint must be implemented"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def x
|
|
12
|
+
whitepoint[0]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def y
|
|
16
|
+
whitepoint[1]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def z
|
|
20
|
+
whitepoint[2]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def xyz
|
|
24
|
+
whitepoint
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s
|
|
28
|
+
name.split("::").last
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Abachrome
|
|
4
|
+
module Illuminants
|
|
5
|
+
class D50 < Base
|
|
6
|
+
def self.x
|
|
7
|
+
0.96422
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.y
|
|
11
|
+
1.0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.z
|
|
15
|
+
0.82521
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.temperature
|
|
19
|
+
5003
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.name
|
|
23
|
+
"D50"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.description
|
|
27
|
+
"CIE Standard Illuminant D50 (Horizon Light)"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Abachrome
|
|
4
|
+
module Illuminants
|
|
5
|
+
class D55 < Base
|
|
6
|
+
def x
|
|
7
|
+
0.33243
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def y
|
|
11
|
+
0.34744
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def z
|
|
15
|
+
0.32013
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def temperature
|
|
19
|
+
5503
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def description
|
|
23
|
+
"D55 (mid-morning/mid-afternoon daylight)"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|