{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RankNTypes #-} -- | This module implements drawing primitives to draw directly into -- the output texture, without generating an intermediate scene -- representation. -- -- If you need to draw complex scenes or plot an important set of -- data, this is the module you should use. The downside is that -- you must specify everything you need at each draw call, there -- is no API to help you propagate constants. -- -- The "stroking" must be done using the functions of the -- `Graphics.Rasterific.Outline` module. module Graphics.Rasterific.Immediate ( DrawContext , DrawOrder( .. ) , orderToDrawing , runDrawContext , fillWithTextureAndMask , fillWithTexture , fillWithTextureNoAA , fillOrder , textToDrawOrders , transformOrder , meshToImage ) where import Control.Monad.ST( ST, runST ) import Data.Maybe( fromMaybe ) import qualified Data.Foldable as F import Control.Monad.Free( liftF ) import Control.Monad.State( evalStateT, execStateT, lift ) import Control.Monad.Trans.State( get ) import Codec.Picture.Types( Image( .. ) , Pixel( .. ) , MutableImage( .. ) , unsafeFreezeImage , fillImageWith ) import Control.Monad.Primitive( PrimMonad, primToPrim ) import qualified Data.Vector.Storable.Mutable as M import Graphics.Rasterific.Compositor import Graphics.Rasterific.Linear( V2( .. ) ) import Graphics.Rasterific.Rasterize import Graphics.Rasterific.Shading import Graphics.Rasterific.QuadraticBezier import Graphics.Rasterific.Types import Graphics.Rasterific.PatchTypes import Graphics.Rasterific.CubicBezier.FastForwardDifference import Graphics.Rasterific.Transformations import Graphics.Rasterific.MeshPatch import Graphics.Rasterific.ComplexPrimitive import Graphics.Rasterific.Command import Graphics.Rasterific.PlaneBoundable import qualified Data.Vector.Unboxed as VU import Graphics.Text.TrueType( Dpi, getStringCurveAtPoint ) -- | Reify a filling function call, to be able to manipulate -- them in a simpler fashion. data DrawOrder px = DrawOrder { -- | Primitives to be filled. _orderPrimitives :: ![[Primitive]] -- | Texture for the filled primitives. , _orderTexture :: !(Texture px) -- | How to fill the primitives. , _orderFillMethod :: !FillMethod -- | Optional mask used for clipping. , _orderMask :: !(Maybe (Texture (PixelBaseComponent px))) -- | Function to perform direct drawing , _orderDirect :: !(forall s. DrawContext (ST s) px ()) } instance PlaneBoundable (DrawOrder px) where planeBounds = foldMap (foldMap planeBounds) . _orderPrimitives transformOrder :: (Point -> Point) -> DrawOrder px -> DrawOrder px transformOrder f order = order { _orderPrimitives = transform f $ _orderPrimitives order } transformOrderM :: Monad m => (Point -> m Point) -> DrawOrder px -> m (DrawOrder px) transformOrderM f order = do v <- transformM f $ _orderPrimitives order return $ order { _orderPrimitives = v} instance Transformable (DrawOrder px) where transform = transformOrder transformM = transformOrderM -- | Transform back a low level drawing order to a more -- high level Drawing orderToDrawing :: DrawOrder px -> Drawing px () orderToDrawing order = usingTexture . mapM_ filler $ _orderPrimitives order where usingTexture sub = liftF $ SetTexture (_orderTexture order) sub () filler prims = liftF $ Fill (_orderFillMethod order) prims () -- | Render the drawing orders on the canvas. fillOrder :: (PrimMonad m, RenderablePixel px) => DrawOrder px -> DrawContext m px () fillOrder o@DrawOrder { _orderMask = Nothing } = do F.forM_ (_orderPrimitives o) $ fillWithTexture (_orderFillMethod o) (_orderTexture o) img <- get lift $ primToPrim $ flip evalStateT img $ _orderDirect o fillOrder o@DrawOrder { _orderMask = Just mask } = do F.forM_ (_orderPrimitives o) $ fillWithTextureAndMask (_orderFillMethod o) (_orderTexture o) mask img <- get lift $ primToPrim $ flip evalStateT img $ _orderDirect o -- | Start an image rendering. See `fillWithTexture` for -- an usage example. This function can work with either -- `IO` or `ST`. runDrawContext :: forall m px . (PrimMonad m, RenderablePixel px) => Int -- ^ Rendering width -> Int -- ^ Rendering height -> px -- ^ Background color -> DrawContext m px () -- ^ Actual drawing computation -> m (Image px) runDrawContext width height background drawing = do buff <- M.new (width * height * componentCount background) let mutable = MutableImage width height buff fillImageWith mutable background img <- execStateT drawing mutable unsafeFreezeImage img mapExec :: Monad m => (a -> m ()) -> [a] -> m () mapExec f = foldr ((>>) . f) (return ()) isCoverageDrawable :: MutableImage s px -> CoverageSpan -> Bool isCoverageDrawable img coverage = _coverageVal coverage > 0 && x >= 0 && y >= 0 && x < imgWidth && y < imgHeight where !imgWidth = fromIntegral $ mutableImageWidth img !imgHeight = fromIntegral $ mutableImageHeight img x = _coverageX coverage y = _coverageY coverage -- | Fill some geometry. -- -- > immediateDrawExample :: Image PixelRGBA8 -- > immediateDrawExample = runST $ -- > runDrawContext 200 200 (PixelRGBA8 0 0 0 0) $ -- > fillWithTexture FillWinding texture geometry -- > where -- > circlePrimitives = circle (V2 100 100) 50 -- > geometry = strokize 4 JoinRound (CapRound, CapRound) circlePrimitives -- > texture = uniformTexture (PixelRGBA8 255 255 255 255) -- -- <> -- fillWithTexture :: (PrimMonad m, RenderablePixel px) => FillMethod -> Texture px -- ^ Color/Texture used for the filling -> [Primitive] -- ^ Primitives to fill -> DrawContext m px () fillWithTexture fillMethod texture els = do img@(MutableImage width height _) <- get let !mini = V2 0 0 !maxi = V2 (fromIntegral width) (fromIntegral height) !filler = primToPrim . transformTextureToFiller meshToImage texture img clipped = foldMap (clip mini maxi) els spans = rasterize fillMethod clipped lift . mapExec filler $ filter (isCoverageDrawable img) spans -- | Function identical to 'fillWithTexture' but with anti-aliasing -- (and transparency) disabled. fillWithTextureNoAA :: (PrimMonad m, RenderablePixel px) => FillMethod -> Texture px -- ^ Color/Texture used for the filling -> [Primitive] -- ^ Primitives to fill -> DrawContext m px () fillWithTextureNoAA fillMethod texture els = do img@(MutableImage width height _) <- get let !mini = V2 0 0 !maxi = V2 (fromIntegral width) (fromIntegral height) !filler = primToPrim . transformTextureToFiller meshToImage texture img clipped = foldMap (clip mini maxi) els spans = rasterize fillMethod clipped lift . mapExec (filler . toOpaqueCoverage) $ filter (isCoverageDrawable img) spans -- | Fill some geometry using a composition mask for visibility. -- -- > immediateDrawMaskExample :: Image PixelRGBA8 -- > immediateDrawMaskExample = runST $ -- > runDrawContext 200 200 (PixelRGBA8 0 0 0 255) $ -- > forM_ [1 .. 10] $ \ix -> -- > fillWithTextureAndMask FillWinding texture mask $ -- > rectangle (V2 10 (ix * 18 - 5)) 180 13 -- > where -- > texture = uniformTexture $ PixelRGBA8 0 0x86 0xc1 255 -- > mask = sampledImageTexture -- > $ runST -- > $ runDrawContext 200 200 0 -- > $ fillWithTexture FillWinding (uniformTexture 255) maskGeometry -- > -- > maskGeometry = strokize 15 JoinRound (CapRound, CapRound) -- > $ circle (V2 100 100) 80 -- -- <> -- fillWithTextureAndMask :: (PrimMonad m, RenderablePixel px) => FillMethod -> Texture px -- ^ Color/Texture used for the filling of the geometry -> Texture (PixelBaseComponent px) -- ^ Texture used for the mask. -> [Primitive] -- ^ Primitives to fill -> DrawContext m px () fillWithTextureAndMask fillMethod texture mask els = do img@(MutableImage width height _) <- get let !mini = V2 0 0 !maxi = V2 (fromIntegral width) (fromIntegral height) spans = rasterize fillMethod $ foldMap (clip mini maxi) els !shader = primToPrim . transformTextureToFiller meshToImage (ModulateTexture texture mask) img lift . mapM_ shader $ filter (isCoverageDrawable img) spans -- | Helper function transforming text range to draw order. textToDrawOrders :: Dpi -- ^ Current output device resolution -> Texture px -- ^ Texture to use if no texture is defined in the range -> Point -- ^ Baseline position -> [TextRange px] -- ^ Text description. -> [DrawOrder px] textToDrawOrders dpi defaultTexture (V2 x y) descriptions = toOrder <$> zip floatCurves linearDescriptions where toOrder (curve, d) = DrawOrder { _orderPrimitives = [beziersOfChar curve] , _orderFillMethod = FillWinding , _orderMask = Nothing , _orderTexture = fromMaybe defaultTexture $ _textTexture d , _orderDirect = return () } floatCurves = getStringCurveAtPoint dpi (x, y) [(_textFont d, _textSize d, _text d) | d <- descriptions] linearDescriptions = concat [map (const d) $ _text d | d <- descriptions] beziersOfChar curves = concat [fmap BezierPrim . bezierFromPath . fmap (uncurry V2) $ VU.toList c | c <- curves] meshToImage :: forall px. (RenderablePixel px) => Maybe Transformation -> Int-> Int -> PatchInterpolation -> MeshPatch px -> Image px meshToImage mayTrans width height i baseMesh | not hasTransparency = rendering | otherwise = runST $ runDrawContext width height background $ fillOrder order where mesh = case mayTrans >>= inverseTransformation of Nothing -> baseMesh Just trans -> transform (applyTransformation trans) baseMesh background = emptyPx :: px clipBackground = emptyValue :: PixelBaseComponent px rendering = runST $ runDrawContext width height background $ case i of PatchBilinear -> mapM_ rasterizeCoonPatch $ coonPatchesOf opaqueMesh PatchBicubic -> mapM_ rasterizeCoonPatch . cubicCoonPatchesOf $ calculateMeshColorDerivative opaqueMesh hasTransparency = F.any ((/= fullValue) . pixelOpacity) $ _meshColors mesh opacifier px = mixWithAlpha (\_ _ a -> a) (\_ _ -> fullValue) px px opaqueMesh = opacifier <$> mesh transparencyMesh = pixelOpacity <$> mesh clipPath = runST $ runDrawContext width height clipBackground $ case i of PatchBilinear -> mapM_ rasterizeCoonPatch $ coonPatchesOf transparencyMesh PatchBicubic -> mapM_ rasterizeCoonPatch . cubicCoonPatchesOf $ calculateMeshColorDerivative transparencyMesh order = DrawOrder { _orderPrimitives = [rectangle (V2 0 0) (fromIntegral width) (fromIntegral height)] , _orderTexture = AlphaModulateTexture (RawTexture rendering) (RawTexture clipPath) , _orderFillMethod = FillWinding , _orderMask = Nothing , _orderDirect = return () }