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

⌈⌋ ⎇ branch:  scripts + snippets


Check-in [4621d9c6bd]

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

Overview
Comment:recombine zip_args into imagick_gif(), distribute some flags onto frame.pngs
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 4621d9c6bd3e88a73033b2103a052ed9a23229b8
User & Date: mario 2022-10-12 13:01:48
Context
2022-10-23
20:02
implement repeatCount, some tests check-in: 58300ed51a user: mario tags: trunk
2022-10-12
13:01
recombine zip_args into imagick_gif(), distribute some flags onto frame.pngs check-in: 4621d9c6bd user: mario tags: trunk
2022-10-11
07:12
hex_color redundant with inkex.Color check-in: c284f16d06 user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to inkscape/export_gif.inx.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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 via ImageMagick/Pillow</description>
  <!--<schema:softwareVersion xmlns:schema="https://schema.org/">1.1</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 via ImageMagick/Pillow</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>
    <option value="SVG→ImageMagick (simple drawings)">SVG→ImageMagick (simple drawings)</option>
    <option value="PNG→Pillow (builtin - a bit faster)">PNG→Pillow (builtin - a bit faster)</option>
    <option value="JavaScript→SVG (embed anim code)">JavaScript→SVG (embed anim code)</option>
  </param>
  <param name="delay" type="float" min="0.01" max="20" precision="2" mode="float" gui-text="Delay between slides (seconds)">0.35</param>









|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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 via ImageMagick/Pillow</description>
  <!--<schema:softwareVersion xmlns:schema="https://schema.org/">1.1</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 via ImageMagick/Pillow</label>
  <param name="file" type="path" mode="file" gui-description="Output filename should end in .gif for actual results. Alternatively can be `APNG:dir/filename.apng` with ImageMagick 7." 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>
    <option value="SVG→ImageMagick (simple drawings)">SVG→ImageMagick (simple drawings)</option>
    <option value="PNG→Pillow (builtin - a bit faster)">PNG→Pillow (builtin - a bit faster)</option>
    <option value="JavaScript→SVG (embed anim code)">JavaScript→SVG (embed anim code)</option>
  </param>
  <param name="delay" type="float" min="0.01" max="20" precision="2" mode="float" gui-text="Delay between slides (seconds)">0.35</param>
43
44
45
46
47
48
49
50

51
52
53
54
55
56
57
        <option value="optimize-transparency">optimize-transparency</option>
        <option value="remove-dups">remove-dups</option>
        <option value="remove-zero">remove-zero</option>
        <option value="trim-bounds">trim-bounds</option>
      </param>
      <param name="extra" type="optiongroup" appearance="combo" gui-description="Some default convert -args." gui-text="Extra args">
        <option value="-quiet">-quiet</option>
        <option value="-resize 640x360">-resize 640x360</option>

        <option value="-alpha background">-alpha background</option>
        <option value="-auto-gamma">-auto-gamma</option>
        <option value="-auto-level">-auto-level</option>
        <option value="-coalesce">-coalesce</option>
        <option value="-colors 64">-colors 64</option>
        <option value="-dither FloydSteinberg">-dither FloydSteinberg</option>
        <option value="-limit disk 1MB">-limit disk 1MB</option>







|
>







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
        <option value="optimize-transparency">optimize-transparency</option>
        <option value="remove-dups">remove-dups</option>
        <option value="remove-zero">remove-zero</option>
        <option value="trim-bounds">trim-bounds</option>
      </param>
      <param name="extra" type="optiongroup" appearance="combo" gui-description="Some default convert -args." gui-text="Extra args">
        <option value="-quiet">-quiet</option>
        <option value="-optimize">-optimize</option>
        <option value="-size 640x360">-size 640x360</option>
        <option value="-alpha background">-alpha background</option>
        <option value="-auto-gamma">-auto-gamma</option>
        <option value="-auto-level">-auto-level</option>
        <option value="-coalesce">-coalesce</option>
        <option value="-colors 64">-colors 64</option>
        <option value="-dither FloydSteinberg">-dither FloydSteinberg</option>
        <option value="-limit disk 1MB">-limit disk 1MB</option>

Changes to inkscape/export_gif.py.

117
118
119
120
121
122
123

124
125
126
127
128
129
130
from inkex.utils import strargs
from inkex.tween import StyleInterpolator, TransformInterpolator
from inkex.transforms import Transform


class GIFExport(inkex.EffectExtension):#, inkex.OutputExtension):#, inkex.RasterOutputExtension
    """ doubles as effect and save-as extension """


    def __init__(self):
        """ init the effect library """
        self.tempdir = None
        self.index = 0 # current slide
        self.win32 = sys.platform == "win32"
        super().__init__()







>







117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
from inkex.utils import strargs
from inkex.tween import StyleInterpolator, TransformInterpolator
from inkex.transforms import Transform


class GIFExport(inkex.EffectExtension):#, inkex.OutputExtension):#, inkex.RasterOutputExtension
    """ 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
        self.win32 = sys.platform == "win32"
        super().__init__()
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268


269

270
271

272
273
274
275
276
277
278
279
280
281

282






283
284
285
286
287
288
289
            formatted += "{:02X}".format(int(255 * min(1.0, color.alpha)))
        return formatted

    def convert_png(self, svg_path, png_path):
        """ Use the convenience wrapper (more likely referencing the current inkscape binary) """
        extra = {}
        if self.options.background and self.options.export_background:
            color = inkex.Color(self.options.background)
            extra.update({
                "export_background": self.hex_color(color, with_alpha=False),
                "export_background_opacity": "{:.2f}".format(color.alpha),
                # 5%/compareOverlay/-coalesce per channel [--alpha=set] [--channel=A] [--ordered-dither=checks] has some effect
                # compareClear/-coalesce [--channel=A --ordered-dither=4x4 --dispose=-1]
            })
        inkex.command.inkscape(
            svg_path, export_filename=png_path, export_type="png",
            export_area_page=True, export_text_to_path=True, **extra,
            #export_dpi=96, --export-width  --export-height
        )

    def imagick_gif(self, temp_files_args):
        """ Run `convert` using list of exported slides """
        inkex.command.call(*[
            shutil.which("magick") or "convert",
            "-delay", float(self.options.delay) * 100,
            "-loop", self.options.loop,
            "-layers", self.options.layers,
            "-fuzz", self.options.fuzz,   # ToDo: distribute some flags into temp_files_args? (-blur -colors -compose -cycle -fx -gamma -grayscale -ordered-dither -resample -resize -transparent )
            "-background", self.hex_color(self.options.background) if self.options.background else "None",
            *shlex.split(self.options.extra),
            *shlex.split(self.options.extra2),


            *self.zip_args(temp_files_args),

            *[os.path.expanduser(self.options.file)]
        ])


    @staticmethod
    def zip_args(temp_files_args):
        """ unzip files and possible -args (fitted list of dicts) """
        for filename, arg_list in temp_files_args:
            for key, val in arg_list.items():
                if key == "write":
                    continue
                yield "-" + key
                yield float(val) * 100 if key == "delay" else val

            yield filename







    def pillow_gif(self, temp_files_args):
        """ Alternative: use PIL to assemble GIF (very rough, only works with PNG preconvert) """
        from PIL import Image # pylint: disable=import-outside-toplevel
        frames = [Image.open(fn_ar[0]) for fn_ar in temp_files_args]
        # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#saving
        frames[0].save(







<

|
|











|
<
<


<
<


>
>
|
>
|
<
>
|
<
|
<

|


|
|
>
|
>
>
>
>
>
>







239
240
241
242
243
244
245

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260


261
262


263
264
265
266
267
268
269

270
271

272

273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
            formatted += "{:02X}".format(int(255 * min(1.0, color.alpha)))
        return formatted

    def convert_png(self, svg_path, png_path):
        """ Use the convenience wrapper (more likely referencing the current inkscape binary) """
        extra = {}
        if self.options.background and self.options.export_background:

            extra.update({
                "export_background": self.hex_color(self.options.background),
                "export_background_opacity": "{:.2f}".format(self.options.background.alpha),
                # 5%/compareOverlay/-coalesce per channel [--alpha=set] [--channel=A] [--ordered-dither=checks] has some effect
                # compareClear/-coalesce [--channel=A --ordered-dither=4x4 --dispose=-1]
            })
        inkex.command.inkscape(
            svg_path, export_filename=png_path, export_type="png",
            export_area_page=True, export_text_to_path=True, **extra,
            #export_dpi=96, --export-width  --export-height
        )

    def imagick_gif(self, temp_files_args):
        """ Run `convert` using list of exported slides """
        args = [


            "-loop", self.options.loop,
            "-layers", self.options.layers,


            *shlex.split(self.options.extra),
            *shlex.split(self.options.extra2),
        ]
        slide_params = {   # some args are per-input file
            "delay": self.options.delay,
            "background": self.hex_color(self.options.background) if self.options.background else "None",
            "fuzz": self.options.fuzz,

            **dict(re.findall(r"-(alpha|blur|channel|colors|compose|cycle|fx|gamma|grayscale|ordered-dither|resample|resize|transparent)\s+(\w+\S+)(?=\s+[^-]|\s*$)", self.options.extra2))
        }

        # zip frame filenames + extra params

        for filename, arg_list in temp_files_args:
            for key, val in {**slide_params, **arg_list}.items():
                if key == "write":
                    continue
                if key == "delay":
                    val = round(float(val) * 100)
                args.extend(["-" + key.strip("-"), val])
            args.extend([filename])
        # execute
        inkex.command.call(
            shutil.which("magick") or "convert",
            *args,
            os.path.expanduser(self.options.file)
        )

    def pillow_gif(self, temp_files_args):
        """ Alternative: use PIL to assemble GIF (very rough, only works with PNG preconvert) """
        from PIL import Image # pylint: disable=import-outside-toplevel
        frames = [Image.open(fn_ar[0]) for fn_ar in temp_files_args]
        # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#saving
        frames[0].save(