mirror of http://192.168.1.51:8099/lmh188/twain3.0
2909 lines
104 KiB
C
2909 lines
104 KiB
C
/*====================================================================*
|
|
- Copyright (C) 2001 Leptonica. All rights reserved.
|
|
-
|
|
- Redistribution and use in source and binary forms, with or without
|
|
- modification, are permitted provided that the following conditions
|
|
- are met:
|
|
- 1. Redistributions of source code must retain the above copyright
|
|
- notice, this list of conditions and the following disclaimer.
|
|
- 2. Redistributions in binary form must reproduce the above
|
|
- copyright notice, this list of conditions and the following
|
|
- disclaimer in the documentation and/or other materials
|
|
- provided with the distribution.
|
|
-
|
|
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
|
|
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*====================================================================*/
|
|
|
|
/*!
|
|
* \file grayquant.c
|
|
* <pre>
|
|
*
|
|
* Thresholding from 8 bpp to 1 bpp
|
|
*
|
|
* Floyd-Steinberg dithering to binary
|
|
* PIX *pixDitherToBinary()
|
|
* PIX *pixDitherToBinarySpec()
|
|
* static void ditherToBinaryLow()
|
|
* void ditherToBinaryLineLow()
|
|
*
|
|
* Simple (pixelwise) binarization with fixed threshold
|
|
* PIX *pixThresholdToBinary()
|
|
* static void thresholdToBinaryLow()
|
|
* void thresholdToBinaryLineLow()
|
|
*
|
|
* Binarization with variable threshold
|
|
* PIX *pixVarThresholdToBinary()
|
|
*
|
|
* Binarization by adaptive mapping
|
|
* PIX *pixAdaptThresholdToBinary()
|
|
* PIX *pixAdaptThresholdToBinaryGen()
|
|
*
|
|
* Generate a binary mask from pixels of particular values
|
|
* PIX *pixGenerateMaskByValue()
|
|
* PIX *pixGenerateMaskByBand()
|
|
*
|
|
* Thresholding from 8 bpp to 2 bpp
|
|
*
|
|
* Floyd-Steinberg-like dithering to 2 bpp
|
|
* PIX *pixDitherTo2bpp()
|
|
* PIX *pixDitherTo2bppSpec()
|
|
* static void ditherTo2bppLow()
|
|
* static void ditherTo2bppLineLow()
|
|
* static l_int32 make8To2DitherTables()
|
|
*
|
|
* Simple (pixelwise) thresholding to 2 bpp with optional cmap
|
|
* PIX *pixThresholdTo2bpp()
|
|
* static void thresholdTo2bppLow()
|
|
*
|
|
* Simple (pixelwise) thresholding from 8 bpp to 4 bpp
|
|
* PIX *pixThresholdTo4bpp()
|
|
* static void thresholdTo4bppLow()
|
|
*
|
|
* 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()
|
|
* static l_int32 *makeGrayQuantTargetTable()
|
|
*
|
|
* Quantization table for arbitrary thresholding of grayscale images
|
|
* l_int32 makeGrayQuantTableArb()
|
|
* static 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()
|
|
* </pre>
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "allheaders.h"
|
|
|
|
static void ditherToBinaryLow(l_uint32 *datad, l_int32 w, l_int32 h,
|
|
l_int32 wpld, l_uint32 *datas, l_int32 wpls,
|
|
l_uint32 *bufs1, l_uint32 *bufs2,
|
|
l_int32 lowerclip, l_int32 upperclip);
|
|
static void thresholdToBinaryLow(l_uint32 *datad, l_int32 w, l_int32 h,
|
|
l_int32 wpld, l_uint32 *datas, l_int32 d,
|
|
l_int32 wpls, l_int32 thresh);
|
|
static void ditherTo2bppLow(l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld,
|
|
l_uint32 *datas, l_int32 wpls, l_uint32 *bufs1,
|
|
l_uint32 *bufs2, l_int32 *tabval, l_int32 *tab38,
|
|
l_int32 *tab14);
|
|
static void ditherTo2bppLineLow(l_uint32 *lined, l_int32 w, l_uint32 *bufs1,
|
|
l_uint32 *bufs2, l_int32 *tabval,
|
|
l_int32 *tab38, l_int32 *tab14,
|
|
l_int32 lastlineflag);
|
|
static l_int32 make8To2DitherTables(l_int32 **ptabval, l_int32 **ptab38,
|
|
l_int32 **ptab14, l_int32 cliptoblack,
|
|
l_int32 cliptowhite);
|
|
static void thresholdTo2bppLow(l_uint32 *datad, l_int32 h, l_int32 wpld,
|
|
l_uint32 *datas, l_int32 wpls, l_int32 *tab);
|
|
static void thresholdTo4bppLow(l_uint32 *datad, l_int32 h, l_int32 wpld,
|
|
l_uint32 *datas, l_int32 wpls, l_int32 *tab);
|
|
static l_int32 *makeGrayQuantTargetTable(l_int32 nlevels, l_int32 depth);
|
|
static l_int32 makeGrayQuantColormapArb(PIX *pixs, l_int32 *tab,
|
|
l_int32 outdepth, PIXCMAP **pcmap);
|
|
static l_int32 numaFillCmapFromHisto(NUMA *na, PIXCMAP *cmap,
|
|
l_float32 minfract, l_int32 maxsize,
|
|
l_int32 **plut);
|
|
|
|
#ifndef NO_CONSOLE_IO
|
|
#define DEBUG_UNROLLING 0
|
|
#endif /* ~NO_CONSOLE_IO */
|
|
|
|
/*------------------------------------------------------------------*
|
|
* Binarization by Floyd-Steinberg dithering *
|
|
*------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixDitherToBinary()
|
|
*
|
|
* \param[in] 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 the same speed
|
|
* as pixDitherToBinaryLUT(), which uses three 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);
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief pixDitherToBinarySpec()
|
|
*
|
|
* \param[in] pixs
|
|
* \param[in] lowerclip lower clip distance to black; use 0 for default
|
|
* \param[in] upperclip upper clip distance to white; use 0 for default
|
|
* \return pixd dithered binary, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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);
|
|
pixCopyInputFormat(pixd, pixs);
|
|
datad = pixGetData(pixd);
|
|
wpld = pixGetWpl(pixd);
|
|
|
|
/* Remove colormap if it exists */
|
|
if ((pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE)) == NULL) {
|
|
pixDestroy(&pixd);
|
|
return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
|
|
}
|
|
datat = pixGetData(pixt);
|
|
wplt = pixGetWpl(pixt);
|
|
|
|
/* Two line buffers, 1 for current line and 2 for next line */
|
|
bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
|
|
bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
|
|
if (!bufs1 || !bufs2) {
|
|
LEPT_FREE(bufs1);
|
|
LEPT_FREE(bufs2);
|
|
pixDestroy(&pixd);
|
|
pixDestroy(&pixt);
|
|
return (PIX *)ERROR_PTR("bufs1, bufs2 not both made", procName, NULL);
|
|
}
|
|
|
|
ditherToBinaryLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
|
|
lowerclip, upperclip);
|
|
|
|
LEPT_FREE(bufs1);
|
|
LEPT_FREE(bufs2);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief ditherToBinaryLow()
|
|
*
|
|
* See comments in pixDitherToBinary() in binarize.c
|
|
*/
|
|
static void
|
|
ditherToBinaryLow(l_uint32 *datad,
|
|
l_int32 w,
|
|
l_int32 h,
|
|
l_int32 wpld,
|
|
l_uint32 *datas,
|
|
l_int32 wpls,
|
|
l_uint32 *bufs1,
|
|
l_uint32 *bufs2,
|
|
l_int32 lowerclip,
|
|
l_int32 upperclip)
|
|
{
|
|
l_int32 i;
|
|
l_uint32 *lined;
|
|
|
|
/* do all lines except last line */
|
|
memcpy(bufs2, datas, 4 * wpls); /* prime the buffer */
|
|
for (i = 0; i < h - 1; i++) {
|
|
memcpy(bufs1, bufs2, 4 * wpls);
|
|
memcpy(bufs2, datas + (i + 1) * wpls, 4 * wpls);
|
|
lined = datad + i * wpld;
|
|
ditherToBinaryLineLow(lined, w, bufs1, bufs2, lowerclip, upperclip, 0);
|
|
}
|
|
|
|
/* do last line */
|
|
memcpy(bufs1, bufs2, 4 * wpls);
|
|
lined = datad + (h - 1) * wpld;
|
|
ditherToBinaryLineLow(lined, w, bufs1, bufs2, lowerclip, upperclip, 1);
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief ditherToBinaryLineLow()
|
|
*
|
|
* \param[in] lined ptr to beginning of dest line
|
|
* \param[in] w width of image in pixels
|
|
* \param[in] bufs1 buffer of current source line
|
|
* \param[in] bufs2 buffer of next source line
|
|
* \param[in] lowerclip lower clip distance to black
|
|
* \param[in] upperclip upper clip distance to white
|
|
* \param[in] lastlineflag 0 if not last dest line, 1 if last dest line
|
|
* \return void
|
|
*
|
|
* Dispatches FS error diffusion dithering for
|
|
* a single line of the image. If lastlineflag == 0,
|
|
* both source buffers are used; otherwise, only bufs1
|
|
* is used. We use source buffers because the error
|
|
* is propagated into them, and we don't want to change
|
|
* the input src image.
|
|
*
|
|
* We break dithering out line by line to make it
|
|
* easier to combine functions like interpolative
|
|
* scaling and error diffusion dithering, as such a
|
|
* combination of operations obviates the need to
|
|
* generate a 2x grayscale image as an intermediary.
|
|
*/
|
|
void
|
|
ditherToBinaryLineLow(l_uint32 *lined,
|
|
l_int32 w,
|
|
l_uint32 *bufs1,
|
|
l_uint32 *bufs2,
|
|
l_int32 lowerclip,
|
|
l_int32 upperclip,
|
|
l_int32 lastlineflag)
|
|
{
|
|
l_int32 j;
|
|
l_int32 oval, eval;
|
|
l_uint8 fval1, fval2, rval, bval, dval;
|
|
|
|
if (lastlineflag == 0) {
|
|
for (j = 0; j < w - 1; j++) {
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (oval > 127) { /* binarize to OFF */
|
|
if ((eval = 255 - oval) > upperclip) {
|
|
/* subtract from neighbors */
|
|
fval1 = (3 * eval) / 8;
|
|
fval2 = eval / 4;
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
rval = L_MAX(0, rval - fval1);
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
bval = L_MAX(0, bval - fval1);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
dval = GET_DATA_BYTE(bufs2, j + 1);
|
|
dval = L_MAX(0, dval - fval2);
|
|
SET_DATA_BYTE(bufs2, j + 1, dval);
|
|
}
|
|
} else { /* oval <= 127; binarize to ON */
|
|
SET_DATA_BIT(lined, j); /* ON pixel */
|
|
if (oval > lowerclip) {
|
|
/* add to neighbors */
|
|
fval1 = (3 * oval) / 8;
|
|
fval2 = oval / 4;
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
rval = L_MIN(255, rval + fval1);
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
bval = L_MIN(255, bval + fval1);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
dval = GET_DATA_BYTE(bufs2, j + 1);
|
|
dval = L_MIN(255, dval + fval2);
|
|
SET_DATA_BYTE(bufs2, j + 1, dval);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* do last column: j = w - 1 */
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (oval > 127) { /* binarize to OFF */
|
|
if ((eval = 255 - oval) > upperclip) {
|
|
/* subtract from neighbors */
|
|
fval1 = (3 * eval) / 8;
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
bval = L_MAX(0, bval - fval1);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
}
|
|
} else { /*oval <= 127; binarize to ON */
|
|
SET_DATA_BIT(lined, j); /* ON pixel */
|
|
if (oval > lowerclip) {
|
|
/* add to neighbors */
|
|
fval1 = (3 * oval) / 8;
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
bval = L_MIN(255, bval + fval1);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
}
|
|
}
|
|
} else { /* lastlineflag == 1 */
|
|
for (j = 0; j < w - 1; j++) {
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (oval > 127) { /* binarize to OFF */
|
|
if ((eval = 255 - oval) > upperclip) {
|
|
/* subtract from neighbors */
|
|
fval1 = (3 * eval) / 8;
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
rval = L_MAX(0, rval - fval1);
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
}
|
|
} else { /* oval <= 127; binarize to ON */
|
|
SET_DATA_BIT(lined, j); /* ON pixel */
|
|
if (oval > lowerclip) {
|
|
/* add to neighbors */
|
|
fval1 = (3 * oval) / 8;
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
rval = L_MIN(255, rval + fval1);
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* do last pixel: (i, j) = (h - 1, w - 1) */
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (oval < 128)
|
|
SET_DATA_BIT(lined, j); /* ON pixel */
|
|
}
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------*
|
|
* Simple (pixelwise) binarization with fixed threshold *
|
|
*------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixThresholdToBinary()
|
|
*
|
|
* \param[in] pixs 4 or 8 bpp
|
|
* \param[in] thresh threshold value
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* Notes:
|
|
* (1) If the source pixel is less than the threshold value,
|
|
* the dest will be 1; otherwise, it will be 0.
|
|
* (2) For example, for 8 bpp src pix, if %thresh == 256, the dest
|
|
* 1 bpp pix is all ones (fg), and if %thresh == 0, the dest
|
|
* pix is all zeros (bg).
|
|
*
|
|
* </pre>
|
|
*/
|
|
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);
|
|
pixCopyInputFormat(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;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief thresholdToBinaryLow()
|
|
*
|
|
* If the source pixel is less than thresh,
|
|
* the dest will be 1; otherwise, it will be 0
|
|
*/
|
|
static void
|
|
thresholdToBinaryLow(l_uint32 *datad,
|
|
l_int32 w,
|
|
l_int32 h,
|
|
l_int32 wpld,
|
|
l_uint32 *datas,
|
|
l_int32 d,
|
|
l_int32 wpls,
|
|
l_int32 thresh)
|
|
{
|
|
l_int32 i;
|
|
l_uint32 *lines, *lined;
|
|
|
|
for (i = 0; i < h; i++) {
|
|
lines = datas + i * wpls;
|
|
lined = datad + i * wpld;
|
|
thresholdToBinaryLineLow(lined, w, lines, d, thresh);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* thresholdToBinaryLineLow()
|
|
*
|
|
*/
|
|
void
|
|
thresholdToBinaryLineLow(l_uint32 *lined,
|
|
l_int32 w,
|
|
l_uint32 *lines,
|
|
l_int32 d,
|
|
l_int32 thresh)
|
|
{
|
|
l_int32 j, k, gval, scount, dcount;
|
|
l_uint32 sword, dword;
|
|
|
|
PROCNAME("thresholdToBinaryLineLow");
|
|
|
|
switch (d)
|
|
{
|
|
case 4:
|
|
/* Unrolled as 4 source words, 1 dest word */
|
|
for (j = 0, scount = 0, dcount = 0; j + 31 < w; j += 32) {
|
|
dword = 0;
|
|
for (k = 0; k < 4; k++) {
|
|
sword = lines[scount++];
|
|
dword <<= 8;
|
|
gval = (sword >> 28) & 0xf;
|
|
/* Trick used here and below: if gval < thresh then
|
|
* gval - thresh < 0, so its high-order bit is 1, and
|
|
* ((gval - thresh) >> 31) & 1 == 1; likewise, if
|
|
* gval >= thresh, then ((gval - thresh) >> 31) & 1 == 0
|
|
* Doing it this way avoids a random (and thus easily
|
|
* mispredicted) branch on each pixel. */
|
|
dword |= ((gval - thresh) >> 24) & 128;
|
|
gval = (sword >> 24) & 0xf;
|
|
dword |= ((gval - thresh) >> 25) & 64;
|
|
gval = (sword >> 20) & 0xf;
|
|
dword |= ((gval - thresh) >> 26) & 32;
|
|
gval = (sword >> 16) & 0xf;
|
|
dword |= ((gval - thresh) >> 27) & 16;
|
|
gval = (sword >> 12) & 0xf;
|
|
dword |= ((gval - thresh) >> 28) & 8;
|
|
gval = (sword >> 8) & 0xf;
|
|
dword |= ((gval - thresh) >> 29) & 4;
|
|
gval = (sword >> 4) & 0xf;
|
|
dword |= ((gval - thresh) >> 30) & 2;
|
|
gval = sword & 0xf;
|
|
dword |= ((gval - thresh) >> 31) & 1;
|
|
}
|
|
lined[dcount++] = dword;
|
|
}
|
|
|
|
if (j < w) {
|
|
dword = 0;
|
|
for (; j < w; j++) {
|
|
if ((j & 7) == 0) {
|
|
sword = lines[scount++];
|
|
}
|
|
gval = (sword >> 28) & 0xf;
|
|
sword <<= 4;
|
|
dword |= (((gval - thresh) >> 31) & 1) << (31 - (j & 31));
|
|
}
|
|
lined[dcount] = dword;
|
|
}
|
|
#if DEBUG_UNROLLING
|
|
#define CHECK_BIT(a, b, c) if (GET_DATA_BIT(a, b) != c) { \
|
|
fprintf(stderr, "Error: mismatch at %d/%d(%d), %d vs %d\n", \
|
|
j, w, d, GET_DATA_BIT(a, b), c); }
|
|
for (j = 0; j < w; j++) {
|
|
gval = GET_DATA_QBIT(lines, j);
|
|
CHECK_BIT(lined, j, gval < thresh ? 1 : 0);
|
|
}
|
|
#endif
|
|
break;
|
|
case 8:
|
|
/* Unrolled as 8 source words, 1 dest word */
|
|
for (j = 0, scount = 0, dcount = 0; j + 31 < w; j += 32) {
|
|
dword = 0;
|
|
for (k = 0; k < 8; k++) {
|
|
sword = lines[scount++];
|
|
dword <<= 4;
|
|
gval = (sword >> 24) & 0xff;
|
|
dword |= ((gval - thresh) >> 28) & 8;
|
|
gval = (sword >> 16) & 0xff;
|
|
dword |= ((gval - thresh) >> 29) & 4;
|
|
gval = (sword >> 8) & 0xff;
|
|
dword |= ((gval - thresh) >> 30) & 2;
|
|
gval = sword & 0xff;
|
|
dword |= ((gval - thresh) >> 31) & 1;
|
|
}
|
|
lined[dcount++] = dword;
|
|
}
|
|
|
|
if (j < w) {
|
|
dword = 0;
|
|
for (; j < w; j++) {
|
|
if ((j & 3) == 0) {
|
|
sword = lines[scount++];
|
|
}
|
|
gval = (sword >> 24) & 0xff;
|
|
sword <<= 8;
|
|
dword |= (l_uint64)(((gval - thresh) >> 31) & 1)
|
|
<< (31 - (j & 31));
|
|
}
|
|
lined[dcount] = dword;
|
|
}
|
|
#if DEBUG_UNROLLING
|
|
for (j = 0; j < w; j++) {
|
|
gval = GET_DATA_BYTE(lines, j);
|
|
CHECK_BIT(lined, j, gval < thresh ? 1 : 0);
|
|
}
|
|
#undef CHECK_BIT
|
|
#endif
|
|
break;
|
|
default:
|
|
L_ERROR("src depth not 4 or 8 bpp\n", procName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------*
|
|
* Binarization with variable threshold *
|
|
*------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixVarThresholdToBinary()
|
|
*
|
|
* \param[in] pixs 8 bpp
|
|
* \param[in] pixg 8 bpp; contains threshold values for each pixel
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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);
|
|
pixCopyResolution(pixd, pixs);
|
|
pixCopyInputFormat(pixd, pixs);
|
|
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;
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------*
|
|
* Binarization by adaptive mapping *
|
|
*------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixAdaptThresholdToBinary()
|
|
*
|
|
* \param[in] pixs 8 bpp
|
|
* \param[in] pixm [optional] 1 bpp image mask; can be null
|
|
* \param[in] gamma gamma correction; must be > 0.0; typically ~1.0
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* Notes:
|
|
* (1) This is a simple convenience function for doing adaptive
|
|
* thresholding on a grayscale image with variable background.
|
|
* It uses default parameters appropriate for typical text images.
|
|
* (2) %pixm is a 1 bpp mask over "image" regions, which are not
|
|
* expected to have a white background. The mask inhibits
|
|
* background finding under the fg pixels of the mask. For
|
|
* images with both text and image, the image regions would
|
|
* be binarized (or quantized) by a different set of operations.
|
|
* (3) As %gamma is increased, the foreground pixels are reduced.
|
|
* (4) Under the covers: The default background value for normalization
|
|
* is 200, so we choose 170 for 'maxval' in pixGammaTRC. Likewise,
|
|
* the default foreground threshold for normalization is 60,
|
|
* so we choose 50 for 'minval' in pixGammaTRC. Because
|
|
* 170 was mapped to 255, choosing 200 for the threshold is
|
|
* quite safe for avoiding speckle noise from the background.
|
|
* </pre>
|
|
*/
|
|
PIX *
|
|
pixAdaptThresholdToBinary(PIX *pixs,
|
|
PIX *pixm,
|
|
l_float32 gamma)
|
|
{
|
|
PROCNAME("pixAdaptThresholdToBinary");
|
|
|
|
if (!pixs || pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
|
|
|
|
return pixAdaptThresholdToBinaryGen(pixs, pixm, gamma, 50, 170, 200);
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief pixAdaptThresholdToBinaryGen()
|
|
*
|
|
* \param[in] pixs 8 bpp
|
|
* \param[in] pixm [optional] 1 bpp image mask; can be null
|
|
* \param[in] gamma gamma correction; must be > 0.0; typically ~1.0
|
|
* \param[in] blackval dark value to set to black (0)
|
|
* \param[in] whiteval light value to set to white (255)
|
|
* \param[in] thresh final threshold for binarization
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* Notes:
|
|
* (1) This is a convenience function for doing adaptive thresholding
|
|
* on a grayscale image with variable background. Also see notes
|
|
* in pixAdaptThresholdToBinary().
|
|
* (2) Reducing %gamma increases the foreground (text) pixels.
|
|
* Use a low value (e.g., 0.5) for images with light text.
|
|
* (3) For normal images, see default args in pixAdaptThresholdToBinary().
|
|
* For images with very light text, these values are appropriate:
|
|
* gamma ~0.5
|
|
* blackval ~70
|
|
* whiteval ~190
|
|
* thresh ~200
|
|
* </pre>
|
|
*/
|
|
PIX *
|
|
pixAdaptThresholdToBinaryGen(PIX *pixs,
|
|
PIX *pixm,
|
|
l_float32 gamma,
|
|
l_int32 blackval,
|
|
l_int32 whiteval,
|
|
l_int32 thresh)
|
|
{
|
|
PIX *pix1, *pixd;
|
|
|
|
PROCNAME("pixAdaptThresholdToBinaryGen");
|
|
|
|
if (!pixs || pixGetDepth(pixs) != 8)
|
|
return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
|
|
|
|
pix1 = pixBackgroundNormSimple(pixs, pixm, NULL);
|
|
pixGammaTRC(pix1, pix1, gamma, blackval, whiteval);
|
|
pixd = pixThresholdToBinary(pix1, thresh);
|
|
pixDestroy(&pix1);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------------------------*
|
|
* Generate a binary mask from pixels of particular value(s) *
|
|
*--------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixGenerateMaskByValue()
|
|
*
|
|
* \param[in] pixs 2, 4 or 8 bpp, or colormapped
|
|
* \param[in] val of pixels for which we set 1 in dest
|
|
* \param[in] usecmap 1 to retain cmap values; 0 to convert to gray
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* Notes:
|
|
* (1) %val is the pixel value that we are selecting. It can be
|
|
* either a gray value or a colormap index.
|
|
* (2) If pixs is colormapped, %usecmap determines if the colormap
|
|
* index values are used, or if the colormap is removed to gray and
|
|
* the gray values are used. For the latter, it generates
|
|
* an approximate grayscale value for each pixel, and then looks
|
|
* for gray pixels with the value %val.
|
|
* </pre>
|
|
*/
|
|
PIX *
|
|
pixGenerateMaskByValue(PIX *pixs,
|
|
l_int32 val,
|
|
l_int32 usecmap)
|
|
{
|
|
l_int32 i, j, w, h, d, wplg, wpld;
|
|
l_uint32 *datag, *datad, *lineg, *lined;
|
|
PIX *pixg, *pixd;
|
|
|
|
PROCNAME("pixGenerateMaskByValue");
|
|
|
|
if (!pixs)
|
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
|
d = pixGetDepth(pixs);
|
|
if (d != 2 && d != 4 && d != 8)
|
|
return (PIX *)ERROR_PTR("not 2, 4 or 8 bpp", procName, NULL);
|
|
|
|
if (!usecmap && pixGetColormap(pixs))
|
|
pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
|
|
else
|
|
pixg = pixClone(pixs);
|
|
pixGetDimensions(pixg, &w, &h, &d);
|
|
if (d == 8 && (val < 0 || val > 255)) {
|
|
pixDestroy(&pixg);
|
|
return (PIX *)ERROR_PTR("val out of 8 bpp range", procName, NULL);
|
|
}
|
|
if (d == 4 && (val < 0 || val > 15)) {
|
|
pixDestroy(&pixg);
|
|
return (PIX *)ERROR_PTR("val out of 4 bpp range", procName, NULL);
|
|
}
|
|
if (d == 2 && (val < 0 || val > 3)) {
|
|
pixDestroy(&pixg);
|
|
return (PIX *)ERROR_PTR("val out of 2 bpp range", procName, NULL);
|
|
}
|
|
|
|
pixd = pixCreate(w, h, 1);
|
|
pixCopyResolution(pixd, pixg);
|
|
pixCopyInputFormat(pixd, pixs);
|
|
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 (d == 8) {
|
|
if (GET_DATA_BYTE(lineg, j) == val)
|
|
SET_DATA_BIT(lined, j);
|
|
} else if (d == 4) {
|
|
if (GET_DATA_QBIT(lineg, j) == val)
|
|
SET_DATA_BIT(lined, j);
|
|
} else { /* d == 2 */
|
|
if (GET_DATA_DIBIT(lineg, j) == val)
|
|
SET_DATA_BIT(lined, j);
|
|
}
|
|
}
|
|
}
|
|
|
|
pixDestroy(&pixg);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief pixGenerateMaskByBand()
|
|
*
|
|
* \param[in] pixs 2, 4 or 8 bpp, or colormapped
|
|
* \param[in] 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
|
|
* \param[in] inband 1 for finding pixels in [lower, upper];
|
|
* 0 for finding pixels in
|
|
* [0, lower) union (upper, 255]
|
|
* \param[in] usecmap 1 to retain cmap values; 0 to convert to gray
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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, %usecmap determines if the colormap
|
|
* values are used, or if the colormap is removed to gray and
|
|
* the gray values are used. For the latter, it generates
|
|
* an approximate grayscale value for each pixel, and then looks
|
|
* for gray pixels with the value %val.
|
|
* </pre>
|
|
*/
|
|
PIX *
|
|
pixGenerateMaskByBand(PIX *pixs,
|
|
l_int32 lower,
|
|
l_int32 upper,
|
|
l_int32 inband,
|
|
l_int32 usecmap)
|
|
{
|
|
l_int32 i, j, w, h, d, 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);
|
|
d = pixGetDepth(pixs);
|
|
if (d != 2 && d != 4 && d != 8)
|
|
return (PIX *)ERROR_PTR("not 2, 4 or 8 bpp", procName, NULL);
|
|
if (lower < 0 || lower > upper)
|
|
return (PIX *)ERROR_PTR("lower < 0 or lower > upper!", procName, NULL);
|
|
|
|
if (!usecmap && pixGetColormap(pixs))
|
|
pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
|
|
else
|
|
pixg = pixClone(pixs);
|
|
pixGetDimensions(pixg, &w, &h, &d);
|
|
if (d == 8 && upper > 255) {
|
|
pixDestroy(&pixg);
|
|
return (PIX *)ERROR_PTR("d == 8 and upper > 255", procName, NULL);
|
|
}
|
|
if (d == 4 && upper > 15) {
|
|
pixDestroy(&pixg);
|
|
return (PIX *)ERROR_PTR("d == 4 and upper > 15", procName, NULL);
|
|
}
|
|
if (d == 2 && upper > 3) {
|
|
pixDestroy(&pixg);
|
|
return (PIX *)ERROR_PTR("d == 2 and upper > 3", procName, NULL);
|
|
}
|
|
|
|
pixd = pixCreate(w, h, 1);
|
|
pixCopyResolution(pixd, pixg);
|
|
pixCopyInputFormat(pixd, pixs);
|
|
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 (d == 8)
|
|
val = GET_DATA_BYTE(lineg, j);
|
|
else if (d == 4)
|
|
val = GET_DATA_QBIT(lineg, j);
|
|
else /* d == 2 */
|
|
val = GET_DATA_DIBIT(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 *
|
|
*------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixDitherTo2bpp()
|
|
*
|
|
* \param[in] pixs 8 bpp
|
|
* \param[in] 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);
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief pixDitherTo2bppSpec()
|
|
*
|
|
* \param[in] pixs 8 bpp
|
|
* \param[in] lowerclip lower clip distance to black; use 0 for default
|
|
* \param[in] upperclip upper clip distance to white; use 0 for default
|
|
* \param[in] cmapflag 1 to generate a colormap
|
|
* \return pixd dithered 2 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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);
|
|
pixCopyInputFormat(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 */
|
|
bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
|
|
bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
|
|
if (!bufs1 || !bufs2) {
|
|
LEPT_FREE(bufs1);
|
|
LEPT_FREE(bufs2);
|
|
pixDestroy(&pixd);
|
|
pixDestroy(&pixt);
|
|
return (PIX *)ERROR_PTR("bufs1, bufs2 not both 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);
|
|
}
|
|
|
|
LEPT_FREE(bufs1);
|
|
LEPT_FREE(bufs2);
|
|
LEPT_FREE(tabval);
|
|
LEPT_FREE(tab38);
|
|
LEPT_FREE(tab14);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief ditherTo2bppLow()
|
|
*
|
|
* Low-level function for doing Floyd-Steinberg error diffusion
|
|
* dithering from 8 bpp (datas) to 2 bpp (datad). Two source
|
|
* line buffers, bufs1 and bufs2, are provided, along with three
|
|
* 256-entry lookup tables: tabval gives the output pixel value,
|
|
* tab38 gives the extra (plus or minus) transferred to the pixels
|
|
* directly to the left and below, and tab14 gives the extra
|
|
* transferred to the diagonal below. The choice of 3/8 and 1/4
|
|
* is traditional but arbitrary when you use a lookup table; the
|
|
* only constraint is that the sum is 1. See other comments
|
|
* below and in grayquant.c.
|
|
*/
|
|
static void
|
|
ditherTo2bppLow(l_uint32 *datad,
|
|
l_int32 w,
|
|
l_int32 h,
|
|
l_int32 wpld,
|
|
l_uint32 *datas,
|
|
l_int32 wpls,
|
|
l_uint32 *bufs1,
|
|
l_uint32 *bufs2,
|
|
l_int32 *tabval,
|
|
l_int32 *tab38,
|
|
l_int32 *tab14)
|
|
{
|
|
l_int32 i;
|
|
l_uint32 *lined;
|
|
|
|
/* do all lines except last line */
|
|
memcpy(bufs2, datas, 4 * wpls); /* prime the buffer */
|
|
for (i = 0; i < h - 1; i++) {
|
|
memcpy(bufs1, bufs2, 4 * wpls);
|
|
memcpy(bufs2, datas + (i + 1) * wpls, 4 * wpls);
|
|
lined = datad + i * wpld;
|
|
ditherTo2bppLineLow(lined, w, bufs1, bufs2, tabval, tab38, tab14, 0);
|
|
}
|
|
|
|
/* do last line */
|
|
memcpy(bufs1, bufs2, 4 * wpls);
|
|
lined = datad + (h - 1) * wpld;
|
|
ditherTo2bppLineLow(lined, w, bufs1, bufs2, tabval, tab38, tab14, 1);
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief ditherTo2bppLineLow()
|
|
*
|
|
* \param[in] lined ptr to beginning of dest line
|
|
* \param[in] w width of image in pixels
|
|
* \param[in] bufs1 buffer of current source line
|
|
* \param[in] bufs2 buffer of next source line
|
|
* \param[in] tabval value to assign for current pixel
|
|
* \param[in] tab38 excess value to give to neighboring 3/8 pixels
|
|
* \param[in] tab14 excess value to give to neighboring 1/4 pixel
|
|
* \param[in] lastlineflag 0 if not last dest line, 1 if last dest line
|
|
* \return void
|
|
*
|
|
* Dispatches error diffusion dithering for
|
|
* a single line of the image. If lastlineflag == 0,
|
|
* both source buffers are used; otherwise, only bufs1
|
|
* is used. We use source buffers because the error
|
|
* is propagated into them, and we don't want to change
|
|
* the input src image.
|
|
*
|
|
* We break dithering out line by line to make it
|
|
* easier to combine functions like interpolative
|
|
* scaling and error diffusion dithering, as such a
|
|
* combination of operations obviates the need to
|
|
* generate a 2x grayscale image as an intermediary.
|
|
*/
|
|
static void
|
|
ditherTo2bppLineLow(l_uint32 *lined,
|
|
l_int32 w,
|
|
l_uint32 *bufs1,
|
|
l_uint32 *bufs2,
|
|
l_int32 *tabval,
|
|
l_int32 *tab38,
|
|
l_int32 *tab14,
|
|
l_int32 lastlineflag)
|
|
{
|
|
l_int32 j;
|
|
l_int32 oval, tab38val, tab14val;
|
|
l_uint8 rval, bval, dval;
|
|
|
|
if (lastlineflag == 0) {
|
|
for (j = 0; j < w - 1; j++) {
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
SET_DATA_DIBIT(lined, j, tabval[oval]);
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
dval = GET_DATA_BYTE(bufs2, j + 1);
|
|
tab38val = tab38[oval];
|
|
tab14val = tab14[oval];
|
|
if (tab38val < 0) {
|
|
rval = L_MAX(0, rval + tab38val);
|
|
bval = L_MAX(0, bval + tab38val);
|
|
dval = L_MAX(0, dval + tab14val);
|
|
} else {
|
|
rval = L_MIN(255, rval + tab38val);
|
|
bval = L_MIN(255, bval + tab38val);
|
|
dval = L_MIN(255, dval + tab14val);
|
|
}
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
SET_DATA_BYTE(bufs2, j + 1, dval);
|
|
}
|
|
|
|
/* do last column: j = w - 1 */
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
SET_DATA_DIBIT(lined, j, tabval[oval]);
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
tab38val = tab38[oval];
|
|
if (tab38val < 0)
|
|
bval = L_MAX(0, bval + tab38val);
|
|
else
|
|
bval = L_MIN(255, bval + tab38val);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
} else { /* lastlineflag == 1 */
|
|
for (j = 0; j < w - 1; j++) {
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
SET_DATA_DIBIT(lined, j, tabval[oval]);
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
tab38val = tab38[oval];
|
|
if (tab38val < 0)
|
|
rval = L_MAX(0, rval + tab38val);
|
|
else
|
|
rval = L_MIN(255, rval + tab38val);
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
}
|
|
|
|
/* do last pixel: (i, j) = (h - 1, w - 1) */
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
SET_DATA_DIBIT(lined, j, tabval[oval]);
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief make8To2DitherTables()
|
|
*
|
|
* \param[out] ptabval value assigned to output pixel; 0, 1, 2 or 3
|
|
* \param[out] ptab38 amount propagated to pixels left and below
|
|
* \param[out] ptab14 amount propagated to pixel to left and down
|
|
* \param[in] cliptoblack values near 0 where the excess is not propagated
|
|
* \param[in] cliptowhite values near 255 where the deficit is not propagated
|
|
*
|
|
* \return 0 if OK, 1 on error
|
|
*/
|
|
static l_int32
|
|
make8To2DitherTables(l_int32 **ptabval,
|
|
l_int32 **ptab38,
|
|
l_int32 **ptab14,
|
|
l_int32 cliptoblack,
|
|
l_int32 cliptowhite)
|
|
{
|
|
l_int32 i;
|
|
l_int32 *tabval, *tab38, *tab14;
|
|
|
|
/* 3 lookup tables: 2-bit value, (3/8)excess, and (1/4)excess */
|
|
tabval = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
tab38 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
tab14 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
*ptabval = tabval;
|
|
*ptab38 = tab38;
|
|
*ptab14 = tab14;
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
if (i <= cliptoblack) {
|
|
tabval[i] = 0;
|
|
tab38[i] = 0;
|
|
tab14[i] = 0;
|
|
} else if (i < 43) {
|
|
tabval[i] = 0;
|
|
tab38[i] = (3 * i + 4) / 8;
|
|
tab14[i] = (i + 2) / 4;
|
|
} else if (i < 85) {
|
|
tabval[i] = 1;
|
|
tab38[i] = (3 * (i - 85) - 4) / 8;
|
|
tab14[i] = ((i - 85) - 2) / 4;
|
|
} else if (i < 128) {
|
|
tabval[i] = 1;
|
|
tab38[i] = (3 * (i - 85) + 4) / 8;
|
|
tab14[i] = ((i - 85) + 2) / 4;
|
|
} else if (i < 170) {
|
|
tabval[i] = 2;
|
|
tab38[i] = (3 * (i - 170) - 4) / 8;
|
|
tab14[i] = ((i - 170) - 2) / 4;
|
|
} else if (i < 213) {
|
|
tabval[i] = 2;
|
|
tab38[i] = (3 * (i - 170) + 4) / 8;
|
|
tab14[i] = ((i - 170) + 2) / 4;
|
|
} else if (i < 255 - cliptowhite) {
|
|
tabval[i] = 3;
|
|
tab38[i] = (3 * (i - 255) - 4) / 8;
|
|
tab14[i] = ((i - 255) - 2) / 4;
|
|
} else { /* i >= 255 - cliptowhite */
|
|
tabval[i] = 3;
|
|
tab38[i] = 0;
|
|
tab14[i] = 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------------------------*
|
|
* Simple (pixelwise) thresholding to 2 bpp with optional colormap *
|
|
*--------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixThresholdTo2bpp()
|
|
*
|
|
* \param[in] pixs 8 bpp
|
|
* \param[in] nlevels equally spaced; must be between 2 and 4
|
|
* \param[in] cmapflag 1 to build colormap; 0 otherwise
|
|
* \return pixd 2 bpp, optionally with colormap, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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);
|
|
|
|
if ((pixd = pixCreate(w, h, 2)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
|
pixCopyResolution(pixd, pixs);
|
|
pixCopyInputFormat(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);
|
|
|
|
/* Make the appropriate table */
|
|
if (cmapflag)
|
|
qtab = makeGrayQuantIndexTable(nlevels);
|
|
else
|
|
qtab = makeGrayQuantTargetTable(4, 2);
|
|
|
|
thresholdTo2bppLow(datad, h, wpld, datat, wplt, qtab);
|
|
|
|
LEPT_FREE(qtab);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief thresholdTo2bppLow()
|
|
*
|
|
* Low-level function for thresholding from 8 bpp (datas) to
|
|
* 2 bpp (datad), using thresholds implicitly defined through %tab,
|
|
* a 256-entry lookup table that gives a 2-bit output value
|
|
* for each possible input.
|
|
*
|
|
* For each line, unroll the loop so that for each 32 bit src word,
|
|
* representing four consecutive 8-bit pixels, we compose one byte
|
|
* of output consisiting of four 2-bit pixels.
|
|
*/
|
|
static void
|
|
thresholdTo2bppLow(l_uint32 *datad,
|
|
l_int32 h,
|
|
l_int32 wpld,
|
|
l_uint32 *datas,
|
|
l_int32 wpls,
|
|
l_int32 *tab)
|
|
{
|
|
l_uint8 sval1, sval2, sval3, sval4, dval;
|
|
l_int32 i, j, k;
|
|
l_uint32 *lines, *lined;
|
|
|
|
for (i = 0; i < h; i++) {
|
|
lines = datas + i * wpls;
|
|
lined = datad + i * wpld;
|
|
for (j = 0; j < wpls; j++) {
|
|
k = 4 * j;
|
|
sval1 = GET_DATA_BYTE(lines, k);
|
|
sval2 = GET_DATA_BYTE(lines, k + 1);
|
|
sval3 = GET_DATA_BYTE(lines, k + 2);
|
|
sval4 = GET_DATA_BYTE(lines, k + 3);
|
|
dval = (tab[sval1] << 6) | (tab[sval2] << 4) |
|
|
(tab[sval3] << 2) | tab[sval4];
|
|
SET_DATA_BYTE(lined, j, dval);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*
|
|
* Simple (pixelwise) thresholding to 4 bpp *
|
|
*----------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixThresholdTo4bpp()
|
|
*
|
|
* \param[in] pixs 8 bpp, can have colormap
|
|
* \param[in] nlevels equally spaced; must be between 2 and 16
|
|
* \param[in] cmapflag 1 to build colormap; 0 otherwise
|
|
* \return pixd 4 bpp, optionally with colormap, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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);
|
|
|
|
if ((pixd = pixCreate(w, h, 4)) == NULL)
|
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
|
pixCopyResolution(pixd, pixs);
|
|
pixCopyInputFormat(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);
|
|
|
|
/* Make the appropriate table */
|
|
if (cmapflag)
|
|
qtab = makeGrayQuantIndexTable(nlevels);
|
|
else
|
|
qtab = makeGrayQuantTargetTable(16, 4);
|
|
|
|
thresholdTo4bppLow(datad, h, wpld, datat, wplt, qtab);
|
|
|
|
LEPT_FREE(qtab);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief thresholdTo4bppLow()
|
|
*
|
|
* Low-level function for thresholding from 8 bpp (datas) to
|
|
* 4 bpp (datad), using thresholds implicitly defined through %tab,
|
|
* a 256-entry lookup table that gives a 4-bit output value
|
|
* for each possible input.
|
|
*
|
|
* For each line, unroll the loop so that for each 32 bit src word,
|
|
* representing four consecutive 8-bit pixels, we compose two bytes
|
|
* of output consisiting of four 4-bit pixels.
|
|
*/
|
|
static void
|
|
thresholdTo4bppLow(l_uint32 *datad,
|
|
l_int32 h,
|
|
l_int32 wpld,
|
|
l_uint32 *datas,
|
|
l_int32 wpls,
|
|
l_int32 *tab)
|
|
{
|
|
l_uint8 sval1, sval2, sval3, sval4;
|
|
l_uint16 dval;
|
|
l_int32 i, j, k;
|
|
l_uint32 *lines, *lined;
|
|
|
|
for (i = 0; i < h; i++) {
|
|
lines = datas + i * wpls;
|
|
lined = datad + i * wpld;
|
|
for (j = 0; j < wpls; j++) {
|
|
k = 4 * j;
|
|
sval1 = GET_DATA_BYTE(lines, k);
|
|
sval2 = GET_DATA_BYTE(lines, k + 1);
|
|
sval3 = GET_DATA_BYTE(lines, k + 2);
|
|
sval4 = GET_DATA_BYTE(lines, k + 3);
|
|
dval = (tab[sval1] << 12) | (tab[sval2] << 8) |
|
|
(tab[sval3] << 4) | tab[sval4];
|
|
SET_DATA_TWO_BYTES(lined, j, dval);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*
|
|
* Simple (pixelwise) thresholding on 8 bpp with optional colormap *
|
|
*----------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixThresholdOn8bpp()
|
|
*
|
|
* \param[in] pixs 8 bpp, can have colormap
|
|
* \param[in] nlevels equally spaced; must be between 2 and 256
|
|
* \param[in] cmapflag 1 to build colormap; 0 otherwise
|
|
* \return pixd 8 bpp, optionally with colormap, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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().
|
|
* </pre>
|
|
*/
|
|
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);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
if (cmapflag)
|
|
qtab = makeGrayQuantIndexTable(nlevels);
|
|
else
|
|
qtab = makeGrayQuantTargetTable(nlevels, 8);
|
|
|
|
pixGetDimensions(pixd, &w, &h, NULL);
|
|
pixCopyResolution(pixd, pixs);
|
|
pixCopyInputFormat(pixd, pixs);
|
|
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);
|
|
}
|
|
}
|
|
|
|
LEPT_FREE(qtab);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*
|
|
* Arbitrary (pixelwise) thresholding from 8 bpp to 2, 4 or 8 bpp *
|
|
*----------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixThresholdGrayArb()
|
|
*
|
|
* \param[in] pixs 8 bpp grayscale; can have colormap
|
|
* \param[in] edgevals string giving edge value of each bin
|
|
* \param[in] outdepth 0, 2, 4 or 8 bpp; 0 is default for min depth
|
|
* \param[in] use_average 1 if use the average pixel value in colormap
|
|
* \param[in] setblack 1 if darkest color is set to black
|
|
* \param[in] setwhite 1 if lightest color is set to white
|
|
* \return pixd 2, 4 or 8 bpp quantized image with colormap,
|
|
* or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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().
|
|
* </pre>
|
|
*/
|
|
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) {
|
|
numaDestroy(&na);
|
|
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\n", 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) {
|
|
LEPT_FREE(qtab);
|
|
pixcmapDestroy(&cmap);
|
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
|
}
|
|
pixCopyResolution(pixd, pixs);
|
|
pixCopyInputFormat(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
LEPT_FREE(qtab);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*
|
|
* Quantization tables for linear thresholds of grayscale images *
|
|
*----------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief makeGrayQuantIndexTable()
|
|
*
|
|
* \param[in] nlevels number of output levels
|
|
* \return table maps input gray level to colormap index,
|
|
* or NULL on error
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
l_int32 *
|
|
makeGrayQuantIndexTable(l_int32 nlevels)
|
|
{
|
|
l_int32 *tab;
|
|
l_int32 i, j, thresh;
|
|
|
|
tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
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;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief makeGrayQuantTargetTable()
|
|
*
|
|
* \param[in] nlevels number of output levels
|
|
* \param[in] 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
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
static l_int32 *
|
|
makeGrayQuantTargetTable(l_int32 nlevels,
|
|
l_int32 depth)
|
|
{
|
|
l_int32 *tab;
|
|
l_int32 i, j, thresh, maxval, quantval;
|
|
|
|
tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
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 *
|
|
*----------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief makeGrayQuantTableArb()
|
|
*
|
|
* \param[in] na numa of bin boundaries
|
|
* \param[in] outdepth of colormap: 1, 2, 4 or 8
|
|
* \param[out] ptab table mapping input gray level to cmap index
|
|
* \param[out] pcmap colormap
|
|
* \return 0 if OK, 1 on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
l_ok
|
|
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 ((cmap = pixcmapCreate(outdepth)) == NULL)
|
|
return ERROR_INT("cmap not made", procName, 1);
|
|
tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
*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;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief makeGrayQuantColormapArb()
|
|
*
|
|
* \param[in] pixs 8 bpp
|
|
* \param[in] tab table mapping input gray level to cmap index
|
|
* \param[in] outdepth of colormap: 1, 2, 4 or 8
|
|
* \param[out] pcmap colormap
|
|
* \return 0 if OK, 1 on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
static 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 *)LEPT_CALLOC(nbins, sizeof(l_int32))) == NULL)
|
|
return ERROR_INT("calloc fail for bincount", procName, 1);
|
|
if ((binave = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32))) == NULL) {
|
|
LEPT_FREE(bincount);
|
|
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 */
|
|
binstart = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32));
|
|
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);
|
|
}
|
|
|
|
LEPT_FREE(bincount);
|
|
LEPT_FREE(binave);
|
|
LEPT_FREE(binstart);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------------------------*
|
|
* Thresholding from 32 bpp rgb to 1 bpp *
|
|
*--------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixGenerateMaskByBand32()
|
|
*
|
|
* \param[in] pixs 32 bpp
|
|
* \param[in] refval reference rgb value
|
|
* \param[in] delm max amount below the ref value for any component
|
|
* \param[in] delp max amount above the ref value for any component
|
|
* \param[in] fractm fractional amount below ref value for all components
|
|
* \param[in] fractp fractional amount above ref value for all components
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* Notes:
|
|
* (1) Generates a 1 bpp mask pixd, the same size as pixs, where
|
|
* the fg pixels in the mask within a band of rgb values
|
|
* surrounding %refval. The band can be chosen in two ways
|
|
* for each component:
|
|
* (a) Use (%delm, %delp) to specify how many levels down and up
|
|
* (b) Use (%fractm, %fractp) to specify the fractional
|
|
* distance toward 0 and 255, respectively.
|
|
* Note that %delm and %delp must be in [0 ... 255], whereas
|
|
* %fractm and %fractp must be in [0.0 - 1.0].
|
|
* (2) Either (%delm, %delp) or (%fractm, %fractp) can be used.
|
|
* Set each value in the other pair to 0.
|
|
* </pre>
|
|
*/
|
|
PIX *
|
|
pixGenerateMaskByBand32(PIX *pixs,
|
|
l_uint32 refval,
|
|
l_int32 delm,
|
|
l_int32 delp,
|
|
l_float32 fractm,
|
|
l_float32 fractp)
|
|
{
|
|
l_int32 i, j, w, h, d, wpls, wpld;
|
|
l_int32 rref, gref, bref, rval, gval, bval;
|
|
l_int32 rmin, gmin, bmin, rmax, gmax, bmax;
|
|
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);
|
|
if (fractm < 0.0 || fractm > 1.0 || fractp < 0.0 || fractp > 1.0)
|
|
return (PIX *)ERROR_PTR("fractm and/or fractp invalid", procName, NULL);
|
|
|
|
extractRGBValues(refval, &rref, &gref, &bref);
|
|
if (fractm == 0.0 && fractp == 0.0) {
|
|
rmin = rref - delm;
|
|
gmin = gref - delm;
|
|
bmin = bref - delm;
|
|
rmax = rref + delm;
|
|
gmax = gref + delm;
|
|
bmax = bref + delm;
|
|
} else if (delm == 0 && delp == 0) {
|
|
rmin = (l_int32)((1.0 - fractm) * rref);
|
|
gmin = (l_int32)((1.0 - fractm) * gref);
|
|
bmin = (l_int32)((1.0 - fractm) * bref);
|
|
rmax = rref + (l_int32)(fractp * (255 - rref));
|
|
gmax = gref + (l_int32)(fractp * (255 - gref));
|
|
bmax = bref + (l_int32)(fractp * (255 - bref));
|
|
} else {
|
|
L_ERROR("bad input: either (delm, delp) or (fractm, fractp) "
|
|
"must be 0\n", procName);
|
|
return NULL;
|
|
}
|
|
|
|
pixd = pixCreate(w, h, 1);
|
|
pixCopyResolution(pixd, pixs);
|
|
pixCopyInputFormat(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 < rmin || rval > rmax)
|
|
continue;
|
|
gval = (pixel >> L_GREEN_SHIFT) & 0xff;
|
|
if (gval < gmin || gval > gmax)
|
|
continue;
|
|
bval = (pixel >> L_BLUE_SHIFT) & 0xff;
|
|
if (bval < bmin || bval > bmax)
|
|
continue;
|
|
SET_DATA_BIT(lined, j);
|
|
}
|
|
}
|
|
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief pixGenerateMaskByDiscr32()
|
|
*
|
|
* \param[in] pixs 32 bpp
|
|
* \param[in] refval1 reference rgb value
|
|
* \param[in] refval2 reference rgb value
|
|
* \param[in] distflag L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE
|
|
* \return pixd 1 bpp, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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);
|
|
pixCopyInputFormat(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 *
|
|
*----------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixGrayQuantFromHisto()
|
|
*
|
|
* \param[in] pixd [optional] quantized pix with cmap; can be null
|
|
* \param[in] pixs 8 bpp gray input pix; not cmapped
|
|
* \param[in] pixm [optional] mask over pixels in pixs to quantize
|
|
* \param[in] 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
|
|
* \param[in] 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
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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\n", procName);
|
|
minfract = 0.05;
|
|
}
|
|
if (maxsize < 2) {
|
|
L_WARNING("maxsize < 2; setting to 10\n", 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("Estimate %d colors!\n", 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\n", 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);
|
|
}
|
|
pixCopyResolution(pixd, pixs);
|
|
pixCopyInputFormat(pixd, pixs);
|
|
|
|
/* 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!\n", 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);
|
|
}
|
|
}
|
|
LEPT_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);
|
|
LEPT_FREE(lut);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief numaFillCmapFromHisto()
|
|
*
|
|
* \param[in] na histogram of gray values
|
|
* \param[in] cmap 8 bpp cmap, possibly initialized with color value
|
|
* \param[in] 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
|
|
* \param[in] 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
|
|
* \param[out] plut lookup table from gray value to colormap index
|
|
* \return 0 if OK, 1 on error
|
|
*
|
|
* <pre>
|
|
* Notes:
|
|
* (1) This static function must be called from pixGrayQuantFromHisto()
|
|
* </pre>
|
|
*/
|
|
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);
|
|
lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
*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);
|
|
}
|
|
|
|
LEPT_FREE(iahisto);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------*
|
|
* Color quantize grayscale image using existing colormap *
|
|
*----------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixGrayQuantFromCmap()
|
|
*
|
|
* \param[in] pixs 8 bpp grayscale without cmap
|
|
* \param[in] cmap to quantize to; of dest pix
|
|
* \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp
|
|
* \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error
|
|
*
|
|
* <pre>
|
|
* 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.
|
|
* </pre>
|
|
*/
|
|
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\n", 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\n", procName);
|
|
cmapd = pixcmapColorToGray(cmap, 0.3, 0.5, 0.2);
|
|
} else {
|
|
cmapd = pixcmapCopy(cmap);
|
|
}
|
|
|
|
/* Make LUT into colormap */
|
|
tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
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);
|
|
}
|
|
}
|
|
|
|
LEPT_FREE(tab);
|
|
return pixd;
|
|
}
|
|
|
|
|
|
#if 0 /* Documentation */
|
|
/*--------------------------------------------------------------------*
|
|
* Implementation of binarization by dithering using LUTs *
|
|
* It is archived here. *
|
|
*--------------------------------------------------------------------*/
|
|
/*!
|
|
* \brief pixDitherToBinaryLUT()
|
|
*
|
|
* \param[in] pixs
|
|
* \param[in] lowerclip lower clip distance to black; use -1 for default
|
|
* \param[in] upperclip upper clip distance to white; use -1 for default
|
|
* \return pixd dithered binary, or NULL on error
|
|
*
|
|
* We don't need two implementations of Floyd-Steinberg dithering,
|
|
* and this one with LUTs is a little more complicated than
|
|
* pixDitherToBinary(). It uses three lookup tables to generate the
|
|
* output pixel value and the excess or deficit carried over to the
|
|
* neighboring pixels. It's here for pedagogical reasons only.
|
|
*/
|
|
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);
|
|
pixCopyInputFormat(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 */
|
|
bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
|
|
bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32));
|
|
if (!bufs1 || !bufs2) {
|
|
LEPT_FREE(bufs1);
|
|
LEPT_FREE(bufs2);
|
|
pixDestroy(&pixd);
|
|
pixDestroy(&pixt);
|
|
return (PIX *)ERROR_PTR("bufs1, bufs2 not both 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);
|
|
|
|
LEPT_FREE(bufs1);
|
|
LEPT_FREE(bufs2);
|
|
LEPT_FREE(tabval);
|
|
LEPT_FREE(tab38);
|
|
LEPT_FREE(tab14);
|
|
pixDestroy(&pixt);
|
|
return pixd;
|
|
}
|
|
|
|
/*!
|
|
* \brief ditherToBinaryLUTLow()
|
|
*
|
|
* Low-level function for doing Floyd-Steinberg error diffusion
|
|
* dithering from 8 bpp (datas) to 1 bpp (datad). Two source
|
|
* line buffers, bufs1 and bufs2, are provided, along with three
|
|
* 256-entry lookup tables: tabval gives the output pixel value,
|
|
* tab38 gives the extra (plus or minus) transferred to the pixels
|
|
* directly to the left and below, and tab14 gives the extra
|
|
* transferred to the diagonal below. The choice of 3/8 and 1/4
|
|
* is traditional but arbitrary when you use a lookup table; the
|
|
* only constraint is that the sum is 1. See other comments below.
|
|
*/
|
|
void
|
|
ditherToBinaryLUTLow(l_uint32 *datad,
|
|
l_int32 w,
|
|
l_int32 h,
|
|
l_int32 wpld,
|
|
l_uint32 *datas,
|
|
l_int32 wpls,
|
|
l_uint32 *bufs1,
|
|
l_uint32 *bufs2,
|
|
l_int32 *tabval,
|
|
l_int32 *tab38,
|
|
l_int32 *tab14)
|
|
{
|
|
l_int32 i;
|
|
l_uint32 *lined;
|
|
|
|
/* do all lines except last line */
|
|
memcpy(bufs2, datas, 4 * wpls); /* prime the buffer */
|
|
for (i = 0; i < h - 1; i++) {
|
|
memcpy(bufs1, bufs2, 4 * wpls);
|
|
memcpy(bufs2, datas + (i + 1) * wpls, 4 * wpls);
|
|
lined = datad + i * wpld;
|
|
ditherToBinaryLineLUTLow(lined, w, bufs1, bufs2,
|
|
tabval, tab38, tab14, 0);
|
|
}
|
|
|
|
/* do last line */
|
|
memcpy(bufs1, bufs2, 4 * wpls);
|
|
lined = datad + (h - 1) * wpld;
|
|
ditherToBinaryLineLUTLow(lined, w, bufs1, bufs2, tabval, tab38, tab14, 1);
|
|
return;
|
|
}
|
|
|
|
/*!
|
|
* \brief ditherToBinaryLineLUTLow()
|
|
*
|
|
* \param[in] lined ptr to beginning of dest line
|
|
* \param[in] w width of image in pixels
|
|
* \param[in] bufs1 buffer of current source line
|
|
* \param[in] bufs2 buffer of next source line
|
|
* \param[in] tabval value to assign for current pixel
|
|
* \param[in] tab38 excess value to give to neighboring 3/8 pixels
|
|
* \param[in] tab14 excess value to give to neighboring 1/4 pixel
|
|
* \param[in] lastlineflag 0 if not last dest line, 1 if last dest line
|
|
* \return void
|
|
*/
|
|
void
|
|
ditherToBinaryLineLUTLow(l_uint32 *lined,
|
|
l_int32 w,
|
|
l_uint32 *bufs1,
|
|
l_uint32 *bufs2,
|
|
l_int32 *tabval,
|
|
l_int32 *tab38,
|
|
l_int32 *tab14,
|
|
l_int32 lastlineflag)
|
|
{
|
|
l_int32 j;
|
|
l_int32 oval, tab38val, tab14val;
|
|
l_uint8 rval, bval, dval;
|
|
|
|
if (lastlineflag == 0) {
|
|
for (j = 0; j < w - 1; j++) {
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (tabval[oval])
|
|
SET_DATA_BIT(lined, j);
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
dval = GET_DATA_BYTE(bufs2, j + 1);
|
|
tab38val = tab38[oval];
|
|
if (tab38val == 0)
|
|
continue;
|
|
tab14val = tab14[oval];
|
|
if (tab38val < 0) {
|
|
rval = L_MAX(0, rval + tab38val);
|
|
bval = L_MAX(0, bval + tab38val);
|
|
dval = L_MAX(0, dval + tab14val);
|
|
} else {
|
|
rval = L_MIN(255, rval + tab38val);
|
|
bval = L_MIN(255, bval + tab38val);
|
|
dval = L_MIN(255, dval + tab14val);
|
|
}
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
SET_DATA_BYTE(bufs2, j + 1, dval);
|
|
}
|
|
|
|
/* do last column: j = w - 1 */
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (tabval[oval])
|
|
SET_DATA_BIT(lined, j);
|
|
bval = GET_DATA_BYTE(bufs2, j);
|
|
tab38val = tab38[oval];
|
|
if (tab38val < 0) {
|
|
bval = L_MAX(0, bval + tab38val);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
} else if (tab38val > 0 ) {
|
|
bval = L_MIN(255, bval + tab38val);
|
|
SET_DATA_BYTE(bufs2, j, bval);
|
|
}
|
|
} else { /* lastlineflag == 1 */
|
|
for (j = 0; j < w - 1; j++) {
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (tabval[oval])
|
|
SET_DATA_BIT(lined, j);
|
|
rval = GET_DATA_BYTE(bufs1, j + 1);
|
|
tab38val = tab38[oval];
|
|
if (tab38val == 0)
|
|
continue;
|
|
if (tab38val < 0)
|
|
rval = L_MAX(0, rval + tab38val);
|
|
else
|
|
rval = L_MIN(255, rval + tab38val);
|
|
SET_DATA_BYTE(bufs1, j + 1, rval);
|
|
}
|
|
|
|
/* do last pixel: (i, j) = (h - 1, w - 1) */
|
|
oval = GET_DATA_BYTE(bufs1, j);
|
|
if (tabval[oval])
|
|
SET_DATA_BIT(lined, j);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*!
|
|
* \brief make8To1DitherTables()
|
|
*
|
|
* \param[out] ptabval value assigned to output pixel; 0 or 1
|
|
* \param[out] ptab38 amount propagated to pixels left and below
|
|
* \param[out] ptab14 amount propagated to pixel to left and down
|
|
* \param[in] lowerclip values near 0 where the excess is not propagated
|
|
* \param[in] upperclip values near 255 where the deficit is not propagated
|
|
*
|
|
* \return 0 if OK, 1 on error
|
|
*/
|
|
l_ok
|
|
make8To1DitherTables(l_int32 **ptabval,
|
|
l_int32 **ptab38,
|
|
l_int32 **ptab14,
|
|
l_int32 lowerclip,
|
|
l_int32 upperclip)
|
|
{
|
|
l_int32 i;
|
|
l_int32 *tabval, *tab38, *tab14;
|
|
|
|
PROCNAME("make8To1DitherTables");
|
|
|
|
if (ptabval) *ptabval = NULL;
|
|
if (ptab38) *ptab38 = NULL;
|
|
if (ptab14) *ptab14 = NULL;
|
|
if (!ptabval || !ptab38 || !ptab14)
|
|
return ERROR_INT("table ptrs not all defined", procName, 1);
|
|
|
|
/* 3 lookup tables: 1-bit value, (3/8)excess, and (1/4)excess */
|
|
tabval = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
tab38 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
tab14 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
|
|
if (!tabval || !tab38 || !tab14)
|
|
return ERROR_INT("calloc failure to make small table", procName, 1);
|
|
*ptabval = tabval;
|
|
*ptab38 = tab38;
|
|
*ptab14 = tab14;
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
if (i <= lowerclip) {
|
|
tabval[i] = 1;
|
|
tab38[i] = 0;
|
|
tab14[i] = 0;
|
|
} else if (i < 128) {
|
|
tabval[i] = 1;
|
|
tab38[i] = (3 * i + 4) / 8;
|
|
tab14[i] = (i + 2) / 4;
|
|
} else if (i < 255 - upperclip) {
|
|
tabval[i] = 0;
|
|
tab38[i] = (3 * (i - 255) + 4) / 8;
|
|
tab14[i] = ((i - 255) + 2) / 4;
|
|
} else { /* i >= 255 - upperclip */
|
|
tabval[i] = 0;
|
|
tab38[i] = 0;
|
|
tab14[i] = 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* Documentation */
|