WindowsのC++にてGDI+を使ってJPEG画像を保存する方法を試したので記載します。
(jpegライブラリなどを入れたりするのが面倒だったので)
ビルド環境
- Windows 10
- Build Tools for Visual Studio 2019
- Visual Studio Code
ソースコード
エラー処理とか関数分割とか一切していないので、実際に使用する際には注意
# 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を使って標準出力に出力するようにした例
# 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);
}