Check-in [483352ece2]
Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | 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) |
---|---|
Timelines: | family | ancestors | trunk |
Files: | files | file ages | folders |
SHA1: |
483352ece2f9b57372acfa7dc4b0c12a |
User & Date: | mario 2022-10-31 09:39:46 |
Context
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) Leaf check-in: 483352ece2 user: mario tags: trunk | |
09:37 | more pacing tests check-in: 64ef7120d0 user: mario tags: trunk | |
Changes
Changes to inkscape/export_gif.py.
︙ | ︙ | |||
38 39 40 41 42 43 44 45 46 47 48 49 50 51 | # { 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: https://github.com/jespino/inkscape-export-layers # # Generates a GIF slideshow from image layers ☰ (Shift+Ctrl+L). The menu | > > > | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | # { 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 # depends: python:inkex, bin:inkscape >= 1.1 # suggests: python:svgelements >= 1.8 # 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 |
︙ | ︙ | |||
468 469 470 471 472 473 474 | self.gif = parent self.convert_png = parent.convert_png self.svg = svg # ← already a deepcopy self.layer = layer self.frames = layer.animate self.delay = float(layer.args.get("delay", self.gif.options.delay)) self.layer.args.update({ | | | 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 | self.gif = parent self.convert_png = parent.convert_png self.svg = svg # ← already a deepcopy self.layer = layer self.frames = layer.animate 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 }) def export(self): """ applies transforms in each subframe, yields filename+args list """ transforms = [] for anim in self.svg.xpath("//svg:animate"): transforms.append([self.animate_style(anim, **anim.attrib), self.pace(anim)]) |
︙ | ︙ | |||
491 492 493 494 495 496 497 498 499 500 501 502 503 504 | apply(pace(index / (self.frames - 1))) # durations are mapped into the 0.0 - 1.0 range # custom export dest = f"{self.gif.tempdir}/{self.gif.index}+{index}.{self.layer.label}.png" GIFExport.export_layers(self, dest, show=True) yield [dest, self.layer.args] @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( | > > > | 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | apply(pace(index / (self.frames - 1))) # durations are mapped into the 0.0 - 1.0 range # custom export dest = f"{self.gif.tempdir}/{self.gif.index}+{index}.{self.layer.label}.png" GIFExport.export_layers(self, dest, show=True) yield [dest, self.layer.args] # reset, in case frame gets animated again ([fixed]/[bg]) self.layer.args["delay"] = self.delay @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( |
︙ | ︙ | |||
537 538 539 540 541 542 543 | return apply def animate_rotate(self, target, anim, start, end): """ simpler <animateTransform type=rotate> handler, does not fold the matrix at 180° """ start = strargs(start) or [0] end = strargs(end) or [0] | | | 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 | return apply def animate_rotate(self, target, anim, start, end): """ simpler <animateTransform type=rotate> handler, does not fold the matrix at 180° """ start = strargs(start) or [0] end = strargs(end) or [0] sfx = [str(coord) for coord in end[1:] + start[1:]] # any center coordinates def apply(time, start=start[0], end=end[0], sfx=" ".join(sfx[0:2]), prepend=self.had_transform(target)): deg = start + (end - start) * time self.adapt(target, transform=prepend + f"rotate({deg} {sfx})") return apply def animate_motion(self, anim, path, **attrib): # pylint: disable=invalid-name |
︙ | ︙ | |||
635 636 637 638 639 640 641 | return (r_time - r_begin) / r_end if attr("repeatCount"): # meh: ignorantly zigzags the repeat into our .delay time slice def vary(r_time, count=int(attr("repeatCount")), vary=vary): """ amplify by repeatCount, modulo into 0.0 … 1.0 interval """ return (count * vary(r_time)) % 1.0 | | | > | | 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 | return (r_time - r_begin) / r_end if attr("repeatCount"): # meh: ignorantly zigzags the repeat into our .delay time slice def vary(r_time, count=int(attr("repeatCount")), vary=vary): """ amplify by repeatCount, modulo into 0.0 … 1.0 interval """ return (count * vary(r_time)) % 1.0 if attr("calcMode") == "discrete": def vary(r_time): """ just snaps from one to the other extreme; nobody will ever use it """ return 0.0 if r_time < 0.5 else 1.0 if attr("calcMode") == "paced" or smooth and not attr("calcMode"): def vary(r_time, vary=vary): """ dense sigmoid (quick approx via KmPlot) """ return vary(10 / (10 + math.exp(-(9 * r_time - 7)))) if attr("calcMode") == "spline" and attr("keyTimes"): # (ignoring the actual keySplines= of course) def vary(r_time, steps=strargs(attr("keyTimes", "0;1")), vary=vary): """ only linear [0, 0.33, 0.66, 1.0] here, values=[] would require interpretation in handlers """ length = len(steps) - 1 dist = 1.0 / length # 0.25 for len=4 offs = int(r_time * length) # r=0.30 → vary index [1] steps = steps[:] + [1.0, 1.0] val_diff = steps[offs+1] - steps[offs] # steps difference 0.33-0.25 off_diff = (r_time - dist * offs) / dist # offset [2]+0.1 to quantify value return vary(steps[offs] + val_diff * off_diff) def vary(r_time, vary=vary): """ guard for invalid params and calculation hiccups """ |
︙ | ︙ |