/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016–2017, 2019–2023, 2025 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.fileformats.graphics.png;
import java.io.*;
import malik.emulator.compression.zlib.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.graphics.*;
public final class PNGDecoder extends Object implements ImageDecoder
{
public static final long SIGNATURE = 0x89504e470d0a1a0aL;
private static final int IHDR = 0x49484452; /* IHDR */
private static final int GAMA = 0x67414d41; /* gAMA */
private static final int PLTE = 0x504c5445; /* PLTE */
private static final int TRNS = 0x74524e53; /* tRNS */
private static final int IDAT = 0x49444154; /* IDAT */
private static final int IEND = 0x49454e44; /* IEND */
static int decodeNormal(byte[] stream, int offset, int pixelType, int bitDepth, int components, byte[] imageData, int imageWidth, int imageHeight, UnwrapMethod method) {
int k = getLineSize(pixelType, bitDepth, components, imageWidth);
int f = getFilterSize(pixelType, bitDepth, components);
int total = imageWidth * components;
int lineSize = k++;
for(int ofs = 0, i = 0, j, lim; i < imageHeight; offset += lineSize, ofs += total, i++)
{
switch(stream[offset++])
{
default:
break;
case 1:
for(lim = offset + lineSize, j = offset + f; j < lim; j++) stream[j] = (byte) (stream[j] + stream[j - f]);
break;
case 2:
if(i > 0) for(lim = offset + lineSize, j = offset; j < lim; j++) stream[j] = (byte) (stream[j] + stream[j - k]);
break;
case 3:
if(i > 0)
{
for(lim = offset + f, j = offset; j < lim; j++) stream[j] = (byte) (stream[j] + ((stream[j - k] & 0xff) >> 1));
for(lim = offset + lineSize; j < lim; j++) stream[j] = (byte) (stream[j] + ((stream[j - k] & 0xff) + (stream[j - f] & 0xff) >> 1));
} else
{
for(lim = offset + lineSize, j = offset + f; j < lim; j++) stream[j] = (byte) (stream[j] + ((stream[j - f] & 0xff) >> 1));
}
break;
case 4:
if(i > 0)
{
for(lim = offset + f, j = offset; j < lim; j++) stream[j] = (byte) (stream[j] + paethPredictor(0, stream[j - k] & 0xff, 0));
for(lim = offset + lineSize; j < lim; j++) stream[j] = (byte) (stream[j] + paethPredictor(stream[j - f] & 0xff, stream[j - k] & 0xff, stream[j - f - k] & 0xff));
} else
{
for(lim = offset + lineSize, j = offset + f; j < lim; j++) stream[j] = (byte) (stream[j] + paethPredictor(stream[j - f] & 0xff, 0, 0));
}
break;
}
method.unwrap(stream, offset, imageData, ofs, total);
}
return offset;
}
static int decodeAdam7(byte[] stream, int offset, int pixelType, int bitDepth, int components, byte[] imageData, int imageWidth, int imageHeight, UnwrapMethod method) {
int[] interlacing;
for(int i = (interlacing = new int[] { 0x0112, 0x1022, 0x0224, 0x2044, 0x0448, 0x4088, 0x0088 }).length; i-- > 0; )
{
int current;
int xstart = (current = interlacing[i]) >> 12;
int ystart = (current >> 8) & 0x0f;
int xinc = (current >> 4) & 0x0f;
int yinc = current & 0x0f;
int width = (imageWidth - xstart + xinc - 1) / xinc;
int height = (imageHeight - ystart + yinc - 1) / yinc;
byte[] data = new byte[width * height * components];
offset = decodeNormal(stream, offset, pixelType, bitDepth, components, data, width, height, method);
for(int y0 = 0, y = ystart; y < imageHeight; y0++, y += yinc) for(int x0 = 0, x = xstart; x < imageWidth; x0++, x += xinc)
{
Array.copy(data, (y0 * width + x0) * components, imageData, (y * imageWidth + x) * components, components);
}
}
return offset;
}
private static void copy(InputStream input, OutputStream output, long count) throws IOException {
int remainder = (int) count & 0xffff;
long fragments = count >> 16;
byte[] buffer = new byte[0x00010000];
for(int i = 0; i < fragments; i++) output.write(buffer, 0, input.read(buffer));
if(remainder > 0) output.write(buffer, 0, input.read(buffer, 0, remainder));
}
private static double power(double base, double exponent) {
return 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 static int readPixel(byte[] data, int offset, int[] gamma, int[] palette, boolean useAlpha, boolean usePalette, boolean useGrayscale) {
int r;
int g;
int b;
int a;
if(usePalette)
{
int p;
a = (p = palette[data[offset] & 0xff]) >> 24;
r = p >> 16;
g = p >> 8;
b = p;
}
else if(useAlpha)
{
if(useGrayscale)
{
r = g = b = data[offset];
a = data[offset + 1];
} else
{
r = data[offset];
g = data[offset + 1];
b = data[offset + 2];
a = data[offset + 3];
}
}
else
{
if(useGrayscale)
{
r = g = b = data[offset];
} else
{
r = data[offset];
g = data[offset + 1];
b = data[offset + 2];
}
a = 0xff;
}
return a << 24 | gamma[r & 0xff] << 16 | gamma[g & 0xff] << 8 | gamma[b & 0xff];
}
private static int getLineSize(int pixelType, int bitDepth, int components, int imageWidth) {
return (pixelType == 0 || pixelType == 3 ? bitDepth * imageWidth + 7 : bitDepth * imageWidth * components) >> 3;
}
private static int getFilterSize(int pixelType, int bitDepth, int components) {
return (pixelType == 0 || pixelType == 3 ? bitDepth < 16 ? 8 : 16 : bitDepth * components) >> 3;
}
private int width;
private int height;
private int[] pixels;
public PNGDecoder() {
}
public void loadFromInputStream(InputStream stream) throws IOException {
loadFromDataStream(new ExtendedDataInputStream(stream));
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
boolean useAlpha = false;
boolean usePalette = false;
boolean useGrayscale = false;
boolean dataHandled = false;
boolean gammaHandled = false;
boolean headerHandled = false;
boolean paletteHandled = false;
boolean transparencyHandled = false;
int chunkName;
int bitDepth = 0;
int pixelType = 0;
int components = 0;
int imageWidth = 0;
int imageHeight = 0;
byte[] imageData = null;
int[] imageGamma = new int[0x0100];
int[] imagePixels = null;
int[] imagePalette = null;
DecodeMethod dmethod = null;
UnwrapMethod umethod = null;
ByteArrayOutputStream compressedData = null;
for(int i = 0x0100; i-- > 0; imageGamma[i] = i);
/* чтение входного потока данных */
do
{
int chunkSize = stream.readInt();
switch(chunkName = stream.readInt())
{
default:
stream.skip((long) chunkSize & 0x00000000ffffffffL);
break;
case IHDR:
{
int w;
int h;
int bd;
int pt;
int cm;
int fm;
int mm;
long area;
if(headerHandled || chunkSize != 0x0d)
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
w = stream.readInt(); /* ширина */
h = stream.readInt(); /* высота */
if((area = ((long) w & 0xffffffffL) * ((long) h & 0xffffffffL)) <= 0L || area > 0x00100000L)
{
throw new UnsupportedDataException("PNGDecoder.loadFromInputStream: неподдерживаемые данные.");
}
bd = stream.readUnsignedByte(); /* глубина цвета */
pt = stream.readUnsignedByte(); /* тип пикселов (0=G, 2=RGB, 3=P, 4=GA, 6=RGBA) */
if((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))
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
cm = stream.readUnsignedByte(); /* алгоритм сжатия */
fm = stream.readUnsignedByte(); /* алгоритм фильтрации */
mm = stream.readUnsignedByte(); /* алгоритм отображения */
if(cm != 0 || fm != 0 || mm < 0 || mm > 1)
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
useAlpha = pt == 4 || pt == 6;
usePalette = pt == 3;
useGrayscale = pt == 0 || pt == 4;
bitDepth = bd;
pixelType = pt;
components = pt == 6 ? 4 : pt == 2 ? 3 : pt == 4 ? 2 : 1;
imageWidth = w;
imageHeight = h;
imagePixels = new int[w * h];
imagePalette = usePalette ? new int[0x0100] : null;
dmethod = mm == 0 ? (DecodeMethod) (new DecodeNormal()) : (DecodeMethod) (new DecodeAdam7());
if(usePalette)
{
switch(bitDepth)
{
default:
break;
case 1:
umethod = new Unwrap1bitPP();
break;
case 2:
umethod = new Unwrap2bitPP();
break;
case 4:
umethod = new Unwrap4bitPP();
break;
case 8:
umethod = new Unwrap8bitPC();
break;
}
} else
{
switch(bitDepth)
{
default:
break;
case 1:
umethod = new Unwrap1bitPC();
break;
case 2:
umethod = new Unwrap2bitPC();
break;
case 4:
umethod = new Unwrap4bitPC();
break;
case 8:
umethod = new Unwrap8bitPC();
break;
case 16:
umethod = new Unwrap16bitPC();
break;
}
}
headerHandled = true;
break;
}
case GAMA:
{
double exponent;
if(!headerHandled || gammaHandled || chunkSize != 0x04)
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
exponent = 1.e+005d / (2.2d * (double) stream.readInt());
for(int i = 0x0100; i-- > 0; ) imageGamma[i] = (int) Math.round(255.d * power((double) i / 255.d, exponent));
gammaHandled = true;
break;
}
case PLTE:
if(!headerHandled || paletteHandled || chunkSize < 0 || chunkSize > 0x0300 || chunkSize % 3 != 0 || !usePalette)
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
for(int imagePaletteLength = chunkSize / 3, i = 0; i < imagePaletteLength; i++)
{
int r = stream.readUnsignedByte();
int g = stream.readUnsignedByte();
int b = stream.readUnsignedByte();
imagePalette[i] = 0xff000000 | r << 16 | g << 8 | b;
}
paletteHandled = true;
break;
case TRNS:
if(!headerHandled || transparencyHandled || chunkSize < 0 || chunkSize > 0x0100)
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
if(usePalette)
{
for(int i = 0; i < chunkSize; i++) imagePalette[i] = imagePalette[i] & 0x00ffffff | stream.readUnsignedByte() << 24;
} else
{
/* прозрачность по цвету не поддерживается */
stream.skip((long) chunkSize & 0x00000000ffffffffL);
}
transparencyHandled = true;
break;
case IDAT:
if(!headerHandled || chunkSize < 0)
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
copy(stream, compressedData == null ? (compressedData = new ByteArrayOutputStream()) : compressedData, (long) chunkSize);
dataHandled = true;
break;
case IEND:
int length;
byte[] rawData;
if(!headerHandled || !dataHandled || chunkSize != 0 || compressedData == null)
{
throw new InvalidDataFormatException("PNGDecoder.loadFromInputStream: неправильный формат данных.");
}
length = imageWidth * imageHeight * components;
rawData = Zlib.decompress(compressedData.toByteArray());
dmethod.decode(rawData, 0, pixelType, bitDepth, components, imageData = new byte[length], imageWidth, imageHeight, umethod);
break;
}
stream.skip(4L); /* игнорируем контрольную сумму куска */
} while(chunkName != IEND);
/* построение изображения */
for(int len = imagePixels.length, offset = 0, i = 0; i < len; offset += components, i++)
{
imagePixels[i] = readPixel(imageData, offset, imageGamma, imagePalette, useAlpha, usePalette, useGrayscale);
}
/* вывод результата */
width = imageWidth;
height = imageHeight;
pixels = imagePixels;
}
public void clear() {
width = 0;
height = 0;
pixels = null;
}
public boolean isEmpty() {
return (width | height) == 0 && pixels == null;
}
public boolean alphaSupported() {
return true;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public int[] getPixels() {
int len;
int[] result;
if((result = pixels) == null) return null;
Array.copy(result, 0, result = new int[len = result.length], 0, len);
return result;
}
}
abstract class UnwrapMethod extends Object
{
public UnwrapMethod() {
}
public abstract void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length);
}
final class Unwrap1bitPC extends UnwrapMethod
{
public Unwrap1bitPC() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
for(int i = 0; i < length; i++) dst[dstOffset++] = (byte) (((src[srcOffset + (i >>> 3)] & 0xff) >> ((i & 0x07) ^ 0x07)) << 7);
}
}
final class Unwrap1bitPP extends UnwrapMethod
{
public Unwrap1bitPP() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
for(int i = 0; i < length; i++) dst[dstOffset++] = (byte) (((src[srcOffset + (i >>> 3)] & 0xff) >> ((i & 0x07) ^ 0x07)) & 0x01);
}
}
final class Unwrap2bitPC extends UnwrapMethod
{
public Unwrap2bitPC() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
for(int i = 0; i < length; i++) dst[dstOffset++] = (byte) (((src[srcOffset + (i >>> 2)] & 0xff) >> (((i & 0x03) ^ 0x03) << 1)) << 6);
}
}
final class Unwrap2bitPP extends UnwrapMethod
{
public Unwrap2bitPP() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
for(int i = 0; i < length; i++) dst[dstOffset++] = (byte) (((src[srcOffset + (i >>> 2)] & 0xff) >> (((i & 0x03) ^ 0x03) << 1)) & 0x03);
}
}
final class Unwrap4bitPC extends UnwrapMethod
{
public Unwrap4bitPC() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
for(int i = 0; i < length; i++) dst[dstOffset++] = (byte) (((src[srcOffset + (i >>> 1)] & 0xff) >> (((i & 0x01) ^ 0x01) << 2)) << 4);
}
}
final class Unwrap4bitPP extends UnwrapMethod
{
public Unwrap4bitPP() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
for(int i = 0; i < length; i++) dst[dstOffset++] = (byte) (((src[srcOffset + (i >>> 1)] & 0xff) >> (((i & 0x01) ^ 0x01) << 2)) & 0x0f);
}
}
final class Unwrap8bitPC extends UnwrapMethod
{
public Unwrap8bitPC() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
Array.copy(src, srcOffset, dst, dstOffset, length);
}
}
final class Unwrap16bitPC extends UnwrapMethod
{
public Unwrap16bitPC() {
}
public void unwrap(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
for(int i = 0; i < length; i++) dst[dstOffset++] = src[srcOffset + (i << 1)];
}
}
abstract class DecodeMethod extends Object
{
public DecodeMethod() {
}
public abstract int decode(byte[] stream, int offset, int pixelType, int bitDepth, int components, byte[] imageData, int imageWidth, int imageHeight, UnwrapMethod method);
}
final class DecodeNormal extends DecodeMethod
{
public DecodeNormal() {
}
public int decode(byte[] stream, int offset, int pixelType, int bitDepth, int components, byte[] imageData, int imageWidth, int imageHeight, UnwrapMethod method) {
return PNGDecoder.decodeNormal(stream, offset, pixelType, bitDepth, components, imageData, imageWidth, imageHeight, method);
}
}
final class DecodeAdam7 extends DecodeMethod
{
public DecodeAdam7() {
}
public int decode(byte[] stream, int offset, int pixelType, int bitDepth, int components, byte[] imageData, int imageWidth, int imageHeight, UnwrapMethod method) {
return PNGDecoder.decodeAdam7(stream, offset, pixelType, bitDepth, components, imageData, imageWidth, imageHeight, method);
}
}