/*
Реализация спецификаций 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.bmp;
import java.io.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.graphics.*;
import malik.emulator.fileformats.graphics.jpeg.*;
import malik.emulator.fileformats.graphics.png.*;
public final class BMPDecoder extends Object implements ImageDecoder
{
public static final long SIGNATURE = 0x424dL;
private static final int BI_RGB = 0;
private static final int BI_RLE8 = 1;
private static final int BI_RLE4 = 2;
private static final int BI_BITFIELDS = 3;
private static final int BI_JPEG = 4;
private static final int BI_PNG = 5;
private static final int BI_ALPHABITFIELDS = 6;
private int width;
private int height;
private int[] pixels;
public BMPDecoder() {
}
public void loadFromInputStream(InputStream stream) throws IOException {
loadFromDataStream(new ExtendedDataInputStream(stream));
}
public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
boolean useReverse = true;
int rBitMask = 0;
int gBitMask = 0;
int bBitMask = 0;
int aBitMask = 0;
int pixelBits = 0;
int compression = 0;
int paletteLength = 0;
int imageWidth = 0;
int imageHeight = 0;
int[] imagePixels = null;
int[] imagePalette = null;
/* чтение входного потока данных */
{
/* bitmap file header */
stream.skip(8L); /* игнорируем размер файла и зарезервированные поля */
int dataOffset = stream.readIntLE(); /* смещение данных изображения */
/* bitmap info */
int headerSize = stream.readIntLE(); /* размер заголовка */
if(headerSize == 0x0c)
{
imageWidth = stream.readUnsignedShortLE(); /* ширина */
imageHeight = stream.readUnsignedShortLE(); /* высота */
}
else if(headerSize >= 0x28)
{
imageWidth = stream.readIntLE(); /* ширина */
imageHeight = stream.readIntLE(); /* высота */
}
else
{
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
if(imageHeight < 0)
{
imageHeight = -imageHeight;
useReverse = false;
}
if((imageWidth | imageHeight) < 0 || imageWidth == 0 || imageHeight == 0)
{
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
if((long) imageWidth * (long) imageHeight > 0x00100000L)
{
throw new UnsupportedDataException("BMPDecoder.loadFromInputStream: неподдерживаемые данные.");
}
stream.skip(2L); /* игнорируем количество плоскостей */
pixelBits = stream.readUnsignedShortLE(); /* разрядность пиксела */
if(pixelBits != 0 && pixelBits != 1 && pixelBits != 2 && pixelBits != 4 && pixelBits != 8 && pixelBits != 16 && pixelBits != 24 && pixelBits != 32 && pixelBits != 48 && pixelBits != 64)
{
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
if(headerSize >= 0x28)
{
compression = stream.readIntLE(); /* алгоритм сжатия */
stream.skip(12L); /* игнорируем размер и плотность пикселов изображения */
int len = stream.readIntLE(); /* длина палитры */
if(len < 0 || len > 0x0100)
{
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
if(len > 0)
{
imagePalette = new int[paletteLength = len];
}
stream.skip(4L); /* игнорируем количество важных цветов палитры */
}
if(imagePalette == null && pixelBits >= 1 && pixelBits <= 8)
{
imagePalette = new int[paletteLength = 1 << pixelBits];
}
if(headerSize >= 0x38)
{
rBitMask = stream.readIntLE(); /* маска красного канала */
gBitMask = stream.readIntLE(); /* маска зелёного канала */
bBitMask = stream.readIntLE(); /* маска синего канала */
aBitMask = stream.readIntLE(); /* маска прозрачного канала */
stream.skip(headerSize - 0x38); /* игнорируем остальные поля */
} else
{
if(pixelBits == 16)
{
rBitMask = 0x7c00;
gBitMask = 0x03e0;
bBitMask = 0x001f;
aBitMask = 0x0000;
}
else if(pixelBits == 32)
{
rBitMask = 0x00ff0000;
gBitMask = 0x0000ff00;
bBitMask = 0x000000ff;
aBitMask = 0xff000000;
}
stream.skip(headerSize - 0x28); /* игнорируем остальные поля */
}
/* bit masks */
int masksSize = 0;
if(headerSize == 0x28 && (pixelBits == 16 || pixelBits == 32))
{
if(compression == BI_BITFIELDS)
{
rBitMask = stream.readIntLE(); /* маска красного канала */
gBitMask = stream.readIntLE(); /* маска зелёного канала */
bBitMask = stream.readIntLE(); /* маска синего канала */
masksSize = 0x0c;
}
else if(compression == BI_ALPHABITFIELDS)
{
rBitMask = stream.readIntLE(); /* маска красного канала */
gBitMask = stream.readIntLE(); /* маска зелёного канала */
bBitMask = stream.readIntLE(); /* маска синего канала */
aBitMask = stream.readIntLE(); /* маска прозрачного канала */
masksSize = 0x10;
}
}
/* color table */
int paletteSize;
if(headerSize >= 0x28)
{
for(int i = 0; i < paletteLength; i++)
{
int bComponent = stream.readUnsignedByte(); /* значение синего канала компонента палитры */
int gComponent = stream.readUnsignedByte(); /* значение зелёного канала компонента палитры */
int rComponent = stream.readUnsignedByte(); /* значение красного канала компонента палитры */
stream.skip(1L); /* игнорируем зарезервированный канал компонента палитры */
imagePalette[i] = 0xff000000 | rComponent << 16 | gComponent << 8 | bComponent;
}
paletteSize = paletteLength << 2;
} else
{
for(int i = 0; i < paletteLength; i++)
{
int bComponent = stream.readUnsignedByte(); /* значение синего канала компонента палитры */
int gComponent = stream.readUnsignedByte(); /* значение зелёного канала компонента палитры */
int rComponent = stream.readUnsignedByte(); /* значение красного канала компонента палитры */
imagePalette[i] = 0xff000000 | rComponent << 16 | gComponent << 8 | bComponent;
}
paletteSize = paletteLength * 3;
}
/* image data */
int descriptorSize = paletteSize + masksSize + headerSize + 0x0e;
if(dataOffset < descriptorSize)
{
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
stream.skip(dataOffset - descriptorSize); /* переход к данным изображения */
int aBitShift;
int rBitShift;
int gBitShift;
int bBitShift;
long padding;
ImageDecoder decoder;
switch(compression)
{
case BI_RGB:
case BI_BITFIELDS:
case BI_ALPHABITFIELDS:
imagePixels = new int[imageWidth * imageHeight];
switch(pixelBits)
{
case 1:
padding = -(imageWidth + (-imageWidth & 7) >> 3) & 3;
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth + (-imageWidth & 7) >> 3; b-- > 0; )
{
int octet = stream.readUnsignedByte(); /* 8 пикселов */
imagePixels[offset + x++] = imagePalette[octet >> 7 & 1];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[octet >> 6 & 1];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[octet >> 5 & 1];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[octet >> 4 & 1];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[octet >> 3 & 1];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[octet >> 2 & 1];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[octet >> 1 & 1];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[octet & 1];
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 2:
padding = -(imageWidth + (-imageWidth & 3) >> 2) & 3;
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth + (-imageWidth & 3) >> 2; b-- > 0; )
{
int quartet = stream.readUnsignedByte(); /* 4 пиксела */
imagePixels[offset + x++] = imagePalette[quartet >> 6 & 3];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[quartet >> 4 & 3];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[quartet >> 2 & 3];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[quartet & 3];
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 4:
padding = -(imageWidth + (-imageWidth & 1) >> 1) & 3;
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth + (-imageWidth & 1) >> 1; b-- > 0; )
{
int pair = stream.readUnsignedByte(); /* 2 пиксела */
imagePixels[offset + x++] = imagePalette[pair >> 4 & 0x0f];
if(x >= imageWidth) break;
imagePixels[offset + x++] = imagePalette[pair & 0x0f];
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 8:
padding = -imageWidth & 3;
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth; b-- > 0; )
{
int pixel = stream.readUnsignedByte(); /* 1 пиксел */
imagePixels[offset + x++] = imagePalette[pixel];
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 16:
padding = (imageWidth & 1) << 1;
aBitShift = rBitShift = gBitShift = bBitShift = Integer.MIN_VALUE;
for(int i = 16; i-- > 0; )
{
if(aBitShift == Integer.MIN_VALUE && (aBitMask >> i & 1) != 0) aBitShift = 31 - i;
if(rBitShift == Integer.MIN_VALUE && (rBitMask >> i & 1) != 0) rBitShift = 23 - i;
if(gBitShift == Integer.MIN_VALUE && (gBitMask >> i & 1) != 0) gBitShift = 15 - i;
if(bBitShift == Integer.MIN_VALUE && (bBitMask >> i & 1) != 0) bBitShift = 7 - i; /* может быть отрицательным */
}
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth; b-- > 0; )
{
int pixel = stream.readUnsignedShortLE(); /* непосредственное значение пиксела */
int aRawValue = (pixel & aBitMask) << aBitShift;
int rRawValue = (pixel & rBitMask) << rBitShift;
int gRawValue = (pixel & gBitMask) << gBitShift;
int bRawValue = bBitShift < 0 ? (pixel & bBitMask) >> -bBitShift : (pixel & bBitMask) << bBitShift;
imagePixels[offset + x++] = ~(aBitMask << aBitShift) & 0xff000000 | aRawValue & 0xff000000 | rRawValue & 0x00ff0000 | gRawValue & 0x0000ff00 | bRawValue & 0x000000ff;
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 24:
padding = imageWidth & 3;
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth; b-- > 0; )
{
int bRawValue = stream.readUnsignedByte(); /* значение синего канала пиксела */
int gRawValue = stream.readUnsignedByte(); /* значение зелёного канала пиксела */
int rRawValue = stream.readUnsignedByte(); /* значение красного канала пиксела */
imagePixels[offset + x++] = 0xff000000 | rRawValue << 16 | gRawValue << 8 | bRawValue;
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 32:
padding = 0L;
aBitShift = rBitShift = gBitShift = bBitShift = 0;
for(int i = 32; i-- > 0; )
{
if(aBitShift == Integer.MIN_VALUE && (aBitMask >> i & 1) != 0) aBitShift = 31 - i;
if(rBitShift == Integer.MIN_VALUE && (rBitMask >> i & 1) != 0) rBitShift = 23 - i; /* может быть отрицательным */
if(gBitShift == Integer.MIN_VALUE && (gBitMask >> i & 1) != 0) gBitShift = 15 - i; /* может быть отрицательным */
if(bBitShift == Integer.MIN_VALUE && (bBitMask >> i & 1) != 0) bBitShift = 7 - i; /* может быть отрицательным */
}
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth; b-- > 0; )
{
int pixel = stream.readUnsignedShortLE(); /* непосредственное значение пиксела */
int aRawValue = (pixel & aBitMask) << aBitShift;
int rRawValue = rBitShift < 0 ? (pixel & rBitMask) >> -rBitShift : (pixel & rBitMask) << rBitShift;
int gRawValue = gBitShift < 0 ? (pixel & gBitMask) >> -gBitShift : (pixel & gBitMask) << gBitShift;
int bRawValue = bBitShift < 0 ? (pixel & bBitMask) >> -bBitShift : (pixel & bBitMask) << bBitShift;
imagePixels[offset + x++] = ~(aBitMask << aBitShift) & 0xff000000 | aRawValue & 0xff000000 | rRawValue & 0x00ff0000 | gRawValue & 0x0000ff00 | bRawValue & 0x000000ff;
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 48:
padding = (imageWidth & 1) << 1;
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth; b-- > 0; )
{
int bRawValue = stream.readUnsignedShortLE() & 0xff00; /* значение синего канала пиксела */
int gRawValue = stream.readUnsignedShortLE() & 0xff00; /* значение зелёного канала пиксела */
int rRawValue = stream.readUnsignedShortLE() & 0xff00; /* значение красного канала пиксела */
imagePixels[offset + x++] = 0xff000000 | rRawValue << 8 | gRawValue | bRawValue >> 8;
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
case 64:
padding = 0L;
for(int offset = useReverse ? imageWidth * (imageHeight - 1) : 0, y = imageHeight; y-- > 0; offset -= useReverse ? imageWidth : -imageWidth)
{
for(int x = 0, b = imageWidth; b-- > 0; )
{
int bRawValue = stream.readUnsignedShortLE() & 0xff00; /* значение синего канала пиксела */
int gRawValue = stream.readUnsignedShortLE() & 0xff00; /* значение зелёного канала пиксела */
int rRawValue = stream.readUnsignedShortLE() & 0xff00; /* значение красного канала пиксела */
int aRawValue = stream.readUnsignedShortLE() & 0xff00; /* значение прозрачного канала пиксела */
imagePixels[offset + x++] = aRawValue << 16 | rRawValue << 8 | gRawValue | bRawValue >> 8;
}
stream.skip(padding); /* игнорируем байты для выравнивания строк пикселов */
}
break;
default:
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
break;
case BI_RLE8:
case BI_RLE4:
throw new UnsupportedDataException("BMPDecoder.loadFromInputStream: неподдерживаемые данные.");
case BI_JPEG:
if(JPEGDecoder.SIGNATURE != stream.readUnsignedShort())
{
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
(decoder = new JPEGDecoder()).loadFromDataStream(stream);
imageWidth = decoder.getWidth();
imageHeight = decoder.getHeight();
imagePixels = decoder.getPixels();
for(int i = imagePixels.length; i-- > 0; ) imagePixels[i] |= 0xff000000;
break;
case BI_PNG:
if(PNGDecoder.SIGNATURE != stream.readLong())
{
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
(decoder = new PNGDecoder()).loadFromDataStream(stream);
imageWidth = decoder.getWidth();
imageHeight = decoder.getHeight();
imagePixels = decoder.getPixels();
break;
default:
throw new InvalidDataFormatException("BMPDecoder.loadFromInputStream: неправильный формат данных.");
}
}
/* вывод результата */
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;
}
}