#lang racket
(require mrlib/image-core)
(require 2htdp/private/image-more)
(require scheme/gui)
(require lang/prim)
(provide-primitives real->int
maybe-color?
name->color
get-pixel-color
pixel-visible?
)
(provide-higher-order-primitive map-image (f _))
(provide-higher-order-primitive map3-image (rfunc gfunc bfunc _))
(provide-higher-order-primitive map-masked-image (f _))
(provide-higher-order-primitive build-image (_ _ f))
(provide-higher-order-primitive build3-image (_ _ rfunc gfunc bfunc))
(provide-higher-order-primitive build-masked-image (_ _ f))
(define MONOCHROME #f)
(define (maybe-color? thing)
(or (color? thing)
(eqv? thing #f)))
(define (name->color name)
(let [[result (send the-color-database find-color name)]]
(if result
(color%->color result)
#f)))
(define (lookup-if-nec c)
(if (color? c)
c
(name->color c)))
(define (color->color% c)
(if (string? c)
c
(make-object color%
(color-red c)
(color-green c)
(color-blue c))))
(define (color%->color c)
(make-color (send c red)
(send c green)
(send c blue)))
(define (natural? it)
(and (integer? it)
(>= it 0)))
(define (color=? c1 c2)
(let [[rc1 (lookup-if-nec c1)]
[rc2 (lookup-if-nec c2)]]
(unless (and (color? rc1) (color? rc2))
(error 'color=? "Expected two colors or color names as arguments"))
(and (= (color-red rc1) (color-red rc2))
(= (color-green rc1) (color-green rc2))
(= (color-blue rc1) (color-blue rc2)))))
(define (build-image-internal width height f mask-bm)
(let* [[bm (make-object bitmap% width height)]
[bmdc (make-object bitmap-dc% bm)]
]
(for ((y (in-range height)))
(for ((x (in-range width)))
(send bmdc set-pixel x y (color->color% (f x y)))
))
(send bmdc set-bitmap #f)
(make-image
(make-translate (quotient width 2) (quotient height 2)
(make-bitmap bm mask-bm 0 1 1 #f #f))
(make-bb width height height)
#f )
))
(define (build-image width height f)
(unless (and (natural? width) (natural? height))
(error 'build-image "Expected natural numbers as first two arguments"))
(unless (and (procedure? f) (procedure-arity-includes? f 2))
(error 'build-image "Expected function with contract number number -> color as third argument"))
(if (or (zero? width) (zero? height)) (rectangle width height "solid" "white")
(let* [[mask-bm (make-object bitmap% width height MONOCHROME)] [mask-bmdc (make-object bitmap-dc% mask-bm)]
[black (make-object color% 0 0 0)]]
(send mask-bmdc set-background black)
(send mask-bmdc clear)
(send mask-bmdc set-bitmap #f)
(build-image-internal width height f mask-bm)
)
)
)
(define (build3-image width height rfunc gfunc bfunc)
(unless (and (natural? width) (natural? height))
(error 'build3-image "Expected natural numbers as first two arguments"))
(unless (and (procedure? rfunc) (procedure-arity-includes? rfunc 2))
(error 'build3-image "Expected function with contract number number -> number as third argument"))
(unless (and (procedure? gfunc) (procedure-arity-includes? gfunc 2))
(error 'build3-image "Expected function with contract number number -> number as fourth argument"))
(unless (and (procedure? bfunc) (procedure-arity-includes? bfunc 2))
(error 'build3-image "Expected function with contract number number -> number as fifth argument"))
(build-image width height
(lambda (x y) (make-color (rfunc x y) (gfunc x y) (bfunc x y)))))
(define (build-masked-image width height f)
(unless (and (natural? width) (natural? height))
(error 'build-masked-image "Expected natural numbers as first two arguments"))
(unless (and (procedure? f) (procedure-arity-includes? f 2))
(error 'build-masked-image "Expected function with contract number number -> maybe-color as third argument"))
(if (or (zero? width) (zero? height)) (rectangle width height "solid" "white")
(let* [[bm (make-object bitmap% width height)]
[bmdc (make-object bitmap-dc% bm)]
[mask-bm (make-object bitmap% width height MONOCHROME)] [mask-bmdc (make-object bitmap-dc% mask-bm)]
[visible (make-object color% 0 0 0)]
[transparent (make-object color% 255 255 255)]]
(for ((y (in-range height)))
(for ((x (in-range width)))
(let* [[mc (f x y)]
[color (if mc (color->color% mc) transparent)]
[mask (if mc visible transparent)]]
(send bmdc set-pixel x y color)
(send mask-bmdc set-pixel x y mask)
)))
(send bmdc set-bitmap #f)
(send mask-bmdc set-bitmap #f)
(make-image
(make-translate (quotient width 2) (quotient height 2)
(make-bitmap bm mask-bm 0 1 1 #f #f))
(make-bb width height height)
#f )
)))
(define (real->int num)
(inexact->exact (round num)))
(define (get-px x y bmdc)
(let ((col (make-object color%)))
(send bmdc get-pixel x y col)
(color%->color col)))
(define get-pixel-color
(let [[last-image #f]
[last-bm #f]
[last-bmdc #f]]
(lambda (x y pic)
(unless (eqv? pic last-image)
(set! last-image pic)
(set! last-bm (make-object bitmap% (image-width pic) (image-height pic)))
(set! last-bmdc (make-object bitmap-dc% last-bm))
(render-image pic last-bmdc 0 0))
(if (and (<= 0 x (image-width pic))
(<= 0 y (image-height pic)))
(get-px x y last-bmdc)
(make-color 0 0 0)))))
(define pixel-visible?
(let [[last-image #f]
[last-bm #f]
[last-bmdc #f]]
(lambda (x y pic)
(unless (eqv? pic last-image)
(set! last-image pic)
(set! last-bm (get-mask pic))
(set! last-bmdc (make-object bitmap-dc% last-bm)))
(let [[mask-pix (get-px x y last-bmdc)]] (and (equal? mask-pix (make-color 0 0 0)) (>= x 0)
(>= y 0)
(< x (image-width pic))
(< y (image-height pic))
)))))
(define (color-diff c1 c2)
(make-color (- (color-red c1) (color-red c2))
(- (color-green c1) (color-green c2))
(- (color-blue c1) (color-blue c2))))
(define (to-grey c)
(local [(define avg (quotient (+ (color-red c) (color-green c) (color-blue c)) 3))]
(make-color avg avg avg)))
(define (get-mask pic)
(let* ((w (image-width pic))
(h (image-height pic))
(bm1 (make-object bitmap% w h))
(bm2 (make-object bitmap% w h))
(mask-bm (make-object bitmap% w h MONOCHROME))
(dc1 (make-object bitmap-dc% bm1))
(dc2 (make-object bitmap-dc% bm2))
(mask-dc (make-object bitmap-dc% mask-bm)))
(render-image (rectangle w h "solid" "white") dc1 0 0)
(render-image (rectangle w h "solid" "black") dc2 0 0)
(render-image pic dc1 0 0)
(render-image pic dc2 0 0)
(for ((x (in-range w)))
(for ((y (in-range h)))
(send mask-dc set-pixel x y
(color->color% (to-grey (color-diff (get-px x y dc1)
(get-px x y dc2))))
)))
(send mask-dc set-bitmap #f)
mask-bm))
(define (map-image f pic)
(unless (and (procedure? f) (procedure-arity-includes? f 3))
(error 'map-image "Expected function with contract number number color -> color as first argument"))
(unless (image? pic)
(error 'map-image "Expected image as second argument"))
(let* [[width (image-width pic)]
[height (image-height pic)]
[bm (make-object bitmap% width height)]
[bmdc (make-object bitmap-dc% bm)]
[mask (get-mask pic)]
]
(render-image pic bmdc 0 0) (build-image-internal (image-width pic) (image-height pic)
(lambda (x y)
(f x y (get-px x y bmdc)))
mask)))
(define (map3-image rfunc gfunc bfunc pic)
(unless (and (procedure? rfunc) (procedure-arity-includes? rfunc 5))
(error 'map3-image "Expected function with contract num(x) num(y) num(r) num(g) num(b) -> num(r) as first argument"))
(unless (and (procedure? gfunc) (procedure-arity-includes? gfunc 5))
(error 'map3-image "Expected function with contract num(x) num(y) num(r) num(g) num(b) -> num(g) as second argument"))
(unless (and (procedure? bfunc) (procedure-arity-includes? bfunc 5))
(error 'map3-image "Expected function with contract num(x) num(y) num(r) num(g) num(b) -> num(b) as third argument"))
(map-image
(lambda (x y c)
(make-color (rfunc x y (color-red c) (color-green c) (color-blue c))
(gfunc x y (color-red c) (color-green c) (color-blue c))
(bfunc x y (color-red c) (color-green c) (color-blue c))))
pic))
(define (map-masked-image f pic)
(unless (and (procedure? f) (procedure-arity-includes? f 3))
(error 'map-masked-image "Expected function with contract number number maybe-color -> maybe-color as first argument"))
(unless (image? pic)
(error 'map-masked-image "Expected image as second argument"))
(let* [[width (image-width pic)]
[height (image-height pic)]
[bm (make-object bitmap% width height)]
[bmdc (make-object bitmap-dc% bm)]
[mask (get-mask pic)]
]
(render-image pic bmdc 0 0)
(build-masked-image
width height
(lambda (x y)
(f x y
(if (pixel-visible? x y pic)
(get-pixel-color x y pic)
#f))))))