/*====================================================================*
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
 -
 -  Redistribution and use in source and binary forms, with or without
 -  modification, are permitted provided that the following conditions
 -  are met:
 -  1. Redistributions of source code must retain the above copyright
 -     notice, this list of conditions and the following disclaimer.
 -  2. Redistributions in binary form must reproduce the above
 -     copyright notice, this list of conditions and the following
 -     disclaimer in the documentation and/or other materials
 -     provided with the distribution.
 -
 -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
 -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *====================================================================*/

/*
 *  paintcmap.c
 *
 *      These in-place functions paint onto colormap images.
 *
 *      Repaint selected pixels in region
 *           l_int32     pixSetSelectCmap()
 *
 *      Repaint non-white pixels in region
 *           l_int32     pixColorGrayCmap()
 *           l_int32     addColorizedGrayToCmap()
 *
 *      Repaint selected pixels through mask
 *           l_int32     pixSetSelectMaskedCmap()
 *
 *      Repaint all pixels through mask
 *           l_int32     pixSetMaskedCmap()
 *
 *
 *  The 'set select' functions condition the setting on a specific
 *  pixel value (i.e., index into the colormap) of the underyling
 *  Pix that is being modified.  The same conditioning is used in
 *  pixBlendCmap().
 *
 *  The pixColorGrayCmap() function sets all truly gray (r = g = b) pixels,
 *  with the exception of either black or white pixels, to a new color.
 *
 *  The pixSetSelectMaskedCmap() function conditions pixel painting
 *  on both a specific pixel value and location within the fg mask.
 *  By contrast, pixSetMaskedCmap() sets all pixels under the
 *  mask foreground, without considering the initial pixel values.
 */

#include <string.h>
#include "allheaders.h"

/*-------------------------------------------------------------*
 *               Repaint selected pixels in region             *
 *-------------------------------------------------------------*/
/*!
 *  pixSetSelectCmap()
 *
 *      Input:  pixs (1, 2, 4 or 8 bpp, with colormap)
 *              box (<optional> region to set color; can be NULL)
 *              sindex (colormap index of pixels to be changed)
 *              rval, gval, bval (new color to paint)
 *      Return: 0 if OK, 1 on error
 *
 *  Note:
 *      (1) This is an in-place operation.
 *      (2) It sets all pixels in region that have the color specified
 *          by the colormap index 'sindex' to the new color.
 *      (3) sindex must be in the existing colormap; otherwise an
 *          error is returned.
 *      (4) If the new color exists in the colormap, it is used;
 *          otherwise, it is added to the colormap.  If it cannot be
 *          added because the colormap is full, an error is returned.
 *      (5) If box is NULL, applies function to the entire image; otherwise,
 *          clips the operation to the intersection of the box and pix.
 *      (6) An example of use would be to set to a specific color all
 *          the light (background) pixels within a certain region of
 *          a 3-level 2 bpp image, while leaving light pixels outside
 *          this region unchanged.
 */
l_int32
pixSetSelectCmap(PIX     *pixs,
                 BOX     *box,
                 l_int32  sindex,
                 l_int32  rval,
                 l_int32  gval,
                 l_int32  bval)
{
l_int32    i, j, w, h, d, n, x1, y1, x2, y2, bw, bh, val, wpls;
l_int32    index;  /* of new color to be set */
l_uint32  *lines, *datas;
PIXCMAP   *cmap;

    PROCNAME("pixSetSelectCmap");

    if (!pixs)
        return ERROR_INT("pixs not defined", procName, 1);
    if ((cmap = pixGetColormap(pixs)) == NULL)
        return ERROR_INT("no colormap", procName, 1);
    d = pixGetDepth(pixs);
    if (d != 1 && d != 2 && d != 4 && d != 8)
        return ERROR_INT("depth not in {1,2,4,8}", procName, 1);

        /* Add new color if necessary; get index of this color in cmap */
    n = pixcmapGetCount(cmap);
    if (sindex >= n)
        return ERROR_INT("sindex too large; no cmap entry", procName, 1);
    if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */
        if (pixcmapAddColor(cmap, rval, gval, bval))
            return ERROR_INT("error adding cmap entry", procName, 1);
        else
            index = n;  /* we've added one color */
    }

        /* Determine the region of substitution */
    pixGetDimensions(pixs, &w, &h, NULL);
    if (!box) {
        x1 = y1 = 0;
        x2 = w;
        y2 = h;
    }
    else {
        boxGetGeometry(box, &x1, &y1, &bw, &bh);
        x2 = x1 + bw - 1;
        y2 = y1 + bh - 1;
    }

        /* Replace pixel value sindex by index in the region */
    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    for (i = y1; i <= y2; i++) {
        if (i < 0 || i >= h)  /* clip */
            continue;
        lines = datas + i * wpls;
        for (j = x1; j <= x2; j++) {
            if (j < 0 || j >= w)  /* clip */
                continue;
            switch (d) {
            case 1:
                val = GET_DATA_BIT(lines, j);
                if (val == sindex) {
                    if (index == 0)
                        CLEAR_DATA_BIT(lines, j);
                    else
                        SET_DATA_BIT(lines, j);
                }
                break;
            case 2:
                val = GET_DATA_DIBIT(lines, j);
                if (val == sindex)
                    SET_DATA_DIBIT(lines, j, index);
                break;
            case 4:
                val = GET_DATA_QBIT(lines, j);
                if (val == sindex)
                    SET_DATA_QBIT(lines, j, index);
                break;
            case 8:
                val = GET_DATA_BYTE(lines, j);
                if (val == sindex)
                    SET_DATA_BYTE(lines, j, index);
                break;
            default:
                return ERROR_INT("depth not in {1,2,4,8}", procName, 1);
            }
        }
    }

    return 0;
}


/*-------------------------------------------------------------*
 *                  Repaint gray pixels in region              *
 *-------------------------------------------------------------*/
/*!
 *  pixColorGrayCmap()
 *
 *      Input:  pixs (2, 4 or 8 bpp, with colormap)
 *              box (<optional> region to set color; can be NULL)
 *              type (L_PAINT_LIGHT, L_PAINT_DARK)
 *              rval, gval, bval (target color)
 *      Return: 0 if OK, 1 on error
 *
 *  Notes:
 *      (1) This is an in-place operation.
 *      (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
 *          preserving antialiasing.
 *          If type == L_PAINT_DARK, it colorizes non-white pixels,
 *          preserving antialiasing.
 *      (3) If box is NULL, applies function to the entire image; otherwise,
 *          clips the operation to the intersection of the box and pix.
 *      (4) This can also be called through pixColorGray().
 *      (5) This increases the colormap size by the number of
 *          different gray (non-black or non-white) colors in the
 *          input colormap.  If there is not enough room in the colormap
 *          for this expansion, it returns 1 (error), and the caller
 *          should check the return value.  If an error is returned
 *          and the cmap is only 2 or 4 bpp, the pix can be converted
 *          to 8 bpp and this function will succeed if run again on
 *          a larger colormap.
 *      (6) Using the darkness of each original pixel in the rect,
 *          it generates a new color (based on the input rgb values).
 *          If type == L_PAINT_LIGHT, the new color is a (generally)
 *          darken-to-black version of the  input rgb color, where the
 *          amount of darkening increases with the darkness of the
 *          original pixel color.
 *          If type == L_PAINT_DARK, the new color is a (generally)
 *          faded-to-white version of the  input rgb color, where the
 *          amount of fading increases with the brightness of the
 *          original pixel color.
 */
l_int32
pixColorGrayCmap(PIX     *pixs,
                 BOX     *box,
                 l_int32  type,
                 l_int32  rval,
                 l_int32  gval,
                 l_int32  bval)
{
l_int32    i, j, w, h, d, x1, y1, x2, y2, bw, bh, wpl;
l_int32    val, nval;
l_int32   *map;
l_uint32  *line, *data;
NUMA      *na;
PIX       *pixt;
PIXCMAP   *cmap, *cmapc;

    PROCNAME("pixColorGrayCmap");

    if (!pixs)
        return ERROR_INT("pixs not defined", procName, 1);
    if ((cmap = pixGetColormap(pixs)) == NULL)
        return ERROR_INT("no colormap", procName, 1);
    d = pixGetDepth(pixs);
    if (d != 2 && d != 4 && d != 8)
        return ERROR_INT("depth not in {2, 4, 8}", procName, 1);
    if (type != L_PAINT_DARK && type != L_PAINT_LIGHT)
        return ERROR_INT("invalid type", procName, 1);

        /* If 2 bpp or 4 bpp, see if the new colors will fit into
         * the existing colormap.  If not, convert in-place to 8 bpp. */
    if (d == 2 || d == 4) {
        cmapc = pixcmapCopy(cmap);  /* experiment with a copy */
        if (addColorizedGrayToCmap(cmapc, type, rval, gval, bval, NULL)) {
            pixt = pixConvertTo8(pixs, 1);
            pixTransferAllData(pixs, &pixt, 0, 0);
        }
        pixcmapDestroy(&cmapc);
    }

        /* Find gray colors, add the corresponding new colors,
         * and set up a mapping table from gray to new.
         * That table has the value 256 for all colors that are
         * not to be mapped. */
    cmap = pixGetColormap(pixs);
    if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na)) {
        numaDestroy(&na);
        return ERROR_INT("no room; cmap full", procName, 1);
    }
    map = numaGetIArray(na);

        /* Determine the region of substitution */
    pixGetDimensions(pixs, &w, &h, &d);  /* d may be different */
    data = pixGetData(pixs);
    wpl = pixGetWpl(pixs);
    if (!box) {
        x1 = y1 = 0;
        x2 = w;
        y2 = h;
    }
    else {
        boxGetGeometry(box, &x1, &y1, &bw, &bh);
        x2 = x1 + bw - 1;
        y2 = y1 + bh - 1;
    }

        /* Remap gray pixels in the region */
    for (i = y1; i <= y2; i++) {
        if (i < 0 || i >= h)  /* clip */
            continue;
        line = data + i * wpl;
        for (j = x1; j <= x2; j++) {
            if (j < 0 || j >= w)  /* clip */
                continue;
            switch (d)
            {
            case 2:
                val = GET_DATA_DIBIT(line, j);
                nval = map[val];
                if (nval != 256)
                    SET_DATA_DIBIT(line, j, nval);
                break;
            case 4:
                val = GET_DATA_QBIT(line, j);
                nval = map[val];
                if (nval != 256)
                    SET_DATA_QBIT(line, j, nval);
                break;
            case 8:
                val = GET_DATA_BYTE(line, j);
                nval = map[val];
                if (nval != 256)
                    SET_DATA_BYTE(line, j, nval);
                break;
            }
        }
    }

    FREE(map);
    numaDestroy(&na);
    return 0;
}


/*!
 *  addColorizedGrayToCmap()
 *
 *      Input:  cmap (from 2 or 4 bpp pix)
 *              type (L_PAINT_LIGHT, L_PAINT_DARK)
 *              rval, gval, bval (target color)
 *              &na (<optional return> table for mapping new cmap entries)
 *      Return: 0 if OK; 1 on error; 2 if new colors will not fit in cmap.
 *
 *  Notes:
 *      (1) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
 *          preserving antialiasing.
 *          If type == L_PAINT_DARK, it colorizes non-white pixels,
 *          preserving antialiasing.
 *      (2) This increases the colormap size by the number of
 *          different gray (non-black or non-white) colors in the
 *          input colormap.  If there is not enough room in the colormap
 *          for this expansion, it returns 1 (treated as a warning);
 *          the caller should check the return value.
 *      (3) This can be used to determine if the new colors will fit in
 *          the cmap, using null for &na.  Returns 0 if they fit; 2 if
 *          they don't fit.
 *      (4) The mapping table contains, for each gray color found, the
 *          index of the corresponding colorized pixel.  Non-gray
 *          pixels are assigned the invalid index 256.
 *      (5) See pixColorGrayCmap() for usage.
 */
l_int32
addColorizedGrayToCmap(PIXCMAP  *cmap,
                       l_int32   type,
                       l_int32   rval,
                       l_int32   gval,
                       l_int32   bval,
                       NUMA    **pna)
{
l_int32  i, n, erval, egval, ebval, nrval, ngval, nbval, newindex;
NUMA    *na;

    PROCNAME("addColorizedGrayToCmap");

    if (pna) *pna = NULL;
    if (!cmap)
        return ERROR_INT("cmap not defined", procName, 1);
    if (type != L_PAINT_DARK && type != L_PAINT_LIGHT)
        return ERROR_INT("invalid type", procName, 1);

    n = pixcmapGetCount(cmap);
    na = numaCreate(n);
    for (i = 0; i < n; i++) {
        pixcmapGetColor(cmap, i, &erval, &egval, &ebval);
        if (type == L_PAINT_LIGHT) {
            if (erval == egval && erval == ebval && erval != 0) {
                nrval = (l_int32)(rval * (l_float32)erval / 255.);
                ngval = (l_int32)(gval * (l_float32)egval / 255.);
                nbval = (l_int32)(bval * (l_float32)ebval / 255.);
                if (pixcmapAddNewColor(cmap, nrval, ngval, nbval, &newindex)) {
                    numaDestroy(&na);
                    L_WARNING("no room; colormap full;", procName);
                    return 2;
                }
                numaAddNumber(na, newindex);
            }
            else
                numaAddNumber(na, 256);  /* invalid number; not gray */
        }
        else {  /* L_PAINT_DARK */
            if (erval == egval && erval == ebval && erval != 255) {
                nrval = rval +
                        (l_int32)((255. - rval) * (l_float32)erval / 255.);
                ngval = gval +
                        (l_int32)((255. - gval) * (l_float32)egval / 255.);
                nbval = bval +
                        (l_int32)((255. - bval) * (l_float32)ebval / 255.);
                if (pixcmapAddNewColor(cmap, nrval, ngval, nbval, &newindex)) {
                    numaDestroy(&na);
                    L_WARNING("no room; colormap full;", procName);
                    return 2;
                }
                numaAddNumber(na, newindex);
            }
            else
                numaAddNumber(na, 256);  /* invalid number; not gray */
        }
    }

    if (pna)
        *pna = na;
    else
        numaDestroy(&na);
    return 0;
}


/*-------------------------------------------------------------*
 *             Repaint selected pixels through mask            *
 *-------------------------------------------------------------*/
/*!
 *  pixSetSelectMaskedCmap()
 *
 *      Input:  pixs (2, 4 or 8 bpp, with colormap)
 *              pixm (<optional> 1 bpp mask; no-op if NULL)
 *              x, y (UL corner of mask relative to pixs)
 *              sindex (colormap index of pixels in pixs to be changed)
 *              rval, gval, bval (new color to substitute)
 *      Return: 0 if OK, 1 on error
 *
 *  Note:
 *      (1) This is an in-place operation.
 *      (2) This paints through the fg of pixm and replaces all pixels
 *          in pixs that have a particular value (sindex) with the new color.
 *      (3) If pixm == NULL, a warning is given.
 *      (4) sindex must be in the existing colormap; otherwise an
 *          error is returned.
 *      (5) If the new color exists in the colormap, it is used;
 *          otherwise, it is added to the colormap.  If the colormap
 *          is full, an error is returned.
 */
l_int32
pixSetSelectMaskedCmap(PIX     *pixs,
                       PIX     *pixm,
                       l_int32  x,
                       l_int32  y,
                       l_int32  sindex,
                       l_int32  rval,
                       l_int32  gval,
                       l_int32  bval)
{
l_int32    i, j, w, h, d, n, wm, hm, wpls, wplm, val;
l_int32    index;  /* of new color to be set */
l_uint32  *lines, *linem, *datas, *datam;
PIXCMAP   *cmap;

    PROCNAME("pixSetSelectMaskedCmap");

    if (!pixs)
        return ERROR_INT("pixs not defined", procName, 1);
    if ((cmap = pixGetColormap(pixs)) == NULL)
        return ERROR_INT("no colormap", procName, 1);
    if (!pixm) {
        L_WARNING("no mask; nothing to do", procName);
        return 0;
    }

    d = pixGetDepth(pixs);
    if (d != 2 && d != 4 && d != 8)
        return ERROR_INT("depth not in {2, 4, 8}", procName, 1);

        /* add new color if necessary; get index of this color in cmap */
    n = pixcmapGetCount(cmap);
    if (sindex >= n)
        return ERROR_INT("sindex too large; no cmap entry", procName, 1);
    if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */
        if (pixcmapAddColor(cmap, rval, gval, bval))
            return ERROR_INT("error adding cmap entry", procName, 1);
        else
            index = n;  /* we've added one color */
    }

        /* replace pixel value sindex by index when fg pixel in pixmc
         * overlays it */
    w = pixGetWidth(pixs);
    h = pixGetHeight(pixs);
    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    wm = pixGetWidth(pixm);
    hm = pixGetHeight(pixm);
    datam = pixGetData(pixm);
    wplm = pixGetWpl(pixm);
    for (i = 0; i < hm; i++) {
        if (i + y < 0 || i + y >= h) continue;
        lines = datas + (y + i) * wpls;
        linem = datam + i * wplm;
        for (j = 0; j < wm; j++) {
            if (j + x < 0  || j + x >= w) continue;
            if (GET_DATA_BIT(linem, j)) {
                switch (d) {
                case 1:
                    val = GET_DATA_BIT(lines, x + j);
                    if (val == sindex) {
                        if (index == 0)
                            CLEAR_DATA_BIT(lines, x + j);
                        else
                            SET_DATA_BIT(lines, x + j);
                    }
                    break;
                case 2:
                    val = GET_DATA_DIBIT(lines, x + j);
                    if (val == sindex)
                        SET_DATA_DIBIT(lines, x + j, index);
                    break;
                case 4:
                    val = GET_DATA_QBIT(lines, x + j);
                    if (val == sindex)
                        SET_DATA_QBIT(lines, x + j, index);
                    break;
                case 8:
                    val = GET_DATA_BYTE(lines, x + j);
                    if (val == sindex)
                        SET_DATA_BYTE(lines, x + j, index);
                    break;
                default:
                    return ERROR_INT("depth not in {1,2,4,8}", procName, 1);
                }
            }
        }
    }

    return 0;
}


/*-------------------------------------------------------------*
 *               Repaint all pixels through mask               *
 *-------------------------------------------------------------*/
/*!
 *  pixSetMaskedCmap()
 *
 *      Input:  pixs (2, 4 or 8 bpp, colormapped)
 *              pixm (<optional> 1 bpp mask; no-op if NULL)
 *              x, y (origin of pixm relative to pixs; can be negative)
 *              rval, gval, bval (new color to set at each masked pixel)
 *      Return: 0 if OK; 1 on error
 *
 *  Notes:
 *      (1) This is an in-place operation.
 *      (2) It paints a single color through the mask (as a stencil).
 *      (3) The mask origin is placed at (x,y) on pixs, and the
 *          operation is clipped to the intersection of the mask and pixs.
 *      (4) If pixm == NULL, a warning is given.
 *      (5) Typically, pixm is a small binary mask located somewhere
 *          on the larger pixs.
 *      (6) If the color is in the colormap, it is used.  Otherwise,
 *          it is added if possible; an error is returned if the
 *          colormap is already full.
 */
l_int32
pixSetMaskedCmap(PIX      *pixs,
                 PIX      *pixm,
                 l_int32   x,
                 l_int32   y,
                 l_int32   rval,
                 l_int32   gval,
                 l_int32   bval)
{
l_int32    w, h, d, wpl, wm, hm, wplm;
l_int32    i, j, index;
l_uint32  *data, *datam, *line, *linem;
PIXCMAP   *cmap;

    PROCNAME("pixSetMaskedCmap");

    if (!pixs)
        return ERROR_INT("pixs not defined", procName, 1);
    if ((cmap = pixGetColormap(pixs)) == NULL)
        return ERROR_INT("no colormap in pixs", procName, 1);
    if (!pixm) {
        L_WARNING("no mask; nothing to do", procName);
        return 0;
    }
    d = pixGetDepth(pixs);
    if (d != 2 && d != 4 && d != 8)
        return ERROR_INT("depth not in {2,4,8}", procName, 1);
    if (pixGetDepth(pixm) != 1)
        return ERROR_INT("pixm not 1 bpp", procName, 1);

        /* Add new color if necessary; store in 'index' */
    if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) {  /* not found */
        if (pixcmapAddColor(cmap, rval, gval, bval))
            return ERROR_INT("no room in cmap", procName, 1);
        index = pixcmapGetCount(cmap) - 1;
    }

    pixGetDimensions(pixs, &w, &h, NULL);
    wpl = pixGetWpl(pixs);
    data = pixGetData(pixs);
    pixGetDimensions(pixm, &wm, &hm, NULL);
    wplm = pixGetWpl(pixm);
    datam = pixGetData(pixm);
    for (i = 0; i < hm; i++) {
        if (i + y < 0 || i + y >= h) continue;
        line = data + (i + y) * wpl;
        linem = datam + i * wplm;
        for (j = 0; j < wm; j++) {
            if (j + x < 0  || j + x >= w) continue;
            if (GET_DATA_BIT(linem, j)) {  /* paint color */
                switch (d)
                {
                case 2:
                    SET_DATA_DIBIT(line, j + x, index);
                    break;
                case 4:
                    SET_DATA_QBIT(line, j + x, index);
                    break;
                case 8:
                    SET_DATA_BYTE(line, j + x, index);
                    break;
                default:
                    return ERROR_INT("depth not in {2,4,8}", procName, 1);
                }
            }
        }
    }

    return 0;
}
