diff --git a/automation/include/karaskel-auto4.lua b/automation/include/karaskel-auto4.lua new file mode 100644 index 000000000..47079b383 --- /dev/null +++ b/automation/include/karaskel-auto4.lua @@ -0,0 +1,310 @@ +--[[ + Copyright (c) 2007, Niels Martin Hansen, Rodrigo Braz Monteiro + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Aegisub Group nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +]] + +include("utils.lua") +include("unicode.lua") + +-- Make sure karaskel table exists +if not karaskel then + karaskel = {} +end + +-- Collect styles and metadata from the subs +function karaskel.collect_head(subs, generate_furigana) + local meta = { res_x = 0, res_y = 0 } + local styles = { n = 0 } + + if not karaskel.furigana_scale then + karaskel.furigana_scale = 0.5 + end + + local i = 1 + while i < #subs do + local l = subs[i] + + if l.class == "style" then + -- Store styles into the style table + styles.n = styles.n + 1 + styles[styles.n] = l + styles[l.name] = l + l.margin_v = l.margin_t + + -- And also generate furigana styles if wanted + if generate_furigana and not l.name:match("furigana") then + local fs = table.copy(l) + fs.fontsize = l.fontsize * karaskel.furigana_scale + fs.outline = l.outline * karaskel.furigana_scale + fs.shadow = l.shadow * karaskel.furigana_scale + fs.name = l.name .. "-furigana" + + styles.n = styles.n + 1 + styles[styles.n] = fs + styles[fs.name] = fs + end + + elseif l.class == "info" then + -- Also look for script resolution + local k = l.key:lower() + if k == "playresx" then + meta.res_x = math.floor(l.value) + elseif k == "playresy" then + meta.res_y = math.floor(l.value) + end + end + end + + -- Fix missing resolution data + if meta.res_x == 0 and meta_res_y == 0 then + meta.res_x = 384 + meta.res_y = 288 + elseif meta.res_x == 0 then + -- This is braindead, but it's how TextSub does things... + if meta.res_y == 1024 then + meta.res_x = 1280 + else + meta.res_x = meta.res_y / 3 * 4 + end + elseif meta.res_y == 0 then + -- As if 1280x960 didn't exist + if meta.res_x == 1280 then + meta.res_y = 1024 + else + meta.res_y = meta.res_x * 3 / 4 + end + end + + return meta, styles +end + + +-- Pre-process line, determining stripped text, karaoke data and splitting off furigana data +-- Modifies the object passed for line +function karaskel.preproc_line_text(meta, styles, line) + -- Assume line is class=dialogue + local kara = aegisub.parse_karaoke_data(line) + line.kara = { n = 0 } + line.furi = { n = 0 } + + line.text_stripped = "" + line.duration = line.end_time - line.start_time + + local worksyl = { } + local cur_inline_fx = "" + for i = 0, #kara do + local syl = kara[i] + + -- Detect any inline-fx tags + local inline_fx = syl.text:match("%{.*\\%-(.-)[}\\]") + if inline_fx then + cur_inline_fx = inline_fx + end + + end +end + + +-- Pre-calculate positioning information for the given line, also layouting furigana text if needed +-- Modifies the object passed for line +function karaskel.preproc_line_pos(meta, styles, line) +end + + +-- Precalc some info on a line +-- Modifies the line parameter +function karaskel.preproc_line(subs, meta, styles, line) + -- Assume line is class=dialogue + local kara = aegisub.parse_karaoke_data(line) + line.kara = { n = 0 } + line.furi = { n = 0 } + + if styles[line.style] then + line.styleref = styles[line.style] + else + aegisub.debug.out(2, "WARNING: Style not found: " .. line.style .. "\n") + line.styleref = styles[1] + end + + line.text_stripped = "" + line.duration = line.end_time - line.start_time + + local curx = 0 + local worksyl = { } + for i = 0, #kara do + local syl = kara[i] + + -- Spaces at the start and end of the syllable are best ignored + local prespace, syltext, postspace = syl.text_stripped:match("^([ \t]*)(.-)([ \t]*)$") + + local prefix = syltext:sub(1,unicode.charwidth(syltext,1)) + if prefix ~= "#" and prefix ~= "#" and i > 0 then + line.kara[line.kara.n] = worksyl + line.kara.n = line.kara.n + 1 + worksyl = { } + end + + -- Check if there is a chance of furigana + -- FIXME: multi-highlights aren't being generated, are they? At least not with syllables with a lone # in + if syltext:find("|") or syltext:find("|") then + syltext = syltext:gsub("|", "|") + local maintext, furitext = syl:match("^(.-)|(.-)$") + syltext = maintext + + local furi = { } + furi.syl = worksyl + + local prefix = furitext:sub(1,unicode.charwidth(furitext,1)) + if prefix == "!" or prefix == "!" then + furi.isbreak = true + furi.spillback = false + elseif prefix == "<" or prefix == "<" then + furi.isbreak = true + furi.spillback = true + else + furi.isbreak = false + furi.spillback = false + end + if furi.isbreak then + furitext = furitext:sub(unicode.charwidth(furitext,1)+1) + end + + furi.start_time = syl.start_time + furi.end_time = syl.end_time + furi.duration = syl.duration + furi.text = furitext + + line.furi.n = line.furi.n + 1 + line.furi[line.furi.n] = furi + end + + -- If this is the start of a highlight group, do regular processing + if prefix ~= "#" and prefix ~= "#" then + -- Update stripped line-text + line.text_stripped = line.text_stripped .. syl.text_stripped + + -- Copy data from syl to worksyl + worksyl.text = syl.text + worksyl.duration = syl.duration + worksyl.kdur = syl.duration / 10 + worksyl.start_time = syl.start_time + worksyl.end_time = syl.end_time + worksyl.tag = syl.tag + worksyl.line = line + worksyl.style = line.styleref + + -- And add new data to worksyl + worksyl.i = line.kara.n + worksyl.text_stripped = syltext + worksyl.width = aegisub.text_extents(line.styleref, syltext) + curx = curx + aegisub.text_extents(line.styleref, prespace) + worksyl.left = curx + worksyl.center = curx + worksyl.width/2 + worksyl.right = curx + worksyl.width + curx = curx + worksyl.width + aegisub.text_extents(line.styleref, postspace) + + -- TODO: inlinefx here + end + + -- And in either case, add highlight data + local hl = { + start_time = worksyl.start_time, + end_time = worksyl.end_time, + duration = worksyl.duration + } + worksyl.highlights = { n = 1, [1] = hl } + end + + -- Add last syllable + line.kara[line.kara.n] = worksyl + + -- Full line sizes + line.width, line.height, line.descent, line.extlead = aegisub.text_extents(line.styleref, line.text_stripped) + -- Effective margins + line.margin_v = line.margin_t + line.eff_margin_l = ((line.margin_l > 0) and line.margin_l) or line.styleref.margin_l + line.eff_margin_r = ((line.margin_r > 0) and line.margin_r) or line.styleref.margin_r + line.eff_margin_t = ((line.margin_t > 0) and line.margin_t) or line.styleref.margin_t + line.eff_margin_b = ((line.margin_b > 0) and line.margin_b) or line.styleref.margin_b + line.eff_margin_v = ((line.margin_v > 0) and line.margin_v) or line.styleref.margin_v + -- And positioning + if line.styleref.align == 1 or line.styleref.align == 4 or line.styleref.align == 7 then + -- Left aligned + line.left = line.eff_margin_l + line.center = line.left + line.width / 2 + line.right = line.left + line.width + line.x = line.left + line.halign = "left" + elseif line.styleref.align == 2 or line.styleref.align == 5 or line.styleref.align == 8 then + -- Centered + line.left = (meta.res_x - line.eff_margin_l - line.eff_margin_r - line.width) / 2 + line.eff_margin_l + line.center = line.left + line.width / 2 + line.right = line.left + line.width + line.x = line.center + line.halign = "center" + elseif line.styleref.align == 3 or line.styleref.align == 6 or line.styleref.align == 9 then + -- Right aligned + line.left = meta.res_x - line.eff_margin_r - line.width + line.center = line.left + line.width / 2 + line.right = line.left + line.width + line.x = line.right + line.halign = "right" + end + line.hcenter = line.center + if line.styleref.align >=1 and line.styleref.align <= 3 then + -- Bottom aligned + line.bottom = meta.res_y - line.eff_margin_b + line.middle = line.bottom - line.height / 2 + line.top = line.bottom - line.height + line.y = line.bottom + line.valign = "bottom" + elseif line.styleref.align >= 4 and line.styleref.align <= 6 then + -- Mid aligned + line.top = (meta.res_y - line.eff_margin_t - line.eff_margin_b) / 2 + line.eff_margin_t + line.middle = line.top + line.height / 2 + line.bottom = line.top + line.height + line.y = line.middle + line.valign = "middle" + elseif line.styleref.align >= 7 and line.styleref.align <= 9 then + -- Top aligned + line.top = line.eff_margin_t + line.middle = line.top + line.height / 2 + line.bottom = line.top + line.height + line.y = line.top + line.valign = "top" + end + line.vcenter = line.middle + + -- Generate furigana style + local furistyle = table.copy(line.styleref) + furistyle.fontsize = furistyle.fontsize / 2 + furistyle.outline = furistyle.outline / 2 + + -- Layout furigana + for i = 1, line.furi.n do + end +end diff --git a/automation/include/karaskel.lua b/automation/include/karaskel.lua index a926bc77d..5c9c903fb 100644 --- a/automation/include/karaskel.lua +++ b/automation/include/karaskel.lua @@ -27,292 +27,10 @@ POSSIBILITY OF SUCH DAMAGE. ]] --- Aegisub Automation 4 Lua karaoke skeleton - --- Compatibility hatch +-- Include the right file, depending on Automation version +-- The Auto4 version has to be in a separate include to avoid parser problems with Lua 5.0 if aegisub.lua_automation_version < 4 then - include("karaskel.auto3") - return -end - -include("utils.lua") -include("unicode.lua") - --- Make sure karaskel table exists -if not karaskel then - karaskel = {} -end - --- Collect styles and metadata from the subs -function karaskel.collect_head(subs, generate_furigana) - local meta = { res_x = 0, res_y = 0 } - local styles = { n = 0 } - - if not karaskel.furigana_scale then - karaskel.furigana_scale = 0.5 - end - - local i = 1 - while i < #subs do - local l = subs[i] - - if l.class == "style" then - -- Store styles into the style table - styles.n = styles.n + 1 - styles[styles.n] = l - styles[l.name] = l - l.margin_v = l.margin_t - - -- And also generate furigana styles if wanted - if generate_furigana and not l.name:match("furigana") then - local fs = table.copy(l) - fs.fontsize = l.fontsize * karaskel.furigana_scale - fs.outline = l.outline * karaskel.furigana_scale - fs.shadow = l.shadow * karaskel.furigana_scale - fs.name = l.name .. "-furigana" - - styles.n = styles.n + 1 - styles[styles.n] = fs - styles[fs.name] = fs - end - - elseif l.class == "info" then - -- Also look for script resolution - local k = l.key:lower() - if k == "playresx" then - meta.res_x = math.floor(l.value) - elseif k == "playresy" then - meta.res_y = math.floor(l.value) - end - end - end - - -- Fix missing resolution data - if meta.res_x == 0 and meta_res_y == 0 then - meta.res_x = 384 - meta.res_y = 288 - elseif meta.res_x == 0 then - -- This is braindead, but it's how TextSub does things... - if meta.res_y == 1024 then - meta.res_x = 1280 - else - meta.res_x = meta.res_y / 3 * 4 - end - elseif meta.res_y == 0 then - -- As if 1280x960 didn't exist - if meta.res_x == 1280 then - meta.res_y = 1024 - else - meta.res_y = meta.res_x * 3 / 4 - end - end - - return meta, styles -end - - --- Pre-process line, determining stripped text, karaoke data and splitting off furigana data --- Modifies the object passed for line -function karaskel.preproc_line_text(meta, styles, line) - -- Assume line is class=dialogue - local kara = aegisub.parse_karaoke_data(line) - line.kara = { n = 0 } - line.furi = { n = 0 } - - line.text_stripped = "" - line.duration = line.end_time - line.start_time - - local worksyl = { } - local cur_inline_fx = "" - for i = 0, #kara do - local syl = kara[i] - - -- Detect any inline-fx tags - local inline_fx = syl.text:match("%{.*\\%-(.-)[}\\]") - if inline_fx then - cur_inline_fx = inline_fx - end - - end -end - - --- Pre-calculate positioning information for the given line, also layouting furigana text if needed --- Modifies the object passed for line -function karaskel.preproc_line_pos(meta, styles, line) -end - - --- Precalc some info on a line --- Modifies the line parameter -function karaskel.preproc_line(subs, meta, styles, line) - -- Assume line is class=dialogue - local kara = aegisub.parse_karaoke_data(line) - line.kara = { n = 0 } - line.furi = { n = 0 } - - if styles[line.style] then - line.styleref = styles[line.style] - else - aegisub.debug.out(2, "WARNING: Style not found: " .. line.style .. "\n") - line.styleref = styles[1] - end - - line.text_stripped = "" - line.duration = line.end_time - line.start_time - - local curx = 0 - local worksyl = { } - for i = 0, #kara do - local syl = kara[i] - - -- Spaces at the start and end of the syllable are best ignored - local prespace, syltext, postspace = syl.text_stripped:match("^([ \t]*)(.-)([ \t]*)$") - - local prefix = syltext:sub(1,unicode.charwidth(syltext,1)) - if prefix ~= "#" and prefix ~= "#" and i > 0 then - line.kara[line.kara.n] = worksyl - line.kara.n = line.kara.n + 1 - worksyl = { } - end - - -- Check if there is a chance of furigana - -- FIXME: multi-highlights aren't being generated, are they? At least not with syllables with a lone # in - if syltext:find("|") or syltext:find("|") then - syltext = syltext:gsub("|", "|") - local maintext, furitext = syl:match("^(.-)|(.-)$") - syltext = maintext - - local furi = { } - furi.syl = worksyl - - local prefix = furitext:sub(1,unicode.charwidth(furitext,1)) - if prefix == "!" or prefix == "!" then - furi.isbreak = true - furi.spillback = false - elseif prefix == "<" or prefix == "<" then - furi.isbreak = true - furi.spillback = true - else - furi.isbreak = false - furi.spillback = false - end - if furi.isbreak then - furitext = furitext:sub(unicode.charwidth(furitext,1)+1) - end - - furi.start_time = syl.start_time - furi.end_time = syl.end_time - furi.duration = syl.duration - furi.text = furitext - - line.furi.n = line.furi.n + 1 - line.furi[line.furi.n] = furi - end - - -- If this is the start of a highlight group, do regular processing - if prefix ~= "#" and prefix ~= "#" then - -- Update stripped line-text - line.text_stripped = line.text_stripped .. syl.text_stripped - - -- Copy data from syl to worksyl - worksyl.text = syl.text - worksyl.duration = syl.duration - worksyl.kdur = syl.duration / 10 - worksyl.start_time = syl.start_time - worksyl.end_time = syl.end_time - worksyl.tag = syl.tag - worksyl.line = line - worksyl.style = line.styleref - - -- And add new data to worksyl - worksyl.i = line.kara.n - worksyl.text_stripped = syltext - worksyl.width = aegisub.text_extents(line.styleref, syltext) - curx = curx + aegisub.text_extents(line.styleref, prespace) - worksyl.left = curx - worksyl.center = curx + worksyl.width/2 - worksyl.right = curx + worksyl.width - curx = curx + worksyl.width + aegisub.text_extents(line.styleref, postspace) - - -- TODO: inlinefx here - end - - -- And in either case, add highlight data - local hl = { - start_time = worksyl.start_time, - end_time = worksyl.end_time, - duration = worksyl.duration - } - worksyl.highlights = { n = 1, [1] = hl } - end - - -- Add last syllable - line.kara[line.kara.n] = worksyl - - -- Full line sizes - line.width, line.height, line.descent, line.extlead = aegisub.text_extents(line.styleref, line.text_stripped) - -- Effective margins - line.margin_v = line.margin_t - line.eff_margin_l = ((line.margin_l > 0) and line.margin_l) or line.styleref.margin_l - line.eff_margin_r = ((line.margin_r > 0) and line.margin_r) or line.styleref.margin_r - line.eff_margin_t = ((line.margin_t > 0) and line.margin_t) or line.styleref.margin_t - line.eff_margin_b = ((line.margin_b > 0) and line.margin_b) or line.styleref.margin_b - line.eff_margin_v = ((line.margin_v > 0) and line.margin_v) or line.styleref.margin_v - -- And positioning - if line.styleref.align == 1 or line.styleref.align == 4 or line.styleref.align == 7 then - -- Left aligned - line.left = line.eff_margin_l - line.center = line.left + line.width / 2 - line.right = line.left + line.width - line.x = line.left - line.halign = "left" - elseif line.styleref.align == 2 or line.styleref.align == 5 or line.styleref.align == 8 then - -- Centered - line.left = (meta.res_x - line.eff_margin_l - line.eff_margin_r - line.width) / 2 + line.eff_margin_l - line.center = line.left + line.width / 2 - line.right = line.left + line.width - line.x = line.center - line.halign = "center" - elseif line.styleref.align == 3 or line.styleref.align == 6 or line.styleref.align == 9 then - -- Right aligned - line.left = meta.res_x - line.eff_margin_r - line.width - line.center = line.left + line.width / 2 - line.right = line.left + line.width - line.x = line.right - line.halign = "right" - end - line.hcenter = line.center - if line.styleref.align >=1 and line.styleref.align <= 3 then - -- Bottom aligned - line.bottom = meta.res_y - line.eff_margin_b - line.middle = line.bottom - line.height / 2 - line.top = line.bottom - line.height - line.y = line.bottom - line.valign = "bottom" - elseif line.styleref.align >= 4 and line.styleref.align <= 6 then - -- Mid aligned - line.top = (meta.res_y - line.eff_margin_t - line.eff_margin_b) / 2 + line.eff_margin_t - line.middle = line.top + line.height / 2 - line.bottom = line.top + line.height - line.y = line.middle - line.valign = "middle" - elseif line.styleref.align >= 7 and line.styleref.align <= 9 then - -- Top aligned - line.top = line.eff_margin_t - line.middle = line.top + line.height / 2 - line.bottom = line.top + line.height - line.y = line.top - line.valign = "top" - end - line.vcenter = line.middle - - -- Generate furigana style - local furistyle = table.copy(line.styleref) - furistyle.fontsize = furistyle.fontsize / 2 - furistyle.outline = furistyle.outline / 2 - - -- Layout furigana - for i = 1, line.furi.n do - end + include "karaskel.auto3" +else + include "karaskel-auto4.lua" end diff --git a/automation/include/utils-auto4.lua b/automation/include/utils-auto4.lua new file mode 100644 index 000000000..a193bfaf7 --- /dev/null +++ b/automation/include/utils-auto4.lua @@ -0,0 +1,247 @@ +--[[ + Copyright (c) 2005-2006, Niels Martin Hansen, Rodrigo Braz Monteiro + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Aegisub Group nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + 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 + +-- Make a deep copy of a table +-- This will do infinite recursion if there's any circular references. (Eg. if you try to copy _G) +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) + local newtab = {} + for key, val in pairs(newtab) do + if type(val) == "table" then + if not circular[val] then + circular[val] = do_copy(val) + end + newtab[key] = circular[val] + else + newtab[key] = val + end + 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 or 0, 16), tonumber(g or 0, 16), tonumber(b or 0, 16), tonumber(a or 0, 16) + 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) +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, g, b) +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 + + r = math.floor(r) + g = math.floor(g) + b = math.floor(b) + return r,g,b +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(first) + 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(first) + return ass_alpha(interpolate(pct, a1, a2)) +end diff --git a/automation/include/utils.lua b/automation/include/utils.lua index d6b8033cc..c08374426 100644 --- a/automation/include/utils.lua +++ b/automation/include/utils.lua @@ -27,227 +27,10 @@ POSSIBILITY OF SUCH DAMAGE. ]] --- Compatibility hatch +-- Include the right file, depending on Automation version +-- The Auto4 version has to be in a separate include to avoid parser problems with Lua 5.0 if aegisub.lua_automation_version < 4 then include "utils.auto3" - return -end - --- 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 - --- Make a deep copy of a table --- This will do infinite recursion if there's any circular references. (Eg. if you try to copy _G) -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) - local newtab = {} - for key, val in pairs(newtab) do - if type(val) == "table" then - if not circular[val] then - circular[val] = do_copy(val) - end - newtab[key] = circular[val] - else - newtab[key] = val - end - 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 or 0, 16), tonumber(g or 0, 16), tonumber(b or 0, 16), tonumber(a or 0, 16) - 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) -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, g, b) -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 - - r = math.floor(r) - g = math.floor(g) - b = math.floor(b) - return r,g,b -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(first) - 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(first) - return ass_alpha(interpolate(pct, a1, a2)) +else + include "utils-auto4.lua" end