1 /*------------------------------------
2 * VisualPng.C -- Shows a PNG image
3 *------------------------------------
5 * Copyright 2000,2017 Willem van Schaik.
7 * This code is released under the libpng license.
8 * For conditions of distribution and use, see the disclaimer
16 #define PROGNAME "VisualPng"
17 #define LONGNAME "Win32 Viewer for PNG-files"
18 #define VERSION "1.0 of 2000 June 07"
24 /* standard includes */
32 /* application includes */
40 /* function prototypes */
42 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
43 BOOL CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ;
45 BOOL CenterAbout (HWND hwndChild, HWND hwndParent);
47 BOOL BuildPngList (PTSTR pstrPathName, TCHAR **ppFileList, int *pFileCount,
50 BOOL SearchPngList (TCHAR *pFileList, int FileCount, int *pFileIndex,
51 PTSTR pstrPrevName, PTSTR pstrNextName);
53 BOOL LoadImageFile(HWND hwnd, PTSTR pstrPathName,
54 png_byte **ppbImage, int *pxImgSize, int *pyImgSize, int *piChannels,
55 png_color *pBkgColor);
57 BOOL DisplayImage (HWND hwnd, BYTE **ppDib,
58 BYTE **ppDiData, int cxWinSize, int cyWinSize,
59 BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
63 BYTE *pDiData, int cxWinSize, int cyWinSize);
66 BYTE *pDiData, int cxWinSize, int cyWinSize,
67 BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
70 /* a few global variables */
72 static char *szProgName = PROGNAME;
73 static char *szAppName = LONGNAME;
74 static char *szIconName = PROGNAME;
75 static char szCmdFileName [MAX_PATH];
79 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
80 PSTR szCmdLine, int iCmdShow)
86 int ixBorders, iyBorders;
88 wndclass.style = CS_HREDRAW | CS_VREDRAW;
89 wndclass.lpfnWndProc = WndProc;
90 wndclass.cbClsExtra = 0;
91 wndclass.cbWndExtra = 0;
92 wndclass.hInstance = hInstance;
93 wndclass.hIcon = LoadIcon (hInstance, szIconName) ;
94 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
95 wndclass.hbrBackground = NULL; /* (HBRUSH) GetStockObject (GRAY_BRUSH); */
96 wndclass.lpszMenuName = szProgName;
97 wndclass.lpszClassName = szProgName;
99 if (!RegisterClass (&wndclass))
101 MessageBox (NULL, TEXT ("Error: this program requires Windows NT!"),
102 szProgName, MB_ICONERROR);
106 /* if filename given on commandline, store it */
107 if ((szCmdLine != NULL) && (*szCmdLine != '\0'))
108 if (szCmdLine[0] == '"')
109 strncpy (szCmdFileName, szCmdLine + 1, strlen(szCmdLine) - 2);
111 strcpy (szCmdFileName, szCmdLine);
113 strcpy (szCmdFileName, "");
115 /* calculate size of window-borders */
116 ixBorders = 2 * (GetSystemMetrics (SM_CXBORDER) +
117 GetSystemMetrics (SM_CXDLGFRAME));
118 iyBorders = 2 * (GetSystemMetrics (SM_CYBORDER) +
119 GetSystemMetrics (SM_CYDLGFRAME)) +
120 GetSystemMetrics (SM_CYCAPTION) +
121 GetSystemMetrics (SM_CYMENUSIZE) +
122 1; /* WvS: don't ask me why? */
124 hwnd = CreateWindow (szProgName, szAppName,
126 CW_USEDEFAULT, CW_USEDEFAULT,
127 512 + 2 * MARGIN + ixBorders, 384 + 2 * MARGIN + iyBorders,
128 /* CW_USEDEFAULT, CW_USEDEFAULT, */
129 NULL, NULL, hInstance, NULL);
131 ShowWindow (hwnd, iCmdShow);
134 hAccel = LoadAccelerators (hInstance, szProgName);
136 while (GetMessage (&msg, NULL, 0, 0))
138 if (!TranslateAccelerator (hwnd, hAccel, &msg))
140 TranslateMessage (&msg);
141 DispatchMessage (&msg);
147 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,
150 static HINSTANCE hInstance ;
152 static PAINTSTRUCT ps;
155 static BITMAPFILEHEADER *pbmfh;
156 static BITMAPINFOHEADER *pbmih;
157 static BYTE *pbImage;
158 static int cxWinSize, cyWinSize;
159 static int cxImgSize, cyImgSize;
160 static int cImgChannels;
161 static png_color bkgColor = {127, 127, 127};
163 static BOOL bStretched = TRUE;
165 static BYTE *pDib = NULL;
166 static BYTE *pDiData = NULL;
168 static TCHAR szImgPathName [MAX_PATH];
169 static TCHAR szTitleName [MAX_PATH];
171 static TCHAR *pPngFileList = NULL;
172 static int iPngFileCount;
173 static int iPngFileIndex;
180 hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
181 PngFileInitialize (hwnd);
183 strcpy (szImgPathName, "");
185 /* in case we process file given on command-line */
187 if (szCmdFileName[0] != '\0')
189 strcpy (szImgPathName, szCmdFileName);
191 /* read the other png-files in the directory for later */
192 /* next/previous commands */
194 BuildPngList (szImgPathName, &pPngFileList, &iPngFileCount,
197 /* load the image from file */
199 if (!LoadImageFile (hwnd, szImgPathName,
200 &pbImage, &cxImgSize, &cyImgSize, &cImgChannels, &bkgColor))
203 /* invalidate the client area for later update */
205 InvalidateRect (hwnd, NULL, TRUE);
207 /* display the PNG into the DIBitmap */
209 DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
210 pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
216 cxWinSize = LOWORD (lParam);
217 cyWinSize = HIWORD (lParam);
219 /* invalidate the client area for later update */
221 InvalidateRect (hwnd, NULL, TRUE);
223 /* display the PNG into the DIBitmap */
225 DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
226 pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
230 case WM_INITMENUPOPUP:
231 hMenu = GetMenu (hwnd);
234 EnableMenuItem (hMenu, IDM_FILE_SAVE, MF_ENABLED);
236 EnableMenuItem (hMenu, IDM_FILE_SAVE, MF_GRAYED);
241 hMenu = GetMenu (hwnd);
243 switch (LOWORD (wParam))
247 /* show the File Open dialog box */
249 if (!PngFileOpenDlg (hwnd, szImgPathName, szTitleName))
252 /* read the other png-files in the directory for later */
253 /* next/previous commands */
255 BuildPngList (szImgPathName, &pPngFileList, &iPngFileCount,
258 /* load the image from file */
260 if (!LoadImageFile (hwnd, szImgPathName,
261 &pbImage, &cxImgSize, &cyImgSize, &cImgChannels, &bkgColor))
264 /* invalidate the client area for later update */
266 InvalidateRect (hwnd, NULL, TRUE);
268 /* display the PNG into the DIBitmap */
270 DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
271 pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
277 /* show the File Save dialog box */
279 if (!PngFileSaveDlg (hwnd, szImgPathName, szTitleName))
282 /* save the PNG to a disk file */
284 SetCursor (LoadCursor (NULL, IDC_WAIT));
287 bOk = PngSaveImage (szImgPathName, pDiData, cxWinSize, cyWinSize,
291 SetCursor (LoadCursor (NULL, IDC_ARROW));
294 MessageBox (hwnd, TEXT ("Error in saving the PNG image"),
295 szProgName, MB_ICONEXCLAMATION | MB_OK);
300 /* read next entry in the directory */
302 if (SearchPngList (pPngFileList, iPngFileCount, &iPngFileIndex,
303 NULL, szImgPathName))
305 if (strcmp (szImgPathName, "") == 0)
308 /* load the image from file */
310 if (!LoadImageFile (hwnd, szImgPathName, &pbImage,
311 &cxImgSize, &cyImgSize, &cImgChannels, &bkgColor))
314 /* invalidate the client area for later update */
316 InvalidateRect (hwnd, NULL, TRUE);
318 /* display the PNG into the DIBitmap */
320 DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
321 pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
326 case IDM_FILE_PREVIOUS:
328 /* read previous entry in the directory */
330 if (SearchPngList (pPngFileList, iPngFileCount, &iPngFileIndex,
331 szImgPathName, NULL))
334 if (strcmp (szImgPathName, "") == 0)
337 /* load the image from file */
339 if (!LoadImageFile (hwnd, szImgPathName, &pbImage, &cxImgSize,
340 &cyImgSize, &cImgChannels, &bkgColor))
343 /* invalidate the client area for later update */
345 InvalidateRect (hwnd, NULL, TRUE);
347 /* display the PNG into the DIBitmap */
349 DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
350 pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
357 /* more cleanup needed... */
359 /* free image buffer */
369 if (pPngFileList != NULL)
381 case IDM_OPTIONS_STRETCH:
382 bStretched = !bStretched;
384 CheckMenuItem (hMenu, IDM_OPTIONS_STRETCH, MF_CHECKED);
386 CheckMenuItem (hMenu, IDM_OPTIONS_STRETCH, MF_UNCHECKED);
388 /* invalidate the client area for later update */
390 InvalidateRect (hwnd, NULL, TRUE);
392 /* display the PNG into the DIBitmap */
394 DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
395 pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
400 DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ;
408 hdc = BeginPaint (hwnd, &ps);
411 SetDIBitsToDevice (hdc, 0, 0, cxWinSize, cyWinSize, 0, 0,
412 0, cyWinSize, pDiData, (BITMAPINFO *) pDib, DIB_RGB_COLORS);
414 EndPaint (hwnd, &ps);
428 return DefWindowProc (hwnd, message, wParam, lParam);
431 BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,
432 WPARAM wParam, LPARAM lParam)
437 ShowWindow (hDlg, SW_HIDE);
438 CenterAbout (hDlg, GetWindow (hDlg, GW_OWNER));
439 ShowWindow (hDlg, SW_SHOW);
443 switch (LOWORD (wParam))
447 EndDialog (hDlg, 0) ;
459 BOOL CenterAbout (HWND hwndChild, HWND hwndParent)
461 RECT rChild, rParent, rWorkArea;
462 int wChild, hChild, wParent, hParent;
466 /* Get the Height and Width of the child window */
467 GetWindowRect (hwndChild, &rChild);
468 wChild = rChild.right - rChild.left;
469 hChild = rChild.bottom - rChild.top;
471 /* Get the Height and Width of the parent window */
472 GetWindowRect (hwndParent, &rParent);
473 wParent = rParent.right - rParent.left;
474 hParent = rParent.bottom - rParent.top;
476 /* Get the limits of the 'workarea' */
477 bResult = SystemParametersInfo(
478 SPI_GETWORKAREA, /* system parameter to query or set */
483 rWorkArea.left = rWorkArea.top = 0;
484 rWorkArea.right = GetSystemMetrics(SM_CXSCREEN);
485 rWorkArea.bottom = GetSystemMetrics(SM_CYSCREEN);
488 /* Calculate new X position, then adjust for workarea */
489 xNew = rParent.left + ((wParent - wChild) /2);
490 if (xNew < rWorkArea.left) {
491 xNew = rWorkArea.left;
492 } else if ((xNew+wChild) > rWorkArea.right) {
493 xNew = rWorkArea.right - wChild;
496 /* Calculate new Y position, then adjust for workarea */
497 yNew = rParent.top + ((hParent - hChild) /2);
498 if (yNew < rWorkArea.top) {
499 yNew = rWorkArea.top;
500 } else if ((yNew+hChild) > rWorkArea.bottom) {
501 yNew = rWorkArea.bottom - hChild;
504 /* Set it, and return */
505 return SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE |
513 BOOL BuildPngList (PTSTR pstrPathName, TCHAR **ppFileList, int *pFileCount,
516 static TCHAR szImgPathName [MAX_PATH];
517 static TCHAR szImgFileName [MAX_PATH];
518 static TCHAR szImgFindName [MAX_PATH];
520 WIN32_FIND_DATA finddata;
523 static TCHAR szTmp [MAX_PATH];
528 /* free previous file-list */
530 if (*ppFileList != NULL)
536 /* extract foldername, filename and search-name */
538 strcpy (szImgPathName, pstrPathName);
539 strcpy (szImgFileName, strrchr (pstrPathName, '\\') + 1);
541 strcpy (szImgFindName, szImgPathName);
542 *(strrchr (szImgFindName, '\\') + 1) = '\0';
543 strcat (szImgFindName, "*.png");
545 /* first cycle: count number of files in directory for memory allocation */
549 hFind = FindFirstFile(szImgFindName, &finddata);
550 bOk = (hFind != (HANDLE) -1);
555 bOk = FindNextFile(hFind, &finddata);
559 /* allocation memory for file-list */
561 *ppFileList = (TCHAR *) malloc (*pFileCount * MAX_PATH);
563 /* second cycle: read directory and store filenames in file-list */
565 hFind = FindFirstFile(szImgFindName, &finddata);
566 bOk = (hFind != (HANDLE) -1);
572 strcpy (*ppFileList + ii, szImgPathName);
573 strcpy (strrchr(*ppFileList + ii, '\\') + 1, finddata.cFileName);
575 if (strcmp(pstrPathName, *ppFileList + ii) == 0)
581 bOk = FindNextFile(hFind, &finddata);
585 /* finally we must sort the file-list */
587 for (i = 0; i < *pFileCount - 1; i++)
590 for (j = i+1; j < *pFileCount; j++)
593 if (strcmp (*ppFileList + ii, *ppFileList + jj) > 0)
595 strcpy (szTmp, *ppFileList + jj);
596 strcpy (*ppFileList + jj, *ppFileList + ii);
597 strcpy (*ppFileList + ii, szTmp);
599 /* check if this was the current image that we moved */
601 if (*pFileIndex == i)
604 if (*pFileIndex == j)
619 TCHAR *pFileList, int FileCount, int *pFileIndex,
620 PTSTR pstrPrevName, PTSTR pstrNextName)
624 /* get previous entry */
626 if (pstrPrevName != NULL)
631 *pFileIndex = FileCount - 1;
633 strcpy (pstrPrevName, pFileList + (*pFileIndex * MAX_PATH));
638 if (pstrNextName != NULL)
640 if (*pFileIndex < FileCount - 1)
645 strcpy (pstrNextName, pFileList + (*pFileIndex * MAX_PATH));
661 BOOL LoadImageFile (HWND hwnd, PTSTR pstrPathName,
662 png_byte **ppbImage, int *pxImgSize, int *pyImgSize,
663 int *piChannels, png_color *pBkgColor)
665 static TCHAR szTmp [MAX_PATH];
667 /* if there's an existing PNG, free the memory */
675 /* Load the entire PNG into memory */
677 SetCursor (LoadCursor (NULL, IDC_WAIT));
680 PngLoadImage (pstrPathName, ppbImage, pxImgSize, pyImgSize, piChannels,
684 SetCursor (LoadCursor (NULL, IDC_ARROW));
686 if (*ppbImage != NULL)
688 sprintf (szTmp, "VisualPng - %s", strrchr(pstrPathName, '\\') + 1);
689 SetWindowText (hwnd, szTmp);
693 MessageBox (hwnd, TEXT ("Error in loading the PNG image"),
694 szProgName, MB_ICONEXCLAMATION | MB_OK);
705 BOOL DisplayImage (HWND hwnd, BYTE **ppDib,
706 BYTE **ppDiData, int cxWinSize, int cyWinSize,
707 BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
711 BYTE *pDiData = *ppDiData;
712 /* BITMAPFILEHEADER *pbmfh; */
713 BITMAPINFOHEADER *pbmih;
715 png_color bkgBlack = {0, 0, 0};
716 png_color bkgGray = {127, 127, 127};
717 png_color bkgWhite = {255, 255, 255};
719 /* allocate memory for the Device Independent bitmap */
721 wDIRowBytes = (WORD) ((3 * cxWinSize + 3L) >> 2) << 2;
729 if (cyWinSize > ((size_t)(-1))/wDIRowBytes) {
731 MessageBox (hwnd, TEXT ("Visual PNG: image is too big");
733 if (!(pDib = (BYTE *) malloc (sizeof(BITMAPINFOHEADER) +
734 wDIRowBytes * cyWinSize)))
736 MessageBox (hwnd, TEXT ("Error in displaying the PNG image"),
737 szProgName, MB_ICONEXCLAMATION | MB_OK);
738 *ppDib = pDib = NULL;
742 memset (pDib, 0, sizeof(BITMAPINFOHEADER));
744 /* initialize the dib-structure */
746 pbmih = (BITMAPINFOHEADER *) pDib;
747 pbmih->biSize = sizeof(BITMAPINFOHEADER);
748 pbmih->biWidth = cxWinSize;
749 pbmih->biHeight = -((long) cyWinSize);
751 pbmih->biBitCount = 24;
752 pbmih->biCompression = 0;
753 pDiData = pDib + sizeof(BITMAPINFOHEADER);
756 /* first fill bitmap with gray and image border */
758 InitBitmap (pDiData, cxWinSize, cyWinSize);
760 /* then fill bitmap with image */
765 pDiData, cxWinSize, cyWinSize,
766 pbImage, cxImgSize, cyImgSize, cImgChannels,
777 BOOL InitBitmap (BYTE *pDiData, int cxWinSize, int cyWinSize)
782 /* initialize the background with gray */
785 for (y = 0; y < cyWinSize; y++)
788 for (x = 0; x < cxWinSize; x++)
796 /* rows start on 4 byte boundaries */
797 while ((col % 4) != 0)
812 BYTE *pDiData, int cxWinSize, int cyWinSize,
813 BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
816 BYTE *pStretchedImage;
820 const int cDIChannels = 3;
823 int cxNewSize, cyNewSize;
824 int cxImgPos, cyImgPos;
832 cxNewSize = cxWinSize - 2 * MARGIN;
833 cyNewSize = cyWinSize - 2 * MARGIN;
835 /* stretch the image to it's window determined size */
837 /* the following two are mathematically the same, but the first
838 * has side-effects because of rounding
840 /* if ((cyNewSize / cxNewSize) > (cyImgSize / cxImgSize)) */
841 if ((cyNewSize * cxImgSize) > (cyImgSize * cxNewSize))
843 cyNewSize = cxNewSize * cyImgSize / cxImgSize;
845 cyImgPos = (cyWinSize - cyNewSize) / 2;
849 cxNewSize = cyNewSize * cxImgSize / cyImgSize;
851 cxImgPos = (cxWinSize - cxNewSize) / 2;
854 if (cyNewSize > ((size_t)(-1))/(cImgChannels * cxNewSize)) {
856 MessageBox (hwnd, TEXT ("Visual PNG: stretched image is too big");
858 pStretchedImage = malloc (cImgChannels * cxNewSize * cyNewSize);
859 pImg = pStretchedImage;
861 for (yNew = 0; yNew < cyNewSize; yNew++)
863 yOld = yNew * cyImgSize / cyNewSize;
864 for (xNew = 0; xNew < cxNewSize; xNew++)
866 xOld = xNew * cxImgSize / cxNewSize;
868 r = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld) + 0);
869 g = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld) + 1);
870 b = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld) + 2);
874 if (cImgChannels == 4)
876 a = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld)
883 /* calculate row-bytes */
885 wImgRowBytes = cImgChannels * cxNewSize;
886 wDIRowBytes = (WORD) ((cDIChannels * cxWinSize + 3L) >> 2) << 2;
888 /* copy image to screen */
890 for (yImg = 0, yWin = cyImgPos; yImg < cyNewSize; yImg++, yWin++)
892 if (yWin >= cyWinSize - cyImgPos)
894 src = pStretchedImage + yImg * wImgRowBytes;
895 dst = pDiData + yWin * wDIRowBytes + cxImgPos * cDIChannels;
897 for (xImg = 0, xWin = cxImgPos; xImg < cxNewSize; xImg++, xWin++)
899 if (xWin >= cxWinSize - cxImgPos)
904 *dst++ = b; /* note the reverse order */
907 if (cImgChannels == 4)
916 if (pStretchedImage != NULL)
918 free (pStretchedImage);
919 pStretchedImage = NULL;
924 /* process the image not-stretched */
928 /* calculate the central position */
930 cxImgPos = (cxWinSize - cxImgSize) / 2;
931 cyImgPos = (cyWinSize - cyImgSize) / 2;
933 /* check for image larger than window */
935 if (cxImgPos < MARGIN)
937 if (cyImgPos < MARGIN)
940 /* calculate both row-bytes */
942 wImgRowBytes = cImgChannels * cxImgSize;
943 wDIRowBytes = (WORD) ((cDIChannels * cxWinSize + 3L) >> 2) << 2;
945 /* copy image to screen */
947 for (yImg = 0, yWin = cyImgPos; yImg < cyImgSize; yImg++, yWin++)
949 if (yWin >= cyWinSize - MARGIN)
951 src = pbImage + yImg * wImgRowBytes;
952 dst = pDiData + yWin * wDIRowBytes + cxImgPos * cDIChannels;
954 for (xImg = 0, xWin = cxImgPos; xImg < cxImgSize; xImg++, xWin++)
956 if (xWin >= cxWinSize - MARGIN)
961 *dst++ = b; /* note the reverse order */
964 if (cImgChannels == 4)