| 1 |
// written in the D programming language |
|---|
| 2 |
|
|---|
| 3 |
/*************************************************************************************************** |
|---|
| 4 |
License: |
|---|
| 5 |
Copyright (c) 2005-2007 Lode Vandevenne |
|---|
| 6 |
All rights reserved. |
|---|
| 7 |
|
|---|
| 8 |
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
|---|
| 9 |
|
|---|
| 10 |
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.<br> |
|---|
| 11 |
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.<br> |
|---|
| 12 |
- Neither the name of Lode Vandevenne nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission.<br> |
|---|
| 13 |
|
|---|
| 14 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 15 |
|
|---|
| 16 |
Authors: Lode Vandevenne (original version in C++), Lutger Blijdestijn (D version) : lutger dot blijdestijn at gmail dot com. |
|---|
| 17 |
|
|---|
| 18 |
About: |
|---|
| 19 |
The decoder is small but sufficient for most purposes. It is compliant to the png specification and |
|---|
| 20 |
has been tested with the png suite. To decode images, only <i>decode</i> is needed. The <i>decode32</i> function is for convenience, |
|---|
| 21 |
it can decode and convert to the common 32-bit RGBA format in one go. |
|---|
| 22 |
The rest of the api exposes the low-level functionality of lodepng, which is made available in order to use this library |
|---|
| 23 |
for png-editing purposes. |
|---|
| 24 |
<br> |
|---|
| 25 |
This module publicly imports lodepng.Common, where you'll find the data types used by both the encoder |
|---|
| 26 |
and decoder, as well as some utility and image format conversion routines. |
|---|
| 27 |
|
|---|
| 28 |
Date: Januari 16, 2008 |
|---|
| 29 |
|
|---|
| 30 |
Examples: |
|---|
| 31 |
Here is an example how you could use LodePNG with opengl, see the api documentation for details. |
|---|
| 32 |
--- |
|---|
| 33 |
uint loadPNG(char[] filename) |
|---|
| 34 |
{ |
|---|
| 35 |
uint textureID; |
|---|
| 36 |
|
|---|
| 37 |
glEnable(GL_TEXTURE_2D); |
|---|
| 38 |
glGenTextures(1, &textureID); |
|---|
| 39 |
glBindTexture(GL_TEXTURE_2D, textureID); |
|---|
| 40 |
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); |
|---|
| 41 |
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); |
|---|
| 42 |
|
|---|
| 43 |
PngInfo info; |
|---|
| 44 |
ubyte[] image = decode32(cast(ubyte[])std.file.read(filename), info); |
|---|
| 45 |
|
|---|
| 46 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.image.width, info.image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
|---|
| 47 |
image.ptr); |
|---|
| 48 |
return textureID; |
|---|
| 49 |
} |
|---|
| 50 |
--- |
|---|
| 51 |
|
|---|
| 52 |
Features: |
|---|
| 53 |
The following features are supported by the decoder: |
|---|
| 54 |
<ul> |
|---|
| 55 |
<li> conformant decoding of PNGs (all color types, bit depth, interlace mode, CRC checking, etc.)</li> |
|---|
| 56 |
<li> support for translucent PNG's, including translucent palettes and color key</li> |
|---|
| 57 |
<li> textual key-value meta-data</li> |
|---|
| 58 |
<li> the following chunks are interpreted by the decoder |
|---|
| 59 |
<ul> |
|---|
| 60 |
<li>IHDR (image information)</li> |
|---|
| 61 |
<li>PLTE (color palette)</li> |
|---|
| 62 |
<li>IDAT (pixel data)</li> |
|---|
| 63 |
<li>IEND (the final chunk)</li> |
|---|
| 64 |
<li>tRNS (transparency for palettized images)</li> |
|---|
| 65 |
<li>bKGD (suggested background color)</li> |
|---|
| 66 |
<li>tEXt (uncompressed latin-1 key-value strings)</li> |
|---|
| 67 |
<li>zTXt (compressed latin-1 key-value strings)</li> |
|---|
| 68 |
<li>iTXt (utf8 key-value strings)</li> |
|---|
| 69 |
</ul> |
|---|
| 70 |
</li> |
|---|
| 71 |
</UL> |
|---|
| 72 |
|
|---|
| 73 |
Limitations: |
|---|
| 74 |
The following features are not supported. |
|---|
| 75 |
<ul> |
|---|
| 76 |
<li> Streaming / progressive display. All data must be available and is processed in one call.</li> |
|---|
| 77 |
<li> The following optional chunk types are not interpreted by the decoder |
|---|
| 78 |
<ul> |
|---|
| 79 |
<li>cHRM (device independent color info) |
|---|
| 80 |
<li>gAMA (device independent color info) |
|---|
| 81 |
<li>iCCP (device independent color info) |
|---|
| 82 |
<li>sBIT (original number of significant bits) |
|---|
| 83 |
<li>sRGB (device independent color info) |
|---|
| 84 |
<li>pHYs (physical pixel dimensions) |
|---|
| 85 |
<li>sPLT (suggested reduced palette) |
|---|
| 86 |
<li>tIME (last image modification time) |
|---|
| 87 |
</ul> |
|---|
| 88 |
</li> |
|---|
| 89 |
</ul> |
|---|
| 90 |
|
|---|
| 91 |
|
|---|
| 92 |
References: |
|---|
| 93 |
$(LINK2 http://members.gamedev.net/lode/projects/LodePNG/, Original lodepng) <br> |
|---|
| 94 |
$(LINK2 http://www.w3.org/TR/PNG/, PNG Specification) <br> |
|---|
| 95 |
$(LINK2 http://www.libpng.org/pub/png/pngsuite.html, PNG Suite: set of test images) <br> |
|---|
| 96 |
$(LINK2 http://optipng.sourceforge.net/, OptiPNG: tool to experimentally optimize png images) |
|---|
| 97 |
*/ |
|---|
| 98 |
|
|---|
| 99 |
module lodepng.Decode; |
|---|
| 100 |
import lodepng.ZlibCodec; |
|---|
| 101 |
import lodepng.util; |
|---|
| 102 |
import std.intrinsic; |
|---|
| 103 |
|
|---|
| 104 |
|
|---|
| 105 |
version (Tango) |
|---|
| 106 |
{ |
|---|
| 107 |
import czlib = tango.io.compress.c.zlib; |
|---|
| 108 |
} |
|---|
| 109 |
else |
|---|
| 110 |
{ |
|---|
| 111 |
import czlib = etc.c.zlib; |
|---|
| 112 |
} |
|---|
| 113 |
|
|---|
| 114 |
public import lodepng.Common; |
|---|
| 115 |
|
|---|
| 116 |
|
|---|
| 117 |
/** Decode source png file |
|---|
| 118 |
|
|---|
| 119 |
If a buffer is provided, it may be used to store the result. See bufferSize for details. |
|---|
| 120 |
|
|---|
| 121 |
Throws: PngException |
|---|
| 122 |
|
|---|
| 123 |
Returns: Decoded image pixels. The color format of the resulting image is the |
|---|
| 124 |
same as the source image, see lodepng.Common.convert and decode32 if a specific color format |
|---|
| 125 |
is desired. |
|---|
| 126 |
|
|---|
| 127 |
Params: |
|---|
| 128 |
source = a png file |
|---|
| 129 |
info = information about the image will be stored in here |
|---|
| 130 |
buffer = optionally provide an array to use as a buffer while decoding |
|---|
| 131 |
dg = optionally provide a delegate that will be called for (and only for) unknown chunks |
|---|
| 132 |
*/ |
|---|
| 133 |
ubyte[] decode(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null, int delegate(inout Chunk) dg = null) |
|---|
| 134 |
{ |
|---|
| 135 |
info.image = source.readHeader(); |
|---|
| 136 |
scope decompressor = new PngDecoder(info.image); |
|---|
| 137 |
|
|---|
| 138 |
foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $])) |
|---|
| 139 |
{ |
|---|
| 140 |
if (chunk.type == IDAT) |
|---|
| 141 |
decompressor(chunk.data); |
|---|
| 142 |
else if (!parseChunk(chunk, info)) |
|---|
| 143 |
if (dg !is null) |
|---|
| 144 |
if (dg(chunk) != 0) |
|---|
| 145 |
return null; |
|---|
| 146 |
|
|---|
| 147 |
} |
|---|
| 148 |
assert(decompressor.ended); |
|---|
| 149 |
|
|---|
| 150 |
return decompressor.reconstructImage()[0..info.image.width * bytesPerPixel(info.image) * info.image.height]; |
|---|
| 151 |
} |
|---|
| 152 |
|
|---|
| 153 |
/*************************************************************************************************** |
|---|
| 154 |
Decode source png file to 32-bit RGBA format |
|---|
| 155 |
|
|---|
| 156 |
Throws: PngException |
|---|
| 157 |
|
|---|
| 158 |
Returns: decoded image pixels in 32-bit RGBA format |
|---|
| 159 |
|
|---|
| 160 |
Params: |
|---|
| 161 |
source = a png file |
|---|
| 162 |
info = information about the image will be stored in here |
|---|
| 163 |
buffer = optionally provide an array to use as a buffer while decoding |
|---|
| 164 |
***************************************************************************************************/ |
|---|
| 165 |
ubyte[] decode32(/+const+/ in ubyte[] source, ref PngInfo info, ubyte[] buffer = null) |
|---|
| 166 |
{ |
|---|
| 167 |
buffer = decode(source, info, buffer); |
|---|
| 168 |
buffer = convert(buffer, info, ColorType.RGBA); |
|---|
| 169 |
info.image.colorType = ColorType.RGBA; |
|---|
| 170 |
info.image.bpp = 32; |
|---|
| 171 |
info.image.bitDepth = 8; |
|---|
| 172 |
info.palette.length = 0; |
|---|
| 173 |
return buffer[0..info.image.width * bytesPerPixel(info.image) * info.image.height]; |
|---|
| 174 |
} |
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 |
/*************************************************************************************************** |
|---|
| 178 |
Parse png image header from memory. |
|---|
| 179 |
|
|---|
| 180 |
throws: PngException |
|---|
| 181 |
|
|---|
| 182 |
returns: header information |
|---|
| 183 |
|
|---|
| 184 |
Params: source=must contain the first 33 bytes of a png file |
|---|
| 185 |
***************************************************************************************************/ |
|---|
| 186 |
PngImage readHeader(/+const+/ in ubyte[] source) |
|---|
| 187 |
in |
|---|
| 188 |
{ |
|---|
| 189 |
assert(source.length >= HEADER_SIZE, "array is too small to contain png header"); |
|---|
| 190 |
} |
|---|
| 191 |
body // see spec: http://www.w3.org/TR/PNG/#11IHDR |
|---|
| 192 |
{ |
|---|
| 193 |
mixin(pngEnforce(`source.length >= HEADER_SIZE`, "png header data is too small")); |
|---|
| 194 |
mixin(pngEnforce(`source[0..8] == [cast(ubyte)137, 80, 78, 71, 13, 10, 26, 10]`, "invalid png header ")); |
|---|
| 195 |
mixin(pngEnforce((toUint(source[12..16]) == IHDR).stringof, "invalid png header")); |
|---|
| 196 |
mixin(pngEnforce(`checkCRC(source[29 .. 33], source[12 .. 29])`, "invalid CRC")); |
|---|
| 197 |
|
|---|
| 198 |
PngImage result; |
|---|
| 199 |
|
|---|
| 200 |
|
|---|
| 201 |
with (result) |
|---|
| 202 |
{ |
|---|
| 203 |
width = toUint(source[16..20]); |
|---|
| 204 |
height = toUint(source[20..24]); |
|---|
| 205 |
bitDepth = source[24]; |
|---|
| 206 |
colorType = cast(ColorType)source[25]; |
|---|
| 207 |
mixin(pngEnforce( `source[26] == 0`, "unsupported compression method in png header" )); |
|---|
| 208 |
mixin(pngEnforce( `source[27] == 0`, "unsupported filter method in png header" )); |
|---|
| 209 |
mixin(pngEnforce( `checkColorValidity(colorType, bitDepth)`, "invalid header: wrong color format" )); |
|---|
| 210 |
interlaced = source[28]; |
|---|
| 211 |
mixin(pngEnforce( `interlaced < 2`, "invalid interlace method in png header" )); |
|---|
| 212 |
bpp = numChannels(colorType) * bitDepth; |
|---|
| 213 |
} |
|---|
| 214 |
|
|---|
| 215 |
return result; |
|---|
| 216 |
} |
|---|
| 217 |
|
|---|
| 218 |
/*************************************************************************************************** |
|---|
| 219 |
Iterates through chunks in source, parses only the header |
|---|
| 220 |
|
|---|
| 221 |
Throws: PngException |
|---|
| 222 |
Params: source = a png file |
|---|
| 223 |
image = the parsed header |
|---|
| 224 |
dg = will be called for each chunk, return anything other than 0 to stop iterating |
|---|
| 225 |
|
|---|
| 226 |
***************************************************************************************************/ |
|---|
| 227 |
void iterateChunks(/+const+/ in ubyte[] source, out PngImage image, int delegate(inout Chunk) dg) |
|---|
| 228 |
{ |
|---|
| 229 |
image = source.readHeader(); |
|---|
| 230 |
|
|---|
| 231 |
foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $])) |
|---|
| 232 |
if (dg(chunk) != 0) |
|---|
| 233 |
return; |
|---|
| 234 |
} |
|---|
| 235 |
|
|---|
| 236 |
/*************************************************************************************************** |
|---|
| 237 |
decode IDAT data |
|---|
| 238 |
|
|---|
| 239 |
***************************************************************************************************/ |
|---|
| 240 |
scope class PngDecoder |
|---|
| 241 |
{ |
|---|
| 242 |
/** constructor */ |
|---|
| 243 |
this(PngImage image) |
|---|
| 244 |
{ |
|---|
| 245 |
this(image, buf); |
|---|
| 246 |
} |
|---|
| 247 |
|
|---|
| 248 |
/** ditto */ |
|---|
| 249 |
this(PngImage image, ref ubyte[] buffer) |
|---|
| 250 |
{ |
|---|
| 251 |
img = image; |
|---|
| 252 |
buf = buffer; |
|---|
| 253 |
if (img.interlaced == 1) |
|---|
| 254 |
buf.length = ((img.width * img.bpp + 7) / 8) * img.height + (img.height * 2); // guess |
|---|
| 255 |
else |
|---|
| 256 |
buf.length = ((img.width * img.bpp + 7) / 8) * img.height + img.height; |
|---|
| 257 |
if (img.interlaced == 1) |
|---|
| 258 |
ilaceBuffer.length = buf.length - img.height; |
|---|
| 259 |
decoder = DecodeStream.create(buf); |
|---|
| 260 |
} |
|---|
| 261 |
|
|---|
| 262 |
/*************************************************************************************************** |
|---|
| 263 |
inflate, call multiple times if there are more than 1 IDAT chunks to be decompressed |
|---|
| 264 |
|
|---|
| 265 |
Throws: ZlibException |
|---|
| 266 |
params: data of an IDAT chunk |
|---|
| 267 |
***************************************************************************************************/ |
|---|
| 268 |
void opCall(ref ubyte[] data) |
|---|
| 269 |
{ |
|---|
| 270 |
decoder(data); |
|---|
| 271 |
return true; |
|---|
| 272 |
} |
|---|
| 273 |
|
|---|
| 274 |
/*************************************************************************************************** |
|---|
| 275 |
Whether inflation has completed |
|---|
| 276 |
|
|---|
| 277 |
***************************************************************************************************/ |
|---|
| 278 |
bool ended() |
|---|
| 279 |
{ |
|---|
| 280 |
return decoder.hasEnded; |
|---|
| 281 |
} |
|---|
| 282 |
|
|---|
| 283 |
/*************************************************************************************************** |
|---|
| 284 |
Apply reconstruction filters and deinterlace if required |
|---|
| 285 |
|
|---|
| 286 |
note that ended() must return true before this function can be called if no data is provided |
|---|
| 287 |
|
|---|
| 288 |
params: |
|---|
| 289 |
filtered=optionally provide uncompressed filtered pixels yourself |
|---|
| 290 |
**************************************************************************************************/ |
|---|
| 291 |
ubyte[] reconstructImage(ubyte[] filtered = null) |
|---|
| 292 |
in |
|---|
| 293 |
{ |
|---|
| 294 |
assert(filtered !is null || ended); |
|---|
| 295 |
|
|---|
| 296 |
} |
|---|
| 297 |
body |
|---|
| 298 |
{ |
|---|
| 299 |
buf = filtered is null ? decoder() : filtered; |
|---|
| 300 |
return (img.interlaced == 0) ? reconstruct(buf, img) : deinterlace(buf, ilaceBuffer, img); |
|---|
| 301 |
} |
|---|
| 302 |
|
|---|
| 303 |
private |
|---|
| 304 |
{ |
|---|
| 305 |
DecodeStream decoder; |
|---|
| 306 |
PngImage img; |
|---|
| 307 |
|
|---|
| 308 |
ubyte[] buf; |
|---|
| 309 |
ubyte[] ilaceBuffer; |
|---|
| 310 |
} |
|---|
| 311 |
} |
|---|
| 312 |
|
|---|
| 313 |
|
|---|
| 314 |
/*************************************************************************************************** |
|---|
| 315 |
parse any known chunk except IDAT |
|---|
| 316 |
|
|---|
| 317 |
params: |
|---|
| 318 |
chunk=chunk to be parsed |
|---|
| 319 |
info=parsed information will be written to info |
|---|
| 320 |
returns: true if a chunk is parsed, false otherwise |
|---|
| 321 |
|
|---|
| 322 |
***************************************************************************************************/ |
|---|
| 323 |
bool parseChunk(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| 324 |
{ |
|---|
| 325 |
switch(chunk.type) |
|---|
| 326 |
{ |
|---|
| 327 |
case PLTE: parsePLTE(chunk, info); |
|---|
| 328 |
break; |
|---|
| 329 |
case tRNS: parsetRNS(chunk, info); |
|---|
| 330 |
break; |
|---|
| 331 |
case bKGD: parsebKGD(chunk, info); |
|---|
| 332 |
break; |
|---|
| 333 |
case zTXt: parsezTXt(chunk, info); |
|---|
| 334 |
break; |
|---|
| 335 |
case tEXt: parsetTXt(chunk, info); |
|---|
| 336 |
break; |
|---|
| 337 |
case iTXt: parseiTXt(chunk, info); |
|---|
| 338 |
break; |
|---|
| 339 |
default: |
|---|
| 340 |
return false; |
|---|
| 341 |
} |
|---|
| 342 |
return true; |
|---|
| 343 |
} |
|---|
| 344 |
|
|---|
| 345 |
/** parse palette chunk */ |
|---|
| 346 |
void parsePLTE(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| 347 |
{ |
|---|
| 348 |
mixin(pngEnforce(`chunk.data.length <= 256 * 3`, "palette size is too large")); |
|---|
| 349 |
info.palette.length = chunk.data.length / 3; |
|---|
| 350 |
foreach(index, ref ubyte[4] color; info.palette) |
|---|
| 351 |
{ |
|---|
| 352 |
color[0..3] = chunk.data[index * 3 .. index * 3 + 3]; |
|---|
| 353 |
color[3] = 255; |
|---|
| 354 |
} |
|---|
| 355 |
} |
|---|
| 356 |
|
|---|
| 357 |
/** parse transparency chunk */ |
|---|
| 358 |
void parsetRNS(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| 359 |
{ |
|---|
| 360 |
if (chunk.data.length == 0) |
|---|
| 361 |
return; |
|---|
| 362 |
info.colorKey = true; |
|---|
| 363 |
if (info.image.colorType == ColorType.Palette) // index-values |
|---|
| 364 |
{ |
|---|
| 365 |
mixin(pngEnforce(`chunk.data.length <= info.palette.length`, "palette size is too large")); |
|---|
| 366 |
foreach(index, alpha; chunk.data) |
|---|
| 367 |
info.palette[index][3] = alpha; |
|---|
| 368 |
} |
|---|
| 369 |
else if (info.image.colorType == ColorType.RGB) |
|---|
| 370 |
{ |
|---|
| 371 |
info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| 372 |
info.keyG = 256U * chunk.data[2] + chunk.data[3]; |
|---|
| 373 |
info.keyB = 256U * chunk.data[4] + chunk.data[5]; |
|---|
| 374 |
} |
|---|
| 375 |
else if (info.image.colorType == ColorType.Greyscale) |
|---|
| 376 |
{ |
|---|
| 377 |
info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| 378 |
} |
|---|
| 379 |
else |
|---|
| 380 |
{ |
|---|
| 381 |
assert(false); |
|---|
| 382 |
} |
|---|
| 383 |
} |
|---|
| 384 |
|
|---|
| 385 |
/** parse background color chunk */ |
|---|
| 386 |
void parsebKGD(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| 387 |
{ |
|---|
| 388 |
if (info.image.colorType == ColorType.Palette || info.image.bitDepth == 16) |
|---|
| 389 |
info.backgroundColor = chunk.data.dup; |
|---|
| 390 |
else |
|---|
| 391 |
{ |
|---|
| 392 |
info.backgroundColor.length = chunk.data.length / 2; |
|---|
| 393 |
foreach(index, ref value; info.backgroundColor) |
|---|
| 394 |
value = chunk.data[index * 2]; |
|---|
| 395 |
} |
|---|
| 396 |
} |
|---|
| 397 |
|
|---|
| 398 |
/** parse latin1 text chunk */ |
|---|
| 399 |
void parsetTXt(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| 400 |
{ |
|---|
| 401 |
if (info.parseText) |
|---|
| 402 |
{ |
|---|
| 403 |
auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 404 |
if (sep > 0) |
|---|
| 405 |
info.latin1Text[chunk.data[0..sep]] = chunk.data[sep + 1 .. $]; |
|---|
| 406 |
} |
|---|
| 407 |
} |
|---|
| 408 |
|
|---|
| 409 |
/** parse latin1 compressed text chunk */ |
|---|
| 410 |
void parsezTXt(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| 411 |
{ |
|---|
| 412 |
if (info.parseText) |
|---|
| 413 |
{ |
|---|
| 414 |
auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 415 |
if (sep > 0) |
|---|
| 416 |
{ |
|---|
| 417 |
if (chunk.data[sep + 1] == 0) |
|---|
| 418 |
info.latin1Text[chunk.data[0..sep]] = chunk.data[sep + 2 .. $]; |
|---|
| 419 |
else |
|---|
| 420 |
{ |
|---|
| 421 |
ubyte[] value; |
|---|
| 422 |
auto decoder = DecodeStream.create(value); |
|---|
| 423 |
decoder(chunk.data[sep + 2 .. $]); |
|---|
| 424 |
info.latin1Text[chunk.data[0..sep]] = value; |
|---|
| 425 |
} |
|---|
| 426 |
} |
|---|
| 427 |
} |
|---|
| 428 |
} |
|---|
| 429 |
|
|---|
| 430 |
/** parse unicode text chunk */ |
|---|
| 431 |
void parseiTXt(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| 432 |
{ |
|---|
| 433 |
if (info.parseText) |
|---|
| 434 |
{ |
|---|
| 435 |
|
|---|
| 436 |
auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 437 |
char[] keyword = cast(char[])chunk.data[0..sep]; |
|---|
| 438 |
bool compressed = chunk.data[sep + 1] == 0 ? false : true; |
|---|
| 439 |
sep += strFind(cast(char[])chunk.data[sep + 3 .. $], 0) + 3; |
|---|
| 440 |
sep += strFind(cast(char[])chunk.data[sep + 1 .. $], 0) + 1; |
|---|
| 441 |
if (!compressed) |
|---|
| 442 |
info.unicodeText[keyword] = cast(char[])chunk.data[sep + 1..$]; |
|---|
| 443 |
else |
|---|
| 444 |
{ |
|---|
| 445 |
ubyte[] value; |
|---|
| 446 |
auto decoder = DecodeStream.create(value); |
|---|
| 447 |
decoder(chunk.data[sep + 1..$]); |
|---|
| 448 |
info.unicodeText[keyword] = cast(char[])value; |
|---|
| 449 |
} |
|---|
| 450 |
} |
|---|
| 451 |
} |
|---|
| 452 |
|
|---|
| 453 |
|
|---|
| 454 |
|
|---|
| 455 |
|
|---|
| 456 |
|
|---|
| 457 |
|
|---|
| 458 |
/*************************************************************************************************** |
|---|
| 459 |
Predict size of buffer needed for decoding |
|---|
| 460 |
|
|---|
| 461 |
Estimate of the amount of heap memory needed to decode an image. Interlaced images, images |
|---|
| 462 |
with a color format of less than 8 bits per pixel and the parsing of certain information |
|---|
| 463 |
such as text will allocate more heap memory. |
|---|
| 464 |
***************************************************************************************************/ |
|---|
| 465 |
uint bufferSize( /+const+/ ref PngImage image) |
|---|
| 466 |
{ |
|---|
| 467 |
return ((image.width * image.bpp + 7) / 8) * image.height + image.height; |
|---|
| 468 |
} |
|---|
| 469 |
|
|---|
| 470 |
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|---|
| 471 |
// // |
|---|
| 472 |
// PRIVATE SECTION // |
|---|
| 473 |
// // |
|---|
| 474 |
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|---|
| 475 |
|
|---|
| 476 |
private: |
|---|
| 477 |
|
|---|
| 478 |
const uint HEADER_SIZE = 34; |
|---|
| 479 |
|
|---|
| 480 |
struct StreamChunkIter |
|---|
| 481 |
{ |
|---|
| 482 |
int opApply(int delegate(ref Chunk chunk) visitor) |
|---|
| 483 |
{ |
|---|
| 484 |
int result = 0; |
|---|
| 485 |
uint pos = 0; |
|---|
| 486 |
Chunk chunk; |
|---|
| 487 |
while(pos + 12 <= stream.length) |
|---|
| 488 |
{ |
|---|
| 489 |
chunk = Chunk.fromStream(stream[pos..$]); |
|---|
| 490 |
if (chunk.type == IEND) |
|---|
| 491 |
break; |
|---|
| 492 |
result = visitor(chunk); |
|---|
| 493 |
if (result) |
|---|
| 494 |
return result; |
|---|
| 495 |
pos += chunk.length; |
|---|
| 496 |
} |
|---|
| 497 |
return result; |
|---|
| 498 |
} |
|---|
| 499 |
ubyte[] stream; |
|---|
| 500 |
} |
|---|
| 501 |
|
|---|
| 502 |
//filter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works |
|---|
| 503 |
//byte per byte (bytewidth = 1)precon is the previous filtered scanline, recon the result, scanline |
|---|
| 504 |
//the current one |
|---|
| 505 |
void unFilterFirstScanline(ubyte[] result, ubyte[] scanline, uint bytewidth, uint filterType) |
|---|
| 506 |
in |
|---|
| 507 |
{ |
|---|
| 508 |
assert(filterType >= 0 && filterType <= 4); |
|---|
| 509 |
} |
|---|
| 510 |
body |
|---|
| 511 |
{ |
|---|
| 512 |
mixin(pngEnforce(`filterType >= 0 && filterType <= 4`, "wrong filter byte: image corrupt?")); |
|---|
| 513 |
switch(filterType) |
|---|
| 514 |
{ |
|---|
| 515 |
case 0: |
|---|
| 516 |
for(uint i = 0; i < scanline.length; i++) |
|---|
| 517 |
result[i] = scanline[i]; |
|---|
| 518 |
break; |
|---|
| 519 |
case 1: |
|---|
| 520 |
for(uint i = 0; i < bytewidth; i++) |
|---|
| 521 |
result[i] = scanline[i]; |
|---|
| 522 |
for(uint i = bytewidth; i < scanline.length; i++) |
|---|
| 523 |
result[i] = scanline[i] + result[i - bytewidth]; |
|---|
| 524 |
break; |
|---|
| 525 |
case 2: |
|---|
| 526 |
for(size_t i = 0; i < scanline.length; i++) |
|---|
| 527 |
result[i] = scanline[i]; |
|---|
| 528 |
break; |
|---|
| 529 |
case 3: |
|---|
| 530 |
for(size_t i = 0; i < bytewidth; i++) |
|---|
| 531 |
result[i] = scanline[i]; |
|---|
| 532 |
for(size_t i = bytewidth; i < scanline.length; i++) |
|---|
| 533 |
result[i] = (scanline[i] + result[i - bytewidth]) / 2; |
|---|
| 534 |
break; |
|---|
| 535 |
case 4: |
|---|
| 536 |
for(size_t i = 0; i < bytewidth; i++) |
|---|
| 537 |
result[i] = scanline[i]; |
|---|
| 538 |
for(size_t i = bytewidth; i < scanline.length; i++) |
|---|
| 539 |
result[i] = cast(ubyte)(scanline[i] + paethPredictor(result[i - bytewidth], 0, 0)); |
|---|
| 540 |
break; |
|---|
| 541 |
default: |
|---|
| 542 |
mixin(pngEnforce("false", "wrong type of filter")); |
|---|
| 543 |
break; |
|---|
| 544 |
|
|---|
| 545 |
} |
|---|
| 546 |
} |
|---|
| 547 |
|
|---|
| 548 |
//filter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works |
|---|
| 549 |
//byte per byte (bytewidth = 1) precon is the previous filtered scanline, recon the result, |
|---|
| 550 |
//scanline the current one |
|---|
| 551 |
void unFilterScanline(ubyte[] result, ubyte[] scanline, ubyte[] previous, uint bytewidth, |
|---|
| 552 |
uint filterType) |
|---|
| 553 |
in |
|---|
| 554 |
{ |
|---|
| 555 |
assert(filterType >= 0 && filterType <= 4); |
|---|
| 556 |
} |
|---|
| 557 |
body |
|---|
| 558 |
{ |
|---|
| 559 |
switch(filterType) |
|---|
| 560 |
{ |
|---|
| 561 |
case 4: |
|---|
| 562 |
for(size_t i = 0; i < bytewidth; i++) |
|---|
| 563 |
result[i] = cast(ubyte)(scanline[i] + previous[i] ); |
|---|
| 564 |
for(size_t i = bytewidth; i < scanline.length; i++) |
|---|
| 565 |
result[i] = cast(ubyte)(scanline[i] + |
|---|
| 566 |
paethPredictor(result[i - bytewidth], |
|---|
| 567 |
previous[i], |
|---|
| 568 |
previous[i - bytewidth])); |
|---|
| 569 |
break; |
|---|
| 570 |
case 3: |
|---|
| 571 |
for(size_t i = 0; i < bytewidth; i++) |
|---|
| 572 |
result[i] = scanline[i] + previous[i] / 2; |
|---|
| 573 |
for(size_t i = bytewidth; i < scanline.length; i++) |
|---|
| 574 |
result[i] = scanline[i] + ((result[i - bytewidth] + previous[i]) / 2); |
|---|
| 575 |
break; |
|---|
| 576 |
case 2: |
|---|
| 577 |
for(size_t i = 0; i < scanline.length; i++) |
|---|
| 578 |
result[i] = scanline[i] + previous[i]; |
|---|
| 579 |
break; |
|---|
| 580 |
case 1: |
|---|
| 581 |
for(size_t i = 0; i < bytewidth; i++) |
|---|
| 582 |
result[i] = scanline[i]; |
|---|
| 583 |
for(size_t i = bytewidth; i < scanline.length; i++) |
|---|
| 584 |
result[i] = scanline[i] + result[i - bytewidth]; |
|---|
| 585 |
break; |
|---|
| 586 |
case 0: |
|---|
| 587 |
for(uint i = 0; i < scanline.length; i++) result[i] = scanline[i]; |
|---|
| 588 |
break; |
|---|
| 589 |
default: |
|---|
| 590 |
mixin(pngEnforce("false", "wrong type of filter")); |
|---|
| 591 |
break; |
|---|
| 592 |
} |
|---|
| 593 |
} |
|---|
| 594 |
|
|---|
| 595 |
ubyte[] deinterlace(in ubyte[] scanlines, ref ubyte[] result, ref PngImage image) |
|---|
| 596 |
{ |
|---|
|
|---|