1 //------------------------------------
2 // VisualPng.C -- Shows a PNG image
3 //------------------------------------
5 // Copyright 2000, Willem van Schaik. For conditions of distribution and
6 // use, see the copyright/license/disclaimer notice in png.h
12 #define PROGNAME "VisualPng"
13 #define LONGNAME "Win32 Viewer for PNG-files"
14 #define VERSION "1.0 of 2000 June 07"
27 // application includes
35 // function prototypes
37 LRESULT CALLBACK
WndProc (HWND
, UINT
, WPARAM
, LPARAM
);
38 BOOL CALLBACK
AboutDlgProc (HWND
, UINT
, WPARAM
, LPARAM
) ;
40 BOOL
CenterAbout (HWND hwndChild
, HWND hwndParent
);
42 BOOL
BuildPngList (PTSTR pstrPathName
, TCHAR
**ppFileList
, int *pFileCount
,
45 BOOL
SearchPngList (TCHAR
*pFileList
, int FileCount
, int *pFileIndex
,
46 PTSTR pstrPrevName
, PTSTR pstrNextName
);
48 BOOL
LoadImageFile(HWND hwnd
, PTSTR pstrPathName
,
49 png_byte
**ppbImage
, int *pxImgSize
, int *pyImgSize
, int *piChannels
,
50 png_color
*pBkgColor
);
52 BOOL
DisplayImage (HWND hwnd
, BYTE
**ppDib
,
53 BYTE
**ppDiData
, int cxWinSize
, int cyWinSize
,
54 BYTE
*pbImage
, int cxImgSize
, int cyImgSize
, int cImgChannels
,
58 BYTE
*pDiData
, int cxWinSize
, int cyWinSize
);
61 BYTE
*pDiData
, int cxWinSize
, int cyWinSize
,
62 BYTE
*pbImage
, int cxImgSize
, int cyImgSize
, int cImgChannels
,
65 // a few global variables
67 static char *szProgName
= PROGNAME
;
68 static char *szAppName
= LONGNAME
;
69 static char *szIconName
= PROGNAME
;
70 static char szCmdFileName
[MAX_PATH
];
74 int WINAPI
WinMain (HINSTANCE hInstance
, HINSTANCE hPrevInstance
,
75 PSTR szCmdLine
, int iCmdShow
)
81 int ixBorders
, iyBorders
;
83 wndclass
.style
= CS_HREDRAW
| CS_VREDRAW
;
84 wndclass
.lpfnWndProc
= WndProc
;
85 wndclass
.cbClsExtra
= 0;
86 wndclass
.cbWndExtra
= 0;
87 wndclass
.hInstance
= hInstance
;
88 wndclass
.hIcon
= LoadIcon (hInstance
, szIconName
) ;
89 wndclass
.hCursor
= LoadCursor (NULL
, IDC_ARROW
);
90 wndclass
.hbrBackground
= NULL
; // (HBRUSH) GetStockObject (GRAY_BRUSH);
91 wndclass
.lpszMenuName
= szProgName
;
92 wndclass
.lpszClassName
= szProgName
;
94 if (!RegisterClass (&wndclass
))
96 MessageBox (NULL
, TEXT ("Error: this program requires Windows NT!"),
97 szProgName
, MB_ICONERROR
);
101 // if filename given on commandline, store it
102 if ((szCmdLine
!= NULL
) && (*szCmdLine
!= '\0'))
103 if (szCmdLine
[0] == '"')
104 strncpy (szCmdFileName
, szCmdLine
+ 1, strlen(szCmdLine
) - 2);
106 strcpy (szCmdFileName
, szCmdLine
);
108 strcpy (szCmdFileName
, "");
110 // calculate size of window-borders
111 ixBorders
= 2 * (GetSystemMetrics (SM_CXBORDER
) +
112 GetSystemMetrics (SM_CXDLGFRAME
));
113 iyBorders
= 2 * (GetSystemMetrics (SM_CYBORDER
) +
114 GetSystemMetrics (SM_CYDLGFRAME
)) +
115 GetSystemMetrics (SM_CYCAPTION
) +
116 GetSystemMetrics (SM_CYMENUSIZE
) +
117 1; /* WvS: don't ask me why? */
119 hwnd
= CreateWindow (szProgName
, szAppName
,
121 CW_USEDEFAULT
, CW_USEDEFAULT
,
122 512 + 2 * MARGIN
+ ixBorders
, 384 + 2 * MARGIN
+ iyBorders
,
123 // CW_USEDEFAULT, CW_USEDEFAULT,
124 NULL
, NULL
, hInstance
, NULL
);
126 ShowWindow (hwnd
, iCmdShow
);
129 hAccel
= LoadAccelerators (hInstance
, szProgName
);
131 while (GetMessage (&msg
, NULL
, 0, 0))
133 if (!TranslateAccelerator (hwnd
, hAccel
, &msg
))
135 TranslateMessage (&msg
);
136 DispatchMessage (&msg
);
142 LRESULT CALLBACK
WndProc (HWND hwnd
, UINT message
, WPARAM wParam
,
145 static HINSTANCE hInstance
;
147 static PAINTSTRUCT ps
;
150 static BITMAPFILEHEADER
*pbmfh
;
151 static BITMAPINFOHEADER
*pbmih
;
152 static BYTE
*pbImage
;
153 static int cxWinSize
, cyWinSize
;
154 static int cxImgSize
, cyImgSize
;
155 static int cImgChannels
;
156 static png_color bkgColor
= {127, 127, 127};
158 static BOOL bStretched
= TRUE
;
160 static BYTE
*pDib
= NULL
;
161 static BYTE
*pDiData
= NULL
;
163 static TCHAR szImgPathName
[MAX_PATH
];
164 static TCHAR szTitleName
[MAX_PATH
];
166 static TCHAR
*pPngFileList
= NULL
;
167 static int iPngFileCount
;
168 static int iPngFileIndex
;
175 hInstance
= ((LPCREATESTRUCT
) lParam
)->hInstance
;
176 PngFileInitialize (hwnd
);
178 strcpy (szImgPathName
, "");
180 // in case we process file given on command-line
182 if (szCmdFileName
[0] != '\0')
184 strcpy (szImgPathName
, szCmdFileName
);
186 // read the other png-files in the directory for later
187 // next/previous commands
189 BuildPngList (szImgPathName
, &pPngFileList
, &iPngFileCount
,
192 // load the image from file
194 if (!LoadImageFile (hwnd
, szImgPathName
,
195 &pbImage
, &cxImgSize
, &cyImgSize
, &cImgChannels
, &bkgColor
))
198 // invalidate the client area for later update
200 InvalidateRect (hwnd
, NULL
, TRUE
);
202 // display the PNG into the DIBitmap
204 DisplayImage (hwnd
, &pDib
, &pDiData
, cxWinSize
, cyWinSize
,
205 pbImage
, cxImgSize
, cyImgSize
, cImgChannels
, bStretched
);
211 cxWinSize
= LOWORD (lParam
);
212 cyWinSize
= HIWORD (lParam
);
214 // invalidate the client area for later update
216 InvalidateRect (hwnd
, NULL
, TRUE
);
218 // display the PNG into the DIBitmap
220 DisplayImage (hwnd
, &pDib
, &pDiData
, cxWinSize
, cyWinSize
,
221 pbImage
, cxImgSize
, cyImgSize
, cImgChannels
, bStretched
);
225 case WM_INITMENUPOPUP
:
226 hMenu
= GetMenu (hwnd
);
229 EnableMenuItem (hMenu
, IDM_FILE_SAVE
, MF_ENABLED
);
231 EnableMenuItem (hMenu
, IDM_FILE_SAVE
, MF_GRAYED
);
236 hMenu
= GetMenu (hwnd
);
238 switch (LOWORD (wParam
))
242 // show the File Open dialog box
244 if (!PngFileOpenDlg (hwnd
, szImgPathName
, szTitleName
))
247 // read the other png-files in the directory for later
248 // next/previous commands
250 BuildPngList (szImgPathName
, &pPngFileList
, &iPngFileCount
,
253 // load the image from file
255 if (!LoadImageFile (hwnd
, szImgPathName
,
256 &pbImage
, &cxImgSize
, &cyImgSize
, &cImgChannels
, &bkgColor
))
259 // invalidate the client area for later update
261 InvalidateRect (hwnd
, NULL
, TRUE
);
263 // display the PNG into the DIBitmap
265 DisplayImage (hwnd
, &pDib
, &pDiData
, cxWinSize
, cyWinSize
,
266 pbImage
, cxImgSize
, cyImgSize
, cImgChannels
, bStretched
);
272 // show the File Save dialog box
274 if (!PngFileSaveDlg (hwnd
, szImgPathName
, szTitleName
))
277 // save the PNG to a disk file
279 SetCursor (LoadCursor (NULL
, IDC_WAIT
));
282 bOk
= PngSaveImage (szImgPathName
, pDiData
, cxWinSize
, cyWinSize
,
286 SetCursor (LoadCursor (NULL
, IDC_ARROW
));
289 MessageBox (hwnd
, TEXT ("Error in saving the PNG image"),
290 szProgName
, MB_ICONEXCLAMATION
| MB_OK
);
295 // read next entry in the directory
297 if (SearchPngList (pPngFileList
, iPngFileCount
, &iPngFileIndex
,
298 NULL
, szImgPathName
))
300 if (strcmp (szImgPathName
, "") == 0)
303 // load the image from file
305 if (!LoadImageFile (hwnd
, szImgPathName
, &pbImage
,
306 &cxImgSize
, &cyImgSize
, &cImgChannels
, &bkgColor
))
309 // invalidate the client area for later update
311 InvalidateRect (hwnd
, NULL
, TRUE
);
313 // display the PNG into the DIBitmap
315 DisplayImage (hwnd
, &pDib
, &pDiData
, cxWinSize
, cyWinSize
,
316 pbImage
, cxImgSize
, cyImgSize
, cImgChannels
, bStretched
);
321 case IDM_FILE_PREVIOUS
:
323 // read previous entry in the directory
325 if (SearchPngList (pPngFileList
, iPngFileCount
, &iPngFileIndex
,
326 szImgPathName
, NULL
))
329 if (strcmp (szImgPathName
, "") == 0)
332 // load the image from file
334 if (!LoadImageFile (hwnd
, szImgPathName
, &pbImage
, &cxImgSize
,
335 &cyImgSize
, &cImgChannels
, &bkgColor
))
338 // invalidate the client area for later update
340 InvalidateRect (hwnd
, NULL
, TRUE
);
342 // display the PNG into the DIBitmap
344 DisplayImage (hwnd
, &pDib
, &pDiData
, cxWinSize
, cyWinSize
,
345 pbImage
, cxImgSize
, cyImgSize
, cImgChannels
, bStretched
);
352 // more cleanup needed...
364 if (pPngFileList
!= NULL
)
376 case IDM_OPTIONS_STRETCH
:
377 bStretched
= !bStretched
;
379 CheckMenuItem (hMenu
, IDM_OPTIONS_STRETCH
, MF_CHECKED
);
381 CheckMenuItem (hMenu
, IDM_OPTIONS_STRETCH
, MF_UNCHECKED
);
383 // invalidate the client area for later update
385 InvalidateRect (hwnd
, NULL
, TRUE
);
387 // display the PNG into the DIBitmap
389 DisplayImage (hwnd
, &pDib
, &pDiData
, cxWinSize
, cyWinSize
,
390 pbImage
, cxImgSize
, cyImgSize
, cImgChannels
, bStretched
);
395 DialogBox (hInstance
, TEXT ("AboutBox"), hwnd
, AboutDlgProc
) ;
403 hdc
= BeginPaint (hwnd
, &ps
);
406 SetDIBitsToDevice (hdc
, 0, 0, cxWinSize
, cyWinSize
, 0, 0,
407 0, cyWinSize
, pDiData
, (BITMAPINFO
*) pDib
, DIB_RGB_COLORS
);
409 EndPaint (hwnd
, &ps
);
423 return DefWindowProc (hwnd
, message
, wParam
, lParam
);
426 BOOL CALLBACK
AboutDlgProc (HWND hDlg
, UINT message
,
427 WPARAM wParam
, LPARAM lParam
)
432 ShowWindow (hDlg
, SW_HIDE
);
433 CenterAbout (hDlg
, GetWindow (hDlg
, GW_OWNER
));
434 ShowWindow (hDlg
, SW_SHOW
);
438 switch (LOWORD (wParam
))
442 EndDialog (hDlg
, 0) ;
454 BOOL
CenterAbout (HWND hwndChild
, HWND hwndParent
)
456 RECT rChild
, rParent
, rWorkArea
;
457 int wChild
, hChild
, wParent
, hParent
;
461 // Get the Height and Width of the child window
462 GetWindowRect (hwndChild
, &rChild
);
463 wChild
= rChild
.right
- rChild
.left
;
464 hChild
= rChild
.bottom
- rChild
.top
;
466 // Get the Height and Width of the parent window
467 GetWindowRect (hwndParent
, &rParent
);
468 wParent
= rParent
.right
- rParent
.left
;
469 hParent
= rParent
.bottom
- rParent
.top
;
471 // Get the limits of the 'workarea'
472 bResult
= SystemParametersInfo(
473 SPI_GETWORKAREA
, // system parameter to query or set
478 rWorkArea
.left
= rWorkArea
.top
= 0;
479 rWorkArea
.right
= GetSystemMetrics(SM_CXSCREEN
);
480 rWorkArea
.bottom
= GetSystemMetrics(SM_CYSCREEN
);
483 // Calculate new X position, then adjust for workarea
484 xNew
= rParent
.left
+ ((wParent
- wChild
) /2);
485 if (xNew
< rWorkArea
.left
) {
486 xNew
= rWorkArea
.left
;
487 } else if ((xNew
+wChild
) > rWorkArea
.right
) {
488 xNew
= rWorkArea
.right
- wChild
;
491 // Calculate new Y position, then adjust for workarea
492 yNew
= rParent
.top
+ ((hParent
- hChild
) /2);
493 if (yNew
< rWorkArea
.top
) {
494 yNew
= rWorkArea
.top
;
495 } else if ((yNew
+hChild
) > rWorkArea
.bottom
) {
496 yNew
= rWorkArea
.bottom
- hChild
;
499 // Set it, and return
500 return SetWindowPos (hwndChild
, NULL
, xNew
, yNew
, 0, 0, SWP_NOSIZE
|
508 BOOL
BuildPngList (PTSTR pstrPathName
, TCHAR
**ppFileList
, int *pFileCount
,
511 static TCHAR szImgPathName
[MAX_PATH
];
512 static TCHAR szImgFileName
[MAX_PATH
];
513 static TCHAR szImgFindName
[MAX_PATH
];
515 WIN32_FIND_DATA finddata
;
518 static TCHAR szTmp
[MAX_PATH
];
523 // free previous file-list
525 if (*ppFileList
!= NULL
)
531 // extract foldername, filename and search-name
533 strcpy (szImgPathName
, pstrPathName
);
534 strcpy (szImgFileName
, strrchr (pstrPathName
, '\\') + 1);
536 strcpy (szImgFindName
, szImgPathName
);
537 *(strrchr (szImgFindName
, '\\') + 1) = '\0';
538 strcat (szImgFindName
, "*.png");
540 // first cycle: count number of files in directory for memory allocation
544 hFind
= FindFirstFile(szImgFindName
, &finddata
);
545 bOk
= (hFind
!= (HANDLE
) -1);
550 bOk
= FindNextFile(hFind
, &finddata
);
554 // allocation memory for file-list
556 *ppFileList
= (TCHAR
*) malloc (*pFileCount
* MAX_PATH
);
558 // second cycle: read directory and store filenames in file-list
560 hFind
= FindFirstFile(szImgFindName
, &finddata
);
561 bOk
= (hFind
!= (HANDLE
) -1);
567 strcpy (*ppFileList
+ ii
, szImgPathName
);
568 strcpy (strrchr(*ppFileList
+ ii
, '\\') + 1, finddata
.cFileName
);
570 if (strcmp(pstrPathName
, *ppFileList
+ ii
) == 0)
576 bOk
= FindNextFile(hFind
, &finddata
);
580 // finally we must sort the file-list
582 for (i
= 0; i
< *pFileCount
- 1; i
++)
585 for (j
= i
+1; j
< *pFileCount
; j
++)
588 if (strcmp (*ppFileList
+ ii
, *ppFileList
+ jj
) > 0)
590 strcpy (szTmp
, *ppFileList
+ jj
);
591 strcpy (*ppFileList
+ jj
, *ppFileList
+ ii
);
592 strcpy (*ppFileList
+ ii
, szTmp
);
594 // check if this was the current image that we moved
596 if (*pFileIndex
== i
)
599 if (*pFileIndex
== j
)
613 TCHAR
*pFileList
, int FileCount
, int *pFileIndex
,
614 PTSTR pstrPrevName
, PTSTR pstrNextName
)
618 // get previous entry
620 if (pstrPrevName
!= NULL
)
625 *pFileIndex
= FileCount
- 1;
627 strcpy (pstrPrevName
, pFileList
+ (*pFileIndex
* MAX_PATH
));
632 if (pstrNextName
!= NULL
)
634 if (*pFileIndex
< FileCount
- 1)
639 strcpy (pstrNextName
, pFileList
+ (*pFileIndex
* MAX_PATH
));
654 BOOL
LoadImageFile (HWND hwnd
, PTSTR pstrPathName
,
655 png_byte
**ppbImage
, int *pxImgSize
, int *pyImgSize
,
656 int *piChannels
, png_color
*pBkgColor
)
658 static TCHAR szTmp
[MAX_PATH
];
660 // if there's an existing PNG, free the memory
668 // Load the entire PNG into memory
670 SetCursor (LoadCursor (NULL
, IDC_WAIT
));
673 PngLoadImage (pstrPathName
, ppbImage
, pxImgSize
, pyImgSize
, piChannels
,
677 SetCursor (LoadCursor (NULL
, IDC_ARROW
));
679 if (*ppbImage
!= NULL
)
681 sprintf (szTmp
, "VisualPng - %s", strrchr(pstrPathName
, '\\') + 1);
682 SetWindowText (hwnd
, szTmp
);
686 MessageBox (hwnd
, TEXT ("Error in loading the PNG image"),
687 szProgName
, MB_ICONEXCLAMATION
| MB_OK
);
698 BOOL
DisplayImage (HWND hwnd
, BYTE
**ppDib
,
699 BYTE
**ppDiData
, int cxWinSize
, int cyWinSize
,
700 BYTE
*pbImage
, int cxImgSize
, int cyImgSize
, int cImgChannels
,
704 BYTE
*pDiData
= *ppDiData
;
705 // BITMAPFILEHEADER *pbmfh;
706 BITMAPINFOHEADER
*pbmih
;
708 png_color bkgBlack
= {0, 0, 0};
709 png_color bkgGray
= {127, 127, 127};
710 png_color bkgWhite
= {255, 255, 255};
712 // allocate memory for the Device Independant bitmap
714 wDIRowBytes
= (WORD
) ((3 * cxWinSize
+ 3L) >> 2) << 2;
722 if (!(pDib
= (BYTE
*) malloc (sizeof(BITMAPINFOHEADER
) +
723 wDIRowBytes
* cyWinSize
)))
725 MessageBox (hwnd
, TEXT ("Error in displaying the PNG image"),
726 szProgName
, MB_ICONEXCLAMATION
| MB_OK
);
727 *ppDib
= pDib
= NULL
;
731 memset (pDib
, 0, sizeof(BITMAPINFOHEADER
));
733 // initialize the dib-structure
735 pbmih
= (BITMAPINFOHEADER
*) pDib
;
736 pbmih
->biSize
= sizeof(BITMAPINFOHEADER
);
737 pbmih
->biWidth
= cxWinSize
;
738 pbmih
->biHeight
= -((long) cyWinSize
);
740 pbmih
->biBitCount
= 24;
741 pbmih
->biCompression
= 0;
742 pDiData
= pDib
+ sizeof(BITMAPINFOHEADER
);
745 // first fill bitmap with gray and image border
747 InitBitmap (pDiData
, cxWinSize
, cyWinSize
);
749 // then fill bitmap with image
754 pDiData
, cxWinSize
, cyWinSize
,
755 pbImage
, cxImgSize
, cyImgSize
, cImgChannels
,
766 BOOL
InitBitmap (BYTE
*pDiData
, int cxWinSize
, int cyWinSize
)
771 // initialize the background with gray
774 for (y
= 0; y
< cyWinSize
; y
++)
777 for (x
= 0; x
< cxWinSize
; x
++)
785 // rows start on 4 byte boundaries
786 while ((col
% 4) != 0)
801 BYTE
*pDiData
, int cxWinSize
, int cyWinSize
,
802 BYTE
*pbImage
, int cxImgSize
, int cyImgSize
, int cImgChannels
,
805 BYTE
*pStretchedImage
;
809 const int cDIChannels
= 3;
812 int cxNewSize
, cyNewSize
;
813 int cxImgPos
, cyImgPos
;
821 cxNewSize
= cxWinSize
- 2 * MARGIN
;
822 cyNewSize
= cyWinSize
- 2 * MARGIN
;
824 // stretch the image to it's window determined size
826 // the following two are the same, but the first has side-effects
827 // because of rounding
828 // if ((cyNewSize / cxNewSize) > (cyImgSize / cxImgSize))
829 if ((cyNewSize
* cxImgSize
) > (cyImgSize
* cxNewSize
))
831 cyNewSize
= cxNewSize
* cyImgSize
/ cxImgSize
;
833 cyImgPos
= (cyWinSize
- cyNewSize
) / 2;
837 cxNewSize
= cyNewSize
* cxImgSize
/ cyImgSize
;
839 cxImgPos
= (cxWinSize
- cxNewSize
) / 2;
842 pStretchedImage
= malloc (cImgChannels
* cxNewSize
* cyNewSize
);
843 pImg
= pStretchedImage
;
845 for (yNew
= 0; yNew
< cyNewSize
; yNew
++)
847 yOld
= yNew
* cyImgSize
/ cyNewSize
;
848 for (xNew
= 0; xNew
< cxNewSize
; xNew
++)
850 xOld
= xNew
* cxImgSize
/ cxNewSize
;
852 r
= *(pbImage
+ cImgChannels
* ((yOld
* cxImgSize
) + xOld
) + 0);
853 g
= *(pbImage
+ cImgChannels
* ((yOld
* cxImgSize
) + xOld
) + 1);
854 b
= *(pbImage
+ cImgChannels
* ((yOld
* cxImgSize
) + xOld
) + 2);
858 if (cImgChannels
== 4)
860 a
= *(pbImage
+ cImgChannels
* ((yOld
* cxImgSize
) + xOld
)
867 // calculate row-bytes
869 wImgRowBytes
= cImgChannels
* cxNewSize
;
870 wDIRowBytes
= (WORD
) ((cDIChannels
* cxWinSize
+ 3L) >> 2) << 2;
872 // copy image to screen
874 for (yImg
= 0, yWin
= cyImgPos
; yImg
< cyNewSize
; yImg
++, yWin
++)
876 if (yWin
>= cyWinSize
- cyImgPos
)
878 src
= pStretchedImage
+ yImg
* wImgRowBytes
;
879 dst
= pDiData
+ yWin
* wDIRowBytes
+ cxImgPos
* cDIChannels
;
881 for (xImg
= 0, xWin
= cxImgPos
; xImg
< cxNewSize
; xImg
++, xWin
++)
883 if (xWin
>= cxWinSize
- cxImgPos
)
888 *dst
++ = b
; /* note the reverse order */
891 if (cImgChannels
== 4)
900 if (pStretchedImage
!= NULL
)
902 free (pStretchedImage
);
903 pStretchedImage
= NULL
;
908 // process the image not-stretched
912 // calculate the central position
914 cxImgPos
= (cxWinSize
- cxImgSize
) / 2;
915 cyImgPos
= (cyWinSize
- cyImgSize
) / 2;
917 // check for image larger than window
919 if (cxImgPos
< MARGIN
)
921 if (cyImgPos
< MARGIN
)
924 // calculate both row-bytes
926 wImgRowBytes
= cImgChannels
* cxImgSize
;
927 wDIRowBytes
= (WORD
) ((cDIChannels
* cxWinSize
+ 3L) >> 2) << 2;
929 // copy image to screen
931 for (yImg
= 0, yWin
= cyImgPos
; yImg
< cyImgSize
; yImg
++, yWin
++)
933 if (yWin
>= cyWinSize
- MARGIN
)
935 src
= pbImage
+ yImg
* wImgRowBytes
;
936 dst
= pDiData
+ yWin
* wDIRowBytes
+ cxImgPos
* cDIChannels
;
938 for (xImg
= 0, xWin
= cxImgPos
; xImg
< cxImgSize
; xImg
++, xWin
++)
940 if (xWin
>= cxWinSize
- MARGIN
)
945 *dst
++ = b
; /* note the reverse order */
948 if (cImgChannels
== 4)