2006年02月02日

HICONとアイコンファイル

本日のお題はいつものように@IT会議室より、FromHandleで生成したIconをSaveしたときというスレッドです。

質問内容をかいつまめば、HICONから作ったIconインスタンスをSaveすると何故か16色で保存される、どうしよう、ってことなんですが。

そもそも何が悪いのか。まずHICONにした時点で色情報が壊れる可能性。これはBitmap.FromHiconメソッドで否定されます。Icon.Saveメソッドも、普通にIconコンストラクタを使えば問題ありません。Bitmapインスタンスに変換するにはBitmap.FromHiconメソッドとIcon.ToBitmapメソッドの両方がありますが、どちらも問題ありません。

何が違うのか。ILDASMで確認してみましょう。

まずコンストラクタはどうなってるんでしょうか。stringを引数にとるコンストラクタでは、おおよそ以下のようにやってるようです。

  1. 文字列からFileStreamインスタンスを作る

  2. FileStreamインスタンスから全データを読み出して格納。

  3. データを解析してCreateIconFromResourceEx関数を呼び出し。

ではHICONからは? Icon.FromHandleメソッドを追っていくと、なんとほぼhandleフィールドを設定しているだけです。そのわりにSizeプロパティとか取得できてるよな、と思ったら初めてSizeプロパティ呼び出すときにGetIconInfo関数でアイコンのHBITMAP取得して、それに対してGetObject関数を呼び出してるんですな。

どうやらなにが原因かははっきりしました。つまりファイルから作ったIconインスタンスには読み込んだデータがそのまま置かれてあり、HICONから作ったIconインスタンスにはそれが無いということです。確認のためにIcon.Saveメソッドを見れば、このデータがある場合はそれをファイルに書き込んで終了しています。では無い場合は? OleCreatePictureIndirect関数なるものを使用して、HICONからCOMインターフェイスIPictureを作成してそのSaveAsFileメソッドを呼び出すようにしているようです。これではこれ以上の解析は難しそうですね。

では最後の手段、HICONには画像データも含まれるんですからそれを頂きましょう。アイコンファイルの形式の解析も必要になるでしょうが、それはとりあえず後回し。

さて、HICONから画像データはどう取得するか。上のIcon.Sizeプロパティを取得するところで軽く触れてますが、ちょうどGetIconInfo関数でHICONから画像用とマスク用の二つのHBITMAPが手に入ります。HBITMAPもハンドルですから直接的な(目に見える形の)画像データではありません。このHBITMAPからファイルに保存するような形式のデータを取得するのがGetDIBits関数です。

で、試したところ。

……HICONの画像データはパレットを保持しないっぽい……。

マスク画像は普通に1bppIndexedのデータが取れるんです。GetDIBColorTable関数を呼んでも2つのRGBQUADが手に入る。がしかし、画像データのほうはどんなアイコンを食わせても全く無視して32bppRgbなデータしか返してくれないんです。これはもうHICONを作成する時点でカラーパレットを展開してベタピクセルに置き換えてるとしか考えられません。確かにアイコンなんてでかくても64x64程度ですからメモリの負担も大したものではなく、パレット使うよりもベタピクセルに展開しておいたほうが速度も稼げるんでしょうけど。

ということで、そもそも元データを保持していないのでHICONから元のアイコンファイルを再現するのは不可能、という話でした。各ピクセルの色を解析して自前でパレットを再構成、なんてことも可能でしょうが、元通りってわけではありませんしね。面倒だし。冒頭のスレのスレ主さんのように素直に32bppRgbのデータをアイコンのデータとして保存するというのも手段の一つです。それでも一応問題なく扱えるアイコンファイルになりますし。

ちなみに、IrfanViewで作成した32bppRgbなアイコンは、.NETでは問題なく読めますが(つまりCreateIconFromResourceEx関数でも問題ないということになるでしょう)、XPでデフォルトのアイコンビューアになっている Windows 画像と FAX ビューア では何故か開くことができません。どうなってるんでしょうね。

以下はコメントも書いてないテストコードです。コメントは書きませんが清書はします。

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
public class IconTest {
  [DllImport("gdi32.dll", SetLastError=true)]
  private static extern int GetDIBColorTable(
    IntPtr dc, int index, int entries,
    [In, Out] RgbQuad[] colors);
  [DllImport("gdi32.dll", SetLastError=true)]
  private static extern int GetDIBits(
    IntPtr dc, IntPtr bmp, int startScan, int scanLineCount,
    [In, Out] byte[] data, IntPtr info, ColorTableType usage);
  [DllImport("gdi32.dll", SetLastError=true)]
  private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
  [DllImport("user32.dll", SetLastError=true)]
  private static extern bool GetIconInfo(
    IntPtr icon, out IconInfo info);
  [DllImport("user32.dll", SetLastError=true)]
  private static extern IntPtr GetDC(IntPtr window);
  [DllImport("user32.dll", SetLastError=true)]
  private static extern bool ReleaseDC(IntPtr window, IntPtr dc);
  [DllImport("gdi32.dll", SetLastError=true)]
  private static extern IntPtr SelectObject(
    IntPtr hdc, IntPtr obj);
  [DllImport("gdi32.dll", SetLastError=true)]
  private static extern bool DeleteObject(IntPtr handle);
  [DllImport("gdi32.dll", SetLastError=true)]
  private static extern bool DeleteDC(IntPtr hdc);

  private enum ColorTableType {
    Rgb     = 0,
    Palette = 1,
  }
  private struct IconInfo {
    public bool IsIcon;
    public int HotSpotX;
    public int HotSoptY;
    public IntPtr MaskHbitmap;
    public IntPtr ColorHbitmap;
  }
  private struct RgbQuad {
    public byte Blue;
    public byte Green;
    public byte Red;
    public byte Reserved;
  }
  private struct BitmapInfoHeader {
    public int Size;
    public int Width;
    public int Height;
    public short Planes;
    public short BitCount;
    public int Compression;
    public int SizeImage;
    public int XPelsPerMeter;
    public int YPelsPerMeter;
    public int ColorTableUsed;
    public int ColorImportant;
  }
  private class BitmapInfo {
    public BitmapInfoHeader Header;
    public RgbQuad[] ColorTable;
    public byte[] ImageData;
    public override string ToString() {
      string header
        = string.Format(
            "Size:({0},{1}) BitCount:{2} SizeImage:{3}",
            this.Header.Width, this.Header.Height,
            this.Header.BitCount, this.Header.SizeImage);
      StringBuilder table = new StringBuilder();
      foreach (RgbQuad color in this.ColorTable) {
        table.AppendFormat("Color [{0},{1},{2}], ",
                           color.Red, color.Green, color.Blue);
      }
      string data = BitConverter.ToString(this.ImageData);
      return string.Format(
          "Header:[{0}]\r\nColorTable:[{1}]\r\nImageData:[{2}]",
          header, table, data.Replace("-", " "));
    }
  }
  static void Main() {
    GetIconImage(new Icon("icooo4bpp16x16.ico").Handle);
  }
  private static void GetIconImage(IntPtr iconHandle) {
    IconInfo iconInfo;
    GetIconInfo(iconHandle, out iconInfo);
    IntPtr maskbmp = iconInfo.MaskHbitmap;
    IntPtr colorbmp = iconInfo.ColorHbitmap;
    BitmapInfo info;
    info = GetDIBits(iconInfo.MaskHbitmap);
    Console.WriteLine(info);
    Console.WriteLine(GetDIBits(iconInfo.ColorHbitmap));
    DeleteObject(iconInfo.MaskHbitmap);
    DeleteObject(iconInfo.ColorHbitmap);
  }
  private static BitmapInfo GetDIBits(IntPtr hBmp) {
    IntPtr wdc = GetDC(IntPtr.Zero);
    IntPtr dc = CreateCompatibleDC(wdc);
    IntPtr old = SelectObject(dc, hBmp);
    BitmapInfo bmpInfo = new BitmapInfo();
    int headerSize = Marshal.SizeOf(typeof(BitmapInfoHeader));
    IntPtr info = Marshal.AllocCoTaskMem(headerSize + 4 * 256);
    try {
      unsafe {
        BitmapInfoHeader* header
            = (BitmapInfoHeader*)info.ToPointer();
        header->Size = headerSize;
        header->BitCount = 0;
        int result = GetDIBits(dc, hBmp, 0, 0, null,
                               info, ColorTableType.Rgb);
        int colorCount = header->ColorTableUsed;
        bmpInfo.ColorTable = new RgbQuad[colorCount];
        if (colorCount > 0) {
          GetDIBColorTable(dc, 0, colorCount,
                           bmpInfo.ColorTable);
        }
        if (result != 0) {
          byte[] data = new byte[header->SizeImage];
          GetDIBits(dc, hBmp, 0, header->Height, data,
                    info, ColorTableType.Rgb);
          bmpInfo.ImageData = data;
        }
        bmpInfo.Header = *header;
        return bmpInfo;
      }
    }
    finally {
      Marshal.FreeCoTaskMem(info);
      SelectObject(dc, old);
      DeleteDC(dc);
      ReleaseDC(IntPtr.Zero, wdc);
    }
  }
}
posted by Hongliang at 07:37| Comment(0) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。