atlas-0: src/Data/Atlas.hs
{-# language TypeOperators #-}
{-# language ScopedTypeVariables #-}
{-# language UndecidableInstances #-}
{-# language MultiParamTypeClasses #-}
{-# language FlexibleInstances #-}
{-# language TemplateHaskell #-}
{-# language QuasiQuotes #-}
{-# language ViewPatterns #-}
{-# language BlockArguments #-}
-- |
-- Copyright : (c) 2019-2021 Edward Kmett
-- License : BSD-2-Clause OR Apache-2.0
-- Maintainer: Edward Kmett <ekmett@gmail.com>
-- Stability : experimental
-- Portability: non-portable
--
-- skyline packing using @stb_rect_pack.h@
module Data.Atlas
( Atlas
, create
, createExplicit
, reset
-- * Setup
, Heuristic(..)
, setHeuristic
, setAllowOutOfMem
-- * Using a context
, Pt(..)
, pack
, pack1
, packM
) where
import Control.Lens
import Control.Monad
import Control.Monad.Primitive
import Control.Monad.Trans.State.Strict
import Data.Atlas.Internal
import Data.Maybe (fromMaybe)
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc
import Foreign.Ptr
import GHC.Stack
import qualified Language.C.Inline as C
import qualified Language.C.Inline.Unsafe as CU
C.context $ C.baseCtx <> C.fptrCtx <> atlasCtx
C.verbatim "#define STB_RECT_PACK_IMPLEMENTATION"
C.include "stb_rect_pack.h"
-- | Create a packing context.
create :: (HasCallStack, PrimMonad m) => Int -> Int -> m (Atlas (PrimState m))
create w h = createExplicit w h Nothing
-- | Initialization with an optional node count, when @node count < width@ is used this results in quantization unless
-- 'setAllowOutOfMem' is enabled. When no value is supplied, it defaults to the width of the 'Atlas'.
createExplicit :: HasCallStack => PrimMonad m => Int -> Int -> Maybe Int -> m (Atlas (PrimState m))
createExplicit width@(fromIntegral -> w) height@(fromIntegral -> h) mn = withFrozenCallStack $ unsafeIOToPrim do
let nodes@(fromIntegral -> n) = fromMaybe width mn
unless (width < 0xffff && height < 0xffff) $ die $ "Atlas.new " ++ show width ++ " " ++ show height ++ ": atlas too large"
fp <- mallocForeignPtrBytes (sizeOfAtlas + sizeOfNode * nodes)
Atlas fp <$ [CU.block|void {
stbrp_context * p = $atlas:fp;
stbrp_init_target(p,$(int w),$(int h),(stbrp_node *)(p+1),$(int n));
}|]
-- | Reinitialize an atlas with the same parameters
reset :: PrimMonad m => Atlas (PrimState m) -> m ()
reset atlas = unsafeIOToPrim
[CU.block| void {
stbrp_context * p = $atlas:atlas;
int heuristic = p->heuristic;
int align = p->align;
stbrp_init_target(p,p->width,p->height,(stbrp_node*)(p+1),p->num_nodes);
p->heuristic = heuristic;
p->align = align;
}|]
setHeuristic :: PrimMonad m => Atlas (PrimState m) -> Heuristic -> m ()
setHeuristic fp (heuristicId -> h) = unsafeIOToPrim [CU.block|void { stbrp_setup_heuristic($atlas:fp,$(int h)); }|]
setAllowOutOfMem :: PrimMonad m => Atlas (PrimState m) -> Bool -> m ()
setAllowOutOfMem fp (fromIntegral . fromEnum -> b) = unsafeIOToPrim [CU.block|void { stbrp_setup_allow_out_of_mem($atlas:fp,$(int b)); }|]
pack
:: (PrimMonad m, Traversable f)
=> Atlas (PrimState m) -- ^ The atlas you want to pack these rectangles into.
-> (a -> Pt) -- ^ for each item you want to pack, extract the size.
-> (Maybe Pt -> a -> b) -- ^ when some fail to pack this will be called, with
-- 'Just' a position for each that succeeded, and
-- 'Nothing' for any that it failed on. The successes
-- and failures may well be out of order.
-> (Pt -> a -> c) -- ^ when all succeed this will be called with each position.
-> f a -- ^ A container full of things that you'd like to pack into the atlas.
-> m (Either (f b) (f c)) -- ^ Either a mixture of successes and failures, or a successful pack.
pack atlas f g h as = stToPrim $ packM atlas (pure . f) g h as
packM
:: (PrimBase m, Traversable f)
=> Atlas (PrimState m) -- ^ The 'Atlas' you want to pack these rectangles into.
-> (a -> m Pt) -- ^ for each item you want to pack, extract the size,
-- with effects in @m@
-> (Maybe Pt -> a -> b) -- ^ when some fail to pack this will be called, with
-- 'Just' a position for each that succeeded, and
-- 'Nothing' for any that it failed on. The successes
-- and failures may well be out of order.
-> (Pt -> a -> c) -- ^ when all succeed, this will be called with each position.
-> f a -- ^ A container full of things that you'd like to pack into the atlas.
-> m (Either (f b) (f c)) -- ^ 'Either' a mixture of successes and failures, or a successful pack.
packM fc f g h as = unsafeIOToPrim do
let n = length as
let cn = fromIntegral n
allocaBytes (n*sizeOfRect) \ rs -> do
iforOf_ folded as \i a -> do
p <- unsafePrimToIO $ f a
pokeWH (plusPtr rs (i*sizeOfRect)) p
res <- [CU.exp|int { stbrp_pack_rects($atlas:fc,$(stbrp_rect *rs),$(int cn)) }|]
if res == 0
then Left <$> evalStateT (traverse (go peekMaybeXY g) as) rs -- partial
else Right <$> evalStateT (traverse (go peekXY h) as) rs -- all allocated
where
go :: (Ptr Rect -> IO u) -> (u -> a -> d) -> a -> StateT (Ptr Rect) IO d
go k gh a = StateT \p -> (\b -> (,) (gh b a) $! plusPtr p sizeOfRect) <$> k p
{-# inline go #-}
-- | Add one rectangle to the 'Atlas'. Using 'pack' can yield significantly better
-- packing than calling this one rectangle at a time.
pack1 :: PrimMonad m => Atlas (PrimState m) -> Pt -> m (Maybe Pt)
pack1 atlas p = stToPrim $ either runIdentity runIdentity <$> pack atlas id const (const . Just) (Identity p)