Can work with png

main
Micke Nordin 2 years ago
parent 326b563917
commit 0102844501
Signed by: micke
GPG Key ID: 0DA0A7A5708FE257

@ -3,6 +3,7 @@
<name>Georeference</name> <name>Georeference</name>
<id>org.smolnet.code.inkscape-coordinates</id> <id>org.smolnet.code.inkscape-coordinates</id>
<description>Geo reference an image</description> <description>Geo reference an image</description>
<!--effect implements-custom-gui="true"-->
<effect> <effect>
<effects-menu> <effects-menu>
<submenu name="Georeference"/> <submenu name="Georeference"/>

@ -15,59 +15,221 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # 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 Description of this extension
""" """
import inkex
import inkex.gui
import gi 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("Gtk", "3.0")
gi.require_version("Gdk", "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): class GeoReferencerWindow(Gtk.Window):
def __init__(self, svg): def __init__(self, pilimage=None) -> None:
super().__init__(title="Georeferencer") 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 | self.set_events(Gdk.EventMask.POINTER_MOTION_MASK |
Gdk.EventMask.BUTTON_PRESS_MASK) Gdk.EventMask.BUTTON_PRESS_MASK)
self.box = Gtk.Box() self.box: Gtk.Box = Gtk.Box()
self.add(self.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.button.connect("clicked", self.on_button_clicked)
self.box.pack_start(self.button, True, True, 0) self.box.pack_start(self.button, True, True, 0)
self.pix_man = inkex.gui.pixmap.PixmapManager() self.event_box: Gtk.EventBox = Gtk.EventBox()
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.set_above_child(above_child=True) self.event_box.set_above_child(above_child=True)
self.event_box.connect('motion-notify-event', self.on_motion_notify) 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('enter-notify-event', self.on_motion_notify)
self.event_box.connect('button-press-event', self.on_mouse_click) self.event_box.connect('button-press-event', self.on_mouse_click)
self.box.pack_start(self.event_box, True, True, 0) self.box.pack_start(self.event_box, True, True, 0)
self.connect("destroy", Gtk.main_quit) self.connect("destroy", Gtk.main_quit)
self.show_all()
def on_button_clicked(self, widget): def message_box(self, message: str):
self.destroy() 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)) widget.set_tooltip_text("x:{}, y:{}".format(event.x, event.y))
def on_mouse_click(self, widget, event): def on_mouse_click(self, widget, event) -> None:
widget.set_tooltip_text("HAHA x:{}, y:{}".format(event.x, event.y)) 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): 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): if not pilimage:
self.window = GeoReferencerWindow(self.svg.tostring()) self.window.message_box("Could not find image in selection")
self.window.show_all() self.window.show_all()
self.eventloop = Gtk.main() self.window.destroy()
else:
self.window.add_image(pilimage, mimetype)
self.window.show_all()
Gtk.main()
if __name__ == '__main__': if __name__ == '__main__':

@ -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…
Cancel
Save