abachrome 0.1.0 → 0.1.2

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +3 -0
  3. data/README.md +10 -0
  4. data/Rakefile +15 -0
  5. data/devenv.lock +88 -17
  6. data/devenv.nix +2 -1
  7. data/devenv.yaml +5 -12
  8. data/lib/abachrome/abc_decimal.rb +201 -7
  9. data/lib/abachrome/color.rb +88 -1
  10. data/lib/abachrome/color_mixins/blend.rb +53 -2
  11. data/lib/abachrome/color_mixins/lighten.rb +49 -2
  12. data/lib/abachrome/color_mixins/to_colorspace.rb +67 -2
  13. data/lib/abachrome/color_mixins/to_lrgb.rb +70 -2
  14. data/lib/abachrome/color_mixins/to_oklab.rb +67 -2
  15. data/lib/abachrome/color_mixins/to_oklch.rb +60 -2
  16. data/lib/abachrome/color_mixins/to_srgb.rb +77 -2
  17. data/lib/abachrome/color_models/hsv.rb +25 -2
  18. data/lib/abachrome/color_models/lms.rb +34 -0
  19. data/lib/abachrome/color_models/oklab.rb +19 -2
  20. data/lib/abachrome/color_models/oklch.rb +42 -2
  21. data/lib/abachrome/color_models/rgb.rb +28 -2
  22. data/lib/abachrome/color_models/xyz.rb +28 -0
  23. data/lib/abachrome/color_space.rb +88 -2
  24. data/lib/abachrome/converter.rb +56 -2
  25. data/lib/abachrome/converters/base.rb +69 -2
  26. data/lib/abachrome/converters/lms_to_lrgb.rb +36 -0
  27. data/lib/abachrome/converters/lms_to_srgb.rb +23 -0
  28. data/lib/abachrome/converters/lms_to_xyz.rb +30 -0
  29. data/lib/abachrome/converters/lrgb_to_lms.rb +0 -0
  30. data/lib/abachrome/converters/lrgb_to_oklab.rb +28 -2
  31. data/lib/abachrome/converters/lrgb_to_srgb.rb +27 -2
  32. data/lib/abachrome/converters/lrgb_to_xyz.rb +29 -0
  33. data/lib/abachrome/converters/oklab_to_lms.rb +41 -0
  34. data/lib/abachrome/converters/oklab_to_lrgb.rb +56 -29
  35. data/lib/abachrome/converters/oklab_to_oklch.rb +31 -2
  36. data/lib/abachrome/converters/oklab_to_srgb.rb +27 -2
  37. data/lib/abachrome/converters/oklch_to_lrgb.rb +66 -6
  38. data/lib/abachrome/converters/oklch_to_oklab.rb +29 -4
  39. data/lib/abachrome/converters/oklch_to_srgb.rb +26 -2
  40. data/lib/abachrome/converters/oklch_to_xyz.rb +66 -0
  41. data/lib/abachrome/converters/srgb_to_lrgb.rb +26 -2
  42. data/lib/abachrome/converters/srgb_to_oklab.rb +28 -2
  43. data/lib/abachrome/converters/srgb_to_oklch.rb +27 -2
  44. data/lib/abachrome/converters/xyz_to_lms.rb +30 -0
  45. data/lib/abachrome/converters/xyz_to_oklab.rb +38 -0
  46. data/lib/abachrome/gamut/base.rb +2 -2
  47. data/lib/abachrome/gamut/p3.rb +2 -2
  48. data/lib/abachrome/gamut/rec2020.rb +2 -2
  49. data/lib/abachrome/gamut/srgb.rb +20 -2
  50. data/lib/abachrome/illuminants/base.rb +2 -2
  51. data/lib/abachrome/illuminants/d50.rb +2 -2
  52. data/lib/abachrome/illuminants/d55.rb +2 -2
  53. data/lib/abachrome/illuminants/d65.rb +2 -2
  54. data/lib/abachrome/illuminants/d75.rb +2 -2
  55. data/lib/abachrome/named/css.rb +149 -158
  56. data/lib/abachrome/outputs/css.rb +2 -2
  57. data/lib/abachrome/palette.rb +112 -2
  58. data/lib/abachrome/palette_mixins/interpolate.rb +20 -2
  59. data/lib/abachrome/palette_mixins/resample.rb +2 -2
  60. data/lib/abachrome/palette_mixins/stretch_luminance.rb +2 -2
  61. data/lib/abachrome/parsers/hex.rb +2 -2
  62. data/lib/abachrome/to_abcd.rb +10 -2
  63. data/lib/abachrome/version.rb +2 -2
  64. data/lib/abachrome.rb +78 -2
  65. data/logo.png +0 -0
  66. data/logo.webp +0 -0
  67. metadata +26 -67
@@ -1,4 +1,22 @@
1
- # frozen_string_literal: true
1
+ # Abachrome::Color - Core color representation class
2
+ #
3
+ # This is the central color class that represents colors across multiple color spaces
4
+ # including sRGB, OKLAB, OKLCH, and linear RGB. The Color class encapsulates color
5
+ # coordinates, alpha values, and color space information while providing methods
6
+ # for color creation, conversion, and manipulation.
7
+ #
8
+ # Key features:
9
+ # - Create colors from RGB, OKLAB, OKLCH values with factory methods
10
+ # - Automatic coordinate validation against color space definitions
11
+ # - Immutable color objects with equality and hash support
12
+ # - Extensible through mixins for color space conversions and operations
13
+ # - High-precision decimal arithmetic using AbcDecimal for accurate calculations
14
+ # - Support for alpha (opacity) values with proper handling in conversions
15
+ #
16
+ # The class uses a mixin system to dynamically include functionality for converting
17
+ # between color spaces, blending operations, and lightness adjustments. All coordinate
18
+ # values are stored as AbcDecimal objects to maintain precision during color science
19
+ # calculations and transformations.
2
20
 
3
21
  require "dry-inflector"
4
22
  require_relative "abc_decimal"
@@ -8,6 +26,13 @@ module Abachrome
8
26
  class Color
9
27
  attr_reader :color_space, :coordinates, :alpha
10
28
 
29
+ # Initializes a new Color object with the specified color space, coordinates, and alpha value.
30
+ #
31
+ # @param color_space [ColorSpace] The color space for this color instance
32
+ # @param coordinates [Array<Numeric, String>] The color coordinates in the specified color space
33
+ # @param alpha [Numeric, String] The alpha (opacity) value, between 0.0 and 1.0 (default: 1.0)
34
+ # @raise [ArgumentError] If the coordinates are invalid for the specified color space
35
+ # @return [Color] A new Color instance
11
36
  def initialize(color_space, coordinates, alpha = AbcDecimal("1.0"))
12
37
  @color_space = color_space
13
38
  @coordinates = coordinates.map { |c| AbcDecimal(c.to_s) }
@@ -25,21 +50,61 @@ module Abachrome
25
50
  include mixin_module
26
51
  end
27
52
 
53
+ # Creates a new Color instance from RGB values
54
+ #
55
+ # @param r [Numeric] The red component value (typically 0-1)
56
+ # @param g [Numeric] The green component value (typically 0-1)
57
+ # @param b [Numeric] The blue component value (typically 0-1)
58
+ # @param a [Numeric] The alpha (opacity) component value (0-1), defaults to 1.0 (fully opaque)
59
+ # @return [Abachrome::Color] A new Color instance in the sRGB color space
28
60
  def self.from_rgb(r, g, b, a = 1.0)
29
61
  space = ColorSpace.find(:srgb)
30
62
  new(space, [r, g, b], a)
31
63
  end
32
64
 
65
+ # Creates a new Color instance from LRGB values
66
+ #
67
+ # @param r [Numeric] The red component value (typically 0-1)
68
+ # @param g [Numeric] The green component value (typically 0-1)
69
+ # @param b [Numeric] The blue component value (typically 0-1)
70
+ # @param a [Numeric] The alpha (opacity) component value (0-1), defaults to 1.0 (fully opaque)
71
+ # @return [Abachrome::Color] A new Color instance in the sRGB color space
72
+ def self.from_lrgb(r, g, b, a = 1.0)
73
+ space = ColorSpace.find(:lrgb)
74
+ new(space, [r, g, b], a)
75
+ end
76
+
77
+ # Creates a new Color object with OKLAB values.
78
+ #
79
+ # @param l [Float] The lightness component (L) of the OKLAB color space
80
+ # @param a [Float] The green-red component (a) of the OKLAB color space
81
+ # @param b [Float] The blue-yellow component (b) of the OKLAB color space
82
+ # @param alpha [Float] The alpha (opacity) value, from 0.0 to 1.0
83
+ # @return [Abachrome::Color] A new Color object in the OKLAB color space
33
84
  def self.from_oklab(l, a, b, alpha = 1.0)
34
85
  space = ColorSpace.find(:oklab)
35
86
  new(space, [l, a, b], alpha)
36
87
  end
37
88
 
89
+ # Creates a new color instance in the OKLCH color space.
90
+ #
91
+ # @param l [Numeric] The lightness component (L), typically in range 0..1
92
+ # @param c [Numeric] The chroma component (C), typically starting from 0 with no upper bound
93
+ # @param h [Numeric] The hue component (H) in degrees, typically in range 0..360
94
+ # @param alpha [Float] The alpha (opacity) component, in range 0..1, defaults to 1.0 (fully opaque)
95
+ # @return [Abachrome::Color] A new Color instance in the OKLCH color space
38
96
  def self.from_oklch(l, c, h, alpha = 1.0)
39
97
  space = ColorSpace.find(:oklch)
40
98
  new(space, [l, c, h], alpha)
41
99
  end
42
100
 
101
+ # Compares this color instance with another for equality.
102
+ #
103
+ # Two colors are considered equal if they have the same color space,
104
+ # coordinates, and alpha value.
105
+ #
106
+ # @param other [Object] The object to compare with
107
+ # @return [Boolean] true if the colors are equal, false otherwise
43
108
  def ==(other)
44
109
  return false unless other.is_a?(Color)
45
110
 
@@ -48,14 +113,31 @@ module Abachrome
48
113
  alpha == other.alpha
49
114
  end
50
115
 
116
+ # Checks if this color is equal to another color object.
117
+ #
118
+ # @param other [Object] The object to compare with
119
+ # @return [Boolean] true if the two colors are equal, false otherwise
120
+ # @see ==
51
121
  def eql?(other)
52
122
  self == other
53
123
  end
54
124
 
125
+ # Generates a hash code for this color instance
126
+ # based on its color space, coordinates, and alpha value.
127
+ # The method first converts these components to strings,
128
+ # then computes a hash of the resulting array.
129
+ #
130
+ # @return [Integer] a hash code that can be used for equality comparison
131
+ # and as a hash key in Hash objects
55
132
  def hash
56
133
  [color_space, coordinates, alpha].map(&:to_s).hash
57
134
  end
58
135
 
136
+ # Returns a string representation of the color in the format "ColorSpaceName(coord1, coord2, coord3, alpha)"
137
+ #
138
+ # @return [String] A human-readable string representation of the color showing its
139
+ # color space name, coordinate values rounded to 3 decimal places, and alpha value
140
+ # (if not 1.0)
59
141
  def to_s
60
142
  coord_str = coordinates.map { |c| c.to_f.round(3) }.join(", ")
61
143
  alpha_str = alpha == AbcDecimal.new("1.0") ? "" : ", #{alpha.to_f.round(3)}"
@@ -64,6 +146,11 @@ module Abachrome
64
146
 
65
147
  private
66
148
 
149
+ # Validates that the number of coordinates matches the expected number for the color space.
150
+ # Compares the size of the coordinates array with the number of coordinates
151
+ # defined in the associated color space.
152
+ # @raise [ArgumentError] when the number of coordinates doesn't match the color space definition
153
+ # @return [nil] if validation passes
67
154
  def validate_coordinates!
68
155
  return if coordinates.size == color_space.coordinates.size
69
156
 
@@ -1,8 +1,38 @@
1
- # frozen_string_literal: true
1
+ # Abachrome::ColorMixins::Blend - Color blending and mixing functionality
2
+ #
3
+ # This mixin provides methods for blending and mixing colors together in various color spaces.
4
+ # The blend operation interpolates between two colors by a specified amount, creating smooth
5
+ # color transitions. All blending operations preserve alpha values and can be performed in
6
+ # the current color space or a specified target color space for optimal results.
7
+ #
8
+ # Key features:
9
+ # - Linear interpolation between colors with configurable blend amounts
10
+ # - Support for blending in different color spaces (sRGB, OKLAB, OKLCH)
11
+ # - Both non-destructive (blend/mix) and destructive (blend!/mix!) variants
12
+ # - Automatic color space conversion when blending colors from different spaces
13
+ # - High-precision decimal arithmetic for accurate color calculations
14
+ #
15
+ # The mixin includes both immutable methods that return new color instances and mutable
16
+ # methods that modify the current color object in place, providing flexibility for
17
+ # different use cases and performance requirements.
2
18
 
3
19
  module Abachrome
4
20
  module ColorMixins
5
21
  module Blend
22
+ # @method blend
23
+ # Interpolates between two colors, creating a new color that is a blend of the two.
24
+ # The blend happens in the specified color space or the current color space if none is provided.
25
+ # #
26
+ # @param [Abachrome::Color] other The color to blend with
27
+ # @param [Float, Integer, #to_d] amount The blend amount between 0 and 1, where 0 returns the original color and 1 returns the other color. Defaults to 0.5 (midpoint)
28
+ # @param [Symbol, nil] target_color_space The color space to perform the blend in (optional)
29
+ # @return [Abachrome::Color] A new color representing the blend of the two colors
30
+ # @example Blend two colors equally
31
+ # red.blend(blue, 0.5)
32
+ # @example Blend with 25% of another color
33
+ # red.blend(blue, 0.25)
34
+ # @example Blend in a specific color space
35
+ # red.blend(blue, 0.5, target_color_space: :oklab)
6
36
  def blend(other, amount = 0.5, target_color_space: nil)
7
37
  amount = AbcDecimal(amount)
8
38
 
@@ -25,6 +55,14 @@ module Abachrome
25
55
  )
26
56
  end
27
57
 
58
+ # Blends this color with another color by the specified amount.
59
+ # This is a destructive version of the blend method, modifying the current
60
+ # color in place.
61
+ #
62
+ # @param other [Abachrome::Color] The color to blend with
63
+ # @param amount [Float] The blend amount, between 0.0 and 1.0, where 0.0 is
64
+ # this color and 1.0 is the other color (default: 0.5)
65
+ # @return [Abachrome::Color] Returns self after modification
28
66
  def blend!(other, amount = 0.5)
29
67
  blended = blend(other, amount)
30
68
  @color_space = blended.color_space
@@ -33,13 +71,26 @@ module Abachrome
33
71
  self
34
72
  end
35
73
 
74
+ # Alias for the blend method that mixes two colors together.
75
+ #
76
+ # @param other [Abachrome::Color] The color to mix with
77
+ # @param amount [Float] The amount to mix, between 0.0 and 1.0, where 0.0 returns the original color and 1.0 returns the other color (default: 0.5)
78
+ # @return [Abachrome::Color] A new color resulting from the mix of the two colors
36
79
  def mix(other, amount = 0.5)
37
80
  blend(other, amount)
38
81
  end
39
82
 
83
+ # Mix the current color with another color.
84
+ #
85
+ # This method is an alias for blend!. It combines the current color with
86
+ # the provided color at the specified amount.
87
+ #
88
+ # @param other [Abachrome::Color] The color to mix with the current color
89
+ # @param amount [Numeric] The amount of the other color to mix in, from 0 to 1 (default: 0.5)
90
+ # @return [self] Returns the modified color object
40
91
  def mix!(other, amount = 0.5)
41
92
  blend!(other, amount)
42
93
  end
43
94
  end
44
95
  end
45
- end
96
+ end
@@ -1,8 +1,32 @@
1
- # frozen_string_literal: true
1
+ # Abachrome::ColorMixins::Lighten - Color lightness adjustment functionality
2
+ #
3
+ # This mixin provides methods for adjusting the lightness of colors by manipulating
4
+ # the L (lightness) component in the OKLAB color space. The OKLAB color space is used
5
+ # because it provides perceptually uniform lightness adjustments that appear more
6
+ # natural to the human eye compared to adjustments in other color spaces.
7
+ #
8
+ # Key features:
9
+ # - Lighten and darken colors with configurable amounts
10
+ # - Both non-destructive (lighten/darken) and destructive (lighten!/darken!) variants
11
+ # - Automatic clamping to valid lightness ranges [0, 1]
12
+ # - High-precision decimal arithmetic for accurate color calculations
13
+ # - Conversion to OKLAB color space for perceptually uniform adjustments
14
+ #
15
+ # The mixin includes both immutable methods that return new color instances and mutable
16
+ # methods that modify the current color object in place, providing flexibility for
17
+ # different use cases and performance requirements.
2
18
 
3
19
  module Abachrome
4
20
  module ColorMixins
5
21
  module Lighten
22
+ # Increases the lightness of a color by the specified amount in the OKLab color space.
23
+ # This method works by extracting the L (lightness) component from the OKLab
24
+ # representation of the color and increasing it by the given amount, ensuring
25
+ # the result stays within the valid range of [0, 1].
26
+ #
27
+ # @param amount [Numeric] The amount to increase the lightness by, as a decimal
28
+ # value between 0 and 1. Defaults to 0.1 (10% increase).
29
+ # @return [Abachrome::Color] A new Color instance with increased lightness.
6
30
  def lighten(amount = 0.1)
7
31
  amount = AbcDecimal(amount)
8
32
  oklab = to_oklab
@@ -19,6 +43,13 @@ module Abachrome
19
43
  )
20
44
  end
21
45
 
46
+ # Increases the lightness of the color by the specified amount and modifies the current color object.
47
+ # This method changes the color in-place, mutating the current object. The color
48
+ # is converted to a lightness-based color space if needed to perform the operation.
49
+ #
50
+ # @param amount [Float] The amount to increase the lightness by, as a decimal value
51
+ # between 0 and 1. Default is 0.1 (10% increase).
52
+ # @return [Abachrome::Color] Returns self for method chaining.
22
53
  def lighten!(amount = 0.1)
23
54
  lightened = lighten(amount)
24
55
  @color_space = lightened.color_space
@@ -27,13 +58,29 @@ module Abachrome
27
58
  self
28
59
  end
29
60
 
61
+ # Darkens a color by decreasing its lightness value.
62
+ #
63
+ # This method is effectively a convenience wrapper around the {#lighten} method,
64
+ # passing a negative amount value to decrease the lightness instead of increasing it.
65
+ #
66
+ # @param amount [Float] The amount to darken the color by, between 0 and 1.
67
+ # Defaults to 0.1 (10% darker).
68
+ # @return [Color] A new color instance with decreased lightness.
69
+ # @see #lighten
30
70
  def darken(amount = 0.1)
31
71
  lighten(-amount)
32
72
  end
33
73
 
74
+ # Decreases the lightness value of the color by the specified amount.
75
+ # Modifies the color in place.
76
+ #
77
+ # @param amount [Float] The amount to darken the color by, as a value between 0 and 1.
78
+ # Defaults to 0.1 (10% darker).
79
+ # @return [Abachrome::Color] Returns self with the modified lightness value.
80
+ # @see #lighten!
34
81
  def darken!(amount = 0.1)
35
82
  lighten!(-amount)
36
83
  end
37
84
  end
38
85
  end
39
- end
86
+ end
@@ -1,14 +1,46 @@
1
- # frozen_string_literal: true
1
+ # Abachrome::ColorMixins::ToColorspace - Color space conversion functionality
2
+ #
3
+ # This mixin provides methods for converting colors between different color spaces within
4
+ # the Abachrome library. It includes both immutable and mutable conversion methods that
5
+ # allow colors to be transformed from their current color space to any registered target
6
+ # color space, such as sRGB, OKLAB, OKLCH, or linear RGB.
7
+ #
8
+ # Key features:
9
+ # - Convert colors to any registered color space with automatic converter lookup
10
+ # - Both non-destructive (to_color_space/convert_to/in_color_space) and destructive variants
11
+ # - Optimized to return the same object when no conversion is needed
12
+ # - Flexible API with multiple method names for different use cases and preferences
13
+ # - Integration with the Converter system for extensible color space transformations
14
+ #
15
+ # The mixin provides a consistent interface for color space conversions while maintaining
16
+ # the precision and accuracy required for color science calculations through the use of
17
+ # the underlying converter infrastructure.
2
18
 
3
19
  module Abachrome
4
20
  module ColorMixins
5
21
  module ToColorspace
22
+ # Converts the current color to the specified target color space.
23
+ #
24
+ # This method transforms the current color into an equivalent color in a different
25
+ # color space. If the target space is the same as the current color space, no
26
+ # conversion is performed and the current color is returned.
27
+ #
28
+ # @param target_space [Abachrome::ColorSpace] The target color space to convert to
29
+ # @return [Abachrome::Color] A new color object in the target color space, or self
30
+ # if the target space is the same as the current color space
6
31
  def to_color_space(target_space)
7
32
  return self if color_space == target_space
8
33
 
9
34
  Converter.convert(self, target_space.name)
10
35
  end
11
36
 
37
+ # Converts the color object to the specified target color space in-place.
38
+ # This method modifies the current object by changing its color space and
39
+ # coordinates to match the target color space.
40
+ #
41
+ # @param target_space [Abachrome::ColorSpace] The color space to convert to
42
+ # @return [Abachrome::Color] Returns self with modified color space and coordinates
43
+ # @see #to_color_space The non-destructive version that returns a new color object
12
44
  def to_color_space!(target_space)
13
45
  unless color_space == target_space
14
46
  converted = to_color_space(target_space)
@@ -18,21 +50,54 @@ module Abachrome
18
50
  self
19
51
  end
20
52
 
53
+ # Convert this color to a different color space.
54
+ #
55
+ # @param space_name [String, Symbol] The name of the target color space to convert to.
56
+ # @return [Abachrome::Color] A new Color object in the specified color space.
57
+ # @example
58
+ # # Convert a color from sRGB to OKLCH
59
+ # rgb_color.convert_to(:oklch)
60
+ # @see Abachrome::ColorSpace.find
61
+ # @see #to_color_space
21
62
  def convert_to(space_name)
22
63
  to_color_space(ColorSpace.find(space_name))
23
64
  end
24
65
 
66
+ # Converts this color to the specified color space in place.
67
+ #
68
+ # @param space_name [String, Symbol] The name or identifier of the target color space to convert to.
69
+ # @return [Abachrome::Color] Returns self with its values converted to the specified color space.
70
+ # @raise [Abachrome::ColorSpaceNotFoundError] If the specified color space is not registered.
71
+ # @see Abachrome::ColorSpace.find
72
+ # @example
73
+ # red = Abachrome::Color.new(1, 0, 0, color_space: :srgb)
74
+ # red.convert_to!(:oklch) # Converts the red color to OKLCH space in place
25
75
  def convert_to!(space_name)
26
76
  to_color_space!(ColorSpace.find(space_name))
27
77
  end
28
78
 
79
+ # Convert a color to a specified color space.
80
+ #
81
+ # @param space_name [Symbol, String] The name of the color space to convert to.
82
+ # @return [Abachrome::Color] A new Color instance in the specified color space.
83
+ # @example
84
+ # red_rgb = Abachrome::Color.new(:srgb, [1, 0, 0])
85
+ # red_lch = red_rgb.in_color_space(:oklch)
29
86
  def in_color_space(space_name)
30
87
  convert_to(space_name)
31
88
  end
32
89
 
90
+ # Converts the color to the specified color space and mutates the current object.
91
+ #
92
+ # @param space_name [Symbol, String] The target color space to convert to (e.g., :oklch, :rgb, :lab)
93
+ # @return [Abachrome::Color] Returns self after conversion
94
+ # @see #convert_to!
95
+ # @example
96
+ # color = Abachrome::Color.new([255, 0, 0], :rgb)
97
+ # color.in_color_space!(:oklch) # Color is now in OKLCH space
33
98
  def in_color_space!(space_name)
34
99
  convert_to!(space_name)
35
100
  end
36
101
  end
37
102
  end
38
- end
103
+ end
@@ -1,16 +1,49 @@
1
- # frozen_string_literal: true
1
+ # Abachrome::ColorMixins::ToLrgb - Linear RGB color space conversion functionality
2
+ #
3
+ # This mixin provides methods for converting colors to the linear RGB (LRGB) color space,
4
+ # which uses a linear relationship between stored numeric values and actual light intensity.
5
+ # Linear RGB is essential for accurate color calculations and serves as an intermediate
6
+ # color space for many color transformations, particularly when converting between
7
+ # different color models.
8
+ #
9
+ # Key features:
10
+ # - Convert colors to linear RGB with automatic converter lookup
11
+ # - Both non-destructive (to_lrgb) and destructive (to_lrgb!) conversion methods
12
+ # - Direct access to linear RGB components (lred, lgreen, lblue)
13
+ # - Utility methods for RGB array and hex string output
14
+ # - Optimized to return the same object when no conversion is needed
15
+ # - High-precision decimal arithmetic for accurate color science calculations
16
+ #
17
+ # The linear RGB color space differs from standard sRGB by removing gamma correction,
18
+ # making it suitable for mathematical operations like blending, lighting calculations,
19
+ # and color space transformations that require linear light behavior.
2
20
 
3
21
  require_relative "../converter"
4
22
 
5
23
  module Abachrome
6
24
  module ColorMixins
7
25
  module ToLrgb
26
+ # Converts this color to the Linear RGB (LRGB) color space.
27
+ # This method transforms the current color to the linear RGB color space,
28
+ # which uses a linear relationship between the stored numeric value and
29
+ # the actual light intensity. If the color is already in the LRGB space,
30
+ # it returns the current object without conversion.
31
+ #
32
+ # @return [Abachrome::Color] A new color object in the LRGB color space,
33
+ # or the original object if already in LRGB space
8
34
  def to_lrgb
9
35
  return self if color_space.name == :lrgb
10
36
 
11
37
  Converter.convert(self, :lrgb)
12
38
  end
13
39
 
40
+ # Converts the current color to the linear RGB (LRGB) color space and updates
41
+ # the receiver's state. If the color is already in LRGB space, this is a no-op.
42
+ #
43
+ # Unlike #to_lrgb which returns a new color instance, this method modifies the
44
+ # current object by changing its color space and coordinates to the LRGB equivalent.
45
+ #
46
+ # @return [Abachrome::Color] the receiver itself, now in LRGB color space
14
47
  def to_lrgb!
15
48
  unless color_space.name == :lrgb
16
49
  lrgb_color = to_lrgb
@@ -20,30 +53,65 @@ module Abachrome
20
53
  self
21
54
  end
22
55
 
56
+ # Returns the linear red component value of the color.
57
+ #
58
+ # This method accesses the first coordinate from the color in linear RGB space.
59
+ # Linear RGB values differ from standard RGB by using a non-gamma-corrected
60
+ # linear representation of luminance.
61
+ #
62
+ # @return [AbcDecimal] The linear red component value, typically in range [0, 1]
23
63
  def lred
24
64
  to_lrgb.coordinates[0]
25
65
  end
26
66
 
67
+ # Retrieves the linear green (lgreen) coordinate from a color by converting it to
68
+ # linear RGB color space first. Linear RGB uses a different scale than standard
69
+ # sRGB, with values representing linear light energy rather than gamma-corrected
70
+ # values.
71
+ #
72
+ # @return [AbcDecimal] The linear green component value from the color's linear
73
+ # RGB representation
27
74
  def lgreen
28
75
  to_lrgb.coordinates[1]
29
76
  end
30
77
 
78
+ # Returns the linear blue channel value of this color after conversion to linear RGB color space.
79
+ #
80
+ # This method converts the current color to the linear RGB color space and extracts the blue
81
+ # component (the third coordinate).
82
+ #
83
+ # @return [AbcDecimal] The linear blue component value, typically in the range [0, 1]
31
84
  def lblue
32
85
  to_lrgb.coordinates[2]
33
86
  end
34
87
 
88
+ # Returns the coordinates of the color in the linear RGB color space.
89
+ #
90
+ # @return [Array<AbcDecimal>] An array of three AbcDecimal values representing
91
+ # the red, green, and blue components in linear RGB color space.
35
92
  def lrgb_values
36
93
  to_lrgb.coordinates
37
94
  end
38
95
 
96
+ # Returns an array of sRGB values as integers in the 0-255 range.
97
+ # This method converts the color to RGB, scales the values to the 0-255 range,
98
+ # rounds to integers, and ensures they are clamped within the valid range.
99
+ #
100
+ # @return [Array<Integer>] An array of three RGB integer values between 0-255
39
101
  def rgb_array
40
102
  to_rgb.coordinates.map { |c| (c * 255).round.clamp(0, 255) }
41
103
  end
42
104
 
105
+ # Returns a hexadecimal string representation of the color in RGB format.
106
+ #
107
+ # @return [String] A hexadecimal color code in the format '#RRGGBB' where RR, GG, and BB
108
+ # are two-digit hexadecimal values for the red, green, and blue components respectively.
109
+ # @example
110
+ # color.rgb_hex # => "#1a2b3c"
43
111
  def rgb_hex
44
112
  r, g, b = rgb_array
45
113
  format("#%02x%02x%02x", r, g, b)
46
114
  end
47
115
  end
48
116
  end
49
- end
117
+ end
@@ -1,16 +1,49 @@
1
- # frozen_string_literal: true
1
+ # Abachrome::ColorMixins::ToOklab - OKLAB color space conversion functionality
2
+ #
3
+ # This mixin provides methods for converting colors to the OKLAB color space, which is a
4
+ # perceptually uniform color space designed for better color manipulation and comparison.
5
+ # OKLAB provides more intuitive lightness adjustments and color blending compared to
6
+ # traditional RGB color spaces, making it ideal for color science applications.
7
+ #
8
+ # Key features:
9
+ # - Convert colors to OKLAB with automatic converter lookup
10
+ # - Both non-destructive (to_oklab) and destructive (to_oklab!) conversion methods
11
+ # - Direct access to OKLAB components (lightness, a, b)
12
+ # - Utility methods for OKLAB array and value extraction
13
+ # - Optimized to return the same object when no conversion is needed
14
+ # - High-precision decimal arithmetic for accurate color science calculations
15
+ #
16
+ # The OKLAB color space uses three components: L (lightness), a (green-red axis), and
17
+ # b (blue-yellow axis), providing a more perceptually uniform representation of colors
18
+ # that better matches human visual perception compared to traditional color spaces.
2
19
 
3
20
  require_relative "../converter"
4
21
 
5
22
  module Abachrome
6
23
  module ColorMixins
7
24
  module ToOklab
25
+ # Converts the current color to the OKLAB color space.
26
+ #
27
+ # If the color is already in OKLAB, it returns the color unchanged.
28
+ # Otherwise, it uses the Converter to transform the color to OKLAB.
29
+ #
30
+ # @return [Abachrome::Color] A new Color object in the OKLAB color space
8
31
  def to_oklab
9
32
  return self if color_space.name == :oklab
10
33
 
11
34
  Converter.convert(self, :oklab)
12
35
  end
13
36
 
37
+ # Converts the color to the OKLAB color space in place.
38
+ # This method transforms the current color into OKLAB space,
39
+ # modifying the original object by updating its color space
40
+ # and coordinates if not already in OKLAB.
41
+ #
42
+ # @example
43
+ # color = Abachrome::Color.from_hex("#ff5500")
44
+ # color.to_oklab! # Color now uses OKLAB color space
45
+ #
46
+ # @return [Abachrome::Color] self, with updated color space and coordinates
14
47
  def to_oklab!
15
48
  unless color_space.name == :oklab
16
49
  oklab_color = to_oklab
@@ -20,29 +53,61 @@ module Abachrome
20
53
  self
21
54
  end
22
55
 
56
+ # Returns the lightness component (L) of the color in the OKLAB color space.
57
+ # The lightness value ranges from 0 (black) to 1 (white) and represents
58
+ # the perceived lightness of the color.
59
+ #
60
+ # @return [AbcDecimal] The lightness (L) value from the OKLAB color space
23
61
  def lightness
24
62
  to_oklab.coordinates[0]
25
63
  end
26
64
 
65
+ # Returns the L (Lightness) component from the OKLAB color space.
66
+ #
67
+ # The L value represents perceptual lightness in the OKLAB color space,
68
+ # typically ranging from 0 (black) to 1 (white).
69
+ #
70
+ # @return [AbcDecimal] The L (Lightness) component from the OKLAB color space
27
71
  def l
28
72
  to_oklab.coordinates[0]
29
73
  end
30
74
 
75
+ # Returns the 'a' component from the OKLAB color space (green-red axis).
76
+ #
77
+ # The 'a' component in OKLAB represents the position on the green-red axis,
78
+ # with negative values being more green and positive values being more red.
79
+ #
80
+ # @return [AbcDecimal] The 'a' component value from the OKLAB color space.
81
+ # @see #to_oklab For the full conversion to OKLAB color space
31
82
  def a
32
83
  to_oklab.coordinates[1]
33
84
  end
34
85
 
86
+ # Returns the B value of the color in OKLAB color space.
87
+ #
88
+ # This method first converts the color to OKLAB color space if needed,
89
+ # then extracts the B component (blue-yellow axis), which is the third
90
+ # coordinate in the OKLAB model.
91
+ #
92
+ # @return [AbcDecimal] The B component value in OKLAB color space
35
93
  def b
36
94
  to_oklab.coordinates[2]
37
95
  end
38
96
 
97
+ # Returns the OKLAB color space coordinates for this color.
98
+ #
99
+ # @return [Array] An array of OKLAB coordinates [L, a, b] representing the color in OKLAB color space
39
100
  def oklab_values
40
101
  to_oklab.coordinates
41
102
  end
42
103
 
104
+ # Returns an array representation of the color's coordinates in the OKLAB color space.
105
+ #
106
+ # @return [Array<AbcDecimal>] An array containing the coordinates of the color
107
+ # in the OKLAB color space in the order [L, a, b]
43
108
  def oklab_array
44
109
  to_oklab.coordinates
45
110
  end
46
111
  end
47
112
  end
48
- end
113
+ end