xref: /reactos/dll/win32/shimgvw/shimgvw.c (revision 6a8c8946)
1 /*
2  * PROJECT:         ReactOS Picture and Fax Viewer
3  * FILE:            dll/win32/shimgvw/shimgvw.c
4  * PURPOSE:         shimgvw.dll
5  * PROGRAMMERS:     Dmitry Chapyshev (dmitry@reactos.org)
6  *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7  */
8 
9 #define WIN32_NO_STATUS
10 #define _INC_WINDOWS
11 #define COM_NO_WINDOWS_H
12 #define INITGUID
13 
14 #include <stdarg.h>
15 
16 #include <windef.h>
17 #include <winbase.h>
18 #include <winnls.h>
19 #include <winreg.h>
20 #include <wingdi.h>
21 #include <wincon.h>
22 #include <windowsx.h>
23 #include <objbase.h>
24 #include <commctrl.h>
25 #include <commdlg.h>
26 #include <gdiplus.h>
27 #include <tchar.h>
28 #include <shlobj.h>
29 #include <strsafe.h>
30 #include <shlwapi.h>
31 
32 #define NDEBUG
33 #include <debug.h>
34 
35 #include "shimgvw.h"
36 
37 HINSTANCE hInstance;
38 SHIMGVW_SETTINGS shiSettings;
39 SHIMGVW_FILENODE *currentFile;
40 GpImage *image;
41 WNDPROC PrevProc = NULL;
42 
43 HWND hDispWnd, hToolBar;
44 
45 /* zooming */
46 #define MIN_ZOOM 10
47 #define MAX_ZOOM 1600
48 UINT ZoomPercents = 100;
49 static const UINT ZoomSteps[] =
50 {
51     10, 25, 50, 100, 200, 400, 800, 1600
52 };
53 
54 /* animation */
55 UINT            m_nFrameIndex = 0;
56 UINT            m_nFrameCount = 0;
57 UINT            m_nLoopIndex = 0;
58 UINT            m_nLoopCount = (UINT)-1;
59 PropertyItem   *m_pDelayItem = NULL;
60 
61 #define ANIME_TIMER_ID  9999
62 
63 static void Anime_FreeInfo(void)
64 {
65     if (m_pDelayItem)
66     {
67         free(m_pDelayItem);
68         m_pDelayItem = NULL;
69     }
70     m_nFrameIndex = 0;
71     m_nFrameCount = 0;
72     m_nLoopIndex = 0;
73     m_nLoopCount = (UINT)-1;
74 }
75 
76 static BOOL Anime_LoadInfo(void)
77 {
78     GUID *dims;
79     UINT nDimCount = 0;
80     UINT cbItem;
81     UINT result;
82     PropertyItem *pItem;
83 
84     Anime_FreeInfo();
85     KillTimer(hDispWnd, ANIME_TIMER_ID);
86 
87     if (!image)
88         return FALSE;
89 
90     GdipImageGetFrameDimensionsCount(image, &nDimCount);
91     if (nDimCount)
92     {
93         dims = (GUID *)calloc(nDimCount, sizeof(GUID));
94         if (dims)
95         {
96             GdipImageGetFrameDimensionsList(image, dims, nDimCount);
97             GdipImageGetFrameCount(image, dims, &result);
98             m_nFrameCount = result;
99             free(dims);
100         }
101     }
102 
103     result = 0;
104     GdipGetPropertyItemSize(image, PropertyTagFrameDelay, &result);
105     cbItem = result;
106     if (cbItem)
107     {
108         m_pDelayItem = (PropertyItem *)malloc(cbItem);
109         GdipGetPropertyItem(image, PropertyTagFrameDelay, cbItem, m_pDelayItem);
110     }
111 
112     result = 0;
113     GdipGetPropertyItemSize(image, PropertyTagLoopCount, &result);
114     cbItem = result;
115     if (cbItem)
116     {
117         pItem = (PropertyItem *)malloc(cbItem);
118         if (pItem)
119         {
120             if (GdipGetPropertyItem(image, PropertyTagLoopCount, cbItem, pItem) == Ok)
121             {
122                 m_nLoopCount = *(WORD *)pItem->value;
123             }
124             free(pItem);
125         }
126     }
127 
128     if (m_pDelayItem)
129     {
130         SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
131     }
132 
133     return m_pDelayItem != NULL;
134 }
135 
136 static void Anime_SetFrameIndex(UINT nFrameIndex)
137 {
138     if (nFrameIndex < m_nFrameCount)
139     {
140         GUID guid = FrameDimensionTime;
141         if (Ok != GdipImageSelectActiveFrame(image, &guid, nFrameIndex))
142         {
143             guid = FrameDimensionPage;
144             GdipImageSelectActiveFrame(image, &guid, nFrameIndex);
145         }
146     }
147     m_nFrameIndex = nFrameIndex;
148 }
149 
150 DWORD Anime_GetFrameDelay(UINT nFrameIndex)
151 {
152     if (nFrameIndex < m_nFrameCount && m_pDelayItem)
153     {
154         return ((DWORD *)m_pDelayItem->value)[m_nFrameIndex] * 10;
155     }
156     return 0;
157 }
158 
159 BOOL Anime_Step(DWORD *pdwDelay)
160 {
161     *pdwDelay = INFINITE;
162     if (m_nLoopCount == (UINT)-1)
163         return FALSE;
164 
165     if (m_nFrameIndex + 1 < m_nFrameCount)
166     {
167         *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
168         Anime_SetFrameIndex(m_nFrameIndex);
169         ++m_nFrameIndex;
170         return TRUE;
171     }
172 
173     if (m_nLoopCount == 0 || m_nLoopIndex < m_nLoopCount)
174     {
175         *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
176         Anime_SetFrameIndex(m_nFrameIndex);
177         m_nFrameIndex = 0;
178         ++m_nLoopIndex;
179         return TRUE;
180     }
181 
182     return FALSE;
183 }
184 
185 static void ZoomInOrOut(BOOL bZoomIn)
186 {
187     INT i;
188 
189     if (bZoomIn)    /* zoom in */
190     {
191         /* find next step */
192         for (i = 0; i < ARRAYSIZE(ZoomSteps); ++i)
193         {
194             if (ZoomPercents < ZoomSteps[i])
195                 break;
196         }
197         if (i == ARRAYSIZE(ZoomSteps))
198             ZoomPercents = MAX_ZOOM;
199         else
200             ZoomPercents = ZoomSteps[i];
201 
202         /* update tool bar buttons */
203         SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, TRUE);
204         if (ZoomPercents >= MAX_ZOOM)
205             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, FALSE);
206         else
207             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, TRUE);
208     }
209     else            /* zoom out */
210     {
211         /* find previous step */
212         for (i = ARRAYSIZE(ZoomSteps); i > 0; )
213         {
214             --i;
215             if (ZoomSteps[i] < ZoomPercents)
216                 break;
217         }
218         if (i < 0)
219             ZoomPercents = MIN_ZOOM;
220         else
221             ZoomPercents = ZoomSteps[i];
222 
223         /* update tool bar buttons */
224         SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMP, TRUE);
225         if (ZoomPercents <= MIN_ZOOM)
226             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, FALSE);
227         else
228             SendMessage(hToolBar, TB_ENABLEBUTTON, IDC_ZOOMM, TRUE);
229     }
230 
231     /* redraw */
232     InvalidateRect(hDispWnd, NULL, TRUE);
233 }
234 
235 static void ResetZoom(void)
236 {
237     RECT Rect;
238     UINT ImageWidth, ImageHeight;
239 
240     /* get disp window size and image size */
241     GetClientRect(hDispWnd, &Rect);
242     GdipGetImageWidth(image, &ImageWidth);
243     GdipGetImageHeight(image, &ImageHeight);
244 
245     /* compare two aspect rates. same as
246        (ImageHeight / ImageWidth < Rect.bottom / Rect.right) in real */
247     if (ImageHeight * Rect.right < Rect.bottom * ImageWidth)
248     {
249         if (Rect.right < ImageWidth)
250         {
251             /* it's large, shrink it */
252             ZoomPercents = (Rect.right * 100) / ImageWidth;
253         }
254         else
255         {
256             /* it's small. show as original size */
257             ZoomPercents = 100;
258         }
259     }
260     else
261     {
262         if (Rect.bottom < ImageHeight)
263         {
264             /* it's large, shrink it */
265             ZoomPercents = (Rect.bottom * 100) / ImageHeight;
266         }
267         else
268         {
269             /* it's small. show as original size */
270             ZoomPercents = 100;
271         }
272     }
273 }
274 
275 /* ToolBar Buttons */
276 static const TBBUTTON Buttons [] =
277 {   /* iBitmap,     idCommand,   fsState,         fsStyle,     bReserved[2], dwData, iString */
278     {TBICON_PREV,   IDC_PREV,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
279     {TBICON_NEXT,   IDC_NEXT,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
280     {15,            0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0},
281     {TBICON_ZOOMP,  IDC_ZOOMP,   TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
282     {TBICON_ZOOMM,  IDC_ZOOMM,   TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
283     {15,            0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0},
284     {TBICON_ROT1,   IDC_ROT1,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
285     {TBICON_ROT2,   IDC_ROT2,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
286     {15,            0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0},
287     {TBICON_SAVE,   IDC_SAVE,    TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
288     {TBICON_PRINT,  IDC_PRINT,   TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
289 };
290 
291 static void pLoadImage(LPWSTR szOpenFileName)
292 {
293     /* check file presence */
294     if (GetFileAttributesW(szOpenFileName) == 0xFFFFFFFF)
295     {
296         DPRINT1("File %s not found!\n", szOpenFileName);
297         return;
298     }
299 
300     /* load now */
301     GdipLoadImageFromFile(szOpenFileName, &image);
302     if (!image)
303     {
304         DPRINT1("GdipLoadImageFromFile() failed\n");
305         return;
306     }
307     Anime_LoadInfo();
308 
309     if (szOpenFileName && szOpenFileName[0])
310         SHAddToRecentDocs(SHARD_PATHW, szOpenFileName);
311 
312     /* reset zoom */
313     ResetZoom();
314 
315     /* redraw */
316     InvalidateRect(hDispWnd, NULL, TRUE);
317 }
318 
319 static void pSaveImageAs(HWND hwnd)
320 {
321     OPENFILENAMEW sfn;
322     ImageCodecInfo *codecInfo;
323     WCHAR szSaveFileName[MAX_PATH];
324     WCHAR *szFilterMask;
325     GUID rawFormat;
326     UINT num;
327     UINT size;
328     size_t sizeRemain;
329     UINT j;
330     WCHAR *c;
331 
332     GdipGetImageEncodersSize(&num, &size);
333     codecInfo = malloc(size);
334     if (!codecInfo)
335     {
336         DPRINT1("malloc() failed in pSaveImageAs()\n");
337         return;
338     }
339 
340     GdipGetImageEncoders(num, size, codecInfo);
341     GdipGetImageRawFormat(image, &rawFormat);
342 
343     sizeRemain = 0;
344 
345     for (j = 0; j < num; ++j)
346     {
347         // Every pair needs space for the Description, twice the Extensions, 1 char for the space, 2 for the braces and 2 for the NULL terminators.
348         sizeRemain = sizeRemain + (((wcslen(codecInfo[j].FormatDescription) + (wcslen(codecInfo[j].FilenameExtension) * 2) + 5) * sizeof(WCHAR)));
349     }
350 
351     /* Add two more chars for the last terminator */
352     sizeRemain = sizeRemain + (sizeof(WCHAR) * 2);
353 
354     szFilterMask = malloc(sizeRemain);
355     if (!szFilterMask)
356     {
357         DPRINT1("cannot allocate memory for filter mask in pSaveImageAs()");
358         free(codecInfo);
359         return;
360     }
361 
362     ZeroMemory(szSaveFileName, sizeof(szSaveFileName));
363     ZeroMemory(szFilterMask, sizeRemain);
364     ZeroMemory(&sfn, sizeof(sfn));
365     sfn.lStructSize = sizeof(sfn);
366     sfn.hwndOwner   = hwnd;
367     sfn.hInstance   = hInstance;
368     sfn.lpstrFile   = szSaveFileName;
369     sfn.lpstrFilter = szFilterMask;
370     sfn.nMaxFile    = MAX_PATH;
371     sfn.Flags       = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
372 
373     c = szFilterMask;
374 
375     for (j = 0; j < num; ++j)
376     {
377         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls (%ls)", codecInfo[j].FormatDescription, codecInfo[j].FilenameExtension);
378 
379         /* Skip the NULL character */
380         c++;
381         sizeRemain -= sizeof(*c);
382 
383         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls", codecInfo[j].FilenameExtension);
384 
385         /* Skip the NULL character */
386         c++;
387         sizeRemain -= sizeof(*c);
388 
389         if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID) != FALSE)
390         {
391             sfn.nFilterIndex = j + 1;
392         }
393     }
394 
395     if (GetSaveFileNameW(&sfn))
396     {
397         if (m_pDelayItem)
398         {
399             /* save animation */
400             KillTimer(hDispWnd, ANIME_TIMER_ID);
401 
402             DPRINT1("FIXME: save animation\n");
403             if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
404             {
405                 DPRINT1("GdipSaveImageToFile() failed\n");
406             }
407 
408             SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
409         }
410         else
411         {
412             /* save non-animation */
413             if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
414             {
415                 DPRINT1("GdipSaveImageToFile() failed\n");
416             }
417         }
418     }
419 
420     free(szFilterMask);
421     free(codecInfo);
422 }
423 
424 static VOID
425 pLoadImageFromNode(SHIMGVW_FILENODE *node, HWND hwnd)
426 {
427     WCHAR szTitleBuf[800];
428     WCHAR szResStr[512];
429     WCHAR *c;
430 
431     if (node)
432     {
433         c = wcsrchr(node->FileName, '\\');
434         if (c)
435         {
436             c++;
437         }
438 
439         LoadStringW(hInstance, IDS_APPTITLE, szResStr, ARRAYSIZE(szResStr));
440         StringCbPrintfW(szTitleBuf, sizeof(szTitleBuf), L"%ls%ls%ls", szResStr, L" - ", c);
441         SetWindowTextW(hwnd, szTitleBuf);
442 
443         if (image)
444         {
445             GdipDisposeImage(image);
446         }
447 
448         pLoadImage(node->FileName);
449     }
450 }
451 
452 static SHIMGVW_FILENODE*
453 pBuildFileList(LPWSTR szFirstFile)
454 {
455     HANDLE hFindHandle;
456     WCHAR *extension;
457     WCHAR szSearchPath[MAX_PATH];
458     WCHAR szSearchMask[MAX_PATH];
459     WCHAR szFileTypes[MAX_PATH];
460     WIN32_FIND_DATAW findData;
461     SHIMGVW_FILENODE *currentNode = NULL;
462     SHIMGVW_FILENODE *root = NULL;
463     SHIMGVW_FILENODE *conductor = NULL;
464     ImageCodecInfo *codecInfo;
465     UINT num;
466     UINT size;
467     UINT j;
468 
469     StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile);
470     PathRemoveFileSpecW(szSearchPath);
471 
472     GdipGetImageDecodersSize(&num, &size);
473     codecInfo = malloc(size);
474     if (!codecInfo)
475     {
476         DPRINT1("malloc() failed in pLoadFileList()\n");
477         return NULL;
478     }
479 
480     GdipGetImageDecoders(num, size, codecInfo);
481 
482     root = malloc(sizeof(SHIMGVW_FILENODE));
483     if (!root)
484     {
485         DPRINT1("malloc() failed in pLoadFileList()\n");
486         free(codecInfo);
487         return NULL;
488     }
489 
490     conductor = root;
491 
492     for (j = 0; j < num; ++j)
493     {
494         StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension);
495 
496         extension = wcstok(szFileTypes, L";");
497         while (extension != NULL)
498         {
499             PathCombineW(szSearchMask, szSearchPath, extension);
500 
501             hFindHandle = FindFirstFileW(szSearchMask, &findData);
502             if (hFindHandle != INVALID_HANDLE_VALUE)
503             {
504                 do
505                 {
506                     PathCombineW(conductor->FileName, szSearchPath, findData.cFileName);
507 
508                     // compare the name of the requested file with the one currently found.
509                     // if the name matches, the current node is returned by the function.
510                     if (wcscmp(szFirstFile, conductor->FileName) == 0)
511                     {
512                         currentNode = conductor;
513                     }
514 
515                     conductor->Next = malloc(sizeof(SHIMGVW_FILENODE));
516 
517                     // if malloc fails, make circular what we have and return it
518                     if (!conductor->Next)
519                     {
520                         DPRINT1("malloc() failed in pLoadFileList()\n");
521 
522                         conductor->Next = root;
523                         root->Prev = conductor;
524 
525                         FindClose(hFindHandle);
526                         free(codecInfo);
527                         return conductor;
528                     }
529 
530                     conductor->Next->Prev = conductor;
531                     conductor = conductor->Next;
532                 }
533                 while (FindNextFileW(hFindHandle, &findData) != 0);
534 
535                 FindClose(hFindHandle);
536             }
537 
538             extension = wcstok(NULL, L";");
539         }
540     }
541 
542     // we now have a node too much in the list. In case the requested file was not found,
543     // we use this node to store the name of it, otherwise we free it.
544     if (currentNode == NULL)
545     {
546         StringCchCopyW(conductor->FileName, MAX_PATH, szFirstFile);
547         currentNode = conductor;
548     }
549     else
550     {
551         conductor = conductor->Prev;
552         free(conductor->Next);
553     }
554 
555     // link the last node with the first one to make the list circular
556     conductor->Next = root;
557     root->Prev = conductor;
558     conductor = currentNode;
559 
560     free(codecInfo);
561 
562     return conductor;
563 }
564 
565 static VOID
566 pFreeFileList(SHIMGVW_FILENODE *root)
567 {
568     SHIMGVW_FILENODE *conductor;
569 
570     root->Prev->Next = NULL;
571     root->Prev = NULL;
572 
573     while (root)
574     {
575         conductor = root;
576         root = conductor->Next;
577         free(conductor);
578     }
579 }
580 
581 static VOID
582 ImageView_UpdateWindow(HWND hwnd)
583 {
584     InvalidateRect(hwnd, NULL, FALSE);
585     UpdateWindow(hwnd);
586 }
587 
588 static HBRUSH CreateCheckerBoardBrush(HDC hdc)
589 {
590     static const CHAR pattern[] =
591         "\x28\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x01\x00\x04\x00\x00\x00"
592         "\x00\x00\x80\x00\x00\x00\x23\x2E\x00\x00\x23\x2E\x00\x00\x10\x00\x00\x00"
593         "\x00\x00\x00\x00\x99\x99\x99\x00\xCC\xCC\xCC\x00\x00\x00\x00\x00\x00\x00"
594         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
595         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
596         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11"
597         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00"
598         "\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
599         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
600         "\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
601         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
602         "\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11"
603         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11";
604 
605     return CreateDIBPatternBrushPt(pattern, DIB_RGB_COLORS);
606 }
607 
608 static VOID
609 ImageView_DrawImage(HWND hwnd)
610 {
611     GpGraphics *graphics;
612     UINT ImageWidth, ImageHeight;
613     INT ZoomedWidth, ZoomedHeight, x, y;
614     PAINTSTRUCT ps;
615     RECT rect, margin;
616     HDC hdc;
617     HBRUSH white;
618     HGDIOBJ hbrOld;
619     UINT uFlags;
620 
621     hdc = BeginPaint(hwnd, &ps);
622     if (!hdc)
623     {
624         DPRINT1("BeginPaint() failed\n");
625         return;
626     }
627 
628     GdipCreateFromHDC(hdc, &graphics);
629     if (!graphics)
630     {
631         DPRINT1("GdipCreateFromHDC() failed\n");
632         return;
633     }
634 
635     GdipGetImageWidth(image, &ImageWidth);
636     GdipGetImageHeight(image, &ImageHeight);
637 
638     if (GetClientRect(hwnd, &rect))
639     {
640         ZoomedWidth = (ImageWidth * ZoomPercents) / 100;
641         ZoomedHeight = (ImageHeight * ZoomPercents) / 100;
642 
643         x = (rect.right - ZoomedWidth) / 2;
644         y = (rect.bottom - ZoomedHeight) / 2;
645 
646         white = GetStockObject(WHITE_BRUSH);
647         // Fill top part
648         margin = rect;
649         margin.bottom = y - 1;
650         FillRect(hdc, &margin, white);
651         // Fill bottom part
652         margin.top = y + ZoomedHeight + 1;
653         margin.bottom = rect.bottom;
654         FillRect(hdc, &margin, white);
655         // Fill left part
656         margin.top = y - 1;
657         margin.bottom = y + ZoomedHeight + 1;
658         margin.right = x - 1;
659         FillRect(hdc, &margin, white);
660         // Fill right part
661         margin.left = x + ZoomedWidth + 1;
662         margin.right = rect.right;
663         FillRect(hdc, &margin, white);
664 
665         DPRINT("x = %d, y = %d, ImageWidth = %u, ImageHeight = %u\n");
666         DPRINT("rect.right = %ld, rect.bottom = %ld\n", rect.right, rect.bottom);
667         DPRINT("ZoomPercents = %d, ZoomedWidth = %d, ZoomedHeight = %d\n",
668                ZoomPercents, ZoomedWidth, ZoomedWidth);
669 
670         if (ZoomPercents % 100 == 0)
671         {
672             GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor);
673             GdipSetSmoothingMode(graphics, SmoothingModeNone);
674         }
675         else
676         {
677             GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear);
678             GdipSetSmoothingMode(graphics, SmoothingModeHighQuality);
679         }
680 
681         uFlags = 0;
682         GdipGetImageFlags(image, &uFlags);
683 
684         if (uFlags & (ImageFlagsHasAlpha | ImageFlagsHasTranslucent))
685         {
686             HBRUSH hbr = CreateCheckerBoardBrush(hdc);
687             hbrOld = SelectObject(hdc, hbr);
688             Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
689             SelectObject(hdc, hbrOld);
690             DeleteObject(hbr);
691         }
692         else
693         {
694             hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
695             Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
696             SelectObject(hdc, hbrOld);
697         }
698 
699         GdipDrawImageRectI(graphics, image, x, y, ZoomedWidth, ZoomedHeight);
700     }
701     GdipDeleteGraphics(graphics);
702     EndPaint(hwnd, &ps);
703 }
704 
705 static BOOL
706 ImageView_LoadSettings()
707 {
708     HKEY hKey;
709     DWORD dwSize;
710 
711     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, KEY_READ, &hKey) == ERROR_SUCCESS)
712     {
713         dwSize = sizeof(SHIMGVW_SETTINGS);
714         if (RegQueryValueEx(hKey, _T("Settings"), NULL, NULL, (LPBYTE)&shiSettings, &dwSize) == ERROR_SUCCESS)
715         {
716             RegCloseKey(hKey);
717             return TRUE;
718         }
719 
720         RegCloseKey(hKey);
721     }
722 
723     return FALSE;
724 }
725 
726 static VOID
727 ImageView_SaveSettings(HWND hwnd)
728 {
729     WINDOWPLACEMENT wp;
730     HKEY hKey;
731 
732     ShowWindow(hwnd, SW_HIDE);
733     wp.length = sizeof(WINDOWPLACEMENT);
734     GetWindowPlacement(hwnd, &wp);
735 
736     shiSettings.Left = wp.rcNormalPosition.left;
737     shiSettings.Top  = wp.rcNormalPosition.top;
738     shiSettings.Right  = wp.rcNormalPosition.right;
739     shiSettings.Bottom = wp.rcNormalPosition.bottom;
740     shiSettings.Maximized = (IsZoomed(hwnd) || (wp.flags & WPF_RESTORETOMAXIMIZED));
741 
742     if (RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, NULL,
743         REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS)
744     {
745         RegSetValueEx(hKey, _T("Settings"), 0, REG_BINARY, (LPBYTE)&shiSettings, sizeof(SHIMGVW_SETTINGS));
746         RegCloseKey(hKey);
747     }
748 }
749 
750 static BOOL
751 ImageView_CreateToolBar(HWND hwnd)
752 {
753     INT numButtons = sizeof(Buttons) / sizeof(Buttons[0]);
754 
755     hToolBar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
756                               WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | CCS_BOTTOM | TBSTYLE_TOOLTIPS,
757                               0, 0, 0, 0, hwnd,
758                               0, hInstance, NULL);
759     if(hToolBar != NULL)
760     {
761         HIMAGELIST hImageList;
762 
763         SendMessage(hToolBar, TB_SETEXTENDEDSTYLE,
764                     0, TBSTYLE_EX_HIDECLIPPEDBUTTONS);
765 
766         SendMessage(hToolBar, TB_BUTTONSTRUCTSIZE,
767                     sizeof(Buttons[0]), 0);
768 
769         hImageList = ImageList_Create(TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, ILC_MASK | ILC_COLOR24, 1, 1);
770 
771         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_PREVICON), IMAGE_BITMAP,
772                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
773 
774         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_NEXTICON), IMAGE_BITMAP,
775                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
776 
777         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ZOOMPICON), IMAGE_BITMAP,
778                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
779 
780         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ZOOMMICON), IMAGE_BITMAP,
781                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
782 
783         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_SAVEICON), IMAGE_BITMAP,
784                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
785 
786         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_PRINTICON), IMAGE_BITMAP,
787                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
788 
789         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ROT1ICON), IMAGE_BITMAP,
790                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
791 
792         ImageList_AddMasked(hImageList, LoadImage(hInstance, MAKEINTRESOURCE(IDB_ROT2ICON), IMAGE_BITMAP,
793                       TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
794 
795         if (hImageList == NULL) return FALSE;
796 
797         ImageList_Destroy((HIMAGELIST)SendMessage(hToolBar, TB_SETIMAGELIST,
798                                                   0, (LPARAM)hImageList));
799 
800         SendMessage(hToolBar, TB_ADDBUTTONS,
801                     numButtons, (LPARAM)Buttons);
802 
803         return TRUE;
804     }
805 
806     return FALSE;
807 }
808 
809 static void ImageView_OnTimer(HWND hwnd)
810 {
811     DWORD dwDelay;
812 
813     KillTimer(hwnd, ANIME_TIMER_ID);
814     InvalidateRect(hwnd, NULL, FALSE);
815 
816     if (Anime_Step(&dwDelay))
817     {
818         SetTimer(hwnd, ANIME_TIMER_ID, dwDelay, NULL);
819     }
820 }
821 
822 LRESULT CALLBACK
823 ImageView_DispWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
824 {
825     switch (Message)
826     {
827         case WM_PAINT:
828         {
829             ImageView_DrawImage(hwnd);
830             return 0L;
831         }
832         case WM_TIMER:
833         {
834             if (wParam == ANIME_TIMER_ID)
835             {
836                 ImageView_OnTimer(hwnd);
837                 return 0;
838             }
839             break;
840         }
841     }
842     return CallWindowProc(PrevProc, hwnd, Message, wParam, lParam);
843 }
844 
845 static VOID
846 ImageView_InitControls(HWND hwnd)
847 {
848     MoveWindow(hwnd, shiSettings.Left, shiSettings.Top,
849                shiSettings.Right - shiSettings.Left,
850                shiSettings.Bottom - shiSettings.Top, TRUE);
851 
852     if (shiSettings.Maximized) ShowWindow(hwnd, SW_MAXIMIZE);
853 
854     hDispWnd = CreateWindowEx(0, WC_STATIC, _T(""),
855                               WS_CHILD | WS_VISIBLE,
856                               0, 0, 0, 0, hwnd, NULL, hInstance, NULL);
857 
858     SetClassLongPtr(hDispWnd, GCL_STYLE, CS_HREDRAW | CS_VREDRAW);
859     PrevProc = (WNDPROC) SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) ImageView_DispWndProc);
860 
861     ImageView_CreateToolBar(hwnd);
862 }
863 
864 static VOID
865 ImageView_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys)
866 {
867     if (zDelta != 0)
868     {
869         ZoomInOrOut(zDelta > 0);
870     }
871 }
872 
873 LRESULT CALLBACK
874 ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
875 {
876     switch (Message)
877     {
878         case WM_CREATE:
879         {
880             ImageView_InitControls(hwnd);
881             return 0L;
882         }
883 
884         case WM_KEYDOWN:
885             switch (LOWORD(wParam))
886             {
887                 case VK_LEFT:
888                     PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_PREV, BN_CLICKED), (LPARAM)NULL);
889                     break;
890 
891                 case VK_RIGHT:
892                     PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_NEXT, BN_CLICKED), (LPARAM)NULL);
893                     break;
894             }
895             break;
896 
897         case WM_COMMAND:
898         {
899             switch (wParam)
900             {
901                 case IDC_PREV:
902                 {
903                     currentFile = currentFile->Prev;
904                     pLoadImageFromNode(currentFile, hwnd);
905                 }
906 
907                 break;
908                 case IDC_NEXT:
909                 {
910                     currentFile = currentFile->Next;
911                     pLoadImageFromNode(currentFile, hwnd);
912                 }
913 
914                 break;
915                 case IDC_ZOOMP:
916                 {
917                     ZoomInOrOut(TRUE);
918                 }
919                 break;
920                 case IDC_ZOOMM:
921                 {
922                     ZoomInOrOut(FALSE);
923                 }
924                 break;
925                 case IDC_SAVE:
926                     pSaveImageAs(hwnd);
927 
928                 break;
929                 case IDC_PRINT:
930 
931                 break;
932                 case IDC_ROT1:
933                 {
934                     GdipImageRotateFlip(image, Rotate270FlipNone);
935                     ImageView_UpdateWindow(hwnd);
936                 }
937 
938                 break;
939                 case IDC_ROT2:
940                 {
941                     GdipImageRotateFlip(image, Rotate90FlipNone);
942                     ImageView_UpdateWindow(hwnd);
943                 }
944 
945                 break;
946             }
947         }
948         break;
949 
950         case WM_MOUSEWHEEL:
951             ImageView_OnMouseWheel(hwnd,
952                 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
953                 (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
954             break;
955 
956         case WM_NOTIFY:
957         {
958             LPNMHDR pnmhdr = (LPNMHDR)lParam;
959 
960             switch (pnmhdr->code)
961             {
962                 case TTN_GETDISPINFO:
963                 {
964                     LPTOOLTIPTEXT lpttt;
965                     UINT idButton;
966 
967                     lpttt = (LPTOOLTIPTEXT)lParam;
968                     idButton = (UINT)lpttt->hdr.idFrom;
969                     lpttt->hinst = hInstance;
970 
971                     switch (idButton)
972                     {
973                         case IDC_PREV:
974                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_PREV_PIC);
975                         break;
976                         case IDC_NEXT:
977                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_NEXT_PIC);
978                         break;
979                         case IDC_ZOOMP:
980                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ZOOM_IN);
981                         break;
982                         case IDC_ZOOMM:
983                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ZOOM_OUT);
984                         break;
985                         case IDC_SAVE:
986                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_SAVEAS);
987                         break;
988                         case IDC_PRINT:
989                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_PRINT);
990                         break;
991                         case IDC_ROT1:
992                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ROT_COUNCW);
993                         break;
994                         case IDC_ROT2:
995                             lpttt->lpszText = MAKEINTRESOURCE(IDS_TOOLTIP_ROT_CLOCKW);
996                         break;
997                     }
998                     return TRUE;
999                 }
1000             }
1001             break;
1002         }
1003         case WM_SIZING:
1004         {
1005             LPRECT pRect = (LPRECT)lParam;
1006             if (pRect->right-pRect->left < 350)
1007                 pRect->right = pRect->left + 350;
1008 
1009             if (pRect->bottom-pRect->top < 290)
1010                 pRect->bottom = pRect->top + 290;
1011             return TRUE;
1012         }
1013         case WM_SIZE:
1014         {
1015             RECT rc;
1016             SendMessage(hToolBar, TB_AUTOSIZE, 0, 0);
1017             GetWindowRect(hToolBar, &rc);
1018             MoveWindow(hDispWnd, 1, 1, LOWORD(lParam) - 1, HIWORD(lParam) - (rc.bottom - rc.top) - 1, TRUE);
1019             /* is it maximized or restored? */
1020             if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)
1021             {
1022                 /* reset zoom */
1023                 ResetZoom();
1024             }
1025             return 0L;
1026         }
1027         case WM_DESTROY:
1028         {
1029             ImageView_SaveSettings(hwnd);
1030             SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) PrevProc);
1031             PostQuitMessage(0);
1032             break;
1033         }
1034     }
1035 
1036     return DefWindowProc(hwnd, Message, wParam, lParam);
1037 }
1038 
1039 LONG WINAPI
1040 ImageView_CreateWindow(HWND hwnd, LPWSTR szFileName)
1041 {
1042     struct GdiplusStartupInput gdiplusStartupInput;
1043     ULONG_PTR gdiplusToken;
1044     WNDCLASS WndClass = {0};
1045     TCHAR szBuf[512];
1046     WCHAR szInitialFile[MAX_PATH];
1047     HWND hMainWnd;
1048     MSG msg;
1049 
1050     if (!ImageView_LoadSettings())
1051     {
1052         shiSettings.Maximized = FALSE;
1053         shiSettings.Left      = 0;
1054         shiSettings.Top       = 0;
1055         shiSettings.Right     = 520;
1056         shiSettings.Bottom    = 400;
1057     }
1058 
1059     // Initialize GDI+
1060     gdiplusStartupInput.GdiplusVersion              = 1;
1061     gdiplusStartupInput.DebugEventCallback          = NULL;
1062     gdiplusStartupInput.SuppressBackgroundThread    = FALSE;
1063     gdiplusStartupInput.SuppressExternalCodecs      = FALSE;
1064 
1065     GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1066     pLoadImage(szFileName);
1067 
1068     // Create the window
1069     WndClass.lpszClassName  = _T("shimgvw_window");
1070     WndClass.lpfnWndProc    = ImageView_WndProc;
1071     WndClass.hInstance      = hInstance;
1072     WndClass.style          = CS_HREDRAW | CS_VREDRAW;
1073     WndClass.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPICON));
1074     WndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
1075     WndClass.hbrBackground  = NULL;   /* less flicker */
1076 
1077     if (!RegisterClass(&WndClass)) return -1;
1078 
1079     LoadString(hInstance, IDS_APPTITLE, szBuf, sizeof(szBuf) / sizeof(TCHAR));
1080     hMainWnd = CreateWindow(_T("shimgvw_window"), szBuf,
1081                             WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CAPTION,
1082                             CW_USEDEFAULT, CW_USEDEFAULT,
1083                             0, 0, NULL, NULL, hInstance, NULL);
1084 
1085     // make sure the path has no quotes on it
1086     wcscpy(szInitialFile, szFileName);
1087     PathUnquoteSpacesW(szInitialFile);
1088 
1089     currentFile = pBuildFileList(szInitialFile);
1090     if (currentFile)
1091     {
1092         pLoadImageFromNode(currentFile, hMainWnd);
1093     }
1094 
1095     // Show it
1096     ShowWindow(hMainWnd, SW_SHOW);
1097     UpdateWindow(hMainWnd);
1098 
1099     // Message Loop
1100     while(GetMessage(&msg,NULL,0,0))
1101     {
1102         TranslateMessage(&msg);
1103         DispatchMessageW(&msg);
1104     }
1105 
1106     pFreeFileList(currentFile);
1107 
1108     if (image)
1109         GdipDisposeImage(image);
1110 
1111     Anime_FreeInfo();
1112 
1113     GdiplusShutdown(gdiplusToken);
1114     return -1;
1115 }
1116 
1117 VOID WINAPI
1118 ImageView_FullscreenW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1119 {
1120     ImageView_CreateWindow(hwnd, (LPWSTR)path);
1121 }
1122 
1123 VOID WINAPI
1124 ImageView_Fullscreen(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1125 {
1126     ImageView_CreateWindow(hwnd, (LPWSTR)path);
1127 }
1128 
1129 VOID WINAPI
1130 ImageView_FullscreenA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1131 {
1132     WCHAR szFile[MAX_PATH];
1133 
1134     if (MultiByteToWideChar(CP_ACP, 0, (char*)path, strlen((char*)path)+1, szFile, MAX_PATH))
1135     {
1136         ImageView_CreateWindow(hwnd, (LPWSTR)szFile);
1137     }
1138 }
1139 
1140 VOID WINAPI
1141 ImageView_PrintTo(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1142 {
1143     DPRINT("ImageView_PrintTo() not implemented\n");
1144 }
1145 
1146 VOID WINAPI
1147 ImageView_PrintToA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1148 {
1149     DPRINT("ImageView_PrintToA() not implemented\n");
1150 }
1151 
1152 VOID WINAPI
1153 ImageView_PrintToW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1154 {
1155     DPRINT("ImageView_PrintToW() not implemented\n");
1156 }
1157 
1158 BOOL WINAPI
1159 DllMain(IN HINSTANCE hinstDLL,
1160         IN DWORD dwReason,
1161         IN LPVOID lpvReserved)
1162 {
1163     switch (dwReason)
1164     {
1165         case DLL_PROCESS_ATTACH:
1166         case DLL_THREAD_ATTACH:
1167             hInstance = hinstDLL;
1168             break;
1169     }
1170 
1171     return TRUE;
1172 }
1173