/* Miranda NG: the free IM client for Microsoft* Windows* Copyright (с) 2012-17 Miranda NG project (https://miranda-ng.org), Copyright (c) 2000-09 Miranda ICQ/IM project, This file is part of Send Screenshot Plus, a Miranda IM plugin. Copyright (c) 2010 Ing.U.Horn Parts of this file based on original sorce code (c) 2004-2006 Sérgio Vieira Rolanski (portet from Borland C++) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "stdafx.h" ///////////////////////////////////////////////////////////////////////////////////////// // Workaround for MS bug ComboBox_SelectItemData int ComboBox_SelectItemData(HWND hwndCtl, LPARAM data) { int i = 0; for (i; i < ComboBox_GetCount(hwndCtl); i++) { if (data == ComboBox_GetItemData(hwndCtl, i)) { ComboBox_SetCurSel(hwndCtl, i); return i; } } return CB_ERR; } ///////////////////////////////////////////////////////////////////////////////////////// // MonitorInfoEnum static BOOL CALLBACK MonitorInfoEnumProc(HMONITOR hMonitor, HDC, LPRECT, LPARAM dwData) { MONITORS* monitors = (MONITORS*)dwData; ++monitors->count; monitors->info = (MONITORINFOEX*)mir_realloc(monitors->info, sizeof(MONITORINFOEX)*monitors->count); monitors->info[monitors->count - 1].cbSize = sizeof(MONITORINFOEX); if (!GetMonitorInfo(hMonitor, (LPMONITORINFO)(monitors->info + monitors->count - 1))) return FALSE; // stop enumeration if error return TRUE; } size_t MonitorInfoEnum(MONITORINFOEX* &myMonitors, RECT &virtualScreen) { MONITORS tmp = { 0, nullptr }; if (EnumDisplayMonitors(nullptr, nullptr, MonitorInfoEnumProc, (LPARAM)&tmp)) { myMonitors = tmp.info; memset(&virtualScreen, 0, sizeof(virtualScreen)); for (size_t i = 0; i < tmp.count; ++i) { UnionRect(&virtualScreen, &virtualScreen, &tmp.info[i].rcMonitor); } return tmp.count; } mir_free(tmp.info); return 0; } FIBITMAP* CreateDIBFromDC(HDC hDC, const RECT* rect, HWND hCapture = nullptr); ///////////////////////////////////////////////////////////////////////////////////////// // capture window as FIBITMAP - caller must FIP->FI_Unload(dib) FIBITMAP* CaptureWindow(HWND hCapture, BOOL bClientArea, BOOL bIndirectCapture) { FIBITMAP* dib; HWND hForegroundWin; RECT rect;//cropping rect if (!hCapture || !IsWindow(hCapture)) return nullptr; hForegroundWin = GetForegroundWindow(); // old foreground window SetForegroundWindow(hCapture); // force target foreground BringWindowToTop(hCapture); // bring it to top as well /// redraw window to prevent runtime artifacts in picture UpdateWindow(hCapture); HWND hParent = GetAncestor(hCapture, GA_PARENT); if (hParent && !IsChild(hParent, hCapture)) hParent = nullptr; if (bIndirectCapture) { intptr_t wastopmost = GetWindowLongPtr(hCapture, GWL_EXSTYLE)&WS_EX_TOPMOST; if (!wastopmost) SetWindowPos(hCapture, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); if (bClientArea) { GetClientRect(hCapture, &rect); ClientToScreen(hCapture, (POINT*)&rect); rect.right += rect.left; rect.bottom += rect.top; } else GetWindowRect(hCapture, &rect); dib = CaptureMonitor(nullptr, &rect); if (!wastopmost) SetWindowPos(hCapture, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } else { HDC hDCsrc; GetWindowRect(hCapture, &rect); if (hParent) hDCsrc = GetDC(hCapture);//hCapture is part of a window, capture that else hDCsrc = GetWindowDC(hCapture);//entire window w/ title bar rect.right = ABS(rect.right - rect.left); rect.bottom = ABS(rect.bottom - rect.top); rect.left = rect.top = 0; // capture window and get FIBITMAP dib = CreateDIBFromDC(hDCsrc, &rect, hCapture); ReleaseDC(hCapture, hDCsrc); if (bClientArea) {//we could capture directly, but doing so breaks GetWindowRgn() and also includes artifacts... GetWindowRect(hCapture, &rect); RECT rectCA; GetClientRect(hCapture, &rectCA); ClientToScreen(hCapture, (POINT*)&rectCA); rectCA.left = ABS(rectCA.left - rect.left); rectCA.top = ABS(rectCA.top - rect.top); rectCA.right += rectCA.left; rectCA.bottom += rectCA.top; /// crop the window to ClientArea FIBITMAP* dibClient = FIP->FI_Copy(dib, rectCA.left, rectCA.top, rectCA.right, rectCA.bottom); FIP->FI_Unload(dib); dib = dibClient; } } if (hForegroundWin) {//restore previous foreground window SetForegroundWindow(hForegroundWin); BringWindowToTop(hForegroundWin); } return dib; } FIBITMAP* CaptureMonitor(const wchar_t* szDevice, const RECT* cropRect/*=NULL*/) { HDC hScrDC; RECT rect; /// get screen resolution if (!szDevice) { hScrDC = CreateDC(L"DISPLAY", nullptr, nullptr, nullptr); rect.left = GetSystemMetrics(SM_XVIRTUALSCREEN); rect.top = GetSystemMetrics(SM_YVIRTUALSCREEN); rect.right = GetSystemMetrics(SM_XVIRTUALSCREEN) + GetSystemMetrics(SM_CXVIRTUALSCREEN); rect.bottom = GetSystemMetrics(SM_YVIRTUALSCREEN) + GetSystemMetrics(SM_CYVIRTUALSCREEN); } else { hScrDC = CreateDC(szDevice, nullptr, nullptr, nullptr); rect.left = rect.top = 0; rect.right = GetDeviceCaps(hScrDC, HORZRES); rect.bottom = GetDeviceCaps(hScrDC, VERTRES); } if (cropRect) { if (cropRect->left > rect.left) rect.left = cropRect->left; if (cropRect->top > rect.top) rect.top = cropRect->top; if (cropRect->right < rect.right) rect.right = cropRect->right; if (cropRect->bottom < rect.bottom) rect.bottom = cropRect->bottom; } FIBITMAP *dib = CreateDIBFromDC(hScrDC, &rect); ReleaseDC(nullptr, hScrDC); return dib; } FIBITMAP* CreateDIBFromDC(HDC hDC, const RECT* rect, HWND hCapture/*=NULL*/) { long width = rect->right - rect->left; long height = rect->bottom - rect->top; // create a DC for the screen and create // a memory DC compatible to screen DC HDC hScrDC = hDC; if (!hScrDC) hScrDC = GetDC(hCapture); HDC hMemDC = CreateCompatibleDC(hScrDC); // create a bitmap compatible with the screen DC HBITMAP hBitmap = CreateCompatibleBitmap(hScrDC, width, height);//width,height // select new bitmap into memory DC SelectObject(hMemDC, hBitmap); if (hCapture && hDC) PrintWindow(hCapture, hMemDC, 0); else // bitblt screen DC to memory DC BitBlt(hMemDC, 0, 0, width, height, hScrDC, rect->left, rect->top, CAPTUREBLT | SRCCOPY); FIBITMAP *dib = FIP->FI_CreateDIBFromHBITMAP(hBitmap); // alpha channel from window is always wrong and sometimes even for desktop (Win7, no aero) // coz GDI do not draw all in alpha mode. // we have to create our own new alpha channel. bool bFixAlpha = true; bool bInvert = false; HBRUSH hBr = CreateSolidBrush(RGB(255, 255, 255));//Create a SolidBrush object for non transparent area HBITMAP hMask = CreateBitmap(width, height, 1, 1, nullptr);// Create monochrome (1 bit) B+W mask bitmap. HDC hMaskDC = CreateCompatibleDC(nullptr); SelectBitmap(hMaskDC, hMask); HRGN hRgn = CreateRectRgn(0, 0, 0, 0); if (hCapture && GetWindowRgn(hCapture, hRgn) == ERROR) { if ((GetWindowLongPtr(hCapture, GWL_EXSTYLE)&WS_EX_LAYERED)) { BYTE bAlpha = 0; COLORREF crKey = 0x00000000; DWORD dwFlags = 0; if (GetLayeredWindowAttributes(hCapture, &crKey, &bAlpha, &dwFlags)) { // per window transparency (like fading in a whole window) if ((dwFlags&LWA_COLORKEY)) { SetBkColor(hMemDC, crKey); BitBlt(hMaskDC, 0, 0, width, height, hMemDC, rect->left, rect->top, SRCCOPY); bInvert = true; } else if ((dwFlags&LWA_ALPHA)) { bFixAlpha = false; } } else { // per-pixel transparency (won't use the WM_PAINT) bFixAlpha = false; } } else { // not layered - fill the window region SetRectRgn(hRgn, 0, 0, width, height); FillRgn(hMaskDC, hRgn, hBr); } } else { if (!hCapture) SetRectRgn(hRgn, 0, 0, width, height);//client area only, no transparency FillRgn(hMaskDC, hRgn, hBr); } DeleteObject(hRgn); if (bFixAlpha) { FIBITMAP* dibMask = FIP->FI_CreateDIBFromHBITMAP(hMask); if (bInvert) FIP->FI_Invert(dibMask); FIBITMAP* dib8 = FIP->FI_ConvertTo8Bits(dibMask); // copy the dib8 alpha mask to dib32 main bitmap FIP->FI_SetChannel(dib, dib8, FICC_ALPHA); FIP->FI_Unload(dibMask); FIP->FI_Unload(dib8); } DeleteDC(hMaskDC); DeleteObject(hMask); DeleteObject(hBr); // clean up DeleteDC(hMemDC); DeleteObject(hBitmap); if (!hDC) ReleaseDC(nullptr, hScrDC); #ifdef _DEBUG switch (FIP->FI_GetImageType(dib)) { case FIT_UNKNOWN: OutputDebugStringA("FIBITMAP Type: FIT_UNKNOWN\r\n"); break; case FIT_BITMAP: OutputDebugStringA("FIBITMAP Type: FIT_BITMAP\r\n"); break; case FIT_UINT16: OutputDebugStringA("FIBITMAP Type: FIT_UINT16\r\n"); break; case FIT_INT16: OutputDebugStringA("FIBITMAP Type: FIT_INT16\r\n"); break; case FIT_UINT32: OutputDebugStringA("FIBITMAP Type: FIT_UINT32\r\n"); break; case FIT_INT32: OutputDebugStringA("FIBITMAP Type: FIT_INT32\r\n"); break; case FIT_FLOAT: OutputDebugStringA("FIBITMAP Type: FIT_FLOAT\r\n"); break; case FIT_DOUBLE: OutputDebugStringA("FIBITMAP Type: FIT_DOUBLE\r\n"); break; case FIT_COMPLEX: OutputDebugStringA("FIBITMAP Type: FIT_COMPLEX\r\n"); break; case FIT_RGB16: OutputDebugStringA("FIBITMAP Type: FIT_RGB16\r\n"); break; case FIT_RGBA16: OutputDebugStringA("FIBITMAP Type: FIT_RGBA16\r\n"); break; case FIT_RGBF: OutputDebugStringA("FIBITMAP Type: FIT_RGBF\r\n"); break; case FIT_RGBAF: OutputDebugStringA("FIBITMAP Type: FIT_RGBAF\r\n"); break; default: OutputDebugStringA("FIBITMAP Type: non detectable image type (error)\r\n"); break; } BOOL inf = FIP->FI_IsTransparent(dib); OutputDebugStringA(inf ? "FIBITMAP Transparent: true\r\n" : "FIBITMAP Transparent: false\r\n"); #endif return dib; } ///////////////////////////////////////////////////////////////////////////////////////// char* GetFileNameA(const wchar_t* pszPath) { const wchar_t* slash = wcsrchr(pszPath, '\\'); if (!slash) slash = wcsrchr(pszPath, '/'); if (slash) return mir_u2a(slash + 1); else return mir_u2a(pszPath); } ///////////////////////////////////////////////////////////////////////////////////////// BOOL GetEncoderClsid(wchar_t *wchMimeType, CLSID &clsidEncoder) { UINT uiNum = 0; UINT uiSize = 0; BOOL bOk = FALSE; Gdiplus::GetImageEncodersSize(&uiNum, &uiSize); if (uiSize > 0) { Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)mir_alloc(uiSize); if (pImageCodecInfo) { Gdiplus::GetImageEncoders(uiNum, uiSize, pImageCodecInfo); for (UINT i = 0; i < uiNum; ++i) { if (!mir_wstrcmp(pImageCodecInfo[i].MimeType, wchMimeType)) { clsidEncoder = pImageCodecInfo[i].Clsid; bOk = TRUE; } } mir_free(pImageCodecInfo); } } return bOk; } ///////////////////////////////////////////////////////////////////////////////////////// void SaveGIF(HBITMAP hBmp, const wchar_t *szFilename) { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); Gdiplus::Bitmap *pBitmap = Gdiplus::Bitmap::FromHBITMAP(hBmp, (HPALETTE)GetStockObject(DEFAULT_PALETTE)); if (pBitmap) { // Get the CLSID of the GIF encoder. CLSID clsidEncoder; if (GetEncoderClsid(L"image/gif", clsidEncoder)) { LPWSTR pswFile = mir_wstrdup(szFilename); pBitmap->Save((const WCHAR*)pswFile, &clsidEncoder, nullptr); mir_free(pswFile); } delete pBitmap; } Gdiplus::GdiplusShutdown(gdiplusToken); } ///////////////////////////////////////////////////////////////////////////////////////// void SaveTIF(HBITMAP hBmp, const wchar_t *szFilename) { //http://www.codeproject.com/Messages/1406708/How-to-reduce-the-size-of-an-Image-using-GDIplus.aspx ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::Status stat; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); Gdiplus::Bitmap *pBitmap = Gdiplus::Bitmap::FromHBITMAP(hBmp, (HPALETTE)GetStockObject(DEFAULT_PALETTE)); if (pBitmap) { // Get the CLSID of the GIF encoder. CLSID EncCLSID; if (GetEncoderClsid(L"image/tiff", EncCLSID)) { //--- Create a 2-parameter array, for Compression and for Color Bit depth Gdiplus::EncoderParameters* EncParams = (Gdiplus::EncoderParameters*) malloc(sizeof(Gdiplus::EncoderParameters) + 1 * sizeof(Gdiplus::EncoderParameter)); // Gdiplus::EncoderParameters pEncoderParameters; //--- Use LZW Compression instead of Group 4, since it works for color and G4 doesn't ULONG ulCompression = Gdiplus::EncoderValueCompressionLZW; ULONG ulColorDepth = 24L; EncParams->Count = 2; EncParams->Parameter[0].Guid = Gdiplus::EncoderCompression; EncParams->Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong; EncParams->Parameter[0].NumberOfValues = 1; EncParams->Parameter[0].Value = &ulCompression; EncParams->Parameter[1].Guid = Gdiplus::EncoderColorDepth; EncParams->Parameter[1].Type = Gdiplus::EncoderParameterValueTypeLong; EncParams->Parameter[1].NumberOfValues = 1; EncParams->Parameter[1].Value = &ulColorDepth; LPWSTR pswFile = mir_wstrdup(szFilename); stat = pBitmap->Save((const WCHAR*)pswFile, &EncCLSID, EncParams); mir_free(pswFile); free(EncParams); } delete pBitmap; } Gdiplus::GdiplusShutdown(gdiplusToken); }