编写ico文件java

时间:2013-08-29 21:44:15

标签: java ico

最近,我对在java中创建.ico文件或Windows图标文件感兴趣。这是我使用的当前代码。我从这里获得了http://en.wikipedia.org/wiki/ICO_%28file_format%29

的文件格式规范
    BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
    Graphics g = img.getGraphics();
    g.setColor(Color.GREEN);
    g.fillRect(0, 0, 16, 16);
    byte[] imgBytes = getImgBytes(img);
    int fileSize = imgBytes.length + 22;
    ByteBuffer bytes = ByteBuffer.allocate(fileSize);
    bytes.order(ByteOrder.LITTLE_ENDIAN);
    bytes.putShort((short) 0);//Reserved must be 0
    bytes.putShort((short) 1);//Image type
    bytes.putShort((short) 1);//Number of image in file
    bytes.put((byte) img.getWidth());//image width
    bytes.put((byte) img.getHeight());//image height
    bytes.put((byte) 0);//number of colors in color palette
    bytes.put((byte) 0);//reserved must be 0
    bytes.putShort((short) 0);//color planes
    bytes.putShort((short) 0);//bits per pixel
    bytes.putInt(imgBytes.length);//image size
    bytes.putInt(22);//image offset
    bytes.put(imgBytes);
    byte[] result = bytes.array();
    FileOutputStream fos = new FileOutputStream("C://Users//Owner//Desktop//picture.ico");
    fos.write(result);
    fos.close();
    fos.flush();

private static byte[] getImgBytes(BufferedImage img) throws IOException
{
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ImageIO.write(img, "png", bos);
    return bos.toByteArray();
}

问题是Windows似乎无法打开图像,当我尝试使用Windows照片库打开图像时出错。但是,当我尝试使用gimp打开图像时,图像打开正常。我究竟做错了什么。我觉得我在搞乱文件标题中的内容。编辑:桌面上的图片看起来是正确的,甚至在我尝试打开时也不是很奇怪。

在我的桌面上,图像看起来像这样 enter image description here

当我尝试在Windows照片库中打开它时会显示此错误

enter image description here

在png尝试失败后,我尝试使用位图图像,这是我的新代码

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import javax.imageio.ImageIO;

public class IconWriter
{
    public static void main(String[] args) throws HeadlessException, AWTException, IOException
    {
        BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        g.setColor(Color.GREEN);
        g.fillRect(0, 0, 16, 16);
        byte[] imgBytes = getImgBytes(img);
        int fileSize = imgBytes.length + 22;
        ByteBuffer bytes = ByteBuffer.allocate(fileSize);
        bytes.order(ByteOrder.LITTLE_ENDIAN);
        bytes.putShort((short) 0);//Reserved must be 0
        bytes.putShort((short) 1);//Image type
        bytes.putShort((short) 1);//Number of images in file
        bytes.put((byte) img.getWidth());//image width
        bytes.put((byte) img.getHeight());//image height
        bytes.put((byte) 0);//number of colors in color palette
        bytes.put((byte) 0);//reserved must be 0
        bytes.putShort((short) 0);//color planes
        bytes.putShort((short) 0);//bits per pixel
        bytes.putInt(imgBytes.length);//image size
        bytes.putInt(22);//image offset
        bytes.put(imgBytes);
        byte[] result = bytes.array();
        FileOutputStream fos = new FileOutputStream("C://Users//Owner//Desktop//hi.ico");
        fos.write(result);
        fos.close();
        fos.flush();
    }

    private static byte[] getImgBytes(BufferedImage img) throws IOException
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(img, "bmp", bos);
        byte[] bytes = bos.toByteArray();
        return Arrays.copyOfRange(bytes, 14, bytes.length);
    }
}

现在,当我尝试在照片库中打开我的图像时,图像看起来像这样我不知道为什么它现在不起作用,特别是为什么奇怪的线条出现,虽然我怀疑它必须与颜色飞机属性在ico图像标题中。 enter image description here

4 个答案:

答案 0 :(得分:4)

奇怪......但是:将BMP图片设置为所需图标的两倍。像以前一样将声明的图标大小保留在ICO标题中,只有图片应该更高。然后保持区域(0,0) - (16,16)黑色(它定义透明度,但我不知道它是如何编码的,所有黑色的不透明作品)。在区域(0,16) - (16,32)的BufferedImage中绘制所需内容。换句话说,将高度的一半添加到所有像素坐标。

请注意,Windows桌面可能会缓存图标并拒绝在桌面上更新它们。如果有疑问,请通过另一个资源管理器窗口打开桌面文件夹,然后在那里执行“更新”。

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

import javax.imageio.ImageIO;

public class IconWriter
{
    public static void main(String[] args) throws IOException
    {
      // note the double height
        BufferedImage img = new BufferedImage(16, 32, BufferedImage.TYPE_INT_RGB);
        Graphics g = img.getGraphics();
        g.setColor(Color.GREEN);
        g.fillRect(0, 16, 16, 16);// added 16 to y coordinate
        byte[] imgBytes = getImgBytes(img);
        int fileSize = imgBytes.length + 22;
        ByteBuffer bytes = ByteBuffer.allocate(fileSize);
        bytes.order(ByteOrder.LITTLE_ENDIAN);
        bytes.putShort((short) 0);//Reserved must be 0
        bytes.putShort((short) 1);//Image type
        bytes.putShort((short) 1);//Number of images in file
        bytes.put((byte) img.getWidth());//image width
        bytes.put((byte) (img.getHeight()>>1));//image height, half the BMP height
        bytes.put((byte) 0);//number of colors in color palette
        bytes.put((byte) 0);//reserved must be 0
        bytes.putShort((short) 0);//color planes
        bytes.putShort((short) 0);//bits per pixel
        bytes.putInt(imgBytes.length);//image size
        bytes.putInt(22);//image offset
        bytes.put(imgBytes);
        byte[] result = bytes.array();
        FileOutputStream fos = new FileOutputStream(System.getProperty("user.home")+"\\Desktop\\hi.ico");
        fos.write(result);
        fos.close();
        fos.flush();
    }

    private static byte[] getImgBytes(BufferedImage img) throws IOException
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(img, "bmp", bos);
        byte[] bytes = bos.toByteArray();
        return Arrays.copyOfRange(bytes, 14, bytes.length);
    }
}

答案 1 :(得分:3)

实际上,规范中提到了你遇到的问题(维基百科)。 引用:

  

小于32位颜色深度[6]的图像遵循一个特定的   格式:图像被编码为由颜色组成的单个图像   掩码(“XOR掩码”)和不透明掩码(“AND掩码”)。

这很复杂。

创建32位图像 - >失败

所以,上面的引用可能会让你想到:“哦,我只需要将图像设为32位而不是24位”,作为一种解决方法。不幸的是,这不会奏效。嗯,实际上存在32位BMP格式。但最后8位并没有真正使用,因为BMP文件并不真正支持透明度。

因此,您可能会想要使用不同的图像类型:INT_ARGB_PRE使用32位颜色深度。但是,只要您尝试使用ImageIO课程进行保存,您就会注意到没有任何反应。流的内容将为null

BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB_PRE);
ImageIO.write(img, "bmp", bos);

替代解决方案:image4j

ImageIO无法处理32位图像,但还有其他库可以解决这个问题。 image4J库可以保存32位bmp文件。但我的猜测是,由于某种原因你不想使用这个库。 (使用image4J会使您的大多数代码无意义,因为image4j具有内置的ICO创建支持。)

第二选项:创建移位的24位图像 - >作品

那么,让我们再看一下维基百科所说的< 32位BMP数据。

  

ICO / CUR的ICONDIRENTRY结构中图像的高度   文件采用预期图像尺寸的(在蒙版之后)   是合成的,而BMP标题中的高度采用了它   组合的两个掩模图像(在它们被合成之前)。   因此,面具必须各自具有相同的尺寸,   并且 BMP标题中指定的高度必须恰好是两倍   ICONDIRENTRY结构中指定的高度

因此,第二种解决方案是创建一个原始大小两倍的图像。实际上,你只需要用下面的那个替换你的getImageBytes函数。如上所述,代码其他部分中指定的ICONDIRENTRY标题会保留原始图像高度。

  private static byte[] getImgBytes(BufferedImage img) throws IOException
  {
    // create a new image, with 2x the original height.
    BufferedImage img2 = new BufferedImage(img.getWidth(), img.getHeight()*2, BufferedImage.TYPE_INT_RGB);

    // copy paste the pixels, but move them half the height.
    Raster sourceRaster = img.getRaster();
    WritableRaster destinationRaster = img2.getRaster();
    destinationRaster.setRect(0, img.getHeight(), sourceRaster);

    // save the new image to BMP format. 
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ImageIO.write(img2, "bmp", bos);

    // strip the first 14 bytes (contains the bitmap-file-header)
    // the next 40 bytes contains the DIB header which we still need.
    // the pixel data follows until the end of the file.
    byte[] bytes = bos.toByteArray();
    return Arrays.copyOfRange(bytes, 14, bytes.length);
  }

我建议使用标题如下:

ByteBuffer bytes = ByteBuffer.allocate(fileSize);
bytes.order(ByteOrder.LITTLE_ENDIAN);

bytes.putShort((short) 0);
bytes.putShort((short) 1);
bytes.putShort((short) 1);
bytes.put((byte) img.getWidth());
bytes.put((byte) img.getHeight()); //no need to multiply
bytes.put((byte) img.getColorModel().getNumColorComponents()); //the pallet size
bytes.put((byte) 0);
bytes.putShort((short) 1); //should be 1
bytes.putShort((short) img.getColorModel().getPixelSize()); //bits per pixel
bytes.putInt(imgBytes.length);
bytes.putInt(22);
bytes.put(imgBytes);

答案 2 :(得分:0)

我认为应该将bytes.putShort((short) 0);//bits per pixel更改为值32而不是0.

如果您在将值更改为32之后获得了您编辑的那张小图片,那么我会说,第二个想法,它实际上可能是16。

答案 3 :(得分:0)

你试过了吗?

bytes.putShort((short) img.getColorModel().getPixelSize()); //bits per pixel

image4j.BMPEncoder#createInfoHeader中看到的image4j.ICOEncoder#write

如果还有其他问题,大多数相关代码似乎都在这两种方法中。