prmpnt.scm
;;;; Primitive Painters

;;;; Painters take a frame and
;;;; draw an image, transformed to fit inside the frame.
;;;; there are four ways to create painters:
;;;; (1) from a constant: number->painter
;;;; (2) from a list of line segments: segment->painter
;;;; (3) form a procedure: procedure->paintera
;;;; (4) from a picture: picture->painter

;;;; This file assumes the following data structures have been defined:
;;;; frames: make-frame, frame-origin, frame-edge1, frame-edge2
;;;; vectors: make-vect, vector-xcor, vector-ycor, vector-scale, vector-add, vector-sub
;;;; segments: make-segment, segment-start, segment-end

;; (declare (usual-integrations))

;;;; get floating vector ops

(define floating-vector-ref (access floating-vector-ref (->environment '(student pictures))))
(define floating-vector-set! (access floating-vector-set! (->environment '(student pictures))))

;;;; The variable *the-screen* represents the target image array that will be
;;;; shown in the graphics window.  The size of *the-screen* determines the resolution
;;;; of the image.

;;;; 128x128 images (displayed on the screen in 257x257 windows) seem
;;;; about right for 6.001.  We can get better quality by doubling the
;;;; resolution, but this is rather slow.   Note: There's some
;;;; (roundoff?) problem that occasioanally tries to plot a point in
;;;; row 256, which is why the windows are 257x257.


(define *screen-width* 128)
(define *screen-height* 128)
(define *last-screen-row* (- *screen-height* 1))
(define *last-screen-column* (- *screen-width* 1))

(define *the-screen* (make-picture *screen-width* *screen-height* 255.))

(define (screen-frame)
  (make-frame (make-vect 0 0)
             (make-vect *screen-width* 0)
             (make-vect 0 *screen-height*)))

(define (set-painter-resolution! res)
  (let ((res (inexact->exact res)))
    (set! *screen-width* res)
    (set! *screen-height* res)
    (set! *last-screen-row* (- *screen-height* 1))
    (set! *last-screen-column* (- *screen-width* 1))
    (set! *the-screen* (make-picture *screen-width* *screen-height* 255.))
    'set))


;;; The simplest painter is just a constant

(define (number->painter number)
  (let ((picture-data (access picture-data (->environment '(student pictures))))
        (invalidate-cached-values (access invalidate-cached-values
                                          (->environment '(student pictures))))
        (h (exact->inexact *screen-width*))
        (v (exact->inexact *screen-height*))
        (value (exact->inexact number)))
    ;;we're going to draw by scanning along the integer points in the frame
    (lambda (frame)
      (let* ((frame (make-inexact-frame frame)) 
             ;;force coords to inexact, because optimized inner
             ;;loops assume inexact
             (frame-map (frame-mapping-info h v  frame)))
        ;;if frame degenerates to a line then don't try to draw
        (if (degenerate? frame-map)
            'done
            (let ((segments (frame-map-segments frame-map))
                  (lowest-row (frame-map-lowest-row frame-map))
                  (highest-row (frame-map-highest-row frame-map)))
              (invalidate-cached-values *the-screen*)
              (let rowloop ((r lowest-row))
                (if (> r highest-row)
                    'done
                    (let ((intersections (row-intersects-segments r segments)))
                      (if (null? intersections)
                          'done
                          (let ((start-col (max 0 (car intersections)))
                                (stop-col (min *last-screen-column* (cadr intersections))))
                            ;;(write-line `(row ,r start: ,start-col stop: ,stop-col))
                            (fill-row-with-value
                             start-col stop-col
                             (vector-ref (picture-data *the-screen*) r)
                             value)))
                      (rowloop (+ r 1)))))))))))

(define (fill-row-with-value start-column end-column row-vector value)
  (if (> start-column end-column)
      'done
      (let fill-loop ((column start-column))
        (if (fix:> column end-column)
            'done
            ;;this is the inner loop
            (begin
              (floating-vector-set! row-vector column value)
              (fill-loop (fix:+ column 1)))))))


;;;; Creating painters from a list of line segments, defined wrt the unit square.

(define (segments->painter segments)
  (lambda (frame)
    (let ((coord-map
           (lambda (v)
             (vector-add (frame-origin frame)
                         (vector-add (vector-scale (vector-xcor v) (frame-edge1 frame))
                                     (vector-scale (vector-ycor v) (frame-edge2 frame)))))))
      (for-each
       (lambda (seg)
         (draw-line-on-screen (coord-map (segment-start seg))
                              (coord-map (segment-end seg))))
       segments))))
         
;;; Draw a line on *the-screen* using Bresenham's algorithm, as
;;; described in Hegron, "Image Synthesis," MIT Press, 1988, p. 23
;;; Clip the points to be on the screen.  We're being inefficient by checking
;;; each point individually to see if it's on screen.

(define (draw-line-on-screen start end)
  (let* ((value (exact->inexact 0))                     ; black pix on white blackground
         (data ((access picture-data (->environment '(student pictures)))
                *the-screen*))
         (invalidate-cached-values (access invalidate-cached-values
                                           (->environment '(student pictures))))
         (xs (vector-xcor start))
         (ys (vector-ycor start))
         (xe (vector-xcor end))
         (ye (vector-ycor end))
         (xinc (if (< xs xe) 1 -1))
         (dx (round->exact (abs (- xs xe))))
         (yinc (if (< ys ye) 1 -1))
         (dy (round->exact (abs (- ys ye))))
         (dx2 (* dx 2))
         (dy2 (* dy 2))
         (x (round->exact xs))
         (y (round->exact ys)))
    (invalidate-cached-values *the-screen*)
    (plot-if-on-screen x y data value)
    (if (> dx dy)
        (let ((dxy (- dy2 dx2)) (s (- dy2 dx)))
          (let loop ((i 1))
            (if (fix:> i dx)
                'done
                (begin
                  (if (fix:>= s 0)
                      (begin (set! y (fix:+ y yinc))
                             (set! s (fix:+ s dxy)))
                      (set! s (fix:+ s dy2)))
                  (set! x (fix:+ x xinc))
                  (plot-if-on-screen x y data value)
                  (loop (fix:+ i 1))))))
        (let ((dxy (- dx2 dy2)) (s (- dx2 dy)))
          (let loop ((i 1))
            (if (fix:> i dy)
                'done
                (begin
                  (if (fix:>= s 0)
                      (begin (set! x (fix:+ x xinc))
                             (set! s (fix:+ s dxy)))
                      (set! s (fix:+ s dx2)))
                  (set! y (fix:+ y yinc))
                  (plot-if-on-screen x y data value)
                  (loop (fix:+ i 1)))))))))

(define (plot-if-on-screen x y data value)
  (if (and (fix:>= x 0)
           (fix:>= y 0)
           (fix:<= x *last-screen-column*)
           (fix:<= y *last-screen-row*))
      (floating-vector-set! (vector-ref data y) x value)))

;;;; Creating painters from procedures

;;;; Creating painters from procedures.  We assume that the procedure
;;;; f is defined on the unit square.
;;;; Then to plot a point p in the target frame,
;;;; we find the inverse image T-1(p) of p under the transformation that
;;;; maps the unit square to the target, and find the value of f at T-1(p).


(define (procedure->painter proc)
  (if (not (procedure? proc))
      (error "Argument not a procedure--PROCEDURE->PAINTER" proc))
  (let ((picture-data (access picture-data (->environment '(student pictures))))
        (invalidate-cached-values
         (access invalidate-cached-values (->environment '(student pictures))))
        (h 1.0)                         ;width and height of unit square
        (v 1.0))
    ;;we're going to draw by scanning along the integer points in the frame
    (lambda (frame)
      (let* ((frame (make-inexact-frame frame)) 
             ;;force coords to inexact, because optimized inner
             ;;loops assume inexact
             (frame-map (frame-mapping-info h v frame)))
        ;;if frame degenerates to a line then don't try to draw
        (if (degenerate? frame-map)
            'done
            (let ((segments (frame-map-segments frame-map))
                  (lowest-row (frame-map-lowest-row frame-map))
                  (highest-row (frame-map-highest-row frame-map))
                  (picture-x-step (frame-map-x-step frame-map))
                  (picture-y-step (frame-map-y-step frame-map))
                  (inv-map (frame-map-inv-map frame-map)))
              (invalidate-cached-values *the-screen*)
              (let rowloop ((r lowest-row))
                (if (> r highest-row)
                    'done
                    (let ((intersections (row-intersects-segments r segments)))
                      (if (null? intersections)
                          'done
                          (let ((start-col (max 0 (car intersections)))
                                (stop-col (min *last-screen-column* (cadr intersections))))
                            ;;(write-line `(row ,r start: ,start-col stop: ,stop-col))
                            (fill-row-from-procedure
                             r start-col stop-col inv-map
                             picture-x-step picture-y-step
                             (vector-ref (picture-data *the-screen*) r)
                             proc)))
                      (rowloop (+ r 1)))))))))))


(define (fill-row-from-procedure row start-column end-column inv-map
                                 picture-x-step picture-y-step
                                 row-vector
                                 proc
                                 )
  (let* ((inexr (exact->inexact row))
         (picture-start-point (inv-map (make-vect (exact->inexact start-column) inexr)))
         (picture-start-x (vector-xcor picture-start-point))
         (picture-start-y (vector-ycor picture-start-point)))
    (if (> start-column end-column)
        'done
        (let fill-loop ((picture-x picture-start-x)
                        (picture-y picture-start-y)
                        (column start-column))
          (if (fix:> column end-column)
              'done
              ;;this is the inner loop
              (begin
                (floating-vector-set!
                 row-vector
                 column
                 (exact->inexact (proc picture-x picture-y)))
                (fill-loop (flo:+ picture-x picture-x-step)
                           (flo:+ picture-y picture-y-step)
                           (fix:+ column 1))))))))

;;;;Creating painters from pictures

;;;;The picture p is defined on some frame,
;;;; Given a point p in the target frame, we compute T-1(p) where T
;;;;is the transformation that takes the picture frame to the
;;;;target frame, and find the picture value at the closest
;;;;integer point.

(define (picture->painter picture)
  (if (not (access picture? (->environment '(student pictures))))
      (error "Argument not a picture--PICTURE->PAINTER" picture))
  (let ((h (exact->inexact (picture-width picture)))
        (v (exact->inexact (picture-height picture)))
        (picture-data (access picture-data (->environment '(student pictures))))
        (invalidate-cached-values (access invalidate-cached-values
                                          (->environment '(student pictures)))))
    ;;we're going to draw by scanning along the integer points in the frame
    (lambda (frame)
      (let* ((frame (make-inexact-frame frame))
             ;;force coords to inexact, because optimized inner
             ;;loops assume inexact
             (frame-map (frame-mapping-info h v frame)))
        ;;if frame degenerates to a line then don't try to draw
        (if (degenerate? frame-map)
            'done
            (let ((segments (frame-map-segments frame-map))
                  (lowest-row (frame-map-lowest-row frame-map))
                  (highest-row (frame-map-highest-row frame-map))
                  (picture-x-step (frame-map-x-step frame-map))
                  (picture-y-step (frame-map-y-step frame-map))
                  (inv-map (frame-map-inv-map frame-map))
                  (picture-array (picture-data picture))
                  (picture-row-max (- (picture-height picture) 1))
                  (picture-row-min 0)
                  (picture-column-max (- (picture-width picture) 1))
                  (picture-column-min 0))
              (invalidate-cached-values *the-screen*)
              (let rowloop ((r lowest-row))
                (if (> r highest-row)
                    'done
                    (let ((intersections (row-intersects-segments r segments)))
                      (if (null? intersections)
                          'done
                          (let ((start-col (max 0 (car intersections)))
                                (stop-col (min *last-screen-column* (cadr intersections))))
                            ;;(write-line `(row ,r start: ,start-col stop: ,stop-col))
                            (fill-row-from-picture
                             r start-col stop-col inv-map
                             picture-x-step picture-y-step
                             (vector-ref (picture-data *the-screen*) r)
                             picture-array
                             picture-row-min picture-row-max
                             picture-column-min picture-column-max)))
                      (rowloop (+ r 1)))))))))))


;;;Here is the loop that fills across a row of the target frame.
;;;It is a little hairy because it needs to make sure that the points
;;;referenced in the picture are actually in bounds.  (Roundoff error
;;;in computing the inverse transformation can drive things out of bounds.)

(define (fill-row-from-picture row start-column end-column inv-map
                               picture-x-step picture-y-step
                               row-vector picture-array
                               picture-row-min picture-row-max
                               picture-column-min picture-column-max
                               )
  (let* ((inexr (exact->inexact row))
         (picture-start-point (inv-map (make-vect (exact->inexact start-column) inexr)))
         (picture-start-x (vector-xcor picture-start-point))
         (picture-start-y (vector-ycor picture-start-point))
         (picture-end-point (inv-map (make-vect (exact->inexact end-column) inexr)))
         (picture-end-x (vector-xcor picture-end-point))
         (picture-end-y (vector-ycor picture-end-point))
	 ;;see comment on trimming, below
	 (x-start->exact (if (> picture-x-step 0) floor->exact ceiling->exact))
	 (y-start->exact (if (> picture-y-step 0) floor->exact ceiling->exact))
	 (x-end->exact (if (> picture-x-step 0) ceiling->exact floor->exact))
	 (y-end->exact (if (> picture-y-step 0) ceiling->exact floor->exact)))
    ;;trim the row from the start and end to make sure that all points
    ;;are in bounds.  When we round to check if the endpoints are in
    ;;the picture, we have to round "outwards", according to the
    ;;direction we are scanning x and y.  I would have thought that
    ;;using round->exact would be enough, but there are screw cases.
    (let find-real-start-loop ()
      (if (or (point-in-picture (x-start->exact picture-start-x) (y-start->exact picture-start-y)
                                picture-row-min picture-row-max
                                picture-column-min picture-column-max)
              (> start-column end-column))
          'done
          (begin (set! picture-start-x (+ picture-start-x picture-x-step))
                 (set! picture-start-y (+ picture-start-y picture-y-step))
                 (set! start-column (+ start-column 1))
                 (find-real-start-loop))))
    (let find-real-end-loop ()
      (if (or (point-in-picture (x-end->exact picture-end-x) (y-end->exact picture-end-y)
                                picture-row-min picture-row-max
                                picture-column-min picture-column-max)
              (< end-column start-column))
          'done
          (begin (set! picture-end-x (- picture-end-x picture-x-step))
                 (set! picture-end-y (- picture-end-y picture-y-step))
                 (set! end-column (- end-column 1))
                 (find-real-end-loop))))
    (if (> start-column end-column)
        'done
        ;;the next two loops are the real inner loops
        (if (= picture-y-step 0)
            ;;optimized loop where picture row doesn't change
            ;;this saves a level of vector access and an add
            (let fill-loop ((picture-x picture-start-x)
                            (column start-column))
              (let ((picture-row  (vector-ref picture-array (round->exact picture-start-y))))
                (if (fix:> column end-column)
                    'done
                    (begin
                      (floating-vector-set! row-vector
                                   column
                                   (floating-vector-ref picture-row (round->exact picture-x)))
                      (fill-loop (flo:+ picture-x picture-x-step)
                                 (fix:+ column 1))))))
            ;;general case where picture row changes
            (let fill-loop ((picture-x picture-start-x)
                            (picture-y picture-start-y)
                            (column start-column))
              (if (fix:> column end-column)
                  'done
                  (begin
                    (floating-vector-set!
                     row-vector
                     column
                     (floating-vector-ref (vector-ref picture-array (round->exact picture-y))
                                          (round->exact picture-x)))
                    (fill-loop (flo:+ picture-x picture-x-step)
                               (flo:+ picture-y picture-y-step)
                               (fix:+ column 1)))))))))

(define (point-in-picture picture-access-column picture-access-row
                          picture-row-min picture-row-max
                          picture-column-min picture-column-max)
  (and (fix:<= picture-access-row picture-row-max)
       (fix:>= picture-access-row picture-row-min)
       (fix:<= picture-access-column picture-column-max)
       (fix:>= picture-access-column picture-column-min)))

;;; given source and target frames compute mapping information, used above.

(define (frame-mapping-info h v frame)
  (let* ((frame-org (frame-origin frame))
         ;;now compute the transformation from picture to frame
         ;;ignoring shift --> matrix [[a11 a12][a21 a22]]
         (a11 (/ (vector-xcor (frame-edge1 frame)) h))
         (a12 (/ (vector-xcor (frame-edge2 frame)) v))
         (a21 (/ (vector-ycor (frame-edge1 frame)) h))
         (a22 (/ (vector-ycor (frame-edge2 frame)) v))
         (det (- (* a11 a22) (* a21 a12))))
    (if (<= (abs det) 1.e-4)
        (list 'degenerate)
        ;;compute inverse transformation
        (let* ((b11 (/ a22 det))
               (b21 (/ (- a21) det))
               (b12 (/ (- a12) det))
               (b22 (/ a11 det))
               (inv-map
                (lambda (v)
                  (let ((v (vector-sub v frame-org)))
                    (let ((x (vector-xcor v)) (y (vector-ycor v)))
                      (make-vect (+ (* b11 x) (* b12 y))
                                 (+ (* b21 x) (* b22 y)))))))
               ;;now, as we step horizontally along the frame,
               ;;we step by [x,y]-->[x+b11,y+b21] along the picture
               ;;we are going to step along the frame in integer coords
               ;;and step along the picture in real number coords
               (x-step b11)                
               (y-step b21)
               (vertices
                (list (frame-origin frame)
                      (vector-add (frame-origin frame) (frame-edge1 frame))
                      (vector-add (frame-origin frame) (frame-edge2 frame))
                      (vector-add (frame-origin frame)
                                  (vector-add (frame-edge1 frame) (frame-edge2 frame)))))
               (segments (points->segment-checks vertices))
               (vector-ycors (map vector-ycor vertices))
               (lowest-row (max 0 (floor->exact (apply min vector-ycors))))
               (highest-row (min *last-screen-row* (ceiling->exact (apply max vector-ycors)))))
          (list 'ok ;not degenerate               
                x-step y-step
                segments
                lowest-row highest-row
                inv-map)))))

(define (degenerate? frame-map)
  (eq? (car frame-map) 'degenerate))

(define (frame-map-x-step frame-map) (list-ref frame-map 1))
(define (frame-map-y-step frame-map) (list-ref frame-map 2))
(define (frame-map-segments frame-map) (list-ref frame-map 3))
(define (frame-map-lowest-row frame-map) (list-ref frame-map 4))
(define (frame-map-highest-row frame-map) (list-ref frame-map 5))
(define (frame-map-inv-map frame-map) (list-ref frame-map 6))


;;;generate data structures to check intersections, given
;;;the four points of a quadrilateral

(define (points->segment-checks points)
  (let* ((p1 (list-ref points 0))
         (p2 (list-ref points 1))
         (p3 (list-ref points 2))
         (p4 (list-ref points 3))
         (s1 (make-segment-check p1 p2))
         (s2 (make-segment-check p1 p3))
         (s3 (make-segment-check p2 p4))
         (s4 (make-segment-check  p3 p4)))
    (list s1 s2 s3 s4)))
         
(define (make-segment-check p1 p2)
  (define (make-seg lower higher)
    (let* ((xs (vector-xcor lower))
           (xe (vector-xcor higher))
           (ys (vector-ycor lower))
           (ye (vector-ycor higher))
           (dx (- xe xs))
           (dy (- ye ys))
           (dx/dy (if (< (abs dy) 1.e-4) false (/ dx dy))))
      (list xs xe ys ye dx/dy)))
   (if (< (vector-ycor p1) (vector-ycor p2))
       (make-seg p1 p2)
       (make-seg p2 p1)))

(define (segment-check-xs s) (list-ref s 0))
(define (segment-check-xe s) (list-ref s 1))
(define (segment-check-ys s) (list-ref s 2))
(define (segment-check-ye s) (list-ref s 3))
(define (segment-check-dx/dy s) (list-ref s 4))


;;;given the row index and the segment structures, find the col indices of the
;;intersections

(define (row-intersects-segments row segments)
  (let* ((y (exact->inexact row))
         (intersections
          (map (lambda (seg) (row-intersects-segment y seg)) segments))
         (flush-nulls (apply append intersections)))
    (if (null? flush-nulls)
        '()
        (let ((start-col (apply min flush-nulls))
              (stop-col (apply max flush-nulls)))
          (list start-col stop-col)))))
          
(define (row-intersects-segment y s)
  (let* ((xs (segment-check-xs s))
         (ys (segment-check-ys s))
         (xe (segment-check-xe s))
         (ye (segment-check-ye s))
         (dx/dy (segment-check-dx/dy s)))
    (if  (not dx/dy)
        ;;if segment is horizontal, it's either at than coord or not
        (if (= (round->exact y) (round->exact ys))
            (list (round->exact xs) (round->exact xe))
            '())
        (if (<= ys y ye)
            (list (round->exact
                   (+ xs (* (- y ys) dx/dy))))
            '()))))

(define (make-inexact-frame frame)
  (make-frame (make-inexact-vect (frame-origin frame))
             (make-inexact-vect (frame-edge1 frame))
             (make-inexact-vect (frame-edge2 frame))))

(define (make-inexact-vect vector)
  (make-vect (exact->inexact (vector-xcor vector))
             (exact->inexact (vector-ycor vector))))