Check-in [58335bd8d7]
Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | prepare potential all-merge mode |
---|---|
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
58335bd8d7b2a0d39e3f5d83fdef7a96 |
User & Date: | mario 2023-05-23 10:10:47 |
Context
2023-11-10
| ||
23:35 | fix `attrib` method name, and translate float detection Leaf check-in: 519aaabdbf user: mario tags: trunk | |
2023-05-23
| ||
10:10 | prepare potential all-merge mode check-in: 58335bd8d7 user: mario tags: trunk | |
2022-10-31
| ||
09:39 | changed spline pacing mode: one less iteration to avert last section stuck at 1.0, reset .frame.args.delay just in case (repeat animations, e.g. fg/bg frames) check-in: 483352ece2 user: mario tags: trunk | |
Changes
Changes to inkscape/export_gif.py.
1 2 3 4 5 6 7 8 9 10 11 | #!/usr/bin/env python # encoding: utf-8 # api: inkscape ##type: effect # category: export # title: GIF slideshow # description: Export and combine layers as animation via ImageMagick/Pillow # id: org.include-once.inkscape.export-gif # license: MITL # version: 1.1 # state: stable | | > > < < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #!/usr/bin/env python # encoding: utf-8 # api: inkscape ##type: effect # category: export # title: GIF slideshow # description: Export and combine layers as animation via ImageMagick/Pillow # id: org.include-once.inkscape.export-gif # license: MITL # version: 1.1 # state: stable # depends: bin:inkscape (>= 1.1), bin:convert, python (>= 3.6) # suggests: python:svgelements >= 1.8 # pylint: disable=line-too-long, missing-module-docstring, bad-whitespace # config: # { name: file, type: file, mode: file, value: "~/anim.gif", description: "Target GIF filename", help: "Output filename should end in .gif for actual results. Alternatively can be `APNG:dir/filename.apng` with ImageMagick 7." } # { name: mode, type: select, select: "PNG→ImageMagick (better quality)|SVG→ImageMagick (simple drawings)|PNG→Pillow (builtin - a bit faster)|JavaScript→SVG (embed anim code)", value: PNG+ImageMagick, description: Conversion mode, help: "There's different backends to assemble the GIF. Pillow also works on Windows without convert.exe installed. The JavaScript mode embeds some code, does not actually generate a GIF output file." } # { name: delay, type: float, value: 0.35, min: 0.01, max: 20, precision: 2, description: "Delay between slides (seconds)", "Can be overriden on a per-slide basis with [--delay=2.5]. Note that convert(1) usually uses »ticks« (*10ms), but this export_gif option is really in seconds." } # { name: loop, type: int, value: 0, description: "Loop limit (0 for endless)" } # [ hidden: 1, disabled: save_as, type: str, value: "0", description: used in saveas_gif.inx, note: "have to use `str` here, cause .inx `false` is not recognized for hidden attributes" ] # { xml: <image width="505" height="50">export_gif.svg</image> } # { name: notebook, type: notebook, value: system, description: "Additional flags and help" } # { category: ImageMagick, name: fuzz, type: select, select: "0%|5%|10%|20%|30%|50%", value: "10%", description: "Fuzzing/dither", help: "Detect similar colors for optimization" } # { category: ImageMagick, name: layers, type: select, select: "optimize|coalesce|compare-any|compare-clear|compare-overlay|composite|dispose|flatten|merge|mosaic|optimize-frame|optimize-plus|optimize-transparency|remove-dups|remove-zero|trim-bounds", value: "optimize", description: "Accumulation/combination of -layers", help: "Can influence optimization or layer combination. Specifically coalesce/merge are pre-interpreted by export_gif to produce accumulating slides." } # { category: ImageMagick, name: extra, type: select, select: "-quiet|-optimize|-size 640x360|-alpha background|-auto-gamma|-auto-level|-coalesce|-colors 64|-dither FloydSteinberg|-limit disk 1MB|-reverse|-monochrome|-transparent white", value: "", description: "Extra args", help: "Some default convert -args." } # { category: ImageMagick, name: extra2, type: str, value: "", description: "Custom args", help: "Specify additional convert(1) options." } # { category: ImageMagick, name: background, type: color, appearance: colorbutton, value: "0", description: "Background color for transparent layers", help: "Only works for SVG→ImageMagick option." } # { category: System, name: preview, type: bool, value: 0, description: "Preview result file", help: "Should bring up default image viewer on resulting GIF (via xdg-open, or start… on Windows)" } # { category: System, name: keep_tmp, type: bool, value: 0, description: "Keep temporary files", help: "Will retain the frame PNGs in the fixed directory /tmp/inkscape.export_gif/" } # { category: System, name: reload_svg, type: bool, value: 0, description: "Reload SVG in Inkscape", help: "Averts the warning popup of lacking result data. But is quite redundant for this tool. And should only be enabled if it's becoming too obnoxious." } # { category: System, name: export_background, type: bool, value: 0, description: "Force background application in PNG export", help: "Uses --export-background for generating PNG slides prior assembly. (See ImageMagick for color tab.)" } # { category: System, name: default_merge, type: bool, value: 0, description: "Default all layers to [merge] - not implemented yet", help: "Requires to annotate actual slides with [background] or [export], because it defaults everything else to down-grouping [merge]" } # { category: Animation, name: subframes, type: int, value: 5, description: "Subframes per ❮animation❯ slide", help: "Can be overridden per [animate=25] or [steps=25] in layer label." } # { category: Animation, name: a_rotate, type: bool, value: 1, description: "Use simpler rotate() handler", help: "Actually works better than tweening, and allows for >90° rotations without collapsing the matrix. OTOH the inkex method might handle positioning better." } # { category: Animation, name: all_anim, type: bool, value: 0, description: "Engage whenever SVG animate instructions are present", help: "Makes [animate] tags redundant, at the expense of longer processing times; and repeat runs for background layers." } # { category: Animation, name: all_pace, type: bool, value: 0, description: "Honor timing information for all slides", help: "Honor begin=, dur=, and some calcMode= settings, for pacing or delayed timing. Otherwise individual layers can be marked with [pace] or [smooth] in addition to [animate]. Else animations run/stretch across the alloted delay time for a slide." } # { category: Help, label: "Layer labels (Ctrl+Shift+L, double click) can specify additional options:" } # { category: Help, label: " 🞂 [fixed] for very permanent foreground layer" } # { category: Help, label: " 🞂 [background] for sticky background images" } # { category: Help, label: " 🞂 [merge] enjoins partial layers; and [exclude] skips them" } # { category: Help, label: " 🞂 [animate=10] generates ❮animate*❯ subframes, flag: [pace] timing" } # { category: Help, label: " 🞂 [--delay=2.5] resets ImageMagick option (delay also for Pillow)", # { category: Help, label: $help, appearance: url } # inx-export: .gif, image/gif, GIF slideshow (*.gif), Graphics Interchange Format 98a # architecture: all # pack: export_gif.py, *.inx, pmd2inks, animate_yo.py, svgelements.py, export_gif.svg, LICENSE=/usr/share/doc/inkscape-export-gif/copyright # permissive: 0.5 # format: off # author: mario#include-once:org # url: https://inkscape.org/~culturaljuice/★export_gif # help: https://fossil.include-once.org/scripts/wiki/inkscape # orig: https://github.com/jespino/inkscape-export-layers # # Generates a GIF slideshow from image layers ☰ (Shift+Ctrl+L). The menu # entry Extensions➜Export➔GIF-slideshow… iterates over layers to generate # animation slides. They get merged from bottom to top. Depending on shape # overlap or layer transparency, there's different usage modes: # # 🞂 With a solid background in each layer, there's no accumulation to # take care of. # 🞂 Alternatively elect layers can be labeled as [background] to stick, # or [fixed] as permanent foreground, or [merge] for down-grouping. # 🞂 If the -layers option is composite/coalesce/merge then all lower # layers remain visible until the current frame. # 🞂 Any [animate] layers get interpolated as is, layer transparency # preserved. # # Requires ImageMagick installed; fit for standard Linux setups. But might |
︙ | ︙ | |||
93 94 95 96 97 98 99 100 101 102 103 104 105 106 | # labels for more refined combinations. # 🞂 Can't run two instances in parallel, due to fixed /tmp/inkscape… dir # 🞂 It's also available as File > Save As... option (fewer options). # 🞂 Tested on Inkscape 1.2 (2022-05-xx) and 1.3-dev (2022-09-xx) # # Might require some experimentation, and heavily utilizing the preview # option. Potential error popups become informative at the very end. # # Code is derived from Xavi's "Export Layers Redux For Inkscape 1.1+". # <https://inkscape.org/~VagabondArcade/%E2%98%85export-layers-redux-for-inkscape-11> # # ZIP contains the `pmd2inks` command-line tool to ease extending the # options dialog (using the <https://pypi.org/project/pluginconf/> format). | > > | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | # labels for more refined combinations. # 🞂 Can't run two instances in parallel, due to fixed /tmp/inkscape… dir # 🞂 It's also available as File > Save As... option (fewer options). # 🞂 Tested on Inkscape 1.2 (2022-05-xx) and 1.3-dev (2022-09-xx) # # Might require some experimentation, and heavily utilizing the preview # option. Potential error popups become informative at the very end. # The save as dialog ".gif" option provides fewer options, but effectively # same behaviours for layer flags. # # Code is derived from Xavi's "Export Layers Redux For Inkscape 1.1+". # <https://inkscape.org/~VagabondArcade/%E2%98%85export-layers-redux-for-inkscape-11> # # ZIP contains the `pmd2inks` command-line tool to ease extending the # options dialog (using the <https://pypi.org/project/pluginconf/> format). |
︙ | ︙ | |||
118 119 120 121 122 123 124 | import math import inkex from inkex.utils import strargs from inkex.tween import StyleInterpolator, TransformInterpolator from inkex.transforms import Transform | | | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | import math import inkex from inkex.utils import strargs from inkex.tween import StyleInterpolator, TransformInterpolator from inkex.transforms import Transform class GIFExport(inkex.EffectExtension):#, inkex.OutputExtension (for export pane):#, inkex.RasterOutputExtension (only works for static PNG conversion) """ doubles as effect and save-as extension """ multi_inx = True def __init__(self): """ init the effect library """ self.tempdir = None self.index = 0 # current slide |
︙ | ︙ | |||
146 147 148 149 150 151 152 153 154 155 156 157 158 159 | pars.add_argument("--extra", type=str, dest="extra", default="", help="Extra args") pars.add_argument("--extra2", type=str, dest="extra2", default="", help="Custom args") pars.add_argument("--background", type=inkex.Color, dest="background", default="0", help="Background color for transparent layers") pars.add_argument("--preview", type=inkex.Boolean, dest="preview", default=False, help="Preview result file") pars.add_argument("--keep_tmp", type=inkex.Boolean, dest="keep_tmp", default=False, help="Keep temporary files") pars.add_argument("--reload_svg", type=inkex.Boolean, dest="reload_svg", default=False, help="Reload SVG in Inkscape") pars.add_argument("--export_background", type=inkex.Boolean, dest="export_background", default=False, help="Force background application in PNG export") pars.add_argument("--subframes", type=int, dest="subframes", default=5, help="Subframes per ❮animation❯ slide") pars.add_argument("--a_rotate", type=inkex.Boolean, dest="a_rotate", default=True, help="Use simpler rotate() handler") pars.add_argument("--all_anim", type=inkex.Boolean, dest="all_anim", default=False, help="Engage whenever SVG animate instructions are present") pars.add_argument("--all_pace", type=inkex.Boolean, dest="all_pace", default=False, help="Honor timing information for all slides") # only used in saveas_gif.inx (but not export_gif.inx, else hidden argument would be populated from preferences.xml history) pars.add_argument("--save_as", type=bool, dest="save_as", default=False, help="used in saveas_gif.inx") | > | 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | pars.add_argument("--extra", type=str, dest="extra", default="", help="Extra args") pars.add_argument("--extra2", type=str, dest="extra2", default="", help="Custom args") pars.add_argument("--background", type=inkex.Color, dest="background", default="0", help="Background color for transparent layers") pars.add_argument("--preview", type=inkex.Boolean, dest="preview", default=False, help="Preview result file") pars.add_argument("--keep_tmp", type=inkex.Boolean, dest="keep_tmp", default=False, help="Keep temporary files") pars.add_argument("--reload_svg", type=inkex.Boolean, dest="reload_svg", default=False, help="Reload SVG in Inkscape") pars.add_argument("--export_background", type=inkex.Boolean, dest="export_background", default=False, help="Force background application in PNG export") pars.add_argument("--default_merge", type=inkex.Boolean, dest="default_merge", default=False, help="Default all layers to [merge] - not implemented yet") pars.add_argument("--subframes", type=int, dest="subframes", default=5, help="Subframes per ❮animation❯ slide") pars.add_argument("--a_rotate", type=inkex.Boolean, dest="a_rotate", default=True, help="Use simpler rotate() handler") pars.add_argument("--all_anim", type=inkex.Boolean, dest="all_anim", default=False, help="Engage whenever SVG animate instructions are present") pars.add_argument("--all_pace", type=inkex.Boolean, dest="all_pace", default=False, help="Honor timing information for all slides") # only used in saveas_gif.inx (but not export_gif.inx, else hidden argument would be populated from preferences.xml history) pars.add_argument("--save_as", type=bool, dest="save_as", default=False, help="used in saveas_gif.inx") |
︙ | ︙ | |||
187 188 189 190 191 192 193 | up_until_mode = re.search("merge|compo|coal|plus", self.options.layers) preconvert_png = re.search("PNG", self.options.mode, re.I) img_ext = "png" if preconvert_png else "svg" # traverse layers, iteratively slicing for individual .svg/.png frames for self.index, layer in enumerate(layers): | | | | 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | up_until_mode = re.search("merge|compo|coal|plus", self.options.layers) preconvert_png = re.search("PNG", self.options.mode, re.I) img_ext = "png" if preconvert_png else "svg" # traverse layers, iteratively slicing for individual .svg/.png frames for self.index, layer in enumerate(layers): if layer.no_frame: # [fixed], [exclude] continue if layer.is_sticky or up_until_mode: fixed_layers.append(layer.id) merge_layers = [ follow.id for follow in layers[self.index+1:] if follow.do_merge ] if layer.animate or self.is_animatable(layer): # produces multiple subframe .pngs anim = AnimationSteps( parent = self, layer = layer, svg = self.export_layers(dest="", show=[layer.id] + fixed_layers + merge_layers) ) temp_files_args.extend(list(anim.export())) else: # current frame into .svg/.png |
︙ | ︙ | |||
350 351 352 353 354 355 356 357 358 359 360 361 362 363 | def get_layers(self): """ Per default all layers are exported, label tags [bg], [exclude], [merge] yield special treatment """ for layer in self.svg.xpath('./svg:g[@inkscape:groupmode="layer"][@inkscape:label]'): label = layer.attrib["{http://www.inkscape.org/namespaces/inkscape}label"] animate = re.findall(r"\[(?:steps|animate)(?:=(\d+))?\]", label) yield Namespace( id = layer.attrib["id"], label = re.sub(r"\W+", "_", label), is_fixed = bool(re.search(r"fixed|\[(is_?fixe?d?|foreground)\]", label)), is_sticky = bool(re.search(r"background|\[(bg)\]", label)), no_frame = bool(re.search(r"fixed|\[(merge|no_frame|exclude)\]", label)), | > > > > > | 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 | def get_layers(self): """ Per default all layers are exported, label tags [bg], [exclude], [merge] yield special treatment """ for layer in self.svg.xpath('./svg:g[@inkscape:groupmode="layer"][@inkscape:label]'): label = layer.attrib["{http://www.inkscape.org/namespaces/inkscape}label"] animate = re.findall(r"\[(?:steps|animate)(?:=(\d+))?\]", label) # all-merge mode? is_export = bool(re.search(r"background|fixed|\[(bg|export|foreground)\]", label)), # no_frame = … or self.options.default_merge # do_merge = self.options.default_merge and not is_export yield Namespace( id = layer.attrib["id"], label = re.sub(r"\W+", "_", label), is_fixed = bool(re.search(r"fixed|\[(is_?fixe?d?|foreground)\]", label)), is_sticky = bool(re.search(r"background|\[(bg)\]", label)), no_frame = bool(re.search(r"fixed|\[(merge|no_frame|exclude)\]", label)), |
︙ | ︙ | |||
421 422 423 424 425 426 427 | ) # return modified SVG tree inkex.base.SvgOutputMixin.save(self, self.options.output) def animate_with_values(func): """ Decorator: unpacks values=1;2;3 into to= invocation of chained apply() steps. | | | < | 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 | ) # return modified SVG tree inkex.base.SvgOutputMixin.save(self, self.options.output) def animate_with_values(func): """ Decorator: unpacks values=1;2;3 into to= invocation of chained apply() steps. The time= parameter is evenly split across partial applicators - the assumption being that pace() already coralls keyTimes= into linear intervals. """ @functools.wraps(func) def wrapped(self, anim, **attrib): if "values" not in attrib: return func(self, anim, **attrib) # prepare multiple applicators, with keyTimes pre-mapped into indexable range |
︙ | ︙ |