/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016, 2019 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.fileformats.graphics.pnglib;
import java.io.*;
import malik.emulator.compression.zlib.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.graphics.*;
public final class PNGDecoder extends Object
implements InputAdapter, DataHolder, DataDecoder, ImageDecoder
{
public static final long PNG_SIGNATURE = 0x89504e470d0a1a0aL;
private static final int HEADER_SIGNATURE = 0x49484452; /* IHDR */
private static final int GAMMA_SIGNATURE = 0x67414d41; /* gAMA */
private static final int PALETTE_SIGNATURE = 0x504c5445; /* PLTE */
private static final int TRANSPARENCY_SIGNATURE = 0x74524e53; /* tRNS */
private static final int DATA_SIGNATURE = 0x49444154; /* IDAT */
private static final int END_SIGNATURE = 0x49454e44; /* IEND */
private static void copyBytes(InputStream input, OutputStream output, long bytesCount)
throws IOException
{
int remainder;
long fragment;
long fragments = bytesCount >> 0x10;
byte[] buffer = new byte[0x00010000];
for(fragment = 0L; fragment < fragments; fragment++)
{
output.write(buffer, 0, input.read(buffer));
}
if((remainder = ((int) bytesCount) & 0xffff) > 0)
{
output.write(buffer, 0, input.read(buffer, 0, remainder));
}
}
private static double power(double base, double exponent)
{
return Double.isNaN(base) || Double.isNaN(exponent) ? Double.NaN :
(base > 0.d ? Math.pow2(exponent * Math.log2(base)) : 0.d);
}
private static int paethPredictor(int a, int b, int c)
{
int p = a + b - c;
int pa = Math.abs(p - a);
int pb = Math.abs(p - b);
int pc = Math.abs(p - c);
return pa <= pb && pa <= pc ? a : (pb <= pc ? b : c);
}
private final class DecodeNormal extends Object
implements DecodeMethod
{
DecodeNormal()
{
}
public int decode(byte[] stream, int startIndex, int pixelType,
byte[] imageData, int imageWidth, int imageHeight)
{
return (PNGDecoder.this).decodeNormal(stream, startIndex, pixelType,
imageData, imageWidth, imageHeight);
}
}
private final class DecodeAdam7 extends Object
implements DecodeMethod
{
DecodeAdam7()
{
}
public int decode(byte[] stream, int startIndex, int pixelType,
byte[] imageData, int imageWidth, int imageHeight)
{
return (PNGDecoder.this).decodeAdam7(stream, startIndex, pixelType,
imageData, imageWidth, imageHeight);
}
}
private boolean useGrayscale;
private boolean usePalette;
private boolean useAlpha;
private int width;
private int height;
private int bitDepth;
private int components;
private byte[] data;
private int[] gamma;
private int[] palette;
public PNGDecoder()
{
clear();
}
public void loadFromInputStream(InputStream stream)
throws IOException, InvalidDataFormatException
{
loadFromDataStream(new DataInputStream(stream));
}
public void loadFromDataStream(DataInputStream stream)
throws IOException, InvalidDataFormatException
{
boolean headerHandled = false;
boolean gammaHandled = false;
boolean paletteHandled = false;
boolean transparencyHandled = false;
int chunkLength;
int chunkStart;
int pixelType = -1;
int imgw = 0;
int imgh = 0;
int cmpn = 0;
DecodeMethod d = null;
ByteArrayOutputStream compressedData = null;
clear();
do
{
chunkLength = stream.readInt();
switch(chunkStart = stream.readInt())
{
default:
{
stream.skip(((long) chunkLength) & 0xffffffffL);
}
break;
case HEADER_SIGNATURE:
{
int w;
int h;
int bd;
int pt;
int cm;
int fm;
int im;
long area;
if(headerHandled || chunkLength != 0x0d)
{
invalidChunkOrder();
}
w = stream.readInt(); /* ширина */
h = stream.readInt(); /* высота */
bd = stream.readByte(); /* глубина цвета */
pt = stream.readByte(); /* тип пикселов */
cm = stream.readByte(); /* алгоритм сжатия */
fm = stream.readByte(); /* алгоритм фильтрации */
im = stream.readByte(); /* алгоритм отображения */
if((area = (((long) w) & 0xffffffffL) * (((long) h) & 0xffffffffL)) <= 0L ||
area > 1000000L || cm != 0 || fm != 0 || (im != 0 && im != 1) || (
(pt != 0 || (bd != 1 && bd != 2 && bd != 4 && bd != 8 && bd != 16)) &&
(pt != 3 || (bd != 1 && bd != 2 && bd != 4 && bd != 8)) &&
((pt != 2 && pt != 4 && pt != 6) || (bd != 8 && bd != 16))))
{
invalidChunkOrder();
}
pixelType = pt;
useGrayscale = pt == 0 || pt == 4;
usePalette = pt == 3;
useAlpha = pt == 4 || pt == 6;
width = imgw = w;
height = imgh = h;
bitDepth = bd;
switch(pt)
{
default:
break;
case 0:
case 3:
components = cmpn = 1;
break;
case 4:
components = cmpn = 2;
break;
case 2:
components = cmpn = 3;
break;
case 6:
components = cmpn = 4;
break;
}
switch(im)
{
default:
break;
case 0:
d = this.new DecodeNormal();
break;
case 1:
d = this.new DecodeAdam7();
break;
}
headerHandled = true;
}
break;
case GAMMA_SIGNATURE:
{
double gammaValue;
double exponent;
int i;
int[] gm;
if((!headerHandled) || gammaHandled || chunkLength != 0x04)
{
invalidChunkOrder();
}
gm = gamma;
gammaValue = (double) stream.readInt();
exponent = 1.e+5d / (2.2d * gammaValue);
for(i = 0x00; i <= 0xff; i++)
{
gm[i] = (int) Math.round(255.d * power(((double) i) / 255.d, exponent));
}
gammaHandled = true;
}
break;
case PALETTE_SIGNATURE:
{
int i;
int r;
int g;
int b;
int lim;
int[] p;
if((!headerHandled) || paletteHandled ||
chunkLength < 0x0000 || chunkLength > 0x0300 ||
(chunkLength % 3) != 0)
{
invalidChunkOrder();
}
lim = chunkLength / 3;
p = palette;
for(i = 0; i < lim; i++)
{
r = stream.readUnsignedByte();
g = stream.readUnsignedByte();
b = stream.readUnsignedByte();
p[i] = 0xff000000 | (r << 0x10) | (g << 0x08) | b;
}
paletteHandled = true;
}
break;
case TRANSPARENCY_SIGNATURE:
{
int i;
int[] p;
if((!headerHandled) || transparencyHandled ||
chunkLength < 0x0000 || chunkLength > 0x0100)
{
invalidChunkOrder();
}
if(usePalette)
{
p = palette;
for(i = 0; i < chunkLength; i++)
{
p[i] = (p[i] & 0x00ffffff) | (stream.readUnsignedByte() << 0x18);
}
} else
{
/* прозрачность по цвету не поддерживается */
stream.skipBytes(chunkLength);
}
transparencyHandled = true;
}
break;
case DATA_SIGNATURE:
{
if((!headerHandled) || chunkLength < 0)
{
invalidChunkOrder();
}
copyBytes(stream, compressedData == null ?
(compressedData = new ByteArrayOutputStream()) : compressedData,
(long) chunkLength);
}
break;
case END_SIGNATURE:
{
if((!headerHandled) || chunkLength != 0 || compressedData == null)
{
invalidChunkOrder();
}
d.decode(Zlib.decompress(compressedData.toByteArray()), 0, pixelType,
data = new byte[imgw * imgh * cmpn], imgw, imgh);
}
break;
}
stream.skipBytes(0x04); /* игнорируем контрольную сумму */
} while(chunkStart != END_SIGNATURE);
}
public void clear()
{
int i;
int[] g;
int[] p;
useGrayscale = false;
usePalette = false;
useAlpha = false;
width = 0;
height = 0;
components = 0;
bitDepth = 0;
data = null;
if((g = gamma) == null)
{
g = gamma = new int[256];
}
if((p = palette) == null)
{
p = palette = new int[256];
}
for(i = 0x00; i <= 0xff; i++)
{
g[i] = i;
p[i] = 0;
}
}
public boolean isEmpty()
{
return data == null;
}
public boolean isAlphaSupported()
{
return true;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
public int[] getPixels()
{
int i;
int size;
int step;
int offset;
int[] result;
if(data == null)
{
return null;
}
offset = 0;
step = components;
result = new int[size = width * height];
for(i = 0; i < size; offset += step, i++)
{
result[i] = readPixel(offset);
}
return result;
}
int decodeNormal(byte[] stream, int startIndex, int pixelType,
byte[] imageData, int imageWidth, int imageHeight)
{
int result = startIndex;
int f = getFilterOffset(pixelType);
int i;
int j;
int k;
int lim;
int ofs = 0;
int total = imageWidth * components;
int lineSize = getLineSize(imageWidth, pixelType);
UnwrapMethod u = null;
k = lineSize + 1;
if(usePalette)
{
switch(bitDepth)
{
default:
break;
case 1:
u = new Unwrap1bitPP();
break;
case 2:
u = new Unwrap2bitPP();
break;
case 4:
u = new Unwrap4bitPP();
break;
case 8:
u = new Unwrap8bitPC();
break;
}
} else
{
switch(bitDepth)
{
default:
break;
case 1:
u = new Unwrap1bitPC();
break;
case 2:
u = new Unwrap2bitPC();
break;
case 4:
u = new Unwrap4bitPC();
break;
case 8:
u = new Unwrap8bitPC();
break;
case 16:
u = new Unwrap16bitPC();
break;
}
}
for(i = 0; i < imageHeight; i++)
{
switch(stream[result++])
{
default:
break;
case 1:
lim = result + lineSize;
for(j = result + f; j < lim; j++)
{
stream[j] = (byte) (stream[j] + stream[j - f]);
}
break;
case 2:
if(i > 0)
{
lim = result + lineSize;
for(j = result; j < lim; j++)
{
stream[j] = (byte) (stream[j] + stream[j - k]);
}
}
break;
case 3:
if(i > 0)
{
lim = result + f;
for(j = result; j < lim; j++)
{
stream[j] = (byte) (stream[j] + (
(stream[j - k] & 0xff) >> 1));
}
lim = result + lineSize;
for(; j < lim; j++)
{
stream[j] = (byte) (stream[j] + ((
(stream[j - f] & 0xff) +
(stream[j - k] & 0xff)) >> 1));
}
} else
{
lim = result + lineSize;
for(j = result + f; j < lim; j++)
{
stream[j] = (byte) (stream[j] + (
(stream[j - f] & 0xff) >> 1));
}
}
break;
case 4:
if(i > 0)
{
lim = result + f;
for(j = result; j < lim; j++)
{
stream[j] = (byte) (stream[j] + paethPredictor(
0,
stream[j - k] & 0xff,
0));
}
lim = result + lineSize;
for(; j < lim; j++)
{
stream[j] = (byte) (stream[j] + paethPredictor(
stream[j - f] & 0xff,
stream[j - k] & 0xff,
stream[j - f - k] & 0xff));
}
} else
{
lim = result + f;
for(j = result; j < lim; j++)
{
stream[j] = (byte) (stream[j] + paethPredictor(
0,
0,
0));
}
lim = result + lineSize;
for(; j < lim; j++)
{
stream[j] = (byte) (stream[j] + paethPredictor(
stream[j - f] & 0xff,
0,
0));
}
}
break;
}
u.unwrap(stream, result, imageData, ofs, total);
ofs += total;
result += lineSize;
}
return result;
}
int decodeAdam7(byte[] stream, int startIndex, int pixelType,
byte[] imageData, int imageWidth, int imageHeight)
{
int result = startIndex;
int currentPassage;
int currentInterlacing;
int width;
int height;
int x0;
int y0;
int x;
int y;
int c = components;
int xstart;
int ystart;
int xinc;
int yinc;
int lim;
byte[] data;
int[] adam7interlacing;
lim = (adam7interlacing = new int[] {
0x0088, 0x4088, 0x0448, 0x2044, 0x0224, 0x1022, 0x0112
}).length;
for(currentPassage = 0; currentPassage < lim; currentPassage++)
{
xstart = ((currentInterlacing = adam7interlacing[currentPassage]) >> 12) & 0x0f;
ystart = (currentInterlacing >> 8) & 0x0f;
xinc = (currentInterlacing >> 4) & 0x0f;
yinc = currentInterlacing & 0x0f;
width = (imageWidth - xstart + xinc - 1) / xinc;
height = (imageHeight - ystart + yinc - 1) / yinc;
data = new byte[width * height * c];
result = decodeNormal(stream, result, pixelType, data, width, height);
for(y0 = 0, y = ystart; y < imageHeight; y0++, y += yinc)
{
for(x0 = 0, x = xstart; x < imageWidth; x0++, x += xinc)
{
Array.copy(data, (y0 * width + x0) * c,
imageData, (y * imageWidth + x) * c, c);
}
}
}
return result;
}
private void invalidChunkOrder()
throws InvalidDataFormatException
{
clear();
throw new InvalidDataFormatException("Декодирование PNG-файлов: " +
"неправильный формат файла.");
}
private int getLineSize(int width, int pixelType)
{
return (pixelType == 0 || pixelType == 3 ?
width * bitDepth + 7 : width * bitDepth * components) >> 3;
}
private int getFilterOffset(int pixelType)
{
return pixelType == 0 || pixelType == 3 ?
(bitDepth < 16 ? 1 : 2) : (bitDepth * components) >> 3;
}
private int readPixel(int offset)
{
int a;
int r;
int g;
int b;
int p;
byte[] d = data;
int[] gm = gamma;
if(usePalette)
{
a = (p = palette[d[offset] & 0xff]) >> 0x18;
r = (p >> 0x10) & 0xff;
g = (p >> 0x08) & 0xff;
b = p & 0xff;
} else
{
if(useAlpha)
{
if(useGrayscale)
{
a = d[offset + 1];
r = g = b = d[offset] & 0xff;
} else
{
a = d[offset + 3];
r = d[offset] & 0xff;
g = d[offset + 1] & 0xff;
b = d[offset + 2] & 0xff;
}
} else
{
a = 0xff;
if(useGrayscale)
{
r = g = b = d[offset] & 0xff;
} else
{
r = d[offset] & 0xff;
g = d[offset + 1] & 0xff;
b = d[offset + 2] & 0xff;
}
}
}
return (a << 0x18) | (gm[r] << 0x10) | (gm[g] << 0x08) | gm[b];
}
}