/*====================================================================* - 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 enhance.c *
* * Gamma TRC (tone reproduction curve) mapping * PIX *pixGammaTRC() * PIX *pixGammaTRCMasked() * PIX *pixGammaTRCWithAlpha() * NUMA *numaGammaTRC() * * Contrast enhancement * PIX *pixContrastTRC() * PIX *pixContrastTRCMasked() * NUMA *numaContrastTRC() * * Histogram equalization * PIX *pixEqualizeTRC() * NUMA *numaEqualizeTRC() * * Generic TRC mapper * PIX *pixTRCMap() * PIX *pixTRCMapGeneral() * * Unsharp-masking * PIX *pixUnsharpMasking() * PIX *pixUnsharpMaskingGray() * PIX *pixUnsharpMaskingFast() * PIX *pixUnsharpMaskingGrayFast() * PIX *pixUnsharpMaskingGray1D() * PIX *pixUnsharpMaskingGray2D() * * Hue and saturation modification * PIX *pixModifyHue() * PIX *pixModifySaturation() * l_int32 pixMeasureSaturation() * PIX *pixModifyBrightness() * * Color shifting * PIX *pixMosaicColorShiftRGB() * PIX *pixColorShiftRGB() * * Darken gray (unsaturated) pixels * PIX *pixDarkenGray() * * General multiplicative constant color transform * PIX *pixMultConstantColor() * PIX *pixMultMatrixColor() * * Edge by bandpass * PIX *pixHalfEdgeByBandpass() * * Gamma correction, contrast enhancement and histogram equalization * apply a simple mapping function to each pixel (or, for color * images, to each sample (i.e., r,g,b) of the pixel). * * ~ Gamma correction either lightens the image or darkens * it, depending on whether the gamma factor is greater * or less than 1.0, respectively. * * ~ Contrast enhancement darkens the pixels that are already * darker than the middle of the dynamic range (128) * and lightens pixels that are lighter than 128. * * ~ Histogram equalization remaps to have the same number * of image pixels at each of 256 intensity values. This is * a quick and dirty method of adjusting contrast and brightness * to bring out details in both light and dark regions. * * Unsharp masking is a more complicated enhancement. * A "high frequency" image, generated by subtracting * the smoothed ("low frequency") part of the image from * itself, has all the energy at the edges. This "edge image" * has 0 average value. A fraction of the edge image is * then added to the original, enhancing the differences * between pixel values at edges. Because we represent * images as l_uint8 arrays, we preserve dynamic range and * handle negative values by doing all the arithmetic on * shifted l_uint16 arrays; the l_uint8 values are recovered * at the end. * * Hue and saturation modification work in HSV space. Because * this is too large for efficient table lookup, each pixel value * is transformed to HSV, modified, and transformed back. * It's not the fastest way to do this, but the method is * easily understood. * * Unsharp masking is never in-place, and returns a clone if no * operation is to be performed. **/ #include
* Notes: * (1) pixd must either be null or equal to pixs. * For in-place operation, set pixd == pixs: * pixGammaTRC(pixs, pixs, ...); * To get a new image, set pixd == null: * pixd = pixGammaTRC(NULL, pixs, ...); * (2) If pixs is colormapped, the colormap is transformed, * either in-place or in a copy of pixs. * (3) We use a gamma mapping between minval and maxval. * (4) If gamma < 1.0, the image will appear darker; * if gamma > 1.0, the image will appear lighter; * (5) If gamma = 1.0 and minval = 0 and maxval = 255, no * enhancement is performed; return a copy unless in-place, * in which case this is a no-op. * (6) For color images that are not colormapped, the mapping * is applied to each component. * (7) minval and maxval are not restricted to the interval [0, 255]. * If minval < 0, an input value of 0 is mapped to a * nonzero output. This will turn black to gray. * If maxval > 255, an input value of 255 is mapped to * an output value less than 255. This will turn * white (e.g., in the background) to gray. * (8) Increasing minval darkens the image. * (9) Decreasing maxval bleaches the image. * (10) Simultaneously increasing minval and decreasing maxval * will darken the image and make the colors more intense; * e.g., minval = 50, maxval = 200. * (11) See numaGammaTRC() for further examples of use. * (12) Use pixTRCMapGeneral() if applying different mappings * to each channel in an RGB image. **/ PIX * pixGammaTRC(PIX *pixd, PIX *pixs, l_float32 gamma, l_int32 minval, l_int32 maxval) { l_int32 d; NUMA *nag; PIXCMAP *cmap; PROCNAME("pixGammaTRC"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd); if (gamma <= 0.0) { L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName); gamma = 1.0; } if (minval >= maxval) return (PIX *)ERROR_PTR("minval not < maxval", procName, pixd); cmap = pixGetColormap(pixs); d = pixGetDepth(pixs); if (!cmap && d != 8 && d != 32) return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd); if (gamma == 1.0 && minval == 0 && maxval == 255) /* no-op */ return pixCopy(pixd, pixs); if (!pixd) /* start with a copy if not in-place */ pixd = pixCopy(NULL, pixs); if (cmap) { pixcmapGammaTRC(pixGetColormap(pixd), gamma, minval, maxval); return pixd; } /* pixd is 8 or 32 bpp */ if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL) return (PIX *)ERROR_PTR("nag not made", procName, pixd); pixTRCMap(pixd, NULL, nag); numaDestroy(&nag); return pixd; } /*! * \brief pixGammaTRCMasked() * * \param[in] pixd [optional] null or equal to pixs * \param[in] pixs 8 or 32 bpp; not colormapped * \param[in] pixm [optional] null or 1 bpp * \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 pixd always * *
* Notes: * (1) Same as pixGammaTRC() except mapping is optionally over * a subset of pixels described by pixm. * (2) Masking does not work for colormapped images. * (3) See pixGammaTRC() for details on how to use the parameters. **/ PIX * pixGammaTRCMasked(PIX *pixd, PIX *pixs, PIX *pixm, l_float32 gamma, l_int32 minval, l_int32 maxval) { l_int32 d; NUMA *nag; PROCNAME("pixGammaTRCMasked"); if (!pixm) return pixGammaTRC(pixd, pixs, gamma, minval, maxval); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("invalid: pixs has a colormap", procName, pixd); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd); d = pixGetDepth(pixs); if (d != 8 && d != 32) return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd); if (minval >= maxval) return (PIX *)ERROR_PTR("minval not < maxval", procName, pixd); if (gamma <= 0.0) { L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName); gamma = 1.0; } if (gamma == 1.0 && minval == 0 && maxval == 255) return pixCopy(pixd, pixs); if (!pixd) /* start with a copy if not in-place */ pixd = pixCopy(NULL, pixs); if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL) return (PIX *)ERROR_PTR("nag not made", procName, pixd); pixTRCMap(pixd, pixm, nag); numaDestroy(&nag); return pixd; } /*! * \brief pixGammaTRCWithAlpha() * * \param[in] pixd [optional] null or equal to pixs * \param[in] pixs 32 bpp * \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 pixd always * *
* Notes: * (1) See usage notes in pixGammaTRC(). * (2) This version saves the alpha channel. It is only valid * for 32 bpp (no colormap), and is a bit slower. **/ PIX * pixGammaTRCWithAlpha(PIX *pixd, PIX *pixs, l_float32 gamma, l_int32 minval, l_int32 maxval) { NUMA *nag; PIX *pixalpha; PROCNAME("pixGammaTRCWithAlpha"); if (!pixs || pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, pixd); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd); if (gamma <= 0.0) { L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName); gamma = 1.0; } if (minval >= maxval) return (PIX *)ERROR_PTR("minval not < maxval", procName, pixd); if (gamma == 1.0 && minval == 0 && maxval == 255) return pixCopy(pixd, pixs); if (!pixd) /* start with a copy if not in-place */ pixd = pixCopy(NULL, pixs); pixalpha = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); /* save */ if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL) return (PIX *)ERROR_PTR("nag not made", procName, pixd); pixTRCMap(pixd, NULL, nag); pixSetRGBComponent(pixd, pixalpha, L_ALPHA_CHANNEL); /* restore */ pixSetSpp(pixd, 4); numaDestroy(&nag); pixDestroy(&pixalpha); return pixd; } /*! * \brief numaGammaTRC() * * \param[in] gamma gamma factor; must be > 0.0 * \param[in] minval input value that gives 0 for output * \param[in] maxval input value that gives 255 for output * \return na, or NULL on error * *
* Notes: * (1) The map is returned as a numa; values are clipped to [0, 255]. * (2) To force all intensities into a range within fraction delta * of white, use: minval = -256 * (1 - delta) / delta * maxval = 255 * (3) To force all intensities into a range within fraction delta * of black, use: minval = 0 * maxval = 256 * (1 - delta) / delta **/ NUMA * numaGammaTRC(l_float32 gamma, l_int32 minval, l_int32 maxval) { l_int32 i, val; l_float32 x, invgamma; NUMA *na; PROCNAME("numaGammaTRC"); if (minval >= maxval) return (NUMA *)ERROR_PTR("minval not < maxval", procName, NULL); if (gamma <= 0.0) { L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName); gamma = 1.0; } invgamma = 1. / gamma; na = numaCreate(256); for (i = 0; i < minval; i++) numaAddNumber(na, 0); for (i = minval; i <= maxval; i++) { if (i < 0) continue; if (i > 255) continue; x = (l_float32)(i - minval) / (l_float32)(maxval - minval); val = (l_int32)(255. * powf(x, invgamma) + 0.5); val = L_MAX(val, 0); val = L_MIN(val, 255); numaAddNumber(na, val); } for (i = maxval + 1; i < 256; i++) numaAddNumber(na, 255); return na; } /*-------------------------------------------------------------* * Contrast enhancement * *-------------------------------------------------------------*/ /*! * \brief pixContrastTRC() * * \param[in] pixd [optional] null or equal to pixs * \param[in] pixs 8 or 32 bpp; or 2, 4 or 8 bpp with colormap * \param[in] factor 0.0 is no enhancement * \return pixd always * *
* Notes: * (1) pixd must either be null or equal to pixs. * For in-place operation, set pixd == pixs: * pixContrastTRC(pixs, pixs, ...); * To get a new image, set pixd == null: * pixd = pixContrastTRC(NULL, pixs, ...); * (2) If pixs is colormapped, the colormap is transformed, * either in-place or in a copy of pixs. * (3) Contrast is enhanced by mapping each color component * using an atan function with maximum slope at 127. * Pixels below 127 are lowered in intensity and pixels * above 127 are increased. * (4) The useful range for the contrast factor is scaled to * be in (0.0 to 1.0), but larger values can also be used. * (5) If factor == 0.0, no enhancement is performed; return a copy * unless in-place, in which case this is a no-op. * (6) For color images that are not colormapped, the mapping * is applied to each component. **/ PIX * pixContrastTRC(PIX *pixd, PIX *pixs, l_float32 factor) { l_int32 d; NUMA *nac; PIXCMAP *cmap; PROCNAME("pixContrastTRC"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd); if (factor < 0.0) { L_WARNING("factor must be >= 0.0; using 0.0\n", procName); factor = 0.0; } if (factor == 0.0) return pixCopy(pixd, pixs); cmap = pixGetColormap(pixs); d = pixGetDepth(pixs); if (!cmap && d != 8 && d != 32) return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd); if (!pixd) /* start with a copy if not in-place */ pixd = pixCopy(NULL, pixs); if (cmap) { pixcmapContrastTRC(pixGetColormap(pixd), factor); return pixd; } /* pixd is 8 or 32 bpp */ if ((nac = numaContrastTRC(factor)) == NULL) return (PIX *)ERROR_PTR("nac not made", procName, pixd); pixTRCMap(pixd, NULL, nac); numaDestroy(&nac); return pixd; } /*! * \brief pixContrastTRCMasked() * * \param[in] pixd [optional] null or equal to pixs * \param[in] pixs 8 or 32 bpp; or 2, 4 or 8 bpp with colormap * \param[in] pixm [optional] null or 1 bpp * \param[in] factor 0.0 is no enhancement * \return pixd always * *
* Notes: * (1) Same as pixContrastTRC() except mapping is optionally over * a subset of pixels described by pixm. * (2) Masking does not work for colormapped images. * (3) See pixContrastTRC() for details on how to use the parameters. **/ PIX * pixContrastTRCMasked(PIX *pixd, PIX *pixs, PIX *pixm, l_float32 factor) { l_int32 d; NUMA *nac; PROCNAME("pixContrastTRCMasked"); if (!pixm) return pixContrastTRC(pixd, pixs, factor); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("invalid: pixs has a colormap", procName, pixd); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd); d = pixGetDepth(pixs); if (d != 8 && d != 32) return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd); if (factor < 0.0) { L_WARNING("factor must be >= 0.0; using 0.0\n", procName); factor = 0.0; } if (factor == 0.0) return pixCopy(pixd, pixs); if (!pixd) /* start with a copy if not in-place */ pixd = pixCopy(NULL, pixs); if ((nac = numaContrastTRC(factor)) == NULL) return (PIX *)ERROR_PTR("nac not made", procName, pixd); pixTRCMap(pixd, pixm, nac); numaDestroy(&nac); return pixd; } /*! * \brief numaContrastTRC() * * \param[in] factor generally between 0.0 [no enhancement] * and 1.0, but can be larger than 1.0 * \return na, or NULL on error * *
* Notes: * (1) The mapping is monotonic increasing, where 0 is mapped * to 0 and 255 is mapped to 255. * (2) As 'factor' is increased from 0.0 (where the mapping is linear), * the map gets closer to its limit as a step function that * jumps from 0 to 255 at the center (input value = 127). **/ NUMA * numaContrastTRC(l_float32 factor) { l_int32 i, val; l_float64 x, ymax, ymin, dely, scale; NUMA *na; PROCNAME("numaContrastTRC"); if (factor < 0.0) { L_WARNING("factor must be >= 0.0; using 0.0; no enhancement\n", procName); factor = 0.0; } if (factor == 0.0) return numaMakeSequence(0, 1, 256); /* linear map */ scale = EnhanceScaleFactor; ymax = atan((l_float64)(1.0 * factor * scale)); ymin = atan((l_float64)(-127. * factor * scale / 128.)); dely = ymax - ymin; na = numaCreate(256); for (i = 0; i < 256; i++) { x = (l_float64)i; val = (l_int32)((255. / dely) * (-ymin + atan((l_float64)(factor * scale * (x - 127.) / 128.))) + 0.5); numaAddNumber(na, val); } return na; } /*-------------------------------------------------------------* * Histogram equalization * *-------------------------------------------------------------*/ /*! * \brief pixEqualizeTRC() * * \param[in] pixd [optional] null or equal to pixs * \param[in] pixs 8 bpp gray, 32 bpp rgb, or colormapped * \param[in] fract fraction of equalization movement of pixel values * \param[in] factor subsampling factor; integer >= 1 * \return pixd, or NULL on error * *
* Notes: * (1) pixd must either be null or equal to pixs. * For in-place operation, set pixd == pixs: * pixEqualizeTRC(pixs, pixs, ...); * To get a new image, set pixd == null: * pixd = pixEqualizeTRC(NULL, pixs, ...); * (2) In histogram equalization, a tone reproduction curve * mapping is used to make the number of pixels at each * intensity equal. * (3) If fract == 0.0, no equalization is performed; return a copy * unless in-place, in which case this is a no-op. * If fract == 1.0, equalization is complete. * (4) Set the subsampling factor > 1 to reduce the amount of computation. * (5) If pixs is colormapped, the colormap is removed and * converted to rgb or grayscale. * (6) If pixs has color, equalization is done in each channel * separately. * (7) Note that even if there is a colormap, we can get an * in-place operation because the intermediate image pixt * is copied back to pixs (which for in-place is the same * as pixd). **/ PIX * pixEqualizeTRC(PIX *pixd, PIX *pixs, l_float32 fract, l_int32 factor) { l_int32 d; NUMA *na; PIX *pixt, *pix8; PIXCMAP *cmap; PROCNAME("pixEqualizeTRC"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd); cmap = pixGetColormap(pixs); d = pixGetDepth(pixs); if (d != 8 && d != 32 && !cmap) return (PIX *)ERROR_PTR("pixs not 8/32 bpp or cmapped", procName, NULL); if (fract < 0.0 || fract > 1.0) return (PIX *)ERROR_PTR("fract not in [0.0 ... 1.0]", procName, NULL); if (factor < 1) return (PIX *)ERROR_PTR("sampling factor < 1", procName, NULL); if (fract == 0.0) return pixCopy(pixd, pixs); /* If there is a colormap, remove it. */ if (cmap) pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); else pixt = pixClone(pixs); /* Make a copy if necessary */ pixd = pixCopy(pixd, pixt); pixDestroy(&pixt); d = pixGetDepth(pixd); if (d == 8) { na = numaEqualizeTRC(pixd, fract, factor); pixTRCMap(pixd, NULL, na); numaDestroy(&na); } else { /* 32 bpp */ pix8 = pixGetRGBComponent(pixd, COLOR_RED); na = numaEqualizeTRC(pix8, fract, factor); pixTRCMap(pix8, NULL, na); pixSetRGBComponent(pixd, pix8, COLOR_RED); numaDestroy(&na); pixDestroy(&pix8); pix8 = pixGetRGBComponent(pixd, COLOR_GREEN); na = numaEqualizeTRC(pix8, fract, factor); pixTRCMap(pix8, NULL, na); pixSetRGBComponent(pixd, pix8, COLOR_GREEN); numaDestroy(&na); pixDestroy(&pix8); pix8 = pixGetRGBComponent(pixd, COLOR_BLUE); na = numaEqualizeTRC(pix8, fract, factor); pixTRCMap(pix8, NULL, na); pixSetRGBComponent(pixd, pix8, COLOR_BLUE); numaDestroy(&na); pixDestroy(&pix8); } return pixd; } /*! * \brief numaEqualizeTRC() * * \param[in] pix 8 bpp, no colormap * \param[in] fract fraction of equalization movement of pixel values * \param[in] factor subsampling factor; integer >= 1 * \return nad, or NULL on error * *
* Notes: * (1) If fract == 0.0, no equalization will be performed. * If fract == 1.0, equalization is complete. * (2) Set the subsampling factor > 1 to reduce the amount of computation. * (3) The map is returned as a numa with 256 values, specifying * the equalized value (array value) for every input value * (the array index). **/ NUMA * numaEqualizeTRC(PIX *pix, l_float32 fract, l_int32 factor) { l_int32 iin, iout, itarg; l_float32 val, sum; NUMA *nah, *nasum, *nad; PROCNAME("numaEqualizeTRC"); if (!pix) return (NUMA *)ERROR_PTR("pix not defined", procName, NULL); if (pixGetDepth(pix) != 8) return (NUMA *)ERROR_PTR("pix not 8 bpp", procName, NULL); if (fract < 0.0 || fract > 1.0) return (NUMA *)ERROR_PTR("fract not in [0.0 ... 1.0]", procName, NULL); if (factor < 1) return (NUMA *)ERROR_PTR("sampling factor < 1", procName, NULL); if (fract == 0.0) L_WARNING("fract = 0.0; no equalization requested\n", procName); if ((nah = pixGetGrayHistogram(pix, factor)) == NULL) return (NUMA *)ERROR_PTR("histogram not made", procName, NULL); numaGetSum(nah, &sum); nasum = numaGetPartialSums(nah); nad = numaCreate(256); for (iin = 0; iin < 256; iin++) { numaGetFValue(nasum, iin, &val); itarg = (l_int32)(255. * val / sum + 0.5); iout = iin + (l_int32)(fract * (itarg - iin)); iout = L_MIN(iout, 255); /* to be safe */ numaAddNumber(nad, iout); } numaDestroy(&nah); numaDestroy(&nasum); return nad; } /*-------------------------------------------------------------* * Generic TRC mapping * *-------------------------------------------------------------*/ /*! * \brief pixTRCMap() * * \param[in] pixs 8 grayscale or 32 bpp rgb; not colormapped * \param[in] pixm [optional] 1 bpp mask * \param[in] na mapping array * \return pixd, or NULL on error * *
* Notes: * (1) This operation is in-place on pixs. * (2) For 32 bpp, this applies the same map to each of the r,g,b * components. * (3) The mapping array is of size 256, and it maps the input * index into values in the range [0, 255]. * (4) If defined, the optional 1 bpp mask pixm has its origin * aligned with pixs, and the map function is applied only * to pixels in pixs under the fg of pixm. * (5) For 32 bpp, this does not save the alpha channel. **/ l_int32 pixTRCMap(PIX *pixs, PIX *pixm, NUMA *na) { l_int32 w, h, d, wm, hm, wpl, wplm, i, j, sval8, dval8; l_uint32 sval32, dval32; l_uint32 *data, *datam, *line, *linem, *tab; PROCNAME("pixTRCMap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetColormap(pixs)) return ERROR_INT("pixs is colormapped", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); if (numaGetCount(na) != 256) return ERROR_INT("na not of size 256", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d != 8 && d != 32) return ERROR_INT("pixs not 8 or 32 bpp", procName, 1); if (pixm) { if (pixGetDepth(pixm) != 1) return ERROR_INT("pixm not 1 bpp", procName, 1); } tab = (l_uint32 *)numaGetIArray(na); /* get the array for efficiency */ wpl = pixGetWpl(pixs); data = pixGetData(pixs); if (!pixm) { if (d == 8) { for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { sval8 = GET_DATA_BYTE(line, j); dval8 = tab[sval8]; SET_DATA_BYTE(line, j, dval8); } } } else { /* d == 32 */ for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { sval32 = *(line + j); dval32 = tab[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT | tab[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT | tab[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT; *(line + j) = dval32; } } } } else { datam = pixGetData(pixm); wplm = pixGetWpl(pixm); pixGetDimensions(pixm, &wm, &hm, NULL); if (d == 8) { for (i = 0; i < h; i++) { if (i >= hm) break; line = data + i * wpl; linem = datam + i * wplm; for (j = 0; j < w; j++) { if (j >= wm) break; if (GET_DATA_BIT(linem, j) == 0) continue; sval8 = GET_DATA_BYTE(line, j); dval8 = tab[sval8]; SET_DATA_BYTE(line, j, dval8); } } } else { /* d == 32 */ for (i = 0; i < h; i++) { if (i >= hm) break; line = data + i * wpl; linem = datam + i * wplm; for (j = 0; j < w; j++) { if (j >= wm) break; if (GET_DATA_BIT(linem, j) == 0) continue; sval32 = *(line + j); dval32 = tab[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT | tab[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT | tab[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT; *(line + j) = dval32; } } } } LEPT_FREE(tab); return 0; } /*! * \brief pixTRCMapGeneral() * * \param[in] pixs 32 bpp rgb; not colormapped * \param[in] pixm [optional] 1 bpp mask * \param[in] nar, nag, nab mapping arrays * \return pixd, or NULL on error * *
* Notes: * (1) This operation is in-place on %pixs. * (2) Each of the r,g,b mapping arrays is of size 256. They map the * input value for that color component into values in the * range [0, 255]. * (3) In the special case where the r, g and b mapping arrays are * all the same, call pixTRCMap() instead. * (4) If defined, the optional 1 bpp mask %pixm has its origin * aligned with %pixs, and the map function is applied only * to pixels in %pixs under the fg of pixm. * (5) The alpha channel is not saved. **/ l_int32 pixTRCMapGeneral(PIX *pixs, PIX *pixm, NUMA *nar, NUMA *nag, NUMA *nab) { l_int32 w, h, wm, hm, wpl, wplm, i, j; l_uint32 sval32, dval32; l_uint32 *data, *datam, *line, *linem, *tabr, *tabg, *tabb; PROCNAME("pixTRCMapGeneral"); if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs not defined or not 32 bpp", procName, 1); if (pixm && pixGetDepth(pixm) != 1) return ERROR_INT("pixm defined and not 1 bpp", procName, 1); if (!nar || !nag || !nab) return ERROR_INT("na{r,g,b} not all defined", procName, 1); if (numaGetCount(nar) != 256 || numaGetCount(nag) != 256 || numaGetCount(nab) != 256) return ERROR_INT("na{r,g,b} not all of size 256", procName, 1); /* Get the arrays for efficiency */ tabr = (l_uint32 *)numaGetIArray(nar); tabg = (l_uint32 *)numaGetIArray(nag); tabb = (l_uint32 *)numaGetIArray(nab); pixGetDimensions(pixs, &w, &h, NULL); wpl = pixGetWpl(pixs); data = pixGetData(pixs); if (!pixm) { for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { sval32 = *(line + j); dval32 = tabr[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT | tabg[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT | tabb[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT; *(line + j) = dval32; } } } else { datam = pixGetData(pixm); wplm = pixGetWpl(pixm); pixGetDimensions(pixm, &wm, &hm, NULL); for (i = 0; i < h; i++) { if (i >= hm) break; line = data + i * wpl; linem = datam + i * wplm; for (j = 0; j < w; j++) { if (j >= wm) break; if (GET_DATA_BIT(linem, j) == 0) continue; sval32 = *(line + j); dval32 = tabr[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT | tabg[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT | tabb[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT; *(line + j) = dval32; } } } LEPT_FREE(tabr); LEPT_FREE(tabg); LEPT_FREE(tabb); return 0; } /*-----------------------------------------------------------------------* * Unsharp masking * *-----------------------------------------------------------------------*/ /*! * \brief pixUnsharpMasking() * * \param[in] pixs all depths except 1 bpp; with or without colormaps * \param[in] halfwidth "half-width" of smoothing filter * \param[in] fract fraction of edge added back into image * \return pixd, or NULL on error * *
* Notes: * (1) We use symmetric smoothing filters of odd dimension, * typically use sizes of 3, 5, 7, etc. The %halfwidth parameter * for these is (size - 1)/2; i.e., 1, 2, 3, etc. * (2) The fract parameter is typically taken in the * range: 0.2 < fract < 0.7 * (3) Returns a clone if no sharpening is requested. **/ PIX * pixUnsharpMasking(PIX *pixs, l_int32 halfwidth, l_float32 fract) { l_int32 d; PIX *pixt, *pixd, *pixr, *pixrs, *pixg, *pixgs, *pixb, *pixbs; PROCNAME("pixUnsharpMasking"); if (!pixs || (pixGetDepth(pixs) == 1)) return (PIX *)ERROR_PTR("pixs not defined or 1 bpp", procName, NULL); if (fract <= 0.0 || halfwidth <= 0) { L_WARNING("no sharpening requested; clone returned\n", procName); return pixClone(pixs); } if (halfwidth == 1 || halfwidth == 2) return pixUnsharpMaskingFast(pixs, halfwidth, fract, L_BOTH_DIRECTIONS); /* Remove colormap; clone if possible; result is either 8 or 32 bpp */ if ((pixt = pixConvertTo8Or32(pixs, L_CLONE, 0)) == NULL) return (PIX *)ERROR_PTR("pixt not made", procName, NULL); /* Sharpen */ d = pixGetDepth(pixt); if (d == 8) { pixd = pixUnsharpMaskingGray(pixt, halfwidth, fract); } else { /* d == 32 */ pixr = pixGetRGBComponent(pixs, COLOR_RED); pixrs = pixUnsharpMaskingGray(pixr, halfwidth, fract); pixDestroy(&pixr); pixg = pixGetRGBComponent(pixs, COLOR_GREEN); pixgs = pixUnsharpMaskingGray(pixg, halfwidth, fract); pixDestroy(&pixg); pixb = pixGetRGBComponent(pixs, COLOR_BLUE); pixbs = pixUnsharpMaskingGray(pixb, halfwidth, fract); pixDestroy(&pixb); pixd = pixCreateRGBImage(pixrs, pixgs, pixbs); pixDestroy(&pixrs); pixDestroy(&pixgs); pixDestroy(&pixbs); if (pixGetSpp(pixs) == 4) pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0); } pixDestroy(&pixt); return pixd; } /*! * \brief pixUnsharpMaskingGray() * * \param[in] pixs 8 bpp; no colormap * \param[in] halfwidth "half-width" of smoothing filter * \param[in] fract fraction of edge added back into image * \return pixd, or NULL on error * *
* Notes: * (1) We use symmetric smoothing filters of odd dimension, * typically use sizes of 3, 5, 7, etc. The %halfwidth parameter * for these is (size - 1)/2; i.e., 1, 2, 3, etc. * (2) The fract parameter is typically taken in the range: * 0.2 < fract < 0.7 * (3) Returns a clone if no sharpening is requested. **/ PIX * pixUnsharpMaskingGray(PIX *pixs, l_int32 halfwidth, l_float32 fract) { l_int32 w, h, d; PIX *pixc, *pixd; PIXACC *pixacc; PROCNAME("pixUnsharpMaskingGray"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8 || pixGetColormap(pixs) != NULL) return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL); if (fract <= 0.0 || halfwidth <= 0) { L_WARNING("no sharpening requested; clone returned\n", procName); return pixClone(pixs); } if (halfwidth == 1 || halfwidth == 2) return pixUnsharpMaskingGrayFast(pixs, halfwidth, fract, L_BOTH_DIRECTIONS); if ((pixc = pixBlockconvGray(pixs, NULL, halfwidth, halfwidth)) == NULL) return (PIX *)ERROR_PTR("pixc not made", procName, NULL); /* Steps: * (1) edge image is pixs - pixc (this is highpass part) * (2) multiply edge image by fract * (3) add fraction of edge to pixs * * To show how this is done with both interfaces to arithmetic * on integer Pix, here is the implementation in the lower-level * function calls: * pixt = pixInitAccumulate(w, h, 0x10000000)) == NULL) * pixAccumulate(pixt, pixs, L_ARITH_ADD); * pixAccumulate(pixt, pixc, L_ARITH_SUBTRACT); * pixMultConstAccumulate(pixt, fract, 0x10000000); * pixAccumulate(pixt, pixs, L_ARITH_ADD); * pixd = pixFinalAccumulate(pixt, 0x10000000, 8)) == NULL) * pixDestroy(&pixt); * * The code below does the same thing using the Pixacc accumulator, * hiding the details of the offset that is needed for subtraction. */ pixacc = pixaccCreate(w, h, 1); pixaccAdd(pixacc, pixs); pixaccSubtract(pixacc, pixc); pixaccMultConst(pixacc, fract); pixaccAdd(pixacc, pixs); pixd = pixaccFinal(pixacc, 8); pixaccDestroy(&pixacc); pixDestroy(&pixc); return pixd; } /*! * \brief pixUnsharpMaskingFast() * * \param[in] pixs all depths except 1 bpp; with or without colormaps * \param[in] halfwidth "half-width" of smoothing filter; 1 and 2 only * \param[in] fract fraction of high frequency added to image * \param[in] direction L_HORIZ, L_VERT, L_BOTH_DIRECTIONS * \return pixd, or NULL on error * *
* Notes: * (1) The fast version uses separable 1-D filters directly on * the input image. The halfwidth is either 1 (full width = 3) * or 2 (full width = 5). * (2) The fract parameter is typically taken in the * range: 0.2 < fract < 0.7 * (3) To skip horizontal sharpening, use %fracth = 0.0; ditto for %fractv * (4) For one dimensional filtering (as an example): * For %halfwidth = 1, the low-pass filter is * L: 1/3 1/3 1/3 * and the high-pass filter is * H = I - L: -1/3 2/3 -1/3 * For %halfwidth = 2, the low-pass filter is * L: 1/5 1/5 1/5 1/5 1/5 * and the high-pass filter is * H = I - L: -1/5 -1/5 4/5 -1/5 -1/5 * The new sharpened pixel value is found by adding some fraction * of the high-pass filter value (which sums to 0) to the * initial pixel value: * N = I + fract * H * (5) For 2D, the sharpening filter is not separable, because the * vertical filter depends on the horizontal location relative * to the filter origin, and v.v. So we either do the full * 2D filter (for %halfwidth == 1) or do the low-pass * convolution separably and then compose with the original pix. * (6) Returns a clone if no sharpening is requested. **/ PIX * pixUnsharpMaskingFast(PIX *pixs, l_int32 halfwidth, l_float32 fract, l_int32 direction) { l_int32 d; PIX *pixt, *pixd, *pixr, *pixrs, *pixg, *pixgs, *pixb, *pixbs; PROCNAME("pixUnsharpMaskingFast"); if (!pixs || (pixGetDepth(pixs) == 1)) return (PIX *)ERROR_PTR("pixs not defined or 1 bpp", procName, NULL); if (fract <= 0.0 || halfwidth <= 0) { L_WARNING("no sharpening requested; clone returned\n", procName); return pixClone(pixs); } if (halfwidth != 1 && halfwidth != 2) return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL); if (direction != L_HORIZ && direction != L_VERT && direction != L_BOTH_DIRECTIONS) return (PIX *)ERROR_PTR("invalid direction", procName, NULL); /* Remove colormap; clone if possible; result is either 8 or 32 bpp */ if ((pixt = pixConvertTo8Or32(pixs, L_CLONE, 0)) == NULL) return (PIX *)ERROR_PTR("pixt not made", procName, NULL); /* Sharpen */ d = pixGetDepth(pixt); if (d == 8) { pixd = pixUnsharpMaskingGrayFast(pixt, halfwidth, fract, direction); } else { /* d == 32 */ pixr = pixGetRGBComponent(pixs, COLOR_RED); pixrs = pixUnsharpMaskingGrayFast(pixr, halfwidth, fract, direction); pixDestroy(&pixr); pixg = pixGetRGBComponent(pixs, COLOR_GREEN); pixgs = pixUnsharpMaskingGrayFast(pixg, halfwidth, fract, direction); pixDestroy(&pixg); pixb = pixGetRGBComponent(pixs, COLOR_BLUE); pixbs = pixUnsharpMaskingGrayFast(pixb, halfwidth, fract, direction); pixDestroy(&pixb); pixd = pixCreateRGBImage(pixrs, pixgs, pixbs); if (pixGetSpp(pixs) == 4) pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0); pixDestroy(&pixrs); pixDestroy(&pixgs); pixDestroy(&pixbs); } pixDestroy(&pixt); return pixd; } /*! * \brief pixUnsharpMaskingGrayFast() * * \param[in] pixs 8 bpp; no colormap * \param[in] halfwidth "half-width" of smoothing filter: 1 or 2 * \param[in] fract fraction of high frequency added to image * \param[in] direction L_HORIZ, L_VERT, L_BOTH_DIRECTIONS * \return pixd, or NULL on error * *
* Notes: * (1) For usage and explanation of the algorithm, see notes * in pixUnsharpMaskingFast(). * (2) Returns a clone if no sharpening is requested. **/ PIX * pixUnsharpMaskingGrayFast(PIX *pixs, l_int32 halfwidth, l_float32 fract, l_int32 direction) { PIX *pixd; PROCNAME("pixUnsharpMaskingGrayFast"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 8 || pixGetColormap(pixs) != NULL) return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL); if (fract <= 0.0 || halfwidth <= 0) { L_WARNING("no sharpening requested; clone returned\n", procName); return pixClone(pixs); } if (halfwidth != 1 && halfwidth != 2) return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL); if (direction != L_HORIZ && direction != L_VERT && direction != L_BOTH_DIRECTIONS) return (PIX *)ERROR_PTR("invalid direction", procName, NULL); if (direction != L_BOTH_DIRECTIONS) pixd = pixUnsharpMaskingGray1D(pixs, halfwidth, fract, direction); else /* 2D sharpening */ pixd = pixUnsharpMaskingGray2D(pixs, halfwidth, fract); return pixd; } /*! * \brief pixUnsharpMaskingGray1D() * * \param[in] pixs 8 bpp; no colormap * \param[in] halfwidth "half-width" of smoothing filter: 1 or 2 * \param[in] fract fraction of high frequency added to image * \param[in] direction filtering direction; use L_HORIZ or L_VERT * \return pixd, or NULL on error * *
* Notes: * (1) For usage and explanation of the algorithm, see notes * in pixUnsharpMaskingFast(). * (2) Returns a clone if no sharpening is requested. **/ PIX * pixUnsharpMaskingGray1D(PIX *pixs, l_int32 halfwidth, l_float32 fract, l_int32 direction) { l_int32 w, h, d, wpls, wpld, i, j, ival; l_uint32 *datas, *datad; l_uint32 *lines, *lines0, *lines1, *lines2, *lines3, *lines4, *lined; l_float32 val, a[5]; PIX *pixd; PROCNAME("pixUnsharpMaskingGray1D"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8 || pixGetColormap(pixs) != NULL) return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL); if (fract <= 0.0 || halfwidth <= 0) { L_WARNING("no sharpening requested; clone returned\n", procName); return pixClone(pixs); } if (halfwidth != 1 && halfwidth != 2) return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL); /* Initialize pixd with pixels from pixs that will not be * set when computing the sharpened values. */ pixd = pixCopyBorder(NULL, pixs, halfwidth, halfwidth, halfwidth, halfwidth); datas = pixGetData(pixs); datad = pixGetData(pixd); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); if (halfwidth == 1) { a[0] = -fract / 3.0; a[1] = 1.0 + fract * 2.0 / 3.0; a[2] = a[0]; } else { /* halfwidth == 2 */ a[0] = -fract / 5.0; a[1] = a[0]; a[2] = 1.0 + fract * 4.0 / 5.0; a[3] = a[0]; a[4] = a[0]; } if (direction == L_HORIZ) { for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; if (halfwidth == 1) { for (j = 1; j < w - 1; j++) { val = a[0] * GET_DATA_BYTE(lines, j - 1) + a[1] * GET_DATA_BYTE(lines, j) + a[2] * GET_DATA_BYTE(lines, j + 1); ival = (l_int32)val; ival = L_MAX(0, ival); ival = L_MIN(255, ival); SET_DATA_BYTE(lined, j, ival); } } else { /* halfwidth == 2 */ for (j = 2; j < w - 2; j++) { val = a[0] * GET_DATA_BYTE(lines, j - 2) + a[1] * GET_DATA_BYTE(lines, j - 1) + a[2] * GET_DATA_BYTE(lines, j) + a[3] * GET_DATA_BYTE(lines, j + 1) + a[4] * GET_DATA_BYTE(lines, j + 2); ival = (l_int32)val; ival = L_MAX(0, ival); ival = L_MIN(255, ival); SET_DATA_BYTE(lined, j, ival); } } } } else { /* direction == L_VERT */ if (halfwidth == 1) { for (i = 1; i < h - 1; i++) { lines0 = datas + (i - 1) * wpls; lines1 = datas + i * wpls; lines2 = datas + (i + 1) * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { val = a[0] * GET_DATA_BYTE(lines0, j) + a[1] * GET_DATA_BYTE(lines1, j) + a[2] * GET_DATA_BYTE(lines2, j); ival = (l_int32)val; ival = L_MAX(0, ival); ival = L_MIN(255, ival); SET_DATA_BYTE(lined, j, ival); } } } else { /* halfwidth == 2 */ for (i = 2; i < h - 2; i++) { lines0 = datas + (i - 2) * wpls; lines1 = datas + (i - 1) * wpls; lines2 = datas + i * wpls; lines3 = datas + (i + 1) * wpls; lines4 = datas + (i + 2) * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { val = a[0] * GET_DATA_BYTE(lines0, j) + a[1] * GET_DATA_BYTE(lines1, j) + a[2] * GET_DATA_BYTE(lines2, j) + a[3] * GET_DATA_BYTE(lines3, j) + a[4] * GET_DATA_BYTE(lines4, j); ival = (l_int32)val; ival = L_MAX(0, ival); ival = L_MIN(255, ival); SET_DATA_BYTE(lined, j, ival); } } } } return pixd; } /*! * \brief pixUnsharpMaskingGray2D() * * \param[in] pixs 8 bpp; no colormap * \param[in] halfwidth "half-width" of smoothing filter: 1 or 2 * \param[in] fract fraction of high frequency added to image * \return pixd, or NULL on error * *
* Notes: * (1) This is for %halfwidth == 1, 2. * (2) The lowpass filter is implemented separably. * (3) Returns a clone if no sharpening is requested. **/ PIX * pixUnsharpMaskingGray2D(PIX *pixs, l_int32 halfwidth, l_float32 fract) { l_int32 w, h, d, wpls, wpld, wplf, i, j, ival, sval; l_uint32 *datas, *datad, *lines, *lined; l_float32 val, norm; l_float32 *dataf, *linef, *linef0, *linef1, *linef2, *linef3, *linef4; PIX *pixd; FPIX *fpix; PROCNAME("pixUnsharpMaskingGray2D"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8 || pixGetColormap(pixs) != NULL) return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL); if (fract <= 0.0 || halfwidth <= 0) { L_WARNING("no sharpening requested; clone returned\n", procName); return pixClone(pixs); } if (halfwidth != 1 && halfwidth != 2) return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL); if ((pixd = pixCopyBorder(NULL, pixs, halfwidth, halfwidth, halfwidth, halfwidth)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); /* Do the low pass separably. Store the result of horizontal * smoothing in an intermediate fpix. */ if ((fpix = fpixCreate(w, h)) == NULL) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("fpix not made", procName, NULL); } dataf = fpixGetData(fpix); wplf = fpixGetWpl(fpix); if (halfwidth == 1) { for (i = 0; i < h; i++) { lines = datas + i * wpls; linef = dataf + i * wplf; for (j = 1; j < w - 1; j++) { val = GET_DATA_BYTE(lines, j - 1) + GET_DATA_BYTE(lines, j) + GET_DATA_BYTE(lines, j + 1); linef[j] = val; } } } else { for (i = 0; i < h; i++) { lines = datas + i * wpls; linef = dataf + i * wplf; for (j = 2; j < w - 2; j++) { val = GET_DATA_BYTE(lines, j - 2) + GET_DATA_BYTE(lines, j - 1) + GET_DATA_BYTE(lines, j) + GET_DATA_BYTE(lines, j + 1) + GET_DATA_BYTE(lines, j + 2); linef[j] = val; } } } /* Do vertical smoothing to finish the low-pass filter. * At each pixel, if L is the lowpass value, I is the * src pixel value and f is the fraction of highpass to * be added to I, then the highpass filter value is * H = I - L * and the new sharpened value is * N = I + f * H. */ if (halfwidth == 1) { for (i = 1; i < h - 1; i++) { linef0 = dataf + (i - 1) * wplf; linef1 = dataf + i * wplf; linef2 = dataf + (i + 1) * wplf; lined = datad + i * wpld; lines = datas + i * wpls; norm = 1.0 / 9.0; for (j = 1; j < w - 1; j++) { val = norm * (linef0[j] + linef1[j] + linef2[j]); /* L: lowpass filter value */ sval = GET_DATA_BYTE(lines, j); /* I: source pixel */ ival = (l_int32)(sval + fract * (sval - val) + 0.5); ival = L_MAX(0, ival); ival = L_MIN(255, ival); SET_DATA_BYTE(lined, j, ival); } } } else { for (i = 2; i < h - 2; i++) { linef0 = dataf + (i - 2) * wplf; linef1 = dataf + (i - 1) * wplf; linef2 = dataf + i * wplf; linef3 = dataf + (i + 1) * wplf; linef4 = dataf + (i + 2) * wplf; lined = datad + i * wpld; lines = datas + i * wpls; norm = 1.0 / 25.0; for (j = 2; j < w - 2; j++) { val = norm * (linef0[j] + linef1[j] + linef2[j] + linef3[j] + linef4[j]); /* L: lowpass filter value */ sval = GET_DATA_BYTE(lines, j); /* I: source pixel */ ival = (l_int32)(sval + fract * (sval - val) + 0.5); ival = L_MAX(0, ival); ival = L_MIN(255, ival); SET_DATA_BYTE(lined, j, ival); } } } fpixDestroy(&fpix); return pixd; } /*-----------------------------------------------------------------------* * Hue and saturation modification * *-----------------------------------------------------------------------*/ /*! * \brief pixModifyHue() * * \param[in] pixd [optional] can be null or equal to pixs * \param[in] pixs 32 bpp rgb * \param[in] fract between -1.0 and 1.0 * \return pixd, or NULL on error * *
* Notes: * (1) pixd must either be null or equal to pixs. * For in-place operation, set pixd == pixs: * pixEqualizeTRC(pixs, pixs, ...); * To get a new image, set pixd == null: * pixd = pixEqualizeTRC(NULL, pixs, ...); * (1) Use fract > 0.0 to increase hue value; < 0.0 to decrease it. * 1.0 (or -1.0) represents a 360 degree rotation; i.e., no change. * (2) If no modification is requested (fract = -1.0 or 0 or 1.0), * return a copy unless in-place, in which case this is a no-op. * (3) See discussion of color-modification methods, in coloring.c. **/ PIX * pixModifyHue(PIX *pixd, PIX *pixs, l_float32 fract) { l_int32 w, h, d, i, j, wpl, delhue; l_int32 rval, gval, bval, hval, sval, vval; l_uint32 *data, *line; PROCNAME("pixModifyHue"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetColormap(pixs) != NULL) return (PIX *)ERROR_PTR("pixs colormapped", procName, NULL); if (pixd && (pixd != pixs)) return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd); pixGetDimensions(pixs, &w, &h, &d); if (d != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (L_ABS(fract) > 1.0) return (PIX *)ERROR_PTR("fract not in [-1.0 ... 1.0]", procName, NULL); pixd = pixCopy(pixd, pixs); delhue = (l_int32)(240 * fract); if (delhue == 0 || delhue == 240 || delhue == -240) { L_WARNING("no change requested in hue\n", procName); return pixd; } if (delhue < 0) delhue += 240; data = pixGetData(pixd); wpl = pixGetWpl(pixd); for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { extractRGBValues(line[j], &rval, &gval, &bval); convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval); hval = (hval + delhue) % 240; convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, line + j); } } if (pixGetSpp(pixs) == 4) pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0); return pixd; } /*! * \brief pixModifySaturation() * * \param[in] pixd [optional] can be null, existing or equal to pixs * \param[in] pixs 32 bpp rgb * \param[in] fract between -1.0 and 1.0 * \return pixd, or NULL on error * *
* Notes: * (1) If fract > 0.0, it gives the fraction that the pixel * saturation is moved from its initial value toward 255. * If fract < 0.0, it gives the fraction that the pixel * saturation is moved from its initial value toward 0. * The limiting values for fract = -1.0 (1.0) thus set the * saturation to 0 (255). * (2) If fract = 0, no modification is requested; return a copy * unless in-place, in which case this is a no-op. * (3) See discussion of color-modification methods, in coloring.c. **/ PIX * pixModifySaturation(PIX *pixd, PIX *pixs, l_float32 fract) { l_int32 w, h, d, i, j, wpl; l_int32 rval, gval, bval, hval, sval, vval; l_uint32 *data, *line; PROCNAME("pixModifySaturation"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (L_ABS(fract) > 1.0) return (PIX *)ERROR_PTR("fract not in [-1.0 ... 1.0]", procName, NULL); pixd = pixCopy(pixd, pixs); if (fract == 0.0) { L_WARNING("no change requested in saturation\n", procName); return pixd; } data = pixGetData(pixd); wpl = pixGetWpl(pixd); for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { extractRGBValues(line[j], &rval, &gval, &bval); convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval); if (fract < 0.0) sval = (l_int32)(sval * (1.0 + fract)); else sval = (l_int32)(sval + fract * (255 - sval)); convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, line + j); } } if (pixGetSpp(pixs) == 4) pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0); return pixd; } /*! * \brief pixMeasureSaturation() * * \param[in] pixs 32 bpp rgb * \param[in] factor subsampling factor; integer >= 1 * \param[out] psat average saturation * \return 0 if OK, 1 on error */ l_int32 pixMeasureSaturation(PIX *pixs, l_int32 factor, l_float32 *psat) { l_int32 w, h, d, i, j, wpl, sum, count; l_int32 rval, gval, bval, hval, sval, vval; l_uint32 *data, *line; PROCNAME("pixMeasureSaturation"); if (!psat) return ERROR_INT("pixs not defined", procName, 1); *psat = 0.0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d != 32) return ERROR_INT("pixs not 32 bpp", procName, 1); if (factor < 1) return ERROR_INT("subsampling factor < 1", procName, 1); data = pixGetData(pixs); wpl = pixGetWpl(pixs); for (i = 0, sum = 0, count = 0; i < h; i += factor) { line = data + i * wpl; for (j = 0; j < w; j += factor) { extractRGBValues(line[j], &rval, &gval, &bval); convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval); sum += sval; count++; } } if (count > 0) *psat = (l_float32)sum / (l_float32)count; return 0; } /*! * \brief pixModifyBrightness() * * \param[in] pixd [optional] can be null, existing or equal to pixs * \param[in] pixs 32 bpp rgb * \param[in] fract between -1.0 and 1.0 * \return pixd, or NULL on error * *
* Notes: * (1) If fract > 0.0, it gives the fraction that the v-parameter, * which is max(r,g,b), is moved from its initial value toward 255. * If fract < 0.0, it gives the fraction that the v-parameter * is moved from its initial value toward 0. * The limiting values for fract = -1.0 (1.0) thus set the * v-parameter to 0 (255). * (2) If fract = 0, no modification is requested; return a copy * unless in-place, in which case this is a no-op. * (3) See discussion of color-modification methods, in coloring.c. **/ PIX * pixModifyBrightness(PIX *pixd, PIX *pixs, l_float32 fract) { l_int32 w, h, d, i, j, wpl; l_int32 rval, gval, bval, hval, sval, vval; l_uint32 *data, *line; PROCNAME("pixModifyBrightness"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (L_ABS(fract) > 1.0) return (PIX *)ERROR_PTR("fract not in [-1.0 ... 1.0]", procName, NULL); pixd = pixCopy(pixd, pixs); if (fract == 0.0) { L_WARNING("no change requested in brightness\n", procName); return pixd; } data = pixGetData(pixd); wpl = pixGetWpl(pixd); for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { extractRGBValues(line[j], &rval, &gval, &bval); convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval); if (fract > 0.0) vval = (l_int32)(vval + fract * (255.0 - vval)); else vval = (l_int32)(vval * (1.0 + fract)); convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, line + j); } } if (pixGetSpp(pixs) == 4) pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0); return pixd; } /*-----------------------------------------------------------------------* * Color shifting * *-----------------------------------------------------------------------*/ /*! * \brief pixMosaicColorShiftRGB() * * \param[in] pixs 32 bpp rgb * \param[in] roff center offset of red component * \param[in] goff center offset of green component * \param[in] boff center offset of blue component * \param[in] delta increments from center offsets [0.0 - 0.1]; * use 0.0 to get the default (0.04) * \param[in] nincr number of increments in each (positive and negative) * direction; use 0 to get the default (2). * \return pix, or NULL on error * *
* Notes: * (1) This generates a mosaic view of the effect of shifting the RGB * components. See pixColorShiftRGB() for details on the shifting. * (2) The offsets (%roff, %goff, %boff) set the color center point, * and the deviations from this are shown separately for deltas * in r, g and b. For each component, we show 2 * %nincr + 1 * images. * (3) Usage: color prints differ from the original due to three factors: * illumination, calibration of the camera in acquisition, * and calibration of the printer. This function can be used * to iteratively match a color print to the original. On each * iteration, the center offsets are set to the best match so * far, and the %delta increments are typically reduced. **/ PIX * pixMosaicColorShiftRGB(PIX *pixs, l_float32 roff, l_float32 goff, l_float32 boff, l_float32 delta, l_int32 nincr) { char buf[64]; l_int32 i; l_float32 del; L_BMF *bmf; PIX *pix1, *pix2, *pix3; PIXA *pixa; PROCNAME("pixMosaicColorShiftRGB"); if (!pixs || pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs undefined or not rgb", procName, NULL); if (roff < -1.0 || roff > 1.0) return (PIX *)ERROR_PTR("roff not in [-1.0, 1.0]", procName, NULL); if (goff < -1.0 || goff > 1.0) return (PIX *)ERROR_PTR("goff not in [-1.0, 1.0]", procName, NULL); if (boff < -1.0 || boff > 1.0) return (PIX *)ERROR_PTR("boff not in [-1.0, 1.0]", procName, NULL); if (delta < 0.0 || delta > 0.1) return (PIX *)ERROR_PTR("delta not in [0.0, 0.1]", procName, NULL); if (delta == 0.0) delta = 0.04; if (nincr < 0 || nincr > 6) return (PIX *)ERROR_PTR("nincr not in [0, 6]", procName, NULL); if (nincr == 0) nincr = 2; pixa = pixaCreate(3 * (2 * nincr + 1)); bmf = bmfCreate(NULL, 8); pix1 = pixScaleToSize(pixs, 400, 0); for (i = 0, del = - nincr * delta; i < 2 * nincr + 1; i++, del += delta) { pix2 = pixColorShiftRGB(pix1, roff + del, goff, boff); snprintf(buf, sizeof(buf), "%4.2f, %4.2f, %4.2f", roff + del, goff, boff); pix3 = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix2); } for (i = 0, del = - nincr * delta; i < 2 * nincr + 1; i++, del += delta) { pix2 = pixColorShiftRGB(pix1, roff, goff + del, boff); snprintf(buf, sizeof(buf), "%4.2f, %4.2f, %4.2f", roff, goff + del, boff); pix3 = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix2); } for (i = 0, del = - nincr * delta; i < 2 * nincr + 1; i++, del += delta) { pix2 = pixColorShiftRGB(pix1, roff, goff, boff + del); snprintf(buf, sizeof(buf), "%4.2f, %4.2f, %4.2f", roff, goff, boff + del); pix3 = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix2); } pixDestroy(&pix1); pix1 = pixaDisplayTiledAndScaled(pixa, 32, 300, 2 * nincr + 1, 0, 30, 2); pixaDestroy(&pixa); bmfDestroy(&bmf); return pix1; } /*! * \brief pixColorShiftRGB() * * \param[in] pixs 32 bpp rgb * \param[in] rfract fractional shift in red component * \param[in] gfract fractional shift in green component * \param[in] bfract fractional shift in blue component * \return pixd, or NULL on error * *
* Notes: * (1) This allows independent fractional shifts of the r,g and b * components. A positive shift pushes to saturation (255); * a negative shift pushes toward 0 (black). * (2) The effect can be imagined using a color wheel that consists * (for our purposes) of these 6 colors, separated by 60 degrees: * red, magenta, blue, cyan, green, yellow * (3) So, for example, a negative shift of the blue component * (bfract < 0) could be accompanied by positive shifts * of red and green to make an image more yellow. * (4) Examples of limiting cases: * rfract = 1 ==> r = 255 * rfract = -1 ==> r = 0 **/ PIX * pixColorShiftRGB(PIX *pixs, l_float32 rfract, l_float32 gfract, l_float32 bfract) { l_int32 w, h, i, j, wpls, wpld, rval, gval, bval; l_int32 *rlut, *glut, *blut; l_uint32 *datas, *datad, *lines, *lined; l_float32 fi; PIX *pixd; PROCNAME("pixColorShiftRGB"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (rfract < -1.0 || rfract > 1.0) return (PIX *)ERROR_PTR("rfract not in [-1.0, 1.0]", procName, NULL); if (gfract < -1.0 || gfract > 1.0) return (PIX *)ERROR_PTR("gfract not in [-1.0, 1.0]", procName, NULL); if (bfract < -1.0 || bfract > 1.0) return (PIX *)ERROR_PTR("bfract not in [-1.0, 1.0]", procName, NULL); if (rfract == 0.0 && gfract == 0.0 && bfract == 0.0) return pixCopy(NULL, pixs); rlut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); glut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); blut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); for (i = 0; i < 256; i++) { fi = i; if (rfract >= 0) { rlut[i] = (l_int32)(fi + (255.0 - fi) * rfract); } else { rlut[i] = (l_int32)(fi * (1.0 + rfract)); } if (gfract >= 0) { glut[i] = (l_int32)(fi + (255.0 - fi) * gfract); } else { glut[i] = (l_int32)(fi * (1.0 + gfract)); } if (bfract >= 0) { blut[i] = (l_int32)(fi + (255.0 - fi) * bfract); } else { blut[i] = (l_int32)(fi * (1.0 + bfract)); } } pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); pixd = pixCreate(w, h, 32); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); composeRGBPixel(rlut[rval], glut[gval], blut[bval], lined + j); } } LEPT_FREE(rlut); LEPT_FREE(glut); LEPT_FREE(blut); return pixd; } /*-----------------------------------------------------------------------* * Darken gray (unsaturated) pixels *-----------------------------------------------------------------------*/ /*! * \brief pixDarkenGray() * * \param[in] pixd [optional] can be null or equal to pixs * \param[in] pixs 32 bpp rgb * \param[in] thresh pixels with max component >= %thresh are unchanged * \param[in] satlimit pixels with saturation >= %satlimit are unchanged * \return pixd, or NULL on error * *
* Notes: * (1) This darkens gray pixels, by a fraction (sat/%satlimit), where * the saturation, sat, is the component difference (max - min). * The pixel value is unchanged if sat >= %satlimit. A typical * value of %satlimit might be 40; the larger the value, the * more that pixels with a smaller saturation will be darkened. * (2) Pixels with max component >= %thresh are unchanged. This can be * used to prevent bright pixels with low saturation from being * darkened. Setting thresh == 0 is a no-op; setting %thresh == 255 * causes the darkening to be applied to all pixels. * (3) This function is useful to enhance pixels relative to a * gray background. * (4) A related function that builds a 1 bpp mask over the gray * pixels is pixMaskOverGrayPixels(). **/ PIX * pixDarkenGray(PIX *pixd, PIX *pixs, l_int32 thresh, l_int32 satlimit) { l_int32 w, h, i, j, wpls, wpld; l_int32 rval, gval, bval, minrg, min, maxrg, max, sat; l_uint32 *datas, *datad, *lines, *lined; l_float32 ratio; PROCNAME("pixDarkenGray"); if (!pixs || pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL); if (thresh < 0 || thresh > 255) return (PIX *)ERROR_PTR("invalid thresh", procName, NULL); if (satlimit < 1) return (PIX *)ERROR_PTR("invalid satlimit", procName, NULL); if (pixd && (pixs != pixd)) return (PIX *)ERROR_PTR("not new or in-place", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); if ((pixd = pixCopy(pixd, pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); minrg = L_MIN(rval, gval); min = L_MIN(minrg, bval); maxrg = L_MAX(rval, gval); max = L_MAX(maxrg, bval); sat = max - min; if (max >= thresh || sat >= satlimit) continue; ratio = (l_float32)sat / (l_float32)satlimit; composeRGBPixel((l_int32)(ratio * rval), (l_int32)(ratio * gval), (l_int32)(ratio * bval), &lined[j]); } } return pixd; } /*-----------------------------------------------------------------------* * General multiplicative constant color transform * *-----------------------------------------------------------------------*/ /*! * \brief pixMultConstantColor() * * \param[in] pixs colormapped or rgb * \param[in] rfact red multiplicative factor * \param[in] gfact green multiplicative factor * \param[in] bfact blue multiplicative factor * \return pixd colormapped or rgb, with colors scaled, or NULL on error * *
* Notes: * (1) rfact, gfact and bfact can only have non-negative values. * They can be greater than 1.0. All transformed component * values are clipped to the interval [0, 255]. * (2) For multiplication with a general 3x3 matrix of constants, * use pixMultMatrixColor(). **/ PIX * pixMultConstantColor(PIX *pixs, l_float32 rfact, l_float32 gfact, l_float32 bfact) { l_int32 i, j, w, h, d, wpls, wpld; l_int32 ncolors, rval, gval, bval, nrval, ngval, nbval; l_uint32 nval; l_uint32 *datas, *datad, *lines, *lined; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixMultConstantColor"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); cmap = pixGetColormap(pixs); if (!cmap && d != 32) return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL); rfact = L_MAX(0.0, rfact); gfact = L_MAX(0.0, gfact); bfact = L_MAX(0.0, bfact); if (cmap) { if ((pixd = pixCopy(NULL, pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); cmap = pixGetColormap(pixd); ncolors = pixcmapGetCount(cmap); for (i = 0; i < ncolors; i++) { pixcmapGetColor(cmap, i, &rval, &gval, &bval); nrval = (l_int32)(rfact * rval); ngval = (l_int32)(gfact * gval); nbval = (l_int32)(bfact * bval); nrval = L_MIN(255, nrval); ngval = L_MIN(255, ngval); nbval = L_MIN(255, nbval); pixcmapResetColor(cmap, i, nrval, ngval, nbval); } return pixd; } if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); datas = pixGetData(pixs); datad = pixGetData(pixd); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); nrval = (l_int32)(rfact * rval); ngval = (l_int32)(gfact * gval); nbval = (l_int32)(bfact * bval); nrval = L_MIN(255, nrval); ngval = L_MIN(255, ngval); nbval = L_MIN(255, nbval); composeRGBPixel(nrval, ngval, nbval, &nval); *(lined + j) = nval; } } return pixd; } /*! * \brief pixMultMatrixColor() * * \param[in] pixs colormapped or rgb * \param[in] kel kernel 3x3 matrix of floats * \return pixd colormapped or rgb, or NULL on error * *
* Notes: * (1) The kernel is a data structure used mostly for floating point * convolution. Here it is a 3x3 matrix of floats that are used * to transform the pixel values by matrix multiplication: * nrval = a[0,0] * rval + a[0,1] * gval + a[0,2] * bval * ngval = a[1,0] * rval + a[1,1] * gval + a[1,2] * bval * nbval = a[2,0] * rval + a[2,1] * gval + a[2,2] * bval * (2) The matrix can be generated in several ways. * See kernel.c for details. Here are two of them: * (a) kel = kernelCreate(3, 3); * kernelSetElement(kel, 0, 0, val00); * kernelSetElement(kel, 0, 1, val01); * ... * (b) from a static string; e.g.,: * const char *kdata = " 0.6 0.3 -0.2 " * " 0.1 1.2 0.4 " * " -0.4 0.2 0.9 "; * kel = kernelCreateFromString(3, 3, 0, 0, kdata); * (3) For the special case where the matrix is diagonal, it is easier * to use pixMultConstantColor(). * (4) Matrix entries can have positive and negative values, and can * be larger than 1.0. All transformed component values * are clipped to [0, 255]. **/ PIX * pixMultMatrixColor(PIX *pixs, L_KERNEL *kel) { l_int32 i, j, index, kw, kh, w, h, d, wpls, wpld; l_int32 ncolors, rval, gval, bval, nrval, ngval, nbval; l_uint32 nval; l_uint32 *datas, *datad, *lines, *lined; l_float32 v[9]; /* use linear array for convenience */ PIX *pixd; PIXCMAP *cmap; PROCNAME("pixMultMatrixColor"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!kel) return (PIX *)ERROR_PTR("kel not defined", procName, NULL); kernelGetParameters(kel, &kw, &kh, NULL, NULL); if (kw != 3 || kh != 3) return (PIX *)ERROR_PTR("matrix not 3x3", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); cmap = pixGetColormap(pixs); if (!cmap && d != 32) return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL); for (i = 0, index = 0; i < 3; i++) for (j = 0; j < 3; j++, index++) kernelGetElement(kel, i, j, v + index); if (cmap) { if ((pixd = pixCopy(NULL, pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); cmap = pixGetColormap(pixd); ncolors = pixcmapGetCount(cmap); for (i = 0; i < ncolors; i++) { pixcmapGetColor(cmap, i, &rval, &gval, &bval); nrval = (l_int32)(v[0] * rval + v[1] * gval + v[2] * bval); ngval = (l_int32)(v[3] * rval + v[4] * gval + v[5] * bval); nbval = (l_int32)(v[6] * rval + v[7] * gval + v[8] * bval); nrval = L_MAX(0, L_MIN(255, nrval)); ngval = L_MAX(0, L_MIN(255, ngval)); nbval = L_MAX(0, L_MIN(255, nbval)); pixcmapResetColor(cmap, i, nrval, ngval, nbval); } return pixd; } if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); datas = pixGetData(pixs); datad = pixGetData(pixd); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); nrval = (l_int32)(v[0] * rval + v[1] * gval + v[2] * bval); ngval = (l_int32)(v[3] * rval + v[4] * gval + v[5] * bval); nbval = (l_int32)(v[6] * rval + v[7] * gval + v[8] * bval); nrval = L_MAX(0, L_MIN(255, nrval)); ngval = L_MAX(0, L_MIN(255, ngval)); nbval = L_MAX(0, L_MIN(255, nbval)); composeRGBPixel(nrval, ngval, nbval, &nval); *(lined + j) = nval; } } return pixd; } /*-------------------------------------------------------------* * Half-edge by bandpass * *-------------------------------------------------------------*/ /*! * \brief pixHalfEdgeByBandpass() * * \param[in] pixs 8 bpp gray or 32 bpp rgb * \param[in] sm1h, sm1v "half-widths" of smoothing filter sm1 * \param[in] sm2h, sm2v "half-widths" of smoothing filter sm2; * require sm2 != sm1 * \return pixd, or NULL on error * *
* Notes: * (1) We use symmetric smoothing filters of odd dimension, * typically use 3, 5, 7, etc. The smoothing parameters * for these are 1, 2, 3, etc. The filter size is related * to the smoothing parameter by * size = 2 * smoothing + 1 * (2) Because we take the difference of two lowpass filters, * this is actually a bandpass filter. * (3) We allow both filters to be anisotropic. * (4) Consider either the h or v component of the 2 filters. * Depending on whether sm1 > sm2 or sm2 > sm1, we get * different halves of the smoothed gradients (or "edges"). * This difference of smoothed signals looks more like * a second derivative of a transition, which we rectify * by not allowing the signal to go below zero. If sm1 < sm2, * the sm2 transition is broader, so the difference between * sm1 and sm2 signals is positive on the upper half of * the transition. Likewise, if sm1 > sm2, the sm1 - sm2 * signal difference is positive on the lower half of * the transition. **/ PIX * pixHalfEdgeByBandpass(PIX *pixs, l_int32 sm1h, l_int32 sm1v, l_int32 sm2h, l_int32 sm2v) { l_int32 d; PIX *pixg, *pixacc, *pixc1, *pixc2; PROCNAME("pixHalfEdgeByBandpass"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (sm1h == sm2h && sm1v == sm2v) return (PIX *)ERROR_PTR("sm2 = sm1", procName, NULL); d = pixGetDepth(pixs); if (d != 8 && d != 32) return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL); if (d == 32) pixg = pixConvertRGBToLuminance(pixs); else /* d == 8 */ pixg = pixClone(pixs); /* Make a convolution accumulator and use it twice */ if ((pixacc = pixBlockconvAccum(pixg)) == NULL) { pixDestroy(&pixg); return (PIX *)ERROR_PTR("pixacc not made", procName, NULL); } if ((pixc1 = pixBlockconvGray(pixg, pixacc, sm1h, sm1v)) == NULL) { pixDestroy(&pixg); pixDestroy(&pixacc); return (PIX *)ERROR_PTR("pixc1 not made", procName, NULL); } pixc2 = pixBlockconvGray(pixg, pixacc, sm2h, sm2v); pixDestroy(&pixg); pixDestroy(&pixacc); if (!pixc2) { pixDestroy(&pixc1); return (PIX *)ERROR_PTR("pixc2 not made", procName, NULL); } /* Compute the half-edge using pixc1 - pixc2. */ pixSubtractGray(pixc1, pixc1, pixc2); pixDestroy(&pixc2); return pixc1; }