{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} module Graphics.Rasterific.Svg.MeshConverter ( mapMeshBaseCoordiantes , convertGradientMesh ) where #if !MIN_VERSION_base(4,8,0) import Data.Monoid( mconcat ) import Control.Applicative( pure, (<$>) ) #endif import Control.Monad.Primitive( PrimMonad, PrimState ) import Control.Monad.Reader.Class( MonadReader ) import Graphics.Rasterific.Linear( (^+^) , (^-^) , lerp ) import qualified Linear as L import qualified Graphics.Rasterific as R import Data.Vector( (//) ) import qualified Data.Vector as V import Codec.Picture( PixelRGBA8( .. ) ) import Graphics.Svg.Types import Graphics.Rasterific.MeshPatch {-import Graphics.Rasterific.Svg.RenderContext-} toBaseX :: R.PlaneBound -> MeshGradient -> Float toBaseX bounds mesh = case _meshGradientX mesh of Num n -> realToFrac n Percent p -> miniX + (maxiX - miniX) * realToFrac p Px n -> realToFrac n Em n -> realToFrac n Pc n -> realToFrac n Mm n -> realToFrac n Cm n -> realToFrac n Point n -> realToFrac n Inches n -> realToFrac n where R.PlaneBound (R.V2 miniX _miniY) (R.V2 maxiX _maxiY) = bounds toBaseY :: R.PlaneBound -> MeshGradient -> Float toBaseY bounds mesh = case _meshGradientY mesh of Num n -> realToFrac n Percent p -> miniY + (maxiY - miniY) * realToFrac p Px n -> realToFrac n Em n -> realToFrac n Pc n -> realToFrac n Mm n -> realToFrac n Cm n -> realToFrac n Point n -> realToFrac n Inches n -> realToFrac n where R.PlaneBound (R.V2 _miniX miniY) (R.V2 _maxiX maxiY) = bounds mapMeshBaseCoordiantes :: ((Number, Number) -> (Number, Number)) -> MeshGradient -> MeshGradient mapMeshBaseCoordiantes f m = m { _meshGradientX = x, _meshGradientY = y } where (x, y) = f (_meshGradientX m, _meshGradientY m) convertGradientMesh :: R.PlaneBound -> R.PlaneBound -> MeshGradient -> MeshPatch PixelRGBA8 convertGradientMesh globalBounds bounds mesh = scaler rmesh where (_, rmesh) = withMesh baseGrid (gatherGeometry svgBasePoint mesh) (w, h) = svgMeshSize mesh colors = gatherColors mesh w h baseGrid = generateLinearGrid w h svgBasePoint svgBasePoint colors svgBasePoint = R.V2 (toBaseX startBounds mesh) (toBaseY startBounds mesh) startBounds = case _meshGradientUnits mesh of CoordUserSpace -> globalBounds CoordBoundingBox -> R.PlaneBound (R.V2 0 0) (R.V2 1 1) delta = R._planeMaxBound bounds ^-^ R._planeMinBound bounds toBoundingBox p = R._planeMinBound bounds ^+^ delta * p scaler :: MeshPatch px -> MeshPatch px scaler = case _meshGradientUnits mesh of CoordUserSpace -> id CoordBoundingBox -> R.transform toBoundingBox gatherGeometry :: (MonadReader (MutableMesh (PrimState m) px) m, PrimMonad m) => R.Point -> MeshGradient -> m () gatherGeometry basePoint = mapM_ goRow . zip [0 ..] . _meshGradientRows where toCurve firstPatchPoint lastPoint p = case _gradientPath p of Just pp -> svgPathToPrimitives firstPatchPoint lastPoint pp Nothing -> lastPoint `straightLine` firstPatchPoint lastOf (R.CubicBezier _ _ _ a) = a firstOf (R.CubicBezier a _ _ _) = a goRow (y, r) = mapM_ (goPatch y) . zip [0 ..] $ _meshGradientRowPatches r goPatch y (x, patch) = case _meshGradientPatchStops patch of -- A B -- +---+ -- | | -- +---+ -- D C [a, b, c, d] -> do let toC = toCurve basePoint northEast = toC basePoint a eastSouth = toC (lastOf northEast) b southWest = toC (lastOf eastSouth) c westNorth = toC (lastOf southWest) d setVertice x y $ firstOf northEast setVertice (x + 1) y $ lastOf northEast setVertice (x + 1) (y + 1) $ firstOf southWest setVertice x (y + 1) $ lastOf southWest horizOrdered northEast horizUnordered southWest vertUnordered westNorth vertOrdered eastSouth -- A B -- +---+ -- | -- +---+ -- C [a, b, c] | y == 0 -> do firstPoint <- getVertice x y closePoint <- getVertice x (y + 1) let toC = toCurve closePoint northEast = toC firstPoint a eastSouth = toC (lastOf northEast) b southWest = toC (lastOf eastSouth) c setVertice (x + 1) y $ firstOf eastSouth setVertice (x + 1) (y + 1) $ lastOf eastSouth horizOrdered northEast horizUnordered southWest vertOrdered eastSouth -- B -- + + -- | | -- +---+ -- D C [b, c, d] -> do firstPoint <- getVertice (x + 1) y closePoint <- getVertice x y let toC = toCurve closePoint eastSouth = toC firstPoint b southWest = toC (lastOf eastSouth) c westNorth = toC (lastOf southWest) d setVertice (x + 1) (y + 1) $ firstOf southWest setVertice x (y + 1) $ lastOf southWest horizUnordered southWest vertUnordered westNorth vertOrdered eastSouth -- B -- + -- | -- +---+ -- C [b, c] -> do firstPoint <- getVertice (x + 1) y closePoint <- getVertice x (y + 1) let toC = toCurve closePoint eastSouth = toC firstPoint b southWest = toC (lastOf eastSouth) c setVertice (x + 1) (y + 1) $ firstOf southWest horizUnordered southWest vertOrdered eastSouth _ -> return () where horizOrdered (R.CubicBezier _ b c _) = setHorizPoints x y $ InterBezier b c horizUnordered (R.CubicBezier _ b c _) = setHorizPoints x (y + 1) $ InterBezier c b vertUnordered (R.CubicBezier _ b c _) = setVertPoints x y $ InterBezier c b vertOrdered (R.CubicBezier _ b c _) = setVertPoints (x + 1) y $ InterBezier b c gatherColors :: MeshGradient -> Int -> Int -> V.Vector PixelRGBA8 gatherColors mesh w h = baseVec // foldMap goRow (zip [0 ..] $ _meshGradientRows mesh) where baseVec = V.replicate ((w + 1) * (h + 1)) $ PixelRGBA8 0 0 0 255 goRow (y, row) = foldMap (goPatch y) . zip [0 ..] $ _meshGradientRowPatches row goPatch y (x, patch) = case _meshGradientPatchStops patch of -- A B -- +---+ -- | | -- +---+ -- D C [a, b, c, d] -> [setAt 0 0 a, setAt 1 0 b, setAt 1 1 c, setAt 0 1 d] -- A B -- +---+ -- | -- +---+ -- C [_a, b, c] | y == 0 -> [setAt 1 0 b, setAt 1 1 c] -- B -- + + -- | | -- +---+ -- D C [_b, c, d] -> [setAt 1 1 c, setAt 0 1 d] -- B -- + -- | -- +---+ -- C [_b, c] -> [setAt 1 1 c] _ -> [] where colorOf s = case _gradientOpacity s of Nothing -> _gradientColor s Just a -> PixelRGBA8 r g b . floor $ 255 * a where PixelRGBA8 r g b _ = _gradientColor s setAt dx dy stop = (idx, colorOf stop) where idx = (y + dy) * (w + 1) + x + dx svgMeshSize :: MeshGradient -> (Int, Int) svgMeshSize mesh = (w, h) where h = length $ _meshGradientRows mesh w = maximum $ length . _meshGradientRowPatches <$> _meshGradientRows mesh svgPathToPrimitives :: R.Point -> R.Point -> GradientPathCommand -> R.CubicBezier svgPathToPrimitives firstPatchPoint = go where go o GClose = o `straightLine` firstPatchPoint go o (GLine OriginRelative c) = o `straightLine` (o ^+^ mp c) go o (GLine OriginAbsolute p) = o `straightLine` mp p go o (GCurve OriginAbsolute c1 c2 e) = R.CubicBezier o (toR c1) (toR c2) (mp e) go o (GCurve OriginRelative c1 c2 e) = R.CubicBezier o (o ^+^ toR c1) (o ^+^ toR c2) (o ^+^ mp e) mp Nothing = firstPatchPoint mp (Just p) = toR p toR :: RPoint -> R.Point {-# INLINE toR #-} toR (L.V2 x y) = realToFrac <$> R.V2 x y straightLine :: R.Point -> R.Point -> R.CubicBezier straightLine a b = R.CubicBezier a p1 p2 b where p1 = lerp (1/3) a b p2 = lerp (2/3) a b