File: Pack.hs

package info (click to toggle)
haskell-bmp 1.2.6.4-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 124 kB
  • sloc: haskell: 911; makefile: 2
file content (263 lines) | stat: -rw-r--r-- 9,675 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
{-# OPTIONS_HADDOCK hide #-}
module Codec.BMP.Pack
        ( packRGBA32ToBMP
        , packRGBA32ToBMP24
        , packRGBA32ToBMP32)
where
import Codec.BMP.Base
import Codec.BMP.BitmapInfo
import Codec.BMP.BitmapInfoV3
import Codec.BMP.FileHeader
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Storable
import System.IO.Unsafe
import Data.Word
import Data.Maybe
import Data.ByteString          as BS
import Data.ByteString.Unsafe   as BS
import Prelude                  as P


-- | Pack a string of RGBA component values into a 32-bit BMP image.
--  
--   Alias for `packRGBA32ToBMP32`.
--
packRGBA32ToBMP
        :: Int          -- ^ Width of image  (must be positive).
        -> Int          -- ^ Height of image (must be positive).
        -> ByteString   -- ^ A string of RGBA component values.
                        --   Must have length (@width * height * 4@)
        -> BMP

packRGBA32ToBMP = packRGBA32ToBMP32
{-# INLINE packRGBA32ToBMP #-}


-- BMP 32 bit -----------------------------------------------------------------
-- | Pack a string of RGBA component values into a 32-bit BMP image.
--
--  * If the given dimensions don't match the input string then `error`.
--
--  * If the width or height fields are negative then `error`.
--
packRGBA32ToBMP32
        :: Int          -- ^ Width of image  (must be positive).
        -> Int          -- ^ Height of image (must be positive).
        -> ByteString   -- ^ A string of RGBA component values.
                        --   Must have length (@width * height * 4@)
        -> BMP

packRGBA32ToBMP32 width height str
 | width < 0    
 = error "Codec.BMP: Negative width field."

 | height < 0   
 = error "Codec.BMP: Negative height field."

 | height * width * 4 /= BS.length str
 = error "Codec.BMP: Image dimensions don't match input data."

 | otherwise
 = let  imageData       = packRGBA32ToBGRA32 width height str
   in   packDataToBMP 32 width height imageData


-- BMP 24 bit -----------------------------------------------------------------
-- | Pack a string of RGBA component values into a 24-bit BMP image,
--   discarding the alpha channel of the source data.
--
--  * If the given dimensions don't match the input string then `error`.
--
--  * If the width or height fields are negative then `error`.

packRGBA32ToBMP24
        :: Int          -- ^ Width of image  (must be positive).
        -> Int          -- ^ Height of image (must be positive).
        -> ByteString   -- ^ A string of RGBA component values.
                        --   Must have length (@width * height * 4@)
        -> BMP

packRGBA32ToBMP24 width height str
 | width < 0    
 = error "Codec.BMP: Negative width field."

 | height < 0   
 = error "Codec.BMP: Negative height field."

 | height * width * 4 /= BS.length str
 = error "Codec.BMP: Image dimensions don't match input data."

 | otherwise
 = let  imageData       = packRGBA32ToBGR24 width height str
   in   packDataToBMP 24 width height imageData


-- data -----------------------------------------------------------------------
-- | Wrap pre-packed image data into BMP image.
--
packDataToBMP
        :: Int          -- ^ Number of bits per pixel
        -> Int          -- ^ Width of image  (must be positive).
        -> Int          -- ^ Height of image (must be positive).
        -> ByteString   -- ^ A string of RGBA component values.
                        --   Must have length (@width * height * 4@)
        -> BMP
        
packDataToBMP bits width height imageData
 = let  fileHeader
                = FileHeader
                { fileHeaderType        = bmpMagic

                , fileHeaderFileSize    
                        = fromIntegral
                        $ sizeOfFileHeader + sizeOfBitmapInfoV3 
                                           + BS.length imageData

                , fileHeaderReserved1   = 0
                , fileHeaderReserved2   = 0
                , fileHeaderOffset      
                        = fromIntegral (sizeOfFileHeader + sizeOfBitmapInfoV3)}

        bitmapInfoV3
                = BitmapInfoV3
                { dib3Size              = fromIntegral sizeOfBitmapInfoV3
                , dib3Width             = fromIntegral width
                , dib3Height            = fromIntegral height
                , dib3HeightFlipped     = False
                , dib3Planes            = 1
                , dib3BitCount          = fromIntegral bits
                , dib3Compression       = CompressionRGB
                , dib3ImageSize         = fromIntegral $ BS.length imageData

                -- The default resolution seems to be 72 pixels per inch.
                --      This equates to 2834 pixels per meter.
                --      Dunno WTF this should be in the header though...
                , dib3PelsPerMeterX     = 2834
                , dib3PelsPerMeterY     = 2834

                , dib3ColorsUsed        = 0
                , dib3ColorsImportant   = 0 }
                
        -- We might as well check to see if we've made a well-formed BMP file.
        -- It would be sad if we couldn't read a file we just wrote.
        errs    = catMaybes             
                        [ checkFileHeader   fileHeader
                        , checkBitmapInfoV3 bitmapInfoV3 
                                           (fromIntegral $ BS.length imageData)]
                
   in   case errs of
         [] -> BMP 
                { bmpFileHeader         = fileHeader
                , bmpBitmapInfo         = InfoV3 bitmapInfoV3
                , bmpRawImageData       = imageData }
         
         _  -> error $ "Codec.BMP: Constructed BMP file has errors, sorry." 
                     ++ show errs


-------------------------------------------------------------------------------
-- | Pack RGBA data into the format need by BMP image data.
packRGBA32ToBGR24 
        :: Int                 -- ^ Width of image.
        -> Int                 -- ^ Height of image.
        -> ByteString          -- ^ Source bytestring holding the image data. 
        -> ByteString          --   output bytestring.
        
packRGBA32ToBGR24 width height str
 | height * width * 4 /= BS.length str
 = error "Codec.BMP: Image dimensions don't match input data."

 | otherwise
 = let  padPerLine      
         = case (width * 3) `mod` 4 of
                0       -> 0
                x       -> 4 - x
                                
        sizeDest        = height * (width * 3 + padPerLine)
   in   unsafePerformIO
         $ allocaBytes sizeDest         $ \bufDest ->
           BS.unsafeUseAsCString str    $ \bufSrc  ->
            do  packRGBA32ToBGR24' width height padPerLine
                        (castPtr bufSrc) (castPtr bufDest)
                bs      <- packCStringLen (bufDest, sizeDest)
                return bs
        
                        
packRGBA32ToBGR24' width height pad ptrSrc ptrDest
 = go 0 0 0 0
 where
        go posX posY oSrc oDest

         -- add padding bytes at the end of each line.
         | posX == width
         = do   mapM_ (\n -> pokeByteOff ptrDest (oDest + n) (0 :: Word8)) 
                        $ P.take pad [0 .. ]
                go 0 (posY + 1) oSrc (oDest + pad)
                
         -- we've finished the image.
         | posY == height
         = return ()
        
         -- process a pixel
         | otherwise
         = do   red     :: Word8  <- peekByteOff ptrSrc (oSrc + 0)
                green   :: Word8  <- peekByteOff ptrSrc (oSrc + 1)
                blue    :: Word8  <- peekByteOff ptrSrc (oSrc + 2)
        
                pokeByteOff ptrDest (oDest + 0) blue
                pokeByteOff ptrDest (oDest + 1) green
                pokeByteOff ptrDest (oDest + 2) red
                
                go (posX + 1) posY (oSrc + 4) (oDest + 3)


-------------------------------------------------------------------------------
-- | Pack RGBA data into the byte order needed by BMP image data.
packRGBA32ToBGRA32 
        :: Int                 -- ^ Width of image.
        -> Int                 -- ^ Height of image.
        -> ByteString          -- ^ Source bytestring holding the image data. 
        -> ByteString          

packRGBA32ToBGRA32 width height str
 | height * width * 4 /= BS.length str
 = error "Codec.BMP: Image dimensions don't match input data."

 | otherwise
 = let  sizeDest        = height * (width * 4)
   in   unsafePerformIO
         $ allocaBytes sizeDest         $ \bufDest ->
           BS.unsafeUseAsCString str    $ \bufSrc  ->
            do  packRGBA32ToBGRA32' width height
                        (castPtr bufSrc) (castPtr bufDest)
                bs      <- packCStringLen (bufDest, sizeDest)
                return  bs
        
packRGBA32ToBGRA32' width height ptrSrc ptrDest
 = go 0 0 0 0
 where
        go posX posY oSrc oDest

         -- advance to the next line.
         | posX == width
         = do  go 0 (posY + 1) oSrc oDest
                
         -- we've finished the image.
         | posY == height
         = return ()
        
         -- process a pixel
         | otherwise
         = do   red     :: Word8  <- peekByteOff ptrSrc (oSrc + 0)
                green   :: Word8  <- peekByteOff ptrSrc (oSrc + 1)
                blue    :: Word8  <- peekByteOff ptrSrc (oSrc + 2)
                alpha   :: Word8  <- peekByteOff ptrSrc (oSrc + 3)
        
                pokeByteOff ptrDest (oDest + 0) blue
                pokeByteOff ptrDest (oDest + 1) green
                pokeByteOff ptrDest (oDest + 2) red
                pokeByteOff ptrDest (oDest + 3) alpha
                
                go (posX + 1) posY (oSrc + 4) (oDest + 4)