racket/collects/picturing-programs/map-image.rkt
Stephen Bloch 4bce35f0a4 Changes to documentation and require/provide lines to get "raco setup" to
work smoothly.  Deleted compiled code and backup files, as well as tests
that were just copied from universe.
2010-12-30 07:46:43 -05:00

316 lines
13 KiB
Racket

#lang racket/base
; Spring 2010: started trying to get this to work.
; Late June 2010: Got build-image and map-image working.
; Added name->color and get-pixel-color.
; Added build-masked-image and map-masked-image.
; July 6, 2010: added change-to-color
; July 28, 2010: added map3-image and build3-image. Is change-to-color really useful?
; Dec. 26, 2010: added color=? to export (duh!)
; Dec. 26, 2010: API for bitmaps has changed for 5.1, so I need to rewrite to match it.
; Dec. 28, 2010: Robby added alphas into the "color" type, and provided an implementation
; of map-image. He recommends using racket/draw bitmaps rather than 2htdp/image bitmaps.
(require racket/draw
racket/snip
racket/class
2htdp/image
(only-in htdp/error natural?)
(only-in mrlib/image-core render-image))
;(require picturing-programs/book-pictures)
;(require mrlib/image-core)
;(require 2htdp/private/image-more)
;; (require 2htdp/private/img-err)
;(require scheme/gui)
(require lang/prim)
(provide-primitives real->int
; maybe-color?
name->color
colorize
get-pixel-color
;pixel-visible?
; change-to-color
color=?
)
(provide-higher-order-primitive map-image (f _))
(provide-higher-order-primitive map3-image (rfunc gfunc bfunc _))
(provide-higher-order-primitive map4-image (rfunc gfunc bfunc afunc _))
;(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 build4-image (_ _ rfunc gfunc bfunc afunc))
;(provide-higher-order-primitive build-masked-image (_ _ f))
(define transparent (make-color 0 0 0 0))
(define (maybe-color? thing)
(or (color? thing)
(eqv? thing #f)
; (image-color? thing) ; handles string & symbol color names
))
(define (broad-color? thing)
(or (maybe-color? thing)
(image-color? thing)))
; color->color% : does the obvious
; Note that color% doesn't have an alpha component, so alpha is lost.
(define (color->color% c)
(if (string? c)
c
(make-object color%
(color-red c)
(color-green c)
(color-blue c))))
; color%->color : does the obvious, with alpha defaulting to full-opaque.
(define (color%->color c)
(make-color (send c red)
(send c green)
(send c blue)))
; name->color : string-or-symbol -> maybe-color
(define (name->color name)
(unless (or (string? name) (symbol? name))
(error 'name->color "argument must be a string or symbol"))
(let [[result (send the-color-database find-color
(if (string? name)
name
(symbol->string name)))]]
(if result
(color%->color result)
#f)))
; colorize : broad-color -> color -- returns #f for unrecognized names
(define (colorize thing)
(cond [(color? thing) thing]
[(eqv? thing #f) transparent]
[(image-color? thing) (name->color thing)]
[else (error 'colorize "Unrecognized type")]))
; colorize-func : (... -> broad-color) -> (... -> color)
(define (colorize-func f)
(compose colorize f))
;; natural? : anything -> boolean
;(define (natural? it)
; (and (integer? it)
; (>= it 0)))
; color=? : broad-color broad-color -> boolean
(define (color=? c1 c2)
(let [[rc1 (colorize c1)]
[rc2 (colorize c2)]]
(unless (and (color? rc1) (color? rc2))
(error 'color=? "Expected two colors or color names as arguments"))
(and (= (color-alpha rc1) (color-alpha rc2)) ; Both alphas MUST be equal.
(or (= (color-alpha rc1) 0) ; If both are transparent, ignore rgb.
(and (= (color-red rc1) (color-red rc2))
(= (color-green rc1) (color-green rc2))
(= (color-blue rc1) (color-blue rc2)))))))
(define (real->int num)
(inexact->exact (round num)))
; get-px : x y w h bytes -> color
(define (get-px x y w h bytes)
(define offset (* 4 (+ x (* y w))))
(make-color (bytes-ref bytes (+ offset 1))
(bytes-ref bytes (+ offset 2))
(bytes-ref bytes (+ offset 3))
(bytes-ref bytes offset)))
; set-px! : bytes x y w h color -> void
(define (set-px! bytes x y w h new-color)
(define offset (* 4 (+ x (* y w))))
(bytes-set! bytes offset (color-alpha new-color))
(bytes-set! bytes (+ offset 1) (color-red new-color))
(bytes-set! bytes (+ offset 2) (color-green new-color))
(bytes-set! bytes (+ offset 3) (color-blue new-color)))
; get-pixel-color : x y image -> color
; This will remember the last image on which it was called.
; Really terrible performance if you call it in alternation
; on two different images, but should be OK if you call it
; lots of times on the same image.
; Returns transparent if you ask about a position outside the picture.
(define get-pixel-color
(let [[last-image #f]
[last-bytes #f]]
(lambda (x y pic)
(define w (image-width pic))
(define h (image-height pic))
(unless (eqv? pic last-image)
; assuming nobody mutates an image between one get-pixel-color and the next
(set! last-image pic)
(define bm (make-bitmap w h))
(define bmdc (make-object bitmap-dc% bm))
(set! last-bytes (make-bytes (* 4 w h)))
(render-image pic bmdc 0 0)
(send bmdc set-bitmap #f)
(send bm get-argb-pixels 0 0 w h last-bytes))
(if (and (<= 0 x (sub1 w))
(<= 0 y (sub1 h)))
(get-px x y w h last-bytes)
transparent))))
;; pixel-visible? : nat(x) nat(y) image -> boolean
;; similar
;(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)]] ; assumes this doesn't crash if out of bounds
; (and (equal? mask-pix (make-color 0 0 0)) ; treat anything else as transparent
; (>= x 0)
; (>= y 0)
; (< x (image-width pic))
; (< y (image-height pic))
; )))))
;
; build-image-internal : natural(width) natural(height) (nat nat -> color) -> image
(define (build-image-internal w h f)
(define bm (make-bitmap w h))
(define bdc (make-object bitmap-dc% bm))
(define bytes (make-bytes (* w h 4)))
(for* ((y (in-range 0 h))
(x (in-range 0 w)))
(set-px! bytes x y w h (f x y)))
(send bm set-argb-pixels 0 0 w h bytes)
(make-object image-snip% bm))
; build-image : natural(width) natural(height) (nat nat -> broad-color) -> image
(define (build-image w h f)
(unless (natural? w)
(error 'build-image "Expected natural number as first argument"))
(unless (natural? h)
(error 'build-image "Expected natural number as second argument"))
(unless (procedure-arity-includes? f 2)
(error 'build-image "Expected function with contract num(x) num(y) -> color as third argument"))
(build-image-internal w h (colorize-func f)))
; build3-image : nat(width) nat(height) rfunc gfunc bfunc -> image
; where each of rfunc, gfunc, bfunc is (nat(x) nat(y) -> nat)
(define (build3-image w h rfunc gfunc bfunc)
(unless (natural? w)
(error 'build3-image "Expected natural number as first argument"))
(unless (natural? h)
(error 'build3-image "Expected natural number as second argument"))
(unless (procedure-arity-includes? rfunc 2)
(error 'build3-image "Expected function with contract num(x) num(y) -> color as third argument"))
(unless (procedure-arity-includes? gfunc 2)
(error 'build3-image "Expected function with contract num(x) num(y) -> color as fourth argument"))
(unless (procedure-arity-includes? bfunc 2)
(error 'build3-image "Expected function with contract num(x) num(y) -> color as fifth argument"))
(build-image-internal w h
(lambda (x y)
(make-color (rfunc x y) (gfunc x y) (bfunc x y)))))
; build4-image : nat(width) nat(height) rfunc gfunc bfunc afunc -> image
; where each of rfunc, gfunc, bfunc, afunc is (nat(x) nat(y) -> nat)
(define (build4-image w h rfunc gfunc bfunc afunc)
(unless (natural? w)
(error 'build-image "Expected natural number as first argument"))
(unless (natural? h)
(error 'build-image "Expected natural number as second argument"))
(unless (procedure-arity-includes? rfunc 2)
(error 'build-image "Expected function with contract num(x) num(y) -> color as third argument"))
(unless (procedure-arity-includes? gfunc 2)
(error 'build-image "Expected function with contract num(x) num(y) -> color as fourth argument"))
(unless (procedure-arity-includes? bfunc 2)
(error 'build-image "Expected function with contract num(x) num(y) -> color as fifth argument"))
(unless (procedure-arity-includes? afunc 2)
(error 'build-image "Expected function with contract num(x) num(y) -> color as sixth argument"))
(build-image-internal w h
(lambda (x y)
(make-color (rfunc x y) (gfunc x y) (bfunc x y) (afunc x y)))))
; map-image-internal : (int int color -> color) image -> image
(define (map-image-internal f img)
(define w (image-width img))
(define h (image-height img))
(define bm (make-bitmap w h))
(define bdc (make-object bitmap-dc% bm))
(render-image img bdc 0 0)
(send bdc set-bitmap #f)
(define bytes (make-bytes (* w h 4)))
(send bm get-argb-pixels 0 0 w h bytes)
(for* ((y (in-range 0 h))
(x (in-range 0 w)))
(set-px! bytes x y w h (f x y (get-px x y w h bytes))))
(send bm set-argb-pixels 0 0 w h bytes)
(make-object image-snip% bm))
; map-image : (int int color -> broad-color) image -> image
(define (map-image f img)
(unless (procedure-arity-includes? f 3)
(error 'map-image "Expected function with contract num(x) num(y) color -> color as first argument"))
(unless (image? img)
(error 'map-image "Expected image as second argument"))
(map-image-internal (colorize-func f) img))
; The version for use before students have seen structs:
; map3-image :
; (int(x) int(y) int(r) int(g) int(b) -> int(r))
; (int(x) int(y) int(r) int(g) int(b) -> int(g))
; (int(x) int(y) int(r) int(g) int(b) -> int(b))
; image -> image
; Note: by default, preserves alpha values from old image.
(define (map3-image rfunc gfunc bfunc pic)
(unless (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 (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 (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"))
(unless (image? pic)
(error 'map3-image "Expected image as fourth argument"))
(map-image-internal
(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))
(color-alpha c)))
pic))
; map4-image :
; (int(x) int(y) int(r) int(g) int(b) int(a) -> int(r))
; (int(x) int(y) int(r) int(g) int(b) int(a) -> int(g))
; (int(x) int(y) int(r) int(g) int(b) int(a) -> int(b))
; (int(x) int(y) int(r) int(g) int(b) int(a) -> int(a))
; image -> image
(define (map4-image rfunc gfunc bfunc afunc pic)
(unless (procedure-arity-includes? rfunc 6)
(error 'map4-image "Expected function with contract num(x) num(y) num(r) num(g) num(b) num(alpha) -> num(r) as first argument"))
(unless (procedure-arity-includes? gfunc 6)
(error 'map4-image "Expected function with contract num(x) num(y) num(r) num(g) num(b) num(alpha) -> num(g) as second argument"))
(unless (procedure-arity-includes? rfunc 6)
(error 'map4-image "Expected function with contract num(x) num(y) num(r) num(g) num(b) num(alpha) -> num(b) as third argument"))
(unless (procedure-arity-includes? gfunc 6)
(error 'map4-image "Expected function with contract num(x) num(y) num(r) num(g) num(b) num(alpha) -> num(alpha) as fourth argument"))
(unless (image? pic)
(error 'map4-image "Expected image as fifth argument"))
(map-image-internal
(lambda (x y c)
(make-color (rfunc x y (color-red c) (color-green c) (color-blue c) (color-alpha c))
(gfunc x y (color-red c) (color-green c) (color-blue c) (color-alpha c))
(bfunc x y (color-red c) (color-green c) (color-blue c) (color-alpha c))
(afunc x y (color-red c) (color-green c) (color-blue c) (color-alpha c))))
pic))