blob: 9c6ad10643e1ef522c55d2ec6ff73cb1b055b64b [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.
*====================================================================*/
/*
* grayquant.c
*
* Thresholding from 8 bpp to 1 bpp
*
* Floyd-Steinberg dithering to binary
* PIX *pixDitherToBinary()
* PIX *pixDitherToBinarySpec()
*
* Simple (pixelwise) binarization with fixed threshold
* PIX *pixThresholdToBinary()
*
* Binarization with variable threshold
* PIX *pixVarThresholdToBinary()
*
* Slower implementation of Floyd-Steinberg dithering, using LUTs
* PIX *pixDitherToBinaryLUT()
*
* Generate a binary mask from pixels of particular values
* PIX *pixGenerateMaskByValue()
* PIX *pixGenerateMaskByBand()
*
* Thresholding from 8 bpp to 2 bpp
*
* Dithering to 2 bpp
* PIX *pixDitherTo2bpp()
* PIX *pixDitherTo2bppSpec()
*
* Simple (pixelwise) thresholding to 2 bpp with optional cmap
* PIX *pixThresholdTo2bpp()
*
* Simple (pixelwise) thresholding from 8 bpp to 4 bpp
* PIX *pixThresholdTo4bpp()
*
* Simple (pixelwise) quantization on 8 bpp grayscale
* PIX *pixThresholdOn8bpp()
*
* Arbitrary (pixelwise) thresholding from 8 bpp to 2, 4 or 8 bpp
* PIX *pixThresholdGrayArb()
*
* Quantization tables for linear thresholds of grayscale images
* l_int32 *makeGrayQuantIndexTable()
* l_int32 *makeGrayQuantTargetTable()
*
* Quantization table for arbitrary thresholding of grayscale images
* l_int32 makeGrayQuantTableArb()
* l_int32 makeGrayQuantColormapArb()
*
* Thresholding from 32 bpp rgb to 1 bpp
* (really color quantization, but it's better placed in this file)
* PIX *pixGenerateMaskByBand32()
* PIX *pixGenerateMaskByDiscr32()
*
* Histogram-based grayscale quantization
* PIX *pixGrayQuantFromHisto()
* static l_int32 numaFillCmapFromHisto()
*
* Color quantize grayscale image using existing colormap
* PIX *pixGrayQuantFromCmap()
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "allheaders.h"
static l_int32 numaFillCmapFromHisto(NUMA *na, PIXCMAP *cmap,
l_float32 minfract, l_int32 maxsize,
l_int32 **plut);
/*------------------------------------------------------------------*
* Binarization by Floyd-Steinberg dithering *
*------------------------------------------------------------------*/
/*!
* pixDitherToBinary()
*
* Input: pixs
* Return: pixd (dithered binary), or null on error
*
* The Floyd-Steinberg error diffusion dithering algorithm
* binarizes an 8 bpp grayscale image to a threshold of 128.
* If a pixel has a value above 127, it is binarized to white
* and the excess (below 255) is subtracted from three
* neighboring pixels in the fractions 3/8 to (i, j+1),
* 3/8 to (i+1, j) and 1/4 to (i+1,j+1), truncating to 0
* if necessary. Likewise, if it the pixel has a value
* below 128, it is binarized to black and the excess above 0
* is added to the neighboring pixels, truncating to 255 if necessary.
*
* This function differs from straight dithering in that it allows
* clipping of grayscale to 0 or 255 if the values are
* sufficiently close, without distribution of the excess.
* This uses default values to specify the range of lower
* and upper values (near 0 and 255, rsp) that are clipped
* to black and white without propagating the excess.
* Not propagating the excess has the effect of reducing the
* snake patterns in parts of the image that are nearly black or white;
* however, it also prevents the attempt to reproduce gray for those values.
*
* The implementation is straightforward. It uses a pair of
* line buffers to avoid changing pixs. It is about 2x faster
* than the implementation using LUTs.
*/
PIX *
pixDitherToBinary(PIX *pixs)
{
PROCNAME("pixDitherToBinary");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
return pixDitherToBinarySpec(pixs, DEFAULT_CLIP_LOWER_1,
DEFAULT_CLIP_UPPER_1);
}
/*!
* pixDitherToBinarySpec()
*
* Input: pixs
* lowerclip (lower clip distance to black; use 0 for default)
* upperclip (upper clip distance to white; use 0 for default)
* Return: pixd (dithered binary), or null on error
*
* Notes:
* (1) See comments above in pixDitherToBinary() for details.
* (2) The input parameters lowerclip and upperclip specify the range
* of lower and upper values (near 0 and 255, rsp) that are
* clipped to black and white without propagating the excess.
* For that reason, lowerclip and upperclip should be small numbers.
*/
PIX *
pixDitherToBinarySpec(PIX *pixs,
l_int32 lowerclip,
l_int32 upperclip)
{
l_int32 w, h, d, wplt, wpld;
l_uint32 *datat, *datad;
l_uint32 *bufs1, *bufs2;
PIX *pixt, *pixd;
PROCNAME("pixDitherToBinarySpec");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
if (lowerclip < 0 || lowerclip > 255)
return (PIX *)ERROR_PTR("invalid value for lowerclip", procName, NULL);
if (upperclip < 0 || upperclip > 255)
return (PIX *)ERROR_PTR("invalid value for upperclip", procName, NULL);
if ((pixd = pixCreate(w, h, 1)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
pixCopyResolution(pixd, pixs);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
/* Remove colormap if it exists */
pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
/* Two line buffers, 1 for current line and 2 for next line */
if ((bufs1 = (l_uint32 *)CALLOC(wplt, sizeof(l_uint32))) == NULL)
return (PIX *)ERROR_PTR("bufs1 not made", procName, NULL);
if ((bufs2 = (l_uint32 *)CALLOC(wplt, sizeof(l_uint32))) == NULL)
return (PIX *)ERROR_PTR("bufs2 not made", procName, NULL);
ditherToBinaryLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
lowerclip, upperclip);
FREE(bufs1);
FREE(bufs2);
pixDestroy(&pixt);
return pixd;
}
/*------------------------------------------------------------------*
* Simple (pixelwise) binarization with fixed threshold *
*------------------------------------------------------------------*/
/*!
* pixThresholdToBinary()
*
* Input: pixs (4 or 8 bpp)
* threshold value
* Return: pixd (1 bpp), or null on error
*
* Notes:
* (1) If the source pixel is less than the threshold value,
* the dest will be 1; otherwise, it will be 0
*/
PIX *
pixThresholdToBinary(PIX *pixs,
l_int32 thresh)
{
l_int32 d, w, h, wplt, wpld;
l_uint32 *datat, *datad;
PIX *pixt, *pixd;
PROCNAME("pixThresholdToBinary");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 4 && d != 8)
return (PIX *)ERROR_PTR("pixs must be 4 or 8 bpp", procName, NULL);
if (thresh < 0)
return (PIX *)ERROR_PTR("thresh must be non-negative", procName, NULL);
if (d == 4 && thresh > 16)
return (PIX *)ERROR_PTR("4 bpp thresh not in {0-16}", procName, NULL);
if (d == 8 && thresh > 256)
return (PIX *)ERROR_PTR("8 bpp thresh not in {0-256}", procName, NULL);
if ((pixd = pixCreate(w, h, 1)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
pixCopyResolution(pixd, pixs);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
/* Remove colormap if it exists. If there is a colormap,
* pixt will be 8 bpp regardless of the depth of pixs. */
pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
if (pixGetColormap(pixs) && d == 4) { /* promoted to 8 bpp */
d = 8;
thresh *= 16;
}
thresholdToBinaryLow(datad, w, h, wpld, datat, d, wplt, thresh);
pixDestroy(&pixt);
return pixd;
}
/*------------------------------------------------------------------*
* Binarization with variable threshold *
*------------------------------------------------------------------*/
/*!
* pixVarThresholdToBinary()
*
* Input: pixs (8 bpp)
* pixg (8 bpp; contains threshold values for each pixel)
* Return: pixd (1 bpp), or null on error
*
* Notes:
* (1) If the pixel in pixs is less than the corresponding pixel
* in pixg, the dest will be 1; otherwise it will be 0.
*/
PIX *
pixVarThresholdToBinary(PIX *pixs,
PIX *pixg)
{
l_int32 i, j, vals, valg, w, h, d, wpls, wplg, wpld;
l_uint32 *datas, *datag, *datad, *lines, *lineg, *lined;
PIX *pixd;
PROCNAME("pixVarThresholdToBinary");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (!pixg)
return (PIX *)ERROR_PTR("pixg not defined", procName, NULL);
if (!pixSizesEqual(pixs, pixg))
return (PIX *)ERROR_PTR("pix sizes not equal", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
pixd = pixCreate(w, h, 1);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
datas = pixGetData(pixs);
wpls = pixGetWpl(pixs);
datag = pixGetData(pixg);
wplg = pixGetWpl(pixg);
for (i = 0; i < h; i++) {
lines = datas + i * wpls;
lineg = datag + i * wplg;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
vals = GET_DATA_BYTE(lines, j);
valg = GET_DATA_BYTE(lineg, j);
if (vals < valg)
SET_DATA_BIT(lined, j);
}
}
return pixd;
}
/*--------------------------------------------------------------------*
* Slower implementation of binarization by dithering using LUTs *
*--------------------------------------------------------------------*/
/*!
* pixDitherToBinaryLUT()
*
* Input: pixs
* lowerclip (lower clip distance to black; use -1 for default)
* upperclip (upper clip distance to white; use -1 for default)
* Return: pixd (dithered binary), or null on error
*
* This implementation is deprecated. You should use pixDitherToBinary().
*
* See comments in pixDitherToBinary()
*
* This implementation additionally uses three lookup tables to
* generate the output pixel value and the excess or deficit
* carried over to the neighboring pixels.
*/
PIX *
pixDitherToBinaryLUT(PIX *pixs,
l_int32 lowerclip,
l_int32 upperclip)
{
l_int32 w, h, d, wplt, wpld;
l_int32 *tabval, *tab38, *tab14;
l_uint32 *datat, *datad;
l_uint32 *bufs1, *bufs2;
PIX *pixt, *pixd;
PROCNAME("pixDitherToBinaryLUT");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
if (lowerclip < 0)
lowerclip = DEFAULT_CLIP_LOWER_1;
if (upperclip < 0)
upperclip = DEFAULT_CLIP_UPPER_1;
if ((pixd = pixCreate(w, h, 1)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
pixCopyResolution(pixd, pixs);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
/* Remove colormap if it exists */
pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
/* Two line buffers, 1 for current line and 2 for next line */
if ((bufs1 = (l_uint32 *)CALLOC(wplt, sizeof(l_uint32))) == NULL)
return (PIX *)ERROR_PTR("bufs1 not made", procName, NULL);
if ((bufs2 = (l_uint32 *)CALLOC(wplt, sizeof(l_uint32))) == NULL)
return (PIX *)ERROR_PTR("bufs2 not made", procName, NULL);
/* 3 lookup tables: 1-bit value, (3/8)excess, and (1/4)excess */
make8To1DitherTables(&tabval, &tab38, &tab14, lowerclip, upperclip);
ditherToBinaryLUTLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
tabval, tab38, tab14);
FREE(bufs1);
FREE(bufs2);
FREE(tabval);
FREE(tab38);
FREE(tab14);
pixDestroy(&pixt);
return pixd;
}
/*--------------------------------------------------------------------*
* Generate a binary mask from pixels of particular value(s) *
*--------------------------------------------------------------------*/
/*!
* pixGenerateMaskByValue()
*
* Input: pixs (8 bpp, or colormapped)
* val (of pixels for which we set 1 in dest)
* Return: pixd (1 bpp), or null on error
*
* Notes:
* (1) @val is the gray value of the pixels that we are selecting.
* (2) If pixs is colormapped, this first removes the colormap to
* generate an approximate grayscale value for each pixel, and
* then looks for gray pixels with the value @val.
* (3) If pixs is colormapped and you want to use @val to select
* the colormap index, you must first call pixDestroyColormap(pixs)
* to remove the colormap from pixs.
*/
PIX *
pixGenerateMaskByValue(PIX *pixs,
l_int32 val)
{
l_int32 i, j, w, h, wplg, wpld;
l_uint32 *datag, *datad, *lineg, *lined;
PIX *pixg, *pixd;
PROCNAME("pixGenerateMaskByValue");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("not 8 bpp", procName, NULL);
if (val < 0 || val > 255)
return (PIX *)ERROR_PTR("val out of 8 bpp range", procName, NULL);
if (pixGetColormap(pixs))
pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
else
pixg = pixClone(pixs);
pixGetDimensions(pixg, &w, &h, NULL);
pixd = pixCreate(w, h, 1);
pixCopyResolution(pixd, pixg);
datag = pixGetData(pixg);
wplg = pixGetWpl(pixg);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
lineg = datag + i * wplg;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
if (GET_DATA_BYTE(lineg, j) == val)
SET_DATA_BIT(lined, j);
}
}
pixDestroy(&pixg);
return pixd;
}
/*!
* pixGenerateMaskByBand()
*
* Input: pixs (8 bpp, or colormapped)
* lower, upper (two pixel values from which a range, either
* between (inband) or outside of (!inband),
* determines which pixels in pixs cause us to
* set a 1 in the dest mask)
* inband (1 for finding pixels in [lower, upper];
* 0 for finding pixels in [0, lower) union (upper, 255])
* Return: pixd (1 bpp), or null on error
*
* Notes:
* (1) Generates a 1 bpp mask pixd, the same size as pixs, where
* the fg pixels in the mask are those either within the specified
* band (for inband == 1) or outside the specified band
* (for inband == 0).
* (2) If pixs is colormapped, this first removes the colormap to
* generate an approximate grayscale value for each pixel, and
* then looks for gray pixels in or out of the given band.
* (3) If pixs is colormapped and you want to the band of values to
* select the colormap indices directly, you must first call
* pixDestroyColormap(pixs) to remove the colormap from pixs.
*/
PIX *
pixGenerateMaskByBand(PIX *pixs,
l_int32 lower,
l_int32 upper,
l_int32 inband)
{
l_int32 i, j, w, h, wplg, wpld, val;
l_uint32 *datag, *datad, *lineg, *lined;
PIX *pixg, *pixd;
PROCNAME("pixGenerateMaskByBand");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("not 8 bpp", procName, NULL);
if (lower < 0 || upper > 255)
return (PIX *)ERROR_PTR("invalid lower and/or upper", procName, NULL);
if (lower > upper)
return (PIX *)ERROR_PTR("lower > upper!", procName, NULL);
if (pixGetColormap(pixs))
pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
else
pixg = pixClone(pixs);
pixGetDimensions(pixg, &w, &h, NULL);
pixd = pixCreate(w, h, 1);
pixCopyResolution(pixd, pixg);
datag = pixGetData(pixg);
wplg = pixGetWpl(pixg);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
lineg = datag + i * wplg;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
val = GET_DATA_BYTE(lineg, j);
if (inband) {
if (val >= lower && val <= upper)
SET_DATA_BIT(lined, j);
}
else { /* out of band */
if (val < lower || val > upper)
SET_DATA_BIT(lined, j);
}
}
}
pixDestroy(&pixg);
return pixd;
}
/*------------------------------------------------------------------*
* Thresholding to 2 bpp by dithering *
*------------------------------------------------------------------*/
/*!
* pixDitherTo2bpp()
*
* Input: pixs (8 bpp)
* cmapflag (1 to generate a colormap)
* Return: pixd (dithered 2 bpp), or null on error
*
* An analog of the Floyd-Steinberg error diffusion dithering
* algorithm is used to "dibitize" an 8 bpp grayscale image
* to 2 bpp, using equally spaced gray values of 0, 85, 170, and 255,
* which are served by thresholds of 43, 128 and 213.
* If cmapflag == 1, the colormap values are set to 0, 85, 170 and 255.
* If a pixel has a value between 0 and 42, it is dibitized
* to 0, and the excess (above 0) is added to the
* three neighboring pixels, in the fractions 3/8 to (i, j+1),
* 3/8 to (i+1, j) and 1/4 to (i+1, j+1), truncating to 255 if
* necessary. If a pixel has a value between 43 and 127, it is
* dibitized to 1, and the excess (above 85) is added to the three
* neighboring pixels as before. If the value is below 85, the
* excess is subtracted. With a value between 128
* and 212, it is dibitized to 2, with the excess on either side
* of 170 distributed as before. Finally, with a value between
* 213 and 255, it is dibitized to 3, with the excess (below 255)
* subtracted from the neighbors. We always truncate to 0 or 255.
* The details can be seen in the lookup table generation.
*
* This function differs from straight dithering in that it allows
* clipping of grayscale to 0 or 255 if the values are
* sufficiently close, without distribution of the excess.
* This uses default values (from pix.h) to specify the range of lower
* and upper values (near 0 and 255, rsp) that are clipped to black
* and white without propagating the excess.
* Not propagating the excess has the effect of reducing the snake
* patterns in parts of the image that are nearly black or white;
* however, it also prevents any attempt to reproduce gray for those values.
*
* The implementation uses 3 lookup tables for simplicity, and
* a pair of line buffers to avoid modifying pixs.
*/
PIX *
pixDitherTo2bpp(PIX *pixs,
l_int32 cmapflag)
{
PROCNAME("pixDitherTo2bpp");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
return pixDitherTo2bppSpec(pixs, DEFAULT_CLIP_LOWER_2,
DEFAULT_CLIP_UPPER_2, cmapflag);
}
/*!
* pixDitherTo2bppSpec()
*
* Input: pixs (8 bpp)
* lowerclip (lower clip distance to black; use 0 for default)
* upperclip (upper clip distance to white; use 0 for default)
* cmapflag (1 to generate a colormap)
* Return: pixd (dithered 2 bpp), or null on error
*
* Notes:
* (1) See comments above in pixDitherTo2bpp() for details.
* (2) The input parameters lowerclip and upperclip specify the range
* of lower and upper values (near 0 and 255, rsp) that are
* clipped to black and white without propagating the excess.
* For that reason, lowerclip and upperclip should be small numbers.
*/
PIX *
pixDitherTo2bppSpec(PIX *pixs,
l_int32 lowerclip,
l_int32 upperclip,
l_int32 cmapflag)
{
l_int32 w, h, d, wplt, wpld;
l_int32 *tabval, *tab38, *tab14;
l_uint32 *datat, *datad;
l_uint32 *bufs1, *bufs2;
PIX *pixt, *pixd;
PIXCMAP *cmap;
PROCNAME("pixDitherTo2bppSpec");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
if (lowerclip < 0 || lowerclip > 255)
return (PIX *)ERROR_PTR("invalid value for lowerclip", procName, NULL);
if (upperclip < 0 || upperclip > 255)
return (PIX *)ERROR_PTR("invalid value for upperclip", procName, NULL);
if ((pixd = pixCreate(w, h, 2)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
pixCopyResolution(pixd, pixs);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
/* If there is a colormap, remove it */
pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
/* Two line buffers, 1 for current line and 2 for next line */
if ((bufs1 = (l_uint32 *)CALLOC(wplt, sizeof(l_uint32))) == NULL)
return (PIX *)ERROR_PTR("bufs1 not made", procName, NULL);
if ((bufs2 = (l_uint32 *)CALLOC(wplt, sizeof(l_uint32))) == NULL)
return (PIX *)ERROR_PTR("bufs2 not made", procName, NULL);
/* 3 lookup tables: 2-bit value, (3/8)excess, and (1/4)excess */
make8To2DitherTables(&tabval, &tab38, &tab14, lowerclip, upperclip);
ditherTo2bppLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
tabval, tab38, tab14);
if (cmapflag) {
cmap = pixcmapCreateLinear(2, 4);
pixSetColormap(pixd, cmap);
}
FREE(bufs1);
FREE(bufs2);
FREE(tabval);
FREE(tab38);
FREE(tab14);
pixDestroy(&pixt);
return pixd;
}
/*--------------------------------------------------------------------*
* Simple (pixelwise) thresholding to 2 bpp with optional colormap *
*--------------------------------------------------------------------*/
/*!
* pixThresholdTo2bpp()
*
* Input: pixs (8 bpp)
* nlevels (equally spaced; must be between 2 and 4)
* cmapflag (1 to build colormap; 0 otherwise)
* Return: pixd (2 bpp, optionally with colormap), or null on error
*
* Notes:
* (1) Valid values for nlevels is the set {2, 3, 4}.
* (2) Any colormap on the input pixs is removed to 8 bpp grayscale.
* (3) This function is typically invoked with cmapflag == 1.
* In the situation where no colormap is desired, nlevels is
* ignored and pixs is thresholded to 4 levels.
* (4) The target output colors are equally spaced, with the
* darkest at 0 and the lightest at 255. The thresholds are
* chosen halfway between adjacent output values. A table
* is built that specifies the mapping from src to dest.
* (5) If cmapflag == 1, a colormap of size 'nlevels' is made,
* and the pixel values in pixs are replaced by their
* appropriate color indices. The number of holdouts,
* 4 - nlevels, will be between 0 and 2.
* (6) If you don't want the thresholding to be equally spaced,
* either first transform the 8 bpp src using pixGammaTRC().
* or, if cmapflag == 1, after calling this function you can use
* pixcmapResetColor() to change any individual colors.
* (7) If a colormap is generated, it will specify (to display
* programs) exactly how each level is to be represented in RGB
* space. When representing text, 3 levels is far better than
* 2 because of the antialiasing of the single gray level,
* and 4 levels (black, white and 2 gray levels) is getting
* close to the perceptual quality of a (nearly continuous)
* grayscale image. With 2 bpp, you can set up a colormap
* and allocate from 2 to 4 levels to represent antialiased text.
* Any left over colormap entries can be used for coloring regions.
* For the same number of levels, the file size of a 2 bpp image
* is about 10% smaller than that of a 4 bpp result for the same
* number of levels. For both 2 bpp and 4 bpp, using 4 levels you
* get compression far better than that of jpeg, because the
* quantization to 4 levels will remove the jpeg ringing in the
* background near character edges.
*/
PIX *
pixThresholdTo2bpp(PIX *pixs,
l_int32 nlevels,
l_int32 cmapflag)
{
l_int32 *qtab;
l_int32 w, h, d, wplt, wpld;
l_uint32 *datat, *datad;
PIX *pixt, *pixd;
PIXCMAP *cmap;
PROCNAME("pixThresholdTo2bpp");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (nlevels < 2 || nlevels > 4)
return (PIX *)ERROR_PTR("nlevels not in {2, 3, 4}", procName, NULL);
/* Make the appropriate table */
if (cmapflag)
qtab = makeGrayQuantIndexTable(nlevels);
else
qtab = makeGrayQuantTargetTable(4, 2);
if ((pixd = pixCreate(w, h, 2)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
pixCopyResolution(pixd, pixs);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
if (cmapflag) { /* hold out (4 - nlevels) cmap entries */
cmap = pixcmapCreateLinear(2, nlevels);
pixSetColormap(pixd, cmap);
}
/* If there is a colormap in the src, remove it */
pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
thresholdTo2bppLow(datad, h, wpld, datat, wplt, qtab);
if (qtab) FREE(qtab);
pixDestroy(&pixt);
return pixd;
}
/*----------------------------------------------------------------------*
* Simple (pixelwise) thresholding to 4 bpp *
*----------------------------------------------------------------------*/
/*!
* pixThresholdTo4bpp()
*
* Input: pixs (8 bpp, can have colormap)
* nlevels (equally spaced; must be between 2 and 16)
* cmapflag (1 to build colormap; 0 otherwise)
* Return: pixd (4 bpp, optionally with colormap), or null on error
*
* Notes:
* (1) Valid values for nlevels is the set {2, ... 16}.
* (2) Any colormap on the input pixs is removed to 8 bpp grayscale.
* (3) This function is typically invoked with cmapflag == 1.
* In the situation where no colormap is desired, nlevels is
* ignored and pixs is thresholded to 16 levels.
* (4) The target output colors are equally spaced, with the
* darkest at 0 and the lightest at 255. The thresholds are
* chosen halfway between adjacent output values. A table
* is built that specifies the mapping from src to dest.
* (5) If cmapflag == 1, a colormap of size 'nlevels' is made,
* and the pixel values in pixs are replaced by their
* appropriate color indices. The number of holdouts,
* 16 - nlevels, will be between 0 and 14.
* (6) If you don't want the thresholding to be equally spaced,
* either first transform the 8 bpp src using pixGammaTRC().
* or, if cmapflag == 1, after calling this function you can use
* pixcmapResetColor() to change any individual colors.
* (7) If a colormap is generated, it will specify, to display
* programs, exactly how each level is to be represented in RGB
* space. When representing text, 3 levels is far better than
* 2 because of the antialiasing of the single gray level,
* and 4 levels (black, white and 2 gray levels) is getting
* close to the perceptual quality of a (nearly continuous)
* grayscale image. Therefore, with 4 bpp, you can set up a
* colormap, allocate a relatively small fraction of the 16
* possible values to represent antialiased text, and use the
* other colormap entries for other things, such as coloring
* text or background. Two other reasons for using a small number
* of gray values for antialiased text are (1) PNG compression
* gets worse as the number of levels that are used is increased,
* and (2) using a small number of levels will filter out most of
* the jpeg ringing that is typically introduced near sharp edges
* of text. This filtering is partly responsible for the improved
* compression.
*/
PIX *
pixThresholdTo4bpp(PIX *pixs,
l_int32 nlevels,
l_int32 cmapflag)
{
l_int32 *qtab;
l_int32 w, h, d, wplt, wpld;
l_uint32 *datat, *datad;
PIX *pixt, *pixd;
PIXCMAP *cmap;
PROCNAME("pixThresholdTo4bpp");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (nlevels < 2 || nlevels > 16)
return (PIX *)ERROR_PTR("nlevels not in [2,...,16]", procName, NULL);
/* Make the appropriate table */
if (cmapflag)
qtab = makeGrayQuantIndexTable(nlevels);
else
qtab = makeGrayQuantTargetTable(16, 4);
if ((pixd = pixCreate(w, h, 4)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
pixCopyResolution(pixd, pixs);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
if (cmapflag) { /* hold out (16 - nlevels) cmap entries */
cmap = pixcmapCreateLinear(4, nlevels);
pixSetColormap(pixd, cmap);
}
/* If there is a colormap in the src, remove it */
pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
thresholdTo4bppLow(datad, h, wpld, datat, wplt, qtab);
if (qtab) FREE(qtab);
pixDestroy(&pixt);
return pixd;
}
/*----------------------------------------------------------------------*
* Simple (pixelwise) thresholding on 8 bpp with optional colormap *
*----------------------------------------------------------------------*/
/*!
* pixThresholdOn8bpp()
*
* Input: pixs (8 bpp, can have colormap)
* nlevels (equally spaced; must be between 2 and 256)
* cmapflag (1 to build colormap; 0 otherwise)
* Return: pixd (8 bpp, optionally with colormap), or null on error
*
* Notes:
* (1) Valid values for nlevels is the set {2,...,256}.
* (2) Any colormap on the input pixs is removed to 8 bpp grayscale.
* (3) If cmapflag == 1, a colormap of size 'nlevels' is made,
* and the pixel values in pixs are replaced by their
* appropriate color indices. Otherwise, the pixel values
* are the actual thresholded (i.e., quantized) grayscale values.
* (4) If you don't want the thresholding to be equally spaced,
* first transform the input 8 bpp src using pixGammaTRC().
*/
PIX *
pixThresholdOn8bpp(PIX *pixs,
l_int32 nlevels,
l_int32 cmapflag)
{
l_int32 *qtab; /* quantization table */
l_int32 i, j, w, h, wpld, val, newval;
l_uint32 *datad, *lined;
PIX *pixd;
PIXCMAP *cmap;
PROCNAME("pixThresholdOn8bpp");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (nlevels < 2 || nlevels > 256)
return (PIX *)ERROR_PTR("nlevels not in [2,...,256]", procName, NULL);
if (cmapflag)
qtab = makeGrayQuantIndexTable(nlevels);
else
qtab = makeGrayQuantTargetTable(nlevels, 8);
/* Get a new pixd; if there is a colormap in the src, remove it */
if (pixGetColormap(pixs))
pixd = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
else
pixd = pixCopy(NULL, pixs);
if (cmapflag) { /* hold out (256 - nlevels) cmap entries */
cmap = pixcmapCreateLinear(8, nlevels);
pixSetColormap(pixd, cmap);
}
pixGetDimensions(pixd, &w, &h, NULL);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
for (i = 0; i < h; i++) {
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
val = GET_DATA_BYTE(lined, j);
newval = qtab[val];
SET_DATA_BYTE(lined, j, newval);
}
}
if (qtab) FREE(qtab);
return pixd;
}
/*----------------------------------------------------------------------*
* Arbitrary (pixelwise) thresholding from 8 bpp to 2, 4 or 8 bpp *
*----------------------------------------------------------------------*/
/*!
* pixThresholdGrayArb()
*
* Input: pixs (8 bpp grayscale; can have colormap)
* edgevals (string giving edge value of each bin)
* outdepth (0, 2, 4 or 8 bpp; 0 is default for min depth)
* use_average (1 if use the average pixel value in colormap)
* setblack (1 if darkest color is set to black)
* setwhite (1 if lightest color is set to white)
* Return: pixd (2, 4 or 8 bpp quantized image with colormap),
* or null on error
*
* Notes:
* (1) This function allows exact specification of the quantization bins.
* The string @edgevals is a space-separated set of values
* specifying the dividing points between output quantization bins.
* These threshold values are assigned to the bin with higher
* values, so that each of them is the smallest value in their bin.
* (2) The output image (pixd) depth is specified by @outdepth. The
* number of bins is the number of edgevals + 1. The
* relation between outdepth and the number of bins is:
* outdepth = 2 nbins <= 4
* outdepth = 4 nbins <= 16
* outdepth = 8 nbins <= 256
* With @outdepth == 0, the minimum required depth for the
* given number of bins is used.
* The output pixd has a colormap.
* (3) The last 3 args determine the specific values that go into
* the colormap.
* (4) For @use_average:
* - if TRUE, the average value of pixels falling in the bin is
* chosen as the representative gray value. Otherwise,
* - if FALSE, the central value of each bin is chosen as
* the representative value.
* The colormap holds the representative value.
* (5) For @setblack, if TRUE the darkest color is set to (0,0,0).
* (6) For @setwhite, if TRUE the lightest color is set to (255,255,255).
* (7) An alternative to using this function to quantize to
* unequally-spaced bins is to first transform the 8 bpp pixs
* using pixGammaTRC(), and follow this with pixThresholdTo4bpp().
*/
PIX *
pixThresholdGrayArb(PIX *pixs,
const char *edgevals,
l_int32 outdepth,
l_int32 use_average,
l_int32 setblack,
l_int32 setwhite)
{
l_int32 *qtab;
l_int32 w, h, d, i, j, n, wplt, wpld, val, newval;
l_uint32 *datat, *datad, *linet, *lined;
NUMA *na;
PIX *pixt, *pixd;
PIXCMAP *cmap;
PROCNAME("pixThresholdGrayArb");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (!edgevals)
return (PIX *)ERROR_PTR("edgevals not defined", procName, NULL);
if (outdepth != 0 && outdepth != 2 && outdepth != 4 && outdepth != 8)
return (PIX *)ERROR_PTR("invalid outdepth", procName, NULL);
/* Parse and sort (if required) the bin edge values */
na = parseStringForNumbers(edgevals, " \t\n,");
n = numaGetCount(na);
if (n > 255)
return (PIX *)ERROR_PTR("more than 256 levels", procName, NULL);
if (outdepth == 0) {
if (n <= 3)
outdepth = 2;
else if (n <= 15)
outdepth = 4;
else
outdepth = 8;
}
else if (n + 1 > (1 << outdepth)) {
L_WARNING("outdepth too small; setting to 8 bpp", procName);
outdepth = 8;
}
numaSort(na, na, L_SORT_INCREASING);
/* Make the quantization LUT and the colormap */
makeGrayQuantTableArb(na, outdepth, &qtab, &cmap);
if (use_average) { /* use the average value in each bin */
pixcmapDestroy(&cmap);
makeGrayQuantColormapArb(pixs, qtab, outdepth, &cmap);
}
pixcmapSetBlackAndWhite(cmap, setblack, setwhite);
numaDestroy(&na);
if ((pixd = pixCreate(w, h, outdepth)) == NULL)
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
pixCopyResolution(pixd, pixs);
pixSetColormap(pixd, cmap);
datad = pixGetData(pixd);
wpld = pixGetWpl(pixd);
/* If there is a colormap in the src, remove it */
pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
datat = pixGetData(pixt);
wplt = pixGetWpl(pixt);
if (outdepth == 2)
thresholdTo2bppLow(datad, h, wpld, datat, wplt, qtab);
else if (outdepth == 4)
thresholdTo4bppLow(datad, h, wpld, datat, wplt, qtab);
else {
for (i = 0; i < h; i++) {
lined = datad + i * wpld;
linet = datat + i * wplt;
for (j = 0; j < w; j++) {
val = GET_DATA_BYTE(linet, j);
newval = qtab[val];
SET_DATA_BYTE(lined, j, newval);
}
}
}
FREE(qtab);
pixDestroy(&pixt);
return pixd;
}
/*----------------------------------------------------------------------*
* Quantization tables for linear thresholds of grayscale images *
*----------------------------------------------------------------------*/
/*!
* makeGrayQuantIndexTable()
*
* Input: nlevels (number of output levels)
* Return: table (maps input gray level to colormap index,
* or null on error)
* Notes:
* (1) 'nlevels' is some number between 2 and 256 (typically 8 or less).
* (2) The table is typically used for quantizing 2, 4 and 8 bpp
* grayscale src pix, and generating a colormapped dest pix.
*/
l_int32 *
makeGrayQuantIndexTable(l_int32 nlevels)
{
l_int32 *tab;
l_int32 i, j, thresh;
PROCNAME("makeGrayQuantIndexTable");
if ((tab = (l_int32 *)CALLOC(256, sizeof(l_int32))) == NULL)
return (l_int32 *)ERROR_PTR("calloc fail for tab", procName, NULL);
for (i = 0; i < 256; i++) {
for (j = 0; j < nlevels; j++) {
thresh = 255 * (2 * j + 1) / (2 * nlevels - 2);
if (i <= thresh) {
tab[i] = j;
/* fprintf(stderr, "tab[%d] = %d\n", i, j); */
break;
}
}
}
return tab;
}
/*!
* makeGrayQuantTargetTable()
*
* Input: nlevels (number of output levels)
* depth (of dest pix, in bpp; 2, 4 or 8 bpp)
* Return: table (maps input gray level to thresholded gray level,
* or null on error)
*
* Notes:
* (1) nlevels is some number between 2 and 2^(depth)
* (2) The table is used in two similar ways:
* - for 8 bpp, it quantizes to a given number of target levels
* - for 2 and 4 bpp, it thresholds to appropriate target values
* that will use the full dynamic range of the dest pix.
* (3) For depth = 8, the number of thresholds chosen is
* ('nlevels' - 1), and the 'nlevels' values stored in the
* table are at the two at the extreme ends, (0, 255), plus
* plus ('nlevels' - 2) values chosen at equal intervals between.
* For example, for depth = 8 and 'nlevels' = 3, the two
* threshold values are 3f and bf, and the three target pixel
* values are 0, 7f and ff.
* (4) For depth < 8, we ignore nlevels, and always use the maximum
* number of levels, which is 2^(depth).
* If you want nlevels < the maximum number, you should always
* use a colormap.
*/
l_int32 *
makeGrayQuantTargetTable(l_int32 nlevels,
l_int32 depth)
{
l_int32 *tab;
l_int32 i, j, thresh, maxval, quantval;
PROCNAME("makeGrayQuantTargetTable");
if ((tab = (l_int32 *)CALLOC(256, sizeof(l_int32))) == NULL)
return (l_int32 *)ERROR_PTR("calloc fail for tab", procName, NULL);
maxval = (1 << depth) - 1;
if (depth < 8)
nlevels = 1 << depth;
for (i = 0; i < 256; i++) {
for (j = 0; j < nlevels; j++) {
thresh = 255 * (2 * j + 1) / (2 * nlevels - 2);
if (i <= thresh) {
quantval = maxval * j / (nlevels - 1);
tab[i] = quantval;
/* fprintf(stderr, "tab[%d] = %d\n", i, tab[i]); */
break;
}
}
}
return tab;
}
/*----------------------------------------------------------------------*
* Quantization table for arbitrary thresholding of grayscale images *
*----------------------------------------------------------------------*/
/*!
* makeGrayQuantTableArb()
*
* Input: na (numa of bin boundaries)
* outdepth (of colormap: 1, 2, 4 or 8)
* &tab (<return> table mapping input gray level to cmap index)
* &cmap (<return> colormap)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) The number of bins is the count of @na + 1.
* (2) The bin boundaries in na must be sorted in increasing order.
* (3) The table is an inverse colormap: it maps input gray level
* to colormap index (the bin number).
* (4) The colormap generated here has quantized values at the
* center of each bin. If you want to use the average gray
* value of pixels within the bin, discard the colormap and
* compute it using makeGrayQuantColormapArb().
* (5) Returns an error if there are not enough levels in the
* output colormap for the number of bins. The number
* of bins must not exceed 2^outdepth.
*/
l_int32
makeGrayQuantTableArb(NUMA *na,
l_int32 outdepth,
l_int32 **ptab,
PIXCMAP **pcmap)
{
l_int32 i, j, n, jstart, ave, val;
l_int32 *tab;
PIXCMAP *cmap;
PROCNAME("makeGrayQuantTableArb");
if (!ptab)
return ERROR_INT("&tab not defined", procName, 1);
*ptab = NULL;
if (!pcmap)
return ERROR_INT("&cmap not defined", procName, 1);
*pcmap = NULL;
if (!na)
return ERROR_INT("na not defined", procName, 1);
n = numaGetCount(na);
if (n + 1 > (1 << outdepth))
return ERROR_INT("more bins than cmap levels", procName, 1);
if ((tab = (l_int32 *)CALLOC(256, sizeof(l_int32))) == NULL)
return ERROR_INT("calloc fail for tab", procName, 1);
if ((cmap = pixcmapCreate(outdepth)) == NULL)
return ERROR_INT("cmap not made", procName, 1);
*ptab = tab;
*pcmap = cmap;
/* First n bins */
jstart = 0;
for (i = 0; i < n; i++) {
numaGetIValue(na, i, &val);
ave = (jstart + val) / 2;
pixcmapAddColor(cmap, ave, ave, ave);
for (j = jstart; j < val; j++)
tab[j] = i;
jstart = val;
}
/* Last bin */
ave = (jstart + 255) / 2;
pixcmapAddColor(cmap, ave, ave, ave);
for (j = jstart; j < 256; j++)
tab[j] = n;
return 0;
}
/*!
* makeGrayQuantColormapArb()
*
* Input: pixs (8 bpp)
* tab (table mapping input gray level to cmap index)
* outdepth (of colormap: 1, 2, 4 or 8)
* &cmap (<return> colormap)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) The table is a 256-entry inverse colormap: it maps input gray
* level to colormap index (the bin number). It is computed
* using makeGrayQuantTableArb().
* (2) The colormap generated here has quantized values at the
* average gray value of the pixels that are in each bin.
* (3) Returns an error if there are not enough levels in the
* output colormap for the number of bins. The number
* of bins must not exceed 2^outdepth.
*/
l_int32
makeGrayQuantColormapArb(PIX *pixs,
l_int32 *tab,
l_int32 outdepth,
PIXCMAP **pcmap)
{
l_int32 i, j, index, w, h, d, nbins, wpl, factor, val;
l_int32 *bincount, *binave, *binstart;
l_uint32 *line, *data;
PROCNAME("makeGrayQuantColormapArb");
if (!pcmap)
return ERROR_INT("&cmap not defined", procName, 1);
*pcmap = NULL;
if (!pixs)
return ERROR_INT("pixs not defined", procName, 1);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return ERROR_INT("pixs not 8 bpp", procName, 1);
if (!tab)
return ERROR_INT("tab not defined", procName, 1);
nbins = tab[255] + 1;
if (nbins > (1 << outdepth))
return ERROR_INT("more bins than cmap levels", procName, 1);
/* Find the count and weighted count for each bin */
if ((bincount = (l_int32 *)CALLOC(nbins, sizeof(l_int32))) == NULL)
return ERROR_INT("calloc fail for bincount", procName, 1);
if ((binave = (l_int32 *)CALLOC(nbins, sizeof(l_int32))) == NULL)
return ERROR_INT("calloc fail for binave", procName, 1);
factor = (l_int32)(sqrt((l_float64)(w * h) / 30000.) + 0.5);
factor = L_MAX(1, factor);
data = pixGetData(pixs);
wpl = pixGetWpl(pixs);
for (i = 0; i < h; i += factor) {
line = data + i * wpl;
for (j = 0; j < w; j += factor) {
val = GET_DATA_BYTE(line, j);
bincount[tab[val]]++;
binave[tab[val]] += val;
}
}
/* Find the smallest gray values in each bin */
if ((binstart = (l_int32 *)CALLOC(nbins, sizeof(l_int32))) == NULL)
return ERROR_INT("calloc fail for binstart", procName, 1);
for (i = 1, index = 1; i < 256; i++) {
if (tab[i] < index) continue;
if (tab[i] == index)
binstart[index++] = i;
}
/* Get the averages. If there are no samples in a bin, use
* the center value of the bin. */
*pcmap = pixcmapCreate(outdepth);
for (i = 0; i < nbins; i++) {
if (bincount[i])
val = binave[i] / bincount[i];
else { /* no samples in the bin */
if (i < nbins - 1)
val = (binstart[i] + binstart[i + 1]) / 2;
else /* last bin */
val = (binstart[i] + 255) / 2;
}
pixcmapAddColor(*pcmap, val, val, val);
}
FREE(bincount);
FREE(binave);
FREE(binstart);
return 0;
}
/*--------------------------------------------------------------------*
* Thresholding from 32 bpp rgb to 1 bpp *
*--------------------------------------------------------------------*/
/*!
* pixGenerateMaskByBand32()
*
* Input: pixs (32 bpp)
* refval (reference rgb value)
* delm (max amount below the ref value for any component)
* delp (max amount above the ref value for any component)
* Return: pixd (1 bpp), or null on error
*
* Notes:
* (1) Generates a 1 bpp mask pixd, the same size as pixs, where
* the fg pixels in the mask are those where each component
* is within -delm to +delp of the reference value.
*/
PIX *
pixGenerateMaskByBand32(PIX *pixs,
l_uint32 refval,
l_int32 delm,
l_int32 delp)
{
l_int32 i, j, w, h, d, wpls, wpld;
l_int32 rref, gref, bref, rval, gval, bval;
l_uint32 pixel;
l_uint32 *datas, *datad, *lines, *lined;
PIX *pixd;
PROCNAME("pixGenerateMaskByBand32");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 32)
return (PIX *)ERROR_PTR("not 32 bpp", procName, NULL);
if (delm < 0 || delp < 0)
return (PIX *)ERROR_PTR("delm and delp must be >= 0", procName, NULL);
extractRGBValues(refval, &rref, &gref, &bref);
pixd = pixCreate(w, h, 1);
pixCopyResolution(pixd, pixs);
datas = pixGetData(pixs);
wpls = pixGetWpl(pixs);
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++) {
pixel = lines[j];
rval = (pixel >> L_RED_SHIFT) & 0xff;
if (rval < rref - delm || rval > rref + delp)
continue;
gval = (pixel >> L_GREEN_SHIFT) & 0xff;
if (gval < gref - delm || gval > gref + delp)
continue;
bval = (pixel >> L_BLUE_SHIFT) & 0xff;
if (bval < bref - delm || bval > bref + delp)
continue;
SET_DATA_BIT(lined, j);
}
}
return pixd;
}
/*!
* pixGenerateMaskByDiscr32()
*
* Input: pixs (32 bpp)
* refval1 (reference rgb value)
* refval2 (reference rgb value)
* distflag (L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE)
* Return: pixd (1 bpp), or null on error
*
* Notes:
* (1) Generates a 1 bpp mask pixd, the same size as pixs, where
* the fg pixels in the mask are those where the pixel in pixs
* is "closer" to refval1 than to refval2.
* (2) "Closer" can be defined in several ways, such as:
* - manhattan distance (L1)
* - euclidean distance (L2)
* - majority vote of the individual components
* Here, we have a choice of L1 or L2.
*/
PIX *
pixGenerateMaskByDiscr32(PIX *pixs,
l_uint32 refval1,
l_uint32 refval2,
l_int32 distflag)
{
l_int32 i, j, w, h, d, wpls, wpld;
l_int32 rref1, gref1, bref1, rref2, gref2, bref2, rval, gval, bval;
l_uint32 pixel, dist1, dist2;
l_uint32 *datas, *datad, *lines, *lined;
PIX *pixd;
PROCNAME("pixGenerateMaskByDiscr32");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
pixGetDimensions(pixs, &w, &h, &d);
if (d != 32)
return (PIX *)ERROR_PTR("not 32 bpp", procName, NULL);
if (distflag != L_MANHATTAN_DISTANCE && distflag != L_EUCLIDEAN_DISTANCE)
return (PIX *)ERROR_PTR("invalid distflag", procName, NULL);
extractRGBValues(refval1, &rref1, &gref1, &bref1);
extractRGBValues(refval2, &rref2, &gref2, &bref2);
pixd = pixCreate(w, h, 1);
pixCopyResolution(pixd, pixs);
datas = pixGetData(pixs);
wpls = pixGetWpl(pixs);
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++) {
pixel = lines[j];
extractRGBValues(pixel, &rval, &gval, &bval);
if (distflag == L_MANHATTAN_DISTANCE) {
dist1 = L_ABS(rref1 - rval);
dist2 = L_ABS(rref2 - rval);
dist1 += L_ABS(gref1 - gval);
dist2 += L_ABS(gref2 - gval);
dist1 += L_ABS(bref1 - bval);
dist2 += L_ABS(bref2 - bval);
}
else {
dist1 = (rref1 - rval) * (rref1 - rval);
dist2 = (rref2 - rval) * (rref2 - rval);
dist1 += (gref1 - gval) * (gref1 - gval);
dist2 += (gref2 - gval) * (gref2 - gval);
dist1 += (bref1 - bval) * (bref1 - bval);
dist2 += (bref2 - bval) * (bref2 - bval);
}
if (dist1 < dist2)
SET_DATA_BIT(lined, j);
}
}
return pixd;
}
/*----------------------------------------------------------------------*
* Histogram-based grayscale quantization *
*----------------------------------------------------------------------*/
/*!
* pixGrayQuantFromHisto()
*
* Input: pixd (<optional> quantized pix with cmap; can be null)
* pixs (8 bpp gray input pix; not cmapped)
* pixm (<optional> mask over pixels in pixs to quantize)
* minfract (minimum fraction of pixels in a set of adjacent
* histo bins that causes the set to be automatically
* set aside as a color in the colormap; must be
* at least 0.01)
* maxsize (maximum number of adjacent bins allowed to represent
* a color, regardless of the population of pixels
* in the bins; must be at least 2)
* Return: pixd (8 bpp, cmapped), or null on error
*
* Notes:
* (1) This is useful for quantizing images with relatively few
* colors, but which may have both color and gray pixels.
* If there are color pixels, it is assumed that an input
* rgb image has been color quantized first so that:
* - pixd has a colormap describing the color pixels
* - pixm is a mask over the non-color pixels in pixd
* - the colormap in pixd, and the color pixels in pixd,
* have been repacked to go from 0 to n-1 (n colors)
* If there are no color pixels, pixd and pixm are both null,
* and all pixels in pixs are quantized to gray.
* (2) A 256-entry histogram is built of the gray values in pixs.
* If pixm exists, the pixels contributing to the histogram are
* restricted to the fg of pixm. A colormap and LUT are generated
* from this histogram. We break up the array into a set
* of intervals, each one constituting a color in the colormap:
* An interval is identified by summing histogram bins until
* either the sum equals or exceeds the @minfract of the total
* number of pixels, or the span itself equals or exceeds @maxsize.
* The color of each bin is always an average of the pixels
* that constitute it.
* (3) Note that we do not specify the number of gray colors in
* the colormap. Instead, we specify two parameters that
* describe the accuracy of the color assignments; this and
* the actual image determine the number of resulting colors.
* (4) If a mask exists and it is not the same size as pixs, make
* a new mask the same size as pixs, with the original mask
* aligned at the UL corners. Set all additional pixels
* in the (larger) new mask set to 1, causing those pixels
* in pixd to be set as gray.
* (5) We estimate the total number of colors (color plus gray);
* if it exceeds 255, return null.
*/
PIX *
pixGrayQuantFromHisto(PIX *pixd,
PIX *pixs,
PIX *pixm,
l_float32 minfract,
l_int32 maxsize)
{
l_int32 w, h, wd, hd, wm, hm, wpls, wplm, wpld;
l_int32 nc, nestim, i, j, vals, vald;
l_int32 *lut;
l_uint32 *datas, *datam, *datad, *lines, *linem, *lined;
NUMA *na;
PIX *pixmr; /* resized mask */
PIXCMAP *cmap;
PROCNAME("pixGrayQuantFromHisto");
if (!pixs || pixGetDepth(pixs) != 8)
return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
if (minfract < 0.01) {
L_WARNING("minfract < 0.01; setting to 0.05", procName);
minfract = 0.05;
}
if (maxsize < 2) {
L_WARNING("maxsize < 2; setting to 10", procName);
maxsize = 10;
}
if ((pixd && !pixm) || (!pixd && pixm))
return (PIX *)ERROR_PTR("(pixd,pixm) not defined together",
procName, NULL);
pixGetDimensions(pixs, &w, &h, NULL);
if (pixd) {
if (pixGetDepth(pixm) != 1)
return (PIX *)ERROR_PTR("pixm not 1 bpp", procName, NULL);
if ((cmap = pixGetColormap(pixd)) == NULL)
return (PIX *)ERROR_PTR("pixd not cmapped", procName, NULL);
pixGetDimensions(pixd, &wd, &hd, NULL);
if (w != wd || h != hd)
return (PIX *)ERROR_PTR("pixs, pixd sizes differ", procName, NULL);
nc = pixcmapGetCount(cmap);
nestim = nc + (l_int32)(1.5 * 255 / maxsize);
fprintf(stderr, "nestim = %d\n", nestim);
if (nestim > 255) {
L_ERROR_INT("Estimate %d colors!", procName, nestim);
return (PIX *)ERROR_PTR("probably too many colors", procName, NULL);
}
pixGetDimensions(pixm, &wm, &hm, NULL);
if (w != wm || h != hm) { /* resize the mask */
L_WARNING("mask and dest sizes not equal", procName);
pixmr = pixCreateNoInit(w, h, 1);
pixRasterop(pixmr, 0, 0, wm, hm, PIX_SRC, pixm, 0, 0);
pixRasterop(pixmr, wm, 0, w - wm, h, PIX_SET, NULL, 0, 0);
pixRasterop(pixmr, 0, hm, wm, h - hm, PIX_SET, NULL, 0, 0);
}
else
pixmr = pixClone(pixm);
}
else {
pixd = pixCreateTemplate(pixs);
cmap = pixcmapCreate(8);
pixSetColormap(pixd, cmap);
}
/* Use original mask, if it exists, to select gray pixels */
na = pixGetGrayHistogramMasked(pixs, pixm, 0, 0, 1);
/* Fill out the cmap with gray colors, and generate the lut
* for pixel assignment. Issue a warning on failure. */
if (numaFillCmapFromHisto(na, cmap, minfract, maxsize, &lut))
L_ERROR("ran out of colors in cmap!", procName);
numaDestroy(&na);
/* Assign the gray pixels to their cmap indices */
datas = pixGetData(pixs);
datad = pixGetData(pixd);
wpls = pixGetWpl(pixs);
wpld = pixGetWpl(pixd);
if (!pixm) {
for (i = 0; i < h; i++) {
lines = datas + i * wpls;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
vals = GET_DATA_BYTE(lines, j);
vald = lut[vals];
SET_DATA_BYTE(lined, j, vald);
}
}
FREE(lut);
return pixd;
}
datam = pixGetData(pixmr);
wplm = pixGetWpl(pixmr);
for (i = 0; i < h; i++) {
lines = datas + i * wpls;
linem = datam + i * wplm;
lined = datad + i * wpld;
for (j = 0; j < w; j++) {
if (!GET_DATA_BIT(linem, j))
continue;
vals = GET_DATA_BYTE(lines, j);
vald = lut[vals];
SET_DATA_BYTE(lined, j, vald);
}
}
pixDestroy(&pixmr);
FREE(lut);
return pixd;
}
/*!
* numaFillCmapFromHisto()
*
* Input: na (histogram of gray values)
* cmap (8 bpp cmap, possibly initialized with color value)
* minfract (minimum fraction of pixels in a set of adjacent
* histo bins that causes the set to be automatically
* set aside as a color in the colormap; must be
* at least 0.01)
* maxsize (maximum number of adjacent bins allowed to represent
* a color, regardless of the population of pixels
* in the bins; must be at least 2)
* &lut (<return> lookup table from gray value to colormap index)
* Return: 0 if OK, 1 on error
*
* Notes:
* (1) This static function must be called from pixGrayQuantFromHisto()
*/
static l_int32
numaFillCmapFromHisto(NUMA *na,
PIXCMAP *cmap,
l_float32 minfract,
l_int32 maxsize,
l_int32 **plut)
{
l_int32 mincount, index, sum, wtsum, span, istart, i, val, ret;
l_int32 *iahisto, *lut;
l_float32 total;
PROCNAME("numaFillCmapFromHisto");
if (!plut)
return ERROR_INT("&lut not defined", procName, 1);
*plut = NULL;
if (!na)
return ERROR_INT("na not defined", procName, 1);
if (!cmap)
return ERROR_INT("cmap not defined", procName, 1);
numaGetSum(na, &total);
mincount = (l_int32)(minfract * total);
iahisto = numaGetIArray(na);
if ((lut = (l_int32 *)CALLOC(256, sizeof(l_int32))) == NULL)
return ERROR_INT("lut not made", procName, 1);
*plut = lut;
index = pixcmapGetCount(cmap); /* start with number of colors
* already reserved */
/* March through, associating colors with sets of adjacent
* gray levels. During the process, the LUT that gives
* the colormap index for each gray level is computed.
* To complete a color, either the total count must equal
* or exceed @mincount, or the current span of colors must
* equal or exceed @maxsize. An empty span is not converted
* into a color; it is simply ignored. When a span is completed for a
* color, the weighted color in the span is added to the colormap. */
sum = 0;
wtsum = 0;
istart = 0;
ret = 0;
for (i = 0; i < 256; i++) {
lut[i] = index;
sum += iahisto[i];
wtsum += i * iahisto[i];
span = i - istart + 1;
if (sum < mincount && span < maxsize)
continue;
if (sum == 0) { /* empty span; don't save */
istart = i + 1;
continue;
}
/* Found new color; sum > 0 */
val = (l_int32)((l_float32)wtsum / (l_float32)sum + 0.5);
ret = pixcmapAddColor(cmap, val, val, val);
istart = i + 1;
sum = 0;
wtsum = 0;
index++;
}
if (istart < 256 && sum > 0) { /* last one */
span = 256 - istart;
val = (l_int32)((l_float32)wtsum / (l_float32)sum + 0.5);
ret = pixcmapAddColor(cmap, val, val, val);
}
FREE(iahisto);
return ret;
}
/*----------------------------------------------------------------------*
* Color quantize grayscale image using existing colormap *
*----------------------------------------------------------------------*/
/*!
* pixGrayQuantFromCmap()
*
* Input: pixs (8 bpp grayscale without cmap)
* cmap (to quantize to; of dest pix)
* mindepth (minimum depth of pixd: can be 2, 4 or 8 bpp)
* Return: pixd (2, 4 or 8 bpp, colormapped), or null on error
*
* Notes:
* (1) In use, pixs is an 8 bpp grayscale image without a colormap.
* If there is an existing colormap, a warning is issued and
* a copy of the input pixs is returned.
*/
PIX *
pixGrayQuantFromCmap(PIX *pixs,
PIXCMAP *cmap,
l_int32 mindepth)
{
l_int32 i, j, index, w, h, d, depth, wpls, wpld;
l_int32 hascolor, vals, vald;
l_int32 *tab;
l_uint32 *datas, *datad, *lines, *lined;
PIXCMAP *cmapd;
PIX *pixd;
PROCNAME("pixGrayQuantFromCmap");
if (!pixs)
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
if (pixGetColormap(pixs) != NULL) {
L_WARNING("pixs already has a colormap; returning a copy", procName);
return pixCopy(NULL, pixs);
}
pixGetDimensions(pixs, &w, &h, &d);
if (d != 8)
return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
if (!cmap)
return (PIX *)ERROR_PTR("cmap not defined", procName, NULL);
if (mindepth != 2 && mindepth != 4 && mindepth != 8)
return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL);
/* Make sure the colormap is gray */
pixcmapHasColor(cmap, &hascolor);
if (hascolor) {
L_WARNING("Converting colormap colors to gray", procName);
cmapd = pixcmapColorToGray(cmap, 0.3, 0.5, 0.2);
}
else
cmapd = pixcmapCopy(cmap);
/* Make LUT into colormap */
if ((tab = (l_int32 *)CALLOC(256, sizeof(l_int32))) == NULL)
return (PIX *)ERROR_PTR("tab not made", procName, NULL);
for (i = 0; i < 256; i++) {
pixcmapGetNearestGrayIndex(cmapd, i, &index);
tab[i] = index;
}
pixcmapGetMinDepth(cmap, &depth);
depth = L_MAX(depth, mindepth);
pixd = pixCreate(w, h, depth);
pixSetColormap(pixd, cmapd);
pixCopyResolution(pixd, pixs);
pixCopyInputFormat(pixd, pixs);
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++) {
vals = GET_DATA_BYTE(lines, j);
vald = tab[vals];
if (depth == 2)
SET_DATA_DIBIT(lined, j, vald);
else if (depth == 4)
SET_DATA_QBIT(lined, j, vald);
else /* depth == 8 */
SET_DATA_BYTE(lined, j, vald);
}
}
FREE(tab);
return pixd;
}