diff --git a/collects/images/private/deep-flomap-struct.rkt b/collects/images/private/deep-flomap-struct.rkt index e60f3d1ad5..c7035adece 100644 --- a/collects/images/private/deep-flomap-struct.rkt +++ b/collects/images/private/deep-flomap-struct.rkt @@ -31,8 +31,7 @@ (match-define (flomap _ 4 w h) argb-fm) (match-define (flomap _ 1 zw zh) z-fm) (unless (and (= w zw) (= h zh)) - (error 'deep-flomap - "expected flomaps of equal dimension; given dimensions ~e×~e and ~e×~e" w h zw zh)) + (error 'deep-flomap "expected same-size flomaps; given sizes ~e×~e and ~e×~e" w h zw zh)) (values argb-fm z-fm))) (: flomap->deep-flomap (flomap -> deep-flomap)) diff --git a/collects/images/private/flomap-effects.rkt b/collects/images/private/flomap-effects.rkt index 08df827257..24cbd8bb61 100644 --- a/collects/images/private/flomap-effects.rkt +++ b/collects/images/private/flomap-effects.rkt @@ -13,13 +13,13 @@ flomap-shadow flomap-shadowed flomap-whirl-morph) -(: colorize-alpha (flomap FlVector -> flomap)) +(: colorize-alpha (flomap (U (Vectorof Real) FlVector) -> flomap)) (define (colorize-alpha fm vs) (match-define (flomap _ 1 w h) fm) (flomap-append-components fm (fm* fm (make-flomap* w h vs)))) (: flomap-shadow (case-> (flomap Real -> flomap) - (flomap Real (Option FlVector) -> flomap))) + (flomap Real (Option (U (Vectorof Real) FlVector)) -> flomap))) (define flomap-shadow (case-lambda [(fm σ) (flomap-shadow fm σ #f)] @@ -27,18 +27,18 @@ (match-define (flomap _ c w h) fm) (cond [(c . = . 0) fm] [else (define alpha-fm (flomap-ref-component fm 0)) - (define color-vs (if (flvector? color) color (make-flvector (- c 1) 0.0))) + (define color-vs (if color color (make-flvector (- c 1) 0.0))) (colorize-alpha (flomap-blur alpha-fm σ) color-vs)])])) (: flomap-shadowed (case-> (flomap Real -> flomap) - (flomap Real (Option FlVector) -> flomap))) + (flomap Real (Option (U (Vectorof Real) FlVector)) -> flomap))) (define flomap-shadowed (case-lambda [(fm σ) (flomap-shadowed fm σ #f)] [(fm σ c) (flomap-cc-superimpose (flomap-shadow fm σ c) fm)])) (: flomap-outline (case-> (flomap Real -> flomap) - (flomap Real (Option FlVector) -> flomap))) + (flomap Real (Option (U (Vectorof Real) FlVector)) -> flomap))) (define flomap-outline (case-lambda [(fm amt) (flomap-outline fm amt #f)] @@ -57,11 +57,11 @@ (define alpha-fm (flomap-ref-component fm 0)) (define new-alpha-fm (fmmax 0.0 (fmmin 1.0 (fm/ (fm- (flomap-blur alpha-fm σ) v-min) (- v-max v-min))))) - (define color-vs (if (flvector? color) color (make-flvector (- c 1) 0.0))) + (define color-vs (if color color (make-flvector (- c 1) 0.0))) (colorize-alpha new-alpha-fm color-vs))])) (: flomap-outlined (case-> (flomap Real -> flomap) - (flomap Real (Option FlVector) -> flomap))) + (flomap Real (Option (U (Vectorof Real) FlVector)) -> flomap))) (define flomap-outlined (case-lambda [(fm amt) (flomap-outlined fm amt #f)] diff --git a/collects/images/private/flomap-resize.rkt b/collects/images/private/flomap-resize.rkt index 6ca3cd411c..37be888cd8 100644 --- a/collects/images/private/flomap-resize.rkt +++ b/collects/images/private/flomap-resize.rkt @@ -164,9 +164,9 @@ [else (let ([height (abs height)]) (flomap-scale*-y fm (abs (exact->inexact (/ height h))) height))])) -;; standard deviation of an unscaled box filter (i.e. f([-1/2,1/2]) = {1}, zero elsewhere) +;; variance of an unscaled box filter (i.e. f([-1/2,1/2]) = {1}, zero elsewhere) (define box-filter-variance (/ 1.0 12.0)) -;; standard deviation of an unscaled triangle filter (simulates effect of linear interpolation) +;; variance of an unscaled triangle filter (simulates effect of linear interpolation) (define triangle-filter-variance (/ 1.0 24.0)) ;; calculates the standard deviation of downscaling blur, assuming linear interpolation will be @@ -175,7 +175,7 @@ (define (stddev-for-scale scale) (define var (- (/ box-filter-variance (sqr scale)) triangle-filter-variance)) - (abs (flsqrt (max 0.0 var)))) + (flsqrt (max 0.0 var))) (: flomap-scale*-x (flomap Flonum Exact-Nonnegative-Integer -> flomap)) (define (flomap-scale*-x fm scale width) @@ -196,17 +196,18 @@ (: flomap-scale*-x/linear (flomap Flonum Exact-Nonnegative-Integer -> flomap)) (define (flomap-scale*-x/linear fm s new-w) (match-define (flomap vs c w h) fm) - (define w-1 (fx- w 1)) + (define w-1 (unsafe-fx+ w -1)) (inline-build-flomap c new-w h (λ (k new-x y _i) (define scaled-x (- (/ (+ (fx->fl new-x) 0.5) s) 0.5)) (define floor-scaled-x (floor scaled-x)) (define x0 (fl->fx floor-scaled-x)) - (cond [(or (x0 . fx< . 0) (x0 . fx>= . w)) 0.0] + (cond [(or (x0 . fx< . -1) (x0 . fx>= . w)) 0.0] [else (define i0 (coords->index c w k x0 y)) - (define v0 (flvector-ref vs i0)) + (define v0 (cond [(x0 . fx= . -1) 0.0] + [else (flvector-ref vs i0)])) (define v1 (cond [(x0 . fx= . w-1) 0.0] [else (flvector-ref vs (unsafe-fx+ i0 c))])) (fl-convex-combination v0 v1 (- scaled-x floor-scaled-x))])))) @@ -214,7 +215,7 @@ (: flomap-scale*-y/linear (flomap Flonum Exact-Nonnegative-Integer -> flomap)) (define (flomap-scale*-y/linear fm s new-h) (match-define (flomap vs c w h) fm) - (define h-1 (fx- h 1)) + (define h-1 (unsafe-fx+ h -1)) (define cw (* c w)) (inline-build-flomap c w new-h @@ -222,10 +223,11 @@ (define scaled-y (- (/ (+ (fx->fl new-y) 0.5) s) 0.5)) (define floor-scaled-y (floor scaled-y)) (define y0 (fl->fx floor-scaled-y)) - (cond [(or (y0 . fx< . 0) (y0 . fx>= . h)) 0.0] + (cond [(or (y0 . fx< . -1) (y0 . fx>= . h)) 0.0] [else (define i0 (coords->index c w k x y0)) - (define v0 (flvector-ref vs i0)) + (define v0 (cond [(y0 . fx= . -1) 0.0] + [else (flvector-ref vs i0)])) (define v1 (cond [(y0 . fx= . h-1) 0.0] [else (flvector-ref vs (unsafe-fx+ i0 cw))])) (fl-convex-combination v0 v1 (- scaled-y floor-scaled-y))])))) diff --git a/collects/images/private/flomap-struct.rkt b/collects/images/private/flomap-struct.rkt index 71161c213e..2219ad60e0 100644 --- a/collects/images/private/flomap-struct.rkt +++ b/collects/images/private/flomap-struct.rkt @@ -9,9 +9,12 @@ (provide flomap flomap? flomap-values flomap-components flomap-width flomap-height ;; Accessors - flomap-size unsafe-flomap-ref flomap-ref flomap-bilinear-ref coords->index + flomap-size coords->index + unsafe-flomap-ref flomap-ref flomap-bilinear-ref + unsafe-flomap-ref* flomap-ref* flomap-bilinear-ref* ;; Basic constructors - make-flomap make-flomap* build-flomap inline-build-flomap + make-flomap build-flomap inline-build-flomap + make-flomap* build-flomap* inline-build-flomap* flomap-ref-component flomap-take-components flomap-drop-components flomap-append-components) (struct: flomap ([values : FlVector] [components : Integer] [width : Integer] [height : Integer]) @@ -43,6 +46,19 @@ (unsafe-flvector-ref vs (coords->index c w k x y))] [else 0.0])) + (: unsafe-flomap-ref* (FlVector Integer Integer Integer Integer Integer -> FlVector)) + (define (unsafe-flomap-ref* vs c w h x y) + (cond [(and (x . fx>= . 0) (x . fx< . w) + (y . fx>= . 0) (y . fx< . h)) + (define i (coords->index c w 0 x y)) + (define point-vs (make-flvector c)) + (let: loop : Void ([k : Nonnegative-Fixnum 0]) + (when (k . < . c) + (unsafe-flvector-set! point-vs k (unsafe-flvector-ref vs (unsafe-fx+ i k))) + (loop (unsafe-fx+ k 1)))) + point-vs] + [else (make-flvector c 0.0)])) + (: flomap-ref (flomap Integer Integer Integer -> Flonum)) (define (flomap-ref fm k x y) (match-define (flomap vs c w h) fm) @@ -50,6 +66,11 @@ (raise-type-error 'flomap-ref (format "nonnegative fixnum < ~e" c) k)) (unsafe-flomap-ref vs c w h k x y)) + (: flomap-ref* (flomap Integer Integer -> FlVector)) + (define (flomap-ref* fm x y) + (match-define (flomap vs c w h) fm) + (unsafe-flomap-ref* vs c w h x y)) + ) ; begin-encourage-inline (: flomap-bilinear-ref (flomap Integer Real Real -> Flonum)) @@ -58,14 +79,14 @@ (cond [(and (k . >= . 0) (k . < . c)) (let ([x (- (exact->inexact x) 0.5)] [y (- (exact->inexact y) 0.5)]) - (cond [(and (x . > . -0.5) (x . < . (+ 0.5 (->fl w))) - (y . > . -0.5) (y . < . (+ 0.5 (->fl h)))) + (cond [(and (x . > . -1.0) (x . < . (->fl w)) + (y . > . -1.0) (y . < . (->fl h))) (define floor-x (floor x)) (define floor-y (floor y)) (define x0 (fl->fx floor-x)) (define y0 (fl->fx floor-y)) - (define x1 (fx+ x0 1)) - (define y1 (fx+ y0 1)) + (define x1 (unsafe-fx+ x0 1)) + (define y1 (unsafe-fx+ y0 1)) (define v00 (unsafe-flomap-ref vs c w h k x0 y0)) (define v10 (unsafe-flomap-ref vs c w h k x1 y0)) (define v01 (unsafe-flomap-ref vs c w h k x0 y1)) @@ -78,6 +99,40 @@ [else (raise-type-error 'flomap-bilinear-ref (format "nonnegative fixnum < ~e" c) 1 fm k x y)])) +(: flomap-bilinear-ref* (flomap Real Real -> FlVector)) +(define (flomap-bilinear-ref* fm x y) + (match-define (flomap vs c w h) fm) + (let ([x (- (exact->inexact x) 0.5)] + [y (- (exact->inexact y) 0.5)]) + (cond [(and (x . > . -1.0) (x . < . (->fl w)) + (y . > . -1.0) (y . < . (->fl h))) + (define floor-x (floor x)) + (define floor-y (floor y)) + (define x0 (fl->fx floor-x)) + (define y0 (fl->fx floor-y)) + (define x1 (unsafe-fx+ x0 1)) + (define y1 (unsafe-fx+ y0 1)) + (define vs00 (unsafe-flomap-ref* vs c w h x0 y0)) + (define vs10 (unsafe-flomap-ref* vs c w h x1 y0)) + (define vs01 (unsafe-flomap-ref* vs c w h x0 y1)) + (define vs11 (unsafe-flomap-ref* vs c w h x1 y1)) + (define xα (- x floor-x)) + (define yα (- y floor-y)) + (define point-vs (make-flvector c)) + (let: loop : FlVector ([k : Nonnegative-Fixnum 0]) + (cond [(k . < . c) + (define v00 (unsafe-flvector-ref vs00 k)) + (define v10 (unsafe-flvector-ref vs10 k)) + (define v01 (unsafe-flvector-ref vs01 k)) + (define v11 (unsafe-flvector-ref vs11 k)) + (define v (fl-convex-combination (fl-convex-combination v00 v10 xα) + (fl-convex-combination v01 v11 xα) + yα)) + (unsafe-flvector-set! point-vs k v) + (loop (unsafe-fx+ k 1))] + [else point-vs]))] + [else (make-flvector c 0.0)]))) + ;; =================================================================================================== ;; Construction and conversion @@ -98,7 +153,8 @@ [w : Integer width] [h : Integer height]) (with-asserts ([c nonnegative-fixnum?] [w nonnegative-fixnum?] [h nonnegative-fixnum?]) - (define vs (make-flvector (* c w h))) + (define fm (make-flomap c w h)) + (define vs (flomap-values fm)) (let: y-loop : flomap ([y : Nonnegative-Fixnum 0] [i : Nonnegative-Fixnum 0]) (cond [(y . fx< . h) @@ -111,19 +167,63 @@ (k-loop (unsafe-fx+ k 1) (unsafe-fx+ i 1))] [else (x-loop (unsafe-fx+ x 1) i)]))] [else (y-loop (unsafe-fx+ y 1) i)]))] - [else (flomap vs c w h)]))))) + [else fm]))))) (: build-flomap (Integer Integer Integer - (Nonnegative-Fixnum Nonnegative-Fixnum Nonnegative-Fixnum - Nonnegative-Fixnum -> Real) + (Nonnegative-Fixnum Nonnegative-Fixnum Nonnegative-Fixnum -> Real) -> flomap)) -(define (build-flomap components width height fun) - (inline-build-flomap components width height (λ (k x y i) (exact->inexact (fun k x y i))))) +(define (build-flomap c w h f) + (inline-build-flomap c w h (λ (k x y i) (exact->inexact (f k x y))))) -(: make-flomap* (Integer Integer FlVector -> flomap)) +#; +(: inline-build-flomap* (Integer Integer Integer + (Nonnegative-Fixnum Nonnegative-Fixnum Nonnegative-Fixnum + -> FlVector) + -> flomap)) +(define-syntax-rule (inline-build-flomap* components width height f) + (let: ([c : Integer components] + [w : Integer width] + [h : Integer height]) + (with-asserts ([c nonnegative-fixnum?] [w nonnegative-fixnum?] [h nonnegative-fixnum?]) + (define fm (make-flomap c w h)) + (define vs (flomap-values fm)) + (let: y-loop : flomap ([y : Nonnegative-Fixnum 0] [i : Nonnegative-Fixnum 0]) + (cond + [(y . fx< . h) + (let: x-loop : flomap ([x : Nonnegative-Fixnum 0] [i : Nonnegative-Fixnum i]) + (cond + [(x . fx< . w) + (define: point-vs : FlVector (f x y i)) + (cond + [(fx= (flvector-length point-vs) c) + (let: k-loop : flomap ([k : Nonnegative-Fixnum 0] [i : Nonnegative-Fixnum i]) + (cond + [(k . fx< . c) (unsafe-flvector-set! vs i (unsafe-flvector-ref point-vs k)) + (k-loop (unsafe-fx+ k 1) (unsafe-fx+ i 1))] + [else (x-loop (unsafe-fx+ x 1) i)]))] + [else (raise-type-error 'inline-build-flomap* (format "length-~e FlVector" c) + point-vs)])] + [else (y-loop (unsafe-fx+ y 1) i)]))] + [else fm]))))) + +(: make-flomap* (Integer Integer (U (Vectorof Real) FlVector) -> flomap)) (define (make-flomap* w h vs) - (define c (flvector-length vs)) - (inline-build-flomap c w h (λ (k _x _y _i) (unsafe-flvector-ref vs k)))) + (let ([vs (->flvector vs)]) + (define c (flvector-length vs)) + (inline-build-flomap* c w h (λ (_x _y _i) vs)))) + +(: build-flomap* (Integer Integer Integer + (Nonnegative-Fixnum Nonnegative-Fixnum -> (U (Vectorof Real) FlVector)) + -> flomap)) +(define (build-flomap* c w h f) + (inline-build-flomap* + c w h + (λ (x y i) + (define point-vs0 (f x y)) + (define point-vs1 (->flvector point-vs0)) + (cond [(fx= (flvector-length point-vs1) c) point-vs1] + [else (raise-type-error 'build-flomap* (format "length-~e Vector or FlVector" c) + point-vs0)])))) (: flomap-ref-component (flomap Integer -> flomap)) (define (flomap-ref-component fm k) @@ -155,8 +255,7 @@ (match-define (flomap vs1 d1 w1 h1) fm1) (match-define (flomap vs2 d2 w2 h2) fm2) (unless (and (= w1 w2) (= h1 h2)) - (error 'flomap-append-components - "expected flomaps with equal dimension; given dimensions ~e×~e and ~e×~e" + (error 'flomap-append-components "expected same-size flomaps; given sizes ~e×~e and ~e×~e" w1 h1 w2 h2)) (inline-build-flomap (fx+ d1 d2) w1 h1 (λ (k x y _i) diff --git a/collects/images/private/flonum.rkt b/collects/images/private/flonum.rkt index e9d6288609..afe54a88dd 100644 --- a/collects/images/private/flonum.rkt +++ b/collects/images/private/flonum.rkt @@ -6,7 +6,10 @@ [flvector-set! old:flvector-set!]) (except-in racket/fixnum fl->fx fx->fl) ; these two functions are untyped racket/math - (only-in racket/unsafe/ops unsafe-flvector-set! unsafe-fx+) + (only-in racket/unsafe/ops + unsafe-flvector-set! unsafe-flvector-ref + unsafe-vector-set! unsafe-vector-ref + unsafe-fx+) racket/performance-hint) (provide (all-defined-out) @@ -35,8 +38,31 @@ (loop (unsafe-fx+ i 1))] [else vs]))))) +(: flvector->vector (FlVector -> (Vectorof Flonum))) +(define (flvector->vector vs) + (define n (flvector-length vs)) + (define new-vs (make-vector n 0.0)) + (let: loop : (Vectorof Flonum) ([k : Nonnegative-Fixnum 0]) + (cond [(k . < . n) (unsafe-vector-set! new-vs k (unsafe-flvector-ref vs k)) + (loop (unsafe-fx+ k 1))] + [else new-vs]))) + +(: real-vector->flvector ((Vectorof Real) -> FlVector)) +(define (real-vector->flvector vs) + (define n (vector-length vs)) + (define new-vs (make-flvector n 0.0)) + (let: loop : FlVector ([k : Nonnegative-Fixnum 0]) + (cond [(k . < . n) (unsafe-flvector-set! new-vs k (exact->inexact (unsafe-vector-ref vs k))) + (loop (unsafe-fx+ k 1))] + [else new-vs]))) + (begin-encourage-inline + (: ->flvector ((U (Vectorof Real) FlVector) -> FlVector)) + (define (->flvector vs) + (cond [(flvector? vs) vs] + [else (real-vector->flvector vs)])) + (: fx->fl (Fixnum -> Flonum)) (define fx->fl ->fl) diff --git a/collects/images/scribblings/flomap.scrbl b/collects/images/scribblings/flomap.scrbl index ad0c06f34d..8e8cc19530 100644 --- a/collects/images/scribblings/flomap.scrbl +++ b/collects/images/scribblings/flomap.scrbl @@ -29,19 +29,19 @@ The following flomap @racket[fm] is used in various examples: @interaction[#:eval flomap-eval (define fm (draw-flomap - (λ (bm-dc) - (send bm-dc set-alpha 0) - (send bm-dc set-background "black") - (send bm-dc clear) - (send bm-dc set-alpha 1/3) - (send bm-dc translate 2 2) - (send bm-dc set-pen "black" 4 'long-dash) - (send bm-dc set-brush "red" 'solid) - (send bm-dc draw-ellipse 0 0 192 192) - (send bm-dc set-brush "green" 'solid) - (send bm-dc draw-ellipse 64 0 192 192) - (send bm-dc set-brush "blue" 'solid) - (send bm-dc draw-ellipse 32 44 192 192)) + (λ (fm-dc) + (send fm-dc set-alpha 0) + (send fm-dc set-background "black") + (send fm-dc clear) + (send fm-dc set-alpha 1/3) + (send fm-dc translate 2 2) + (send fm-dc set-pen "black" 4 'long-dash) + (send fm-dc set-brush "red" 'solid) + (send fm-dc draw-ellipse 0 0 192 192) + (send fm-dc set-brush "green" 'solid) + (send fm-dc draw-ellipse 64 0 192 192) + (send fm-dc set-brush "blue" 'solid) + (send fm-dc draw-ellipse 32 44 192 192)) 260 240)) (flomap->bitmap fm)] It is typical to use @racket[flomap->bitmap] to visualize a flomap at the REPL. @@ -68,7 +68,7 @@ There are three main reasons to use flomaps: @item{@bold{Range.} A floating-point value can also represent about 4.6 quintillion distinct intensities above saturation (@racket[1.0]). If distinguishing oversaturated values is important, flomaps have the range for it. - Further, floating-point images are (approximately) closed under pointwise arithmetic. + Further, floating-point images are closed under pointwise arithmetic (up to floating-point error). } @item{@bold{Speed.} The @racketmodname[images/flomap] module benefits greatly from Typed Racket's type-directed optimizations. @@ -76,7 +76,7 @@ There are three main reasons to use flomaps: } ) For these reasons, other parts of the @racket[images] library use flomaps internally, to represent and operate on -ARGB and RGB images, light maps, shadow maps, height maps, and normal maps. +RGB and ARGB images, light maps, shadow maps, height maps, and normal maps. @subsection[#:tag "flomap:conceptual"]{Conceptual Model} @@ -88,23 +88,24 @@ The following example creates a 10×10 bitmap with RGB components, and indexes i It also attempts to index component @racket[3], which doesn't exist. Note that @racket[flomap-ref] accepts its coordinate arguments in a standard order: @racket[k] @racket[x] @racket[y] (with @racket[k] for @bold{k}omponent). @interaction[#:eval flomap-eval - (define magenta-fm (make-flomap* 10 10 (flvector 1.0 0.0 1.0))) + (define magenta-fm (make-flomap* 10 10 #(0.5 0.0 1.0))) (flomap->bitmap magenta-fm) - (flomap-ref magenta-fm 0 0 0) - (flomap-ref magenta-fm 0 -1 0) - (flomap-ref magenta-fm 0 0 1000) + (flomap-ref* magenta-fm 0 0) + (flomap-ref* magenta-fm -1 0) + (flomap-ref* magenta-fm 0 1000) (flomap-ref magenta-fm 3 0 0)] Many flomap functions, such as @racket[flomap-bilinear-ref], treat their arguments as if every @italic{real} @racket[x] @racket[y] coordinate has values. In all such cases, known nonzero values are at half-integer coordinates and others are interpolated. @examples[#:eval flomap-eval - (flomap-bilinear-ref magenta-fm 0 0.5 0.5) - (flomap-bilinear-ref magenta-fm 0 0.25 0.25) - (flomap-bilinear-ref magenta-fm 0 0.0 0.0)] + (flomap-bilinear-ref* magenta-fm 0.5 0.5) + (flomap-bilinear-ref* magenta-fm 0.25 0.25) + (flomap-bilinear-ref* magenta-fm 0.0 0.0) + (flomap-bilinear-ref* magenta-fm -0.25 -0.25)] This conceptual model allows us to treat flomaps as if they were multi-valued functions on @racket[Real]×@racket[Real]. -For example, we might plot the red component of an icon: +For example, we might plot the red component of an ARGB icon: @interaction[#:eval flomap-eval (require images/icons/misc plot) (define icon-fm (bomb-flomap "azure" "orange" 48)) @@ -112,7 +113,7 @@ For example, we might plot the red component of an icon: (define-values (icon-width icon-height) (flomap-size icon-fm)) (plot3d-bitmap (contour-intervals3d (λ (x y) (flomap-bilinear-ref icon-fm 1 x y)) - 0 icon-width 0 icon-height))] + -0.5 (+ 0.5 icon-width) -0.5 (+ 0.5 icon-height)))] Notice that the plot's maximum height is above saturation (@racket[1.0]). The tallest peak corresponds to the specular highlight (the shiny part) on the bomb. Specular highlights are one case where it is important to operate on oversaturated values without truncating them---until it is time to display the image. @@ -143,10 +144,10 @@ As an example of the last point, consider blur. The following example creates an alpha-multiplied flomap using @racket[draw-flomap]. It blurs the flomap using a general-purpose (i.e. non-alpha-aware) blur function, then converts the flomap to non-alpha-multiplied and does the same. @interaction[#:eval flomap-eval - (define circle-fm (draw-flomap (λ (dc) - (send dc set-pen "black" 1 'transparent) - (send dc set-brush "green" 'solid) - (send dc draw-ellipse 10 10 30 30)) + (define circle-fm (draw-flomap (λ (fm-dc) + (send fm-dc set-pen "black" 1 'transparent) + (send fm-dc set-brush "green" 'solid) + (send fm-dc draw-ellipse 10 10 30 30)) 50 50)) (flomap->bitmap circle-fm) (flomap->bitmap (flomap-blur circle-fm 4 4)) @@ -239,10 +240,17 @@ Returns the width and height of @racket[fm] as nonnegative fixnums. @defproc[(flomap-ref [fm flomap] [k Integer] [x Integer] [y Integer]) Float]{ Returns @racket[fm]'s value at @racket[k] @racket[x] @racket[y]. - + If @racket[x] or @racket[y] is out of bounds, this function returns @racket[0.0]. If @racket[k] is out of bounds, it raises an error. -See @secref{flomap:conceptual} to read about why. +The @secref{flomap:conceptual} section explains why @racket[k] is treated differently. +} + +@defproc[(flomap-ref* [fm flomap] [x Integer] [y Integer]) FlVector]{ +Returns @racket[fm]'s component values at @racket[x] @racket[y] as an flvector. + +If @racket[x] or @racket[y] is out of bounds, this function returns an flvector filled with @racket[0.0]. +It always returns an flvector of length @racket[(flomap-components fm)]. } @defproc[(flomap-bilinear-ref [fm flomap] [k Integer] [x Real] [y Real]) Float]{ @@ -252,9 +260,14 @@ Like all other @racket[flomap] functions that operate on real-valued coordinates Mathematically, if @racket[x] = @racket[(+ i 0.5)] and @racket[y] = @racket[(+ j 0.5)] for any integers @racket[i] and @racket[j], then @racket[(flomap-bilinear-ref fm k x y)] = @racket[(flomap-ref fm k i j)]. -If @racket[x] or @racket[y] is out of bounds, this function returns @racket[0.0]. +Suppose @racket[fm] is size @racket[w]×@racket[h]. +If @racket[x] ≤ @racket[-0.5] or @racket[x] ≥ @racket[(+ w 0.5)], this function returns @racket[0.0]; similarly for @racket[y] and @racket[h]. If @racket[k] is out of bounds, it raises an error. -See @secref{flomap:conceptual} to read about why. +The @secref{flomap:conceptual} section explains why @racket[k] is treated differently. +} + +@defproc[(flomap-bilinear-ref* [fm flomap] [x Real] [y Real]) FlVector]{ +Like @racket[flomap-bilinear-ref], but returns an flvector containing estimates of all the components at @racket[x] @racket[y]. } @defproc[(flomap-min-value [fm flomap]) Float] @@ -291,6 +304,12 @@ This function is used by some library functions, such as @racket[flomap-bilinear From untyped code, applying this function is likely no faster than applying @racket[flomap-ref], because of extra contract checks. } +@defproc[(unsafe-flomap-ref* [vs FlVector] + [c Integer] [w Integer] [h Integer] + [x Integer] [y Integer]) FlVector]{ +Like @racket[unsafe-flomap-ref], but returns an flvector containing all the component values at @racket[x] @racket[y]. +} + @; =================================================================================================== @@ -316,6 +335,8 @@ See @secref{flomap:opacity} for a discussion of opacity (alpha) representation. A zero-size @racket[fm] is padded by one point in any zero direction before conversion. For example, if @racket[fm] is size 0×1, the result of @racket[(flomap->bitmap fm)] is size 1×1. + +Values are clamped to between @racket[0.0] and @racket[1.0] before conversion. } @defproc[(bitmap->flomap [bm Any]) flomap]{ @@ -327,52 +348,68 @@ The argument type is imprecise because Typed Racket does not support the object @defproc[(make-flomap [c Integer] [w Integer] [h Integer] [v Real 0.0]) flomap]{ Returns a @racket[w]×@racket[h] flomap with @racket[c] components, with every value initialized to @racket[v]. +Analogous to @racket[make-vector]. To create flomaps filled with a solid color, use @racket[make-flomap*]. } -@defproc[(make-flomap* [w Integer] [h Integer] [vs FlVector]) flomap]{ -Returns a @racket[w]×@racket[h] flomap with @racket[(flvector-length vs)] color components, with each known point initialized using the values in @racket[vs]. +@defproc[(make-flomap* [w Integer] [h Integer] [vs (U (Vectorof Real) FlVector)]) flomap]{ +Returns a @racket[w]×@racket[h] flomap with each point's components initialized using the values in @racket[vs]. +Analogous to @racket[make-vector]. -The following two examples create magenta bitmaps with an alpha channel: +The following two examples create an RGB and an ARGB flomap: @interaction[#:eval flomap-eval - (flomap->bitmap (make-flomap* 100 100 (flvector 1.0 1.0 0.0 1.0))) - (flomap->bitmap (make-flomap* 100 100 (flvector 0.5 0.5 0.0 0.5)))] + (flomap->bitmap (make-flomap* 100 100 #(0.5 0.0 1.0))) + (flomap->bitmap (make-flomap* 100 100 #(0.5 0.25 0.0 0.5)))] See @secref{flomap:opacity} for a discussion of opacity (alpha) representation. } @defproc[(build-flomap [c Integer] [w Integer] [h Integer] - [f (Nonnegative-Fixnum Nonnegative-Fixnum Nonnegative-Fixnum - Nonnegative-Fixnum -> Real)]) flomap]{ + [f (Nonnegative-Fixnum Nonnegative-Fixnum Nonnegative-Fixnum -> Real)]) flomap]{ Returns a @racket[w]×@racket[h] flomap with @racket[c] color components, with values defined by @racket[f]. +Analogous to @racket[build-vector]. + +The function @racket[f] receives three arguments @racket[k] @racket[x] @racket[y]: the color component and two positional coordinates. -The function @racket[f] receives four arguments @racket[k] @racket[x] @racket[y] @racket[i]: the color component, two positional coordinates, and a precalculated index into the flomap's @racketid[values] vector. @examples[#:eval flomap-eval (flomap->bitmap (build-flomap 1 100 100 - (λ (k x y i) (/ (+ x y) 200)))) + (λ (k x y) (/ (+ x y) 200)))) (define sine-fm (build-flomap 1 100 100 - (λ (k x y i) + (λ (k x y) (* 1/2 (+ 1 (sin (sqrt (+ (sqr (- x 50)) (sqr (- y 50)))))))))) (flomap->bitmap sine-fm)] + +To build a flomap from a function that returns vectors, see @racket[build-flomap*]. } -@defform[(inline-build-flomap c w h f)]{ -A macro version of @racket[build-flomap]. -The function or macro @racket[f] must return a @racket[Float], not a @racket[Real] as the @racket[f] argument to @racket[build-flomap] can. +@defproc[(build-flomap* [c Integer] [w Integer] [h Integer] + [f (Nonnegative-Fixnum Nonnegative-Fixnum -> (U (Vectorof Real) FlVector))]) flomap]{ +Returns a @racket[w]×@racket[h] flomap with @racket[c] color components. +Its values are defined by @racket[f], which returns vectors of point components. +The vectors returned by @racket[f] must be length @racket[c]. -Using @racket[inline-build-flomap] instead of @racket[build-flomap] often ensures that @racket[f] is inlined, and therefore floats remain unboxed. -Many library functions use @racket[inline-build-flomap] internally for speed, notably @racket[fm+] and the other pointwise arithmetic operators. +Analogous to @racket[build-vector]. -@bold{This is not available in untyped Racket.} +@examples[#:eval flomap-eval + (flomap->bitmap + (build-flomap* 4 100 100 + (λ (x y) + (vector (/ (+ x y) 200) + (/ (+ (- 100 x) y) 200) + (/ (+ (- 100 x) (- 100 y)) 200) + (/ (+ x (- 100 y)) 200))))) + + (build-flomap* 4 100 100 + (λ (x y) (vector (/ (+ x y) 200))))] } @defproc[(draw-flomap [draw (Any -> Any)] [w Integer] [h Integer]) flomap]{ Returns a @racket[w]×@racket[h] bitmap drawn by @racket[draw]. -Think of it as the flomap version of @racketmodname[slideshow]'s @racket[dc]. +Analogous to @racketmodname[slideshow]'s @racket[dc]. The @racket[draw] function should accept a @racket[dc<%>] instance and use its drawing methods to draw on an underlying bitmap. The bitmap is converted to a flomap using @racket[bitmap->flomap] and returned. @@ -395,6 +432,41 @@ You should not generally have to use these functions, because @racket[bitmap->fl See @secref{flomap:opacity} for a discussion of opacity (alpha) representation. } +@defform[(inline-build-flomap c w h f) + #:contracts ([c Integer] + [w Integer] + [h Integer] + [f (Nonnegative-Fixnum Nonnegative-Fixnum Nonnegative-Fixnum + Nonnegative-Fixnum -> Float)])]{ +A macro version of @racket[build-flomap]. + +There are three differences between the function @racket[f] passed to @racket[build-flomap] and the @racket[f] passed to @racket[inline-build-flomap]. +First, the @racket[f] passed to @racket[inline-build-flomap] can be a macro. +Second, it receives arguments @racket[k] @racket[x] @racket[y] @racket[i], where @racket[i] is a precalculated index into the result's @racketid[values]. +Third, it must return a @racket[Float]. + +Using @racket[inline-build-flomap] instead of @racket[build-flomap] often ensures that @racket[f] is inlined, and therefore floats remain unboxed. +Many library functions use @racket[inline-build-flomap] internally for speed, notably @racket[fm+] and the other pointwise arithmetic operators. + +@bold{This is not available in untyped Racket.} +} + +@defform[(inline-build-flomap* c w h f) + #:contracts ([c Integer] + [w Integer] + [h Integer] + [f (Nonnegative-Fixnum Nonnegative-Fixnum + Nonnegative-Fixnum -> FlVector)])]{ +A macro version of @racket[build-flomap*]. + +There are three differences between the function @racket[f] passed to @racket[build-flomap*] and the @racket[f] passed to @racket[inline-build-flomap*]. +First, the @racket[f] passed to @racket[inline-build-flomap*] can be a macro. +Second, it receives arguments @racket[x] @racket[y] @racket[i], where @racket[i] is a precalculated index into the result's @racketid[values]. +Third, it must return a @racket[FlVector]. + +@bold{This is not available in untyped Racket.} +} + @; =================================================================================================== @@ -466,23 +538,14 @@ For example, to estimate the local gradient magnitude at each point in a flomap: Lifts a unary floating-point function to operate pointwise on flomaps. } -@defform[(inline-flomap-lift f)]{ -A macro version of @racket[flomap-lift]. -The function or macro @racket[f] must return a @racket[Float], not a @racket[Real] as the @racket[f] argument to @racket[flomap-lift] can. - -Using @racket[inline-flomap-lift] instead of @racket[flomap-lift] often ensures that @racket[f] is inlined, and therefore floats remain unboxed. - -@bold{This is not available in untyped Racket.} -} - @defproc[(flomap-normalize [fm flomap]) flomap]{ Returns a flomap like @racket[fm], but with values linearly rescaled to be between @racket[0.0] and @racket[1.0] inclusive. @examples[#:eval flomap-eval (define gray-fm - (build-flomap 1 100 100 (λ (k x y i) (+ 0.375 (/ (+ x y) 800))))) + (build-flomap 1 100 100 (λ (k x y) (+ 0.375 (/ (+ x y) 800))))) (flomap->bitmap gray-fm) (flomap->bitmap (flomap-normalize gray-fm))] -Besides increasing contrast, you could use this function to debug a rendering pipeline that produces overbright intermediate flomaps. +Besides increasing contrast, you could use this function to visualize oversaturated flomaps, or visualize flomaps that don't correspond directly to displayed images, such as height maps and normal maps. } @defproc[(fm+ [fm1 (U Real flomap)] [fm2 (U Real flomap)]) flomap] @@ -504,7 +567,7 @@ Binary operations accept the following argument combinations, in either order: Any other argument combination will raise a type error. @examples[#:eval flomap-eval - (define fm1 (build-flomap 1 260 240 (λ (k x y i) (/ (+ x y) 500)))) + (define fm1 (build-flomap 1 260 240 (λ (k x y) (/ (+ x y) 500)))) (define fm2 (fm- 1.0 fm1)) (flomap->bitmap fm1) (flomap->bitmap fm2) @@ -526,7 +589,16 @@ Because @racket[fm] is an alpha-multiplied flomap (see @secref{flomap:opacity}), Lifts a binary floating-point function to operate pointwise on flomaps, allowing the same argument combinations as @racket[fm+] and others. } -@defform[(inline-flomap-lift2 f)]{ +@defform[(inline-flomap-lift f) #:contracts ([f (Float -> Float)])]{ +A macro version of @racket[flomap-lift]. +The function or macro @racket[f] must return a @racket[Float], not a @racket[Real] as the @racket[f] argument to @racket[flomap-lift] can. + +Using @racket[inline-flomap-lift] instead of @racket[flomap-lift] often ensures that @racket[f] is inlined, and therefore floats remain unboxed. + +@bold{This is not available in untyped Racket.} +} + +@defform[(inline-flomap-lift2 f) #:contracts ([f (Float Float -> Float)])]{ A macro version of @racket[flomap-lift2]. The function or macro @racket[f] must return a @racket[Float], not a @racket[Real] as the @racket[f] argument to @racket[flomap-lift2] can. @@ -550,10 +622,10 @@ These return, per-component, estimates of the local @italic{x}- and @italic{y}-d Equivalent to @racket[(values (flomap-gradient-x fm) (flomap-gradient-y fm))]. @examples[#:eval flomap-eval - (let-values ([(dx-fm dy-fm) (flomap-gradient - (flomap-drop-components fm 1))]) - (values (flomap->bitmap (fm* 0.5 (fm+ 1.0 dx-fm))) - (flomap->bitmap (fm* 0.5 (fm+ 1.0 dy-fm)))))] + (define-values (dx-fm dy-fm) + (flomap-gradient (flomap-drop-components fm 1))) + (values (flomap->bitmap (fm* 0.5 (fm+ 1.0 dx-fm))) + (flomap->bitmap (fm* 0.5 (fm+ 1.0 dy-fm))))] } @defproc[(flomap-gradient-normal [fm flomap]) flomap]{ @@ -561,7 +633,8 @@ Given a one-component flomap, returns a @racket[3]-component flomap containing e In other words, @racket[flomap-normal] converts height maps to normal maps. @examples[#:eval flomap-eval (flomap->bitmap sine-fm) - (flomap->bitmap (flomap-gradient-normal sine-fm))] + (flomap->bitmap (flomap-gradient-normal sine-fm)) + (flomap-gradient-normal fm)] } @@ -595,7 +668,7 @@ Like @racket[flomap-gaussian-blur-x], but per-column instead of per-row. @defproc[(flomap-box-blur [fm flomap] [x-radius Real] [y-radius Real x-radius]) flomap]{ Returns @racket[fm] convolved, per-component, with a box kernel with radii @racket[x-radius] and @racket[y-radius]. -The radii are of the largest ellipse that would fit in the box. +The radii are of the largest axis-aligned ellipse that would fit in the box. @examples[#:eval flomap-eval (flomap->bitmap (flomap-box-blur (flomap-inset fm 4) 4)) (flomap->bitmap (flomap-box-blur (flomap-inset fm 4 1) 4 1))] @@ -662,8 +735,8 @@ This function cannot return a larger flomap. @examples[#:eval flomap-eval (define small-circle-fm - (draw-flomap (λ (dc) - (send dc draw-ellipse 20 20 10 10)) + (draw-flomap (λ (fm-dc) + (send fm-dc draw-ellipse 20 20 10 10)) 100 100)) (flomap->bitmap small-circle-fm) (flomap->bitmap (flomap-trim small-circle-fm))] @@ -741,11 +814,11 @@ The result is expanded as necessary. @examples[#:eval flomap-eval (flomap-pin fm -10 -10 sine-fm) (define circle-fm - (draw-flomap (λ (the-dc) - (send the-dc set-pen "black" 4 'short-dash) - (send the-dc set-brush "yellow" 'solid) - (send the-dc set-alpha 1/2) - (send the-dc draw-ellipse 2 2 124 124)) + (draw-flomap (λ (fm-dc) + (send fm-dc set-pen "black" 4 'short-dash) + (send fm-dc set-brush "yellow" 'solid) + (send fm-dc set-alpha 1/2) + (send fm-dc draw-ellipse 2 2 124 124)) 128 128)) (flomap->bitmap (flomap-pin fm 0 0 circle-fm 64 64)) (flomap->bitmap (flomap-pin sine-fm 50 0 sine-fm))] @@ -795,9 +868,10 @@ See @racket[flomap-pin] and @racket[flomap-pin*] for implementation details. @defproc[(flomap-hc-append [fm0 flomap] [fm flomap] ...) flomap] @defproc[(flomap-hb-append [fm0 flomap] [fm flomap] ...) flomap]{ These create a new flomap by spatially appending the flomaps in the argument list. -The two-letter abbreviation determines direction (@racket[v] or @racket[h]) and alignment (@racket[l], @racket[c], @racket[r], or @racket[t], @racket[c], @racket[b]). +The two-letter abbreviation determines direction (@racketid[v] or @racketid[h]) and alignment (@racketid[l], @racketid[c], @racketid[r], or @racketid[t], @racketid[c], @racketid[b]). @examples[#:eval flomap-eval - (flomap->bitmap (flomap-ht-append circle-fm fm circle-fm))] + (flomap->bitmap (flomap-ht-append circle-fm fm + (flomap-scale circle-fm 1/2)))] See @racket[flomap-pin] and @racket[flomap-pin*] for implementation details. }