277 lines
11 KiB
Racket
277 lines
11 KiB
Racket
#lang racket/base
|
|
(require (for-syntax racket/base)
|
|
racket/unsafe/ops
|
|
racket/contract
|
|
racket/class
|
|
racket/draw
|
|
racket/pretty
|
|
racket/math
|
|
unstable/future
|
|
slideshow/pict)
|
|
|
|
;; TODO: use clipping regions to avoid computing unused pixels
|
|
;; TODO: tweak parameters so that gaussian and iterated-box modes are closer
|
|
|
|
(define nneg-real/c (and/c real? (not/c negative?)))
|
|
|
|
(provide/contract
|
|
[blur
|
|
(->* (pict? nneg-real/c)
|
|
(nneg-real/c
|
|
#:mode (or/c 'gaussian 'iterated-box 'single-box))
|
|
pict?)]
|
|
[shadow
|
|
(->* (pict? nneg-real/c)
|
|
(real? real?
|
|
#:color (or/c #f string? (is-a?/c color%))
|
|
#:shadow-color (or/c #f string? (is-a?/c color%))
|
|
#:mode (or/c 'gaussian 'iterated-box 'single-box))
|
|
pict?)]
|
|
[blur-bitmap!
|
|
(->* ((is-a?/c bitmap%) exact-nonnegative-integer?)
|
|
(exact-nonnegative-integer?
|
|
#:mode (or/c 'gaussian 'iterated-box 'single-box))
|
|
void?)])
|
|
|
|
;; ----
|
|
|
|
(define (blur p hbr [vbr hbr] #:mode [mode 'iterated-box] #:auto-inset? [auto-inset? #f])
|
|
(let ([blurred (*blur (inset p hbr vbr) hbr vbr mode)])
|
|
(cond [auto-inset? blurred]
|
|
[else (inset blurred (- hbr) (- vbr))])))
|
|
|
|
(define (shadow p br [dx 0] [dy dx]
|
|
#:color [c #f]
|
|
#:shadow-color [shc #f]
|
|
#:mode [mode 'iterated-box]
|
|
#:auto-inset? [auto-inset? #f])
|
|
;; FIXME: should auto-inset also use dx, dy?
|
|
(define (colorize* p c)
|
|
(if c (colorize p c) p))
|
|
(let ([result
|
|
(pin-under (colorize* p c)
|
|
dx dy
|
|
(blur (colorize* p shc) br #:mode mode))])
|
|
(cond [auto-inset? (inset result br)]
|
|
[else result])))
|
|
|
|
(define (blur-bitmap! bmp hbr [vbr hbr] #:mode [mode 'iterated-box])
|
|
(blur! bmp hbr vbr mode))
|
|
|
|
;; ----
|
|
|
|
(define MAX-RADIUS (expt 2 10))
|
|
(define MAX-WEIGHT (expt 2 5))
|
|
(define BOX-ITERATIONS 3)
|
|
|
|
(define (*blur p hbr vbr mode)
|
|
(let* ([w (pict-width p)]
|
|
[h (pict-height p)]
|
|
[drawer (make-pict-drawer p)])
|
|
(dc (lambda (dc x y)
|
|
(let-values ([(sx sy) (send dc get-scale)])
|
|
(let* ([pxw (ceil/e (* w sx))]
|
|
[pxh (ceil/e (* h sy))]
|
|
[hbr* (min (ceil/e (* hbr sx)) pxw MAX-RADIUS)]
|
|
[vbr* (min (ceil/e (* vbr sy)) pxh MAX-RADIUS)]
|
|
[bmp (make-object bitmap% pxw pxh #f #t)]
|
|
[bdc (new bitmap-dc% (bitmap bmp))])
|
|
(send bdc set-scale sx sy)
|
|
(send bdc set-font (send dc get-font))
|
|
(send bdc set-pen (send dc get-pen))
|
|
(send bdc set-brush (send dc get-brush))
|
|
(send bdc set-text-foreground (send dc get-text-foreground))
|
|
(drawer bdc 0 0)
|
|
(blur! bmp hbr* vbr* mode)
|
|
(send dc set-scale 1.0 1.0)
|
|
(send dc draw-bitmap bmp (* x sx) (* y sy))
|
|
(send dc set-scale sx sy))))
|
|
w h)))
|
|
|
|
(define (blur! bmp hbr vbr mode)
|
|
(let* ([w (send bmp get-width)]
|
|
[h (send bmp get-height)]
|
|
[pix (make-bytes (* w h 4))]
|
|
[out (make-bytes (* w h 4))])
|
|
(send bmp get-argb-pixels 0 0 w h pix #f #t)
|
|
(case mode
|
|
((gaussian)
|
|
(let* ([h-cvec (gaussian-cvec hbr)]
|
|
[v-cvec (gaussian-cvec vbr)])
|
|
(convolve-h pix out h-cvec w h)
|
|
(convolve-v out pix v-cvec w h)))
|
|
((single-box)
|
|
(box-h pix out hbr w h 1)
|
|
(box-v out pix vbr w h 1))
|
|
((iterated-box)
|
|
(let ([hbr (ceil/e (/ hbr BOX-ITERATIONS))]
|
|
[vbr (ceil/e (/ vbr BOX-ITERATIONS))])
|
|
(box-h pix out hbr w h BOX-ITERATIONS)
|
|
(let-values ([(pix* out*)
|
|
(cond [(even? BOX-ITERATIONS) (values out pix)]
|
|
[else (values pix out)])])
|
|
(box-v pix* out* vbr w h BOX-ITERATIONS))))
|
|
(else (error 'blur! "bad mode")))
|
|
(send bmp set-argb-pixels 0 0 w h pix #f #t)
|
|
(void)))
|
|
|
|
;; ----
|
|
|
|
(define (ceil/e x) (inexact->exact (ceiling x)))
|
|
(define (round/e x) (inexact->exact (round x)))
|
|
|
|
(define (convolve-line* cvec start end get-val set-val)
|
|
(define CVEC-LEN (vector-length cvec))
|
|
(define CVEC-HALF (unsafe-fxquotient CVEC-LEN 2))
|
|
(let ([non-zero-alpha?
|
|
(for/or ([outi (in-range start end)])
|
|
(positive? (get-val outi 0)))])
|
|
(cond [non-zero-alpha?
|
|
(for ([outi (in-range start end)])
|
|
(define lo-ci (unsafe-fx+ start (unsafe-fx- CVEC-HALF outi)))
|
|
(define hi-ci (unsafe-fx+ end (unsafe-fx- CVEC-HALF outi)))
|
|
(define-values (na nr ng nb sumw)
|
|
(for/fold ([na 0] [nr 0] [ng 0] [nb 0] [sumw 0])
|
|
([ci (in-range (unsafe-fxmax 0 lo-ci) (unsafe-fxmin hi-ci CVEC-LEN))])
|
|
(let ([ini (unsafe-fx+ outi (unsafe-fx- ci CVEC-HALF))]
|
|
[w (unsafe-vector-ref cvec ci)])
|
|
(values (unsafe-fx+ na (unsafe-fx* w (get-val ini 0)))
|
|
(unsafe-fx+ nr (unsafe-fx* w (get-val ini 1)))
|
|
(unsafe-fx+ ng (unsafe-fx* w (get-val ini 2)))
|
|
(unsafe-fx+ nb (unsafe-fx* w (get-val ini 3)))
|
|
(unsafe-fx+ sumw w)))))
|
|
(set-val outi 0 (unsafe-fxquotient na sumw))
|
|
(set-val outi 1 (unsafe-fxquotient nr sumw))
|
|
(set-val outi 2 (unsafe-fxquotient ng sumw))
|
|
(set-val outi 3 (unsafe-fxquotient nb sumw)))]
|
|
[else
|
|
(for ([outi (in-range start end)])
|
|
(set-val outi 0 0)
|
|
(set-val outi 1 0)
|
|
(set-val outi 2 0)
|
|
(set-val outi 3 0))])))
|
|
|
|
(define (convolve-h in out cvec w h)
|
|
(define (get-val i offset) (bytes-ref in (unsafe-fx+ offset (unsafe-fx* 4 i))))
|
|
(define (set-val i offset v) (bytes-set! out (unsafe-fx+ offset (unsafe-fx* 4 i)) v))
|
|
(for/async ([row (in-range h)])
|
|
(convolve-line* cvec (unsafe-fx* row w) (unsafe-fx* (add1 row) w) get-val set-val)))
|
|
|
|
(define (convolve-v in out cvec w h)
|
|
(define ((mkget col) i offset)
|
|
(bytes-ref in (unsafe-fx+ (unsafe-fx* 4 (unsafe-fx+ (unsafe-fx* w i) col)) offset)))
|
|
(define ((mkset col) i offset v)
|
|
(bytes-set! out (unsafe-fx+ (unsafe-fx* 4 (unsafe-fx+ (unsafe-fx* w i) col)) offset) v))
|
|
(for/async ([col (in-range w)])
|
|
(convolve-line* cvec 0 h (mkget col) (mkset col))))
|
|
|
|
(define (gaussian-cvec radius [bias 'none]) ;; .84
|
|
(define sigma 1)
|
|
(define (G x)
|
|
(/ (exp (- (/ (sqr x) (* 2 sigma sigma))))
|
|
(sqrt (* 2 pi sigma sigma))))
|
|
(cond [(zero? radius)
|
|
(vector 1)]
|
|
[else
|
|
(build-vector
|
|
(+ 1 radius radius)
|
|
(lambda (x)
|
|
(cond [(and (< x radius) (eq? bias 'left))
|
|
0]
|
|
[(and (> x radius) (eq? bias 'right))
|
|
0]
|
|
[else
|
|
(ceil/e (* MAX-WEIGHT (G (/ (* 3 sigma (- x radius)) radius))))])))]))
|
|
|
|
;; ----
|
|
|
|
;; iterated box blur
|
|
|
|
(define (box-line* radius start end get-val set-val)
|
|
(let ([non-zero-alpha?
|
|
(for/or ([outi (in-range start end)])
|
|
(positive? (get-val outi 0)))])
|
|
(cond [non-zero-alpha?
|
|
(for/fold ([wA 0] [wR 0] [wG 0] [wB 0] [wW 0])
|
|
([leadI (in-range start (+ end radius))])
|
|
;; (eprintf "leadI = ~s, wA = ~s, wW = ~s\n" leadI wA wW)
|
|
(let*-values ([(outI) (unsafe-fx- leadI radius)]
|
|
[(tailI) (unsafe-fx- leadI (unsafe-fx+ radius radius))]
|
|
[(addA addR addG addB addW)
|
|
(cond [(unsafe-fx< leadI end)
|
|
(values (get-val leadI 0)
|
|
(get-val leadI 1)
|
|
(get-val leadI 2)
|
|
(get-val leadI 3)
|
|
1)]
|
|
[else (values 0 0 0 0 0)])]
|
|
[(dropA dropR dropG dropB dropW)
|
|
(cond [(unsafe-fx>= tailI start)
|
|
(values (get-val tailI 0)
|
|
(get-val tailI 1)
|
|
(get-val tailI 2)
|
|
(get-val tailI 3)
|
|
1)]
|
|
[else (values 0 0 0 0 0)])]
|
|
[(nwA) (unsafe-fx+ wA addA)]
|
|
[(nwR) (unsafe-fx+ wR addR)]
|
|
[(nwG) (unsafe-fx+ wG addG)]
|
|
[(nwB) (unsafe-fx+ wB addB)]
|
|
[(nwW) (unsafe-fx+ wW addW)])
|
|
(when (and (unsafe-fx>= outI start) (unsafe-fx< outI end))
|
|
;; (eprintf "setting ~a = (~a,...)\n" outI (quotient nwA nwW))
|
|
(set-val outI 0 (unsafe-fxquotient nwA nwW))
|
|
(set-val outI 1 (unsafe-fxquotient nwR nwW))
|
|
(set-val outI 2 (unsafe-fxquotient nwG nwW))
|
|
(set-val outI 3 (unsafe-fxquotient nwB nwW)))
|
|
(values (unsafe-fx- nwA dropA)
|
|
(unsafe-fx- nwR dropR)
|
|
(unsafe-fx- nwG dropG)
|
|
(unsafe-fx- nwB dropB)
|
|
(unsafe-fx- nwW dropW))))]
|
|
[else
|
|
(for ([outI (in-range start end)])
|
|
(set-val outI 0 0)
|
|
(set-val outI 1 0)
|
|
(set-val outI 2 0)
|
|
(set-val outI 3 0))])))
|
|
|
|
(define (box-h in out radius w h iterations)
|
|
(define (get-val i offset) (bytes-ref in (unsafe-fx+ offset (unsafe-fx* 4 i))))
|
|
(define (set-val i offset v) (bytes-set! out (unsafe-fx+ offset (unsafe-fx* 4 i)) v))
|
|
(define (get-val* i offset) (bytes-ref out (unsafe-fx+ offset (unsafe-fx* 4 i))))
|
|
(define (set-val* i offset v) (bytes-set! in (unsafe-fx+ offset (unsafe-fx* 4 i)) v))
|
|
(for/async ([row (in-range h)])
|
|
(for ([iter (in-range iterations)])
|
|
(box-line* radius (* row w) (* (add1 row) w)
|
|
(if (even? iter) get-val get-val*)
|
|
(if (even? iter) set-val set-val*)))))
|
|
|
|
(define (box-v in out radius w h iterations)
|
|
(define ((mkget col) i offset)
|
|
(bytes-ref in (unsafe-fx+ (unsafe-fx* 4 (unsafe-fx+ (unsafe-fx* w i) col)) offset)))
|
|
(define ((mkset col) i offset v)
|
|
(bytes-set! out (unsafe-fx+ (unsafe-fx* 4 (unsafe-fx+ (unsafe-fx* w i) col)) offset) v))
|
|
(define ((mkget* col) i offset)
|
|
(bytes-ref out (unsafe-fx+ (unsafe-fx* 4 (unsafe-fx+ (unsafe-fx* w i) col)) offset)))
|
|
(define ((mkset* col) i offset v)
|
|
(bytes-set! in (unsafe-fx+ (unsafe-fx* 4 (unsafe-fx+ (unsafe-fx* w i) col)) offset) v))
|
|
(for/async ([col (in-range w)])
|
|
(let ([get (mkget col)]
|
|
[get* (mkget* col)]
|
|
[set (mkset col)]
|
|
[set* (mkset* col)])
|
|
(for ([iter (in-range iterations)])
|
|
(box-line* radius 0 h
|
|
(if (even? iter) get get*)
|
|
(if (even? iter) set set*))))))
|
|
|
|
;; ----
|
|
|
|
;; used for benchmarking to force effectively lazy dc pict constructor
|
|
(define (p->bmp p)
|
|
(let* ([bmp (make-object bitmap% (ceil/e (pict-width p)) (ceil/e (pict-height p)))]
|
|
[bdc (new bitmap-dc% (bitmap bmp))])
|
|
(draw-pict p bdc 0 0)
|
|
bmp))
|