From 0102844501f4e6f482c0cc7fb1ef0ec66eacfdaf Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Mon, 29 Aug 2022 12:05:01 +0200 Subject: [PATCH] Can work with png --- coordinates_extension.inx | 1 + coordinates_extension.py | 206 ++++++++++++++++++++++++++--- coordinates_extension.py.faulty | 228 ++++++++++++++++++++++++++++++++ coordinates_extension.py.old | 159 ++++++++++++++++++++++ 4 files changed, 572 insertions(+), 22 deletions(-) create mode 100755 coordinates_extension.py.faulty create mode 100755 coordinates_extension.py.old diff --git a/coordinates_extension.inx b/coordinates_extension.inx index 638d042..6964b26 100644 --- a/coordinates_extension.inx +++ b/coordinates_extension.inx @@ -3,6 +3,7 @@ Georeference org.smolnet.code.inkscape-coordinates Geo reference an image + diff --git a/coordinates_extension.py b/coordinates_extension.py index 4745a0b..2cef4f5 100755 --- a/coordinates_extension.py +++ b/coordinates_extension.py @@ -15,59 +15,221 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # noqa: E501 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. # """ Description of this extension """ -import inkex -import inkex.gui import gi +import math +import io +import inkex +import base64 +from inkex import Image as IImage +from PIL import Image gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") -from gi.repository import Gtk, Gdk # noqa: E402 +from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, Gio # noqa: E402 + + +class GeoReferencerPoint(): + def __init__( + self, x: float, y: float, to_x: float = 0, to_y: float = 0 + ) -> None: + self.x: float = x + self.y: float = y + self.to_x: float = to_x + self.to_y: float = to_y + + +class GeoReferencer(): + def __init__(self, window) -> None: + self.points: list[GeoReferencerPoint] = list() + self.window = window + if 'pilimage' in self.window: + self.img = self.window.pilimage + + def add_point(self, point: GeoReferencerPoint) -> None: + self.points.append(point) + + def add_img(self, img) -> None: + self.img = img + + def do_transform(self): + if len(self.points) < 2: + self.window.message_box( + "Not enough points for georeferencing available.") + return None + ratio = get_scale_factor(self.points[0], self.points[1]) + angle = get_rotation_angle(self.points[0], self.points[1]) + width = int(self.img.width * ratio) + height = int(self.img.height * ratio) + self.window.message_box( + "w: {}, h: {}, s: {}, a: {}".format(width, height, ratio, angle)) + resized = self.img.resize( + (width, height), Image.Resampling.LANCZOS) + rotated = resized.rotate(angle) + return rotated class GeoReferencerWindow(Gtk.Window): - def __init__(self, svg): + def __init__(self, pilimage=None) -> None: super().__init__(title="Georeferencer") - self.svg = svg + if pilimage: + self.add_image(pilimage) + self.georeferencer: GeoReferencer = GeoReferencer(self) self.set_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK) - self.box = Gtk.Box() + self.box: Gtk.Box = Gtk.Box() self.add(self.box) - self.button = Gtk.Button(label="Georeference") + self.button: Gtk.Button = Gtk.Button(label="Georeference") self.button.connect("clicked", self.on_button_clicked) self.box.pack_start(self.button, True, True, 0) - self.pix_man = inkex.gui.pixmap.PixmapManager() - self.pixmap = self.pix_man.get_pixmap(self.svg) - self.event_box = Gtk.EventBox() - self.image = Gtk.Image.new_from_pixbuf(self.pixmap) - self.event_box.add(self.image) + self.event_box: Gtk.EventBox = Gtk.EventBox() self.event_box.set_above_child(above_child=True) self.event_box.connect('motion-notify-event', self.on_motion_notify) self.event_box.connect('enter-notify-event', self.on_motion_notify) self.event_box.connect('button-press-event', self.on_mouse_click) self.box.pack_start(self.event_box, True, True, 0) self.connect("destroy", Gtk.main_quit) + self.show_all() - def on_button_clicked(self, widget): - self.destroy() + def message_box(self, message: str): + dialog = Gtk.MessageDialog(transient_for=self, + modal=False, destroy_with_parent=True, + message_type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.OK, + text=message) + dialog.show_all() + dialog.run() + dialog.destroy() - def on_motion_notify(self, widget, event): + def on_button_clicked(self, widget) -> None: + gtkimage = pil_to_gtk(self.georeferencer.do_transform()) + self.event_box.remove(self.gtkimage) + self.gtkimage = gtkimage + self.event_box.add(self.gtkimage) + self.gtkimage.show_all() + self.queue_draw() + # self.destroy() + + def on_motion_notify(self, widget, event) -> None: widget.set_tooltip_text("x:{}, y:{}".format(event.x, event.y)) - def on_mouse_click(self, widget, event): - widget.set_tooltip_text("HAHA x:{}, y:{}".format(event.x, event.y)) + def on_mouse_click(self, widget, event) -> None: + dialog = Gtk.MessageDialog(transient_for=self, + modal=True, destroy_with_parent=True, + message_type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.OK_CANCEL, + text="Set new coordinates") + point_box = dialog.get_content_area() + x_label = Gtk.Label() + y_label = Gtk.Label() + x_label.set_text("x:") + y_label.set_text("y:") + x_entry = Gtk.Entry() + y_entry = Gtk.Entry() + point_box.pack_start(x_label, True, True, 0) + point_box.pack_start(x_entry, True, True, 0) + point_box.pack_start(y_label, True, True, 0) + point_box.pack_start(y_entry, True, True, 0) + dialog.show_all() + response = dialog.run() + if response == Gtk.ResponseType.OK: + if x_entry.get_text() and y_entry.get_text(): + to_x = float(x_entry.get_text()) + to_y = float(y_entry.get_text()) + point = GeoReferencerPoint(event.x, event.y, to_x, to_y) + self.georeferencer.add_point(point) + dialog.destroy() + + def add_image(self, pilimage, mimetype): + self.pilimage = pilimage + self.georeferencer.add_img(pilimage) + self.gtkimage = pil_to_gtk(pilimage, mimetype, window=self) + self.event_box.add(self.gtkimage) + self.event_box.show_all() + + +def pil_to_gtk(pilimage: Image, mimetype, window: GeoReferencerWindow = None): + if window: + msgfunc = window.message_box + else: + msgfunc = print + glibbytes = GLib.Bytes.new(pilimage.tobytes()) + if mimetype == "image/png": + glibdata = glibbytes.get_data() + gdkpixbuf = GdkPixbuf.Pixbuf.new_from_data( + glibdata, GdkPixbuf.Colorspace.RGB, True, 8, pilimage.width, pilimage.height, len( + pilimage.getbands( + ))*pilimage.width, None, None) + elif mimetype == "image/jpeg": + glibstream = Gio.MemoryInputStream.new_from_bytes(glibbytes) + gdkpixbuf = GdkPixbuf.Pixbuf.new_from_stream(glibstream, None) + else: + msgfunc("Unknown mimetype, can only handle jpg and png") + + gtkimage = Gtk.Image() + gtkimage.set_from_pixbuf(gdkpixbuf) + return gtkimage + + +def get_scale_factor(a: GeoReferencerPoint, b: GeoReferencerPoint) -> float: + mag_v = ((b.x - a.x)*(b.x - a.x)) + ((b.y - a.y)*(b.y - a.y)) + mag_w = ((b.to_x - a.to_x)*(b.to_x - a.to_x)) + \ + ((b.to_y - a.to_y)*(b.to_y - a.to_y)) + return mag_w/mag_v + + +def get_dot_product(a: GeoReferencerPoint, b: GeoReferencerPoint) -> float: + a1 = b.x-a.x + a2 = b.to_x - a.to_x + b1 = b.y - a.y + b2 = b.to_y - a.to_y + return (a1*b1) + (a2*b2) + + +def get_rotation_angle(a: GeoReferencerPoint, b: GeoReferencerPoint) -> float: + dot = get_dot_product(a, b) + mag_v = ((b.x - a.x)*(b.x - a.x)) + ((b.y - a.y)*(b.y - a.y)) + mag_w = ((b.to_x - a.to_x)*(b.to_x - a.to_x)) + \ + ((b.to_y - a.to_y)*(b.to_y - a.to_y)) + return (((dot/mag_v) / mag_w)*(180/math.pi)) class CoordinatesExtension(inkex.EffectExtension): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.window: GeoReferencerWindow = GeoReferencerWindow() + self.image_mimetype = str() + + def effect(self) -> None: + images = self.svg.selection.get(IImage) + for elem in images: + xlink = elem.get("xlink:href") + if xlink is not None and xlink[:5] == "data:": + data = xlink[5:] + (mimetype, data) = data.split(";", 1) + self.image_mimetype = mimetype + (base, data) = data.split(",", 1) + if base != "base64": + self.window.message_box( + "Unable to decode encoding {}.").format(base) + return + else: + imgdata = base64.b64decode(data) + pilimage = Image.open(io.BytesIO(imgdata)) - def effect(self): - self.window = GeoReferencerWindow(self.svg.tostring()) - self.window.show_all() - self.eventloop = Gtk.main() + if not pilimage: + self.window.message_box("Could not find image in selection") + self.window.show_all() + self.window.destroy() + else: + self.window.add_image(pilimage, mimetype) + self.window.show_all() + Gtk.main() if __name__ == '__main__': diff --git a/coordinates_extension.py.faulty b/coordinates_extension.py.faulty new file mode 100755 index 0000000..2cdc4ee --- /dev/null +++ b/coordinates_extension.py.faulty @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# coding=utf-8 +# +# Copyright (C) 2022 Mikael Nordin, hej@mic.ke +# +# 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +""" +Description of this extension +""" +import gi +import math +import io +import inkex +import base64 +from inkex import Image as IImage +from PIL import Image + +gi.require_version("Gtk", "3.0") +gi.require_version("Gdk", "3.0") +from gi.repository import Gtk, Gdk, GdkPixbuf, GLib # noqa: E402 + + +class GeoReferencerPoint(): + def __init__( + self, x: float, y: float, to_x: float = 0, to_y: float = 0 + ) -> None: + self.x: float = x + self.y: float = y + self.to_x: float = to_x + self.to_y: float = to_y + + +class GeoReferencer(): + def __init__(self, window) -> None: + self.points: list[GeoReferencerPoint] = list() + self.window = window + if 'pilimage' in self.window: + self.img = self.window.pilimage + + def add_point(self, point: GeoReferencerPoint) -> None: + self.points.append(point) + + def add_img(self, img) -> None: + self.img = img + + def do_transform(self): + if len(self.points) < 2: + self.window.message_box( + "Not enough points for georeferencing available.") + return None + ratio = get_scale_factor(self.points[0], self.points[1]) + angle = get_rotation_angle(self.points[0], self.points[1]) + width = int(self.img.width * ratio) + height = int(self.img.height * ratio) + self.window.message_box( + "w: {}, h: {}, s: {}, a: {}".format(width, height, ratio, angle)) + resized = self.img.resize( + (width, height), Image.Resampling.LANCZOS) + rotated = resized.rotate(angle) + return rotated + + +class GeoReferencerWindow(Gtk.Window): + def __init__(self, pilimage=None) -> None: + super().__init__(title="Georeferencer") + if pilimage: + self.add_image(pilimage) + self.georeferencer: GeoReferencer = GeoReferencer(self) + self.set_events(Gdk.EventMask.POINTER_MOTION_MASK | + Gdk.EventMask.BUTTON_PRESS_MASK) + self.box: Gtk.Box = Gtk.Box() + self.add(self.box) + self.button: Gtk.Button = Gtk.Button(label="Georeference") + self.button.connect("clicked", self.on_button_clicked) + self.box.pack_start(self.button, True, True, 0) + self.event_box: Gtk.EventBox = Gtk.EventBox() + self.event_box.set_above_child(above_child=True) + self.event_box.connect('motion-notify-event', self.on_motion_notify) + self.event_box.connect('enter-notify-event', self.on_motion_notify) + self.event_box.connect('button-press-event', self.on_mouse_click) + self.box.pack_start(self.event_box, True, True, 0) + self.connect("destroy", Gtk.main_quit) + self.show_all() + + def message_box(self, message: str): + dialog = Gtk.MessageDialog(transient_for=self, + modal=False, destroy_with_parent=True, + message_type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.OK, + text=message) + dialog.show_all() + dialog.run() + dialog.destroy() + + def on_button_clicked(self, widget) -> None: + gtkimage = pil_to_gtk(self.georeferencer.do_transform()) + self.event_box.remove(self.gtkimage) + self.gtkimage = gtkimage + self.event_box.add(self.gtkimage) + self.gtkimage.show_all() + self.queue_draw() + # self.destroy() + + def on_motion_notify(self, widget, event) -> None: + widget.set_tooltip_text("x:{}, y:{}".format(event.x, event.y)) + + def on_mouse_click(self, widget, event) -> None: + dialog = Gtk.MessageDialog(transient_for=self, + modal=True, destroy_with_parent=True, + message_type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.OK_CANCEL, + text="Set new coordinates") + point_box = dialog.get_content_area() + x_label = Gtk.Label() + y_label = Gtk.Label() + x_label.set_text("x:") + y_label.set_text("y:") + x_entry = Gtk.Entry() + y_entry = Gtk.Entry() + point_box.pack_start(x_label, True, True, 0) + point_box.pack_start(x_entry, True, True, 0) + point_box.pack_start(y_label, True, True, 0) + point_box.pack_start(y_entry, True, True, 0) + dialog.show_all() + response = dialog.run() + if response == Gtk.ResponseType.OK: + if x_entry.get_text() and y_entry.get_text(): + to_x = float(x_entry.get_text()) + to_y = float(y_entry.get_text()) + point = GeoReferencerPoint(event.x, event.y, to_x, to_y) + self.georeferencer.add_point(point) + dialog.destroy() + + def add_image(self, pilimage): + self.pilimage = pilimage + self.georeferencer.add_img(pilimage) + self.gtkimage = pil_to_gtk(pilimage, window=self) + self.event_box.add(self.gtkimage) + self.event_box.show_all() + + +def pil_to_gtk(pilimage: Image, window: GeoReferencerWindow = None): + if window: + msgfunc = window.message_box + else: + msgfunc = print + glibbytes = GLib.Bytes.new(pilimage.tobytes()) + glibdata = glibbytes.get_data() + msgfunc(len(glibdata)) + gdkpixbuf = GdkPixbuf.Pixbuf.new_from_data( + glibdata, GdkPixbuf.Colorspace.RGB, True, 8, pilimage.width, pilimage.height, len( + pilimage.getbands( + ))*pilimage.width, None, None) + + gtkimage = Gtk.Image() + gtkimage.set_from_pixbuf(gdkpixbuf) + return gtkimage + + +def get_scale_factor(a: GeoReferencerPoint, b: GeoReferencerPoint) -> float: + mag_v = ((b.x - a.x)*(b.x - a.x)) + ((b.y - a.y)*(b.y - a.y)) + mag_w = ((b.to_x - a.to_x)*(b.to_x - a.to_x)) + \ + ((b.to_y - a.to_y)*(b.to_y - a.to_y)) + return mag_w/mag_v + + +def get_dot_product(a: GeoReferencerPoint, b: GeoReferencerPoint) -> float: + a1 = b.x-a.x + a2 = b.to_x - a.to_x + b1 = b.y - a.y + b2 = b.to_y - a.to_y + return (a1*b1) + (a2*b2) + + +def get_rotation_angle(a: GeoReferencerPoint, b: GeoReferencerPoint) -> float: + dot = get_dot_product(a, b) + mag_v = ((b.x - a.x)*(b.x - a.x)) + ((b.y - a.y)*(b.y - a.y)) + mag_w = ((b.to_x - a.to_x)*(b.to_x - a.to_x)) + \ + ((b.to_y - a.to_y)*(b.to_y - a.to_y)) + return (((dot/mag_v) / mag_w)*(180/math.pi)) + + +class CoordinatesExtension(inkex.EffectExtension): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.window: GeoReferencerWindow = GeoReferencerWindow() + + def effect(self) -> None: + images = self.svg.selection.get(IImage) + for elem in images: + xlink = elem.get("xlink:href") + if xlink is not None and xlink[:5] == "data:": + data = xlink[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + if base != "base64": + self.debug_box( + "Unable to decode encoding {}.").format(base) + else: + imgdata = base64.b64decode(data) + pilimage = Image.open(io.BytesIO(imgdata)) + + if not pilimage: + self.window.message_box("Could not find image in selection") + self.window.show_all() + self.window.destroy() + else: + self.window.add_image(pilimage) + self.window.show_all() + Gtk.main() + + +if __name__ == '__main__': + CoordinatesExtension().run() diff --git a/coordinates_extension.py.old b/coordinates_extension.py.old new file mode 100755 index 0000000..7f18c6d --- /dev/null +++ b/coordinates_extension.py.old @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# coding=utf-8 +# +# Copyright (C) 2022 Mikael Nordin, hej@mic.ke +# +# 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +""" +Description of this extension +""" +import inkex +from inkex import Image as IImage +import gi +import io +import base64 +from PIL import Image + +gi.require_version("Gtk", "3.0") +gi.require_version("Gdk", "3.0") +from gi.repository import Gtk, Gdk # noqa: E402 + + +class GeoReferencerPoint(): + def __init__(self, x: int, y: int, to_x: int = 0, to_y: int = 0) -> None: + self.x: int = x + self.y: int = y + self.to_x: int = to_x + self.to_y: int = to_y + + +class GeoReferencer(): + def __init__(self, window) -> None: + self.points: list[GeoReferencerPoint] = list() + self.window = window + + def add_point(self, point: GeoReferencerPoint) -> None: + self.points.append(point) + + def add_img(self, img) -> None: + self.img = img + + def do_transform(self): + pass + + +class GeoReferencerWindow(Gtk.Window): + def __init__(self) -> None: + super().__init__(title="Georeferencer") + self.georeferencer: GeoReferencer = GeoReferencer(self) + self.set_events(Gdk.EventMask.POINTER_MOTION_MASK | + Gdk.EventMask.BUTTON_PRESS_MASK) + self.box: Gtk.Box = Gtk.Box() + self.add(self.box) + self.button: Gtk.Button = Gtk.Button(label="Georeference") + self.button.connect("clicked", self.on_button_clicked) + self.box.pack_start(self.button, True, True, 0) + self.event_box: Gtk.EventBox = Gtk.EventBox() + self.event_box.set_above_child(above_child=True) + self.event_box.connect('motion-notify-event', self.on_motion_notify) + self.event_box.connect('enter-notify-event', self.on_motion_notify) + self.event_box.connect('button-press-event', self.on_mouse_click) + self.box.pack_start(self.event_box, True, True, 0) + self.connect("destroy", Gtk.main_quit) + + def debug_box(self, message: str): + dialog = Gtk.MessageDialog(transient_for=self, + modal=False, destroy_with_parent=True, + message_type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.OK, + text=message) + dialog.show_all() + dialog.run() + dialog.destroy() + + def on_button_clicked(self, widget) -> None: + img = self.georeferencer.do_transform() + self.event_box.remove(self.image) + self.image = img + self.event_box.add(self.image) + self.image.show_all() + self.queue_draw() + # self.destroy() + + def on_motion_notify(self, widget, event) -> None: + widget.set_tooltip_text("x:{}, y:{}".format(event.x, event.y)) + + def on_mouse_click(self, widget, event) -> None: + dialog = Gtk.MessageDialog(transient_for=self, + modal=True, destroy_with_parent=True, + message_type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.OK_CANCEL, + text="Set new coordinates") + point_box = dialog.get_content_area() + x_label = Gtk.Label() + y_label = Gtk.Label() + x_label.set_text("x:") + y_label.set_text("y:") + x_entry = Gtk.Entry() + y_entry = Gtk.Entry() + point_box.pack_start(x_label, True, True, 0) + point_box.pack_start(x_entry, True, True, 0) + point_box.pack_start(y_label, True, True, 0) + point_box.pack_start(y_entry, True, True, 0) + dialog.show_all() + response = dialog.run() + if response == Gtk.ResponseType.OK: + to_x = int(x_entry.get_text()) + to_y = int(y_entry.get_text()) + point = GeoReferencerPoint(event.x, event.y, to_x, to_y) + self.georeferencer.add_point(point) + dialog.destroy() + + def add_image(self, img): + self.image = img + self.georeferencer.add_img(self.image) + self.event_box.add(self.image) + self.show_all() + + +class CoordinatesExtension(inkex.EffectExtension): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.window: GeoReferencerWindow = GeoReferencerWindow() + + def effect(self) -> None: + images = self.svg.selection.get(IImage) + for elem in images: + xlink = elem.get("xlink:href") + if xlink is not None and xlink[:5] == "data:": + data = xlink[5:] + (mimetype, data) = data.split(";", 1) + (base, data) = data.split(",", 1) + if base != "base64": + self.debug_box( + "Unable to decode encoding {}.").format(base) + else: + imgdata = base64.b64decode(data) + image = Image.open(io.BytesIO(imgdata)) + + self.window.add_image(image) + self.window.show_all() + Gtk.main() + + +if __name__ == '__main__': + CoordinatesExtension().run()