【问题标题】:Writing ico files java编写ico文件java
【发布时间】:2013-09-02 12:11:45
【问题描述】:

最近我对在 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);
    }
}

现在,当我尝试在照片库中打开我的图像时,图像看起来像这样在 ico 图像标题中。

【问题讨论】:

  • 除非您绝对需要重新发明轮子,否则请查看image4j
  • 我喜欢在 java 中做的一件事是弄清楚不同文件格式的内部是如何工作的,我也不想处理任何许可证
  • 你也可以阅读 image4j 源代码来和你的比较。与@MadProgrammer 提供的链接相同,并按照它进行下载。源代码在您下载的 zip 文件中。
  • image4j 是免费和开源的。您可以下载源代码,看看作者是如何为您的问题提供一些想法的......
  • 很难找到自己的路

标签: java ico


【解决方案1】:

奇怪……但是:使 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);
    }
}

【讨论】:

  • 但是为什么会这样
  • 老实说,我不知道。我刚刚在用 Gimp 打开损坏的 ICO 后发现它显示的内容 BMP 的高度只有图标的一半。然后我在 Gimp 中修复了它并将其保存回来并检查了正常运行的 ICO 中 BMP 的大小,它翻了一番。一旦你知道在哪里看,就很容易通过反复试验发现其余部分。感觉有点骇人听闻,但这对于 Windows 文件格式来说并不少见。
【解决方案2】:

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

颜色小于 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 位图像 -> 有效

那么,让我们再看看维基百科对

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);

【讨论】:

    【解决方案3】:

    你试过了吗:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 我不知道你是否会收到编辑答案的通知,所以我将在这里添加一个评论,我希望会有同样的效果。
      【解决方案5】:

      请在下面尝试, 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)

      【讨论】:

        猜你喜欢
        • 2023-03-23
        • 2010-09-27
        • 2021-01-04
        • 2013-03-23
        • 2014-11-19
        • 2011-08-21
        • 1970-01-01
        • 2012-08-24
        相关资源
        最近更新 更多