#include <windows.h>
#include <objidl.h>
#include "allegro5/allegro.h"
#include "allegro5/allegro_image.h"
#include "allegro5/internal/aintern.h"
#include "allegro5/internal/aintern_convert.h"
#include "allegro5/internal/aintern_exitfunc.h"
#include "allegro5/internal/aintern_image.h"
#include "iio.h"
#ifdef ALLEGRO_CFG_IIO_HAVE_GDIPLUS_LOWERCASE_H
#include <gdiplus.h>
#else
#include <GdiPlus.h>
#endif
ALLEGRO_DEBUG_CHANNEL("image")
using namespace Gdiplus;
#if !defined(_MSC_VER) && !defined(__uuidof)
#define __uuidof(x) (IID_ ## x)
#endif
static bool gdiplus_inited = false;
static ULONG_PTR gdiplusToken = 0;
* http://msdn.microsoft.com/en-us/library/ms533843%28VS.85%29.aspx
*/
static int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) {
return -1;
}
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(al_malloc(size));
if (pImageCodecInfo == NULL) {
return -1;
}
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j) {
if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
al_free(pImageCodecInfo);
return j;
}
}
al_free(pImageCodecInfo);
return -1;
}
*/
class AllegroWindowsStream : public IStream
{
long refCount;
ALLEGRO_FILE *fp;
public:
AllegroWindowsStream(ALLEGRO_FILE *fp) :
refCount(1),
fp(fp)
{
this->fp = fp;
}
virtual ~AllegroWindowsStream()
{
}
virtual ULONG STDMETHODCALLTYPE AddRef(void)
{
return (ULONG) InterlockedIncrement(&refCount);
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
{
if (iid == __uuidof(IUnknown) || iid == __uuidof(ISequentialStream)
|| iid == __uuidof(IStream)) {
*ppvObject = static_cast<IStream*>(this);
AddRef();
return S_OK;
}
else {
return E_NOINTERFACE;
}
}
virtual ULONG STDMETHODCALLTYPE Release(void)
{
ULONG ret = InterlockedDecrement(&refCount);
if (ret == 0) {
delete this;
return 0;
}
return ret;
}
virtual HRESULT STDMETHODCALLTYPE Read(void *pv, ULONG cb, ULONG *pcbRead)
{
size_t read = al_fread(fp, pv, cb);
if (pcbRead) {
*pcbRead = read;
}
return read == cb ? S_OK : S_FALSE;
}
virtual HRESULT STDMETHODCALLTYPE Write(const void *pv, ULONG cb,
ULONG *pcbWritten)
{
size_t written = al_fwrite(fp, pv, cb);
if (pcbWritten) {
*pcbWritten = written;
}
return written == cb ? S_OK : STG_E_CANTSAVE;
}
virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin,
ULARGE_INTEGER *plibNewPosition)
{
ALLEGRO_SEEK o;
if (dwOrigin == STREAM_SEEK_SET) {
o = ALLEGRO_SEEK_SET;
}
else if (dwOrigin == STREAM_SEEK_CUR) {
o = ALLEGRO_SEEK_CUR;
}
else {
o = ALLEGRO_SEEK_END;
}
bool ret = al_fseek(fp, dlibMove.QuadPart, o);
if (plibNewPosition) {
int64_t pos = al_ftell(fp);
if (pos == -1) {
return STG_E_INVALIDFUNCTION;
}
plibNewPosition->QuadPart = pos;
}
return ret ? S_OK : STG_E_INVALIDFUNCTION;
}
virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG *pstatstg, DWORD grfStatFlag)
{
(void) grfStatFlag;
memset(pstatstg, 0, sizeof(*pstatstg));
pstatstg->type = STGTY_STREAM;
pstatstg->cbSize.QuadPart = al_fsize(fp);
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE Clone(IStream **ppstm)
{
(void) ppstm;
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags)
{
(void) grfCommitFlags;
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE CopyTo (IStream *pstm, ULARGE_INTEGER cb,
ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten)
{
(void) pstm;
(void) cb;
(void) pcbRead;
(void) pcbWritten;
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER libOffset,
ULARGE_INTEGER cb, DWORD dwLockType)
{
(void) libOffset;
(void) cb;
(void) dwLockType;
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE Revert()
{
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER libNewSize)
{
(void) libNewSize;
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER libOffset,
ULARGE_INTEGER cb, DWORD dwLockType)
{
(void) libOffset;
(void) cb;
(void) dwLockType;
return E_NOTIMPL;
}
};
static void load_indexed_data(Gdiplus::Bitmap *gdi_bmp,
ALLEGRO_BITMAP *a_bmp, uint32_t w, uint32_t h)
{
Gdiplus::BitmapData *gdi_lock = new Gdiplus::BitmapData();
Gdiplus::Rect rect(0, 0, w, h);
if (!gdi_bmp->LockBits(&rect, Gdiplus::ImageLockModeRead,
PixelFormat8bppIndexed, gdi_lock))
{
ALLEGRO_LOCKED_REGION *a_lock = al_lock_bitmap(a_bmp,
ALLEGRO_PIXEL_FORMAT_SINGLE_CHANNEL_8, ALLEGRO_LOCK_WRITEONLY);
if (a_lock) {
unsigned char *in = (unsigned char *)gdi_lock->Scan0;
unsigned char *out = (unsigned char *)a_lock->data;
if (gdi_lock->Stride == a_lock->pitch) {
memcpy(out, in, h * gdi_lock->Stride);
}
else {
uint32_t rows = h;
while (rows--) {
memcpy(out, in, w);
in += gdi_lock->Stride;
out += a_lock->pitch;
}
}
al_unlock_bitmap(a_bmp);
}
gdi_bmp->UnlockBits(gdi_lock);
}
delete gdi_lock;
}
static void load_non_indexed_data(Gdiplus::Bitmap *gdi_bmp,
ALLEGRO_BITMAP *a_bmp, uint32_t w, uint32_t h, bool premul)
{
Gdiplus::BitmapData *gdi_lock = new Gdiplus::BitmapData();
Gdiplus::Rect rect(0, 0, w, h);
if (!gdi_bmp->LockBits(&rect, Gdiplus::ImageLockModeRead,
PixelFormat32bppARGB, gdi_lock))
{
ALLEGRO_LOCKED_REGION *a_lock = al_lock_bitmap(a_bmp,
ALLEGRO_PIXEL_FORMAT_ARGB_8888, ALLEGRO_LOCK_WRITEONLY);
if (a_lock) {
unsigned char *in = (unsigned char *)gdi_lock->Scan0;
unsigned char *out = (unsigned char *)a_lock->data;
if (premul) {
int in_inc = gdi_lock->Stride - (w*4);
int out_inc = a_lock->pitch - (w*4);
for (unsigned int y = 0; y < h; y++) {
for (unsigned int x = 0; x < w; x++) {
unsigned char r, g, b, a;
b = *in++;
g = *in++;
r = *in++;
a = *in++;
b = b * a / 255;
g = g * a / 255;
r = r * a / 255;
*out++ = b;
*out++ = g;
*out++ = r;
*out++ = a;
}
in += in_inc;
out += out_inc;
}
}
else {
if (gdi_lock->Stride == a_lock->pitch) {
memcpy(out, in, h * gdi_lock->Stride);
}
else {
uint32_t rows = h;
while (rows--) {
memcpy(out, in, w * 4);
in += gdi_lock->Stride;
out += a_lock->pitch;
}
}
}
al_unlock_bitmap(a_bmp);
}
gdi_bmp->UnlockBits(gdi_lock);
}
delete gdi_lock;
}
ALLEGRO_BITMAP *_al_load_gdiplus_bitmap_f(ALLEGRO_FILE *fp, int flags)
{
AllegroWindowsStream *s = new AllegroWindowsStream(fp);
if (!s) {
ALLEGRO_ERROR("Unable to create AllegroWindowsStream.\n");
return NULL;
}
ALLEGRO_BITMAP *a_bmp = NULL;
Gdiplus::Bitmap *gdi_bmp = Gdiplus::Bitmap::FromStream(s, false);
if (gdi_bmp) {
const uint32_t w = gdi_bmp->GetWidth();
const uint32_t h = gdi_bmp->GetHeight();
const PixelFormat pf = gdi_bmp->GetPixelFormat();
bool premul = !(flags & ALLEGRO_NO_PREMULTIPLIED_ALPHA);
bool keep_index = (flags & ALLEGRO_KEEP_INDEX);
a_bmp = al_create_bitmap(w, h);
if (a_bmp) {
if (pf == PixelFormat8bppIndexed && keep_index) {
load_indexed_data(gdi_bmp, a_bmp, w, h);
}
else {
load_non_indexed_data(gdi_bmp, a_bmp, w, h, premul);
}
}
delete gdi_bmp;
}
s->Release();
return a_bmp;
}
ALLEGRO_BITMAP *_al_load_gdiplus_bitmap(const char *filename, int flags)
{
ALLEGRO_BITMAP *bmp = NULL;
ALLEGRO_FILE *fp;
fp = al_fopen(filename, "rb");
if (fp) {
bmp = _al_load_gdiplus_bitmap_f(fp, flags);
al_fclose(fp);
}
return bmp;
}
bool _al_save_gdiplus_bitmap_f(ALLEGRO_FILE *fp, const char *ident,
ALLEGRO_BITMAP *a_bmp)
{
CLSID encoder;
int encoder_status = -1;
if (!_al_stricmp(ident, ".bmp")) {
encoder_status = GetEncoderClsid(L"image/bmp", &encoder);
}
else if (!_al_stricmp(ident, ".jpg") || !_al_stricmp(ident, ".jpeg")) {
encoder_status = GetEncoderClsid(L"image/jpeg", &encoder);
}
else if (!_al_stricmp(ident, ".gif")) {
encoder_status = GetEncoderClsid(L"image/gif", &encoder);
}
else if (!_al_stricmp(ident, ".tif") || !_al_stricmp(ident, ".tiff")) {
encoder_status = GetEncoderClsid(L"image/tiff", &encoder);
}
else if (!_al_stricmp(ident, ".png")) {
encoder_status = GetEncoderClsid(L"image/png", &encoder);
}
if (encoder_status == -1) {
ALLEGRO_ERROR("Invalid encoder status.\n");
return false;
}
AllegroWindowsStream *s = new AllegroWindowsStream(fp);
if (!s) {
ALLEGRO_ERROR("Couldn't create AllegroWindowsStream.\n");
return false;
}
const int w = al_get_bitmap_width(a_bmp), h = al_get_bitmap_height(a_bmp);
bool ret = false;
Gdiplus::Bitmap *gdi_bmp = new Gdiplus::Bitmap(w, h, PixelFormat32bppARGB);
if (gdi_bmp) {
Gdiplus::Rect rect(0, 0, w, h);
Gdiplus::BitmapData *gdi_lock = new Gdiplus::BitmapData();
if (!gdi_bmp->LockBits(&rect, Gdiplus::ImageLockModeWrite,
PixelFormat32bppARGB, gdi_lock)) {
ALLEGRO_LOCKED_REGION *a_lock = al_lock_bitmap(
a_bmp, ALLEGRO_PIXEL_FORMAT_ARGB_8888, ALLEGRO_LOCK_READONLY);
if (a_lock) {
unsigned char *in = (unsigned char *)a_lock->data;
unsigned char *out = (unsigned char *)gdi_lock->Scan0;
if (gdi_lock->Stride == a_lock->pitch) {
memcpy(out, in, h * gdi_lock->Stride);
}
else {
uint32_t rows = h;
while (rows--) {
memcpy(out, in, w * 4);
in += a_lock->pitch;
out += gdi_lock->Stride;
}
}
al_unlock_bitmap(a_bmp);
}
gdi_bmp->UnlockBits(gdi_lock);
}
ret = (gdi_bmp->Save(s, &encoder, NULL) == 0);
delete gdi_lock;
delete gdi_bmp;
}
s->Release();
return ret;
}
bool _al_save_gdiplus_bitmap(const char *filename, ALLEGRO_BITMAP *bmp)
{
ALLEGRO_FILE *fp;
bool ret = false;
fp = al_fopen(filename, "wb");
if (fp) {
ALLEGRO_PATH *path = al_create_path(filename);
if (path) {
ret = _al_save_gdiplus_bitmap_f(fp, al_get_path_extension(path), bmp);
al_destroy_path(path);
}
al_fclose(fp);
}
return ret;
}
bool _al_save_gdiplus_png_f(ALLEGRO_FILE *f, ALLEGRO_BITMAP *bmp)
{
return _al_save_gdiplus_bitmap_f(f, ".png", bmp);
}
bool _al_save_gdiplus_jpg_f(ALLEGRO_FILE *f, ALLEGRO_BITMAP *bmp)
{
return _al_save_gdiplus_bitmap_f(f, ".jpg", bmp);
}
bool _al_save_gdiplus_tif_f(ALLEGRO_FILE *f, ALLEGRO_BITMAP *bmp)
{
return _al_save_gdiplus_bitmap_f(f, ".tif", bmp);
}
bool _al_save_gdiplus_gif_f(ALLEGRO_FILE *f, ALLEGRO_BITMAP *bmp)
{
return _al_save_gdiplus_bitmap_f(f, ".gif", bmp);
}
bool _al_init_gdiplus()
{
if (!gdiplus_inited) {
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
if (Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL)
== Gdiplus::Ok) {
gdiplus_inited = TRUE;
_al_add_exit_func(_al_shutdown_gdiplus, "_al_shutdown_gdiplus");
}
}
return gdiplus_inited;
}
void _al_shutdown_gdiplus()
{
if (gdiplus_inited) {
Gdiplus::GdiplusShutdown(gdiplusToken);
gdiplus_inited = false;
}
}