Check-in [f99254a9be]
Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Add global options for subframes, [pace] and custom _rotate handler. |
---|---|
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
f99254a9be15383c429e2df1f4420a25 |
User & Date: | mario 2022-10-05 15:34:29 |
Context
2022-10-05
| ||
18:03 | float(delay) cast for im check-in: 6489cd92f6 user: mario tags: trunk | |
15:34 | Add global options for subframes, [pace] and custom _rotate handler. check-in: f99254a9be user: mario tags: trunk | |
07:15 | Add @animate_with_values decorator for animate_style at least (nobody is ever going to engage this, but it's a "cheap" feature). Fix iteration range (0.0……1.0) by limiting on (frames - 1). Also introduce deco graphics for options dialog. check-in: e2ba243817 user: mario tags: trunk | |
Changes
Changes to inkscape/export_gif.inx.
1 2 3 4 | <?xml version="1.0" encoding="UTF-8"?> <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> <name>GIF slideshow</name> <description>Export and combine layers as animation using ImageMagick</description> | | | 1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="UTF-8"?> <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> <name>GIF slideshow</name> <description>Export and combine layers as animation using ImageMagick</description> <!--<schema:softwareVersion xmlns:schema="https://schema.org/">1.0-rc1</schema:softwareVersion>--> <category>Export</category> <id>org.include-once.inkscape.export-gif</id> <dependency type="executable" location="inx">export_gif.py</dependency> <label appearance="header">Export and combine layers as animation using ImageMagick</label> <param name="file" type="path" mode="file" gui-description="Output filename" gui-text="Target GIF filename">~/anim.gif</param> <param name="mode" type="optiongroup" appearance="combo" gui-description="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." gui-text="Conversion mode"> <option value="PNG→ImageMagick (better quality)">PNG→ImageMagick (better quality)</option> |
︙ | ︙ | |||
65 66 67 68 69 70 71 | <page name="System" gui-text="System"> <param name="preview" type="bool" mode="checkbox" gui-description="Should bring up default image viewer on resulting GIF (via xdg-open, or start… on Windows)" gui-text="Preview result file">false</param> <param name="keep_tmp" type="bool" mode="checkbox" gui-description="Will retain the frame PNGs in the fixed directory /tmp/inkscape.export_gif/" gui-text="Keep temporary files">false</param> <param name="reload_svg" type="bool" mode="checkbox" gui-description="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." gui-text="Reload SVG in Inkscape">false</param> </page> <page name="Animation" gui-text="Animation"> <param name="subframes" type="int" min="0" max="100" precision="1" gui-description="Can be overridden per [animate=25] or [steps=25] in layer label." gui-text="Subframes per ❮animation❯ slide">5</param> | > > | | | | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | <page name="System" gui-text="System"> <param name="preview" type="bool" mode="checkbox" gui-description="Should bring up default image viewer on resulting GIF (via xdg-open, or start… on Windows)" gui-text="Preview result file">false</param> <param name="keep_tmp" type="bool" mode="checkbox" gui-description="Will retain the frame PNGs in the fixed directory /tmp/inkscape.export_gif/" gui-text="Keep temporary files">false</param> <param name="reload_svg" type="bool" mode="checkbox" gui-description="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." gui-text="Reload SVG in Inkscape">false</param> </page> <page name="Animation" gui-text="Animation"> <param name="subframes" type="int" min="0" max="100" precision="1" gui-description="Can be overridden per [animate=25] or [steps=25] in layer label." gui-text="Subframes per ❮animation❯ slide">5</param> <param name="a_rotate" type="bool" mode="checkbox" gui-description="Actually works better than tweening, and allows for >90° rotations without collapsing the matrix. OTOH the inkex method might handle positioning better." gui-text="Use simpler rotate() handler">true</param> <param name="all_anim" type="bool" mode="checkbox" gui-description="Makes [animate] tags redundant, at the expense of longer processing times; and repeat runs for background layers." gui-text="Engage whenever SVG animate instructions are present">false</param> <param name="all_pace" type="bool" mode="checkbox" gui-description="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." gui-text="Honor timing information for all slides">false</param> </page> <page name="Help" gui-text="Help"> <label>Layer labels (Ctrl+Shift+L, double click) can specify additional options:</label> <label>🞂 [fixed] for very permanent foreground layer</label> <label>🞂 [background] for sticky background images</label> <label>🞂 [merge] enjoins partial layers; and [exclude] skips them</label> <label>🞂 [animate=10] generates ❮animate*❯ subframes, flag: [pace] timing</label> <label appearance="url">https://fossil.include-once.org/scripts/wiki/inkscape</label> </page> </param> <effect needs-live-preview="false"> <object-type>all</object-type> <effects-menu> <submenu name="Export"/> |
︙ | ︙ | |||
108 109 110 111 112 113 114 | 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("-"+"-subframes", type=int, dest="subframes", default=5, help="Subframes per ❮animation❯ slide") | > > | | 110 111 112 113 114 115 116 117 118 119 120 121 | 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("-"+"-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") --> </inkscape-extension> |
Changes to inkscape/export_gif.py.
1 2 3 4 5 6 7 8 9 | #!/usr/bin/env python # encoding: utf-8 # api: inkscape ##type: effect # category: export # title: GIF slideshow # description: Export and combine layers as animation using ImageMagick # id: org.include-once.inkscape.export-gif # license: MITL | | > > | | | | | 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 | #!/usr/bin/env python # encoding: utf-8 # api: inkscape ##type: effect # category: export # title: GIF slideshow # description: Export and combine layers as animation using ImageMagick # id: org.include-once.inkscape.export-gif # license: MITL # version: 1.0-rc1 # state: beta # depends: bin:inkscape (>= 1.1), bin:convert, python (>= 3.6), python:svgelements # 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" } # { 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|-resize 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: 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 # format: off # author: mario#include-once:org # url: https://inkscape.org/~culturaljuice/★export_gif # help: https://fossil.include-once.org/scripts/wiki/inkscape # orig: Xavi, https://github.com/jespino/inkscape-export-layers # # Generates a GIF slideshow from image layers ☰ (Shift+Ctrl+L). The menu |
︙ | ︙ | |||
117 118 119 120 121 122 123 124 125 126 127 128 129 130 | from argparse import Namespace import re import functools import textwrap import shlex import math import inkex def convert_png(svg_path, output_path): """ Use the convenience wrapper (more likely referencing the current inkscape binary) """ inkex.command.inkscape( svg_path, export_area_page=True, export_type="png", export_filename=output_path, #export_text_to_path=True, | > > | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | from argparse import Namespace import re import functools import textwrap import shlex import math import inkex import inkex.tween import inkex.transforms def convert_png(svg_path, output_path): """ Use the convenience wrapper (more likely referencing the current inkscape binary) """ inkex.command.inkscape( svg_path, export_area_page=True, export_type="png", export_filename=output_path, #export_text_to_path=True, |
︙ | ︙ | |||
165 166 167 168 169 170 171 | 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("--subframes", type=int, dest="subframes", default=5, help="Subframes per ❮animation❯ slide") | > > | | 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | 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("--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="Test for SVG animate instructions to engage") 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") def make_temp(self): """ separate temp directory for easier cleanup """ self.tempdir = tempfile.gettempdir() + "/inkscape.export_gif" if not os.path.exists(self.tempdir): |
︙ | ︙ | |||
212 213 214 215 216 217 218 | 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 ] | | | 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | 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 subframes anim = AnimationSteps( parent = self, layer = layer, svg = self.export_layers(dest="", show=[layer.id] + fixed_layers + merge_layers) ) temp_files_args.extend(list(anim.steps())) else: |
︙ | ︙ | |||
301 302 303 304 305 306 307 308 309 310 311 312 313 314 | """ remove tmp files + dir, automatically invoked by base.run() """ super().clean_up() if self.options.keep_tmp or not self.tempdir or not os.path.exists(self.tempdir): return for file_name in os.listdir(self.tempdir): os.unlink(self.tempdir + "/" + file_name) os.rmdir(self.tempdir) def export_layers(self, dest, show): """ Export selected layers of SVG to the `dest` file. Implicitly converting if requested. :arg str dest: path to export SVG or PNG file. :arg list show: layers to show. each element is a string. """ | > > > > > > > > | 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | """ remove tmp files + dir, automatically invoked by base.run() """ super().clean_up() if self.options.keep_tmp or not self.tempdir or not os.path.exists(self.tempdir): return for file_name in os.listdir(self.tempdir): os.unlink(self.tempdir + "/" + file_name) os.rmdir(self.tempdir) def is_animatable(self, layer): """ Check for presence of <animate*> tags in current frame """ if self.options.all_anim: if self.svg.xpath(f"""count(//svg:g[@id="{layer.id}"]//*[starts-with(local-name(),"animate")])"""): # `0.0` if none layer.animate = [str(self.options.subframes)] return True return False def export_layers(self, dest, show): """ Export selected layers of SVG to the `dest` file. Implicitly converting if requested. :arg str dest: path to export SVG or PNG file. :arg list show: layers to show. each element is a string. """ |
︙ | ︙ | |||
403 404 405 406 407 408 409 | 1 ) ) # return modified SVG tree inkex.base.SvgOutputMixin.save(self, self.options.output) def animate_with_values(func): | > | > > > > | 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 | 1 ) ) # 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 e.g. keyTimes= into linear intervals. (Has to be defined prior AnimationSteps.) """ @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 values = re.split(r"\s*;\s*", attrib["values"]) |
︙ | ︙ | |||
438 439 440 441 442 443 444 | doc: https://edutechwiki.unige.ch/en/Using_Inkscape_for_web_animation, https://wiki.inkscape.org/wiki/index.php/SVG_Animation """ def __init__(self, parent, svg, layer): """ inherit from GifExport, and main layer loop """ | < > | > > | 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 | doc: https://edutechwiki.unige.ch/en/Using_Inkscape_for_web_animation, https://wiki.inkscape.org/wiki/index.php/SVG_Animation """ def __init__(self, parent, svg, layer): """ inherit from GifExport, and main layer loop """ self.gif = parent self.svg = svg # ← already a deepcopy self.layer = layer #raise Exception(layer.animate) if layer.animate and re.match(r"^\d+$", layer.animate[0]): self.frames = int(layer.animate[0]) else: self.frames = self.gif.options.subframes self.delay = float(layer.args.get("delay", self.gif.options.delay)) self.layer.args.update({ "delay": self.delay / self.frames # frame time split between animation steps }) #raise Exception(self.frames) def steps(self): |
︙ | ︙ | |||
477 478 479 480 481 482 483 | @animate_with_values def animate_style(self, anim, attributeName, to, **attrib): """ <animate to="#000000" attributeName="fill" /> """ target = self.get_target(anim) #self.adapt(target, css={attributeName: attrib["from"]}) transformed = self.adapt( copy.deepcopy(target), | | | | | | > > | | | < < > > > > > > > > > > > > > > | 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | @animate_with_values def animate_style(self, anim, attributeName, to, **attrib): """ <animate to="#000000" attributeName="fill" /> """ target = self.get_target(anim) #self.adapt(target, css={attributeName: attrib["from"]}) transformed = self.adapt( copy.deepcopy(target), css = { attributeName: re.sub(r"^(#\w{6}).+", r"\1", to) } # transparency #rrggbbAA isn't helping tween.Style ) inter = inkex.tween.StyleInterpolator(target, transformed) def apply(time, target=target, tween=inter): self.adapt(target, css=tween.interpolate(time)) return apply #@animate_with_values def animate_transform(self, anim, **attrib): """ <animateTransform to="360 0 40" attributeName="transform" /> """ target = self.get_target(anim) _type = attrib.get("type", "translate") defaults = { "rotate": "0", # rotate works up until 90 degrees, at 180 starts to just scale "translate": "0,0", "scale": "1,1", "matrix": "0,0,0,0,0,0", "skewX": "0", "skewY": "0", } if _type == "rotate" and self.gif.options.a_rotate: return self.animate_rotate(target, anim, attrib.get("from"), attrib.get("to")) src = inkex.transforms.Transform(_type + "(" + attrib.get("from", defaults.get(_type, "0,0")) + ")") dest = inkex.transforms.Transform(_type + "(" + attrib["to"] + ")") inter = inkex.tween.TransformInterpolator(src, dest) def apply(time, target=target, tween=inter): self.adapt(target, transform=tween.interpolate(time)) return apply def animate_rotate(self, target, anim, start, end): """ simpler <animateTransform type=rotate> handler, does not fold the matrix at 180° """ start = inkex.utils.strargs(start) or [0] end = inkex.utils.strargs(end) or [0] sfx = [str(coord) for coord in (end[1:] + start[1:])] def apply(time, start=start[0], end=end[0], sfx=" ".join(sfx[0:2])): deg = start + (end - start) * time self.adapt( target, transform=inkex.transforms.Transform(f"rotate({deg} {sfx})") ) return apply def animate_motion(self, anim, path, **attrib): # pylint: disable=invalid-name """ <animateMotion path="M10,20" /> """ target = self.get_target(anim) orig_transform = str(target.transform) # prepend in adapt #if inkex.paths.Path(path)[0].is_relative: |
︙ | ︙ | |||
568 569 570 571 572 573 574 | return self.svg.getElementById(animation.attrib["xlink:href"][1:]) else: return animation.getparent() # pylint: disable=function-redefined, no-else-return, bare-except def pace(self, anim, smooth=False): """ | | > > | | 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 | return self.svg.getElementById(animation.attrib["xlink:href"][1:]) else: return animation.getparent() # pylint: disable=function-redefined, no-else-return, bare-except def pace(self, anim, smooth=False): """ Some relative time slice folding into linear 0.0 … 1.0 frame interval. Might be able to recognize some of: begin, dur, keyTimes, calcMode, fill, etc. Simply chains bounding functions. Enabled on [pace],[time],[smooth] label flags. (Global enable option would probably make more sense, right now we just have a disable flag.... """ attr = anim.attrib.get r_begin = self.seconds(attr("begin")) / self.delay r_end = r_begin + self.seconds(attr("dur"), self.delay) / self.delay def vary(r_time): """ no transform """ return r_time if not {"pace", "time", "smooth"} & set(self.layer.tags) or not self.gif.options.all_pace: return vary if r_begin or r_end: def vary(r_time, vary=vary): """ shift and scale """ if r_time < r_begin: return 0.0 |
︙ | ︙ |
Changes to inkscape/export_gif.svg.
︙ | ︙ | |||
22 23 24 25 26 27 28 | inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="1" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" showgrid="false" showguides="true" | | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="1" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" showgrid="false" showguides="true" inkscape:zoom="19.02887" inkscape:cx="419.44161" inkscape:cy="25.960553" inkscape:window-width="1920" inkscape:window-height="1008" inkscape:window-x="0" inkscape:window-y="25" inkscape:window-maximized="1" inkscape:current-layer="layer1" /><defs id="defs1"><linearGradient |
︙ | ︙ | |||
47 48 49 50 51 52 53 | clipPathUnits="userSpaceOnUse" id="clipPath17"><path id="path17" style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none" d="m 53.782227,2.249602 a 4.3656245,4.3656245 0 0 0 -4.365625,4.365625 4.3656245,4.3656245 0 0 0 4.365625,4.365626 4.3656245,4.3656245 0 0 0 4.365625,-4.365626 4.3656245,4.3656245 0 0 0 -4.365625,-4.365625 z m 0,1.8107422 A 2.5549657,2.5549657 0 0 1 56.33711,6.615227 2.5549657,2.5549657 0 0 1 53.782227,9.1701095 2.5549657,2.5549657 0 0 1 51.227344,6.615227 2.5549657,2.5549657 0 0 1 53.782227,4.0603442 Z" /></clipPath><linearGradient inkscape:collect="always" xlink:href="#linearGradient18" | < < < < < < < < | > | > | > | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | clipPathUnits="userSpaceOnUse" id="clipPath17"><path id="path17" style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-dasharray:none" d="m 53.782227,2.249602 a 4.3656245,4.3656245 0 0 0 -4.365625,4.365625 4.3656245,4.3656245 0 0 0 4.365625,4.365626 4.3656245,4.3656245 0 0 0 4.365625,-4.365626 4.3656245,4.3656245 0 0 0 -4.365625,-4.365625 z m 0,1.8107422 A 2.5549657,2.5549657 0 0 1 56.33711,6.615227 2.5549657,2.5549657 0 0 1 53.782227,9.1701095 2.5549657,2.5549657 0 0 1 51.227344,6.615227 2.5549657,2.5549657 0 0 1 53.782227,4.0603442 Z" /></clipPath><linearGradient inkscape:collect="always" xlink:href="#linearGradient18" id="linearGradient25" gradientUnits="userSpaceOnUse" x1="82.891304" y1="11.076679" x2="50.925728" y2="-7.18854" gradientTransform="matrix(0.78717788,0,0,0.78717788,17.870299,1.3305908)" /><linearGradient inkscape:collect="always" xlink:href="#linearGradient18" id="linearGradient26" gradientUnits="userSpaceOnUse" x1="82.891304" y1="11.076679" x2="50.925728" y2="-7.18854" gradientTransform="matrix(0.78717788,0,0,0.78717788,17.870299,1.3305908)" /><linearGradient inkscape:collect="always" xlink:href="#linearGradient18" id="linearGradient27" gradientUnits="userSpaceOnUse" x1="82.891304" y1="11.076679" x2="50.925728" y2="-7.18854" gradientTransform="matrix(0.78717788,0,0,0.78717788,17.870299,1.3305908)" /></defs><g inkscape:label="Ebene 1" inkscape:groupmode="layer" id="layer1"><path style="fill:#666666;stroke:#808080;stroke-width:0.201369px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 13.51408,4.5518071 1.167257,4.0584537 1.073763,-0.6347675 2.001695,2.8892047 1.010901,-0.73816 -2.16932,-2.6915459 0.880065,-0.9465373 z" id="path1" sodipodi:nodetypes="cccccccc" /><path |
︙ | ︙ | |||
122 123 124 125 126 127 128 | d="m 31.683913,3.6215577 -1.281898,1.2818987 h 6.159121 l 1.28834,-1.2883398 z" id="path8" /><path style="fill:#666666;stroke:#808080;stroke-width:0.127659px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 32.5838,5.8940246 -1.281899,1.2818986 h 6.159121 l 1.28834,-1.2883398 z" id="path9" /><path style="fill:#666666;stroke:#808080;stroke-width:0.127659px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 33.750881,8.1664914 32.468982,9.44839 h 6.15912 l 1.28834,-1.2883398 z" | | < < < | < > | | < > | | < > | | < > < | > | < < < < < < < < < < < | > | | | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | d="m 31.683913,3.6215577 -1.281898,1.2818987 h 6.159121 l 1.28834,-1.2883398 z" id="path8" /><path style="fill:#666666;stroke:#808080;stroke-width:0.127659px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 32.5838,5.8940246 -1.281899,1.2818986 h 6.159121 l 1.28834,-1.2883398 z" id="path9" /><path style="fill:#666666;stroke:#808080;stroke-width:0.127659px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 33.750881,8.1664914 32.468982,9.44839 h 6.15912 l 1.28834,-1.2883398 z" id="path10" /><path style="fill:url(#linearGradient25);fill-opacity:1;stroke:#808080;stroke-width:0.187134px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 71.156149,2.7829806 71.471275,8.5616131 80.30846,7.9078353 79.751368,2.0453356 Z" id="path20" /><path style="fill:url(#linearGradient26);fill-opacity:1;stroke:#808080;stroke-width:0.187134px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 72.520756,3.9781177 0.315127,5.7786322 8.837186,-0.6537771 -0.557093,-5.8625 z" id="path21" /><path style="fill:url(#linearGradient27);fill-opacity:1;stroke:#808080;stroke-width:0.187134px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 73.979158,5.2401799 0.315127,5.7786321 8.837186,-0.653778 -0.557092,-5.8624991 z" id="path22" /><path style="fill:#666666;stroke:#808080;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none" d="m 113.29256,6.2586796 c 2.35321,-7.7260104 11.8973,-0.570756 5.11853,3.2900998 -0.16573,-1.2152656 -0.18286,-1.5151627 -1.19294,0.5781386 1.97988,1.106639 2.01257,1.213414 1.46548,0.0993 8.32802,-4.5591326 -3.55614,-13.3928491 -6.15516,-3.9827965 0.67538,0.053446 0.38202,0.00118 0.76409,0.015258 z" id="path27" sodipodi:nodetypes="cccccc" /><path id="path19" style="fill:#666666;stroke:#808080;stroke-width:0.226957px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 97.411443,4.2760691 -3.922556,2.2646985 3.890638,2.2465243 0.0053,-0.7340655 -2.62331,-1.5146751 2.644583,-1.5270869 z" /><path id="path25" style="fill:#666666;stroke:#808080;stroke-width:0.226957px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 99.912567,4.2760691 3.922573,2.2646985 -3.890654,2.2465243 -0.0051,-0.7340655 2.623324,-1.5146751 -2.644597,-1.5270869 z" /><path id="path13" style="fill:#666666;stroke:#808080;stroke-width:0.224634;stroke-linejoin:round;stroke-dasharray:none" d="m 55.011468,2.0565004 v 0.9288562 a 3.2583114,3.2583114 0 0 0 -1.137419,0.4709091 l -0.656641,-0.6566418 -0.926535,0.9265343 0.656642,0.6564486 a 3.2583114,3.2583114 0 0 0 -0.470716,1.137418 H 51.54775 v 1.3103813 h 0.929049 a 3.2583114,3.2583114 0 0 0 0.470909,1.1374184 l -0.656835,0.6566414 0.926535,0.9265352 0.656448,-0.6566423 a 3.2583114,3.2583114 0 0 0 1.137612,0.4707158 v 0.9290494 h 1.310187 V 9.3650746 A 3.2583114,3.2583114 0 0 0 57.459267,8.8943588 L 58.115716,9.5510011 59.04225,8.6244659 58.385801,7.9678245 a 3.2583114,3.2583114 0 0 0 0.470523,-1.1374184 h 0.929049 V 5.5200248 H 58.858065 A 3.2583114,3.2583114 0 0 0 58.386576,4.3818329 L 59.04225,3.7261582 58.115716,2.7996239 57.460041,3.4552984 A 3.2583114,3.2583114 0 0 0 56.321655,2.9834217 V 2.0565004 Z m 0.655093,2.0403492 a 2.0784853,2.0784853 0 0 1 2.078463,2.0784628 2.0784853,2.0784853 0 0 1 -2.078463,2.0784624 2.0784853,2.0784853 0 0 1 -2.078462,-2.0784624 2.0784853,2.0784853 0 0 1 2.078462,-2.0784628 z" /><path style="fill:#666666;fill-opacity:1;stroke:#808080;stroke-width:0.27074px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 117.13171,4.1528651 -1.83054,3.1706054 h 3.82216 z" id="path11" inkscape:transform-center-x="-0.031183631" inkscape:transform-center-y="-0.0067147625"><animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 117.21225 5.738167799999999" to="360 117.21225 5.738167799999999" begin="0s" dur="5s" repeatCount="indefinite" /></path></g></svg> |