asciinema_win 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.
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rich
4
+ # Represents an RGB color triplet with values from 0-255.
5
+ # This is an immutable value object used for true color representation.
6
+ class ColorTriplet
7
+ # @return [Integer] Red component (0-255)
8
+ attr_reader :red
9
+
10
+ # @return [Integer] Green component (0-255)
11
+ attr_reader :green
12
+
13
+ # @return [Integer] Blue component (0-255)
14
+ attr_reader :blue
15
+
16
+ # Create a new color triplet
17
+ # @param red [Integer] Red component (0-255)
18
+ # @param green [Integer] Green component (0-255)
19
+ # @param blue [Integer] Blue component (0-255)
20
+ def initialize(red, green, blue)
21
+ @red = clamp_component(red)
22
+ @green = clamp_component(green)
23
+ @blue = clamp_component(blue)
24
+ freeze
25
+ end
26
+
27
+ # @return [String] Hexadecimal representation (e.g., "#ff0000")
28
+ def hex
29
+ format("%02x%02x%02x", @red, @green, @blue)
30
+ end
31
+
32
+ # @return [String] RGB string representation (e.g., "rgb(255, 0, 0)")
33
+ def rgb
34
+ "rgb(#{@red}, #{@green}, #{@blue})"
35
+ end
36
+
37
+ # @return [Array<Float>] Normalized components (0.0-1.0)
38
+ def normalized
39
+ [@red / 255.0, @green / 255.0, @blue / 255.0]
40
+ end
41
+
42
+ # @return [Array<Integer>] Components as array [red, green, blue]
43
+ def to_a
44
+ [@red, @green, @blue]
45
+ end
46
+
47
+ # @return [Hash] Components as hash
48
+ def to_h
49
+ { red: @red, green: @green, blue: @blue }
50
+ end
51
+
52
+ # Check equality with another triplet
53
+ # @param other [ColorTriplet, Object] Object to compare
54
+ # @return [Boolean]
55
+ def ==(other)
56
+ return false unless other.is_a?(ColorTriplet)
57
+
58
+ @red == other.red && @green == other.green && @blue == other.blue
59
+ end
60
+
61
+ alias eql? ==
62
+
63
+ # @return [Integer] Hash code for use in hash tables
64
+ def hash
65
+ [@red, @green, @blue].hash
66
+ end
67
+
68
+ # @return [String] String representation
69
+ def to_s
70
+ hex
71
+ end
72
+
73
+ # @return [String] Inspect representation
74
+ def inspect
75
+ "#<Rich::ColorTriplet #{hex} (#{@red}, #{@green}, #{@blue})>"
76
+ end
77
+
78
+ # Deconstruct for pattern matching
79
+ # @return [Array<Integer>]
80
+ def deconstruct
81
+ to_a
82
+ end
83
+
84
+ # Deconstruct for pattern matching with keys
85
+ # @param keys [Array<Symbol>]
86
+ # @return [Hash]
87
+ def deconstruct_keys(keys)
88
+ to_h.slice(*(keys || [:red, :green, :blue]))
89
+ end
90
+
91
+ # Calculate the perceived luminance of the color
92
+ # Uses the formula for relative luminance from WCAG 2.0
93
+ # @return [Float] Luminance value (0.0-1.0)
94
+ def luminance
95
+ r, g, b = normalized.map do |c|
96
+ c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055)**2.4
97
+ end
98
+ 0.2126 * r + 0.7152 * g + 0.0722 * b
99
+ end
100
+
101
+ # Check if this is a "dark" color based on luminance
102
+ # @return [Boolean]
103
+ def dark?
104
+ luminance < 0.5
105
+ end
106
+
107
+ # Check if this is a "light" color based on luminance
108
+ # @return [Boolean]
109
+ def light?
110
+ !dark?
111
+ end
112
+
113
+ # Blend this color with another
114
+ # @param other [ColorTriplet] Color to blend with
115
+ # @param factor [Float] Blend factor (0.0 = this color, 1.0 = other color)
116
+ # @return [ColorTriplet] Blended color
117
+ def blend(other, factor = 0.5)
118
+ factor = [[factor, 0.0].max, 1.0].min
119
+
120
+ new_r = (@red + (other.red - @red) * factor).round
121
+ new_g = (@green + (other.green - @green) * factor).round
122
+ new_b = (@blue + (other.blue - @blue) * factor).round
123
+
124
+ ColorTriplet.new(new_r, new_g, new_b)
125
+ end
126
+
127
+ # Calculate color distance (Euclidean in RGB space)
128
+ # @param other [ColorTriplet] Color to compare
129
+ # @return [Float] Distance value
130
+ def distance(other)
131
+ dr = @red - other.red
132
+ dg = @green - other.green
133
+ db = @blue - other.blue
134
+ Math.sqrt(dr * dr + dg * dg + db * db)
135
+ end
136
+
137
+ # Calculate weighted color distance (better perceptual accuracy)
138
+ # Uses weighted Euclidean distance based on human color perception
139
+ # @param other [ColorTriplet] Color to compare
140
+ # @return [Float] Weighted distance value
141
+ def weighted_distance(other)
142
+ dr = @red - other.red
143
+ dg = @green - other.green
144
+ db = @blue - other.blue
145
+
146
+ # Weighted by perceptual importance (red-green component is most important)
147
+ r_mean = (@red + other.red) / 2.0
148
+ weight_r = 2.0 + r_mean / 256.0
149
+ weight_g = 4.0
150
+ weight_b = 2.0 + (255.0 - r_mean) / 256.0
151
+
152
+ Math.sqrt(weight_r * dr * dr + weight_g * dg * dg + weight_b * db * db)
153
+ end
154
+
155
+ class << self
156
+ # Create from hex string
157
+ # @param hex_str [String] Hex color string (e.g., "#ff0000" or "ff0000")
158
+ # @return [ColorTriplet]
159
+ def from_hex(hex_str)
160
+ hex_str = hex_str.delete_prefix("#")
161
+ raise ArgumentError, "Invalid hex color: #{hex_str}" unless hex_str.match?(/\A[0-9a-fA-F]{6}\z/)
162
+
163
+ r = hex_str[0, 2].to_i(16)
164
+ g = hex_str[2, 2].to_i(16)
165
+ b = hex_str[4, 2].to_i(16)
166
+
167
+ new(r, g, b)
168
+ end
169
+
170
+ # Create from normalized values (0.0-1.0)
171
+ # @param r [Float] Red component (0.0-1.0)
172
+ # @param g [Float] Green component (0.0-1.0)
173
+ # @param b [Float] Blue component (0.0-1.0)
174
+ # @return [ColorTriplet]
175
+ def from_normalized(r, g, b)
176
+ new(
177
+ (r * 255).round,
178
+ (g * 255).round,
179
+ (b * 255).round
180
+ )
181
+ end
182
+
183
+ # Create from HSL values
184
+ # @param h [Float] Hue (0-360)
185
+ # @param s [Float] Saturation (0-100)
186
+ # @param l [Float] Lightness (0-100)
187
+ # @return [ColorTriplet]
188
+ def from_hsl(h, s, l)
189
+ h = h % 360
190
+ s = s / 100.0
191
+ l = l / 100.0
192
+
193
+ c = (1 - (2 * l - 1).abs) * s
194
+ x = c * (1 - ((h / 60.0) % 2 - 1).abs)
195
+ m = l - c / 2.0
196
+
197
+ r, g, b = case (h / 60).floor
198
+ when 0 then [c, x, 0]
199
+ when 1 then [x, c, 0]
200
+ when 2 then [0, c, x]
201
+ when 3 then [0, x, c]
202
+ when 4 then [x, 0, c]
203
+ else [c, 0, x]
204
+ end
205
+
206
+ new(
207
+ ((r + m) * 255).round,
208
+ ((g + m) * 255).round,
209
+ ((b + m) * 255).round
210
+ )
211
+ end
212
+ end
213
+
214
+ private
215
+
216
+ def clamp_component(value)
217
+ [[value.to_i, 0].max, 255].min
218
+ end
219
+ end
220
+ end