Bitmap: Dで手軽にビットマップを扱うクラス

こちらは未完成です。
バグを見つけた方はご指摘下さると嬉しいです。


リソースやファイルからビットマップを読み込んで、
DIBSectionを作り出します。
バイスコンテキストを持っているので、
GDIで描画できます。
ファイル書き出し機能もおまけで付けました。

module bitmap;

//------------------------------------------------------------------------------------------//

import win32.windows;
import std.c.string;
import std.stream;
import std.utf;

//------------------------------------------------------------------------------------------//

pragma(lib, "gdi32.lib");

static if (WINVER < 0x0500)
{
    const DWORD
        NOMIRRORBITMAP = 0x80000000,
        CAPTUREBLT     = 0x40000000;
}

//------------------------------------------------------------------------------------------//

const uint RASTER = SRCCOPY | CAPTUREBLT;

//------------------------------------------------------------------------------------------//

class BitmapException : Exception
{
    this(string message)
    {
        super(message);
    }
}

//------------------------------------------------------------------------------------------//

interface Image
{
// Methods
public:
    void Create(int w, int h, ushort bitCount=32, uint clrUsed=0);
    void Create(BITMAPINFO* bi);
    void Create(Image image);
    void Load(ushort rsrcID);
    void Load(string filename);
    void Save(string filename);
    void Dispose();
    void Draw(HDC hDC, int left=0, int top=0, uint dwRaster=RASTER);
    void Draw(HDC hDC, int left=0, int top=0, int width=0, int height=0,
              int sx=0, int sy=0, int sw=0, int sh=0, uint dwRaster=RASTER);
    
// Properties
public:
    int         Width();
    int         Height();
    ushort      BitCount();
    uint        ColorUsed();
    uint        Size();
    HDC         hDC();
    BITMAPINFO* BmpInfo();
    HANDLE      pBits();
}

//------------------------------------------------------------------------------------------//

class Bitmap : Image
{
// Attributes
protected:
    int         m_width      = 0;
    int         m_height     = 0;
    ushort      m_bitCount   = 0;
    uint        m_clrUsed    = 0;
    int         m_lineLength = 0;
    HDC         m_hDC        = null;
    HBITMAP     m_hBitmap    = null;
    HPALETTE    m_hPalette   = null;    
    BITMAPINFO* m_lpBI       = null;
    HANDLE      m_pbits      = null;
    
// Methods
public:
    // コンストラクタ
    this()
    {
    }
    
    this(int width, int height, ushort bitCount=32, uint clrUsed=0)
    {
        Create(width, height, bitCount, clrUsed);
    }
    
    this(BITMAPINFO* bi)
    {
        Create(bi);
    }
    
    this(Image image)
    {
        Create(image);
    }
    
    this(ushort rsrcID)
    {
        Load(rsrcID);
    }
    
    this(string filename)
    {
        Load(filename);
    }
    
    // デストラクタ
    ~this()
    {
        Dispose();
    }
    
    // ビットマップを解放する
    void Dispose()
    {
        m_width      = 0;
        m_height     = 0;
        m_bitCount   = 0;
        m_clrUsed    = 0;
        m_lineLength = 0;
        DeleteDC(m_hDC);          m_hDC      = null; /// DCの解放がオブジェクトの解放より先らしい
        DeleteObject(m_hBitmap);  m_hBitmap  = null; /// m_pbitsはここで解放される
        DeleteObject(m_hPalette); m_hPalette = null;
        m_lpBI       = null;
        m_pbits      = null;
    }
    
    // ビットマップを描画する
    void Draw(HDC hDC, int left = 0, int top = 0, uint dwRaster = RASTER)
    {
        Draw(hDC, left, top, 0, 0, 0, 0, 0, 0, dwRaster);
    }
    
    // ビットマップを描画する
    void Draw(HDC hDC, int left = 0, int top = 0, int width = 0, int height = 0,
               int sx = 0, int sy = 0, int sw = 0, int sh = 0, uint dwRaster = RASTER)
    {
        // パレットの選択
        HPALETTE hOldPalette = null;
        if (m_hPalette)
        {
            hOldPalette = SelectPalette(hDC, m_hPalette, false);
            if (hOldPalette is null)
            {
                throw new BitmapException(r"パレット選択エラー");
            }
            
            RealizePalette(hDC);
        }
        
        if (width == 0 || height == 0)
        {
            // 描画
            BitBlt(hDC, left, top, m_width, m_height, m_hDC, 0, 0, dwRaster);
        }
        else
        {
            // 描画元画像サイズのデフォルトを指定
            if (sw == 0) sw = m_width;
            if (sh == 0) sh = m_height;
            
            // ビットマップ伸縮モードの設定
            auto oldSthMode = SetStretchBltMode(hDC, COLORONCOLOR); /// 縮小・拡大ともこちらのモードが最適
            if (oldSthMode == 0)
            {
                throw new BitmapException(r"ビットマップ伸縮モード設定エラー");
            }
            
            // 描画
            StretchDIBits(hDC, left, top, width, height, sx, sy, sw, sh,
                          m_pbits, m_lpBI, DIB_RGB_COLORS, dwRaster);
            
            // ビットマップ伸縮モードの設定解除
            SetStretchBltMode(hDC, oldSthMode);
        }
        
        // パレットの選択解除
        if (hOldPalette)
        {
            SelectPalette(hDC, hOldPalette, false);
        }
    }
    
    // 空のビットマップを作成する
    void Create(int width=1, int height=1, ushort bitCount=32, uint clrUsed=0)
    {
        try
        {
            Dispose();
            
            // データをメンバ変数に記憶
            /// ここで m_width, m_height, m_bitCount, m_clrUsed, m_lineLength が初期化される
            StoreData(width, height, bitCount, clrUsed);
            
            // ヘッダ情報の作成
            size_t bmpInfoSize = BITMAPINFOHEADER.sizeof + RGBQUAD.sizeof * m_clrUsed;
            m_lpBI = cast(BITMAPINFO*)new ubyte[bmpInfoSize];
            
            m_lpBI.bmiHeader.biSize          = BITMAPINFOHEADER.sizeof;
            m_lpBI.bmiHeader.biWidth         = m_width;
            m_lpBI.bmiHeader.biHeight        = m_height;
            m_lpBI.bmiHeader.biPlanes        = 1;
            m_lpBI.bmiHeader.biBitCount      = m_bitCount;
            m_lpBI.bmiHeader.biCompression   = BI_RGB;
            m_lpBI.bmiHeader.biSizeImage     = this.Size;
            m_lpBI.bmiHeader.biXPelsPerMeter = 0;
            m_lpBI.bmiHeader.biYPelsPerMeter = 0;
            m_lpBI.bmiHeader.biClrUsed       = m_clrUsed;
            m_lpBI.bmiHeader.biClrImportant  = 0;
            
            // 対応している形式かどうか
            Check();
            
            // パレットデータの作成
            CreatePalette();
            
            // 互換DCの作成
            CreateCompatibleDC();
            
            // DIBSectionの生成
            CreateDIBSection();
            
        }
        catch (Object o)
        {
            Dispose();
            throw o;
        }
    }
    
    // ヘッダ情報からビットマップを作成する
    void Create(BITMAPINFO* bmi)
    {
        try
        {
            // 空のビットマップを作成
            /// ここで m_lpBI が初期化される
            Create(bmi.bmiHeader.biWidth, bmi.bmiHeader.biHeight,
                        bmi.bmiHeader.biBitCount, bmi.bmiHeader.biClrUsed);
            
            // ヘッダ情報をコピー
            m_lpBI.bmiHeader.biXPelsPerMeter = bmi.bmiHeader.biXPelsPerMeter;
            m_lpBI.bmiHeader.biYPelsPerMeter = bmi.bmiHeader.biYPelsPerMeter;
            m_lpBI.bmiHeader.biClrImportant  = bmi.bmiHeader.biClrImportant;
            
            // パレットデータをコピー
            memcpy(m_lpBI + BITMAPINFOHEADER.sizeof, bmi + bmi.bmiHeader.biSize, RGBQUAD.sizeof * m_clrUsed);
            
        }
        catch (Object o)
        {
            Dispose();
            throw o;
        }
    }
    
    // イメージオブジェクトからビットマップを作成する
    void Create(Image image)
    {
        try
        {
            Dispose();
            
            // ヘッダ情報からビットマップを作成
            Create(image.BmpInfo);
            
            // ピクセルデータのコピー
            image.Draw(this.hDC);
            
        }
        catch (Object o)
        {
            Dispose();
            throw o;
        }
    }
    
    // リソースから読み込む
    void Load(ushort rsrcID)
    {
        try
        {
            Dispose();
            
            // リソースの読み込み
            auto hRsrc = FindResource(GetModuleHandle(null), MAKEINTRESOURCE(rsrcID), RT_BITMAP);
            if(hRsrc is null)
            {
                throw new BitmapException(r"リソースが見つかりませんでした");
            }
            auto hBmp = LoadResource(GetModuleHandle(null), hRsrc);
            if(hBmp is null)
            {
                throw new BitmapException(r"リソースの読み込みに失敗しました");
            }
            
            // ヘッダ情報およびパレット情報の読み込み
            /// 仮にヘッダのバージョンがV4形式やV5形式でもこれで対応できます。
            m_lpBI = cast(BITMAPINFO*)LockResource(hBmp);
            if(m_lpBI is null)
            {
                throw new BitmapException(r"ヘッダ情報の読み込みに失敗しました");
            }
            
            // ヘッダ情報のうちよく使うデータをメンバ変数に記憶
            StoreData(m_lpBI.bmiHeader.biWidth, m_lpBI.bmiHeader.biHeight,
                        m_lpBI.bmiHeader.biBitCount, m_lpBI.bmiHeader.biClrUsed);
            
            // 対応している形式かどうか
            Check();
            
            // パレットデータの作成
            CreatePalette();
            
            // 互換DCの作成
            CreateCompatibleDC();
            
            // DIBSectionの生成
            CreateDIBSection();
            
            // ピクセルデータの読み込み
            void* source = m_lpBI + m_lpBI.bmiHeader.biSize + RGBQUAD.sizeof * m_clrUsed;
            memcpy(m_pbits, source, this.Size);
        }
        catch (Object o)
        {
            Dispose();
            throw o;
        }
        finally
        {
            /// リソースやリソースロックの解放はしなくてよいらしい
            /// http://www.microsoft.com/japan/msdn/library/ja/jpwinui/html/Toppage_Resource.asp
        }
    }
    
    // ファイルから読み込む
    void Load(string filename)
    {
        File file;
        try
        {
            Dispose();
            
            file = new File(filename, FileMode.In);
            
            // ファイルヘッダの読み込み
            BITMAPFILEHEADER bmpfh;
            file.readExact(&bmpfh, bmpfh.sizeof);
            if (bmpfh.bfType != 0x4D42)
            {
                throw new BitmapException(r"ビットマップちゃうやん");
            }
            
            // ヘッダ情報およびパレット情報の読み込み
            /// 仮にヘッダのバージョンがV4形式やV5形式でもこれで対応できます。
            size_t size = bmpfh.bfOffBits - bmpfh.sizeof;
            m_lpBI = cast(BITMAPINFO*)new ubyte[size];
            file.readExact(m_lpBI, size);
            if(m_lpBI is null)
            {
                throw new BitmapException(r"ヘッダ情報の読み込みに失敗しました");
            }
            
            // ヘッダ情報のうちよく使うデータをメンバ変数に記憶
            StoreData(m_lpBI.bmiHeader.biWidth, m_lpBI.bmiHeader.biHeight,
                        m_lpBI.bmiHeader.biBitCount, m_lpBI.bmiHeader.biClrUsed);
            
            // 対応している形式かどうか
            Check();
            
            // パレットデータの作成
            CreatePalette();
            
            // 互換DCの作成
            CreateCompatibleDC();
            
            // DIBSectionの生成
            CreateDIBSection();
            
            // ピクセルデータの読み込み
            file.readExact(m_pbits, this.Size);
        }
        catch (Object o)
        {
            Dispose();
            throw o;
        }
        finally
        {
            if ( file )    file.close();
        }
    }
    
    // ファイルに書き出す
    void Save(string filename)
    {
        File file;
        try
        {
            file = new std.stream.File(filename, FileMode.OutNew);
            
            // ファイルヘッダの書き出し
            BITMAPFILEHEADER bmpfh;
            bmpfh.bfType      = 0x4d42; /// `BM`
            bmpfh.bfOffBits   = bmpfh.sizeof + m_lpBI.bmiHeader.biSize + RGBQUAD.sizeof * m_clrUsed;
            bmpfh.bfReserved1 = 0;
            bmpfh.bfReserved2 = 0;
            bmpfh.bfSize      = bmpfh.bfOffBits + m_lpBI.bmiHeader.biSizeImage;
            file.writeExact(&bmpfh, bmpfh.sizeof);
            
            // ヘッダ情報およびパレット情報の書き出し
            file.writeExact(m_lpBI, m_lpBI.bmiHeader.biSize + RGBQUAD.sizeof * m_clrUsed);
            
            // ピクセルデータの書き出し
            file.writeExact(m_pbits, this.Size);
        }
        catch (Object o)
        {
            throw o;
        }
        finally
        {
            if ( file ) file.close();
        }
    }
    
protected:
    // データをメンバ変数に記憶
    void StoreData(int width, int height, ushort bitCount, uint clrUsed)
    {
        m_width      = width;
        m_height     = height;
        m_bitCount   = bitCount; /// 1, 4, 8, (16,) 24, 32, ...
        m_clrUsed    = (bitCount > 8) ? 0 : (clrUsed > 0) ? clrUsed : (1 << bitCount);
        m_lineLength = (((width * bitCount) + 31) & ~31) / 8; /// 4バイト境界に揃える
    }
    
    // 対応している形式かどうか調べる
    void Check()
    {
        if ( m_lpBI.bmiHeader.biSize != BITMAPINFOHEADER.sizeof &&
            m_lpBI.bmiHeader.biSize != BITMAPV4HEADER.sizeof &&
            m_lpBI.bmiHeader.biSize != BITMAPV5HEADER.sizeof )
        {
            throw new BitmapException(r"対応していないヘッダ形式です");
        }
        
        if ( m_lpBI.bmiHeader.biCompression == BI_RGB )
        {
            switch ( m_bitCount )
            {
            case 1:
            case 4:
            case 8:
            case 16:
            case 24:
            case 32:
                return;
            default:
                throw new BitmapException(r"対応していないビット深度です");
            }
        }
        else if ( m_lpBI.bmiHeader.biCompression == BI_BITFIELDS )
        {
            switch ( m_bitCount )
            {
            case 16:
            case 32:
                return;
            default:
                throw new BitmapException(r"対応していないビット深度です");
            }
        }
        else
        {
            throw new BitmapException(r"圧縮ビットマップには非対応です");
        }
        
    }
    
    // パレットデータを作成する
    void CreatePalette()
    {
        if ( m_bitCount > 8 )
        {
            return; /// フルカラーの場合は必要なし
        }
        
        try
        {
            // 色数に応じて必要なメモリを確保
            auto lpLogPal = cast(LOGPALETTE*)new ubyte[LOGPALETTE.sizeof + PALETTEENTRY.sizeof * m_clrUsed];
            lpLogPal.palVersion = 0x300;
            lpLogPal.palNumEntries = cast(ushort)m_clrUsed;
            
            for ( size_t i = 0; i < m_clrUsed; i++ )
            {
                lpLogPal.palPalEntry[i].peRed   = m_lpBI.bmiColors[i].rgbRed;
                lpLogPal.palPalEntry[i].peGreen = m_lpBI.bmiColors[i].rgbGreen;
                lpLogPal.palPalEntry[i].peBlue  = m_lpBI.bmiColors[i].rgbBlue;
            }
            
            m_hPalette = .CreatePalette(lpLogPal);
            if (m_hPalette is null)
            {
                throw new BitmapException(r"パレットデータの作成に失敗しました");
            }
        }
        catch (Object o)
        {
            DeleteObject(m_hPalette);
            m_hPalette = null;
            throw o;
        }
    }
    
    // デスクトップと互換性のあるデバイスコンテキストを作成する
    void CreateCompatibleDC()
    {
        HDC hDesktopDC;
        try
        {
            hDesktopDC = GetDC(null);
            m_hDC = .CreateCompatibleDC(hDesktopDC);
            if (m_hDC is null)
            {
                throw new BitmapException(r"デバイスコンテキストが作成できませんでした");
            }
        }
        catch (Object o)
        {
            DeleteDC(m_hDC);
            m_hDC = null;
            throw o;
        }
        finally
        {
            ReleaseDC(null, hDesktopDC);
        }
    }
    
    // DIBSectionを生成する
    void CreateDIBSection()
    {
        try
        {
            // DIBSectionの作成
            m_hBitmap = .CreateDIBSection(m_hDC, m_lpBI, DIB_RGB_COLORS, cast(void**)&m_pbits, null, 0);
            if (m_hBitmap is null)
            {
                throw new BitmapException(r"DIBSectionの生成に失敗しました");
            }
            
            // ビットマップオブジェクトをデバイスコンテキストにセット
            HGDIOBJ hOldBmp = SelectObject(m_hDC, m_hBitmap);
            DeleteObject(hOldBmp);
        }
        catch (Object o)
        {
            DeleteObject(m_hBitmap);
            m_hBitmap = null;
            throw o;
        }
    }
    
// Properties
public:
    int         Width()     { return m_width; }
    int         Height()    { return m_height; }
    ushort      BitCount()  { return m_bitCount; }
    uint        ColorUsed() { return m_clrUsed; }
    uint        Size()      { return m_lineLength * m_height; }
    HDC         hDC()       { return m_hDC; }
    HPALETTE    hPalette()  { return m_hPalette; }
    HBITMAP     hBitmap()   { return m_hBitmap; }
    BITMAPINFO* BmpInfo()   { return m_lpBI; }
    HANDLE      pBits()     { return m_pbits; }
}
  • ライセンスは暫定的にNYSLで。
  • ご質問・ご指摘等 歓迎します