reactivity-0.2.3.0: src/FRP/Reactivity/Draw.hs
{-# LANGUAGE Unsafe, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, GeneralizedNewtypeDeriving, ImplicitParams, ForeignFunctionInterface #-}
-- | A Draw monad for drawing in bitmaps.
module FRP.Reactivity.Draw (defBackground, Draw(unDraw), pack, onNewBitmap, unsafeOnBitmap, newBitmap, onBitmap, askDims, getPixel, setPixel, askDims', getPixel', getRValue, getGValue, getBValue, rgb, blend, mask, Font, defFont, textOut, textDimensions, areaFill, useFilter, convolve, flipped, swap, correlate, blur, enTuple, unTuple, resample, horzGradient, vertGradient, laplacian, fillRect, function, function', drawBitmap, graffito, spline, ellipse) where
import Foreign.Ptr
import Foreign.Storable
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Marshal.Utils
import Data.ByteString (copy, index, replicate)
import qualified Data.ByteString as B
import Data.ByteString.Unsafe
import Data.Bits
import Data.Int
import Data.Word
import Data.Fixed
import Data.Array hiding (index)
import Control.Arrow
import Control.Monad.Reader
import Control.Monad.Trans
import Control.Applicative
import Control.Exception hiding (mask)
import Control.Monad
import Control.CUtils.Conc
import Control.Concurrent
import Codec.BMP
import System.IO.Unsafe
import System.Mem.Weak
import Graphics.Win32 hiding (ellipse, fillRect, textOut)
import Graphics.Win32Extras
import Prelude hiding (replicate)
defBackground = rgb 245 246 247
newtype Draw t = Draw { unDraw :: ReaderT ((Int32, Int32), Ptr CChar, Maybe (Chan (Int32, Int32, COLORREF))) IO t } deriving (Functor, Applicative, Monad)
bi bmp = case bmpBitmapInfo bmp of
InfoV3 b -> b
InfoV4 b -> dib4InfoV3 b
sizeOfFileHeader :: Int
sizeOfFileHeader = 14
-- | Magic number that should come at the start of a BMP file.
bmpMagic :: Word16
bmpMagic = 0x4d42
-- | Size of `BitmapInfoV3` header (in bytes)
sizeOfBitmapInfoV3 :: Int
sizeOfBitmapInfoV3 = 40
--
-- | Pack a 24-bit bitmap, but without error checking.
pack width height str = let
fileHeader
= FileHeader
{ fileHeaderType = bmpMagic
, fileHeaderFileSize = fromIntegral
$ sizeOfFileHeader + sizeOfBitmapInfoV3 + B.length str
, fileHeaderReserved1 = 0
, fileHeaderReserved2 = 0
, fileHeaderOffset = fromIntegral (sizeOfFileHeader + sizeOfBitmapInfoV3) }
bitmapInfoV3
= BitmapInfoV3
{ dib3Size = fromIntegral sizeOfBitmapInfoV3
, dib3Width = fromIntegral width
, dib3Height = fromIntegral height
, dib3HeightFlipped = False
, dib3Planes = 1
, dib3BitCount = 24
, dib3Compression = CompressionRGB
, dib3ImageSize = fromIntegral $ (3 * width + 3) `quot` 4 * 4 * height
, dib3PelsPerMeterX = 2834
, dib3PelsPerMeterY = 2834
, dib3ColorsUsed = 0
, dib3ColorsImportant = 0 }
in BMP
{ bmpFileHeader = fileHeader
, bmpBitmapInfo = InfoV3 bitmapInfoV3
, bmpRawImageData = str }
-- | Create a new bitmap and draw on it.
{-# NOINLINE onNewBitmap #-}
onNewBitmap :: (Int32, Int32) -> Draw t -> (BMP, t)
onNewBitmap (wd32, ht32) draw = unsafePerformIO $ do
let sz = fromIntegral $ nBytes wd32 ht32
p <- mallocBytes sz
let Draw st = fillRect defBackground (0, 0, wd32, ht32) >> draw
x <- runReaderT st ((wd32, ht32), p, Nothing)
ar <- unsafePackCStringLen (p, sz)
addFinalizer ar (free p)
return (pack wd ht ar, x) where
wd = fromIntegral wd32
ht = fromIntegral ht32
-- | Create a new blank bitmap.
{-# INLINE newBitmap #-}
newBitmap dim = fst $ onNewBitmap dim (return ())
-- | Make a copy of a bitmap and draw on it. This only works with 24-bit bitmaps
-- (but the Codec.BMP library provides conversion functions)
{-# NOINLINE onBitmap #-}
onBitmap bmp draw = unsafeOnBitmap (bmp { bmpRawImageData = copy $ bmpRawImageData bmp }) draw
-- | An unsafe version of 'onBitmap' that scribbles on the old bitmap, violating
-- referential transparency.
{-# NOINLINE unsafeOnBitmap #-}
unsafeOnBitmap :: BMP -> Draw t -> (BMP, t)
unsafeOnBitmap bmp draw = unsafePerformIO $ unsafeUseAsCString (bmpRawImageData bmp) $ \p -> do
let Draw st = draw
x <- runReaderT st ((wd32, ht32), p, Nothing)
return (pack wd ht (bmpRawImageData bmp), x) where
(wd32, ht32) = askDims' bmp
wd = fromIntegral wd32
ht = fromIntegral ht32
calculateByte wid x y = (3 * wid + 3) `quot` 4 * 4 * y + 3 * x
nBytes wd32 ht32 = calculateByte wd32 0 ht32
-- | Retrieve the bitmap's dimensions.
askDims = Draw $ liftM (\(pr, _, _) -> pr) ask
{-# INLINE unsafeGetPixel #-}
unsafeGetPixel (x, y) = Draw $ do
((wid, _), ar, _) <- ask
let byte = calculateByte wid x y
lift $ do
b <- peekByteOff ar (fromIntegral byte)
g <- peekByteOff ar (fromIntegral $ byte + 1)
r <- peekByteOff ar (fromIntegral $ byte + 2)
return (rgb r g b)
-- |
getPixel (x, y) = do
b <- inBounds (x, y)
unless b $ do
(wid, ht) <- askDims
Draw $ lift $ throwIO $ IndexOutOfBounds $ "FRP.Draw.getPixel: " ++ show (x, y) ++ " not in " ++ show wid ++ "x" ++ show ht ++ " bitmap"
unsafeGetPixel (x, y)
makeLPARAM x y = shiftL y 16 .|. x
{-# INLINE unsafeSetPixel #-}
unsafeSetPixel (x, y) clr = Draw $ do
((wid, _), ar, chn) <- ask
let byte = calculateByte wid x y
lift $ maybe
(do
pokeByteOff ar (fromIntegral byte) (getBValue clr)
pokeByteOff ar (fromIntegral $ byte + 1) (getGValue clr)
pokeByteOff ar (fromIntegral $ byte + 2) (getRValue clr))
(\chn -> writeChan chn (x, y, clr))
chn
-- |
setPixel pt clr = do
b <- inBounds pt
when b $ unsafeSetPixel pt clr
-- |
askDims' :: BMP -> (Int32, Int32)
askDims' bmp = (fromIntegral (dib3Width (bi bmp)), fromIntegral (dib3Height (bi bmp)))
-- |
getPixel' (x, y) bmp = rgb
(index dat (byte + 2))
(index dat (byte + 1))
(index dat byte) where
dat = bmpRawImageData bmp
wid = fst (askDims' bmp)
byte = fromIntegral $ calculateByte wid x y
-- | Blend a colour into a pixel with a certain intensity.
blend :: Double -> (Int32, Int32) -> COLORREF -> Draw ()
blend intensity pt clr = do
b <- inBounds pt
when b $ do
clr2 <- unsafeGetPixel pt
let formula f = round $ fromIntegral (f clr) * intensity + fromIntegral (f clr2) * (1 - intensity)
unsafeSetPixel pt $ rgb
(formula getRValue)
(formula getGValue)
(formula getBValue)
-- | Fill a region according to the given mask bitmap.
mask intensity (x, y) bmp = do
let (wid, ht) = askDims' bmp
let f = if intensity >= 1 then
\x2 y2 -> setPixel (x + x2, y + y2)
else
\x2 y2 -> blend intensity (x + x2, y + y2)
mapM_ (\y2 -> mapM_ (\x2 -> f x2 y2 (getPixel' (x2, y2) bmp)) [0..wid-1]) [0..ht-1]
foreign import stdcall unsafe "windows.h GetDIBits"
c_GetDIBits :: HDC -> HBITMAP -> INT -> INT -> LPVOID -> LPBITMAPINFO -> ColorFormat -> IO INT
type Font = (COLORREF, COLORREF, String, Int32, DWORD, Bool, Bool)
-- | A default font option
defFont :: Font
defFont = (defBackground, 0, "Tahoma", 14, fW_NORMAL, False, False)
-- | Draw some text at the point in the bitmap.
textOut :: String -> POINT -> Font -> Draw ()
textOut text pt (bgclr, fgclr, font, sz, weight, italic, underline) = void $ Draw $ ask >>= \tup -> lift $ do
-- This procedure draws the text in a DDB, grabs that DDB into a DIB, then masks it into the original image.
dc <- getDC Nothing
finally
(do
cdc <- createCompatibleDC (Just dc)
finally
(withRECT (0, 0, 32767, 32767) $ \p -> do
setBkColor cdc bgclr
setTextColor cdc fgclr
fhdl <- createFont (sz * 72 `quot` 96) 0 0 0 weight italic underline False dEFAULT_CHARSET 0 0 0 0 font
finally
(do
oldFont <- selectFont cdc fhdl
finally
(do
-- Measure text
withTStringLen text $ \(s, l) -> c_DrawText cdc s (fromIntegral l) p dT_CALCRECT
-- Create a DDB
rt@(_, _, x, y) <- peekRECT p
bmp <- createCompatibleBitmap dc x y
finally
(runReaderT
(unDraw $ mask 1 pt $ fst $ onNewBitmap (x, y) $ Draw $ ask >>= \(_, pbits, _) -> lift $ do
oldBmp <- selectBitmap cdc bmp
finally
(do
drawText cdc text rt 0
withBITMAP (bmpBitmapInfo (pack x y B.empty)) $ \p -> c_GetDIBits cdc bmp 0 y (castPtr pbits) p dIB_RGB_COLORS)
(selectBitmap cdc oldBmp))
tup)
(deleteBitmap bmp))
(selectFont cdc oldFont))
(deleteFont fhdl))
(deleteDC cdc))
(releaseDC Nothing dc)
-- | Get the dimensions of a piece of text in a given font. (Note: this may be slightly different
-- on different platforms).
textDimensions :: String -> Font -> POINT
textDimensions text (_, _, font, sz, weight, italic, underline) = unsafePerformIO $ do
dc <- getDC Nothing
finally
(do
fhdl <- createFont (sz * 72 `quot` 96) 0 0 0 weight italic underline False dEFAULT_CHARSET 0 0 0 0 font
finally
(do
oldFont <- selectFont dc fhdl
finally
(withRECT (0, 0, 32767, 32767) $ \p -> do
withTStringLen text $ \(s, l) -> c_DrawText dc s (fromIntegral l) p dT_CALCRECT
(_, _, x, y) <- peekRECT p
return (x, y))
(selectFont dc oldFont))
(deleteFont fhdl))
(releaseDC Nothing dc)
----------------------------------------
-- Area fill
inBounds (x, y) = do
(wid, ht) <- askDims
return (inRange (0, wid - 1) x && inRange (0, ht - 1) y)
areaFillImpl _ [] _ _ = return ()
areaFillImpl 0 _ _ _ = return ()
areaFillImpl n frontier clrFill clrMatch = do
mapM_ (\pt -> unsafeSetPixel pt clrFill) frontier
let choices = concat [ [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)] | (x, y) <- frontier ]
ok <- mapM (\pt -> do
b <- inBounds pt
if b then
liftM (==clrMatch) (getPixel pt)
else
return False) choices
areaFillImpl (n - 1) (map fst $ filter snd $ zip choices ok) clrFill clrMatch
-- | Do an area fill with the given colour.
areaFill clr pt@(x, y) = do
clrMatch <- getPixel pt
unsafeSetPixel pt clr
let choices = listArray (0, 3) [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)]
Draw $ do
d <- ask
let ?seq = True
lift $ conc_ (fmap (\choice -> runReaderT (unDraw $ areaFillImpl 10 [choice] clr clrMatch) d) choices)
----------------------------------------
-- Convolution
{-# INLINE useFilter #-}
useFilter filter shift im x y = let
(kw, kh) = askDims' filter
(wid, ht) = askDims' im
halfX = kw `quot` 2
halfY = kh `quot` 2
x1 = (x - halfX) `max` 0
y1 = (y - halfY) `max` 0
x2 = (x + halfX) `min` wid
y2 = (y + halfY) `min` ht
shiftBy = fromIntegral (if shift then 127 else 0)
sz = if shift then
fromIntegral $ (y2 - y1) * (x2 - x1) * 127
else
sum (map (\y -> sum $ map (\x -> enTuple (getPixel' (x, y) filter))
[0..x2-x1-1]) [0..y2-y1-1]) `max` 1 in
shiftBy + sum (map (\y -> sum $ map (\x -> (enTuple (getPixel' (x, y) filter) - shiftBy) * enTuple (getPixel' (x1 + x, y1 + y) im)) [0..x2-x1-1]) [0..y2-y1-1]) / sz
-- | Standard convolution (with cutoffs).
convolve :: BMP -> Bool -> BMP -> Draw ()
convolve filter shift bmp = function' (\x y -> unTuple $ useFilter filter shift bmp x y)
-- | Flip the current image corner to corner.
flipped = do
(wid, ht) <- askDims
mapM_ (\y -> mapM_ (\x -> unsafeSwap (x, y) (wid - 1 - x, ht - 1 - y)) [0..wid-1]) [0..ht`div`2-1]
unsafeSwap pt pt2 = do
clr <- unsafeGetPixel pt
clr2 <- unsafeGetPixel pt2
unsafeSetPixel pt clr2
unsafeSetPixel pt2 clr
-- | Swaps two pixels.
swap pt pt2 = do
clr <- getPixel pt
clr2 <- getPixel pt2
unsafeSetPixel pt clr2
unsafeSetPixel pt2 clr
-- | Standard correlation
correlate :: BMP -> Bool -> BMP -> Draw ()
correlate kernel = convolve (fst $ onBitmap kernel flipped)
intensity x = rgb n n n where
n = round $ 255 * x
-- | Gaussian blur filter.
blur :: Double -> BMP
blur sigma = fst $ onNewBitmap (2 * dist, 2 * dist) $ function' $ \x y -> intensity $ f (x - dist, y - dist) where
dist = round (3 * sigma)
f (x, y) = exp (-0.5 * (fromIntegral x ^ 2 + fromIntegral y ^ 2) / sigma ^ 2) / (2 * pi)
----------------------------------------
-- Bilinear resampling
instance (Num t, Num u, Num v) => Num (t, u, v) where
(n1, n2, n3) + (m1, m2, m3) = (n1 + m1, n2 + m2, n3 + m3)
negate (n1, n2, n3) = (-n1, -n2, -n3)
(n1, n2, n3) * (m1, m2, m3) = (n1 * m1, n2 * m2, n3 * m3)
signum (n1, n2, n3) = (signum n1, signum n2, signum n3)
abs (n1, n2, n3) = (abs n1, abs n2, abs n3)
fromInteger n = (fromInteger n, fromInteger n, fromInteger n)
instance (Fractional t, Fractional u, Fractional v) => Fractional (t, u, v) where
fromRational n = (fromRational n, fromRational n, fromRational n)
recip (n1, n2, n3) = (recip n1, recip n2, recip n3)
enTuple :: COLORREF -> (Fixed E6, Fixed E6, Fixed E6)
enTuple x = (fromIntegral $ getRValue x, fromIntegral $ getGValue x, fromIntegral $ getBValue x)
unTuple :: (Fixed E6, Fixed E6, Fixed E6) -> COLORREF
unTuple (r, g, b) = rgb (round r) (round g) (round b)
{-# INLINE weights #-}
weights f (lo, hi) = if floor lo == floor hi then
f (floor lo)
else fromRational (recip (hi - lo)) * (fromRational (fromIntegral (ceiling lo) - lo) * f (floor lo)
+ fromRational (hi - fromIntegral (floor hi)) * f (floor hi)
+ sum (map f [ceiling lo..floor hi-1]))
{-# INLINE weightsSpecial1 #-}
weightsSpecial1 factor f (lo, hi) = fromRational (recip (fromIntegral factor)) * sum (map f [ceiling lo..floor hi-1])
{-# INLINE weightsSpecial2 #-}
weightsSpecial2 f (lo, _) = f (floor lo)
{-# INLINE weightsSpecial3 #-}
weightsSpecial3 f (lo, hi) = fromRational (recip (hi - lo)) * (fromRational (fromIntegral (ceiling lo) - lo) * f (floor lo)
+ fromRational (hi - fromIntegral (floor hi)) * f (floor hi)
+ sum (map f [ceiling lo..floor hi-1]))
-- | Averages over the pixels in the given rectangle.
{-# INLINE sample #-}
sample weights im ((loX, loY), (hiX, hiY)) = unTuple $ weights (\y -> weights (\x -> enTuple $ getPixel' (x, y) im) (loX, hiX)) (loY, hiY)
rescale2 hi hi2 x = fromIntegral (x * (hi2 + 1)) / fromIntegral (hi + 1)
-- | Rescales a coordinate by moving it from the given first rectangle to the proportionate spot in the second rectangle.
rescale (hiX, hiY) (hiX2, hiY2) (x, y) = (rescale2 hiX hiX2 x, rescale2 hiY hiY2 y)
down1 (x, y) = (x - 1, y - 1)
-- | Resamples the given image into the current image.
resample :: BMP -> Draw ()
resample im = do
bnds <- askDims
if bnds == askDims' im then
mask 1 (0, 0) im
else let (factor, m) = divMod (fst (askDims' im)) (fst bnds) in
function' $ \x y -> sample
-- These are optimizations for the case when the input image is an integral multiple of the output in size...
(if m == 0 && snd (askDims' im) `mod` snd bnds == 0 then
weightsSpecial1 factor
-- and the opposite...
else if fst bnds `mod` fst (askDims' im) == 0 && snd bnds `mod` snd (askDims' im) == 0 then
weightsSpecial2
-- and for when the image is being downsampled.
else if fst bnds < fst (askDims' im) && snd bnds < snd (askDims' im) then
weightsSpecial3
else
weights)
im (rescale (down1 bnds) (down1 $ askDims' im) (x, y), rescale (down1 bnds) (down1 $ askDims' im) (x + 1, y + 1))
horzGradient = fst $ onNewBitmap (3, 3) $ function' $ \x _ -> intensity (fromIntegral x / 2)
vertGradient = fst $ onNewBitmap (3, 3) $ function' $ \_ y -> intensity (fromIntegral y / 2)
laplacian = fst $ onNewBitmap (3, 3) $ function' $ curry (listArray ((0, 0), (2, 2)) [0,-1,0,-1,10,-1,0,-1,0] !)
----------------------------------------
-- Quadratic splines
stepsI :: Int
stepsI = 100
steps :: Double
steps = fromIntegral stepsI
graph (ax, ay, len) (x, y, direction, ddydirection, dy, t) = (x + direction / steps, y + direction * dy / steps, direction, ddydirection, dy + 2 * direction * ddydirection * ay / ax * step, t + step) where
step = sqrt (1 + dy ^ 2) / len / steps
-- Compute pixel intensities for a piece of the spline.
-- TODO: Some parameter signs and flags need to be twiddled for the various quadrants, so this doesn't work currently.
intensities _ [] _ = []
intensities swapped (parm@(ax, bx, cx, ay, by, cy):parms) tuple =
-- The intensities made by 'graph' only make sense in a certain range,
-- so when you get to a 45 degree angle, you switch x and y.
processed ++ if t >= 1 then
intensities swapped parms (x, y, direction, direction * ddydirection, dy, 0)
else
intensities (not swapped) ((ay, by, cy, ax, bx, cx):parms) (y, x, signum dy * direction, direction * ddydirection, recip dy, t)
where
(ok, (x, y, direction, ddydirection, dy, t):_) = break (\(_, _, _, _, dy, t) -> abs dy > 1 || t >= 1) $ iterate (graph (ax, ay, len parm)) tuple
processed = map (\(x, y, _, _, _, _) -> let
(y', n) = properFraction y
(a, b) = if swapped then (y', floor x) else (floor x, y') in
-- Intensity can't be 0, or we can't area fill.
((a, b), recip 255 `max` if {-(dx > 0) == -}dy > 0 then 1 - n else n)) ok
rotate1 ls = tail ls ++ [head ls]
unrotate1 ls = last ls : init ls
pairUp ls = zip ls (rotate1 ls)
-- Compute the length of a piece of the spline.
len (a, b, _, c, d, _) = f 1 - f 0 where
f t = x t * (a ^ 2 * t + a * b + c * (c * t + d)) / 2 / (a ^ 2 + c ^ 2) + 1 / 2 / (a ^ 2 + c ^ 2) ** 1.5 * (b * c - a * d) ^ 2 * log (sqrt (a ^ 2 + c ^ 2) * x t + a ^ 2 * t + a * b + c ^ 2 * t + c * d)
x t = sqrt (t ^ 2 * (a ^ 2 + c ^ 2) + 2 * a * b * t + b ^ 2 + 2 * c * d * t + d ^ 2)
windows _ [] = []
windows n ls = tk : windows n dr where
(tk, dr) = splitAt n ls
-- Draw the fringe of a spline.
drawAA aa parms@((_, bx, cx, _, by, cy):_) = mapM_ (\(pt, i) -> (if aa then blend i else setPixel) pt (rgb 255 255 255))
$ map head $ windows stepsI $ drop (stepsI `quot` 2)
$ intensities
False
parms
(cx, cy, 1, 1, by / bx, 0)
-- Compute parameters of the spline.
splineCoefficients = tail . scanl (\(accel, slope, _) (x1, x2) -> let slope' = 2 * accel + slope in
(x2 - x1 - slope', slope', x1)) (0, 0, 0) . pairUp
-- | Draw an antialiased filled spline.
-- The first knot should be roughly at the bottom of the spline, and they should
-- go around counter clockwise.
spline aa clr knots = do
let parmX = unrotate1 $ splineCoefficients $ rotate1 $ map fst knots
let parmY = splineCoefficients $ map snd knots
let parameters = zipWith (\(ax, bx, cx) (ay, by, cy) -> (ax, bx, cx, ay, by, cy)) parmX parmY
dims <- askDims
let (bmp, _) = onNewBitmap dims $ do
drawAA aa parameters
-- areaFill (rgb 255 255 255) (ceiling *** ((+1) . ceiling) $ head knots)
mask 1 (0, 0) bmp
----------------------------------------
-- Basic shapes
clipRect (x1, y1, x2, y2) (x1a, y1a, x2a, y2a) = (x1 `max` x1a, y1 `max` y1a, x2 `min` x2a, y2 `min` y2a)
-- | Fill a rectangle.
fillRect clr rect = do
(wid, ht) <- askDims
function rect (\_ _ _ -> clr)
-- | Fill using a functional.
function :: (Int32, Int32, Int32, Int32) -> (COLORREF -> Int32 -> Int32 -> COLORREF) -> Draw ()
function rt f = Draw $ do
d <- ask
let ?seq = True
(wid, ht) <- unDraw askDims
let (x1, y1, x2, y2) = clipRect rt (0, 0, wid, ht)
lift $ concF_ (fromIntegral (y2 - y1)) (\y -> mapM_ (\x -> runReaderT (unDraw $ unsafeGetPixel (x, fromIntegral y + y1) >>= \c -> unsafeSetPixel (x, fromIntegral y + y1) (f c x (fromIntegral y + y1))) d) [x1..x2-1])
function' f = do
(wid, ht) <- askDims
function (0, 0, wid, ht) (const f)
mean x y = (y - x) / 2 + x
-- | A filled ellipse.
ellipse aa clr (x1, y1, x2, y2) = spline aa clr [(mean x1 x2, y1), (x2, mean y1 y2), (mean x1 x2, y2), (x1, mean y1 y2)]
----------------------------------------
-- Drawing in a device context.
drawBitmap wnd hdc wd ht p = do
let wd32 = fromIntegral wd
let ht32 = fromIntegral ht
-- Draw the bitmap
withBITMAP (InfoV3 (BitmapInfoV3 40 wd ht False 1 24 CompressionRGB (4 * wd32 * ht32) 0 0 0 0)) $ \pBmp ->
c_SetDIBitsToDevice hdc 0 0 wd32 ht32 0 0 0 ht32 p pBmp dIB_RGB_COLORS
-- | Put up a quick window to visualize the drawing as it's being drawn.
graffito (x1, y1, x2, y2) d = do
chn <- newChan
wnd <- frameWindow Nothing Nothing nullPtr "Graffito" $ \wnd msg wParam lParam -> do
if msg == wM_CREATE then do
moveWindow wnd (fromIntegral x1) (fromIntegral y1) (fromIntegral (x2 - x1)) (fromIntegral (y2 - y1)) True
else if msg == wM_PAINT then
allocaPAINTSTRUCT $ \ps -> do
hdc <- beginPaint wnd ps
forkIO $ runReaderT (unDraw d) ((x2 - x1, y2 - y1), nullPtr, Just chn) >> writeChan chn (-1, 0, 0)
let loop = do { (x, y, clr) <- readChan chn; when (x >= 0) $ do { set hdc x (y2 - y1 - 1 - y) clr; loop } } in
loop
endPaint wnd ps
else if msg == wM_CLOSE then
void $ postMessage nullPtr wM_QUIT 0 0
else
return ()
defWindowProc (Just wnd) msg wParam lParam
postMessage wnd wM_CREATE 0 0
allocaMessage $ \msg ->
let loop =
do
b <- getMessage msg Nothing
when b $ do
translateMessage msg
dispatchMessage msg
loop in
loop