/*
Реализация спецификаций 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 PNGEncoder extends Object
implements OutputAdapter, DataHolder, DataEncoder, ImageEncoder
{
public static final long PNG_SIGNATURE = 0x89504e470d0a1a0aL;
private static final int HEADER_SIGNATURE = 0x49484452; /* IHDR */
private static final int TEXT_SIGNATURE = 0x74455874; /* tEXt */
private static final int DATA_SIGNATURE = 0x49444154; /* IDAT */
private static final int END_SIGNATURE = 0x49454e44; /* IEND */
private boolean alphaSupported;
private int width;
private int height;
private int[] pixels;
public PNGEncoder()
{
}
public void saveToOutputStream(OutputStream stream)
throws IOException, EmptyAdapterException
{
saveToDataStream(new DataOutputStream(stream));
}
public void saveToDataStream(DataOutputStream stream)
throws IOException, EmptyAdapterException
{
boolean alphaSupported;
int i;
int j;
int width;
int height;
int offset;
int length;
int byteIndex;
int sbyteIndex;
int pixelIndex;
int pixel;
byte[] chunkBytes;
int[] pixels;
Checksum32 checksum;
ByteArrayOutputStream chunk;
DataOutputStream chunkOutput;
if((pixels = this.pixels) == null)
{
throw new EmptyAdapterException("Кодирование PNG-файлов: " +
"адаптер пуст.");
}
alphaSupported = this.alphaSupported;
width = this.width;
height = this.height;
checksum = new CRC32();
chunkOutput = new DataOutputStream(chunk = new ByteArrayOutputStream(0x20));
stream.writeLong(PNG_SIGNATURE);
/* Кусок-заголовок */
chunk.reset();
chunkOutput.writeInt(HEADER_SIGNATURE);
chunkOutput.writeInt(width); /* ширина */
chunkOutput.writeInt(height); /* высота */
chunkOutput.writeByte(8); /* глубина цвета */
chunkOutput.writeByte(alphaSupported ? 6 : 2); /* тип пикселов */
chunkOutput.writeByte(0); /* алгоритм сжатия */
chunkOutput.writeByte(0); /* алгоритм фильтрации */
chunkOutput.writeByte(0); /* алгоритм отображения */
length = (chunkBytes = chunk.toByteArray()).length;
checksum.reset();
checksum.update(chunkBytes, 0, length);
stream.writeInt(length - 4);
stream.write(chunkBytes);
stream.writeInt(checksum.getValue());
/* Кусок-таг */
chunk.reset();
chunkOutput.writeInt(TEXT_SIGNATURE);
chunkOutput.write("Software".getBytes());
chunkOutput.write(0);
chunkOutput.write("Malik Emulator https://malik-elaborarer.ru/emulator/".getBytes());
length = (chunkBytes = chunk.toByteArray()).length;
checksum.reset();
checksum.update(chunkBytes, 0, length);
stream.writeInt(length - 4);
stream.write(chunkBytes);
stream.writeInt(checksum.getValue());
/* Кусок-данные */
chunkBytes = new byte[height * ((offset = alphaSupported ? 4 : 3) * width + 1)];
for(byteIndex = 0, pixelIndex = 0, j = 0; j < height; j++)
{
for(chunkBytes[byteIndex++] = (byte) 1, i = 0; i < width; i++)
{
pixel = pixels[pixelIndex++];
chunkBytes[byteIndex++] = (byte) (pixel >> 16);
chunkBytes[byteIndex++] = (byte) (pixel >> 8);
chunkBytes[byteIndex++] = (byte) pixel;
if(alphaSupported)
{
chunkBytes[byteIndex++] = (byte) (pixel >> 24);
}
}
for(sbyteIndex = byteIndex - 1, i *= offset; i-- > offset; sbyteIndex--)
{
chunkBytes[sbyteIndex] -= chunkBytes[sbyteIndex - offset];
}
}
chunk.reset();
chunkOutput.writeInt(DATA_SIGNATURE);
chunkOutput.write(Zlib.compress(chunkBytes, 7));
length = (chunkBytes = chunk.toByteArray()).length;
checksum.reset();
checksum.update(chunkBytes, 0, length);
stream.writeInt(length - 4);
stream.write(chunkBytes);
stream.writeInt(checksum.getValue());
/* Кусок-конец */
chunk.reset();
chunkOutput.writeInt(END_SIGNATURE);
length = (chunkBytes = chunk.toByteArray()).length;
checksum.reset();
checksum.update(chunkBytes, 0, length);
stream.writeInt(length - 4);
stream.write(chunkBytes);
stream.writeInt(checksum.getValue());
}
public void clear()
{
alphaSupported = false;
width = 0;
height = 0;
pixels = null;
}
public boolean isEmpty()
{
return pixels == null;
}
public void setPixels(boolean alphaSupported, int width, int height, int[] pixels)
throws InvalidDataFormatException
{
if(width <= 0 || height <= 0)
{
throw new InvalidDataFormatException("Кодирование PNG-файлов: " +
"размеры могут быть только положительными.");
}
if(pixels == null || ((long) width) * ((long) height) > (long) pixels.length)
{
throw new InvalidDataFormatException("Кодирование PNG-файлов: " +
"длина массива пикселов меньше заданных размеров.");
}
this.alphaSupported = alphaSupported;
this.width = width;
this.height = height;
this.pixels = pixels;
}
}