2024-10-23 16:39:16 +02:00
|
|
|
"""
|
|
|
|
This little library provides functions to add corner pin functionality
|
|
|
|
into python in general, and pyglet specifically.
|
|
|
|
|
|
|
|
Long time ago I used a set of javascript functions from Cihad Turhan (see
|
|
|
|
https://stackoverflow.com/a/30646172), which I now ported to python.
|
|
|
|
These computations might be faster with numpy, but for me these run only
|
|
|
|
incidently so won't really hurt performance.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import pyglet
|
|
|
|
from typing import Optional
|
|
|
|
import logging
|
2024-11-08 18:14:14 +01:00
|
|
|
import copy
|
2024-10-23 16:39:16 +02:00
|
|
|
|
|
|
|
def adj(m): # Compute the adjugate of m
|
|
|
|
return [
|
|
|
|
m[4]*m[8]-m[5]*m[7], m[2]*m[7]-m[1]*m[8], m[1]*m[5]-m[2]*m[4],
|
|
|
|
m[5]*m[6]-m[3]*m[8], m[0]*m[8]-m[2]*m[6], m[2]*m[3]-m[0]*m[5],
|
|
|
|
m[3]*m[7]-m[4]*m[6], m[1]*m[6]-m[0]*m[7], m[0]*m[4]-m[1]*m[3]
|
|
|
|
]
|
|
|
|
|
|
|
|
def multmm(a, b) -> list[float]: # multiply two matrices
|
|
|
|
c = [0.]*9
|
|
|
|
for i in range(3):
|
|
|
|
for j in range(3):
|
|
|
|
cij = 0
|
|
|
|
for k in range(3):
|
|
|
|
cij += a[3*i + k]*b[3*k + j]
|
|
|
|
c[3 * i + j] = cij
|
|
|
|
return c
|
|
|
|
|
|
|
|
def multmv(m, v): # multiply matrix and vector
|
|
|
|
return [
|
|
|
|
m[0]*v[0] + m[1]*v[1] + m[2]*v[2],
|
|
|
|
m[3]*v[0] + m[4]*v[1] + m[5]*v[2],
|
|
|
|
m[6]*v[0] + m[7]*v[1] + m[8]*v[2]
|
|
|
|
]
|
|
|
|
|
|
|
|
# def pdbg(m, v) :
|
|
|
|
# r = multmv(m, v)
|
|
|
|
# return r + " (" + r[0]/r[2] + ", " + r[1]/r[2] + ")"
|
|
|
|
|
|
|
|
def basisToPoints(x1, y1, x2, y2, x3, y3, x4, y4) :
|
|
|
|
m = [
|
|
|
|
x1, x2, x3,
|
|
|
|
y1, y2, y3,
|
|
|
|
1, 1, 1
|
|
|
|
]
|
|
|
|
v = multmv(adj(m), [x4, y4, 1])
|
|
|
|
return multmm(m, [
|
|
|
|
v[0], 0, 0,
|
|
|
|
0, v[1], 0,
|
|
|
|
0, 0, v[2]
|
|
|
|
])
|
|
|
|
|
|
|
|
def general2DProjection(
|
|
|
|
x1s, y1s, x1d, y1d,
|
|
|
|
x2s, y2s, x2d, y2d,
|
|
|
|
x3s, y3s, x3d, y3d,
|
|
|
|
x4s, y4s, x4d, y4d
|
|
|
|
) :
|
|
|
|
s = basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s)
|
|
|
|
d = basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d)
|
|
|
|
return multmm(d, adj(s))
|
|
|
|
|
2024-11-08 18:14:14 +01:00
|
|
|
def transform2d_wh(w, h, x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d):
|
|
|
|
"""
|
|
|
|
Transform the image corners based on width and height of the image
|
|
|
|
"""
|
|
|
|
return transform2d(0, 0, x1d, y1d, w, 0, x2d, y2d, 0, h, x3d, y3d, w, h, x4d, y4d)
|
|
|
|
|
|
|
|
def transform2d(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s, x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d):
|
|
|
|
"""
|
|
|
|
Transform the image based on 4 arbitrarily placed pins
|
|
|
|
"""
|
|
|
|
t = general2DProjection(x1s, y1s, x1d, y1d, x2s, y2s, x2d, y2d, x3s, y3s, x3d, y3d, x4s, y4s, x4d, y4d)
|
2024-10-23 16:39:16 +02:00
|
|
|
for i in range(9):
|
|
|
|
t[i] = t[i] / t[8]
|
|
|
|
|
|
|
|
t = [t[0], t[3], 0, t[6],
|
|
|
|
t[1], t[4], 0, t[7],
|
|
|
|
0 , 0 , 1, 0 ,
|
|
|
|
t[2], t[5], 0, t[8]]
|
|
|
|
return t
|
|
|
|
|
|
|
|
# Below are all pyglet specific functions
|
|
|
|
|
|
|
|
class PygletCornerPin(pyglet.event.EventDispatcher):
|
2024-11-08 18:14:14 +01:00
|
|
|
def __init__(self, window: pyglet.window.Window, corners: Optional[list[list[float]]] = None, source_points: Optional[list[list[float]]] = None) -> None:
|
2024-10-23 16:39:16 +02:00
|
|
|
"""
|
|
|
|
Creates CornerPin utility for a given Pyglet window. If not given, corners default to the
|
|
|
|
bottom left, bottom right, top left, top right corners of the window (i.e. no transform)
|
|
|
|
|
|
|
|
Do not forget to register event handlers by running `window.register_handlers(pins)`.
|
|
|
|
"""
|
|
|
|
self.window = window
|
2024-11-08 18:14:14 +01:00
|
|
|
self.source_points = source_points if source_points else [
|
2024-10-23 16:39:16 +02:00
|
|
|
[0, 0],
|
|
|
|
[self.window.width, 0],
|
|
|
|
[0, self.window.height],
|
|
|
|
[self.window.width, self.window.height],
|
|
|
|
]
|
2024-11-08 18:14:14 +01:00
|
|
|
self.pin_positions = corners if corners else copy.deepcopy(self.source_points)
|
|
|
|
|
2024-10-23 16:39:16 +02:00
|
|
|
self.batch = pyglet.graphics.Batch()
|
|
|
|
self.dragging: Optional[int] = None
|
|
|
|
self.handles = [
|
2024-11-08 18:14:14 +01:00
|
|
|
pyglet.shapes.Arc(c[0],c[1],20, thickness=2, color=(0,0,255,255), batch=self.batch) for c in self.pin_positions
|
2024-10-23 16:39:16 +02:00
|
|
|
]
|
2024-11-05 18:33:11 +01:00
|
|
|
self.current_corner = None
|
2024-11-11 10:31:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
self.window.view = self.transform2d_matrix(*[x for c in self.source_points for x in c], *[x for c in self.pin_positions for x in c])
|
2024-10-23 16:39:16 +02:00
|
|
|
|
|
|
|
def update_handles(self):
|
2024-11-08 18:14:14 +01:00
|
|
|
for i, c in enumerate(self.pin_positions):
|
2024-10-23 16:39:16 +02:00
|
|
|
self.handles[i].x = c[0]
|
|
|
|
self.handles[i].y = c[1]
|
|
|
|
|
|
|
|
def draw(self):
|
|
|
|
"""
|
|
|
|
Draw the handles in the on_draw loop
|
|
|
|
"""
|
|
|
|
self.batch.draw()
|
|
|
|
|
2024-11-08 18:14:14 +01:00
|
|
|
def transform2d_matrix(self,x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s, x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d):
|
2024-10-23 16:39:16 +02:00
|
|
|
"""
|
|
|
|
Calculate the transform matrix
|
|
|
|
"""
|
|
|
|
t = transform2d(
|
2024-11-08 18:14:14 +01:00
|
|
|
x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s, x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d)
|
2024-10-23 16:39:16 +02:00
|
|
|
return pyglet.math.Mat4(t)
|
|
|
|
|
|
|
|
# event handlers
|
|
|
|
|
|
|
|
def on_mouse_press(self, x, y, button, modifiers):
|
|
|
|
r = 20
|
|
|
|
currentcorner = None
|
|
|
|
dist = r * r
|
|
|
|
for i in range(4):
|
2024-11-08 18:14:14 +01:00
|
|
|
dx = x - self.pin_positions[i][0]
|
|
|
|
dy = y - self.pin_positions[i][1]
|
2024-10-23 16:39:16 +02:00
|
|
|
if dist > dx**2 + dy ** 2:
|
|
|
|
dist = dx**2 + dy ** 2
|
|
|
|
currentcorner = i
|
|
|
|
|
|
|
|
self.dragging = currentcorner
|
|
|
|
return pyglet.event.EVENT_HANDLED if self.dragging else pyglet.event.EVENT_UNHANDLED
|
|
|
|
|
|
|
|
def on_mouse_release(self, x, y, button, modifiers):
|
|
|
|
if self.dragging is None:
|
|
|
|
return pyglet.event.EVENT_UNHANDLED
|
|
|
|
self.dragging = None
|
2024-11-08 18:14:14 +01:00
|
|
|
logging.debug(f"Corner pins set to {self.pin_positions}")
|
2024-10-23 16:39:16 +02:00
|
|
|
return pyglet.event.EVENT_HANDLED
|
|
|
|
|
|
|
|
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
|
|
|
|
if self.dragging is None:
|
|
|
|
return pyglet.event.EVENT_UNHANDLED
|
|
|
|
|
2024-11-08 18:14:14 +01:00
|
|
|
self.pin_positions[self.dragging][0] = x
|
|
|
|
self.pin_positions[self.dragging][1] = y
|
2024-10-23 16:39:16 +02:00
|
|
|
self.update_view()
|
2024-11-05 18:33:11 +01:00
|
|
|
|
|
|
|
def on_key_release(self, symbol: int, modifiers: int):
|
|
|
|
if symbol not in [
|
|
|
|
pyglet.window.key._1, pyglet.window.key._2, pyglet.window.key._3, pyglet.window.key._4,
|
|
|
|
pyglet.window.key.ESCAPE,
|
|
|
|
pyglet.window.key.UP, pyglet.window.key.DOWN, pyglet.window.key.LEFT, pyglet.window.key.RIGHT,
|
|
|
|
]:
|
|
|
|
return pyglet.event.EVENT_UNHANDLED
|
|
|
|
|
|
|
|
if symbol == pyglet.window.key._1:
|
|
|
|
self.current_corner = 2
|
|
|
|
if symbol == pyglet.window.key._2:
|
|
|
|
self.current_corner = 3
|
|
|
|
if symbol == pyglet.window.key._3:
|
|
|
|
self.current_corner = 0
|
|
|
|
if symbol == pyglet.window.key._4:
|
|
|
|
self.current_corner = 1
|
|
|
|
if symbol == pyglet.window.key.ESCAPE:
|
|
|
|
self.current_corner = None
|
|
|
|
|
|
|
|
for i, h in enumerate(self.handles):
|
|
|
|
h.color = (255,0,0,255) if i == self.current_corner else (0,0,255,255)
|
|
|
|
|
|
|
|
if self.current_corner is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if symbol not in [pyglet.window.key.UP,pyglet.window.key.DOWN, pyglet.window.key.LEFT, pyglet.window.key.RIGHT]:
|
|
|
|
return
|
|
|
|
|
|
|
|
base = 2
|
|
|
|
if modifiers & pyglet.window.key.MOD_SHIFT:
|
|
|
|
base += 10
|
|
|
|
if modifiers & pyglet.window.key.MOD_CTRL:
|
|
|
|
base *= 2
|
|
|
|
|
|
|
|
if symbol == pyglet.window.key.RIGHT:
|
2024-11-08 18:14:14 +01:00
|
|
|
self.pin_positions[self.current_corner][0] += base
|
2024-11-05 18:33:11 +01:00
|
|
|
if symbol == pyglet.window.key.LEFT:
|
2024-11-08 18:14:14 +01:00
|
|
|
self.pin_positions[self.current_corner][0] -= base
|
2024-11-05 18:33:11 +01:00
|
|
|
if symbol == pyglet.window.key.UP:
|
2024-11-08 18:14:14 +01:00
|
|
|
self.pin_positions[self.current_corner][1] += base
|
2024-11-05 18:33:11 +01:00
|
|
|
if symbol == pyglet.window.key.DOWN:
|
2024-11-08 18:14:14 +01:00
|
|
|
self.pin_positions[self.current_corner][1] -= base
|
2024-11-05 18:33:11 +01:00
|
|
|
|
|
|
|
self.update_view()
|
2024-10-23 16:39:16 +02:00
|
|
|
|
|
|
|
def update_view(self):
|
2024-11-08 18:14:14 +01:00
|
|
|
self.window.view = self.transform2d_matrix(*[x for c in self.source_points for x in c], *[x for c in self.pin_positions for x in c])
|