cleared up and add questions about intent of work.
[CommonLispStat.git] / external / clem / src / transform.lisp
blobbae4e84551a938d9ba7d14f1f5946258764bf0df
1 ;;; transform.lisp
2 ;;; affine transformations for the clem matrix package
3 ;;;
4 ;;; Copyright (c) 2004-2006 Cyrus Harmon (ch-lisp@bobobeach.com)
5 ;;; All rights reserved.
6 ;;;
7 ;;; Redistribution and use in source and binary forms, with or without
8 ;;; modification, are permitted provided that the following conditions
9 ;;; are met:
10 ;;;
11 ;;; * Redistributions of source code must retain the above copyright
12 ;;; notice, this list of conditions and the following disclaimer.
13 ;;;
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.
18 ;;;
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.
30 ;;;
32 (in-package :clem)
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)
42 (:documentation
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.
82 ;;;
83 ;;; Ok, rethinking...
84 ;;;
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.
89 ;;;
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
99 ;;; at first:
101 ;;; m = input matrix
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
112 ;;;
113 ;;; x = (x1 . x2) begin and end x coordinates of output matrix
114 ;;; y = (y1 . y2) begin and end y coordinates of output matrix
115 ;;;
116 ;;; examples:
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
126 &key u v x y
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)))
135 (unless u
136 (setf u (cons 0 (cols m))))
137 (unless v
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)
141 (unless x
142 (setf x (cons (floor x1) (ceiling x2))))
143 (unless y
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
161 (append
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)
167 &key
168 (y-shift 0d0)
169 (x-shift 0d0)
170 (theta 0d0)
171 (y-scale 1d0)
172 (x-scale 1d0)
173 (y-shear 0d0)
174 (x-shear 0d0))
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)
190 xfrm)
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))))
196 xfrm)
198 (defmethod copy-affine-transformation ((xfrm affine-transformation))
199 (mat-copy xfrm))
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
221 (x-shift 0d0)
222 (y-shift 0d0)
223 (x-scale 1.0d0)
224 (y-scale 1.0d0)
225 (x-shear 0.0d0)
226 (y-shear 0.0d0)
227 (theta 0d0))
228 (let ((xfrm (make-instance 'affine-transformation)))
229 (set-affine-transformation-parameters xfrm
230 :x-shift x-shift
231 :y-shift y-shift
232 :theta theta
233 :x-scale x-scale
234 :y-scale y-scale
235 :x-shear x-shear
236 :y-shear y-shear)
237 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)
243 &key
244 u v x y
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)))
255 (rows mat)))
256 (cols (if x (truncate (- (cdr x) (car x)))
257 (cols mat))))
258 (let ((m (make-instance matrix-class
259 :rows rows
260 :cols cols
261 :initial-element
262 (coerce 0 (element-type (class-of mat))))))
263 (apply #'transform-matrix mat m xfrm
264 (append
265 (when u (list :u u))
266 (when v (list :v v))
267 (when x (list :x x))
268 (when y (list :y y))
269 (when background-supplied-p
270 (list :background background))
271 (when interpolation-supplied-p
272 (list :interpolation interpolation))))
273 m)))
275 (defun resize-matrix (m y x &key (interpolation :bilinear))
276 (let ((oldy (rows m))
277 (oldx (cols m)))
278 (let ((xfrm (make-affine-transformation :x-scale (/ x oldx)
279 :y-scale (/ y oldy))))
280 (let ((n (affine-transform
281 m xfrm
282 :interpolation interpolation
283 :u `(0 . ,oldx) :v `(0 . ,oldy)
284 :x `(0 . ,x) :y `(0 . ,y))))
285 n))))
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))
292 (progn
293 (set-affine-transformation-matrix p r)
295 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)
317 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,
331 and y-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
336 :x-shift x-shift
337 :theta theta
338 :y-scale (exp y-scale)
339 :x-scale (exp x-scale)
340 :y-shear y-shear
341 :x-shear x-shear)))
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))
348 (ecase i
349 (0 (/ (the double-float (y-shift xfrm)) 100d0))
350 (1 (/ (the double-float (x-shift xfrm)) 100d0))
351 (2 (theta xfrm))
352 (3 (y-scale xfrm))
353 (4 (x-scale xfrm))
354 (5 (y-shear xfrm))
355 (6 (x-shear xfrm))))
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)
362 (type fixnum i))
363 (ecase i
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))
382 src))
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))
393 dest))
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))
403 dest)
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)
407 transvec
408 (list :y-shift y-shift
409 :x-shift x-shift
410 :theta theta
411 :y-scale y-scale
412 :x-scale x-scale
413 :y-shear y-shear
414 :x-shear x-shear)))