// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 1993, 1994  Microsoft Corporation.  All Rights Reserved.
//
//  MODULE:    fileio.c
//
//  PURPOSE:   Performs application and instance specific initialization.
//
//  FUNCTIONS:
//    LoadDIBSection    - Loads a DIB from file into buffer created by 
//                        CreateDIBSection
//    SaveDIB           - Saves the specified dib in a file
//    MyRead            - Reads files larger than 64k bytes in size
//    MyWrite           - Writes data to files in 32k byte chunks
//
//  COMMENTS:
//
 
#include <windows.h>       // required for all Windows applications     
#include <windowsx.h>
#include <commctrl.h>      // prototypes and structure for the common controls.

#include "globals.h"       // prototypes specific to this application
#include "toolbar.h"
#include "palette.h"
#include "dibutil.h"
                                      
// Dib Header Marker - used in writing DIBs to files
#define DIB_HEADER_MARKER   ((WORD)('M' << 8) | 'B')

// Local Function Prototypes

BOOL MyRead(HFILE, LPSTR, DWORD);
LRESULT CALLBACK MyWrite(HFILE, VOID *, DWORD);
                              
//
//  FUNCTION: LoadDIBSection(HWND, LPSTR)
//
//  PURPOSE: Loads the specified DIB from a file, and reads it into the buffer
//    created by CreateDIBSection.
//
//  PARAMETERS:
//    hwnd          - specifies window for display context
//    lpFileName    - specifies the file to load a DIB from
//
//  RETURN VALUE:
//    A handle to a bitmap, or NULL if unsuccessful.
//
//  COMMENTS:
//    The routines in this sample were not all written to handle OS/2 DIBs.
//    This function will reject any file that is not a Windows DIB.
//
//
HBITMAP LoadDIBSection(HWND hwnd, LPSTR lpFileName)
{   
    HDC                 hdc;
    HFILE               hFile;
    BITMAPFILEHEADER    bmfHeader;    
    UINT                nNumColors;
    HDIB                hDIBNew, hDIBInfoTemp;  
    LPBITMAPINFOHEADER  lpbih;    
    OFSTRUCT            ofs;    
    UINT                uBytes;

    // Set the cursor to a hourglass, in case the loading operation
    // takes more than a sec, the user will know what's going on.
    SetCursor(LoadCursor(NULL, IDC_WAIT)); 
    
    // open the DIB file
    hFile = OpenFile(lpFileName, &ofs, OF_READ);
    if (hFile == -1)
    {
        DIBError(ERR_FILENOTFOUND);
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        return NULL;
    }
       
    // Allocate memory for header & color table.
    hDIBNew = GlobalAlloc(GHND, 
                          (DWORD)(sizeof(BITMAPINFOHEADER) +
                          256 * sizeof(RGBQUAD)));

    if (!hDIBNew)
    {    
        DIBError(ERR_MEMORY);
        goto ErrExit;
    }

    lpbih = (LPBITMAPINFOHEADER)GlobalLock(hDIBNew);
    if (!lpbih)
    {
        DIBError(ERR_LOCK);
        goto ErrExitFree;  
    }    

    // read the BITMAPFILEHEADER from our file

    if (sizeof(BITMAPFILEHEADER) != _lread(hFile, (LPSTR)&bmfHeader,
            sizeof(BITMAPFILEHEADER))) 
    {
        DIBError(ERR_READ);               
        goto ErrExitUnlock;
    }    

    if (bmfHeader.bfType != 0x4d42)  // 'BM'
    {
        DIBError(ERR_NOT_DIB);
        goto ErrExitUnlock;
    }    

    // read the BITMAPINFOHEADER
    if (sizeof(BITMAPINFOHEADER) != _lread(hFile, (LPSTR)lpbih,
            sizeof(BITMAPINFOHEADER)))
    {
        DIBError(ERR_READ);        
        goto ErrExitUnlock;
    }    

    // Check to see that it's a Windows DIB -- an OS/2 DIB would cause 
    // problems with some of the functions in this sample since the fields
    // in the header are different and color table entries are smaller.
    // If it's not a Windows DIB (e.g. if biSize is wrong), return NULL.
    if (lpbih->biSize != sizeof(BITMAPINFOHEADER))
    {    
        DIBError(ERR_NOT_DIB);
        goto ErrExitUnlock;
    }    
     
    // check to see if it's a format we support
    switch (lpbih->biBitCount)
    {
        case 1:
        case 4:
        case 8:            
        case 16:
        case 24:   

            // the format is supported so continue
            break;

        default:
            // the format isn't supported so fail
            DIBError(ERR_UNSUPPORTEDFORMAT);
            goto ErrExitUnlock;            
    }
    
    // Now determine the size of the color table and read it.  Since the
    // bitmap bits are offset in the file by bfOffBits, do some special 
    // processing to make sure the bits directly follow the color table  
    if (!(nNumColors = (UINT)lpbih->biClrUsed))
    {
        // color table only for 8 bits per pixel or less

        if (lpbih->biBitCount <= 8)
            nNumColors = 1 << lpbih->biBitCount; // standard size table
    }

    // fill in some default values if they are zero
    if (lpbih->biClrUsed == 0)
        lpbih->biClrUsed = nNumColors;

    if (lpbih->biSizeImage == 0)
        lpbih->biSizeImage = WIDTHBYTES((DWORD)lpbih->biWidth * 
                             (DWORD)lpbih->biBitCount) * lpbih->biHeight;

    // get a proper-sized buffer for header, color table and bits
    GlobalUnlock(hDIBNew);
    hDIBInfoTemp = GlobalReAlloc(hDIBNew, 
                                 lpbih->biSize + nNumColors *
                                 sizeof(RGBQUAD) + lpbih->biSizeImage, 
                                 0);

    if (!hDIBInfoTemp) // can't resize buffer for loading
    {
        DIBError(ERR_MEMORY);
        goto ErrExitFree;
    }    
    else
        hDIBNew = hDIBInfoTemp;

    lpbih = (LPBITMAPINFOHEADER)GlobalLock(hDIBNew);
    if (!lpbih) 
    {
        DIBError(ERR_LOCK);    
        goto ErrExitFree;
    }
      
    // read the color table
    uBytes = _lread(hFile, 
                    (LPSTR)(lpbih) + lpbih->biSize, 
                    nNumColors * sizeof(RGBQUAD)); 
                    
    if (uBytes == HFILE_ERROR)  
    {
        DIBError(ERR_READ);
        goto ErrExitUnlock;
    } 
    
    // we've reached the point of no return (no undo)
    // remove existing DIB section
    RemoveDIBSection();   
    
    // set global variable to new DIB header    
    hDIBInfo = hDIBNew;
                                                
    // create new DIB section
    hdc = GetDC(hwnd);  
    hBitmap = CreateDIBSection(hdc, (LPBITMAPINFO)lpbih, DIB_RGB_COLORS, 
        &lpvBits, 0, 0L);
    ReleaseDC(hwnd, hdc);
    
    if (!hBitmap || !lpvBits)
    {   
        DIBError(ERR_CREATEDIBSECTION);
          
        if (hBitmap)
            DeleteObject(hBitmap);         
            
        // create default wash for palette
        if (bPalDevice)
            hPalette = CreateDefaultPalette();
                                    
        goto ErrExitUnlock;
    }
     
    // fill the buffer allocated by CreateDIBSection with bitmap bits        
    _llseek(hFile, bmfHeader.bfOffBits, SEEK_SET);
    if (!MyRead(hFile, (LPSTR)lpvBits, (lpbih->biSizeImage)))
    {
        // failed to read the DIB bits into the DIB section so get rid of it
        DIBError(ERR_READ);
        DeleteObject(hBitmap);
        goto ErrExitUnlock;
    }
    
    // create a palette for the DIB section from the DIB color table
    hPalette = CreateDIBPalette(hDIBInfo);
    // if hPalette is NULL it won't lead to failure condition, so don't check it

    // unlock the DIB header but don't free it!
    GlobalUnlock(hDIBInfo);
     
    _lclose(hFile); 
    SetCursor(LoadCursor(NULL, IDC_ARROW));
    
    // update status of menu and toolbar   
    EnableMenuItem(hMenu, IDM_FILESAVEAS, MF_ENABLED);    
    EnableMenuItem(hMenu, IDM_FILECLOSE, MF_ENABLED);
    EnableMenuItem(hMenu, IDM_CLEAR, MF_ENABLED); 
   
    return hBitmap;
           
ErrExitUnlock:
    // note: hDIBNew == hDIBInfo if failure occurred after CreateDIBSection call
    GlobalUnlock(hDIBNew);    

ErrExitFree:
    // note: hDIBNew == hDIBInfo if failure occurred after CreateDIBSection call    
    GlobalFree(hDIBNew);
   
ErrExit:
    _lclose(hFile);
    SetCursor(LoadCursor(NULL, IDC_ARROW));
    return NULL;
}

           
//
//  FUNCTION: SaveDIB(HDIB, LPVOID, LPSTR)
//
//  PURPOSE:  Saves a DIB to a file.
//
//  PARAMETERS:
//    hDib       - Handle to BITMAPFINFO and color table (if any) of dib
//    lpvDIBBits - Pointer to bitmap bits
//    lpFileName - Pointer to full pathname to save DIB under
//
//  RETURN VALUE:
//    0 if successful, or one of ERR_INVALIDHANDLE, ERR_OPEN or ERR_LOCK.
//
//  COMMENTS:
//    The routines in this sample were not all written to handle OS/2 DIBs.
//    This function will reject any file that is not a Windows DIB.
//
//
WORD SaveDIB(HDIB hDib, LPVOID lpvDIBits, LPSTR lpFileName)
{
    BITMAPFILEHEADER    bmfHdr;     // Header for Bitmap file
    LPBITMAPINFOHEADER  lpbih;       // Pointer to DIB info structure
    HFILE               fh;         // file handle for opened file
    OFSTRUCT            of;         // OpenFile structure
    DWORD               dwDIBHeaderSize, dwDIBBitsSize;
    DWORD               dwError;    // Error return from MyWrite

    if (!hDib)
        return ERR_INVALIDHANDLE;

    fh = OpenFile(lpFileName, &of, OF_CREATE | OF_READWRITE);

    if (fh == HFILE_ERROR)
        return ERR_OPEN;

    // Get a pointer to the DIB memory, the first of which contains
    // a BITMAPINFO structure

    lpbih = (LPBITMAPINFOHEADER)GlobalLock(hDib);
    if (!lpbih)
        return ERR_LOCK;

    // Check to see if we're dealing with an OS/2 DIB.  If so, don't
    // save it because our functions aren't written to deal with these
    // DIBs.

    if (lpbih->biSize != sizeof(BITMAPINFOHEADER))
    {
        GlobalUnlock(hDib);
        return ERR_NOT_DIB;
    }

    // Fill in the fields of the file header

    // Fill in file type (first 2 bytes must be "BM" for a bitmap)

    bmfHdr.bfType = DIB_HEADER_MARKER;  // "BM"

    // Calculating the size of the DIB is a bit tricky (if we want to
    // do it right).  The easiest way to do this is to call GlobalSize()
    // on our global handle, but since the size of our global memory may have
    // been padded a few bytes, we may end up writing out a few too
    // many bytes to the file (which may cause problems with some apps,
    // like HC 3.0).
    //
    // So, instead let's calculate the size manually.
    //
    // We most likely do not have a packed DIB to work with, in other words,
    // the bitmap bits do not immediately follow the DIB header and color table
    // (if any).  So calculate sizes individually.
 
    // Calculate size of header and color table (if any)
    dwDIBHeaderSize = *(LPDWORD)lpbih + PaletteSize((LPSTR)lpbih);  

    // Now calculate the size of the image

    // It's an RLE bitmap, we can't calculate size, so trust the biSizeImage
    // field

    if ((lpbih->biCompression == BI_RLE8) || (lpbih->biCompression == BI_RLE4))
        dwDIBBitsSize = lpbih->biSizeImage;
    else
    {       
        // It's not RLE, so size is Width (DWORD aligned) * Height

        dwDIBBitsSize = WIDTHBYTES((lpbih->biWidth)*((DWORD)lpbih->biBitCount)) *
                lpbih->biHeight;
       
        // Now, since we have calculated the correct size, why don't we
        // fill in the biSizeImage field (this will fix any .BMP files which 
        // have this field incorrect).

        lpbih->biSizeImage = dwDIBBitsSize;
    }


    // Calculate file size by adding DIB size to sizeof(BITMAPFILEHEADER)
                   
    bmfHdr.bfSize = dwDIBHeaderSize + dwDIBBitsSize + sizeof(BITMAPFILEHEADER);
    bmfHdr.bfReserved1 = 0;
    bmfHdr.bfReserved2 = 0;

    // Now, calculate the offset the actual bitmap bits will be in
    // the file -- It's the Bitmap file header plus the DIB header,
    // plus the size of the color table.
    
    bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + dwDIBHeaderSize;

    // Write the file header

    _lwrite(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER));

    // Write the DIB header and the bits -- use local version of
    // MyWrite, so we can write more than 32767 bytes of data
    
    dwError = MyWrite(fh, (LPSTR)lpbih, dwDIBHeaderSize); 
    if (dwError != 0)
        dwError = MyWrite(fh, (LPSTR)lpvBits, dwDIBBitsSize);
        
    GlobalUnlock(hDib);
    _lclose(fh);

    if (dwError == 0)
        return ERR_SAVE; // oops, something happened in the write
    else
        return 0; // Success code
}

//
//  FUNCTION: MyRead(int, LPSTR, DWORD)
//
//  PURPOSE:  Routine to read files greater than 64K bytes in size.
//
//  PARAMETERS:
//    hFile     - Handle to the file to be read.
//    lpBuffer  - Pointer to buffer to receive data.
//    dwSize    - Size of the file in bytes.
//
//  RETURN VALUE:
//    TRUE  - Success
//    FALSE - An error occurred.
//
//  COMMENTS:
//
//
BOOL MyRead(HFILE hFile, LPSTR lpBuffer, DWORD dwSize)
{
    int nBytes;

    // Read in the data in 32767 byte chunks (or a smaller amount if it's
    // the last chunk of data read)

    while (dwSize)
    {
        nBytes = (int)(dwSize > (DWORD)32767 ? 32767 : LOWORD(dwSize));

        if (_lread(hFile, (LPSTR)lpBuffer, nBytes) != (WORD)nBytes)
            return FALSE;

        dwSize -= nBytes;
        lpBuffer += nBytes;
    }
    
    return TRUE;
}

//
//  FUNCTION: MyWrite(int, LPSTR, DWORD)
//
//  PURPOSE:  Writes data in steps of 32k till all the data is written.
//            Normal _lwrite uses a WORD as 3rd parameter, so it is
//            limited to 32767 bytes, but this procedure is not.
//
//  PARAMETERS:
//    iFileHandle - Handle of the file to write to.
//    lpBuffer    - Pointer to buffer to receive data.
//    dwBytes     - Amount of data to write in bytes.
//
//  RETURN VALUE:
//    Number of bytes written if successful, or 0 if there is an error.
//
//  COMMENTS:
//
//
LRESULT CALLBACK MyWrite(HFILE iFileHandle, LPSTR lpBuffer, DWORD dwBytes)
{
    DWORD dwBytesTmp = dwBytes;       // Save # of bytes for return value

   
    // Write out the data in 32767 byte chunks.

    while (dwBytes > 32767)
    {
        if (_lwrite(iFileHandle, (LPSTR)lpBuffer, (WORD)32767) != 32767)
            return 0;
        dwBytes -= 32767;
        lpBuffer += 32767;
    }

    // Write out the last chunk (which is < 32767 bytes)

    if (_lwrite(iFileHandle, (LPSTR)lpBuffer, (WORD)dwBytes) != (WORD)dwBytes)
        return 0;  
        
    return dwBytesTmp;
}


