10

最近我对在 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 打开图像时,图像打开得很好。我究竟做错了什么。我觉得我在文件头中弄乱了一些东西。编辑:即使是桌面上的陌生人,图片看起来也不错,只是当我尝试打开它时却没有。

在我的桌面上,图像看起来像这样 在此处输入图像描述

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

在此处输入图像描述

在尝试 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);
    }
}

现在,当我尝试在照片库中打开我的图像时,图像看起来像这样图片标题。 在此处输入图像描述

4

5 回答 5

5

奇怪……但是:使 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);
    }
}
于 2013-09-04T16:28:14.820 回答
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);
于 2013-09-07T13:39:20.410 回答
0

你有没有尝试过:

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

如中所示image4j.BMPEncoder#createInfoHeader,由image4j.ICOEncoder#write?调用

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

于 2013-09-01T06:43:58.687 回答
0

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

如果您在将值更改为 32 后得到您编辑的那张小图片,那么我要说的是,再想一想,它实际上可能是 16。

于 2013-09-01T01:49:54.947 回答
0

请在下面尝试,ImageIo 将仅支持 png、jpg、jpeg、gif、bmp 格式。

要编写图标,请添加以下依赖项。

<!-- https://mvnrepository.com/artifact/net.sf.image4j/image4j -->
<dependency>
    <groupId>net.sf.image4j</groupId>
    <artifactId>image4j</artifactId>
    <version>0.7zensight1</version>
</dependency>

使用ICODecoder.write(image, file)

于 2021-12-01T18:01:44.460 回答