On 17/06/2012 08:55, cal wrote:
<snip>
If you don't care too much about compression level, you simply zlib
compress the data, write it out by image row/scanline, include
appropriate header and chunk info, and you're done.
<snip>
Not quite. You need to encode it by scanline, add the filter byte at the beginning of
each line, _then_ zlib compress it. The filter byte can just be 0 throughout if you don't
want to worry about filtering.
If you just want a basic truecolour or greyscale PNG encoder, then it's a
matter of:
- write the PNG signature
- write the IHDR, being careful about byte order
- write the IDAT - zlib compressed image data
- write the IEND
You also need to compute the CRC of each chunk, but std.zlib has a function to do that as
well.
FWIW a while ago I wrote a simple experimental program that generates an image and encodes
it as a PNG. And I've just tweaked it and updated it to D2 (attached). It supports only
truecolour with 8 bits per sample, but it supports filtering, though it isn't adaptive
(you just specify the filter to use when you run the program).
Stewart.
import
std.file,
std.math,
std.path,
std.stdio,
std.zlib;
const ubyte[] header = [ 137, 80, 78, 71, 13, 10, 26, 10 ];
const uint WIDTH = 100, HEIGHT = 100;
align(1) struct IHDR {
uint width = WIDTH;
uint height = HEIGHT;
ubyte bitDepth = 8;
ubyte colourType = 2;
ubyte compressMethod, filterMethod, interlaceMethod;
version (LittleEndian) {
IHDR bigEndian() {
IHDR result = this;
result.width = .bigEndian(width);
result.height = .bigEndian(height);
return result;
}
} else {
ref IHDR bigEndian() { return this; }
}
}
align(1) struct RGB {
ubyte red, green, blue;
}
void main(string[] a) {
ubyte filterType;
string outputFile = "makepng.png";
if (a.length > 1 && a[1].length == 1 && a[1][0] >= '0' && a[1][0] <=
'4') {
filterType = cast(ubyte) (a[1][0] - '0');
a = a[1..$];
}
if (a.length > 1) {
outputFile = defaultExtension(a[1], "png");
}
void[] pngData = header.dup;
pngData ~= makeChunk("IHDR", IHDR.init.bigEndian);
RGB[HEIGHT][WIDTH] imageData;
foreach (y, ref scanline; imageData) {
foreach (x, ref pixel; scanline) {
if ((x-50) * (x-50) + (y-50) * (y-50) < 2000) {
pixel.red = 255;
pixel.green = cast(ubyte) (2 * x);
pixel.blue = cast(ubyte) (255 * y / 100);
}
}
}
ubyte[] imageData2;
if (filterType == 0) {
foreach (y, ref scanLine; imageData) {
imageData2 ~= 0;
imageData2 ~= cast(ubyte[]) scanLine;
}
} else {
Predictor predictor = predictors[filterType];
ubyte[] prevLine, thisLine;
prevLine.length = 3 * (WIDTH + 1);
thisLine.length = 3 * (WIDTH + 1);
foreach (y, ref scanLine; imageData) {
thisLine[3..$] = cast(ubyte[]) scanLine;
imageData2 ~= filterType;
for (uint x = 0; x < 3 * WIDTH; x++) {
imageData2 ~= cast(ubyte) (thisLine[x + 3]
- predictor(thisLine[x], prevLine[x+3],
prevLine[x]));
}
ubyte[] tempLine = prevLine;
prevLine = thisLine;
thisLine = tempLine;
}
}
assert (imageData2.length == (3 * WIDTH + 1) * HEIGHT);
pngData ~= makeChunkV("IDAT", compress(imageData2));
writeln(pngData.length);
pngData ~= makeChunkV("IEND", null);
std.file.write(outputFile, pngData);
}
version (LittleEndian) {
uint bigEndian(uint value) {
return (value << 24) | ((value & 0x0000FF00) << 8)
| ((value & 0x00FF0000) >> 8) | (value >> 24);
}
} else {
uint bigEndian(uint value) { return value; }
}
void[] bigEndianBytes(uint value) {
uint[] be = [bigEndian(value)];
return be;
}
void[] makeChunkV(in string type, in void[] data)
in {
assert(type.length == 4);
} body {
void[] typeAndData = type ~ data;
uint crc = crc32(0, typeAndData);
return bigEndianBytes(data.length) ~ typeAndData ~ bigEndianBytes(crc);
}
void[] makeChunk(T)(in string type, in T data) {
static assert (!is(T : void[]));
return makeChunkV(type, cast(void[]) (&data)[0..1]);
}
// FILTER PREDICTOR FUNCTIONS
alias ubyte function(ubyte, ubyte, ubyte) Predictor;
Predictor[5] predictors = [
&noFilter, &subFilter, &upFilter, &averageFilter, &paethFilter
];
ubyte noFilter(ubyte left, ubyte up, ubyte leftUp) {
return 0;
}
ubyte subFilter(ubyte left, ubyte up, ubyte leftUp) {
return left;
}
ubyte upFilter(ubyte left, ubyte up, ubyte leftUp) {
return up;
}
ubyte averageFilter(ubyte left, ubyte up, ubyte leftUp) {
return (left + up) >> 1;
}
ubyte paethFilter(ubyte left, ubyte up, ubyte leftUp) {
int
p = left + up - leftUp,
pa = abs(p - left),
pb = abs(p - up),
pc = abs(p - leftUp);
if (pa <= pb && pa <= pc) return left;
if (pb <= pc) return up;
return leftUp;
}