unmagic-color 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +274 -0
- data/data/rgb.txt +164 -0
- data/lib/unmagic/color/hsl.rb +421 -0
- data/lib/unmagic/color/oklch.rb +433 -0
- data/lib/unmagic/color/rgb/hex.rb +104 -0
- data/lib/unmagic/color/rgb/named.rb +112 -0
- data/lib/unmagic/color/rgb.rb +390 -0
- data/lib/unmagic/color/string/hash_function.rb +316 -0
- data/lib/unmagic/color/util/percentage.rb +143 -0
- data/lib/unmagic/color.rb +402 -0
- data/lib/unmagic_color.rb +3 -0
- metadata +55 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unmagic
|
|
4
|
+
# Utility classes for color manipulation
|
|
5
|
+
module Util
|
|
6
|
+
# Represents a percentage value with validation and formatting capabilities.
|
|
7
|
+
# Handles both direct percentage values and ratio calculations.
|
|
8
|
+
#
|
|
9
|
+
# @example Direct percentage value
|
|
10
|
+
# percentage = Percentage.new(75.5)
|
|
11
|
+
# percentage.to_s
|
|
12
|
+
# #=> "75.5%"
|
|
13
|
+
# percentage.value
|
|
14
|
+
# #=> 75.5
|
|
15
|
+
#
|
|
16
|
+
# @example Calculated from ratio
|
|
17
|
+
# percentage = Percentage.new(50, 100)
|
|
18
|
+
# percentage.to_s
|
|
19
|
+
# #=> "50.0%"
|
|
20
|
+
#
|
|
21
|
+
# @example Progress tracking
|
|
22
|
+
# percentage = Percentage.new(current_item, total_items)
|
|
23
|
+
# percentage.to_s
|
|
24
|
+
# #=> "25.0%"
|
|
25
|
+
class Percentage
|
|
26
|
+
include Comparable
|
|
27
|
+
|
|
28
|
+
attr_reader :value
|
|
29
|
+
|
|
30
|
+
# Create a new percentage
|
|
31
|
+
#
|
|
32
|
+
# @param args [Array<Numeric>] Either a single percentage value (0-100) or numerator and denominator
|
|
33
|
+
def initialize(*args)
|
|
34
|
+
case args.length
|
|
35
|
+
when 1
|
|
36
|
+
@value = args[0].to_f
|
|
37
|
+
when 2
|
|
38
|
+
numerator, denominator = args
|
|
39
|
+
@value = if denominator.to_f.zero?
|
|
40
|
+
0.0
|
|
41
|
+
else
|
|
42
|
+
(numerator.to_f / denominator.to_f * 100.0)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 1..2)"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Clamp to valid percentage range
|
|
49
|
+
@value = @value.clamp(0.0, 100.0)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Format as percentage string with configurable decimal places
|
|
53
|
+
#
|
|
54
|
+
# @param decimal_places [Integer] Number of decimal places to display
|
|
55
|
+
# @return [String] Formatted percentage string
|
|
56
|
+
def to_s(decimal_places: 1)
|
|
57
|
+
"#{@value.round(decimal_places)}%"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get the raw percentage value (0.0 to 100.0)
|
|
61
|
+
def to_f
|
|
62
|
+
@value
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get the percentage as a ratio (0.0 to 1.0)
|
|
66
|
+
def to_ratio
|
|
67
|
+
@value / 100.0
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Comparison operator for Comparable
|
|
71
|
+
#
|
|
72
|
+
# @param other [Percentage, Numeric] Value to compare with
|
|
73
|
+
# @return [Integer, nil] -1, 0, 1, or nil
|
|
74
|
+
def <=>(other)
|
|
75
|
+
case other
|
|
76
|
+
when Percentage
|
|
77
|
+
@value <=> other.value
|
|
78
|
+
when Numeric
|
|
79
|
+
@value <=> other.to_f
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Equality comparison
|
|
84
|
+
#
|
|
85
|
+
# @param other [Object] Value to compare with
|
|
86
|
+
# @return [Boolean] true if values are equal
|
|
87
|
+
def ==(other)
|
|
88
|
+
case other
|
|
89
|
+
when Percentage
|
|
90
|
+
@value == other.value
|
|
91
|
+
when Numeric
|
|
92
|
+
@value == other.to_f
|
|
93
|
+
else
|
|
94
|
+
false
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Add percentages (clamped to 100%)
|
|
99
|
+
#
|
|
100
|
+
# @param other [Percentage, Numeric] Value to add
|
|
101
|
+
# @return [Percentage] New percentage with sum of values
|
|
102
|
+
def +(other)
|
|
103
|
+
case other
|
|
104
|
+
when Percentage
|
|
105
|
+
Percentage.new([value + other.value, 100.0].min)
|
|
106
|
+
when Numeric
|
|
107
|
+
Percentage.new([value + other.to_f, 100.0].min)
|
|
108
|
+
else
|
|
109
|
+
raise TypeError, "can't add #{other.class} to Percentage"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Subtract percentages (clamped to 0%)
|
|
114
|
+
#
|
|
115
|
+
# @param other [Percentage, Numeric] Value to subtract
|
|
116
|
+
# @return [Percentage] New percentage with difference of values
|
|
117
|
+
def -(other)
|
|
118
|
+
case other
|
|
119
|
+
when Percentage
|
|
120
|
+
Percentage.new([value - other.value, 0.0].max)
|
|
121
|
+
when Numeric
|
|
122
|
+
Percentage.new([value - other.to_f, 0.0].max)
|
|
123
|
+
else
|
|
124
|
+
raise TypeError, "can't subtract #{other.class} from Percentage"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Absolute value
|
|
129
|
+
#
|
|
130
|
+
# @return [Percentage] New percentage with absolute value
|
|
131
|
+
def abs
|
|
132
|
+
self.class.new(@value.abs)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check if percentage is zero
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean] true if percentage is 0.0
|
|
138
|
+
def zero?
|
|
139
|
+
@value.zero?
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# @private
|
|
4
|
+
module Unmagic
|
|
5
|
+
# Base class for working with colors in different color spaces.
|
|
6
|
+
#
|
|
7
|
+
# ## Understanding Colors
|
|
8
|
+
#
|
|
9
|
+
# A color is simply a way to describe what we see. Just like you can describe
|
|
10
|
+
# a location using different coordinate systems (street address, latitude/longitude, etc.),
|
|
11
|
+
# you can describe colors using different "color spaces."
|
|
12
|
+
#
|
|
13
|
+
# This library supports three main color spaces:
|
|
14
|
+
#
|
|
15
|
+
# - {RGB}: Describes colors as a mix of Red, Green, and Blue light (like your screen)
|
|
16
|
+
# - {HSL}: Describes colors using Hue (color wheel position), Saturation (intensity),
|
|
17
|
+
# and Lightness (brightness)
|
|
18
|
+
# - {OKLCH}: A modern color space that matches how humans perceive color differences
|
|
19
|
+
#
|
|
20
|
+
# ## Basic Usage
|
|
21
|
+
#
|
|
22
|
+
# Parse a color from a string:
|
|
23
|
+
#
|
|
24
|
+
# color = Unmagic::Color.parse("#FF5733")
|
|
25
|
+
# color = Unmagic::Color["rgb(255, 87, 51)"]
|
|
26
|
+
# color = Unmagic::Color.parse("hsl(9, 100%, 60%)")
|
|
27
|
+
#
|
|
28
|
+
# Convert between color spaces:
|
|
29
|
+
#
|
|
30
|
+
# rgb = Unmagic::Color.parse("#FF5733")
|
|
31
|
+
# hsl = rgb.to_hsl
|
|
32
|
+
# oklch = rgb.to_oklch
|
|
33
|
+
#
|
|
34
|
+
# Manipulate colors:
|
|
35
|
+
#
|
|
36
|
+
# lighter = color.lighten(0.2)
|
|
37
|
+
# darker = color.darken(0.1)
|
|
38
|
+
# mixed = color.blend(other_color, 0.5)
|
|
39
|
+
class Color
|
|
40
|
+
# @private
|
|
41
|
+
class Error < StandardError; end
|
|
42
|
+
# @private
|
|
43
|
+
class ParseError < Error; end
|
|
44
|
+
|
|
45
|
+
require_relative "color/rgb"
|
|
46
|
+
require_relative "color/rgb/hex"
|
|
47
|
+
require_relative "color/rgb/named"
|
|
48
|
+
require_relative "color/hsl"
|
|
49
|
+
require_relative "color/oklch"
|
|
50
|
+
require_relative "color/string/hash_function"
|
|
51
|
+
require_relative "color/util/percentage"
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
# Parse a color string into the appropriate color space object.
|
|
55
|
+
#
|
|
56
|
+
# This method automatically detects the format and returns the correct color type.
|
|
57
|
+
# Supported formats include hex colors, RGB, HSL, OKLCH, and named colors.
|
|
58
|
+
#
|
|
59
|
+
# @param input [String, Color] The color string to parse, or an existing Color object
|
|
60
|
+
# @return [RGB, HSL, OKLCH] A color object in the appropriate color space
|
|
61
|
+
# @raise [ParseError] If the input is nil, empty, or in an unrecognized format
|
|
62
|
+
#
|
|
63
|
+
# @example Parse a hex color
|
|
64
|
+
# Unmagic::Color.parse("#FF5733")
|
|
65
|
+
#
|
|
66
|
+
# @example Parse an RGB color
|
|
67
|
+
# Unmagic::Color.parse("rgb(255, 87, 51)")
|
|
68
|
+
#
|
|
69
|
+
# @example Parse an HSL color
|
|
70
|
+
# Unmagic::Color.parse("hsl(9, 100%, 60%)")
|
|
71
|
+
#
|
|
72
|
+
# @example Parse an OKLCH color
|
|
73
|
+
# Unmagic::Color.parse("oklch(0.65 0.15 30)")
|
|
74
|
+
#
|
|
75
|
+
# @example Parse a named color
|
|
76
|
+
# Unmagic::Color.parse("goldenrod")
|
|
77
|
+
#
|
|
78
|
+
# @example Pass through an existing color
|
|
79
|
+
# color = Unmagic::Color.parse("#FF5733")
|
|
80
|
+
# Unmagic::Color.parse(color)
|
|
81
|
+
def parse(input)
|
|
82
|
+
return input if input.is_a?(self)
|
|
83
|
+
raise ParseError, "Can't pass nil as a color" if input.nil?
|
|
84
|
+
|
|
85
|
+
input = input.strip
|
|
86
|
+
raise ParseError, "Can't parse empty string" if input == ""
|
|
87
|
+
|
|
88
|
+
# Try hex or RGB format
|
|
89
|
+
if input.start_with?("#") || input.match?(/\A[0-9A-Fa-f]{3,6}\z/) || input.start_with?("rgb")
|
|
90
|
+
RGB.parse(input)
|
|
91
|
+
elsif input.start_with?("hsl")
|
|
92
|
+
HSL.parse(input)
|
|
93
|
+
elsif input.start_with?("oklch")
|
|
94
|
+
OKLCH.parse(input)
|
|
95
|
+
elsif RGB::Named.valid?(input)
|
|
96
|
+
RGB::Named.parse(input)
|
|
97
|
+
else
|
|
98
|
+
raise ParseError, "Unknown color #{input.inspect}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Parse a color string using bracket notation.
|
|
103
|
+
#
|
|
104
|
+
# This is a convenient alias for {parse}.
|
|
105
|
+
#
|
|
106
|
+
# @param value [String, Color] The color string to parse
|
|
107
|
+
# @return [RGB, HSL, OKLCH] A color object in the appropriate color space
|
|
108
|
+
# @raise [ParseError] If the input is invalid
|
|
109
|
+
#
|
|
110
|
+
# @example
|
|
111
|
+
# Unmagic::Color["#FF5733"]
|
|
112
|
+
# Unmagic::Color["hsl(9, 100%, 60%)"]
|
|
113
|
+
def [](value)
|
|
114
|
+
parse(value)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Base unit for RGB components (0-255)
|
|
119
|
+
Component = Data.define(:value) do
|
|
120
|
+
include Comparable
|
|
121
|
+
|
|
122
|
+
# @param value [Numeric] Component value (0-255)
|
|
123
|
+
def initialize(value:)
|
|
124
|
+
super(value: value.to_i.clamp(0, 255))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def to_i = value
|
|
128
|
+
def to_f = value.to_f
|
|
129
|
+
|
|
130
|
+
# @param other [Component, Numeric] Value to compare
|
|
131
|
+
# @return [Integer, nil] Comparison result
|
|
132
|
+
def <=>(other)
|
|
133
|
+
case other
|
|
134
|
+
when Component, Numeric
|
|
135
|
+
value <=> other.to_f
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @param other [Numeric] Multiplier
|
|
140
|
+
# @return [Component] New component
|
|
141
|
+
def *(other)
|
|
142
|
+
self.class.new(value: value * other.to_f)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @param other [Numeric] Divisor
|
|
146
|
+
# @return [Component] New component
|
|
147
|
+
def /(other)
|
|
148
|
+
self.class.new(value: value / other.to_f)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @param other [Numeric] Value to add
|
|
152
|
+
# @return [Component] New component
|
|
153
|
+
def +(other)
|
|
154
|
+
self.class.new(value: value + other.to_f)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @param other [Numeric] Value to subtract
|
|
158
|
+
# @return [Component] New component
|
|
159
|
+
def -(other)
|
|
160
|
+
self.class.new(value: value - other.to_f)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @return [Component] Absolute value
|
|
164
|
+
def abs
|
|
165
|
+
self.class.new(value: value.abs)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Type alias for red component
|
|
170
|
+
Red = Component
|
|
171
|
+
# Type alias for green component
|
|
172
|
+
Green = Component
|
|
173
|
+
# Type alias for blue component
|
|
174
|
+
Blue = Component
|
|
175
|
+
|
|
176
|
+
# Angular unit for hue (0-360 degrees, wrapping)
|
|
177
|
+
Hue = Data.define(:value) do
|
|
178
|
+
include Comparable
|
|
179
|
+
|
|
180
|
+
# @param value [Numeric] Hue value in degrees
|
|
181
|
+
def initialize(value:)
|
|
182
|
+
super(value: value.to_f % 360)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def to_f = value
|
|
186
|
+
def degrees = value
|
|
187
|
+
|
|
188
|
+
# @param other [Hue, Numeric] Value to compare
|
|
189
|
+
# @return [Integer, nil] Comparison result
|
|
190
|
+
def <=>(other)
|
|
191
|
+
case other
|
|
192
|
+
when Hue, Numeric
|
|
193
|
+
value <=> other.to_f
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @param other [Numeric] Multiplier
|
|
198
|
+
# @return [Hue] New hue
|
|
199
|
+
def *(other)
|
|
200
|
+
self.class.new(value: value * other.to_f)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# @param other [Numeric] Divisor
|
|
204
|
+
# @return [Hue] New hue
|
|
205
|
+
def /(other)
|
|
206
|
+
self.class.new(value: value / other.to_f)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @param other [Numeric] Value to add
|
|
210
|
+
# @return [Hue] New hue
|
|
211
|
+
def +(other)
|
|
212
|
+
self.class.new(value: value + other.to_f)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @param other [Numeric] Value to subtract
|
|
216
|
+
# @return [Hue] New hue
|
|
217
|
+
def -(other)
|
|
218
|
+
self.class.new(value: value - other.to_f)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @return [Hue] Absolute value
|
|
222
|
+
def abs
|
|
223
|
+
self.class.new(value: value.abs)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# OKLCH chroma unit (0-0.5)
|
|
228
|
+
Chroma = Data.define(:value) do
|
|
229
|
+
include Comparable
|
|
230
|
+
|
|
231
|
+
# @param value [Numeric] Chroma value (0-0.5)
|
|
232
|
+
def initialize(value:)
|
|
233
|
+
super(value: value.to_f.clamp(0, 0.5))
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# @return [Float] Chroma value
|
|
237
|
+
def to_f = value
|
|
238
|
+
|
|
239
|
+
# @param other [Chroma, Numeric] Value to compare
|
|
240
|
+
# @return [Integer, nil] Comparison result
|
|
241
|
+
def <=>(other)
|
|
242
|
+
case other
|
|
243
|
+
when Chroma, Numeric
|
|
244
|
+
value <=> other.to_f
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# @param other [Numeric] Multiplier
|
|
249
|
+
# @return [Chroma] New chroma
|
|
250
|
+
def *(other)
|
|
251
|
+
self.class.new(value: value * other.to_f)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# @param other [Numeric] Divisor
|
|
255
|
+
# @return [Chroma] New chroma
|
|
256
|
+
def /(other)
|
|
257
|
+
self.class.new(value: value / other.to_f)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# @param other [Numeric] Value to add
|
|
261
|
+
# @return [Chroma] New chroma
|
|
262
|
+
def +(other)
|
|
263
|
+
self.class.new(value: value + other.to_f)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# @param other [Numeric] Value to subtract
|
|
267
|
+
# @return [Chroma] New chroma
|
|
268
|
+
def -(other)
|
|
269
|
+
self.class.new(value: value - other.to_f)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# @return [Chroma] Absolute value
|
|
273
|
+
def abs
|
|
274
|
+
self.class.new(value: value.abs)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Percentage-based units
|
|
279
|
+
# Saturation percentage (0-100%)
|
|
280
|
+
class Saturation < Unmagic::Util::Percentage; end
|
|
281
|
+
# Lightness percentage (0-100%)
|
|
282
|
+
class Lightness < Unmagic::Util::Percentage; end
|
|
283
|
+
|
|
284
|
+
# Convert this color to RGB color space.
|
|
285
|
+
#
|
|
286
|
+
# RGB represents colors as a combination of Red, Green, and Blue light,
|
|
287
|
+
# with each component ranging from 0-255.
|
|
288
|
+
#
|
|
289
|
+
# @return [RGB] The color in RGB color space
|
|
290
|
+
def to_rgb
|
|
291
|
+
raise NotImplementedError
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Convert this color to HSL color space.
|
|
295
|
+
#
|
|
296
|
+
# HSL represents colors using Hue (0-360°), Saturation (0-100%),
|
|
297
|
+
# and Lightness (0-100%).
|
|
298
|
+
#
|
|
299
|
+
# @return [HSL] The color in HSL color space
|
|
300
|
+
def to_hsl
|
|
301
|
+
raise NotImplementedError
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Convert this color to OKLCH color space.
|
|
305
|
+
#
|
|
306
|
+
# OKLCH is a perceptually uniform color space that better matches
|
|
307
|
+
# how humans perceive color differences.
|
|
308
|
+
#
|
|
309
|
+
# @return [OKLCH] The color in OKLCH color space
|
|
310
|
+
def to_oklch
|
|
311
|
+
raise NotImplementedError
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Calculate the perceptual luminance of this color.
|
|
315
|
+
#
|
|
316
|
+
# Luminance represents how bright the color appears to the human eye,
|
|
317
|
+
# accounting for the fact that we perceive green as brighter than red,
|
|
318
|
+
# and red as brighter than blue.
|
|
319
|
+
#
|
|
320
|
+
# @return [Float] The luminance value from 0.0 (black) to 1.0 (white)
|
|
321
|
+
def luminance
|
|
322
|
+
raise NotImplementedError
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Blend this color with another color.
|
|
326
|
+
#
|
|
327
|
+
# Creates a new color by mixing this color with another. The amount
|
|
328
|
+
# parameter controls how much of the other color to mix in.
|
|
329
|
+
#
|
|
330
|
+
# @param other [Color] The color to blend with
|
|
331
|
+
# @param amount [Float] How much of the other color to use (0.0 to 1.0)
|
|
332
|
+
# @return [Color] A new color that is a blend of the two colors
|
|
333
|
+
#
|
|
334
|
+
# @example Mix two colors equally
|
|
335
|
+
# red = Unmagic::Color.parse("#FF0000")
|
|
336
|
+
# blue = Unmagic::Color.parse("#0000FF")
|
|
337
|
+
# red.blend(blue, 0.5)
|
|
338
|
+
#
|
|
339
|
+
# @example Add a hint of another color
|
|
340
|
+
# base = Unmagic::Color.parse("#336699")
|
|
341
|
+
# base.blend(Unmagic::Color.parse("#FF0000"), 0.1)
|
|
342
|
+
def blend(other, amount = 0.5)
|
|
343
|
+
raise NotImplementedError
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Create a lighter version of this color.
|
|
347
|
+
#
|
|
348
|
+
# Returns a new color that is lighter than the original. The exact
|
|
349
|
+
# implementation depends on the color space.
|
|
350
|
+
#
|
|
351
|
+
# @param amount [Float] How much to lighten (0.0 to 1.0)
|
|
352
|
+
# @return [Color] A new, lighter color
|
|
353
|
+
#
|
|
354
|
+
# @example Make a color 20% lighter
|
|
355
|
+
# dark = Unmagic::Color.parse("#336699")
|
|
356
|
+
# dark.lighten(0.2)
|
|
357
|
+
def lighten(amount = 0.1)
|
|
358
|
+
raise NotImplementedError
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Create a darker version of this color.
|
|
362
|
+
#
|
|
363
|
+
# Returns a new color that is darker than the original. The exact
|
|
364
|
+
# implementation depends on the color space.
|
|
365
|
+
#
|
|
366
|
+
# @param amount [Float] How much to darken (0.0 to 1.0)
|
|
367
|
+
# @return [Color] A new, darker color
|
|
368
|
+
#
|
|
369
|
+
# @example Make a color 10% darker
|
|
370
|
+
# bright = Unmagic::Color.parse("#FF9966")
|
|
371
|
+
# bright.darken(0.1)
|
|
372
|
+
def darken(amount = 0.1)
|
|
373
|
+
raise NotImplementedError
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Check if this is a light color.
|
|
377
|
+
#
|
|
378
|
+
# A color is considered light if its luminance is greater than 0.5.
|
|
379
|
+
# This is useful for determining whether to use dark or light text
|
|
380
|
+
# on a colored background.
|
|
381
|
+
#
|
|
382
|
+
# @return [Boolean] true if the color is light, false otherwise
|
|
383
|
+
#
|
|
384
|
+
# @example Choose text color based on background
|
|
385
|
+
# bg = Unmagic::Color.parse("#FFFF00") # Yellow
|
|
386
|
+
# text_color = bg.light? ? "#000000" : "#FFFFFF"
|
|
387
|
+
# # => "#000000"
|
|
388
|
+
def light?
|
|
389
|
+
luminance > 0.5
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Check if this is a dark color.
|
|
393
|
+
#
|
|
394
|
+
# A color is considered dark if its luminance is 0.5 or less.
|
|
395
|
+
# This is the opposite of {#light?}.
|
|
396
|
+
#
|
|
397
|
+
# @return [Boolean] true if the color is dark, false otherwise
|
|
398
|
+
def dark?
|
|
399
|
+
!light?
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: unmagic-color
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Keith Pitt
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Parse, convert, and manipulate colors with support for RGB, Hex, HSL
|
|
13
|
+
formats, contrast calculations, and color blending
|
|
14
|
+
email:
|
|
15
|
+
- keith@unreasonable-magic.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- README.md
|
|
21
|
+
- data/rgb.txt
|
|
22
|
+
- lib/unmagic/color.rb
|
|
23
|
+
- lib/unmagic/color/hsl.rb
|
|
24
|
+
- lib/unmagic/color/oklch.rb
|
|
25
|
+
- lib/unmagic/color/rgb.rb
|
|
26
|
+
- lib/unmagic/color/rgb/hex.rb
|
|
27
|
+
- lib/unmagic/color/rgb/named.rb
|
|
28
|
+
- lib/unmagic/color/string/hash_function.rb
|
|
29
|
+
- lib/unmagic/color/util/percentage.rb
|
|
30
|
+
- lib/unmagic_color.rb
|
|
31
|
+
homepage: https://github.com/unreasonable-magic/unmagic-color
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata:
|
|
35
|
+
homepage_uri: https://github.com/unreasonable-magic/unmagic-color
|
|
36
|
+
source_code_uri: https://github.com/unreasonable-magic/unmagic-color
|
|
37
|
+
changelog_uri: https://github.com/unreasonable-magic/unmagic-color/CHANGELOG.md
|
|
38
|
+
rdoc_options: []
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '3.0'
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
51
|
+
requirements: []
|
|
52
|
+
rubygems_version: 3.6.9
|
|
53
|
+
specification_version: 4
|
|
54
|
+
summary: Comprehensive color manipulation library
|
|
55
|
+
test_files: []
|