parent
326b563917
commit
0102844501
@ -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()
|
@ -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()
|
Loading…
Reference in new issue