packages feed

JuicyPixels-blp-0.6.0.0: src/Codec/Picture/Blp/Internal/Data.hs

{-|
The structure of BLP1 and BLP2 files as the parser reads them, and of BLP1
files as the encoder writes them. See @docs/format.md@ in the source
repository for the full format.

'BlpCompression', 'BlpFormat', t'BlpEncodeOptions' and 'BlpEncodeError' are
re-exported from "Codec.Picture.Blp", where they belong to the stable API.

This is an internal module: its API may change between minor versions.
-}
module Codec.Picture.Blp.Internal.Data(
    BlpStruct(..)
  , blpCompression
  , mipChainLength
  , mipLevelsDownTo
  , BlpCompression(..)
  , BlpExt(..)
  , Blp2Struct(..)
  , blp2Compression
  , Blp2Content(..)
  , BlpFile(..)
  , BlpFormat(..)
  , BlpEncodeOptions(..)
  , BlpEncodeError(..)
  ) where

import Codec.Picture
import Data.ByteString (ByteString)
import Data.Vector (Vector)
import Data.Word
import GHC.Generics

import Codec.Picture.Blp.Internal.Dxt (DxtFormat(..))
import Codec.Picture.Blp.Types

-- | A parsed BLP1 file: the 156 byte header and the image data after it. The
-- content type (header dword @0x04@) follows from 'blpExt', see
-- 'blpCompression'.
data BlpStruct = BlpStruct {
  -- | Header dword @0x08@: alpha bits per pixel. 0, 1, 4 and 8 are valid for
  -- paletted content, 0 and 8 for JPEG; any other value means no alpha.
  blpAlphaBits :: !Word32
  -- | Width of mip 0 in pixels.
, blpWidth :: !Word32
  -- | Height of mip 0 in pixels.
, blpHeight :: !Word32
  -- | Header dword @0x14@. It has no known effect and is kept as read.
, blpExtra :: !Word32
  -- | Header dword @0x18@: zero means only mip 0 is present.
, blpHasMipmaps :: !Word32
  -- | Everything after the header, including the mip levels.
, blpExt :: BlpExt
} deriving (Show, Generic)

-- | The part of the file after the header.
data BlpExt =
    -- | JPEG content. Each mip level is @blpJpegHeader <> level@, a
    -- four-component JPEG whose components are B, G, R and A.
    BlpJpeg {
      blpJpegHeader :: !ByteString -- ^ Shared JPEG header, at most 624 bytes
    , blpJpegData :: ![ByteString] -- ^ Rest of each mip level, mip 0 first
    }
    -- | Paletted content. Each mip level holds the index list followed by the
    -- alpha list, packed at 'blpAlphaBits' bits per pixel; the level's size
    -- tells where one ends and the other begins.
  | BlpPaletted {
      blpPalette :: !(Vector PixelRGBA8) -- ^ 256 entries stored as B, G, R and padding
    , blpPalettedMipMaps :: ![ByteString] -- ^ Raw data of each mip level, mip 0 first
    }
  deriving (Show, Generic)

-- | A parsed BLP2 file, the World of Warcraft variant: the 148 byte header,
-- the palette and the raw mip levels.
data Blp2Struct = Blp2Struct {
  -- | Header byte @0x09@: alpha bits per pixel. 0 means the image is
  -- opaque, whatever the content holds.
  blp2AlphaDepth :: !Word8
  -- | Header byte @0x0A@: which DXT format, for DXT content (0, 1 or 7);
  -- kept as read.
, blp2AlphaType :: !Word8
  -- | Header byte @0x0B@: zero means only mip 0 is present. Kept as read;
  -- the slots decide which levels are read, as in BLP1.
, blp2HasMips :: !Word8
  -- | Width of mip 0 in pixels.
, blp2Width :: !Word32
  -- | Height of mip 0 in pixels.
, blp2Height :: !Word32
  -- | How the levels are stored, from header bytes @0x08@ and @0x0A@.
, blp2Content :: !Blp2Content
  -- | Raw data of each mip level, mip 0 first.
, blp2MipMaps :: ![ByteString]
} deriving (Show, Generic)

-- | The content of a BLP2 file.
data Blp2Content =
    -- | Encoding 1: palette indices then alpha, as in paletted BLP1. 256
    -- entries stored as B, G, R and padding.
    Blp2Paletted !(Vector PixelRGBA8)
    -- | Encoding 2: DXT blocks.
  | Blp2Dxt !DxtFormat
    -- | Encoding 3: four bytes per pixel, B, G, R and A.
  | Blp2Bgra
  deriving (Show, Generic)

-- | How a BLP2 file stores its pixels.
blp2Compression :: Blp2Struct -> BlpCompression
blp2Compression blp = case blp2Content blp of
  Blp2Paletted _ -> BlpCompressionUncompressed
  Blp2Dxt (Dxt1 _) -> BlpCompressionDxt1
  Blp2Dxt Dxt3 -> BlpCompressionDxt3
  Blp2Dxt Dxt5 -> BlpCompressionDxt5
  Blp2Bgra -> BlpCompressionBgra

-- | A parsed BLP1 or BLP2 file.
data BlpFile = BlpFile1 !BlpStruct | BlpFile2 !Blp2Struct
  deriving (Show, Generic)

-- | Header dword @0x04@ (@content@ in the specification) of a file.
blpCompression :: BlpStruct -> BlpCompression
blpCompression blp = case blpExt blp of
  BlpJpeg {} -> BlpCompressionJPEG
  BlpPaletted {} -> BlpCompressionUncompressed


-- | Number of levels in a full mip chain: halving the larger side down to 1,
-- at most 16.
mipChainLength :: Int -> Int -> Int
mipChainLength = mipLevelsDownTo 1

-- | Number of mip levels of a @w@ x @h@ texture, mip 0 included, whose
-- larger side is at least the given length (taken as 1 when smaller), at
-- most 16. Zero when the texture itself is smaller.
mipLevelsDownTo :: Int -> Int -> Int -> Int
mipLevelsDownTo minSide w h = min 16 . length . takeWhile (>= max 1 minSide) $ iterate (`div` 2) (max w h)