potrace-0.1.0.0: src/Graphics/Potrace/Base.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
-- |
-- Module : Graphics.Potrace.Base
-- Copyright : (c) 2015 Christopher Chalmers
-- License : BSD-style (see LICENSE)
-- Maintainer : c.chalmers@me.com
--
-- A mid-level interface to potrace. Includes helpers for making
-- bitmap images. This is useful for other libraries that want to
-- implement their own tools on top of potrace (see
-- "potrace-diagrams").
--
-- In principle this could be in a separate module but there's already
-- three separate potrace modules and the dependencies aren't huge.
module Graphics.Potrace.Base
(
-- * Tracing
Curve (..)
, Segment (..)
, P2 (..)
, trace
, trace'
, traceForest
, traceForest'
-- * Bitmaps
, Bitmap (..)
, generate
, index
-- * Parameters
, Parameters (..)
, TurnPolicy (..)
) where
import Control.Applicative
import Data.Bits
import Data.Default
import Data.Monoid
import Data.Tree
import qualified Data.Vector.Storable as V
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable
import System.IO.Unsafe (unsafePerformIO)
import Bindings.Potrace
------------------------------------------------------------------------
-- Bitmap
------------------------------------------------------------------------
-- | Data type to represent a bit packed image. This can be passed to
-- potrace via 'trace'. The constructor is exported by
-- 'Graphics.Potrace.Base' but be aware the underlying vector has a
-- host dependant form. You are advised to use 'generate' to create a
-- 'Bitmap'.
data Bitmap = Bitmap
{ bitmapWidth :: Int
, bitmapHeight :: Int
, bitmapDy :: Int
, bitmapData :: V.Vector CULong
}
-- | Given an image and a predicate for whether a pixel is on or off,
-- create a bit-packed image suitable for passing to potrace.
generate :: Int -> Int -> (Int -> Int -> Bool) -> Bitmap
generate w h f = Bitmap w h dy v
where
-- The format of the vector potrace needs depends on the size and
-- endianness of the machine.
m = sizeOf (0 :: CULong) * 8
-- Potrace works in blocks of the Word size m. We make each x
-- (horizontal) line n words wide, the last word only uses r of its
-- bits.
(n,r) = w `divMod` m
dy = if r == 0 then n else n + 1
-- Number of potential pixels (bits) in each x line (we only use w of them).
l = dy * m
-- Each x line is made up of dy words, so we need dy*h words for
-- the whole picture.
v = V.generate (dy*h) $ \i ->
-- The starting point in the image for current block of pixels.
let (y,x) = (i*m) `divMod` l
-- Number of pixels we need to fill.
c | x + m > w = r
| otherwise = m
-- Loop to fill word with bits. XXX Deal with endianness
go !k !b
| k < 0 = b
| f (x + k) y = go (k - 1) $ setBit b (m - 1 - k)
| otherwise = go (k - 1) b
in go (c - 1) 0
{-# INLINE generate #-}
-- | Index a pixel in a 'Bitmap'. This is mainly here for debugging
-- purposes.
index :: Bitmap -> Int -> Int -> Bool
index (Bitmap w h dy v) i j = testBit w (m - 1 - r)
where
m = sizeOf (0 :: CULong) * 8
w = v V.! (j * dy + (i `div` m))
(n,r) = i `divMod` m
{-# INLINE index #-}
------------------------------------------------------------------------
-- Parameters
------------------------------------------------------------------------
-- | Parameters to control the tracing operation of potrace. The default
-- parameters are
--
-- @
-- Parameters
-- { 'turdSize' = 2
-- , 'turnPolicy' = 'MinorityTP'
-- , 'alphaMax' = 1.0
-- , 'optTolerance' = 'Just' 0.2
-- }
-- @
data Parameters = Parameters
{ _turdSize :: Int -- ^ See 'Graphics.Potrace.turdSize'.
, _turnPolicy :: TurnPolicy -- ^ See 'Graphics.Potrace.turnPolicy'.
, _alphaMax :: Double -- ^ See 'Graphics.Potrace.alphaMax'.
, _optTolerance :: (Maybe Double) -- ^ See 'Graphics.Potrace.optTolerance'.
}
instance Default Parameters where
def = Parameters
{ _turdSize = 2
, _turnPolicy = MinorityTP
, _alphaMax = 1.0
, _optTolerance = Just 0.2
}
-- | How to resolve ambiguities during decomposition of bitmaps into
-- paths.
data TurnPolicy
= BlackTP -- ^ Prefers to connect black (foreground) components
| WhiteTP -- ^ Prefers to connect white (background) components.
| LeftTP -- ^ Always take a left turn.
| RightTP -- ^ Always take a right turn.
| MinorityTP -- ^ Prefers to connect the color (black or white) that
-- occurs least frequently in a local neighborhood of the
-- current position.
| MajorityTP -- ^ Prefers to connect the color (black or white) that
-- occurs most frequently in a local neighborhood of
-- the current position. (default)
| RandomTP -- ^ Choose pseudo-randomly
-- Parameter internals -------------------------------------------------
-- | Allocate new parameters from potrace's default parameters. This
-- should protect against future changes to the options. When no
-- longer needed, these should be deallocated with
-- 'p'potrace_param_free'
params :: Parameters -> IO (Ptr C'potrace_param_s)
params (Parameters ts tp am ot) = do
c_param <- c'potrace_param_default
poke (p'potrace_param_s'turdsize c_param) $ fromIntegral ts
poke (p'potrace_param_s'turnpolicy c_param) $ turn tp
poke (p'potrace_param_s'alphamax c_param) $ CDouble am
poke (p'potrace_param_s'opticurve c_param) $ optiOn
poke (p'potrace_param_s'opttolerance c_param) $ optiVal
return c_param
where (optiOn, optiVal) = case ot of
Just t -> (1, CDouble t)
Nothing -> (0, 0)
turn :: TurnPolicy -> CInt
turn = \case
BlackTP -> c'POTRACE_TURNPOLICY_BLACK
WhiteTP -> c'POTRACE_TURNPOLICY_WHITE
LeftTP -> c'POTRACE_TURNPOLICY_LEFT
RightTP -> c'POTRACE_TURNPOLICY_RIGHT
MinorityTP -> c'POTRACE_TURNPOLICY_MINORITY
MajorityTP -> c'POTRACE_TURNPOLICY_MAJORITY
RandomTP -> c'POTRACE_TURNPOLICY_RANDOM
------------------------------------------------------------------------
-- Tracing
------------------------------------------------------------------------
-- | Data type representing a 2D point were the origin is at the bottom
-- left.
data P2 = P2 {-# UNPACK #-} !Double {-# UNPACK #-} !Double
deriving (Show, Read)
-- | potrace defines a segment as either 'Bezier' or a 'Corner' (in most
-- systems this is equivalent to linear segments).
data Segment
= Bezier {-# UNPACK #-} !P2 {-# UNPACK #-} !P2 {-# UNPACK #-} !P2
| Corner {-# UNPACK #-} !P2 {-# UNPACK #-} !P2
deriving (Show, Read)
-- | A curve is a list of segments. The starting point is provided for
-- convenience but this is just the final point of the last segment.
data Curve = Curve {-# UNPACK #-} !P2 [Segment]
-- | Trace the bitmap image to a list of curves using potrace with 'def'
-- parameters.
trace :: Bitmap -> [Curve]
trace = trace' def
-- | Trace the bitmap image to a list of curves using potrace with given
-- parameters.
trace' :: Parameters -> Bitmap -> [Curve]
trace' p bm = unsafePerformIO $ unsafeWithImage pathList p bm
-- | Trace the bitmap image as a forest of curves using potrace with 'def'
-- parameters. Each child curve is completely contained in it's
-- parent.
traceForest :: Bitmap -> Forest Curve
traceForest = traceForest' def
-- | Trace the bitmap image as a forest of curves using potrace with
-- given parameters parameters. Each child curve is completely
-- contained in it's parent.
traceForest' :: Parameters -> Bitmap -> Forest Curve
traceForest' p bm = unsafePerformIO $ unsafeWithImage pathForest p bm
-- Internals -----------------------------------------------------------
-- | Go though the curve and turn in into a list of segments.
curve :: C'potrace_curve_s -> IO Curve
curve (C'potrace_curve_s n ts ps_) = Curve <$> p0 <*> go 0 where
ps = castPtr ps_
-- The last point in the vector is also the starting point
p0 = peekElemOff ps (3*fromIntegral n - 1)
go i | i >= fromIntegral n = pure []
go i = do
let o = 3*i
t <- peekElemOff ts i
s <- if t == c'POTRACE_CORNER
then Corner <$> peekElemOff ps (o + 1)
<*> peekElemOff ps (o + 2)
else Bezier <$> peekElemOff ps (o + 0)
<*> peekElemOff ps (o + 1)
<*> peekElemOff ps (o + 2)
ss <- go (i+1)
return (s:ss)
-- | Helper for constructing lists from linked lists of pointers. If the
-- ptr is 'nullPtr' return mempty, otherwise pass the pointer value to
-- the function.
onPtr :: (Storable a, Monoid b) => (a -> IO b) -> Ptr a -> IO b
onPtr f ptr
| ptr == nullPtr = pure mempty
| otherwise = peek ptr >>= f
{-# INLINE onPtr #-}
type State = C'potrace_state_s
-- | List of curves in order, suitable for passing to a renderer.
pathList :: State -> IO [Curve]
pathList (C'potrace_state_s _i ptr0 _) = go ptr0 where
go = onPtr $ \p -> do
s <- curve $ c'potrace_path_s'curve p
ss <- go (c'potrace_path_s'next p)
pure (s:ss)
-- | Tree structure of paths where each child is enclosed within it's
-- parent.
pathForest :: State -> IO (Forest Curve)
pathForest (C'potrace_state_s _i ptr0 _) = go ptr0 where
go = onPtr $ \p -> do
s <- curve $ c'potrace_path_s'curve p
ch <- go $ c'potrace_path_s'childlist p
sb <- go $ c'potrace_path_s'sibling p
pure (Node s ch : sb)
unsafeWithImage :: (State -> IO r) -> Parameters -> Bitmap -> IO r
unsafeWithImage f ps (Bitmap w h dy v) = V.unsafeWith v $ \p -> do
c_param <- params ps
alloca $ \c_bitmap -> do
poke c_bitmap $ C'potrace_bitmap_s (fromIntegral w) (fromIntegral h) (fromIntegral dy) p
c_status <- c'potrace_trace c_param c_bitmap
r <- peek c_status >>= f
c'potrace_state_free c_status
c'potrace_param_free c_param
return r
------------------------------------------------------------------------
-- Instances
------------------------------------------------------------------------
instance Storable P2 where
sizeOf _ = 2 * sizeOf (undefined::Double)
{-# INLINE sizeOf #-}
alignment _ = alignment (undefined::Double)
{-# INLINE alignment #-}
poke ptr (P2 x y) = poke ptr' x >> pokeElemOff ptr' 1 y
where ptr' = castPtr ptr
{-# INLINE poke #-}
peek ptr = P2 <$> peek ptr' <*> peekElemOff ptr' 1
where ptr' = castPtr ptr
{-# INLINE peek #-}