#include "dctx_win.inl"
#include "../image.inl"
#include "../dctxh.h"
#include "../imgutil.inl"
#include "../color.h"
#include "../palette.h"
#include "../pixbuf.h"
#include <core/buffer.h>
#include <core/heap.h>
#include <core/stream.h>
#include <sewer/bmem.h>
#include <sewer/cassert.h>
#include <sewer/ptr.h>
#include <sewer/unicode.h>
#if !defined(__WINDOWS__)
#error This file is only for WINDOWS
#endif
#include <sewer/nowarn.hxx>
#include "shlwapi.h"
#include <sewer/warn.hxx>
#include "draw2d_win.ixx"
typedef IStream *(__stdcall *fnSHCreateMemStream)(const BYTE *pInit, UINT cbInit);
static fnSHCreateMemStream i_kSHCreateMemStream = NULL;
static ULONG_PTR i_GDIPLUSTOKEN = 0L;
struct _osimage_t
{
Gdiplus::Bitmap *bitmap;
};
static IStream *i_SHCreateMemStream(const BYTE *pInit, UINT cbInit)
{
return SHCreateMemStream(pInit, cbInit);
}
void osimage_alloc_globals(void)
{
HMODULE libShlWapi = LoadLibrary(L"shlwapi.dll");
cassert_no_null(libShlWapi);
Gdiplus::GdiplusStartupInput startup;
Gdiplus::GdiplusStartup(&i_GDIPLUSTOKEN, &startup, NULL);
#if defined(_MSC_VER)
#pragma warning(disable : 4191)
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
i_kSHCreateMemStream = (fnSHCreateMemStream)GetProcAddress(libShlWapi, (LPCSTR)12);
#if defined(_MSC_VER)
#pragma warning(default : 4191)
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic warning "-Wcast-function-type"
#endif
cassert_no_nullf(i_kSHCreateMemStream);
FreeLibrary(libShlWapi);
}
void osimage_dealloc_globals(void)
{
Gdiplus::GdiplusShutdown(i_GDIPLUSTOKEN);
i_GDIPLUSTOKEN = 0L;
}
typedef struct _argb_t
{
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t a;
} _ARGB;
#define ARGB(r, g, b, a) \
((uint32_t)((((a)&0xff) << 24) | (((r)&0xff) << 16) | (((g)&0xff) << 8) | ((b)&0xff)))
#define ABGR(r, g, b, a) \
((uint32_t)((((a)&0xff) << 24) | (((b)&0xff) << 16) | (((g)&0xff) << 8) | ((r)&0xff)))
OSImage *osimage_create_from_pixels(const uint32_t width, const uint32_t height, const pixformat_t format, const byte_t *pixel_data)
{
Gdiplus::Bitmap *bitmap = NULL;
Gdiplus::BitmapData bdata;
byte_t *dest_data = NULL;
uint32_t i, j, stride;
switch (format)
{
case ekGRAY8:
bitmap = new Gdiplus::Bitmap((INT)width, (INT)height, PixelFormat8bppIndexed);
bitmap->LockBits(NULL, Gdiplus::ImageLockModeWrite, PixelFormat8bppIndexed, &bdata);
stride = bdata.Stride - width;
dest_data = cast(bdata.Scan0, byte_t);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
dest_data[0] = pixel_data[0];
dest_data += 1;
pixel_data += 1;
}
dest_data += stride;
}
bitmap->UnlockBits(&bdata);
bitmap->SetPalette(_dctx_8bpp_grayscale_palette());
break;
case ekRGB24:
bitmap = new Gdiplus::Bitmap((INT)width, (INT)height, PixelFormat24bppRGB);
bitmap->LockBits(NULL, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &bdata);
stride = bdata.Stride - (width * 3);
dest_data = cast(bdata.Scan0, byte_t);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
dest_data[0] = pixel_data[2];
dest_data[1] = pixel_data[1];
dest_data[2] = pixel_data[0];
dest_data += 3;
pixel_data += 3;
}
dest_data += stride;
}
bitmap->UnlockBits(&bdata);
break;
case ekRGBA32:
bitmap = new Gdiplus::Bitmap((INT)width, (INT)height, PixelFormat32bppARGB);
bitmap->LockBits(NULL, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &bdata);
stride = bdata.Stride - (width * 4);
dest_data = cast(bdata.Scan0, byte_t);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
*cast(dest_data, uint32_t) = ARGB(pixel_data[0], pixel_data[1], pixel_data[2], pixel_data[3]);
dest_data += 4;
pixel_data += 4;
}
dest_data += stride;
}
bitmap->UnlockBits(&bdata);
break;
case ekINDEX1:
case ekINDEX2:
case ekINDEX4:
case ekINDEX8:
case ekFIMAGE:
cassert_default();
}
{
OSImage *image = heap_new0(OSImage);
image->bitmap = bitmap;
return image;
}
}
OSImage *osimage_create_from_data(const byte_t *data, const uint32_t size_in_bytes)
{
IStream *stream = NULL;
Gdiplus::Bitmap *bitmap = NULL;
cassert_no_null(data);
cassert(size_in_bytes > 0);
stream = i_SHCreateMemStream(cast_const(data, BYTE), (UINT)size_in_bytes);
bitmap = Gdiplus::Bitmap::FromStream(stream, TRUE);
stream->Release();
{
OSImage *image = heap_new0(OSImage);
image->bitmap = bitmap;
return image;
}
}
OSImage *osimage_create_from_type(const char_t *file_type)
{
WCHAR wextension[64];
DWORD dwFileAttributes;
uint32_t num_bytes;
if (strcmp(file_type, ".") == 0)
{
num_bytes = unicode_convers("\fdsfg", cast(wextension, char_t), ekUTF8, ekUTF16, sizeof(wextension));
dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
}
else
{
num_bytes = unicode_convers(file_type, cast(wextension, char_t), ekUTF8, ekUTF16, sizeof(wextension));
dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
}
if (num_bytes < sizeof(wextension))
{
SHFILEINFO psfi;
DWORD_PTR ok;
Gdiplus::Bitmap *bitmap = NULL;
ok = SHGetFileInfo(wextension, dwFileAttributes, &psfi, sizeof(psfi), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES);
cassert_unref(ok != 0, ok);
bitmap = Gdiplus::Bitmap::FromHICON(psfi.hIcon);
{
OSImage *image = heap_new0(OSImage);
image->bitmap = bitmap;
return image;
}
}
else
{
cassert(FALSE);
return NULL;
}
}
OSImage *osimage_create_scaled(const OSImage *image, const uint32_t new_width, const uint32_t new_height)
{
UINT current_width, current_height;
real32_t scale_factor_x, scale_factor_y;
Gdiplus::Bitmap *bitmap = NULL;
cassert_no_null(image);
cassert_no_null(image->bitmap);
current_width = image->bitmap->GetWidth();
current_height = image->bitmap->GetHeight();
scale_factor_x = (real32_t)new_width / (real32_t)current_width;
scale_factor_y = (real32_t)new_height / (real32_t)current_height;
bitmap = new Gdiplus::Bitmap((INT)new_width, (INT)new_height, image->bitmap->GetPixelFormat());
Gdiplus::Graphics g(bitmap);
g.ScaleTransform(scale_factor_x, scale_factor_y);
g.DrawImage(image->bitmap, (INT)0, (INT)0, (INT)current_width, (INT)current_height);
{
OSImage *nimage = heap_new0(OSImage);
nimage->bitmap = bitmap;
return nimage;
}
}
OSImage *osimage_from_context(DCtx **ctx)
{
Gdiplus::Bitmap *bitmap;
cassert_no_null(ctx);
cassert_no_null(*ctx);
bitmap = (*ctx)->bitmap;
cassert_no_null(bitmap);
dctx_destroy(ctx);
{
OSImage *image = heap_new0(OSImage);
image->bitmap = bitmap;
return image;
}
}
void osimage_destroy(OSImage **image)
{
cassert_no_null(image);
cassert_no_null(*image);
delete (*image)->bitmap;
heap_delete(image, OSImage);
}
static bool_t i_is_gray_palette(const Gdiplus::ColorPalette *palette)
{
for (UINT i = 0; i < palette->Count; ++i)
{
_ARGB *color = (_ARGB *)(palette->Entries + i);
if (color->a != 255)
return FALSE;
if (color->r != color->g || color->r != color->b)
return FALSE;
}
return TRUE;
}
static bool_t i_is_alpha_palette(const Gdiplus::ColorPalette *palette)
{
for (UINT i = 0; i < palette->Count; ++i)
{
_ARGB *color = (_ARGB *)(palette->Entries + i);
if (color->a != 255)
return TRUE;
}
return FALSE;
}
static void i_indexed_info(Gdiplus::Bitmap *bitmap, pixformat_t *format, Pixbuf **pixels)
{
uint32_t psize = (uint32_t)bitmap->GetPaletteSize();
Gdiplus::ColorPalette *pal = (Gdiplus::ColorPalette *)heap_malloc(psize, "ImageImpPalette");
cassert_no_null(format);
bitmap->GetPalette(pal, (INT)psize);
if (i_is_gray_palette(pal) == TRUE)
*format = ekGRAY8;
else if (i_is_alpha_palette(pal) == TRUE)
*format = ekRGBA32;
else
*format = ekRGB24;
if (pixels != NULL)
{
Palette *palette = palette_create((uint32_t)pal->Count);
color_t *color = palette_colors(palette);
Gdiplus::PixelFormat pf = bitmap->GetPixelFormat();
Gdiplus::BitmapData data;
uint32_t i;
uint32_t ibpp = 8;
for (i = 0; i < pal->Count; ++i)
{
_ARGB *argb = cast(pal->Entries + i, _ARGB);
color[i] = color_rgba(argb->r, argb->g, argb->b, argb->a);
}
switch (pf)
{
case PixelFormat1bppIndexed:
ibpp = 1;
break;
case PixelFormat4bppIndexed:
ibpp = 4;
break;
case PixelFormat8bppIndexed:
ibpp = 8;
break;
cassert_default();
}
bitmap->LockBits(NULL, Gdiplus::ImageLockModeRead, pf, &data);
switch (*format)
{
case ekGRAY8:
*pixels = _imgutil_indexed_to_gray((uint32_t)data.Width, (uint32_t)data.Height, cast_const(data.Scan0, byte_t), (uint32_t)data.Stride, ibpp, color);
break;
case ekRGB24:
*pixels = _imgutil_indexed_to_rgb((uint32_t)data.Width, (uint32_t)data.Height, cast_const(data.Scan0, byte_t), (uint32_t)data.Stride, ibpp, color);
break;
case ekRGBA32:
*pixels = _imgutil_indexed_to_rgba((uint32_t)data.Width, (uint32_t)data.Height, cast_const(data.Scan0, byte_t), (uint32_t)data.Stride, ibpp, color);
break;
case ekINDEX1:
case ekINDEX2:
case ekINDEX4:
case ekINDEX8:
case ekFIMAGE:
cassert_default();
}
bitmap->UnlockBits(&data);
palette_destroy(&palette);
}
heap_free(dcast(&pal, byte_t), (uint32_t)psize, "ImageImpPalette");
}
static bool_t i_rgb_is_gray(Gdiplus::Bitmap *bitmap, const uint32_t bpp)
{
bool_t gray = TRUE;
Gdiplus::PixelFormat pf = bitmap->GetPixelFormat();
Gdiplus::BitmapData data;
const byte_t *src = NULL;
UINT i, j;
bitmap->LockBits(NULL, Gdiplus::ImageLockModeRead, pf, &data);
src = cast_const(data.Scan0, byte_t);
for (j = 0; j < data.Height && gray == TRUE; ++j)
{
for (i = 0; i < data.Width; ++i)
{
if (src[0] != src[1] || src[0] != src[2])
{
gray = FALSE;
break;
}
src += bpp;
}
src += data.Stride - (data.Width * bpp);
}
bitmap->UnlockBits(&data);
return gray;
}
static bool_t i_rgba_has_alpha(Gdiplus::Bitmap *bitmap)
{
bool_t alpha = FALSE;
const uint32_t bpp = 4;
Gdiplus::PixelFormat pf = bitmap->GetPixelFormat();
Gdiplus::BitmapData data;
const byte_t *src = NULL;
UINT i, j;
cassert(pf == PixelFormat32bppARGB);
bitmap->LockBits(NULL, Gdiplus::ImageLockModeRead, pf, &data);
src = cast_const(data.Scan0, byte_t);
for (j = 0; j < data.Height && alpha == FALSE; ++j)
{
for (i = 0; i < data.Width; ++i)
{
if (src[3] != 255)
{
alpha = TRUE;
break;
}
src += bpp;
}
src += data.Stride - (data.Width * bpp);
}
bitmap->UnlockBits(&data);
return alpha;
}
static Pixbuf *i_rgb24_pixels(Gdiplus::Bitmap *bitmap, const pixformat_t format)
{
Pixbuf *pixels = NULL;
Gdiplus::PixelFormat pf = bitmap->GetPixelFormat();
Gdiplus::BitmapData data;
uint32_t bpp = (uint32_t)((pf == PixelFormat24bppRGB) ? 3 : 4);
const byte_t *src = NULL;
byte_t *dest = NULL;
UINT i, j;
cassert(format == ekGRAY8 || format == ekRGB24);
bitmap->LockBits(NULL, Gdiplus::ImageLockModeRead, pf, &data);
pixels = pixbuf_create((uint32_t)data.Width, (uint32_t)data.Height, format);
src = cast_const(data.Scan0, byte_t);
dest = pixbuf_data(pixels);
if (format == ekRGB24)
{
for (j = 0; j < data.Height; ++j)
{
for (i = 0; i < data.Width; ++i)
{
dest[0] = src[2];
dest[1] = src[1];
dest[2] = src[0];
dest += 3;
src += bpp;
}
src += data.Stride - (data.Width * bpp);
}
}
else
{
cassert(format == ekGRAY8);
for (j = 0; j < data.Height; ++j)
{
for (i = 0; i < data.Width; ++i)
{
dest[0] = src[0];
cassert(dest[0] == src[1]);
cassert(dest[0] == src[2]);
dest += 1;
src += bpp;
}
src += data.Stride - (data.Width * bpp);
}
}
bitmap->UnlockBits(&data);
return pixels;
}
static Pixbuf *i_rgba32_pixels(Gdiplus::Bitmap *bitmap, const pixformat_t format)
{
Pixbuf *pixels = NULL;
Gdiplus::PixelFormat pf = bitmap->GetPixelFormat();
Gdiplus::BitmapData data;
const byte_t *src = NULL;
byte_t *dest = NULL;
UINT i, j;
bitmap->LockBits(NULL, Gdiplus::ImageLockModeRead, pf, &data);
pixels = pixbuf_create((uint32_t)data.Width, (uint32_t)data.Height, format);
src = cast_const(data.Scan0, byte_t);
dest = pixbuf_data(pixels);
if (format == ekRGBA32)
{
for (j = 0; j < data.Height; ++j)
{
for (i = 0; i < data.Width; ++i)
{
*cast(dest, uint32_t) = ABGR(src[2], src[1], src[0], src[3]);
src += 4;
dest += 4;
}
src += data.Stride - (data.Width * 4);
}
}
else if (format == ekRGB24)
{
for (j = 0; j < data.Height; ++j)
{
for (i = 0; i < data.Width; ++i)
{
dest[0] = src[2];
dest[1] = src[1];
dest[2] = src[0];
dest += 3;
src += 4;
}
src += data.Stride - (data.Width * 4);
}
}
else
{
cassert(format == ekGRAY8);
for (j = 0; j < data.Height; ++j)
{
for (i = 0; i < data.Width; ++i)
{
dest[0] = src[0];
cassert(dest[0] == src[1]);
cassert(dest[0] == src[2]);
dest += 1;
src += 4;
}
src += data.Stride - (data.Width * 4);
}
}
bitmap->UnlockBits(&data);
return pixels;
}
void osimage_info(const OSImage *image, uint32_t *width, uint32_t *height, pixformat_t *format, Pixbuf **pixels)
{
Gdiplus::Bitmap *bitmap = NULL;
cassert_no_null(image);
bitmap = image->bitmap;
if (width != NULL)
*width = (uint32_t)bitmap->GetWidth();
if (height != NULL)
*height = (uint32_t)bitmap->GetHeight();
if (format != NULL || pixels != NULL)
{
Gdiplus::PixelFormat pf = bitmap->GetPixelFormat();
pixformat_t lformat = ENUM_MAX(pixformat_t);
switch (pf)
{
case PixelFormat1bppIndexed:
case PixelFormat4bppIndexed:
case PixelFormat8bppIndexed:
i_indexed_info(bitmap, &lformat, pixels);
break;
case PixelFormat24bppRGB:
case PixelFormat32bppRGB:
if (i_rgb_is_gray(bitmap, (uint32_t)((pf == PixelFormat24bppRGB) ? 3 : 4)) == TRUE)
lformat = ekGRAY8;
else
lformat = ekRGB24;
if (pixels != NULL)
*pixels = i_rgb24_pixels(bitmap, lformat);
break;
case PixelFormat32bppARGB:
if (i_rgba_has_alpha(bitmap) == TRUE)
lformat = ekRGBA32;
else if (i_rgb_is_gray(bitmap, 4) == TRUE)
lformat = ekGRAY8;
else
lformat = ekRGB24;
if (pixels != NULL)
*pixels = i_rgba32_pixels(bitmap, lformat);
break;
cassert_default();
}
if (format != NULL)
*format = lformat;
}
}
static bool_t i_get_encoder(const codec_t codec, CLSID *clsid)
{
const WCHAR *encoder_mime = NULL;
bool_t found = FALSE;
switch (codec)
{
case ekJPG:
encoder_mime = L"image/jpeg";
break;
case ekPNG:
encoder_mime = L"image/png";
break;
case ekBMP:
encoder_mime = L"image/bmp";
break;
case ekGIF:
encoder_mime = L"image/gif";
break;
cassert_default();
}
if (encoder_mime != NULL)
{
UINT num = 0;
UINT size = 0;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size != 0)
{
Gdiplus::ImageCodecInfo *pImageCodecInfo = cast(heap_malloc((uint32_t)size, "ImageImpEncoder"), Gdiplus::ImageCodecInfo);
Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
for (UINT i = 0; i < num; ++i)
{
if (wcscmp(pImageCodecInfo[i].MimeType, encoder_mime) == 0)
{
*clsid = pImageCodecInfo[i].Clsid;
found = TRUE;
break;
}
}
heap_free(dcast(&pImageCodecInfo, byte_t), (uint32_t)size, "ImageImpEncoder");
}
}
return found;
}
bool_t osimage_available_codec(const OSImage *image, const codec_t codec)
{
CLSID clsid;
unref(image);
return i_get_encoder(codec, &clsid);
}
void osimage_write(const OSImage *image, const codec_t codec, Stream *stm)
{
Gdiplus::Bitmap *bitmap = NULL;
CLSID clsid;
IStream *stream = NULL;
Gdiplus::Status status = Gdiplus::NotImplemented;
uint32_t size = 0;
byte_t *data = NULL;
cassert_no_null(image);
bitmap = image->bitmap;
if (i_get_encoder(codec, &clsid) == FALSE)
return;
stream = i_kSHCreateMemStream(NULL, 0);
status = bitmap->Save(stream, &clsid, NULL);
cassert_unref(status == Gdiplus::Ok, status);
{
STATSTG stats;
stream->Stat(&stats, 0);
cassert(stats.cbSize.QuadPart <= 0xFFFFFFFF);
size = (uint32_t)stats.cbSize.QuadPart;
}
data = heap_malloc(size, "ImgEncoded");
{
LARGE_INTEGER offset;
HRESULT res;
offset.QuadPart = 0L;
res = stream->Seek(offset, SEEK_SET, NULL);
cassert_unref(res == S_OK, res);
}
{
ULONG readed;
HRESULT res;
res = stream->Read(cast(data, void), (ULONG)size, &readed);
cassert_unref(res == S_OK, res);
cassert(readed == (ULONG)size);
}
stm_write(stm, data, size);
heap_free(&data, size, "ImgEncoded");
stream->Release();
}
void osimage_frames(const OSImage *image, uint32_t *num_frames, uint32_t *num_loops)
{
Gdiplus::Bitmap *bitmap = NULL;
GUID dimension;
cassert_no_null(image);
cassert_no_null(num_frames);
unref(num_loops);
bitmap = image->bitmap;
bitmap->GetFrameDimensionsList(&dimension, 1);
*num_frames = (uint32_t)bitmap->GetFrameCount(&dimension);
}
void osimage_frame(const OSImage *image, const uint32_t frame_index, real32_t *frame_length)
{
Gdiplus::Bitmap *bitmap = NULL;
UINT property_size;
Gdiplus::PropertyItem *property_item = NULL;
cassert_no_null(image);
cassert_no_null(frame_length);
bitmap = image->bitmap;
property_size = bitmap->GetPropertyItemSize(PropertyTagFrameDelay);
property_item = (Gdiplus::PropertyItem *)heap_malloc(property_size, "ImgImpPropItem");
bitmap->GetPropertyItem(PropertyTagFrameDelay, property_size, property_item);
cassert(property_item->type == PropertyTagTypeLong);
*frame_length = 0.01f * (real32_t)(cast(property_item->value, long)[frame_index]);
heap_free(dcast(&property_item, byte_t), property_size, "ImgImpPropItem");
}
const void *osimage_native(const OSImage *osimage)
{
cassert_no_null(osimage);
return osimage->bitmap;
}