[mcclim-cvs] CVS mcclim

crhodes crhodes at common-lisp.net
Wed Jul 11 15:26:20 UTC 2007


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]




More information about the Mcclim-cvs mailing list