{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} #define SVG_2 -- | Module defining the type of mesh patch grid. module Graphics.Rasterific.MeshPatch ( -- * Types InterBezier( .. ) , Derivatives( .. ) , MeshPatch( .. ) , CubicCoefficient( .. ) -- * Functions , calculateMeshColorDerivative , verticeAt , generateLinearGrid , generateImageMesh -- * Extraction functions -- ** Simple , coonPatchAt , tensorPatchAt , coonImagePatchAt , tensorImagePatchAt , coonPatchAtWithDerivative , tensorPatchAtWithDerivative -- ** Multiple , coonPatchesOf , tensorPatchesOf , imagePatchesOf , tensorImagePatchesOf , cubicCoonPatchesOf , cubicTensorPatchesOf -- * Mutable mesh , MutableMesh , thawMesh , freezeMesh -- * Monadic mesh creation , withMesh , setVertice , getVertice , setHorizPoints , setVertPoints , setColor ) where {-import Debug.Trace-} {-import Text.Printf-} import Control.Monad.ST( runST ) import Control.Monad.Reader( runReaderT ) import Control.Monad.Reader.Class import Control.Monad.Primitive( PrimMonad, PrimState ) import Data.Vector( (!) ) import qualified Data.Vector as V import qualified Data.Vector.Mutable as MV import qualified Data.Vector.Generic as VG import Codec.Picture( Image( imageWidth, imageHeight ) ) import Graphics.Rasterific.Linear import Graphics.Rasterific.MiniLens import Graphics.Rasterific.Types import Graphics.Rasterific.Compositor import Graphics.Rasterific.Transformations import Graphics.Rasterific.PatchTypes #ifdef SVG_2 slopeOf :: (Additive h, Applicative h) => h Float -> h Float -> h Float -> Point -> Point -> Point -> h Float slopeOf prevColor thisColor nextColor prevPoint thisPoint nextPoint | nearZero distPrev || nearZero distNext = zero | otherwise = slopeVal <$> slopePrev <*> slope <*> slopeNext where distPrev = thisPoint `distance` prevPoint distNext = thisPoint `distance` nextPoint slopePrev | nearZero distPrev = zero | otherwise = (thisColor ^-^ prevColor) ^/ distPrev slopeNext | nearZero distNext = zero | otherwise = (nextColor ^-^ thisColor) ^/ distNext slope = (slopePrev ^+^ slopeNext) ^* 0.5 slopeVal :: Float -> Float -> Float -> Float slopeVal sp s sn | signum sp /= signum sn = 0 | abs s > abs minSlope = minSlope | otherwise = s where minSlope | abs sp < abs sn = 3 * sp | otherwise = 3 * sn #else slopeBasic :: (Additive h) => h Float -> h Float -> Point -> Point -> h Float slopeBasic prevColor nextColor prevPoint nextPoint | nearZero d = zero | otherwise = (nextColor ^-^ prevColor) ^/ d where d = prevPoint `distance` nextPoint #endif -- | Prepare a gradient mesh to use cubic color interpolation, see -- renderCubicMesh documentation to see the global use of this function. calculateMeshColorDerivative :: forall px. (InterpolablePixel px) => MeshPatch px -> MeshPatch (Derivative px) calculateMeshColorDerivative mesh = mesh { _meshColors = withEdgesDerivatives } where withEdgesDerivatives = colorDerivatives V.// (topDerivative <> bottomDerivative <> leftDerivative <> rightDerivative) colorDerivatives = V.fromListN (w * h) [interiorDerivative x y | y <- [0 .. h - 1], x <- [0 .. w - 1]] w = _meshPatchWidth mesh + 1 h = _meshPatchHeight mesh + 1 clampX = max 0 . min (w - 1) clampY = max 0 . min (h - 1) rawColorAt x y =_meshColors mesh V.! (y * w + x) atColor x y = toFloatPixel $ rawColorAt (clampX x) (clampY y) #ifdef SVG_2 isOnVerticalBorder x = x == 0 || x == w - 1 isOnHorizontalBorder y = y == 0 || y == h - 1 #endif pointAt x y = verticeAt mesh (clampX x) (clampY y) derivAt x y = colorDerivatives V.! (y * w + x) topDerivative = [edgeDerivative yDerivative 0 1 x 0 | x <- [1 .. w - 2]] bottomDerivative = [edgeDerivative yDerivative 0 (-1) x (h - 1) | x <- [1 .. w - 2]] leftDerivative = [edgeDerivative xDerivative 1 0 0 y | y <- [1 .. h - 2]] rightDerivative = [edgeDerivative xDerivative (-1) 0 (w - 1) y | y <- [1 .. h - 2]] edgeDerivative :: Lens' (Derivative px) (Holder px Float) -> Int -> Int -> Int -> Int -> (Int, Derivative px) edgeDerivative coord dx dy x y | nearZero d = (ix, oldDeriv) | otherwise = (ix, oldDeriv & coord .~ otherDeriv) where ix = y * w + x oldDeriv = derivAt x y derivs = oldDeriv .^ coord otherDeriv = (c ^/ d) ^-^ derivs c = (atColor (x+dx) (y+dy) ^-^ atColor x y) ^* 2 d = pointAt (x+dx) (y+dy) `distance` pointAt x y -- General case interiorDerivative x y #ifdef SVG_2 | isOnHorizontalBorder y && isOnVerticalBorder x = Derivative thisColor zero zero zero | isOnHorizontalBorder y = Derivative thisColor dx zero zero | isOnVerticalBorder x = Derivative thisColor zero dy zero #endif | otherwise = Derivative thisColor dx dy dxy where #ifdef SVG_2 dx = slopeOf cxPrev thisColor cxNext xPrev this xNext dy = slopeOf cyPrev thisColor cyNext yPrev this yNext -- -} dxy = zero #else dx = slopeBasic cxPrev cxNext xPrev xNext dy = slopeBasic cyPrev cyNext yPrev yNext dxy | nearZero xyDist = zero | otherwise = (cxyNext ^-^ cyxPrev ^-^ cyxNext ^+^ cxyPrev) ^/ (xyDist) xyDist = (xNext `distance` xPrev) * (yNext `distance` yPrev) cxyPrev = atColor (x - 1) (y - 1) xyPrev = pointAt (x - 1) (y - 1) cxyNext = atColor (x + 1) (y + 1) xyNext = pointAt (x + 1) (y + 1) cyxPrev = atColor (x - 1) (y + 1) yxPrev = pointAt (x - 1) (y + 1) cyxNext = atColor (x + 1) (y - 1) yxNext = pointAt (x + 1) (y - 1) #endif cxPrev = atColor (x - 1) y thisColor = atColor x y cxNext = atColor (x + 1) y cyPrev = atColor x (y - 1) cyNext = atColor x (y + 1) xPrev = pointAt (x - 1) y this = pointAt x y xNext = pointAt (x + 1) y yPrev = pointAt x (y - 1) yNext = pointAt x (y + 1) -- | Mutable version of a MeshPatch data MutableMesh s px = MutableMesh { _meshMutWidth :: !Int , _meshMutHeight :: !Int , _meshMutPrimaryVertices :: !(MV.MVector s Point) , _meshMutHorizSecondary :: !(MV.MVector s InterBezier) , _meshMutVertSecondary :: !(MV.MVector s InterBezier) , _meshMutColors :: !(MV.MVector s px) , _meshMutTensorDerivatives :: !(Maybe (MV.MVector s Derivatives)) } -- | Normal mesh to mutable mesh thawMesh :: PrimMonad m => MeshPatch px -> m (MutableMesh (PrimState m) px) thawMesh MeshPatch { .. } = do let _meshMutWidth = _meshPatchWidth _meshMutHeight = _meshPatchHeight _meshMutPrimaryVertices <- V.thaw _meshPrimaryVertices _meshMutHorizSecondary <- V.thaw _meshHorizontalSecondary _meshMutVertSecondary <- V.thaw _meshVerticalSecondary _meshMutColors <- V.thaw _meshColors _meshMutTensorDerivatives <- case _meshTensorDerivatives of Nothing -> return Nothing Just v -> Just <$> V.thaw v return MutableMesh { .. } -- | Mutable mesh to freezed mesh. freezeMesh :: PrimMonad m => MutableMesh (PrimState m) px -> m (MeshPatch px) freezeMesh MutableMesh { .. } = do let _meshPatchWidth = _meshMutWidth _meshPatchHeight = _meshMutHeight _meshPrimaryVertices <- V.freeze _meshMutPrimaryVertices _meshHorizontalSecondary <- V.freeze _meshMutHorizSecondary _meshVerticalSecondary <- V.freeze _meshMutVertSecondary _meshTensorDerivatives <- case _meshMutTensorDerivatives of Nothing -> return Nothing Just v -> Just <$> V.freeze v _meshColors <- V.freeze _meshMutColors return MeshPatch { .. } -- | Retrieve a mesh primary vertice purely verticeAt :: MeshPatch px -> Int -- ^ Between 0 and _meshPatchWidth + 1 (excluded) -> Int -- ^ Between 0 and _meshPatchHeight + 1 (excluded) -> Point verticeAt m x y = _meshPrimaryVertices m ! idx where idx = y * (_meshPatchWidth m + 1) + x -- | Given an original MeshPatch, provide context to mutate it through -- modification functions. withMesh :: MeshPatch px -> (forall m. (MonadReader (MutableMesh (PrimState m) px) m, PrimMonad m) => m a) -> (a, MeshPatch px) withMesh mesh act = runST $ do mut <- thawMesh mesh v <- runReaderT act mut final <- freezeMesh mut return (v, final) -- | Set the vertice of a mesh at a given coordinate setVertice :: (MonadReader (MutableMesh (PrimState m) px) m, PrimMonad m) => Int -- ^ x coordinate in [0, w] -> Int -- ^ y coordinate in [0, h] -> Point -- ^ new point value -> m () setVertice x y p = do MutableMesh { .. } <- ask let idx = y * (_meshMutWidth + 1) + x MV.write _meshMutPrimaryVertices idx p -- | Get the position of vertice getVertice :: (MonadReader (MutableMesh (PrimState m) px) m, PrimMonad m) => Int -> Int -> m Point getVertice x y = do p <- ask let idx = y * (_meshMutWidth p + 1) + x MV.read (_meshMutPrimaryVertices p) idx -- | Set the two control bezier points horizontally setHorizPoints :: (MonadReader (MutableMesh (PrimState m) px) m, PrimMonad m) => Int -> Int -> InterBezier -> m () setHorizPoints x y p = do MutableMesh { .. } <- ask let idx = y * _meshMutWidth + x MV.write _meshMutHorizSecondary idx p -- | Set the two control bezier points vertically setVertPoints :: (MonadReader (MutableMesh (PrimState m) px) m, PrimMonad m) => Int -> Int -> InterBezier -> m () setVertPoints x y p = do MutableMesh { .. } <- ask let idx = y * (_meshMutWidth + 1) + x MV.write _meshMutVertSecondary idx p -- | Set the value associated to a vertex setColor :: (MonadReader (MutableMesh (PrimState m) px) m, PrimMonad m) => Int -> Int -> px -> m () setColor x y p = do MutableMesh { .. } <- ask let idx = y * (_meshMutWidth + 1) + x MV.write _meshMutColors idx p -- | Generate a meshpatch at the size given by the image and -- a number of cell in a mesh generateImageMesh :: Int -- ^ Horizontal cell count -> Int -- ^ Vertical cell count -> Point -- ^ Position of the corner upper left -> Image px -- ^ Image to transform through a mesh -> MeshPatch (ImageMesh px) generateImageMesh w h base img = generateLinearGrid w h base (V2 dx dy) infos where dx = fromIntegral (imageWidth img) / fromIntegral w dy = fromIntegral (imageHeight img) / fromIntegral h infos = V.fromListN ((w + 1) * (h + 1)) [ImageMesh img $ trans <> scaling | y <- [0 .. h] , x <- [0 .. w] , let fx = fromIntegral x fy = fromIntegral y trans = translate (V2 (fx * dx) (fy * dy)) scaling = scale dx dy] -- | Generate a valid gradient with the shape of a simple grid -- using some simple information. You can use `thawMesh` and `freezeMesh` -- to mutate it. generateLinearGrid :: Int -- ^ Width in patch -> Int -- ^ Height in patch -> Point -- ^ Position of the upper left corner -> V2 Float -- ^ Size of each patch in x adn y -> V.Vector px -- ^ Vector of values, size must be (width + 1) * (height + 1) -> MeshPatch px generateLinearGrid w h base (V2 dx dy) colors = MeshPatch { _meshPatchWidth = w , _meshPatchHeight = h , _meshPrimaryVertices = vertices , _meshHorizontalSecondary = hSecondary , _meshVerticalSecondary = vSecondary , _meshTensorDerivatives = Nothing , _meshColors = colors } where vertexCount = (w + 1) * (h + 1) vertices = V.fromListN vertexCount [base ^+^ V2 (dx * fromIntegral x) (dy * fromIntegral y) | y <- [0 .. h], x <- [0 .. w]] at x y = vertices ! (y * (w + 1) + x) hSecondary = V.fromListN ((h + 1) * w) [InterBezier (p0 ^+^ delta ^* (1/3)) (p0 ^+^ delta ^* (2/3)) | y <- [0 .. h], x <- [0 .. w - 1] , let p0 = at x y p1 = at (x + 1) y delta = p1 ^-^ p0 ] vSecondary = V.fromListN ((w + 1) * h) [InterBezier (p0 ^+^ delta ^* (1/3)) (p0 ^+^ delta ^* (2/3)) | y <- [0 .. h - 1], x <- [0 .. w] , let p0 = at x y p1 = at x (y + 1) delta = p1 ^-^ p0 ] type ColorPreparator px pxt = ParametricValues px -> pxt -- | Extract a coon patch at a given position. coonPatchAt :: MeshPatch px -> Int -- ^ x -> Int -- ^ y -> CoonPatch (ParametricValues px) coonPatchAt = coonPatchAt' id -- | Extract a tensor patch at a given position tensorPatchAt :: MeshPatch px -> Int -- ^ x -> Int -- ^ y -> TensorPatch (ParametricValues px) tensorPatchAt = tensorPatchAt' id -- | Extract an image patch out of a mesh at a given position. coonImagePatchAt :: MeshPatch (ImageMesh px) -> Int -- ^ x -> Int -- ^ y -> CoonPatch (ImageMesh px) coonImagePatchAt = coonPatchAt' _northValue -- | Extract a tensor image patch out of a mesh at -- a given position. tensorImagePatchAt :: MeshPatch (ImageMesh px) -> Int -- ^ x -> Int -- ^ y -> TensorPatch (ImageMesh px) tensorImagePatchAt = tensorPatchAt' _northValue -- | Extract a coon patch for cubic interpolation at a given position -- see `calculateMeshColorDerivative` coonPatchAtWithDerivative :: (InterpolablePixel px) => MeshPatch (Derivative px) -> Int -- ^ x -> Int -- ^ y -> CoonPatch (CubicCoefficient px) coonPatchAtWithDerivative = coonPatchAt' cubicPreparator -- | Extract a tensor patch for cubic interpolation at a given position -- see `calculateMeshColorDerivative` tensorPatchAtWithDerivative :: (InterpolablePixel px) => MeshPatch (Derivative px) -> Int -- ^ x -> Int -- ^ y -> TensorPatch (CubicCoefficient px) tensorPatchAtWithDerivative = tensorPatchAt' cubicPreparator rawMatrix :: V.Vector (V.Vector Float) rawMatrix = V.fromListN 16 $ V.fromListN 16 <$> [ [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] , [ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] , [-3, 3, 0, 0, -2,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] , [ 2,-2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] , [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ] , [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ] , [ 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2,-1, 0, 0 ] , [ 0, 0, 0, 0, 0, 0, 0, 0, 2,-2, 0, 0, 1, 1, 0, 0 ] , [-3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0, 0, 0, 0, 0 ] , [ 0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0 ] , [ 9,-9,-9, 9, 6, 3,-6,-3, 6,-6, 3,-3, 4, 2, 2, 1 ] , [-6, 6, 6,-6, -3,-3, 3, 3, -4, 4,-2, 2, -2,-2,-1,-1 ] , [ 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 ] , [ 0, 0, 0, 0, 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0 ] , [-6, 6, 6,-6, -4,-2, 4, 2, -3, 3,-3, 3, -2,-1,-2,-1 ] , [ 4,-4,-4, 4, 2, 2,-2,-2, 2,-2, 2,-2, 1, 1, 1, 1 ] ] cubicPreparator :: (InterpolablePixel px) => ParametricValues (Derivative px) -> CubicCoefficient px cubicPreparator ParametricValues { .. } = CubicCoefficient $ ParametricValues (sliceAt 0) (sliceAt 4) (sliceAt 8) (sliceAt 12) where Derivative c00 fx00 fy00 fxy00 = _northValue Derivative c10 fx10 fy10 fxy10 = _eastValue Derivative c01 fx01 fy01 fxy01 = _westValue Derivative c11 fx11 fy11 fxy11 = _southValue resultVector = mulVec $ V.fromListN 16 [ c00, c10, c01, c11 , fx00, fx10, fx01, fx11 , fy00, fy10, fy01, fy11 ,fxy00, fxy10, fxy01, fxy11 ] mulVec vec = VG.foldl' (^+^) zero . VG.zipWith (^*) vec <$> rawMatrix sliceAt i = V4 (resultVector V.! i) (resultVector V.! (i + 1)) (resultVector V.! (i + 2)) (resultVector V.! (i + 3)) tensorPatchAt' :: ColorPreparator px pxt -> MeshPatch px -> Int -> Int -> TensorPatch pxt tensorPatchAt' preparator mesh@MeshPatch { _meshTensorDerivatives = Nothing } x y = toTensorPatch $ coonPatchAt' preparator mesh x y tensorPatchAt' preparator mesh x y = TensorPatch { _curve0 = CubicBezier p00 p01 p02 p03 , _curve1 = CubicBezier p10 p11 p12 p13 , _curve2 = CubicBezier p20 p21 p22 p23 , _curve3 = CubicBezier p30 p31 p32 p33 , _tensorValues = preparator $ ParametricValues { _northValue = c00 , _eastValue = c03 , _southValue = c33 , _westValue = c30 } } where w = _meshPatchWidth mesh vertices = _meshPrimaryVertices mesh colors = _meshColors mesh hInter = _meshHorizontalSecondary mesh vInter = _meshVerticalSecondary mesh baseIx = (w + 1) * y + x p00 = vertices ! baseIx c00 = colors ! baseIx p03 = vertices ! (baseIx + 1) c03 = colors ! (baseIx + 1) p30 = vertices ! (baseIx + w + 1) c30 = colors ! (baseIx + w + 1) p33 = vertices ! (baseIx + w + 2) c33 = colors ! (baseIx + w + 2) baseH = w * y + x InterBezier p01 p02 = hInter ! baseH InterBezier p31 p32 = hInter ! (baseH + w) baseV = (w + 1) * y + x InterBezier p10 p20 = vInter ! baseV InterBezier p13 p23 = vInter ! (baseV + 1) Derivatives p11 p12 p21 p22 = case _meshTensorDerivatives mesh of Nothing -> error "Not a tensor patch" Just v -> v ! (w * y + x) coonPatchAt' :: ColorPreparator px pxt -> MeshPatch px -> Int -> Int -> CoonPatch pxt coonPatchAt' preparator mesh x y = CoonPatch { _north = CubicBezier p00 p01 p02 p03 , _east = CubicBezier p03 p13 p23 p33 , _south = CubicBezier p33 p32 p31 p30 , _west = CubicBezier p30 p20 p10 p00 , _coonValues = preparator $ ParametricValues { _northValue = c00 , _eastValue = c03 , _southValue = c33 , _westValue = c30 } } where w = _meshPatchWidth mesh vertices = _meshPrimaryVertices mesh colors = _meshColors mesh hInter = _meshHorizontalSecondary mesh vInter = _meshVerticalSecondary mesh baseIx = (w + 1) * y + x p00 = vertices ! baseIx c00 = colors ! baseIx p03 = vertices ! (baseIx + 1) c03 = colors ! (baseIx + 1) p30 = vertices ! (baseIx + w + 1) c30 = colors ! (baseIx + w + 1) p33 = vertices ! (baseIx + w + 2) c33 = colors ! (baseIx + w + 2) baseH = w * y + x InterBezier p01 p02 = hInter ! baseH InterBezier p31 p32 = hInter ! (baseH + w) baseV = (w + 1) * y + x InterBezier p10 p20 = vInter ! baseV InterBezier p13 p23 = vInter ! (baseV + 1) -- | Extract a list of all the coon patches of the mesh. coonPatchesOf :: MeshPatch px -> [CoonPatch (ParametricValues px)] coonPatchesOf mesh@MeshPatch { .. } = [coonPatchAt mesh x y | y <- [0 .. _meshPatchHeight - 1], x <- [0 .. _meshPatchWidth - 1]] -- | Extract a list of all the tensor patches of the mesh. tensorPatchesOf :: MeshPatch px -> [TensorPatch (ParametricValues px)] tensorPatchesOf mesh@MeshPatch { .. } = [tensorPatchAt mesh x y | y <- [0 .. _meshPatchHeight - 1], x <- [0 .. _meshPatchWidth - 1]] -- | Extract all the coon patch of a mesh using an image interpolation. imagePatchesOf :: MeshPatch (ImageMesh px) -> [CoonPatch (ImageMesh px)] imagePatchesOf mesh@MeshPatch { .. } = [coonImagePatchAt mesh x y | y <- [0 .. _meshPatchHeight - 1], x <- [0 .. _meshPatchWidth - 1]] -- | Extract all the tensor patch of a mesh using an image interpolation. tensorImagePatchesOf :: MeshPatch (ImageMesh px) -> [TensorPatch (ImageMesh px)] tensorImagePatchesOf mesh@MeshPatch { .. } = [tensorImagePatchAt mesh x y | y <- [0 .. _meshPatchHeight - 1], x <- [0 .. _meshPatchWidth - 1]] -- | Extract all the coon patch of a mesh using cubic interpolation. cubicCoonPatchesOf :: (InterpolablePixel px) => MeshPatch (Derivative px) -> [CoonPatch (CubicCoefficient px)] cubicCoonPatchesOf mesh@MeshPatch { .. } = [coonPatchAtWithDerivative mesh x y | y <- [0 .. _meshPatchHeight - 1] , x <- [0 .. _meshPatchWidth - 1] ] -- | Extract all the tensor patch of a mesh using cubic interpolation. cubicTensorPatchesOf :: (InterpolablePixel px) => MeshPatch (Derivative px) -> [TensorPatch (CubicCoefficient px)] cubicTensorPatchesOf mesh@MeshPatch { .. } = [tensorPatchAtWithDerivative mesh x y | y <- [0 .. _meshPatchHeight - 1] , x <- [0 .. _meshPatchWidth - 1] ]