Collection of mostly command line tools / PHP scripts. Somewhat out of date.

⌈⌋ ⎇ branch:  scripts + snippets


Check-in [abf02b681a]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:prepend existing transform for animotion
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: abf02b681acdfa8864ae559551067262fef96db9
User & Date: mario 2022-10-02 06:24:42
Context
2022-10-02
19:18
add basic time mapping check-in: 52564363ae user: mario tags: trunk
06:24
prepend existing transform for animotion check-in: abf02b681a user: mario tags: trunk
06:23
add remove_child option, shorten notebook page options check-in: 157fc9695e user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to inkscape/export_gif.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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: 0.9
# state: beta
# depends: bin:inkscape (>= 1.1), bin:convert, python (>= 3.6)
# pylint: disable=line-too-long, missing-module-docstring, bad-whitespace
# config:
#    { name: file, type: file, mode: file, value: "~/anim.gif", description: "Target GIF 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 }
#    { name: delay, type: float, value: 0.35, min: 0.01, max: 20, precision: 2, description: "Delay between slides (seconds)" }
#    { name: loop, type: int, value: 0, description: "Loop limit (0 for endless)" }
#    { name: fuzz, type: select, select: "0%|5%|10%|20%|30%|50%", value: "10%", description: "Fuzzing/dither" }











|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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: 0.9
# 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" }
#    { 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 }
#    { name: delay, type: float, value: 0.35, min: 0.01, max: 20, precision: 2, description: "Delay between slides (seconds)" }
#    { name: loop, type: int, value: 0, description: "Loop limit (0 for endless)" }
#    { name: fuzz, type: select, select: "0%|5%|10%|20%|30%|50%", value: "10%", description: "Fuzzing/dither" }
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
69
70
71
# pack: export_gif.py, *.inx, pmd2inks, animate_yo.py, LICENSE=/usr/share/doc/inkscape-export-gif/copyright
# format: off
# author: mario#include-once:org
# url: https://inkscape.org/~culturaljuice/★export_gif
# orig: Xavi, https://github.com/jespino/inkscape-export-layers
#
# Generates a GIF slideshow from image layers ☰  (Shift+Ctrl+L). The menu
# option 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.


#
# Requires ImageMagick installed; fit for standard Linux setups. But might
# work with convert.exe on Windows. And alternatively there's the builtin
# Pillow conversion method. The options mostly map to IM flags.
#
#  🞂 Without the PNG conversion step, SVG interpretation is up to Imagick,
#    and will not render fancy font/path effects. Just for plain old SVGs.
#  🞂 The [PNG→Pillow] option works without ImageMagick, but often generates
#    rougher GIFs, yet might produce smaller result files.
#  🞂 In [JavaScript] mode, no output file will be generated. It just adds a
#    script for animating slides into the current document (web views).
#  🞂 A [fixed] layer label describes a permanent foreground (until overdrawn),
#    but never constitues a frame. Whereas [background] layers only become
#    active and permanent when its frame is reached. Additionally [merge]
#    labels will enjoin partial layers to the preceding full slide. But also
#    [exclude] to omit layers entirely.
#  🞂 There's also a rudimentary [animate=5] option to craft subframes
#    from embedded <animate*> instructions.

#  🞂 Each layer label may also specify ImageMagick flags [--delay=2.5].
#  🞂 The "default background color" is useful for otherwise transparent
#    layers. Half-transparancy can be useful to gradually fade out lower
#    frames (only seems to work in SVG/ImageMagick -displace mode).
#  🞂 Extra arguments are just a list of sample options, might not be
#    particularly useful by themselves. Use the second input or [--flag=X]
#    labels for more refined combinations.







|









>
>

















|
>







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
69
70
71
72
73
74
# pack: export_gif.py, *.inx, pmd2inks, animate_yo.py, LICENSE=/usr/share/doc/inkscape-export-gif/copyright
# format: off
# author: mario#include-once:org
# url: https://inkscape.org/~culturaljuice/★export_gif
# orig: Xavi, https://github.com/jespino/inkscape-export-layers
#
# Generates a GIF slideshow from image layers ☰  (Shift+Ctrl+L). The menu
# option 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
# work with convert.exe on Windows. And alternatively there's the builtin
# Pillow conversion method. The options mostly map to IM flags.
#
#  🞂 Without the PNG conversion step, SVG interpretation is up to Imagick,
#    and will not render fancy font/path effects. Just for plain old SVGs.
#  🞂 The [PNG→Pillow] option works without ImageMagick, but often generates
#    rougher GIFs, yet might produce smaller result files.
#  🞂 In [JavaScript] mode, no output file will be generated. It just adds a
#    script for animating slides into the current document (web views).
#  🞂 A [fixed] layer label describes a permanent foreground (until overdrawn),
#    but never constitues a frame. Whereas [background] layers only become
#    active and permanent when its frame is reached. Additionally [merge]
#    labels will enjoin partial layers to the preceding full slide. But also
#    [exclude] to omit layers entirely.
#  🞂 There's also a rudimentary [animate=5] option to craft subframes
#    from embedded <animate*> instructions. Motions require `pip install
#    svgelements` or a bundled version for accurate paths.
#  🞂 Each layer label may also specify ImageMagick flags [--delay=2.5].
#  🞂 The "default background color" is useful for otherwise transparent
#    layers. Half-transparancy can be useful to gradually fade out lower
#    frames (only seems to work in SVG/ImageMagick -displace mode).
#  🞂 Extra arguments are just a list of sample options, might not be
#    particularly useful by themselves. Use the second input or [--flag=X]
#    labels for more refined combinations.
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# v0.7 · introduce combined modes · JavaScript embed option
# v0.6 · support file→save-as invocation
# v0.5 · revamped layer combination handling
# v0.4 · support SVG export by removing nodes
# v0.3 · introduced more imagemagick options
# v0.2 · migrated to plugin meta data · more robust file handling
# v0.1 · prototype
#


import sys
import copy
import os
import tempfile
from argparse import Namespace







<







90
91
92
93
94
95
96

97
98
99
100
101
102
103
# v0.7 · introduce combined modes · JavaScript embed option
# v0.6 · support file→save-as invocation
# v0.5 · revamped layer combination handling
# v0.4 · support SVG export by removing nodes
# v0.3 · introduced more imagemagick options
# v0.2 · migrated to plugin meta data · more robust file handling
# v0.1 · prototype



import sys
import copy
import os
import tempfile
from argparse import Namespace
130
131
132
133
134
135
136

137
138
139
140
141
142
143
144
    def __init__(self):
        """ init the effect library """
        self.tempdir = None
        self.index = 0 # current slide
        self.win32 = sys.platform == "win32"
        super().__init__()


    def add_arguments(self, pars):
        """ populate self.options from script args """
        pars.add_argument("--file", type=str, dest="file", default="~/anim.gif", help="Target GIF filename")
        pars.add_argument("--mode", type=str, dest="mode", default="PNG→Pillow", help="Operation mode")
        pars.add_argument("--delay", type=float, dest="delay", default=0.35, help="Delay between slides (seconds)")
        pars.add_argument("--loop", type=int, dest="loop", default=0, help="Loop limit (0 for endless)")
        pars.add_argument("--fuzz", type=str, dest="fuzz", default="5%", help="Fuzzing/dither")
        pars.add_argument("--layers", type=str, dest="layers", default="optimize-transparency", help="Accumulation/combination of -layers")







>
|







132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    def __init__(self):
        """ init the effect library """
        self.tempdir = None
        self.index = 0 # current slide
        self.win32 = sys.platform == "win32"
        super().__init__()

    @staticmethod
    def add_arguments(pars):
        """ populate self.options from script args """
        pars.add_argument("--file", type=str, dest="file", default="~/anim.gif", help="Target GIF filename")
        pars.add_argument("--mode", type=str, dest="mode", default="PNG→Pillow", help="Operation mode")
        pars.add_argument("--delay", type=float, dest="delay", default=0.35, help="Delay between slides (seconds)")
        pars.add_argument("--loop", type=int, dest="loop", default=0, help="Loop limit (0 for endless)")
        pars.add_argument("--fuzz", type=str, dest="fuzz", default="5%", help="Fuzzing/dither")
        pars.add_argument("--layers", type=str, dest="layers", default="optimize-transparency", help="Accumulation/combination of -layers")
382
383
384
385
386
387
388
389
390

391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
        # return modified SVG tree
        inkex.base.SvgOutputMixin.save(self, self.options.output)


class AnimationSteps(): # pylint: disable=invalid-name, unused-argument, import-outside-toplevel
    """
    Try to interpolate some frames from <animate*> interpretations.
    This won't progress beyond the most rudimentary of implementions
    (just color changes, linear moving, and probably scaling).


    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 """
        import inkex.tween # pylint: disable=unused-import, import-outside-toplevel, redefined-outer-name
        self.gif = parent
        self.svg = svg
        self.layer = copy.deepcopy(layer)
        self.frames = int((layer.animate or [5])[0])
        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)







|
|
>









|







385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
        # return modified SVG tree
        inkex.base.SvgOutputMixin.save(self, self.options.output)


class AnimationSteps(): # pylint: disable=invalid-name, unused-argument, import-outside-toplevel
    """
    Try to interpolate some frames from <animate*> interpretations.
    This won't progress beyond the most rudimentary of implementions.
    Currently color changes, scaling, rotation, some moving (proper
    path traversal hinges on svgelements).

    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 """
        import inkex.tween # pylint: disable=unused-import, import-outside-toplevel, redefined-outer-name
        self.gif = parent
        self.svg = svg # already a deepcopy
        self.layer = copy.deepcopy(layer)
        self.frames = int((layer.animate or [5])[0])
        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)
461
462
463
464
465
466
467

468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
            self.adapt(target, transform=tween.interpolate(time))

        return apply

    def animate_motion(self, anim, path, **attrib): # pylint: disable=invalid-name
        """ <animateMotion path="M10,20" /> """
        target = self.get_target(anim)

        
        #if inkex.paths.Path(path)[0].is_relative:
            #bbox = target.bounding_box()
            #origin_xy = [bbox.left, bbox.top]

        try:
            import svgelements
            point = svgelements.Path(path).point
        except ModuleNotFoundError:
            point = self.rough_path(path)

        def apply(time, point=point):
            dx, dy = point(time)
            self.adapt(target, transform=f"translate({dx} {dy})")

        return apply

    @staticmethod
    def rough_path(path):
        """ traverse any points+control in a zig-zag way, length discounted, absolute coordinates, etc. """
        pairs = [







>
|










|

|







465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
            self.adapt(target, transform=tween.interpolate(time))

        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:
            #bbox = target.bounding_box()
            #origin_xy = [bbox.left, bbox.top]

        try:
            import svgelements
            point = svgelements.Path(path).point
        except ModuleNotFoundError:
            point = self.rough_path(path)

        def apply(time, point=point, prepend=orig_transform):
            dx, dy = point(time)
            self.adapt(target, transform=f"{prepend} translate({dx} {dy})")

        return apply

    @staticmethod
    def rough_path(path):
        """ traverse any points+control in a zig-zag way, length discounted, absolute coordinates, etc. """
        pairs = [