/*
Реализация спецификаций 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.jpeg;
import java.io.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.graphics.*;
public class JPEGDecoder extends Object implements ImageDecoder
{
public static final long SIGNATURE = 0xffd8L;
static final int DC = 0;
static final int AC = 1;
static final int MIN = 0xff00;
static final int SOF0 = 0xffc0;
static final int SOF1 = 0xffc1;
static final int SOF2 = 0xffc2;
static final int SOF3 = 0xffc3;
static final int DHT = 0xffc4;
static final int SOF5 = 0xffc5;
static final int SOF6 = 0xffc6;
static final int SOF7 = 0xffc7;
static final int JPG = 0xffc8;
static final int SOF9 = 0xffc9;
static final int SOF10 = 0xffca;
static final int SOF11 = 0xffcb;
static final int DAC = 0xffcc;
static final int SOF13 = 0xffcd;
static final int SOF14 = 0xffce;
static final int SOF15 = 0xffcf;
static final int RST0 = 0xffd0;
static final int RST1 = 0xffd1;
static final int RST2 = 0xffd2;
static final int RST3 = 0xffd3;
static final int RST4 = 0xffd4;
static final int RST5 = 0xffd5;
static final int RST6 = 0xffd6;
static final int RST7 = 0xffd7;
static final int EOI = 0xffd9;
static final int SOS = 0xffda;
static final int DQT = 0xffdb;
static final int DNL = 0xffdc;
static final int DRI = 0xffdd;
static final int DHP = 0xffde;
static final int EXP = 0xffdf;
static final int APP0 = 0xffe0;
static final int APP1 = 0xffe1;
static final int APP2 = 0xffe2;
static final int APP3 = 0xffe3;
static final int APP4 = 0xffe4;
static final int APP5 = 0xffe5;
static final int APP6 = 0xffe6;
static final int APP7 = 0xffe7;
static final int APP8 = 0xffe8;
static final int APP9 = 0xffe9;
static final int APP10 = 0xffea;
static final int APP11 = 0xffeb;
static final int APP12 = 0xffec;
static final int APP13 = 0xffed;
static final int APP14 = 0xffee;
static final int APP15 = 0xffef;
static final int COM = 0xfffe;
static final int MAX = 0xffff;
static final int MAX_COMPONENTS = 3;
static final byte[] ORDER_ZIGZAG_INVERTED;
private static final int ONE_SQRT_2 = 5793;
private static final int COS_1_16_PI = 4017; /* cos(1/16π) */
private static final int SIN_6_16_PI = 3784; /* cos(2/16π) */
private static final int COS_3_16_PI = 3406; /* cos(3/16π) */
private static final int HALF_SQRT_2 = 2896; /* cos(4/16π) */
private static final int SIN_3_16_PI = 2276; /* cos(5/16π) */
private static final int COS_6_16_PI = 1567; /* cos(6/16π) */
private static final int SIN_1_16_PI = 799; /* cos(7/16π) */
static {
ORDER_ZIGZAG_INVERTED = new byte[] {
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
};
}
private static void translateY(ImageHeaderChunk header, Component[] components, int width, int height, int[] pixels) {
int horzSizeY;
int[] blocksY;
Component componentY = components[0];
horzSizeY = componentY.horzSize;
blocksY = componentY.blocks;
for(int index = 0, iy = 0; iy < height; iy++) for(int ix = 0; ix < width; ix++)
{
int col = ix >> 3;
int row = iy >> 3;
int hrz = ix & 0x07;
int vrt = iy & 0x07;
int y;
if((y = blocksY[col + row * horzSizeY << 6 | vrt << 3 | hrz]) < 0x00)
{
y = 0x00;
}
else if(y > 0xff)
{
y = 0xff;
}
pixels[index++] = y << 16 | y << 8 | y;
}
}
private static void translateYCbCr(ImageHeaderChunk header, Component[] components, int width, int height, int[] pixels) {
int horzScaleY;
int horzScaleCb;
int horzScaleCr;
int horzScaleMax;
int vertScaleY;
int vertScaleCb;
int vertScaleCr;
int vertScaleMax;
int horzSizeY;
int horzSizeCb;
int horzSizeCr;
int[] blocksY;
int[] blocksCb;
int[] blocksCr;
Component componentY = components[0];
Component componentCb = components[1];
Component componentCr = components[2];
horzScaleY = componentY.horzFactor;
horzScaleCb = componentCb.horzFactor;
horzScaleCr = componentCr.horzFactor;
horzScaleMax = header.maxHorzFactor;
vertScaleY = componentY.vertFactor;
vertScaleCb = componentCb.vertFactor;
vertScaleCr = componentCr.vertFactor;
vertScaleMax = header.maxVertFactor;
horzSizeY = componentY.horzSize;
horzSizeCb = componentCb.horzSize;
horzSizeCr = componentCr.horzSize;
blocksY = componentY.blocks;
blocksCb = componentCb.blocks;
blocksCr = componentCr.blocks;
for(int index = 0, iy = 0; iy < height; iy++) for(int ix = 0; ix < width; ix++)
{
int sx;
int sy;
int col;
int row;
int hrz;
int vrt;
int y;
int cb;
int cr;
int r;
int g;
int b;
col = (sx = ix * horzScaleY / horzScaleMax) >> 3;
row = (sy = iy * vertScaleY / vertScaleMax) >> 3;
hrz = sx & 0x07;
vrt = sy & 0x07;
y = blocksY[col + row * horzSizeY << 6 | vrt << 3 | hrz] << 12;
col = (sx = ix * horzScaleCb / horzScaleMax) >> 3;
row = (sy = iy * vertScaleCb / vertScaleMax) >> 3;
hrz = sx & 0x07;
vrt = sy & 0x07;
cb = blocksCb[col + row * horzSizeCb << 6 | vrt << 3 | hrz] - 0x80;
col = (sx = ix * horzScaleCr / horzScaleMax) >> 3;
row = (sy = iy * vertScaleCr / vertScaleMax) >> 3;
hrz = sx & 0x07;
vrt = sy & 0x07;
cr = blocksCr[col + row * horzSizeCr << 6 | vrt << 3 | hrz] - 0x80;
r = y + cr * 0x166f >> 12;
g = y - cb * 0x0582 - cr * 0x0b6d >> 12;
b = y + cb * 0x1c5a >> 12;
pixels[index++] = (r < 0 ? 0 : r > 0xff ? 0xff : r) << 16 | (g < 0 ? 0 : g > 0xff ? 0xff : g) << 8 | (b < 0 ? 0 : b > 0xff ? 0xff : b);
}
}
private static int[] buildImage(ImageHeaderChunk header, int[][] quantizationTables, int width, int height) throws InvalidDataFormatException {
int componentsCount;
int[] p = new int[0x40];
int[] result = new int[width * height];
Component[] components = new Component[componentsCount = header.getComponentsCount()];
/* деквантование, обратное дискретное косинусное преобразование */
for(int componentIndex = componentsCount; componentIndex-- > 0; )
{
int[] q;
int[] b;
Component component = components[componentIndex] = header.getComponent(header.getComponentId(componentIndex));
if((q = quantizationTables[component.quantizationTableID]) == null)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
for(int blen = (b = component.blocks).length, offset = 0; offset < blen; offset += 0x40)
{
int t;
int v0;
int v1;
int v2;
int v3;
int v4;
int v5;
int v6;
int v7;
/* деквантование */
for(int index = 0; index < 0x40; index++) p[index] = b[offset + index] * q[index];
/* обратное дискретное косинусное преобразование */
for(int r = 0; r < 8 * 8; r += 8)
{
if((p[r + 1] | p[r + 2] | p[r + 3] | p[r + 4] | p[r + 5] | p[r + 6] | p[r + 7]) == 0)
{
Array.fill(p, r, 8, ONE_SQRT_2 * p[r] + 0x0200 >> 10);
continue;
}
/* стадия 4 */
v0 = ONE_SQRT_2 * p[r] + 0x80 >> 8;
v1 = ONE_SQRT_2 * p[r + 4] + 0x80 >> 8;
v2 = p[r + 2];
v3 = p[r + 6];
v4 = HALF_SQRT_2 * ((v5 = p[r + 1]) - (v6 = p[r + 7])) + 0x80 >> 8;
v7 = HALF_SQRT_2 * (v5 + v6) + 0x80 >> 8;
v5 = p[r + 3] << 4;
v6 = p[r + 5] << 4;
/* стадия 3 */
t = v0 - v1 + 1 >> 1;
v0 = v0 + v1 + 1 >> 1;
v1 = t;
t = v2 * SIN_6_16_PI + v3 * COS_6_16_PI + 0x80 >> 8;
v2 = v2 * COS_6_16_PI - v3 * SIN_6_16_PI + 0x80 >> 8;
v3 = t;
t = v4 - v6 + 1 >> 1;
v4 = v4 + v6 + 1 >> 1;
v6 = t;
t = v7 + v5 + 1 >> 1;
v5 = v7 - v5 + 1 >> 1;
v7 = t;
/* стадия 2 */
t = v0 - v3 + 1 >> 1;
v0 = v0 + v3 + 1 >> 1;
v3 = t;
t = v1 - v2 + 1 >> 1;
v1 = v1 + v2 + 1 >> 1;
v2 = t;
t = v4 * SIN_3_16_PI + v7 * COS_3_16_PI + 0x0800 >> 12;
v4 = v4 * COS_3_16_PI - v7 * SIN_3_16_PI + 0x0800 >> 12;
v7 = t;
t = v5 * SIN_1_16_PI + v6 * COS_1_16_PI + 0x0800 >> 12;
v5 = v5 * COS_1_16_PI - v6 * SIN_1_16_PI + 0x0800 >> 12;
v6 = t;
/* стадия 1 */
p[r] = v0 + v7;
p[r + 7] = v0 - v7;
p[r + 1] = v1 + v6;
p[r + 6] = v1 - v6;
p[r + 2] = v2 + v5;
p[r + 5] = v2 - v5;
p[r + 3] = v3 + v4;
p[r + 4] = v3 - v4;
}
for(int c = 0; c < 8; c++)
{
if((p[c + 1 * 8] | p[c + 2 * 8] | p[c + 3 * 8] | p[c + 4 * 8] | p[c + 5 * 8] | p[c + 6 * 8] | p[c + 7 * 8]) == 0)
{
t = ONE_SQRT_2 * p[c] + 0x2000 >> 14;
p[c] = t;
p[c + 1 * 8] = t;
p[c + 2 * 8] = t;
p[c + 3 * 8] = t;
p[c + 4 * 8] = t;
p[c + 5 * 8] = t;
p[c + 6 * 8] = t;
p[c + 7 * 8] = t;
continue;
}
/* стадия 4 */
v0 = ONE_SQRT_2 * p[c] + 0x0800 >> 12;
v1 = ONE_SQRT_2 * p[c + 4 * 8] + 0x0800 >> 12;
v2 = p[c + 2 * 8];
v3 = p[c + 6 * 8];
v4 = HALF_SQRT_2 * ((v5 = p[c + 1 * 8]) - (v6 = p[c + 7 * 8])) + 0x0800 >> 12;
v7 = HALF_SQRT_2 * (v5 + v6) + 0x0800 >> 12;
v5 = p[c + 3 * 8];
v6 = p[c + 5 * 8];
/* стадия 3 */
t = v0 - v1 + 1 >> 1;
v0 = v0 + v1 + 1 >> 1;
v1 = t;
t = v2 * SIN_6_16_PI + v3 * COS_6_16_PI + 0x0800 >> 12;
v2 = v2 * COS_6_16_PI - v3 * SIN_6_16_PI + 0x0800 >> 12;
v3 = t;
t = v4 - v6 + 1 >> 1;
v4 = v4 + v6 + 1 >> 1;
v6 = t;
t = v7 + v5 + 1 >> 1;
v5 = v7 - v5 + 1 >> 1;
v7 = t;
/* стадия 2 */
t = v0 - v3 + 1 >> 1;
v0 = v0 + v3 + 1 >> 1;
v3 = t;
t = v1 - v2 + 1 >> 1;
v1 = v1 + v2 + 1 >> 1;
v2 = t;
t = v4 * SIN_3_16_PI + v7 * COS_3_16_PI + 0x0800 >> 12;
v4 = v4 * COS_3_16_PI - v7 * SIN_3_16_PI + 0x0800 >> 12;
v7 = t;
t = v5 * SIN_1_16_PI + v6 * COS_1_16_PI + 0x0800 >> 12;
v5 = v5 * COS_1_16_PI - v6 * SIN_1_16_PI + 0x0800 >> 12;
v6 = t;
/* стадия 1 */
p[c] = v0 + v7;
p[c + 7 * 8] = v0 - v7;
p[c + 1 * 8] = v1 + v6;
p[c + 6 * 8] = v1 - v6;
p[c + 2 * 8] = v2 + v5;
p[c + 5 * 8] = v2 - v5;
p[c + 3 * 8] = v3 + v4;
p[c + 4 * 8] = v3 - v4;
}
for(int index = 0; index < 0x40; index++) b[offset + index] = (p[index] + 8 >> 4) + 0x80;
}
}
/* преобразование в формат пиксела 0x00rrggbb */
switch(componentsCount)
{
case 1:
case 2:
translateY(header, components, width, height, result);
break;
case 3:
translateYCbCr(header, components, width, height, result);
break;
}
return result;
}
private int width;
private int height;
private int[] pixels;
public JPEGDecoder() {
}
public void loadFromInputStream(InputStream stream) throws IOException {
loadFromDataStream(new ExtendedDataInputStream(stream));
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
int chunkName;
int restartInterval = 0;
int imageWidth = 0;
int imageHeight = 0;
int[] imagePixels = null;
int[][] quantizationTables = new int[0x10][];
ChaffmanTable[] chaffmanTables = new ChaffmanTable[0x20];
StartOfScanChunk scan = new StartOfScanChunk();
ImageHeaderChunk header = null;
ScanDecoder decoder = null;
/* чтение входного потока данных */
do
{
switch(chunkName = stream.readUnsignedShort())
{
default:
if(chunkName < MIN || chunkName > MAX)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
throw new UnsupportedDataException("JPEGDecoder.loadFromInputStream: неподдерживаемые данные.");
case SOF0:
case SOF1:
case SOF2:
if(header != null)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
((Chunk) (header = new ImageHeaderChunk(chunkName))).loadFromDataStream(stream);
imageWidth = header.width;
imageHeight = header.height;
break;
case DHT:
case DQT:
{
Object[] tables;
TableSetChunk chunk;
if(chunkName == DQT)
{
tables = quantizationTables;
chunk = new QuantizationTableSetChunk();
} else
{
tables = chaffmanTables;
chunk = new ChaffmanTableSetChunk();
}
((Chunk) chunk).loadFromDataStream(stream);
for(int i = chunk.getTablesCount(); i-- > 0; )
{
int id = chunk.getTableId(i);
tables[id] = chunk.getTable(i);
}
break;
}
case EOI:
if(header == null)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
break;
case SOS:
if(header == null)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
if(decoder == null) decoder = new ScanDecoder(stream.getSourceStream(), header);
((Chunk) scan).loadFromDataStream(stream);
for(int i = scan.getComponentsCount(); i-- > 0; )
{
ChaffmanTable ac;
ChaffmanTable dc;
Component component = header.getComponent(scan.getComponentId(i));
component.chaffmanTableAC = ac = chaffmanTables[scan.getComponentChaffmanTableId(i, AC)];
component.chaffmanTableDC = dc = chaffmanTables[scan.getComponentChaffmanTableId(i, DC)];
if(ac == null || dc == null)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
}
decoder.decodeScan(restartInterval, scan);
break;
case DRI:
restartInterval = stream.readInt() & 0xffff;
break;
case APP0:
case APP1:
case APP2:
case APP3:
case APP4:
case APP5:
case APP6:
case APP7:
case APP8:
case APP9:
case APP10:
case APP11:
case APP12:
case APP13:
case APP14:
case APP15:
case COM:
int len;
if((len = stream.readUnsignedShort() - 2) < 0 || stream.skipBytes(len) != len)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
break;
}
} while(chunkName != EOI);
/* построение изображения */
imagePixels = buildImage(header, quantizationTables, imageWidth, imageHeight);
/* вывод результата */
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 false;
}
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;
}
}
final class JPEGBitInputStream extends Object
{
private int readedByte;
private int lastBitIndex;
private final InputStream stream;
public JPEGBitInputStream(InputStream stream) {
this.stream = stream;
}
public void reset() {
lastBitIndex = 0;
}
public int readBit() throws IOException {
int i = lastBitIndex;
int r = readedByte;
if(i > 0)
{
lastBitIndex = --i;
} else
{
InputStream source = stream;
lastBitIndex = i = 7;
if((readedByte = r = source.read()) < 0)
{
throw new EOFException("DataInputStream.readUnsignedByte: достигнут конец потока данных.");
}
if(r == 0xff)
{
int byte0;
if((byte0 = source.read()) < 0 || byte0 > 0xff)
{
throw new EOFException("DataInputStream.readUnsignedByte: достигнут конец потока данных.");
}
switch(JPEGDecoder.MIN | byte0)
{
case JPEGDecoder.MIN:
break;
case JPEGDecoder.DNL:
if(source.skip(4L) != 4L)
{
throw new EOFException("DataInputStream.readUnsignedByte: достигнут конец потока данных.");
}
return lastBitIndex = 0;
default:
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
}
}
return r >> i & 0x01;
}
public int readBits(int quantity) throws IOException {
int result;
for(result = 0; quantity-- > 0; result = result << 1 | readBit());
return result;
}
public int readAndExtend(int quantity) throws IOException {
int bits;
return quantity <= 0 ? 0 : (bits = readBits(quantity)) < (1 << (quantity - 1)) ? bits + (-1 << quantity) + 1 : bits;
}
public int refineAC(int ac, int shift) throws IOException {
if(ac > 0)
{
if(readBit() != 0) ac += 1 << shift;
}
else if(ac < 0)
{
if(readBit() != 0) ac += -1 << shift;
}
return ac;
}
}
final class Component extends Object
{
public final int quantizationTableID;
public final int horzFactor;
public final int vertFactor;
public final int horzSize;
public final int[] blocks;
public ChaffmanTable chaffmanTableDC;
public ChaffmanTable chaffmanTableAC;
public Component(int horzFactor, int vertFactor, int quantizationTableID, int horzSize, int vertSize) {
this.quantizationTableID = quantizationTableID;
this.horzFactor = horzFactor;
this.vertFactor = vertFactor;
this.horzSize = horzSize;
this.blocks = new int[horzSize * vertSize << 6];
}
public int getBlocksOffset(int col, int row) {
return col + row * horzSize << 6;
}
}
final class ChaffmanTable extends Object
{
private final byte[] valueSet;
private final short[] valueIndices;
private final int[] minCodes;
private final int[] maxCodes;
public ChaffmanTable(byte[] valueSet, short[] valueIndices, int[] minCodes, int[] maxCodes) {
this.valueSet = valueSet;
this.valueIndices = valueIndices;
this.minCodes = minCodes;
this.maxCodes = maxCodes;
}
public int readValue(JPEGBitInputStream stream) throws IOException {
int i;
int code = stream.readBit();
int[] maxCodes = this.maxCodes;
for(i = 0; code > maxCodes[i]; i++) code = code << 1 | stream.readBit();
return valueSet[valueIndices[i] + code - minCodes[i]] & 0xff;
}
}
abstract class Chunk extends Object
{
public static int readSize(DataInputStream stream) throws IOException {
int result;
if((result = stream.readUnsignedShort() - 2) < 0)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
return result;
}
public final int chunkName;
public Chunk(int chunkName) {
this.chunkName = chunkName;
}
public abstract void loadFromDataStream(ExtendedDataInputStream stream) throws IOException;
}
final class ImageHeaderChunk extends Chunk
{
public static final int BASELINE = 0;
public static final int LOSSLESS = 3;
public static final int SEQUENTIAL = 1;
public static final int PROGRESSIVE = 2;
public int width;
public int height;
public int horzBlocks;
public int vertBlocks;
public int maxHorzFactor;
public int maxVertFactor;
private byte[] componentsIds;
private final Component[] components;
public ImageHeaderChunk(int chunkName) {
super(chunkName);
this.components = new Component[0x0100];
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
int iw;
int ih;
int bw;
int bh;
int hb;
int vb;
int mhf;
int mvf;
int chunkLen;
byte[] componentData;
Component[] componentProps;
if((chunkLen = readSize(stream)) < 9)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
stream.readByte(); /* пропускаем точность */
if((ih = stream.readUnsignedShort()) < 1 || (iw = stream.readUnsignedShort()) < 1)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
if((long) iw * (long) ih > 0x00100000L)
{
throw new UnsupportedDataException("JPEGDecoder.loadFromInputStream: неподдерживаемые данные.");
}
if(stream.readUnsignedByte() * 3 != (chunkLen -= 6))
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
if(chunkLen > JPEGDecoder.MAX_COMPONENTS * 3)
{
throw new UnsupportedDataException("JPEGDecoder.loadFromInputStream: неподдерживаемые данные.");
}
stream.readFully(componentData = new byte[chunkLen]);
componentProps = components;
mhf = mvf = 0;
for(int i = 1; i < chunkLen; i += 3)
{
int byte0;
int ihf = (byte0 = componentData[i]) >> 4 & 0x0f;
int ivf = byte0 & 0x0f;
if(ihf * ivf == 0)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
if(mhf < ihf) mhf = ihf;
if(mvf < ivf) mvf = ivf;
}
bw = mhf << 3;
bh = mvf << 3;
hb = (iw + bw - 1) / bw;
vb = (ih + bh - 1) / bh;
for(int i = 0; i < chunkLen; )
{
int byte0;
int index = componentData[i++] & 0xff;
int ihf = (byte0 = componentData[i++]) >> 4 & 0x0f;
int ivf = byte0 & 0x0f;
int iqt = componentData[i++] & 0x0f;
componentProps[index] = new Component(ihf, ivf, iqt, hb * ihf, vb * ivf);
}
width = iw;
height = ih;
horzBlocks = hb;
vertBlocks = vb;
maxHorzFactor = mhf;
maxVertFactor = mvf;
componentsIds = componentData;
}
public boolean isDifferentialCoding() {
return (chunkName & 0x04) != 0;
}
public boolean isArithmeticCoding() {
return chunkName >= JPEGDecoder.SOF9;
}
public int getCodingType() {
return chunkName & 0x03;
}
public int getComponentsCount() {
return componentsIds.length / 3;
}
public int getComponentId(int index) {
return componentsIds[index * 3] & 0xff;
}
public Component getComponent(int componentId) {
return components[componentId];
}
}
abstract class TableSetChunk extends Chunk
{
public TableSetChunk(int chunkName) {
super(chunkName);
}
public abstract void loadFromDataStream(ExtendedDataInputStream stream) throws IOException;
public abstract int getTablesCount();
public abstract int getTableId(int index);
public abstract Object getTable(int index);
}
final class QuantizationTableSetChunk extends TableSetChunk
{
private int count;
private byte[] ids;
private Object[] tables;
public QuantizationTableSetChunk() {
super(JPEGDecoder.DQT);
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
int chunkLen;
int dataCount;
byte[] order;
byte[] dataIds;
int[][] dataTables;
if((chunkLen = readSize(stream)) < 0x41)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
dataCount = 0;
dataIds = new byte[1];
dataTables = new int[1][];
order = JPEGDecoder.ORDER_ZIGZAG_INVERTED;
do
{
int byte0;
int valueLen;
int[] table;
switch(valueLen = (byte0 = stream.readUnsignedByte()) >> 4)
{
case 0:
if(chunkLen < 0x41)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
chunkLen -= 0x41;
break;
case 1:
if(chunkLen < 0x81)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
chunkLen -= 0x81;
break;
default:
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
table = new int[0x40];
for(int i = 0; i < 0x40; i++) table[order[i]] = valueLen == 1 ? stream.readUnsignedShort() : stream.readUnsignedByte();
if(dataIds.length == dataCount)
{
int newLen = (dataCount << 1) + 1;
Array.copy(dataIds, 0, dataIds = new byte[newLen], 0, dataCount);
Array.copy(dataTables, 0, dataTables = new int[newLen][], 0, dataCount);
}
dataTables[dataCount] = table;
dataIds[dataCount] = (byte) (byte0 & 0x0f);
dataCount++;
} while(chunkLen > 0);
count = dataCount;
ids = dataIds;
tables = dataTables;
}
public int getTablesCount() {
return count;
}
public int getTableId(int index) {
return ids[index];
}
public Object getTable(int index) {
return tables[index];
}
}
final class ChaffmanTableSetChunk extends TableSetChunk
{
private int count;
private byte[] ids;
private Object[] tables;
public ChaffmanTableSetChunk() {
super(JPEGDecoder.DHT);
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
int chunkLen;
int dataCount;
byte[] dataIds;
int[] bits;
ChaffmanTable[] dataTables;
if((chunkLen = readSize(stream)) < 0x11)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
dataCount = 0;
dataIds = new byte[1];
dataTables = new ChaffmanTable[1];
bits = new int[0x10];
do
{
int id;
int count;
byte[] valueSet;
short[] valueIndices;
int[] minCodes;
int[] maxCodes;
int[] chaCodes;
int[] chaCodesLens;
if(chunkLen < 0x11)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
chunkLen -= 0x11;
id = stream.readByte();
count = 0;
for(int i = 0; i < 0x10; i++) count += (bits[i] = stream.readUnsignedByte());
if(chunkLen < count)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
chunkLen -= count;
stream.readFully(valueSet = new byte[count]);
chaCodesLens = new int[count];
for(int index = 0, i = 1; i <= 0x10; i++) for(int len = bits[i - 1], j = 0; j < len; j++) chaCodesLens[index++] = i;
chaCodes = new int[count];
for(int index = 0, code = 0, si = count <= 0 ? 0 : chaCodesLens[0], p = 0; p < count; code <<= 1, si++) for(; p < count && chaCodesLens[p] == si; p++) chaCodes[index++] = code++;
valueIndices = new short[0x10];
minCodes = new int[0x10];
maxCodes = new int[0x10];
for(int k = 0, i = 0; i < 0x10; i++)
{
int bsize;
if((bsize = bits[i]) <= 0)
{
maxCodes[i] = -1;
continue;
}
valueIndices[i] = (short) k;
minCodes[i] = chaCodes[k];
maxCodes[i] = chaCodes[(k += bsize) - 1];
}
if(dataIds.length == dataCount)
{
int newLen = (dataCount << 1) + 1;
Array.copy(dataIds, 0, dataIds = new byte[newLen], 0, dataCount);
Array.copy(dataTables, 0, dataTables = new ChaffmanTable[newLen], 0, dataCount);
}
dataTables[dataCount] = new ChaffmanTable(valueSet, valueIndices, minCodes, maxCodes);
dataIds[dataCount] = (byte) (id & 0x1f);
dataCount++;
} while(chunkLen > 0);
count = dataCount;
ids = dataIds;
tables = dataTables;
}
public int getTablesCount() {
return count;
}
public int getTableId(int index) {
return ids[index];
}
public Object getTable(int index) {
return tables[index];
}
}
final class StartOfScanChunk extends Chunk
{
public int spectralSelectionBegin;
public int spectralSelectionEnd;
public int approximateBitPositionHigh;
public int approximateBitPositionLow;
private byte[] componentsIds;
public StartOfScanChunk() {
super(JPEGDecoder.SOS);
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
int byte0;
int chunkLen;
byte[] componentData;
if((chunkLen = readSize(stream)) < 6 || (stream.readUnsignedByte() << 1) != (chunkLen -= 4))
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
stream.readFully(componentData = new byte[chunkLen]);
spectralSelectionBegin = stream.readUnsignedByte();
spectralSelectionEnd = stream.readUnsignedByte();
approximateBitPositionHigh = (byte0 = stream.readByte()) >> 4 & 0x0f;
approximateBitPositionLow = byte0 & 0x0f;
componentsIds = componentData;
}
public boolean isVeryProgressive() {
int begin = spectralSelectionBegin;
int end = spectralSelectionEnd;
int high = approximateBitPositionHigh;
int low = approximateBitPositionLow;
return (begin <= 0 && end <= 0 || begin <= end && end <= 0x3f) && low <= 0x0d && high <= 0x0d && (high <= 0 || high == low + 1) && (begin <= 0 || getComponentsCount() == 1);
}
public boolean isACProgressive() {
return spectralSelectionBegin != 0 && spectralSelectionEnd != 0;
}
public boolean isDCProgressive() {
return (spectralSelectionBegin | spectralSelectionEnd) == 0;
}
public int getComponentsCount() {
return componentsIds.length >> 1;
}
public int getComponentId(int index) {
return componentsIds[index << 1] & 0xff;
}
public int getComponentChaffmanTableId(int index, int table) {
return componentsIds[(index << 1) + 1] >> ((table ^ 1) << 2) & 0x0f | table << 4;
}
}
final class ScanDecoder extends Object
{
private final boolean progressive;
private int eobrun;
private final int[] dcs;
private StartOfScanChunk scan;
private final InputStream source;
private final JPEGBitInputStream stream;
private final ImageHeaderChunk header;
public ScanDecoder(InputStream stream, ImageHeaderChunk header) {
this.progressive = header.getCodingType() == ImageHeaderChunk.PROGRESSIVE;
this.dcs = new int[JPEGDecoder.MAX_COMPONENTS];
this.source = stream;
this.stream = new JPEGBitInputStream(stream);
this.header = header;
}
public void decodeScan(int restartInterval, StartOfScanChunk scan) throws IOException {
boolean first;
int horzBlocks;
int vertBlocks;
int spectralEnd;
int spectralBegin;
int restartRemain;
int approximateBit;
int componentsCount;
ImageHeaderChunk header;
if(progressive && !scan.isVeryProgressive())
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
stream.reset();
this.scan = scan;
header = this.header;
horzBlocks = header.horzBlocks;
vertBlocks = header.vertBlocks;
Array.fill(dcs, 0, JPEGDecoder.MAX_COMPONENTS, 0);
if((componentsCount = scan.getComponentsCount()) == 1)
{
int mcuWidth;
int mcuHeight;
Component component = header.getComponent(scan.getComponentId(0));
mcuWidth = (header.maxHorzFactor << 3) / component.horzFactor;
mcuHeight = (header.maxVertFactor << 3) / component.vertFactor;
horzBlocks = (header.width + mcuWidth - 1) / mcuWidth;
vertBlocks = (header.height + mcuHeight - 1) / mcuHeight;
}
first = scan.approximateBitPositionHigh == 0;
spectralBegin = scan.spectralSelectionBegin;
spectralEnd = scan.spectralSelectionEnd;
approximateBit = scan.approximateBitPositionLow;
restartRemain = restartInterval;
for(int ymcu = 0; ymcu < vertBlocks; ymcu++) for(int xmcu = 0; xmcu < horzBlocks; xmcu++)
{
if(restartInterval != 0)
{
if(restartRemain == 0)
{
handleRestartInterval();
restartRemain = restartInterval;
}
restartRemain--;
}
decodeMCU(xmcu, ymcu, componentsCount, first, spectralBegin, spectralEnd, approximateBit);
}
}
private void decodeMCU(int xmcu, int ymcu, int componentsCount, boolean first, int begin, int end, int approximateBit) throws IOException {
boolean progressive = this.progressive;
ImageHeaderChunk header = this.header;
StartOfScanChunk scan = this.scan;
for(int componentIndex = 0; componentIndex < componentsCount; componentIndex++)
{
int hf;
int vf;
int[] blocks;
Component component;
blocks = (component = header.getComponent(scan.getComponentId(componentIndex))).blocks;
if(componentsCount == 1)
{
hf = 1;
vf = 1;
} else
{
hf = component.horzFactor;
vf = component.vertFactor;
}
for(int v = 0; v < vf; v++) for(int h = 0; h < hf; h++)
{
int offset = component.getBlocksOffset(h + hf * xmcu, v + vf * ymcu);
if(!progressive) Array.fill(blocks, offset, 0x40, 0);
if(!progressive || scan.isDCProgressive()) decodeDCCoefficient(blocks, offset, component, componentIndex, first, approximateBit);
if(!progressive)
{
decodeACCoefficients(blocks, offset, component);
}
else if(scan.isACProgressive())
{
if(first)
{
decodeACFirstCoefficients(blocks, offset, component, begin, end, approximateBit);
} else
{
decodeACRefineCoefficients(blocks, offset, component, begin, end, approximateBit);
}
}
}
}
}
private void decodeACCoefficients(int[] blocks, int offset, Component component) throws IOException {
byte[] order = JPEGDecoder.ORDER_ZIGZAG_INVERTED;
ChaffmanTable table = component.chaffmanTableAC;
JPEGBitInputStream stream = this.stream;
for(int k = 1; k < 0x40; )
{
int rs = table.readValue(stream);
int s = rs & 0x0f;
int r = rs >> 4;
if(s == 0)
{
if(r == 0x0f)
{
k += 0x10;
} else
{
break;
}
} else
{
int bits = stream.readAndExtend(s);
blocks[offset + order[k += r]] = bits;
k++;
}
}
}
private void decodeACFirstCoefficients(int[] blocks, int offset, Component component, int begin, int end, int approximateBit) throws IOException {
int eobrun;
byte[] order;
ChaffmanTable table;
JPEGBitInputStream stream;
if((eobrun = this.eobrun) > 0)
{
this.eobrun = eobrun - 1;
return;
}
order = JPEGDecoder.ORDER_ZIGZAG_INVERTED;
table = component.chaffmanTableAC;
stream = this.stream;
for(int k = begin; k <= end; )
{
int rs = table.readValue(stream);
int s = rs & 0x0f;
int r = rs >> 4;
if(s == 0)
{
if(r == 0x0f)
{
k += 0x10;
} else
{
this.eobrun = (1 << r) + stream.readBits(r) - 1;
break;
}
} else
{
int bits = stream.readAndExtend(s);
blocks[offset + order[k += r]] = bits << approximateBit;
k++;
}
}
}
private void decodeACRefineCoefficients(int[] blocks, int offset, Component component, int begin, int end, int approximateBit) throws IOException {
int eobrun = this.eobrun;
byte[] order = JPEGDecoder.ORDER_ZIGZAG_INVERTED;
ChaffmanTable table = component.chaffmanTableAC;
JPEGBitInputStream stream = this.stream;
for(int k = begin; k <= end; )
{
if(eobrun > 0)
{
for(; k <= end; k++)
{
int index = offset + order[k];
blocks[index] = stream.refineAC(blocks[index], approximateBit);
}
eobrun--;
} else
{
int rs = table.readValue(stream);
int s = rs & 0x0f;
int r = rs >> 4;
if(s == 0)
{
if(r == 0x0f)
{
for(int zeros = 0; zeros < 0x10 && k <= end; k++)
{
int index = offset + order[k];
if(blocks[index] != 0)
{
blocks[index] = stream.refineAC(blocks[index], approximateBit);
} else
{
zeros++;
}
}
} else
{
eobrun = (1 << r) + stream.readBits(r);
}
} else
{
int bits = stream.readBits(s);
int index = offset + order[k];
for(int zeros = 0; (zeros < r || blocks[index] != 0) && k <= end; index = offset + order[++k])
{
if(blocks[index] != 0)
{
blocks[index] = stream.refineAC(blocks[index], approximateBit);
} else
{
zeros++;
}
}
blocks[index] = (bits != 0 ? 1 : -1) << approximateBit;
k++;
}
}
}
this.eobrun = eobrun;
}
private void decodeDCCoefficient(int[] blocks, int offset, Component component, int componentIndex, boolean first, int approximateBit) throws IOException {
boolean progressive = this.progressive;
int dc = 0;
ChaffmanTable table = component.chaffmanTableDC;
if(progressive && !first)
{
int bit = stream.readBit();
dc = blocks[offset] + (bit << approximateBit);
} else
{
int q;
int[] dcs;
JPEGBitInputStream stream = this.stream;
dc = (dcs = this.dcs)[componentIndex];
if((q = table.readValue(stream)) != 0)
{
int diff = stream.readAndExtend(q);
dcs[componentIndex] = (dc += diff);
}
if(!progressive) dc <<= approximateBit;
}
blocks[offset] = dc;
}
private void handleRestartInterval() throws IOException {
int byte0;
int chunkName;
InputStream source = this.source;
do
{
if((byte0 = source.read()) < 0)
{
throw new EOFException("DataInputStream.readUnsignedByte: достигнут конец потока данных.");
}
} while(byte0 != 0xff);
do
{
if((byte0 = source.read()) < 0)
{
throw new EOFException("DataInputStream.readUnsignedByte: достигнут конец потока данных.");
}
} while(byte0 == 0xff);
if((chunkName = JPEGDecoder.MIN | byte0) < JPEGDecoder.RST0 || chunkName > JPEGDecoder.RST7)
{
throw new InvalidDataFormatException("JPEGDecoder.loadFromInputStream: неправильный формат данных.");
}
eobrun = 0;
stream.reset();
Array.fill(dcs, 0, JPEGDecoder.MAX_COMPONENTS, 0);
}
}