tiles.ss
#lang mzscheme
; Modified 1/19/2005 to be compatible with new image.ss contracts.
; Modified 2/16/2005 to include stuff from world.ss as well as image.ss
; Modified 2/17/2005 to provide on-update-event (which requires overriding a few
; functions from world.ss)
; Modified 5/20/2005 to rename on-update-event as on-redraw-event, and
; handle pinholes more consistently in image-beside and image-above.
; Modified 1/26/2006 to remove the functions I was replacing in image.ss
; (since image.ss now does things the way I wanted) and
; to remove my tweaked copy of world.ss (since world.ss now does things the
; way I wanted).
; Modified 7/12/2006 to allow image-beside and image-above to take variable numbers of arguments.
; Modified 7/26/2006 to add image-beside-align-top, image-beside-align-bottom, image-above-align-left, and image-above-align-right.
; Modified 12/17/2007 to add crop-top, crop-bottom, crop-left, crop-right.  Also utility functions slice-pic and unslice-pic.
; Modified 12/26/2007 to provide all-from image.ss, so we never have to mention image.ss at all.
; Modified 8/15/2008 to add image-above-align-center and image-beside-align-center.

  (require
;   (lib "etc.ss")
;   (lib "class.ss")
   (lib "list.ss") ; foldl
;   (lib "mred.ss" "mred")
;   (lib "error.ss" "htdp")
;   (prefix beg: (lib "htdp-beginner.ss" "lang"))
;   (lib "prim.ss" "lang")
;   (all-except (lib "image.ss" "htdp") rectangle circle ellipse triangle)
;   (rename (lib "image.ss" "htdp") rectangle^ rectangle)
;   (rename (lib "image.ss" "htdp") circle^ circle)
;   (rename (lib "image.ss" "htdp") ellipse^ ellipse)
;   (rename (lib "image.ss" "htdp") triangle^ triangle)
    (lib "image.ss" "htdp")   ; overlay/xy, etc.
;   (lib "error.ss" "htdp")
;    scheme/contract
   )
  ; Make this (provide/contract
  ;               (image-above (->* (image? image?) () #:rest (listof image?) image?))
  ;               etc.
  ;               (reflect-vert (-> image? image?))
  ;               etc.
  (provide image-above
           image-above-align-right
           image-above-align-left
           image-above-align-center
           image-beside
           image-beside-align-top
           image-beside-align-bottom
           image-beside-align-center
           reflect-vert
           reflect-horiz
           reflect-main-diag
           reflect-other-diag
           rotate-cw
           rotate-ccw
           rotate-180
;           rectangle
;           circle
;           ellipse
;           triangle
           show-pinhole
           (all-from (lib "image.ss" "htdp"))
           crop-top
           crop-bottom
           crop-left
           crop-right
           )

  

  
  ; Added functions to image.ss
  
  ; image-translate : image int int => image
  (define (image-translate image dx dy)
    (overlay/xy (rectangle (+ (image-width image) dx)
                           (+ (image-height image) dy)
                           'solid
                           'white)
                dx
                dy 
                image))
  
  
  ; pinhole-to-bottom : image -> number
  (define (pinhole-to-bottom img)
    (- (image-height img) (pinhole-y img)))
  ; pinhole-to-top : image -> number
  (define (pinhole-to-top img)
    (pinhole-y img))
  ; pinhole-to-left : image -> number
  (define (pinhole-to-left img)
    (pinhole-x  img))
  ; pinhole-to-right : image -> number
  (define (pinhole-to-right img)
    (- (image-width img) (pinhole-x img)))
  
; concat-vert : image image => image
  (define (concat-vert under over)
    (let [[dy (+ (pinhole-to-bottom over) (pinhole-to-top under))]]
      (move-pinhole
       (overlay/xy over 0 (round (+ (pinhole-to-bottom over) (pinhole-to-top under))) under)
       0 (round (/ dy 2)))))

; image-above : image image ... -> image
  (define (image-above . images)
    (cond [(null? images) (error 'image-above "Expected two or more images; given 0")]
          [(null? (cdr images)) (error 'image-above "Expected two or more images; given 1")]
          [else (foldl concat-vert (car images) (cdr images))]))

; image-above-align-right : image image ... -> image
  (define (image-above-align-right . images)
    (apply image-above
           (map (lambda (img)
                  (move-pinhole img (pinhole-to-right img) 0))
                images)))
  
; image-above-align-left : image image ... -> image
  (define (image-above-align-left . images)
    (apply image-above
           (map (lambda (img)
                  (move-pinhole img (- (pinhole-to-left img)) 0))
                images)))
  
  
  ; concat-horiz : image image => image
  (define (concat-horiz right left)
    (let [[dx (+ (pinhole-to-right left) (pinhole-to-left right))]]
      (move-pinhole
       (overlay/xy left dx 0 right)
       (round (/ dx 2)) 0)))

; image-beside : image image ... => image
  (define  (image-beside . images)
    (cond [(null? images) (error 'image-beside "Expected two or more images; given 0")]
          [(null? (cdr images)) (error 'image-beside "Expected two or more images; given 1")]
          [else (foldl concat-horiz (car images) (cdr images))]))
; image-beside-align-top : image image ... -> image
  (define (image-beside-align-top . images)
    (apply image-beside
           (map (lambda (img)
                  (move-pinhole img 0 (- (pinhole-to-top img))))
                images)))
  
; image-beside-align-bottom : image image ... -> image
  (define (image-beside-align-bottom . images)
    (apply image-beside
           (map (lambda (img)
                  (move-pinhole img 0 (pinhole-to-bottom img)))
                images)))

  ; center-pinhole : image -> image
  (define (center-pinhole img)
    (put-pinhole img
                 (quotient (image-width img) 2)
                 (quotient (image-height img) 2)))
  
  ; image-beside-align-center : image image ... -> image
  (define (image-beside-align-center . images)
    (apply image-beside
           (map center-pinhole images)))
  
  ; image-above-align-center : image image ... -> image
  (define (image-above-align-center . images)
    (apply image-above
           (map center-pinhole images)))
  
  ; show-pinhole : image -> image
  ; assumes the image is not solid black
  (define (show-pinhole img)
    (overlay img (circle 2 "solid" "black")))
;  "Examples of show-pinhole:"
;  (show-pinhole (rectangle 50 30 "solid" "blue"))
;  "should be a 50x30 blue rectangle with a black dot in the center"
  ; first-n : list n => list of length n (or less)
  (define (first-n L n)
    (cond [(null? L) ()]
          [(<= n 0) ()]
          [else (cons (car L)
                      (first-n (cdr L) (- n 1)))]))
  ;"Examples of first-n:"
  ;(first-n empty 2) "should be" empty
  ;(first-n (list 'a) 2) "should be" (list 'a)
  ;(first-n (list 'a 'b 'c) 2) "should be" (list 'a 'b)
  
  ; rest-n : list n => list of length n smaller (or 0)
  (define (rest-n L n)
    (cond [(null? L) ()]
          [(<= n 0) L]
          [else (rest-n (cdr L) (- n 1))]))
  ;"Examples of rest-n:"
  ;(rest-n empty 2) "should be" empty
  ;(rest-n (list 'a) 2) "should be" empty
  ;(rest-n (list 'a 'b 'c) 2) "should be" (list 'c)
  
  ; slice : list n => list-of-lists
  ; Assumes n divides the length of the list
  (define (slice L width)
    (cond [(null? L) ()]
          [else (cons (first-n L width)
                      (slice (rest-n L width) width))]))
  
  ;"Examples of slice:"
  ;(slice (list 1 2 3 4 5 6) 2) "should be" (list (list 1 2) (list 3 4) (list 5 6))
  
  ; slice-pic : image n -> list-of-lists
  (define (slice-pic picture)
    (slice (image->color-list picture)
           (image-width picture)))
  
  ; unslice : list-of-lists => list
  (define (unslice lists)
    (apply append lists))
  ;"Example of unslice:"
  ;(unslice (list (list 1 2) (list 3 4) (list 5))) "should be" (list 1 2 3 4 5)
  
  ; unslice-pic : list-of-lists width height phx phy -> image
  (define (unslice-pic pixels width height phx phy)
    (color-list->image
     (unslice pixels)
     width height phx phy))
  
  ; reflect-horiz : image => image
  (define (reflect-horiz picture)
    (unslice-pic
     (map reverse
          (slice-pic picture))
     (image-width picture)
     (image-height picture)
     (pinhole-to-right picture)
     (pinhole-to-top picture)))
  
  
  ; A lot of the following would be cleaner and simpler if MrEd
  ; could handle pictures with height and/or width equal to 0!
  
  ; crop-left : image number -> image
  ; deletes that many pixels from left edge of image
  (define (crop-left picture pixels)
    (let* ((new-width (max 1 (- (image-width picture) pixels)))
           (new-phx (max 0 (- (pinhole-x picture) pixels)))
           (real-pixels (- (image-width picture) new-width)))
      (unslice-pic
       (map
        (lambda (row) (rest-n row real-pixels))
        (slice-pic picture))
       new-width
       (image-height picture)
       new-phx
       (pinhole-y picture))))
  
  ; crop-top : image number -> image
  ; deletes that many pixels from top edge of image
  (define (crop-top picture pixels)
    (let* ((new-height (max 1 (- (image-height picture) pixels)))
           (new-phy (max 0 (- (pinhole-y picture) pixels)))
           (real-pixels (- (image-height picture) new-height)))
      (unslice-pic
       (rest-n
        (slice-pic picture)
        real-pixels)
       (image-width picture)
       new-height
       (pinhole-x picture)
       new-phy)))
  
  ; crop-right : image number -> image
  ; deletes that many pixels from right edge of image
  (define (crop-right picture pixels)
    (let* ((new-width (max 1 (- (image-width picture) pixels)))
           (new-phx (min (pinhole-x picture) new-width)))
      (unslice-pic
       (map
        (lambda (row) (first-n row new-width))
        (slice-pic picture))
       new-width
       (image-height picture)
       new-phx
       (pinhole-y picture))))
  
  ; crop-bottom : image number -> image
  ; deletes that many pixels from bottom edge of image
  (define (crop-bottom picture pixels)
    (let* ((new-height (max 1 (- (image-height picture) pixels)))
           (new-phy (min (pinhole-y picture) new-height)))
      (unslice-pic
       (first-n
        (slice-pic picture)
        new-height)
       (image-width picture)
       new-height
       (pinhole-x picture)
       new-phy)))
  
  ; reflect-vert : image => image
  (define (reflect-vert picture)
    (unslice-pic
     (reverse 
      (slice-pic picture))
     (image-width picture)
     (image-height picture)
     (pinhole-to-left picture)
     (pinhole-to-bottom picture)))
  
  ; ncons-each : list => list-of-one-element-lists
  (define (ncons-each L)
    (map list L))
  ;"Examples of ncons-each:"
  ;(ncons-each empty) "should be" empty
  ;(ncons-each (list 'a)) "should be" (list (list 'a))
  ;(ncons-each (list 'a 'b)) "should be" (list (list 'a) (list 'b))
  
  ; transpose : list-of-lists => list-of-lists
  ; Assumes all lists are the same length
  ; Assumes there's at least one row and at least one column
  (define (transpose rows)
    (apply map (cons list rows)))
  ;"Exampls of transpose:"
  ;(transpose (list (list 'a))) "should be" (list (list 'a))
  ;(transpose (list (list 'a 'b))) "should be" (list (list 'a) (list 'b))
  ;(transpose (list (list 'a) (list 'b))) "should be" (list (list 'a 'b))
  ;(transpose (list (list 'a 'b) (list 'c 'd))) "should be" (list (list 'a 'c) (list 'b 'd))
  
  ; reflect-main-diag : image => image
  (define (reflect-main-diag picture)
    (unslice-pic
     (transpose
      (slice-pic picture))
     (image-height picture)
     (image-width picture)
     (pinhole-y picture)
     (pinhole-x picture)))
  
  ; reflect-other-diag : image => image
  (define (reflect-other-diag picture)
    ;  (reflect-vert
    ;   (reflect-main-diag
    ;    (reflect-vert picture))))
    (unslice-pic
     (reverse
      (transpose
       (reverse
        (slice-pic picture))))
     (image-height picture)
     (image-width picture)
     (pinhole-to-bottom picture)
     (pinhole-to-right picture)))
  
  ; The following should probably be rewritten to rotate around the pinhole....
  ; rotate-cw : image => image
  (define (rotate-cw picture)
    ;  (reflect-main-diag (reflect-vert picture)))
    (unslice-pic
     (transpose
      (reverse
       (slice-pic picture)))
     (image-height picture)
     (image-width picture)
     (pinhole-to-bottom picture)
     (pinhole-to-left picture)))
  
  ; rotate-ccw : image => image
  ; Ditto.
  (define (rotate-ccw picture)
    ;  (reflect-vert (reflect-main-diag picture)))
    (unslice-pic
     (reverse
      (transpose
       (slice-pic picture)))
     (image-height picture)
     (image-width picture)
     (pinhole-to-top picture)
     (pinhole-to-right picture)))
  
  ; rotate-180 : image => image
  (define (rotate-180 picture)
    ; (rotate-cw (rotate-cw picture))
    ; (reflect-vert (reflect-horiz picture))
    (unslice-pic
     (reverse
      (map reverse
           (slice-pic picture)))
     (image-width picture)
     (image-height picture)
     (pinhole-to-right picture)
     (pinhole-to-bottom picture)))