Fix mistakes in PNG decoder

This commit is contained in:
UnknownShadow200 2017-10-08 01:09:01 +11:00
parent 75f031fd90
commit c6f7e557c5
5 changed files with 45 additions and 44 deletions

View File

@ -65,20 +65,15 @@ void Png_CheckHeader(Stream* stream) {
} }
} }
void Png_Filter(UInt8 type, UInt8 bpp, UInt8* line, UInt8* prior, UInt32 lineLen) { void Png_Filter(UInt8 type, UInt8 bytesPerPixel, UInt8* line, UInt8* prior, UInt32 lineLen) {
UInt32 i; UInt32 i, j;
Int32 p, pa, pb, pc;
UInt8 a, b, c;
switch (type) { switch (type) {
case PNG_FILTER_NONE: case PNG_FILTER_NONE:
return; return;
case PNG_FILTER_SUB: case PNG_FILTER_SUB:
a = 0; for (i = bytesPerPixel, j = 0; i < lineLen; i++, j++) {
for (i = 0; i < lineLen; i++) { line[i] = (UInt8)(line[i] + line[j]);
line[i] = (UInt8)(line[i] + a);
a = line[i];
} }
return; return;
@ -89,27 +84,26 @@ void Png_Filter(UInt8 type, UInt8 bpp, UInt8* line, UInt8* prior, UInt32 lineLen
return; return;
case PNG_FILTER_AVERAGE: case PNG_FILTER_AVERAGE:
a = 0; for (i = 0; i < bytesPerPixel; i++) {
for (i = 0; i < lineLen; i++) { line[i] = (UInt8)(line[i] + (prior[i] >> 1));
line[i] = (UInt8)(line[i] + ((prior[i] + a) >> 1)); }
a = line[i]; for (j = 0; i < lineLen; i++, j++) {
line[i] = (UInt8)(line[i] + ((prior[i] + line[j]) >> 1));
} }
return; return;
case PNG_FILTER_PAETH: case PNG_FILTER_PAETH:
a = 0; c = 0; /* TODO: verify this is right */
for (i = 0; i < lineLen; i++) { for (i = bytesPerPixel, j = 0; i < lineLen; i++, j++) {
b = prior[i]; UInt8 a = line[j], b = prior[i], c = prior[j];
p = a + b - c; Int32 p = a + b - c;
pa = Math_AbsI(p - a); Int32 pa = Math_AbsI(p - a);
pb = Math_AbsI(p - b); Int32 pb = Math_AbsI(p - b);
pc = Math_AbsI(p - c); Int32 pc = Math_AbsI(p - c);
if (pa <= pb && pa <= pc) { line[i] = (UInt8)(line[i] + a); } if (pa <= pb && pa <= pc) { line[i] = (UInt8)(line[i] + a); }
else if (pb <= pc) { line[i] = (UInt8)(line[i] + b); } else if (pb <= pc) { line[i] = (UInt8)(line[i] + b); }
else { line[i] = (UInt8)(line[i] + c); } else { line[i] = (UInt8)(line[i] + c); }
a = line[i]; c = b;
} }
return; return;
@ -119,14 +113,14 @@ void Png_Filter(UInt8 type, UInt8 bpp, UInt8* line, UInt8* prior, UInt32 lineLen
} }
} }
void Png_Expand_GRAYSCALE(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) { void Png_Expand_GRAYSCALE(UInt8 bitsPerSample, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) {
Int32 i, j; Int32 i, j;
#define PNG_DO_GRAYSCALE(tmp, dstI, srcI, scale) tmp = src[srcI] * scale; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, 255); #define PNG_DO_GRAYSCALE(tmp, dstI, srcI, scale) tmp = src[srcI] * scale; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, 255);
#define PNG_DO_GRAYSCALE_X(tmp, dstI, srcI) tmp = src[srcI]; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, 255); #define PNG_DO_GRAYSCALE_X(tmp, dstI, srcI) tmp = src[srcI]; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, 255);
UInt8 cur, rgb1, rgb2, rgb3, rgb4; UInt8 cur, rgb1, rgb2, rgb3, rgb4;
Int32 mask; Int32 mask;
switch (bpp) { switch (bitsPerSample) {
case 1: case 1:
for (i = 0, j = 0; i < (width & ~0x7); i += 8, j++) { for (i = 0, j = 0; i < (width & ~0x7); i += 8, j++) {
cur = src[j]; cur = src[j];
@ -174,12 +168,12 @@ void Png_Expand_GRAYSCALE(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, U
} }
} }
void Png_Expand_RGB(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) { void Png_Expand_RGB(UInt8 bitsPerSample, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) {
Int32 i, j; Int32 i, j;
#define PNG_DO_RGB__8(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 1], src[srcI + 2], 255); #define PNG_DO_RGB__8(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 1], src[srcI + 2], 255);
#define PNG_DO_RGB_16(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 3], src[srcI + 5], 255); #define PNG_DO_RGB_16(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 3], src[srcI + 5], 255);
if (bpp == 8) { if (bitsPerSample == 8) {
for (i = 0, j = 0; i < (width & ~0x03); i += 4, j += 12) { for (i = 0, j = 0; i < (width & ~0x03); i += 4, j += 12) {
PNG_DO_RGB__8(i , j ); PNG_DO_RGB__8(i + 1, j + 3); PNG_DO_RGB__8(i , j ); PNG_DO_RGB__8(i + 1, j + 3);
PNG_DO_RGB__8(i + 2, j + 6); PNG_DO_RGB__8(i + 3, j + 9); PNG_DO_RGB__8(i + 2, j + 6); PNG_DO_RGB__8(i + 3, j + 9);
@ -190,13 +184,13 @@ void Png_Expand_RGB(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, UInt32*
} }
} }
void Png_Expand_INDEXED(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) { void Png_Expand_INDEXED(UInt8 bitsPerSample, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) {
Int32 i, j; Int32 i, j;
#define PNG_DO_INDEXED(dstI, srcI) dst[dstI] = palette[srcI]; #define PNG_DO_INDEXED(dstI, srcI) dst[dstI] = palette[srcI];
UInt8 cur; UInt8 cur;
Int32 mask; Int32 mask;
switch (bpp) { switch (bitsPerSample) {
case 1: case 1:
for (i = 0, j = 0; i < (width & ~0x7); i += 8, j++) { for (i = 0, j = 0; i < (width & ~0x7); i += 8, j++) {
cur = src[j]; cur = src[j];
@ -241,13 +235,13 @@ void Png_Expand_INDEXED(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, UIn
} }
} }
void Png_Expand_GRAYSCALE_A(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) { void Png_Expand_GRAYSCALE_A(UInt8 bitsPerSample, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) {
Int32 i, j; Int32 i, j;
#define PNG_DO_GRAYSCALE_A__8(tmp, dstI, srcI) tmp = src[srcI]; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, src[srcI + 1]); #define PNG_DO_GRAYSCALE_A__8(tmp, dstI, srcI) tmp = src[srcI]; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, src[srcI + 1]);
#define PNG_DO_GRAYSCALE_A_16(tmp, dstI, srcI) tmp = src[srcI]; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, src[srcI + 2]); #define PNG_DO_GRAYSCALE_A_16(tmp, dstI, srcI) tmp = src[srcI]; dst[dstI] = PackedCol_ARGB(tmp, tmp, tmp, src[srcI + 2]);
UInt8 rgb1, rgb2, rgb3, rgb4; UInt8 rgb1, rgb2, rgb3, rgb4;
if (bpp == 8) { if (bitsPerSample == 8) {
for (i = 0, j = 0; i < (width & ~0x3); i += 4, j += 8) { for (i = 0, j = 0; i < (width & ~0x3); i += 4, j += 8) {
PNG_DO_GRAYSCALE_A__8(rgb1, i , j ); PNG_DO_GRAYSCALE_A__8(rgb2, i + 1, j + 2); PNG_DO_GRAYSCALE_A__8(rgb1, i , j ); PNG_DO_GRAYSCALE_A__8(rgb2, i + 1, j + 2);
PNG_DO_GRAYSCALE_A__8(rgb3, i + 2, j + 4); PNG_DO_GRAYSCALE_A__8(rgb4, i + 3, j + 5); PNG_DO_GRAYSCALE_A__8(rgb3, i + 2, j + 4); PNG_DO_GRAYSCALE_A__8(rgb4, i + 3, j + 5);
@ -258,12 +252,12 @@ void Png_Expand_GRAYSCALE_A(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src,
} }
} }
void Png_Expand_RGB_A(UInt8 bpp, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) { void Png_Expand_RGB_A(UInt8 bitsPerSample, Int32 width, UInt32* palette, UInt8* src, UInt32* dst) {
Int32 i, j; Int32 i, j;
#define PNG_DO_RGB_A__8(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 1], src[srcI + 2], src[srcI + 3]); #define PNG_DO_RGB_A__8(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 1], src[srcI + 2], src[srcI + 3]);
#define PNG_DO_RGB_A_16(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 3], src[srcI + 5], src[srcI + 7]); #define PNG_DO_RGB_A_16(dstI, srcI) dst[dstI] = PackedCol_ARGB(src[srcI], src[srcI + 3], src[srcI + 5], src[srcI + 7]);
if (bpp == 8) { if (bitsPerSample == 8) {
for (i = 0, j = 0; i < (width & ~0x3); i += 4, j += 16) { for (i = 0, j = 0; i < (width & ~0x3); i += 4, j += 16) {
PNG_DO_RGB_A__8(i , j ); PNG_DO_RGB_A__8(i + 1, j + 4 ); PNG_DO_RGB_A__8(i , j ); PNG_DO_RGB_A__8(i + 1, j + 4 );
PNG_DO_RGB_A__8(i + 2, j + 8 ); PNG_DO_RGB_A__8(i + 3, j + 12); PNG_DO_RGB_A__8(i + 2, j + 8 ); PNG_DO_RGB_A__8(i + 3, j + 12);
@ -287,12 +281,13 @@ void Png_ComputeTransparency(Bitmap* bmp, UInt32 transparentCol) {
} }
} }
/* TODO: Test a lot of .png files and ensure output is right */
#define PNG_MAX_DIMS 0x10000L #define PNG_MAX_DIMS 0x10000L
void Bitmap_DecodePng(Bitmap* bmp, Stream* stream) { void Bitmap_DecodePng(Bitmap* bmp, Stream* stream) {
Png_CheckHeader(stream); Png_CheckHeader(stream);
Bitmap_Create(bmp, 0, 0, NULL); Bitmap_Create(bmp, 0, 0, NULL);
UInt32 transparentCol = PackedCol_ARGB(0, 0, 0, 255); UInt32 transparentCol = PackedCol_ARGB(0, 0, 0, 255);
UInt8 col, bpp; UInt8 col, bitsPerSample, bytesPerPixel;
Png_RowExpander rowExpander; Png_RowExpander rowExpander;
UInt32 palette[PNG_PALETTE]; UInt32 palette[PNG_PALETTE];
@ -329,19 +324,20 @@ void Bitmap_DecodePng(Bitmap* bmp, Stream* stream) {
bmp->Scan0 = Platform_MemAlloc(Bitmap_DataSize(bmp->Width, bmp->Height)); bmp->Scan0 = Platform_MemAlloc(Bitmap_DataSize(bmp->Width, bmp->Height));
if (bmp->Scan0 == NULL) ErrorHandler_Fail("Failed to allocate memory for PNG bitmap"); if (bmp->Scan0 == NULL) ErrorHandler_Fail("Failed to allocate memory for PNG bitmap");
bpp = Stream_ReadUInt8(stream); bitsPerSample = Stream_ReadUInt8(stream);
if (bpp > 16 || !Math_IsPowOf2(bpp)) ErrorHandler_Fail("PNG has invalid bits per pixel"); if (bitsPerSample > 16 || !Math_IsPowOf2(bitsPerSample)) ErrorHandler_Fail("PNG has invalid bits per pixel");
col = Stream_ReadUInt8(stream); col = Stream_ReadUInt8(stream);
if (col == 1 || col == 3 || col > 6) ErrorHandler_Fail("PNG has invalid colour type"); if (col == 1 || col == 3 || col > 6) ErrorHandler_Fail("PNG has invalid colour type");
if (bpp < 8 && (col >= PNG_COL_RGB && col != PNG_COL_INDEXED)) ErrorHandler_Fail("PNG has invalid bpp for this colour type"); if (bitsPerSample < 8 && (col >= PNG_COL_RGB && col != PNG_COL_INDEXED)) ErrorHandler_Fail("PNG has invalid bpp for this colour type");
if (bpp == 16 && col == PNG_COL_INDEXED) ErrorHandler_Fail("PNG has invalid bpp for this colour type"); if (bitsPerSample == 16 && col == PNG_COL_INDEXED) ErrorHandler_Fail("PNG has invalid bpp for this colour type");
if (Stream_ReadUInt8(stream) != 0) ErrorHandler_Fail("PNG compression method must be DEFLATE"); if (Stream_ReadUInt8(stream) != 0) ErrorHandler_Fail("PNG compression method must be DEFLATE");
if (Stream_ReadUInt8(stream) != 0) ErrorHandler_Fail("PNG filter method must be ADAPTIVE"); if (Stream_ReadUInt8(stream) != 0) ErrorHandler_Fail("PNG filter method must be ADAPTIVE");
if (Stream_ReadUInt8(stream) != 0) ErrorHandler_Fail("PNG interlacing not supported"); if (Stream_ReadUInt8(stream) != 0) ErrorHandler_Fail("PNG interlacing not supported");
UInt32 samplesPerPixel[7] = { 1,0,3,1,2,0,4 }; UInt32 samplesPerPixel[7] = { 1,0,3,1,2,0,4 };
scanlineSize = ((samplesPerPixel[col] * bpp * bmp->Width) + 7) >> 3; scanlineSize = ((samplesPerPixel[col] * bitsPerSample * bmp->Width) + 7) >> 3;
bytesPerPixel = ((samplesPerPixel[col] * bitsPerSample) + 7) >> 3;
Platform_MemSet(scanlineA, 0, scanlineSize + 1); /* Prior row should be 0 per PNG spec */ Platform_MemSet(scanlineA, 0, scanlineSize + 1); /* Prior row should be 0 per PNG spec */
switch (col) { switch (col) {
@ -406,7 +402,6 @@ void Bitmap_DecodePng(Bitmap* bmp, Stream* stream) {
while (scanlineY < bmp->Height) { while (scanlineY < bmp->Height) {
UInt32 toRead = scanlineBytes - scanlineIndex, read; UInt32 toRead = scanlineBytes - scanlineIndex, read;
UInt8* scanline = (scanlineY & 1) == 0 ? scanlineB : scanlineA; UInt8* scanline = (scanlineY & 1) == 0 ? scanlineB : scanlineA;
UInt32 pos = Platform_FilePosition(stream->Data);
ReturnCode code = compStream.Read(&compStream, &scanline[scanlineIndex], toRead, &read); ReturnCode code = compStream.Read(&compStream, &scanline[scanlineIndex], toRead, &read);
if (code != 0) ErrorHandler_FailWithCode(code, "PNG - reading image bulk data"); if (code != 0) ErrorHandler_FailWithCode(code, "PNG - reading image bulk data");
@ -414,8 +409,8 @@ void Bitmap_DecodePng(Bitmap* bmp, Stream* stream) {
scanlineIndex += read; scanlineIndex += read;
if (scanlineIndex == scanlineBytes) { if (scanlineIndex == scanlineBytes) {
UInt8* prior = (scanlineY & 1) == 0 ? scanlineA : scanlineB; UInt8* prior = (scanlineY & 1) == 0 ? scanlineA : scanlineB;
Png_Filter(scanline[0], bpp, &scanline[1], &prior[1], scanlineSize); Png_Filter(scanline[0], bytesPerPixel, &scanline[1], &prior[1], scanlineSize);
rowExpander(bpp, bmp->Width, palette, &scanline[1], Bitmap_GetRow(bmp, scanlineY)); rowExpander(bitsPerSample, bmp->Width, palette, &scanline[1], Bitmap_GetRow(bmp, scanlineY));
scanlineIndex = 0; scanlineIndex = 0;
scanlineY++; scanlineY++;

View File

@ -33,6 +33,11 @@ void Bitmap_CopyBlock(Int32 srcX, Int32 srcY, Int32 dstX, Int32 dstY, Bitmap* sr
void Bitmap_CopyRow(Int32 srcY, Int32 dstY, Bitmap* src, Bitmap* dst, Int32 width); void Bitmap_CopyRow(Int32 srcY, Int32 dstY, Bitmap* src, Bitmap* dst, Int32 width);
/* Allocates a new bitmap of the given dimensions. You are responsible for freeing its memory! */ /* Allocates a new bitmap of the given dimensions. You are responsible for freeing its memory! */
void Bitmap_Allocate(Bitmap* bmp, Int32 width, Int32 height); void Bitmap_Allocate(Bitmap* bmp, Int32 width, Int32 height);
/*
Partially based off information from
https://handmade.network/forums/wip/t/2363-implementing_a_basic_png_reader_the_handmade_way
https://github.com/nothings/stb/blob/master/stb_image.h
*/
/* Decodes a PNG bitmap from the given stream. */ /* Decodes a PNG bitmap from the given stream. */
void Bitmap_DecodePng(Bitmap* bmp, Stream* stream); void Bitmap_DecodePng(Bitmap* bmp, Stream* stream);
#endif #endif

View File

@ -10,6 +10,7 @@
/* Max number of characters strings can have. */ /* Max number of characters strings can have. */
#define STRING_SIZE 64 #define STRING_SIZE 64
#define FILENAME_SIZE 260
/* Chunk axis length, in blocks. */ /* Chunk axis length, in blocks. */
#define CHUNK_SIZE 16 #define CHUNK_SIZE 16

View File

@ -50,7 +50,7 @@ Int32 Stream_TryReadByte(Stream* stream) {
void Stream_SetName(Stream* stream, STRING_TRANSIENT String* name) { void Stream_SetName(Stream* stream, STRING_TRANSIENT String* name) {
stream->Name = String_FromRawBuffer(stream->NameBuffer, STREAM_NAME_LEN); stream->Name = String_FromRawBuffer(stream->NameBuffer, FILENAME_SIZE);
String_AppendString(&stream->Name, name); String_AppendString(&stream->Name, name);
} }

View File

@ -3,6 +3,7 @@
#include "Typedefs.h" #include "Typedefs.h"
#include "String.h" #include "String.h"
#include "ErrorHandler.h" #include "ErrorHandler.h"
#include "Constants.h"
/* Defines an abstract way of reading and writing data in a streaming manner. /* Defines an abstract way of reading and writing data in a streaming manner.
Also provides common helper methods for reading/writing data to/from streams. Also provides common helper methods for reading/writing data to/from streams.
Copyright 2017 ClassicalSharp | Licensed under BSD-3 Copyright 2017 ClassicalSharp | Licensed under BSD-3
@ -11,7 +12,6 @@
#define STREAM_SEEKFROM_BEGIN 0 #define STREAM_SEEKFROM_BEGIN 0
#define STREAM_SEEKFROM_CURRENT 1 #define STREAM_SEEKFROM_CURRENT 1
#define STREAM_SEEKFROM_END 2 #define STREAM_SEEKFROM_END 2
#define STREAM_NAME_LEN 256
typedef ReturnCode (*Stream_Operation)(struct Stream_* stream, UInt8* data, UInt32 count, UInt32* modified); typedef ReturnCode (*Stream_Operation)(struct Stream_* stream, UInt8* data, UInt32 count, UInt32* modified);
typedef ReturnCode (*Stream_Seek)(struct Stream_* stream, Int32 offset, Int32 seekType); typedef ReturnCode (*Stream_Seek)(struct Stream_* stream, Int32 offset, Int32 seekType);
@ -32,7 +32,7 @@ typedef struct Stream_ {
/* General purpose numerical metadata for the stream. */ /* General purpose numerical metadata for the stream. */
UInt32 Data2; UInt32 Data2;
/* Raw name buffer */ /* Raw name buffer */
UInt8 NameBuffer[String_BufferSize(STREAM_NAME_LEN)]; UInt8 NameBuffer[String_BufferSize(FILENAME_SIZE)];
/* The name of the stream. */ /* The name of the stream. */
String Name; String Name;
} Stream; } Stream;