{-# LANGUAGE PatternGuards #-}
{-# OPTIONS_HADDOCK hide #-}
module Codec.BMP.BitmapInfoV3
	( BitmapInfoV3	(..)
	, Compression (..)
	, sizeOfBitmapInfoV3
	, checkBitmapInfoV3
        , imageSizeFromBitmapInfoV3)
where
import Codec.BMP.Error
import Codec.BMP.Compression
import Data.Binary
import Data.Binary.Get	
import Data.Binary.Put
import Data.Int
import Debug.Trace


-- | Device Independent Bitmap (DIB) header for Windows V3.
data BitmapInfoV3
	= BitmapInfoV3			
	{ -- | (+0) Size of the image header, in bytes.
	  dib3Size		:: Word32

	  -- | (+4) Width of the image, in pixels.
	, dib3Width		:: Word32
	
	  -- | (+8) Height of the image, in pixels.
	, dib3Height		:: Word32
	
          -- | If the height field in the file is negative then this is interpreted
          --   as an image with the rows flipped.
        , dib3HeightFlipped     :: Bool

	  -- | (+12) Number of color planes.
	, dib3Planes		:: Word16

	  -- | (+14) Number of bits per pixel.
	, dib3BitCount		:: Word16

	  -- | (+16) Image compression mode.
	, dib3Compression	:: Compression

	  -- | (+20) Size of raw image data.
	  --   Some encoders set this to zero, so we need to calculate it based
          --   on the overall file size.
	  -- 
	  --   If it is non-zero then we check it matches the file size - header
          --   size.
	, dib3ImageSize		:: Word32

	  -- | (+24) Prefered resolution in pixels per meter, along the X axis.
	, dib3PelsPerMeterX	:: Word32

	  -- | (+28) Prefered resolution in pixels per meter, along the Y axis.
	, dib3PelsPerMeterY	:: Word32

	  -- | (+32) Number of color entries that are used.
	, dib3ColorsUsed	:: Word32

	  -- | (+36) Number of significant colors.
	, dib3ColorsImportant	:: Word32
	}
	deriving (Show)


-- | Size of `BitmapInfoV3` header (in bytes)
sizeOfBitmapInfoV3 :: Int
sizeOfBitmapInfoV3 = 40


instance Binary BitmapInfoV3 where
 get
  = do	size	  <- getWord32le
	width	  <- getWord32le

        -- We're supposed to treat the height field as a signed integer.
        -- If it's negative it means the image is flipped along the X axis.
        -- (which is crazy, but we just have to eat it)
	heightW32 <- getWord32le
        let heightI32 = (fromIntegral heightW32 :: Int32)
        let (height, flipped)
                = if heightI32 < 0
                        then (fromIntegral (abs heightI32), True)
                        else (heightW32,                      False)

	planes	<- getWord16le
	bitc	<- getWord16le
	comp	<- get
	imgsize	<- getWord32le
	pelsX	<- getWord32le
	pelsY	<- getWord32le
	cused	<- getWord32le
	cimp	<- getWord32le
	
	return	$ BitmapInfoV3
		{ dib3Size		= size
		, dib3Width		= width
		, dib3Height		= height
                , dib3HeightFlipped     = flipped
		, dib3Planes		= planes
		, dib3BitCount		= bitc
		, dib3Compression	= comp
		, dib3ImageSize		= imgsize
		, dib3PelsPerMeterX	= pelsX
		, dib3PelsPerMeterY	= pelsY
		, dib3ColorsUsed	= cused
		, dib3ColorsImportant	= cimp }

 put header
  = do	putWord32le 	$ dib3Size header
	putWord32le	$ dib3Width header
	putWord32le	$ dib3Height header
	putWord16le	$ dib3Planes header
	putWord16le	$ dib3BitCount header
	put		$ dib3Compression header
	putWord32le	$ dib3ImageSize header
	putWord32le	$ dib3PelsPerMeterX header
	putWord32le	$ dib3PelsPerMeterY header
	putWord32le	$ dib3ColorsUsed header
	putWord32le	$ dib3ColorsImportant header
	
		
-- | Check headers for problems and unsupported features.	 
checkBitmapInfoV3 :: BitmapInfoV3 -> Word32 -> Maybe Error
checkBitmapInfoV3 header physicalBufferSize

        -- We only handle a single color plane.
	| dib3Planes header /= 1
	= Just	$ ErrorUnhandledPlanesCount $ dib3Planes header
	
        -- We only handle 24 and 32 bit images.
	| dib3BitCount header /= 24
        , dib3BitCount header /= 32
	= Just 	$ ErrorUnhandledColorDepth $ dib3BitCount header

        -- If the image size field in the header is non-zero, 
        -- then it must be less than the physical size of the image buffer.
        --  The buffer may be larger than the size of the image stated
        --  in the header, because some encoders add padding to the end.
        | headerImageSize               <- dib3ImageSize header
        , headerImageSize /= 0
        , physicalBufferSize              < headerImageSize
        = Just  $ ErrorImagePhysicalSizeMismatch
                        headerImageSize physicalBufferSize

        -- Check that the physical buffer contains enough image data.
        --  The buffer may be larger than the size of the image stated
        --  in the header, because some encoders add padding to the end.
        | Just calculatedImageSize      <- imageSizeFromBitmapInfoV3 header
        , fromIntegral physicalBufferSize < calculatedImageSize
        = trace (show header)
        $ Just  $ ErrorImageDataTruncated 
                        calculatedImageSize
                        (fromIntegral physicalBufferSize)

        -- We only handle uncompresssed images.
        |   dib3Compression header /= CompressionRGB
         && dib3Compression header /= CompressionBitFields
        = Just  $ ErrorUnhandledCompressionMode (dib3Compression header)

	| otherwise
	= Nothing
	

-- | Compute the size of the image data from the header.
--
--   * We can't just use the 'dib3ImageSize' field because some encoders
--     set this to zero.
--
--   * We also can't use the physical size of the data in the file because
--     some encoders add zero padding bytes on the end.   
--
imageSizeFromBitmapInfoV3 :: BitmapInfoV3 -> Maybe Int
imageSizeFromBitmapInfoV3 header
        | dib3BitCount    header == 32
        , dib3Planes      header == 1
        ,   dib3Compression header == CompressionRGB
         || dib3Compression header == CompressionBitFields
        = Just $ fromIntegral (dib3Width header * dib3Height header * 4)

        | dib3BitCount    header == 24
        , dib3Planes      header == 1
        ,   dib3Compression header == CompressionRGB
         || dib3Compression header == CompressionBitFields
        = let   imageBytesPerLine = dib3Width header * 3
                tailBytesPerLine  = imageBytesPerLine `mod` 4
                padBytesPerLine   = if tailBytesPerLine > 0
                                        then 4 - tailBytesPerLine
                                        else 0
          in    Just $ fromIntegral 
                     $ dib3Height header * imageBytesPerLine + padBytesPerLine

        | otherwise
        = trace (show header) $ Nothing