From 0216d9d5bdec9565e3d2fbc0acac8658b9651865 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Sun, 28 Apr 2013 17:00:49 -0700 Subject: [PATCH] Make utils.lua a proper module with a compat wrapper --- aegisub/automation/include/aegisub/util.lua | 209 +++++++++++++ aegisub/automation/include/aegisub/util.moon | 211 +++++++++++++ aegisub/automation/include/utils-auto4.lua | 303 +------------------ 3 files changed, 436 insertions(+), 287 deletions(-) create mode 100644 aegisub/automation/include/aegisub/util.lua create mode 100644 aegisub/automation/include/aegisub/util.moon diff --git a/aegisub/automation/include/aegisub/util.lua b/aegisub/automation/include/aegisub/util.lua new file mode 100644 index 000000000..54baa677d --- /dev/null +++ b/aegisub/automation/include/aegisub/util.lua @@ -0,0 +1,209 @@ +local utils = { + 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, + 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 utils diff --git a/aegisub/automation/include/aegisub/util.moon b/aegisub/automation/include/aegisub/util.moon new file mode 100644 index 000000000..fd6caf7d8 --- /dev/null +++ b/aegisub/automation/include/aegisub/util.moon @@ -0,0 +1,211 @@ +-- Copyright (c) 2005-2010, Niels Martin Hansen, Rodrigo Braz Monteiro +-- Copyright (c) 2013, Thomas Goyne +-- +-- Permission to use, copy, modify, and distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- 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} + + -- 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 + + -- 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 + + -- Extract colour components of an ASS colour + extract_color: (s) -> + local 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) + + -- 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 + + -- Then an alpha override + a = s\match '&H(%x%x)&' + if a then + return 0, 0, 0, tonumber(a, 16) + + -- 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 alpha override code from a style definition colour code + alpha_from_style: (scolor) -> ass_alpha select 4, extract_color scolor + + -- 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 + + -- Converts HSV (Hue, Saturation, Value) to RGB + HSV_to_RGB: (H, S, V) -> + r, g, b = 0, 0, 0 + + -- 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 + error "math.floor(H % 360 / 60) should be [0, 6), is #{Hi}?" + + return 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) + + 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 + 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 + P + + r = get_component(Tr) + g = get_component(Tg) + b = get_component(Tb) + + return math.floor(r*255+0.5), math.floor(g*255+0.5), math.floor(b*255+0.5) + + -- Removes spaces at the start and end of string + trim: (s) -> s\gsub '^%s*(.-)%s*$', '%1' + + -- 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, '' + + -- Iterator function for headtail + words: (s) -> -> + return if s == '' + head, tail = string.headtail s + s = tail + head + + -- 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 diff --git a/aegisub/automation/include/utils-auto4.lua b/aegisub/automation/include/utils-auto4.lua index a227d37f7..200cdc7db 100644 --- a/aegisub/automation/include/utils-auto4.lua +++ b/aegisub/automation/include/utils-auto4.lua @@ -27,291 +27,20 @@ POSSIBILITY OF SUCH DAMAGE. ]] --- Make a shallow copy of a table -function table.copy(oldtab) - local newtab = {} - for key, val in pairs(oldtab) do - newtab[key] = val - end - return newtab -end --- Compability -copy_line = table.copy +util = require 'aegisub.util' --- Make a deep copy of a table --- Retains equality of table references inside the copy and handles self-referencing structures -function table.copy_deep(srctab) - -- Table to hold subtables already copied, to avoid circular references causing infinite recursion - local circular = {} - local function do_copy(oldtab) - -- Check if we know the source already - if circular[oldtab] then - -- Use already-made copy - return circular[oldtab] - else - -- Prepare a new table to copy into - local newtab = {} - -- Register it as known - circular[oldtab] = newtab - -- Copy fields - for key, val in pairs(oldtab) do - -- Copy tables recursively, everything else normally - if type(val) == "table" then - newtab[key] = do_copy(val) - else - newtab[key] = val - end - end - return newtab - end - end - return do_copy(srctab) -end - --- Generates ASS hexadecimal string from R,G,B integer components, in &HBBGGRR& format -function ass_color(r,g,b) - return string.format("&H%02X%02X%02X&",b,g,r) -end --- Format an alpha-string for \Xa style overrides -function ass_alpha(a) - return string.format("&H%02X&", a) -end --- Format an ABGR string for use in style definitions (these don't end with & either) -function ass_style_color(r,g,b,a) - return string.format("&H%02X%02X%02X%02X",a,b,g,r) -end - --- Extract colour components of an ASS colour -function extract_color(s) - local 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) - end - - -- 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 - end - - -- Then an alpha override - a = s:match("&H(%x%x)&") - if a then - return 0, 0, 0, tonumber(a, 16) - end - - -- 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 - end - - -- Failed... - return nil -end - --- Create an alpha override code from a style definition colour code -function alpha_from_style(scolor) - local r, g, b, a = extract_color(scolor) - return ass_alpha(a or 0) -end - --- Create an colour override code from a style definition colour code -function color_from_style(scolor) - local r, g, b = extract_color(scolor) - return ass_color(r or 0, g or 0, b or 0) -end - --- Converts HSV (Hue, Saturation, Value) to RGB -function HSV_to_RGB(H,S,V) - local r,g,b = 0,0,0 - - -- Saturation is zero, make grey - if S == 0 then - r = V*255 - if r < 0 then - r = 0 - end - if r > 255 then - r = 255 - end - g = r - b = r - - -- Else, calculate color - else - -- Calculate subvalues - H = H % 360 -- Put H in range [0,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) - - -- Do math based on hue index - 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 - aegisub.debug.out(2, "RGB_to_HSV: Hi got an unexpected value: %d\n\n", Hi) - end - end - - return r,g,b -end - --- Convert HSL (Hue, Saturation, Luminance) to RGB --- Contributed by Gundamn -function HSL_to_RGB(H, S, L) - local r, g, b; - - -- Make sure input is in range - H = H % 360 - S = clamp(S, 0, 1) - L = clamp(L, 0, 1) - - if S == 0 then - -- Simple case if saturation is 0, all grey - r = L - g = L - b = L - - else - -- More common case, saturated colour - 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 function get_component(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 - --- Removes spaces at the start and end of string -function string.trim(s) - return (string.gsub(s, "^%s*(.-)%s*$", "%1")) -end - --- Get the "head" and "tail" of a string, treating it as a sequence of words separated by one or more space-characters -function string.headtail(s) - local a, b, head, tail = string.find(s, "(.-)%s+(.*)") - if a then - return head, tail - else - return s, "" - end -end - --- Iterator function for headtail -function string.words(s) - local t = s - local function wordloop() - if t == "" then - return nil - end - local head, tail = string.headtail(t) - t = tail - return head - end - return wordloop, nil, nil -end - --- Clamp a number value to a range -function clamp(val, min, max) - if val < min then - return min - elseif val > max then - return max - else - return val - end -end - --- Interpolate between two numbers -function interpolate(pct, min, max) - if pct <= 0 then - return min - elseif pct >= 1 then - return max - else - return pct * (max - min) + min - end -end - --- Interpolate between two colour values, given in either style definition or style override format --- Return in style override format -function interpolate_color(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 between two alpha values, given either in style override or as part as a style definition colour --- Return in style override format -function interpolate_alpha(pct, first, last) - local r1, g1, b1, a1 = extract_color(first) - local r2, g2, b2, a2 = extract_color(last) - return ass_alpha(interpolate(pct, a1, a2)) -end +table.copy = util.copy +copy_line = util.copy +table.copy_deep = util.deep_copy +ass_color = util.ass_color +ass_alpha = util.ass_alpha +ass_style_color = util.ass_style_color +extract_color = util.extract_color +alpha_from_style = util.alpha_from_style +color_from_style = util.color_from_style +HSV_to_RGB = util.HSV_to_RGB +HSL_to_RGB = util.HSL_to_RGB +clamp = util.clamp +interpolate = util.interpolate +interpolate_color = util.interpolate_color +interpolate_alpha = util.interpolate_alpha