mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-29 06:12:18 -06:00
865 lines
34 KiB
C
865 lines
34 KiB
C
/*====================================================================*
|
|
- 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.
|
|
*====================================================================*/
|
|
|
|
/*
|
|
* colorcontent.c
|
|
*
|
|
* Builds an image of the color content, on a per-pixel basis,
|
|
* as a measure of the amount of divergence of each color
|
|
* component (R,G,B) from gray.
|
|
* l_int32 pixColorContent()
|
|
*
|
|
* Finds the 'amount' of color in an image, on a per-pixel basis,
|
|
* as a measure of the difference of the pixel color from gray.
|
|
* PIX *pixColorMagnitude()
|
|
*
|
|
* Finds the fraction of pixels with "color" that are not close to black
|
|
* l_int32 pixColorFraction()
|
|
*
|
|
* Finds the number of perceptually significant gray intensities
|
|
* in a grayscale image.
|
|
* l_int32 pixNumSignificantGrayColors()
|
|
*
|
|
* Identifies images where color quantization will cause posterization
|
|
* due to the existence of many colors in low-gradient regions.
|
|
* l_int32 pixColorsForQuantization()
|
|
*
|
|
* Finds the number of unique colors in an image
|
|
* l_int32 pixNumColors()
|
|
*
|
|
* Color is tricky. If we consider gray (r = g = b) to have no color
|
|
* content, how should we define the color content in each component
|
|
* of an arbitrary pixel, as well as the overall color magnitude?
|
|
*
|
|
* I can think of three ways to define the color content in each component:
|
|
*
|
|
* (1) Linear. For each component, take the difference from the average
|
|
* of all three.
|
|
* (2) Linear. For each component, take the difference from the average
|
|
* of the other two.
|
|
* (3) Nonlinear. For each component, take the minimum of the differences
|
|
* from the other two.
|
|
*
|
|
* How might one choose from among these? Consider two different situations:
|
|
* (a) r = g = 0, b = 255 {255} /255/
|
|
* (b) r = 0, g = 127, b = 255 {191} /128/
|
|
* How much g is in each of these? The three methods above give:
|
|
* (a) 1: 85 2: 127 3: 0 [85]
|
|
* (b) 1: 0 2: 0 3: 127 [0]
|
|
* How much b is in each of these?
|
|
* (a) 1: 170 2: 255 3: 255 [255]
|
|
* (b) 1: 127 2: 191 3: 127 [191]
|
|
* The number I'd "like" to give is in []. (Please don't ask why, it's
|
|
* just a feeling.
|
|
*
|
|
* So my preferences seem to be somewhere between (1) and (2).
|
|
* (3) is just too "decisive!" Let's pick (2).
|
|
*
|
|
* We also allow compensation for white imbalance. For each
|
|
* component, we do a linear TRC (gamma = 1.0), where the black
|
|
* point remains at 0 and the white point is given by the input
|
|
* parameter. This is equivalent to doing a global remapping,
|
|
* as with pixGlobalNormRGB(), followed by color content (or magnitude)
|
|
* computation, but without the overhead of first creating the
|
|
* white point normalized image.
|
|
*
|
|
* Another useful property is the overall color magnitude in the pixel.
|
|
* For this there are again several choices, such as:
|
|
* (a) rms deviation from the mean
|
|
* (b) the average L1 deviation from the mean
|
|
* (c) the maximum (over components) of one of the color
|
|
* content measures given above.
|
|
*
|
|
* For now, we will choose two of the methods in (c):
|
|
* L_MAX_DIFF_FROM_AVERAGE_2
|
|
* Define the color magnitude as the maximum over components
|
|
* of the difference between the component value and the
|
|
* average of the other two. It is easy to show that
|
|
* this is equivalent to selecting the two component values
|
|
* that are closest to each other, averaging them, and
|
|
* using the distance from that average to the third component.
|
|
* For (a) and (b) above, this value is in {..}.
|
|
* L_MAX_MIN_DIFF_FROM_2
|
|
* Define the color magnitude as the maximum over components
|
|
* of the minimum difference between the component value and the
|
|
* other two values. It is easy to show that this is equivalent
|
|
* to selecting the intermediate value of the three differences
|
|
* between the three components. For (a) and (b) above,
|
|
* this value is in /../.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "allheaders.h"
|
|
|
|
|
|
/*!
|
|
* pixColorContent()
|
|
*
|
|
* Input: pixs (32 bpp rgb or 8 bpp colormapped)
|
|
* rwhite, gwhite, bwhite (color value associated with white point)
|
|
* mingray (min gray value for which color is measured)
|
|
* &pixr (<optional return> 8 bpp red 'content')
|
|
* &pixg (<optional return> 8 bpp green 'content')
|
|
* &pixb (<optional return> 8 bpp blue 'content')
|
|
* Return: 0 if OK, 1 on error
|
|
*
|
|
* Notes:
|
|
* (1) This returns the color content in each component, which is
|
|
* a measure of the deviation from gray, and is defined
|
|
* as the difference between the component and the average of
|
|
* the other two components. See the discussion at the
|
|
* top of this file.
|
|
* (2) The three numbers (rwhite, gwhite and bwhite) can be thought
|
|
* of as the values in the image corresponding to white.
|
|
* They are used to compensate for an unbalanced color white point.
|
|
* They must either be all 0 or all non-zero. To turn this
|
|
* off, set them all to 0.
|
|
* (3) If the maximum component after white point correction,
|
|
* max(r,g,b), is less than mingray, all color components
|
|
* for that pixel are set to zero.
|
|
* Use mingray = 0 to turn off this filtering of dark pixels.
|
|
* (4) Therefore, use 0 for all four input parameters if the color
|
|
* magnitude is to be calculated without either white balance
|
|
* correction or dark filtering.
|
|
*/
|
|
l_int32
|
|
pixColorContent(PIX *pixs,
|
|
l_int32 rwhite,
|
|
l_int32 gwhite,
|
|
l_int32 bwhite,
|
|
l_int32 mingray,
|
|
PIX **ppixr,
|
|
PIX **ppixg,
|
|
PIX **ppixb)
|
|
{
|
|
l_int32 w, h, d, i, j, wplc, wplr, wplg, wplb;
|
|
l_int32 rval, gval, bval, rgdiff, rbdiff, gbdiff, maxval, colorval;
|
|
l_int32 *rtab, *gtab, *btab;
|
|
l_uint32 pixel;
|
|
l_uint32 *datac, *datar, *datag, *datab, *linec, *liner, *lineg, *lineb;
|
|
NUMA *nar, *nag, *nab;
|
|
PIX *pixc; /* rgb */
|
|
PIX *pixr, *pixg, *pixb; /* 8 bpp grayscale */
|
|
PIXCMAP *cmap;
|
|
|
|
PROCNAME("pixColorContent");
|
|
|
|
if (!pixs)
|
|
return ERROR_INT("pixs not defined", procName, 1);
|
|
if (!ppixr && !ppixg && !ppixb)
|
|
return ERROR_INT("nothing to compute", procName, 1);
|
|
if (mingray < 0) mingray = 0;
|
|
pixGetDimensions(pixs, &w, &h, &d);
|
|
if (mingray > 255)
|
|
return ERROR_INT("mingray > 255", procName, 1);
|
|
if (rwhite < 0 || gwhite < 0 || bwhite < 0)
|
|
return ERROR_INT("some white vals are negative", procName, 1);
|
|
if ((rwhite || gwhite || bwhite) && (rwhite * gwhite * bwhite == 0))
|
|
return ERROR_INT("white vals not all zero or all nonzero", procName, 1);
|
|
|
|
cmap = pixGetColormap(pixs);
|
|
if (!cmap && d != 32)
|
|
return ERROR_INT("pixs neither cmapped nor 32 bpp", procName, 1);
|
|
if (cmap)
|
|
pixc = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
|
|
else
|
|
pixc = pixClone(pixs);
|
|
|
|
pixr = pixg = pixb = NULL;
|
|
w = pixGetWidth(pixc);
|
|
h = pixGetHeight(pixc);
|
|
if (ppixr) {
|
|
pixr = pixCreate(w, h, 8);
|
|
datar = pixGetData(pixr);
|
|
wplr = pixGetWpl(pixr);
|
|
*ppixr = pixr;
|
|
}
|
|
if (ppixg) {
|
|
pixg = pixCreate(w, h, 8);
|
|
datag = pixGetData(pixg);
|
|
wplg = pixGetWpl(pixg);
|
|
*ppixg = pixg;
|
|
}
|
|
if (ppixb) {
|
|
pixb = pixCreate(w, h, 8);
|
|
datab = pixGetData(pixb);
|
|
wplb = pixGetWpl(pixb);
|
|
*ppixb = pixb;
|
|
}
|
|
|
|
datac = pixGetData(pixc);
|
|
wplc = pixGetWpl(pixc);
|
|
if (rwhite) { /* all white pt vals are nonzero */
|
|
nar = numaGammaTRC(1.0, 0, rwhite);
|
|
rtab = numaGetIArray(nar);
|
|
nag = numaGammaTRC(1.0, 0, gwhite);
|
|
gtab = numaGetIArray(nag);
|
|
nab = numaGammaTRC(1.0, 0, bwhite);
|
|
btab = numaGetIArray(nab);
|
|
}
|
|
for (i = 0; i < h; i++) {
|
|
linec = datac + i * wplc;
|
|
if (pixr)
|
|
liner = datar + i * wplr;
|
|
if (pixg)
|
|
lineg = datag + i * wplg;
|
|
if (pixb)
|
|
lineb = datab + i * wplb;
|
|
for (j = 0; j < w; j++) {
|
|
pixel = linec[j];
|
|
extractRGBValues(pixel, &rval, &gval, &bval);
|
|
if (rwhite) { /* color correct for white point */
|
|
rval = rtab[rval];
|
|
gval = gtab[gval];
|
|
bval = btab[bval];
|
|
}
|
|
if (mingray > 0) { /* dark pixels have no color value */
|
|
maxval = L_MAX(rval, gval);
|
|
maxval = L_MAX(maxval, bval);
|
|
if (maxval < mingray)
|
|
continue; /* colorval = 0 for each component */
|
|
}
|
|
rgdiff = L_ABS(rval - gval);
|
|
rbdiff = L_ABS(rval - bval);
|
|
gbdiff = L_ABS(gval - bval);
|
|
if (pixr) {
|
|
colorval = (rgdiff + rbdiff) / 2;
|
|
SET_DATA_BYTE(liner, j, colorval);
|
|
}
|
|
if (pixg) {
|
|
colorval = (rgdiff + gbdiff) / 2;
|
|
SET_DATA_BYTE(lineg, j, colorval);
|
|
}
|
|
if (pixb) {
|
|
colorval = (rbdiff + gbdiff) / 2;
|
|
SET_DATA_BYTE(lineb, j, colorval);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rwhite) {
|
|
numaDestroy(&nar);
|
|
numaDestroy(&nag);
|
|
numaDestroy(&nab);
|
|
FREE(rtab);
|
|
FREE(gtab);
|
|
FREE(btab);
|
|
}
|
|
pixDestroy(&pixc);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixColorMagnitude()
|
|
*
|
|
* Input: pixs (32 bpp rgb or 8 bpp colormapped)
|
|
* rwhite, gwhite, bwhite (color value associated with white point)
|
|
* type (chooses the method for calculating the color magnitude:
|
|
* L_MAX_DIFF_FROM_AVERAGE_2, MAX_MIN_DIFF_FROM_2)
|
|
* Return: pixd (8 bpp, amount of color in each source pixel),
|
|
* or NULL on error
|
|
*
|
|
* Notes:
|
|
* (1) For an RGB image, a gray pixel is one where all three components
|
|
* are equal. We define the amount of color in an RGB pixel by
|
|
* considering the absolute value of the differences between the
|
|
* components, of which there are three. Consider the two largest
|
|
* of these differences. The pixel component in common to these
|
|
* two differences is the color farthest from the other two.
|
|
* The color magnitude in an RGB pixel can be taken as:
|
|
* * the average of these two differences; i.e., the
|
|
* average distance from the two components that are
|
|
* nearest to each other to the third component, or
|
|
* * the minimum value of these two differences; i.e., the
|
|
* maximum over all components of the minimum distance
|
|
* from that component to the other two components.
|
|
* (2) As an example, suppose that R and G are the closest in
|
|
* magnitude. Then the color is determined as either:
|
|
* * the average distance of B from these two; namely,
|
|
* (|B - R| + |B - G|) / 2, which can also be found
|
|
* from |B - (R + G) / 2|, or
|
|
* * the minimum distance of B from these two; namely,
|
|
* min(|B - R|, |B - G|).
|
|
* (3) The three numbers (rwhite, gwhite and bwhite) can be thought
|
|
* of as the values in the image corresponding to white.
|
|
* They are used to compensate for an unbalanced color white point.
|
|
* They must either be all 0 or all non-zero. To turn this
|
|
* off, set them all to 0.
|
|
* (4) We allow the following methods for choosing the color
|
|
* magnitude from the three components:
|
|
* * L_MAX_DIFF_FROM_AVERAGE_2
|
|
* * L_MAX_MIN_DIFF_FROM_2
|
|
* These are described above in (1) and (2), as well as at
|
|
* the top of this file.
|
|
*/
|
|
PIX *
|
|
pixColorMagnitude(PIX *pixs,
|
|
l_int32 rwhite,
|
|
l_int32 gwhite,
|
|
l_int32 bwhite,
|
|
l_int32 type)
|
|
{
|
|
l_int32 w, h, d, i, j, wplc, wpld;
|
|
l_int32 rval, gval, bval, rdist, gdist, bdist, colorval;
|
|
l_int32 rgdist, rbdist, gbdist, mindist, maxdist;
|
|
l_int32 *rtab, *gtab, *btab;
|
|
l_uint32 pixel;
|
|
l_uint32 *datac, *datad, *linec, *lined;
|
|
NUMA *nar, *nag, *nab;
|
|
PIX *pixc, *pixd;
|
|
PIXCMAP *cmap;
|
|
|
|
PROCNAME("pixColorMagnitude");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
pixGetDimensions(pixs, &w, &h, &d);
|
|
if (type != L_MAX_DIFF_FROM_AVERAGE_2 && type != L_MAX_MIN_DIFF_FROM_2)
|
|
return (PIX *)ERROR_PTR("invalid type", procName, NULL);
|
|
if (rwhite < 0 || gwhite < 0 || bwhite < 0)
|
|
return (PIX *)ERROR_PTR("some white vals are negative", procName, NULL);
|
|
if ((rwhite || gwhite || bwhite) && (rwhite * gwhite * bwhite == 0))
|
|
return (PIX *)ERROR_PTR("white vals not all zero or all nonzero",
|
|
procName, NULL);
|
|
|
|
cmap = pixGetColormap(pixs);
|
|
if (!cmap && d != 32)
|
|
return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
|
|
if (cmap)
|
|
pixc = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
|
|
else
|
|
pixc = pixClone(pixs);
|
|
|
|
pixd = pixCreate(w, h, 8);
|
|
datad = pixGetData(pixd);
|
|
wpld = pixGetWpl(pixd);
|
|
datac = pixGetData(pixc);
|
|
wplc = pixGetWpl(pixc);
|
|
if (rwhite) { /* all white pt vals are nonzero */
|
|
nar = numaGammaTRC(1.0, 0, rwhite);
|
|
rtab = numaGetIArray(nar);
|
|
nag = numaGammaTRC(1.0, 0, gwhite);
|
|
gtab = numaGetIArray(nag);
|
|
nab = numaGammaTRC(1.0, 0, bwhite);
|
|
btab = numaGetIArray(nab);
|
|
}
|
|
for (i = 0; i < h; i++) {
|
|
linec = datac + i * wplc;
|
|
lined = datad + i * wpld;
|
|
for (j = 0; j < w; j++) {
|
|
pixel = linec[j];
|
|
extractRGBValues(pixel, &rval, &gval, &bval);
|
|
if (rwhite) { /* color correct for white point */
|
|
rval = rtab[rval];
|
|
gval = gtab[gval];
|
|
bval = btab[bval];
|
|
}
|
|
if (type == L_MAX_DIFF_FROM_AVERAGE_2) {
|
|
rdist = ((gval + bval ) / 2 - rval);
|
|
rdist = L_ABS(rdist);
|
|
gdist = ((rval + bval ) / 2 - gval);
|
|
gdist = L_ABS(gdist);
|
|
bdist = ((rval + gval ) / 2 - bval);
|
|
bdist = L_ABS(bdist);
|
|
colorval = L_MAX(rdist, gdist);
|
|
colorval = L_MAX(colorval, bdist);
|
|
}
|
|
else { /* type == L_MAX_MIN_DIFF_FROM_2; choose intermed dist */
|
|
rgdist = L_ABS(rval - gval);
|
|
rbdist = L_ABS(rval - bval);
|
|
gbdist = L_ABS(gval - bval);
|
|
maxdist = L_MAX(rgdist, rbdist);
|
|
if (gbdist >= maxdist)
|
|
colorval = maxdist;
|
|
else { /* gbdist is smallest or intermediate */
|
|
mindist = L_MIN(rgdist, rbdist);
|
|
colorval = L_MAX(mindist, gbdist);
|
|
}
|
|
}
|
|
SET_DATA_BYTE(lined, j, colorval);
|
|
}
|
|
}
|
|
|
|
if (rwhite) {
|
|
numaDestroy(&nar);
|
|
numaDestroy(&nag);
|
|
numaDestroy(&nab);
|
|
FREE(rtab);
|
|
FREE(gtab);
|
|
FREE(btab);
|
|
}
|
|
pixDestroy(&pixc);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixColorFraction()
|
|
*
|
|
* Input: pixs (32 bpp rgb)
|
|
* darkthresh (dark threshold for minimum of average value to
|
|
* be considered in the statistics; typ. 20)
|
|
* lightthresh (threshold near white, above which the pixel
|
|
* is not considered in the statistics; typ. 248)
|
|
* diffthresh (thresh for the maximum difference from
|
|
* the average of component values)
|
|
* factor (subsampling factor)
|
|
* &pixfract (<return> fraction of pixels in intermediate
|
|
* brightness range that were considered
|
|
* for color content)
|
|
* &colorfract (<return> fraction of pixels that meet the
|
|
* criterion for sufficient color; 0.0 on error)
|
|
* Return: 0 if OK, 1 on error
|
|
*
|
|
* Notes:
|
|
* (1) This function is asking the question: to what extent does the
|
|
* image appear to have color? The amount of color a pixel
|
|
* appears to have depends on both the deviation of the
|
|
* individual components from their average and on the average
|
|
* intensity itself. For example, the color will be much more
|
|
* obvious with a small deviation from white than the same
|
|
* deviation from black.
|
|
* (2) Any pixel that meets these three tests is considered a
|
|
* colorful pixel:
|
|
* (a) the average of components must equal or exceed @darkthresh
|
|
* (b) the average of components must not exceed @lightthresh
|
|
* (c) at least one component must differ from the average
|
|
* by at least @diffthresh
|
|
* (3) The dark pixels are removed from consideration because
|
|
* they don't appear to have color.
|
|
* (4) The very lightest pixels are removed because if an image
|
|
* has a lot of "white", the color fraction will be artificially
|
|
* low, even if all the other pixels are colorful.
|
|
* (5) If pixfract is very small, there are few pixels that are neither
|
|
* black nor white. If colorfract is very small, the pixels
|
|
* that are neither black nor white have very little color
|
|
* content. The product 'pixfract * colorfract' gives the
|
|
* fraction of pixels with significant color content.
|
|
* (6) One use of this function is as a preprocessing step for median
|
|
* cut quantization (colorquant2.c), which does a very poor job
|
|
* splitting the color space into rectangular volume elements when
|
|
* all the pixels are near the diagonal of the color cube. For
|
|
* octree quantization of an image with only gray values, the
|
|
* 2^(level) octcubes on the diagonal are the only ones
|
|
* that can be occupied.
|
|
*/
|
|
l_int32
|
|
pixColorFraction(PIX *pixs,
|
|
l_int32 darkthresh,
|
|
l_int32 lightthresh,
|
|
l_int32 diffthresh,
|
|
l_int32 factor,
|
|
l_float32 *ppixfract,
|
|
l_float32 *pcolorfract)
|
|
{
|
|
l_int32 i, j, w, h, wpl, rval, gval, bval, ave;
|
|
l_int32 total, npix, ncolor, rdiff, gdiff, bdiff, maxdiff;
|
|
l_uint32 pixel;
|
|
l_uint32 *data, *line;
|
|
|
|
PROCNAME("pixColorFraction");
|
|
|
|
if (!ppixfract || !pcolorfract)
|
|
return ERROR_INT("&pixfract and &colorfract not both defined",
|
|
procName, 1);
|
|
*ppixfract = 0.0;
|
|
*pcolorfract = 0.0;
|
|
if (!pixs || pixGetDepth(pixs) != 32)
|
|
return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
|
|
|
|
pixGetDimensions(pixs, &w, &h, NULL);
|
|
data = pixGetData(pixs);
|
|
wpl = pixGetWpl(pixs);
|
|
npix = ncolor = total = 0;
|
|
for (i = 0; i < h; i += factor) {
|
|
line = data + i * wpl;
|
|
for (j = 0; j < w; j += factor) {
|
|
total++;
|
|
pixel = line[j];
|
|
extractRGBValues(pixel, &rval, &gval, &bval);
|
|
ave = (l_int32)(0.333 * (rval + gval + bval));
|
|
if (ave < darkthresh || ave > lightthresh)
|
|
continue;
|
|
npix++;
|
|
rdiff = L_ABS(rval - ave);
|
|
gdiff = L_ABS(gval - ave);
|
|
bdiff = L_ABS(bval - ave);
|
|
maxdiff = L_MAX(rdiff, gdiff);
|
|
maxdiff = L_MAX(maxdiff, bdiff);
|
|
if (maxdiff >= diffthresh)
|
|
ncolor++;
|
|
}
|
|
}
|
|
|
|
if (npix == 0) {
|
|
L_WARNING("No pixels found for consideration", procName);
|
|
return 0;
|
|
}
|
|
*ppixfract = (l_float32)npix / (l_float32)total;
|
|
*pcolorfract = (l_float32)ncolor / (l_float32)npix;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixNumSignificantGrayColors()
|
|
*
|
|
* Input: pixs (8 bpp gray)
|
|
* darkthresh (dark threshold for minimum intensity to be
|
|
* considered; typ. 20)
|
|
* lightthresh (threshold near white, for maximum intensity
|
|
* to be considered; typ. 236)
|
|
* minfract (minimum fraction of all pixels to include a level
|
|
* as significant; typ. 0.0001)
|
|
* factor (subsample factor; integer >= 1)
|
|
* &ncolors (<return> number of significant colors; 0 on error)
|
|
* Return: 0 if OK, 1 on error
|
|
*
|
|
* Notes:
|
|
* (1) This function is asking the question: how many perceptually
|
|
* significant gray color levels is in this pix?
|
|
* A color level must meet 3 criteria to be significant:
|
|
* - it can't be too close to black
|
|
* - it can't be too close to white
|
|
* - it must have at least some minimum fractional population
|
|
* (2) Use -1 for default values for darkthresh, lightthresh and minfract.
|
|
* (3) Choose default of darkthresh = 20, because variations in very
|
|
* dark pixels are not visually significant.
|
|
* (4) Choose default of lightthresh = 236, because document images
|
|
* that have been jpeg'd typically have near-white pixels in the
|
|
* 8x8 jpeg blocks, and these should not be counted. It is desirable
|
|
* to obtain a clean image by quantizing this noise away.
|
|
*/
|
|
l_int32
|
|
pixNumSignificantGrayColors(PIX *pixs,
|
|
l_int32 darkthresh,
|
|
l_int32 lightthresh,
|
|
l_float32 minfract,
|
|
l_int32 factor,
|
|
l_int32 *pncolors)
|
|
{
|
|
l_int32 i, w, h, count, mincount, ncolors;
|
|
NUMA *na;
|
|
|
|
PROCNAME("pixNumSignificantGrayColors");
|
|
|
|
if (!pncolors)
|
|
return ERROR_INT("&ncolors not defined", procName, 1);
|
|
*pncolors = 0;
|
|
if (!pixs || pixGetDepth(pixs) != 8)
|
|
return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
|
|
if (darkthresh < 0) darkthresh = 20; /* defaults */
|
|
if (lightthresh < 0) lightthresh = 236;
|
|
if (minfract < 0.0) minfract = 0.0001;
|
|
if (minfract > 1.0)
|
|
return ERROR_INT("minfract > 1.0", procName, 1);
|
|
if (lightthresh > 255 || darkthresh >= lightthresh)
|
|
return ERROR_INT("invalid thresholds", procName, 1);
|
|
if (factor < 1) factor = 1;
|
|
|
|
pixGetDimensions(pixs, &w, &h, NULL);
|
|
mincount = (l_int32)(minfract * w * h);
|
|
if ((na = pixGetGrayHistogram(pixs, factor)) == NULL)
|
|
return ERROR_INT("na not made", procName, 1);
|
|
ncolors = 2; /* add in black and white */
|
|
for (i = darkthresh; i <= lightthresh; i++) {
|
|
numaGetIValue(na, i, &count);
|
|
if (count >= mincount)
|
|
ncolors++;
|
|
}
|
|
|
|
*pncolors = ncolors;
|
|
numaDestroy(&na);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixColorsForQuantization()
|
|
* Input: pixs (8 bpp gray or 32 bpp rgb; with or without colormap)
|
|
* thresh (binary threshold on edge gradient; 0 for default)
|
|
* &ncolors (<return> the number of colors found)
|
|
* &iscolor (<optional return> 1 if significant color is found;
|
|
* 0 otherwise. If pixs is 8 bpp, this is 0)
|
|
* debug (1 to output masked image that is tested for colors;
|
|
* 0 otherwise)
|
|
* Return: 0 if OK, 1 on error.
|
|
*
|
|
* Notes:
|
|
* (1) Grayscale and color quantization is often useful to achieve highly
|
|
* compressed images with little visible distortion. However,
|
|
* gray or color washes (regions of low gradient) can defeat
|
|
* this approach to high compression. How can one determine
|
|
* if an image is expected to compress well using gray or
|
|
* color quantization? We use the fact that
|
|
* - gray washes, when quantized with less than 50 intensities,
|
|
* have posterization (visible boundaries between regions
|
|
* of uniform 'color') and poor lossless compression
|
|
* - color washes, when quantized with level 4 octcubes,
|
|
* typically result in both posterization and the occupancy
|
|
* of many level 4 octcubes.
|
|
* (2) This function finds a measure of the number of colors that are
|
|
* found in low-gradient regions of an image. By its
|
|
* magnitude relative to some threshold (not specified in
|
|
* this function), it gives a good indication of whether
|
|
* quantization will generate posterization. This number
|
|
* is larger for images with regions of slowly varying
|
|
* intensity (if 8 bpp) or color (if rgb). Such images, if
|
|
* quantized, may require dithering to avoid posterization,
|
|
* and lossless compression is then expected to be poor.
|
|
* (3) Images can have colors either intrinsically or as jpeg
|
|
* compression artifacts. This function effectively ignores
|
|
* jpeg quantization noise in the white background of grayscale
|
|
* or color images.
|
|
* (4) The number of colors in the low-gradient regions increases
|
|
* monotonically with the threshold @thresh on the edge gradient.
|
|
* (5) If pixs has a colormap, the number of colors returned is
|
|
* the number in the colormap.
|
|
* (6) The image is tested for color. If there is very little color,
|
|
* it is thresholded to gray and the number of gray levels in
|
|
* the low gradient regions is found. If the image has color,
|
|
* the number of occupied level 4 octcubes is found.
|
|
* (7) When using the default threshold on the gradient (15),
|
|
* images (both gray and rgb) where ncolors is greater than
|
|
* about 15 will compress poorly with either lossless
|
|
* compression or dithered quantization, and they may be
|
|
* posterized with non-dithered quantization.
|
|
* (8) For grayscale images, or images without significant color,
|
|
* this returns the number of significant gray levels in
|
|
* the low-gradient regions. The actual number of gray levels
|
|
* can be large due to jpeg compression noise in the background.
|
|
* (9) Similarly, for color images, the actual number of different
|
|
* (r,g,b) colors in the low-gradient regions (rather than the
|
|
* number of occupied level 4 octcubes) can be quite large, e.g.,
|
|
* due to jpeg compression noise, even for regions that appear
|
|
* to be of a single color. By quantizing to level 4 octcubes,
|
|
* most of these superfluous colors are removed from the counting.
|
|
*/
|
|
l_int32
|
|
pixColorsForQuantization(PIX *pixs,
|
|
l_int32 thresh,
|
|
l_int32 *pncolors,
|
|
l_int32 *piscolor,
|
|
l_int32 debug)
|
|
{
|
|
l_int32 w, h, d, minside, factor;
|
|
l_float32 pixfract, colorfract;
|
|
PIX *pixt, *pixsc, *pixg, *pixe, *pixb, *pixm;
|
|
PIXCMAP *cmap;
|
|
|
|
PROCNAME("pixColorsForQuantization");
|
|
|
|
if (!pncolors)
|
|
return ERROR_INT("&ncolors not defined", procName, 1);
|
|
*pncolors = 0;
|
|
if (!pixs)
|
|
return ERROR_INT("pixs not defined", procName, 1);
|
|
if ((cmap = pixGetColormap(pixs)) != NULL) {
|
|
*pncolors = pixcmapGetCount(cmap);
|
|
if (piscolor)
|
|
pixcmapHasColor(cmap, piscolor);
|
|
return 0;
|
|
}
|
|
|
|
pixGetDimensions(pixs, &w, &h, &d);
|
|
if (d != 8 && d != 32)
|
|
return ERROR_INT("pixs not 8 or 32 bpp", procName, 1);
|
|
if (thresh <= 0)
|
|
thresh = 15; /* default */
|
|
if (piscolor)
|
|
*piscolor = 0;
|
|
|
|
/* First test if 32 bpp has any significant color; if not,
|
|
* convert it to gray. Colors whose average values are within
|
|
* 20 of black or 8 of white are ignored because they're not
|
|
* very 'colorful'. If less than 1/10000 of the pixels have
|
|
* significant color, consider the image to be gray. */
|
|
minside = L_MIN(w, h);
|
|
if (d == 8)
|
|
pixt = pixClone(pixs);
|
|
else { /* d == 32 */
|
|
factor = L_MAX(1, minside / 200);
|
|
pixColorFraction(pixs, 20, 248, 12, factor, &pixfract, &colorfract);
|
|
if (pixfract * colorfract < 0.0001) {
|
|
pixt = pixGetRGBComponent(pixs, COLOR_RED);
|
|
d = 8;
|
|
}
|
|
else { /* d == 32 */
|
|
pixt = pixClone(pixs);
|
|
if (piscolor)
|
|
*piscolor = 1;
|
|
}
|
|
}
|
|
|
|
/* If the smallest side is less than 1000, do not downscale.
|
|
* If it is in [1000 ... 2000), downscale by 2x. If it is >= 2000,
|
|
* downscale by 4x. Factors of 2 are chosen for speed. The
|
|
* actual resolution at which subsequent calculations take place
|
|
* is not strongly dependent on downscaling. */
|
|
factor = L_MAX(1, minside / 500);
|
|
if (factor == 1)
|
|
pixsc = pixCopy(NULL, pixt); /* to be sure pixs is unchanged */
|
|
else if (factor == 2 || factor == 3)
|
|
pixsc = pixScaleAreaMap2(pixt);
|
|
else
|
|
pixsc = pixScaleAreaMap(pixt, 0.25, 0.25);
|
|
|
|
/* Basic edge mask generation procedure:
|
|
* - work on a grayscale image
|
|
* - get a 1 bpp edge mask by using an edge filter and
|
|
* thresholding to get fg pixels at the edges
|
|
* - dilate with a 7x7 brick Sel to get mask over all pixels
|
|
* within a small distance from the nearest edge pixel */
|
|
if (d == 8)
|
|
pixg = pixClone(pixsc);
|
|
else /* d == 32 */
|
|
pixg = pixConvertRGBToLuminance(pixsc);
|
|
pixe = pixSobelEdgeFilter(pixg, L_ALL_EDGES);
|
|
pixb = pixThresholdToBinary(pixe, thresh);
|
|
pixInvert(pixb, pixb);
|
|
pixm = pixMorphSequence(pixb, "d7.7", 0);
|
|
|
|
/* Mask the near-edge pixels to white, and count the colors.
|
|
* If grayscale, don't count colors within 20 levels of
|
|
* black or white, and only count colors with a fraction
|
|
* of at least 1/10000 of the image pixels.
|
|
* If color, count the number of level 4 octcubes that
|
|
* contain at least 20 pixels. These magic numbers are guesses
|
|
* as to what should work, based on a small data set. Results
|
|
* should not be sensitive to their actual values. */
|
|
if (d == 8) {
|
|
pixSetMasked(pixg, pixm, 0xff);
|
|
if (debug) pixWrite("junkpix8.png", pixg, IFF_PNG);
|
|
pixNumSignificantGrayColors(pixg, 20, 236, 0.0001, 1, pncolors);
|
|
}
|
|
else { /* d == 32 */
|
|
pixSetMasked(pixsc, pixm, 0xffffffff);
|
|
if (debug) pixWrite("junkpix32.png", pixsc, IFF_PNG);
|
|
pixNumberOccupiedOctcubes(pixsc, 4, 20, -1, pncolors);
|
|
}
|
|
|
|
pixDestroy(&pixt);
|
|
pixDestroy(&pixsc);
|
|
pixDestroy(&pixg);
|
|
pixDestroy(&pixe);
|
|
pixDestroy(&pixb);
|
|
pixDestroy(&pixm);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*!
|
|
* pixNumColors()
|
|
* Input: pixs (2, 4, 8, 32 bpp)
|
|
* factor (subsampling factor; integer)
|
|
* &ncolors (<return> the number of colors found, or 0 if
|
|
* there are more than 256)
|
|
* Return: 0 if OK, 1 on error.
|
|
*
|
|
* Notes:
|
|
* (1) This returns the actual number of colors found in the image,
|
|
* even if there is a colormap. If @factor == 1 and the
|
|
* number of colors differs from the number of entries
|
|
* in the colormap, a warning is issued.
|
|
* (2) Use @factor == 1 to find the actual number of colors.
|
|
* Use @factor > 1 to quickly find the approximate number of colors.
|
|
* (3) For d = 2, 4 or 8 bpp grayscale, this returns the number
|
|
* of colors found in the image in 'ncolors'.
|
|
* (4) For d = 32 bpp (rgb), if the number of colors is
|
|
* greater than 256, this returns 0 in 'ncolors'.
|
|
*/
|
|
l_int32
|
|
pixNumColors(PIX *pixs,
|
|
l_int32 factor,
|
|
l_int32 *pncolors)
|
|
{
|
|
l_int32 w, h, d, i, j, wpl, hashsize, sum, count;
|
|
l_int32 rval, gval, bval, val;
|
|
l_int32 *inta;
|
|
l_uint32 pixel;
|
|
l_uint32 *data, *line;
|
|
PIXCMAP *cmap;
|
|
|
|
PROCNAME("pixNumColors");
|
|
|
|
if (!pncolors)
|
|
return ERROR_INT("&ncolors not defined", procName, 1);
|
|
*pncolors = 0;
|
|
if (!pixs)
|
|
return ERROR_INT("pixs not defined", procName, 1);
|
|
pixGetDimensions(pixs, &w, &h, &d);
|
|
if (d != 2 && d != 4 && d != 8 && d != 32)
|
|
return ERROR_INT("d not in {2, 4, 8, 32}", procName, 1);
|
|
if (factor < 1) factor = 1;
|
|
|
|
data = pixGetData(pixs);
|
|
wpl = pixGetWpl(pixs);
|
|
sum = 0;
|
|
if (d != 32) { /* grayscale */
|
|
inta = (l_int32 *)CALLOC(256, sizeof(l_int32));
|
|
for (i = 0; i < h; i += factor) {
|
|
line = data + i * wpl;
|
|
for (j = 0; j < w; j += factor) {
|
|
if (d == 8)
|
|
val = GET_DATA_BYTE(line, j);
|
|
else if (d == 4)
|
|
val = GET_DATA_QBIT(line, j);
|
|
else /* d == 2 */
|
|
val = GET_DATA_DIBIT(line, j);
|
|
inta[val] = 1;
|
|
}
|
|
}
|
|
for (i = 0; i < 256; i++)
|
|
if (inta[i]) sum++;
|
|
*pncolors = sum;
|
|
FREE(inta);
|
|
|
|
if (factor == 1 && ((cmap = pixGetColormap(pixs)) != NULL)) {
|
|
count = pixcmapGetCount(cmap);
|
|
if (sum != count)
|
|
L_WARNING_INT("colormap size %d differs from actual colors",
|
|
procName, count);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 32 bpp rgb; quit if we get above 256 colors */
|
|
hashsize = 5507; /* big and prime; collisions are not likely */
|
|
inta = (l_int32 *)CALLOC(hashsize, sizeof(l_int32));
|
|
for (i = 0; i < h; i += factor) {
|
|
line = data + i * wpl;
|
|
for (j = 0; j < w; j += factor) {
|
|
pixel = line[j];
|
|
extractRGBValues(pixel, &rval, &gval, &bval);
|
|
val = (137 * rval + 269 * gval + 353 * bval) % hashsize;
|
|
if (inta[val] == 0) {
|
|
inta[val] = 1;
|
|
sum++;
|
|
if (sum > 256) {
|
|
FREE(inta);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*pncolors = sum;
|
|
FREE(inta);
|
|
return 0;
|
|
}
|
|
|
|
|