Skip to content

Commit f6d0738

Browse files
committed
refactor: new optimizations and documentation to gradientty.
1 parent 48dcd1a commit f6d0738

File tree

1 file changed

+105
-38
lines changed

1 file changed

+105
-38
lines changed

src/gradientty.cr

Lines changed: 105 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,66 @@
11
module Gradientty
22
# Represents a gradient color instance for rendering strings with color transitions.
3+
#
4+
# Create an instance using `Gradientty.gradient(["#hex1", "#hex2", ...])`.
5+
# Use `#call` for single-line strings or `#multiline` for multi-line strings.
36
struct ColorInstance
47
# The array of RGB color stops used for the gradient.
8+
# Each color is an Array of three Int32 values: [R, G, B].
59
property colors : Array(Array(Int32))
610

711
# Initializes a ColorInstance with an array of RGB color stops.
12+
#
13+
# *colors* - An array of RGB color stops.
814
def initialize(colors : Array(Array(Int32)))
915
@colors = colors
1016
end
1117

12-
# Applies the gradient to a string, returning the string with ANSI color codes.
13-
# Does not add a line break at the end.
18+
# Applies the gradient to a single-line string.
19+
#
20+
# Spaces and newlines are preserved.
21+
# The string is returned with ANSI color codes applied.
1422
#
15-
# Skips coloring for spaces and newlines.
23+
# *str* - The string to apply the gradient to.
1624
#
1725
# Example:
1826
# ```
19-
# puts Gradientty.gradient(["#ff0000", "#0000ff"]).call("Hello")
27+
# custom = Gradientty.gradient(["#ff0000", "#00ff00"])
28+
# puts custom.call("Hello")
2029
# ```
2130
def call(str : String)
2231
return "" if str.empty?
2332
len = str.size
24-
result = ""
2533

26-
str.chars.each_with_index do |c, i|
27-
if c == " " || c == "\n"
28-
result += c
29-
next
30-
end
34+
String.build do |io|
35+
str.chars.each_with_index do |c, i|
36+
if c == ' ' || c == '\n'
37+
io << c
38+
next
39+
end
3140

32-
r = interpolate(@colors, i, len, 0)
33-
g = interpolate(@colors, i, len, 1)
34-
b = interpolate(@colors, i, len, 2)
41+
r = interpolate(@colors, i, len, 0)
42+
g = interpolate(@colors, i, len, 1)
43+
b = interpolate(@colors, i, len, 2)
3544

36-
result += "\e[38;2;#{r};#{g};#{b}m#{c}"
45+
io << "\e[38;2;#{r};#{g};#{b}m#{c}"
46+
end
47+
io << "\e[0m"
3748
end
38-
39-
result + "\e[0m"
4049
end
4150

42-
# Applies a gradient to a multiline string.
51+
# Applies the gradient to a multi-line string.
4352
#
4453
# Each line receives the full gradient individually by default.
45-
# If `continuous` is true, the gradient is applied as if the entire
46-
# string were a single long line, so the color flows across lines.
54+
# If *continuous* is true, the gradient flows continuously across all lines.
4755
#
48-
# Parameters:
49-
# - *str* = The multiline string to color.
50-
# - *continuous* = false - Whether to apply the gradient continuously across all lines.
56+
# *str* - The multi-line string to color.
57+
# *continuous* - Whether to apply gradient continuously across all lines (default: false).
5158
#
52-
# Returns:
53-
# - String - The colored string with ANSI codes, ready for terminal output.
59+
# Example:
60+
# ```
61+
# custom = Gradientty.gradient(["#ff0000", "#00ff00", "#0000ff"])
62+
# puts custom.multiline("Hello\nWorld", true)
63+
# ```
5464
def multiline(str : String, continuous = false)
5565
if continuous
5666
call(str.chomp) + "\e[0m"
@@ -60,53 +70,110 @@ module Gradientty
6070
end
6171

6272
# Linearly interpolates between color stops for a given channel (R, G, or B).
73+
#
6374
# Used internally for gradient calculation.
6475
private def interpolate(colors : Array(Array(Int32)), i : Int32, len : Int32, channel : Int32) : Int32
65-
start_c = colors.first[channel]
66-
end_c = colors.last[channel]
67-
((start_c + ((end_c - start_c) * i.to_f / (len - 1))).round).to_i
76+
return colors.first[channel] if len <= 1
77+
78+
pos = i.to_f / (len - 1) * (colors.size - 1)
79+
idx = pos.floor.to_i
80+
next_idx = Math.min(idx + 1, colors.size - 1)
81+
t = pos - idx
82+
83+
start_c = colors[idx][channel]
84+
end_c = colors[next_idx][channel]
85+
86+
(start_c + (end_c - start_c) * t).round.to_i
6887
end
6988
end
7089

71-
# Converts a hex color string (e.g., "#ff00aa") to an array of RGB `Int32` values.
72-
# Used internally.
90+
# Converts a hex color string (e.g., "#ff00aa") to an array of RGB Int32 values.
91+
#
92+
# *hex* - The hex color string.
93+
#
94+
# Example:
95+
# ```
96+
# Gradientty.hex_to_rgb("#ff00aa") # => [255, 0, 170]
97+
# ```
7398
private def self.hex_to_rgb(hex : String) : Array(Int32)
74-
hex = hex.gsub("#", "")
75-
[
76-
hex[0..1].to_i(16),
77-
hex[2..3].to_i(16),
78-
hex[4..5].to_i(16),
79-
]
99+
hex = hex.lstrip('#')
100+
[0, 2, 4].map { |i| hex[i, 2].to_i(16) }
80101
end
81102

82-
# Factory method: creates a `ColorInstance` from an array of hex color strings.
103+
# Factory method: creates a ColorInstance from an array of hex color strings.
104+
#
105+
# *colors* - Array of hex color strings for the gradient.
83106
#
84107
# Example:
85108
# ```
86-
# = Gradientty.gradient(["#ff0000", "#00ff00"])
109+
# custom = Gradientty.gradient(["#ff0000", "#00ff00"])
110+
# puts custom.call("Hello")
87111
# ```
88112
def self.gradient(colors : Array(String))
89113
rgb_colors = colors.map { |h| hex_to_rgb(h) }
90114
ColorInstance.new(rgb_colors)
91115
end
92116

117+
# Private helper to apply gradient directly to a string.
93118
private def self.apply_gradient(colors : Array(String), str : String)
94119
instance = gradient(colors)
95120
str.includes?("\n") ? instance.multiline(str) : instance.call(str)
96121
end
97122

123+
# Predefined crystal-style gradient.
124+
#
125+
# *str* - The string to color.
126+
#
127+
# Example:
128+
# ```
129+
# puts Gradientty.crystal("Hello Crystal!")
130+
# ```
98131
def self.crystal(str : String)
99132
apply_gradient(["#000000", "#777777", "#FFFFFF"], str)
100133
end
101134

135+
# Predefined rainbow gradient.
136+
#
137+
# *str* - The string to color.
138+
#
139+
# Example:
140+
# ```
141+
# puts Gradientty.rainbow("Hello Rainbow!")
142+
# ```
102143
def self.rainbow(str : String)
103-
apply_gradient(["#ff0000", "#ff7f00", "#ffff00", "#00ff00", "#0000ff", "#4b0082", "#9400d3"], str)
144+
apply_gradient([
145+
"#FF0000",
146+
"#FF7F00",
147+
"#FFFF00",
148+
"#7FFF00",
149+
"#00FF00",
150+
"#00FF7F",
151+
"#00FFFF",
152+
"#0000FF",
153+
"#8B00FF",
154+
], str)
104155
end
105156

157+
# Predefined pastel gradient.
158+
#
159+
# *str* - The string to color.
160+
#
161+
# Example:
162+
# ```
163+
# puts Gradientty.pastel("Hello Pastel!")
164+
# ```
106165
def self.pastel(str : String)
107166
apply_gradient(["#74ebd5", "#74ecd5"], str)
108167
end
109168

169+
# Predefined vice-style gradient.
170+
#
171+
# *str* - The string to color.
172+
#
173+
# Example:
174+
# ```
175+
# puts Gradientty.vice("Hello Vice!")
176+
# ```
110177
def self.vice(str : String)
111178
apply_gradient(["#5ee7df", "#b490ca"], str)
112179
end

0 commit comments

Comments
 (0)