gdritter repos hvif / master Graphics / HVIF.hs
master

Tree @master (Download .tar.gz)

HVIF.hs @master

99d3538
a3c0aca
dc983cb
99d3538
17a8bbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dc983cb
 
17a8bbd
99d3538
 
4959f25
99d3538
17a8bbd
99d3538
 
 
 
 
 
 
 
 
 
 
 
 
17a8bbd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dc983cb
 
 
17a8bbd
 
 
 
5bf503e
 
17a8bbd
 
 
 
 
 
 
 
 
dc983cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17a8bbd
 
99d3538
 
 
 
 
 
 
 
 
dc983cb
99d3538
 
 
 
 
17a8bbd
99d3538
 
 
 
 
 
 
 
 
 
dc983cb
99d3538
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4959f25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17a8bbd
4959f25
17a8bbd
4959f25
17a8bbd
4959f25
17a8bbd
4959f25
 
99d3538
 
 
 
 
 
4959f25
 
99d3538
 
 
dc983cb
 
 
 
99d3538
 
4959f25
 
 
5bf503e
 
99d3538
dc983cb
 
 
 
 
 
5bf503e
 
 
99d3538
 
 
 
 
 
 
 
 
 
 
 
4959f25
 
 
 
 
 
a3c0aca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dc983cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE MultiWayIf #-}

module Graphics.HVIF
(
-- * Decoding
  decodeFile
-- * Types
, HVIFFile(..)
-- ** Style Section
, Style(..)
, Gradient(..)
, GradientType(..)
, GradientFlags(..)
, GradientStop(..)
-- ** Paths Section
, Path(..)
, PathFlags(..)
, Point(..)
, Command(..)
-- ** Shape Section
, Shape(..)
, PathRef(..)
, StyleRef(..)
, ShapeFlags(..)
, Translation(..)
, LodScale(..)
) where

import           Control.Monad (replicateM, when)
import           Data.Bits ((.|.), (.&.), clearBit, shift, testBit)
import           Data.ByteString (ByteString)
import           Data.Sequence (Seq, (<|))
import qualified Data.Sequence as S
import           Data.Serialize
import           Data.Word

data HVIFFile = HVIFFile
  { hvifColors :: Seq Style
  , hvifPaths  :: Seq Path
  , hvifShapes :: Seq Shape
  } deriving (Eq, Show)

decodeFile :: ByteString -> Either String HVIFFile
decodeFile = runGet pFile

instance Serialize HVIFFile where
  get = pFile
  put = error "[unfinished]"

-- Style Section

data Style
  = ColorSolid Word8 Word8 Word8 Word8
  | ColorGradient Gradient
  | ColorSolidNoAlpha Word8 Word8 Word8
  | ColorSolidGray Word8 Word8
  | ColorSolidGrayNoAlpha Word8
    deriving (Eq, Show)

data Gradient = Gradient
  { gType  :: GradientType
  , gFlags :: GradientFlags
  , gStops :: Seq GradientStop
  } deriving (Eq, Show)

data GradientType
  = GTLinear
  | GTCircular
  | GTDiamond
  | GTConic
  | GTXY
  | GTSqrtXY
    deriving (Eq, Show)

data GradientFlags = GradientFlags
  { gfTransform :: Bool
  , gfNoAlpha   :: Bool
  , gf16Bit     :: Bool
  , gfGrays     :: Bool
  } deriving (Eq, Show)

data GradientStop = GradientStop
  { gsOffset :: Word8
  , gsRed    :: Word8
  , gsGreen  :: Word8
  , gsBlue   :: Word8
  , gsAlpha  :: Word8
  } deriving (Eq, Show)

-- Path Section

data Path = Path
  { pathFlags  :: PathFlags
  , pathPoints :: Seq Command
  } deriving (Eq, Show)

data PathFlags = PathFlags
  { pfClosed       :: Bool
  , pfUsesCommands :: Bool
  , pfNoCurves     :: Bool
  } deriving (Eq, Show)

data Point = Point
  { coordX :: Float
  , coordY :: Float
  } deriving (Eq, Show)

data Command
  = CmdHLine Float
  | CmdVLine Float
  | CmdLine Point
  | CmdCurve Point Point Point
    deriving (Eq, Show)

-- Shape Section

data Shape = Shape
  { shapeStyle     :: StyleRef
  , shapePaths     :: Seq PathRef
  , shapeFlags     :: ShapeFlags
  , shapeTransform :: Maybe Matrix
  , shapeTranslate :: Maybe Translation
  , shapeLodScale  :: Maybe LodScale
  , shapeTransList :: Seq Transformer
  } deriving (Eq, Show)

type Matrix = Seq Float

newtype PathRef = PathRef { prIdx :: Int } deriving (Eq, Show)
newtype StyleRef = StyleRef { stIdx :: Int } deriving (Eq, Show)

data ShapeFlags = ShapeFlags
  { sfTransform       :: Bool
  , sfHinting         :: Bool
  , sfLodScale        :: Bool
  , sfHasTransformers :: Bool
  , sfTranslation     :: Bool
  } deriving (Eq, Show)

data Translation = Translation
  { transX :: Float
  , transY :: Float
  } deriving (Eq, Show)

data LodScale = LodScale
  { lsMin :: Float
  , lsMax :: Float
  } deriving (Eq, Show)

data Transformer
  = TransformerAffine Matrix
  | TransformerContour Float Word8 Word8
  | TransformerPerspective -- Not fully supported, I think?
  | TransformerStroke Float Word8 Word8 Word8
    deriving (Eq, Show)

-- Decoding code

getSeveral :: Get a -> Get (Seq a)
getSeveral getter = do
  count <- getWord8
  S.fromList `fmap` replicateM (fromIntegral count) getter

pFile :: Get HVIFFile
pFile = do
  header <- getByteString 4
  when (header /= "ncif") $
    fail "Missing `ncif' header"
  hvifColors <- getSeveral pStyle
  hvifPaths  <- getSeveral pPath
  hvifShapes <- getSeveral pShape
  return HVIFFile { .. }

-- Style section

pStyle :: Get Style
pStyle = do
  sType <- getWord8
  case sType of
    0x01 -> ColorSolid <$> get <*> get <*> get <*> get
    0x02 -> ColorGradient <$> pGradient
    0x03 -> ColorSolidNoAlpha <$> get <*> get <*> get
    0x04 -> ColorSolidGray <$> get <*> get
    0x05 -> ColorSolidGrayNoAlpha <$> get
    _    -> getWord16be >> fail "invalid"

pGradient :: Get Gradient
pGradient = do
  gType  <- pGradientType
  gFlags <- pGradientFlags
  gStops <- getSeveral (pGradientStop gFlags)
  return Gradient { .. }

pGradientType :: Get GradientType
pGradientType = do
  gType <- getWord8
  case gType of
    00 -> return GTLinear
    01 -> return GTCircular
    02 -> return GTDiamond
    03 -> return GTConic
    04 -> return GTXY
    05 -> return GTSqrtXY
    _  -> fail ("Unknown gradient type: " ++ show gType)

pGradientFlags :: Get GradientFlags
pGradientFlags = do
  gFlags <- getWord8
  return $ GradientFlags
    { gfTransform = testBit gFlags 1
    , gfNoAlpha   = testBit gFlags 2
    , gf16Bit     = testBit gFlags 3
    , gfGrays     = testBit gFlags 4
    }

pGradientStop :: GradientFlags -> Get GradientStop
pGradientStop flags = do
  offset <- get
  (r, g, b) <-
    if gfGrays flags
      then do
        val <- get
        return (val, val, val)
      else do
        r <- get
        g <- get
        b <- get
        return (r, g, b)
  a <-
    if gfNoAlpha flags
      then return 0xff
      else get
  return $ GradientStop offset r g b a

-- Path Section

pPath :: Get Path
pPath = do
  pathFlags <- pPathFlags
  pathPoints <- pPoints pathFlags
  return Path { .. }

pPathFlags :: Get PathFlags
pPathFlags = do
  pFlags <- getWord8
  return $ PathFlags
    { pfClosed       = testBit pFlags 1
    , pfUsesCommands = testBit pFlags 2
    , pfNoCurves     = testBit pFlags 3
    }

pPoints :: PathFlags -> Get (Seq Command)
pPoints PathFlags { pfUsesCommands = False
                  , pfNoCurves = False } =
  getSeveral pCurveCommand
pPoints PathFlags { pfUsesCommands = False
                  , pfNoCurves = True } =
  getSeveral pLineCommand
pPoints PathFlags { pfUsesCommands = True } =
  pCommandList

pLineCommand :: Get Command
pLineCommand = CmdLine <$> (Point <$> pCoord <*> pCoord)

pCurveCommand :: Get Command
pCurveCommand = CmdCurve <$> (Point <$> pCoord <*> pCoord)
                         <*> (Point <$> pCoord <*> pCoord)
                         <*> (Point <$> pCoord <*> pCoord)

pCommandList :: Get (Seq Command)
pCommandList = do
  pointCount <- getWord8
  let cmdByteCount = (pointCount + 3) `div` 4
  cmdBytes <- replicateM (fromIntegral cmdByteCount) getWord8
  let go n
        | n == fromIntegral pointCount = return S.empty
        | otherwise =
            let bIdx = n `div` 4
                iIdx = (n `mod` 4) * 2
            in case (cmdBytes !! bIdx) `shift` (negate iIdx) .&. 0x03 of
                 0x00 ->
                   (<|) <$> (CmdHLine <$> pCoord) <*> go (n+1)
                 0x01 ->
                   (<|) <$> (CmdVLine <$> pCoord) <*> go (n+1)
                 0x02 ->
                   (<|) <$> pLineCommand <*> go (n+1)
                 0x03 ->
                   (<|) <$> pCurveCommand <*> go (n+1)
                 _ -> error "[unreachable]"
  go 0

pCoord :: Get Float
pCoord = do
  b1 <- getWord8
  if testBit b1 7 then do
    b2 <- getWord8
    let cVal :: Word16 = (clearBit (fromIntegral b1) 7 `shift` 8) .|. fromIntegral b2
    return (fromIntegral cVal / 102.0 - 128.0)
  else
    return (fromIntegral b1 - 32.0)

ifFlag :: Bool -> Get a -> Get (Maybe a)
ifFlag True  m = Just <$> m
ifFlag False _ = pure Nothing

pShape :: Get Shape
pShape = do
  sType <- getWord8
  when (sType /= 0x0a) $
    fail ("Unknown shape type: " ++ show sType)
  shapeStyle <- StyleRef . fromIntegral <$> getWord8
  shapePaths <- getSeveral ((PathRef . fromIntegral) <$> getWord8)
  shapeFlags <- pShapeFlags
  shapeTransform <- ifFlag (sfTransform shapeFlags) $
    pMatrix
  shapeTranslate <- ifFlag (sfTranslation shapeFlags) $
    Translation <$> pCoord <*> pCoord
  shapeLodScale <- ifFlag (sfLodScale shapeFlags) $
    pLodScale
  shapeTransList <- if (sfHasTransformers shapeFlags)
    then getSeveral pTransformer
    else return S.empty
  return Shape { .. }

pShapeFlags :: Get ShapeFlags
pShapeFlags = do
  sFlags <- getWord8
  return ShapeFlags
    { sfTransform       = testBit sFlags 1
    , sfHinting         = testBit sFlags 2
    , sfLodScale        = testBit sFlags 3
    , sfHasTransformers = testBit sFlags 4
    , sfTranslation     = testBit sFlags 5
    }

pMatrix :: Get Matrix
pMatrix = S.fromList `fmap` replicateM 6 pFloat

pFloat :: Get Float
pFloat = do
  b1 <- fromIntegral <$> getWord8
  b2 <- fromIntegral <$> getWord8
  b3 <- fromIntegral <$> getWord8
  let sVal :: Word32 = (b1 `shift` 16) .|. (b2 `shift` 8) .|. b3
      sMask = 0b100000000000000000000000 -- == 0x800000
      eMask = 0b011111100000000000000000 -- == 0x7e0000
      mMask = 0b000000011111111111111111 -- == 0x01ffff
      sign =  (sVal .&. sMask) `shift` (-23)
      expo = ((sVal .&. eMask) `shift` (-17)) - 32
      mant =  (sVal .&. mMask) `shift` 6
      val  = (sign `shift` 31) .|. ((expo + 127) `shift` 23) .|. mant
  if sVal == 0
    then return 0.0
    else castToFloat val

castToFloat :: Word32 -> Get Float
castToFloat w32 =
  let bs = encode w32
  in case runGet getFloat32be bs of
    Left err -> fail err
    Right x  -> return x

pLodScale :: Get LodScale
pLodScale = do
  minS <- fromIntegral <$> getWord8
  maxS <- fromIntegral <$> getWord8
  return LodScale
    { lsMin = minS / 63.75
    , lsMax = maxS / 63.75
    }

pTransformer :: Get Transformer
pTransformer = do
  tType <- getWord8
  case tType of
    20 -> TransformerAffine <$> pMatrix
    21 -> do
      width      <- fromIntegral <$> getWord8
      lineJoin   <- getWord8
      miterLimit <- getWord8
      return (TransformerContour (width - 128.0) lineJoin miterLimit)
    22 -> pure TransformerPerspective
    23 -> do
      width <- fromIntegral <$> getWord8
      lineOptions <- getWord8
      miterLimit  <- getWord8
      let lineJoin = lineOptions .&. 15
          lineCap  = lineOptions `shift` 4
      return (TransformerStroke (width - 128.0) lineJoin lineCap miterLimit)
    _ -> fail ("Unknown transformer type: " ++ show tType)