/*
 *
 * iaverage.c   Image averaging
 *
 * (C) Copyright Microsoft Corporation 1993. All rights reserved.
 *
 */

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <memory.h>
#include <stdlib.h>
#include <ctype.h>
#include <memory.h>
#include <string.h>
#include <msvideo.h>
#include "iaverage.h"

#define WIDTHBYTES(i)     ((unsigned)((i+31)&(~31))/8)  /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (int)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#define RGB16(r,g,b)      ((((WORD)(r) >> 3) << 10) | \
                          (((WORD)(g) >> 3) << 5)  | \
                          (((WORD)(b) >> 3) ) )

typedef BYTE _huge  *   HPBYTE;
typedef WORD _huge  *   HPWORD;
typedef DWORD _huge *   HPDWORD;

/* Description:
        A sequence of images are averaged together using 16 bit
        accumulators for each of the Red, Green, and Blue components.
        The final processing step is to divide the accumulated values
        by the number of frames averaged, and transfer the results back
        into the destination DIB.

        Certain death will result if the image format is changed between
        iaverageInit and iaverageFini calls.
*/

//
// table to map a 5bit index (0-31) to a 8 bit value (0-255)
//
static BYTE aw5to8[32] = {(BYTE)-1};



/*
 *  iaverageInit
 *      Allocate memory for subsequent image averaging
 *      Return FALSE on error
 *
 */
BOOL iaverageInit (LPIAVERAGE FAR * lppia, LPBITMAPINFO lpbi, HPALETTE hPal)
{
    DWORD       dwSizeImage;
    LPIAVERAGE  lpia;
    int         i;

    *lppia = NULL;

    // Check for legal DIB formats
    if (lpbi->bmiHeader.biCompression != BI_RGB)
        return FALSE;

    if (lpbi->bmiHeader.biBitCount != 8 &&
        lpbi->bmiHeader.biBitCount != 16 &&
        lpbi->bmiHeader.biBitCount != 24 &&
        lpbi->bmiHeader.biBitCount != 32)
        return FALSE;

    //
    // init the 5bit to 8bit conversion table.
    //
    if (aw5to8[0] != 0)
        for (i=0; i<32; i++)
            aw5to8[i] = (BYTE)(i * 255 / 31);

    // Alloc memory for the image average structure
    lpia = (LPIAVERAGE) GlobalAllocPtr (GHND, sizeof (IAVERAGE));

    if (!lpia)
        return FALSE;

    // Save a copy of the header
    lpia->bi.bmiHeader = lpbi->bmiHeader;

    // and a copy of the color table and an inverse mapping table
    // if the image is 8 bit
    if (lpbi->bmiHeader.biBitCount == 8) {
        WORD r, g, b;
        LPBYTE lpB;

        hmemcpy (lpia->bi.bmiColors,
                        lpbi->bmiColors,
                        lpbi->bmiHeader.biClrUsed * sizeof (RGBQUAD));

        // Allocate and init the inverse LUT
        lpia->lpInverseMap= (LPBYTE) GlobalAllocPtr (GHND, 1024L * 32);
        lpB = lpia-> lpInverseMap;
        for (r = 0; r < 256; r += 8)
            for (g = 0; g < 256; g += 8)
                for (b = 0; b < 256; b += 8)
                    *lpB++ = (BYTE) GetNearestPaletteIndex (hPal, RGB(r,g,b));

    }

    dwSizeImage = lpbi->bmiHeader.biSizeImage;

    lpia->lpRGB = (LPINT) GlobalAllocPtr (GHND,
                        dwSizeImage * sizeof (WORD) * 3);

    if (lpia->lpRGB == NULL) {
        // Allocation failed, clean up
        iaverageFini (lpia);
        return FALSE;
    }

    *lppia = lpia;

    return TRUE;
}


/*
 *  iaverageFini
 *      Free memory used for image averaging
 *      and the iaverage structure itself
 *
 */
BOOL iaverageFini (LPIAVERAGE lpia)
{
    if (lpia == NULL)
        return FALSE;

    if (lpia->lpInverseMap)
        GlobalFreePtr (lpia->lpInverseMap);
    if (lpia->lpRGB)
        GlobalFreePtr (lpia->lpRGB);

    GlobalFreePtr (lpia);

    return TRUE;
}


/*
 *  iaverageZero
 *      Zeros the accumulator
 *
 */
BOOL iaverageZero (LPIAVERAGE lpia)
{
    DWORD   dwC;
    HPWORD  hpW;

    if (lpia == NULL)
        return FALSE;

    hpW = (HPWORD) lpia->lpRGB;
    dwC = lpia->bi.bmiHeader.biSizeImage * 3;
    while (--dwC)
        *hpW++ = 0;

    lpia-> iCount = 0;

    return TRUE;
}

/*
 *  iaverageSum
 *      Add the current image into the accumulator
 *      Image format must be 16 or 24 bit RGB
 */
BOOL iaverageSum (LPIAVERAGE lpia, LPVOID lpBits)
{
    HPWORD      hpRGB;
    DWORD       dwC;
    WORD        wRGB16;
    HPWORD      hpW;
    HPBYTE      hpB;
    WORD        w;

    if (lpia == NULL)
        return FALSE;

    hpRGB   = (HPWORD) lpia->lpRGB;

    if (lpia->bi.bmiHeader.biBitCount == 8) {
        hpB = (HPBYTE) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage; --dwC; ) {
            w = (WORD) *hpB++;
            *hpRGB++   += lpia->bi.bmiColors[w].rgbBlue;
            *hpRGB++   += lpia->bi.bmiColors[w].rgbGreen;
            *hpRGB++   += lpia->bi.bmiColors[w].rgbRed;
        }
    }

    else if (lpia->bi.bmiHeader.biBitCount == 16) {
        hpW = (HPWORD) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage / 2; --dwC; ) {
            wRGB16 = *hpW++;

            *hpRGB++  += aw5to8 [wRGB16         & 0x1f]; // b
            *hpRGB++  += aw5to8 [(wRGB16 >> 5)  & 0x1f]; // g
            *hpRGB++  += aw5to8 [(wRGB16 >> 10) & 0x1f]; // r

        }
    }

    else if (lpia->bi.bmiHeader.biBitCount == 24) {
        hpB = (HPBYTE) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage; --dwC; ) {
            *hpRGB++  += *hpB++;
        }
    }

    else if (lpia->bi.bmiHeader.biBitCount == 32) {
        hpB = (HPBYTE) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage / 4; --dwC; ) {
            *hpRGB++  += *hpB++; // b
            *hpRGB++  += *hpB++; // g
            *hpRGB++  += *hpB++; // r
            hpB++;
        }
    }

    lpia-> iCount++;            // Image counter

    return TRUE;
}

/*
 *  iaverageDivide
 *      Divide by the number of images captured, and xfer back into the
 *      destination DIB.
 *
 */
BOOL iaverageDivide (LPIAVERAGE lpia, LPVOID lpBits)
{
    HPWORD      hpRGB;
    WORD        r, g, b, w;
    DWORD       dwC;
    HPWORD      hpW;
    HPBYTE      hpB;

    if (lpia == NULL || lpia-> iCount == 0)
        return FALSE;

    hpRGB   = (HPWORD) lpia->lpRGB;

    if (lpia->bi.bmiHeader.biBitCount == 8) {
        hpB = (HPBYTE) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage; --dwC; ) {
            b = *hpRGB++ / lpia-> iCount;
            g = *hpRGB++ / lpia-> iCount;
            r = *hpRGB++ / lpia-> iCount;

            w = RGB16(r,g,b) & 0x7FFF;
            *hpB++ = * (lpia->lpInverseMap + w);

        }
    }

    else if (lpia->bi.bmiHeader.biBitCount == 16) {
        hpW = (HPWORD) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage / 2; --dwC; ) {
            b = *hpRGB++ / lpia-> iCount;
            g = *hpRGB++ / lpia-> iCount;
            r = *hpRGB++ / lpia-> iCount;

            *hpW++ = RGB16 (r, g, b);
        }
    }

    else if (lpia->bi.bmiHeader.biBitCount == 24) {
        hpB = (HPBYTE) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage; --dwC; ) {
            *hpB++ = (BYTE) (*hpRGB++  / lpia-> iCount);
        }
    }

    else if (lpia->bi.bmiHeader.biBitCount == 32) {
        hpB = (HPBYTE) lpBits;
        for (dwC = lpia->bi.bmiHeader.biSizeImage / 4; --dwC; ) {
            *hpB++ = (BYTE) (*hpRGB++ / lpia-> iCount); // b
            *hpB++ = (BYTE) (*hpRGB++ / lpia-> iCount); // g
            *hpB++ = (BYTE) (*hpRGB++ / lpia-> iCount); // r
            hpB++;
        }
    }

    return TRUE;
}

// The following appropriated from Toddla's CDIB

/*****************************************************************************
 *
 *  SumRGB
 *
 *****************************************************************************/

#define SumRGB16(b0,b1,b2,b3) (\
             ((((WORD)pal.palPalEntry[b0].peRed +         \
                (WORD)pal.palPalEntry[b1].peRed +         \
                (WORD)pal.palPalEntry[b2].peRed +         \
                (WORD)pal.palPalEntry[b3].peRed)          \
                & 0x03E) << 5) |                          \
                                                          \
             ((((WORD)pal.palPalEntry[b0].peGreen +       \
                (WORD)pal.palPalEntry[b1].peGreen +       \
                (WORD)pal.palPalEntry[b2].peGreen +       \
                (WORD)pal.palPalEntry[b3].peGreen)        \
                & 0x003E)) |                              \
                                                          \
             ((((WORD)pal.palPalEntry[b0].peBlue +        \
                (WORD)pal.palPalEntry[b1].peBlue +        \
                (WORD)pal.palPalEntry[b2].peBlue +        \
                (WORD)pal.palPalEntry[b3].peBlue)         \
                & 0x003E) >> 5) )

/*****************************************************************************
 *
 *  RGB16
 *
 *****************************************************************************/

typedef struct { BYTE b,g,r; } RGB24;

#define rgb16(r,g,b) (\
            ((UINT)(r) << 10) |  \
            ((UINT)(g) << 5)  |  \
            ((UINT)(b) << 0)  )

#define RGB16R(rgb)     aw5to8[((UINT)(rgb) >> 10) & 0x1F]
#define RGB16G(rgb)     aw5to8[((UINT)(rgb) >> 5)  & 0x1F]
#define RGB16B(rgb)     aw5to8[((UINT)(rgb) >> 0)  & 0x1F]
#define RGB16r(rgb)     ((BYTE)((UINT)(rgb) >> 10) & 0x1F)
#define RGB16g(rgb)     ((BYTE)((UINT)(rgb) >> 5)  & 0x1F)
#define RGB16b(rgb)     ((BYTE)((UINT)(rgb) >> 0)  & 0x1F)

/*****************************************************************************
 *
 *  Pel() used for 24bit Crunch
 *
 *****************************************************************************/

#define Pel(p,x) (BYTE)(BitCount == 1 ? Pel1(p,x) : \
                        BitCount == 4 ? Pel4(p,x) : Pel8(p,x))

#define Pel1(p,x)   (BYTE)bit(((HPBYTE)(p))[(x)/8],7-((x)%8))
#define Pel4(p,x)   (BYTE)((x & 1) ? (((HPBYTE)(p))[(x)/2] & 15) : (((HPBYTE)(p))[(x)/2] >> 4))
#define Pel8(p,x)   (BYTE)(((HPBYTE)(p))[(x)])
#define Pel16(p,x)  (((HPWORD)(p))[(x)])
#define Pel24(p,x)  (((RGB24 _huge *)(p))[(x)])

/*****************************************************************************
 *
 *  CrunchDIB  - shrink a DIB down by 2 with color averaging
 *
 *     this routine works on 8, 16, 24, and 32 bpp DIBs
 *
 *     this routine can't be used "in place"
 *
 *****************************************************************************/

BOOL CrunchDIB(
    LPIAVERAGE lpia,                // image averaging structure
    LPBITMAPINFOHEADER  lpbiSrc,    // BITMAPINFO of source
    LPVOID              lpSrc,      // input bits to crunch
    LPBITMAPINFOHEADER  lpbiDst,    // BITMAPINFO of dest
    LPVOID              lpDst)      // output bits to crunch
{
    HPBYTE      pbSrc;
    HPBYTE      pbDst;
    HPBYTE      pb;
    HPWORD      pw;
    BYTE        r,g,b,b0,b1,b2,b3;
    WORD        w0,w1,w2,w3;
    RGB24       rgb0,rgb1,rgb2,rgb3;
    int         WidthBytesSrc;
    int         WidthBytesDst;
    UINT        x;
    UINT        y;
    UINT        dx;
    UINT        dy;
    int         i;
    COLORREF    rgb;
    int         BitCount;
    UINT        aw5to8[32];

     struct {
        WORD         palVersion;
	WORD         palNumEntries;
	PALETTEENTRY palPalEntry[256];
    }   pal;

   if (lpbiSrc->biCompression != BI_RGB)
        return FALSE;

    BitCount = (int)lpbiSrc->biBitCount;

    if (BitCount == 16)
        for (i=0; i<32; i++)
            aw5to8[i] = (UINT)i * 255u / 31u;

    dx = (int)lpbiDst->biWidth;
    WidthBytesDst = (((UINT)lpbiDst->biBitCount * dx + 31)&~31) / 8;

    dy = (int)lpbiSrc->biHeight;
    dx = (int)lpbiSrc->biWidth;
    WidthBytesSrc = (((UINT)lpbiSrc->biBitCount * dx + 31)&~31) / 8;

    dx &= ~1;
    dy &= ~1;

    pbSrc = lpSrc;
    pbDst = lpDst;

    if (lpbiSrc->biClrUsed == 0 && lpbiSrc->biBitCount <= 8)
        lpbiSrc->biClrUsed = (1 << (int)lpbiSrc->biBitCount);

    pal.palVersion = 0x300;
    pal.palNumEntries = (int)lpbiSrc->biClrUsed;

    for (i=0; i<(int)pal.palNumEntries; i++)
    {
        pal.palPalEntry[i].peRed   = ((LPRGBQUAD)(lpbiSrc+1))[i].rgbRed;
        pal.palPalEntry[i].peGreen = ((LPRGBQUAD)(lpbiSrc+1))[i].rgbGreen;
        pal.palPalEntry[i].peBlue  = ((LPRGBQUAD)(lpbiSrc+1))[i].rgbBlue;
        pal.palPalEntry[i].peFlags = 0;
    }

    if (lpbiDst->biBitCount == 8)
        _fmemcpy(lpbiDst+1,lpbiSrc+1,(int)lpbiSrc->biClrUsed*sizeof(RGBQUAD));

    if ((int)lpbiDst->biBitCount == (int)lpbiSrc->biBitCount)
    {
        switch((int)lpbiSrc->biBitCount)
        {
        case 8:
            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

                for (x=0; x<dx; x+=2)
                {
                    b0 = Pel8(pbSrc,x);
                    b1 = Pel8(pbSrc+WidthBytesSrc, x);
                    b2 = Pel8(pbSrc,x+1);
                    b3 = Pel8(pbSrc+WidthBytesSrc,x+1);

                    r = (BYTE) ((
                        (WORD)pal.palPalEntry[b0].peRed +
                        (WORD)pal.palPalEntry[b1].peRed +
                        (WORD)pal.palPalEntry[b2].peRed +
                        (WORD)pal.palPalEntry[b3].peRed) >> 2);

                    g = (BYTE) ((
                        (WORD)pal.palPalEntry[b0].peGreen +
                        (WORD)pal.palPalEntry[b1].peGreen +
                        (WORD)pal.palPalEntry[b2].peGreen +
                        (WORD)pal.palPalEntry[b3].peGreen) >> 2);

                    b = (BYTE) ((
                        (WORD)pal.palPalEntry[b0].peBlue +
                        (WORD)pal.palPalEntry[b1].peBlue +
                        (WORD)pal.palPalEntry[b2].peBlue +
                        (WORD)pal.palPalEntry[b3].peBlue) >> 2);

                    *pb++ = (BYTE)(*(lpia->lpInverseMap +
                                RGB16 (r, g, b)));
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        case 16:
            for (y=0; y<dy; y+=2)
            {
                pw = (HPWORD)pbDst;

		for (x=0; x<dx; x += 2)
                {
                    w0 = Pel16(pbSrc,x);
                    w1 = Pel16(pbSrc,x+1);
                    w2 = Pel16(pbSrc+WidthBytesSrc,x);
                    w3 = Pel16(pbSrc+WidthBytesSrc,x+1);

                    r = ((BYTE)RGB16r(w0) + RGB16r(w1) + RGB16r(w2) + RGB16r(w3)) >> 2;
                    g = ((BYTE)RGB16g(w0) + RGB16g(w1) + RGB16g(w2) + RGB16g(w3)) >> 2;
                    b = ((BYTE)RGB16b(w0) + RGB16b(w1) + RGB16b(w2) + RGB16b(w3)) >> 2;

                    *pw++ = rgb16(r,g,b);
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        case 24:
            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

		for (x=0; x<dx; x += 2)
                {
                    rgb0 = Pel24(pbSrc,x);
                    rgb1 = Pel24(pbSrc,x+1);
                    rgb2 = Pel24(pbSrc+WidthBytesSrc,x);
                    rgb3 = Pel24(pbSrc+WidthBytesSrc,x+1);

                    rgb = RGB(
                        ((UINT)rgb0.r + rgb1.r + rgb2.r + rgb3.r)/4,
                        ((UINT)rgb0.g + rgb1.g + rgb2.g + rgb3.g)/4,
                        ((UINT)rgb0.b + rgb1.b + rgb2.b + rgb3.b)/4);

                    *pb++ = GetBValue(rgb);
                    *pb++ = GetGValue(rgb);
                    *pb++ = GetRValue(rgb);
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        case 32:
            for (y=0; y<dy; y+=2)
            {
                pb = pbDst;

		for (x=0; x<dx; x += 2)
                {
                    rgb0 = Pel24(pbSrc,x);
                    rgb1 = Pel24(pbSrc,x+1);
                    rgb2 = Pel24(pbSrc+WidthBytesSrc,x);
                    rgb3 = Pel24(pbSrc+WidthBytesSrc,x+1);

                    rgb = RGB(
                        ((UINT)rgb0.r + rgb1.r + rgb2.r + rgb3.r)/4,
                        ((UINT)rgb0.g + rgb1.g + rgb2.g + rgb3.g)/4,
                        ((UINT)rgb0.b + rgb1.b + rgb2.b + rgb3.b)/4);

                    *pb++ = GetBValue(rgb);
                    *pb++ = GetGValue(rgb);
                    *pb++ = GetRValue(rgb);
                    pb++;
                }

                pbSrc += WidthBytesSrc*2;
                pbDst += WidthBytesDst;
            }
            break;

        default:
            return FALSE;
        }
    }
    else
    {
        return FALSE;
    }

    return TRUE;
}