ktx-codec-0.0.2.0: src/Codec/Ktx2/DFD/Khronos/BasicV2.hs
module Codec.Ktx2.DFD.Khronos.BasicV2 where
import Data.Binary (Binary(..))
import Data.Binary.Get (Get, getWord8, getWord16le, getWord32le, runGetOrFail)
import Data.Binary.Put (PutM, putWord8, putWord16le, putWord32le, runPut)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BSL
import Data.ByteString.Lazy qualified as LBS
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import Data.Word (Word8, Word16, Word32)
import GHC.Generics (Generic)
import Codec.Ktx2.DFD qualified as DFD
-- | Khronos
pattern VENDOR_ID :: (Eq a, Num a) => a
pattern VENDOR_ID = 0
-- | Basic DFD Block
pattern DESCRIPTOR_TYPE :: (Eq a, Num a) => a
pattern DESCRIPTOR_TYPE = 0
-- | KDF v1.3
pattern VERSION :: (Eq a, Num a) => a
pattern VERSION = 2
{- |
A basic descriptor block is designed to encode common metadata associated with bulk data — especially image or texture data.
While this descriptor holds more information about the data interpretation than is needed by many applications,
a comprehensive encoding reduces the risk of metadata needed by different APIs being lost in translation.
The format is described in terms of a repeating axis-aligned texel block composed of samples.
Each sample contains a single channel of information with a single spatial offset within the texel block,
and consists of an amount of contiguous data. This descriptor block consists of information about the interpretation
of the texel block as a whole, supplemented by a description of a number of samples taken from one or more planes of contiguous memory.
<https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html>
-}
data BasicV2 = BasicV2
{ colorModel :: Word8
, colorPrimaries :: Word8
, transferFunction :: Word8
, flags :: Word8
, texelBlockDimension0 :: Word8
{- ^
The value held in each of these fields is one fewer than the size of the block in that dimension —
that is, a value of 0 represents a size of 1, a value of 1 represents a size of 2, etc.
A texel block which covers fewer than four dimensions should have a size of 1 in each dimension
that it lacks, and therefore the corresponding fields in the representation should be 0.
-}
, texelBlockDimension1 :: Word8
, texelBlockDimension2 :: Word8
, texelBlockDimension3 :: Word8
, bytesPlane0 :: Word8
, bytesPlane1 :: Word8
, bytesPlane2 :: Word8
, bytesPlane3 :: Word8
, bytesPlane4 :: Word8
, bytesPlane5 :: Word8
, bytesPlane6 :: Word8
, bytesPlane7 :: Word8
, samples :: Vector Sample
}
deriving (Eq, Show, Generic)
getter :: Int -> Get BasicV2
getter numSamples = do
colorModel <- getWord8
colorPrimaries <- getWord8
transferFunction <- getWord8
flags <- getWord8
texelBlockDimension0 <- fmap (+ 1) getWord8
texelBlockDimension1 <- fmap (+ 1) getWord8
texelBlockDimension2 <- fmap (+ 1) getWord8
texelBlockDimension3 <- fmap (+ 1) getWord8
bytesPlane0 <- getWord8
bytesPlane1 <- getWord8
bytesPlane2 <- getWord8
bytesPlane3 <- getWord8
bytesPlane4 <- getWord8
bytesPlane5 <- getWord8
bytesPlane6 <- getWord8
bytesPlane7 <- getWord8
samples <- Vector.replicateM numSamples get
pure BasicV2{..}
putter :: BasicV2 -> PutM ()
putter BasicV2{..} = do
putWord8 colorModel
putWord8 colorPrimaries
putWord8 transferFunction
putWord8 flags
putWord8 $ texelBlockDimension0 - 1
putWord8 $ texelBlockDimension1 - 1
putWord8 $ texelBlockDimension2 - 1
putWord8 $ texelBlockDimension3 - 1
putWord8 bytesPlane0
putWord8 bytesPlane1
putWord8 bytesPlane2
putWord8 bytesPlane3
putWord8 bytesPlane4
putWord8 bytesPlane5
putWord8 bytesPlane6
putWord8 bytesPlane7
Vector.mapM_ put samples
data Sample = Sample
{ bitOffset :: Word16
, bitLength :: Word8
, channelType :: Word8
, samplePosition0 :: Word8
, samplePosition1 :: Word8
, samplePosition2 :: Word8
, samplePosition3 :: Word8
, sampleLower :: Word32
, sampleUpper :: Word32
}
deriving (Eq, Show, Generic)
instance Binary Sample where
get = do
bitOffset <- getWord16le
bitLength <- fmap (+1) getWord8
channelType <- getWord8
samplePosition0 <- getWord8
samplePosition1 <- getWord8
samplePosition2 <- getWord8
samplePosition3 <- getWord8
sampleLower <- getWord32le
sampleUpper <- getWord32le
pure Sample{..}
put Sample{..} = do
putWord16le bitOffset
putWord8 $ bitLength - 1
putWord8 channelType
putWord8 samplePosition0
putWord8 samplePosition1
putWord8 samplePosition2
putWord8 samplePosition3
putWord32le sampleLower
putWord32le sampleUpper
fromBlock :: DFD.Block -> Maybe BasicV2
fromBlock DFD.Block{descriptorBlockSize, body} =
case runGetOrFail (getter numSamples) (LBS.fromChunks [body]) of
Left _err ->
Nothing
Right (_bytes, _offset, ok) ->
Just ok
where
numSamples =
div (fromIntegral descriptorBlockSize - 24) 16
toBlock :: BasicV2 -> DFD.Block
toBlock v2 =
DFD.Block
{ descriptorType =
DESCRIPTOR_TYPE
, vendorId =
VENDOR_ID
, versionNumber =
VERSION
, descriptorBlockSize =
fromIntegral $ BS.length body + 8
, body =
body
}
where
body = BSL.toStrict . runPut $ putter v2