Update of /project/mcclim/cvsroot/mcclim In directory clnet:/tmp/cvs-serv13045
Modified Files: bezier.lisp Log Message: Bezier designs which draw in the right place in all backends (I think).
The implementation prior to this worked for the replay on an output-recording stream, and probably worked for the first draw using the pixmap (fall-through) rendering method. It did not work for the first draw on a backend with native bezier drawing routines, basically because the design was being passed through untransformed by the medium transformation. So:
* define a method on medium-draw-bezier-design* specialized on transform-coordinates-mixin, to transform the region appropriately before passing down to backend-drawing functions. This method runs after the output-recording-stream method, so sadly we're now doing some transformations twice.
* this implies deleting the translated-bezier-design class, as returning an object of a different class from transform-region meant that the idiom of doing (defmethod medium-draw-foo* :around ((medium t-c-mixin) foo) (let ((foo (transform-region (medium-transformation medium) foo))) (call-next-method medium foo))) would be in violation of the restriction that the set of applicable methods not change when using call next method.
* deleting the translated-bezier-design class would mean losing the cacheing of pixmap renderings, so restore that by keeping track of the original design in all bezier-design subclasses, and use that in ensure-pixmap.
* this on its own is still too slow, so for bezier-areas and bezier-unions additionally keep track of accumulated translation transformations, only performing the transformation of individual segments or areas when they are necessary. (A similar approach could be used for differences, but I ran out of energy; we have however recovered most of the speed loss from the introduction of this extra correctness.)
* the Postscript and gtkairo backends, with their medium-draw-bezier* methods, needed some adjustment to perform the transformations themselves.
Please test!
--- /project/mcclim/cvsroot/mcclim/bezier.lisp 2006/12/26 16:44:45 1.1 +++ /project/mcclim/cvsroot/mcclim/bezier.lisp 2007/07/11 15:26:20 1.2 @@ -56,7 +56,8 @@ (- (* (realpart z) (point-y v)) (* (imagpart z) (point-x v))))
-(defclass bezier-design (design) ()) +(defclass bezier-design (design) + ((%or :accessor original-region :initform nil)))
(defgeneric medium-draw-bezier-design* (stream design))
@@ -80,6 +81,12 @@ (when (stream-drawing-p stream) (medium-draw-bezier-design* medium design)))))
+(defmethod medium-draw-bezier-design* :around + ((medium transform-coordinates-mixin) design) + (let* ((tr (medium-transformation medium)) + (design (transform-region tr design))) + (call-next-method medium design))) + (defmethod replay-output-record ((record bezier-design-output-record) stream &optional (region +everywhere+) (x-offset 0) (y-offset 0)) (declare (ignore x-offset y-offset region)) @@ -108,12 +115,12 @@ (values min-x min-y max-x max-y)))
(defclass segments-mixin (bounding-rectangle-mixin) - ((%segments :initarg :segments :initform '() :reader segments))) + ((%segments :initarg :segments :initform '() :reader %segments)))
-(defun compute-bounding-rectangle* (segments-mixin) +(defmethod compute-bounding-rectangle* ((segments-mixin segments-mixin)) (multiple-value-bind (final-min-x final-min-y final-max-x final-max-y) - (segment-bounding-rectangle (car (segments segments-mixin))) - (loop for segment in (cdr (segments segments-mixin)) + (segment-bounding-rectangle (car (%segments segments-mixin))) + (loop for segment in (cdr (%segments segments-mixin)) do (multiple-value-bind (min-x min-y max-x max-y) (segment-bounding-rectangle segment) (setf final-min-x (min final-min-x min-x) @@ -171,7 +178,7 @@ (make-instance 'bezier-curve :segments (mapcar (lambda (segment) (transform-segment transformation segment)) - (segments path)))) + (%segments path))))
(defmethod region-equal ((p1 point) (p2 point)) (let ((coordinate-epsilon (* #.(expt 2 10) double-float-epsilon))) @@ -179,49 +186,33 @@ (<= (abs (- (point-y p1) (point-y p2))) coordinate-epsilon))))
(defmethod region-union ((r1 bezier-curve) (r2 bezier-curve)) - (let ((p (slot-value (car (last (segments r1))) 'p3)) - (seg (car (segments r2)))) + (let ((p (slot-value (car (last (%segments r1))) 'p3)) + (seg (car (%segments r2)))) (if (region-equal p (slot-value seg 'p0)) (with-slots (p1 p2 p3) seg (make-instance 'bezier-curve - :segments (append (segments r1) + :segments (append (%segments r1) (cons (make-bezier-segment p p1 p2 p3) - (cdr (segments r2)))))) + (cdr (%segments r2)))))) (call-next-method))))
-;;; A region that translates a different region -(defclass translated-bezier-design (region bezier-design) - ((%translation :initarg :translation :reader translation) - (%region :initarg :region :reader original-region))) - -(defmethod bounding-rectangle* ((region translated-bezier-design)) - (let ((translation (translation region))) - (multiple-value-bind (min-x min-y max-x max-y) - (bounding-rectangle* (original-region region)) - (multiple-value-bind (final-min-x final-min-y) - (transform-position translation min-x min-y) - (multiple-value-bind (final-max-x final-max-y) - (transform-position translation max-x max-y) - (values final-min-x final-min-y final-max-x final-max-y)))))) - -(defgeneric really-transform-region (transformation region)) - ;;; an area defined as a closed path of Bezier curve segments -(defclass bezier-area (area bezier-design segments-mixin bounding-rectangle-mixin) ()) +(defclass bezier-area (area bezier-design segments-mixin bounding-rectangle-mixin) + ((%trans :initarg :transformation :reader transformation :initform +identity-transformation+)))
(defgeneric close-path (path))
(defmethod close-path ((path bezier-curve)) - (let ((segments (segments path))) + (let ((segments (%segments path))) (assert (region-equal (slot-value (car segments) 'p0) (slot-value (car (last segments)) 'p3))) (make-instance 'bezier-area :segments segments)))
(defun path-start (path) - (slot-value (car (segments path)) 'p0)) + (slot-value (car (%segments path)) 'p0))
(defun path-end (path) - (slot-value (car (last (segments path))) 'p3)) + (slot-value (car (last (%segments path))) 'p3))
(defun make-bezier-area (point-seq) (assert (region-equal (car point-seq) (car (last point-seq)))) @@ -232,18 +223,26 @@ (coordinate= (cadr coord-seq) (car (last coord-seq))))) (make-bezier-thing* 'bezier-area coord-seq))
-(defmethod really-transform-region (transformation (area bezier-area)) - (make-instance 'bezier-area - :segments (mapcar (lambda (segment) - (transform-segment transformation segment)) - (segments area)))) +(defmethod segments ((area bezier-area)) + (let ((tr (transformation area))) + (mapcar (lambda (s) (transform-segment tr s)) (%segments area))))
(defmethod transform-region (transformation (area bezier-area)) - (if (translation-transformation-p transformation) - (make-instance 'translated-bezier-design - :translation transformation - :region area) - (really-transform-region transformation area))) + (let* ((tr (transformation area)) + (result (if (translation-transformation-p transformation) + (make-instance 'bezier-area :segments (%segments area) + :transformation + (compose-transformations transformation tr)) + (make-instance 'bezier-area + :segments (mapcar (lambda (s) (transform-segment transformation s)) (segments area)))))) + (when (translation-transformation-p transformation) + (setf (original-region result) (or (original-region area) area))) + result)) + +(defmethod compute-bounding-rectangle* ((area bezier-area)) + (multiple-value-bind (lx ly ux uy) (call-next-method) + (let ((tr (transformation area))) + (transform-rectangle* tr lx ly ux uy))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; @@ -251,20 +250,20 @@
;;; A union of bezier areas. This is not itself a bezier area. (defclass bezier-union (area bezier-design) - ((%areas :initarg :areas :initform '() :reader areas))) - -(defmethod really-transform-region (transformation (area bezier-union)) - (let ((areas (loop for area in (areas area) - collect (transform-region transformation area)))) - (make-instance 'bezier-union - :areas areas))) + ((%trans :initarg :transformation :reader transformation :initform +identity-transformation+) + (%areas :initarg :areas :initform '() :reader areas)))
-(defmethod transform-region (transformation (area bezier-union)) - (if (translation-transformation-p transformation) - (make-instance 'translated-bezier-design - :translation transformation - :region area) - (really-transform-region transformation area))) +(defmethod transform-region (transformation (union bezier-union)) + (let* ((tr (transformation union)) + (new-tr (compose-transformations transformation tr)) + (result (if (translation-transformation-p transformation) + (make-instance 'bezier-union :areas (areas union) + :transformation new-tr) + (make-instance 'bezier-union + :areas (loop for area in (areas union) collect (transform-region new-tr area)))))) + (when (translation-transformation-p transformation) + (setf (original-region result) (or (original-region union) union))) + result))
(defun bounding-rectangle-of-areas (areas) (multiple-value-bind (final-min-x final-min-y final-max-x final-max-y) @@ -279,43 +278,57 @@ (values final-min-x final-min-y final-max-x final-max-y)))
(defmethod bounding-rectangle* ((design bezier-union)) - (bounding-rectangle-of-areas (areas design))) + (multiple-value-bind (lx ly ux uy) + (bounding-rectangle-of-areas (areas design)) + (transform-rectangle* (transformation design) lx ly ux uy)))
(defmethod region-union ((r1 bezier-area) (r2 bezier-area)) - (make-instance 'bezier-union - :areas (list r1 r2))) + (make-instance 'bezier-union :areas (list r1 r2)))
(defmethod region-union ((r1 bezier-union) (r2 bezier-area)) - (make-instance 'bezier-union - :areas (cons r2 (areas r1)))) + (let ((tr (transformation r1))) + (make-instance 'bezier-union + :areas (cons (untransform-region tr r2) (areas r1)) + :transformation tr)))
(defmethod region-union ((r1 bezier-area) (r2 bezier-union)) - (make-instance 'bezier-union - :areas (cons r1 (areas r2)))) + (let ((tr (transformation r2))) + (make-instance 'bezier-union + :areas (cons (untransform-region tr r1) (areas r2)) + :transformation tr)))
(defmethod region-union ((r1 bezier-union) (r2 bezier-union)) - (make-instance 'bezier-union - :areas (append (areas r1) (areas r2)))) + (let ((tr1 (transformation r1)) + (tr2 (transformation r2))) + (if (transformation-equal tr1 tr2) + (make-instance 'bezier-union + :areas (append (areas r1) (areas r2)) + :transformation tr1) + (let ((len1 (length (areas r1))) + (len2 (length (areas r2)))) + (if (> len2 len1) + (make-instance 'bezier-union + :areas (append (mapcar (lambda (r) (untransform-region tr2 (transform-region tr1 r))) (areas r1)) (areas r2)) + :transformation tr2) + (make-instance 'bezier-union + :areas (append (mapcar (lambda (r) (untransform-region tr1 (transform-region tr2 r))) (areas r2)) (areas r1)) + :transformation tr1))))))
(defclass bezier-difference (area bezier-design) ((%positive-areas :initarg :positive-areas :initform '() :reader positive-areas) (%negative-areas :initarg :negative-areas :initform '() :reader negative-areas)))
-(defmethod really-transform-region (transformation (area bezier-difference)) - (let ((pareas (loop for area in (positive-areas area) - collect (transform-region transformation area))) - (nareas (loop for area in (negative-areas area) - collect (transform-region transformation area)))) - (make-instance 'bezier-difference - :positive-areas pareas - :negative-areas nareas))) - (defmethod transform-region (transformation (area bezier-difference)) - (if (translation-transformation-p transformation) - (make-instance 'translated-bezier-design - :translation transformation - :region area) - (really-transform-region transformation area))) + (let* ((pareas (loop for area in (positive-areas area) + collect (transform-region transformation area))) + (nareas (loop for area in (negative-areas area) + collect (transform-region transformation area))) + (result (make-instance 'bezier-difference + :positive-areas pareas + :negative-areas nareas))) + (when (translation-transformation-p transformation) + (setf (original-region result) (or (original-region area) area))) + result))
(defmethod bounding-rectangle* ((design bezier-difference)) (bounding-rectangle-of-areas (positive-areas design))) @@ -326,19 +339,23 @@ :negative-areas (list r2)))
(defmethod region-difference ((r1 bezier-area) (r2 bezier-union)) - (make-instance 'bezier-difference - :positive-areas (list r1) - :negative-areas (areas r2))) + (let ((tr (transformation r2))) + (make-instance 'bezier-difference + :positive-areas (list r1) + :negative-areas (mapcar (lambda (r) (transform-region tr r)) (areas r2)))))
(defmethod region-difference ((r1 bezier-union) (r2 bezier-area)) - (make-instance 'bezier-difference - :positive-areas (areas r1) - :negative-areas (list r2))) + (let ((tr (transformation r1))) + (make-instance 'bezier-difference + :positive-areas (mapcar (lambda (r) (transform-region tr r)) (areas r1)) + :negative-areas (list r2))))
(defmethod region-difference ((r1 bezier-union) (r2 bezier-union)) - (make-instance 'bezier-difference - :positive-areas (areas r1) - :negative-areas (areas r2))) + (let ((tr1 (transformation r1)) + (tr2 (transformation r2))) + (make-instance 'bezier-difference + :positive-areas (mapcar (lambda (r) (transform-region tr1 r)) (areas r1)) + :negative-areas (mapcar (lambda (r) (transform-region tr2 r)) (areas r2)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; @@ -369,7 +386,7 @@ (%polygonalize p0 p1 p2 p3)))
(defmethod polygonalize ((path bezier-curve)) - (let ((segments (segments path))) + (let ((segments (%segments path))) (make-polyline (cons (slot-value (car segments) 'p0) (mapcan #'polygonalize segments))))) @@ -390,11 +407,12 @@
(defmethod reverse-path ((path bezier-curve)) (make-instance 'bezier-curve - :segments (reverse (mapcar #'reverse-segment (segments path))))) + :segments (reverse (mapcar #'reverse-segment (%segments path)))))
(defmethod reverse-path ((path bezier-area)) (make-instance 'bezier-area - :segments (reverse (mapcar #'reverse-segment (segments path))))) + :segments (reverse (mapcar #'reverse-segment (%segments path))) + :transformation (transformation path)))
;;; slanting transformation are used by Metafont (defun make-slanting-transformation (slant) @@ -574,7 +592,7 @@ (defmethod convolute-regions ((area bezier-area) (path bezier-curve)) (let ((polygon (polygonalize area))) (make-instance 'bezier-union - :areas (loop for segment in (segments path) + :areas (loop for segment in (%segments path) append (convolute-polygon-and-segment area polygon segment)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -670,9 +688,6 @@ (defmethod positive-negative-areas ((design bezier-difference)) (values (positive-areas design) (negative-areas design)))
-(defmethod positive-negative-areas ((design translated-bezier-design)) - (positive-negative-areas (original-region design))) - (defun render-to-array (design) (multiple-value-bind (positive-areas negative-areas) (positive-negative-areas design) @@ -695,9 +710,6 @@ (render-polygon result polygon 1 min-x min-y))) result))))
-(defparameter *x* 0) -(defparameter *y* 0) - (defparameter *pixmaps* (make-hash-table :test #'equal))
(defun resolve-ink (medium) @@ -715,8 +727,9 @@
(defgeneric ensure-pixmap (medium design))
-(defmethod ensure-pixmap (medium design) - (let ((pixmap (gethash (list (medium-sheet medium) (resolve-ink medium) design) +(defmethod ensure-pixmap (medium rdesign) + (let* ((design (or (original-region rdesign) rdesign)) + (pixmap (gethash (list (medium-sheet medium) (resolve-ink medium) design) *pixmaps*))) (when (null pixmap) (let* ((picture (render-to-array design)) @@ -743,17 +756,21 @@ pixmap))) pixmap))
-(defmethod ensure-pixmap (medium (design translated-bezier-design)) - (ensure-pixmap medium (original-region design))) - (defun render-through-pixmap (design medium) (multiple-value-bind (min-x min-y) (bounding-rectangle* design) - (setf min-x (floor min-x) - min-y (floor min-y)) - (let ((pixmap (ensure-pixmap medium design))) - (copy-from-pixmap pixmap 0 0 (pixmap-width pixmap) (pixmap-height pixmap) - (medium-sheet medium) (+ *x* min-x) (+ *y* min-y))))) + ;; the design we've got has already been transformed by the + ;; medium/user transformation, and COPY-FROM-PIXMAP is in user + ;; coordinates. So we need to transform back (or set the medium's + ;; transformation to be +IDENTITY-TRANSFORMATION+ temporarily, but + ;; that's even uglier) + (multiple-value-bind (utmin-x utmin-y) + (untransform-position (medium-transformation medium) min-x min-y) + (setf min-x (floor utmin-x) + min-y (floor utmin-y)) + (let ((pixmap (ensure-pixmap medium design))) + (copy-from-pixmap pixmap 0 0 (pixmap-width pixmap) (pixmap-height pixmap) + (medium-sheet medium) min-x min-y)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; @@ -778,73 +795,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; -;;; Special cases on region-union and region-intersection - -(defmethod region-union ((r1 translated-bezier-design) (r2 bezier-curve)) - (region-union (really-transform-region (translation r1) (original-region r1)) r2)) - -(defmethod region-union ((r1 translated-bezier-design) (r2 bezier-area))
[64 lines skipped]