[gimp] plug-ins: port file-openraster to Python 3 + new API.



commit 13ea5caec29b63b2b444f1381e550db688fa870b
Author: Jehan <jehan girinstud io>
Date:   Sun Aug 25 04:05:40 2019 +0200

    plug-ins: port file-openraster to Python 3 + new API.
    
    Apart from porting, only code logics change is the whole
    encode()/decode() code because it created a string vs bytes mess and the
    zipfile API apparently didn't like to deal with bytes, even though the
    docs say otherwise.
    It's hard to test on my UTF-8 system, so please anyone working with
    non-UTF-8 paths, we welcome reports if ORA load/save does not work
    properly.
    
    Other than this, load, save and load_thumb were all tested and working
    properly so far.

 plug-ins/pygimp/plug-ins/file-openraster.py | 393 ---------------------------
 plug-ins/python/Makefile.am                 |   5 +-
 plug-ins/python/file-openraster.py          | 399 ++++++++++++++++++++++++++++
 3 files changed, 403 insertions(+), 394 deletions(-)
---
diff --git a/plug-ins/python/Makefile.am b/plug-ins/python/Makefile.am
index 16b7b82e2d..2654592109 100644
--- a/plug-ins/python/Makefile.am
+++ b/plug-ins/python/Makefile.am
@@ -9,9 +9,11 @@ palette_offset_SCRIPTS = palette-offset.py
 palette_to_gradientdir = $(gimpplugindir)/plug-ins/palette-to-gradient
 palette_to_gradient_SCRIPTS = palette-to-gradient.py
 
+file_openrasterdir= $(gimpplugindir)/plug-ins/file-openraster
+file_openraster_SCRIPTS = file-openraster.py
+
 # TODO: to be ported:
 ##     colorxhtml.py
-##     file-openraster.py
 ##     foggify.py
 ##     gradients-save-as-css.py
 ##     histogram-export.py
@@ -29,6 +31,7 @@ if GIMP_UNSTABLE
 endif
 
 EXTRA_DIST = \
+       file-openraster.py              \
        palette-offset.py               \
        palette-to-gradient.py
 
diff --git a/plug-ins/python/file-openraster.py b/plug-ins/python/file-openraster.py
new file mode 100755
index 0000000000..ba62f531bc
--- /dev/null
+++ b/plug-ins/python/file-openraster.py
@@ -0,0 +1,399 @@
+#!/usr/bin/env python3
+
+# GIMP Plug-in for the OpenRaster file format
+# http://create.freedesktop.org/wiki/OpenRaster
+
+# Copyright (C) 2009 by Jon Nordby <jononor gmail com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Based on MyPaint source code by Martin Renold
+# http://gitorious.org/mypaint/mypaint/blobs/edd84bcc1e091d0d56aa6d26637aa8a925987b6a/lib/document.py
+
+import gi
+gi.require_version('Gimp', '3.0')
+from gi.repository import Gimp
+gi.require_version('Gegl', '0.4')
+from gi.repository import Gegl
+from gi.repository import GObject
+from gi.repository import GLib
+from gi.repository import Gio
+
+import os, sys, tempfile, zipfile
+import xml.etree.ElementTree as ET
+
+NESTED_STACK_END = object()
+
+layermodes_map = {
+    "svg:src-over": Gimp.LayerMode.NORMAL,
+    "svg:multiply": Gimp.LayerMode.MULTIPLY_LEGACY,
+    "svg:screen": Gimp.LayerMode.SCREEN_LEGACY,
+    "svg:overlay": Gimp.LayerMode.OVERLAY,
+    "svg:darken": Gimp.LayerMode.DARKEN_ONLY_LEGACY,
+    "svg:lighten": Gimp.LayerMode.LIGHTEN_ONLY_LEGACY,
+    "svg:color-dodge": Gimp.LayerMode.DODGE_LEGACY,
+    "svg:color-burn": Gimp.LayerMode.BURN_LEGACY,
+    "svg:hard-light": Gimp.LayerMode.HARDLIGHT_LEGACY,
+    "svg:soft-light": Gimp.LayerMode.SOFTLIGHT_LEGACY,
+    "svg:difference": Gimp.LayerMode.DIFFERENCE_LEGACY,
+    "svg:color": Gimp.LayerMode.HSL_COLOR_LEGACY,
+    "svg:luminosity": Gimp.LayerMode.HSV_VALUE_LEGACY,
+    "svg:hue": Gimp.LayerMode.HSV_HUE_LEGACY,
+    "svg:saturation": Gimp.LayerMode.HSV_SATURATION_LEGACY,
+    "svg:plus": Gimp.LayerMode.ADDITION_LEGACY,
+}
+
+def reverse_map(mapping):
+    return dict((v,k) for k, v in mapping.items())
+
+def get_image_attributes(orafile):
+    xml = orafile.read('stack.xml')
+    image = ET.fromstring(xml)
+    stack = image.find('stack')
+    w = int(image.attrib.get('w', ''))
+    h = int(image.attrib.get('h', ''))
+
+    return stack, w, h
+
+def get_layer_attributes(layer):
+    a = layer.attrib
+    path = a.get('src', '')
+    name = a.get('name', '')
+    x = int(a.get('x', '0'))
+    y = int(a.get('y', '0'))
+    opac = float(a.get('opacity', '1.0'))
+    visible = a.get('visibility', 'visible') != 'hidden'
+    m = a.get('composite-op', 'svg:src-over')
+    layer_mode = layermodes_map.get(m, Gimp.LayerMode.NORMAL)
+
+    return path, name, x, y, opac, visible, layer_mode
+
+def get_group_layer_attributes(layer):
+    a = layer.attrib
+    name = a.get('name', '')
+    opac = float(a.get('opacity', '1.0'))
+    visible = a.get('visibility', 'visible') != 'hidden'
+    m = a.get('composite-op', 'svg:src-over')
+    layer_mode = layermodes_map.get(m, NORMAL_MODE)
+
+    return name, 0, 0, opac, visible, layer_mode
+
+def thumbnail_ora(procedure, file, thumb_size, args, data):
+    tempdir = tempfile.mkdtemp('gimp-plugin-file-openraster')
+    orafile = zipfile.ZipFile(file.peek_path())
+    stack, w, h = get_image_attributes(orafile)
+
+    # create temp file
+    tmp = os.path.join(tempdir, 'tmp.png')
+    with open(tmp, 'wb') as fid:
+        fid.write(orafile.read('Thumbnails/thumbnail.png'))
+
+    args = Gimp.ValueArray.new(2)
+    args.insert(0, GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE))
+    args.insert(1, GObject.Value(GObject.TYPE_STRING, tmp))
+    img = Gimp.get_pdb().run_procedure('file-png-load', args)
+    img = img.index(1)
+    img = Gimp.Image.get_by_id(img)
+    # TODO: scaling
+    os.remove(tmp)
+    os.rmdir(tempdir)
+
+    retval = Gimp.ValueArray.new(2)
+    retval.insert(0, GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS))
+    retval.insert(1, GObject.Value(Gimp.Image, img))
+    retval.insert(2, GObject.Value(GObject.TYPE_INT, w))
+    retval.insert(3, GObject.Value(GObject.TYPE_INT, h))
+    retval.insert(4, GObject.Value(Gimp.ImageType, Gimp.ImageType.RGB_IMAGE))
+    retval.insert(5, GObject.Value(GObject.TYPE_INT, 1))
+
+    return retval
+
+def save_ora(procedure, run_mode, image, drawable, file, args, data):
+    def write_file_str(zfile, fname, data):
+        # work around a permission bug in the zipfile library:
+        # http://bugs.python.org/issue3394
+        zi = zipfile.ZipInfo(fname)
+        zi.external_attr = int("100644", 8) << 16
+        zfile.writestr(zi, data)
+
+    tempdir = tempfile.mkdtemp('gimp-plugin-file-openraster')
+
+    # use .tmpsave extension, so we don't overwrite a valid file if
+    # there is an exception
+    orafile = zipfile.ZipFile(file.peek_path() + '.tmpsave', 'w', compression=zipfile.ZIP_STORED)
+
+    write_file_str(orafile, 'mimetype', 'image/openraster') # must be the first file written
+
+    # build image attributes
+    xml_image = ET.Element('image')
+    stack = ET.SubElement(xml_image, 'stack')
+    a = xml_image.attrib
+    a['w'] = str(image.width())
+    a['h'] = str(image.height())
+
+    def store_layer(image, drawable, path):
+        tmp = os.path.join(tempdir, 'tmp.png')
+        interlace, compression = 0, 2
+        args = Gimp.ValueArray.new(11)
+        args.insert(0, GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE))
+        args.insert(1, GObject.Value(Gimp.Image, image))
+        args.insert(2, GObject.Value(Gimp.Drawable, drawable))
+        args.insert(3, GObject.Value(GObject.TYPE_STRING, tmp))
+        args.insert(4, GObject.Value(GObject.TYPE_STRING, 'tmp.png'))
+        args.insert(5, GObject.Value(GObject.TYPE_BOOLEAN, interlace))
+        args.insert(6, GObject.Value(GObject.TYPE_INT, compression))
+        # write all PNG chunks except oFFs(ets)
+        args.insert(7, GObject.Value(GObject.TYPE_BOOLEAN, True))
+        args.insert(8, GObject.Value(GObject.TYPE_BOOLEAN, True))
+        args.insert(9, GObject.Value(GObject.TYPE_BOOLEAN, False))
+        args.insert(10, GObject.Value(GObject.TYPE_BOOLEAN, True))
+        Gimp.get_pdb().run_procedure('file-png-save', args)
+        orafile.write(tmp, path)
+        os.remove(tmp)
+
+    def add_layer(parent, x, y, opac, gimp_layer, path, visible=True):
+        store_layer(image, gimp_layer, path)
+        # create layer attributes
+        layer = ET.Element('layer')
+        parent.append(layer)
+        a = layer.attrib
+        a['src'] = path
+        a['name'] = gimp_layer.get_name()
+        a['x'] = str(x)
+        a['y'] = str(y)
+        a['opacity'] = str(opac)
+        a['visibility'] = 'visible' if visible else 'hidden'
+        a['composite-op'] = reverse_map(layermodes_map).get(gimp_layer.get_mode(), 'svg:src-over')
+        return layer
+
+    def add_group_layer(parent, opac, gimp_layer, visible=True):
+        # create layer attributes
+        group_layer = ET.Element('stack')
+        parent.append(group_layer)
+        a = group_layer.attrib
+        a['name'] = gimp_layer.name
+        a['opacity'] = str(opac)
+        a['visibility'] = 'visible' if visible else 'hidden'
+        a['composite-op'] = reverse_map(layermodes_map).get(gimp_layer.get_mode(), 'svg:src-over')
+        return group_layer
+
+
+    def enumerate_layers(layers):
+        for layer in layers:
+            if not layer.is_group():
+                yield layer
+            else:
+                yield layer
+                for sublayer in enumerate_layers(layer.get_children()):
+                    yield sublayer
+                yield NESTED_STACK_END
+
+    # save layers
+    parent_groups = []
+    i = 0
+    for lay in enumerate_layers(image.get_layers()):
+        if lay is NESTED_STACK_END:
+            parent_groups.pop()
+            continue
+        _, x, y = lay.offsets()
+        opac = lay.get_opacity () / 100.0 # needs to be between 0.0 and 1.0
+
+        if not parent_groups:
+            path_name = 'data/{:03d}.png'.format(i)
+            i += 1
+        else:
+            path_name = 'data/{}-{:03d}.png'.format(
+                parent_groups[-1][1], parent_groups[-1][2])
+            parent_groups[-1][2] += 1
+
+        parent = stack if not parent_groups else parent_groups[-1][0]
+
+        if lay.is_group():
+            group = add_group_layer(parent, opac, lay, lay.get_visible())
+            group_path = ("{:03d}".format(i) if not parent_groups else
+                parent_groups[-1][1] + "-{:03d}".format(parent_groups[-1][2]))
+            parent_groups.append([group, group_path , 0])
+        else:
+            add_layer(parent, x, y, opac, lay, path_name, lay.get_visible())
+
+    # save mergedimage
+    args = Gimp.ValueArray.new(1)
+    args.insert(0, GObject.Value(Gimp.Image, image))
+    thumb = Gimp.get_pdb().run_procedure('gimp-image-duplicate', args)
+    thumb = thumb.index(1)
+    thumb = Gimp.Image.get_by_id(thumb)
+    thumb_layer = thumb.merge_visible_layers (Gimp.MergeType.CLIP_TO_IMAGE)
+    store_layer (thumb, thumb_layer, 'mergedimage.png')
+
+    # save thumbnail
+    w, h = image.width(), image.height()
+    if max (w, h) > 256:
+        # should be at most 256x256, without changing aspect ratio
+        if w > h:
+            w, h = 256, max(h*256/w, 1)
+        else:
+            w, h = max(w*256/h, 1), 256
+        thumb_layer.scale(w, h, False)
+    if thumb.get_precision() != Gimp.Precision.U8_GAMMA:
+        thumb.convert_precision (Gimp.Precision.U8_GAMMA)
+    store_layer(thumb, thumb_layer, 'Thumbnails/thumbnail.png')
+    thumb.delete()
+
+    # write stack.xml
+    xml = ET.tostring(xml_image, encoding='UTF-8')
+    write_file_str(orafile, 'stack.xml', xml)
+
+    # finish up
+    orafile.close()
+    os.rmdir(tempdir)
+    if os.path.exists(file.peek_path()):
+        os.remove(file.peek_path()) # win32 needs that
+    os.rename(file.peek_path() + '.tmpsave', file.peek_path())
+
+    retval = Gimp.ValueArray.new(1)
+    retval.insert(0, GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS))
+
+    return retval
+
+def load_ora(procedure, run_mode, file, args, data):
+    tempdir = tempfile.mkdtemp('gimp-plugin-file-openraster')
+    orafile = zipfile.ZipFile(file.peek_path())
+    stack, w, h = get_image_attributes(orafile)
+
+    img = Gimp.Image.new(w, h, Gimp.ImageBaseType.RGB)
+    img.set_filename (file.peek_path())
+
+    def get_layers(root):
+        """iterates over layers and nested stacks"""
+        for item in root:
+            if item.tag == 'layer':
+                yield item
+            elif item.tag == 'stack':
+                yield item
+                for subitem in get_layers(item):
+                    yield subitem
+                yield NESTED_STACK_END
+
+    parent_groups = []
+
+    layer_no = 0
+    for item in get_layers(stack):
+
+        if item is NESTED_STACK_END:
+            parent_groups.pop()
+            continue
+
+        if item.tag == 'stack':
+            name, x, y, opac, visible, layer_mode = get_group_layer_attributes(item)
+            gimp_layer = img.layer_group_new()
+
+        else:
+            path, name, x, y, opac, visible, layer_mode = get_layer_attributes(item)
+
+            if not path.lower().endswith('.png'):
+                continue
+            if not name:
+                # use the filename without extension as name
+                n = os.path.basename(path)
+                name = os.path.splitext(n)[0]
+
+            # create temp file. Needed because gimp cannot load files from inside a zip file
+            tmp = os.path.join(tempdir, 'tmp.png')
+            with open(tmp, 'wb') as fid:
+                try:
+                    data = orafile.read(path)
+                except KeyError:
+                    # support for bad zip files (saved by old versions of this plugin)
+                    data = orafile.read(path.encode('utf-8'))
+                    print('WARNING: bad OpenRaster ZIP file. There is an utf-8 encoded filename that does 
not have the utf-8 flag set:', repr(path))
+                fid.write(data)
+
+            # import layer, set attributes and add to image
+            args = Gimp.ValueArray.new(3)
+            arg0 = GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE)
+            args.insert(0, arg0)
+            arg1 = GObject.Value(Gimp.Image, img)
+            args.insert(1, arg1)
+            arg2 = GObject.Value(GObject.TYPE_STRING, tmp)
+            args.insert(2, arg2)
+            gimp_layer = Gimp.get_pdb().run_procedure('gimp-file-load-layer', args)
+            gimp_layer = gimp_layer.index(1)
+            gimp_layer = Gimp.Item.get_by_id(gimp_layer)
+            os.remove(tmp)
+        gimp_layer.set_name(name)
+        gimp_layer.set_mode(layer_mode)
+        gimp_layer.set_offsets(x, y)  # move to correct position
+        gimp_layer.set_opacity(opac * 100)  # a float between 0 and 100
+        gimp_layer.set_visible(visible)
+
+        img.insert_layer(gimp_layer,
+                         parent_groups[-1][0] if parent_groups else None,
+                         parent_groups[-1][1] if parent_groups else layer_no)
+        if parent_groups:
+            parent_groups[-1][1] += 1
+        else:
+            layer_no += 1
+
+        if gimp_layer.is_group():
+            parent_groups.append([gimp_layer, 0])
+
+    os.rmdir(tempdir)
+
+    retval = Gimp.ValueArray.new(2)
+    arg0 = GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS)
+    retval.insert(0, arg0)
+    arg1 = GObject.Value(Gimp.Image, img)
+    retval.insert(1, arg1)
+
+    return retval
+
+
+class FileOpenRaster (Gimp.PlugIn):
+    ## GimpPlugIn virtual methods ##
+    def do_query_procedures(self):
+        self.set_translation_domain("gimp30-python",
+                                    Gio.file_new_for_path(Gimp.locale_directory()))
+
+        return [ 'file-openraster-save',
+                 'file-openraster-load',
+                 'file-openraster-load-thumb' ]
+
+    def do_create_procedure(self, name):
+        if name == 'file-openraster-save':
+            procedure = Gimp.SaveProcedure.new(self, name,
+                                               Gimp.PDBProcType.PLUGIN,
+                                               save_ora, None)
+            procedure.set_image_types("*");
+            procedure.set_documentation ('save an OpenRaster (.ora) file',
+                                         'save an OpenRaster (.ora) file',
+                                         name)
+            procedure.set_menu_label('OpenRaster')
+            procedure.set_extensions ("ora");
+        elif name == 'file-openraster-load':
+            procedure = Gimp.LoadProcedure.new (self, name,
+                                                Gimp.PDBProcType.PLUGIN,
+                                                load_ora, None)
+            procedure.set_menu_label('OpenRaster')
+            procedure.set_documentation ('load an OpenRaster (.ora) file',
+                                         'load an OpenRaster (.ora) file',
+                                         name)
+            procedure.set_mime_types ("image/openraster");
+            procedure.set_extensions ("ora");
+            procedure.set_thumbnail_loader ('file-openraster-load-thumb');
+        else: # 'file-openraster-load-thumb'
+            procedure = Gimp.ThumbnailProcedure.new (self, name,
+                                                     Gimp.PDBProcType.PLUGIN,
+                                                     thumbnail_ora, None)
+            procedure.set_documentation ('loads a thumbnail from an OpenRaster (.ora) file',
+                                         'loads a thumbnail from an OpenRaster (.ora) file',
+                                         name)
+            pass
+        procedure.set_attribution('Jon Nordby', #author
+                                  'Jon Nordby', #copyright
+                                  '2009') #year
+        return procedure
+
+Gimp.main(FileOpenRaster.__gtype__, sys.argv)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]