JPG and PNG decoder

Stewart Gordon smjg_1998 at yahoo.com
Sun Jun 17 06:25:31 PDT 2012


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.
-------------- next part --------------
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;
}


More information about the Digitalmars-d-announce mailing list