/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/* $XConsortium: ilXycbcr.c /main/3 1995/10/23 15:41:23 rswiston $ */
/**---------------------------------------------------------------------
***	
***    (c)Copyright 1991 Hewlett-Packard Co.
***    
***                             RESTRICTED RIGHTS LEGEND
***    Use, duplication, or disclosure by the U.S. Government is subject to
***    restrictions as set forth in sub-paragraph (c)(1)(ii) of the Rights in
***    Technical Data and Computer Software clause in DFARS 252.227-7013.
***                             Hewlett-Packard Company
***                             3000 Hanover Street
***                             Palo Alto, CA 94304 U.S.A.
***    Rights for non-DOD U.S. Government Departments and Agencies are as set
***    forth in FAR 52.227-19(c)(1,2).
***
***-------------------------------------------------------------------*/

#include "ilint.h"
#include "ilXint.h"
#include <math.h>
#include "ilpipelem.h"
#include "ilerrors.h"

    /*  Number of bits of Y and Cb/Cr used to dither before a table lookup to
        convert to a dithered pixel.  The number of levels dithered to (nLY/CbCr) is
        1<<DYCBCR_NBITS_Y/CBCR.  The YCbCr->RGB lookup table is "nLY * nLCbCr * nLCbCr"
        longs in size.
    */
#define DYCBCR_NBITS_Y      6
#define DYCBCR_NBITS_CBCR   4

    /*  Tables generated by /il/util/gendithertables.c */
static unsigned int _il2x2DitherKernel[4] = {42, 234, 170, 106};

        /*  Private data for YCbCr to dithered pixel filter */
typedef struct {
    unsigned long   pixels[1 << (DYCBCR_NBITS_Y + DYCBCR_NBITS_CBCR + DYCBCR_NBITS_CBCR)];
    } ilDitherYCbCrPrivRec, *ilDitherYCbCrPrivPtr;


        /*  ---------------------- ilFastYCbCrDitherExecute ------------------------ */
        /*  Execute() function: dither from subsampled-by-2 YCbCr to 8 bit pixels.
        */
static ilError ilFastYCbCrDitherExecute (
    ilExecuteData          *pData,
    long                    dstLine,
    long                   *pNLines
    )
{
ilDitherYCbCrPrivPtr        pPriv;
ilImagePlaneInfo           *pPlane;
long                        nLinesDiv2, halfWidth;
long                        CbRowBytes, CrRowBytes;
ilPtr                       pYLine, pCbLine, pCrLine, pDstLine;
unsigned long     *pTable;
long               YRowBytes, dstRowBytes, nPixelsDiv2, CbCr, temp0, temp1;
ilPtr              pY, pCb, pCr, pDst;

        /*  This filter handles a pipe image of YCbCr subsampled by 2 in Cb/Cr only.
            The # of lines of Cb/Cr is therefore 1/2 the # of lines of Y.
            Note: can only handle images with even width/height; checked elsewhere.
        */
    pPlane = pData->pSrcImage->plane;
    YRowBytes = pPlane->nBytesPerRow;
    pYLine = pPlane->pPixels;
    pPlane++;
    CbRowBytes = pPlane->nBytesPerRow;
    pCbLine = pPlane->pPixels;
    pPlane++;
    CrRowBytes = pPlane->nBytesPerRow;
    pCrLine = pPlane->pPixels;
    if (pData->srcLine) {
        pYLine  += pData->srcLine * YRowBytes;
        pCbLine += (pData->srcLine >> 1) * CbRowBytes;
        pCrLine += (pData->srcLine >> 1) * CrRowBytes;
        }
    pPlane = pData->pDstImage->plane;
    dstRowBytes = pPlane->nBytesPerRow;
    pDstLine = pPlane->pPixels + dstLine * dstRowBytes;

    pPriv = (ilDitherYCbCrPrivPtr)pData->pPrivate;
    halfWidth = pData->pSrcImage->width >> 1;   /* 1/2 # of lines, pixels per line */
    nLinesDiv2 = *pNLines >> 1;
    pTable = pPriv->pixels;

        /*  Do 4 pixels at a time, using 4 Ys for each
            Cb,Cr pair: get Cb,Cr into upper bits of "CbCr", then add in each Y
            and use result to index into table that yields 4 dithered pixels:
            <left><right><below><below right>.
        */
    while (nLinesDiv2-- > 0) {
        pY = pYLine;
        pYLine += YRowBytes << 1;       /* skip 2 lines; read 2 lines each loop */
        pCb = pCbLine;
        pCbLine += CbRowBytes;
        pCr = pCrLine;
        pCrLine += CrRowBytes;
        pDst = pDstLine;
        pDstLine += dstRowBytes << 1;   /* skip 2 lines; write 2 lines each loop */
        nPixelsDiv2 = halfWidth;
        while (nPixelsDiv2-- > 0) {
            temp0 = *pCb++;
            CbCr = *pCr++;
            temp0 >>= (8 - DYCBCR_NBITS_CBCR);
            CbCr >>= (8 - DYCBCR_NBITS_CBCR);
            CbCr += temp0 << DYCBCR_NBITS_CBCR;
            CbCr <<= DYCBCR_NBITS_Y;    /* CbCr now in upper bits w/ room for Y */

            temp0 = pY[YRowBytes];      /* do pixel and pixel below */
            temp1 = *pY++;
            temp0 = CbCr + (temp0 >> (8-DYCBCR_NBITS_Y));
            pDst[dstRowBytes] = pTable[temp0] >> 8;
            temp1 = CbCr + (temp1 >> (8-DYCBCR_NBITS_Y));
            *pDst++ = pTable[temp1] >> 24;

            temp0 = pY[YRowBytes];      /* do pixel and pixel below */
            temp1 = *pY++;
            temp0 = CbCr + (temp0 >> (8-DYCBCR_NBITS_Y));
            pDst[dstRowBytes] = pTable[temp0];
            temp1 = CbCr + (temp1 >> (8-DYCBCR_NBITS_Y));
            *pDst++ = pTable[temp1] >> 16;
            }
        }

    return IL_OK;
}

        /*  -------------------- ilFastYCbCrDitherDoubleExecute ---------------------- */
        /*  Execute() function: dither and double the given # of src lines. 
        */
static ilError ilFastYCbCrDitherDoubleExecute (
    ilExecuteData          *pData,
    long                    dstLine,
    long                   *pNLines
    )
{
ilDitherYCbCrPrivPtr        pPriv;
ilImagePlaneInfo           *pPlane;
long                        nLinesDiv2, halfWidth;
long                        CbRowBytes, CrRowBytes, dstRowBytes;
ilPtr                       pYLine, pCbLine, pCrLine;
unsigned long     *pTable;
long               YRowBytes, nPixelsDiv2, CbCr, temp0, temp1;
long               dstRowShorts, dstRowShortsTimes2, dstRowShortsTimes3;
ilPtr              pY, pCb, pCr;
unsigned short    *pDst;
unsigned short             *pDstLine;

        /*  This filter handles a pipe image of YCbCr subsampled by 2 in Cb/Cr only.
            The # of lines of Cb/Cr is therefore 1/2 the # of lines of Y.
            Note: can only handle images with even width/height; checked elsewhere.
        */
    pPlane = pData->pSrcImage->plane;
    YRowBytes = pPlane->nBytesPerRow;
    pYLine = pPlane->pPixels;
    pPlane++;
    CbRowBytes = pPlane->nBytesPerRow;
    pCbLine = pPlane->pPixels;
    pPlane++;
    CrRowBytes = pPlane->nBytesPerRow;
    pCrLine = pPlane->pPixels;
    if (pData->srcLine) {
        pYLine  += pData->srcLine * YRowBytes;
        pCbLine += (pData->srcLine >> 1) * CbRowBytes;
        pCrLine += (pData->srcLine >> 1) * CrRowBytes;
        }

        /*  Access dst buffer as shorts */
    pPlane = pData->pDstImage->plane;
    dstRowBytes = pPlane->nBytesPerRow;
    pDstLine = (unsigned short *)(pPlane->pPixels + dstLine * dstRowBytes);
    dstRowShorts = dstRowBytes >> 1;
    dstRowShortsTimes2 = dstRowShorts * 2;
    dstRowShortsTimes3 = dstRowShorts * 3;

    pPriv = (ilDitherYCbCrPrivPtr)pData->pPrivate;
    halfWidth = pData->pSrcImage->width >> 1;   /* 1/2 # of lines, pixels per line */
    nLinesDiv2 = *pNLines >> 1;
    *pNLines <<= 1;                             /* write 2x # of src lines */
    pTable = pPriv->pixels;

        /*  Do 4 src pixels at a time, using 4 Ys for each Cb,Cr pair: get Cb,Cr into 
            upper bits of "CbCr", then add in each Y and use result to index into table 
            that yields 4 dithered pixels: <left><right><below><below right>.
            To double, output those 4 pixels as shown above; 1 src pixel becomes 4 dst.
        */
    while (nLinesDiv2-- > 0) {
        pY = pYLine;
        pYLine += YRowBytes << 1;       /* skip 2 lines; read 2 lines each loop */
        pCb = pCbLine;
        pCbLine += CbRowBytes;
        pCr = pCrLine;
        pCrLine += CrRowBytes;
        pDst = pDstLine;
        pDstLine += dstRowShorts << 2;   /* skip 4 lines; write 4 lines each loop */
        nPixelsDiv2 = halfWidth;
        while (nPixelsDiv2-- > 0) {
            temp0 = *pCb++;
            CbCr = *pCr++;
            temp0 >>= (8 - DYCBCR_NBITS_CBCR);
            CbCr >>= (8 - DYCBCR_NBITS_CBCR);
            CbCr += temp0 << DYCBCR_NBITS_CBCR;
            CbCr <<= DYCBCR_NBITS_Y;    /* CbCr now in upper bits w/ room for Y */

            temp0 = pY[YRowBytes];      /* do pixel and pixel below */
            temp1 = *pY++;
            temp0 = CbCr + (temp0 >> (8-DYCBCR_NBITS_Y));
            temp0 = pTable[temp0];
            pDst[dstRowShortsTimes3] = (unsigned short)temp0;
            pDst[dstRowShortsTimes2] = (unsigned short)(temp0 >> 16);
            temp1 = CbCr + (temp1 >> (8-DYCBCR_NBITS_Y));
            temp1 = pTable[temp1];
            pDst[dstRowShorts] = (unsigned short)temp1;
            *pDst++ = (unsigned short)(temp1 >> 16);

            temp0 = pY[YRowBytes];      /* do pixel and pixel below */
            temp1 = *pY++;
            temp0 = CbCr + (temp0 >> (8-DYCBCR_NBITS_Y));
            temp0 = pTable[temp0];
            pDst[dstRowShortsTimes3] = (unsigned short)temp0;
            pDst[dstRowShortsTimes2] = (unsigned short)(temp0 >> 16);
            temp1 = CbCr + (temp1 >> (8-DYCBCR_NBITS_Y));
            temp1 = pTable[temp1];
            pDst[dstRowShorts] = (unsigned short)temp1;
            *pDst++ = (unsigned short)(temp1 >> 16);
            }
        }

    return IL_OK;
}



    /*  ---------------------------- ilSetupYCbCrDitherTables ----------------------- */
    /*  Setup *pTable as necessary for the YCbCr to RGB (dither) conversion.
    */
static void ilSetupYCbCrDitherTables (
    ilYCbCrInfo   *pYCbCr,
    int                     nBitsR,
    int                     nBitsG,
    int                     nBitsB,
    ilPtr                   pMapPixels,
    unsigned long          *pTable
    )
{
int                         nTableEntries, nLevelsY, nLevelsCbCr;
int                         Y, Cb, Cr, refY, refCb, refCr;
int                R, G, B, pixel, i, temp, kernel;
double                      Lr, Lg, Lb;
double                      ditherR, ditherG, ditherB, ditherY, ditherCbCr;

#define CVT_TO_RGB { \
                int tY = Y - pYCbCr->sample[0].refBlack;\
                int tCb = Cb - pYCbCr->sample[1].refBlack;\
                int tCr = Cr - pYCbCr->sample[2].refBlack;\
                R = tCr * (2 - 2 * Lr) + tY;\
                B = tCb * (2 - 2 * Lb) + tY;\
                G = (tY - Lb * B - Lr * R) / Lg;\
                if (R < 0) R = 0; else if (R > 255) R = 255;\
                if (G < 0) G = 0; else if (G > 255) G = 255;\
                if (B < 0) B = 0; else if (B > 255) B = 255;\
                }

#define YCBCR_DEBUG_TABLE 1
#ifdef YCBCR_DEBUG_TABLE
        /*  Set pMapPixels to identity, for debugging */
    ilByte  debugMapPixels[256];
    int     tmp;
    int     doDebug = FALSE;

    if (doDebug) {
        for (tmp = 0; tmp < 256; tmp++)
            debugMapPixels[tmp] = tmp;
        pMapPixels = debugMapPixels;
        }
#endif

        /*  Build the YCbCr to dither-RGB lookup table.  The table is indexed by
            a YCbCr value <Cb><Cr><Y>, where <Y> is "DYCBCR_NBITS_Y" bits
            in size and <Cb><Cr> are each "DYCBCR_NBITS_CBCR" in size.  Indexed that way
            the table yields either four dithered pixels or a long 24 X pixel.
                Each table entry is calculated by converting the YCbCr value to 
            its RGB equivalent and either dithering to form 4 pixels or a single
            long X pixel.
        */
    nLevelsY = 1 << DYCBCR_NBITS_Y;
    nLevelsCbCr = 1 << DYCBCR_NBITS_CBCR;

    Lr = ((double)pYCbCr->lumaRed / (double)10000);
    Lg = ((double)pYCbCr->lumaGreen / (double)10000);
    Lb = ((double)pYCbCr->lumaBlue / (double)10000);

    ditherR = ((1 << nBitsR) - 1) * (double)256 / (double)255;
    ditherG = ((1 << nBitsG) - 1) * (double)256 / (double)255;
    ditherB = ((1 << nBitsB) - 1) * (double)256 / (double)255;

    for (refCb = 0; refCb < nLevelsCbCr; refCb++) {
        Cb = (255 * refCb) / (nLevelsCbCr - 1);

        for (refCr = 0; refCr < nLevelsCbCr; refCr++) {
            Cr = (255 * refCr) / (nLevelsCbCr - 1);

            for (refY = 0; refY < nLevelsY; refY++) {
                Y = (255 * refY) / (nLevelsY - 1);

                    /*  Store 4 dithered pixels, the result of dithering RGB with
                        a 2x2 kernel.  See /ilc/ildither.c for equivalent code.
                    */
#define DITHER
#ifdef COLORSLAM
                CVT_TO_RGB
                pixel = R >> (8 - nBitsR);
                pixel <<= nBitsG;
                pixel |= G >> (8 - nBitsG);
                pixel <<= nBitsB;
                pixel |= B >> (8 - nBitsB);
                pixel = pMapPixels[pixel];
                *pTable++ = (pixel << 24) | (pixel << 16) | (pixel << 8) | pixel;
#else
#ifdef DIFFUSION
{
                int temp1, temp2;
                int halfR = (1 << (7 - nBitsR)) - 1;
                int halfG = (1 << (7 - nBitsG)) - 1;
                int halfB = (1 << (7 - nBitsB)) - 1;
                int maskR = ~((1 << (8 - nBitsR)) - 1);
                int maskG = ~((1 << (8 - nBitsG)) - 1);
                int maskB = ~((1 << (8 - nBitsB)) - 1);
                CVT_TO_RGB
                for (i = 0, pixel = 0; i < 4; i++) {
                    temp1 = R + halfR;
                    if (temp1 > 255) temp1 = 255; else if (temp1 < 0) temp1 = 0;
                    R += R - (temp1 & maskR);
                    temp = temp1 >> (8 - nBitsR);

                    temp1 = G + halfG;
                    if (temp1 > 255) temp1 = 255; else if (temp1 < 0) temp1 = 0;
                    G += G - (temp1 & maskG);
                    temp <<= nBitsG;
                    temp |= temp1 >> (8 - nBitsG);

                    temp1 = B + halfB;
                    if (temp1 > 255) temp1 = 255; else if (temp1 < 0) temp1 = 0;
                    B += B - (temp1 & maskB);
                    temp <<= nBitsB;
                    temp |= temp1 >> (8 - nBitsB);

                    pixel <<= 8;
                    pixel |= pMapPixels[temp];
                    }
                *pTable++ = pixel;
}
#else
#ifdef DITHER
                CVT_TO_RGB
                R = (double)R * ditherR;
                G = (double)G * ditherG;
                B = (double)B * ditherB;

                for (i = 0, pixel = 0; i < 4; i++) {
                    kernel = _il2x2DitherKernel[i];
                    temp = (R + kernel) >> 8;
                    temp <<= nBitsG;
                    temp |= (G + kernel) >> 8;
                    temp <<= nBitsB;
                    temp |= (B + kernel) >> 8;
                    pixel <<= 8;
                    pixel |= pMapPixels[temp];
                    }
                *pTable++ = pixel;
#endif
#endif
#endif
                }
            }
        }
}

        /*  ------------------------ _ilFastYCbCrDither ---------------------------- */
        /*  Called by ilConvertForXWrite() to do fast dithered display of YCbCr.
            The pipe image must be planar, subsampled by 2 in Cb/Cr, 1 in Y.
            "pMapPixels" points to 256 bytes from the XWC map image, which are folded
            into the lookup table used for this function.
        */
IL_PRIVATE ilBool _ilFastYCbCrDither (
    ilPipe                  pipe,
    ilImageDes             *pDes,
    ilPipeInfo             *pInfo,
    int                     nBitsRed,
    int                     nBitsGreen,
    int                     nBitsBlue,
    ilBool                  doDouble,
    ilPtr                   pMapPixels
    )
{
ilImageDes                  des;
ilDitherYCbCrPrivPtr        pPriv;
ilDstElementData            dstData;
ilSrcElementData            srcData;

        /*  Add a filter to convert subsampled YCbCr to a dithered palette image.
            Force a single strip; image is at least 2 by 2 
        */
    srcData.consumerImage = (ilObject)NULL;
    srcData.stripHeight = pInfo->height;
    srcData.constantStrip = TRUE;
    srcData.minBufferHeight = 0;

    dstData.producerObject = (ilObject)NULL;
    dstData.pDes = IL_DES_GRAY;             /* arbitrary; not really used */
    dstData.pFormat = IL_FORMAT_BYTE;
    dstData.width = pInfo->width;
    dstData.height = pInfo->height;
    dstData.stripHeight = srcData.stripHeight;
    if (doDouble) {
        dstData.width *= 2;
        dstData.height *= 2;
        dstData.stripHeight *= 2;
        }
    dstData.constantStrip = pInfo->constantStrip;
    dstData.pPalette = (unsigned short *)NULL;

    pPriv = (ilDitherYCbCrPrivPtr)ilAddPipeElement (pipe, IL_FILTER, 
            sizeof(ilDitherYCbCrPrivRec), 0, &srcData, &dstData, IL_NPF, IL_NPF, IL_NPF, 
            (doDouble) ? ilFastYCbCrDitherDoubleExecute : ilFastYCbCrDitherExecute, 0);
    if (!pPriv)
        return FALSE;

        /*  Init the table in private */
    ilSetupYCbCrDitherTables (&pDes->typeInfo.YCbCr, nBitsRed, nBitsGreen, nBitsBlue,
                              pMapPixels, pPriv->pixels);

    return TRUE;
}
