private/bitmap-test-util.ss
(module bitmap-test-util mzscheme
  (require (lib "mred.ss" "mred")
           (lib "mrpict.ss" "texpict")
           (lib "framework.ss" "framework")
           (lib "class.ss")
           "../pict.ss"
           "../reduction-semantics.ss")

  (provide test done)
  
  (define-struct failed-test (panel))
  
  (define tests 0)
  (define failed '())
  (define (done)
    (printf "~a tests" tests)
    (if (null? failed)
        (printf ", all passed\n")
        (printf ", ~a failed\n" (length failed))))
  
  (define-syntax (test stx)
    (syntax-case stx ()
      [(_ test-exp bitmap-filename)
       #`(test/proc
          #,(syntax-line stx)
          (λ () test-exp)
          bitmap-filename)]))
  
  (define (test/proc line-number func raw-bitmap-filename)
    (set! tests (+ tests 1))
    (let* ([bitmap-filename (build-path "bmps" raw-bitmap-filename)]
           [old-bitmap (if (file-exists? bitmap-filename)
                           (make-object bitmap% bitmap-filename)
                           (let* ([bm (make-object bitmap% 100 20)]
                                  [bdc (make-object bitmap-dc% bm)])
                             (send bdc clear)
                             (send bdc draw-text "does not exist" 0 0)
                             (send bdc set-bitmap #f)
                             bm))]
           [pict (parameterize ([dc-for-text-size (make-object bitmap-dc% (make-object bitmap% 1 1))])
                   (func))]
           [new-bitmap (make-object bitmap% 
                         (inexact->exact (pict-width pict)) 
                         (inexact->exact (pict-height pict)))]
           [bdc (make-object bitmap-dc% new-bitmap)])
      (send bdc clear)
      (draw-pict pict bdc 0 0)
      (send bdc set-bitmap #f)
      (let ([diff-bitmap (compute-diffs old-bitmap new-bitmap)])
        (when diff-bitmap
          (let ([failed-panel (make-failed-panel line-number bitmap-filename old-bitmap new-bitmap diff-bitmap)])
            (set! failed (append failed (list (make-failed-test failed-panel)))))))))
  
  (define (compute-diffs old-bitmap new-bitmap)
    (let* ([w (max (send old-bitmap get-width)
                   (send new-bitmap get-width))]
           [h (max (send old-bitmap get-height)
                   (send new-bitmap get-height))]
           [diff-bitmap (make-object bitmap% w h)]
           [new (make-object bitmap-dc% new-bitmap)]
           [old (make-object bitmap-dc% old-bitmap)]
           [diff (make-object bitmap-dc% diff-bitmap)]
           [new-c (make-object color%)]
           [old-c (make-object color%)]
           [any-different? #f])
      (let loop ([x 0])
        (unless (= x w)
          (let loop ([y 0])
            (unless (= y h)
              (cond
                [(and (<= x (send new-bitmap get-width))
                      (<= y (send new-bitmap get-height))
                      (<= x (send old-bitmap get-width))
                      (<= y (send old-bitmap get-height)))
                 (send new get-pixel x y new-c)
                 (send old get-pixel x y old-c)
                 (cond
                   [(and (= (send new-c red) (send old-c red))
                         (= (send new-c green) (send old-c green))
                         (= (send new-c blue) (send old-c blue)))
                    (send diff set-pixel x y new-c)]
                   [else
                    (set! any-different? #t)
                    (send new-c set 255 0 0)
                    (send diff set-pixel x y new-c)])]
                [else 
                 (set! any-different? #t)
                 (send new-c set 255 0 0)
                 (send diff set-pixel x y new-c)])
              (loop (+ y 1))))
          (loop (+ x 1))))
      (send diff set-bitmap #f)
      (send old set-bitmap #f)
      (send new set-bitmap #f)
      (and any-different? diff-bitmap)))
  
  (define test-result-single-panel #f)
  (define (get-test-result-single-panel)
    (cond
      [test-result-single-panel
       test-result-single-panel]
      [else
       (let ()
         (define f (new frame% [label "bitmap-test.ss failures"]))
         (define lined (new vertical-panel% [parent f] [style '(border)]))
         (define sp (new panel:single% [parent lined]))
         (define current-index 0)
         (define hp (new horizontal-panel% [parent f]))
         (define prev 
           (new button% 
                [label "Prev"] 
                [parent hp]
                [callback
                 (λ (x y)
                   (set! current-index (modulo (- current-index 1) (length failed)))
                   (update-gui))]))
         (define next (new button% 
                           [label "Next"] 
                           [parent hp]
                           [callback
                            (λ (x y)
                              (set! current-index (modulo (+ current-index 1) (length failed)))
                              (update-gui))]))
         (define (update-gui) 
           (send sp active-child (failed-test-panel (list-ref failed current-index))))
         (set! test-result-single-panel sp)
         (send f show #t)
         sp)]))
         
  (define (make-failed-panel line-number filename old-bitmap new-bitmap diff-bitmap)
    (define f (new vertical-panel% [parent (get-test-result-single-panel)]))
    (define msg (new message% [label (format "line ~a" line-number)] [parent f]))
    (define hp (new horizontal-panel% [parent f]))
    (define vp1 (new vertical-panel% [parent hp]))
    (define vp2 (new vertical-panel% [parent hp]))
    (define chk (new check-box% 
                     [label "Show diff"]
                     [parent f]
                     [callback
                      (λ (_1 _2)
                        (cond
                          [(send chk get-value)
                           (send chk set-label "Hide diff")
                           (send right-hand set-label diff-bitmap)]
                          [else
                           (send chk set-label "Show diff")
                           (send right-hand set-label new-bitmap)]))]))
    (define btn (new button%
                     [parent f]
                     [label "Save"]
                     [callback
                      (λ (x y)
                        (send new-bitmap save-file filename 'png))]))
    (define left-label (new message% [parent vp1] [label "Old"]))
    (define left-hand (new message%
                           [parent vp1]
                           [label diff-bitmap]))
    (define right-label (new message% [parent vp2] [label "New"]))
    (define right-hand (new message%
                            [parent vp2]
                            [label diff-bitmap]))
    (send left-hand set-label old-bitmap)
    (send right-hand set-label new-bitmap)
    f))