GDI+

GDI+の処理に失敗するときには、初期化しているか確認します。

GDI+の利用には、標準的に次のヘッダーとライブラリが必要です。

#include <windows.h>
#include <Gdiplus.h>

#pragma comment (lib,"Gdiplus.lib")

初期化と終了処理

GDI+オブジェクトを生成する前にはGdiplusStartup()でGDI+を初期化し、それらが不要になったら破棄した後にGdiplusShutdown()を呼び出さねばなりません。

ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;

Gdiplus::Status status = GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

// GDI+ オブジェクトの生成

// …

// GDI+ オブジェクトの破棄

GdiplusShutdown(gdiplusToken);

GdiplusStartup()

Windows GDI+を初期化できます。

Status GdiplusStartup(
  ULONG_PTR                 *token, // トークン。GDI+を終了するときに、これをGdiplusShutdown()へ渡す
  const GdiplusStartupInput *input, // GDI+バージョンなどの情報
  GdiplusStartupOutput      *output // フックやアンフックの通知を受け取る構造体。GdiplusStartupInput.SuppressBackgroundThreadをTRUEとしていなければ、NULLで良い
);
GdiplusStartup function (gdiplusinit.h) - Win32 apps | Microsoft Learn

実行の誤りは、戻り値のStatus列挙型で確認できます。この関数を呼び出さずにGDI+オブジェクトを生成しようとすると、nullやStatus::GdiplusNotInitializedが返されます。Status::Win32Errorが返されたときは、GetLastError()で具体的なエラーコードを取得できます。

この関数の呼び出しごとに、GdiplusShutdown()の呼び出しが必要です。

Status列挙型

列挙子 内容
Ok 0 the method call was successful.
GenericError 1 there was an error on the method call, which is identified as something other than those defined by the other elements of this enumeration.
InvalidParameter 2 one of the arguments passed to the method was not valid.
OutOfMemory 3 the operating system is out of memory and could not allocate memory to process the method call. For an explanation of how constructors use the OutOfMemory status, see the Remarks section at the end of this topic.
ObjectBusy 4 one of the arguments specified in the API call is already in use in another thread.
InsufficientBuffer 5 a buffer specified as an argument in the API call is not large enough to hold the data to be received.
NotImplemented 6 the method is not implemented.
Win32Error 7 the method generated a Win32 error.
WrongState 8 the object is in an invalid state to satisfy the API call. For example, calling Pen::GetColor from a pen that is not a single, solid color results in a WrongState status.
Aborted 9 the method was aborted.
FileNotFound 10 the specified image file or metafile cannot be found.
ValueOverflow 11 the method performed an arithmetic operation that produced a numeric overflow.
AccessDenied 12 a write operation is not allowed on the specified file.
UnknownImageFormat 13 the specified image file format is not known.
FontFamilyNotFound 14 the specified font family cannot be found. Either the font family name is incorrect or the font family is not installed.
FontStyleNotFound 15 the specified style is not available for the specified font family.
NotTrueTypeFont 16 the font retrieved from an HDC or LOGFONT is not a TrueType font and cannot be used with GDI+.
UnsupportedGdiplusVersion 17 the version of GDI+ that is installed on the system is incompatible with the version with which the application was compiled.
GdiplusNotInitialized 18 the GDI+API is not in an initialized state. To function, all GDI+ objects require that GDI+ be in an initialized state. Initialize GDI+ by calling GdiplusStartup.
PropertyNotFound 19 the specified property does not exist in the image.
PropertyNotSupported 20 the specified property is not supported by the format of the image and, therefore, cannot be set.
ProfileNotFound 21 the color profile required to save an image in CMYK format was not found.
Status (gdiplustypes.h) - Win32 apps | Microsoft Learn

GdiplusShutdown()

この関数の呼び出し前に、生成したすべてのGDI+オブジェクトを破棄します。

この関数の呼び出し後にGDI+オブジェクトを破棄しようとすると、アクセス違反が発生します。

int main()
{
    ULONG_PTR gdiplusToken;
    GdiplusStartupInput gdiplusInput;
    GdiplusStartup(&gdiplusToken, &gdiplusInput, NULL);

    Bitmap bitmap(L"C:\\sample.bmp");

    GdiplusShutdown(gdiplusToken);
    return 0; // ブロックを抜けるときにbitmapが破棄されるため、アクセス違反となる
}

Image

保存

Image.Save()

画像をファイルに保存できます。

Status Save(
  [in]  const WCHAR             *filename,
  [in]  const CLSID             *clsidEncoder, // 画像の保存に使用するエンコーダーを指定するCLSID
  [in]  const EncoderParameters *encoderParams // エンコーダーで使用されるパラメータ
);
Image::Save - Win32 apps | Microsoft Learn

GDI+を初期化していないと、Status::InvalidParameterが返されます。

IStreamを引数に取るオーバーロードでは、ストリームへ出力できます。

Status Save(
  [in] IStream                 *stream,
  [in] const CLSID             *clsidEncoder,
  [in] const EncoderParameters *encoderParams
);
Image::Save(IN IStream,IN const CLSID,IN const EncoderParameters) (gdiplusheaders.h) - Win32 apps | Microsoft Learn
HRESULT WriteToStream(Gdiplus::Image *image)
{
    IStream *stream = NULL;
    HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
    if (FAILED(hr)) goto done;

    CLSID clsidEncoder;
    hr = CLSIDFromString(L"{557CF401-1A04-11D3-9A73-0000F81EF32E}", &clsidEncoder); // image/jpeg
    if (FAILED(hr)) goto done;

    Gdiplus::Status status = image->Save(stream, &clsidEncoder);
    if (status != Gdiplus::Status::Ok) throw status;

    // ストリームからバッファへ読み込み
    HGLOBAL memoryHandle = NULL;
    hr = GetHGlobalFromStream(stream, &memoryHandle);
    if (FAILED(hr)) goto done;

    size_t bufferSize = GlobalSize(memoryHandle);
    char *buffer = new char[bufferSize];

    LPVOID pMemoryHandle = GlobalLock(memoryHandle);
    if (pMemoryHandle == NULL) throw GetLastError();

    memcpy(buffer, pMemoryHandle, bufferSize);
    if (GlobalUnlock(memoryHandle) == TRUE) throw GetLastError();

    // バッファから出力ストリームへ書き込み
    _setmode(_fileno(stdout), _O_BINARY);
    std::cout.write(buffer, bufferSize);

done:
    stream->Release();
    return hr;
}
C++ gdi::Bitmap to PNG Image in memory - Stack Overflow

ストリームからは、istream.read()でも読み込めます。

LARGE_INTEGER potision;
potision.QuadPart = 0;
hr = stream->Seek(potision, STREAM_SEEK_SET, NULL);
if (FAILED(hr)) goto done;

STATSTG streamStat;
hr = stream->Stat(&streamStat, 0);
if (FAILED(hr)) goto done;

ULONG bufferSize = (ULONG)streamStat.cbSize.QuadPart;
char *buffer = new char[bufferSize];

ULONG read;
hr = stream->Read(buffer, bufferSize, &read);
if (FAILED(hr)) goto done;
CLSID (Class Identifier)

CLSIDを表す文字列は、Retrieving the Class Identifier for an Encoderにあるサンプルコードで下表のように得られます。

MimeType CLSIDを表す文字列
image/bmp {557CF400-1A04-11D3-9A73-0000F81EF32E}
image/jpeg {557CF401-1A04-11D3-9A73-0000F81EF32E}
image/gif {557CF402-1A04-11D3-9A73-0000F81EF32E}
image/tiff {557CF405-1A04-11D3-9A73-0000F81EF32E}
image/png {557CF406-1A04-11D3-9A73-0000F81EF32E}
ImageCodecInfo class (Windows) | Microsoft Learn

そしてCLSIDFromString()で、文字列からCLSIDを得られます。

HRESULT CLSIDFromString(
  [in]  LPCOLESTR lpsz,
  [out] LPCLSID   pclsid
);
CLSIDFromString function (combaseapi.h) - Win32 apps | Microsoft Learn

メタデータ

書き込み

SetPropertyItem()で書き込めます。

Status SetPropertyItem(
  [in] const PropertyItem *item
);
Image::SetPropertyItem (gdiplusheaders.h) - Win32 apps | Microsoft Learn
char *str = "sample";

PropertyItem item;
item.id = PropertyTagImageDescription;
item.type = PropertyTagTypeASCII;
item.length = strlen(str) + 1; // null終端文字を含む
item.value = str;

Image image(L"C:\\sample.jpg");
Status status = image.SetPropertyItem(&item);

Bitmap

作成

画像ファイルから

画像ファイルからBitmapを作成できます。

void Bitmap(
  IN const WCHAR *filename,                 // 画像ファイルのパス
  IN BOOL        useEmbeddedColorManagement // TRUEならば、画像ファイルに埋め込まれた色補正を適用する。省略した場合はFALSE
);
Bitmap::Bitmap(IN const WCHAR,IN BOOL) | Microsoft Learn
Bitmap bitmap(L"C:\\sample.bmp");

CLSID clsidEncoder;
CLSIDFromString(L"{557CF401-1A04-11D3-9A73-0000F81EF32E}", &clsidEncoder);

Status status = bitmap.Save(L"C:\\sample.jpg", &clsidEncoder, NULL);

new演算子でオブジェクトを動的に割り当てるとき、

Bitmap *bitmap = new Bitmap(L"C:\\sample.bmp");

GDI+を初期化していなかったり、すでに終了していた場合はNULLが返されます。

バイト配列から

バイト配列からBitmapを作成できます。

void Bitmap(
  INT         width,  // ピクセル単位の幅
  INT         height, // ピクセル単位の高さ
  INT         stride, // 1行あたりのバイト数。1ピクセルあたり16ビットのフォーマットならば、2バイト (16ビット) x ピクセル単位の幅の値
  PixelFormat format, // Image Pixel Format Constantsで定義されているピクセルフォーマット
  BYTE        *scan0  // ピクセル データを含むバイト配列
);
Bitmap::Bitmap(INT,INT,INT,PixelFormat,BYTE) | Microsoft Learn

scan0で示されるメモリ ブロックの割り当てと解放は、呼び出し元 (caller) が担当します。そして解放は、このBitmapを破棄した後とします。

画像データを自前で用意すれば、新規に作成することもできます。

const int width = 16;
const int height = 16;

BYTE scan0[width*height];
for (int i = 0; i < width*height; i++)
{
    scan0[i] = (i%width == i / width) ? 0 : 0xff;
}

Bitmap bitmap(width, height, width * 1, PixelFormat8bppIndexed, scan0);

CLSID clsidEncoder;
CLSIDFromString(L"{557CF400-1A04-11D3-9A73-0000F81EF32E}", &clsidEncoder);

Status status = bitmap.Save(L"C:\\sample.bmp", &clsidEncoder, NULL);

これは「」のような画像を生成します。このときLockBits()で取得したBitmapData.Scan0へ、バイト配列のデータをコピーする方法もあります。

BYTE *buffer = new BYTE[width*height];

const int BYTES_PER_PIXEL = 1;
int bufferSize = width * height * BYTES_PER_PIXEL;
...


Bitmap bitmap(width, height, PixelFormat8bppIndexed);

Rect rect(0, 0, width, height);
BitmapData bitmapData;

Status status = bitmap.LockBits(
    &rect,
    ImageLockModeWrite,
    bitmap.GetPixelFormat(),
    &bitmapData);

CopyMemory(bitmapData.Scan0, buffer, bufferSize);

status = bitmap.UnlockBits(&bitmapData);

データ変換

バイト配列

Bitmap bitmap(L"C:\\sample.bmp");

Rect rect(0, 0, bitmap.GetWidth(), bitmap.GetHeight());
BitmapData bitmapData;

Status status = bitmap.LockBits(
    &rect,
    ImageLockModeRead,
    PixelFormat24bppRGB,
    &bitmapData);

BYTE *rawData = (BYTE*)bitmapData.Scan0;
int stride = bitmapData.Stride;

// バイト配列を使用した処理

bitmap.UnlockBits(&bitmapData);
c++ - Gdiplus::Bitmap to BYTE array? - Stack Overflow Using LockBits in GDI+ | The Supercomputing Blog

GDIビットマップへのハンドル

Gdiplus::Bitmap *bitmap1 = new Gdiplus::Bitmap(L"C:\\sample.bmp");

// ビットマップへのハンドルの取得
HBITMAP hbm;
Gdiplus::Status status = bitmap1->GetHBITMAP(Gdiplus::Color::Black, &hbm);


// ハンドルからビットマップを作成
HPALETTE hpal = NULL;
Gdiplus::Bitmap *bitmap2 = new Gdiplus::Bitmap(hbm, hpal);
Bitmap::GetHBITMAP (gdiplusheaders.h) - Win32 apps | Microsoft Learn Bitmap::Bitmap(IN HBITMAP,IN HPALETTE) (gdiplusheaders.h) - Win32 apps | Microsoft Learn

ハンドルから作成したビットマックを削除するまで、元のビットマップを削除してはなりません。Remarks - Bitmap::Bitmap(IN HBITMAP,IN HPALETTE) (gdiplusheaders.h) - Win32 apps | Microsoft Learn

Microsoft Learnから検索