/*************************************************************************
 *
 * Source file for Windows 95/Win32.
 *
 * The function LoadTIFFinDIB in this source file let you load
 * a TIFF file and build a memory DIB with it and return the
 * HANDLE (HDIB) of the memory block containing the DIB.
 *
 *  Example :
 *
 *   HDIB   hDIB;
 *   hDIB = LoadTIFFinDIB("sample.tif");
 *
 *
 * To build this source file you must include the TIFF library
 * in your project.
 *
 * 4/12/95   Philippe Tenenhaus   100423.3705@compuserve.com
 *
 ************************************************************************/

#include "tiffio.h"

#define HDIB HANDLE
#define IS_WIN30_DIB(lpbi) ((*(LPDWORD)(lpbi)) == sizeof(BITMAPINFOHEADER))
#define CVT(x) (((x)*255L) / ((1L << 16) - 1))

static HDIB CreateDIB(DWORD dwWidth, DWORD dwHeight, WORD wBitCount);
static LPSTR FindDIBBits(LPSTR lpDIB);
static WORD PaletteSize(LPSTR lpDIB);
static WORD DIBNumColors(LPSTR lpDIB);
static int checkcmap(int n, uint16_t *r, uint16_t *g, uint16_t *b);

/*************************************************************************
 *
 * HDIB LoadTIFFinDIB(LPSTR lpFileName)
 *
 * Parameter:
 *
 * LPSTR lpDIB      - File name of a tiff imag
 *
 * Return Value:
 *
 * LPSTR            - HANDLE of a DIB
 *
 * Description:
 *
 * This function load a TIFF file and build a memory DIB with it
 * and return the HANDLE (HDIB) of the memory block containing
 * the DIB.
 *
 * 4/12/95   Philippe Tenenhaus   100423.3705@compuserve.com
 *
 ************************************************************************/

HDIB LoadTIFFinDIB(LPSTR lpFileName)
{
    TIFF *tif;
    unsigned long imageLength;
    unsigned long imageWidth;
    unsigned int BitsPerSample;
    unsigned long LineSize;
    unsigned int SamplePerPixel;
    unsigned long RowsPerStrip;
    int PhotometricInterpretation;
    long nrow;
    unsigned long row;
    char *buf;
    LPBITMAPINFOHEADER lpDIB;
    HDIB hDIB;
    char *lpBits;
    HGLOBAL hStrip;
    int i, l;
    int Align;

    tif = TIFFOpen(lpFileName, "r");

    if (!tif)
        goto TiffOpenError;

    TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imageWidth);
    TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
    TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &BitsPerSample);
    TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &RowsPerStrip);
    TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &RowsPerStrip);
    TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &PhotometricInterpretation);

    LineSize = TIFFScanlineSize(tif); // Number of byte in ine line

    SamplePerPixel = (int)(LineSize / imageWidth);

    // Align = Number of byte to add at the end of each line of the DIB
    Align = 4 - (LineSize % 4);
    if (Align == 4)
        Align = 0;

    // Create a new DIB
    hDIB = CreateDIB((DWORD)imageWidth, (DWORD)imageLength,
                     (WORD)(BitsPerSample * SamplePerPixel));
    lpDIB = (LPBITMAPINFOHEADER)GlobalLock(hDIB);
    if (!lpDIB)
        goto OutOfDIBMemory;

    if (lpDIB)
        lpBits = FindDIBBits((LPSTR)lpDIB);
    else
        lpBits = NULL;

    // In the tiff file the lines are save from up to down
    // In a DIB the lines must be save from down to up
    if (lpBits)
    {
        lpBits = FindDIBBits((LPSTR)lpDIB);
        lpBits += ((imageWidth * SamplePerPixel) + Align) * (imageLength - 1);
        // now lpBits pointe on the bottom line

        hStrip = GlobalAlloc(GHND, TIFFStripSize(tif));
        buf = GlobalLock(hStrip);

        if (!buf)
            goto OutOfBufMemory;

        // PhotometricInterpretation = 2 image is RGB
        // PhotometricInterpretation = 3 image have a color palette
        if (PhotometricInterpretation == 3)
        {
            uint16_t *red;
            uint16_t *green;
            uint16_t *blue;
            int16_t i;
            LPBITMAPINFO lpbmi;
            int Palette16Bits;

            TIFFGetField(tif, TIFFTAG_COLORMAP, &red, &green, &blue);

            // Is the palette 16 or 8 bits ?
            if (checkcmap(1 << BitsPerSample, red, green, blue) == 16)
                Palette16Bits = TRUE;
            else
                Palette16Bits = FALSE;

            lpbmi = (LPBITMAPINFO)lpDIB;

            // load the palette in the DIB
            for (i = (1 << BitsPerSample) - 1; i >= 0; i--)
            {
                if (Palette16Bits)
                {
                    lpbmi->bmiColors[i].rgbRed = (BYTE)CVT(red[i]);
                    lpbmi->bmiColors[i].rgbGreen = (BYTE)CVT(green[i]);
                    lpbmi->bmiColors[i].rgbBlue = (BYTE)CVT(blue[i]);
                }
                else
                {
                    lpbmi->bmiColors[i].rgbRed = (BYTE)red[i];
                    lpbmi->bmiColors[i].rgbGreen = (BYTE)green[i];
                    lpbmi->bmiColors[i].rgbBlue = (BYTE)blue[i];
                }
            }
        }

        // read the tiff lines and save them in the DIB
        // with RGB mode, we have to change the order of the 3 samples RGB
        <=> BGR for (row = 0; row < imageLength; row += RowsPerStrip)
        {
            nrow = (row + RowsPerStrip > imageLength ? imageLength - row
                                                     : RowsPerStrip);
            if (TIFFReadEncodedStrip(tif, TIFFComputeStrip(tif, row, 0), buf,
                                     nrow * LineSize) == -1)
            {
                goto TiffReadError;
            }
            else
            {
                for (l = 0; l < nrow; l++)
                {
                    if (SamplePerPixel == 3)
                        for (i = 0; i < (int)(imageWidth); i++)
                        {
                            lpBits[i * SamplePerPixel + 0] =
                                buf[l * LineSize + i * Sample PerPixel + 2];
                            lpBits[i * SamplePerPixel + 1] =
                                buf[l * LineSize + i * Sample PerPixel + 1];
                            lpBits[i * SamplePerPixel + 2] =
                                buf[l * LineSize + i * Sample PerPixel + 0];
                        }
                    else
                        memcpy(lpBits, &buf[(int)(l * LineSize)],
                               (int)imageWidth * SamplePerPixel);

                    lpBits -= imageWidth * SamplePerPixel + Align;
                }
            }
        }
        GlobalUnlock(hStrip);
        GlobalFree(hStrip);
        GlobalUnlock(hDIB);
        TIFFClose(tif);
    }

    return hDIB;

OutOfBufMemory:

TiffReadError:
    GlobalUnlock(hDIB);
    GlobalFree(hStrip);
OutOfDIBMemory:
    TIFFClose(tif);
TiffOpenError:
    return (HANDLE)0;
}

static int checkcmap(int n, uint16_t *r, uint16_t *g, uint16_t *b)
{
    while (n-- > 0)
        if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256)
            return (16);

    return (8);
}

/*************************************************************************
 * All the following functions were created by microsoft, they are
 * parts of the sample project "wincap" given with the SDK Win32.
 *
 * Microsoft says that :
 *
 *  You have a royalty-free right to use, modify, reproduce and
 *  distribute the Sample Files (and/or any modified version) in
 *  any way you find useful, provided that you agree that
 *  Microsoft has no warranty obligations or liability for any
 *  Sample Application Files which are modified.
 *
 ************************************************************************/

HDIB CreateDIB(DWORD dwWidth, DWORD dwHeight, WORD wBitCount)
{
    BITMAPINFOHEADER bi;     // bitmap header
    LPBITMAPINFOHEADER lpbi; // pointer to BITMAPINFOHEADER
    DWORD dwLen;             // size of memory block
    HDIB hDIB;
    DWORD dwBytesPerLine; // Number of bytes per scanline

    // Make sure bits per pixel is valid
    if (wBitCount <= 1)
        wBitCount = 1;
    else if (wBitCount <= 4)
        wBitCount = 4;
    else if (wBitCount <= 8)
        wBitCount = 8;
    else if (wBitCount <= 24)
        wBitCount = 24;
    else
        wBitCount = 4; // set default value to 4 if parameter is bogus

    // initialize BITMAPINFOHEADER
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = dwWidth;      // fill in width from parameter
    bi.biHeight = dwHeight;    // fill in height from parameter
    bi.biPlanes = 1;           // must be 1
    bi.biBitCount = wBitCount; // from parameter
    bi.biCompression = BI_RGB;
    bi.biSizeImage =
        (dwWidth * dwHeight * wBitCount) / 8; // 0;           // 0's here
    mean "default" bi.biXPelsPerMeter = 2834; // 0;
    bi.biYPelsPerMeter = 2834;                // 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    // calculate size of memory block required to store the DIB.  This
    // block should be big enough to hold the BITMAPINFOHEADER, the color
    // table, and the bits

    dwBytesPerLine = (((wBitCount * dwWidth) + 31) / 32 * 4);
    dwLen = bi.biSize + PaletteSize((LPSTR)&bi) + (dwBytesPerLine * dwHeight);

    // alloc memory block to store our bitmap
    hDIB = GlobalAlloc(GHND, dwLen);

    // major bummer if we couldn't get memory block
    if (!hDIB)
    {
        return NULL;
    }

    // lock memory and get pointer to it
    lpbi = (VOID FAR *)GlobalLock(hDIB);

    // use our bitmap info structure to fill in first part of
    // our DIB with the BITMAPINFOHEADER
    *lpbi = bi;

    // Since we don't know what the colortable and bits should contain,
    // just leave these blank.  Unlock the DIB and return the HDIB.

    GlobalUnlock(hDIB);

    /* return handle to the DIB */
    return hDIB;
}

LPSTR FAR FindDIBBits(LPSTR lpDIB)
{
    return (lpDIB + *(LPDWORD)lpDIB + PaletteSize(lpDIB));
}

WORD FAR PaletteSize(LPSTR lpDIB)
{
    /* calculate the size required by the palette */
    if (IS_WIN30_DIB(lpDIB))
        return (DIBNumColors(lpDIB) * sizeof(RGBQUAD));
    else
        return (DIBNumColors(lpDIB) * sizeof(RGBTRIPLE));
}

WORD DIBNumColors(LPSTR lpDIB)
{
    WORD wBitCount; // DIB bit count

    /*  If this is a Windows-style DIB, the number of colors in the
     *  color table can be less than the number of bits per pixel
     *  allows for (i.e. lpbi->biClrUsed can be set to some value).
     *  If this is the case, return the appropriate value.
     */

    if (IS_WIN30_DIB(lpDIB))
    {
        DWORD dwClrUsed;

        dwClrUsed = ((LPBITMAPINFOHEADER)lpDIB)->biClrUsed;
        if (dwClrUsed)
            return (WORD)dwClrUsed;
    }

    /*  Calculate the number of colors in the color table based on
     *  the number of bits per pixel for the DIB.
     */
    if (IS_WIN30_DIB(lpDIB))
        wBitCount = ((LPBITMAPINFOHEADER)lpDIB)->biBitCount;
    else
        wBitCount = ((LPBITMAPCOREHEADER)lpDIB)->bcBitCount;

    /* return number of colors based on bits per pixel */
    switch (wBitCount)
    {
        case 1:
            return 2;

        case 4:
            return 16;

        case 8:
            return 256;

        default:
            return 0;
    }
}
