/* Miranda SmileyAdd Plugin Copyright (C) 2008 - 2011 Boris Krasnovskiy 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 version 2 of the License. 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, see . */ #include "imagecache.h" #include "options.h" typedef BOOL (WINAPI *tAlphaBlend)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION); static tAlphaBlend pAlphaBlend; static FI_INTERFACE *fei; static HANDLE g_hMutexIm; static OBJLIST g_imagecache(25, ImageType::CompareImg); static bkstring lastdllname; static HMODULE lastmodule; static time_t laststamp; static UINT_PTR timerId; static void CALLBACK timerProc(HWND, UINT, UINT_PTR, DWORD) { WaitForSingleObject(g_hMutexIm, 3000); const time_t ts = time(NULL) - 10; if ( lastmodule && ts > laststamp) { FreeLibrary(lastmodule); lastmodule = NULL; lastdllname.clear(); } for (int i=g_imagecache.getCount(); i--; ) g_imagecache[i].ProcessTimerTick(ts); if (g_imagecache.getCount() == 0) { g_imagecache.destroy(); if (timerId && (timerId+1) && lastmodule == NULL) { KillTimer(NULL, timerId); timerId = 0; } } ReleaseMutex(g_hMutexIm); } static void CALLBACK sttMainThreadCallback( PVOID ) { if (timerId == 0xffffffff) timerId = SetTimer(NULL, 0, 10000, (TIMERPROC)timerProc); } static HMODULE LoadDll(const bkstring& file) { WaitForSingleObject(g_hMutexIm, 3000); if (lastdllname != file) { FreeLibrary(lastmodule); lastdllname = file; lastmodule = LoadLibraryEx(file.c_str(), NULL, LOAD_LIBRARY_AS_DATAFILE); } laststamp = time(NULL); ReleaseMutex(g_hMutexIm); return lastmodule; } ImageBase::ImageBase(unsigned id) { m_id = id; m_lRefCount = 1; m_timestamp = 0; } long ImageBase::AddRef(void) { WaitForSingleObject(g_hMutexIm, 3000); long cnt = ++m_lRefCount; ReleaseMutex(g_hMutexIm); return cnt; } long ImageBase::Release(void) { WaitForSingleObject(g_hMutexIm, 3000); long cnt = m_lRefCount; if (cnt) m_lRefCount = --cnt; if (cnt == 0) m_timestamp = time(NULL); ReleaseMutex(g_hMutexIm); return cnt; } void ImageBase::ProcessTimerTick(time_t ts) { WaitForSingleObject(g_hMutexIm, 3000); if (m_lRefCount == 0 && m_timestamp < ts ) { if (!g_imagecache.remove(this)) delete this; } ReleaseMutex(g_hMutexIm); } int ImageBase::CompareImg(const ImageBase* p1, const ImageBase* p2) { unsigned id1 = p1->m_id; unsigned id2 = p2->m_id; if (id1 == id2) return 0; else return id1 < id2 ? -1 : 1; } void ImageBase::Draw(HDC hdc, RECT& rc, bool clip) { HRGN hrgn = NULL; if (clip) { hrgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); SelectClipRgn(hdc, hrgn); } SIZE iSize; GetSize(iSize); const int sizeX = rc.right - rc.left; const int sizeY = rc.bottom - rc.top; const int x = rc.left + (sizeX > iSize.cx || clip ? (sizeX - iSize.cx) / 2 : 0); const int y = rc.top + (sizeY > iSize.cy || clip ? (sizeY - iSize.cy) / 2 : 0); const int scaleX = sizeX > iSize.cx || clip ? iSize.cx : sizeX; const int scaleY = sizeY > iSize.cy || clip ? iSize.cy : sizeY; DrawInternal(hdc, x, y, scaleX, scaleY); if (clip) { SelectClipRgn(hdc, NULL); DeleteObject(hrgn); } } HBITMAP ImageBase::GetBitmap(COLORREF bkgClr, int sizeX, int sizeY) { RECT rc = { 0, 0, sizeX, sizeY }; if (sizeX == 0 || sizeY == 0) { SIZE iSize; GetSize(iSize); if (sizeX == 0) rc.right = iSize.cx; if (sizeY == 0) rc.bottom = iSize.cy; } HBRUSH hBkgBrush = CreateSolidBrush(bkgClr); HDC hdc = GetDC(NULL); HBITMAP hBmp = CreateCompatibleBitmap(hdc, rc.right, rc.bottom); HDC hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hBmp); FillRect(hdcMem, &rc, hBkgBrush); Draw(hdcMem, rc, false); DeleteDC(hdcMem); ReleaseDC(NULL, hdc); DeleteObject(hBkgBrush); return hBmp; } int ImageBase::SelectNextFrame(const int frame) { int res = (frame + 1) % GetFrameCount(); SelectFrame(res); return res; } IconType::IconType(const unsigned id, const bkstring& file, const int index, const IcoTypeEnum type) : ImageBase(id) { m_SmileyIcon = NULL; switch (type) { case icoDll: { const HMODULE hModule = LoadDll(file); if (hModule != NULL) m_SmileyIcon = (HICON) LoadImage(hModule, MAKEINTRESOURCE(-index), IMAGE_ICON, 0, 0, 0); } break; case icoFile: m_SmileyIcon = (HICON) LoadImage(NULL, file.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE); break; default: ExtractIconEx(file.c_str(), index, NULL, &m_SmileyIcon, 1); break; } } IconType::~IconType() { DestroyIcon(m_SmileyIcon); } void IconType::DrawInternal(HDC hdc, int x, int y, int sizeX, int sizeY) { if (m_SmileyIcon != NULL) DrawIconEx(hdc, x, y, m_SmileyIcon, sizeX, sizeY, 0, NULL, DI_NORMAL); } HICON IconType::GetIcon(void) { return (HICON)CopyImage(m_SmileyIcon, IMAGE_ICON, 0, 0, 0); } void IconType::GetSize(SIZE& size) { if (m_SmileyIcon != NULL) { ICONINFO ii; BITMAP bm; GetIconInfo(m_SmileyIcon, &ii); GetObject(ii.hbmColor, sizeof(bm), &bm); size.cx = bm.bmWidth; size.cy = bm.bmHeight; DeleteObject(ii.hbmMask); DeleteObject(ii.hbmColor); } } ImageListItemType::ImageListItemType(const unsigned id, HIMAGELIST hImList, int index) : ImageBase(id) { m_index = index; m_hImList = hImList; } void ImageListItemType::DrawInternal(HDC hdc, int x, int y, int sizeX, int sizeY) { SIZE iSize; GetSize(iSize); if (sizeX >= iSize.cx && sizeY >= iSize.cy) ImageList_Draw(m_hImList, m_index, hdc, x, y, ILD_TRANSPARENT); else { HICON hIcon = ImageList_GetIconFixed(m_hImList, m_index, ILD_TRANSPARENT); DrawIconEx(hdc, x, y, hIcon, sizeX, sizeY, 0, NULL, DI_NORMAL); DestroyIcon(hIcon); } } HICON ImageListItemType::GetIcon(void) { return ImageList_GetIconFixed(m_hImList, m_index, ILD_TRANSPARENT); } void ImageListItemType::GetSize(SIZE& size) { ImageList_GetIconSize(m_hImList, (int*)&size.cx, (int*)&size.cy); } ImageType::ImageType(const unsigned id, const bkstring& file, IStream* pStream) : ImageBase(id) { m_bmp = NULL; m_pPropertyItem = NULL; m_nCurrentFrame = 0; m_nFrameCount = 0; if (!InitGdiPlus()) return; if (pStream) m_bmp = new Gdiplus::Bitmap(pStream); else m_bmp = new Gdiplus::Bitmap(T2W_SM(file.c_str())); if (m_bmp->GetLastStatus() != Gdiplus::Ok) { delete m_bmp; m_bmp = NULL; return; } GUID pageGuid = Gdiplus::FrameDimensionTime; m_nFrameCount = m_bmp->GetFrameCount(&pageGuid); if (IsAnimated()) { int nSize = m_bmp->GetPropertyItemSize(PropertyTagFrameDelay); m_pPropertyItem = (Gdiplus::PropertyItem*) new char[nSize]; m_bmp->GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem); } } ImageType::ImageType(const unsigned id, const bkstring& file, const int index, const IcoTypeEnum type) : ImageBase(id) { m_bmp = NULL; m_pPropertyItem = NULL; m_nCurrentFrame = 0; m_nFrameCount = 0; if (!InitGdiPlus()) return; switch (type) { case icoDll: { const HMODULE hModule = LoadDll(file); if (hModule != NULL) { HICON hIcon = (HICON) LoadImage(hModule, MAKEINTRESOURCE(-index), IMAGE_ICON, 0, 0, 0); m_bmp = new Gdiplus::Bitmap(hIcon); DestroyIcon(hIcon); } } break; case icoFile: m_bmp = new Gdiplus::Bitmap(T2W_SM(file.c_str())); break; default: HICON hIcon = NULL; ExtractIconEx(file.c_str(), index, NULL, &hIcon, 1); m_bmp = new Gdiplus::Bitmap(hIcon); DestroyIcon(hIcon); break; } if (m_bmp->GetLastStatus() != Gdiplus::Ok) { delete m_bmp; m_bmp = NULL; return; } } ImageType::~ImageType(void) { if (m_pPropertyItem) delete[] m_pPropertyItem; if (m_bmp) delete m_bmp; } void ImageType::SelectFrame(int frame) { if ((unsigned)frame >= (unsigned)m_nFrameCount) frame = 0; if (IsAnimated() && frame != m_nCurrentFrame) { m_nCurrentFrame = frame; GUID pageGuid = Gdiplus::FrameDimensionTime; m_bmp->SelectActiveFrame(&pageGuid, frame); } } void ImageType::DrawInternal(HDC hdc, int x, int y, int sizeX, int sizeY) { if (m_bmp == NULL) return; WaitForSingleObject(g_hMutexIm, 3000); { Gdiplus::Graphics grp(hdc); // if (opt.HQScaling) grp.SetInterpolationMode(Gdiplus::InterpolationModeBicubic); grp.DrawImage(m_bmp, x, y, sizeX, sizeY); } ReleaseMutex(g_hMutexIm); } int ImageType::GetFrameDelay(void) const { return ((long*) m_pPropertyItem->value)[m_nCurrentFrame]; } HICON ImageType::GetIcon(void) { if (m_bmp == NULL) return NULL; HICON hIcon = NULL; WaitForSingleObject(g_hMutexIm, 3000); m_bmp->GetHICON(&hIcon); ReleaseMutex(g_hMutexIm); return hIcon; } void ImageType::GetSize(SIZE& size) { if (m_bmp) { size.cx = m_bmp->GetWidth(); size.cy = m_bmp->GetHeight(); } else { size.cx = 0; size.cy = 0; } } ImageFType::ImageFType(const unsigned id) : ImageBase(id) { m_bmp = NULL; } ImageFType::ImageFType(const unsigned id, const bkstring& file) : ImageBase(id) { m_bmp = NULL; FREE_IMAGE_FORMAT fif = fei->FI_GetFileTypeT(file.c_str(), 0); if (fif == FIF_UNKNOWN) fif = fei->FI_GetFIFFromFilenameT(file.c_str()); if (fif == FIF_UNKNOWN) return; FIBITMAP *dib = fei->FI_LoadT(fif, file.c_str(), 0); if (dib == NULL) return; bool transp = fei->FI_IsTransparent(dib) != 0; FREE_IMAGE_TYPE imt = fei->FI_GetImageType(dib); unsigned bpp = fei->FI_GetBPP(dib); if (transp && bpp != 32 || imt == FIT_RGBA16) { FIBITMAP *tdib = fei->FI_ConvertTo32Bits(dib); fei->FI_Unload(dib); dib = tdib; } else if (!transp && bpp > 24) { FIBITMAP *tdib = fei->FI_ConvertTo24Bits(dib); fei->FI_Unload(dib); dib = tdib; } m_bmp = fei->FI_CreateHBITMAPFromDIB(dib); fei->FI_Unload(dib); if (transp) fei->FI_Premultiply(m_bmp); } ImageFType::~ImageFType() { DeleteObject(m_bmp); } void ImageFType::DrawInternal(HDC hdc, int x, int y, int sizeX, int sizeY) { if (m_bmp == NULL) return; HDC hdcImg = CreateCompatibleDC(hdc); HBITMAP oldBmp = (HBITMAP) SelectObject(hdcImg, m_bmp); BITMAP bm; GetObject(m_bmp, sizeof(bm), &bm); if (bm.bmBitsPixel == 32 && pAlphaBlend) { BLENDFUNCTION bf = {0}; bf.SourceConstantAlpha = 255; bf.AlphaFormat = AC_SRC_ALPHA; pAlphaBlend(hdc, x, y, sizeX, sizeY, hdcImg, 0, 0, bm.bmWidth, bm.bmHeight, bf); } else { BitBlt(hdc, x, y, sizeX, sizeY, hdcImg, 0, 0, SRCCOPY); } SelectObject(hdcImg, oldBmp); DeleteDC(hdcImg); } HICON ImageFType::GetIcon(void) { if (m_bmp == NULL) return NULL; HICON hIcon; BITMAP bm; GetObject(m_bmp, sizeof(bm), &bm); ICONINFO ii; ii.fIcon = TRUE; ii.xHotspot = 0; ii.yHotspot = 0; if (bm.bmBitsPixel == 32 && GetWinVer() < 0x0501) { int slen = bm.bmWidth * 4; int len = bm.bmHeight * slen; BYTE* p = (BYTE*)mir_alloc(len); BYTE* maskBits = (BYTE*)mir_calloc(len); BYTE* colorBits = (BYTE*)mir_calloc(len); GetBitmapBits(m_bmp, len, p); for (int y = 0; y < bm.bmHeight; ++y) { int shift = y * slen; BYTE *px = p + shift; BYTE *color = colorBits + shift; BYTE *mask = maskBits + shift; for (int x = 0; x < bm.bmWidth; ++x) { for(int i = 0; i < 4; i++) { mask[i] = px[3]; color[i] = px[i]; } px += 4; mask += 4; color += 4; } } ii.hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 32, maskBits); ii.hbmColor = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 32, colorBits); hIcon = CreateIconIndirect(&ii); DeleteObject(ii.hbmMask); DeleteObject(ii.hbmColor); mir_free(p); mir_free(maskBits); mir_free(colorBits); } else { ii.hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL); ii.hbmColor = m_bmp; hIcon = CreateIconIndirect(&ii); DeleteObject(ii.hbmMask); } return hIcon; } void ImageFType::GetSize(SIZE& size) { if (m_bmp) { BITMAP bm; GetObject(m_bmp, sizeof(bm), &bm); size.cx = bm.bmWidth; size.cy = bm.bmHeight; } else { size.cx = 0; size.cy = 0; } } /* ImageFAniType::ImageFAniType(const unsigned id, const bkstring& file) : ImageFType(id) { m_fmbmp = NULL; m_nCurrentFrame = -1; m_FrameDelay = NULL; FREE_IMAGE_FORMAT fif = fei->FI_GetFileTypeT(file.c_str(), 0); if (fif == FIF_UNKNOWN) fif = fei->FI_GetFIFFromFilenameT(file.c_str()); m_fmbmp = fei->FI_OpenMultiBitmap(fif, T2A_SM(file.c_str()), FALSE, TRUE, TRUE, GIF_PLAYBACK); if (m_fmbmp == NULL) return; m_nFrameCount = fei->FI_GetPageCount(m_fmbmp); m_bmpl = (HBITMAP*)mir_calloc(m_nFrameCount*sizeof(HBITMAP)); m_FrameDelay = (int*)mir_calloc(m_nFrameCount*sizeof(int)); SelectFrame(0); } ImageFAniType::~ImageFAniType() { if (m_fmbmp) fei->FI_CloseMultiBitmap(m_fmbmp, 0); for (int i=0; i= (unsigned)m_nFrameCount) frame = 0; if (frame == m_nCurrentFrame) return; m_nCurrentFrame = frame; if (m_bmpl[frame]) { m_bmp = m_bmpl[frame]; return; } FITAG *tag = NULL; FIBITMAP *dib = fei->FI_LockPage(m_fmbmp, frame); if (dib == NULL) return; if (fei->FI_GetMetadata(FIMD_ANIMATION, dib, "FrameTime", &tag)) m_FrameDelay[frame] = *(LONG *)fei->FI_GetTagValue(tag) / 10; m_bmpl[frame] = m_bmp = fei->FI_CreateHBITMAPFromDIB(dib); if (fei->FI_IsTransparent(dib)) fei->FI_Premultiply(m_bmp); fei->FI_UnlockPage(m_fmbmp, dib, FALSE); } */ #pragma optimize("gt", on) // MurmurHash2, by Austin Appleby unsigned int __fastcall hash( const void * key, unsigned int len ) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const unsigned int m = 0x5bd1e995; const int r = 24; // Initialize the hash to a 'random' value unsigned int h = len; // Mix 4 bytes at a time into the hash const unsigned char * data = (const unsigned char *)key; while(len >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } // Handle the last few bytes of the input array switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; // Do a few final mixes of the hash to ensure the last few // bytes are well-incorporated. h ^= h >> 13; h *= m; h ^= h >> 15; return h; } #pragma optimize("", on) void InitImageCache(void) { g_hMutexIm = CreateMutex(NULL, FALSE, NULL); CallService(MS_IMG_GETINTERFACE, FI_IF_VERSION, (LPARAM) &fei); pAlphaBlend = (tAlphaBlend) GetProcAddress(GetModuleHandleA("gdi32"), "GdiAlphaBlend"); if (pAlphaBlend == NULL) pAlphaBlend = (tAlphaBlend) GetProcAddress(LoadLibraryA("msimg32"), "AlphaBlend"); } void DestroyImageCache(void) { WaitForSingleObject(g_hMutexIm, 3000); if (timerId) KillTimer(NULL, timerId); if (lastmodule) FreeLibrary(lastmodule); g_imagecache.destroy(); ReleaseMutex(g_hMutexIm); CloseHandle(g_hMutexIm); } ImageBase* AddCacheImage(const bkstring& file, int index) { bkstring tmpfile(file); tmpfile.appendfmt(_T("#%d"), index); unsigned id = hash(tmpfile.c_str(), (unsigned int)tmpfile.size() * sizeof(TCHAR)); WaitForSingleObject(g_hMutexIm, 3000); ImageBase srch(id); ImageBase *img = g_imagecache.find(&srch); if (img == NULL) { bkstring::size_type ind = file.find_last_of('.'); if (ind == file.npos) return NULL; bkstring ext = file.substr(ind+1); if (ext.comparei(_T("dll")) == 0 || ext.comparei(_T("exe")) == 0) img = opt.HQScaling ? (ImageBase*)new ImageType(id, file, index, icoDll) : (ImageBase*)new IconType(id, file, index, icoDll); else if (ext.comparei(_T("ico")) == 0) img = opt.HQScaling ? (ImageBase*)new ImageType(id, file, 0, icoFile) : (ImageBase*)new IconType(id, file, 0, icoFile); else if (ext.comparei(_T("icl")) == 0) img = opt.HQScaling ? (ImageBase*)new ImageType(id, file, index, icoIcl) : (ImageBase*)new IconType(id, file, index, icoIcl); else if (ext.comparei(_T("gif")) == 0) img = new ImageType(id, file, NULL); else if (fei == NULL || ext.comparei(_T("tif")) == 0 || ext.comparei(_T("tiff")) == 0) img = new ImageType(id, file, NULL); else img = opt.HQScaling ? (ImageBase*)new ImageType(id, file, NULL) : (ImageBase*)new ImageFType(id, file); g_imagecache.insert(img); if (timerId == 0) { timerId = 0xffffffff; CallFunctionAsync(sttMainThreadCallback, NULL); } } else img->AddRef(); ReleaseMutex(g_hMutexIm); return img; }