Class: Ruby2D::Color
- Inherits:
-
Object
- Object
- Ruby2D::Color
- Defined in:
- lib/ruby2d/color.rb
Overview
A color from a keyword, hex value, or RGBA array
Defined Under Namespace
Classes: Set
Constant Summary collapse
- PARSE_CACHE_MAX =
Bounded cache of parsed color strings, keyed by the user input.
'random'is never cached (re-randomized on each call). 256- RENDER_CACHE_MAX =
Bounded cache of shared Color instances for immediate-mode
.rendercalls, keyed by the color string. SeeColor.for_render. 256- NAMED_COLORS =
Based on clrs.cc
{ 'navy' => '#001F3F', 'blue' => '#0074D9', 'aqua' => '#7FDBFF', 'teal' => '#39CCCC', 'olive' => '#3D9970', 'green' => '#2ECC40', 'lime' => '#01FF70', 'yellow' => '#FFDC00', 'orange' => '#FF851B', 'red' => '#FF4136', 'brown' => '#663300', 'fuchsia' => '#F012BE', 'purple' => '#B10DC9', 'maroon' => '#85144B', 'white' => '#FFFFFF', 'silver' => '#DDDDDD', 'gray' => '#AAAAAA', 'black' => '#111111', 'random' => '' }.freeze
Instance Attribute Summary collapse
-
#a ⇒ Object
Returns the value of attribute a.
-
#b ⇒ Object
Returns the value of attribute b.
-
#g ⇒ Object
Returns the value of attribute g.
-
#r ⇒ Object
Returns the value of attribute r.
Class Method Summary collapse
-
.for_render(colors) ⇒ Object
Resolve a color for an immediate-mode class-level
.rendercall. -
.hex?(color_string) ⇒ Boolean
Check if the string is a valid hex color value Byte comparisons, not slicing:
valid?calls this on everyColor.newand on every per-vertex color of every immediate-mode draw, and the readable form ([0],[1..],.chars, plus a fresh literal for the allowed set) allocated about ten short-lived strings each time. -
.parse_string(color) ⇒ Object
Parse a color string into a frozen
[r, g, b, a]tuple, caching the result. -
.rgba_array?(colors) ⇒ Boolean
A flat
[r, g, b]or[r, g, b, a]numeric array — the array form a single color takes (an array of colors is aColor::Set, and its elements are never bare Numerics). -
.set(colors) ⇒ Object
Create a Color or Color::Set from the given value.
-
.valid?(color) ⇒ Boolean
Check if the value is a valid color.
Instance Method Summary collapse
-
#initialize(color) ⇒ Color
constructor
Create a color from a keyword, hex string, array, or Color.
-
#opacity ⇒ Object
Get the opacity.
-
#opacity=(opacity) ⇒ Object
Set the opacity.
-
#to_a ⇒ Object
Return the color components as an array.
-
#vertex(_i) ⇒ Object
The color for the i-th vertex.
Constructor Details
#initialize(color) ⇒ Color
Create a color from a keyword, hex string, array, or Color
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/ruby2d/color.rb', line 102 def initialize(color) raise Error, "#{color.inspect} is not a valid color" unless self.class.valid? color case color when String init_from_string color when Array @r = channel(color[0]) @g = channel(color[1]) @b = channel(color[2]) @a = color.length == 4 ? channel(color[3]) : 1.0 when Color @r = color.r @g = color.g @b = color.b @a = color.a end end |
Instance Attribute Details
#a ⇒ Object
Returns the value of attribute a.
66 67 68 |
# File 'lib/ruby2d/color.rb', line 66 def a @a end |
#b ⇒ Object
Returns the value of attribute b.
66 67 68 |
# File 'lib/ruby2d/color.rb', line 66 def b @b end |
#g ⇒ Object
Returns the value of attribute g.
66 67 68 |
# File 'lib/ruby2d/color.rb', line 66 def g @g end |
#r ⇒ Object
Returns the value of attribute r.
66 67 68 |
# File 'lib/ruby2d/color.rb', line 66 def r @r end |
Class Method Details
.for_render(colors) ⇒ Object
Resolve a color for an immediate-mode class-level .render call.
Behaves like .set, except string colors and flat [r, g, b(, a)]
numeric arrays return a shared cached Color instance, skipping the
validation re-scan and object allocation .new pays on every call —
those two forms are the common case in per-frame draws, and on the web
(mruby/wasm) that per-call cost dominates the frame budget. The cached
instance is read and forwarded to the native draw call immediately; it
must never be stored on an object or handed to user code (a later
mutation would corrupt every subsequent lookup) — use .set anywhere
the color is kept. 'random' is never cached, so each call still
rolls a fresh color.
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/ruby2d/color.rb', line 149 def for_render(colors) if colors.is_a?(String) cached = @render_cache[colors] return cached if cached return Color.new(colors) if colors == 'random' @render_cache.shift if @render_cache.size >= RENDER_CACHE_MAX @render_cache[colors] = Color.new(colors) elsif colors.is_a?(Array) && colors[0].is_a?(Numeric) && rgba_array?(colors) # ^ The two inline checks pre-screen the non-match cases (an array of # colors starts with a String/Array/Color, never a Numeric) so this # per-draw-call path only pays the `rgba_array?` method call when the # input is almost certainly a cacheable [r, g, b(, a)] array. # Array keys are looked up by value, so mutating a previously seen # array can't corrupt the mapping — it just misses and inserts a new # entry. The stored key is a frozen copy for the same reason. cached = @render_cache[colors] return cached if cached c = Color.new(colors) @render_cache.shift if @render_cache.size >= RENDER_CACHE_MAX @render_cache[colors.dup.freeze] = c else set(colors) end end |
.hex?(color_string) ⇒ Boolean
Check if the string is a valid hex color value
Byte comparisons, not slicing: valid? calls this on every Color.new
and on every per-vertex color of every immediate-mode draw, and the
readable form ([0], [1..], .chars, plus a fresh literal for the
allowed set) allocated about ten short-lived strings each time.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/ruby2d/color.rb', line 199 def hex?(color_string) return false unless color_string.instance_of?(String) && color_string.getbyte(0) == 35 # '#' len = color_string.length return false unless len == 4 || len == 7 || len == 9 i = 1 while i < len b = color_string.getbyte(i) return false unless (b >= 48 && b <= 57) || # 0-9 (b >= 65 && b <= 70) || # A-F (b >= 97 && b <= 102) # a-f i += 1 end true end |
.parse_string(color) ⇒ Object
Parse a color string into a frozen [r, g, b, a] tuple, caching the
result. Named colors and hex strings are cached; 'random' is not.
Callers must not mutate the returned array.
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/ruby2d/color.rb', line 233 def parse_string(color) # Check the cache first so the hot path (a previously-seen named or hex # color) returns before the `'random'` literal comparison, which would # otherwise allocate a fresh `'random'` string on every call. `'random'` # is never cached, so it still falls through to a fresh value below. cached = @parse_cache[color] return cached if cached return [rand, rand, rand, 1.0] if color == 'random' source = hex?(color) ? color : NAMED_COLORS[color] rgba = hex_to_f(source).freeze @parse_cache.shift if @parse_cache.size >= PARSE_CACHE_MAX @parse_cache[color] = rgba end |
.rgba_array?(colors) ⇒ Boolean
A flat [r, g, b] or [r, g, b, a] numeric array — the array form a
single color takes (an array of colors is a Color::Set, and its
elements are never bare Numerics). while, not blocks — this guards
the per-draw-call render path and block calls dominate on wasm mruby.
180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/ruby2d/color.rb', line 180 def rgba_array?(colors) return false unless colors.is_a?(Array) n = colors.length return false unless n == 3 || n == 4 i = 0 while i < n return false unless colors[i].is_a?(Numeric) i += 1 end true end |
.set(colors) ⇒ Object
Create a Color or Color::Set from the given value
123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/ruby2d/color.rb', line 123 def set(colors) # Already a Color::Set (e.g. re-applying a gradient fill): pass through. return colors if colors.is_a?(Color::Set) # A non-empty array of valid colors becomes a `Color::Set`. An empty # array is not a valid gradient, so it falls through to `Color.new`, # which raises a clear "not a valid color" at the mistake site. if colors.is_a?(Array) && !colors.empty? && colors.all? { |el| Color.valid? el } Color::Set.new(colors) # Otherwise, return single color else Color.new(colors) end end |
.valid?(color) ⇒ Boolean
Check if the value is a valid color
219 220 221 222 223 224 225 226 227 228 |
# File 'lib/ruby2d/color.rb', line 219 def valid?(color) color.is_a?(Color) || # color object NAMED_COLORS.key?(color) || # keyword hex?(color) || # hexadecimal value ( # [r, g, b] or [r, g, b, a] numbers color.instance_of?(Array) && (color.length == 3 || color.length == 4) && color.all? { |el| el.is_a?(Numeric) } ) end |
Instance Method Details
#opacity ⇒ Object
Get the opacity
263 264 265 |
# File 'lib/ruby2d/color.rb', line 263 def opacity @a end |
#opacity=(opacity) ⇒ Object
Set the opacity. Must be a single number; per-vertex opacity (an array) is only supported by shapes that handle it explicitly, such as Polyline. The value is clamped to 0.0..1.0 so an animation that momentarily drives opacity out of range degrades to fully transparent/opaque rather than wrapping the Uint8 alpha cast into a wrong, near-opaque byte.
272 273 274 275 276 277 278 |
# File 'lib/ruby2d/color.rb', line 272 def opacity=(opacity) unless opacity.is_a?(Numeric) raise ArgumentError, "opacity must be a number between 0.0 and 1.0, got #{opacity.inspect}" end @a = opacity.clamp(0.0, 1.0) end |
#to_a ⇒ Object
Return the color components as an array
281 282 283 |
# File 'lib/ruby2d/color.rb', line 281 def to_a [@r, @g, @b, @a] end |
#vertex(_i) ⇒ Object
The color for the i-th vertex. A single Color represents "every vertex
is the same color," so every index returns self. Mirrors Color::Set#vertex
so per-vertex rendering code can call color.vertex(i) without branching.
288 289 290 |
# File 'lib/ruby2d/color.rb', line 288 def vertex(_i) self end |