/****************************************************************************
 *
 *   cappal.c
 *
 *   Palette processing module.
 *
 *   Microsoft Video for Windows Sample Capture Class
 *
 *   Copyright (c) 1992, 1993 Microsoft Corporation.  All Rights Reserved.
 *
 *    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.
 *
 ***************************************************************************/

#define INC_OLE2
#pragma warning(disable:4103)
#include <windows.h>
#include <windowsx.h>
#include <win32.h>
#include <mmsystem.h>
#include <vfw.h>

#include "ivideo32.h"
#include "avicapi.h"
#include "cappal.h"
#include "capdib.h"
#include "dibmap.h"

//
// Allocate and initialize palette resources at Window create time
//
BOOL PalInit (LPCAPSTREAM lpcs)
{
    return (PalGetPaletteFromDriver (lpcs));
}

//
// FreePaletteCache - free the RGB555 Xlate table
//
void FreePaletteCache (LPCAPSTREAM lpcs)
{
    if (lpcs->lpCacheXlateTable) {
        GlobalFreePtr (lpcs->lpCacheXlateTable);
	lpcs->lpCacheXlateTable = NULL;
    }
}

//
// Release palette resources at Window destroy time
//
void PalFini (LPCAPSTREAM lpcs)
{
    PalDeleteCurrentPalette (lpcs);

    FreePaletteCache (lpcs);
}

//
// Delete our palette if it isn't the system default palette
//
void PalDeleteCurrentPalette (LPCAPSTREAM lpcs)
{
    if (lpcs->hPalCurrent &&
            (lpcs->hPalCurrent != GetStockObject(DEFAULT_PALETTE)))
        DeleteObject (lpcs->hPalCurrent);
    lpcs->hPalCurrent = NULL;
}

//
// Get the current palette (from the driver)
// Returns: TRUE if the driver can supply a palette
//

BOOL PalGetPaletteFromDriver (LPCAPSTREAM lpcs)
{
    FCLOGPALETTE        pal;

    PalDeleteCurrentPalette (lpcs);

    pal.palVersion = 0x0300;
    pal.palNumEntries = 256;

    lpcs->sCapDrvCaps.fDriverSuppliesPalettes = FALSE;  // assume the worst

    if (lpcs->fHardwareConnected) {
        if (videoConfigure (lpcs->hVideoIn,
                DVM_PALETTE,
                VIDEO_CONFIGURE_GET | VIDEO_CONFIGURE_CURRENT, NULL,
                (LPVOID)&pal, sizeof(pal),
                NULL, 0 ) == DV_ERR_OK) {
            if (lpcs->hPalCurrent = CreatePalette ((LPLOGPALETTE) &pal))
                lpcs->sCapDrvCaps.fDriverSuppliesPalettes = TRUE;
        }
    }
    if (!lpcs->hPalCurrent)
        lpcs->hPalCurrent = GetStockObject (DEFAULT_PALETTE);

    DibNewPalette (lpcs, lpcs->hPalCurrent);

    return (lpcs->sCapDrvCaps.fDriverSuppliesPalettes);
}

//
// Set the current palette used for capture by sending a copy to the driver
// and then copying the entries to out DIB.
// This may also be called when reconnecting a driver and using a cached
// copy of the palette.
// Returns TRUE on success, or FALSE on failure.
//
DWORD PalSendPaletteToDriver (LPCAPSTREAM lpcs, HPALETTE hpal, LPBYTE lpXlateTable)
{
    short               nColors;
    FCLOGPALETTE        pal;
    HCURSOR             hOldCursor;

    // The following can take a while so repaint our parent
    UpdateWindow (GetParent (lpcs-> hwnd));
    UpdateWindow (lpcs->hwnd);

    if (!hpal)
        return FALSE;

    // Allocate a xlate table cache?
    if (lpXlateTable) {
        if (lpcs->lpCacheXlateTable == NULL) {
            lpcs->lpCacheXlateTable = GlobalAllocPtr (GHND, 0x8000l);
            if (!lpcs->lpCacheXlateTable)
                return FALSE;
        }

        // If we're not using the cached table, update the cache
        if (lpcs->lpCacheXlateTable != lpXlateTable)
            _fmemcpy (lpcs->lpCacheXlateTable, lpXlateTable, (UINT) 0x8000l);
    }
    else {
        FreePaletteCache (lpcs);
    }

    // Don't destroy the current palette when reconnecting...
    if (hpal != lpcs->hPalCurrent) {
        PalDeleteCurrentPalette (lpcs);
        lpcs->hPalCurrent = hpal;
    }

    GetObject(hpal, sizeof(short), (LPVOID)&nColors);

    if( nColors <= 1 ) {    //!!>
        return( FALSE );
    }

    nColors = min(256, nColors);

    hOldCursor = SetCursor (lpcs-> hWaitCursor);

    statusUpdateStatus (lpcs, IDS_CAP_STAT_PALETTE_BUILD);

    pal.palVersion = 0x0300;
    pal.palNumEntries = nColors;

    GetPaletteEntries(hpal, 0, nColors, pal.palPalEntry);

    if (lpcs-> fHardwareConnected) {

        // first try to send both the xlate table and the palette
        if ((!lpXlateTable) || (videoConfigure( lpcs->hVideoIn,
                    DVM_PALETTERGB555,
                    VIDEO_CONFIGURE_SET, NULL,
                    (LPLOGPALETTE)&pal, sizeof(pal),
                    lpXlateTable, (DWORD) 0x8000) != 0)) {

            // else send just the palette and make the driver build the table
            if (videoConfigure( lpcs->hVideoIn,
                    DVM_PALETTE,
                    VIDEO_CONFIGURE_SET, NULL,
                    (LPLOGPALETTE)&pal, sizeof(pal),
                    NULL, 0 )) {
                // Scrncap doesn't support setting a palette, so
                // delete the palette cache
                FreePaletteCache (lpcs);
            }
        }
    }

    // Supermac wants us to get the palette again, they might have
    // mucked with it!
    PalGetPaletteFromDriver (lpcs);

    // Since the palette has changed, delete any existing compression
    // output format;  this forces a new output format to be selected
    if (lpcs->CompVars.lpbiOut) {
        GlobalFreePtr (lpcs->CompVars.lpbiOut);
        lpcs->CompVars.lpbiOut = NULL;
    }
    if (lpcs->CompVars.hic) {
        if (ICSeqCompressFrameStart(&lpcs->CompVars, lpcs->lpBitsInfo) == 0) {
            errorUpdateError (lpcs, IDS_CAP_COMPRESSOR_ERROR);
        }
    }

    InvalidateRect (lpcs->hwnd, NULL, TRUE);
    UpdateWindow (lpcs->hwnd);

    SetCursor (hOldCursor);
    statusUpdateStatus (lpcs, 0);

    return (TRUE);
}

//
// CopyPalette, makes a copy of a GDI logical palette
// Returns: a handle to the newly created palette, or NULL if error
//

HPALETTE CopyPalette (HPALETTE hpal)
{
    LPLOGPALETTE        lppal;
    short               nNumEntries;

    if (!hpal)
        return NULL;

    GetObject (hpal,sizeof(short),(LPVOID)&nNumEntries);

    if (nNumEntries == 0)
        return NULL;

    lppal = (LPLOGPALETTE) GlobalAllocPtr (GHND,
                sizeof(LOGPALETTE) + nNumEntries * sizeof(PALETTEENTRY));

    if (!lppal)
        return NULL;

    lppal->palVersion    = 0x300;
    lppal->palNumEntries = nNumEntries;

    GetPaletteEntries(hpal,0,nNumEntries,lppal->palPalEntry);

    hpal = CreatePalette(lppal);

    GlobalFreePtr (lppal);

    return hpal;
}


//
// Allocate resources needed for palette capture
// Returns DV_ERR_OK on success, or DV_ERR... on failure.
// Note: if Init fails, you MUST call the Fini function to
// release resources.
//
DWORD CapturePaletteInit (LPCAPSTREAM lpcs, LPCAPPAL lpcp)
{
    DWORD dwError = DV_ERR_OK;

    lpcp->lpBits = NULL;
    lpcp->lp16to8 = NULL;
    lpcp->lpHistogram = NULL;
    lpcp->lpbiSave = NULL;
    lpcp->wNumFrames = 0;

    // Init an RGB16 header
    lpcp->bi16.biSize         = sizeof(BITMAPINFOHEADER);
    lpcp->bi16.biWidth        = lpcs->dxBits;
    lpcp->bi16.biHeight       = lpcs->dyBits;
    lpcp->bi16.biPlanes       = 1;
    lpcp->bi16.biBitCount     = 16;
    lpcp->bi16.biCompression  = BI_RGB;
    lpcp->bi16.biSizeImage    = DIBWIDTHBYTES(lpcp->bi16) * lpcp->bi16.biHeight;
    lpcp->bi16.biXPelsPerMeter= 0;
    lpcp->bi16.biYPelsPerMeter= 0;
    lpcp->bi16.biClrUsed      = 0;
    lpcp->bi16.biClrImportant = 0;

    // Allocate memory for the histogram, DIB, and XLate table
    lpcp->lpBits  = GlobalAllocPtr (GHND, lpcp->bi16.biSizeImage);
    lpcp->lp16to8 = GlobalAllocPtr (GHND, 0x8000l);
    lpcp->lpHistogram = InitHistogram(NULL);

    if (!lpcp->lpBits || !lpcp->lp16to8 || !lpcp->lpHistogram) {
        dwError = DV_ERR_NOMEM;
        goto PalInitError;
    }

    // Init the video header
    lpcp->vHdr.lpData = lpcp->lpBits;
    lpcp->vHdr.dwBufferLength = lpcp->bi16.biSizeImage;
    lpcp->vHdr.dwUser = 0;
    lpcp->vHdr.dwFlags = 0;

    // Save the current format
    lpcp->lpbiSave = DibGetCurrentFormat (lpcs);

    // Make sure we can set the format to 16 bit RGB
    if(dwError = videoConfigure( lpcs->hVideoIn, DVM_FORMAT,
            VIDEO_CONFIGURE_SET, NULL,
            (LPBITMAPINFOHEADER)&lpcp->bi16, sizeof(BITMAPINFOHEADER),
            NULL, 0 ) ) {
        goto PalInitError;
    }

    // Put everything back the way it was
    if (dwError = videoConfigure( lpcs->hVideoIn, DVM_FORMAT,
            VIDEO_CONFIGURE_SET, NULL,
            (LPBITMAPINFOHEADER)lpcp->lpbiSave, lpcp->lpbiSave->bmiHeader.biSize,
            NULL, 0 )) {
        goto PalInitError;
    }

PalInitError:
    return dwError;
}

//
// Free resources used for palette capture
//
DWORD CapturePaletteFini (LPCAPSTREAM lpcs, LPCAPPAL lpcp)
{
    if (lpcp->lpBits) {
        GlobalFreePtr (lpcp->lpBits);
        lpcp->lpBits = NULL;
    }
    if (lpcp->lp16to8) {
        GlobalFreePtr (lpcp->lp16to8);
        lpcp->lp16to8 = NULL;
    }
    if (lpcp->lpHistogram) {
        FreeHistogram(lpcp->lpHistogram);
        lpcp->lpHistogram = NULL;
    }
    if (lpcp->lpbiSave) {
        GlobalFreePtr (lpcp->lpbiSave);
        lpcp->lpbiSave = NULL;
    }
    return DV_ERR_OK;
}

//
//  CapturePaletteFrames() The workhorse of capture palette.
//
DWORD CapturePaletteFrames (LPCAPSTREAM lpcs, LPCAPPAL lpcp, int nCount)
{
    int j;
    DWORD dwError;

    // switch to RGB16 format
    if (dwError = videoConfigure( lpcs->hVideoIn,
                DVM_FORMAT,
                VIDEO_CONFIGURE_SET, NULL,
                (LPBITMAPINFOHEADER)&lpcp->bi16, sizeof(BITMAPINFOHEADER),
                NULL, 0 ))
        goto CaptureFramesError;

    for (j = 0; j < nCount; j++){
        // Get a frame
        dwError = videoFrame(lpcs->hVideoIn, &lpcp->vHdr);

        // Let the user see it
        InvalidateRect (lpcs->hwnd, NULL, TRUE);
        UpdateWindow (lpcs->hwnd);

        // Histogram it
        DibHistogram(&lpcp->bi16, lpcp->lpBits, 0, 0, -1, -1, lpcp->lpHistogram);
        lpcp->wNumFrames++;
    }

    dwError = videoConfigure( lpcs->hVideoIn,
                DVM_FORMAT,
                VIDEO_CONFIGURE_SET, NULL,
                (LPBITMAPINFOHEADER)lpcp->lpbiSave,
                lpcp->lpbiSave->bmiHeader.biSize,
                NULL, 0 );

//    videoFrame( lpcs->hVideoIn, &lpcs->VidHdr );

CaptureFramesError:
    return dwError;
}

//
//  CapturePaletteAuto() capture a palette from the video source
//  without user intervention.
//  Returns TRUE on success, FALSE on error
//
BOOL CapturePaletteAuto (LPCAPSTREAM lpcs, int nCount, int nColors)
{
    HPALETTE    hpal;
    HCURSOR     hOldCursor;
    DWORD       dwError = DV_ERR_OK;
    CAPPAL      cappal;
    LPCAPPAL    lpcp;

    lpcp = &cappal;

    if (!lpcs->sCapDrvCaps.fDriverSuppliesPalettes)
        return FALSE;

    if (nColors <= 0 || nColors > 256)
        return FALSE;

    lpcp->wNumColors = max (nColors, 2);  // at least 2 colors

    if (nCount <= 0)
        return FALSE;

    if (dwError = CapturePaletteInit (lpcs, lpcp))
        goto PalAutoExit;

    hOldCursor = SetCursor(lpcs->hWaitCursor);

    CapturePaletteFrames (lpcs, lpcp, nCount);

    /* we grabbed a frame, time to compute a palette */
    statusUpdateStatus(lpcs, IDS_CAP_STAT_OPTPAL_BUILD);

    // The HPALETTE returned in the following becomes
    // our "global" palette, hence is not deleted here.
    hpal = HistogramPalette(lpcp->lpHistogram, lpcp->lp16to8, lpcp->wNumColors);

    // Send driver both the pal and xlate table
    PalSendPaletteToDriver(lpcs, hpal, (LPBYTE)lpcp->lp16to8 );

    videoFrame( lpcs->hVideoIn, &lpcs->VidHdr );  // Update the display with a new image

    SetCursor(hOldCursor);

    InvalidateRect(lpcs->hwnd, NULL, TRUE);
    UpdateWindow(lpcs->hwnd);
    lpcs->fUsingDefaultPalette = FALSE;

PalAutoExit:
    CapturePaletteFini (lpcs, lpcp);
    statusUpdateStatus(lpcs, 0);

   // If an error happened, display it
   if (dwError)
        errorDriverID (lpcs, dwError);

    return (dwError == DV_ERR_OK);
}

//
//  CapturePaletteManual() capture a palette from the video source
//  with user intervention.
//  fGrab is TRUE on all but the last frame captured
//  Returns TRUE on success, FALSE on error
//
BOOL CapturePaletteManual (LPCAPSTREAM lpcs, BOOL fGrab, int nColors)
{
    HPALETTE    hpal;
    HCURSOR     hOldCursor;
    LPCAPPAL    lpcp;
    DWORD       dwError = DV_ERR_OK;

    if (!lpcs->sCapDrvCaps.fDriverSuppliesPalettes)
        return FALSE;

    hOldCursor = SetCursor(lpcs->hWaitCursor);

    // We're initializing for the first time, so alloc everything
    if (lpcs->lpCapPal == NULL) {

        if (lpcp = (LPCAPPAL) GlobalAllocPtr (GHND, sizeof(CAPPAL))) {
            lpcs->lpCapPal = lpcp;

            if (nColors == 0)
                nColors = 256;
            lpcp->wNumColors = min (nColors, 256);
            dwError = CapturePaletteInit (lpcs, lpcp);
        }
        else
            dwError = IDS_CAP_OUTOFMEM;
    }
    lpcp = lpcs->lpCapPal;

    if (dwError != DV_ERR_OK)
        goto PalManualExit;

    // Add a frame to the histogram
    // Handle the case of telling us to stop before we ever started
    if (fGrab || !fGrab && (lpcp->wNumFrames == 0)) {
        CapturePaletteFrames (lpcs, lpcp, 1);
        lpcs->fUsingDefaultPalette = FALSE;
    }
    // All done, send the new palette to the driver
    if (!fGrab) {
        statusUpdateStatus(lpcs, IDS_CAP_STAT_OPTPAL_BUILD);

        // The HPALETTE returned in the following becomes
        // our "global" palette, hence is not deleted here.
        hpal = HistogramPalette(lpcp->lpHistogram,
                        lpcp->lp16to8, lpcp->wNumColors);

        // Send driver both the pal and xlate table
        PalSendPaletteToDriver(lpcs, hpal, (LPBYTE)lpcp->lp16to8 );
    }

    videoFrame( lpcs->hVideoIn, &lpcs->VidHdr );  // Update the display with a new image
    InvalidateRect(lpcs->hwnd, NULL, TRUE);
    UpdateWindow(lpcs->hwnd);

PalManualExit:
    if (!fGrab || (dwError != DV_ERR_OK)) {
        if (lpcp != NULL) {
            CapturePaletteFini (lpcs, lpcp);
            GlobalFreePtr (lpcp);
            lpcs->lpCapPal = NULL;
        }
    }

    SetCursor(hOldCursor);
    statusUpdateStatus(lpcs, 0);

    // If an error happened, display it
    if (dwError) {
        errorUpdateError (lpcs, (UINT) dwError);
    }

    return (dwError == DV_ERR_OK);
}



/*--------------------------------------------------------------+
| fileSavePalette - save the current palette in a file  	|
|								|
+--------------------------------------------------------------*/
BOOL FAR PASCAL fileSavePalette(LPCAPSTREAM lpcs, LPTSTR lpszFileName)
{
    HPALETTE            hpal;
    HMMIO               hmmio;
    WORD	        w;
    HCURSOR             hOldCursor;
    MMCKINFO            ckRiff;
    MMCKINFO            ck;
    short               nColors;
    FCLOGPALETTE        pal;
    BOOL                fOK = FALSE;

    if ((hpal = lpcs->hPalCurrent) == NULL)
        return FALSE;

    hmmio = mmioOpen(lpszFileName, NULL, MMIO_WRITE);
    if( !hmmio ) {
	/* try and create */
        hmmio = mmioOpen(lpszFileName, NULL, MMIO_CREATE | MMIO_WRITE);
	if( !hmmio ) {
	    /* find out if the file was read only or we are just */
	    /* totally hosed up here.				 */
	    hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
	    if (hmmio){
		/* file was read only, error on it */
                errorUpdateError (lpcs, IDS_CAP_READONLYFILE, (LPTSTR) lpszFileName);
		mmioClose(hmmio, 0);
		return FALSE;
	    } else {
		/* even weirder error has occured here, give CANTOPEN */
                errorUpdateError (lpcs, IDS_CAP_CANTOPEN, (LPTSTR) lpszFileName);
		return FALSE;
	    }
	}
    }

    hOldCursor = SetCursor( lpcs-> hWaitCursor );

    /* Seek to beginning of file, so we can write the header. */
    mmioSeek(hmmio, 0, SEEK_SET);

    /* Create RIFF chunk */
    ckRiff.fccType = mmioFOURCC('P','A','L',' ');
    if(mmioCreateChunk (hmmio,&ckRiff,MMIO_CREATERIFF)) {
         goto FileError;
    }

    /* Create Palette chunk */
    ck.cksize = 0;
    ck.ckid = mmioFOURCC('d','a','t','a');
    if(mmioCreateChunk(hmmio,&ck,0)) {
         goto FileError;
    }

    // Get the palette data here
    GetObject(hpal, sizeof(short), (LPVOID)&nColors);

    pal.palVersion = 0x0300;
    pal.palNumEntries = nColors;

    GetPaletteEntries(hpal, 0, nColors, pal.palPalEntry);

    // Calc the size of the logpalette
    // which is the sizeof palVersion + sizeof palNumEntries + colors
    w = sizeof (WORD) + sizeof (WORD) + nColors * sizeof (PALETTEENTRY);

    // Write out the palette
    if(mmioWrite(hmmio, (LPBYTE)&pal, (DWORD) w) != (LONG) w) {
        goto FileError;
    }

    if(mmioAscend(hmmio, &ck, 0)) {
        goto FileError;
    }

    if(mmioAscend(hmmio, &ckRiff, 0)) {
        goto FileError;
    }

    fOK = TRUE;

FileError:
    mmioClose( hmmio, 0 );

    SetCursor( hOldCursor );

    if (!fOK)
        errorUpdateError (lpcs, IDS_CAP_ERRORPALSAVE, (LPTSTR) lpszFileName);

    return fOK;
}


/*--------------------------------------------------------------+
| fileOpenPalette - use a new palette from the specified file   |
|								|
+--------------------------------------------------------------*/
BOOL FAR PASCAL fileOpenPalette(LPCAPSTREAM lpcs, LPTSTR lpszFileName)
{
    HPALETTE            hpal;
    HMMIO               hmmio;
    WORD	        w;
    HCURSOR             hOldCursor;
    MMCKINFO            ckRiff;
    MMCKINFO            ck;
    FCLOGPALETTE        pal;
    BOOL                fOK = FALSE;

    if ((hpal = lpcs->hPalCurrent) == NULL)
        return FALSE;

    hmmio = mmioOpen(lpszFileName, NULL, MMIO_READ);
    if( !hmmio ) {
        errorUpdateError (lpcs, IDS_CAP_ERRORPALOPEN, (LPTSTR) lpszFileName);
        return FALSE;
    }

    hOldCursor = SetCursor( lpcs-> hWaitCursor );

    /* Seek to beginning of file, so we can read the header. */
    mmioSeek(hmmio, 0, SEEK_SET);

    /* Find the RIFF chunk */
    ckRiff.fccType = mmioFOURCC('P','A','L',' ');
    if(mmioDescend (hmmio, &ckRiff, NULL, MMIO_FINDRIFF)) {
         goto PalOpenError;
    }

    /* Find the data chunk */
    ck.cksize = 0;
    ck.ckid = mmioFOURCC('d','a','t','a');
    if(mmioDescend (hmmio, &ck, &ckRiff, MMIO_FINDCHUNK)) {
         goto PalOpenError;
    }

    // First read just the Version and number of entries
    // which is the sizeof palVersion + sizeof palNumEntries
    w = sizeof (WORD) + sizeof (WORD);
    if(mmioRead(hmmio, (LPBYTE)&pal, (DWORD) w) != (LONG) w) {
        goto PalOpenError;
    }

    // Do a bit of checking
    if ((pal.palVersion != 0x0300) || (pal.palNumEntries > 256))
        goto PalOpenError;

    // Now get the actual palette data
    // which is the sizeof palVersion + sizeof palNumEntries
    w = pal.palNumEntries * sizeof (PALETTEENTRY);
    if(mmioRead(hmmio, (LPBYTE)&pal.palPalEntry, (DWORD) w) != (LONG) w) {
        goto PalOpenError;
    }

    if (hpal = CreatePalette ((LPLOGPALETTE) &pal)) {
        PalSendPaletteToDriver (lpcs, hpal, NULL /*lpXlateTable */);
        fOK = TRUE;
    }

    videoFrame( lpcs->hVideoIn, &lpcs->VidHdr );  // grab a new frame

PalOpenError:
    mmioClose( hmmio, 0 );

    SetCursor( hOldCursor );
    InvalidateRect(lpcs->hwnd, NULL, TRUE);
    UpdateWindow(lpcs->hwnd);		// update the display with new frame

    if (!fOK)
        errorUpdateError (lpcs, IDS_CAP_ERRORPALOPEN, (LPTSTR) lpszFileName);
    else
    lpcs->fUsingDefaultPalette = FALSE;

    return fOK;
}