{-# LANGUAGE TemplateHaskell, TypeFamilies, ExistentialQuantification, TypeOperators, ScopedTypeVariables, TupleSections #-} module Data.Bitmap.StringRGB32.Internal ( BitmapImageString(..) , BitmapStringRGB32(..), bmps_dimensions, bmps_data , encodeIBF_RGB32' , tryIBF_RGB32' , padByte ) where import Control.Applicative import Control.Arrow import Control.Monad.Record import Data.Binary import Data.Bitmap.Class import Data.Bitmap.Pixel import Data.Bitmap.Reflectable import Data.Bitmap.Searchable import Data.Bitmap.Types import Data.Bitmap.Util hiding (padByte) import Data.Bits import qualified Data.ByteString as B import qualified Data.Serialize as S import qualified Data.String.Class as S import Data.Tagged import Text.Printf -- | Polymorphic container of a string data BitmapImageString = forall s. (S.StringCells s) => BitmapImageString {_polyval_bitmapImageString :: s} instance Eq BitmapImageString where a == b = case (a, b) of ((BitmapImageString sa), (BitmapImageString sb)) -> S.toStrictByteString sa == S.toStrictByteString sb a /= b = case (a, b) of ((BitmapImageString sa), (BitmapImageString sb)) -> S.toStrictByteString sa /= S.toStrictByteString sb -- | A bitmap represented as a string, which contains a series of aligned rows, which themselves consist of a series of pixels stored in 4 bytes in which the most significant byte is unused (thus the rows are always aligned to a four-byte boundary) data BitmapStringRGB32 = BitmapStringRGB32 { _bmps_dimensions :: (Int, Int) -- ^ Width and height of the bitmap , _bmps_data :: BitmapImageString -- ^ Data stored in a string } mkLabels [''BitmapStringRGB32] instance Binary BitmapStringRGB32 where get = pure BitmapStringRGB32 <*> get <*> (BitmapImageString <$> (get :: Get B.ByteString)) put b = put (bmps_dimensions <: b) >> put (case bmps_data <: b of (BitmapImageString s) -> S.toLazyByteString s) instance S.Serialize BitmapStringRGB32 where get = pure BitmapStringRGB32 <*> S.get <*> (BitmapImageString <$> (S.get :: S.Get B.ByteString)) put b = S.put (bmps_dimensions <: b) >> S.put (case bmps_data <: b of (BitmapImageString s) -> S.toLazyByteString s) instance Bitmap BitmapStringRGB32 where type BIndexType BitmapStringRGB32 = Int type BPixelType BitmapStringRGB32 = PixelRGB depth = const Depth24RGB dimensions = (bmps_dimensions <:) getPixel b (row, column) = let (width, _) = bmps_dimensions <: b bytesPixel = 4 bytesRow = 4 * width offset = bytesRow * row + bytesPixel * column in case bmps_data <: b of (BitmapImageString s) -> PixelRGB $ ((fromIntegral . S.toWord8 $ s `S.index` (offset + 1)) `shiftL` 16) .|. ((fromIntegral . S.toWord8 $ s `S.index` (offset + 2)) `shiftL` 8) .|. ((fromIntegral . S.toWord8 $ s `S.index` (offset + 3))) constructPixels f dms@(width, height) = BitmapStringRGB32 dms . (BitmapImageString :: B.ByteString -> BitmapImageString) $ S.unfoldrN (4 * width * height) getComponent (0 :: Int, 0 :: Int, 0 :: Int) where getComponent (row, column, orgb) | orgb > 3 = getComponent (row, succ column, 0) | column > maxColumn = getComponent (succ row, 0, 0) | row > maxRow = Nothing | otherwise = let pixel = f (row, column) componentGetter = case orgb of 0 -> const padCell 1 -> untag' . S.toMainChar . (red <:) 2 -> untag' . S.toMainChar . (green <:) 3 -> untag' . S.toMainChar . (blue <:) _ -> undefined in Just (componentGetter pixel, (row, column, succ orgb)) maxRow = abs . pred $ height maxColumn = abs . pred $ width padCell = untag' . S.toMainChar $ padByte untag' = untag :: Tagged B.ByteString a -> a imageEncoders = updateIdentifiableElements (map (second unwrapGenericBitmapSerializer) defaultImageEncoders) $ [ (IBF_RGB32, ImageEncoder $ encodeIBF_RGB32') ] imageDecoders = updateIdentifiableElements (map (second unwrapGenericBitmapSerializer) defaultImageDecoders) $ [ (IBF_RGB32, ImageDecoder $ tryIBF_RGB32') ] encodeIBF_RGB32' :: (S.StringCells s) => BitmapStringRGB32 -> s encodeIBF_RGB32' b = case (bmps_data <: b) of (BitmapImageString s) -> S.fromStringCells s tryIBF_RGB32' :: (S.StringCells s) => BitmapStringRGB32 -> s -> Either String BitmapStringRGB32 tryIBF_RGB32' bmp s | S.length s < minLength = Left $ printf "Data.Bitmap.StringRGB32.Internal.tryIBF_RGB32': string is too small to contain the pixels of a bitmap with the dimensions of the passed bitmap, which are (%d, %d); the string is %d bytes long, but needs to be at least %d bytes long" (fromIntegral width :: Integer) (fromIntegral height :: Integer) (S.length s) minLength | otherwise = Right $ (bmps_data =: BitmapImageString s) bmp where (width, height) = bmps_dimensions <: bmp minLength = 4 * width * height padByte :: Word8 padByte = 0x00 instance BitmapSearchable BitmapStringRGB32 where findSubBitmapEqual super sub = case (bmps_data <: super, bmps_data <: sub) of ((BitmapImageString dataSuper), (BitmapImageString dataSub)) -> let (widthSuper, heightSuper) = bmps_dimensions <: super (widthSub, heightSub) = bmps_dimensions <: sub superBytesPerRow = 4 * widthSuper subBytesPerRow = 4 * widthSub maxSuperRow = heightSuper - heightSub maxSuperColumn = widthSuper - widthSub maxOffRow = abs . pred $ heightSub r' i@(row, column) | column > maxSuperColumn = r' (succ row, 0) | row > maxSuperRow = Nothing | matches 0 = Just i | otherwise = r' (row, succ column) where superBaseIndex = row * superBytesPerRow + 4 * column matches offRow | offRow > maxOffRow = True | (S.toStringCells :: S.StringCells s => s -> B.ByteString) (subStr (superBaseIndex + offRow * superBytesPerRow) subBytesPerRow dataSuper) /= (S.toStringCells :: (S.StringCells s) => s -> B.ByteString) (subStr (offRow * subBytesPerRow) subBytesPerRow dataSub) = False | otherwise = matches (succ offRow) in r' instance BitmapReflectable BitmapStringRGB32