//------------------------------------
//  VisualPng.C -- Shows a PNG image
//------------------------------------

// Copyright 2000, Willem van Schaik.

// This code is released under the libpng license.
// For conditions of distribution and use, see the disclaimer
// and license in png.h

// switches

// defines

#define PROGNAME  "VisualPng"
#define LONGNAME  "Win32 Viewer for PNG-files"
#define VERSION   "1.0 of 2000 June 07"

// constants

#define MARGIN 8

// standard includes

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

// application includes

#include "png.h"
#include "pngfile.h"
#include "resource.h"

// macros

// function prototypes

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
BOOL    CALLBACK AboutDlgProc (HWND, UINT, WPARAM, LPARAM) ;

BOOL CenterAbout (HWND hwndChild, HWND hwndParent);

BOOL BuildPngList (PTSTR pstrPathName, TCHAR **ppFileList, int *pFileCount,
        int *pFileIndex);

BOOL SearchPngList (TCHAR *pFileList, int FileCount, int *pFileIndex,
        PTSTR pstrPrevName, PTSTR pstrNextName);

BOOL LoadImageFile(HWND hwnd, PTSTR pstrPathName,
        png_byte **ppbImage, int *pxImgSize, int *pyImgSize, int *piChannels,
        png_color *pBkgColor);

BOOL DisplayImage (HWND hwnd, BYTE **ppDib,
        BYTE **ppDiData, int cxWinSize, int cyWinSize,
        BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
        BOOL bStretched);

BOOL InitBitmap (
        BYTE *pDiData, int cxWinSize, int cyWinSize);

BOOL FillBitmap (
        BYTE *pDiData, int cxWinSize, int cyWinSize,
        BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
        BOOL bStretched);

// a few global variables

static char *szProgName = PROGNAME;
static char *szAppName = LONGNAME;
static char *szIconName = PROGNAME;
static char szCmdFileName [MAX_PATH];

// MAIN routine

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
    HACCEL   hAccel;
    HWND     hwnd;
    MSG      msg;
    WNDCLASS wndclass;
    int ixBorders, iyBorders;

    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = WndProc;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = LoadIcon (hInstance, szIconName) ;
    wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = NULL; // (HBRUSH) GetStockObject (GRAY_BRUSH);
    wndclass.lpszMenuName  = szProgName;
    wndclass.lpszClassName = szProgName;

    if (!RegisterClass (&wndclass))
    {
        MessageBox (NULL, TEXT ("Error: this program requires Windows NT!"),
            szProgName, MB_ICONERROR);
        return 0;
    }

    // if filename given on commandline, store it
    if ((szCmdLine != NULL) && (*szCmdLine != '\0'))
        if (szCmdLine[0] == '"')
            strncpy (szCmdFileName, szCmdLine + 1, strlen(szCmdLine) - 2);
        else
            strcpy (szCmdFileName, szCmdLine);
    else
        strcpy (szCmdFileName, "");

    // calculate size of window-borders
    ixBorders = 2 * (GetSystemMetrics (SM_CXBORDER) +
                     GetSystemMetrics (SM_CXDLGFRAME));
    iyBorders = 2 * (GetSystemMetrics (SM_CYBORDER) +
                     GetSystemMetrics (SM_CYDLGFRAME)) +
                     GetSystemMetrics (SM_CYCAPTION) +
                     GetSystemMetrics (SM_CYMENUSIZE) +
                     1; /* WvS: don't ask me why? */

    hwnd = CreateWindow (szProgName, szAppName,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        512 + 2 * MARGIN + ixBorders, 384 + 2 * MARGIN + iyBorders,
//      CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);

    ShowWindow (hwnd, iCmdShow);
    UpdateWindow (hwnd);

    hAccel = LoadAccelerators (hInstance, szProgName);

    while (GetMessage (&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator (hwnd, hAccel, &msg))
        {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,
        LPARAM lParam)
{
    static HINSTANCE          hInstance ;
    static HDC                hdc;
    static PAINTSTRUCT        ps;
    static HMENU              hMenu;

    static BITMAPFILEHEADER  *pbmfh;
    static BITMAPINFOHEADER  *pbmih;
    static BYTE              *pbImage;
    static int                cxWinSize, cyWinSize;
    static int                cxImgSize, cyImgSize;
    static int                cImgChannels;
    static png_color          bkgColor = {127, 127, 127};

    static BOOL               bStretched = TRUE;

    static BYTE              *pDib = NULL;
    static BYTE              *pDiData = NULL;

    static TCHAR              szImgPathName [MAX_PATH];
    static TCHAR              szTitleName [MAX_PATH];

    static TCHAR             *pPngFileList = NULL;
    static int                iPngFileCount;
    static int                iPngFileIndex;

    BOOL                      bOk;

    switch (message)
    {
    case WM_CREATE:
        hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
        PngFileInitialize (hwnd);

        strcpy (szImgPathName, "");

        // in case we process file given on command-line

        if (szCmdFileName[0] != '\0')
        {
            strcpy (szImgPathName, szCmdFileName);

            // read the other png-files in the directory for later
            // next/previous commands

            BuildPngList (szImgPathName, &pPngFileList, &iPngFileCount,
                          &iPngFileIndex);

            // load the image from file

            if (!LoadImageFile (hwnd, szImgPathName,
                &pbImage, &cxImgSize, &cyImgSize, &cImgChannels, &bkgColor))
                return 0;

            // invalidate the client area for later update

            InvalidateRect (hwnd, NULL, TRUE);

            // display the PNG into the DIBitmap

            DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
                pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
        }

        return 0;

    case WM_SIZE:
        cxWinSize = LOWORD (lParam);
        cyWinSize = HIWORD (lParam);

        // invalidate the client area for later update

        InvalidateRect (hwnd, NULL, TRUE);

        // display the PNG into the DIBitmap

        DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
            pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);

        return 0;

    case WM_INITMENUPOPUP:
        hMenu = GetMenu (hwnd);

        if (pbImage)
            EnableMenuItem (hMenu, IDM_FILE_SAVE, MF_ENABLED);
        else
            EnableMenuItem (hMenu, IDM_FILE_SAVE, MF_GRAYED);

        return 0;

    case WM_COMMAND:
        hMenu = GetMenu (hwnd);

        switch (LOWORD (wParam))
        {
        case IDM_FILE_OPEN:

            // show the File Open dialog box

            if (!PngFileOpenDlg (hwnd, szImgPathName, szTitleName))
                return 0;

            // read the other png-files in the directory for later
            // next/previous commands

            BuildPngList (szImgPathName, &pPngFileList, &iPngFileCount,
                          &iPngFileIndex);

            // load the image from file

            if (!LoadImageFile (hwnd, szImgPathName,
                &pbImage, &cxImgSize, &cyImgSize, &cImgChannels, &bkgColor))
                return 0;

            // invalidate the client area for later update

            InvalidateRect (hwnd, NULL, TRUE);

            // display the PNG into the DIBitmap

            DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
                pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);

            return 0;

        case IDM_FILE_SAVE:

            // show the File Save dialog box

            if (!PngFileSaveDlg (hwnd, szImgPathName, szTitleName))
                return 0;

            // save the PNG to a disk file

            SetCursor (LoadCursor (NULL, IDC_WAIT));
            ShowCursor (TRUE);

            bOk = PngSaveImage (szImgPathName, pDiData, cxWinSize, cyWinSize,
                  bkgColor);

            ShowCursor (FALSE);
            SetCursor (LoadCursor (NULL, IDC_ARROW));

            if (!bOk)
                MessageBox (hwnd, TEXT ("Error in saving the PNG image"),
                szProgName, MB_ICONEXCLAMATION | MB_OK);
            return 0;

        case IDM_FILE_NEXT:

            // read next entry in the directory

            if (SearchPngList (pPngFileList, iPngFileCount, &iPngFileIndex,
                NULL, szImgPathName))
            {
                if (strcmp (szImgPathName, "") == 0)
                    return 0;
                
                // load the image from file
                
                if (!LoadImageFile (hwnd, szImgPathName, &pbImage,
                        &cxImgSize, &cyImgSize, &cImgChannels, &bkgColor))
                    return 0;
                
                // invalidate the client area for later update
                
                InvalidateRect (hwnd, NULL, TRUE);
                
                // display the PNG into the DIBitmap
                
                DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
                    pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
            }
            
            return 0;

        case IDM_FILE_PREVIOUS:

            // read previous entry in the directory

            if (SearchPngList (pPngFileList, iPngFileCount, &iPngFileIndex,
                szImgPathName, NULL))
            {
                
                if (strcmp (szImgPathName, "") == 0)
                    return 0;
                
                // load the image from file
                
                if (!LoadImageFile (hwnd, szImgPathName, &pbImage, &cxImgSize,
                    &cyImgSize, &cImgChannels, &bkgColor))
                    return 0;
                
                // invalidate the client area for later update
                
                InvalidateRect (hwnd, NULL, TRUE);
                
                // display the PNG into the DIBitmap
                
                DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
                    pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);
            }

            return 0;

        case IDM_FILE_EXIT:

            // more cleanup needed...

            // free image buffer

            if (pDib != NULL)
            {
                free (pDib);
                pDib = NULL;
            }

            // free file-list

            if (pPngFileList != NULL)
            {
                free (pPngFileList);
                pPngFileList = NULL;
            }

            // let's go ...

            exit (0);

            return 0;

        case IDM_OPTIONS_STRETCH:
            bStretched = !bStretched;
            if (bStretched)
                CheckMenuItem (hMenu, IDM_OPTIONS_STRETCH, MF_CHECKED);
            else
                CheckMenuItem (hMenu, IDM_OPTIONS_STRETCH, MF_UNCHECKED);

            // invalidate the client area for later update

            InvalidateRect (hwnd, NULL, TRUE);

            // display the PNG into the DIBitmap

            DisplayImage (hwnd, &pDib, &pDiData, cxWinSize, cyWinSize,
                pbImage, cxImgSize, cyImgSize, cImgChannels, bStretched);

            return 0;

        case IDM_HELP_ABOUT:
            DialogBox (hInstance, TEXT ("AboutBox"), hwnd, AboutDlgProc) ;
            return 0;

        } // end switch

        break;

    case WM_PAINT:
        hdc = BeginPaint (hwnd, &ps);

        if (pDib)
            SetDIBitsToDevice (hdc, 0, 0, cxWinSize, cyWinSize, 0, 0,
                0, cyWinSize, pDiData, (BITMAPINFO *) pDib, DIB_RGB_COLORS);

        EndPaint (hwnd, &ps);
        return 0;

    case WM_DESTROY:
        if (pbmfh)
        {
            free (pbmfh);
            pbmfh = NULL;
        }

        PostQuitMessage (0);
        return 0;
    }

    return DefWindowProc (hwnd, message, wParam, lParam);
}

BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT message,
                            WPARAM wParam, LPARAM lParam)
{
     switch (message)
     {
     case WM_INITDIALOG :
          ShowWindow (hDlg, SW_HIDE);
          CenterAbout (hDlg, GetWindow (hDlg, GW_OWNER));
          ShowWindow (hDlg, SW_SHOW);
          return TRUE ;

     case WM_COMMAND :
          switch (LOWORD (wParam))
          {
          case IDOK :
          case IDCANCEL :
               EndDialog (hDlg, 0) ;
               return TRUE ;
          }
          break ;
     }
     return FALSE ;
}

//---------------
//  CenterAbout
//---------------

BOOL CenterAbout (HWND hwndChild, HWND hwndParent)
{
   RECT    rChild, rParent, rWorkArea;
   int     wChild, hChild, wParent, hParent;
   int     xNew, yNew;
   BOOL  bResult;

   // Get the Height and Width of the child window
   GetWindowRect (hwndChild, &rChild);
   wChild = rChild.right - rChild.left;
   hChild = rChild.bottom - rChild.top;

   // Get the Height and Width of the parent window
   GetWindowRect (hwndParent, &rParent);
   wParent = rParent.right - rParent.left;
   hParent = rParent.bottom - rParent.top;

   // Get the limits of the 'workarea'
   bResult = SystemParametersInfo(
      SPI_GETWORKAREA,  // system parameter to query or set
      sizeof(RECT),
      &rWorkArea,
      0);
   if (!bResult) {
      rWorkArea.left = rWorkArea.top = 0;
      rWorkArea.right = GetSystemMetrics(SM_CXSCREEN);
      rWorkArea.bottom = GetSystemMetrics(SM_CYSCREEN);
   }

   // Calculate new X position, then adjust for workarea
   xNew = rParent.left + ((wParent - wChild) /2);
   if (xNew < rWorkArea.left) {
      xNew = rWorkArea.left;
   } else if ((xNew+wChild) > rWorkArea.right) {
      xNew = rWorkArea.right - wChild;
   }

   // Calculate new Y position, then adjust for workarea
   yNew = rParent.top  + ((hParent - hChild) /2);
   if (yNew < rWorkArea.top) {
      yNew = rWorkArea.top;
   } else if ((yNew+hChild) > rWorkArea.bottom) {
      yNew = rWorkArea.bottom - hChild;
   }

   // Set it, and return
   return SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE |
          SWP_NOZORDER);
}

//----------------
//  BuildPngList
//----------------

BOOL BuildPngList (PTSTR pstrPathName, TCHAR **ppFileList, int *pFileCount,
     int *pFileIndex)
{
    static TCHAR              szImgPathName [MAX_PATH];
    static TCHAR              szImgFileName [MAX_PATH];
    static TCHAR              szImgFindName [MAX_PATH];

    WIN32_FIND_DATA           finddata;
    HANDLE                    hFind;

    static TCHAR              szTmp [MAX_PATH];
    BOOL                      bOk;
    int                       i, ii;
    int                       j, jj;

    // free previous file-list

    if (*ppFileList != NULL)
    {
        free (*ppFileList);
        *ppFileList = NULL;
    }

    // extract foldername, filename and search-name

    strcpy (szImgPathName, pstrPathName);
    strcpy (szImgFileName, strrchr (pstrPathName, '\\') + 1);

    strcpy (szImgFindName, szImgPathName);
    *(strrchr (szImgFindName, '\\') + 1) = '\0';
    strcat (szImgFindName, "*.png");

    // first cycle: count number of files in directory for memory allocation

    *pFileCount = 0;

    hFind = FindFirstFile(szImgFindName, &finddata);
    bOk = (hFind != (HANDLE) -1);

    while (bOk)
    {
        *pFileCount += 1;
        bOk = FindNextFile(hFind, &finddata);
    }
    FindClose(hFind);

    // allocation memory for file-list

    *ppFileList = (TCHAR *) malloc (*pFileCount * MAX_PATH);

    // second cycle: read directory and store filenames in file-list

    hFind = FindFirstFile(szImgFindName, &finddata);
    bOk = (hFind != (HANDLE) -1);

    i = 0;
    ii = 0;
    while (bOk)
    {
        strcpy (*ppFileList + ii, szImgPathName);
        strcpy (strrchr(*ppFileList + ii, '\\') + 1, finddata.cFileName);

        if (strcmp(pstrPathName, *ppFileList + ii) == 0)
            *pFileIndex = i;

        ii += MAX_PATH;
        i++;

        bOk = FindNextFile(hFind, &finddata);
    }
    FindClose(hFind);

    // finally we must sort the file-list

    for (i = 0; i < *pFileCount - 1; i++)
    {
        ii = i * MAX_PATH;
        for (j = i+1; j < *pFileCount; j++)
        {
            jj = j * MAX_PATH;
            if (strcmp (*ppFileList + ii, *ppFileList + jj) > 0)
            {
                strcpy (szTmp, *ppFileList + jj);
                strcpy (*ppFileList + jj, *ppFileList + ii);
                strcpy (*ppFileList + ii, szTmp);

                // check if this was the current image that we moved

                if (*pFileIndex == i)
                    *pFileIndex = j;
                else
                    if (*pFileIndex == j)
                        *pFileIndex = i;
            }
        }
    }

    return TRUE;
}

//----------------
//  SearchPngList
//----------------

BOOL SearchPngList (
        TCHAR *pFileList, int FileCount, int *pFileIndex,
        PTSTR pstrPrevName, PTSTR pstrNextName)
{
    if (FileCount > 0)
    {
        // get previous entry
        
        if (pstrPrevName != NULL)
        {
            if (*pFileIndex > 0)
                *pFileIndex -= 1;
            else
                *pFileIndex = FileCount - 1;
            
            strcpy (pstrPrevName, pFileList + (*pFileIndex * MAX_PATH));
        }
        
        // get next entry
        
        if (pstrNextName != NULL)
        {
            if (*pFileIndex < FileCount - 1)
                *pFileIndex += 1;
            else
                *pFileIndex = 0;
            
            strcpy (pstrNextName, pFileList + (*pFileIndex * MAX_PATH));
        }
        
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

//-----------------
//  LoadImageFile
//-----------------

BOOL LoadImageFile (HWND hwnd, PTSTR pstrPathName,
                png_byte **ppbImage, int *pxImgSize, int *pyImgSize,
                int *piChannels, png_color *pBkgColor)
{
    static TCHAR szTmp [MAX_PATH];

    // if there's an existing PNG, free the memory

    if (*ppbImage)
    {
        free (*ppbImage);
        *ppbImage = NULL;
    }

    // Load the entire PNG into memory

    SetCursor (LoadCursor (NULL, IDC_WAIT));
    ShowCursor (TRUE);

    PngLoadImage (pstrPathName, ppbImage, pxImgSize, pyImgSize, piChannels,
                  pBkgColor);

    ShowCursor (FALSE);
    SetCursor (LoadCursor (NULL, IDC_ARROW));

    if (*ppbImage != NULL)
    {
        sprintf (szTmp, "VisualPng - %s", strrchr(pstrPathName, '\\') + 1);
        SetWindowText (hwnd, szTmp);
    }
    else
    {
        MessageBox (hwnd, TEXT ("Error in loading the PNG image"),
            szProgName, MB_ICONEXCLAMATION | MB_OK);
        return FALSE;
    }

    return TRUE;
}

//----------------
//  DisplayImage
//----------------

BOOL DisplayImage (HWND hwnd, BYTE **ppDib,
        BYTE **ppDiData, int cxWinSize, int cyWinSize,
        BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
        BOOL bStretched)
{
    BYTE                       *pDib = *ppDib;
    BYTE                       *pDiData = *ppDiData;
    // BITMAPFILEHEADER        *pbmfh;
    BITMAPINFOHEADER           *pbmih;
    WORD                        wDIRowBytes;
    png_color                   bkgBlack = {0, 0, 0};
    png_color                   bkgGray  = {127, 127, 127};
    png_color                   bkgWhite = {255, 255, 255};

    // allocate memory for the Device Independant bitmap

    wDIRowBytes = (WORD) ((3 * cxWinSize + 3L) >> 2) << 2;

    if (pDib)
    {
        free (pDib);
        pDib = NULL;
    }

    if (!(pDib = (BYTE *) malloc (sizeof(BITMAPINFOHEADER) +
        wDIRowBytes * cyWinSize)))
    {
        MessageBox (hwnd, TEXT ("Error in displaying the PNG image"),
            szProgName, MB_ICONEXCLAMATION | MB_OK);
        *ppDib = pDib = NULL;
        return FALSE;
    }
    *ppDib = pDib;
    memset (pDib, 0, sizeof(BITMAPINFOHEADER));

    // initialize the dib-structure

    pbmih = (BITMAPINFOHEADER *) pDib;
    pbmih->biSize = sizeof(BITMAPINFOHEADER);
    pbmih->biWidth = cxWinSize;
    pbmih->biHeight = -((long) cyWinSize);
    pbmih->biPlanes = 1;
    pbmih->biBitCount = 24;
    pbmih->biCompression = 0;
    pDiData = pDib + sizeof(BITMAPINFOHEADER);
    *ppDiData = pDiData;

    // first fill bitmap with gray and image border

    InitBitmap (pDiData, cxWinSize, cyWinSize);

    // then fill bitmap with image

    if (pbImage)
    {
        FillBitmap (
            pDiData, cxWinSize, cyWinSize,
            pbImage, cxImgSize, cyImgSize, cImgChannels,
            bStretched);
    }

    return TRUE;
}

//--------------
//  InitBitmap
//--------------

BOOL InitBitmap (BYTE *pDiData, int cxWinSize, int cyWinSize)
{
    BYTE *dst;
    int x, y, col;

    // initialize the background with gray

    dst = pDiData;
    for (y = 0; y < cyWinSize; y++)
    {
        col = 0;
        for (x = 0; x < cxWinSize; x++)
        {
            // fill with GRAY
            *dst++ = 127;
            *dst++ = 127;
            *dst++ = 127;
            col += 3;
        }
        // rows start on 4 byte boundaries
        while ((col % 4) != 0)
        {
            dst++;
            col++;
        }
    }

    return TRUE;
}

//--------------
//  FillBitmap
//--------------

BOOL FillBitmap (
        BYTE *pDiData, int cxWinSize, int cyWinSize,
        BYTE *pbImage, int cxImgSize, int cyImgSize, int cImgChannels,
        BOOL bStretched)
{
    BYTE *pStretchedImage;
    BYTE *pImg;
    BYTE *src, *dst;
    BYTE r, g, b, a;
    const int cDIChannels = 3;
    WORD wImgRowBytes;
    WORD wDIRowBytes;
    int cxNewSize, cyNewSize;
    int cxImgPos, cyImgPos;
    int xImg, yImg;
    int xWin, yWin;
    int xOld, yOld;
    int xNew, yNew;

    if (bStretched)
    {
        cxNewSize = cxWinSize - 2 * MARGIN;
        cyNewSize = cyWinSize - 2 * MARGIN;

        // stretch the image to it's window determined size

        // the following two are the same, but the first has side-effects
        // because of rounding
//      if ((cyNewSize / cxNewSize) > (cyImgSize / cxImgSize))
        if ((cyNewSize * cxImgSize) > (cyImgSize * cxNewSize))
        {
            cyNewSize = cxNewSize * cyImgSize / cxImgSize;
            cxImgPos = MARGIN;
            cyImgPos = (cyWinSize - cyNewSize) / 2;
        }
        else
        {
            cxNewSize = cyNewSize * cxImgSize / cyImgSize;
            cyImgPos = MARGIN;
            cxImgPos = (cxWinSize - cxNewSize) / 2;
        }

        pStretchedImage = malloc (cImgChannels * cxNewSize * cyNewSize);
        pImg = pStretchedImage;

        for (yNew = 0; yNew < cyNewSize; yNew++)
        {
            yOld = yNew * cyImgSize / cyNewSize;
            for (xNew = 0; xNew < cxNewSize; xNew++)
            {
                xOld = xNew * cxImgSize / cxNewSize;

                r = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld) + 0);
                g = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld) + 1);
                b = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld) + 2);
                *pImg++ = r;
                *pImg++ = g;
                *pImg++ = b;
                if (cImgChannels == 4)
                {
                    a = *(pbImage + cImgChannels * ((yOld * cxImgSize) + xOld)
                        + 3);
                    *pImg++ = a;
                }
            }
        }

        // calculate row-bytes

        wImgRowBytes = cImgChannels * cxNewSize;
        wDIRowBytes = (WORD) ((cDIChannels * cxWinSize + 3L) >> 2) << 2;

        // copy image to screen

        for (yImg = 0, yWin = cyImgPos; yImg < cyNewSize; yImg++, yWin++)
        {
            if (yWin >= cyWinSize - cyImgPos)
                break;
            src = pStretchedImage + yImg * wImgRowBytes;
            dst = pDiData + yWin * wDIRowBytes + cxImgPos * cDIChannels;

            for (xImg = 0, xWin = cxImgPos; xImg < cxNewSize; xImg++, xWin++)
            {
                if (xWin >= cxWinSize - cxImgPos)
                    break;
                r = *src++;
                g = *src++;
                b = *src++;
                *dst++ = b; /* note the reverse order */
                *dst++ = g;
                *dst++ = r;
                if (cImgChannels == 4)
                {
                    a = *src++;
                }
            }
        }

        // free memory

        if (pStretchedImage != NULL)
        {
            free (pStretchedImage);
            pStretchedImage = NULL;
        }

    }

    // process the image not-stretched

    else
    {
        // calculate the central position

        cxImgPos = (cxWinSize - cxImgSize) / 2;
        cyImgPos = (cyWinSize - cyImgSize) / 2;

        // check for image larger than window

        if (cxImgPos < MARGIN)
            cxImgPos = MARGIN;
        if (cyImgPos < MARGIN)
            cyImgPos = MARGIN;

        // calculate both row-bytes

        wImgRowBytes = cImgChannels * cxImgSize;
        wDIRowBytes = (WORD) ((cDIChannels * cxWinSize + 3L) >> 2) << 2;

        // copy image to screen

        for (yImg = 0, yWin = cyImgPos; yImg < cyImgSize; yImg++, yWin++)
        {
            if (yWin >= cyWinSize - MARGIN)
                break;
            src = pbImage + yImg * wImgRowBytes;
            dst = pDiData + yWin * wDIRowBytes + cxImgPos * cDIChannels;

            for (xImg = 0, xWin = cxImgPos; xImg < cxImgSize; xImg++, xWin++)
            {
                if (xWin >= cxWinSize - MARGIN)
                    break;
                r = *src++;
                g = *src++;
                b = *src++;
                *dst++ = b; /* note the reverse order */
                *dst++ = g;
                *dst++ = r;
                if (cImgChannels == 4)
                {
                    a = *src++;
                }
            }
        }
    }

    return TRUE;
}

//-----------------
//  end of source
//-----------------