| 1 |
/*************************************************************************************************** |
|---|
| 2 |
License: |
|---|
| 3 |
Copyright (c) 2005-2007 Lode Vandevenne |
|---|
| 4 |
All rights reserved. |
|---|
| 5 |
|
|---|
| 6 |
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
|---|
| 7 |
|
|---|
| 8 |
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.<br> |
|---|
| 9 |
- 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> |
|---|
| 10 |
- 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> |
|---|
| 11 |
|
|---|
| 12 |
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. |
|---|
| 13 |
|
|---|
| 14 |
Authors: Lode Vandevenne (original version in C++), Lutger Blijdestijn (D version) : lutger dot blijdestijn at gmail dot com, |
|---|
| 15 |
Stewart Gordon (bug fixes) |
|---|
| 16 |
|
|---|
| 17 |
Date: Januari 16, 2008 |
|---|
| 18 |
|
|---|
| 19 |
About: |
|---|
| 20 |
The lodepng encoder can encode images of any of the supported png color formats to 24-bit RGB or |
|---|
| 21 |
32-bit RGBA png images. Conversion, if needed, is done automatically. It is compatible with the |
|---|
| 22 |
Phobos and Tango libraries. <br> |
|---|
| 23 |
This module publicly imports lodepng.Common, where you'll find the data types used by both the encoder |
|---|
| 24 |
and decoder, as well as some utility and image format conversion routines.<br> |
|---|
| 25 |
|
|---|
| 26 |
Features: |
|---|
| 27 |
The following features are understood by the encoder: |
|---|
| 28 |
<ul> |
|---|
| 29 |
<li> conformant encoding of 24-bit RGB and 32-bit RGBA PNG images </li> |
|---|
| 30 |
<li> automatic conversion of other color formats </li> |
|---|
| 31 |
<li> setting the compression and filter methods </li> |
|---|
| 32 |
<li> textual key-value metadata: normal and compressed latin1, unicode (utf-8) </li> |
|---|
| 33 |
<li> transparency / colorkey </li> |
|---|
| 34 |
<li> encoding raw chunks </li> |
|---|
| 35 |
<li> the following chunks are written by the encoder |
|---|
| 36 |
<ul> |
|---|
| 37 |
<li>IHDR (image information)</li> |
|---|
| 38 |
<li>IDAT (pixel data)</li> |
|---|
| 39 |
<li>IEND (the final chunk)</li> |
|---|
| 40 |
<li>tRNS (colorkey)</li> |
|---|
| 41 |
<li>bKGD (suggested background color) </li> |
|---|
| 42 |
<li>tEXt (uncompressed latin-1 key-value strings)</li> |
|---|
| 43 |
<li>zTXt (compressed latin-1 key-value strings)</li> |
|---|
| 44 |
<li>iTXt (utf8 key-value strings)</li> |
|---|
| 45 |
</ul> |
|---|
| 46 |
</li> |
|---|
| 47 |
</UL> |
|---|
| 48 |
|
|---|
| 49 |
<b>Limitations:</b><br> |
|---|
| 50 |
The following features are not supported. |
|---|
| 51 |
<ul> |
|---|
| 52 |
<li> Ouput in any color formats other than 24-bit RGB or 32-bit RGBA</li> |
|---|
| 53 |
<li> Interlacing </li> |
|---|
| 54 |
<li> The encoder does not understand the following chunk types: |
|---|
| 55 |
<ul> |
|---|
| 56 |
<li>PLTE (color palette)</li> |
|---|
| 57 |
<li>cHRM (device independent color info) </li> |
|---|
| 58 |
<li>gAMA (device independent color info) </li> |
|---|
| 59 |
<li>iCCP (device independent color info) </li> |
|---|
| 60 |
<li>sBIT (original number of significant bits) </li> |
|---|
| 61 |
<li>sRGB (device independent color info) </li> |
|---|
| 62 |
<li>pHYs (physical pixel dimensions) </li> |
|---|
| 63 |
<li>sPLT (suggested reduced palette) </li> |
|---|
| 64 |
<li>tIME (last image modification time) </li> |
|---|
| 65 |
</ul> |
|---|
| 66 |
</li> |
|---|
| 67 |
</ul> |
|---|
| 68 |
|
|---|
| 69 |
References: |
|---|
| 70 |
$(LINK2 http://members.gamedev.net/lode/projects/LodePNG/, Original lodepng) <br> |
|---|
| 71 |
$(LINK2 http://www.w3.org/TR/PNG/, PNG Specification) <br> |
|---|
| 72 |
$(LINK2 http://www.libpng.org/pub/png/pngsuite.html, PNG Suite: set of test images) <br> |
|---|
| 73 |
$(LINK2 http://optipng.sourceforge.net/, OptiPNG: tool to experimentally optimize png images) |
|---|
| 74 |
***************************************************************************************************/ |
|---|
| 75 |
module lodepng.Encode; |
|---|
| 76 |
|
|---|
| 77 |
version (Tango) |
|---|
| 78 |
{ |
|---|
| 79 |
import tango.math.Math; |
|---|
| 80 |
import tango.stdc.string; |
|---|
| 81 |
} |
|---|
| 82 |
else |
|---|
| 83 |
{ |
|---|
| 84 |
import std.c.string; |
|---|
| 85 |
import std.math; |
|---|
| 86 |
} |
|---|
| 87 |
import lodepng.ZlibCodec; |
|---|
| 88 |
public import lodepng.Common; |
|---|
| 89 |
|
|---|
| 90 |
/*************************************************************************************************** |
|---|
| 91 |
Encode pixels as a png file |
|---|
| 92 |
|
|---|
| 93 |
This function will attempt to convert to 24-bit RGB if it is a lossless operation, otherwise |
|---|
| 94 |
the resulting image will be in the 32-bit RGBA format. The array returned can be written to disk |
|---|
| 95 |
as a png file. |
|---|
| 96 |
|
|---|
| 97 |
params: |
|---|
| 98 |
source=the pixels to be encoded |
|---|
| 99 |
info=description of the source pixels |
|---|
| 100 |
throws: PngException |
|---|
| 101 |
returns: png file of the raw pixels provided by source and described by settings.info |
|---|
| 102 |
***************************************************************************************************/ |
|---|
| 103 |
ubyte[] encode(in ubyte[] source, in PngInfo info) |
|---|
| 104 |
{ |
|---|
| 105 |
ubyte[] buf; |
|---|
| 106 |
Chunk[] chunks; |
|---|
| 107 |
return _encode(source, Settings(info), buf, chunks); |
|---|
| 108 |
} |
|---|
| 109 |
|
|---|
| 110 |
/*************************************************************************************************** |
|---|
| 111 |
Encode pixels and / or raw chunks as a png file |
|---|
| 112 |
|
|---|
| 113 |
If chunkList contains IDAT chunks, they must be in order and no source pixels should be provided |
|---|
| 114 |
|
|---|
| 115 |
params: |
|---|
| 116 |
source=the pixels to be encoded, can be null |
|---|
| 117 |
info=description of the source pixels |
|---|
| 118 |
chunkList=array of chunks to be written |
|---|
| 119 |
throws: PngException |
|---|
| 120 |
returns: png file of the raw pixels provided by source and described by info |
|---|
| 121 |
***************************************************************************************************/ |
|---|
| 122 |
ubyte[] encode(in ubyte[] source, in PngInfo info, ref Chunk[] chunkList) |
|---|
| 123 |
{ |
|---|
| 124 |
ubyte[] buf; |
|---|
| 125 |
return _encode(source, Settings(info), buf, chunkList); |
|---|
| 126 |
} |
|---|
| 127 |
|
|---|
| 128 |
|
|---|
| 129 |
/*************************************************************************************************** |
|---|
| 130 |
Encode pixels as a png file |
|---|
| 131 |
|
|---|
| 132 |
see_also: Settings |
|---|
| 133 |
params: |
|---|
| 134 |
source=the pixels to be encoded |
|---|
| 135 |
options=description and options needed to encode the png file |
|---|
| 136 |
throws: PngException |
|---|
| 137 |
returns: png file of the raw pixels provided by source and described by options.info |
|---|
| 138 |
***************************************************************************************************/ |
|---|
| 139 |
ubyte[] encode(in ubyte[] source, Settings options) |
|---|
| 140 |
{ |
|---|
| 141 |
ubyte[] buf; |
|---|
| 142 |
Chunk[] chunks; |
|---|
| 143 |
return _encode(source, options, buf, chunks); |
|---|
| 144 |
} |
|---|
| 145 |
|
|---|
| 146 |
/*************************************************************************************************** |
|---|
| 147 |
Encode pixels as a png file |
|---|
| 148 |
|
|---|
| 149 |
see_also: Settings |
|---|
| 150 |
params: |
|---|
| 151 |
source=the pixels to be encoded |
|---|
| 152 |
options=description and options needed to encode the png file |
|---|
| 153 |
chunkList=array of chunks to be written |
|---|
| 154 |
throws: PngException |
|---|
| 155 |
returns: png file of the raw pixels provided by source and described by options.info |
|---|
| 156 |
***************************************************************************************************/ |
|---|
| 157 |
ubyte[] encode(in ubyte[] source, Settings options, ref Chunk[] chunkList) |
|---|
| 158 |
{ |
|---|
| 159 |
ubyte[] buf; |
|---|
| 160 |
return _encode(source, options, buf, chunkList); |
|---|
| 161 |
} |
|---|
| 162 |
|
|---|
| 163 |
/*************************************************************************************************** |
|---|
| 164 |
Filter method |
|---|
| 165 |
|
|---|
| 166 |
The png specification defines five types of filters. In addition each scanline can have a |
|---|
| 167 |
different filtering method applied (Dynamic). The latter method usually gives the best compression, |
|---|
| 168 |
when a fixed method is preferred paeth gives the best result most of the times. |
|---|
| 169 |
|
|---|
| 170 |
***************************************************************************************************/ |
|---|
| 171 |
enum FilterStrategy : ubyte |
|---|
| 172 |
{ |
|---|
| 173 |
None = 0, /// |
|---|
| 174 |
Up = 1, /// |
|---|
| 175 |
Sub = 2, /// |
|---|
| 176 |
Average = 3, /// |
|---|
| 177 |
Paeth = 4, /// |
|---|
| 178 |
Dynamic, /// |
|---|
| 179 |
} |
|---|
| 180 |
|
|---|
| 181 |
/*************************************************************************************************** |
|---|
| 182 |
Zlib compression method |
|---|
| 183 |
|
|---|
| 184 |
Which compression scheme works best depends on the type of image. Tools such as optipng can |
|---|
| 185 |
figure this out experimentally. However, RLE seems to give the best results in compression and |
|---|
| 186 |
performance. |
|---|
| 187 |
|
|---|
| 188 |
***************************************************************************************************/ |
|---|
| 189 |
enum CompressionStrategy : ubyte |
|---|
| 190 |
{ |
|---|
| 191 |
|
|---|
| 192 |
Default = 0, /// |
|---|
| 193 |
Filtered = 1, /// |
|---|
| 194 |
RLE = 3, /// |
|---|
| 195 |
None = ubyte.max /// |
|---|
| 196 |
} |
|---|
| 197 |
|
|---|
| 198 |
/** Controls all information needed to encode a png file */ |
|---|
| 199 |
struct Settings |
|---|
| 200 |
{ |
|---|
| 201 |
/** constructor */ |
|---|
| 202 |
static Settings opCall() |
|---|
| 203 |
{ |
|---|
| 204 |
Settings result; |
|---|
| 205 |
return result; |
|---|
| 206 |
} |
|---|
| 207 |
|
|---|
| 208 |
/** ditto */ |
|---|
| 209 |
static Settings opCall(PngInfo info, bool autoRemoveAlpha = false) |
|---|
| 210 |
{ |
|---|
| 211 |
Settings result; |
|---|
| 212 |
result.info = info; |
|---|
| 213 |
result.autoRemoveAlpha = autoRemoveAlpha; |
|---|
| 214 |
return result; |
|---|
| 215 |
} |
|---|
| 216 |
|
|---|
| 217 |
/** ditto */ |
|---|
| 218 |
static Settings opCall(PngImage image, bool autoRemoveAlpha = false) |
|---|
| 219 |
{ |
|---|
| 220 |
Settings result; |
|---|
| 221 |
result.info.image = image; |
|---|
| 222 |
result.autoRemoveAlpha = autoRemoveAlpha; |
|---|
| 223 |
return result; |
|---|
| 224 |
} |
|---|
| 225 |
|
|---|
| 226 |
invariant() |
|---|
| 227 |
{ |
|---|
| 228 |
assert(compressionLevel >=0 && compressionLevel <= 9, "invalid zlib compression level"); |
|---|
| 229 |
assert(targetColorType == ColorType.Any || |
|---|
| 230 |
targetColorType == ColorType.RGB || |
|---|
| 231 |
targetColorType == ColorType.RGBA, "colortype is not supported"); |
|---|
| 232 |
} |
|---|
| 233 |
|
|---|
| 234 |
/** png image information */ |
|---|
| 235 |
PngInfo info; |
|---|
| 236 |
|
|---|
| 237 |
/*********************************************************************************************** |
|---|
| 238 |
The colortype of the target image |
|---|
| 239 |
|
|---|
| 240 |
lodepng can only encode in RGB(A) format (yet?)). If the format is set ColorType.Any, RGB or |
|---|
| 241 |
RGBA is chosen depending on whether the source image has an alpha channel. |
|---|
| 242 |
***********************************************************************************************/ |
|---|
| 243 |
ColorType targetColorType = ColorType.Any; |
|---|
| 244 |
|
|---|
| 245 |
/*********************************************************************************************** |
|---|
| 246 |
Remove alpha channel |
|---|
| 247 |
|
|---|
| 248 |
If set to true and the source image has an alpha channel, this will be removed if (and |
|---|
| 249 |
only if) the image is fully opaque or a colorkey can be written. This is considered a |
|---|
| 250 |
lossless operation. |
|---|
| 251 |
***********************************************************************************************/ |
|---|
| 252 |
bool autoRemoveAlpha = false; |
|---|
| 253 |
/** if zlib compression is to be used on text */ |
|---|
| 254 |
bool compressText = false; |
|---|
| 255 |
/** zlib compression level, affects memory use. Must be in range 0-9 */ |
|---|
| 256 |
ubyte compressionLevel = 6; |
|---|
| 257 |
/** see FilterStrategy */ |
|---|
| 258 |
FilterStrategy filterStrategy = FilterStrategy.Dynamic; |
|---|
| 259 |
/** see CompressionStrategy */ |
|---|
| 260 |
CompressionStrategy compressionStrategy = CompressionStrategy.RLE; |
|---|
| 261 |
} |
|---|
| 262 |
|
|---|
| 263 |
private |
|---|
| 264 |
{ |
|---|
| 265 |
ubyte[] _encode( in ubyte[] source, in Settings settings, ref ubyte[] buffer, ref Chunk[] ChunkList) |
|---|
| 266 |
{ |
|---|
| 267 |
// TODO: be more sparing with memory here, can at least avoid one array copy |
|---|
| 268 |
|
|---|
| 269 |
// find out what colortype of target should be and whether colorkey (tRNS) should be made |
|---|
| 270 |
ColorType destColor = (settings.targetColorType == ColorType.RGB || |
|---|
| 271 |
settings.targetColorType == ColorType.RGBA) ? settings.targetColorType : |
|---|
| 272 |
settings.info.image.colorType; |
|---|
| 273 |
// colorkey |
|---|
| 274 |
if (source !is null) |
|---|
| 275 |
{ |
|---|
| 276 |
if (!(destColor == ColorType.RGB || destColor == ColorType.RGBA)) |
|---|
| 277 |
destColor = (hasAlphaChannel(settings.info.image.colorType)) ? ColorType.RGBA : ColorType.RGB; |
|---|
| 278 |
|
|---|
| 279 |
if (settings.autoRemoveAlpha && destColor == ColorType.RGBA && !settings.info.colorKey) |
|---|
| 280 |
{ |
|---|
| 281 |
ubyte[] colorKey; |
|---|
| 282 |
if (opaqueOrColorkey(source, settings.info, colorKey)) |
|---|
| 283 |
{ |
|---|
| 284 |
if (colorKey.length) |
|---|
| 285 |
ChunkList ~= Chunk(tRNS, colorKey); |
|---|
| 286 |
destColor = ColorType.RGB; |
|---|
| 287 |
} |
|---|
| 288 |
} |
|---|
| 289 |
} |
|---|
| 290 |
|
|---|
| 291 |
// properties of image to be written |
|---|
| 292 |
auto image = PngImage(settings.info.image.width, settings.info.image.height, 8, destColor, source is null ? settings.info.image.interlaced : 0); |
|---|
| 293 |
ChunkList ~= Chunk(IHDR, headerData(image)); |
|---|
| 294 |
|
|---|
| 295 |
if (source !is null) |
|---|
| 296 |
{ |
|---|
| 297 |
// convert pixels if necessary |
|---|
| 298 |
ubyte[] pixels; |
|---|
| 299 |
if (settings.info.image.colorType != destColor || settings.info.image.bitDepth != 8) |
|---|
| 300 |
pixels = convert(source, settings.info, destColor); |
|---|
| 301 |
else |
|---|
| 302 |
pixels = source; |
|---|
| 303 |
ChunkList ~= Chunk(IDAT, |
|---|
| 304 |
Encoder.create(settings.compressionStrategy, |
|---|
| 305 |
settings.compressionLevel)(filter(pixels, image, settings.filterStrategy))); |
|---|
| 306 |
} |
|---|
| 307 |
if(settings.info.colorKey) |
|---|
| 308 |
ChunkList ~= Chunk(tRNS, [0, cast(ubyte)settings.info.keyR, 0, cast(ubyte)settings.info.keyG, 0, cast(ubyte)settings.info.keyB]); |
|---|
| 309 |
if(settings.info.backgroundColor.length == 3) |
|---|
| 310 |
ChunkList ~= Chunk( bKGD, [ |
|---|
| 311 |
0, settings.info.backgroundColor[0], |
|---|
| 312 |
0, settings.info.backgroundColor[1], |
|---|
| 313 |
0, settings.info.backgroundColor[2] ]); |
|---|
| 314 |
else if(settings.info.backgroundColor.length == 6) |
|---|
| 315 |
ChunkList ~= Chunk( bKGD, settings.info.backgroundColor); |
|---|
| 316 |
if (settings.info.text !is null && (settings.info.text.latin1Text.length > 0 || settings.info.text.unicodeText.length > 0)) |
|---|
| 317 |
ChunkList ~= chunkifyText(settings); |
|---|
| 318 |
ChunkList.insertSort(); |
|---|
| 319 |
ChunkList ~= Chunk(IEND, []); |
|---|
| 320 |
|
|---|
| 321 |
// pre-allocate space needed |
|---|
| 322 |
uint pngLength = 8; |
|---|
| 323 |
foreach(chunk; ChunkList) |
|---|
| 324 |
pngLength += chunk.length; |
|---|
| 325 |
buffer.length = pngLength; |
|---|
| 326 |
buffer.length = 0; |
|---|
| 327 |
|
|---|
| 328 |
// create and write all data |
|---|
| 329 |
writeSignature(buffer); |
|---|
| 330 |
foreach(chunk; ChunkList) |
|---|
| 331 |
writeChunk(buffer, chunk); |
|---|
| 332 |
|
|---|
| 333 |
return buffer; |
|---|
| 334 |
} |
|---|
| 335 |
|
|---|
| 336 |
Chunk[] chunkifyText(ref Settings settings) |
|---|
| 337 |
{ |
|---|
| 338 |
Chunk[] result; |
|---|
| 339 |
if (settings.compressText) |
|---|
| 340 |
{ |
|---|
| 341 |
auto enc = Encoder.create(); |
|---|
| 342 |
foreach(ubyte[] keyword, ubyte[] value; settings.info.text) |
|---|
| 343 |
result ~= Chunk(zTXt, keyword ~ cast(ubyte[])[0, 0] ~ enc(value)); |
|---|
| 344 |
foreach(char[] keyword, char[] value; settings.info.text) |
|---|
| 345 |
result ~= Chunk(iTXt, cast(ubyte[])keyword ~ cast(ubyte[])[0, 1, 0, 0, 0] ~ enc(cast(ubyte[])value)); |
|---|
| 346 |
} |
|---|
| 347 |
else |
|---|
| 348 |
{ |
|---|
| 349 |
foreach(ubyte[] keyword, ubyte[] value; settings.info.text) |
|---|
| 350 |
result ~= Chunk(tEXt, keyword ~ cast(ubyte[])[0] ~ value); |
|---|
| 351 |
foreach(char[] keyword, char[] value; settings.info.text) |
|---|
| 352 |
result ~= Chunk(iTXt, cast(ubyte[])keyword ~ cast(ubyte[])[0, 0, 0, 0, 0] ~ cast(ubyte[])value); |
|---|
| 353 |
} |
|---|
| 354 |
return result; |
|---|
| 355 |
} |
|---|
| 356 |
|
|---|
| 357 |
ubyte[] filter(in ubyte[] source, ref PngImage image, FilterStrategy filterMethod = FilterStrategy.Dynamic) |
|---|
| 358 |
{ |
|---|
| 359 |
/* adaptive filtering */ |
|---|
| 360 |
|
|---|
| 361 |
ubyte[] buffer = new ubyte[image.width * (image.bpp / 8) * image.height]; |
|---|
| 362 |
uint bytewidth = (image.bpp + 7) / 8; |
|---|
| 363 |
|
|---|
| 364 |
uint scanlength = image.width * bytewidth; |
|---|
| 365 |
buffer.length = image.height * (scanlength + 1) + image.height; |
|---|
| 366 |
ubyte[] line, previous; |
|---|
| 367 |
buffer.length = 0; |
|---|
| 368 |
line = source[0..scanlength]; |
|---|
| 369 |
ubyte bestFilter = 0; |
|---|
| 370 |
|
|---|
| 371 |
uint absSum(ubyte[] array) |
|---|
| 372 |
{ |
|---|
| 373 |
uint result = 0; |
|---|
| 374 |
foreach(value; array) |
|---|
| 375 |
result += abs(cast(int)(cast(byte)value)); |
|---|
| 376 |
return result; |
|---|
| 377 |
} |
|---|
| 378 |
|
|---|
| 379 |
uint smallest = absSum(filterMap(line, &None, bytewidth)); |
|---|
| 380 |
|
|---|
| 381 |
void setSmallest(uint sum, ubyte filterType) |
|---|
| 382 |
{ |
|---|
| 383 |
if (sum < smallest) |
|---|
| 384 |
{ |
|---|
| 385 |
smallest = sum; |
|---|
| 386 |
bestFilter = filterType; |
|---|
| 387 |
} |
|---|
| 388 |
} |
|---|
| 389 |
|
|---|
| 390 |
if (filterMethod == FilterStrategy.Dynamic) |
|---|
| 391 |
{ |
|---|
| 392 |
for (ubyte f = 1; f < 5; f++) |
|---|
| 393 |
setSmallest(absSum(dynFilterMap(line, f, bytewidth)), f); |
|---|
| 394 |
buffer ~= bestFilter; |
|---|
| 395 |
buffer ~= dynFilterMap(line, bestFilter, bytewidth); |
|---|
| 396 |
|
|---|
| 397 |
for (int y = 1; y < image.height; y++) |
|---|
| 398 |
{ |
|---|
| 399 |
line = source[scanlength * y..scanlength * y + scanlength]; |
|---|
| 400 |
previous = source[scanlength * (y - 1)..scanlength * y]; |
|---|
| 401 |
|
|---|
| 402 |
bestFilter = 0; |
|---|
| 403 |
smallest = absSum(filterMap(line, &None, bytewidth)); |
|---|
| 404 |
for (ubyte f = 1; f < 5; f++) |
|---|
| 405 |
setSmallest(absSum(dynFilterMap(previous, line, f, bytewidth)), f); |
|---|
| 406 |
|
|---|
| 407 |
buffer ~= bestFilter; |
|---|
| 408 |
buffer ~= dynFilterMap(previous, line, bestFilter, bytewidth); |
|---|
| 409 |
} |
|---|
| 410 |
} |
|---|
| 411 |
else |
|---|
| 412 |
{ |
|---|
| 413 |
buffer ~= cast(ubyte)filterMethod; |
|---|
| 414 |
buffer ~= dynFilterMap(line, cast(ubyte)filterMethod, bytewidth); |
|---|
| 415 |
|
|---|
| 416 |
for (int y = 1; y < image.height; y++) |
|---|
| 417 |
{ |
|---|
| 418 |
line = source[scanlength * y..scanlength * y + scanlength]; |
|---|
| 419 |
previous = source[scanlength * (y - 1)..scanlength * y]; |
|---|
| 420 |
|
|---|
| 421 |
buffer ~= cast(ubyte)filterMethod; |
|---|
| 422 |
buffer ~= dynFilterMap(previous, line, cast(ubyte)filterMethod, bytewidth); |
|---|
| 423 |
} |
|---|
| 424 |
} |
|---|
| 425 |
return buffer; |
|---|
| 426 |
} |
|---|
| 427 |
|
|---|
| 428 |
bool opaqueOrColorkey(in ubyte[] image, ref PngInfo info, out ubyte[] colorKey) |
|---|
| 429 |
{ |
|---|
| 430 |
if (info.image.colorType == ColorType.Greyscale || info.image.colorType == ColorType.RGB) |
|---|
| 431 |
return false; |
|---|
| 432 |
else if (info.image.colorType == ColorType.RGBA ) |
|---|
| 433 |
{ |
|---|
| 434 |
uint numpixels = info.image.width * info.image.height; |
|---|
| 435 |
|
|---|
| 436 |
ubyte[3] ckey; |
|---|
| 437 |
bool hasCkey = false; |
|---|
| 438 |
|
|---|
| 439 |
for(size_t i = 0; i < numpixels; i++) |
|---|
| 440 |
{ |
|---|
| 441 |
if(image[i * 4 + 3] != 255) |
|---|
| 442 |
{ |
|---|
| 443 |
if(image[i * 4 + 3] == 0) |
|---|
| 444 |
{ |
|---|
| 445 |
if (hasCkey) |
|---|
| 446 |
{ |
|---|
| 447 |
if (ckey[0] != image[i * 4] || ckey[1] != image[i * 4 + 1] || |
|---|
| 448 |
ckey[2] != image[i * 4 + 2] ) |
|---|
| 449 |
return false; |
|---|
| 450 |
} |
|---|
| 451 |
else |
|---|
| 452 |
{ |
|---|
| 453 |
hasCkey = true; |
|---|
| 454 |
ckey[0..2] = image[i * 4 .. i * 4 + 2]; |
|---|
| 455 |
} |
|---|
| 456 |
} |
|---|
| 457 |
else |
|---|
| 458 |
return false; |
|---|
| 459 |
} |
|---|
| 460 |
} |
|---|
| 461 |
if (hasCkey) |
|---|
| 462 |
{ |
|---|
| 463 |
colorKey = new ubyte[6]; |
|---|
| 464 |
colorKey[1] = ckey[0]; |
|---|
| 465 |
colorKey[3] = ckey[1]; |
|---|
| 466 |
colorKey[5] = ckey[2]; |
|---|
| 467 |
} |
|---|
| 468 |
return true; |
|---|
| 469 |
} |
|---|
| 470 |
else if(info.image.colorType == ColorType.GreyscaleAlpha) |
|---|
| 471 |
{ |
|---|
| 472 |
size_t numpixels = image.length / 2; |
|---|
| 473 |
|
|---|
| 474 |
ubyte[1] ckey; |
|---|
| 475 |
bool hasCkey = false; |
|---|
| 476 |
|
|---|
| 477 |
for(size_t i = 0; i < numpixels; i++) |
|---|
| 478 |
{ |
|---|
| 479 |
if(image[i * 2 + 1] != 255) |
|---|
| 480 |
{ |
|---|
| 481 |
if (hasCkey) |
|---|
| 482 |
{ |
|---|
| 483 |
if (ckey[0] != image[i * 2]) |
|---|
| 484 |
return false; |
|---|
| 485 |
} |
|---|
| 486 |
else |
|---|
| 487 |
{ |
|---|
| 488 |
hasCkey = true; |
|---|
| 489 |
ckey[0] = image[i * 2]; |
|---|
| 490 |
} |
|---|
| 491 |
} |
|---|
| 492 |
} |
|---|
| 493 |
if (hasCkey) |
|---|
| 494 |
{ |
|---|
| 495 |
colorKey = new ubyte[2]; |
|---|
| 496 |
colorKey[1] = ckey[0]; |
|---|
| 497 |
} |
|---|
| 498 |
return true; |
|---|
| 499 |
} |
|---|
| 500 |
else if (info.image.colorType == ColorType.Palette) |
|---|
| 501 |
{ |
|---|
| 502 |
// TODO: implement this (compression optimization) |
|---|
| 503 |
return false; |
|---|
| 504 |
} |
|---|
| 505 |
} |
|---|
| 506 |
|
|---|
| 507 |
ubyte[] headerData(ref PngImage image) |
|---|
| 508 |
{ |
|---|
| 509 |
ubyte[] header = new ubyte[13]; |
|---|
| 510 |
header.length = 0; |
|---|
| 511 |
|
|---|
| 512 |
header.concatUint(image.width); |
|---|
| 513 |
header.concatUint(image.height); |
|---|
| 514 |
header ~= image.bitDepth; |
|---|
| 515 |
header ~= image.colorType; |
|---|
| 516 |
header ~= 0; //compression method |
|---|
| 517 |
header ~= 0; //filter method |
|---|
| 518 |
header ~= image.interlaced; //interlace method |
|---|
| 519 |
|
|---|
| 520 |
return header; |
|---|
| 521 |
} |
|---|
| 522 |
|
|---|
| 523 |
void concatUint(ref ubyte[] bytestream, uint num) |
|---|
| 524 |
{ |
|---|
| 525 |
bytestream.length = bytestream.length + 4; |
|---|
| 526 |
bytestream[$-4] = num >> 24; |
|---|
| 527 |
bytestream[$-3] = num >> 16; |
|---|
| 528 |
bytestream[$-2] = num >> 8; |
|---|
| 529 |
bytestream[$-1] = num; |
|---|
| 530 |
} |
|---|
| 531 |
|
|---|
| 532 |
void writeChunk(ref ubyte[] bytestream, ref Chunk chunk) |
|---|
| 533 |
{ |
|---|
| 534 |
bytestream.concatUint(chunk.data.length); |
|---|
| 535 |
|
|---|
| 536 |
bytestream.length = bytestream.length + 4; |
|---|
| 537 |
bytestream[$-4] = (chunk.type & 0xff000000) >> 24 ; |
|---|
| 538 |
bytestream[$-3] = (chunk.type & 0x00ff0000) >> 16; |
|---|
| 539 |
bytestream[$-2] = (chunk.type & 0x0000ff00) >> 8; |
|---|
| 540 |
bytestream[$-1] = chunk.type & 0x000000ff; |
|---|
| 541 |
|
|---|
| 542 |
bytestream.length = bytestream.length + chunk.data.length; |
|---|
| 543 |
bytestream[$ - chunk.data.length..$] = chunk.data; |
|---|
| 544 |
|
|---|
| 545 |
uint CRC = createCRC(bytestream[$ - chunk.data.length - 4.. $]); |
|---|
| 546 |
bytestream.concatUint(CRC); |
|---|
| 547 |
} |
|---|
| 548 |
|
|---|
| 549 |
void writeSignature(ref ubyte[] byteStream) |
|---|
| 550 |
{ |
|---|
| 551 |
byteStream ~= [137, 80, 78, 71, 13, 10, 26, 10]; |
|---|
| 552 |
} |
|---|
| 553 |
|
|---|
| 554 |
/++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|---|
| 555 |
From the png spec: pixels for filtering are defined as follows such, where x is the current |
|---|
| 556 |
being filtered and c, b correspond to the previous scanline: |
|---|
| 557 |
c b |
|---|
| 558 |
a x |
|---|
| 559 |
|
|---|
| 560 |
filters: construction Reconstruction |
|---|
| 561 |
0 None Filt(x) = Orig(x) Recon(x) = Filt(x) |
|---|
| 562 |
1 Sub Filt(x) = Orig(x) - Orig(a) Recon(x) = Filt(x) + Recon(a) |
|---|
| 563 |
2 Up Filt(x) = Orig(x) - Orig(b) Recon(x) |
|---|