From 5ff1d6955496b3cf9a35042c9ac35db43bc336b1 Mon Sep 17 00:00:00 2001 From: Thomas Deutschmann Date: Tue, 30 Mar 2021 10:59:39 +0200 Subject: Import Ghostscript 9.54 Signed-off-by: Thomas Deutschmann --- leptonica/src/colormap.c | 2433 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2433 insertions(+) create mode 100644 leptonica/src/colormap.c (limited to 'leptonica/src/colormap.c') diff --git a/leptonica/src/colormap.c b/leptonica/src/colormap.c new file mode 100644 index 00000000..c56c67cd --- /dev/null +++ b/leptonica/src/colormap.c @@ -0,0 +1,2433 @@ +/*====================================================================* + - 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. + *====================================================================*/ + +/*! + * \file colormap.c + *
+ *
+ *      Colormap creation, copy, destruction, addition
+ *           PIXCMAP    *pixcmapCreate()
+ *           PIXCMAP    *pixcmapCreateRandom()
+ *           PIXCMAP    *pixcmapCreateLinear()
+ *           PIXCMAP    *pixcmapCopy()
+ *           void        pixcmapDestroy()
+ *           l_int32     pixcmapIsValid()
+ *           l_int32     pixcmapAddColor()
+ *           l_int32     pixcmapAddRGBA()
+ *           l_int32     pixcmapAddNewColor()
+ *           l_int32     pixcmapAddNearestColor()
+ *           l_int32     pixcmapUsableColor()
+ *           l_int32     pixcmapAddBlackOrWhite()
+ *           l_int32     pixcmapSetBlackAndWhite()
+ *           l_int32     pixcmapGetCount()
+ *           l_int32     pixcmapGetDepth()
+ *           l_int32     pixcmapGetMinDepth()
+ *           l_int32     pixcmapGetFreeCount()
+ *           l_int32     pixcmapClear()
+ *
+ *      Colormap random access and test
+ *           l_int32     pixcmapGetColor()
+ *           l_int32     pixcmapGetColor32()
+ *           l_int32     pixcmapGetRGBA()
+ *           l_int32     pixcmapGetRGBA32()
+ *           l_int32     pixcmapResetColor()
+ *           l_int32     pixcmapSetAlpha()
+ *           l_int32     pixcmapGetIndex()
+ *           l_int32     pixcmapHasColor()
+ *           l_int32     pixcmapIsOpaque()
+ *           l_int32     pixcmapIsBlackAndWhite()
+ *           l_int32     pixcmapCountGrayColors()
+ *           l_int32     pixcmapGetRankIntensity()
+ *           l_int32     pixcmapGetNearestIndex()
+ *           l_int32     pixcmapGetNearestGrayIndex()
+ *           l_int32     pixcmapGetDistanceToColor()
+ *           l_int32     pixcmapGetRangeValues()
+ *
+ *      Colormap conversion
+ *           PIXCMAP    *pixcmapGrayToFalseColor()
+ *           PIXCMAP    *pixcmapGrayToColor()
+ *           PIXCMAP    *pixcmapColorToGray()
+ *           PIXCMAP    *pixcmapConvertTo4()
+ *           PIXCMAP    *pixcmapConvertTo8()
+ *
+ *      Colormap I/O
+ *           l_int32     pixcmapRead()
+ *           l_int32     pixcmapReadStream()
+ *           l_int32     pixcmapReadMem()
+ *           l_int32     pixcmapWrite()
+ *           l_int32     pixcmapWriteStream()
+ *           l_int32     pixcmapWriteMem()
+ *
+ *      Extract colormap arrays and serialization
+ *           l_int32     pixcmapToArrays()
+ *           l_int32     pixcmapToRGBTable()
+ *           l_int32     pixcmapSerializeToMemory()
+ *           PIXCMAP    *pixcmapDeserializeFromMemory()
+ *           char       *pixcmapConvertToHex()
+ *
+ *      Colormap transforms
+ *           l_int32     pixcmapGammaTRC()
+ *           l_int32     pixcmapContrastTRC()
+ *           l_int32     pixcmapShiftIntensity()
+ *           l_int32     pixcmapShiftByComponent()
+ *
+ *  Note:
+ *      (1) colormaps in leptonica have a maximum of 256 entries.
+ *      (2) nalloc, the allocated size of the palette array, is related
+ *          to the depth d of the pixels by:
+ *                 nalloc = 2^(d)
+ *
+ * 
+ */ + +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include "allheaders.h" + +/*-------------------------------------------------------------* + * Colormap creation and addition * + *-------------------------------------------------------------*/ +/*! + * \brief pixcmapCreate() + * + * \param[in] depth of pix, in bpp + * \return cmap, or NULL on error + */ +PIXCMAP * +pixcmapCreate(l_int32 depth) +{ +RGBA_QUAD *cta; +PIXCMAP *cmap; + + PROCNAME("pixcmapCreate"); + + if (depth != 1 && depth != 2 && depth !=4 && depth != 8) + return (PIXCMAP *)ERROR_PTR("depth not in {1,2,4,8}", procName, NULL); + + cmap = (PIXCMAP *)LEPT_CALLOC(1, sizeof(PIXCMAP)); + cmap->depth = depth; + cmap->nalloc = 1 << depth; + cta = (RGBA_QUAD *)LEPT_CALLOC(cmap->nalloc, sizeof(RGBA_QUAD)); + cmap->array = cta; + cmap->n = 0; + return cmap; +} + + +/*! + * \brief pixcmapCreateRandom() + * + * \param[in] depth of pix, in bpp: 2, 4 or 8 + * \param[in] hasblack 1 if the first color is black; 0 if no black + * \param[in] haswhite 1 if the last color is white; 0 if no white + * \return cmap, or NULL on error + * + *
+ * Notes:
+ *      (1) This sets up a colormap with random colors,
+ *          where the first color is optionally black, the last color
+ *          is optionally white, and the remaining colors are
+ *          chosen randomly.
+ *      (2) The number of randomly chosen colors is:
+ *               2^(depth) - haswhite - hasblack
+ *      (3) Because rand() is seeded, it might disrupt otherwise
+ *          deterministic results if also used elsewhere in a program.
+ *      (4) rand() is not threadsafe, and will generate garbage if run
+ *          on multiple threads at once -- though garbage is generally
+ *          what you want from a random number generator!
+ *      (5) Modern rand()s have equal randomness in low and high order
+ *          bits, but older ones don't.  Here, we're just using rand()
+ *          to choose colors for output.
+ * 
+ */ +PIXCMAP * +pixcmapCreateRandom(l_int32 depth, + l_int32 hasblack, + l_int32 haswhite) +{ +l_int32 ncolors, i; +l_int32 red[256], green[256], blue[256]; +PIXCMAP *cmap; + + PROCNAME("pixcmapCreateRandom"); + + if (depth != 2 && depth != 4 && depth != 8) + return (PIXCMAP *)ERROR_PTR("depth not in {2, 4, 8}", procName, NULL); + if (hasblack != 0) hasblack = 1; + if (haswhite != 0) haswhite = 1; + + cmap = pixcmapCreate(depth); + ncolors = 1 << depth; + if (hasblack) /* first color is optionally black */ + pixcmapAddColor(cmap, 0, 0, 0); + for (i = hasblack; i < ncolors - haswhite; i++) { + red[i] = (l_uint32)rand() & 0xff; + green[i] = (l_uint32)rand() & 0xff; + blue[i] = (l_uint32)rand() & 0xff; + pixcmapAddColor(cmap, red[i], green[i], blue[i]); + } + if (haswhite) /* last color is optionally white */ + pixcmapAddColor(cmap, 255, 255, 255); + + return cmap; +} + + +/*! + * \brief pixcmapCreateLinear() + * + * \param[in] d depth of pix for this colormap; 1, 2, 4 or 8 + * \param[in] nlevels valid in range [2, 2^d] + * \return cmap, or NULL on error + * + *
+ * Notes:
+ *      (1) Colormap has equally spaced gray color values
+ *          from black (0, 0, 0) to white (255, 255, 255).
+ * 
+ */ +PIXCMAP * +pixcmapCreateLinear(l_int32 d, + l_int32 nlevels) +{ +l_int32 maxlevels, i, val; +PIXCMAP *cmap; + + PROCNAME("pixcmapCreateLinear"); + + if (d != 1 && d != 2 && d !=4 && d != 8) + return (PIXCMAP *)ERROR_PTR("d not in {1, 2, 4, 8}", procName, NULL); + maxlevels = 1 << d; + if (nlevels < 2 || nlevels > maxlevels) + return (PIXCMAP *)ERROR_PTR("invalid nlevels", procName, NULL); + + cmap = pixcmapCreate(d); + for (i = 0; i < nlevels; i++) { + val = (255 * i) / (nlevels - 1); + pixcmapAddColor(cmap, val, val, val); + } + return cmap; +} + + +/*! + * \brief pixcmapCopy() + * + * \param[in] cmaps + * \return cmapd, or NULL on error + */ +PIXCMAP * +pixcmapCopy(const PIXCMAP *cmaps) +{ +l_int32 nbytes, valid; +PIXCMAP *cmapd; + + PROCNAME("pixcmapCopy"); + + if (!cmaps) + return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL); + pixcmapIsValid(cmaps, NULL, &valid); + if (!valid) + return (PIXCMAP *)ERROR_PTR("invalid cmap", procName, NULL); + + cmapd = (PIXCMAP *)LEPT_CALLOC(1, sizeof(PIXCMAP)); + nbytes = cmaps->nalloc * sizeof(RGBA_QUAD); + cmapd->array = (void *)LEPT_CALLOC(1, nbytes); + memcpy(cmapd->array, cmaps->array, cmaps->n * sizeof(RGBA_QUAD)); + cmapd->n = cmaps->n; + cmapd->nalloc = cmaps->nalloc; + cmapd->depth = cmaps->depth; + return cmapd; +} + + +/*! + * \brief pixcmapDestroy() + * + * \param[in,out] pcmap set to null on return + * \return void + */ +void +pixcmapDestroy(PIXCMAP **pcmap) +{ +PIXCMAP *cmap; + + PROCNAME("pixcmapDestroy"); + + if (pcmap == NULL) { + L_WARNING("ptr address is null!\n", procName); + return; + } + + if ((cmap = *pcmap) == NULL) + return; + + LEPT_FREE(cmap->array); + LEPT_FREE(cmap); + *pcmap = NULL; +} + +/*! + * \brief pixcmapIsValid() + * + * \param[in] cmap + * \param[in] pix optional; can be NULL + * \param[out] pvalid return 1 if valid; 0 if not + * \return 0 if OK, 1 on error or if cmap is not valid + * + *
+ * Notes:
+ *      (1) If %pix is input, this will veify that pixel values cannot
+ *          overflow the colormap.  This is a relatively expensive operation
+ *          that may need to check all the pixel values.
+ *      (2) If %pix is input, there must be at least one color in the
+ *          colormap if it is to be valid with any pix, even if the
+ *          pixels are all 0.
+ * 
+ */ +l_ok +pixcmapIsValid(const PIXCMAP *cmap, + PIX *pix, + l_int32 *pvalid) +{ +l_int32 d, depth, nalloc, maxindex, maxcolors; + + PROCNAME("pixcmapIsValid"); + + if (!pvalid) + return ERROR_INT("&valid not defined", procName, 1); + *pvalid = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (!cmap->array) + return ERROR_INT("cmap array not defined", procName, 1); + d = cmap->depth; + if (d != 1 && d != 2 && d != 4 && d != 8) { + L_ERROR("invalid cmap depth: %d\n", procName, d); + return 1; + } + nalloc = cmap->nalloc; + if (nalloc != (1 << d)) { + L_ERROR("invalid cmap nalloc = %d; d = %d\n", procName, nalloc, d); + return 1; + } + if (cmap->n < 0 || cmap->n > nalloc) { + L_ERROR("invalid cmap n: %d; nalloc = %d\n", procName, cmap->n, nalloc); + return 1; + } + + /* If a pix is given, it must have a depth no larger than 8 */ + if (pix) { + depth = pixGetDepth(pix); + if (depth > 8) { + L_ERROR("pix depth %d > 8\n", procName, depth); + return 1; + } + maxcolors = 1 << depth; + } + + /* To prevent indexing overflow into the cmap, the pix depth + * must not exceed the cmap depth. Do not require depth equality, + * because some functions such as median cut quantizers allow + * the cmap depth to be bigger than the pix depth. */ + if (pix && (depth > d)) { + L_ERROR("(pix depth = %d) > (cmap depth = %d)\n", procName, depth, d); + return 1; + } + if (pix && cmap->n < 1) { + L_ERROR("cmap array is empty; invalid with any pix\n", procName); + return 1; + } + + /* Do not let the colormap have more colors than the pixels + * can address. The png encoder considers this to be an + * "invalid palette length". For example, for 1 bpp, the + * colormap may have a depth > 1, but it must not have more + * than 2 colors. */ + if (pix && (cmap->n > maxcolors)) { + L_ERROR("cmap entries = %d > max colors for pix = %d\n", procName, + cmap->n, maxcolors); + return 1; + } + + /* Where the colormap or the pix may have been corrupted, and + * in particular when reading or writing image files, it should + * be verified that the image pixel values do not exceed the + * max indexing into the colormap array. */ + if (pix) { + pixGetMaxColorIndex(pix, &maxindex); + if (maxindex >= cmap->n) { + L_ERROR("(max index = %d) >= (num colors = %d)\n", procName, + maxindex, cmap->n); + return 1; + } + } + + *pvalid = 1; + return 0; +} + + +/*! + * \brief pixcmapAddColor() + * + * \param[in] cmap + * \param[in] rval, gval, bval colormap entry to be added; each number + * is in range [0, ... 255] + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This always adds the color if there is room.
+ *      (2) The alpha component is 255 (opaque)
+ * 
+ */ +l_ok +pixcmapAddColor(PIXCMAP *cmap, + l_int32 rval, + l_int32 gval, + l_int32 bval) +{ +RGBA_QUAD *cta; + + PROCNAME("pixcmapAddColor"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (cmap->n >= cmap->nalloc) + return ERROR_INT("no free color entries", procName, 1); + + cta = (RGBA_QUAD *)cmap->array; + cta[cmap->n].red = rval; + cta[cmap->n].green = gval; + cta[cmap->n].blue = bval; + cta[cmap->n].alpha = 255; + cmap->n++; + return 0; +} + + +/*! + * \brief pixcmapAddRGBA() + * + * \param[in] cmap + * \param[in] rval, gval, bval, aval colormap entry to be added; + * each number is in range [0, ... 255] + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This always adds the color if there is room.
+ * 
+ */ +l_ok +pixcmapAddRGBA(PIXCMAP *cmap, + l_int32 rval, + l_int32 gval, + l_int32 bval, + l_int32 aval) +{ +RGBA_QUAD *cta; + + PROCNAME("pixcmapAddRGBA"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (cmap->n >= cmap->nalloc) + return ERROR_INT("no free color entries", procName, 1); + + cta = (RGBA_QUAD *)cmap->array; + cta[cmap->n].red = rval; + cta[cmap->n].green = gval; + cta[cmap->n].blue = bval; + cta[cmap->n].alpha = aval; + cmap->n++; + return 0; +} + + +/*! + * \brief pixcmapAddNewColor() + * + * \param[in] cmap + * \param[in] rval, gval, bval colormap entry to be added; each number + * is in range [0, ... 255] + * \param[out] pindex index of color + * \return 0 if OK, 1 on error; 2 if unable to add color + * + *
+ * Notes:
+ *      (1) This only adds color if not already there.
+ *      (2) The alpha component is 255 (opaque)
+ *      (3) This returns the index of the new (or existing) color.
+ *      (4) Returns 2 with a warning if unable to add this color;
+ *          the caller should check the return value.
+ * 
+ */ +l_ok +pixcmapAddNewColor(PIXCMAP *cmap, + l_int32 rval, + l_int32 gval, + l_int32 bval, + l_int32 *pindex) +{ + PROCNAME("pixcmapAddNewColor"); + + if (!pindex) + return ERROR_INT("&index not defined", procName, 1); + *pindex = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + /* Check if the color is already present. */ + if (!pixcmapGetIndex(cmap, rval, gval, bval, pindex)) /* found */ + return 0; + + /* We need to add the color. Is there room? */ + if (cmap->n >= cmap->nalloc) { + L_WARNING("no free color entries\n", procName); + return 2; + } + + /* There's room. Add it. */ + pixcmapAddColor(cmap, rval, gval, bval); + *pindex = pixcmapGetCount(cmap) - 1; + return 0; +} + + +/*! + * \brief pixcmapAddNearestColor() + * + * \param[in] cmap + * \param[in] rval, gval, bval colormap entry to be added; each number + * is in range [0, ... 255] + * \param[out] pindex index of color + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This only adds color if not already there.
+ *      (2) The alpha component is 255 (opaque)
+ *      (3) If it's not in the colormap and there is no room to add
+ *          another color, this returns the index of the nearest color.
+ * 
+ */ +l_ok +pixcmapAddNearestColor(PIXCMAP *cmap, + l_int32 rval, + l_int32 gval, + l_int32 bval, + l_int32 *pindex) +{ + PROCNAME("pixcmapAddNearestColor"); + + if (!pindex) + return ERROR_INT("&index not defined", procName, 1); + *pindex = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + /* Check if the color is already present. */ + if (!pixcmapGetIndex(cmap, rval, gval, bval, pindex)) /* found */ + return 0; + + /* We need to add the color. Is there room? */ + if (cmap->n < cmap->nalloc) { + pixcmapAddColor(cmap, rval, gval, bval); + *pindex = pixcmapGetCount(cmap) - 1; + return 0; + } + + /* There's no room. Return the index of the nearest color */ + pixcmapGetNearestIndex(cmap, rval, gval, bval, pindex); + return 0; +} + + +/*! + * \brief pixcmapUsableColor() + * + * \param[in] cmap + * \param[in] rval, gval, bval colormap entry to be added; each number + * is in range [0, ... 255] + * \param[out] pusable 1 if usable; 0 if not + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This checks if the color already exists or if there is
+ *          room to add it.  It makes no change in the colormap.
+ * 
+ */ +l_ok +pixcmapUsableColor(PIXCMAP *cmap, + l_int32 rval, + l_int32 gval, + l_int32 bval, + l_int32 *pusable) +{ +l_int32 index; + + PROCNAME("pixcmapUsableColor"); + + if (!pusable) + return ERROR_INT("&usable not defined", procName, 1); + *pusable = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + /* Is there room to add it? */ + if (cmap->n < cmap->nalloc) { + *pusable = 1; + return 0; + } + + /* No room; check if the color is already present. */ + if (!pixcmapGetIndex(cmap, rval, gval, bval, &index)) /* found */ + *pusable = 1; + return 0; +} + + +/*! + * \brief pixcmapAddBlackOrWhite() + * + * \param[in] cmap + * \param[in] color 0 for black, 1 for white + * \param[out] pindex [optional] index of color; can be null + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This only adds color if not already there.
+ *      (2) The alpha component is 255 (opaque)
+ *      (3) This sets index to the requested color.
+ *      (4) If there is no room in the colormap, returns the index
+ *          of the closest color.
+ * 
+ */ +l_ok +pixcmapAddBlackOrWhite(PIXCMAP *cmap, + l_int32 color, + l_int32 *pindex) +{ +l_int32 index; + + PROCNAME("pixcmapAddBlackOrWhite"); + + if (pindex) *pindex = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + if (color == 0) { /* black */ + if (pixcmapGetFreeCount(cmap) > 0) + pixcmapAddNewColor(cmap, 0, 0, 0, &index); + else + pixcmapGetRankIntensity(cmap, 0.0, &index); + } else { /* white */ + if (pixcmapGetFreeCount(cmap) > 0) + pixcmapAddNewColor(cmap, 255, 255, 255, &index); + else + pixcmapGetRankIntensity(cmap, 1.0, &index); + } + + if (pindex) + *pindex = index; + return 0; +} + + +/*! + * \brief pixcmapSetBlackAndWhite() + * + * \param[in] cmap + * \param[in] setblack 0 for no operation; 1 to set darkest color to black + * \param[in] setwhite 0 for no operation; 1 to set lightest color to white + * \return 0 if OK, 1 on error + */ +l_ok +pixcmapSetBlackAndWhite(PIXCMAP *cmap, + l_int32 setblack, + l_int32 setwhite) +{ +l_int32 index; + + PROCNAME("pixcmapSetBlackAndWhite"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + if (setblack) { + pixcmapGetRankIntensity(cmap, 0.0, &index); + pixcmapResetColor(cmap, index, 0, 0, 0); + } + if (setwhite) { + pixcmapGetRankIntensity(cmap, 1.0, &index); + pixcmapResetColor(cmap, index, 255, 255, 255); + } + return 0; +} + + +/*! + * \brief pixcmapGetCount() + * + * \param[in] cmap + * \return count, or 0 on error + */ +l_int32 +pixcmapGetCount(const PIXCMAP *cmap) +{ + PROCNAME("pixcmapGetCount"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 0); + return cmap->n; +} + + +/*! + * \brief pixcmapGetFreeCount() + * + * \param[in] cmap + * \return free entries, or 0 on error + */ +l_int32 +pixcmapGetFreeCount(PIXCMAP *cmap) +{ + PROCNAME("pixcmapGetFreeCount"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 0); + return (cmap->nalloc - cmap->n); +} + + +/*! + * \brief pixcmapGetDepth() + * + * \param[in] cmap + * \return depth, or 0 on error + */ +l_int32 +pixcmapGetDepth(PIXCMAP *cmap) +{ + PROCNAME("pixcmapGetDepth"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 0); + return cmap->depth; +} + + +/*! + * \brief pixcmapGetMinDepth() + * + * \param[in] cmap + * \param[out] pmindepth minimum depth to support the colormap + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) On error, &mindepth is returned as 0.
+ * 
+ */ +l_ok +pixcmapGetMinDepth(PIXCMAP *cmap, + l_int32 *pmindepth) +{ +l_int32 ncolors; + + PROCNAME("pixcmapGetMinDepth"); + + if (!pmindepth) + return ERROR_INT("&mindepth not defined", procName, 1); + *pmindepth = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + ncolors = pixcmapGetCount(cmap); + if (ncolors <= 4) + *pmindepth = 2; + else if (ncolors <= 16) + *pmindepth = 4; + else /* ncolors > 16 */ + *pmindepth = 8; + return 0; +} + + +/*! + * \brief pixcmapClear() + * + * \param[in] cmap + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This removes the colors by setting the count to 0.
+ * 
+ */ +l_ok +pixcmapClear(PIXCMAP *cmap) +{ + PROCNAME("pixcmapClear"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + cmap->n = 0; + return 0; +} + + +/*-------------------------------------------------------------* + * Colormap random access * + *-------------------------------------------------------------*/ +/*! + * \brief pixcmapGetColor() + * + * \param[in] cmap + * \param[in] index + * \param[out] prval, pgval, pbval each color value + * \return 0 if OK, 1 if not accessible caller should check + */ +l_ok +pixcmapGetColor(PIXCMAP *cmap, + l_int32 index, + l_int32 *prval, + l_int32 *pgval, + l_int32 *pbval) +{ +RGBA_QUAD *cta; + + PROCNAME("pixcmapGetColor"); + + if (!prval || !pgval || !pbval) + return ERROR_INT("&rval, &gval, &bval not all defined", procName, 1); + *prval = *pgval = *pbval = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (index < 0 || index >= cmap->n) + return ERROR_INT("index out of bounds", procName, 1); + + cta = (RGBA_QUAD *)cmap->array; + *prval = cta[index].red; + *pgval = cta[index].green; + *pbval = cta[index].blue; + return 0; +} + + +/*! + * \brief pixcmapGetColor32() + * + * \param[in] cmap + * \param[in] index + * \param[out] pval32 32-bit rgb color value + * \return 0 if OK, 1 if not accessible caller should check + * + *
+ * Notes:
+ *      (1) The returned alpha channel value is 255.
+ * 
+ */ +l_ok +pixcmapGetColor32(PIXCMAP *cmap, + l_int32 index, + l_uint32 *pval32) +{ +l_int32 rval, gval, bval; + + PROCNAME("pixcmapGetColor32"); + + if (!pval32) + return ERROR_INT("&val32 not defined", procName, 1); + *pval32 = 0; + + if (pixcmapGetColor(cmap, index, &rval, &gval, &bval) != 0) + return ERROR_INT("rgb values not found", procName, 1); + composeRGBAPixel(rval, gval, bval, 255, pval32); + return 0; +} + + +/*! + * \brief pixcmapGetRGBA() + * + * \param[in] cmap + * \param[in] index + * \param[out] prval, pgval, pbval, paval each color value + * \return 0 if OK, 1 if not accessible caller should check + */ +l_ok +pixcmapGetRGBA(PIXCMAP *cmap, + l_int32 index, + l_int32 *prval, + l_int32 *pgval, + l_int32 *pbval, + l_int32 *paval) +{ +RGBA_QUAD *cta; + + PROCNAME("pixcmapGetRGBA"); + + if (!prval || !pgval || !pbval || !paval) + return ERROR_INT("&rval, &gval, &bval, &aval not all defined", + procName, 1); + *prval = *pgval = *pbval = *paval = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (index < 0 || index >= cmap->n) + return ERROR_INT("index out of bounds", procName, 1); + + cta = (RGBA_QUAD *)cmap->array; + *prval = cta[index].red; + *pgval = cta[index].green; + *pbval = cta[index].blue; + *paval = cta[index].alpha; + return 0; +} + + +/*! + * \brief pixcmapGetRGBA32() + * + * \param[in] cmap + * \param[in] index + * \param[out] pval32 32-bit rgba color value + * \return 0 if OK, 1 if not accessible caller should check + */ +l_ok +pixcmapGetRGBA32(PIXCMAP *cmap, + l_int32 index, + l_uint32 *pval32) +{ +l_int32 rval, gval, bval, aval; + + PROCNAME("pixcmapGetRGBA32"); + + if (!pval32) + return ERROR_INT("&val32 not defined", procName, 1); + *pval32 = 0; + + if (pixcmapGetRGBA(cmap, index, &rval, &gval, &bval, &aval) != 0) + return ERROR_INT("rgba values not found", procName, 1); + composeRGBAPixel(rval, gval, bval, aval, pval32); + return 0; +} + + +/*! + * \brief pixcmapResetColor() + * + * \param[in] cmap + * \param[in] index + * \param[in] rval, gval, bval colormap entry to be reset; each number + * is in range [0, ... 255] + * \return 0 if OK, 1 if not accessible caller should check + * + *
+ * Notes:
+ *      (1) This resets sets the color of an entry that has already
+ *          been set and included in the count of colors.
+ *      (2) The alpha component is 255 (opaque)
+ * 
+ */ +l_ok +pixcmapResetColor(PIXCMAP *cmap, + l_int32 index, + l_int32 rval, + l_int32 gval, + l_int32 bval) +{ +RGBA_QUAD *cta; + + PROCNAME("pixcmapResetColor"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (index < 0 || index >= cmap->n) + return ERROR_INT("index out of bounds", procName, 1); + + cta = (RGBA_QUAD *)cmap->array; + cta[index].red = rval; + cta[index].green = gval; + cta[index].blue = bval; + cta[index].alpha = 255; + return 0; +} + + +/*! + * \brief pixcmapSetAlpha() + * + * \param[in] cmap + * \param[in] index + * \param[in] aval in range [0, ... 255] + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This modifies the transparency of one entry in a colormap.
+ *          The alpha component by default is 255 (opaque).
+ *          This is used when extracting the colormap from a PNG file
+ *          without decoding the image.
+ * 
+ */ +l_ok +pixcmapSetAlpha(PIXCMAP *cmap, + l_int32 index, + l_int32 aval) +{ +RGBA_QUAD *cta; + + PROCNAME("pixcmapSetAlpha"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (index < 0 || index >= cmap->n) + return ERROR_INT("index out of bounds", procName, 1); + + cta = (RGBA_QUAD *)cmap->array; + cta[index].alpha = aval; + return 0; +} + + +/*! + * \brief pixcmapGetIndex() + * + * \param[in] cmap + * \param[in] rval, gval, bval colormap colors to search for; each number + * is in range [0, ... 255] + * \param[out] pindex value of index found + * \return 0 if found, 1 if not found caller must check + */ +l_int32 +pixcmapGetIndex(PIXCMAP *cmap, + l_int32 rval, + l_int32 gval, + l_int32 bval, + l_int32 *pindex) +{ +l_int32 n, i; +RGBA_QUAD *cta; + + PROCNAME("pixcmapGetIndex"); + + if (!pindex) + return ERROR_INT("&index not defined", procName, 1); + *pindex = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + n = pixcmapGetCount(cmap); + + cta = (RGBA_QUAD *)cmap->array; + for (i = 0; i < n; i++) { + if (rval == cta[i].red && + gval == cta[i].green && + bval == cta[i].blue) { + *pindex = i; + return 0; + } + } + return 1; +} + + +/*! + * \brief pixcmapHasColor() + * + * \param[in] cmap + * \param[out] pcolor TRUE if cmap has color; FALSE otherwise + * \return 0 if OK, 1 on error + */ +l_ok +pixcmapHasColor(PIXCMAP *cmap, + l_int32 *pcolor) +{ +l_int32 n, i; +l_int32 *rmap, *gmap, *bmap; + + PROCNAME("pixcmapHasColor"); + + if (!pcolor) + return ERROR_INT("&color not defined", procName, 1); + *pcolor = FALSE; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + if (pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL)) + return ERROR_INT("colormap arrays not made", procName, 1); + n = pixcmapGetCount(cmap); + for (i = 0; i < n; i++) { + if ((rmap[i] != gmap[i]) || (rmap[i] != bmap[i])) { + *pcolor = TRUE; + break; + } + } + + LEPT_FREE(rmap); + LEPT_FREE(gmap); + LEPT_FREE(bmap); + return 0; +} + + +/*! + * \brief pixcmapIsOpaque() + * + * \param[in] cmap + * \param[out] popaque TRUE if fully opaque: all entries are 255 + * \return 0 if OK, 1 on error + */ +l_ok +pixcmapIsOpaque(PIXCMAP *cmap, + l_int32 *popaque) +{ +l_int32 i, n; +RGBA_QUAD *cta; + + PROCNAME("pixcmapIsOpaque"); + + if (!popaque) + return ERROR_INT("&opaque not defined", procName, 1); + *popaque = TRUE; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + n = pixcmapGetCount(cmap); + cta = (RGBA_QUAD *)cmap->array; + for (i = 0; i < n; i++) { + if (cta[i].alpha != 255) { + *popaque = FALSE; + break; + } + } + return 0; +} + + +/*! + * \brief pixcmapIsBlackAndWhite() + * + * \param[in] cmap + * \param[out] pblackwhite TRUE if the cmap has only two colors: + * black (0,0,0) and white (255,255,255) + * \return 0 if OK, 1 on error + */ +l_ok +pixcmapIsBlackAndWhite(PIXCMAP *cmap, + l_int32 *pblackwhite) +{ +l_int32 val0, val1, hascolor; +RGBA_QUAD *cta; + + PROCNAME("pixcmapIsBlackAndWhite"); + + if (!pblackwhite) + return ERROR_INT("&blackwhite not defined", procName, 1); + *pblackwhite = FALSE; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (pixcmapGetCount(cmap) != 2) + return 0; + + pixcmapHasColor(cmap, &hascolor); + if (hascolor) return 0; + + cta = (RGBA_QUAD *)cmap->array; + val0 = cta[0].red; + val1 = cta[1].red; + if ((val0 == 0 && val1 == 255) || (val0 == 255 && val1 == 0)) + *pblackwhite = TRUE; + return 0; +} + + +/*! + * \brief pixcmapCountGrayColors() + * + * \param[in] cmap + * \param[out] pngray number of gray colors + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) This counts the unique gray colors, including black and white.
+ * 
+ */ +l_ok +pixcmapCountGrayColors(PIXCMAP *cmap, + l_int32 *pngray) +{ +l_int32 n, i, rval, gval, bval, count; +l_int32 *array; + + PROCNAME("pixcmapCountGrayColors"); + + if (!pngray) + return ERROR_INT("&ngray not defined", procName, 1); + *pngray = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + array = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); + n = pixcmapGetCount(cmap); + count = 0; + for (i = 0; i < n; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + if ((rval == gval) && (rval == bval) && (array[rval] == 0)) { + array[rval] = 1; + count++; + } + } + + LEPT_FREE(array); + *pngray = count; + return 0; +} + + +/*! + * \brief pixcmapGetRankIntensity() + * + * \param[in] cmap + * \param[in] rankval 0.0 for darkest, 1.0 for lightest color + * \param[out] pindex the index into the colormap that corresponds + * to the rank intensity color + * \return 0 if OK, 1 on error + */ +l_ok +pixcmapGetRankIntensity(PIXCMAP *cmap, + l_float32 rankval, + l_int32 *pindex) +{ +l_int32 n, i, rval, gval, bval, rankindex; +NUMA *na, *nasort; + + PROCNAME("pixcmapGetRankIntensity"); + + if (!pindex) + return ERROR_INT("&index not defined", procName, 1); + *pindex = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (rankval < 0.0 || rankval > 1.0) + return ERROR_INT("rankval not in [0.0 ... 1.0]", procName, 1); + + n = pixcmapGetCount(cmap); + na = numaCreate(n); + for (i = 0; i < n; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + numaAddNumber(na, rval + gval + bval); + } + nasort = numaGetSortIndex(na, L_SORT_INCREASING); + rankindex = (l_int32)(rankval * (n - 1) + 0.5); + numaGetIValue(nasort, rankindex, pindex); + + numaDestroy(&na); + numaDestroy(&nasort); + return 0; +} + + +/*! + * \brief pixcmapGetNearestIndex() + * + * \param[in] cmap + * \param[in] rval, gval, bval colormap colors to search for; each number + * is in range [0, ... 255] + * \param[out] pindex the index of the nearest color + * \return 0 if OK, 1 on error caller must check + * + *
+ * Notes:
+ *      (1) Returns the index of the exact color if possible, otherwise the
+ *          index of the color closest to the target color.
+ *      (2) Nearest color is that which is the least sum-of-squares distance
+ *          from the target color.
+ * 
+ */ +l_ok +pixcmapGetNearestIndex(PIXCMAP *cmap, + l_int32 rval, + l_int32 gval, + l_int32 bval, + l_int32 *pindex) +{ +l_int32 i, n, delta, dist, mindist; +RGBA_QUAD *cta; + + PROCNAME("pixcmapGetNearestIndex"); + + if (!pindex) + return ERROR_INT("&index not defined", procName, 1); + *pindex = UNDEF; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + if ((cta = (RGBA_QUAD *)cmap->array) == NULL) + return ERROR_INT("cta not defined(!)", procName, 1); + n = pixcmapGetCount(cmap); + + mindist = 3 * 255 * 255 + 1; + for (i = 0; i < n; i++) { + delta = cta[i].red - rval; + dist = delta * delta; + delta = cta[i].green - gval; + dist += delta * delta; + delta = cta[i].blue - bval; + dist += delta * delta; + if (dist < mindist) { + *pindex = i; + if (dist == 0) + break; + mindist = dist; + } + } + + return 0; +} + + +/*! + * \brief pixcmapGetNearestGrayIndex() + * + * \param[in] cmap + * \param[in] val gray value to search for; in range [0, ... 255] + * \param[out] pindex the index of the nearest color + * \return 0 if OK, 1 on error caller must check + * + *
+ * Notes:
+ *      (1) This should be used on gray colormaps.  It uses only the
+ *          green value of the colormap.
+ *      (2) Returns the index of the exact color if possible, otherwise the
+ *          index of the color closest to the target color.
+ * 
+ */ +l_ok +pixcmapGetNearestGrayIndex(PIXCMAP *cmap, + l_int32 val, + l_int32 *pindex) +{ +l_int32 i, n, dist, mindist; +RGBA_QUAD *cta; + + PROCNAME("pixcmapGetNearestGrayIndex"); + + if (!pindex) + return ERROR_INT("&index not defined", procName, 1); + *pindex = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (val < 0 || val > 255) + return ERROR_INT("val not in [0 ... 255]", procName, 1); + + if ((cta = (RGBA_QUAD *)cmap->array) == NULL) + return ERROR_INT("cta not defined(!)", procName, 1); + n = pixcmapGetCount(cmap); + + mindist = 256; + for (i = 0; i < n; i++) { + dist = cta[i].green - val; + dist = L_ABS(dist); + if (dist < mindist) { + *pindex = i; + if (dist == 0) + break; + mindist = dist; + } + } + + return 0; +} + + +/*! + * \brief pixcmapGetDistanceToColor() + * + * \param[in] cmap + * \param[in] index + * \param[in] rval, gval, bval target color + * \param[out] pdist the distance from the cmap entry to target + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) Returns the L2 distance (squared) between the color at index i
+ *          and the target color.
+ * 
+ */ +l_ok +pixcmapGetDistanceToColor(PIXCMAP *cmap, + l_int32 index, + l_int32 rval, + l_int32 gval, + l_int32 bval, + l_int32 *pdist) +{ +l_int32 n, delta, dist; +RGBA_QUAD *cta; + + PROCNAME("pixcmapGetDistanceToColor"); + + if (!pdist) + return ERROR_INT("&dist not defined", procName, 1); + *pdist = UNDEF; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + n = pixcmapGetCount(cmap); + if (index >= n) + return ERROR_INT("invalid index", procName, 1); + + if ((cta = (RGBA_QUAD *)cmap->array) == NULL) + return ERROR_INT("cta not defined(!)", procName, 1); + + delta = cta[index].red - rval; + dist = delta * delta; + delta = cta[index].green - gval; + dist += delta * delta; + delta = cta[index].blue - bval; + dist += delta * delta; + *pdist = dist; + + return 0; +} + + +/*! + * \brief pixcmapGetRangeValues() + * + * \param[in] cmap + * \param[in] select L_SELECT_RED, L_SELECT_GREEN, L_SELECT_BLUE or + * L_SELECT_AVERAGE + * \param[out] pminval [optional] minimum value of component + * \param[out] pmaxval [optional] maximum value of component + * \param[out] pminindex [optional] index of minimum value + * \param[out] pmaxindex [optional] index of maximum value + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) Returns, for selected components (or the average), the
+ *          the extreme values (min and/or max) and their indices
+ *          that are found in the cmap.
+ * 
+ */ +l_ok +pixcmapGetRangeValues(PIXCMAP *cmap, + l_int32 select, + l_int32 *pminval, + l_int32 *pmaxval, + l_int32 *pminindex, + l_int32 *pmaxindex) +{ +l_int32 i, n, imin, imax, minval, maxval, rval, gval, bval, aveval; + + PROCNAME("pixcmapGetRangeValues"); + + if (pminval) *pminval = UNDEF; + if (pmaxval) *pmaxval = UNDEF; + if (pminindex) *pminindex = UNDEF; + if (pmaxindex) *pmaxindex = UNDEF; + if (!pminval && !pmaxval && !pminindex && !pmaxindex) + return ERROR_INT("no result requested", procName, 1); + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + imin = UNDEF; + imax = UNDEF; + minval = 100000; + maxval = -1; + n = pixcmapGetCount(cmap); + for (i = 0; i < n; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + if (select == L_SELECT_RED) { + if (rval < minval) { + minval = rval; + imin = i; + } + if (rval > maxval) { + maxval = rval; + imax = i; + } + } else if (select == L_SELECT_GREEN) { + if (gval < minval) { + minval = gval; + imin = i; + } + if (gval > maxval) { + maxval = gval; + imax = i; + } + } else if (select == L_SELECT_BLUE) { + if (bval < minval) { + minval = bval; + imin = i; + } + if (bval > maxval) { + maxval = bval; + imax = i; + } + } else if (select == L_SELECT_AVERAGE) { + aveval = (rval + gval + bval) / 3; + if (aveval < minval) { + minval = aveval; + imin = i; + } + if (aveval > maxval) { + maxval = aveval; + imax = i; + } + } else { + return ERROR_INT("invalid selection", procName, 1); + } + } + + if (pminval) *pminval = minval; + if (pmaxval) *pmaxval = maxval; + if (pminindex) *pminindex = imin; + if (pmaxindex) *pmaxindex = imax; + return 0; +} + + +/*-------------------------------------------------------------* + * Colormap conversion * + *-------------------------------------------------------------*/ +/*! + * \brief pixcmapGrayToFalseColor() + * + * \param[in] gamma (factor) 0.0 or 1.0 for default; > 1.0 for brighter; + * 2.0 is quite nice + * \return cmap, or NULL on error + * + *
+ * Notes:
+ *      (1) This creates a colormap that maps from gray to false colors.
+ *          The colormap is modeled after the Matlap "jet" configuration.
+ * 
+ */ +PIXCMAP * +pixcmapGrayToFalseColor(l_float32 gamma) +{ +l_int32 i, rval, gval, bval; +l_int32 *curve; +l_float32 invgamma, x; +PIXCMAP *cmap; + + if (gamma <= 0.0) gamma = 1.0; + + /* Generate curve for transition part of color map */ + curve = (l_int32 *)LEPT_CALLOC(64, sizeof(l_int32)); + invgamma = 1. / gamma; + for (i = 0; i < 64; i++) { + x = (l_float32)i / 64.; + curve[i] = (l_int32)(255. * powf(x, invgamma) + 0.5); + } + + cmap = pixcmapCreate(8); + for (i = 0; i < 256; i++) { + if (i < 32) { + rval = 0; + gval = 0; + bval = curve[i + 32]; + } else if (i < 96) { /* 32 - 95 */ + rval = 0; + gval = curve[i - 32]; + bval = 255; + } else if (i < 160) { /* 96 - 159 */ + rval = curve[i - 96]; + gval = 255; + bval = curve[159 - i]; + } else if (i < 224) { /* 160 - 223 */ + rval = 255; + gval = curve[223 - i]; + bval = 0; + } else { /* 224 - 255 */ + rval = curve[287 - i]; + gval = 0; + bval = 0; + } + pixcmapAddColor(cmap, rval, gval, bval); + } + + LEPT_FREE(curve); + return cmap; +} + + +/*! + * \brief pixcmapGrayToColor() + * + * \param[in] color + * \return cmap, or NULL on error + * + *
+ * Notes:
+ *      (1) This creates a colormap that maps from gray to
+ *          a specific color.  In the mapping, each component
+ *          is faded to white, depending on the gray value.
+ *      (2) In use, this is simply attached to a grayscale pix
+ *          to give it the input color.
+ * 
+ */ +PIXCMAP * +pixcmapGrayToColor(l_uint32 color) +{ +l_int32 i, rval, gval, bval; +PIXCMAP *cmap; + + extractRGBValues(color, &rval, &gval, &bval); + cmap = pixcmapCreate(8); + for (i = 0; i < 256; i++) { + pixcmapAddColor(cmap, rval + (i * (255 - rval)) / 255, + gval + (i * (255 - gval)) / 255, + bval + (i * (255 - bval)) / 255); + } + + return cmap; +} + + +/*! + * \brief pixcmapColorToGray() + * + * \param[in] cmaps + * \param[in] rwt, gwt, bwt non-negative; these should add to 1.0 + * \return cmap gray, or NULL on error + * + *
+ * Notes:
+ *      (1) This creates a gray colormap from an arbitrary colormap.
+ *      (2) In use, attach the output gray colormap to the pix
+ *          (or a copy of it) that provided the input colormap.
+ * 
+ */ +PIXCMAP * +pixcmapColorToGray(PIXCMAP *cmaps, + l_float32 rwt, + l_float32 gwt, + l_float32 bwt) +{ +l_int32 i, n, rval, gval, bval, val; +l_float32 sum; +PIXCMAP *cmapd; + + PROCNAME("pixcmapColorToGray"); + + if (!cmaps) + return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL); + if (rwt < 0.0 || gwt < 0.0 || bwt < 0.0) + return (PIXCMAP *)ERROR_PTR("weights not all >= 0.0", procName, NULL); + + /* Make sure the sum of weights is 1.0; otherwise, you can get + * overflow in the gray value. */ + sum = rwt + gwt + bwt; + if (sum == 0.0) { + L_WARNING("all weights zero; setting equal to 1/3\n", procName); + rwt = gwt = bwt = 0.33333f; + sum = 1.0; + } + if (L_ABS(sum - 1.0) > 0.0001) { /* maintain ratios with sum == 1.0 */ + L_WARNING("weights don't sum to 1; maintaining ratios\n", procName); + rwt = rwt / sum; + gwt = gwt / sum; + bwt = bwt / sum; + } + + if ((cmapd = pixcmapCopy(cmaps)) == NULL) + return (PIXCMAP *)ERROR_PTR("cmapd not made", procName, NULL); + n = pixcmapGetCount(cmapd); + for (i = 0; i < n; i++) { + pixcmapGetColor(cmapd, i, &rval, &gval, &bval); + val = (l_int32)(rwt * rval + gwt * gval + bwt * bval + 0.5); + pixcmapResetColor(cmapd, i, val, val, val); + } + + return cmapd; +} + + +/*! + * \brief pixcmapConvertTo4() + * + * \param[in] cmaps colormap for 2 bpp pix + * \return cmapd (4 bpp) + * + *
+ * Notes:
+ *      (1) This converts a 2 bpp colormap to 4 bpp.  The colors
+ *          are the same; the output colormap entry array has size 16.
+ * 
+ */ +PIXCMAP * +pixcmapConvertTo4(PIXCMAP *cmaps) +{ +l_int32 i, n, rval, gval, bval; +PIXCMAP *cmapd; + + PROCNAME("pixcmapConvertTo4"); + + if (!cmaps) + return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL); + if (pixcmapGetDepth(cmaps) != 2) + return (PIXCMAP *)ERROR_PTR("cmaps not for 2 bpp pix", procName, NULL); + + cmapd = pixcmapCreate(4); + n = pixcmapGetCount(cmaps); + for (i = 0; i < n; i++) { + pixcmapGetColor(cmaps, i, &rval, &gval, &bval); + pixcmapAddColor(cmapd, rval, gval, bval); + } + return cmapd; +} + + +/*! + * \brief pixcmapConvertTo8() + * + * \param[in] cmaps colormap for 2 bpp or 4 bpp pix + * \return cmapd (8 bpp) + * + *
+ * Notes:
+ *      (1) This converts a 2 bpp or 4 bpp colormap to 8 bpp.  The colors
+ *          are the same; the output colormap entry array has size 256.
+ * 
+ */ +PIXCMAP * +pixcmapConvertTo8(PIXCMAP *cmaps) +{ +l_int32 i, n, depth, rval, gval, bval; +PIXCMAP *cmapd; + + PROCNAME("pixcmapConvertTo8"); + + if (!cmaps) + return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL); + depth = pixcmapGetDepth(cmaps); + if (depth == 8) return pixcmapCopy(cmaps); + if (depth != 2 && depth != 4) + return (PIXCMAP *)ERROR_PTR("cmaps not 2 or 4 bpp", procName, NULL); + + cmapd = pixcmapCreate(8); + n = pixcmapGetCount(cmaps); + for (i = 0; i < n; i++) { + pixcmapGetColor(cmaps, i, &rval, &gval, &bval); + pixcmapAddColor(cmapd, rval, gval, bval); + } + return cmapd; +} + + +/*-------------------------------------------------------------* + * Colormap I/O * + *-------------------------------------------------------------*/ +/*! + * \brief pixcmapRead() + * + * \param[in] filename + * \return cmap, or NULL on error + */ +PIXCMAP * +pixcmapRead(const char *filename) +{ +FILE *fp; +PIXCMAP *cmap; + + PROCNAME("pixcmapRead"); + + if (!filename) + return (PIXCMAP *)ERROR_PTR("filename not defined", procName, NULL); + + if ((fp = fopenReadStream(filename)) == NULL) + return (PIXCMAP *)ERROR_PTR("stream not opened", procName, NULL); + cmap = pixcmapReadStream(fp); + fclose(fp); + if (!cmap) + return (PIXCMAP *)ERROR_PTR("cmap not read", procName, NULL); + return cmap; +} + + +/*! + * \brief pixcmapReadStream() + * + * \param[in] fp file stream + * \return cmap, or NULL on error + */ +PIXCMAP * +pixcmapReadStream(FILE *fp) +{ +l_int32 rval, gval, bval, aval, ignore; +l_int32 i, index, ret, depth, ncolors; +PIXCMAP *cmap; + + PROCNAME("pixcmapReadStream"); + + if (!fp) + return (PIXCMAP *)ERROR_PTR("stream not defined", procName, NULL); + + ret = fscanf(fp, "\nPixcmap: depth = %d bpp; %d colors\n", + &depth, &ncolors); + if (ret != 2 || + (depth != 1 && depth != 2 && depth != 4 && depth != 8) || + (ncolors < 2 || ncolors > 256)) + return (PIXCMAP *)ERROR_PTR("invalid cmap size", procName, NULL); + ignore = fscanf(fp, "Color R-val G-val B-val Alpha\n"); + ignore = fscanf(fp, "----------------------------------------\n"); + + if ((cmap = pixcmapCreate(depth)) == NULL) + return (PIXCMAP *)ERROR_PTR("cmap not made", procName, NULL); + for (i = 0; i < ncolors; i++) { + if (fscanf(fp, "%3d %3d %3d %3d %3d\n", + &index, &rval, &gval, &bval, &aval) != 5) { + pixcmapDestroy(&cmap); + return (PIXCMAP *)ERROR_PTR("invalid entry", procName, NULL); + } + pixcmapAddRGBA(cmap, rval, gval, bval, aval); + } + return cmap; +} + + +/*! + * \brief pixcmapReadMem() + * + * \param[in] data serialization of pixcmap; in ascii + * \param[in] size of data in bytes; can use strlen to get it + * \return cmap, or NULL on error + */ +PIXCMAP * +pixcmapReadMem(const l_uint8 *data, + size_t size) +{ +FILE *fp; +PIXCMAP *cmap; + + PROCNAME("pixcmapReadMem"); + + if (!data) + return (PIXCMAP *)ERROR_PTR("data not defined", procName, NULL); + if ((fp = fopenReadFromMemory(data, size)) == NULL) + return (PIXCMAP *)ERROR_PTR("stream not opened", procName, NULL); + + cmap = pixcmapReadStream(fp); + fclose(fp); + if (!cmap) L_ERROR("cmap not read\n", procName); + return cmap; +} + + +/*! + * \brief pixcmapWrite() + * + * \param[in] filename + * \param[in] cmap + * \return 0 if OK, 1 on error + */ +l_ok +pixcmapWrite(const char *filename, + const PIXCMAP *cmap) +{ +l_int32 ret; +FILE *fp; + + PROCNAME("pixcmapWrite"); + + if (!filename) + return ERROR_INT("filename not defined", procName, 1); + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + if ((fp = fopenWriteStream(filename, "w")) == NULL) + return ERROR_INT("stream not opened", procName, 1); + ret = pixcmapWriteStream(fp, cmap); + fclose(fp); + if (ret) + return ERROR_INT("cmap not written to stream", procName, 1); + return 0; +} + + + +/*! + * \brief pixcmapWriteStream() + * + * \param[in] fp file stream + \param[in] cmap + * \return 0 if OK, 1 on error + */ +l_ok +pixcmapWriteStream(FILE *fp, + const PIXCMAP *cmap) +{ +l_int32 *rmap, *gmap, *bmap, *amap; +l_int32 i; + + PROCNAME("pixcmapWriteStream"); + + if (!fp) + return ERROR_INT("stream not defined", procName, 1); + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + if (pixcmapToArrays(cmap, &rmap, &gmap, &bmap, &amap)) + return ERROR_INT("colormap arrays not made", procName, 1); + + fprintf(fp, "\nPixcmap: depth = %d bpp; %d colors\n", cmap->depth, cmap->n); + fprintf(fp, "Color R-val G-val B-val Alpha\n"); + fprintf(fp, "----------------------------------------\n"); + for (i = 0; i < cmap->n; i++) + fprintf(fp, "%3d %3d %3d %3d %3d\n", + i, rmap[i], gmap[i], bmap[i], amap[i]); + fprintf(fp, "\n"); + + LEPT_FREE(rmap); + LEPT_FREE(gmap); + LEPT_FREE(bmap); + LEPT_FREE(amap); + return 0; +} + + +/*! + * \brief pixcmapWriteMem() + * + * \param[out] pdata data of serialized pixcmap; ascii + * \param[out] psize size of returned data + * \param[in] cmap + * \return 0 if OK, 1 on error + * + *
+ * Notes:
+ *      (1) Serializes a pixcmap in memory and puts the result in a buffer.
+ * 
+ */ +l_ok +pixcmapWriteMem(l_uint8 **pdata, + size_t *psize, + const PIXCMAP *cmap) +{ +l_int32 ret; +FILE *fp; + + PROCNAME("pixcmapWriteMem"); + + if (pdata) *pdata = NULL; + if (psize) *psize = 0; + if (!pdata) + return ERROR_INT("&data not defined", procName, 1); + if (!psize) + return ERROR_INT("&size not defined", procName, 1); + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + +#if HAVE_FMEMOPEN + if ((fp = open_memstream((char **)pdata, psize)) == NULL) + return ERROR_INT("stream not opened", procName, 1); + ret = pixcmapWriteStream(fp, cmap); +#else + L_INFO("work-around: writing to a temp file\n", procName); + #ifdef _WIN32 + if ((fp = fopenWriteWinTempfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", procName, 1); + #else + if ((fp = tmpfile()) == NULL) + return ERROR_INT("tmpfile stream not opened", procName, 1); + #endif /* _WIN32 */ + ret = pixcmapWriteStream(fp, cmap); + rewind(fp); + *pdata = l_binaryReadStream(fp, psize); +#endif /* HAVE_FMEMOPEN */ + fclose(fp); + return ret; +} + + +/*----------------------------------------------------------------------* + * Extract colormap arrays and serialization * + *----------------------------------------------------------------------*/ +/*! + * \brief pixcmapToArrays() + * + * \param[in] cmap colormap + * \param[out] prmap array of red values + * \param[out] pgmap array of green values + * \param[out] pbmap array of blue values + * \param[out] pamap [optional] array of alpha (transparency) values + * \return 0 if OK; 1 on error + */ +l_ok +pixcmapToArrays(const PIXCMAP *cmap, + l_int32 **prmap, + l_int32 **pgmap, + l_int32 **pbmap, + l_int32 **pamap) +{ +l_int32 *rmap, *gmap, *bmap, *amap; +l_int32 i, ncolors; +RGBA_QUAD *cta; + + PROCNAME("pixcmapToArrays"); + + if (!prmap || !pgmap || !pbmap) + return ERROR_INT("&rmap, &gmap, &bmap not all defined", procName, 1); + *prmap = *pgmap = *pbmap = NULL; + if (pamap) *pamap = NULL; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + ncolors = pixcmapGetCount(cmap); + rmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32)); + gmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32)); + bmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32)); + *prmap = rmap; + *pgmap = gmap; + *pbmap = bmap; + if (pamap) { + amap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32)); + *pamap = amap; + } + + cta = (RGBA_QUAD *)cmap->array; + for (i = 0; i < ncolors; i++) { + rmap[i] = cta[i].red; + gmap[i] = cta[i].green; + bmap[i] = cta[i].blue; + if (pamap) + amap[i] = cta[i].alpha; + } + + return 0; +} + + +/*! + * \brief pixcmapToRGBTable() + * + * \param[in] cmap colormap + * \param[out] ptab table of rgba values for the colormap + * \param[out] pncolors [optional] size of table + * \return 0 if OK; 1 on error + */ +l_ok +pixcmapToRGBTable(PIXCMAP *cmap, + l_uint32 **ptab, + l_int32 *pncolors) +{ +l_int32 i, ncolors, rval, gval, bval, aval; +l_uint32 *tab; + + PROCNAME("pixcmapToRGBTable"); + + if (!ptab) + return ERROR_INT("&tab not defined", procName, 1); + *ptab = NULL; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + ncolors = pixcmapGetCount(cmap); + if (pncolors) *pncolors = ncolors; + tab = (l_uint32 *)LEPT_CALLOC(ncolors, sizeof(l_uint32)); + *ptab = tab; + + for (i = 0; i < ncolors; i++) { + pixcmapGetRGBA(cmap, i, &rval, &gval, &bval, &aval); + composeRGBAPixel(rval, gval, bval, aval, &tab[i]); + } + return 0; +} + + +/*! + * \brief pixcmapSerializeToMemory() + * + * \param[in] cmap colormap + * \param[in] cpc components/color: 3 for rgb, 4 for rgba + * \param[out] pncolors number of colors in table + * \param[out] pdata binary string, cpc bytes per color + * \return 0 if OK; 1 on error + * + *
+ * Notes:
+ *      (1) When serializing to store in a pdf, use %cpc = 3.
+ * 
+ */ +l_ok +pixcmapSerializeToMemory(PIXCMAP *cmap, + l_int32 cpc, + l_int32 *pncolors, + l_uint8 **pdata) +{ +l_int32 i, ncolors, rval, gval, bval, aval; +l_uint8 *data; + + PROCNAME("pixcmapSerializeToMemory"); + + if (!pdata) + return ERROR_INT("&data not defined", procName, 1); + *pdata = NULL; + if (!pncolors) + return ERROR_INT("&ncolors not defined", procName, 1); + *pncolors = 0; + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (cpc != 3 && cpc != 4) + return ERROR_INT("cpc not 3 or 4", procName, 1); + + ncolors = pixcmapGetCount(cmap); + *pncolors = ncolors; + data = (l_uint8 *)LEPT_CALLOC((size_t)cpc * ncolors, sizeof(l_uint8)); + *pdata = data; + + for (i = 0; i < ncolors; i++) { + pixcmapGetRGBA(cmap, i, &rval, &gval, &bval, &aval); + data[cpc * i] = rval; + data[cpc * i + 1] = gval; + data[cpc * i + 2] = bval; + if (cpc == 4) + data[cpc * i + 3] = aval; + } + return 0; +} + + +/*! + * \brief pixcmapDeserializeFromMemory() + * + * \param[in] data binary string, 3 or 4 bytes per color + * \param[in] cpc components/color: 3 for rgb, 4 for rgba + * \param[in] ncolors > 0 + * \return cmap, or NULL on error + */ +PIXCMAP * +pixcmapDeserializeFromMemory(l_uint8 *data, + l_int32 cpc, + l_int32 ncolors) +{ +l_int32 i, d, rval, gval, bval, aval; +PIXCMAP *cmap; + + PROCNAME("pixcmapDeserializeFromMemory"); + + if (!data) + return (PIXCMAP *)ERROR_PTR("data not defined", procName, NULL); + if (cpc != 3 && cpc != 4) + return (PIXCMAP *)ERROR_PTR("cpc not 3 or 4", procName, NULL); + if (ncolors <= 0) + return (PIXCMAP *)ERROR_PTR("no entries", procName, NULL); + if (ncolors > 256) + return (PIXCMAP *)ERROR_PTR("ncolors > 256", procName, NULL); + + if (ncolors > 16) + d = 8; + else if (ncolors > 4) + d = 4; + else if (ncolors > 2) + d = 2; + else + d = 1; + cmap = pixcmapCreate(d); + for (i = 0; i < ncolors; i++) { + rval = data[cpc * i]; + gval = data[cpc * i + 1]; + bval = data[cpc * i + 2]; + if (cpc == 4) + aval = data[cpc * i + 3]; + else + aval = 255; /* opaque */ + pixcmapAddRGBA(cmap, rval, gval, bval, aval); + } + + return cmap; +} + + +/*! + * \brief pixcmapConvertToHex() + * + * \param[in] data binary serialized data + * \param[in] ncolors in colormap + * \return hexdata bracketed, space-separated ascii hex string, + * or NULL on error. + * + *
+ * Notes:
+ *      (1) The number of bytes in %data is 3 * ncolors.
+ *      (2) Output is in form:
+ *             < r0g0b0 r1g1b1 ... rngnbn >
+ *          where r0, g0, b0 ... are each 2 bytes of hex ascii
+ *      (3) This is used in pdf files to express the colormap as an
+ *          array in ascii (human-readable) format.
+ * 
+ */ +char * +pixcmapConvertToHex(l_uint8 *data, + l_int32 ncolors) +{ +l_int32 i, j, hexbytes; +char *hexdata = NULL; +char buf[4]; + + PROCNAME("pixcmapConvertToHex"); + + if (!data) + return (char *)ERROR_PTR("data not defined", procName, NULL); + if (ncolors < 1) + return (char *)ERROR_PTR("no colors", procName, NULL); + + hexbytes = 2 + (2 * 3 + 1) * ncolors + 2; + hexdata = (char *)LEPT_CALLOC(hexbytes, sizeof(char)); + hexdata[0] = '<'; + hexdata[1] = ' '; + + for (i = 0; i < ncolors; i++) { + j = 2 + (2 * 3 + 1) * i; + snprintf(buf, sizeof(buf), "%02x", data[3 * i]); + hexdata[j] = buf[0]; + hexdata[j + 1] = buf[1]; + snprintf(buf, sizeof(buf), "%02x", data[3 * i + 1]); + hexdata[j + 2] = buf[0]; + hexdata[j + 3] = buf[1]; + snprintf(buf, sizeof(buf), "%02x", data[3 * i + 2]); + hexdata[j + 4] = buf[0]; + hexdata[j + 5] = buf[1]; + hexdata[j + 6] = ' '; + } + hexdata[j + 7] = '>'; + hexdata[j + 8] = '\0'; + return hexdata; +} + + +/*-------------------------------------------------------------* + * Colormap transforms * + *-------------------------------------------------------------*/ +/*! + * \brief pixcmapGammaTRC() + * + * \param[in] cmap colormap + * \param[in] gamma gamma correction; must be > 0.0 + * \param[in] minval input value that gives 0 for output; can be < 0 + * \param[in] maxval input value that gives 255 for output; can be > 255 + * \return 0 if OK; 1 on error + * + *
+ * Notes:
+ *      (1) This is an in-place transform
+ *      (2) See pixGammaTRC() and numaGammaTRC() in enhance.c
+ *          for description and use of transform
+ * 
+ */ +l_ok +pixcmapGammaTRC(PIXCMAP *cmap, + l_float32 gamma, + l_int32 minval, + l_int32 maxval) +{ +l_int32 rval, gval, bval, trval, tgval, tbval, i, ncolors; +NUMA *nag; + + PROCNAME("pixcmapGammaTRC"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (gamma <= 0.0) { + L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName); + gamma = 1.0; + } + if (minval >= maxval) + return ERROR_INT("minval not < maxval", procName, 1); + + if (gamma == 1.0 && minval == 0 && maxval == 255) /* no-op */ + return 0; + + if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL) + return ERROR_INT("nag not made", procName, 1); + + ncolors = pixcmapGetCount(cmap); + for (i = 0; i < ncolors; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + numaGetIValue(nag, rval, &trval); + numaGetIValue(nag, gval, &tgval); + numaGetIValue(nag, bval, &tbval); + pixcmapResetColor(cmap, i, trval, tgval, tbval); + } + + numaDestroy(&nag); + return 0; +} + + +/*! + * \brief pixcmapContrastTRC() + * + * \param[in] cmap colormap + * \param[in] factor generally between 0.0 [no enhancement] + * and 1.0, but can be larger than 1.0 + * \return 0 if OK; 1 on error + * + *
+ * Notes:
+ *      (1) This is an in-place transform
+ *      (2) See pixContrastTRC() and numaContrastTRC() in enhance.c
+ *          for description and use of transform
+ * 
+ */ +l_ok +pixcmapContrastTRC(PIXCMAP *cmap, + l_float32 factor) +{ +l_int32 i, ncolors, rval, gval, bval, trval, tgval, tbval; +NUMA *nac; + + PROCNAME("pixcmapContrastTRC"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (factor < 0.0) { + L_WARNING("factor must be >= 0.0; setting to 0.0\n", procName); + factor = 0.0; + } + + if ((nac = numaContrastTRC(factor)) == NULL) + return ERROR_INT("nac not made", procName, 1); + + ncolors = pixcmapGetCount(cmap); + for (i = 0; i < ncolors; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + numaGetIValue(nac, rval, &trval); + numaGetIValue(nac, gval, &tgval); + numaGetIValue(nac, bval, &tbval); + pixcmapResetColor(cmap, i, trval, tgval, tbval); + } + + numaDestroy(&nac); + return 0; +} + + +/*! + * \brief pixcmapShiftIntensity() + * + * \param[in] cmap colormap + * \param[in] fraction between -1.0 and +1.0 + * \return 0 if OK; 1 on error + * + *
+ * Notes:
+ *      (1) This is an in-place transform
+ *      (2) It does a proportional shift of the intensity for each color.
+ *      (3) If fraction < 0.0, it moves all colors towards (0,0,0).
+ *          This darkens the image.
+ *          If fraction > 0.0, it moves all colors towards (255,255,255)
+ *          This fades the image.
+ *      (4) The equivalent transform can be accomplished with pixcmapGammaTRC(),
+ *          but it is considerably more difficult (see numaGammaTRC()).
+ * 
+ */ +l_ok +pixcmapShiftIntensity(PIXCMAP *cmap, + l_float32 fraction) +{ +l_int32 i, ncolors, rval, gval, bval; + + PROCNAME("pixcmapShiftIntensity"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + if (fraction < -1.0 || fraction > 1.0) + return ERROR_INT("fraction not in [-1.0, 1.0]", procName, 1); + + ncolors = pixcmapGetCount(cmap); + for (i = 0; i < ncolors; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + if (fraction < 0.0) + pixcmapResetColor(cmap, i, + (l_int32)((1.0 + fraction) * rval), + (l_int32)((1.0 + fraction) * gval), + (l_int32)((1.0 + fraction) * bval)); + else + pixcmapResetColor(cmap, i, + rval + (l_int32)(fraction * (255 - rval)), + gval + (l_int32)(fraction * (255 - gval)), + bval + (l_int32)(fraction * (255 - bval))); + } + + return 0; +} + + +/*! + * \brief pixcmapShiftByComponent() + * + * \param[in] cmap colormap + * \param[in] srcval source color: 0xrrggbb00 + * \param[in] dstval target color: 0xrrggbb00 + * \return 0 if OK; 1 on error + * + *
+ * Notes:
+ *      (1) This is an in-place transform
+ *      (2) It implements pixelShiftByComponent() for each color.
+ *          The mapping is specified by srcval and dstval.
+ *      (3) If a component decreases, the component in the colormap
+ *          decreases by the same ratio.  Likewise for increasing, except
+ *          all ratios are taken with respect to the distance from 255.
+ * 
+ */ +l_ok +pixcmapShiftByComponent(PIXCMAP *cmap, + l_uint32 srcval, + l_uint32 dstval) +{ +l_int32 i, ncolors, rval, gval, bval; +l_uint32 newval; + + PROCNAME("pixcmapShiftByComponent"); + + if (!cmap) + return ERROR_INT("cmap not defined", procName, 1); + + ncolors = pixcmapGetCount(cmap); + for (i = 0; i < ncolors; i++) { + pixcmapGetColor(cmap, i, &rval, &gval, &bval); + pixelShiftByComponent(rval, gval, bval, srcval, dstval, &newval); + extractRGBValues(newval, &rval, &gval, &bval); + pixcmapResetColor(cmap, i, rval, gval, bval); + } + + return 0; +} -- cgit v1.2.3-65-gdbad