2019-08-25 10:05:40 +08:00
|
|
|
#!/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 = {
|
2021-01-26 03:59:12 +08:00
|
|
|
"svg:src-over": Gimp.LayerMode.NORMAL,
|
|
|
|
"svg:multiply": Gimp.LayerMode.MULTIPLY,
|
|
|
|
"svg:screen": Gimp.LayerMode.SCREEN,
|
|
|
|
"svg:overlay": Gimp.LayerMode.OVERLAY,
|
|
|
|
"svg:darken": Gimp.LayerMode.DARKEN_ONLY,
|
|
|
|
"svg:lighten": Gimp.LayerMode.LIGHTEN_ONLY,
|
|
|
|
"svg:color-dodge": Gimp.LayerMode.DODGE,
|
|
|
|
"svg:color-burn": Gimp.LayerMode.BURN,
|
|
|
|
"svg:hard-light": Gimp.LayerMode.HARDLIGHT,
|
|
|
|
"svg:soft-light": Gimp.LayerMode.SOFTLIGHT,
|
|
|
|
"svg:difference": Gimp.LayerMode.DIFFERENCE,
|
|
|
|
"svg:color": Gimp.LayerMode.HSL_COLOR,
|
|
|
|
"svg:luminosity": Gimp.LayerMode.HSV_VALUE,
|
|
|
|
"svg:hue": Gimp.LayerMode.HSV_HUE,
|
|
|
|
"svg:saturation": Gimp.LayerMode.HSV_SATURATION,
|
|
|
|
"svg:plus": Gimp.LayerMode.ADDITION,
|
2019-08-25 10:05:40 +08:00
|
|
|
}
|
|
|
|
|
2021-01-28 03:11:39 +08:00
|
|
|
# There are less svg blending ops than we have GIMP blend modes.
|
|
|
|
# We are going to map them as closely as possible.
|
|
|
|
gimp_layermodes_map = {
|
|
|
|
Gimp.LayerMode.NORMAL: "svg:src-over",
|
|
|
|
Gimp.LayerMode.NORMAL_LEGACY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.MULTIPLY: "svg:multiply",
|
|
|
|
Gimp.LayerMode.MULTIPLY_LEGACY: "svg:multiply",
|
|
|
|
Gimp.LayerMode.SCREEN: "svg:screen",
|
|
|
|
Gimp.LayerMode.SCREEN_LEGACY: "svg:screen",
|
|
|
|
Gimp.LayerMode.OVERLAY: "svg:overlay",
|
|
|
|
Gimp.LayerMode.OVERLAY_LEGACY: "svg:overlay",
|
|
|
|
Gimp.LayerMode.DARKEN_ONLY: "svg:darken",
|
|
|
|
Gimp.LayerMode.DARKEN_ONLY_LEGACY: "svg:darken",
|
|
|
|
Gimp.LayerMode.LIGHTEN_ONLY: "svg:lighten",
|
|
|
|
Gimp.LayerMode.LIGHTEN_ONLY_LEGACY: "svg:lighten",
|
|
|
|
Gimp.LayerMode.DODGE: "svg:color-dodge",
|
|
|
|
Gimp.LayerMode.DODGE_LEGACY: "svg:color-dodge",
|
|
|
|
Gimp.LayerMode.BURN: "svg:color-burn",
|
|
|
|
Gimp.LayerMode.BURN_LEGACY: "svg:color-burn",
|
|
|
|
Gimp.LayerMode.HARDLIGHT: "svg:hard-light",
|
|
|
|
Gimp.LayerMode.HARDLIGHT_LEGACY: "svg:hard-light",
|
|
|
|
Gimp.LayerMode.SOFTLIGHT: "svg:soft-light",
|
|
|
|
Gimp.LayerMode.SOFTLIGHT_LEGACY: "svg:soft-light",
|
|
|
|
Gimp.LayerMode.DIFFERENCE: "svg:difference",
|
|
|
|
Gimp.LayerMode.DIFFERENCE_LEGACY: "svg:difference",
|
|
|
|
Gimp.LayerMode.HSL_COLOR: "svg:color",
|
|
|
|
Gimp.LayerMode.HSL_COLOR_LEGACY: "svg:color",
|
|
|
|
Gimp.LayerMode.HSV_VALUE: "svg:luminosity",
|
|
|
|
Gimp.LayerMode.HSV_VALUE_LEGACY: "svg:luminosity",
|
|
|
|
Gimp.LayerMode.HSV_HUE: "svg:hue",
|
|
|
|
Gimp.LayerMode.HSV_HUE_LEGACY: "svg:hue",
|
|
|
|
Gimp.LayerMode.HSV_SATURATION: "svg:saturation",
|
|
|
|
Gimp.LayerMode.HSV_SATURATION_LEGACY: "svg:saturation",
|
|
|
|
Gimp.LayerMode.ADDITION: "svg:plus",
|
|
|
|
Gimp.LayerMode.ADDITION_LEGACY: "svg:plus",
|
|
|
|
|
|
|
|
# FIXME Determine the closest available layer mode
|
|
|
|
# Alternatively we could add additional modes
|
|
|
|
# e.g. something like "gimp:dissolve", this
|
|
|
|
# is what Krita seems to do too
|
|
|
|
Gimp.LayerMode.DISSOLVE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.DIVIDE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.DIVIDE_LEGACY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.BEHIND: "svg:src-over",
|
|
|
|
Gimp.LayerMode.BEHIND_LEGACY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.GRAIN_EXTRACT: "svg:src-over",
|
|
|
|
Gimp.LayerMode.GRAIN_EXTRACT_LEGACY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.GRAIN_MERGE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.GRAIN_MERGE_LEGACY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.COLOR_ERASE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.COLOR_ERASE_LEGACY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LCH_HUE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LCH_CHROMA: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LCH_COLOR: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LCH_LIGHTNESS: "svg:src-over",
|
|
|
|
Gimp.LayerMode.SUBTRACT: "svg:src-over",
|
|
|
|
Gimp.LayerMode.SUBTRACT_LEGACY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.VIVID_LIGHT: "svg:src-over",
|
|
|
|
Gimp.LayerMode.PIN_LIGHT: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LINEAR_LIGHT: "svg:src-over",
|
|
|
|
Gimp.LayerMode.HARD_MIX: "svg:src-over",
|
|
|
|
Gimp.LayerMode.EXCLUSION: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LINEAR_BURN: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LUMA_DARKEN_ONLY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LUMA_LIGHTEN_ONLY: "svg:src-over",
|
|
|
|
Gimp.LayerMode.LUMINANCE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.COLOR_ERASE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.ERASE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.MERGE: "svg:src-over",
|
|
|
|
Gimp.LayerMode.SPLIT: "svg:src-over",
|
|
|
|
Gimp.LayerMode.PASS_THROUGH: "svg:src-over",
|
|
|
|
}
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
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')
|
2021-01-26 00:58:53 +08:00
|
|
|
layer_mode = layermodes_map.get(m, Gimp.LayerMode.NORMAL)
|
2019-08-25 10:05:40 +08:00
|
|
|
|
|
|
|
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'))
|
|
|
|
|
2022-02-10 05:50:28 +08:00
|
|
|
thumb_file = Gio.file_new_for_path(tmp)
|
2023-10-20 23:14:46 +08:00
|
|
|
pdb_proc = Gimp.get_pdb().lookup_procedure('file-png-load')
|
|
|
|
pdb_config = pdb_proc.create_config()
|
|
|
|
pdb_config.set_property('run-mode', Gimp.RunMode.NONINTERACTIVE)
|
|
|
|
pdb_config.set_property('file', thumb_file)
|
|
|
|
result = pdb_proc.run(pdb_config)
|
2019-08-25 10:05:40 +08:00
|
|
|
os.remove(tmp)
|
|
|
|
os.rmdir(tempdir)
|
|
|
|
|
2021-01-28 08:00:31 +08:00
|
|
|
if (result.index(0) == Gimp.PDBStatusType.SUCCESS):
|
|
|
|
img = result.index(1)
|
|
|
|
# TODO: scaling
|
|
|
|
|
|
|
|
return Gimp.ValueArray.new_from_values([
|
|
|
|
GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS),
|
|
|
|
GObject.Value(Gimp.Image, img),
|
|
|
|
GObject.Value(GObject.TYPE_INT, w),
|
|
|
|
GObject.Value(GObject.TYPE_INT, h),
|
|
|
|
GObject.Value(Gimp.ImageType, Gimp.ImageType.RGB_IMAGE),
|
|
|
|
GObject.Value(GObject.TYPE_INT, 1)
|
|
|
|
])
|
|
|
|
else:
|
2022-02-10 05:50:28 +08:00
|
|
|
return procedure.new_return_values(result.index(0), GLib.Error(result.index(1)))
|
2019-08-25 10:05:40 +08:00
|
|
|
|
2024-04-30 21:50:24 +08:00
|
|
|
def export_ora(procedure, run_mode, image, file, metadata, config, data):
|
2019-08-25 10:05:40 +08:00
|
|
|
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)
|
|
|
|
|
2021-01-28 07:38:05 +08:00
|
|
|
Gimp.progress_init("Exporting openraster image")
|
2019-08-25 10:05:40 +08:00
|
|
|
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
|
2021-04-22 01:04:46 +08:00
|
|
|
a['w'] = str(image.get_width())
|
|
|
|
a['h'] = str(image.get_height())
|
2019-08-25 10:05:40 +08:00
|
|
|
|
|
|
|
def store_layer(image, drawable, path):
|
|
|
|
tmp = os.path.join(tempdir, 'tmp.png')
|
|
|
|
interlace, compression = 0, 2
|
gimppdb: Allow more easy bindable API
Plug-ins that work from different bindings probably want to use their
own list-type to specify arguments, rather than working with a more
cumbersome `GimpValueArray`.
This new API should make it less verbose. For example:
```
args = Gimp.ValueArray.new(5)
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, mask))
args.insert(3, GObject.Value(GObject.TYPE_INT, int(time.time())))
args.insert(4, GObject.Value(GObject.TYPE_DOUBLE, turbulence))
Gimp.get_pdb().run_procedure('plug-in-plasma', args)
```
becomes
```
Gimp.get_pdb().run_procedure('plug-in-plasma', [
GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE),
GObject.Value(Gimp.Image, image),
GObject.Value(Gimp.Drawable, mask),
GObject.Value(GObject.TYPE_INT, int(time.time())),
GObject.Value(GObject.TYPE_DOUBLE, turbulence),
])
```
2020-05-20 00:43:43 +08:00
|
|
|
|
2024-04-30 21:50:24 +08:00
|
|
|
#TODO: Use GimpExportOptions for this once available
|
|
|
|
width, height = drawable.get_width(), drawable.get_height()
|
|
|
|
tmp_img = Gimp.Image.new(width, height, image.get_base_type())
|
|
|
|
tmp_layer = Gimp.Layer.new_from_drawable (drawable, tmp_img)
|
|
|
|
tmp_img.insert_layer (tmp_layer, None, 0)
|
|
|
|
|
2024-04-13 23:10:25 +08:00
|
|
|
pdb_proc = Gimp.get_pdb().lookup_procedure('file-png-export')
|
2023-10-20 23:14:46 +08:00
|
|
|
pdb_config = pdb_proc.create_config()
|
|
|
|
pdb_config.set_property('run-mode', Gimp.RunMode.NONINTERACTIVE)
|
2024-04-30 21:50:24 +08:00
|
|
|
pdb_config.set_property('image', tmp_img)
|
2023-10-20 23:14:46 +08:00
|
|
|
pdb_config.set_property('file', Gio.File.new_for_path(tmp))
|
|
|
|
pdb_config.set_property('interlaced', interlace)
|
|
|
|
pdb_config.set_property('compression', compression)
|
|
|
|
# write all PNG chunks except oFFs(ets)
|
|
|
|
pdb_config.set_property('bkgd', True)
|
|
|
|
pdb_config.set_property('offs', False)
|
|
|
|
pdb_config.set_property('phys', True)
|
|
|
|
pdb_config.set_property('time', True)
|
|
|
|
pdb_config.set_property('save-transparent', True)
|
|
|
|
pdb_proc.run(pdb_config)
|
2020-11-01 00:49:54 +08:00
|
|
|
if (os.path.exists(tmp)):
|
|
|
|
orafile.write(tmp, path)
|
|
|
|
os.remove(tmp)
|
|
|
|
else:
|
|
|
|
print("Error removing ", tmp)
|
2019-08-25 10:05:40 +08:00
|
|
|
|
2024-04-30 21:50:24 +08:00
|
|
|
tmp_img.delete()
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
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'
|
2021-01-28 03:11:39 +08:00
|
|
|
a['composite-op'] = gimp_layermodes_map.get(gimp_layer.get_mode(), 'svg:src-over')
|
2019-08-25 10:05:40 +08:00
|
|
|
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
|
2021-01-26 00:58:53 +08:00
|
|
|
a['name'] = gimp_layer.get_name()
|
2019-08-25 10:05:40 +08:00
|
|
|
a['opacity'] = str(opac)
|
|
|
|
a['visibility'] = 'visible' if visible else 'hidden'
|
2021-01-28 03:11:39 +08:00
|
|
|
a['composite-op'] = gimp_layermodes_map.get(gimp_layer.get_mode(), 'svg:src-over')
|
2019-08-25 10:05:40 +08:00
|
|
|
return group_layer
|
|
|
|
|
|
|
|
|
|
|
|
def enumerate_layers(layers):
|
|
|
|
for layer in layers:
|
|
|
|
if not layer.is_group():
|
|
|
|
yield layer
|
|
|
|
else:
|
|
|
|
yield layer
|
2024-07-06 20:18:36 +08:00
|
|
|
for sublayer in enumerate_layers(layer.get_children()):
|
2019-08-25 10:05:40 +08:00
|
|
|
yield sublayer
|
|
|
|
yield NESTED_STACK_END
|
|
|
|
|
|
|
|
# save layers
|
|
|
|
parent_groups = []
|
|
|
|
i = 0
|
2021-01-28 07:38:05 +08:00
|
|
|
|
2024-07-06 20:18:36 +08:00
|
|
|
layer_stack = image.get_layers()
|
2021-01-28 07:38:05 +08:00
|
|
|
# Number of top level layers for tracking progress
|
|
|
|
lay_cnt = len(layer_stack)
|
|
|
|
|
|
|
|
for lay in enumerate_layers(layer_stack):
|
|
|
|
prev_lay = i
|
2019-08-25 10:05:40 +08:00
|
|
|
if lay is NESTED_STACK_END:
|
|
|
|
parent_groups.pop()
|
|
|
|
continue
|
2021-04-22 01:04:46 +08:00
|
|
|
_, x, y = lay.get_offsets()
|
2019-08-25 10:05:40 +08:00
|
|
|
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())
|
|
|
|
|
2021-01-28 07:38:05 +08:00
|
|
|
if (i > prev_lay):
|
|
|
|
Gimp.progress_update(i/lay_cnt)
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
# save mergedimage
|
2020-11-01 00:49:54 +08:00
|
|
|
thumb = image.duplicate()
|
2019-08-25 10:05:40 +08:00
|
|
|
thumb_layer = thumb.merge_visible_layers (Gimp.MergeType.CLIP_TO_IMAGE)
|
|
|
|
store_layer (thumb, thumb_layer, 'mergedimage.png')
|
|
|
|
|
|
|
|
# save thumbnail
|
2021-04-22 01:04:46 +08:00
|
|
|
w, h = image.get_width(), image.get_height()
|
2019-08-25 10:05:40 +08:00
|
|
|
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())
|
|
|
|
|
2021-01-28 07:38:05 +08:00
|
|
|
Gimp.progress_end()
|
|
|
|
|
gimppdb: Allow more easy bindable API
Plug-ins that work from different bindings probably want to use their
own list-type to specify arguments, rather than working with a more
cumbersome `GimpValueArray`.
This new API should make it less verbose. For example:
```
args = Gimp.ValueArray.new(5)
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, mask))
args.insert(3, GObject.Value(GObject.TYPE_INT, int(time.time())))
args.insert(4, GObject.Value(GObject.TYPE_DOUBLE, turbulence))
Gimp.get_pdb().run_procedure('plug-in-plasma', args)
```
becomes
```
Gimp.get_pdb().run_procedure('plug-in-plasma', [
GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE),
GObject.Value(Gimp.Image, image),
GObject.Value(Gimp.Drawable, mask),
GObject.Value(GObject.TYPE_INT, int(time.time())),
GObject.Value(GObject.TYPE_DOUBLE, turbulence),
])
```
2020-05-20 00:43:43 +08:00
|
|
|
return Gimp.ValueArray.new_from_values([
|
|
|
|
GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS)
|
|
|
|
])
|
2019-08-25 10:05:40 +08:00
|
|
|
|
2023-08-06 09:36:50 +08:00
|
|
|
def load_ora(procedure, run_mode, file, metadata, flags, config, data):
|
2019-08-25 10:05:40 +08:00
|
|
|
tempdir = tempfile.mkdtemp('gimp-plugin-file-openraster')
|
|
|
|
orafile = zipfile.ZipFile(file.peek_path())
|
|
|
|
stack, w, h = get_image_attributes(orafile)
|
|
|
|
|
2021-01-28 07:38:05 +08:00
|
|
|
Gimp.progress_init("Loading openraster image")
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
img = Gimp.Image.new(w, h, Gimp.ImageBaseType.RGB)
|
|
|
|
|
|
|
|
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 = []
|
|
|
|
|
2021-01-28 07:38:05 +08:00
|
|
|
# Number of top level layers for tracking progress
|
|
|
|
lay_cnt = len(stack)
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
layer_no = 0
|
|
|
|
for item in get_layers(stack):
|
2021-01-28 07:38:05 +08:00
|
|
|
prev_lay = layer_no
|
2019-08-25 10:05:40 +08:00
|
|
|
|
|
|
|
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)
|
2024-07-07 20:37:33 +08:00
|
|
|
gimp_layer = Gimp.GroupLayer.new(img, name)
|
2019-08-25 10:05:40 +08:00
|
|
|
|
|
|
|
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
|
2023-10-20 23:14:46 +08:00
|
|
|
pdb_proc = Gimp.get_pdb().lookup_procedure('gimp-file-load-layer')
|
|
|
|
pdb_config = pdb_proc.create_config()
|
|
|
|
pdb_config.set_property('run-mode', Gimp.RunMode.NONINTERACTIVE)
|
|
|
|
pdb_config.set_property('image', img)
|
|
|
|
pdb_config.set_property('file', Gio.File.new_for_path(tmp))
|
|
|
|
result = pdb_proc.run(pdb_config)
|
2021-01-28 08:00:31 +08:00
|
|
|
if (result.index(0) == Gimp.PDBStatusType.SUCCESS):
|
2023-10-20 23:14:46 +08:00
|
|
|
gimp_layer = result.index(1)
|
2021-01-28 08:00:31 +08:00
|
|
|
os.remove(tmp)
|
|
|
|
else:
|
|
|
|
print("Error loading layer from openraster image.")
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
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])
|
|
|
|
|
2021-01-28 07:38:05 +08:00
|
|
|
if (layer_no > prev_lay):
|
|
|
|
Gimp.progress_update(layer_no/lay_cnt)
|
|
|
|
|
|
|
|
Gimp.progress_end()
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
os.rmdir(tempdir)
|
|
|
|
|
gimppdb: Allow more easy bindable API
Plug-ins that work from different bindings probably want to use their
own list-type to specify arguments, rather than working with a more
cumbersome `GimpValueArray`.
This new API should make it less verbose. For example:
```
args = Gimp.ValueArray.new(5)
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, mask))
args.insert(3, GObject.Value(GObject.TYPE_INT, int(time.time())))
args.insert(4, GObject.Value(GObject.TYPE_DOUBLE, turbulence))
Gimp.get_pdb().run_procedure('plug-in-plasma', args)
```
becomes
```
Gimp.get_pdb().run_procedure('plug-in-plasma', [
GObject.Value(Gimp.RunMode, Gimp.RunMode.NONINTERACTIVE),
GObject.Value(Gimp.Image, image),
GObject.Value(Gimp.Drawable, mask),
GObject.Value(GObject.TYPE_INT, int(time.time())),
GObject.Value(GObject.TYPE_DOUBLE, turbulence),
])
```
2020-05-20 00:43:43 +08:00
|
|
|
return Gimp.ValueArray.new_from_values([
|
|
|
|
GObject.Value(Gimp.PDBStatusType, Gimp.PDBStatusType.SUCCESS),
|
|
|
|
GObject.Value(Gimp.Image, img),
|
2023-08-06 09:36:50 +08:00
|
|
|
]), flags
|
2019-08-25 10:05:40 +08:00
|
|
|
|
|
|
|
|
|
|
|
class FileOpenRaster (Gimp.PlugIn):
|
|
|
|
## GimpPlugIn virtual methods ##
|
2022-05-26 06:59:36 +08:00
|
|
|
def do_set_i18n(self, procname):
|
|
|
|
return True, 'gimp30-python', None
|
|
|
|
|
2019-08-25 10:05:40 +08:00
|
|
|
def do_query_procedures(self):
|
2019-09-11 01:36:54 +08:00
|
|
|
return [ 'file-openraster-load-thumb',
|
2019-08-25 10:05:40 +08:00
|
|
|
'file-openraster-load',
|
2024-04-13 23:10:25 +08:00
|
|
|
'file-openraster-export' ]
|
2019-08-25 10:05:40 +08:00
|
|
|
|
|
|
|
def do_create_procedure(self, name):
|
2024-04-13 23:10:25 +08:00
|
|
|
if name == 'file-openraster-export':
|
2024-04-20 11:08:57 +08:00
|
|
|
procedure = Gimp.ExportProcedure.new(self, name,
|
|
|
|
Gimp.PDBProcType.PLUGIN,
|
|
|
|
False, export_ora, None)
|
2019-08-25 10:05:40 +08:00
|
|
|
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)
|
|
|
|
procedure.set_attribution('Jon Nordby', #author
|
|
|
|
'Jon Nordby', #copyright
|
|
|
|
'2009') #year
|
|
|
|
return procedure
|
|
|
|
|
|
|
|
Gimp.main(FileOpenRaster.__gtype__, sys.argv)
|