Запись файлов ico java
Недавно я заинтересовался созданием файлов .ico или файлов значков Windows в java. Это текущий код, который я использую. Я получил спецификации формата файла здесь 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 Photo Gallery. Однако, когда я пытаюсь открыть изображение с помощью gimp, изображение открывается отлично. Что я делаю не так. Я чувствую, что я что-то испортил в заголовке файла. Редактировать: Даже незнакомец на рабочем столе изображение выглядит правильно, просто не тогда, когда я пытаюсь его открыть.
На моем рабочем столе изображение выглядит так:
![enter image description here]()
Когда я пытаюсь открыть его в Windows Photo Gallery, он отображает эту ошибку
![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);
}
}
теперь, когда я пытаюсь открыть свое изображение в фотогалерее, изображение выглядит так, я не знаю, почему он сейчас не работает и особенно почему появляются странные строки, хотя я подозреваю, что это связано с атрибутом color planes в заголовке изображения ico.
![enter image description here]()
Ответы
Ответ 1
Собственно, проблема, о которой вы говорите, упоминается в спецификациях (в Википедии).
Цитата:
Изображения с глубиной менее 32 бит цвета [6] следуют конкретным format: изображение кодируется как одно изображение, состоящее из цвета mask ( "XOR mask" ) вместе с маской непрозрачности ( "И маска" ).
Это очень сложно.
Создание 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
libs может сохранять 32-битные BMP файлы. Но я предполагаю, что по какой-то причине вы не хотите использовать эту библиотеку. (Использование image4J
сделает большую часть вашего кода выше бессмысленным, потому что image4J
имеет встроенную поддержку создания ICO).
Вторая опция: создание сдвинутого 24-битного изображения → works
Итак, позвольте еще раз взглянуть на то, что википедия говорит о < 32-битные данные BMP.
Высота изображения в структуре ICONDIRENTRY ICO/CUR файл принимает размеры предполагаемых размеров изображения (после масок составлены), тогда как высота в заголовке 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
Странно... но: сделайте изображение BMP в два раза выше, чем нужный значок. Сохраняйте объявленный размер значка в заголовке ICO, как и раньше, только изображение должно быть выше. Затем сохраните область (0,0) - (16,16) черную (ее определение прозрачности, но я не знаю, как она закодирована, все черные для непрозрачных работ). Нарисуйте нужное содержимое в BufferedImage в области (0,16) - (16,32). Другими словами, добавьте половину высоты ко всем пиксельным координатам.
Помните, что Windows Desktop может кэшировать значки и отказываться обновлять их на рабочем столе. Если у вас есть сомнения, откройте папку рабочего стола через другое окно Explorer и выполните там "Обновить".
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);
}
}
Ответ 3
Я считаю, что bytes.putShort((short) 0);//bits per pixel
следует изменить, чтобы иметь значение 32 вместо 0.
Если вы получаете эту маленькую картинку, которую вы отредактировали, изменив значение до 32, я скажу, что, по-моему, это, вероятно, на самом деле 16.
Ответ 4
Вы пробовали:
bytes.putShort((short) img.getColorModel().getPixelSize()); //bits per pixel
как показано в image4j.BMPEncoder#createInfoHeader
, который вызывается image4j.ICOEncoder#write
?
Если есть другие проблемы, большинство соответствующих кода для вас, похоже, находятся в этих двух методах.