2 ;;; affine transformations for the clem matrix package
4 ;;; Copyright (c) 2004-2006 Cyrus Harmon (ch-lisp@bobobeach.com)
5 ;;; All rights reserved.
7 ;;; Redistribution and use in source and binary forms, with or without
8 ;;; modification, are permitted provided that the following conditions
11 ;;; * Redistributions of source code must retain the above copyright
12 ;;; notice, this list of conditions and the following disclaimer.
14 ;;; * Redistributions in binary form must reproduce the above
15 ;;; copyright notice, this list of conditions and the following
16 ;;; disclaimer in the documentation and/or other materials
17 ;;; provided with the distribution.
19 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
20 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 (defclass affine-transformation
(double-float-matrix)
35 ((dimensions :initarg
:dimensions
:initform
'(3 3) :type
(or list null
)))
36 (:metaclass standard-matrix-class
)
37 (:documentation
"a matrix that represents an affine-transformation"))
39 (defgeneric transform-matrix
40 (m n xfrm
&key u v x y
41 interpolation background
)
43 "applies the affine transform xfrm to the contents of matrix m
44 and places the contents in n. The default supported classes
45 of interpolation are :quadratic, :bilinear
46 and :nearest-neighbor."))
48 (defgeneric copy-affine-transformation
(xfrm))
49 (defgeneric move-affine-transformation
(src dest
))
51 (defun transform-coord (x y xfrm
)
52 "applies the affine transformation xfrm to the point {x,y} and
53 returns the position of the point after applying the transformation"
54 (let ((coord1 (make-instance 'double-float-matrix
:rows
3 :cols
1)))
55 (setf (mref coord1
0 0) (coerce x
'double-float
)
56 (mref coord1
1 0) (coerce y
'double-float
)
57 (mref coord1
2 0) 1d0
)
58 (let ((coord2 (mat-mult xfrm coord1
)))
59 (values (mref coord2
0 0) (mref coord2
1 0)))))
61 (defun compute-bounds (x1 y1 x2 y2 xfrm
)
62 "takes a region bound by x1 and x2 on the x-axis and y1 and y2 on
63 the y-axis and returns the coordinates of the bounding rectangle
64 after applying the affine transform xfrm"
65 (multiple-value-bind (p1 q1
)
66 (transform-coord x1 y1 xfrm
)
67 (multiple-value-bind (p2 q2
)
68 (transform-coord x2 y2 xfrm
)
69 (multiple-value-bind (p3 q3
)
70 (transform-coord x1 y2 xfrm
)
71 (multiple-value-bind (p4 q4
)
72 (transform-coord x2 y1 xfrm
)
73 (values (min p1 p2 p3 p4
) ;; x1'
74 (min q1 q2 q3 q4
) ;; y1'
75 (max p1 p2 p3 p4
) ;; x2'
76 (max q1 q2 q3 q4
))))))) ;; y2'
78 ;;; I need to rethink what to do about the output matrix for the
79 ;;; moment I pass it in and it is the same size as the input matrix. I
80 ;;; should probably compute the required size of the thing and make a
81 ;;; new matrix as apporpriate.
85 ;;; we have an input matrix, an output matrix and an affine
86 ;;; transformation, represented by a matrix.
87 ;;; this is enough to go on but it would probably be nice to offer
88 ;;; more parameters to make affine transforms easier to use.
90 ;;; the problem is that both input and output matrices themselves can
91 ;;; be considered as living in coordinate spaces. One approach would
92 ;;; be to leave this as is, ranging from 0 to rows - 1 rows and 0 to
93 ;;; cols - 1 cols. Alternatively, we can allow input and output
94 ;;; coordinates, onto which the affine transform is applied and the
95 ;;; appropriate transformed matrix generated. We probably also need to
96 ;;; specify a pixel-space coordinate for the input and output matrices
97 ;;; as well, although there are lots of possible to interpret
98 ;;; those. Let'stick to the matrix-space coordinates and figure those
102 ;;; mr = input matrix rows - 1, mc = input matrix cols -1
104 ;;; n = output matrix
105 ;;; nr = output matrix rows - 1, nc = output matrix cols - 1
107 ;;; xfrm - affine transformation, specifies the mapping of points from
108 ;;; input space to output space
110 ;;; u = (u1 . u2) begin and end x coordinates of input matrix
111 ;;; v = (v1 . v2) begin and end y coordinates of input matrix
113 ;;; x = (x1 . x2) begin and end x coordinates of output matrix
114 ;;; y = (y1 . y2) begin and end y coordinates of output matrix
118 ;;; keeping the transformed matrix fully in the new matrix:
119 ;;; 2x doubling transformation
120 ;;; u = (0 . 100), v = (0 . 100), x = (0 . 200), y = (0 . 200)
122 ;;; 2x doubling of a matrix shifted
123 ;;; u = (100 . 200), v = (100 . 200), x = (200 . 400), y = (200 . 400)
125 (defmethod transform-matrix (m n xfrm
127 (interpolation :nearest-neighbor interpolation-supplied-p
)
128 (background nil background-supplied-p
))
129 "applies the affine transform xfrm to the contents of matrix m
130 and places the contents in n. The default supported
131 classes. The default supported classes of interpolation
132 are :quadratic, :bilinear and :nearest-neighbor. If no
133 interpolation is supplied, the default is :nearest-neighbor."
134 (let ((xfrm-shift (mat-copy xfrm
)))
136 (setf u
(cons 0 (cols m
))))
138 (setf v
(cons 0 (rows m
))))
139 (multiple-value-bind (x1 y1 x2 y2
)
140 (compute-bounds (car u
) (car v
) (cdr u
) (cdr v
) xfrm
)
142 (setf x
(cons (floor x1
) (ceiling x2
))))
144 (setf y
(cons (floor y1
) (ceiling y2
)))))
146 ;; Need to rework math to do the right thing here!
148 (let ((pre-shift1 (make-affine-transformation
149 :y-shift
(car v
) :x-shift
(car u
)))
150 (pre-shift2 (make-affine-transformation
151 :y-scale
(/ (- (cdr v
) (car v
)) (rows m
))
152 :x-scale
(/ (- (cdr u
) (car u
)) (cols m
)))))
153 (setf xfrm-shift
(mat-mult xfrm-shift
(mat-mult pre-shift1 pre-shift2
))))
154 (let ((post-shift (make-affine-transformation
155 :y-shift
(- (car y
)) :x-shift
(- (car x
))))
156 (post-shift2 (make-affine-transformation
157 :y-scale
(/ (rows n
) (- (cdr y
) (car y
)))
158 :x-scale
(/ (cols n
) (- (cdr x
) (car x
))))))
159 (setf xfrm-shift
(mat-mult post-shift
(mat-mult post-shift2 xfrm-shift
))))
160 (apply #'%transform-matrix m n xfrm-shift
162 (when background-supplied-p
(list :background background
))
163 (when interpolation-supplied-p
(list :interpolation interpolation
))))))
166 (defmethod set-affine-transformation-parameters ((xfrm affine-transformation
)
175 (setf (mref xfrm
0 0) (- (* (cos theta
) x-scale
)
176 (* (sin theta
) y-scale y-shear
)))
177 (setf (mref xfrm
0 1) (- (* (cos theta
) x-scale x-shear
)
178 (* (sin theta
) y-scale
)))
179 (setf (mref xfrm
0 2) (coerce x-shift
'double-float
))
181 (setf (mref xfrm
1 0) (+ (* (sin theta
) x-scale
)
182 (* (cos theta
) y-scale y-shear
)))
183 (setf (mref xfrm
1 1) (+ (* (sin theta
) x-scale x-shear
)
184 (* (cos theta
) y-scale
)))
185 (setf (mref xfrm
1 2) (coerce y-shift
'double-float
))
187 (setf (mref xfrm
2 0) 0d0
)
188 (setf (mref xfrm
2 1) 0d0
)
189 (setf (mref xfrm
2 2) 1d0
)
192 (defmethod set-affine-transformation-matrix ((xfrm affine-transformation
) (m matrix
))
193 (dotimes (i (rows m
))
194 (dotimes (j (cols m
))
195 (setf (mref xfrm i j
) (mref m i j
))))
198 (defmethod copy-affine-transformation ((xfrm affine-transformation
))
201 (defmethod invert-affine-transformation ((xfrm affine-transformation
))
202 (let ((inv (copy-affine-transformation xfrm
)))
203 (set-affine-transformation-matrix inv
(clem:invert-matrix xfrm
))))
205 (defmethod move-affine-transformation ((src affine-transformation
)
206 (dest affine-transformation
))
207 (mat-copy-into src dest
))
209 (defmethod shared-initialize :after
210 ((object affine-transformation
) slot-names
&rest args
)
211 (declare (ignore slot-names args
)))
213 ;;; Creates 3x3 matrix that represents an affine transformation.
214 ;;; since we have an arbitarty 2d matrix (row-major order) and we
215 ;;; haven't really fixed x and y axes, we have some choice as
216 ;;; to how to represent this. Current convention is that
217 ;;; y == row and x == col, so rows 0 and 1 of this matrix are
218 ;;; swapped WRT the usual parameterization of this kind of
219 ;;; affine transformation matrix.
220 (defun make-affine-transformation (&key
228 (let ((xfrm (make-instance 'affine-transformation
)))
229 (set-affine-transformation-parameters xfrm
240 (defgeneric affine-transform
(mat xfrm
&key u v x y interpolation background matrix-class
))
241 (defmethod affine-transform ((mat matrix
)
242 (xfrm affine-transformation
)
245 (interpolation nil interpolation-supplied-p
)
246 (background nil background-supplied-p
)
247 (matrix-class (class-of mat
)))
248 (unless u
(setf u
(cons 0 (cols mat
))))
249 (unless v
(setf v
(cons 0 (rows mat
))))
250 (multiple-value-bind (x1 y1 x2 y2
)
251 (compute-bounds (car u
) (car v
) (cdr u
) (cdr v
) xfrm
)
252 (unless x
(setf x
(cons (floor x1
) (ceiling x2
))))
253 (unless y
(setf y
(cons (floor y1
) (ceiling y2
)))))
254 (let ((rows (if y
(truncate (- (cdr y
) (car y
)))
256 (cols (if x
(truncate (- (cdr x
) (car x
)))
258 (let ((m (make-instance matrix-class
262 (coerce 0 (element-type (class-of mat
))))))
263 (apply #'transform-matrix mat m xfrm
269 (when background-supplied-p
270 (list :background background
))
271 (when interpolation-supplied-p
272 (list :interpolation interpolation
))))
275 (defun resize-matrix (m y x
&key
(interpolation :bilinear
))
276 (let ((oldy (rows m
))
278 (let ((xfrm (make-affine-transformation :x-scale
(/ x oldx
)
279 :y-scale
(/ y oldy
))))
280 (let ((n (affine-transform
282 :interpolation interpolation
283 :u
`(0 .
,oldx
) :v
`(0 .
,oldy
)
284 :x
`(0 .
,x
) :y
`(0 .
,y
))))
287 (defmethod mat-mult ((m affine-transformation
)
289 (let ((p (make-instance 'clem
::affine-transformation
))
290 (r (call-next-method)))
291 (if (equal (dim n
) '(3 3))
293 (set-affine-transformation-matrix p r
)
297 (defmethod mat-add ((m affine-transformation
)
298 (n affine-transformation
) &key in-place
)
299 (let ((p (make-instance 'clem
::affine-transformation
))
300 (r (call-next-method)))
301 (set-affine-transformation-matrix p r
)))
303 (defmethod mat-subtr :around
304 ((m affine-transformation
)
305 n
&key in-place
(result-type 'clem
::affine-transformation
))
306 (declare (ignorable matrix-class
))
307 (let ((p (make-instance result-type
))
308 (r (call-next-method)))
309 (set-affine-transformation-matrix p r
)))
311 (defmethod mat-hprod ((m affine-transformation
)
313 (let ((p (make-instance 'clem
::affine-transformation
))
314 (r (call-next-method)))
315 (if (equal (dim n
) '(3 3))
316 (set-affine-transformation-matrix p r
)
320 (defclass affine-transformation-7-parameters
()
321 ((y-shift :accessor y-shift
:initarg
:y-shift
:initform
0d0
:type double-float
)
322 (x-shift :accessor x-shift
:initarg
:x-shift
:initform
0d0
:type double-float
)
323 (theta :accessor theta
:initarg
:theta
:initform
0d0
:type double-float
)
324 (y-scale :accessor y-scale
:initarg
:y-scale
:initform
0d0
:type double-float
)
325 (x-scale :accessor x-scale
:initarg
:x-scale
:initform
0d0
:type double-float
)
326 (y-shear :accessor y-shear
:initarg
:y-shear
:initform
0d0
:type double-float
)
327 (x-shear :accessor x-shear
:initarg
:x-shear
:initform
0d0
:type double-float
))
328 (:documentation
"a set of parameters for use in
329 (over-) parameterizing an affine transformation by use of seven
330 parameters, x-shift, y-shift, theta, x-scale, y-scale, x-shear,
333 (defun make-affine-transformation-matrix-from-7-parameters (transvec)
334 (with-slots (y-shift x-shift theta y-scale x-scale y-shear x-shear
) transvec
335 (clem:make-affine-transformation
:y-shift y-shift
338 :y-scale
(exp y-scale
)
339 :x-scale
(exp x-scale
)
343 (declaim (inline transformation-parameter
))
344 (declaim (ftype (function (affine-transformation-7-parameters fixnum
) double-float
) transforamtion-parameter
))
346 (defun transformation-parameter (xfrm i
)
347 (declare (type fixnum i
))
349 (0 (/ (the double-float
(y-shift xfrm
)) 100d0
))
350 (1 (/ (the double-float
(x-shift xfrm
)) 100d0
))
357 (declaim (ftype (function (double-float clem
:affine-transformation fixnum
)
358 clem
:affine-transformation
) (setf transforamtion-parameter
)))
359 (declaim (inline (setf transformation-parameter
)))
360 (defun (setf transformation-parameter
) (v xfrm i
)
361 (declare (type double-float v
)
364 (0 (setf (y-shift xfrm
) (* v
100d0
)))
365 (1 (setf (x-shift xfrm
) (* v
100d0
)))
366 (2 (setf (theta xfrm
) v
))
367 (3 (setf (y-scale xfrm
) v
))
368 (4 (setf (x-scale xfrm
) v
))
369 (5 (setf (y-shear xfrm
) v
))
370 (6 (setf (x-shear xfrm
) v
))))
373 (defun decf-transformation-parameters (src delta
)
374 (with-slots (y-shift x-shift theta y-scale x-scale y-shear x-shear
) src
375 (decf y-shift
(y-shift delta
))
376 (decf x-shift
(x-shift delta
))
377 (decf theta
(theta delta
))
378 (decf y-scale
(y-scale delta
))
379 (decf x-scale
(x-scale delta
))
380 (decf y-shear
(y-shear delta
))
381 (decf x-shear
(x-shear delta
))
384 (defun copy-affine-transformation-7-parameters (src)
385 (let ((dest (make-instance 'affine-transformation-7-parameters
)))
386 (setf (y-shift dest
) (y-shift src
)
387 (x-shift dest
) (x-shift src
)
388 (theta dest
) (theta src
)
389 (y-scale dest
) (y-scale src
)
390 (x-scale dest
) (x-scale src
)
391 (y-shear dest
) (y-shear src
)
392 (x-shear dest
) (x-shear src
))
395 (defun move-affine-transformation-7-parameters (src dest
)
396 (setf (y-shift dest
) (y-shift src
)
397 (x-shift dest
) (x-shift src
)
398 (theta dest
) (theta src
)
399 (y-scale dest
) (y-scale src
)
400 (x-scale dest
) (x-scale src
)
401 (y-shear dest
) (y-shear src
)
402 (x-shear dest
) (x-shear src
))
405 (defun get-affine-transformation-7-parameters-properties (transvec)
406 (with-slots (y-shift x-shift theta y-scale x-scale y-shear x-shear
)
408 (list :y-shift y-shift