-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathExport.hs
More file actions
270 lines (235 loc) · 10.5 KB
/
Export.hs
File metadata and controls
270 lines (235 loc) · 10.5 KB
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
264
265
266
267
268
269
270
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Module implementing a basic png export, no filtering is applied, but
-- export at least valid images.
module Codec.Picture.Png.Internal.Export( PngSavable( .. )
, PngPaletteSaveable( .. )
, writePng
, encodeDynamicPng
, writeDynamicPng
) where
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid( mempty )
#endif
import Control.Monad( forM_ )
import Control.Monad.ST( ST, runST )
import Data.Bits( unsafeShiftR, (.&.) )
import Data.Binary( encode )
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid( (<>) )
#endif
import Data.Word(Word8, Word16)
import qualified Codec.Compression.Zlib as Z
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as Lb
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Storable.Mutable as M
import Codec.Picture.Types
import Codec.Picture.Png.Internal.Type
import Codec.Picture.Png.Internal.Metadata
import Codec.Picture.Metadata( Metadatas )
import Codec.Picture.VectorByteConversion( blitVector, toByteString )
-- | Encode a paletted image into a png if possible.
class PngPaletteSaveable a where
-- | Encode a paletted image as a color indexed 8-bit PNG.
-- the palette must have between 1 and 256 values in it.
-- Accepts `PixelRGB8` and `PixelRGBA8` as palette pixel type
encodePalettedPng :: Image a -> Image Pixel8 -> Either String Lb.ByteString
encodePalettedPng = encodePalettedPngWithMetadata mempty
-- | Equivalent to 'encodePalettedPng' but allow writing of metadatas.
-- See `encodePngWithMetadata` for the details of encoded metadatas
-- Accepts `PixelRGB8` and `PixelRGBA8` as palette pixel type
encodePalettedPngWithMetadata :: Metadatas -> Image a -> Image Pixel8 -> Either String Lb.ByteString
instance PngPaletteSaveable PixelRGB8 where
encodePalettedPngWithMetadata metas pal img
| w <= 0 || w > 256 || h /= 1 = Left "Invalid palette"
| VS.any isTooBig $ imageData img =
Left "Image contains indexes absent from the palette"
| otherwise = Right $ genericEncodePng (Just pal) Nothing PngIndexedColor metas img
where w = imageWidth pal
h = imageHeight pal
isTooBig v = fromIntegral v >= w
instance PngPaletteSaveable PixelRGBA8 where
encodePalettedPngWithMetadata metas pal img
| w <= 0 || w > 256 || h /= 1 = Left "Invalid palette"
| VS.any isTooBig $ imageData img =
Left "Image contains indexes absent from the palette"
| otherwise = Right $ genericEncodePng (Just opaquePalette) (Just alphaPal) PngIndexedColor metas img
where
w = imageWidth pal
h = imageHeight pal
opaquePalette = dropAlphaLayer pal
alphaPal = imageData $ extractComponent PlaneAlpha pal
isTooBig v = fromIntegral v >= w
-- | Encode an image into a png if possible.
class PngSavable a where
-- | Transform an image into a png encoded bytestring, ready
-- to be written as a file.
encodePng :: Image a -> Lb.ByteString
encodePng = encodePngWithMetadata mempty
-- | Encode a png using some metadatas. The following metadata keys will
-- be stored in a `tEXt` field :
--
-- * 'Codec.Picture.Metadata.Title'
-- * 'Codec.Picture.Metadata.Description'
-- * 'Codec.Picture.Metadata.Author'
-- * 'Codec.Picture.Metadata.Copyright'
-- * 'Codec.Picture.Metadata.Software'
-- * 'Codec.Picture.Metadata.Comment'
-- * 'Codec.Picture.Metadata.Disclaimer'
-- * 'Codec.Picture.Metadata.Source'
-- * 'Codec.Picture.Metadata.Warning'
-- * 'Codec.Picture.Metadata.Unknown' using the key present in the constructor.
--
-- the following metadata will be stored in the `gAMA` chunk.
--
-- * 'Codec.Picture.Metadata.Gamma'
--
-- The following metadata will be stored in a `pHYs` chunk
--
-- * 'Codec.Picture.Metadata.DpiX'
-- * 'Codec.Picture.Metadata.DpiY'
encodePngWithMetadata :: Metadatas -> Image a -> Lb.ByteString
preparePngHeader :: Image a -> PngImageType -> Word8 -> PngIHdr
preparePngHeader (Image { imageWidth = w, imageHeight = h }) imgType depth = PngIHdr
{ width = fromIntegral w
, height = fromIntegral h
, bitDepth = depth
, colourType = imgType
, compressionMethod = 0
, filterMethod = 0
, interlaceMethod = PngNoInterlace
}
-- | Helper function to directly write an image as a png on disk.
writePng :: (PngSavable pixel) => FilePath -> Image pixel -> IO ()
writePng path img = Lb.writeFile path $ encodePng img
endChunk :: PngRawChunk
endChunk = mkRawChunk iENDSignature mempty
prepareIDatChunk :: Lb.ByteString -> PngRawChunk
prepareIDatChunk = mkRawChunk iDATSignature
genericEncode16BitsPng :: forall px. (Pixel px, PixelBaseComponent px ~ Word16)
=> PngImageType -> Metadatas -> Image px -> Lb.ByteString
genericEncode16BitsPng imgKind metas
image@(Image { imageWidth = w, imageHeight = h, imageData = arr }) =
encode PngRawImage { header = hdr
, chunks = encodeMetadatas metas
<> [ prepareIDatChunk imgEncodedData
, endChunk
]
}
where hdr = preparePngHeader image imgKind 16
zero = B.singleton 0
compCount = componentCount (undefined :: px)
lineSize = compCount * w
blitToByteString vec = blitVector vec 0 (lineSize * 2)
encodeLine line = blitToByteString $ runST $ do
finalVec <- M.new $ lineSize * 2 :: ST s (M.STVector s Word8)
let baseIndex = line * lineSize
forM_ [0 .. lineSize - 1] $ \ix -> do
let v = arr `VS.unsafeIndex` (baseIndex + ix)
high = fromIntegral $ (v `unsafeShiftR` 8) .&. 0xFF
low = fromIntegral $ v .&. 0xFF
(finalVec `M.unsafeWrite` (ix * 2 + 0)) high
(finalVec `M.unsafeWrite` (ix * 2 + 1)) low
VS.unsafeFreeze finalVec
imgEncodedData = Z.compress . Lb.fromChunks
$ concat [[zero, encodeLine line] | line <- [0 .. h - 1]]
preparePalette :: Palette -> PngRawChunk
preparePalette pal = PngRawChunk
{ chunkLength = fromIntegral $ imageWidth pal * 3
, chunkType = pLTESignature
, chunkCRC = pngComputeCrc [pLTESignature, binaryData]
, chunkData = binaryData
}
where binaryData = Lb.fromChunks [toByteString $ imageData pal]
preparePaletteAlpha :: VS.Vector Pixel8 -> PngRawChunk
preparePaletteAlpha alphaPal = PngRawChunk
{ chunkLength = fromIntegral $ VS.length alphaPal
, chunkType = tRNSSignature
, chunkCRC = pngComputeCrc [tRNSSignature, binaryData]
, chunkData = binaryData
}
where binaryData = Lb.fromChunks [toByteString alphaPal]
type PaletteAlpha = VS.Vector Pixel8
genericEncodePng :: forall px. (Pixel px, PixelBaseComponent px ~ Word8)
=> Maybe Palette
-> Maybe PaletteAlpha
-> PngImageType -> Metadatas -> Image px
-> Lb.ByteString
genericEncodePng palette palAlpha imgKind metas
image@(Image { imageWidth = w, imageHeight = h, imageData = arr }) =
encode PngRawImage { header = hdr
, chunks = encodeMetadatas metas
<> paletteChunk
<> transpChunk
<> [ prepareIDatChunk imgEncodedData
, endChunk
]}
where
hdr = preparePngHeader image imgKind 8
zero = B.singleton 0
compCount = componentCount (undefined :: px)
paletteChunk = case palette of
Nothing -> []
Just p -> [preparePalette p]
transpChunk = case palAlpha of
Nothing -> []
Just p -> [preparePaletteAlpha p]
lineSize = compCount * w
encodeLine line = blitVector arr (line * lineSize) lineSize
imgEncodedData = Z.compress
. Lb.fromChunks
$ concat [[zero, encodeLine line] | line <- [0 .. h - 1]]
instance PngSavable PixelRGBA8 where
encodePngWithMetadata = genericEncodePng Nothing Nothing PngTrueColourWithAlpha
instance PngSavable PixelRGB8 where
encodePngWithMetadata = genericEncodePng Nothing Nothing PngTrueColour
instance PngSavable Pixel8 where
encodePngWithMetadata = genericEncodePng Nothing Nothing PngGreyscale
instance PngSavable PixelYA8 where
encodePngWithMetadata = genericEncodePng Nothing Nothing PngGreyscaleWithAlpha
instance PngSavable PixelYA16 where
encodePngWithMetadata = genericEncode16BitsPng PngGreyscaleWithAlpha
instance PngSavable Pixel16 where
encodePngWithMetadata = genericEncode16BitsPng PngGreyscale
instance PngSavable PixelRGB16 where
encodePngWithMetadata = genericEncode16BitsPng PngTrueColour
instance PngSavable PixelRGBA16 where
encodePngWithMetadata = genericEncode16BitsPng PngTrueColourWithAlpha
-- | Write a dynamic image in a .png image file if possible.
-- The same restriction as encodeDynamicPng apply.
writeDynamicPng :: FilePath -> DynamicImage -> IO (Either String Bool)
writeDynamicPng path img = case encodeDynamicPng img of
Left err -> return $ Left err
Right b -> Lb.writeFile path b >> return (Right True)
-- | Encode a dynamic image in PNG if possible, supported images are:
--
-- * 'ImageY8'
--
-- * 'ImageY16'
--
-- * 'ImageYA8'
--
-- * 'ImageYA16'
--
-- * 'ImageRGB8'
--
-- * 'ImageRGB16'
--
-- * 'ImageRGBA8'
--
-- * 'ImageRGBA16'
--
encodeDynamicPng :: DynamicImage -> Either String Lb.ByteString
encodeDynamicPng (ImageRGB8 img) = Right $ encodePng img
encodeDynamicPng (ImageRGBA8 img) = Right $ encodePng img
encodeDynamicPng (ImageY8 img) = Right $ encodePng img
encodeDynamicPng (ImageY16 img) = Right $ encodePng img
encodeDynamicPng (ImageYA8 img) = Right $ encodePng img
encodeDynamicPng (ImageYA16 img) = Right $ encodePng img
encodeDynamicPng (ImageRGB16 img) = Right $ encodePng img
encodeDynamicPng (ImageRGBA16 img) = Right $ encodePng img
encodeDynamicPng _ = Left "Unsupported image format for PNG export"