[C++]GDI+でJPEG保存

WindowsのC++にてGDI+を使ってJPEG画像を保存する方法を試したので記載します。
（jpegライブラリなどを入れたりするのが面倒だったので）

# ビルド環境

* Windows 10
* Build Tools for Visual Studio 2019
* Visual Studio Code

# ソースコード

エラー処理とか関数分割とか一切していないので、実際に使用する際には注意

```cpp
#include <Windows.h>
#include <gdiplus.h>
#include <gdipluspixelformats.h>
#pragma comment(lib, "gdiplus.lib")

static int extendStride(int stride)
{
    const int STRIDE_UNIT_BYTE = 4;
    return (((stride + STRIDE_UNIT_BYTE - 1) / STRIDE_UNIT_BYTE) * STRIDE_UNIT_BYTE);
}

void main(void)
{
    int width = 300;
    int height = 300;
    int gdiStride = extendStride(width * 3);  // RGB用
    // 画像データ作成(RGB) 動作確認用
    byte* pixels = (byte*)malloc(gdiStride * height);
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int i = (x * 3) + (y * gdiStride);
            pixels[i+0] = (byte)(x & 0xff);                 // B
            pixels[i+1] = (byte)(y & 0xff);                 // G
            pixels[i+2] = (byte)((0x80 + x + y) & 0xff);    // R
        }
    }
    // jpeg保存
    {
        // GDI+初期化
        Gdiplus::GdiplusStartupInput input;
        ULONG_PTR token;
        Gdiplus::GdiplusStartup(&token, &input, NULL);

        CLSID id;
        // EncoderのCLSIDの取得
        {
            UINT num = 0;
            UINT size = 0;
            Gdiplus::GetImageEncodersSize(&num, &size);
            Gdiplus::ImageCodecInfo *pImageCodecInfo = new Gdiplus::ImageCodecInfo[size];
            Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
            for (UINT j = 0; j < num; ++j) {
                if (wcscmp(pImageCodecInfo[j].MimeType, L"image/jpeg") ==0) {
                    id = pImageCodecInfo[j].Clsid;
                    break;
                }
            }
            delete [] pImageCodecInfo;
        }    
        // 保存処理
        {
            Gdiplus::Bitmap image(width, height, gdiStride, PixelFormat24bppRGB, pixels);
            image.Save(L"out.jpg", &id);
        }
        // GDI+解放
        Gdiplus::GdiplusShutdown(token);
    }
    free(pixels);
}
```

IStreamを使って標準出力に出力するようにした例

```cpp

#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <Windows.h>
#include <gdiplus.h>
#include <gdipluspixelformats.h> // PixelFormat24bppRGB
#pragma comment(lib, "gdiplus.lib")

class FilePointerStream : IStream 
{
public:
    static BOOL CreateInstance(FILE* fp, IStream **ppStream) {
        FilePointerStream* pFileStream = new FilePointerStream(fp);
        if (!pFileStream) return FALSE;
        _setmode( _fileno(fp), _O_BINARY);
        pFileStream->QueryInterface(IID_IStream, (void **)ppStream);
        return TRUE;
    }
private:
    // コンストラクタ／デストラクタ
    FilePointerStream(FILE* fp) { 
        m_fp = fp; 
        m_nRefCnt = 0;
    }
    virtual ~FilePointerStream(void) {}

    // IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) {
        if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ISequentialStream) || IsEqualIID(riid, IID_IStream)) {
            *ppvObject = static_cast<IStream *>(this);
            AddRef();   // 参照カウンタをインクリメント
            return S_OK;
        }
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }
    virtual ULONG STDMETHODCALLTYPE AddRef(void) { return ++m_nRefCnt; }
    virtual ULONG STDMETHODCALLTYPE Release(void) { 
        if (--m_nRefCnt > 0) { return m_nRefCnt; }
        delete this;
        fprintf(stderr, "Release  delete\n");
        return 0;
    }

    // ISequentialStream
    virtual HRESULT STDMETHODCALLTYPE Read(void* pv, ULONG cb, ULONG *pcbRead) {
        size_t ret = fread(pv, cb, 1, m_fp);
        if (1 != ret) { return S_FALSE; }
        if (NULL != pcbRead) { *pcbRead = (ULONG)cb; }
        return S_OK;
    }
    virtual HRESULT STDMETHODCALLTYPE Write(const void* pv, ULONG cb, ULONG* pcbWritten) {
        size_t ret = fwrite(pv, cb, 1, m_fp);
        if (1 != ret) { return S_FALSE; }
        if (NULL != pcbWritten) { *pcbWritten = (ULONG)cb; }
        return S_OK;
    }

    // IStream
    virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition) {
        int origin = SEEK_SET;
        if (dwOrigin == STREAM_SEEK_SET) origin = SEEK_SET;
        if (dwOrigin == STREAM_SEEK_CUR) origin = SEEK_CUR;
        if (dwOrigin == STREAM_SEEK_END) origin = SEEK_END;
        if (0 != fseek(m_fp, dlibMove.LowPart, origin)) { return E_UNEXPECTED; }
        if (plibNewPosition != NULL) { plibNewPosition->LowPart = ftell(m_fp); }
        return S_OK;
    }
    virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream *, ULARGE_INTEGER, ULARGE_INTEGER *, ULARGE_INTEGER *) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE Commit(DWORD) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE Revert(void) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG *, DWORD) {return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE Clone(IStream **) { return E_NOTIMPL; }
    long m_nRefCnt;
    FILE* m_fp;
};

static int extendStride(int stride)
{
    const int STRIDE_UNIT_BYTE = 4;
    return (((stride + STRIDE_UNIT_BYTE - 1) / STRIDE_UNIT_BYTE) * STRIDE_UNIT_BYTE);
}

void main(void)
{
    int width = 300;
    int height = 300;
    int gdiStride = extendStride(width * 3);  // RGB用
    // 画像データ作成(RGB) 動作確認用
    byte* pixels = (byte*)malloc(gdiStride * height);
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int i = (x * 3) + (y * gdiStride);
            pixels[i+0] = (byte)(x & 0xff);                 // B
            pixels[i+1] = (byte)(y & 0xff);                 // G
            pixels[i+2] = (byte)((0x80 + x + y) & 0xff);    // R
        }
    }

    IStream* pStdoutStream;
    FilePointerStream::CreateInstance( stdout, &pStdoutStream);
    // jpeg保存
    {
        // GDI+初期化
        Gdiplus::GdiplusStartupInput input;
        ULONG_PTR token;
        Gdiplus::GdiplusStartup(&token, &input, NULL);

        CLSID id;
        // CLSIDの取得
        {
            UINT num = 0;
            UINT size = 0;
            Gdiplus::GetImageEncodersSize(&num, &size);
            Gdiplus::ImageCodecInfo *pImageCodecInfo = new Gdiplus::ImageCodecInfo[size];
            Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
            for (UINT j = 0; j < num; ++j) {
                if (wcscmp(pImageCodecInfo[j].MimeType, L"image/jpeg") ==0) {
                    id = pImageCodecInfo[j].Clsid;
                    break;
                }
            }
            delete [] pImageCodecInfo;
        }    
        // 保存処理
        {
            Gdiplus::Bitmap image(width, height, gdiStride, PixelFormat24bppRGB, pixels);
            image.Save(pStdoutStream, &id);
            pStdoutStream->Release();
        }
        // GDI+解放
        Gdiplus::GdiplusShutdown(token);
    }
    free(pixels);
}
```