From 1b96bf748fc8beaad7e07cc25629b2385811d555 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 2 May 2013 07:42:02 -0700 Subject: [PATCH] Fix calls to other util functions from within util functions --- aegisub/automation/include/aegisub/util.lua | 437 ++++++++++--------- aegisub/automation/include/aegisub/util.moon | 338 +++++++------- 2 files changed, 406 insertions(+), 369 deletions(-) diff --git a/aegisub/automation/include/aegisub/util.lua b/aegisub/automation/include/aegisub/util.lua index 54baa677d..cd0380657 100644 --- a/aegisub/automation/include/aegisub/util.lua +++ b/aegisub/automation/include/aegisub/util.lua @@ -1,209 +1,242 @@ -local utils = { - copy = function(tbl) +local sformat = string.format +local copy +copy = function(tbl) + return (function() + local _tbl_0 = { } + for k, v in pairs(tbl) do + _tbl_0[k] = v + end + return _tbl_0 + end)() +end +local deep_copy +deep_copy = function(tbl) + local seen = { } + copy = function(val) + if type(tbl) ~= 'table' then + return val + end + if seen[tbl] then + return seen[val] + end + seen[val] = tbl return (function() local _tbl_0 = { } - for k, v in pairs(tbl) do - _tbl_0[k] = v + for k, v in pairs(val) do + _tbl_0[k] = copy(v) end return _tbl_0 end)() - end, - deep_copy = function(tbl) - local seen = { } - local copy - copy = function(val) - if type(tbl) ~= 'table' then - return val - end - if seen[tbl] then - return seen[val] - end - seen[val] = tbl - return (function() - local _tbl_0 = { } - for k, v in pairs(val) do - _tbl_0[k] = copy(v) - end - return _tbl_0 - end)() - end - return copy(tbl) - end, - ass_color = function(r, g, b) - return string.format("&H%02X%02X%02X&", b, g, r) - end, - ass_alpha = function(a) - return string.format("&H%02X&", a) - end, - ass_style_color = function(r, g, b, a) - return string.format("&H%02X%02X%02X%02X", a, b, g, r) - end, - extract_color = function(s) - local a, b, g, r - a, b, g, r = s:match('&H(%x%x)(%x%x)(%x%x)(%x%x)') - if a then - return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a, 16) - end - b, g, r = s:match('&H(%x%x)(%x%x)(%x%x)&') - if b then - return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), 0 - end - a = s:match('&H(%x%x)&') - if a then - return 0, 0, 0, tonumber(a, 16) - end - r, g, b, a = s:match('#(%x%x)(%x?%x?)(%x?%x?)(%x?%x?)') - if r then - return tonumber(r, 16), tonumber(g, 16) or 0, tonumber(b, 16) or 0, tonumber(a, 16) or 0 - end - end, - alpha_from_style = function(scolor) - return ass_alpha(select(4, extract_color(scolor))) - end, - color_from_style = function(scolor) - local r, g, b = extract_color(scolor) - return ass_color(r or 0, g or 0, b or 0) - end, - HSV_to_RGB = function(H, S, V) - local r, g, b = 0, 0, 0 - if S == 0 then - r = self:clamp(V * 255, 0, 255) - g = r - b = r - else - H = math.abs(H) % 360 - local Hi = math.floor(H / 60) - local f = H / 60.0 - Hi - local p = V * (1 - S) - local q = V * (1 - f * S) - local t = V * (1 - (1 - f) * S) - if Hi == 0 then - r = V * 255.0 - g = t * 255.0 - b = p * 255.0 - elseif Hi == 1 then - r = q * 255.0 - g = V * 255.0 - b = p * 255.0 - elseif Hi == 2 then - r = p * 255.0 - g = V * 255.0 - b = t * 255.0 - elseif Hi == 3 then - r = p * 255.0 - g = q * 255.0 - b = V * 255.0 - elseif Hi == 4 then - r = t * 255.0 - g = p * 255.0 - b = V * 255.0 - elseif Hi == 5 then - r = V * 255.0 - g = p * 255.0 - b = q * 255.0 - else - error("math.floor(H % 360 / 60) should be [0, 6), is " .. tostring(Hi) .. "?") - end - end - return r, g, b - end, - HSL_to_RGB = function(H, S, L) - local r, g, b - H = math.abs(H) % 360 - S = clamp(S, 0, 1) - L = clamp(L, 0, 1) - if S == 0 then - r = L - g = L - b = L - else - local Q - if L < 0.5 then - Q = L * (1.0 + S) - else - Q = L + S - (L * S) - end - local P = 2.0 * L - Q - local Hk = H / 360 - local Tr, Tg, Tb - if Hk < 1 / 3 then - Tr = Hk + 1 / 3 - Tg = Hk - Tb = Hk + 2 / 3 - elseif Hk > 2 / 3 then - Tr = Hk - 2 / 3 - Tg = Hk - Tb = Hk - 1 / 3 - else - Tr = Hk + 1 / 3 - Tg = Hk - Tb = Hk - 1 / 3 - end - local get_component - get_component = function(T) - if T < 1 / 6 then - return P + ((Q - P) * 6.0 * T) - elseif 1 / 6 <= T and T < 1 / 2 then - return Q - elseif 1 / 2 <= T and T < 2 / 3 then - return P + ((Q - P) * (2 / 3 - T) * 6.0) - else - return P - end - end - r = get_component(Tr) - g = get_component(Tg) - b = get_component(Tb) - end - return math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5) - end, - trim = function(s) - return s:gsub('^%s*(.-)%s*$', '%1') - end, - headtail = function(s) - local a, b, head, tail = s:find('(.-)%s+(.*)') - if a then - return head, tail - else - return s, '' - end - end, - words = function(s) - return function() - if s == '' then - return - end - local head, tail = string.headtail(s) - s = tail - return head - end - end, - clamp = function(val, min, max) - if val < min then - return min - elseif val > max then - return max - else - return val - end - end, - interpolate = function(pct, min, max) - if pct <= 0 then - return min - elseif pct >= 1 then - return max - else - return pct * (max - min) + min - end - end, - interpolate_color = function(pct, first, last) - local r1, g1, b1 = extract_color(first) - local r2, g2, b2 = extract_color(last) - local r, g, b = interpolate(pct, r1, r2), interpolate(pct, g1, g2), interpolate(pct, b1, b2) - return ass_color(r, g, b) - end, - interpolate_alpha = function(pct, first, last) - return ass_alpha(interpolate(pct, select(4, extract_color(first)), select(4, extract_color(last)))) end + return copy(tbl) +end +local ass_color +ass_color = function(r, g, b) + return sformat("&H%02X%02X%02X&", b, g, r) +end +local ass_alpha +ass_alpha = function(a) + return sformat("&H%02X&", a) +end +local ass_style_color +ass_style_color = function(r, g, b, a) + return sformat("&H%02X%02X%02X%02X", a, b, g, r) +end +local extract_color +extract_color = function(s) + local a, b, g, r + a, b, g, r = s:match('&H(%x%x)(%x%x)(%x%x)(%x%x)') + if a then + return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a, 16) + end + b, g, r = s:match('&H(%x%x)(%x%x)(%x%x)&') + if b then + return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), 0 + end + a = s:match('&H(%x%x)&') + if a then + return 0, 0, 0, tonumber(a, 16) + end + r, g, b, a = s:match('#(%x%x)(%x?%x?)(%x?%x?)(%x?%x?)') + if r then + return tonumber(r, 16), tonumber(g, 16) or 0, tonumber(b, 16) or 0, tonumber(a, 16) or 0 + end +end +local alpha_from_style +alpha_from_style = function(scolor) + return ass_alpha(select(4, extract_color(scolor))) +end +local color_from_style +color_from_style = function(scolor) + local r, g, b = extract_color(scolor) + return ass_color(r or 0, g or 0, b or 0) +end +local HSV_to_RGB +HSV_to_RGB = function(H, S, V) + local r, g, b = 0, 0, 0 + if S == 0 then + r = self:clamp(V * 255, 0, 255) + g = r + b = r + else + H = math.abs(H) % 360 + local Hi = math.floor(H / 60) + local f = H / 60.0 - Hi + local p = V * (1 - S) + local q = V * (1 - f * S) + local t = V * (1 - (1 - f) * S) + if Hi == 0 then + r = V * 255.0 + g = t * 255.0 + b = p * 255.0 + elseif Hi == 1 then + r = q * 255.0 + g = V * 255.0 + b = p * 255.0 + elseif Hi == 2 then + r = p * 255.0 + g = V * 255.0 + b = t * 255.0 + elseif Hi == 3 then + r = p * 255.0 + g = q * 255.0 + b = V * 255.0 + elseif Hi == 4 then + r = t * 255.0 + g = p * 255.0 + b = V * 255.0 + elseif Hi == 5 then + r = V * 255.0 + g = p * 255.0 + b = q * 255.0 + else + error("math.floor(H % 360 / 60) should be [0, 6), is " .. tostring(Hi) .. "?") + end + end + return r, g, b +end +local HSL_to_RGB +HSL_to_RGB = function(H, S, L) + local r, g, b + H = math.abs(H) % 360 + S = clamp(S, 0, 1) + L = clamp(L, 0, 1) + if S == 0 then + r = L + g = L + b = L + else + local Q + if L < 0.5 then + Q = L * (1.0 + S) + else + Q = L + S - (L * S) + end + local P = 2.0 * L - Q + local Hk = H / 360 + local Tr, Tg, Tb + if Hk < 1 / 3 then + Tr = Hk + 1 / 3 + Tg = Hk + Tb = Hk + 2 / 3 + elseif Hk > 2 / 3 then + Tr = Hk - 2 / 3 + Tg = Hk + Tb = Hk - 1 / 3 + else + Tr = Hk + 1 / 3 + Tg = Hk + Tb = Hk - 1 / 3 + end + local get_component + get_component = function(T) + if T < 1 / 6 then + return P + ((Q - P) * 6.0 * T) + elseif 1 / 6 <= T and T < 1 / 2 then + return Q + elseif 1 / 2 <= T and T < 2 / 3 then + return P + ((Q - P) * (2 / 3 - T) * 6.0) + else + return P + end + end + r = get_component(Tr) + g = get_component(Tg) + b = get_component(Tb) + end + return math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5) +end +local trim +trim = function(s) + return s:gsub('^%s*(.-)%s*$', '%1') +end +local headtail +headtail = function(s) + local a, b, head, tail = s:find('(.-)%s+(.*)') + if a then + return head, tail + else + return s, '' + end +end +local words +words = function(s) + return function() + if s == '' then + return + end + local head, tail = headtail(s) + s = tail + return head + end +end +local clamp +clamp = function(val, min, max) + if val < min then + return min + elseif val > max then + return max + else + return val + end +end +local interpolate +interpolate = function(pct, min, max) + if pct <= 0 then + return min + elseif pct >= 1 then + return max + else + return pct * (max - min) + min + end +end +local interpolate_color +interpolate_color = function(pct, first, last) + local r1, g1, b1 = extract_color(first) + local r2, g2, b2 = extract_color(last) + local r, g, b = interpolate(pct, r1, r2), interpolate(pct, g1, g2), interpolate(pct, b1, b2) + return ass_color(r, g, b) +end +local interpolate_alpha +interpolate_alpha = function(pct, first, last) + return ass_alpha(interpolate(pct, select(4, extract_color(first)), select(4, extract_color(last)))) +end +return { + copy = copy, + deep_copy = deep_copy, + ass_color = ass_color, + ass_alpha = ass_alpha, + ass_style_color = ass_style_color, + extract_color = extract_color, + alpha_from_style = alpha_from_style, + color_from_style = color_from_style, + HSV_to_RGB = HSV_to_RGB, + HSL_to_RGB = HSL_to_RGB, + trim = trim, + headtail = headtail, + words = words, + clamp = clamp, + interpolate = interpolate, + interpolate_color = interpolate_color, + interpolate_alpha = interpolate_alpha } -return utils diff --git a/aegisub/automation/include/aegisub/util.moon b/aegisub/automation/include/aegisub/util.moon index fd6caf7d8..8e320f0eb 100644 --- a/aegisub/automation/include/aegisub/util.moon +++ b/aegisub/automation/include/aegisub/util.moon @@ -13,199 +13,203 @@ -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -utils = - -- Make a shallow copy of a table - copy: (tbl) -> {k, v for k, v in pairs tbl} +sformat = string.format - -- Make a deep copy of a table - -- Retains equality of table references inside the copy and handles self-referencing structures - deep_copy: (tbl) -> - seen = {} - copy = (val) -> - return val if type(tbl) != 'table' - return seen[val] if seen[tbl] - seen[val] = tbl - {k, copy(v) for k, v in pairs val} - copy tbl +-- Make a shallow copy of a table +copy = (tbl) -> {k, v for k, v in pairs tbl} - -- Generates ASS hexadecimal string from R, G, B integer components, in &HBBGGRR& format - ass_color: (r, g, b) -> string.format "&H%02X%02X%02X&", b, g, r - -- Format an alpha-string for \Xa style overrides - ass_alpha: (a) -> string.format "&H%02X&", a - -- Format an ABGR string for use in style definitions (these don't end with & either) - ass_style_color: (r, g, b, a) -> string.format "&H%02X%02X%02X%02X", a, b, g, r +-- Make a deep copy of a table +-- Retains equality of table references inside the copy and handles self-referencing structures +deep_copy = (tbl) -> + seen = {} + copy = (val) -> + return val if type(tbl) != 'table' + return seen[val] if seen[tbl] + seen[val] = tbl + {k, copy(v) for k, v in pairs val} + copy tbl - -- Extract colour components of an ASS colour - extract_color: (s) -> - local a, b, g, r +-- Generates ASS hexadecimal string from R, G, B integer components, in &HBBGGRR& format +ass_color = (r, g, b) -> sformat "&H%02X%02X%02X&", b, g, r +-- Format an alpha-string for \Xa style overrides +ass_alpha = (a) -> sformat "&H%02X&", a +-- Format an ABGR string for use in style definitions (these don't end with & either) +ass_style_color = (r, g, b, a) -> sformat "&H%02X%02X%02X%02X", a, b, g, r - -- Try a style first - a, b, g, r = s\match '&H(%x%x)(%x%x)(%x%x)(%x%x)' - if a then - return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a, 16) +-- Extract colour components of an ASS colour +extract_color = (s) -> + local a, b, g, r - -- Then a colour override - b, g, r = s\match '&H(%x%x)(%x%x)(%x%x)&' - if b then - return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), 0 + -- Try a style first + a, b, g, r = s\match '&H(%x%x)(%x%x)(%x%x)(%x%x)' + if a then + return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a, 16) - -- Then an alpha override - a = s\match '&H(%x%x)&' - if a then - return 0, 0, 0, tonumber(a, 16) + -- Then a colour override + b, g, r = s\match '&H(%x%x)(%x%x)(%x%x)&' + if b then + return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), 0 - -- Ok how about HTML format then? - r, g, b, a = s\match '#(%x%x)(%x?%x?)(%x?%x?)(%x?%x?)' - if r then - return tonumber(r, 16), tonumber(g, 16) or 0, tonumber(b, 16) or 0, tonumber(a, 16) or 0 + -- Then an alpha override + a = s\match '&H(%x%x)&' + if a then + return 0, 0, 0, tonumber(a, 16) - -- Create an alpha override code from a style definition colour code - alpha_from_style: (scolor) -> ass_alpha select 4, extract_color scolor + -- Ok how about HTML format then? + r, g, b, a = s\match '#(%x%x)(%x?%x?)(%x?%x?)(%x?%x?)' + if r then + return tonumber(r, 16), tonumber(g, 16) or 0, tonumber(b, 16) or 0, tonumber(a, 16) or 0 - -- Create an colour override code from a style definition colour code - color_from_style: (scolor) -> - r, g, b = extract_color scolor - ass_color r or 0, g or 0, b or 0 +-- Create an alpha override code from a style definition colour code +alpha_from_style = (scolor) -> ass_alpha select 4, extract_color scolor - -- Converts HSV (Hue, Saturation, Value) to RGB - HSV_to_RGB: (H, S, V) -> - r, g, b = 0, 0, 0 +-- Create an colour override code from a style definition colour code +color_from_style = (scolor) -> + r, g, b = extract_color scolor + ass_color r or 0, g or 0, b or 0 - -- Saturation is zero, make grey - if S == 0 - r = @clamp(V*255, 0, 255) - g = r - b = r +-- Converts HSV (Hue, Saturation, Value) to RGB +HSV_to_RGB = (H, S, V) -> + r, g, b = 0, 0, 0 - -- Else, calculate color + -- Saturation is zero, make grey + if S == 0 + r = @clamp(V*255, 0, 255) + g = r + b = r + + -- Else, calculate color + else + -- Calculate subvalues + H = math.abs(H) % 360 -- Put H in range [0, 360) + Hi = math.floor(H/60) + f = H/60.0 - Hi + p = V*(1-S) + q = V*(1-f*S) + t = V*(1-(1-f)*S) + + -- Do math based on hue index + if Hi == 0 + r = V*255.0 + g = t*255.0 + b = p*255.0 + elseif Hi == 1 + r = q*255.0 + g = V*255.0 + b = p*255.0 + elseif Hi == 2 + r = p*255.0 + g = V*255.0 + b = t*255.0 + elseif Hi == 3 + r = p*255.0 + g = q*255.0 + b = V*255.0 + elseif Hi == 4 + r = t*255.0 + g = p*255.0 + b = V*255.0 + elseif Hi == 5 + r = V*255.0 + g = p*255.0 + b = q*255.0 else - -- Calculate subvalues - H = math.abs(H) % 360 -- Put H in range [0, 360) - Hi = math.floor(H/60) - f = H/60.0 - Hi - p = V*(1-S) - q = V*(1-f*S) - t = V*(1-(1-f)*S) + error "math.floor(H % 360 / 60) should be [0, 6), is #{Hi}?" - -- Do math based on hue index - if Hi == 0 - r = V*255.0 - g = t*255.0 - b = p*255.0 - elseif Hi == 1 - r = q*255.0 - g = V*255.0 - b = p*255.0 - elseif Hi == 2 - r = p*255.0 - g = V*255.0 - b = t*255.0 - elseif Hi == 3 - r = p*255.0 - g = q*255.0 - b = V*255.0 - elseif Hi == 4 - r = t*255.0 - g = p*255.0 - b = V*255.0 - elseif Hi == 5 - r = V*255.0 - g = p*255.0 - b = q*255.0 - else - error "math.floor(H % 360 / 60) should be [0, 6), is #{Hi}?" + return r, g, b - return r, g, b +-- Convert HSL (Hue, Saturation, Luminance) to RGB +-- Contributed by Gundamn +HSL_to_RGB = (H, S, L) -> + local r, g, b - -- Convert HSL (Hue, Saturation, Luminance) to RGB - -- Contributed by Gundamn - HSL_to_RGB: (H, S, L) -> - local r, g, b + -- Make sure input is in range + H = math.abs(H) % 360 + S = clamp(S, 0, 1) + L = clamp(L, 0, 1) - -- Make sure input is in range - H = math.abs(H) % 360 - S = clamp(S, 0, 1) - L = clamp(L, 0, 1) - - if S == 0 -- Simple case if saturation is 0, all grey - r = L - g = L - b = L + if S == 0 -- Simple case if saturation is 0, all grey + r = L + g = L + b = L + else + -- More common case, saturated colour + Q = if L < 0.5 + L * (1.0 + S) else - -- More common case, saturated colour - Q = if L < 0.5 - L * (1.0 + S) + L + S - (L * S) + + P = 2.0 * L - Q + + Hk = H / 360 + + local Tr, Tg, Tb + if Hk < 1/3 + Tr = Hk + 1/3 + Tg = Hk + Tb = Hk + 2/3 + elseif Hk > 2/3 + Tr = Hk - 2/3 + Tg = Hk + Tb = Hk - 1/3 + else + Tr = Hk + 1/3 + Tg = Hk + Tb = Hk - 1/3 + + get_component = (T) -> + if T < 1/6 + P + ((Q - P) * 6.0 * T) + elseif 1/6 <= T and T < 1/2 + Q + elseif 1/2 <= T and T < 2/3 + P + ((Q - P) * (2/3 - T) * 6.0) else - L + S - (L * S) + P - P = 2.0 * L - Q + r = get_component(Tr) + g = get_component(Tg) + b = get_component(Tb) - Hk = H / 360 + return math.floor(r*255+0.5), math.floor(g*255+0.5), math.floor(b*255+0.5) - local Tr, Tg, Tb - if Hk < 1/3 - Tr = Hk + 1/3 - Tg = Hk - Tb = Hk + 2/3 - elseif Hk > 2/3 - Tr = Hk - 2/3 - Tg = Hk - Tb = Hk - 1/3 - else - Tr = Hk + 1/3 - Tg = Hk - Tb = Hk - 1/3 +-- Removes spaces at the start and end of string +trim = (s) -> s\gsub '^%s*(.-)%s*$', '%1' - get_component = (T) -> - if T < 1/6 - P + ((Q - P) * 6.0 * T) - elseif 1/6 <= T and T < 1/2 - Q - elseif 1/2 <= T and T < 2/3 - P + ((Q - P) * (2/3 - T) * 6.0) - else - P +-- Get the 'head' and 'tail' of a string, treating it as a sequence of words separated by one or more space-characters +headtail = (s) -> + a, b, head, tail = s\find '(.-)%s+(.*)' + if a then head, tail else s, '' - r = get_component(Tr) - g = get_component(Tg) - b = get_component(Tb) +-- Iterator function for headtail +words = (s) -> -> + return if s == '' + head, tail = headtail s + s = tail + head - return math.floor(r*255+0.5), math.floor(g*255+0.5), math.floor(b*255+0.5) +-- Clamp a number value to a range +clamp = (val, min, max) -> + if val < min then min elseif val > max then max else val - -- Removes spaces at the start and end of string - trim: (s) -> s\gsub '^%s*(.-)%s*$', '%1' +-- Interpolate between two numbers +interpolate = (pct, min, max) -> + if pct <= 0 then min elseif pct >= 1 then max else pct * (max - min) + min - -- Get the 'head' and 'tail' of a string, treating it as a sequence of words separated by one or more space-characters - headtail: (s) -> - a, b, head, tail = s\find '(.-)%s+(.*)' - if a then head, tail else s, '' +-- Interpolate between two colour values, given in either style definition or style override format +-- Return in style override format +interpolate_color = (pct, first, last) -> + r1, g1, b1 = extract_color first + r2, g2, b2 = extract_color last + r, g, b = interpolate(pct, r1, r2), interpolate(pct, g1, g2), interpolate(pct, b1, b2) + ass_color r, g, b - -- Iterator function for headtail - words: (s) -> -> - return if s == '' - head, tail = string.headtail s - s = tail - head +-- Interpolate between two alpha values, given either in style override or as part as a style definition colour +-- Return in style override format +interpolate_alpha = (pct, first, last) -> + ass_alpha interpolate pct, select(4, extract_color first), select(4, extract_color last) - -- Clamp a number value to a range - clamp: (val, min, max) -> - if val < min then min elseif val > max then max else val - - -- Interpolate between two numbers - interpolate: (pct, min, max) -> - if pct <= 0 then min elseif pct >= 1 then max else pct * (max - min) + min - - -- Interpolate between two colour values, given in either style definition or style override format - -- Return in style override format - interpolate_color: (pct, first, last) -> - r1, g1, b1 = extract_color first - r2, g2, b2 = extract_color last - r, g, b = interpolate(pct, r1, r2), interpolate(pct, g1, g2), interpolate(pct, b1, b2) - ass_color r, g, b - - -- Interpolate between two alpha values, given either in style override or as part as a style definition colour - -- Return in style override format - interpolate_alpha: (pct, first, last) -> - ass_alpha interpolate pct, select(4, extract_color first), select(4, extract_color last) - -utils +{ :copy, :deep_copy, :ass_color, :ass_alpha, :ass_style_color, + :extract_color, :alpha_from_style, :color_from_style, :HSV_to_RGB, + :HSL_to_RGB, :trim, :headtail, :words, :clamp, :interpolate, + :interpolate_color, :interpolate_alpha }