blob: 4bbb44a69f1223f15b846fa157f01fb156196394 [file] [log] [blame]
/*====================================================================*
- Copyright (C) 2001 Leptonica. All rights reserved.
- This software is distributed in the hope that it will be
- useful, but with NO WARRANTY OF ANY KIND.
- No author or distributor accepts responsibility to anyone for the
- consequences of using this software, or for whether it serves any
- particular purpose or works at all, unless he or she says so in
- writing. Everyone is granted permission to copy, modify and
- redistribute this source code, for commercial or non-commercial
- purposes, with the following restrictions: (1) the origin of this
- source code must not be misrepresented; (2) modified versions must
- be plainly marked as such; and (3) this notice may not be removed
- or altered from any source or modified source distribution.
*====================================================================*/
/*
* enhance.c
*
* Gamma TRC (tone reproduction curve) mapping
* PIX *pixGammaTRC()
* PIX *pixGammaTRCMasked()
* NUMA *numaGammaTRC()
*
* Contrast enhancement
* PIX *pixContrastTRC()
* PIX *pixContrastTRCMasked()
* NUMA *numaContrastTRC()
*
* Histogram equalization
* PIX *pixEqualizeTRC()
* NUMA *numaEqualizeTRC()
*
* Generic TRC mapper
* PIX *pixTRCMap()
*
* 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()
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "allheaders.h"
/* Scales contrast enhancement factor to have a useful range
* between 0.0 and 1.0 */
static const l_float32 ENHANCE_SCALE_FACTOR = 5.;
/* Default number of pixels sampled to determine histogram */
static const l_int32 DEFAULT_HISTO_SAMPLES = 100000;
/*-------------------------------------------------------------*
* Gamma TRC (tone reproduction curve) mapping *
*-------------------------------------------------------------*/
/*!
* pixGammaTRC()
*
* Input: pixd (<optional> null or equal to pixs)
* pixs (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
* gamma (gamma correction; must be > 0.0)
* minval (input value that gives 0 for output; can be < 0)
* maxval (input value that gives 255 for output; can be > 255)
* Return: pixd always
*
* Notes:
* (1) pixd must either be null or equal to pixs.
* Set pixd == pixs to get in-place operation;
* set pixd == null to get new image.
* (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;
* if gamma == 1.0 and minval == 0 and maxval == 255, return a clone
* (5) For color images that are not colormapped, the mapping
* is applied to each component.
* (6) 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.
* (7) Increasing minval darkens the image.
* (8) Decreasing maxval bleaches the image.
* (9) Simultaneously increasing minval and decreasing maxval
* will darken the image and make the colors more intense;
* e.g., minval = 50, maxval = 200.
* (10) See numaGammaTRC() for further examples of use.
*/
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", 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 pixClone(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) {
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;
}
/*!
* pixGammaTRCMasked()
*
* Input: pixd (<optional> null or equal to pixs)
* pixs (8 or 32 bpp; not colormapped)
* pixm (<optional> null or 1 bpp)
* gamma (gamma correction; must be > 0.0)
* minval (input value that gives 0 for output; can be < 0)
* 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", procName);
gamma = 1.0;
}
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;
}
/*!
* numaGammaTRC()
*
* Input: gamma (gamma factor; must be > 0.0)
* minval (input value that gives 0 for output)
* 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", 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 *
*-------------------------------------------------------------*/
/*!
* pixContrastTRC()
*
* Input: pixd (<optional> null or equal to pixs)
* pixs (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
* factor (0.0 is no enhancement)
* Return: pixd always
*
* Notes:
* (1) pixd must either be null or equal to pixs.
* Set pixd == pixs to get in-place operation;
* set pixd == null to get new image.
* (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.
* 0.0 corresponds to no enhancement; return a clone.
* (5) 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", procName);
factor = 0.0;
}
if (factor == 0.0)
return pixClone(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;
}
/*!
* pixContrastTRCMasked()
*
* Input: pixd (<optional> null or equal to pixs)
* pixs (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
* pixm (<optional> null or 1 bpp)
* 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", procName);
factor = 0.0;
}
if (factor == 0.0)
return pixClone(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;
}
/*!
* numaContrastTRC()
*
* Input: 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", procName);
factor = 0.0;
}
if (factor == 0.0)
return numaMakeSequence(0, 1, 256); /* linear map */
scale = ENHANCE_SCALE_FACTOR;
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 *
*-------------------------------------------------------------*/
/*!
* pixEqualizeTRC()
*
* Input: pixd (<optional> null or equal to pixs)
* pixs (8 bpp, or colormapped)
* fract (fraction of equalization movement of pixel values)
* factor (subsampling factor; integer >= 1)
* Return: pixd, or null on error
*
* Notes:
* (1) pixd must either be null or equal to pixs.
* Set pixd == pixs to get in-place operation;
* set pixd == null to get new image.
* (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.
* If fract == 1.0, equalization is complete.
* (4) Set the subsampling factor > 1 to reduce the amount of computation.
* (5) If the pix is colormapped, the colormap is removed and
* it is converted to grayscale.
* (6) 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)
{
NUMA *na;
PIX *pixt;
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);
if ((pixGetDepth(pixs) != 8) && !cmap)
return (PIX *)ERROR_PTR("pixs not 8 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_TO_GRAYSCALE);
else
pixt = pixClone(pixs);
/* Make a copy if necessary */
pixd = pixCopy(pixd, pixt);
pixDestroy(&pixt);
if ((na = numaEqualizeTRC(pixd, fract, factor)) == NULL)
return (PIX *)ERROR_PTR("na not made", procName, pixd);
pixTRCMap(pixd, NULL, na);
numaDestroy(&na);
return pixd;
}
/*!
* numaEqualizeTRC()
*
* Input: pix (8 bpp, no colormap)
* fract (fraction of equalization movement of pixel values)
* 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", 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 *
*-------------------------------------------------------------*/
/*!
* pixTRCMap()
*
* Input: pixs (8 grayscale or 32 bpp rgb; not colormapped)
* pixm (<optional> 1 bpp mask)
* 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.
*/
l_int32
pixTRCMap(PIX *pixs,
PIX *pixm,
NUMA *na)
{
l_int32 w, h, d, wm, hm, wpl, wplm, i, j, sval8, dval8;
l_int32 *tab;
l_uint32 sval32, dval32;
l_uint32 *data, *datam, *line, *linem;
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 = 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;
}
}
}
}
FREE(tab);
return 0;
}
/*-----------------------------------------------------------------------*
* Unsharp masking *
*-----------------------------------------------------------------------*/
/*!
* pixUnsharpMasking()
*
* Input: pixs (all depths except 1 bpp; with or without colormaps)
* halfwidth ("half-width" of smoothing filter)
* 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
*/
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", 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, 0, 1)) == 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);
}
pixDestroy(&pixt);
return pixd;
}
/*!
* pixUnsharpMaskingGray()
*
* Input: pixs (8 bpp; no colormap)
* halfwidth ("half-width" of smoothing filter)
* 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
*/
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", 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;
}
/*!
* pixUnsharpMaskingFast()
*
* Input: pixs (all depths except 1 bpp; with or without colormaps)
* halfwidth ("half-width" of smoothing filter; 1 and 2 only)
* fract (fraction of high frequency added to image)
* 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.
*/
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", 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, 0, 1)) == 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);
pixDestroy(&pixrs);
pixDestroy(&pixgs);
pixDestroy(&pixbs);
}
pixDestroy(&pixt);
return pixd;
}
/*!
* pixUnsharpMaskingGrayFast()
*
* Input: pixs (8 bpp; no colormap)
* halfwidth ("half-width" of smoothing filter: 1 or 2)
* fract (fraction of high frequency added to image)
* 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().
*/
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", 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;
}
/*!
* pixUnsharpMaskingGray1D()
*
* Input: pixs (8 bpp; no colormap)
* halfwidth ("half-width" of smoothing filter: 1 or 2)
* fract (fraction of high frequency added to image)
* direction (of filtering; 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().
*/
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", 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;
}
/*!
* pixUnsharpMaskingGray2D()
*
* Input: pixs (8 bpp; no colormap)
* halfwidth ("half-width" of smoothing filter: 1 or 2)
* fract (fraction of high frequency added to image)
* Return: pixd, or null on error
*
* Notes:
* (1) For halfwidth == 1, we implement the full sharpening filter
* directly. For halfwidth == 2, we implement the the lowpass
* filter separably and then compute the sharpening result locally.
*/
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, *lines0, *lines1, *lines2, *lined;
l_float32 val, a[9];
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", procName);
return pixClone(pixs);
}
if (halfwidth != 1 && halfwidth != 2)
return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL);
pixd = pixCopyBorder(NULL, pixs, halfwidth, halfwidth,
halfwidth, halfwidth);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
datas = pixGetData(pixs);
wpls = pixGetWpl(pixs);
if (halfwidth == 1) {
for (i = 0; i < 9; i++)
a[i] = -fract / 9.0;
a[4] = 1.0 + fract * 8.0 / 9.0;
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 = 1; j < w - 1; j++) {
val = a[0] * GET_DATA_BYTE(lines0, j - 1) +
a[1] * GET_DATA_BYTE(lines0, j) +
a[2] * GET_DATA_BYTE(lines0, j + 1) +
a[3] * GET_DATA_BYTE(lines1, j - 1) +
a[4] * GET_DATA_BYTE(lines1, j) +
a[5] * GET_DATA_BYTE(lines1, j + 1) +
a[6] * GET_DATA_BYTE(lines2, j - 1) +
a[7] * GET_DATA_BYTE(lines2, j) +
a[8] * GET_DATA_BYTE(lines2, j + 1);
ival = (l_int32)(val + 0.5);
ival = L_MAX(0, ival);
ival = L_MIN(255, ival);
SET_DATA_BYTE(lined, j, ival);
}
}
return pixd;
}
/* For halfwidth == 2, do the low pass separably. Store
* the result of horizontal smoothing in an intermediate fpix. */
fpix = fpixCreate(w, h);
dataf = fpixGetData(fpix);
wplf = fpixGetWpl(fpix);
for (i = 2; i < h - 2; 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.
*/
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;
for (j = 2; j < w - 2; j++) {
val = 0.04 * (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 *
*-----------------------------------------------------------------------*/
/*!
* pixModifyHue()
*
* Input: pixd (<optional> can be null, existing or equal to pixs)
* pixs (32 bpp rgb)
* fract (between -1.0 and 1.0)
* Return: pixd, or null on error
*
* Notes:
* (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.
*/
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);
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", 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);
}
}
return pixd;
}
/*!
* pixModifySaturation()
*
* Input: pixd (<optional> can be null, existing or equal to pixs)
* pixs (32 bpp rgb)
* 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).
*/
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", 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);
}
}
return pixd;
}
/*!
* pixMeasureSaturation()
*
* Input: pixs (32 bpp rgb)
* factor (subsampling factor; integer >= 1)
* &sat (<return> average saturation)
* Return: pixd, or null 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;
}
/*-----------------------------------------------------------------------*
* General multiplicative constant color transform *
*-----------------------------------------------------------------------*/
/*
* pixMultConstantColor()
*
* Input: pixs (colormapped or rgb)
* rfact, gfact, bfact (multiplicative factors on each component)
* 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;
}
/*
* pixMultMatrixColor()
*
* Input: pixs (colormapped or rgb)
* 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 *
*-------------------------------------------------------------*/
/*!
* pixHalfEdgeByBandpass()
*
* Input: pixs (8 bpp gray or 32 bpp rgb)
* sm1h, sm1v ("half-widths" of smoothing filter sm1)
* 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)
return (PIX *)ERROR_PTR("pixacc not made", procName, NULL);
if ((pixc1 = pixBlockconvGray(pixg, pixacc, sm1h, sm1v)) == NULL)
return (PIX *)ERROR_PTR("pixc1 not made", procName, NULL);
if ((pixc2 = pixBlockconvGray(pixg, pixacc, sm2h, sm2v)) == NULL)
return (PIX *)ERROR_PTR("pixc2 not made", procName, NULL);
pixDestroy(&pixacc);
/* Compute the half-edge using pixc1 - pixc2. */
pixSubtractGray(pixc1, pixc1, pixc2);
pixDestroy(&pixg);
pixDestroy(&pixc2);
return pixc1;
}