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

フォト ギャラリーで自分の画像を開こうとすると、画像が次のように表示されます。なぜ機能しないのか、特に奇妙な線が表示される理由はわかりませんが、ico の色平面属性に関係していると思われます画像ヘッダー。 ここに画像の説明を入力

4

5 に答える 5

3

実際、あなたが抱えている問題は仕様 (ウィキペディア) に記載されています。見積もり:

色深度が 32 ビット未満の画像[6] は特定の形式に従います。画像は、カラー マスク (「XOR マスク」) と不透明度マスク (「AND マスク」) で構成される単一の画像としてエンコードされます。

それは非常に複雑です。

32 ビット イメージの作成 -> 失敗

したがって、上記の引用は、回避策として、「ああ、イメージを 24 ビットではなく 32 ビットにする必要がある」と思わせるかもしれません。残念ながら、それはうまくいきません。実は、32 ビットの BMP フォーマットが存在します。しかし、BMP ファイルは実際には透明度をサポートしていないため、最後の 8 ビットは実際には使用されません。

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 ファイルを保存できます。しかし、何らかの理由でこのライブラリを使用したくないのではないかと思います。(組み込みのICO作成サポートがimage4Jあるため、使用すると上記のコードのほとんどが無意味になります)。image4j

2 番目のオプション: シフトされた 24 ビット イメージを作成する -> 機能する

それでは、ウィキペディアが 32 ビット未満の BMP データについて述べていることをもう一度見てみましょう。

ICO/CUR ファイルの ICONDIRENTRY 構造内の画像の高さは、 (マスクが合成された後) 意図した画像の寸法の高さとなりますが、BMP ヘッダーの高さは、2 つのマスク画像が結合される前の高さになります。合成されます)。したがって、マスクはそれぞれ同じ寸法である必要があり、BMP ヘッダーで指定された高さは ICONDIRENTRY 構造で指定された高さのちょうど 2 倍でなければなりません

したがって、2 番目の解決策は、元のサイズの 2 倍のイメージを作成することです。そして実際には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) 0);//bits per pixel値を 0 ではなく 32 に変更する必要があると思います。

値を 32 に変更した後に編集した小さな画像を取得している場合は、よく考えてみると、おそらく実際には 16 であると言えます。

于 2013-09-01T01:49:54.947 に答える