BMPDecoder.java

Переключить прокрутку окна
Загрузить этот исходный код

/*
    Реализация спецификаций 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;
    }
}