最近,我对在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图像标题中。
答案 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位而不是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);
ImageIO
无法处理32位图像,但还有其他库可以解决这个问题。 image4J
库可以保存32位bmp文件。但我的猜测是,由于某种原因你不想使用这个库。 (使用image4J
会使您的大多数代码无意义,因为image4j
具有内置的ICO创建支持。)
那么,让我们再看一下维基百科所说的< 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
?
如果还有其他问题,大多数相关代码似乎都在这两种方法中。