
/*
 *  UnCompface - 48x48x1 image decompression
 *
 *  Copyright (c) James Ashton - Sydney University - June 1990.
 *
 *  Written 11th November 1989.
 *
 *  Permission to use, copy, modify, and distribute this software and its
 *  documentation for any purpose and without fee is hereby granted, provided
 *  that the above copyright notice appear in all copies and that both that
 *  copyright notice and this permission notice appear in supporting
 *  documentation.  This software is provided "as is" without express or
 *  implied warranty.
 */
#ifdef _WIN32

#include "xface.h"
#include <memory.h>

/* output line length for compressed data */
#define MAXLINELEN 78


//Static members initialization
Prob XFaceOb::levels[4][3] = 
{
    {{1, 255},  {251, 0},   {4, 251}},  /* Top of tree almost always grey */
    {{1, 255},  {200, 0},   {55, 200}},
    {{33, 223}, {159, 0},   {64, 159}},
    {{131, 0},  {0, 0},     {125, 131}} /* Grey disallowed at bottom */
} ;

/* At the bottom of the octree 2x2 elements are considered black if any
 * pixel is black.  The probabilities below give the distribution of the
 * 16 possible 2x2 patterns.  All white is not really a possibility and
 * has a probability range of zero.  Again, experimentally derived data */
Prob XFaceOb::freqs[16] = 
{
    {0, 0},     {38, 0},    {38, 38},   {13, 152},
    {38, 76},   {13, 165},  {13, 178},  {6, 230},
    {38, 114},  {13, 191},  {13, 204},  {6, 236},
    {13, 217},  {6, 242},   {5, 248},   {3, 253}
} ;     
        
Guesses XFaceOb::G =
#include "xface_data.h"
;

//mono conversion mask
static const BYTE byMask[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

/*
int XFaceOb::XFace2BMPFile( LPCSTR szData, CBufFile *pOutFile, BOOL bIsFinal )
{
    static int Xstate = 0;
    BOOL bOK = TRUE;

    if (!szData || !pOutFile)
        return -1;
    
    if (Xstate == 0)
    {
        //1st time thru
        BigClear();

        if (strnicmp(szData, "X-Face: ", 8) == 0)
            szData += 8;
    }

    if (bIsFinal)
        Xstate = 0;
    else
        Xstate = 1;


    try
    {
        BigRead(szData);
    }
    catch(...)
    {
        bOK = FALSE;
        Xstate = 0;
    }

    if (Xstate == 0)
    {

      try
        {

            ZeroMemory( &F, sizeof(F));

            UnCompress(F, 16, 16, 0);
            UnCompress(F + 16, 16, 16, 0);
            UnCompress(F + 32, 16, 16, 0);
            UnCompress(F + WIDTH * 16, 16, 16, 0);
            UnCompress(F + WIDTH * 16 + 16, 16, 16, 0);
            UnCompress(F + WIDTH * 16 + 32, 16, 16, 0);
            UnCompress(F + WIDTH * 32, 16, 16, 0);
            UnCompress(F + WIDTH * 32 + 16, 16, 16, 0);
            UnCompress(F + WIDTH * 32 + 32, 16, 16, 0);

            Gen(F);
        }
        catch ( ... )
        {
            bOK = FALSE;
        
        }


        if (bOK)
        {
            HGLOBAL hDib = NULL;

            if (Face2BMP(&hDib) )
            {

              LPBITMAPINFOHEADER lpbi = ( LPBITMAPINFOHEADER) hDib;

              long size = ::GlobalSize(hDib);

              // write file header
              BITMAPFILEHEADER bmf;
              bmf.bfType = (WORD)('B'  | ('M' << 8));
              bmf.bfSize = sizeof(bmf) + size;
              bmf.bfReserved1 = 0;
              bmf.bfReserved2 = 0;
              bmf.bfOffBits = sizeof(bmf) + lpbi->biSize + (lpbi->biBitCount < 9 ? (1 << lpbi->biBitCount) * sizeof(RGBQUAD) : 0);

              
              pOutFile->Write( (LPBYTE) &bmf, sizeof(bmf));
              pOutFile->Write( (LPBYTE)lpbi, size);

            }

            GlobalFree(hDib);

            return 1;


        }

    

    }

    return (Xstate == 0 ? -1 : 1);

}

*/

int XFaceOb::Xface2BMP( LPCSTR szData, HGLOBAL *phBMP)
{


    if (szData && *szData) 
    {
        
        if (uncompface(szData) < 0) 
            return -1;
    }
    else
    {
        return -1;
    }

    return Face2BMP( phBMP);


}

int XFaceOb::Face2BMP( HGLOBAL *phBMP)
{

    BITMAPINFOHEADER far *pbiHdr = NULL;
    RGBQUAD * pPalette = NULL;
    LPBYTE pBits = NULL;
    *phBMP = NULL;




    long lBytesPerRow= ((((WIDTH * BITS) + 31) & ~31) >> 3);

    DWORD dwSize =  lBytesPerRow * HEIGHT;
    dwSize += sizeof(BITMAPINFOHEADER);
    dwSize += (1 << BITS) * sizeof(RGBQUAD);

    pbiHdr = (BITMAPINFOHEADER far *) GlobalAlloc(GPTR, dwSize);
    memset(pbiHdr, 0, dwSize);

    
    //create a new DIB 
    pbiHdr->biSize = sizeof (BITMAPINFOHEADER);         
    pbiHdr->biWidth = WIDTH;        
    pbiHdr->biHeight = HEIGHT;       
    pbiHdr->biPlanes = 1;       
    pbiHdr->biBitCount = BITS;     
    pbiHdr->biCompression = BI_RGB;  
    pbiHdr->biSizeImage = 0;    
    pbiHdr->biXPelsPerMeter = 0;
    pbiHdr->biYPelsPerMeter = 0;
    pbiHdr->biClrUsed = (1 << pbiHdr->biBitCount);      
    pbiHdr->biClrImportant = 0;

    pPalette = (RGBQUAD*) ((LPBYTE)(pbiHdr) + pbiHdr->biSize); 
    pPalette[1].rgbRed =  255;
    pPalette[1].rgbGreen =  255;
    pPalette[1].rgbBlue =  255;

    pPalette[0].rgbRed =  0;
    pPalette[0].rgbGreen =  0;
    pPalette[0].rgbBlue =  0;


    pBits = ((LPBYTE) (pbiHdr)) + pbiHdr->biSize +  (1 << BITS) * sizeof(RGBQUAD);

    //copy each pixel row by row
    LPBYTE p = (LPBYTE) F;
    for (int nRow = 0; nRow < pbiHdr->biHeight; nRow++)
    {
        
        int nb = 0;
        LPBYTE pByte = pBits + ((HEIGHT-nRow-1) * lBytesPerRow);
        
        for (int ni = 0; ni <  pbiHdr->biWidth; ni++)
        {
            
        
            if ( *(p+ni))
            {
                *(pByte) &= ~byMask[nb];
            }
            else
            {
                *(pByte) |= byMask[nb];
            }


            if (++nb > 7)
            {
                nb = 0;
                pByte++;
            }

            
        } // width

        p = p + WIDTH;

    } //height

    *phBMP = (HGLOBAL) pbiHdr;

    
    return 1;

}

int XFaceOb::uncompface(const char *fbuf)
{
    int status = 1;
    
    //if (!(status = setjmp(comp_env))) 
    __try  //try
    {
        UnCompAll(fbuf);
        Gen(F);
    }
    __finally //catch ( ... )
    {
        status = 0;
    }
    return(status);
}

void XFaceOb::BigClear(void)
{
    B.b_words = 0;
}





void XFaceOb::PopGreys(unsigned char *f, int wid, int hei)
{
    if (wid > 3) 
    {
        wid /= 2;
        hei /= 2;
        PopGreys(f, wid, hei);
        PopGreys(f + wid, wid, hei);
        PopGreys(f + WIDTH * hei, wid, hei);
        PopGreys(f + WIDTH * hei + wid, wid, hei);
    }
    else 
    {
        wid = BigPop(freqs);
        if (wid & 1)
            *f = 1;
        if (wid & 2)
            *(f + 1) = 1;
        if (wid & 4)
            *(f + WIDTH) = 1;
        if (wid & 8)
            *(f + WIDTH + 1) = 1;
    }
}




void XFaceOb::UnCompress(unsigned char *f, int wid, int hei, int lev)
{
    switch (BigPop(&levels[lev][0])) 
    {
        case WHITE :
            return;
        case BLACK :
            PopGreys(f, wid, hei);
            return;
        default :
            wid /= 2;
            hei /= 2;
            lev++;
            UnCompress(f, wid, hei, lev);
            UnCompress(f + wid, wid, hei, lev);
            UnCompress(f + hei * WIDTH, wid, hei, lev);
            UnCompress(f + wid + hei * WIDTH, wid, hei, lev);
            return;
    }
}


void XFaceOb::UnCompAll(const char *fbuf)
{
    register unsigned char *p;

    BigClear();
    BigRead(fbuf);
    p = F;
    while (p < F + PIXELS)
        *(p++) = 0;
    UnCompress(F, 16, 16, 0);
    UnCompress(F + 16, 16, 16, 0);
    UnCompress(F + 32, 16, 16, 0);
    UnCompress(F + WIDTH * 16, 16, 16, 0);
    UnCompress(F + WIDTH * 16 + 16, 16, 16, 0);
    UnCompress(F + WIDTH * 16 + 32, 16, 16, 0);
    UnCompress(F + WIDTH * 32, 16, 16, 0);
    UnCompress(F + WIDTH * 32 + 16, 16, 16, 0);
    UnCompress(F + WIDTH * 32 + 32, 16, 16, 0);
}




void XFaceOb::Gen(unsigned char *f)
{
    register int m, l, k, j, i, h;

    for (j = 0; j < HEIGHT;  j++) 
    {
        for (i = 0; i < WIDTH;  i++) 
        {
            h = i + j * WIDTH;
            k = 0;
            for (l = i - 2; l <= i + 2; l++)
                for (m = j - 2; m <= j; m++) 
                {
                    if ((l >= i) && (m == j))
                        continue;
                    if ((l > 0) && (l <= WIDTH) && (m > 0))
                        k = *(f+l+m*WIDTH) ? k*2+1 : k*2;
                }
            switch (i) 
            {
                case 1 :
                    switch (j) 
                    {
                        case 1 : GEN(g_22);
                        case 2 : GEN(g_21);
                        default : GEN(g_20);
                    }
                    break;
                case 2 :
                    switch (j) 
                    {
                        case 1 : GEN(g_12);
                        case 2 : GEN(g_11);
                        default : GEN(g_10);
                    }
                    break;
                case WIDTH - 1 :
                    switch (j) 
                    {
                        case 1 : GEN(g_42);
                        case 2 : GEN(g_41);
                        default : GEN(g_40);
                    }
                    break;
                case WIDTH :
                    switch (j) 
                    {
                        case 1 : GEN(g_32);
                        case 2 : GEN(g_31);
                        default : GEN(g_30);
                    }
                    break;
                default :
                    switch (j) 
                    {
                        case 1 : GEN(g_02);
                        case 2 : GEN(g_01);
                        default : GEN(g_00);
                    }
                    break;
            }
        }
    }
}


void XFaceOb::BigRead(const char *fbuf)
{
    register int c;

    while (*fbuf != '\0') 
    {
        c = *(fbuf++);
        if ((c < FIRSTPRINT) || (c > LASTPRINT))
            continue;
        BigMul(NUMPRINTS);
        BigAdd((unsigned char)(c - FIRSTPRINT));
    }
}


int XFaceOb::BigPop(Prob *p)
{
    static unsigned char tmp;
    register int i;

    BigDiv(0, &tmp);
    i = 0;
    while ((tmp < p->p_offset) || (tmp >= p->p_range + p->p_offset)) 
    {
        p++;
        i++;
    }
    BigMul((BYTE)p->p_range);
    BigAdd((BYTE)(tmp - p->p_offset));
    return i;
}


/*
 * Divide B by a storing the result in B and the remainder in the word
 * pointer to by r
 */

void XFaceOb::BigDiv(unsigned char a, unsigned char *r)
{
    register int i;
    register unsigned char *w;
    register COMP c, d;

    a &= WORDMASK;
    if ((a == 1) || (B.b_words == 0)) 
    {
        *r = 0;
        return;
    }
    if (a == 0) 
    {       /* treat this as a == WORDCARRY */
                /* and just shift everything right a WORD */
        i = --B.b_words;
        w = B.b_word;
        *r = *w;
        while (i--) 
        {
            *w = *(w + 1);
            w++;
        }
        *w = 0;
        return;
    }
    w = B.b_word + (i = B.b_words);
    c = 0;
    while (i--) 
    {
        c <<= BITSPERWORD;
        c += (COMP)*--w;
        d = c / (COMP)a;
        c = c % (COMP)a;
        *w = (unsigned char)(d & WORDMASK);
    }
    *r = (unsigned char)c;
    if (B.b_word[B.b_words - 1] == 0)
        B.b_words--;
}


/*
 * Multiply a by B storing the result in B
 */

void XFaceOb::BigMul(unsigned char a)
{
    register int i;
    register unsigned char *w;
    register COMP c;

    a &= WORDMASK;
    if ((a == 1) || (B.b_words == 0))
        return;
    if (a == 0) 
    {       /* treat this as a == WORDCARRY */
                /* and just shift everything left a WORD */
        if ((i = B.b_words++) >= MAXWORDS - 1)
            throw ERR_INTERNAL;
            //longjmp(comp_env, ERR_INTERNAL);
        w = B.b_word + i;
        while (i--) 
        {
            *w = *(w - 1);
            w--;
        }
        *w = 0;
        return;
    }
    i = B.b_words;
    w = B.b_word;
    c = 0;
    while (i--) 
    {
        c += (COMP)*w * (COMP)a;
        *(w++) = (unsigned char)(c & WORDMASK);
        c >>= BITSPERWORD;
    }
    if (c) 
    {
        if (B.b_words++ >= MAXWORDS)
            throw ERR_INTERNAL;
            //longjmp(comp_env, ERR_INTERNAL);
        *w = (unsigned char) (COMP)(c & WORDMASK);
    }
}


/*
 * Add to a to B storing the result in B
 */

void XFaceOb::BigAdd(unsigned char a)
{
    register int i;
    register unsigned char *w;
    register COMP c;

    a &= WORDMASK;
    if (a == 0)
        return;
    i = 0;
    w = B.b_word;
    c = a;
    while ((i < B.b_words) && c) 
    {
        c += (COMP)*w;
        *w++ = (unsigned char)(c & WORDMASK);
        c >>= BITSPERWORD;
        i++;
    }
    if ((i == B.b_words) && c) 
    {
        if (B.b_words++ >= MAXWORDS)
            throw ERR_INTERNAL;
            //longjmp(comp_env, ERR_INTERNAL);
        *w = (unsigned char) (COMP)(c & WORDMASK);
    }
}

#ifdef DO_COMPRESS

int XFaceOb::BMP2Xface( LPSTR szData, int nLen,  HGLOBAL hBmp)
{
    
    if (!szData || nLen < 50 || !hBmp)
        return -1;  //invlaid args
    
    
    LPBITMAPINFOHEADER lpbmi = (LPBITMAPINFOHEADER) hBmp;
    
    if (lpbmi->biSize != sizeof(BITMAPINFOHEADER) ||
        lpbmi->biBitCount != 1 ||
        lpbmi->biCompression != BI_RGB ||
        lpbmi->biWidth < WIDTH ||
        abs(lpbmi->biHeight) < HEIGHT )
        return -2; //invalid format

    long lBytesPerRow= ((((lpbmi->biWidth * lpbmi->biBitCount) + 31) & ~31) >> 3);

    DWORD dwSize =  lBytesPerRow * lpbmi->biHeight;
    dwSize += sizeof(BITMAPINFOHEADER);
    dwSize += (1 << lpbmi->biBitCount) * sizeof(RGBQUAD);

    LPBYTE pBits = (LPBYTE) lpbmi;
    pBits += sizeof(BITMAPINFOHEADER) + (1 << lpbmi->biBitCount) * sizeof(RGBQUAD);

    ZeroMemory(&F, sizeof(F));

    LPBYTE pChar;

    for (int row = 0; row < HEIGHT ; row++)
    {
        
        int nb = 0;
        LPBYTE pByte = pBits;
        pChar = F + row * WIDTH;


        if ( (short)lpbmi->biHeight < 0)
            pByte += row * lBytesPerRow;
        else
            pByte += (HEIGHT-row-1) * lBytesPerRow;
        
    
        for (int col = 0; col < WIDTH; col++)
        {

            if ( (*pByte & byMask[nb]) )
            {
                *pChar++ = 1;
            }
            else
            {
                *pChar++ = 0;
            }

            if (++nb > 7)
            {
                nb = 0;
                pByte++;
            }


        }

        

    }

    memcpy(F2, F, sizeof(F));
    Gen(F2);

    CompAll( szData );


    return 1;



}


//this code is only needed to compress an X-Face
void XFaceOb::BigPush(Prob *p)
{
    static unsigned char tmp;

    BigDiv(p->p_range, &tmp);
    BigMul(0);
    BigAdd((BYTE)(tmp + p->p_offset));
}


void XFaceOb::RevPush(Prob *p)
{
    if (NumProbs >= PIXELS * 2 - 1)
        throw ERR_INTERNAL;
        //longjmp(comp_env, ERR_INTERNAL);
    ProbBuf[NumProbs++] = p;
}


void XFaceOb::PushGreys(unsigned char *f,int wid, int hei)
{
    if (wid > 3)
    {
        wid /= 2;
        hei /= 2;
        PushGreys(f, wid, hei);
        PushGreys(f + wid, wid, hei);
        PushGreys(f + WIDTH * hei, wid, hei);
        PushGreys(f + WIDTH * hei + wid, wid, hei);
    }
    else
        RevPush(freqs + *f + 2 * *(f + 1) + 4 * *(f + WIDTH) +
          8 * *(f + WIDTH + 1));
}

int XFaceOb::Same(register unsigned char *f,register int wid, register int hei)
{
    register unsigned char val, *row;
    register int x;

    val = *f;
    while (hei--)
    {
        row = f;
        x = wid;
        while (x--)
            if (*(row++) != val)
                return(0);
        f += WIDTH;
    }
    return 1;
}

int XFaceOb::AllBlack(unsigned char *f, int wid, int hei)
{
    if (wid > 3)
    {
        wid /= 2;
        hei /= 2;
        return (AllBlack(f, wid, hei) && AllBlack(f + wid, wid, hei) &&
          AllBlack(f + WIDTH * hei, wid, hei) &&
          AllBlack(f + WIDTH * hei + wid, wid, hei));
    }
    else
        return (*f || *(f + 1) || *(f + WIDTH) || *(f + WIDTH + 1));
}

int XFaceOb::AllWhite(unsigned char *f,int wid, int hei)
{
    return ((*f == 0) && Same(f, wid, hei));
}

void XFaceOb::BigWrite(register  char *fbuf)
{
    static unsigned char tmp;
    static unsigned char buf[DIGITS];
    register unsigned char *s;
    register int i;

    s = buf;
    while (B.b_words > 0)
    {
        BigDiv(NUMPRINTS, &tmp);
        *(s++) = (BYTE)( tmp + FIRSTPRINT);
    }
    i = 7;  /* leave room for the field name on the first line */
    *(fbuf++) = ' ';
    while (s-- > buf)
    {
        if (i == 0)
            *(fbuf++) = ' ';
        *(fbuf++) = *s;
        if (++i >= MAXLINELEN)
        {
            *(fbuf++) = '\n';
            i = 0;
        }
    }
    if (i > 0)
        *(fbuf++) = '\n';
    *(fbuf++) = '\0';
}


void XFaceOb::CompAll( char *fbuf)
{
    Compress(F, 16, 16, 0);
    Compress(F + 16, 16, 16, 0);
    Compress(F + 32, 16, 16, 0);
    Compress(F + WIDTH * 16, 16, 16, 0);
    Compress(F + WIDTH * 16 + 16, 16, 16, 0);
    Compress(F + WIDTH * 16 + 32, 16, 16, 0);
    Compress(F + WIDTH * 32, 16, 16, 0);
    Compress(F + WIDTH * 32 + 16, 16, 16, 0);
    Compress(F + WIDTH * 32 + 32, 16, 16, 0);
    BigClear();
    while (NumProbs > 0)
        BigPush(ProbBuf[--NumProbs]);
    BigWrite(fbuf);
}

void XFaceOb::Compress(register unsigned char *f,register int wid, register int hei, register int lev)
{
    if (AllWhite(f, wid, hei))
    {
        RevPush(&levels[lev][WHITE]);
        return;
    }
    if (AllBlack(f, wid, hei))
    {
        RevPush(&levels[lev][BLACK]);
        PushGreys(f, wid, hei);
        return;
    }
    RevPush(&levels[lev][GREY]);
    wid /= 2;
    hei /= 2;
    lev++;
    Compress(f, wid, hei, lev);
    Compress(f + wid, wid, hei, lev);
    Compress(f + hei * WIDTH, wid, hei, lev);
    Compress(f + wid + hei * WIDTH, wid, hei, lev);
}

void XFaceOb::WriteFace( char *fbuf)
{
    register unsigned char *s, *t;
    register int i, bits;

    s = F;
    t = (unsigned char*) fbuf;
    bits = i = 0;
    while (s < F + PIXELS) 
    {
        if (*(s++))
            i = i * 2 + 1;
        else
            i *= 2;
        if (++bits == BITSPERBYTE) 
        {
            *(t++) = (BYTE) i;
            bits = i = 0;
        }
    }

}

#endif  //DO_COMPRESS


#endif //_WIN32