ultimatepp/uppsrc/CtrlCore/ImageWin32.cpp

651 lines
17 KiB
C++

#include "CtrlCore.h"
#ifdef GUI_WIN
#include <shellapi.h>
namespace Upp {
#define LTIMING(x) // RTIMING(x)
#define LLOG(x) // DLOG(x)
bool ImageFallBack
// = true
;
class BitmapInfo32__ {
union {
BITMAPV5HEADER bi;
BITMAPINFOHEADER hi;
};
public:
operator BITMAPINFO *() { return (BITMAPINFO *)&bi; }
operator BITMAPINFOHEADER *() { return (BITMAPINFOHEADER *)&bi; }
BITMAPINFOHEADER *operator->() { return (BITMAPINFOHEADER *)&bi; }
BitmapInfo32__(int cx, int cy, bool surface = false);
};
BitmapInfo32__::BitmapInfo32__(int cx, int cy, bool surface)
{
if(surface) {
ZeroMemory(&hi, sizeof(BITMAPINFOHEADER));
hi.biSize = sizeof(BITMAPINFOHEADER);
hi.biPlanes = 1;
hi.biBitCount = 32;
hi.biCompression = BI_RGB;
hi.biSizeImage = 0;
hi.biClrUsed = 0;
hi.biClrImportant = 0;
hi.biWidth = cx;
hi.biHeight = -cy;
}
else {
ZeroMemory(&bi, sizeof(BITMAPV5HEADER));
bi.bV5Size = sizeof(BITMAPV5HEADER);
bi.bV5Width = cx;
bi.bV5Height = -cy;
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
bi.bV5RedMask = 0x00FF0000;
bi.bV5GreenMask = 0x0000FF00;
bi.bV5BlueMask = 0x000000FF;
bi.bV5AlphaMask = 0xFF000000;
bi.bV5CSType = LCS_sRGB;
}
}
HBITMAP CreateBitMask(const RGBA *data, Size sz, Size tsz, Size csz, RGBA *ct)
{
LTIMING("CreateBitMask");
GuiLock __;
Fill(ct, RGBAZero(), tsz.cx * tsz.cy);
int linelen = (tsz.cx + 15) >> 4 << 1;
Buffer<byte> mask(tsz.cy * linelen, 0xff);
byte *m = mask;
RGBA *ty = ct;
const RGBA *sy = data;
for(int y = 0; y < csz.cy; y++) {
const RGBA *s = sy;
RGBA *t = ty;
for(int x = 0; x < csz.cx; x++) {
if(s->a > 128) {
*t = *s;
m[x >> 3] &= ~(0x80 >> (x & 7));
}
else
t->r = t->g = t->b = 0;
t->a = 0;
t++;
s++;
}
m += linelen;
sy += sz.cx;
ty += tsz.cx;
}
return ::CreateBitmap(tsz.cx, tsz.cy, 1, 1, mask);
}
void SetSurface(HDC dc, const Rect& dest, const RGBA *pixels, Size srcsz, Point srcoff)
{
LTIMING("SetSurface");
GuiLock __;
BitmapInfo32__ bi(srcsz.cx, srcsz.cy, true);
::SetDIBitsToDevice(dc, dest.left, dest.top, dest.GetWidth(), dest.GetHeight(),
srcoff.x, -srcoff.y - dest.Height() + srcsz.cy, 0, srcsz.cy, pixels, bi,
DIB_RGB_COLORS);
}
void SetSurface(HDC dc, int x, int y, int cx, int cy, const RGBA *pixels)
{
SetSurface(dc, RectC(x, y, cx, cy), pixels, Size(cx, cy), Point(0, 0));
}
void SetSurface(SystemDraw& w, int x, int y, int cx, int cy, const RGBA *pixels)
{
SetSurface(w.GetHandle(), x, y, cx, cy, pixels);
}
void SetSurface(SystemDraw& w, const Rect& dest, const RGBA *pixels, Size psz, Point poff)
{
SetSurface(w.GetHandle(), dest, pixels, psz, poff);
}
class DrawSurface : NoCopy {
int x, y;
Size size;
RGBA *pixels;
HDC dc, dcMem;
HBITMAP hbmp, hbmpOld;
void Init(SystemDraw& w, int x, int y, int cx, int cy);
RGBA* Line(int i) const { ASSERT(i >= 0 && i < size.cy); return (RGBA *)pixels + size.cx * (size.cy - i - 1); }
public:
operator RGBA *() { return pixels; }
Size GetSize() const { return size; }
RGBA *operator[](int i) { return Line(i); }
const RGBA *operator[](int i) const { return Line(i); }
int GetLineDelta() const { return -size.cx; }
DrawSurface(SystemDraw& w, const Rect& r);
DrawSurface(SystemDraw& w, int x, int y, int cx, int cy);
~DrawSurface();
};
void DrawSurface::Init(SystemDraw& w, int _x, int _y, int cx, int cy)
{
LTIMING("DrawSurface");
GuiLock __;
dc = w.GetHandle();
size = Size(cx, cy);
x = _x;
y = _y;
dcMem = ::CreateCompatibleDC(dc);
BitmapInfo32__ bi(cx, cy, true);
hbmp = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (void **)&pixels, NULL, 0);
hbmpOld = (HBITMAP) ::SelectObject(dcMem, hbmp);
::BitBlt(dcMem, 0, 0, cx, cy, dc, x, y, SRCCOPY);
}
DrawSurface::DrawSurface(SystemDraw& w, const Rect& r)
{
Init(w, r.left, r.top, r.Width(), r.Height());
}
DrawSurface::DrawSurface(SystemDraw& w, int x, int y, int cx, int cy)
{
Init(w, x, y, cx, cy);
}
DrawSurface::~DrawSurface()
{
GuiLock __;
::BitBlt(dc, x, y, size.cx, size.cy, dcMem, 0, 0, SRCCOPY);
::DeleteObject(::SelectObject(dcMem, hbmpOld));
::DeleteDC(dcMem);
}
#ifndef PLATFORM_WINCE
typedef BOOL (WINAPI *tAlphaBlend)(HDC hdcDest, int nXOriginDest, int nYOriginDest,
int nWidthDest, int nHeightDest,
HDC hdcSrc, int nXOriginSrc, int nYOriginSrc,
int nWidthSrc, int nHeightSrc, BLENDFUNCTION blendFunction);
static tAlphaBlend fnAlphaBlend()
{
GuiLock __;
static tAlphaBlend pSet;
static bool inited = false;
if(!inited) {
inited = true;
if(HMODULE hDLL = LoadLibrary("msimg32.dll"))
pSet = (tAlphaBlend) GetProcAddress(hDLL, "AlphaBlend");
}
return pSet;
}
#endif
struct ImageSysData {
Image img;
HBITMAP hbmp;
HBITMAP hmask;
HBITMAP himg;
RGBA *section;
void Init(const Image& img);
void CreateHBMP(HDC dc, const RGBA *data);
void Paint(SystemDraw& w, int x, int y, const Rect& src, Color c);
~ImageSysData();
};
void ImageSysData::Init(const Image& _img)
{
img = _img;
hbmp = hmask = himg = NULL;
LLOG("ImageSysData::Init " << img.GetSerialId() << " " << img.GetSize());
}
ImageSysData::~ImageSysData()
{
SysImageReleased(img);
if(hbmp) {
GuiLock __;
DeleteObject(hbmp);
}
if(hmask) {
GuiLock __;
DeleteObject(hmask);
}
if(himg) {
GuiLock __;
DeleteObject(himg);
}
hbmp = hmask = himg = NULL;
img.Clear();
}
void ImageSysData::CreateHBMP(HDC dc, const RGBA *data)
{
LTIMING("CreateHBMP");
LLOG("Creating BMP for " << img.GetSerialId() << ' ' << img.GetSize());
Size sz = img.GetSize();
BitmapInfo32__ bi(sz.cx, sz.cy);
HDC dcMem = ::CreateCompatibleDC(dc);
RGBA *pixels;
HBITMAP hbmp32 = CreateDIBSection(dcMem, bi, DIB_RGB_COLORS, (void **)&pixels, NULL, 0);
HDC hbmpOld = (HDC) ::SelectObject(dcMem, hbmp32);
Copy(pixels, data, (int)img.GetLength());
HDC dcMem2 = ::CreateCompatibleDC(dc);
hbmp = ::CreateCompatibleBitmap(dc, sz.cx, sz.cy);
HBITMAP o2 = (HBITMAP)::SelectObject(dcMem2, hbmp);
::BitBlt(dcMem2, 0, 0, sz.cx, sz.cy, dcMem, 0, 0, SRCCOPY);
::SelectObject(dcMem2, o2);
::DeleteDC(dcMem2);
::SelectObject(dcMem, hbmpOld);
::DeleteObject(hbmp32);
::DeleteDC(dcMem);
}
void ImageSysData::Paint(SystemDraw& w, int x, int y, const Rect& src, Color c)
{
HDC dc = w.GetHandle();
Size sz = img.GetSize();
int len = sz.cx * sz.cy;
Rect sr = src & sz;
Size ssz = sr.Size();
if(sr.IsEmpty())
return;
if(fnAlphaBlend() && IsNull(c) && !ImageFallBack &&
!(w.IsPrinter() && (GetDeviceCaps(dc, SHADEBLENDCAPS) & (SB_PIXEL_ALPHA|SB_PREMULT_ALPHA)) !=
(SB_PIXEL_ALPHA|SB_PREMULT_ALPHA))) {
if(!himg) {
LTIMING("Image Alpha create");
BitmapInfo32__ bi(sz.cx, sz.cy);
himg = CreateDIBSection(ScreenHDC(), bi, DIB_RGB_COLORS, (void **)&section, NULL, 0);
if(!himg) return; // Return on release.
Copy(section, ~img, (int)img.GetLength());
}
LTIMING("Image Alpha blit");
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 255;
bf.AlphaFormat = AC_SRC_ALPHA;
HDC dcMem = w.GetCompatibleDC();
HBITMAP o = (HBITMAP)::SelectObject(dcMem, himg);
fnAlphaBlend()(dc, x, y, ssz.cx, ssz.cy, dcMem, sr.left, sr.top, ssz.cx, ssz.cy, bf);
::SelectObject(dcMem, o);
SysImageRealized(img);
}
else {
LTIMING("Image Alpha sw");
DrawSurface sf(w, x, y, ssz.cx, ssz.cy);
RGBA *t = sf;
if(w.IsPrinter()) // We have got here because printer does not support alpha blending
Fill(t, White(), ssz.cx * ssz.cy);
for(int i = sr.top; i < sr.bottom; i++) {
if(IsNull(c))
AlphaBlendOpaque(t, img[i] + sr.left, ssz.cx);
else
AlphaBlendOpaque(t, img[i] + sr.left, ssz.cx, c);
t += ssz.cx;
}
}
}
struct ImageSysDataMaker : LRUCache<ImageSysData, int64>::Maker {
Image img;
virtual int64 Key() const { return img.GetSerialId(); }
virtual int Make(ImageSysData& object) const { object.Init(img); return (int)img.GetLength(); }
};
void SystemDraw::SysDrawImageOp(int x, int y, const Image& _img, const Rect& src, Color color)
{
LLOG("SysDrawImageOp " << img.GetSerialId() << ' ' << img.GetSize());
GuiLock __;
Image img = _img;
if(img.GetLength() == 0)
return;
Size sz = img.GetSize();
int kind = img.GetKindNoScan();
if(kind == IMAGE_OPAQUE && !IsNull(color)) {
DrawRect(x, y, sz.cx, sz.cy, color);
return;
}
if(kind == IMAGE_OPAQUE && (!IsPrinter() || (GetDeviceCaps(GetHandle(), RASTERCAPS) & RC_DIBTODEV)) && src == img.GetSize()) {
LTIMING("Image Opaque direct set");
SetSurface(*this, x, y, sz.cx, sz.cy, ~img);
return;
}
if(!IsNull(color)) { // avoid SW emulation
img = CachedSetColorKeepAlpha(img, color);
color = Null;
}
ImageSysDataMaker m;
static LRUCache<ImageSysData, int64> cache;
static int Rsz;
Rsz += (int)img.GetLength();
if(Rsz > 200 * 200) { // we do not want to do this for each small image painted...
Rsz = 0;
cache.Remove([](const ImageSysData& object) { return object.img.GetRefCount() == 1; });
}
LLOG("SysImage cache pixels " << cache.GetSize() << ", count " << cache.GetCount());
sz = Ctrl::GetVirtualScreenArea().GetSize();
m.img = IsPrinter() && GetDeviceCaps(GetHandle(), NUMCOLORS) == 2 ? Dither(img, 360) : img; // If printer does not support color, dither
cache.Get(m).Paint(*this, x, y, src, color);
cache.Shrink(4 * sz.cx * sz.cy, 1000);
}
void ImageDraw::Section::Init(int cx, int cy)
{
GuiLock __;
dc = ::CreateCompatibleDC(ScreenHDC());
BitmapInfo32__ bi(cx, cy);
hbmp = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (void **)&pixels, NULL, 0);
hbmpOld = (HBITMAP) ::SelectObject(dc, hbmp);
}
ImageDraw::Section::~Section()
{
GuiLock __;
::DeleteObject(::SelectObject(dc, hbmpOld));
::DeleteDC(dc);
}
void ImageDraw::Init()
{
GuiLock __;
rgb.Init(size.cx, size.cy);
a.Init(size.cx, size.cy);
Attach(rgb.dc);
InitClip(size);
alpha.Attach(a.dc);
alpha.InitClip(size);
has_alpha = false;
}
Draw& ImageDraw::Alpha()
{
if(!has_alpha) {
alpha.DrawRect(size, GrayColor(0));
has_alpha = true;
}
return alpha;
}
Image ImageDraw::Get(bool pm) const
{
ImageBuffer b(size);
int n = size.cx * size.cy;
memcpy(~b, rgb.pixels, n * sizeof(RGBA));
const RGBA *s = a.pixels;
const RGBA *e = a.pixels + n;
RGBA *t = b;
if(has_alpha) {
while(s < e) {
t->a = s->r;
t++;
s++;
}
if(pm)
Premultiply(b);
b.SetKind(IMAGE_ALPHA);
}
else {
while(s < e) {
t->a = 255;
t++;
s++;
}
b.SetKind(IMAGE_OPAQUE);
}
return Image(b);
}
ImageDraw::operator Image() const
{
return Get(true);
}
Image ImageDraw::GetStraight() const
{
return Get(false);
}
ImageDraw::ImageDraw(Size sz)
{
size = sz;
Init();
}
ImageDraw::ImageDraw(int cx, int cy)
{
size = Size(cx, cy);
Init();
}
ImageDraw::~ImageDraw()
{
Detach();
alpha.Detach();
}
#ifdef PLATFORM_WINCE
Image Image::Arrow() { return Null; }
Image Image::Wait() { return Null; }
Image Image::IBeam() { return Null; }
Image Image::No() { return Null; }
Image Image::SizeAll() { return Null; }
Image Image::SizeHorz() { return Null; }
Image Image::SizeVert() { return Null; }
Image Image::SizeTopLeft() { return Null; }
Image Image::SizeTop() { return Null; }
Image Image::SizeTopRight() { return Null; }
Image Image::SizeLeft() { return Null; }
Image Image::SizeRight() { return Null; }
Image Image::SizeBottomLeft() { return Null; }
Image Image::SizeBottom() { return Null; }
Image Image::SizeBottomRight() { return Null; }
#else
static Image sWin32Icon(HICON icon, bool cursor)
{
GuiLock __;
ICONINFO iconinfo;
if(!icon || !GetIconInfo(icon, &iconinfo))
return Image();
BITMAP bm;
::GetObject((HGDIOBJ)iconinfo.hbmMask, sizeof(BITMAP), (LPVOID)&bm);
HDC dcMem = ::CreateCompatibleDC(NULL);
Size sz(bm.bmWidth, bm.bmHeight);
BitmapInfo32__ bi(sz.cx, sz.cy);
int len = sz.cx * sz.cy;
Buffer<RGBA> mask(len);
::SelectObject(dcMem, iconinfo.hbmColor);
::GetDIBits(dcMem, iconinfo.hbmMask, 0, sz.cy, ~mask, bi, DIB_RGB_COLORS);
ImageBuffer b(sz.cx, iconinfo.hbmColor ? sz.cy : sz.cy / 2);
b.SetHotSpot(Point(iconinfo.xHotspot, iconinfo.yHotspot));
if(iconinfo.hbmColor) {
::SelectObject(dcMem, iconinfo.hbmColor);
::GetDIBits(dcMem, iconinfo.hbmColor, 0, sz.cy, ~b, bi, DIB_RGB_COLORS);
RGBA *s = ~b;
RGBA *e = s + len;
RGBA *m = mask;
while(s < e) {
if(s->a != 255 && s->a != 0) {
Premultiply(b);
goto alpha;
}
s++;
}
s = ~b;
while(s < e) {
s->a = m->r ? 0 : 255;
s++;
m++;
}
}
else {
len /= 2;
RGBA *s = ~b;
RGBA *e = s + len;
RGBA *c = mask + len;
RGBA *m = mask;
while(s < e) {
s->a = (m->r & ~c->r) ? 0 : 255;
s->r = s->g = s->b = (c->r & ~m->r) ? 255 : 0;
s++;
m++;
c++;
}
}
alpha:
::DeleteDC(dcMem);
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
Image img(b);
::DestroyIcon(icon);
return img;
}
Image SystemDraw::Win32IconCursor(LPCSTR id, int iconsize, bool cursor)
{
HICON icon;
if(cursor)
icon = (HICON)LoadCursor(0, id);
else
if(iconsize)
icon = (HICON)LoadImage(GetModuleHandle(NULL), id,
IMAGE_ICON, iconsize, iconsize, LR_DEFAULTCOLOR);
else {
static HRESULT (WINAPI *LoadIconWithScaleDown)(HINSTANCE hinst, PCWSTR pszName, int cx, int cy, HICON *phico);
ONCELOCK {
DllFn(LoadIconWithScaleDown, "Comctl32.dll", "LoadIconWithScaleDown");
}
icon = NULL;
if(LoadIconWithScaleDown)
LoadIconWithScaleDown(0, (PCWSTR) id, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), &icon);
else
icon = LoadIcon(0, id);
}
Image img = sWin32Icon(icon, cursor);
if(cursor)
img.SetAuxData(reinterpret_cast<uint64>(id));
return img;
}
Image Win32DllIcon(const char *dll, int ii, bool large)
{
HICON icon;
if(ExtractIconEx(dll, ii, large ? &icon : NULL, large ? NULL : &icon, 1) == 1)
return sWin32Icon(icon, false);
return Null;
}
Image Win32Icon(LPCSTR id, int iconsize)
{
return SystemDraw::Win32IconCursor(id, iconsize, false);
}
Image Win32Icon(int id, int iconsize)
{
return Win32Icon(MAKEINTRESOURCE(id), iconsize);
}
Image Win32Cursor(LPCSTR id)
{
return SystemDraw::Win32IconCursor(id, 0, true);
}
Image Win32Cursor(int id)
{
return Win32Cursor(MAKEINTRESOURCE(id));
}
HICON SystemDraw::IconWin32(const Image& img_, bool cursor)
{
GuiLock __;
Image img = img_;
if(img.IsEmpty())
return NULL;
if(cursor) {
LPCSTR id = reinterpret_cast<LPCSTR>(img.GetAuxData());
if(id)
return (HICON)LoadCursor(0, id);
}
img = Unmultiply(img); // it seems like Win32 wants them unmultiplied...
Size sz = img.GetSize();
ICONINFO iconinfo;
iconinfo.fIcon = !cursor;
Point p = img.GetHotSpot();
iconinfo.xHotspot = p.x;
iconinfo.yHotspot = p.y;
Size tsz = sz;
Size csz = Size(min(tsz.cx, sz.cx), min(tsz.cy, sz.cy));
if(!ImageFallBack) {
RGBA *pixels;
BitmapInfo32__ bi(tsz.cx, tsz.cy);
HDC dcMem = ::CreateCompatibleDC(NULL);
iconinfo.hbmColor = ::CreateDIBSection(dcMem, bi, DIB_RGB_COLORS, (void **)&pixels, NULL, 0);
iconinfo.hbmMask = ::CreateBitmap(tsz.cx, tsz.cy, 1, 1, NULL);
Fill(pixels, RGBAZero(), tsz.cx * tsz.cy);
for(int y = 0; y < csz.cy; y++)
memcpy(pixels + y * tsz.cx, img[y], csz.cx * sizeof(RGBA));
::DeleteDC(dcMem);
}
else {
Buffer<RGBA> h(tsz.cx * tsz.cy);
HDC dc = ::GetDC(NULL);
BitmapInfo32__ bi(tsz.cx, tsz.cy);
iconinfo.hbmMask = CreateBitMask(~img, sz, tsz, csz, h);
iconinfo.hbmColor = ::CreateDIBitmap(dc, bi, CBM_INIT, h, bi, DIB_RGB_COLORS);
::ReleaseDC(NULL, dc);
}
HICON icon = ::CreateIconIndirect(&iconinfo);
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
return icon;
}
#define WCURSOR_(x)\
{ Image m; INTERLOCKED { static Image img = Win32Cursor(x); m = img; } return m; }
Image Image::Arrow() WCURSOR_(IDC_ARROW)
Image Image::Wait() WCURSOR_(IDC_WAIT)
Image Image::IBeam() WCURSOR_(IDC_IBEAM)
Image Image::No() WCURSOR_(IDC_NO)
Image Image::SizeAll() WCURSOR_(IDC_SIZEALL)
Image Image::SizeHorz() WCURSOR_(IDC_SIZEWE)
Image Image::SizeVert() WCURSOR_(IDC_SIZENS)
Image Image::SizeTopLeft() WCURSOR_(IDC_SIZENWSE)
Image Image::SizeTop() WCURSOR_(IDC_SIZENS)
Image Image::SizeTopRight() WCURSOR_(IDC_SIZENESW)
Image Image::SizeLeft() WCURSOR_(IDC_SIZEWE)
Image Image::SizeRight() WCURSOR_(IDC_SIZEWE)
Image Image::SizeBottomLeft() WCURSOR_(IDC_SIZENESW)
Image Image::SizeBottom() WCURSOR_(IDC_SIZENS)
Image Image::SizeBottomRight() WCURSOR_(IDC_SIZENWSE)
Image Image::Cross() WCURSOR_(IDC_CROSS)
Image Image::Hand() WCURSOR_(IDC_HAND)
#endif
}
#endif