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,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.
|