packages feed

JuicyPixels-1.3: Codec/Picture/Gif/Writer.hs

{-# LANGUAGE OverloadedStrings #-}

module Writer where
import GifImage
import LZW
import Data.Bits
import Control.Monad (liftM)
import qualified Data.ByteString as B
import qualified Data.Vector as V
import Data.ByteString.Char8 hiding (head, concat, map)
import Data.Binary.BitPut
import Data.ByteString.Lazy (toChunks)

putWord8 :: Int -> BitPut
putWord8 = putNBits 8

putDescriptor :: ScreenDescriptor -> BitPut
putDescriptor ds = do
  putWord8 (screenWidth ds)
  putWord8 (screenWidth ds `shiftR` 8)
  putWord8 (screenHeight ds)
  putWord8 (screenHeight ds `shiftR` 8)
  putBit   $ hasGlobalMap ds
  putNBits 3 (bitsOfColorResolution ds - 1)
  putBit   False
  putNBits 3 (screenBitsPerPixel ds - 1)
  putWord8 (backgroundIndex ds)
  putWord8 0

putGlobalMap :: ColorMap -> BitPut
putGlobalMap cm = do
    sequence_ $ putColor `liftM` V.toList cm
  where
    putColor (r,g,b) = do
      putWord8 $ fromIntegral r
      putWord8 $ fromIntegral g
      putWord8 $ fromIntegral b


putImage :: ScreenDescriptor -> Image -> BitPut
putImage _ (descriptor, Just map, raster) = do
  putImageDescriptor $ descriptor
  putGlobalMap $ map
  putRasterData raster $ bitsPerPixel descriptor
putImage ds (descriptor, Nothing, raster) = do
  putImageDescriptor $ descriptor
  putRasterData raster $ screenBitsPerPixel ds

putImageDescriptor :: ImageDescriptor -> BitPut
putImageDescriptor ds = do
  putWord8 0x2c
  putWord8 $ pixelsFromLeft ds
  putWord8 $ pixelsFromLeft ds `shiftR` 8
  putWord8 $ pixelsFromTop ds
  putWord8 $ pixelsFromTop ds `shiftR` 8
  putWord8 $ imageWidth ds
  putWord8 $ imageWidth ds `shiftR` 8
  putWord8 $ imageHeight ds
  putWord8 $ imageHeight ds `shiftR` 8
  putBit   $ hasLocalMap ds
  putBit   $ interlaced ds
  putNBits 3 (0 :: Int)
  putNBits 3 (bitsPerPixel ds - 1)


putRasterData :: Raster -> Int -> BitPut
putRasterData raster rootSize = do
    let intList = map fst $ concat .V.toList $ V.map V.toList $ raster :: [Int]
    let lzw = lzwEncode rootSize intList :: ByteString
    putWord8 rootSize
    putBlocks lzw
  where
    putBlocks :: ByteString -> BitPut
    putBlocks arr
      | B.null arr = putWord8 0
      | otherwise =
          let bytes  = B.take 255 arr
              actual = B.length bytes
          in do
            putWord8 actual
            putByteString bytes
            putBlocks (B.drop actual arr)

putGifTerminator :: BitPut
putGifTerminator = do
  putWord8 0x3b

gifWriter :: GifImage -> BitPut
gifWriter image = do
  putByteString "GIF87a"
  putDescriptor $ gifScreenDescriptor image
  putGlobalMap  $ gifGlobalMap image
  putImage (gifScreenDescriptor image) `mapM_` images image
  putGifTerminator

runGifWriter :: GifImage -> ByteString
runGifWriter image =
  B.concat . toChunks $ runBitPut (gifWriter image)