Невозможно прочитать изображения из файла ico, поскольку ширина и высота равны 0 - PullRequest
0 голосов
/ 25 мая 2018

Я пытаюсь прочитать файл ico, который содержит два изображения ico bitcount = 8.Мне известен формат ICONDIRENTRY (https://msdn.microsoft.com/en-us/library/ms997538.aspx), и этот код в основном работает отдельно от нескольких конкретных файлов ico. Ниже приведен мой код -

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.io.FileInputStream;
import java.nio.ByteOrder;
import java.util.ArrayList;

/**
 * Created by dsomesh8 on 5/25/2018.
 */
public class Program {
    private static ArrayList<IconDirEntry> iconDirEntries;


    private static final byte SEED = -67;
    private static final byte SEED2 = 107;
    private static final String HEADER = "@OB@";
    private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    private static ImageInputStream in;


    public  static void main(String[] args) {
        FileInputStream fis = null;
        try {
            //C:\Users\dsomesh8\Downloads\Logs\test\Tool.ico
            //C:\Users\dsomesh8\Downloads\icons\zen.ico
            String filePath = "C:\\Users\\dsomesh8\\Downloads\\Logs\\test\\Tool.ico";
            //String filePath="C:\\Users\\tdivya\\Downloads\\test.ico";
            fis = new FileInputStream(filePath);
            in = ImageIO.createImageInputStream(fis);
            ArrayList<IconDirEntry> list=decodeIcon(in);
            IconImage nweIcon=new IconImage(list.get(0));
            //iconDirEntries = new ArrayList<IconDirEntry>();
            //boolean res = ;
        } catch (java.io.FileNotFoundException fnfe) {
            //WebLogger.debug("Input icon file " + filePath + " is missing");
        } catch (java.io.IOException ioe) {
            //WebLogger.debug("IO Exception reading the icon file " + filePath);
        }
    }

    private static ArrayList<IconDirEntry> decodeIcon(ImageInputStream in)
    {
        try
        {
            in.setByteOrder(ByteOrder.LITTLE_ENDIAN);

            in.readShort();             // idReserved field

            if(in.readShort() != 1)    // idType field
                return null;

            int imgCount = in.readShort();  //No of icon entries

            iconDirEntries = new ArrayList<IconDirEntry>();
            System.out.println(imgCount);
            for(int i = 0; i < imgCount; i++)
            {

                IconDirEntry dirEntry = new IconDirEntry(in);
                System.out.println(dirEntry.toString());
                iconDirEntries.add(dirEntry);
            }



        }
        catch(java.io.IOException ioe)
        {
            // WebLogger.debug("IOException reading the reserved field of the icon");
            return null;
        }
        return  iconDirEntries;
    }
}


import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;


/*
typdef struct
{
   BITMAPINFOHEADER   icHeader;   // DIB header
   RGBQUAD         icColors[1];   // Color table
   BYTE            icXOR[1];      // DIB bits for XOR mask
   BYTE            icAND[1];      // DIB bits for AND mask
} ICONIMAGE, *LPICONIMAGE;

typedef struct tagBITMAPINFOHEADER{
  DWORD  biSize;
  LONG   biWidth;
  LONG   biHeight;
  WORD   biPlanes;
  WORD   biBitCount;
  DWORD  biCompression;
  DWORD  biSizeImage;
  LONG   biXPelsPerMeter;
  LONG   biYPelsPerMeter;
  DWORD  biClrUsed;
  DWORD  biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

typedef struct tagRGBQUAD {
  BYTE    rgbBlue;
  BYTE    rgbGreen;
  BYTE    rgbRed;
  BYTE    rgbReserved;
} RGBQUAD;
*/


public class IconImage
{
    private int biSize;
    private int biWidth;
    private int biHeight;
    private int biPlanes;
    private int biBitCount;
    private int biCompression;
    private int biSizeImage;
    private int biXPelsPerMeter;
    private int biYPelsPerMeter;
    private int biClrUsed;
    private int biClrImportant;

    private byte[] rgbQuad;
    private byte[] icXOR;
    private byte[] icAND;

    private RGBQuad[] colors;
    private byte[] andMask;
    private byte[] xorMask;

    private IconDirEntry entry;
    private ImageInputStream iis;

    public IconImage(IconDirEntry entry)
    {
        this.entry = entry;

        try
        {
            iis = ImageIO.createImageInputStream(new ByteArrayInputStream(entry.getImageData()));
            iis.setByteOrder(java.nio.ByteOrder.LITTLE_ENDIAN);

            biSize = iis.readInt();
            biWidth = iis.readInt();
            biHeight = iis.readInt();
            biPlanes = iis.readShort();
            biBitCount = iis.readShort();

            biCompression = iis.readInt();
            biSizeImage = iis.readInt();
            biXPelsPerMeter = iis.readInt();
            biYPelsPerMeter = iis.readInt();
            biClrUsed = iis.readInt();
            biClrImportant = iis.readInt();

            if(entry.getBitCount() <= 8)
            {
                int nColors = (int)(Math.pow(2, biBitCount));
                colors = new RGBQuad[nColors]; //color table specifying colors uses in the image

                for(int i = 0; i < colors.length; i++)
                {
                    colors[i] = new RGBQuad(iis);
                }

                int bitsPerPixel = biBitCount;
                int pixelsPerByte = 8/bitsPerPixel;
                int nPixels = biWidth*biHeight/2; //biHeight is twice of actual height
                int nBytes = nPixels/pixelsPerByte;

                xorMask = new byte[nBytes];
                for(int i = 0; i < nBytes; i++)
                {
                    xorMask[i] = (byte)iis.readUnsignedByte();
                }

                int paddedWidth = 0;
                if(biWidth <= 32)
                    paddedWidth = 32;
                else
                {
                    int rem = biWidth%32;
                    if(rem == 0)
                        paddedWidth = biWidth;
                    else
                        paddedWidth = (biWidth/32 + 1)*32; //Round off to the next multiple of 32
                }

                int len = paddedWidth*(biHeight/2)/8;
                //AND mask is a monochrome DIB, with a color depth of 1 bpp
                andMask = new byte[len];
                for(int i = 0; i < len; i++)
                {
                    andMask[i] = (byte)iis.readUnsignedByte();
                }
            }

        }
        catch(Exception ioe)
        {
            System.out.println("Exception while reading image details for icon entry");
        }

    }

    public int[] getPixelValues()
    {
        int nRows = entry.getHeight();
        int nCols = entry.getWidth();
        int bpp = entry.getBitCount()/8; //Bytes per pixel
        int[] pixelValues = new int[nRows*nCols];

        for(int row = 0; row < nRows; row++)
        {

            byte[] rowData = new byte[nCols*bpp];
            try
            {
                iis.readFully(rowData);
            }
            catch(Exception e)
            {
                System.out.println("Exception reading the image data for this entry!!!");
            }

            int curRow = nRows - row; //Moving upwards starting from the last row
            int pos = (curRow - 1)*nCols; //Index of first pixel at current row

            int iByte = 0; //Iterator for each byte

            for(int col = 0; col < nCols; col++)
            {
                int pixelValue = 0;

                pixelValue = (rowData[iByte++] & 0xFF);

                if(bpp > 1)
                    pixelValue += ((rowData[iByte++] & 0xFF) << 8);

                if(bpp > 2)
                    pixelValue += ((rowData[iByte++] & 0xFF) << 16);

                if(bpp > 3)
                    pixelValue += ((rowData[iByte++] & 0xFF) << 24);
                else
                {
                    //if (pixelValue == 0)
                    pixelValue += ((255 & 0xFF) << 24);
                }

                pixelValues[pos] = pixelValue;
                pos++;
            }
        }

        return pixelValues;
    }

    public BufferedImage getIconGraphics()
    {
        BufferedImage buffImg = new BufferedImage(entry.getWidth(), entry.getHeight(), BufferedImage.TYPE_INT_ARGB);
        final Color TRANSPARENT = new Color(0, 0, 0, 0);

        Graphics2D g = buffImg.createGraphics();
        for(int y = biHeight/2 - 1; y >= 0; y--)
        {
            for(int x = 0; x < biWidth; x++)
            {
                if(isTransparent(x, y))
                    g.setColor(TRANSPARENT);
                else
                    g.setColor(getRGB(x, y));

                g.fillRect(x, entry.getHeight() - y - 1, 1, 1);
            }

        }

        return buffImg;
    }

    private boolean isTransparent(int x, int y)
    {
        int paddedWidth = 0;
        if(biWidth <= 32)
            paddedWidth = 32;
        else
        {
            int rem = biWidth%32;
            if(rem == 0)
                paddedWidth = biWidth;
            else
                paddedWidth = (biWidth/32 + 1)*32; //Round off to the next multiple of 32
        }

        int pixelIndex = (paddedWidth*y) + x;
        int andByteIndex = pixelIndex/8;
        int andByte = andMask[andByteIndex];
        int pos = x%8; //position of bit in the byte, for pixel x,y
        int nRightShift = 8 - (pos + 1); //Right shift needed to get the bit to LSB; increment of 1 since x starts from 0
        int pixelBit = andByte >> nRightShift;
        int andMask = pixelBit & 1;
        return (andMask == 1);
    }

    private Color getRGB(int x, int y)
    {
        int pixelIndex = (biWidth*y) + x;
        int bitsPerPixel = biBitCount;
        int pixelsPerByte = 8/bitsPerPixel;
        int xorByteIndex = pixelIndex/pixelsPerByte;

        int shift = ((pixelsPerByte - (x%pixelsPerByte) - 1)*biBitCount);
        int colIdx = (xorMask[xorByteIndex] >> shift) & ((1 << biBitCount) - 1);

        int b = colors[colIdx].getBlue();
        int g = colors[colIdx].getGreen();
        int r = colors[colIdx].getRed();

        return new Color(r, g, b);
    }

}


import com.sun.imageio.plugins.common.ReaderUtil;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;



/*
typedef struct
{
    BYTE        bWidth;          // Width, in pixels, of the image
    BYTE        bHeight;         // Height, in pixels, of the image
    BYTE        bColorCount;     // Number of colors in image (0 if >=8bpp)
    BYTE        bReserved;       // Reserved ( must be 0)
    WORD        wPlanes;         // Color Planes
    WORD        wBitCount;       // Bits per pixel
    DWORD       dwBytesInRes;    // How many bytes in this resource?
    DWORD       dwImageOffset;   // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
*/

public class IconDirEntry
{
    private short width;
    private short height;
    private short colorCount;
    private short reserved;

    private int planes;
    private int bitCount;
    private int bytesInResource;
    private int imageOffset;

    private byte[] imgData;

    public IconDirEntry(ImageInputStream in)
    {
        try
        {
            //System.out.println("canDecodeInput-"+canDecodeInput(in));
           // bitCount = readBitCountFromImageData(imgData);
            width = (short)in.readUnsignedByte();

            height = (short)in.readUnsignedByte();
            colorCount = new Byte(in.readByte()).shortValue();
            reserved = new Byte(in.readByte()).shortValue();

            planes = in.readShort();
            bitCount = in.readShort();
            bytesInResource = in.readInt();
            imageOffset = in.readInt();

            /*
            System.out.println("val : " + width);
            System.out.println("val : " + height);
            System.out.println("val : " + colorCount);
            System.out.println("val : " + reserved);

            System.out.println("val : " + planes);
            System.out.println("val : " + bitCount);
            System.out.println("val : " + bytesInResource);
            System.out.println("val : " + imageOffset);
            System.out.println("\n");
            */

            in.mark();

            long curPos = in.getStreamPosition();
            int nBytesToSkip = imageOffset - (int)curPos;
            in.skipBytes(nBytesToSkip);

            imgData = new byte[bytesInResource];
            try
            {
                in.read(imgData);
            }
            finally
            {
                in.reset();
            }

            // Certain icons will not specify the bitCount at the icon entry level.
            // For such cases, read the bitCount from the image data
            if(bitCount == 0 && imageOffset > 0)
                bitCount = readBitCountFromImageData(imgData);

        }
        catch(Exception e)
        {
            System.out.println("Exception reading icon entry");
        }
    }


    /*
     * Image data structure:
            typdef struct
            {
               BITMAPINFOHEADER   icHeader;   // DIB header
               RGBQUAD         icColors[1];   // Color table
               BYTE            icXOR[1];      // DIB bits for XOR mask
               BYTE            icAND[1];      // DIB bits for AND mask
            } ICONIMAGE, *LPICONIMAGE;

            typedef struct tagBITMAPINFOHEADER{
              DWORD  biSize;
              LONG   biWidth;
              LONG   biHeight;
              WORD   biPlanes;
              WORD   biBitCount;
              DWORD  biCompression;
              DWORD  biSizeImage;
              LONG   biXPelsPerMeter;
              LONG   biYPelsPerMeter;
              DWORD  biClrUsed;
              DWORD  biClrImportant;
            } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
    *
    * Read biBitCount
    */
    private int readBitCountFromImageData(byte[] imgData) throws IOException
    {
        ImageInputStream iis = ImageIO.createImageInputStream(new ByteArrayInputStream(imgData));
        iis.setByteOrder(java.nio.ByteOrder.LITTLE_ENDIAN);

        // These many number of bytes can actually be skipped. Reading for code clarity.
        iis.readInt();   // biSize
        iis.readInt();   // biWidth
        iis.readInt();   // biHeight
        iis.readShort(); // biPlanes

        int biBitCount = iis.readShort();

        return biBitCount;
    }


    public short getWidth()
    {
        return width;
    }

    public short getHeight()
    {
        return height;
    }

    public int getBitCount()
    {
        return bitCount;
    }

    public byte[] getImageData()
    {
        return imgData;
    }

}

Проблемная область:
iis = ImageIO.createImageInputStream (new ByteArrayInputStream (entry.getImageData ()));

опубликовать это сообщение Я получаю счетчик битов как большое целое число, хотя фактическое количество битов равно 8. Так что при создании массива такого большого размеравыдает следующее исключение -

"java.lang.OutOfMemoryError: Размер запрашиваемого массива превышает ограничение виртуальной машины"

Файл ico, для которого он не работает, равен https://www.dropbox.com/s/euh52s0vc2s2ryf/Tool.ico?dl=0

1 Ответ

0 голосов
/ 25 мая 2018

Для этого значка данные изображения не соответствуют структуре tagBITMAPINFOHEADER.Вместо этого они представляют собой внедренные изображения PNG, которые вы можете распознать по первому слову, которое не является обычным размером (как при структуре tagBITMAPINFOHEADER), а является волшебным словом изображений PNG.

В этом можно убедиться, изменив начало конструктора IconImage на

public IconImage(IconDirEntry entry)
{
    this.entry = entry;
    try
    {
        final ByteArrayInputStream bais = new ByteArrayInputStream(entry.getImageData());
        bais.mark(4);
        iis = ImageIO.createImageInputStream(bais);
        iis.setByteOrder(java.nio.ByteOrder.LITTLE_ENDIAN);
        biSize = iis.readInt();
        if(biSize == 0x474e5089) { //PNG instead of tagBITMAPINFOHEADER)
            bais.reset();
            BufferedImage bi = ImageIO.read(bais);
            System.out.println("read embedded PNG "+bi.getWidth()+" x "+bi.getHeight());
            return;
        }
…

Волшебное слово …PNG, первый байт 0x89, но порядок изменился, когда вы читали его какзначение int с прямым порядком байтов, поэтому его (('G'<<24)|('N'<<16)|('P'<<8)|0x89).

Я оставляю на ваше усмотрение реструктуризацию вашего кода для обработки обоих случаев с помощью общего интерфейса…

...