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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/.rubocop.yml +10 -0
  4. data/CHANGELOG.md +21 -0
  5. data/CLA.md +45 -0
  6. data/CODE-OF-CONDUCT.md +9 -0
  7. data/LICENSE +19 -0
  8. data/README.md +315 -0
  9. data/Rakefile +15 -0
  10. data/SECURITY.md +94 -0
  11. data/abachrome-float.gemspec +36 -0
  12. data/demos/ncurses/plasma.rb +124 -0
  13. data/devenv.lock +171 -0
  14. data/devenv.nix +52 -0
  15. data/devenv.yaml +8 -0
  16. data/lib/abachrome/color.rb +197 -0
  17. data/lib/abachrome/color_mixins/blend.rb +100 -0
  18. data/lib/abachrome/color_mixins/lighten.rb +90 -0
  19. data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
  20. data/lib/abachrome/color_mixins/to_colorspace.rb +107 -0
  21. data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
  22. data/lib/abachrome/color_mixins/to_lrgb.rb +121 -0
  23. data/lib/abachrome/color_mixins/to_oklab.rb +117 -0
  24. data/lib/abachrome/color_mixins/to_oklch.rb +110 -0
  25. data/lib/abachrome/color_mixins/to_srgb.rb +142 -0
  26. data/lib/abachrome/color_models/cmyk.rb +159 -0
  27. data/lib/abachrome/color_models/hsv.rb +49 -0
  28. data/lib/abachrome/color_models/lms.rb +38 -0
  29. data/lib/abachrome/color_models/oklab.rb +37 -0
  30. data/lib/abachrome/color_models/oklch.rb +91 -0
  31. data/lib/abachrome/color_models/rgb.rb +58 -0
  32. data/lib/abachrome/color_models/xyz.rb +31 -0
  33. data/lib/abachrome/color_models/yiq.rb +37 -0
  34. data/lib/abachrome/color_space.rb +199 -0
  35. data/lib/abachrome/converter.rb +117 -0
  36. data/lib/abachrome/converters/base.rb +128 -0
  37. data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
  38. data/lib/abachrome/converters/lms_to_lrgb.rb +40 -0
  39. data/lib/abachrome/converters/lms_to_srgb.rb +27 -0
  40. data/lib/abachrome/converters/lms_to_xyz.rb +34 -0
  41. data/lib/abachrome/converters/lrgb_to_lms.rb +3 -0
  42. data/lib/abachrome/converters/lrgb_to_oklab.rb +57 -0
  43. data/lib/abachrome/converters/lrgb_to_srgb.rb +59 -0
  44. data/lib/abachrome/converters/lrgb_to_xyz.rb +33 -0
  45. data/lib/abachrome/converters/oklab_to_lms.rb +44 -0
  46. data/lib/abachrome/converters/oklab_to_lrgb.rb +71 -0
  47. data/lib/abachrome/converters/oklab_to_oklch.rb +56 -0
  48. data/lib/abachrome/converters/oklab_to_srgb.rb +46 -0
  49. data/lib/abachrome/converters/oklch_to_lrgb.rb +79 -0
  50. data/lib/abachrome/converters/oklch_to_oklab.rb +52 -0
  51. data/lib/abachrome/converters/oklch_to_srgb.rb +46 -0
  52. data/lib/abachrome/converters/oklch_to_xyz.rb +70 -0
  53. data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
  54. data/lib/abachrome/converters/srgb_to_lrgb.rb +55 -0
  55. data/lib/abachrome/converters/srgb_to_oklab.rb +45 -0
  56. data/lib/abachrome/converters/srgb_to_oklch.rb +47 -0
  57. data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
  58. data/lib/abachrome/converters/xyz_to_lms.rb +34 -0
  59. data/lib/abachrome/converters/xyz_to_oklab.rb +42 -0
  60. data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
  61. data/lib/abachrome/gamut/base.rb +74 -0
  62. data/lib/abachrome/gamut/p3.rb +27 -0
  63. data/lib/abachrome/gamut/rec2020.rb +25 -0
  64. data/lib/abachrome/gamut/srgb.rb +49 -0
  65. data/lib/abachrome/illuminants/base.rb +35 -0
  66. data/lib/abachrome/illuminants/d50.rb +33 -0
  67. data/lib/abachrome/illuminants/d55.rb +29 -0
  68. data/lib/abachrome/illuminants/d65.rb +37 -0
  69. data/lib/abachrome/illuminants/d75.rb +29 -0
  70. data/lib/abachrome/named/css.rb +157 -0
  71. data/lib/abachrome/named/tailwind.rb +301 -0
  72. data/lib/abachrome/outputs/css.rb +119 -0
  73. data/lib/abachrome/palette.rb +244 -0
  74. data/lib/abachrome/palette_mixins/interpolate.rb +53 -0
  75. data/lib/abachrome/palette_mixins/resample.rb +61 -0
  76. data/lib/abachrome/palette_mixins/stretch_luminance.rb +72 -0
  77. data/lib/abachrome/parsers/css.rb +452 -0
  78. data/lib/abachrome/parsers/hex.rb +52 -0
  79. data/lib/abachrome/parsers/tailwind.rb +45 -0
  80. data/lib/abachrome/spectral.rb +276 -0
  81. data/lib/abachrome/to_abcd.rb +23 -0
  82. data/lib/abachrome/version.rb +7 -0
  83. data/lib/abachrome.rb +242 -0
  84. data/logo.png +0 -0
  85. data/logo.webp +0 -0
  86. data/security/assesments/2025-10-12-SECURITY_ASSESSMENT.md +53 -0
  87. data/security/vex.json +21 -0
  88. metadata +146 -0
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::Lighten - Color lightness adjustment functionality
4
+ #
5
+ # This mixin provides methods for adjusting the lightness of colors by manipulating
6
+ # the L (lightness) component in the OKLAB color space. The OKLAB color space is used
7
+ # because it provides perceptually uniform lightness adjustments that appear more
8
+ # natural to the human eye compared to adjustments in other color spaces.
9
+ #
10
+ # Key features:
11
+ # - Lighten and darken colors with configurable amounts
12
+ # - Both non-destructive (lighten/darken) and destructive (lighten!/darken!) variants
13
+ # - Automatic clamping to valid lightness ranges [0, 1]
14
+ # - High-precision decimal arithmetic for accurate color calculations
15
+ # - Conversion to OKLAB color space for perceptually uniform adjustments
16
+ #
17
+ # The mixin includes both immutable methods that return new color instances and mutable
18
+ # methods that modify the current color object in place, providing flexibility for
19
+ # different use cases and performance requirements.
20
+
21
+ module Abachrome
22
+ module ColorMixins
23
+ module Lighten
24
+ # Increases the lightness of a color by the specified amount in the OKLab color space.
25
+ # This method works by extracting the L (lightness) component from the OKLab
26
+ # representation of the color and increasing it by the given amount, ensuring
27
+ # the result stays within the valid range of [0, 1].
28
+ #
29
+ # @param amount [Numeric] The amount to increase the lightness by, as a decimal
30
+ # value between 0 and 1. Defaults to 0.1 (10% increase).
31
+ # @return [Abachrome::Color] A new Color instance with increased lightness.
32
+ def lighten(amount = 0.1)
33
+ amount = amount.to_f
34
+ oklab = to_oklab
35
+ l, a, b = oklab.coordinates
36
+
37
+ new_l = l + amount
38
+ new_l = "1.0".to_f if new_l > 1
39
+ new_l = "0.0".to_f if new_l.negative?
40
+
41
+ Color.new(
42
+ ColorSpace.find(:oklab),
43
+ [new_l, a, b],
44
+ alpha
45
+ )
46
+ end
47
+
48
+ # Increases the lightness of the color by the specified amount and modifies the current color object.
49
+ # This method changes the color in-place, mutating the current object. The color
50
+ # is converted to a lightness-based color space if needed to perform the operation.
51
+ #
52
+ # @param amount [Float] The amount to increase the lightness by, as a decimal value
53
+ # between 0 and 1. Default is 0.1 (10% increase).
54
+ # @return [Abachrome::Color] Returns self for method chaining.
55
+ def lighten!(amount = 0.1)
56
+ lightened = lighten(amount)
57
+ @color_space = lightened.color_space
58
+ @coordinates = lightened.coordinates
59
+ @alpha = lightened.alpha
60
+ self
61
+ end
62
+
63
+ # Darkens a color by decreasing its lightness value.
64
+ #
65
+ # This method is effectively a convenience wrapper around the {#lighten} method,
66
+ # passing a negative amount value to decrease the lightness instead of increasing it.
67
+ #
68
+ # @param amount [Float] The amount to darken the color by, between 0 and 1.
69
+ # Defaults to 0.1 (10% darker).
70
+ # @return [Color] A new color instance with decreased lightness.
71
+ # @see #lighten
72
+ def darken(amount = 0.1)
73
+ lighten(-amount)
74
+ end
75
+
76
+ # Decreases the lightness value of the color by the specified amount.
77
+ # Modifies the color in place.
78
+ #
79
+ # @param amount [Float] The amount to darken the color by, as a value between 0 and 1.
80
+ # Defaults to 0.1 (10% darker).
81
+ # @return [Abachrome::Color] Returns self with the modified lightness value.
82
+ # @see #lighten!
83
+ def darken!(amount = 0.1)
84
+ lighten!(-amount)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::SpectralMix - Kubelka-Munk spectral color mixing
4
+ #
5
+ # This mixin adds spectral mixing capabilities to Color objects using the
6
+ # Kubelka-Munk theory for realistic paint-like color mixing.
7
+ #
8
+ # Unlike simple RGB blending or interpolation in perceptual color spaces,
9
+ # spectral mixing simulates how real pigments interact with light through
10
+ # absorption and scattering. This produces more realistic results, especially
11
+ # when mixing complementary colors.
12
+ #
13
+ # Key features:
14
+ # - Physics-based color mixing using Kubelka-Munk theory
15
+ # - Avoids muddy browns when mixing complementary colors
16
+ # - Supports tinting strength for different pigment concentrations
17
+ # - More realistic than linear RGB or LAB interpolation
18
+
19
+ module Abachrome
20
+ module ColorMixins
21
+ module SpectralMix
22
+ # Mix this color with another color using Kubelka-Munk spectral mixing.
23
+ #
24
+ # This method produces more realistic color mixing than simple RGB or LAB
25
+ # interpolation by simulating how real pigments absorb and scatter light.
26
+ #
27
+ # @param other [Abachrome::Color] The color to mix with
28
+ # @param amount [Float] The mix ratio, between 0 and 1. 0.5 means equal mixing.
29
+ # Values closer to 0 favor this color, values closer to 1 favor the other color.
30
+ # @param tinting_strength_self [Float] Tinting strength of this color (default: 1.0)
31
+ # Higher values mean stronger pigment concentration
32
+ # @param tinting_strength_other [Float] Tinting strength of other color (default: 1.0)
33
+ # @return [Abachrome::Color] A new color representing the spectral mix
34
+ #
35
+ # @example Mix red and blue equally
36
+ # red = Abachrome.from_rgb(1, 0, 0)
37
+ # blue = Abachrome.from_rgb(0, 0, 1)
38
+ # purple = red.spectral_mix(blue, 0.5)
39
+ #
40
+ # @example Mix with 25% of blue
41
+ # mostly_red = red.spectral_mix(blue, 0.25)
42
+ #
43
+ # @example Mix with different tinting strengths
44
+ # # Stronger blue pigment
45
+ # purple = red.spectral_mix(blue, 0.5, tinting_strength_other: 2.0)
46
+ def spectral_mix(other, amount = 0.5, tinting_strength_self: 1.0, tinting_strength_other: 1.0)
47
+ require_relative "../spectral"
48
+
49
+ # Convert amount to weights
50
+ # amount = 0 means 100% self, 0% other
51
+ # amount = 0.5 means 50% self, 50% other
52
+ # amount = 1 means 0% self, 100% other
53
+ weight_self = 1.0 - amount.to_f
54
+ weight_other = amount.to_f
55
+
56
+ colors = [
57
+ { color: self, weight: weight_self },
58
+ { color: other, weight: weight_other }
59
+ ]
60
+
61
+ tinting_strengths = {
62
+ self => tinting_strength_self.to_f,
63
+ other => tinting_strength_other.to_f
64
+ }
65
+
66
+ Spectral.mix(colors, tinting_strengths: tinting_strengths)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::ToColorspace - Color space conversion functionality
4
+ #
5
+ # This mixin provides methods for converting colors between different color spaces within
6
+ # the Abachrome library. It includes both immutable and mutable conversion methods that
7
+ # allow colors to be transformed from their current color space to any registered target
8
+ # color space, such as sRGB, OKLAB, OKLCH, or linear RGB.
9
+ #
10
+ # Key features:
11
+ # - Convert colors to any registered color space with automatic converter lookup
12
+ # - Both non-destructive (to_color_space/convert_to/in_color_space) and destructive variants
13
+ # - Optimized to return the same object when no conversion is needed
14
+ # - Flexible API with multiple method names for different use cases and preferences
15
+ # - Integration with the Converter system for extensible color space transformations
16
+ #
17
+ # The mixin provides a consistent interface for color space conversions while maintaining
18
+ # the precision and accuracy required for color science calculations through the use of
19
+ # the underlying converter infrastructure.
20
+
21
+ module Abachrome
22
+ module ColorMixins
23
+ module ToColorspace
24
+ # Converts the current color to the specified target color space.
25
+ #
26
+ # This method transforms the current color into an equivalent color in a different
27
+ # color space. If the target space is the same as the current color space, no
28
+ # conversion is performed and the current color is returned.
29
+ #
30
+ # @param target_space [Abachrome::ColorSpace] The target color space to convert to
31
+ # @return [Abachrome::Color] A new color object in the target color space, or self
32
+ # if the target space is the same as the current color space
33
+ def to_color_space(target_space)
34
+ return self if color_space == target_space
35
+
36
+ Converter.convert(self, target_space.name)
37
+ end
38
+
39
+ # Converts the color object to the specified target color space in-place.
40
+ # This method modifies the current object by changing its color space and
41
+ # coordinates to match the target color space.
42
+ #
43
+ # @param target_space [Abachrome::ColorSpace] The color space to convert to
44
+ # @return [Abachrome::Color] Returns self with modified color space and coordinates
45
+ # @see #to_color_space The non-destructive version that returns a new color object
46
+ def to_color_space!(target_space)
47
+ unless color_space == target_space
48
+ converted = to_color_space(target_space)
49
+ @color_space = converted.color_space
50
+ @coordinates = converted.coordinates
51
+ end
52
+ self
53
+ end
54
+
55
+ # Convert this color to a different color space.
56
+ #
57
+ # @param space_name [String, Symbol] The name of the target color space to convert to.
58
+ # @return [Abachrome::Color] A new Color object in the specified color space.
59
+ # @example
60
+ # # Convert a color from sRGB to OKLCH
61
+ # rgb_color.convert_to(:oklch)
62
+ # @see Abachrome::ColorSpace.find
63
+ # @see #to_color_space
64
+ def convert_to(space_name)
65
+ to_color_space(ColorSpace.find(space_name))
66
+ end
67
+
68
+ # Converts this color to the specified color space in place.
69
+ #
70
+ # @param space_name [String, Symbol] The name or identifier of the target color space to convert to.
71
+ # @return [Abachrome::Color] Returns self with its values converted to the specified color space.
72
+ # @raise [Abachrome::ColorSpaceNotFoundError] If the specified color space is not registered.
73
+ # @see Abachrome::ColorSpace.find
74
+ # @example
75
+ # red = Abachrome::Color.new(1, 0, 0, color_space: :srgb)
76
+ # red.convert_to!(:oklch) # Converts the red color to OKLCH space in place
77
+ def convert_to!(space_name)
78
+ to_color_space!(ColorSpace.find(space_name))
79
+ end
80
+
81
+ # Convert a color to a specified color space.
82
+ #
83
+ # @param space_name [Symbol, String] The name of the color space to convert to.
84
+ # @return [Abachrome::Color] A new Color instance in the specified color space.
85
+ # @example
86
+ # red_rgb = Abachrome::Color.new(:srgb, [1, 0, 0])
87
+ # red_lch = red_rgb.in_color_space(:oklch)
88
+ def in_color_space(space_name)
89
+ convert_to(space_name)
90
+ end
91
+
92
+ # Converts the color to the specified color space and mutates the current object.
93
+ #
94
+ # @param space_name [Symbol, String] The target color space to convert to (e.g., :oklch, :rgb, :lab)
95
+ # @return [Abachrome::Color] Returns self after conversion
96
+ # @see #convert_to!
97
+ # @example
98
+ # color = Abachrome::Color.new([255, 0, 0], :rgb)
99
+ # color.in_color_space!(:oklch) # Color is now in OKLCH space
100
+ def in_color_space!(space_name)
101
+ convert_to!(space_name)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::ToGrayscale - Grayscale conversion mixin
4
+ #
5
+ # This mixin provides methods for converting colors to grayscale using various
6
+ # standard luma calculations. It supports both legacy Rec. 601 (SDTV) and modern
7
+ # Rec. 709 (HDTV) coefficients for accurate grayscale conversion based on human
8
+ # eye sensitivity.
9
+ #
10
+ # Key features:
11
+ # - Rec. 601 luma: Y = 0.299R + 0.587G + 0.114B (NTSC/SDTV standard)
12
+ # - Rec. 709 luma: Y = 0.2126R + 0.7152G + 0.0722B (HDTV standard)
13
+ # - Maintains alpha channel during conversion
14
+ # - Uses BigDecimal for precise calculations
15
+ #
16
+ # This is essential for image processing, accessibility checks, and any application
17
+ # that needs perceptually accurate grayscale conversion rather than simple averaging.
18
+
19
+ module Abachrome
20
+ module ColorMixins
21
+ module ToGrayscale
22
+ # Converts the color to grayscale using Rec. 601 luma coefficients (legacy NTSC standard).
23
+ # This is the same calculation used in the YIQ color space's Y component.
24
+ #
25
+ # @return [Abachrome::Color] A grayscale version of the color in sRGB space
26
+ def to_grayscale_601
27
+ rgb_color = to_srgb
28
+ r, g, b = rgb_color.coordinates
29
+
30
+ # Rec. 601 luma: Y = 0.299R + 0.587G + 0.114B
31
+ luma = (AD("0.299") * r) + (AD("0.587") * g) + (AD("0.114") * b)
32
+
33
+ Color.from_rgb(luma, luma, luma, alpha)
34
+ end
35
+
36
+ # Converts the color to grayscale using Rec. 709 luma coefficients (HDTV standard).
37
+ #
38
+ # @return [Abachrome::Color] A grayscale version of the color in sRGB space
39
+ def to_grayscale_709
40
+ rgb_color = to_srgb
41
+ r, g, b = rgb_color.coordinates
42
+
43
+ # Rec. 709 luma: Y = 0.2126R + 0.7152G + 0.0722B
44
+ luma = (AD("0.2126") * r) + (AD("0.7152") * g) + (AD("0.0722") * b)
45
+
46
+ Color.from_rgb(luma, luma, luma, alpha)
47
+ end
48
+
49
+ # Converts the color to grayscale using the default Rec. 601 standard.
50
+ # This is an alias for to_grayscale_601 and matches the legacy behavior
51
+ # expected by most image processing applications.
52
+ #
53
+ # @return [Abachrome::Color] A grayscale version of the color in sRGB space
54
+ def to_grayscale
55
+ to_grayscale_601
56
+ end
57
+
58
+ # Calculates the relative luminance (luma) value using Rec. 601 coefficients.
59
+ # This returns just the Y component without creating a new color.
60
+ #
61
+ # @return [AbcDecimal] The luma value in range [0, 1]
62
+ def luma_601
63
+ rgb_color = to_srgb
64
+ r, g, b = rgb_color.coordinates
65
+ (AD("0.299") * r) + (AD("0.587") * g) + (AD("0.114") * b)
66
+ end
67
+
68
+ # Calculates the relative luminance (luma) value using Rec. 709 coefficients.
69
+ # This returns just the Y component without creating a new color.
70
+ #
71
+ # @return [AbcDecimal] The luma value in range [0, 1]
72
+ def luma_709
73
+ rgb_color = to_srgb
74
+ r, g, b = rgb_color.coordinates
75
+ (AD("0.2126") * r) + (AD("0.7152") * g) + (AD("0.0722") * b)
76
+ end
77
+
78
+ # Calculates the relative luminance using the default Rec. 601 standard.
79
+ # Alias for luma_601.
80
+ #
81
+ # @return [AbcDecimal] The luma value in range [0, 1]
82
+ def luma
83
+ luma_601
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::ToLrgb - Linear RGB color space conversion functionality
4
+ #
5
+ # This mixin provides methods for converting colors to the linear RGB (LRGB) color space,
6
+ # which uses a linear relationship between stored numeric values and actual light intensity.
7
+ # Linear RGB is essential for accurate color calculations and serves as an intermediate
8
+ # color space for many color transformations, particularly when converting between
9
+ # different color models.
10
+ #
11
+ # Key features:
12
+ # - Convert colors to linear RGB with automatic converter lookup
13
+ # - Both non-destructive (to_lrgb) and destructive (to_lrgb!) conversion methods
14
+ # - Direct access to linear RGB components (lred, lgreen, lblue)
15
+ # - Utility methods for RGB array and hex string output
16
+ # - Optimized to return the same object when no conversion is needed
17
+ # - High-precision decimal arithmetic for accurate color science calculations
18
+ #
19
+ # The linear RGB color space differs from standard sRGB by removing gamma correction,
20
+ # making it suitable for mathematical operations like blending, lighting calculations,
21
+ # and color space transformations that require linear light behavior.
22
+
23
+ require_relative "../converter"
24
+
25
+ module Abachrome
26
+ module ColorMixins
27
+ module ToLrgb
28
+ # Converts this color to the Linear RGB (LRGB) color space.
29
+ # This method transforms the current color to the linear RGB color space,
30
+ # which uses a linear relationship between the stored numeric value and
31
+ # the actual light intensity. If the color is already in the LRGB space,
32
+ # it returns the current object without conversion.
33
+ #
34
+ # @return [Abachrome::Color] A new color object in the LRGB color space,
35
+ # or the original object if already in LRGB space
36
+ def to_lrgb
37
+ return self if color_space.name == :lrgb
38
+
39
+ Converter.convert(self, :lrgb)
40
+ end
41
+
42
+ # Converts the current color to the linear RGB (LRGB) color space and updates
43
+ # the receiver's state. If the color is already in LRGB space, this is a no-op.
44
+ #
45
+ # Unlike #to_lrgb which returns a new color instance, this method modifies the
46
+ # current object by changing its color space and coordinates to the LRGB equivalent.
47
+ #
48
+ # @return [Abachrome::Color] the receiver itself, now in LRGB color space
49
+ def to_lrgb!
50
+ unless color_space.name == :lrgb
51
+ lrgb_color = to_lrgb
52
+ @color_space = lrgb_color.color_space
53
+ @coordinates = lrgb_color.coordinates
54
+ end
55
+ self
56
+ end
57
+
58
+ # Returns the linear red component value of the color.
59
+ #
60
+ # This method accesses the first coordinate from the color in linear RGB space.
61
+ # Linear RGB values differ from standard RGB by using a non-gamma-corrected
62
+ # linear representation of luminance.
63
+ #
64
+ # @return [AbcDecimal] The linear red component value, typically in range [0, 1]
65
+ def lred
66
+ to_lrgb.coordinates[0]
67
+ end
68
+
69
+ # Retrieves the linear green (lgreen) coordinate from a color by converting it to
70
+ # linear RGB color space first. Linear RGB uses a different scale than standard
71
+ # sRGB, with values representing linear light energy rather than gamma-corrected
72
+ # values.
73
+ #
74
+ # @return [AbcDecimal] The linear green component value from the color's linear
75
+ # RGB representation
76
+ def lgreen
77
+ to_lrgb.coordinates[1]
78
+ end
79
+
80
+ # Returns the linear blue channel value of this color after conversion to linear RGB color space.
81
+ #
82
+ # This method converts the current color to the linear RGB color space and extracts the blue
83
+ # component (the third coordinate).
84
+ #
85
+ # @return [AbcDecimal] The linear blue component value, typically in the range [0, 1]
86
+ def lblue
87
+ to_lrgb.coordinates[2]
88
+ end
89
+
90
+ # Returns the coordinates of the color in the linear RGB color space.
91
+ #
92
+ # @return [Array<AbcDecimal>] An array of three AbcDecimal values representing
93
+ # the red, green, and blue components in linear RGB color space.
94
+ def lrgb_values
95
+ to_lrgb.coordinates
96
+ end
97
+
98
+ # Returns an array of sRGB values as integers in the 0-255 range.
99
+ # This method converts the color to RGB, scales the values to the 0-255 range,
100
+ # rounds to integers, and ensures they are clamped within the valid range.
101
+ #
102
+ # @return [Array<Integer>] An array of three RGB integer values between 0-255
103
+ def rgb_array
104
+ to_rgb.coordinates.map { |c| (c * 255).round.clamp(0, 255) }
105
+ end
106
+
107
+ # Returns a hexadecimal string representation of the color in RGB format.
108
+ #
109
+ # @return [String] A hexadecimal color code in the format '#RRGGBB' where RR, GG, and BB
110
+ # are two-digit hexadecimal values for the red, green, and blue components respectively.
111
+ # @example
112
+ # color.rgb_hex # => "#1a2b3c"
113
+ def rgb_hex
114
+ r, g, b = rgb_array
115
+ format("#%02x%02x%02x", r, g, b)
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::ToOklab - OKLAB color space conversion functionality
4
+ #
5
+ # This mixin provides methods for converting colors to the OKLAB color space, which is a
6
+ # perceptually uniform color space designed for better color manipulation and comparison.
7
+ # OKLAB provides more intuitive lightness adjustments and color blending compared to
8
+ # traditional RGB color spaces, making it ideal for color science applications.
9
+ #
10
+ # Key features:
11
+ # - Convert colors to OKLAB with automatic converter lookup
12
+ # - Both non-destructive (to_oklab) and destructive (to_oklab!) conversion methods
13
+ # - Direct access to OKLAB components (lightness, a, b)
14
+ # - Utility methods for OKLAB 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 OKLAB color space uses three components: L (lightness), a (green-red axis), and
19
+ # b (blue-yellow axis), providing a more perceptually uniform representation of colors
20
+ # that better matches human visual perception compared to traditional color spaces.
21
+
22
+ require_relative "../converter"
23
+
24
+ module Abachrome
25
+ module ColorMixins
26
+ module ToOklab
27
+ # Converts the current color to the OKLAB color space.
28
+ #
29
+ # If the color is already in OKLAB, it returns the color unchanged.
30
+ # Otherwise, it uses the Converter to transform the color to OKLAB.
31
+ #
32
+ # @return [Abachrome::Color] A new Color object in the OKLAB color space
33
+ def to_oklab
34
+ return self if color_space.name == :oklab
35
+
36
+ Converter.convert(self, :oklab)
37
+ end
38
+
39
+ # Converts the color to the OKLAB color space in place.
40
+ # This method transforms the current color into OKLAB space,
41
+ # modifying the original object by updating its color space
42
+ # and coordinates if not already in OKLAB.
43
+ #
44
+ # @example
45
+ # color = Abachrome::Color.from_hex("#ff5500")
46
+ # color.to_oklab! # Color now uses OKLAB color space
47
+ #
48
+ # @return [Abachrome::Color] self, with updated color space and coordinates
49
+ def to_oklab!
50
+ unless color_space.name == :oklab
51
+ oklab_color = to_oklab
52
+ @color_space = oklab_color.color_space
53
+ @coordinates = oklab_color.coordinates
54
+ end
55
+ self
56
+ end
57
+
58
+ # Returns the lightness component (L) of the color in the OKLAB color space.
59
+ # The lightness value ranges from 0 (black) to 1 (white) and represents
60
+ # the perceived lightness of the color.
61
+ #
62
+ # @return [AbcDecimal] The lightness (L) value from the OKLAB color space
63
+ def lightness
64
+ to_oklab.coordinates[0]
65
+ end
66
+
67
+ # Returns the L (Lightness) component from the OKLAB color space.
68
+ #
69
+ # The L value represents perceptual lightness in the OKLAB color space,
70
+ # typically ranging from 0 (black) to 1 (white).
71
+ #
72
+ # @return [AbcDecimal] The L (Lightness) component from the OKLAB color space
73
+ def l
74
+ to_oklab.coordinates[0]
75
+ end
76
+
77
+ # Returns the 'a' component from the OKLAB color space (green-red axis).
78
+ #
79
+ # The 'a' component in OKLAB represents the position on the green-red axis,
80
+ # with negative values being more green and positive values being more red.
81
+ #
82
+ # @return [AbcDecimal] The 'a' component value from the OKLAB color space.
83
+ # @see #to_oklab For the full conversion to OKLAB color space
84
+ def a
85
+ to_oklab.coordinates[1]
86
+ end
87
+
88
+ # Returns the B value of the color in OKLAB color space.
89
+ #
90
+ # This method first converts the color to OKLAB color space if needed,
91
+ # then extracts the B component (blue-yellow axis), which is the third
92
+ # coordinate in the OKLAB model.
93
+ #
94
+ # @return [AbcDecimal] The B component value in OKLAB color space
95
+ def b
96
+ to_oklab.coordinates[2]
97
+ end
98
+
99
+ # Returns the OKLAB color space coordinates for this color.
100
+ #
101
+ # @return [Array] An array of OKLAB coordinates [L, a, b] representing the color in OKLAB color space
102
+ def oklab_values
103
+ to_oklab.coordinates
104
+ end
105
+
106
+ # Returns an array representation of the color's coordinates in the OKLAB color space.
107
+ #
108
+ # @return [Array<AbcDecimal>] An array containing the coordinates of the color
109
+ # in the OKLAB color space in the order [L, a, b]
110
+ def oklab_array
111
+ to_oklab.coordinates
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.