Check for empty icon file, and catch all exceptions when trying to load an icon file
[TortoiseGit.git] / src / Utils / MiscUI / Picture.cpp
blob371fc8d2783f2dd4b2d0a37d5ea36388b1218d2c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include <olectl.h>
21 #include "shlwapi.h"
22 #include <locale>
23 #include <algorithm>
24 #include "Picture.h"
25 #include "SmartHandle.h"
27 #pragma comment(lib, "shlwapi.lib")
28 #pragma comment(lib, "gdiplus.lib")
30 #define HIMETRIC_INCH 2540
32 CPicture::CPicture()
33 : m_IPicture(NULL)
34 , m_Height(0)
35 , m_Weight(0)
36 , m_Width(0)
37 , pBitmap(NULL)
38 , bHaveGDIPlus(false)
39 , m_ip(InterpolationModeDefault)
40 , hIcons(NULL)
41 , lpIcons(NULL)
42 , nCurrentIcon(0)
43 , bIsIcon(false)
44 , bIsTiff(false)
45 , m_nSize(0)
46 , m_ColorDepth(0)
47 , hGlobal(NULL)
48 , gdiplusToken(NULL)
52 CPicture::~CPicture()
54 FreePictureData(); // Important - Avoid Leaks...
55 delete pBitmap;
56 if (bHaveGDIPlus)
57 GdiplusShutdown(gdiplusToken);
61 void CPicture::FreePictureData()
63 if (m_IPicture != NULL)
65 m_IPicture->Release();
66 m_IPicture = NULL;
67 m_Height = 0;
68 m_Weight = 0;
69 m_Width = 0;
70 m_nSize = 0;
72 if (hIcons)
74 LPICONDIR lpIconDir = (LPICONDIR)lpIcons;
75 if (lpIconDir)
77 for (int i=0; i<lpIconDir->idCount; ++i)
79 DestroyIcon(hIcons[i]);
82 delete [] hIcons;
83 hIcons = NULL;
85 delete [] lpIcons;
88 // Util function to ease loading of FreeImage library
89 static FARPROC s_GetProcAddressEx(HMODULE hDll, const char* procName, bool& valid)
91 FARPROC proc = NULL;
93 if (valid)
95 proc = GetProcAddress(hDll, procName);
97 if (!proc)
98 valid = false;
101 return proc;
104 tstring CPicture::GetFileSizeAsText(bool bAbbrev /* = true */)
106 TCHAR buf[100] = {0};
107 if (bAbbrev)
108 StrFormatByteSize(m_nSize, buf, _countof(buf));
109 else
110 _stprintf_s(buf, _T("%ld Bytes"), m_nSize);
112 return tstring(buf);
115 bool CPicture::Load(tstring sFilePathName)
117 bool bResult = false;
118 bIsIcon = false;
119 lpIcons = NULL;
120 //CFile PictureFile;
121 //CFileException e;
122 FreePictureData(); // Important - Avoid Leaks...
124 // No-op if no file specified
125 if (sFilePathName.empty())
126 return true;
128 // Load & initialize the GDI+ library if available
129 HMODULE hGdiPlusLib = LoadLibrary(_T("gdiplus.dll"));
130 if (hGdiPlusLib && GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) == Ok)
132 bHaveGDIPlus = true;
134 // Since we loaded the gdiplus.dll only to check if it's available, we
135 // can safely free the library here again - GdiplusStartup() loaded it too
136 // and reference counting will make sure that it stays loaded until GdiplusShutdown()
137 // is called.
138 FreeLibrary(hGdiPlusLib);
140 // Attempt to load using GDI+ if available
141 if (bHaveGDIPlus)
143 pBitmap = new Bitmap(sFilePathName.c_str(), FALSE);
144 GUID guid;
145 pBitmap->GetRawFormat(&guid);
147 if (pBitmap->GetLastStatus() != Ok)
149 delete pBitmap;
150 pBitmap = NULL;
153 // gdiplus only loads the first icon found in an icon file
154 // so we have to handle icon files ourselves :(
156 // Even though gdiplus can load icons, it can't load the new
157 // icons from Vista - in Vista, the icon format changed slightly.
158 // But the LoadIcon/LoadImage API still can load those icons,
159 // at least those dimensions which are also used on pre-Vista
160 // systems.
161 // For that reason, we don't rely on gdiplus telling us if
162 // the image format is "icon" or not, we also check the
163 // file extension for ".ico".
164 std::transform(sFilePathName.begin(), sFilePathName.end(), sFilePathName.begin(), ::tolower);
165 bIsIcon = (guid == ImageFormatIcon) || (_tcsstr(sFilePathName.c_str(), _T(".ico")) != NULL);
166 bIsTiff = (guid == ImageFormatTIFF) || (_tcsstr(sFilePathName.c_str(), _T(".tiff")) != NULL);
168 if (bIsIcon)
170 // Icon file, get special treatment...
171 if (pBitmap)
173 // Cleanup first...
174 delete (pBitmap);
175 pBitmap = NULL;
176 bIsIcon = true;
179 CAutoFile hFile = CreateFile(sFilePathName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
180 if (hFile)
182 BY_HANDLE_FILE_INFORMATION fileinfo;
183 if (GetFileInformationByHandle(hFile, &fileinfo))
185 lpIcons = new BYTE[fileinfo.nFileSizeLow];
186 DWORD readbytes;
187 if (ReadFile(hFile, lpIcons, fileinfo.nFileSizeLow, &readbytes, NULL))
189 // we have the icon. Now gather the information we need later
190 if (readbytes >= sizeof(ICONDIR))
192 // we are going to open same file second time so we have to close the file now
193 hFile.CloseHandle();
195 LPICONDIR lpIconDir = (LPICONDIR)lpIcons;
196 if ((lpIconDir->idCount)&&(lpIconDir->idCount * sizeof(ICONDIR) <= fileinfo.nFileIndexLow))
200 nCurrentIcon = 0;
201 hIcons = new HICON[lpIconDir->idCount];
202 m_Width = lpIconDir->idEntries[0].bWidth;
203 m_Height = lpIconDir->idEntries[0].bHeight;
204 for (int i=0; i<lpIconDir->idCount; ++i)
206 hIcons[i] = (HICON)LoadImage(NULL, sFilePathName.c_str(), IMAGE_ICON,
207 lpIconDir->idEntries[i].bWidth,
208 lpIconDir->idEntries[i].bHeight,
209 LR_LOADFROMFILE);
211 bResult = true;
213 catch (...)
215 delete [] lpIcons;
216 lpIcons = NULL;
217 bResult = false;
220 else
222 delete [] lpIcons;
223 lpIcons = NULL;
224 bResult = false;
227 else
229 delete [] lpIcons;
230 lpIcons = NULL;
231 bResult = false;
234 else
236 delete [] lpIcons;
237 lpIcons = NULL;
242 else if (pBitmap) // Image loaded successfully with GDI+
244 m_Height = pBitmap->GetHeight();
245 m_Width = pBitmap->GetWidth();
246 bResult = true;
249 // If still failed to load the file...
250 if (!bResult)
252 // Attempt to load the FreeImage library as an optional DLL to support additional formats
254 // NOTE: Currently just loading via FreeImage & using GDI+ for drawing.
255 // It might be nice to remove this dependency in the future.
256 HMODULE hFreeImageLib = LoadLibrary(_T("FreeImage.dll"));
258 // FreeImage DLL functions
259 typedef const char* (__stdcall *FreeImage_GetVersion_t)(void);
260 typedef int (__stdcall *FreeImage_GetFileType_t)(const TCHAR *filename, int size);
261 typedef int (__stdcall *FreeImage_GetFIFFromFilename_t)(const TCHAR *filename);
262 typedef void* (__stdcall *FreeImage_Load_t)(int format, const TCHAR *filename, int flags);
263 typedef void (__stdcall *FreeImage_Unload_t)(void* dib);
264 typedef int (__stdcall *FreeImage_GetColorType_t)(void* dib);
265 typedef unsigned (__stdcall *FreeImage_GetWidth_t)(void* dib);
266 typedef unsigned (__stdcall *FreeImage_GetHeight_t)(void* dib);
267 typedef void (__stdcall *FreeImage_ConvertToRawBits_t)(BYTE *bits, void *dib, int pitch, unsigned bpp, unsigned red_mask, unsigned green_mask, unsigned blue_mask, BOOL topdown);
269 FreeImage_GetVersion_t FreeImage_GetVersion = NULL;
270 FreeImage_GetFileType_t FreeImage_GetFileType = NULL;
271 FreeImage_GetFIFFromFilename_t FreeImage_GetFIFFromFilename = NULL;
272 FreeImage_Load_t FreeImage_Load = NULL;
273 FreeImage_Unload_t FreeImage_Unload = NULL;
274 FreeImage_GetColorType_t FreeImage_GetColorType = NULL;
275 FreeImage_GetWidth_t FreeImage_GetWidth = NULL;
276 FreeImage_GetHeight_t FreeImage_GetHeight = NULL;
277 FreeImage_ConvertToRawBits_t FreeImage_ConvertToRawBits = NULL;
279 if (hFreeImageLib)
281 bool exportsValid = true;
283 //FreeImage_GetVersion = (FreeImage_GetVersion_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_GetVersion@0", valid);
284 FreeImage_GetWidth = (FreeImage_GetWidth_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_GetWidth@4", exportsValid);
285 FreeImage_GetHeight = (FreeImage_GetHeight_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_GetHeight@4", exportsValid);
286 FreeImage_Unload = (FreeImage_Unload_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_Unload@4", exportsValid);
287 FreeImage_ConvertToRawBits = (FreeImage_ConvertToRawBits_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_ConvertToRawBits@32", exportsValid);
289 #ifdef UNICODE
290 FreeImage_GetFileType = (FreeImage_GetFileType_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_GetFileTypeU@8", exportsValid);
291 FreeImage_GetFIFFromFilename = (FreeImage_GetFIFFromFilename_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_GetFIFFromFilenameU@4", exportsValid);
292 FreeImage_Load = (FreeImage_Load_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_LoadU@12", exportsValid);
293 #else
294 FreeImage_GetFileType = (FreeImage_GetFileType_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_GetFileType@8", exportsValid);
295 FreeImage_GetFIFFromFilename = (FreeImage_GetFIFFromFilename_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_GetFIFFromFilename@4", exportsValid);
296 FreeImage_Load = (FreeImage_Load_t)s_GetProcAddressEx(hFreeImageLib, "_FreeImage_Load@12", exportsValid);
297 #endif
299 //const char* version = FreeImage_GetVersion();
301 // Check the DLL is using compatible exports
302 if (exportsValid)
304 // Derive file type from file header.
305 int fileType = FreeImage_GetFileType(sFilePathName.c_str(), 0);
306 if (fileType < 0)
308 // No file header available, attempt to parse file name for extension.
309 fileType = FreeImage_GetFIFFromFilename(sFilePathName.c_str());
312 // If we have a valid file type
313 if (fileType >= 0)
315 void* dib = FreeImage_Load(fileType, sFilePathName.c_str(), 0);
317 if (dib)
319 unsigned width = FreeImage_GetWidth(dib);
320 unsigned height = FreeImage_GetHeight(dib);
322 // Create a GDI+ bitmap to load into...
323 pBitmap = new Bitmap(width, height, PixelFormat32bppARGB);
325 if (pBitmap && pBitmap->GetLastStatus() == Ok)
327 // Write & convert the loaded data into the GDI+ Bitmap
328 Rect rect(0, 0, width, height);
329 BitmapData bitmapData;
330 if (pBitmap->LockBits(&rect, ImageLockModeWrite, PixelFormat32bppARGB, &bitmapData) == Ok)
332 FreeImage_ConvertToRawBits((BYTE*)bitmapData.Scan0, dib, bitmapData.Stride, 32, 0xff << RED_SHIFT, 0xff << GREEN_SHIFT, 0xff << BLUE_SHIFT, FALSE);
334 pBitmap->UnlockBits(&bitmapData);
336 m_Width = width;
337 m_Height = height;
338 bResult = true;
340 else // Failed to lock the destination Bitmap
342 delete pBitmap;
343 pBitmap = NULL;
346 else // Bitmap allocation failed
348 delete pBitmap;
349 pBitmap = NULL;
352 FreeImage_Unload(dib);
353 dib = NULL;
358 FreeLibrary(hFreeImageLib);
359 hFreeImageLib = NULL;
363 else // GDI+ Unavailable...
365 int nSize = 0;
366 pBitmap = NULL;
367 CAutoFile hFile = CreateFile(sFilePathName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN, NULL);
368 if (hFile)
370 BY_HANDLE_FILE_INFORMATION fileinfo;
371 if (GetFileInformationByHandle(hFile, &fileinfo))
373 BYTE * buffer = new BYTE[fileinfo.nFileSizeLow];
374 DWORD readbytes;
375 if (ReadFile(hFile, buffer, fileinfo.nFileSizeLow, &readbytes, NULL))
377 if (LoadPictureData(buffer, readbytes))
379 m_nSize = fileinfo.nFileSizeLow;
380 bResult = true;
383 delete [] buffer;
386 else
387 return bResult;
389 m_Name = sFilePathName;
390 m_Weight = nSize; // Update Picture Size Info...
392 if(m_IPicture != NULL) // Do Not Try To Read From Memory That Does Not Exist...
394 m_IPicture->get_Height(&m_Height);
395 m_IPicture->get_Width(&m_Width);
396 // Calculate Its Size On a "Standard" (96 DPI) Device Context
397 m_Height = MulDiv(m_Height, 96, HIMETRIC_INCH);
398 m_Width = MulDiv(m_Width, 96, HIMETRIC_INCH);
400 else // Picture Data Is Not a Known Picture Type
402 m_Height = 0;
403 m_Width = 0;
404 bResult = false;
408 if ((bResult)&&(m_nSize == 0))
410 CAutoFile hFile = CreateFile(sFilePathName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN, NULL);
411 if (hFile)
413 BY_HANDLE_FILE_INFORMATION fileinfo;
414 if (GetFileInformationByHandle(hFile, &fileinfo))
416 m_nSize = fileinfo.nFileSizeLow;
421 m_ColorDepth = GetColorDepth();
423 return(bResult);
426 bool CPicture::LoadPictureData(BYTE *pBuffer, int nSize)
428 bool bResult = false;
430 HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, nSize);
432 if(hGlobal == NULL)
434 return(false);
437 void* pData = GlobalLock(hGlobal);
438 if (pData == NULL)
439 return false;
440 memcpy(pData, pBuffer, nSize);
441 GlobalUnlock(hGlobal);
443 IStream* pStream = NULL;
445 if ((CreateStreamOnHGlobal(hGlobal, true, &pStream) == S_OK)&&(pStream))
447 HRESULT hr = OleLoadPicture(pStream, nSize, false, IID_IPicture, (LPVOID *)&m_IPicture);
448 pStream->Release();
449 pStream = NULL;
451 bResult = hr == S_OK;
454 FreeResource(hGlobal); // 16Bit Windows Needs This (32Bit - Automatic Release)
456 return(bResult);
459 bool CPicture::Show(HDC hDC, RECT DrawRect)
461 if (hDC == NULL)
462 return false;
463 if (bIsIcon && lpIcons)
465 ::DrawIconEx(hDC, DrawRect.left, DrawRect.top, hIcons[nCurrentIcon], DrawRect.right-DrawRect.left, DrawRect.bottom-DrawRect.top, 0, NULL, DI_NORMAL);
466 return true;
468 if ((m_IPicture == NULL)&&(pBitmap == NULL))
469 return false;
471 if (m_IPicture)
473 long Width = 0;
474 long Height = 0;
475 m_IPicture->get_Width(&Width);
476 m_IPicture->get_Height(&Height);
478 HRESULT hr = m_IPicture->Render(hDC,
479 DrawRect.left, // Left
480 DrawRect.top, // Top
481 DrawRect.right - DrawRect.left, // Right
482 DrawRect.bottom - DrawRect.top, // Bottom
484 Height,
485 Width,
486 -Height,
487 &DrawRect);
489 if (SUCCEEDED(hr))
490 return(true);
492 else if (pBitmap)
494 Graphics graphics(hDC);
495 graphics.SetInterpolationMode(m_ip);
496 graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
497 ImageAttributes attr;
498 attr.SetWrapMode(WrapModeTileFlipXY);
499 Rect rect(DrawRect.left, DrawRect.top, DrawRect.right-DrawRect.left, DrawRect.bottom-DrawRect.top);
500 graphics.DrawImage(pBitmap, rect, 0, 0, m_Width, m_Height, UnitPixel, &attr);
501 return true;
504 return(false);
507 bool CPicture::UpdateSizeOnDC(HDC hDC)
509 if(hDC == NULL || m_IPicture == NULL) { m_Height = 0; m_Width = 0; return(false); };
511 m_IPicture->get_Height(&m_Height);
512 m_IPicture->get_Width(&m_Width);
514 // Get Current DPI - Dot Per Inch
515 int CurrentDPI_X = GetDeviceCaps(hDC, LOGPIXELSX);
516 int CurrentDPI_Y = GetDeviceCaps(hDC, LOGPIXELSY);
518 m_Height = MulDiv(m_Height, CurrentDPI_Y, HIMETRIC_INCH);
519 m_Width = MulDiv(m_Width, CurrentDPI_X, HIMETRIC_INCH);
521 return(true);
524 UINT CPicture::GetColorDepth() const
526 if (bIsIcon && lpIcons)
528 LPICONDIR lpIconDir = (LPICONDIR)lpIcons;
529 return lpIconDir->idEntries[nCurrentIcon].wBitCount;
531 switch (GetPixelFormat())
533 case PixelFormat1bppIndexed:
534 return 1;
535 case PixelFormat4bppIndexed:
536 return 4;
537 case PixelFormat8bppIndexed:
538 return 8;
539 case PixelFormat16bppARGB1555:
540 case PixelFormat16bppGrayScale:
541 case PixelFormat16bppRGB555:
542 case PixelFormat16bppRGB565:
543 return 16;
544 case PixelFormat24bppRGB:
545 return 24;
546 case PixelFormat32bppARGB:
547 case PixelFormat32bppPARGB:
548 case PixelFormat32bppRGB:
549 return 32;
550 case PixelFormat48bppRGB:
551 return 48;
552 case PixelFormat64bppARGB:
553 case PixelFormat64bppPARGB:
554 return 64;
556 return 0;
559 UINT CPicture::GetNumberOfFrames(int dimension)
561 if (bIsIcon && lpIcons)
563 return 1;
565 if (pBitmap == NULL)
566 return 0;
567 UINT count = pBitmap->GetFrameDimensionsCount();
568 GUID* pDimensionIDs = (GUID*)malloc(sizeof(GUID)*count);
570 pBitmap->GetFrameDimensionsList(pDimensionIDs, count);
572 UINT frameCount = pBitmap->GetFrameCount(&pDimensionIDs[dimension]);
574 free(pDimensionIDs);
575 return frameCount;
578 UINT CPicture::GetNumberOfDimensions()
580 if (bIsIcon && lpIcons)
582 LPICONDIR lpIconDir = (LPICONDIR)lpIcons;
583 return lpIconDir->idCount;
585 return pBitmap ? pBitmap->GetFrameDimensionsCount() : 0;
588 long CPicture::SetActiveFrame(UINT frame)
590 if (bIsIcon && lpIcons)
592 nCurrentIcon = frame-1;
593 m_Height = GetHeight();
594 m_Width = GetWidth();
595 return 0;
597 if (pBitmap == NULL)
598 return 0;
599 UINT count = 0;
600 count = pBitmap->GetFrameDimensionsCount();
601 GUID* pDimensionIDs = (GUID*)malloc(sizeof(GUID)*count);
603 pBitmap->GetFrameDimensionsList(pDimensionIDs, count);
605 UINT frameCount = pBitmap->GetFrameCount(&pDimensionIDs[0]);
607 free(pDimensionIDs);
609 if (frame > frameCount)
610 return 0;
612 GUID pageGuid = FrameDimensionTime;
613 if (bIsTiff)
614 pageGuid = FrameDimensionPage;
615 pBitmap->SelectActiveFrame(&pageGuid, frame);
617 // Assume that the image has a property item of type PropertyItemEquipMake.
618 // Get the size of that property item.
619 int nSize = pBitmap->GetPropertyItemSize(PropertyTagFrameDelay);
621 // Allocate a buffer to receive the property item.
622 PropertyItem* pPropertyItem = (PropertyItem*) malloc(nSize);
624 Status s = pBitmap->GetPropertyItem(PropertyTagFrameDelay, nSize, pPropertyItem);
626 UINT prevframe = frame;
627 if (prevframe > 0)
628 prevframe--;
629 long delay = 0;
630 if (s == Ok)
632 delay = ((long*)pPropertyItem->value)[prevframe] * 10;
634 free(pPropertyItem);
635 m_Height = GetHeight();
636 m_Width = GetWidth();
637 return delay;
640 UINT CPicture::GetHeight() const
642 if ((bIsIcon)&&(lpIcons))
644 LPICONDIR lpIconDir = (LPICONDIR)lpIcons;
645 return lpIconDir->idEntries[nCurrentIcon].bHeight;
647 return pBitmap ? pBitmap->GetHeight() : 0;
650 UINT CPicture::GetWidth() const
652 if ((bIsIcon)&&(lpIcons))
654 LPICONDIR lpIconDir = (LPICONDIR)lpIcons;
655 return lpIconDir->idEntries[nCurrentIcon].bWidth;
657 return pBitmap ? pBitmap->GetWidth() : 0;
660 PixelFormat CPicture::GetPixelFormat() const
662 if ((bIsIcon)&&(lpIcons))
664 LPICONDIR lpIconDir = (LPICONDIR)lpIcons;
665 if (lpIconDir->idEntries[nCurrentIcon].wPlanes == 1)
667 if (lpIconDir->idEntries[nCurrentIcon].wBitCount == 1)
668 return PixelFormat1bppIndexed;
669 if (lpIconDir->idEntries[nCurrentIcon].wBitCount == 4)
670 return PixelFormat4bppIndexed;
671 if (lpIconDir->idEntries[nCurrentIcon].wBitCount == 8)
672 return PixelFormat8bppIndexed;
674 if (lpIconDir->idEntries[nCurrentIcon].wBitCount == 32)
676 return PixelFormat32bppARGB;
679 return pBitmap ? pBitmap->GetPixelFormat() : 0;