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;
}

Reply via email to