/*
 * 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: ilbigray.c /main/6 1996/09/24 17:12:45 drk $ */
/**---------------------------------------------------------------------
***	
***    (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).
***
***-------------------------------------------------------------------*/

        /*  /ilc/ilbigray.c : Contains ilBitonalToGray(), which adds a pipe element 
            to convert the pipe image (which must be IL_BITONAL) to gray scale.
        */

#include "ilint.h"
#include "ilpipelem.h"
#include "ilscaleint.h"
#include "ilerrors.h"

#ifdef LSB_BIT_ORDER
extern void flip_bits(ilPtr start, unsigned n);
#else
# define flip_bits(s,n)          /*EMPTY*/
#endif

#define IL_FILTER_SIZE         3                   /* size of one side of filter square */
#define IL_FILTER_SQUARE (IL_FILTER_SIZE*IL_FILTER_SIZE)  /* square size of filter */
#define MIN_DESIRED_STRIP     16                   /* arbitrary: desired size of strips */

        /*  Private data for pipe elements.
            See comments under ilInit9BitFilter() for a description of "filterValues".
        */
typedef struct {

        /*  Data inited by ilScaleBitonalToGray() when pipe element added. */
    ilByte          filterValues [512];         /* should be first for efficiency! */
    ilBool          bitonalOutput;              /* true: outputting bitonal, else gray */
    int            *pGrayThreshold;             /* bitonal out only: ptr to threshold */
    int             defaultGrayThreshold;       /* pGrayThreshold -> this if defaulting */
    long            srcWidth, srcHeight;        /* size of src (pipe) image */
    long            dstWidth, dstHeight;        /* size of dst (output) image */
    long            widthDiff, heightDiff;      /* src - dst of width/height */
    long            srcBufferHeight;            /* height of perm src image, else 0 */

        /*  Data inited by ilBiGrayInit() */
    unsigned int    grayThreshold;              /* *pGrayThreshold for this pass */
    long            lineAcc;                    /* line accumulator for vert scaling */
    long            srcRowBytes;                /* bytes/row of src image */
    ilPtr           pSrcPixels;                 /* ptr to start of src pixels */
    long            dstRowBytes;                /* bytes/row of dst image */
    ilPtr           pDstPixels;                 /* ptr to start of dst pixels */
    } ilBiGrayPrivRec, *ilBiGrayPrivPtr;


        /*  --------------------------- ilInit9BitFilter ---------------------------- */
        /*  Init "*pTable", a 512 entry table, with the values to be indexed by a 9 bit
            number formed as follows:
                             -----------
                            | 8 | 7 | 6 |   <- top source line
                             -----------
                            | 5 | 4 | 3 |   <- bit "4" is logical center point
                             -----------
                            | 2 | 1 | 0 |
                             -----------
            The bits are shifted into a 9 bit number, as above, then used as an index
            into the table.  The value in entry 0xff (all bits on) should equal
            the max gray value ("nLevels" - 1).
        */
static void ilInit9BitFilter (
    long            srcWidth,
    long            srcHeight,
    long            dstWidth,
    long            dstHeight,
    unsigned long   nLevels,
    ilBool          blackIsZero,
    ilByte         *pTable
    )
{
double              filter [IL_FILTER_SQUARE];
double              fraction, widthScale, heightScale, totalArea, widthSmall, heightSmall;
long                value, nLevelsM1;
int                 i;

        /*  Setup "filter" with the fractional (0..1) contribution of each pixel to
            the whole, in the bit order described above (bit 0 is the top-left pixel).
            The sum of all values should ~= 1.0.  The portion of each pixel selected
            is based on a square of size "srcWidth/dstWidth" by "srcHeight/dstHeight",
            mapped over the source pixels, centered on pixel 4 (above).  If the scale
            factor is greater than 3.0 in either direction, make it 3.0 (all pixels
            contribute equally in that direction.
        */
    widthScale = (double)srcWidth / (double)dstWidth;
    if (widthScale > 3.0)
        widthScale = 3.0;
    heightScale = (double)srcHeight / (double)dstHeight;
    if (heightScale > 3.0)
        heightScale = 3.0;
    totalArea = widthScale * heightScale;

        /*  The center pixel is a 1x1 square.
            The four corner pixels are overlayed by a rect of width or height (wh) =
            (wh - 1) / 2: the whole side is "wh", - 1 for center, / 2 cause there are 2.
            The four side pixels have width/height of corner pixels, but other dim = 1.
        */
    filter [4] = 1.0 / totalArea;
    widthSmall = (widthScale - 1.0) / 2.0;
    heightSmall = (heightScale - 1.0) / 2.0;
    filter [0] = filter [2] = filter [6] = filter [8] = 
        (widthSmall * heightSmall) / totalArea;
    filter [1] = filter [7] = heightSmall / totalArea;
    filter [3] = filter [5] = widthSmall / totalArea;

        /*  Convert the filter to an array indexed by a 9 bit number: the filter bits
            as shown above, in order "876543210".  If whitePixel is 0, add the filter
            fraction if bit is off. If whitePixel not zero than add the filter fraction 
            if bit is on.
        */
    nLevelsM1 = nLevels - 1;
    if (!blackIsZero) {
        for (i = 0; i < 512; i++) {
            fraction = 0.0;
            if (!(i & 1))   fraction += filter [0];
            if (!(i & 2))   fraction += filter [1];
            if (!(i & 4))   fraction += filter [2];
            if (!(i & 8))   fraction += filter [3];
            if (!(i & 16))  fraction += filter [4];
            if (!(i & 32))  fraction += filter [5];
            if (!(i & 64))  fraction += filter [6];
            if (!(i & 128)) fraction += filter [7];
            if (!(i & 256)) fraction += filter [8];
            value = fraction * nLevelsM1 + 0.5;
            if (value > nLevelsM1)
                value = nLevelsM1;
            pTable [i] = value;
            }
        }
    else {          /* a 1 is white */
        for (i = 0; i < 512; i++) {
            fraction = 0.0;
            if (i & 1)   fraction += filter [0];
            if (i & 2)   fraction += filter [1];
            if (i & 4)   fraction += filter [2];
            if (i & 8)   fraction += filter [3];
            if (i & 16)  fraction += filter [4];
            if (i & 32)  fraction += filter [5];
            if (i & 64)  fraction += filter [6];
            if (i & 128) fraction += filter [7];
            if (i & 256) fraction += filter [8];
            value = fraction * nLevelsM1 + 0.5;
            if (value > nLevelsM1)
                value = nLevelsM1;
            pTable [i] = value;
            }
        }
}

        /*  --------------------- ilBiGrayInit -------------------------- */
        /*  Init() function: copy values from given images to private for fast reference.
        */
static ilError ilBiGrayInit (
    ilBiGrayPrivPtr        pPriv,
    ilImageInfo        *pSrcImage,
    ilImageInfo        *pDstImage
    )
{

    pPriv->lineAcc = pPriv->heightDiff;
    pPriv->srcRowBytes = pSrcImage->plane[0].nBytesPerRow;
    pPriv->pSrcPixels = pSrcImage->plane[0].pPixels;
    pPriv->dstRowBytes = pDstImage->plane[0].nBytesPerRow;
    pPriv->pDstPixels = pDstImage->plane[0].pPixels;

        /*  Copy caller's threshold if scaling to bitonal.
        */
    if (pPriv->bitonalOutput)
        pPriv->grayThreshold = *pPriv->pGrayThreshold;

    return IL_OK;
}

        /*  --------------------- ilBiGrayExecute -------------------------- */
        /*  Execute() pipe element function for scaling bitonal to gray.
        */
static ilError ilBiGrayExecute (
    ilExecuteData          *pData,
    long                    dstLine,
    long                   *pNLines
    )
{
#define ULPTR               CARD32 *
#ifdef LSB_BIT_ORDER
# define SPECIAL_MASK_BIT    0x00000001          /* for LSB bit order */
# define SHIFT_MASK(m)       ((m) <<= 1)
#else
# define SPECIAL_MASK_BIT    0x80000000          /* for MSB bit order */
# define SHIFT_MASK(m)       ((m) >>= 1)
#endif
long                        nLines, nLinesWritten;
ilPtr                       pSrcLine, pDstLine;
long                        colAcc, nDstBits, dstWidth, bottomLine, line;
long               srcBytesAbove, srcBytesBelow;
ilBiGrayPrivPtr    pPriv;
CARD32             index, *pSrc;
int                shift, bitNumber;

        /*  Point pSrcLine to srcLine: = middle line of 3x3 matrix.
            Set bottomLine to last available line in the buffer.
        */
    pPriv = (ilBiGrayPrivPtr)pData->pPrivate;
    dstWidth = pPriv->dstWidth;
    pSrcLine = pPriv->pSrcPixels + pData->srcLine * pPriv->srcRowBytes;
    pDstLine = pPriv->pDstPixels + dstLine * pPriv->dstRowBytes;

    nLines = *pNLines;
    if (nLines <= 0)
        return 0;                               /* no lines, EXIT */
    if (pPriv->srcBufferHeight)
         bottomLine = pPriv->srcBufferHeight - 1;
    else bottomLine = pData->srcLine + nLines - 1;
    line = pData->srcLine - 1;                  /* line of top of 3x3 matrix */

    nLinesWritten = 0;
    while (TRUE) {
        while (pPriv->lineAcc > 0) {
            if (nLines-- <= 0) goto BGLinesDone;
            pPriv->lineAcc -= pPriv->dstHeight;
            pSrcLine += pPriv->srcRowBytes;
            line++;
            }
        if (nLines-- <= 0) goto BGLinesDone;
        pPriv->lineAcc += pPriv->heightDiff;

            /*  Algorithm: skip src bits, using basic scale algorithm.  To form dst bit,
                form 9 bit value from 3x3, with top bits in high order bits.  Lookup
                the 9 bit value in table of correct gray scale.  "bitNumber" is the
                # of the left bit of 3x3; 31 = left bit of long.  If bitNumber is < 2,
                the bits cross a long boundary: get bits from bytes, low order of this
                long and high order byte of next long.
                    pSrc will point to the middle line of 3x3; the line above is 
                "-srcBytesAbove" away; the line below is "srcBytesBelow" away.  If the 3x3
                is outside of the buffer being read, set "srcBytesAbove/Below" to 0,
                causing the middle line to be re-read and replicated to replace the
                unaccessible line.
            */
        srcBytesAbove = (line < 0) ? 0 : pPriv->srcRowBytes;
        srcBytesBelow = ((line + 2) > bottomLine) ? 0 : pPriv->srcRowBytes;
        nDstBits = pPriv->dstWidth;

            /*  Set pSrc and bitNumber to point to the rightmost bit of the long before
                the first long.  The "colAcc > 0" check below will be true, causing a bump
                to the next bit (first bit in row), _except_ when "widthDiff" is 0 (no
                hori scaling).  In this case, set colAcc to 1, forcing the first colAcc>0
                check to be true and bumping past first bit - the rest proceeds normally.
            */
        pSrc = ((ULPTR)pSrcLine) - 1;
        bitNumber = 0;
        colAcc = pPriv->widthDiff;
        if (colAcc <= 0)
            colAcc = 1;

            /*  If bitonal output, logically produce a gray pixel, then output a 1 if
                gray is < threshold.
            */
        if (pPriv->bitonalOutput) {
            ULPTR           pDst;
            CARD32          mask, outLong;

            pDst = (ULPTR)pDstLine;
            mask = SPECIAL_MASK_BIT;
            outLong = 0;                /* fill with 1's as necessary */

            while (TRUE) {
                while (colAcc > 0) {
                    colAcc -= dstWidth;
                    if (--bitNumber < 0) {
                        pSrc++;
                        bitNumber = 31;
                        }
                    }
                colAcc += pPriv->widthDiff;

                   /*  If done and mask != left bit on, output bits in "outLong". */
                if (nDstBits <= 0) {
                    if (mask != SPECIAL_MASK_BIT) {
			 flip_bits((ilPtr)&outLong, sizeof(outLong));
                        *pDst++ = outLong;
		     }
                    break;                      /* done this line; exit while */
                    }
                if (--nDstBits <= 0) {
                    INT32 rightBit = (((ilPtr)pSrc - pSrcLine) << 3) + 34 - bitNumber;
                    if ((rightBit > pPriv->srcWidth) && (pPriv->srcWidth > 3))
                        if (++bitNumber > 31) {bitNumber = 0; pSrc--; }
                    }
                if (bitNumber >= 2) {
                    shift = bitNumber - 2;
                    index = (*((ULPTR)((ilPtr)pSrc - srcBytesAbove)) >> shift) & 7;
                    index <<= 3;
                    index |= (*pSrc >> shift) & 7;
                    index <<= 3;
                    index |= (*((ULPTR)((ilPtr)pSrc + srcBytesBelow)) >> shift) & 7;
                    }
                else {
                    ilPtr pSrcTemp = (ilPtr)pSrc - srcBytesAbove + 3;
                    shift = 6 + bitNumber;
                    index = ((CARD32)((*pSrcTemp << 8) | *(pSrcTemp + 1)) >> shift) & 7;
                    index <<= 3;
                    pSrcTemp += srcBytesAbove;
                    index |= ((CARD32)((*pSrcTemp << 8) | *(pSrcTemp + 1)) >> shift) & 7;
                    index <<= 3;
                    pSrcTemp += srcBytesBelow;
                    index |= ((CARD32)((*pSrcTemp << 8) | *(pSrcTemp + 1)) >> shift) & 7;
                    }
                if (pPriv->filterValues [index] < pPriv->grayThreshold)
                    outLong |= mask;
                if (!(SHIFT_MASK(mask))) {
                    flip_bits((ilPtr)&outLong, sizeof(outLong));
                    *pDst++ = outLong;
                    mask = SPECIAL_MASK_BIT;
                    outLong = 0;
                    }
                if (--bitNumber < 0) {              /* next src bit */
                    pSrc++;
                    bitNumber = 31;
                    }
                }   /* END column loop */
            }
        else {      /* outputting gray */
            ilPtr       pDst;
            pDst = pDstLine;

            while (TRUE) {
                while (colAcc > 0) {
                    colAcc -= dstWidth;
                    if (--bitNumber < 0) {
                        pSrc++;
                        bitNumber = 31;
                        }
                    }
                colAcc += pPriv->widthDiff;
                if (nDstBits <= 0)
                    break;                      /* done this line; exit while */
                if (--nDstBits <= 0) {
                    INT32 rightBit = (((ilPtr)pSrc - pSrcLine) << 3) + 34 - bitNumber;
                    if ((rightBit > pPriv->srcWidth) && (pPriv->srcWidth > 3))
                        if (++bitNumber > 31) {bitNumber = 0; pSrc--; }
                    }
                if (bitNumber >= 2) {
                    shift = bitNumber - 2;
                    index = (*((ULPTR)((ilPtr)pSrc - srcBytesAbove)) >> shift) & 7;
                    index <<= 3;
                    index |= (*pSrc >> shift) & 7;
                    index <<= 3;
                    index |= (*((ULPTR)((ilPtr)pSrc + srcBytesBelow)) >> shift) & 7;
                    }
                else {
                    ilPtr pSrcTemp = (ilPtr)pSrc - srcBytesAbove + 3;
                    shift = 6 + bitNumber;
                    index = ((CARD32)((*pSrcTemp << 8) | *(pSrcTemp + 1)) >> shift) & 7;
                    index <<= 3;
                    pSrcTemp += srcBytesAbove;
                    index |= ((CARD32)((*pSrcTemp << 8) | *(pSrcTemp + 1)) >> shift) & 7;
                    index <<= 3;
                    pSrcTemp += srcBytesBelow;
                    index |= ((CARD32)((*pSrcTemp << 8) | *(pSrcTemp + 1)) >> shift) & 7;
                    }
                *pDst++ = pPriv->filterValues [index];
                if (--bitNumber < 0) {              /* next src bit */
                    pSrc++;
                    bitNumber = 31;
                    }
                }   /* END column loop */
            }

            /*  Line done; next src, dst lines; inc line index and # lines written.
            */
        pSrcLine += pPriv->srcRowBytes;
        line++;
        pDstLine += pPriv->dstRowBytes;
        nLinesWritten++;
        }       /* END loop, one dst line */

BGLinesDone:
    *pNLines = nLinesWritten;
    return IL_OK;
}


    /*  ------------------------- ilScaleBitonalToGray ---------------------------- */
    /*  Called by ilScale().
        Adds an element to "pipe" to scale the pipe image to "dstWidth" by "dstHeight"
        which both must be > 0.  The input pipe image must be a bitonal,
        bit-per-pixel image, with the given "blackIsZero".  "pInfo" points to the
        pipe info, "dstWidth/Height" is the (> 0) size to scale to, and nGrayLevels
        is the number of levels of gray.  The output image is a gray image.
            Only a scale down in both directions is allowed; the following must be true:
        srcWidth must be >= dstWidth and srcHeight must be >= dstHeight!
    */
IL_PRIVATE void _ilScaleBitonalToGray (
    ilPipe              pipe,
    unsigned long       dstWidth,
    unsigned long       dstHeight,
    unsigned long       nGrayLevels,
    ilBool              blackIsZero,
    ilPipeInfo         *pInfo
    )
{
ilBiGrayPrivPtr pPriv;
ilDstElementData        dstData;
ilImageDes              des;


    /* Idiot check the number of gray levels */

    if ((nGrayLevels < 2) || (nGrayLevels > 256)) {
        ilDeclarePipeInvalid (pipe, IL_ERROR_LEVELS_PER_SAMPLE);
        return;                                                     /* EXIT */
        }

        /*  Add a filter to do the scale, then init *pPriv.
        */
    dstData.producerObject = (ilObject)NULL;
    des = *IL_DES_GRAY;
    des.nLevelsPerSample[0] = nGrayLevels;
    dstData.pDes = &des;
    dstData.pFormat = IL_FORMAT_BYTE;
    dstData.width = dstWidth;
    dstData.height = dstHeight;
    dstData.stripHeight = ((dstHeight * pInfo->recommendedStripHeight) / pInfo->height) + 1;
    dstData.constantStrip = FALSE;
    dstData.pPalette = (unsigned short *)NULL;
    pPriv = (ilBiGrayPrivPtr)ilAddPipeElement (pipe, IL_FILTER, sizeof (ilBiGrayPrivRec), 
                         IL_ADD_PIPE_HOLD_SRC, (ilSrcElementData *)NULL, &dstData, 
                         ilBiGrayInit, IL_NPF, IL_NPF, ilBiGrayExecute, NULL, 0);
    if (!pPriv)
        return;

        /*  Pipe element added, init pPriv.  If not a tempImage, reading directly from
            a permanent image: set srcHeight to image height; else set to 0.
        */
    pPriv->bitonalOutput = FALSE;
    pPriv->srcWidth = pInfo->width;
    pPriv->srcHeight = pInfo->height;
    pPriv->dstWidth = dstWidth;
    pPriv->dstHeight = dstHeight;
    pPriv->widthDiff = pInfo->width - dstWidth;
    pPriv->heightDiff = pInfo->height - dstHeight;
    if (!pInfo->tempImage)
         pPriv->srcBufferHeight = pInfo->height;
    else pPriv->srcBufferHeight = 0;

        /*  Init the filter table in private based on scale factors and # gray levels.
        */
    ilInit9BitFilter (pPriv->srcWidth, pPriv->srcHeight, pPriv->dstWidth, 
                      pPriv->dstHeight, nGrayLevels, blackIsZero, pPriv->filterValues);

    pipe->context->error = IL_OK;
}


    /*  ------------------------- ilAreaScaleBitonal ---------------------------- */
    /*  Called by ilScale().
        Adds an element to "pipe" to scale the pipe image to "dstWidth" by "dstHeight"
        which both must be > 0.  The input pipe image must be a bitonal,
        bit-per-pixel image, with _any_ "blackIsZero".  The resulting image is of the 
        same type. "pInfo" points to the pipe info, "dstWidth/Height" is the (> 0) size 
        to scale to.
            Only a scale down in both directions is allowed; the following must be true:
        srcWidth must be >= dstWidth and srcHeight must be >= dstHeight!
            Logically the same as ilScaleBitonalToGray(): a gray byte is produced for
        each destination pixel, using 256 levels of gray.  If that byte is 
        >= "*pGrayThreshold"; a white pixel is written; else a black pixel is written.
            NOTE: the value at "*pGrayThreshold" is read each time the pipe is executed
        (during Init()); the caller change its value and get different results without
        recreating the pipe.  If "pGrayThreshold" is NULL, a default value is used.
    */
IL_PRIVATE void _ilAreaScaleBitonal (
    ilPipe              pipe,
    unsigned long       dstWidth,
    unsigned long       dstHeight,
    int                *pGrayThreshold,
    ilPipeInfo         *pInfo
    )
{
ilBiGrayPrivPtr pPriv;
ilDstElementData        dstData;
#define                 DEFAULT_GRAY_THRESHOLD  128     /* assumes 256 levels */

        /*  Add a filter to do the scale, then init *pPriv. 
        */
    dstData.producerObject = (ilObject)NULL;
    dstData.pDes = (ilImageDes *)NULL;
    dstData.pFormat = IL_FORMAT_BIT;
    dstData.width = dstWidth;
    dstData.height = dstHeight;
    dstData.stripHeight = ((dstHeight * pInfo->recommendedStripHeight) / pInfo->height) + 1;
    dstData.constantStrip = FALSE;
    dstData.pPalette = (unsigned short *)NULL;
    pPriv = (ilBiGrayPrivPtr)ilAddPipeElement (pipe, IL_FILTER, sizeof (ilBiGrayPrivRec), 
                         IL_ADD_PIPE_HOLD_SRC, (ilSrcElementData *)NULL, &dstData, 
                         ilBiGrayInit, IL_NPF, IL_NPF, ilBiGrayExecute, NULL, 0);
    if (!pPriv)
        return;

        /*  Pipe element added, init pPriv.  If not a tempImage, reading directly from
            a permanent image: set srcHeight to image height; else set to 0.
        */
    pPriv->bitonalOutput = TRUE;
    if (pGrayThreshold)
        pPriv->pGrayThreshold = pGrayThreshold;
    else {
        pPriv->defaultGrayThreshold = DEFAULT_GRAY_THRESHOLD;
        pPriv->pGrayThreshold = &pPriv->defaultGrayThreshold;
        }
    pPriv->srcWidth = pInfo->width;
    pPriv->srcHeight = pInfo->height;
    pPriv->dstWidth = dstWidth;
    pPriv->dstHeight = dstHeight;
    pPriv->widthDiff = pInfo->width - dstWidth;
    pPriv->heightDiff = pInfo->height - dstHeight;
    if (!pInfo->tempImage)
         pPriv->srcBufferHeight = pInfo->height;
    else pPriv->srcBufferHeight = 0;

        /*  Init the filter table in private based on scale factors and 256 gray levels.
            Note that "blackIsZero" is passed as FALSE below, because we want to output
            the same blackIsZero as input.  We output a zero when the gray is 
            >= threshold; this works for either sense of blackIsZero.
        */
    ilInit9BitFilter (pPriv->srcWidth, pPriv->srcHeight, pPriv->dstWidth, 
                      pPriv->dstHeight, 256, FALSE, pPriv->filterValues);

    pipe->context->error = IL_OK;
}


