mirror of https://github.com/bsnes-emu/bsnes.git
424 lines
11 KiB
C++
424 lines
11 KiB
C++
|
#ifndef NALL_PNG_HPP
|
||
|
#define NALL_PNG_HPP
|
||
|
|
||
|
//PNG image decoder
|
||
|
//author: byuu
|
||
|
|
||
|
#include <nall/inflate.hpp>
|
||
|
#include <nall/string.hpp>
|
||
|
|
||
|
namespace nall {
|
||
|
|
||
|
struct png {
|
||
|
uint32_t *data;
|
||
|
unsigned size;
|
||
|
|
||
|
struct Info {
|
||
|
unsigned width;
|
||
|
unsigned height;
|
||
|
unsigned bitDepth;
|
||
|
unsigned colorType;
|
||
|
unsigned compressionMethod;
|
||
|
unsigned filterType;
|
||
|
unsigned interlaceMethod;
|
||
|
|
||
|
unsigned bytesPerPixel;
|
||
|
unsigned pitch;
|
||
|
|
||
|
uint8_t palette[256][3];
|
||
|
} info;
|
||
|
|
||
|
uint8_t *rawData;
|
||
|
unsigned rawSize;
|
||
|
|
||
|
inline bool decode(const string &filename);
|
||
|
inline bool decode(const uint8_t *sourceData, unsigned sourceSize);
|
||
|
inline void transform();
|
||
|
inline void alphaTransform(uint32_t rgb = 0xffffff);
|
||
|
inline png();
|
||
|
inline ~png();
|
||
|
|
||
|
protected:
|
||
|
enum class FourCC : unsigned {
|
||
|
IHDR = 0x49484452,
|
||
|
PLTE = 0x504c5445,
|
||
|
IDAT = 0x49444154,
|
||
|
IEND = 0x49454e44,
|
||
|
};
|
||
|
|
||
|
static const unsigned interlace[7][4];
|
||
|
unsigned bitpos;
|
||
|
|
||
|
inline unsigned inflateSize();
|
||
|
inline bool deinterlace(const uint8_t *&inputData, unsigned pass);
|
||
|
inline bool filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height);
|
||
|
inline unsigned read(const uint8_t *data, unsigned length);
|
||
|
inline unsigned decode(const uint8_t *&data);
|
||
|
inline unsigned readbits(const uint8_t *&data);
|
||
|
inline unsigned scale(unsigned n);
|
||
|
};
|
||
|
|
||
|
bool png::decode(const string &filename) {
|
||
|
uint8_t *data;
|
||
|
unsigned size;
|
||
|
if(file::read(filename, data, size) == false) return false;
|
||
|
bool result = decode(data, size);
|
||
|
delete[] data;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
bool png::decode(const uint8_t *sourceData, unsigned sourceSize) {
|
||
|
if(sourceSize < 8) return false;
|
||
|
if(read(sourceData + 0, 4) != 0x89504e47) return false;
|
||
|
if(read(sourceData + 4, 4) != 0x0d0a1a0a) return false;
|
||
|
|
||
|
uint8_t *compressedData = 0;
|
||
|
unsigned compressedSize = 0;
|
||
|
|
||
|
unsigned offset = 8;
|
||
|
while(offset < sourceSize) {
|
||
|
unsigned length = read(sourceData + offset + 0, 4);
|
||
|
unsigned fourCC = read(sourceData + offset + 4, 4);
|
||
|
unsigned checksum = read(sourceData + offset + 8 + length, 4);
|
||
|
|
||
|
if(fourCC == (unsigned)FourCC::IHDR) {
|
||
|
info.width = read(sourceData + offset + 8, 4);
|
||
|
info.height = read(sourceData + offset + 12, 4);
|
||
|
info.bitDepth = read(sourceData + offset + 16, 1);
|
||
|
info.colorType = read(sourceData + offset + 17, 1);
|
||
|
info.compressionMethod = read(sourceData + offset + 18, 1);
|
||
|
info.filterType = read(sourceData + offset + 19, 1);
|
||
|
info.interlaceMethod = read(sourceData + offset + 20, 1);
|
||
|
|
||
|
if(info.bitDepth == 0 || info.bitDepth > 16) return false;
|
||
|
if(info.bitDepth & (info.bitDepth - 1)) return false; //not a power of two
|
||
|
if(info.compressionMethod != 0) return false;
|
||
|
if(info.filterType != 0) return false;
|
||
|
if(info.interlaceMethod != 0 && info.interlaceMethod != 1) return false;
|
||
|
|
||
|
switch(info.colorType) {
|
||
|
case 0: info.bytesPerPixel = info.bitDepth * 1; break; //L
|
||
|
case 2: info.bytesPerPixel = info.bitDepth * 3; break; //R,G,B
|
||
|
case 3: info.bytesPerPixel = info.bitDepth * 1; break; //P
|
||
|
case 4: info.bytesPerPixel = info.bitDepth * 2; break; //L,A
|
||
|
case 6: info.bytesPerPixel = info.bitDepth * 4; break; //R,G,B,A
|
||
|
default: return false;
|
||
|
}
|
||
|
|
||
|
if(info.colorType == 2 || info.colorType == 4 || info.colorType == 6)
|
||
|
if(info.bitDepth != 8 && info.bitDepth != 16) return false;
|
||
|
if(info.colorType == 3 && info.bitDepth == 16) return false;
|
||
|
|
||
|
info.bytesPerPixel = (info.bytesPerPixel + 7) / 8;
|
||
|
info.pitch = (int)info.width * info.bytesPerPixel;
|
||
|
}
|
||
|
|
||
|
if(fourCC == (unsigned)FourCC::PLTE) {
|
||
|
if(length % 3) return false;
|
||
|
for(unsigned n = 0, p = offset + 8; n < length / 3; n++) {
|
||
|
info.palette[n][0] = sourceData[p++];
|
||
|
info.palette[n][1] = sourceData[p++];
|
||
|
info.palette[n][2] = sourceData[p++];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(fourCC == (unsigned)FourCC::IDAT) {
|
||
|
compressedData = (uint8_t*)realloc(compressedData, compressedSize + length);
|
||
|
memcpy(compressedData + compressedSize, sourceData + offset + 8, length);
|
||
|
compressedSize += length;
|
||
|
}
|
||
|
|
||
|
if(fourCC == (unsigned)FourCC::IEND) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
offset += 4 + 4 + length + 4;
|
||
|
}
|
||
|
|
||
|
unsigned interlacedSize = inflateSize();
|
||
|
uint8_t *interlacedData = new uint8_t[interlacedSize];
|
||
|
|
||
|
bool result = inflate(interlacedData, interlacedSize, compressedData + 2, compressedSize - 6);
|
||
|
delete[] compressedData;
|
||
|
|
||
|
if(result == false) {
|
||
|
delete[] interlacedData;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
rawSize = info.width * info.height * info.bytesPerPixel;
|
||
|
rawData = new uint8_t[rawSize];
|
||
|
|
||
|
if(info.interlaceMethod == 0) {
|
||
|
if(filter(rawData, interlacedData, info.width, info.height) == false) {
|
||
|
delete[] interlacedData;
|
||
|
delete[] rawData;
|
||
|
rawData = 0;
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
const uint8_t *passData = interlacedData;
|
||
|
for(unsigned pass = 0; pass < 7; pass++) {
|
||
|
if(deinterlace(passData, pass) == false) {
|
||
|
delete[] interlacedData;
|
||
|
delete[] rawData;
|
||
|
rawData = 0;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
delete[] interlacedData;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
const unsigned png::interlace[7][4] = {
|
||
|
//x-distance, y-distance, x-origin, y-origin
|
||
|
{ 8, 8, 0, 0 },
|
||
|
{ 8, 8, 4, 0 },
|
||
|
{ 4, 8, 0, 4 },
|
||
|
{ 4, 4, 2, 0 },
|
||
|
{ 2, 4, 0, 2 },
|
||
|
{ 2, 2, 1, 0 },
|
||
|
{ 1, 2, 0, 1 },
|
||
|
};
|
||
|
|
||
|
unsigned png::inflateSize() {
|
||
|
if(info.interlaceMethod == 0) {
|
||
|
return info.width * info.height * info.bytesPerPixel + info.height;
|
||
|
}
|
||
|
|
||
|
unsigned size = 0;
|
||
|
for(unsigned pass = 0; pass < 7; pass++) {
|
||
|
unsigned xd = interlace[pass][0], yd = interlace[pass][1];
|
||
|
unsigned xo = interlace[pass][2], yo = interlace[pass][3];
|
||
|
unsigned width = (info.width + (xd - xo - 1)) / xd;
|
||
|
unsigned height = (info.height + (yd - yo - 1)) / yd;
|
||
|
if(width == 0 || height == 0) continue;
|
||
|
size += width * height * info.bytesPerPixel + height;
|
||
|
}
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
bool png::deinterlace(const uint8_t *&inputData, unsigned pass) {
|
||
|
unsigned xd = interlace[pass][0], yd = interlace[pass][1];
|
||
|
unsigned xo = interlace[pass][2], yo = interlace[pass][3];
|
||
|
unsigned width = (info.width + (xd - xo - 1)) / xd;
|
||
|
unsigned height = (info.height + (yd - yo - 1)) / yd;
|
||
|
if(width == 0 || height == 0) return true;
|
||
|
|
||
|
unsigned outputSize = width * height * info.bytesPerPixel;
|
||
|
uint8_t *outputData = new uint8_t[outputSize];
|
||
|
bool result = filter(outputData, inputData, width, height);
|
||
|
|
||
|
const uint8_t *rd = outputData;
|
||
|
for(unsigned y = yo; y < info.height; y += yd) {
|
||
|
uint8_t *wr = rawData + y * info.pitch;
|
||
|
for(unsigned x = xo; x < info.width; x += xd) {
|
||
|
for(unsigned b = 0; b < info.bytesPerPixel; b++) {
|
||
|
wr[x * info.bytesPerPixel + b] = *rd++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inputData += outputSize + height;
|
||
|
delete[] outputData;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
bool png::filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height) {
|
||
|
uint8_t *wr = outputData;
|
||
|
const uint8_t *rd = inputData;
|
||
|
int bpp = info.bytesPerPixel, pitch = width * bpp;
|
||
|
for(int y = 0; y < height; y++) {
|
||
|
uint8_t filter = *rd++;
|
||
|
|
||
|
switch(filter) {
|
||
|
case 0x00: //None
|
||
|
for(int x = 0; x < pitch; x++) {
|
||
|
wr[x] = rd[x];
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 0x01: //Subtract
|
||
|
for(int x = 0; x < pitch; x++) {
|
||
|
wr[x] = rd[x] + (x - bpp < 0 ? 0 : wr[x - bpp]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 0x02: //Above
|
||
|
for(int x = 0; x < pitch; x++) {
|
||
|
wr[x] = rd[x] + (y - 1 < 0 ? 0 : wr[x - pitch]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 0x03: //Average
|
||
|
for(int x = 0; x < pitch; x++) {
|
||
|
short a = x - bpp < 0 ? 0 : wr[x - bpp];
|
||
|
short b = y - 1 < 0 ? 0 : wr[x - pitch];
|
||
|
|
||
|
wr[x] = rd[x] + (uint8_t)((a + b) / 2);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 0x04: //Paeth
|
||
|
for(int x = 0; x < pitch; x++) {
|
||
|
short a = x - bpp < 0 ? 0 : wr[x - bpp];
|
||
|
short b = y - 1 < 0 ? 0 : wr[x - pitch];
|
||
|
short c = x - bpp < 0 || y - 1 < 0 ? 0 : wr[x - pitch - bpp];
|
||
|
|
||
|
short p = a + b - c;
|
||
|
short pa = p > a ? p - a : a - p;
|
||
|
short pb = p > b ? p - b : b - p;
|
||
|
short pc = p > c ? p - c : c - p;
|
||
|
|
||
|
uint8_t paeth = (uint8_t)((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c);
|
||
|
|
||
|
wr[x] = rd[x] + paeth;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default: //Invalid
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
rd += pitch;
|
||
|
wr += pitch;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
unsigned png::read(const uint8_t *data, unsigned length) {
|
||
|
unsigned result = 0;
|
||
|
while(length--) result = (result << 8) | (*data++);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
unsigned png::decode(const uint8_t *&data) {
|
||
|
unsigned p, r, g, b, a;
|
||
|
|
||
|
switch(info.colorType) {
|
||
|
case 0: //L
|
||
|
r = g = b = scale(readbits(data));
|
||
|
a = 0xff;
|
||
|
break;
|
||
|
case 2: //R,G,B
|
||
|
r = scale(readbits(data));
|
||
|
g = scale(readbits(data));
|
||
|
b = scale(readbits(data));
|
||
|
a = 0xff;
|
||
|
break;
|
||
|
case 3: //P
|
||
|
p = readbits(data);
|
||
|
r = info.palette[p][0];
|
||
|
g = info.palette[p][1];
|
||
|
b = info.palette[p][2];
|
||
|
a = 0xff;
|
||
|
break;
|
||
|
case 4: //L,A
|
||
|
r = g = b = scale(readbits(data));
|
||
|
a = scale(readbits(data));
|
||
|
break;
|
||
|
case 6: //R,G,B,A
|
||
|
r = scale(readbits(data));
|
||
|
g = scale(readbits(data));
|
||
|
b = scale(readbits(data));
|
||
|
a = scale(readbits(data));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return (a << 24) | (r << 16) | (g << 8) | (b << 0);
|
||
|
}
|
||
|
|
||
|
unsigned png::readbits(const uint8_t *&data) {
|
||
|
unsigned result = 0;
|
||
|
switch(info.bitDepth) {
|
||
|
case 1:
|
||
|
result = (*data >> bitpos) & 1;
|
||
|
bitpos++;
|
||
|
if(bitpos == 8) { data++; bitpos = 0; }
|
||
|
break;
|
||
|
case 2:
|
||
|
result = (*data >> bitpos) & 3;
|
||
|
bitpos += 2;
|
||
|
if(bitpos == 8) { data++; bitpos = 0; }
|
||
|
break;
|
||
|
case 4:
|
||
|
result = (*data >> bitpos) & 15;
|
||
|
bitpos += 4;
|
||
|
if(bitpos == 8) { data++; bitpos = 0; }
|
||
|
break;
|
||
|
case 8:
|
||
|
result = *data++;
|
||
|
break;
|
||
|
case 16:
|
||
|
result = (data[0] << 8) | (data[1] << 0);
|
||
|
data += 2;
|
||
|
break;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
unsigned png::scale(unsigned n) {
|
||
|
switch(info.bitDepth) {
|
||
|
case 1: return n ? 0xff : 0x00;
|
||
|
case 2: return n * 0x55;
|
||
|
case 4: return n * 0x11;
|
||
|
case 8: return n;
|
||
|
case 16: return n >> 8;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void png::transform() {
|
||
|
if(data) delete[] data;
|
||
|
data = new uint32_t[info.width * info.height];
|
||
|
|
||
|
bitpos = 0;
|
||
|
const uint8_t *rd = rawData;
|
||
|
for(unsigned y = 0; y < info.height; y++) {
|
||
|
uint32_t *wr = data + y * info.width;
|
||
|
for(unsigned x = 0; x < info.width; x++) {
|
||
|
wr[x] = decode(rd);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void png::alphaTransform(uint32_t rgb) {
|
||
|
transform();
|
||
|
|
||
|
uint8_t ir = rgb >> 16;
|
||
|
uint8_t ig = rgb >> 8;
|
||
|
uint8_t ib = rgb >> 0;
|
||
|
|
||
|
uint32_t *p = data;
|
||
|
for(unsigned y = 0; y < info.height; y++) {
|
||
|
for(unsigned x = 0; x < info.width; x++) {
|
||
|
uint32_t pixel = *p;
|
||
|
uint8_t a = pixel >> 24;
|
||
|
uint8_t r = pixel >> 16;
|
||
|
uint8_t g = pixel >> 8;
|
||
|
uint8_t b = pixel >> 0;
|
||
|
|
||
|
r = (r * a) + (ir * (255 - a)) >> 8;
|
||
|
g = (g * a) + (ig * (255 - a)) >> 8;
|
||
|
b = (b * a) + (ib * (255 - a)) >> 8;
|
||
|
|
||
|
*p++ = (255 << 24) | (r << 16) | (g << 8) | (b << 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
png::png() : data(0), rawData(0) {
|
||
|
}
|
||
|
|
||
|
png::~png() {
|
||
|
if(data) delete[] data;
|
||
|
if(rawData) delete[] rawData;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif
|