/*! * \brief pixLocToColorTransform() * * \param[in] pixs 1 bpp * \return pixd 32 bpp rgb, or NULL on error * * <pre> * Notes: * (1) This generates an RGB image where each component value * is coded depending on the (x.y) location and the size * of the fg connected component that the pixel in pixs belongs to. * It is independent of the 4-fold orthogonal orientation, and * only weakly depends on translations and small angle rotations. * Background pixels are black. * (2) Such encodings can be compared between two 1 bpp images * by performing this transform and calculating the * "earth-mover" distance on the resulting R,G,B histograms. * </pre> */ PIX * pixLocToColorTransform(PIX *pixs) { l_int32 w, h, w2, h2, wpls, wplr, wplg, wplb, wplcc, i, j, rval, gval, bval; l_float32 invw2, invh2; l_uint32 *datas, *datar, *datag, *datab, *datacc; l_uint32 *lines, *liner, *lineg, *lineb, *linecc; PIX *pix1, *pixcc, *pixr, *pixg, *pixb, *pixd; PROCNAME("pixLocToColorTransform"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Label each pixel with the area of the c.c. to which it belongs. * Clip the result to 255 in an 8 bpp pix. This is used for * the blue component of pixd. */ pixGetDimensions(pixs, &w, &h, NULL); w2 = w / 2; h2 = h / 2; invw2 = 255.0 / (l_float32)w2; invh2 = 255.0 / (l_float32)h2; pix1 = pixConnCompAreaTransform(pixs, 8); pixcc = pixConvert32To8(pix1, L_LS_TWO_BYTES, L_CLIP_TO_FF); pixDestroy(&pix1); /* Label the red and green components depending on the location * of the fg pixels, in a way that is 4-fold rotationally invariant. */ pixr = pixCreate(w, h, 8); pixg = pixCreate(w, h, 8); pixb = pixCreate(w, h, 8); wpls = pixGetWpl(pixs); wplr = pixGetWpl(pixr); wplg = pixGetWpl(pixg); wplb = pixGetWpl(pixb); wplcc = pixGetWpl(pixcc); datas = pixGetData(pixs); datar = pixGetData(pixr); datag = pixGetData(pixg); datab = pixGetData(pixb); datacc = pixGetData(pixcc); for (i = 0; i < h; i++) { lines = datas + i * wpls; liner = datar + i * wplr; lineg = datag + i * wplg; lineb = datab + i * wplb; linecc = datacc+ i * wplcc; for (j = 0; j < w; j++) { if (GET_DATA_BIT(lines, j) == 0) continue; if (w < h) { rval = invh2 * L_ABS((l_float32)(i - h2)); gval = invw2 * L_ABS((l_float32)(j - w2)); } else { rval = invw2 * L_ABS((l_float32)(j - w2)); gval = invh2 * L_ABS((l_float32)(i - h2)); } bval = GET_DATA_BYTE(linecc, j); SET_DATA_BYTE(liner, j, rval); SET_DATA_BYTE(lineg, j, gval); SET_DATA_BYTE(lineb, j, bval); } } pixd = pixCreateRGBImage(pixr, pixg, pixb); pixDestroy(&pixcc); pixDestroy(&pixr); pixDestroy(&pixg); pixDestroy(&pixb); return pixd; }
/*! * \brief pixSauvolaBinarize() * * \param[in] pixs 8 bpp grayscale; not colormapped * \param[in] whsize window half-width for measuring local statistics * \param[in] factor factor for reducing threshold due to variance; >= 0 * \param[in] addborder 1 to add border of width (%whsize + 1) on all sides * \param[out] ppixm [optional] local mean values * \param[out] ppixsd [optional] local standard deviation values * \param[out] ppixth [optional] threshold values * \param[out] ppixd [optional] thresholded image * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) The window width and height are 2 * %whsize + 1. The minimum * value for %whsize is 2; typically it is \>= 7.. * (2) The local statistics, measured over the window, are the * average and standard deviation. * (3) The measurements of the mean and standard deviation are * performed inside a border of (%whsize + 1) pixels. If pixs does * not have these added border pixels, use %addborder = 1 to add * it here; otherwise use %addborder = 0. * (4) The Sauvola threshold is determined from the formula: * t = m * (1 - k * (1 - s / 128)) * where: * t = local threshold * m = local mean * k = %factor (\>= 0) [ typ. 0.35 ] * s = local standard deviation, which is maximized at * 127.5 when half the samples are 0 and half are 255. * (5) The basic idea of Niblack and Sauvola binarization is that * the local threshold should be less than the median value, * and the larger the variance, the closer to the median * it should be chosen. Typical values for k are between * 0.2 and 0.5. * </pre> */ l_int32 pixSauvolaBinarize(PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 addborder, PIX **ppixm, PIX **ppixsd, PIX **ppixth, PIX **ppixd) { l_int32 w, h; PIX *pixg, *pixsc, *pixm, *pixms, *pixth, *pixd; PROCNAME("pixSauvolaBinarize"); if (ppixm) *ppixm = NULL; if (ppixsd) *ppixsd = NULL; if (ppixth) *ppixth = NULL; if (ppixd) *ppixd = NULL; if (!ppixm && !ppixsd && !ppixth && !ppixd) return ERROR_INT("no outputs", procName, 1); if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs undefined or not 8 bpp", procName, 1); if (pixGetColormap(pixs)) return ERROR_INT("pixs is cmapped", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); if (whsize < 2) return ERROR_INT("whsize must be >= 2", procName, 1); if (w < 2 * whsize + 3 || h < 2 * whsize + 3) return ERROR_INT("whsize too large for image", procName, 1); if (factor < 0.0) return ERROR_INT("factor must be >= 0", procName, 1); if (addborder) { pixg = pixAddMirroredBorder(pixs, whsize + 1, whsize + 1, whsize + 1, whsize + 1); pixsc = pixClone(pixs); } else { pixg = pixClone(pixs); pixsc = pixRemoveBorder(pixs, whsize + 1); } if (!pixg || !pixsc) return ERROR_INT("pixg and pixsc not made", procName, 1); /* All these functions strip off the border pixels. */ if (ppixm || ppixth || ppixd) pixm = pixWindowedMean(pixg, whsize, whsize, 1, 1); if (ppixsd || ppixth || ppixd) pixms = pixWindowedMeanSquare(pixg, whsize, whsize, 1); if (ppixth || ppixd) pixth = pixSauvolaGetThreshold(pixm, pixms, factor, ppixsd); if (ppixd) { pixd = pixApplyLocalThreshold(pixsc, pixth, 1); pixCopyResolution(pixd, pixs); } if (ppixm) *ppixm = pixm; else pixDestroy(&pixm); pixDestroy(&pixms); if (ppixth) *ppixth = pixth; else pixDestroy(&pixth); if (ppixd) *ppixd = pixd; else pixDestroy(&pixd); pixDestroy(&pixg); pixDestroy(&pixsc); return 0; }
/*! * \brief pixThresholdByConnComp() * * \param[in] pixs depth > 1, colormap OK * \param[in] pixm [optional] 1 bpp mask giving region to ignore by setting * pixels to white; use NULL if no mask * \param[in] start, end, incr binarization threshold levels to test * \param[in] thresh48 threshold on normalized difference between the * numbers of 4 and 8 connected components * \param[in] threshdiff threshold on normalized difference between the * number of 4 cc at successive iterations * \param[out] pglobthresh [optional] best global threshold; 0 * if no threshold is found * \param[out] ppixd [optional] image thresholded to binary, or * null if no threshold is found * \param[in] debugflag 1 for plotted results * \return 0 if OK, 1 on error or if no threshold is found * * <pre> * Notes: * (1) This finds a global threshold based on connected components. * Although slow, it is reasonable to use it in a situation where * (a) the background in the image is relatively uniform, and * (b) the result will be fed to an OCR program that accepts 1 bpp * images and works best with easily segmented characters. * The reason for (b) is that this selects a threshold with a * minimum number of both broken characters and merged characters. * (2) If the pix has color, it is converted to gray using the * max component. * (3) Input 0 to use default values for any of these inputs: * %start, %end, %incr, %thresh48, %threshdiff. * (4) This approach can be understood as follows. When the * binarization threshold is varied, the numbers of c.c. identify * four regimes: * (a) For low thresholds, text is broken into small pieces, and * the number of c.c. is large, with the 4 c.c. significantly * exceeding the 8 c.c. * (b) As the threshold rises toward the optimum value, the text * characters coalesce and there is very little difference * between the numbers of 4 and 8 c.c, which both go * through a minimum. * (c) Above this, the image background gets noisy because some * pixels are(thresholded to foreground, and the numbers * of c.c. quickly increase, with the 4 c.c. significantly * larger than the 8 c.c. * (d) At even higher thresholds, the image background noise * coalesces as it becomes mostly foreground, and the * number of c.c. drops quickly. * (5) If there is no global threshold that distinguishes foreground * text from background (e.g., weak text over a background that * has significant variation and/or bleedthrough), this returns 1, * which the caller should check. * </pre> */ l_int32 pixThresholdByConnComp(PIX *pixs, PIX *pixm, l_int32 start, l_int32 end, l_int32 incr, l_float32 thresh48, l_float32 threshdiff, l_int32 *pglobthresh, PIX **ppixd, l_int32 debugflag) { l_int32 i, thresh, n, n4, n8, mincounts, found, globthresh; l_float32 count4, count8, firstcount4, prevcount4, diff48, diff4; GPLOT *gplot; NUMA *na4, *na8; PIX *pix1, *pix2, *pix3; PROCNAME("pixThresholdByConnComp"); if (pglobthresh) *pglobthresh = 0; if (ppixd) *ppixd = NULL; if (!pixs || pixGetDepth(pixs) == 1) return ERROR_INT("pixs undefined or 1 bpp", procName, 1); if (pixm && pixGetDepth(pixm) != 1) return ERROR_INT("pixm must be 1 bpp", procName, 1); /* Assign default values if requested */ if (start <= 0) start = 80; if (end <= 0) end = 200; if (incr <= 0) incr = 10; if (thresh48 <= 0.0) thresh48 = 0.01; if (threshdiff <= 0.0) threshdiff = 0.01; if (start > end) return ERROR_INT("invalid start,end", procName, 1); /* Make 8 bpp, using the max component if color. */ if (pixGetColormap(pixs)) pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); else pix1 = pixClone(pixs); if (pixGetDepth(pix1) == 32) pix2 = pixConvertRGBToGrayMinMax(pix1, L_CHOOSE_MAX); else pix2 = pixConvertTo8(pix1, 0); pixDestroy(&pix1); /* Mask out any non-text regions. Do this in-place, because pix2 * can never be the same pix as pixs. */ if (pixm) pixSetMasked(pix2, pixm, 255); /* Make sure there are enough components to get a valid signal */ pix3 = pixConvertTo1(pix2, start); pixCountConnComp(pix3, 4, &n4); pixDestroy(&pix3); mincounts = 500; if (n4 < mincounts) { L_INFO("Insufficient component count: %d\n", procName, n4); pixDestroy(&pix2); return 1; } /* Compute the c.c. data */ na4 = numaCreate(0); na8 = numaCreate(0); numaSetParameters(na4, start, incr); numaSetParameters(na8, start, incr); for (thresh = start, i = 0; thresh <= end; thresh += incr, i++) { pix3 = pixConvertTo1(pix2, thresh); pixCountConnComp(pix3, 4, &n4); pixCountConnComp(pix3, 8, &n8); numaAddNumber(na4, n4); numaAddNumber(na8, n8); pixDestroy(&pix3); } if (debugflag) { gplot = gplotCreate("/tmp/threshroot", GPLOT_PNG, "number of cc vs. threshold", "threshold", "number of cc"); gplotAddPlot(gplot, NULL, na4, GPLOT_LINES, "plot 4cc"); gplotAddPlot(gplot, NULL, na8, GPLOT_LINES, "plot 8cc"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } n = numaGetCount(na4); found = FALSE; for (i = 0; i < n; i++) { if (i == 0) { numaGetFValue(na4, i, &firstcount4); prevcount4 = firstcount4; } else { numaGetFValue(na4, i, &count4); numaGetFValue(na8, i, &count8); diff48 = (count4 - count8) / firstcount4; diff4 = L_ABS(prevcount4 - count4) / firstcount4; if (debugflag) { fprintf(stderr, "diff48 = %7.3f, diff4 = %7.3f\n", diff48, diff4); } if (diff48 < thresh48 && diff4 < threshdiff) { found = TRUE; break; } prevcount4 = count4; } } numaDestroy(&na4); numaDestroy(&na8); if (found) { globthresh = start + i * incr; if (pglobthresh) *pglobthresh = globthresh; if (ppixd) { *ppixd = pixConvertTo1(pix2, globthresh); pixCopyResolution(*ppixd, pixs); } if (debugflag) fprintf(stderr, "global threshold = %d\n", globthresh); pixDestroy(&pix2); return 0; } if (debugflag) fprintf(stderr, "no global threshold found\n"); pixDestroy(&pix2); return 1; }
/*! * pixDisplayWriteFormat() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * reduction (-1 to reset/erase; 0 to disable; * otherwise this is a reduction factor) * format (IFF_PNG or IFF_JFIF_JPEG) * Return: 0 if OK; 1 on error * * Notes: * (1) This writes files if reduction > 0. These can be displayed using * pixDisplayMultiple("/tmp/display/file*"); * (2) All previously written files can be erased by calling with * reduction < 0; the value of pixs is ignored. * (3) If reduction > 1 and depth == 1, this does a scale-to-gray * reduction. * (4) This function uses a static internal variable to number * output files written by a single process. Behavior * with a shared library may be unpredictable. * (5) Output file format is as follows: * format == IFF_JFIF_JPEG: * png if d < 8 or d == 16 or if the output pix * has a colormap. Otherwise, output is jpg. * format == IFF_PNG: * png (lossless) on all images. * (6) For 16 bpp, the choice of full dynamic range with log scale * is the best for displaying these images. Alternative outputs are * pix8 = pixMaxDynamicRange(pixt, L_LINEAR_SCALE); * pix8 = pixConvert16To8(pixt, 0); // low order byte * pix8 = pixConvert16To8(pixt, 1); // high order byte */ l_int32 pixDisplayWriteFormat(PIX *pixs, l_int32 reduction, l_int32 format) { char buf[L_BUF_SIZE]; char *fname; l_float32 scale; PIX *pixt, *pix8; static l_int32 index = 0; /* caution: not .so or thread safe */ PROCNAME("pixDisplayWriteFormat"); if (reduction == 0) return 0; if (reduction < 0) { index = 0; /* reset; this will cause erasure at next call to write */ return 0; } if (format != IFF_JFIF_JPEG && format != IFF_PNG) return ERROR_INT("invalid format", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (index == 0) { lept_rmdir("display"); lept_mkdir("display"); } index++; if (reduction == 1) { pixt = pixClone(pixs); } else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pixt = pixScaleToGray(pixs, scale); else pixt = pixScale(pixs, scale, scale); } if (pixGetDepth(pixt) == 16) { pix8 = pixMaxDynamicRange(pixt, L_LOG_SCALE); snprintf(buf, L_BUF_SIZE, "file.%03d.png", index); fname = genPathname("/tmp/display", buf); pixWrite(fname, pix8, IFF_PNG); pixDestroy(&pix8); } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt) || format == IFF_PNG) { snprintf(buf, L_BUF_SIZE, "file.%03d.png", index); fname = genPathname("/tmp/display", buf); pixWrite(fname, pixt, IFF_PNG); } else { snprintf(buf, L_BUF_SIZE, "file.%03d.jpg", index); fname = genPathname("/tmp/display", buf); pixWrite(fname, pixt, format); } FREE(fname); pixDestroy(&pixt); return 0; }
/*! * \brief pixMaskedThreshOnBackgroundNorm() * * \param[in] pixs 8 bpp grayscale; not colormapped * \param[in] pixim [optional] 1 bpp 'image' mask; can be null * \param[in] sx, sy tile size in pixels * \param[in] thresh threshold for determining foreground * \param[in] mincount min threshold on counts in a tile * \param[in] smoothx half-width of block convolution kernel width * \param[in] smoothy half-width of block convolution kernel height * \param[in] scorefract fraction of the max Otsu score; typ. ~ 0.1 * \param[out] pthresh [optional] threshold value that was * used on the normalized image * \return pixd 1 bpp thresholded image, or NULL on error * * <pre> * Notes: * (1) This begins with a standard background normalization. * Additionally, there is a flexible background norm, that * will adapt to a rapidly varying background, and this * puts white pixels in the background near regions with * significant foreground. The white pixels are turned into * a 1 bpp selection mask by binarization followed by dilation. * Otsu thresholding is performed on the input image to get an * estimate of the threshold in the non-mask regions. * The background normalized image is thresholded with two * different values, and the result is combined using * the selection mask. * (2) Note that the numbers 255 (for bgval target) and 190 (for * thresholding on pixn) are tied together, and explicitly * defined in this function. * (3) See pixBackgroundNorm() for meaning and typical values * of input parameters. For a start, you can try: * sx, sy = 10, 15 * thresh = 100 * mincount = 50 * smoothx, smoothy = 2 * </pre> */ PIX * pixMaskedThreshOnBackgroundNorm(PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh) { l_int32 w, h, highthresh; l_uint32 val; PIX *pixn, *pixm, *pixd, *pix1, *pix2, *pix3, *pix4; PROCNAME("pixMaskedThreshOnBackgroundNorm"); if (pthresh) *pthresh = 0; if (!pixs || pixGetDepth(pixs) != 8) return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL); if (pixGetColormap(pixs)) return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL); if (sx < 4 || sy < 4) return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL); if (mincount > sx * sy) { L_WARNING("mincount too large for tile size\n", procName); mincount = (sx * sy) / 3; } /* Standard background normalization */ pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh, mincount, 255, smoothx, smoothy); if (!pixn) return (PIX *)ERROR_PTR("pixn not made", procName, NULL); /* Special background normalization for adaptation to quickly * varying background. Threshold on the very light parts, * which tend to be near significant edges, and dilate to * form a mask over regions that are typically text. The * dilation size is chosen to cover the text completely, * except for very thick fonts. */ pix1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 20); pix2 = pixThresholdToBinary(pix1, 240); pixInvert(pix2, pix2); pixm = pixMorphSequence(pix2, "d21.21", 0); pixDestroy(&pix1); pixDestroy(&pix2); /* Use Otsu to get a global threshold estimate for the image, * which is stored as a single pixel in pix3. */ pixGetDimensions(pixs, &w, &h, NULL); pixOtsuAdaptiveThreshold(pixs, w, h, 0, 0, scorefract, &pix3, NULL); if (pix3 && pthresh) { pixGetPixel(pix3, 0, 0, &val); *pthresh = val; } pixDestroy(&pix3); /* Threshold the background normalized images differentially, * using a high value correlated with the background normalization * for the part of the image under the mask (i.e., near the * darker, thicker foreground), and a value that depends on the Otsu * threshold for the rest of the image. This gives a solid * (high) thresholding for the foreground parts of the image, * while allowing the background and light foreground to be * reasonably well cleaned using a threshold adapted to the * input image. */ highthresh = L_MIN(256, val + 30); pixd = pixThresholdToBinary(pixn, highthresh); /* for bg and light fg */ pix4 = pixThresholdToBinary(pixn, 190); /* for heavier fg */ pixCombineMasked(pixd, pix4, pixm); pixDestroy(&pix4); pixDestroy(&pixm); pixDestroy(&pixn); if (!pixd) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); else return pixd; }
/*! * pixWrite() * * Input: filename * pix * format (defined in imageio.h) * Return: 0 if OK; 1 on error * * Notes: * (1) Open for write using binary mode (with the "b" flag) * to avoid having Windows automatically translate the NL * into CRLF, which corrupts image files. On non-windows * systems this flag should be ignored, per ISO C90. * Thanks to Dave Bryan for pointing this out. * (2) If the default image format IFF_DEFAULT is requested: * use the input format if known; otherwise, use a lossless format. * (3) There are two modes with respect to file naming. * (a) The default code writes to @filename. * (b) If WRITE_AS_NAMED is defined to 0, it's a bit fancier. * Then, if @filename does not have a file extension, one is * automatically appended, depending on the requested format. * The original intent for providing option (b) was to insure * that filenames on Windows have an extension that matches * the image compression. However, this is not the default. */ l_int32 pixWrite(const char *filename, PIX *pix, l_int32 format) { char *fname; FILE *fp; PROCNAME("pixWrite"); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (!filename) return ERROR_INT("filename not defined", procName, 1); if (format == IFF_JP2) return ERROR_INT("jp2 not supported", procName, 1); fname = genPathname(filename, NULL); #if WRITE_AS_NAMED /* Default */ if ((fp = fopenWriteStream(fname, "wb+")) == NULL) { FREE(fname); return ERROR_INT("stream not opened", procName, 1); } #else /* Add an extension to the output name if none exists */ {l_int32 extlen; char *extension, *filebuf; splitPathAtExtension(fname, NULL, &extension); extlen = strlen(extension); FREE(extension); if (extlen == 0) { if (format == IFF_DEFAULT || format == IFF_UNKNOWN) format = pixChooseOutputFormat(pix); filebuf = (char *)CALLOC(strlen(fname) + 10, sizeof(char)); if (!filebuf) { return ERROR_INT("filebuf not made", procName, 1); FREE(fname); } strncpy(filebuf, fname, strlen(fname)); strcat(filebuf, "."); strcat(filebuf, ImageFileFormatExtensions[format]); } else { filebuf = (char *)fname; } fp = fopenWriteStream(filebuf, "wb+"); if (filebuf != fname) FREE(filebuf); if (fp == NULL) { FREE(fname); return ERROR_INT("stream not opened", procName, 1); } } #endif /* WRITE_AS_NAMED */ FREE(fname); if (pixWriteStream(fp, pix, format)) { fclose(fp); return ERROR_INT("pix not written to stream", procName, 1); } /* Close the stream except if GIF under windows, because * EGifCloseFile() closes the windows file stream! */ if (format != IFF_GIF) fclose(fp); #ifndef _WIN32 else /* gif file */ fclose(fp); #endif /* ! _WIN32 */ return 0; }
/*! * pixWriteMem() * * Input: &data (<return> data of tiff compressed image) * &size (<return> size of returned data) * pix * format (defined in imageio.h) * Return: 0 if OK, 1 on error * * Notes: * (1) On windows, this will only write tiff and PostScript to memory. * For other formats, it requires open_memstream(3). * (2) PostScript output is uncompressed, in hex ascii. * Most printers support level 2 compression (tiff_g4 for 1 bpp, * jpeg for 8 and 32 bpp). */ l_int32 pixWriteMem(l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 format) { l_int32 ret; PROCNAME("pixWriteMem"); if (!pdata) return ERROR_INT("&data not defined", procName, 1 ); if (!psize) return ERROR_INT("&size not defined", procName, 1 ); if (!pix) return ERROR_INT("&pix not defined", procName, 1 ); if (format == IFF_DEFAULT) format = pixChooseOutputFormat(pix); switch(format) { case IFF_BMP: ret = pixWriteMemBmp(pdata, psize, pix); break; case IFF_JFIF_JPEG: /* default quality; baseline sequential */ ret = pixWriteMemJpeg(pdata, psize, pix, 75, 0); break; case IFF_PNG: /* no gamma value stored */ ret = pixWriteMemPng(pdata, psize, pix, 0.0); break; case IFF_TIFF: /* uncompressed */ case IFF_TIFF_PACKBITS: /* compressed, binary only */ case IFF_TIFF_RLE: /* compressed, binary only */ case IFF_TIFF_G3: /* compressed, binary only */ case IFF_TIFF_G4: /* compressed, binary only */ case IFF_TIFF_LZW: /* compressed, all depths */ case IFF_TIFF_ZIP: /* compressed, all depths */ ret = pixWriteMemTiff(pdata, psize, pix, format); break; case IFF_PNM: ret = pixWriteMemPnm(pdata, psize, pix); break; case IFF_PS: ret = pixWriteMemPS(pdata, psize, pix, NULL, 0, DEFAULT_SCALING); break; case IFF_GIF: ret = pixWriteMemGif(pdata, psize, pix); break; case IFF_JP2: return ERROR_INT("jp2 not supported", procName, 1); break; case IFF_SPIX: ret = pixWriteMemSpix(pdata, psize, pix); break; default: return ERROR_INT("unknown format", procName, 1); break; } return ret; }
/*! * pixaDisplayTiledInRows() * * Input: pixa * outdepth (output depth: 1, 8 or 32 bpp) * maxwidth (of output image) * scalefactor (applied to every pix; use 1.0 for no scaling) * background (0 for white, 1 for black; this is the color * of the spacing between the images) * spacing (between images, and on outside) * border (width of black border added to each image; * use 0 for no border) * Return: pixd (of tiled images), or null on error * * Notes: * (1) This saves a pixa to a single image file of width not to * exceed maxwidth, with background color either white or black, * and with each row tiled such that the top of each pix is * aligned and separated by 'spacing' from the next one. * A black border can be added to each pix. * (2) All pix are converted to outdepth; existing colormaps are removed. * (3) This does a reasonably spacewise-efficient job of laying * out the individual pix images into a tiled composite. */ PIX * pixaDisplayTiledInRows(PIXA *pixa, l_int32 outdepth, l_int32 maxwidth, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border) { l_int32 h; /* cumulative height over all the rows */ l_int32 w; /* cumulative height in the current row */ l_int32 bordval, wtry, wt, ht; l_int32 irow; /* index of current pix in current row */ l_int32 wmaxrow; /* width of the largest row */ l_int32 maxh; /* max height in row */ l_int32 i, j, index, n, x, y, nrows, ninrow; NUMA *nainrow; /* number of pix in the row */ NUMA *namaxh; /* height of max pix in the row */ PIX *pix, *pixn, *pixt, *pixd; PIXA *pixan; PROCNAME("pixaDisplayTiledInRows"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); if (outdepth != 1 && outdepth != 8 && outdepth != 32) return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL); if (border < 0) border = 0; if (scalefactor <= 0.0) scalefactor = 1.0; if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Normalize depths, scale, remove colormaps; optionally add border */ pixan = pixaCreate(n); bordval = (outdepth == 1) ? 1 : 0; for (i = 0; i < n; i++) { if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) continue; if (outdepth == 1) pixn = pixConvertTo1(pix, 128); else if (outdepth == 8) pixn = pixConvertTo8(pix, FALSE); else /* outdepth == 32 */ pixn = pixConvertTo32(pix); pixDestroy(&pix); if (scalefactor != 1.0) pixt = pixScale(pixn, scalefactor, scalefactor); else pixt = pixClone(pixn); if (border) pixd = pixAddBorder(pixt, border, bordval); else pixd = pixClone(pixt); pixDestroy(&pixn); pixDestroy(&pixt); pixaAddPix(pixan, pixd, L_INSERT); } if (pixaGetCount(pixan) != n) { n = pixaGetCount(pixan); L_WARNING_INT("only got %d components", procName, n); if (n == 0) { pixaDestroy(&pixan); return (PIX *)ERROR_PTR("no components", procName, NULL); } } /* Compute parameters for layout */ nainrow = numaCreate(0); namaxh = numaCreate(0); wmaxrow = 0; w = h = spacing; maxh = 0; /* max height in row */ for (i = 0, irow = 0; i < n; i++, irow++) { pixaGetPixDimensions(pixan, i, &wt, &ht, NULL); wtry = w + wt + spacing; if (wtry > maxwidth) { /* end the current row and start next one */ numaAddNumber(nainrow, irow); numaAddNumber(namaxh, maxh); wmaxrow = L_MAX(wmaxrow, w); h += maxh + spacing; irow = 0; w = wt + 2 * spacing; maxh = ht; } else { w = wtry; maxh = L_MAX(maxh, ht); } } /* Enter the parameters for the last row */ numaAddNumber(nainrow, irow); numaAddNumber(namaxh, maxh); wmaxrow = L_MAX(wmaxrow, w); h += maxh + spacing; if ((pixd = pixCreate(wmaxrow, h, outdepth)) == NULL) { numaDestroy(&nainrow); numaDestroy(&namaxh); pixaDestroy(&pixan); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } /* Reset the background color if necessary */ if ((background == 1 && outdepth == 1) || (background == 0 && outdepth != 1)) pixSetAll(pixd); /* Blit the images to the dest */ nrows = numaGetCount(nainrow); y = spacing; for (i = 0, index = 0; i < nrows; i++) { /* over rows */ numaGetIValue(nainrow, i, &ninrow); numaGetIValue(namaxh, i, &maxh); x = spacing; for (j = 0; j < ninrow; j++, index++) { /* over pix in row */ pix = pixaGetPix(pixan, index, L_CLONE); pixGetDimensions(pix, &wt, &ht, NULL); pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pix, 0, 0); pixDestroy(&pix); x += wt + spacing; } y += maxh + spacing; } numaDestroy(&nainrow); numaDestroy(&namaxh); pixaDestroy(&pixan); return pixd; }
/*! * pixaDisplayTiledAndScaled() * * Input: pixa * outdepth (output depth: 1, 8 or 32 bpp) * tilewidth (each pix is scaled to this width) * ncols (number of tiles in each row) * background (0 for white, 1 for black; this is the color * of the spacing between the images) * spacing (between images, and on outside) * border (width of additional black border on each image; * use 0 for no border) * Return: pix of tiled images, or null on error * * Notes: * (1) This can be used to tile a number of renderings of * an image that are at different scales and depths. * (2) Each image, after scaling and optionally adding the * black border, has width 'tilewidth'. Thus, the border does * not affect the spacing between the image tiles. The * maximum allowed border width is tilewidth / 5. */ PIX * pixaDisplayTiledAndScaled(PIXA *pixa, l_int32 outdepth, l_int32 tilewidth, l_int32 ncols, l_int32 background, l_int32 spacing, l_int32 border) { l_int32 x, y, w, h, wd, hd, d; l_int32 i, n, nrows, maxht, ninrow, irow, bordval; l_int32 *rowht; l_float32 scalefact; PIX *pix, *pixn, *pixt, *pixb, *pixd; PIXA *pixan; PROCNAME("pixaDisplayTiledAndScaled"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); if (outdepth != 1 && outdepth != 8 && outdepth != 32) return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL); if (border < 0 || border > tilewidth / 5) border = 0; if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Normalize scale and depth for each pix; optionally add border */ pixan = pixaCreate(n); bordval = (outdepth == 1) ? 1 : 0; for (i = 0; i < n; i++) { if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) continue; pixGetDimensions(pix, &w, &h, &d); scalefact = (l_float32)(tilewidth - 2 * border) / (l_float32)w; if (d == 1 && outdepth > 1 && scalefact < 1.0) pixt = pixScaleToGray(pix, scalefact); else pixt = pixScale(pix, scalefact, scalefact); if (outdepth == 1) pixn = pixConvertTo1(pixt, 128); else if (outdepth == 8) pixn = pixConvertTo8(pixt, FALSE); else /* outdepth == 32 */ pixn = pixConvertTo32(pixt); pixDestroy(&pixt); if (border) pixb = pixAddBorder(pixn, border, bordval); else pixb = pixClone(pixn); pixaAddPix(pixan, pixb, L_INSERT); pixDestroy(&pix); pixDestroy(&pixn); } if ((n = pixaGetCount(pixan)) == 0) { /* should not have changed! */ pixaDestroy(&pixan); return (PIX *)ERROR_PTR("no components", procName, NULL); } /* Determine the size of each row and of pixd */ wd = tilewidth * ncols + spacing * (ncols + 1); nrows = (n + ncols - 1) / ncols; if ((rowht = (l_int32 *)CALLOC(nrows, sizeof(l_int32))) == NULL) return (PIX *)ERROR_PTR("rowht array not made", procName, NULL); maxht = 0; ninrow = 0; irow = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixan, i, L_CLONE); ninrow++; pixGetDimensions(pix, &w, &h, NULL); maxht = L_MAX(h, maxht); if (ninrow == ncols) { rowht[irow] = maxht; maxht = ninrow = 0; /* reset */ irow++; } pixDestroy(&pix); } if (ninrow > 0) { /* last fencepost */ rowht[irow] = maxht; irow++; /* total number of rows */ } nrows = irow; hd = spacing * (nrows + 1); for (i = 0; i < nrows; i++) hd += rowht[i]; pixd = pixCreate(wd, hd, outdepth); if ((background == 1 && outdepth == 1) || (background == 0 && outdepth != 1)) pixSetAll(pixd); /* Now blit images to pixd */ x = y = spacing; irow = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixan, i, L_CLONE); pixGetDimensions(pix, &w, &h, NULL); if (i && ((i % ncols) == 0)) { /* start new row */ x = spacing; y += spacing + rowht[irow]; irow++; } pixRasterop(pixd, x, y, w, h, PIX_SRC, pix, 0, 0); x += tilewidth + spacing; pixDestroy(&pix); } pixaDestroy(&pixan); FREE(rowht); return pixd; }
/*! * pixaDisplayOnLattice() * * Input: pixa * xspace * yspace * Return: pix of composite images, or null on error * * Notes: * (1) This places each pix on sequentially on a regular lattice * in the rendered composite. If a pix is too large to fit in the * allocated lattice space, it is not rendered. * (2) If any pix has a colormap, all pix are rendered in rgb. * (3) This is useful when putting bitmaps of components, * such as characters, into a single image. */ PIX * pixaDisplayOnLattice(PIXA *pixa, l_int32 xspace, l_int32 yspace) { l_int32 n, nw, nh, w, h, d, wt, ht; l_int32 index, i, j, hascmap; PIX *pix, *pixt, *pixd; PIXA *pixat; PROCNAME("pixaDisplayOnLattice"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); /* If any pix have colormaps, generate rgb */ if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); pixaAnyColormaps(pixa, &hascmap); if (hascmap) { pixat = pixaCreate(n); for (i = 0; i < n; i++) { pixt = pixaGetPix(pixa, i, L_CLONE); pix = pixConvertTo32(pixt); pixaAddPix(pixat, pix, L_INSERT); pixDestroy(&pixt); } } else pixat = pixaCopy(pixa, L_CLONE); nw = (l_int32)sqrt((l_float64)n); nh = (n + nw - 1) / nw; w = xspace * nw; h = yspace * nh; /* Use the first pix in pixa to determine the depth. */ pixaGetPixDimensions(pixat, 0, NULL, NULL, &d); if ((pixd = pixCreate(w, h, d)) == NULL) { pixaDestroy(&pixat); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } index = 0; for (i = 0; i < nh; i++) { for (j = 0; j < nw && index < n; j++, index++) { pixt = pixaGetPix(pixat, index, L_CLONE); pixGetDimensions(pixt, &wt, &ht, NULL); if (wt > xspace || ht > yspace) { fprintf(stderr, "pix(%d) omitted; size %dx%d\n", index, wt, ht); pixDestroy(&pixt); continue; } pixRasterop(pixd, j * xspace, i * yspace, wt, ht, PIX_PAINT, pixt, 0, 0); pixDestroy(&pixt); } } pixaDestroy(&pixat); return pixd; }
/*! * pixaDisplayTiled() * * Input: pixa * maxwidth (of output image) * background (0 for white, 1 for black) * spacing * Return: pix of tiled images, or null on error * * Notes: * (1) This saves a pixa to a single image file of width not to * exceed maxwidth, with background color either white or black, * and with each subimage spaced on a regular lattice. * (2) The lattice size is determined from the largest width and height, * separately, of all pix in the pixa. * (3) All pix in the pixa must be of equal depth. * (4) If any pix has a colormap, all pix are rendered in rgb. * (5) Careful: because no components are omitted, this is * dangerous if there are thousands of small components and * one or more very large one, because the size of the * resulting pix can be huge! */ PIX * pixaDisplayTiled(PIXA *pixa, l_int32 maxwidth, l_int32 background, l_int32 spacing) { l_int32 w, h, wmax, hmax, wd, hd, d, hascmap; l_int32 i, j, n, ni, ncols, nrows; l_int32 ystart, xstart, wt, ht; PIX *pix, *pixt, *pixd; PIXA *pixat; PROCNAME("pixaDisplayTiled"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); /* If any pix have colormaps, generate rgb */ if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); pixaAnyColormaps(pixa, &hascmap); if (hascmap) { pixat = pixaCreate(n); for (i = 0; i < n; i++) { pixt = pixaGetPix(pixa, i, L_CLONE); pix = pixConvertTo32(pixt); pixaAddPix(pixat, pix, L_INSERT); pixDestroy(&pixt); } } else pixat = pixaCopy(pixa, L_CLONE); /* Find the largest width and height of the subimages */ wmax = hmax = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixat, i, L_CLONE); pixGetDimensions(pix, &w, &h, NULL); if (i == 0) d = pixGetDepth(pix); else if (d != pixGetDepth(pix)) { pixDestroy(&pix); pixaDestroy(&pixat); return (PIX *)ERROR_PTR("depths not equal", procName, NULL); } if (w > wmax) wmax = w; if (h > hmax) hmax = h; pixDestroy(&pix); } /* Get the number of rows and columns and the output image size */ spacing = L_MAX(spacing, 0); ncols = (l_int32)((l_float32)(maxwidth - spacing) / (l_float32)(wmax + spacing)); nrows = (n + ncols - 1) / ncols; wd = wmax * ncols + spacing * (ncols + 1); hd = hmax * nrows + spacing * (nrows + 1); if ((pixd = pixCreate(wd, hd, d)) == NULL) { pixaDestroy(&pixat); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } #if 0 fprintf(stderr, " nrows = %d, ncols = %d, wmax = %d, hmax = %d\n", nrows, ncols, wmax, hmax); fprintf(stderr, " space = %d, wd = %d, hd = %d, n = %d\n", space, wd, hd, n); #endif /* Reset the background color if necessary */ if ((background == 1 && d == 1) || (background == 0 && d != 1)) pixSetAll(pixd); /* Blit the images to the dest */ for (i = 0, ni = 0; i < nrows; i++) { ystart = spacing + i * (hmax + spacing); for (j = 0; j < ncols && ni < n; j++, ni++) { xstart = spacing + j * (wmax + spacing); pix = pixaGetPix(pixat, ni, L_CLONE); wt = pixGetWidth(pix); ht = pixGetHeight(pix); pixRasterop(pixd, xstart, ystart, wt, ht, PIX_SRC, pix, 0, 0); pixDestroy(&pix); } } pixaDestroy(&pixat); return pixd; }
/*! * pixaDisplayOnColor() * * Input: pixa * w, h (if set to 0, determines the size from the * b.b. of the components in pixa) * color (background color to use) * Return: pix, or null on error * * Notes: * (1) This uses the boxes to place each pix in the rendered composite. * (2) Set w = h = 0 to use the b.b. of the components to determine * the size of the returned pix. * (3) If any pix in @pixa are colormapped, or if the pix have * different depths, it returns a 32 bpp pix. Otherwise, * the depth of the returned pixa equals that of the pix in @pixa. * (4) If the pixa is empty, return null. */ PIX * pixaDisplayOnColor(PIXA *pixa, l_int32 w, l_int32 h, l_uint32 bgcolor) { l_int32 i, n, xb, yb, wb, hb, hascmap, maxdepth, same; BOXA *boxa; PIX *pixt1, *pixt2, *pixd; PIXA *pixat; PROCNAME("pixaDisplayOnColor"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* If w and h are not input, determine the minimum size * required to contain the origin and all c.c. */ if (w == 0 || h == 0) { boxa = pixaGetBoxa(pixa, L_CLONE); boxaGetExtent(boxa, &w, &h, NULL); boxaDestroy(&boxa); } /* If any pix have colormaps, or if they have different depths, * generate rgb */ pixaAnyColormaps(pixa, &hascmap); pixaGetDepthInfo(pixa, &maxdepth, &same); if (hascmap || !same) { maxdepth = 32; pixat = pixaCreate(n); for (i = 0; i < n; i++) { pixt1 = pixaGetPix(pixa, i, L_CLONE); pixt2 = pixConvertTo32(pixt1); pixaAddPix(pixat, pixt2, L_INSERT); pixDestroy(&pixt1); } } else pixat = pixaCopy(pixa, L_CLONE); /* Make the output pix and set the background color */ if ((pixd = pixCreate(w, h, maxdepth)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); if ((maxdepth == 1 && bgcolor > 0) || (maxdepth == 2 && bgcolor >= 0x3) || (maxdepth == 4 && bgcolor >= 0xf) || (maxdepth == 8 && bgcolor >= 0xff) || (maxdepth == 16 && bgcolor >= 0xffff) || (maxdepth == 32 && bgcolor >= 0xffffff00)) { pixSetAll(pixd); } else if (bgcolor > 0) pixSetAllArbitrary(pixd, bgcolor); /* Blit each pix into its place */ for (i = 0; i < n; i++) { if (pixaGetBoxGeometry(pixat, i, &xb, &yb, &wb, &hb)) { L_WARNING("no box found!", procName); continue; } pixt1 = pixaGetPix(pixat, i, L_CLONE); pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pixt1, 0, 0); pixDestroy(&pixt1); } pixaDestroy(&pixat); return pixd; }
/*! * pixaaDisplayByPixa() * * Input: pixaa * xspace between pix in pixa * yspace between pixa * max width of output pix * Return: pix, or null on error * * Notes: * (1) Displays each pixa on a line (or set of lines), * in order from top to bottom. Within each pixa, * the pix are displayed in order from left to right. * (2) The size of each pix in each pixa is assumed to be * approximately equal to the size of the first pix in * the pixa. If this assumption is not correct, this * function will not work properly. * (3) This ignores the boxa of the pixaa. */ PIX * pixaaDisplayByPixa(PIXAA *pixaa, l_int32 xspace, l_int32 yspace, l_int32 maxw) { l_int32 i, j, npixa, npix; l_int32 width, height, depth, nlines, lwidth; l_int32 x, y, w, h, w0, h0; PIX *pixt, *pixd; PIXA *pixa; PROCNAME("pixaaDisplayByPixa"); if (!pixaa) return (PIX *)ERROR_PTR("pixaa not defined", procName, NULL); if ((npixa = pixaaGetCount(pixaa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Get size of output pix. The width is the minimum of the * maxw and the largest pixa line width. The height is whatever * it needs to be to accommodate all pixa. */ height = 2 * yspace; width = 0; for (i = 0; i < npixa; i++) { pixa = pixaaGetPixa(pixaa, i, L_CLONE); npix = pixaGetCount(pixa); pixt = pixaGetPix(pixa, 0, L_CLONE); if (i == 0) depth = pixGetDepth(pixt); w = pixGetWidth(pixt); lwidth = npix * (w + xspace); nlines = (lwidth + maxw - 1) / maxw; if (nlines > 1) width = maxw; else width = L_MAX(lwidth, width); height += nlines * (pixGetHeight(pixt) + yspace); pixDestroy(&pixt); pixaDestroy(&pixa); } if ((pixd = pixCreate(width, height, depth)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); /* Now layout the pix by pixa */ y = yspace; for (i = 0; i < npixa; i++) { x = 0; pixa = pixaaGetPixa(pixaa, i, L_CLONE); npix = pixaGetCount(pixa); for (j = 0; j < npix; j++) { pixt = pixaGetPix(pixa, j, L_CLONE); if (j == 0) { w0 = pixGetWidth(pixt); h0 = pixGetHeight(pixt); } w = pixGetWidth(pixt); if (width == maxw && x + w >= maxw) { x = 0; y += h0 + yspace; } h = pixGetHeight(pixt); pixRasterop(pixd, x, y, w, h, PIX_PAINT, pixt, 0, 0); pixDestroy(&pixt); x += w0 + xspace; } y += h0 + yspace; pixaDestroy(&pixa); } return pixd; }
/*! * regTestCompareFiles() * * Input: stream (for output; use NULL to generate golden files) * argv ([0] == name of reg test) * index1 (of one output file from reg test) * index2 (of another output file from reg test) * &success (<return> 0 on if different; input value on success) * Return: 0 if OK, 1 on error (a failure in comparison is not an error) * * Notes: * (1) If @fp != NULL, this function compares two golden files to * determine if they are the same. If @fp == NULL, this is a * "generate" operation; don't do the comparison. * (2) This function can be called repeatedly in a single reg test. * (3) The value for @success is initialized to TRUE in the reg test * setup before this function is called for the first time. * A failure in any file comparison is registered as a failure * of the regression test. * (4) The canonical format of the golden filenames is: * /tmp/<root of main name>_golden.<index>.<ext of localname> * e.g., * /tmp/maze_golden.0.png */ l_int32 regTestCompareFiles(FILE *fp, char **argv, l_int32 index1, l_int32 index2, l_int32 *psuccess) { char *root, *name1, *name2; char namebuf[64]; l_int32 error,same; SARRAY *sa; PROCNAME("regTestCompareFiles"); if (!psuccess) return ERROR_INT("&success not defined", procName, 1); if (index1 < 0 || index2 < 0) return ERROR_INT("index1 and/or index2 is negative", procName, 1); if (index1 == index2) return ERROR_INT("index1 must differ from index2", procName, 1); if (!fp) /* no-op */ return 0; /* Generate partial golden file names and find the actual * paths to them. */ error = FALSE; name1 = name2 = NULL; if ((root = getRootNameFromArgv0(argv[0])) == NULL) return ERROR_INT("invalid root", procName, 1); snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", root, index1); sa = getSortedPathnamesInDirectory("/tmp", namebuf, 0, 0); if (sarrayGetCount(sa) != 1) error = TRUE; else name1 = sarrayGetString(sa, 0, L_COPY); sarrayDestroy(&sa); snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", root, index2); sa = getSortedPathnamesInDirectory("/tmp", namebuf, 0, 0); if (sarrayGetCount(sa) != 1) error = TRUE; else name2 = sarrayGetString(sa, 0, L_COPY); sarrayDestroy(&sa); FREE(root); if (error == TRUE) { if (name1) FREE(name1); if (name2) FREE(name2); L_ERROR("golden files not found", procName); return 1; } /* Test and record on failure */ filesAreIdentical(name1, name2, &same); if (!same) { fprintf(fp, "Failure in %s: comparing %s with %s\n", argv[0], name1, name2); fprintf(stderr, "Failure in %s: comparing %s with %s\n", argv[0], name1, name2); *psuccess = 0; } FREE(name1); FREE(name2); return 0; }
/*! * gplotRead() * * Input: filename * Return: gplot, or NULL on error */ GPLOT * gplotRead(const char *filename) { char buf[L_BUF_SIZE]; char *rootname, *title, *xlabel, *ylabel, *ignores; l_int32 outformat, ret, version, ignore; FILE *fp; GPLOT *gplot; PROCNAME("gplotRead"); if (!filename) return (GPLOT *)ERROR_PTR("filename not defined", procName, NULL); if ((fp = fopenReadStream(filename)) == NULL) return (GPLOT *)ERROR_PTR("stream not opened", procName, NULL); ret = fscanf(fp, "Gplot Version %d\n", &version); if (ret != 1) { fclose(fp); return (GPLOT *)ERROR_PTR("not a gplot file", procName, NULL); } if (version != GPLOT_VERSION_NUMBER) { fclose(fp); return (GPLOT *)ERROR_PTR("invalid gplot version", procName, NULL); } ignore = fscanf(fp, "Rootname: %s\n", buf); rootname = stringNew(buf); ignore = fscanf(fp, "Output format: %d\n", &outformat); ignores = fgets(buf, L_BUF_SIZE, fp); /* Title: ... */ title = stringNew(buf + 7); title[strlen(title) - 1] = '\0'; ignores = fgets(buf, L_BUF_SIZE, fp); /* X axis label: ... */ xlabel = stringNew(buf + 14); xlabel[strlen(xlabel) - 1] = '\0'; ignores = fgets(buf, L_BUF_SIZE, fp); /* Y axis label: ... */ ylabel = stringNew(buf + 14); ylabel[strlen(ylabel) - 1] = '\0'; if (!(gplot = gplotCreate(rootname, outformat, title, xlabel, ylabel))) { fclose(fp); return (GPLOT *)ERROR_PTR("gplot not made", procName, NULL); } FREE(rootname); FREE(title); FREE(xlabel); FREE(ylabel); sarrayDestroy(&gplot->cmddata); sarrayDestroy(&gplot->datanames); sarrayDestroy(&gplot->plotdata); sarrayDestroy(&gplot->plottitles); numaDestroy(&gplot->plotstyles); ignore = fscanf(fp, "Commandfile name: %s\n", buf); stringReplace(&gplot->cmdname, buf); ignore = fscanf(fp, "\nCommandfile data:"); gplot->cmddata = sarrayReadStream(fp); ignore = fscanf(fp, "\nDatafile names:"); gplot->datanames = sarrayReadStream(fp); ignore = fscanf(fp, "\nPlot data:"); gplot->plotdata = sarrayReadStream(fp); ignore = fscanf(fp, "\nPlot titles:"); gplot->plottitles = sarrayReadStream(fp); ignore = fscanf(fp, "\nPlot styles:"); gplot->plotstyles = numaReadStream(fp); ignore = fscanf(fp, "Number of plots: %d\n", &gplot->nplots); ignore = fscanf(fp, "Output file name: %s\n", buf); stringReplace(&gplot->outname, buf); ignore = fscanf(fp, "Axis scaling: %d\n", &gplot->scaling); fclose(fp); return gplot; }
/*! * pixaaDisplay() * * Input: pixaa * w, h (if set to 0, determines the size from the * b.b. of the components in pixaa) * Return: pix, or null on error * * Notes: * (1) Each pix of the pixaa is displayed at the location given by * its box, translated by the box of the containing pixa * if it exists. */ PIX * pixaaDisplay(PIXAA *pixaa, l_int32 w, l_int32 h) { l_int32 i, j, n, nbox, na, d, wmax, hmax, x, y, xb, yb, wb, hb; BOXA *boxa1; /* top-level boxa */ BOXA *boxa; PIX *pixt, *pixd; PIXA *pixa; PROCNAME("pixaaDisplay"); if (!pixaa) return (PIX *)ERROR_PTR("pixaa not defined", procName, NULL); n = pixaaGetCount(pixaa); if (n == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* If w and h not input, determine the minimum size required * to contain the origin and all c.c. */ boxa1 = pixaaGetBoxa(pixaa, L_CLONE); nbox = boxaGetCount(boxa1); if (w == 0 || h == 0) { if (nbox == n) boxaGetExtent(boxa1, &w, &h, NULL); else { /* have to use the lower-level boxa for each pixa */ wmax = hmax = 0; for (i = 0; i < n; i++) { pixa = pixaaGetPixa(pixaa, i, L_CLONE); boxa = pixaGetBoxa(pixa, L_CLONE); boxaGetExtent(boxa, &w, &h, NULL); wmax = L_MAX(wmax, w); hmax = L_MAX(hmax, h); pixaDestroy(&pixa); boxaDestroy(&boxa); } w = wmax; h = hmax; } } /* Get depth from first pix */ pixa = pixaaGetPixa(pixaa, 0, L_CLONE); pixt = pixaGetPix(pixa, 0, L_CLONE); d = pixGetDepth(pixt); pixaDestroy(&pixa); pixDestroy(&pixt); if ((pixd = pixCreate(w, h, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); x = y = 0; for (i = 0; i < n; i++) { pixa = pixaaGetPixa(pixaa, i, L_CLONE); if (nbox == n) boxaGetBoxGeometry(boxa1, i, &x, &y, NULL, NULL); na = pixaGetCount(pixa); for (j = 0; j < na; j++) { pixaGetBoxGeometry(pixa, j, &xb, &yb, &wb, &hb); pixt = pixaGetPix(pixa, j, L_CLONE); pixRasterop(pixd, x + xb, y + yb, wb, hb, PIX_PAINT, pixt, 0, 0); pixDestroy(&pixt); } pixaDestroy(&pixa); } boxaDestroy(&boxa1); return pixd; }
/*! * pixSaveTiledOutline() * * Input: pixs (1, 2, 4, 8, 32 bpp) * pixa (the pix are accumulated here) * scalefactor (0.0 to disable; otherwise this is a scale factor) * newrow (0 if placed on the same row as previous; 1 otherwise) * space (horizontal and vertical spacing, in pixels) * linewidth (width of added outline for image; 0 for no outline) * dp (depth of pixa; 8 or 32 bpp; only used on first call) * Return: 0 if OK, 1 on error. * * Notes: * (1) Before calling this function for the first time, use * pixaCreate() to make the @pixa that will accumulate the pix. * This is passed in each time pixSaveTiled() is called. * (2) @scalefactor scales the input image. After scaling and * possible depth conversion, the image is saved in the input * pixa, along with a box that specifies the location to * place it when tiled later. Disable saving the pix by * setting @scalefactor == 0.0. * (3) @newrow and @space specify the location of the new pix * with respect to the last one(s) that were entered. * (4) @dp specifies the depth at which all pix are saved. It can * be only 8 or 32 bpp. Any colormap is removed. This is only * used at the first invocation. * (5) This function uses two variables from call to call. * If they were static, the function would not be .so or thread * safe, and furthermore, there would be interference with two or * more pixa accumulating images at a time. Consequently, * we use the first pix in the pixa to store and obtain both * the depth and the current position of the bottom (one pixel * below the lowest image raster line when laid out using * the boxa). The bottom variable is stored in the input format * field, which is the only field available for storing an int. */ l_int32 pixSaveTiledOutline(PIX *pixs, PIXA *pixa, l_float32 scalefactor, l_int32 newrow, l_int32 space, l_int32 linewidth, l_int32 dp) { l_int32 n, top, left, bx, by, bw, w, h, depth, bottom; BOX *box; PIX *pix1, *pix2, *pix3, *pix4; PROCNAME("pixSaveTiledOutline"); if (scalefactor == 0.0) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); if (n == 0) { bottom = 0; if (dp != 8 && dp != 32) { L_WARNING("dp not 8 or 32 bpp; using 32\n", procName); depth = 32; } else { depth = dp; } } else { /* extract the depth and bottom params from the first pix */ pix1 = pixaGetPix(pixa, 0, L_CLONE); depth = pixGetDepth(pix1); bottom = pixGetInputFormat(pix1); /* not typical usage! */ pixDestroy(&pix1); } /* Remove colormap if it exists; otherwise a copy. This * guarantees that pix4 is not a clone of pixs. */ pix1 = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY); /* Scale and convert to output depth */ if (scalefactor == 1.0) { pix2 = pixClone(pix1); } else if (scalefactor > 1.0) { pix2 = pixScale(pix1, scalefactor, scalefactor); } else if (scalefactor < 1.0) { if (pixGetDepth(pix1) == 1) pix2 = pixScaleToGray(pix1, scalefactor); else pix2 = pixScale(pix1, scalefactor, scalefactor); } pixDestroy(&pix1); if (depth == 8) pix3 = pixConvertTo8(pix2, 0); else pix3 = pixConvertTo32(pix2); pixDestroy(&pix2); /* Add black outline */ if (linewidth > 0) pix4 = pixAddBorder(pix3, linewidth, 0); else pix4 = pixClone(pix3); pixDestroy(&pix3); /* Find position of current pix (UL corner plus size) */ if (n == 0) { top = 0; left = 0; } else if (newrow == 1) { top = bottom + space; left = 0; } else if (n > 0) { pixaGetBoxGeometry(pixa, n - 1, &bx, &by, &bw, NULL); top = by; left = bx + bw + space; } pixGetDimensions(pix4, &w, &h, NULL); bottom = L_MAX(bottom, top + h); box = boxCreate(left, top, w, h); pixaAddPix(pixa, pix4, L_INSERT); pixaAddBox(pixa, box, L_INSERT); /* Save the new bottom value */ pix1 = pixaGetPix(pixa, 0, L_CLONE); pixSetInputFormat(pix1, bottom); /* not typical usage! */ pixDestroy(&pix1); return 0; }
/*! * pixApplyVerticalDisparity() * * Input: pixs (1, 8 or 32 bpp) * fpix (vertical disparity array) * Return: pixd (modified by fpix), or null on error * * Notes: * (1) This applies the vertical disparity array to the specified * image. For src pixels above the image, we use the pixels * in the first raster line. */ PIX * pixApplyVerticalDisparity(PIX *pixs, FPIX *fpix) { l_int32 i, j, w, h, d, fw, fh, wpld, wplf, isrc, val8; l_uint32 *datad, *lined; l_float32 *dataf, *linef; void **lineptrs; PIX *pixd; PROCNAME("pixApplyVerticalDisparity"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!fpix) return (PIX *)ERROR_PTR("fpix not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 1 && d != 8 && d != 32) return (PIX *)ERROR_PTR("pix not 1, 8 or 32 bpp", procName, NULL); fpixGetDimensions(fpix, &fw, &fh); if (fw < w || fh < h) { fprintf(stderr, "fw = %d, w = %d, fh = %d, h = %d\n", fw, w, fh, h); return (PIX *)ERROR_PTR("invalid fpix size", procName, NULL); } pixd = pixCreateTemplate(pixs); datad = pixGetData(pixd); dataf = fpixGetData(fpix); wpld = pixGetWpl(pixd); wplf = fpixGetWpl(fpix); if (d == 1) { lineptrs = pixGetLinePtrs(pixs, NULL); for (i = 0; i < h; i++) { lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < w; j++) { isrc = (l_int32)(i - linef[j] + 0.5); if (isrc < 0) isrc = 0; if (isrc > h - 1) isrc = h - 1; if (GET_DATA_BIT(lineptrs[isrc], j)) SET_DATA_BIT(lined, j); } } } else if (d == 8) { lineptrs = pixGetLinePtrs(pixs, NULL); for (i = 0; i < h; i++) { lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < w; j++) { isrc = (l_int32)(i - linef[j] + 0.5); if (isrc < 0) isrc = 0; if (isrc > h - 1) isrc = h - 1; val8 = GET_DATA_BYTE(lineptrs[isrc], j); SET_DATA_BYTE(lined, j, val8); } } } else { /* d == 32 */ lineptrs = pixGetLinePtrs(pixs, NULL); for (i = 0; i < h; i++) { lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < w; j++) { isrc = (l_int32)(i - linef[j] + 0.5); if (isrc < 0) isrc = 0; if (isrc > h - 1) isrc = h - 1; lined[j] = GET_DATA_FOUR_BYTES(lineptrs[isrc], j); } } } FREE(lineptrs); return pixd; }
/*! * pixWriteStream() * * Input: stream * pix * format * Return: 0 if OK; 1 on error. */ l_int32 pixWriteStream(FILE *fp, PIX *pix, l_int32 format) { PROCNAME("pixWriteStream"); if (!fp) return ERROR_INT("stream not defined", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (format == IFF_DEFAULT) format = pixChooseOutputFormat(pix); switch(format) { case IFF_BMP: pixWriteStreamBmp(fp, pix); break; case IFF_JFIF_JPEG: /* default quality; baseline sequential */ return pixWriteStreamJpeg(fp, pix, 75, 0); break; case IFF_PNG: /* no gamma value stored */ return pixWriteStreamPng(fp, pix, 0.0); break; case IFF_TIFF: /* uncompressed */ case IFF_TIFF_PACKBITS: /* compressed, binary only */ case IFF_TIFF_RLE: /* compressed, binary only */ case IFF_TIFF_G3: /* compressed, binary only */ case IFF_TIFF_G4: /* compressed, binary only */ case IFF_TIFF_LZW: /* compressed, all depths */ case IFF_TIFF_ZIP: /* compressed, all depths */ return pixWriteStreamTiff(fp, pix, format); break; case IFF_PNM: return pixWriteStreamPnm(fp, pix); break; case IFF_GIF: return pixWriteStreamGif(fp, pix); break; case IFF_PS: return pixWriteStreamPS(fp, pix, NULL, 0, DEFAULT_SCALING); break; case IFF_JP2: return ERROR_INT("jp2 format not supported", procName, 1); break; case IFF_WEBP: return pixWriteStreamWebP(fp, pix, 80, 0); break; case IFF_LPDF: return pixWriteStreamPdf(fp, pix, 0, NULL); break; case IFF_SPIX: return pixWriteStreamSpix(fp, pix); break; default: return ERROR_INT("unknown format", procName, 1); break; } return 0; }
/*! * pixApplyHorizontalDisparity() * * Input: pixs (1, 8 or 32 bpp) * fpix (horizontal disparity array) * extraw (extra width added to pixd) * Return: pixd (modified by fpix), or null on error * * Notes: * (1) This applies the horizontal disparity array to the specified * image. */ PIX * pixApplyHorizontalDisparity(PIX *pixs, FPIX *fpix, l_int32 extraw) { l_int32 i, j, w, h, d, wd, fw, fh, wpls, wpld, wplf, jsrc, val8; l_uint32 *datas, *lines, *datad, *lined; l_float32 *dataf, *linef; PIX *pixd; PROCNAME("pixApplyHorizontalDisparity"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!fpix) return (PIX *)ERROR_PTR("fpix not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 1 && d != 8 && d != 32) return (PIX *)ERROR_PTR("pix not 1, 8 or 32 bpp", procName, NULL); fpixGetDimensions(fpix, &fw, &fh); if (fw < w + extraw || fh < h) { fprintf(stderr, "fw = %d, w = %d, fh = %d, h = %d\n", fw, w, fh, h); return (PIX *)ERROR_PTR("invalid fpix size", procName, NULL); } wd = w + extraw; pixd = pixCreate(wd, h, d); datas = pixGetData(pixs); datad = pixGetData(pixd); dataf = fpixGetData(fpix); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); wplf = fpixGetWpl(fpix); if (d == 1) { for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < wd; j++) { jsrc = (l_int32)(j - linef[j] + 0.5); if (jsrc < 0) jsrc = 0; if (jsrc > w - 1) jsrc = w - 1; if (GET_DATA_BIT(lines, jsrc)) SET_DATA_BIT(lined, j); } } } else if (d == 8) { for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < wd; j++) { jsrc = (l_int32)(j - linef[j] + 0.5); if (jsrc < 0) jsrc = 0; if (jsrc > w - 1) jsrc = w - 1; val8 = GET_DATA_BYTE(lines, jsrc); SET_DATA_BYTE(lined, j, val8); } } } else { /* d == 32 */ for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < wd; j++) { jsrc = (l_int32)(j - linef[j] + 0.5); if (jsrc < 0) jsrc = 0; if (jsrc > w - 1) jsrc = w - 1; lined[j] = lines[jsrc]; } } } return pixd; }
/*! * pixDisplayWithTitle() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * x, y (location of display frame) * title (<optional> on frame; can be NULL); * dispflag (1 to write, else disabled) * Return: 0 if OK; 1 on error * * Notes: * (1) See notes for pixDisplay(). * (2) This displays the image if dispflag == 1. */ l_int32 pixDisplayWithTitle(PIX *pixs, l_int32 x, l_int32 y, const char *title, l_int32 dispflag) { char *tempname; char buffer[L_BUF_SIZE]; static l_int32 index = 0; /* caution: not .so or thread safe */ l_int32 w, h, d, spp, maxheight, opaque, threeviews, ignore; l_float32 ratw, rath, ratmin; PIX *pix0, *pix1, *pix2; PIXCMAP *cmap; #ifndef _WIN32 l_int32 wt, ht; #else char *pathname; char fullpath[_MAX_PATH]; #endif /* _WIN32 */ PROCNAME("pixDisplayWithTitle"); if (dispflag != 1) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (var_DISPLAY_PROG != L_DISPLAY_WITH_XZGV && var_DISPLAY_PROG != L_DISPLAY_WITH_XLI && var_DISPLAY_PROG != L_DISPLAY_WITH_XV && var_DISPLAY_PROG != L_DISPLAY_WITH_IV && var_DISPLAY_PROG != L_DISPLAY_WITH_OPEN) { return ERROR_INT("no program chosen for display", procName, 1); } /* Display with three views if either spp = 4 or if colormapped * and the alpha component is not fully opaque */ opaque = TRUE; if ((cmap = pixGetColormap(pixs)) != NULL) pixcmapIsOpaque(cmap, &opaque); spp = pixGetSpp(pixs); threeviews = (spp == 4 || !opaque) ? TRUE : FALSE; /* If colormapped and not opaque, remove the colormap to RGBA */ if (!opaque) pix0 = pixRemoveColormap(pixs, REMOVE_CMAP_WITH_ALPHA); else pix0 = pixClone(pixs); /* Scale if necessary; this will also remove a colormap */ pixGetDimensions(pix0, &w, &h, &d); maxheight = (threeviews) ? MAX_DISPLAY_HEIGHT / 3 : MAX_DISPLAY_HEIGHT; if (w <= MAX_DISPLAY_WIDTH && h <= maxheight) { if (d == 16) /* take MSB */ pix1 = pixConvert16To8(pix0, 1); else pix1 = pixClone(pix0); } else { ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w; rath = (l_float32)maxheight / (l_float32)h; ratmin = L_MIN(ratw, rath); if (ratmin < 0.125 && d == 1) pix1 = pixScaleToGray8(pix0); else if (ratmin < 0.25 && d == 1) pix1 = pixScaleToGray4(pix0); else if (ratmin < 0.33 && d == 1) pix1 = pixScaleToGray3(pix0); else if (ratmin < 0.5 && d == 1) pix1 = pixScaleToGray2(pix0); else pix1 = pixScale(pix0, ratmin, ratmin); } pixDestroy(&pix0); if (!pix1) return ERROR_INT("pix1 not made", procName, 1); /* Generate the three views if required */ if (threeviews) pix2 = pixDisplayLayersRGBA(pix1, 0xffffff00, 0); else pix2 = pixClone(pix1); if (index == 0) { lept_rmdir("disp"); lept_mkdir("disp"); } index++; if (pixGetDepth(pix2) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.png", index); pixWrite(buffer, pix2, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.jpg", index); pixWrite(buffer, pix2, IFF_JFIF_JPEG); } tempname = stringNew(buffer); #ifndef _WIN32 /* Unix */ if (var_DISPLAY_PROG == L_DISPLAY_WITH_XZGV) { /* no way to display title */ pixGetDimensions(pix2, &wt, &ht, NULL); snprintf(buffer, L_BUF_SIZE, "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10, x, y, tempname); } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XLI) { if (title) { snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d -title \"%s\" %s &", x, y, title, tempname); } else { snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d %s &", x, y, tempname); } } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XV) { if (title) { snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d -name \"%s\" %s &", x, y, title, tempname); } else { snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d %s &", x, y, tempname); } } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_OPEN) { snprintf(buffer, L_BUF_SIZE, "open %s &", tempname); } ignore = system(buffer); #else /* _WIN32 */ /* Windows: L_DISPLAY_WITH_IV */ pathname = genPathname(tempname, NULL); _fullpath(fullpath, pathname, sizeof(fullpath)); if (title) { snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"", fullpath, x, y, title); } else { snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d)", fullpath, x, y); } ignore = system(buffer); FREE(pathname); #endif /* _WIN32 */ pixDestroy(&pix1); pixDestroy(&pix2); FREE(tempname); return 0; }
/*! * dewarpBuildModel() * * Input: dew * debugflag (1 for debugging output) * Return: 0 if OK, 1 on error * * Notes: * (1) This is the basic function that builds the vertical * disparity array, which allows determination of the * src pixel in the input image corresponding to each * dest pixel in the dewarped image. * (2) The method is as follows: * * Estimate the centers of all the long textlines and * fit a LS quadratic to each one. This smooths the curves. * * Sample each curve at a regular interval, find the y-value * of the flat point on each curve, and subtract the sampled * curve value from this value. This is the vertical * disparity. * * Fit a LS quadratic to each set of vertically aligned * disparity samples. This smooths the disparity values * in the vertical direction. Then resample at the same * regular interval, We now have a regular grid of smoothed * vertical disparity valuels. * * Interpolate this grid to get a full resolution disparity * map. This can be applied directly to the src image * pixels to dewarp the image in the vertical direction, * making all textlines horizontal. */ l_int32 dewarpBuildModel(L_DEWARP *dew, l_int32 debugflag) { char *tempname; l_int32 i, j, nlines, nx, ny, sampling; l_float32 c0, c1, c2, x, y, flaty, val; l_float32 *faflats; NUMA *nax, *nafit, *nacurve, *nacurves, *naflat, *naflats, *naflatsi; PIX *pixs, *pixt1, *pixt2; PTA *pta, *ptad; PTAA *ptaa1, *ptaa2, *ptaa3, *ptaa4, *ptaa5, *ptaa6, *ptaa7; FPIX *fpix1, *fpix2, *fpix3; PROCNAME("dewarpBuildModel"); if (!dew) return ERROR_INT("dew not defined", procName, 1); pixs = dew->pixs; if (debugflag) { pixDisplayWithTitle(pixs, 0, 0, "pixs", 1); pixWriteTempfile("/tmp", "pixs.png", pixs, IFF_PNG, NULL); } /* Make initial estimate of centers of textlines */ ptaa1 = pixGetTextlineCenters(pixs, DEBUG_TEXTLINE_CENTERS); if (debugflag) { pixt1 = pixConvertTo32(pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa1); pixWriteTempfile("/tmp", "lines1.png", pixt2, IFF_PNG, NULL); pixDestroy(&pixt1); pixDestroy(&pixt2); } /* Remove all lines that are not near the length * of the longest line. */ ptaa2 = ptaaRemoveShortLines(pixs, ptaa1, 0.8, DEBUG_SHORT_LINES); if (debugflag) { pixt1 = pixConvertTo32(pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa2); pixWriteTempfile("/tmp", "lines2.png", pixt2, IFF_PNG, NULL); pixDestroy(&pixt1); pixDestroy(&pixt2); } nlines = ptaaGetCount(ptaa2); if (nlines < dew->minlines) return ERROR_INT("insufficient lines to build model", procName, 1); /* Do quadratic fit to smooth each line. A single quadratic * over the entire width of the line appears to be sufficient. * Quartics tend to overfit to noise. Each line is thus * represented by three coefficients: c2 * x^2 + c1 * x + c0. * Using the coefficients, sample each fitted curve uniformly * across the full width of the image. */ sampling = dew->sampling; nx = dew->nx; ny = dew->ny; ptaa3 = ptaaCreate(nlines); nacurve = numaCreate(nlines); /* stores curvature coeff c2 */ for (i = 0; i < nlines; i++) { /* for each line */ pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL); numaAddNumber(nacurve, c2); ptad = ptaCreate(nx); for (j = 0; j < nx; j++) { /* uniformly sampled in x */ x = j * sampling; applyQuadraticFit(c2, c1, c0, x, &y); ptaAddPt(ptad, x, y); } ptaaAddPta(ptaa3, ptad, L_INSERT); ptaDestroy(&pta); } if (debugflag) { ptaa4 = ptaaCreate(nlines); for (i = 0; i < nlines; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetQuadraticLSF(pta, NULL, NULL, NULL, &nafit); ptad = ptaCreateFromNuma(nax, nafit); ptaaAddPta(ptaa4, ptad, L_INSERT); ptaDestroy(&pta); numaDestroy(&nax); numaDestroy(&nafit); } pixt1 = pixConvertTo32(pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa4); pixWriteTempfile("/tmp", "lines3.png", pixt2, IFF_PNG, NULL); pixDestroy(&pixt1); pixDestroy(&pixt2); ptaaDestroy(&ptaa4); } /* Find and save the flat points in each curve. */ naflat = numaCreate(nlines); for (i = 0; i < nlines; i++) { pta = ptaaGetPta(ptaa3, i, L_CLONE); numaGetFValue(nacurve, i, &c2); if (c2 <= 0) /* flat point at bottom; max value of y in curve */ ptaGetRange(pta, NULL, NULL, NULL, &flaty); else /* flat point at top; min value of y in curve */ ptaGetRange(pta, NULL, NULL, &flaty, NULL); numaAddNumber(naflat, flaty); ptaDestroy(&pta); } /* Sort the lines in ptaa3 by their position */ naflatsi = numaGetSortIndex(naflat, L_SORT_INCREASING); naflats = numaSortByIndex(naflat, naflatsi); nacurves = numaSortByIndex(nacurve, naflatsi); dew->naflats = naflats; dew->nacurves = nacurves; ptaa4 = ptaaSortByIndex(ptaa3, naflatsi); numaDestroy(&naflat); numaDestroy(&nacurve); numaDestroy(&naflatsi); if (debugflag) { tempname = genTempFilename("/tmp", "naflats.na", 0); numaWrite(tempname, naflats); FREE(tempname); } /* Convert the sampled points in ptaa3 to a sampled disparity with * with respect to the flat point in the curve. */ ptaa5 = ptaaCreate(nlines); for (i = 0; i < nlines; i++) { pta = ptaaGetPta(ptaa4, i, L_CLONE); numaGetFValue(naflats, i, &flaty); ptad = ptaCreate(nx); for (j = 0; j < nx; j++) { ptaGetPt(pta, j, &x, &y); ptaAddPt(ptad, x, flaty - y); } ptaaAddPta(ptaa5, ptad, L_INSERT); ptaDestroy(&pta); } if (debugflag) { tempname = genTempFilename("/tmp", "ptaa5.ptaa", 0); ptaaWrite(tempname, ptaa5, 0); FREE(tempname); } /* Generate a ptaa taking vertical 'columns' from ptaa5. * We want to fit the vertical disparity on the column to the * vertical position of the line, which we call 'y' here and * obtain from naflats. */ ptaa6 = ptaaCreate(nx); faflats = numaGetFArray(naflats, L_NOCOPY); for (j = 0; j < nx; j++) { pta = ptaCreate(nlines); for (i = 0; i < nlines; i++) { y = faflats[i]; ptaaGetPt(ptaa5, i, j, NULL, &val); /* disparity value */ ptaAddPt(pta, y, val); } ptaaAddPta(ptaa6, pta, L_INSERT); } if (debugflag) { tempname = genTempFilename("/tmp", "ptaa6.ptaa", 0); ptaaWrite(tempname, ptaa6, 0); FREE(tempname); } /* Do quadratic fit vertically on a subset of pixel columns * for the vertical displacement, which identifies the * src pixel(s) for each dest pixel. Sample the displacement * on a regular grid in the vertical direction. */ ptaa7 = ptaaCreate(nx); /* uniformly sampled across full height of image */ for (j = 0; j < nx; j++) { /* for each column */ pta = ptaaGetPta(ptaa6, j, L_CLONE); ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL); ptad = ptaCreate(ny); for (i = 0; i < ny; i++) { /* uniformly sampled in y */ y = i * sampling; applyQuadraticFit(c2, c1, c0, y, &val); ptaAddPt(ptad, y, val); } ptaaAddPta(ptaa7, ptad, L_INSERT); ptaDestroy(&pta); } if (debugflag) { tempname = genTempFilename("/tmp", "ptaa7.ptaa", 0); ptaaWrite(tempname, ptaa7, 0); FREE(tempname); } /* Save the result in a fpix at the specified subsampling */ fpix1 = fpixCreate(nx, ny); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { ptaaGetPt(ptaa7, j, i, NULL, &val); fpixSetPixel(fpix1, j, i, val); } } dew->sampvdispar = fpix1; /* Generate a full res fpix for vertical dewarping. We require that * the size of this fpix is at least as big as the input image. */ fpix2 = fpixScaleByInteger(fpix1, sampling); dew->fullvdispar = fpix2; if (debugflag) { pixt1 = fpixRenderContours(fpix2, -2., 2.0, 0.2); pixWriteTempfile("/tmp", "vert-contours.png", pixt1, IFF_PNG, NULL); pixDisplay(pixt1, 1000, 0); pixDestroy(&pixt1); } /* Generate full res and sampled fpix for horizontal dewarping. This * works to the extent that the line curvature is due to bending * out of the plane normal to the camera, and not wide-angle * "fishbowl" distortion. Also generate the sampled horizontal * disparity array. */ if (dew->applyhoriz) { fpix3 = fpixBuildHorizontalDisparity(fpix2, 0, &dew->extraw); dew->fullhdispar = fpix3; dew->samphdispar = fpixSampledDisparity(fpix3, dew->sampling); if (debugflag) { pixt1 = fpixRenderContours(fpix3, -2., 2.0, 0.2); pixWriteTempfile("/tmp", "horiz-contours.png", pixt1, IFF_PNG, NULL); pixDisplay(pixt1, 1000, 0); pixDestroy(&pixt1); } } dew->success = 1; ptaaDestroy(&ptaa1); ptaaDestroy(&ptaa2); ptaaDestroy(&ptaa3); ptaaDestroy(&ptaa4); ptaaDestroy(&ptaa5); ptaaDestroy(&ptaa6); ptaaDestroy(&ptaa7); return 0; }
/*! * \brief pixOtsuAdaptiveThreshold() * * \param[in] pixs 8 bpp * \param[in] sx, sy desired tile dimensions; actual size may vary * \param[in] smoothx, smoothy half-width of convolution kernel applied to * threshold array: use 0 for no smoothing * \param[in] scorefract fraction of the max Otsu score; typ. 0.1; * use 0.0 for standard Otsu * \param[out] ppixth [optional] array of threshold values * found for each tile * \param[out] ppixd [optional] thresholded input pixs, based on * the threshold array * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) The Otsu method finds a single global threshold for an image. * This function allows a locally adapted threshold to be * found for each tile into which the image is broken up. * (2) The array of threshold values, one for each tile, constitutes * a highly downscaled image. This array is optionally * smoothed using a convolution. The full width and height of the * convolution kernel are (2 * %smoothx + 1) and (2 * %smoothy + 1). * (3) The minimum tile dimension allowed is 16. If such small * tiles are used, it is recommended to use smoothing, because * without smoothing, each small tile determines the splitting * threshold independently. A tile that is entirely in the * image bg will then hallucinate fg, resulting in a very noisy * binarization. The smoothing should be large enough that no * tile is only influenced by one type (fg or bg) of pixels, * because it will force a split of its pixels. * (4) To get a single global threshold for the entire image, use * input values of %sx and %sy that are larger than the image. * For this situation, the smoothing parameters are ignored. * (5) The threshold values partition the image pixels into two classes: * one whose values are less than the threshold and another * whose values are greater than or equal to the threshold. * This is the same use of 'threshold' as in pixThresholdToBinary(). * (6) The scorefract is the fraction of the maximum Otsu score, which * is used to determine the range over which the histogram minimum * is searched. See numaSplitDistribution() for details on the * underlying method of choosing a threshold. * (7) This uses enables a modified version of the Otsu criterion for * splitting the distribution of pixels in each tile into a * fg and bg part. The modification consists of searching for * a minimum in the histogram over a range of pixel values where * the Otsu score is within a defined fraction, %scorefract, * of the max score. To get the original Otsu algorithm, set * %scorefract == 0. * (8) N.B. This method is NOT recommended for images with weak text * and significant background noise, such as bleedthrough, because * of the problem noted in (3) above for tiling. Use Sauvola. * </pre> */ l_int32 pixOtsuAdaptiveThreshold(PIX *pixs, l_int32 sx, l_int32 sy, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, PIX **ppixth, PIX **ppixd) { l_int32 w, h, nx, ny, i, j, thresh; l_uint32 val; PIX *pixt, *pixb, *pixthresh, *pixth, *pixd; PIXTILING *pt; PROCNAME("pixOtsuAdaptiveThreshold"); if (!ppixth && !ppixd) return ERROR_INT("neither &pixth nor &pixd defined", procName, 1); if (ppixth) *ppixth = NULL; if (ppixd) *ppixd = NULL; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs not defined or not 8 bpp", procName, 1); if (sx < 16 || sy < 16) return ERROR_INT("sx and sy must be >= 16", procName, 1); /* Compute the threshold array for the tiles */ pixGetDimensions(pixs, &w, &h, NULL); nx = L_MAX(1, w / sx); ny = L_MAX(1, h / sy); smoothx = L_MIN(smoothx, (nx - 1) / 2); smoothy = L_MIN(smoothy, (ny - 1) / 2); pt = pixTilingCreate(pixs, nx, ny, 0, 0, 0, 0); pixthresh = pixCreate(nx, ny, 8); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { pixt = pixTilingGetTile(pt, i, j); pixSplitDistributionFgBg(pixt, scorefract, 1, &thresh, NULL, NULL, NULL); pixSetPixel(pixthresh, j, i, thresh); /* see note (4) */ pixDestroy(&pixt); } } /* Optionally smooth the threshold array */ if (smoothx > 0 || smoothy > 0) pixth = pixBlockconv(pixthresh, smoothx, smoothy); else pixth = pixClone(pixthresh); pixDestroy(&pixthresh); /* Optionally apply the threshold array to binarize pixs */ if (ppixd) { pixd = pixCreate(w, h, 1); pixCopyResolution(pixd, pixs); for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { pixt = pixTilingGetTile(pt, i, j); pixGetPixel(pixth, j, i, &val); pixb = pixThresholdToBinary(pixt, val); pixTilingPaintTile(pixd, i, j, pixb, pt); pixDestroy(&pixt); pixDestroy(&pixb); } } *ppixd = pixd; } if (ppixth) *ppixth = pixth; else pixDestroy(&pixth); pixTilingDestroy(&pt); return 0; }
/*! * pixGetTextlineCenters() * * Input: pixs (1 bpp) * debugflag (1 for debug output) * Return: ptaa (of center values of textlines) * * Notes: * (1) This in general does not have a point for each value * of x, because there will be gaps between words. * It doesn't matter because we will fit a quadratic to the * points that we do have. */ PTAA * pixGetTextlineCenters(PIX *pixs, l_int32 debugflag) { l_int32 i, w, h, bx, by, nsegs; BOXA *boxa; PIX *pix, *pixt1, *pixt2, *pixt3; PIXA *pixa1, *pixa2; PTA *pta; PTAA *ptaa; PROCNAME("pixGetTextlineCenters"); if (!pixs || pixGetDepth(pixs) != 1) return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); /* Filter to solidify the text lines within the x-height region, * and to remove most of the ascenders and descenders. */ pixt1 = pixMorphSequence(pixs, "c15.1 + o15.1 + c30.1", 0); pixDisplayWithTitle(pixt1, 0, 800, "pix1", debugflag); /* Get the 8-connected components ... */ boxa = pixConnComp(pixt1, &pixa1, 8); pixDestroy(&pixt1); boxaDestroy(&boxa); if (pixaGetCount(pixa1) == 0) { pixaDestroy(&pixa1); return NULL; } /* ... and remove the short and thin c.c */ pixa2 = pixaSelectBySize(pixa1, 100, 4, L_SELECT_IF_BOTH, L_SELECT_IF_GT, 0); if ((nsegs = pixaGetCount(pixa2)) == 0) { pixaDestroy(&pixa2); return NULL; } if (debugflag) { pixt2 = pixaDisplay(pixa2, w, h); pixDisplayWithTitle(pixt2, 800, 800, "pix2", 1); pixDestroy(&pixt2); } /* For each c.c., get the weighted center of each vertical column. * The result is a set of points going approximately through * the center of the x-height part of the text line. */ ptaa = ptaaCreate(nsegs); for (i = 0; i < nsegs; i++) { pixaGetBoxGeometry(pixa2, i, &bx, &by, NULL, NULL); pix = pixaGetPix(pixa2, i, L_CLONE); pta = pixGetMeanVerticals(pix, bx, by); ptaaAddPta(ptaa, pta, L_INSERT); pixDestroy(&pix); } if (debugflag) { pixt3 = pixCreateTemplate(pixt2); pix = pixDisplayPtaa(pixt3, ptaa); pixDisplayWithTitle(pix, 0, 1400, "pix3", 1); pixDestroy(&pix); pixDestroy(&pixt3); } pixaDestroy(&pixa1); pixaDestroy(&pixa2); return ptaa; }
/*! * \brief pixSauvolaBinarizeTiled() * * \param[in] pixs 8 bpp grayscale, not colormapped * \param[in] whsize window half-width for measuring local statistics * \param[in] factor factor for reducing threshold due to variance; >= 0 * \param[in] nx, ny subdivision into tiles; >= 1 * \param[out] ppixth [optional] Sauvola threshold values * \param[out] ppixd [optional] thresholded image * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) The window width and height are 2 * %whsize + 1. The minimum * value for %whsize is 2; typically it is \>= 7.. * (2) For nx == ny == 1, this defaults to pixSauvolaBinarize(). * (3) Why a tiled version? * (a) Because the mean value accumulator is a uint32, overflow * can occur for an image with more than 16M pixels. * (b) The mean value accumulator array for 16M pixels is 64 MB. * The mean square accumulator array for 16M pixels is 128 MB. * Using tiles reduces the size of these arrays. * (c) Each tile can be processed independently, in parallel, * on a multicore processor. * (4) The Sauvola threshold is determined from the formula: * t = m * (1 - k * (1 - s / 128)) * See pixSauvolaBinarize() for details. * </pre> */ l_int32 pixSauvolaBinarizeTiled(PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 nx, l_int32 ny, PIX **ppixth, PIX **ppixd) { l_int32 i, j, w, h, xrat, yrat; PIX *pixth, *pixd, *tileth, *tiled, *pixt; PIX **ptileth, **ptiled; PIXTILING *pt; PROCNAME("pixSauvolaBinarizeTiled"); if (!ppixth && !ppixd) return ERROR_INT("no outputs", procName, 1); if (ppixth) *ppixth = NULL; if (ppixd) *ppixd = NULL; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs undefined or not 8 bpp", procName, 1); if (pixGetColormap(pixs)) return ERROR_INT("pixs is cmapped", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); if (whsize < 2) return ERROR_INT("whsize must be >= 2", procName, 1); if (w < 2 * whsize + 3 || h < 2 * whsize + 3) return ERROR_INT("whsize too large for image", procName, 1); if (factor < 0.0) return ERROR_INT("factor must be >= 0", procName, 1); if (nx <= 1 && ny <= 1) return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL, ppixth, ppixd); /* Test to see if the tiles are too small. The required * condition is that the tile dimensions must be at least * (whsize + 2) x (whsize + 2). */ xrat = w / nx; yrat = h / ny; if (xrat < whsize + 2) { nx = w / (whsize + 2); L_WARNING("tile width too small; nx reduced to %d\n", procName, nx); } if (yrat < whsize + 2) { ny = h / (whsize + 2); L_WARNING("tile height too small; ny reduced to %d\n", procName, ny); } if (nx <= 1 && ny <= 1) return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL, ppixth, ppixd); /* We can use pixtiling for painting both outputs, if requested */ if (ppixth) { pixth = pixCreateNoInit(w, h, 8); *ppixth = pixth; } if (ppixd) { pixd = pixCreateNoInit(w, h, 1); *ppixd = pixd; } pt = pixTilingCreate(pixs, nx, ny, 0, 0, whsize + 1, whsize + 1); pixTilingNoStripOnPaint(pt); /* pixSauvolaBinarize() does the stripping */ for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { pixt = pixTilingGetTile(pt, i, j); ptileth = (ppixth) ? &tileth : NULL; ptiled = (ppixd) ? &tiled : NULL; pixSauvolaBinarize(pixt, whsize, factor, 0, NULL, NULL, ptileth, ptiled); if (ppixth) { /* do not strip */ pixTilingPaintTile(pixth, i, j, tileth, pt); pixDestroy(&tileth); } if (ppixd) { pixTilingPaintTile(pixd, i, j, tiled, pt); pixDestroy(&tiled); } pixDestroy(&pixt); } } pixTilingDestroy(&pt); return 0; }
/*! * gplotAddPlot() * * Input: gplot * nax (<optional> numa: set to null for Y_VS_I; * required for Y_VS_X) * nay (numa: required for both Y_VS_I and Y_VS_X) * plotstyle (GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES, * GPLOT_LINESPOINTS, GPLOT_DOTS) * plottitle (<optional> title for individual plot) * Return: 0 if OK, 1 on error * * Notes: * (1) There are 2 options for (x,y) values: * o To plot an array vs the index, set nax = NULL. * o To plot one array vs another, use both nax and nay. * (2) If nax is defined, it must be the same size as nay. * (3) The 'plottitle' string can have spaces, double * quotes and backquotes, but not single quotes. */ l_int32 gplotAddPlot(GPLOT *gplot, NUMA *nax, NUMA *nay, l_int32 plotstyle, const char *plottitle) { char buf[L_BUF_SIZE]; char emptystring[] = ""; char *datastr, *title; l_int32 n, i; l_float32 valx, valy, startx, delx; SARRAY *sa; PROCNAME("gplotAddPlot"); if (!gplot) return ERROR_INT("gplot not defined", procName, 1); if (!nay) return ERROR_INT("nay not defined", procName, 1); if (plotstyle != GPLOT_LINES && plotstyle != GPLOT_POINTS && plotstyle != GPLOT_IMPULSES && plotstyle != GPLOT_LINESPOINTS && plotstyle != GPLOT_DOTS) return ERROR_INT("invalid plotstyle", procName, 1); n = numaGetCount(nay); numaGetParameters(nay, &startx, &delx); if (nax) { if (n != numaGetCount(nax)) return ERROR_INT("nax and nay sizes differ", procName, 1); } /* Save plotstyle and plottitle */ numaAddNumber(gplot->plotstyles, plotstyle); if (plottitle) { title = stringNew(plottitle); sarrayAddString(gplot->plottitles, title, L_INSERT); } else { sarrayAddString(gplot->plottitles, emptystring, L_COPY); } /* Generate and save data filename */ gplot->nplots++; snprintf(buf, L_BUF_SIZE, "%s.data.%d", gplot->rootname, gplot->nplots); sarrayAddString(gplot->datanames, buf, L_COPY); /* Generate data and save as a string */ sa = sarrayCreate(n); for (i = 0; i < n; i++) { if (nax) numaGetFValue(nax, i, &valx); else valx = startx + i * delx; numaGetFValue(nay, i, &valy); snprintf(buf, L_BUF_SIZE, "%f %f\n", valx, valy); sarrayAddString(sa, buf, L_COPY); } datastr = sarrayToString(sa, 0); sarrayAddString(gplot->plotdata, datastr, L_INSERT); sarrayDestroy(&sa); return 0; }
/*! * \brief pixSauvolaGetThreshold() * * \param[in] pixm 8 bpp grayscale; not colormapped * \param[in] pixms 32 bpp * \param[in] factor factor for reducing threshold due to variance; >= 0 * \param[out] ppixsd [optional] local standard deviation * \return pixd 8 bpp, sauvola threshold values, or NULL on error * * <pre> * Notes: * (1) The Sauvola threshold is determined from the formula: * t = m * (1 - k * (1 - s / 128)) * where: * t = local threshold * m = local mean * k = %factor (\>= 0) [ typ. 0.35 ] * s = local standard deviation, which is maximized at * 127.5 when half the samples are 0 and half are 255. * (2) See pixSauvolaBinarize() for other details. * (3) Important definitions and relations for computing averages: * v == pixel value * E(p) == expected value of p == average of p over some pixel set * S(v) == square of v == v * v * mv == E(v) == expected pixel value == mean value * ms == E(S(v)) == expected square of pixel values * == mean square value * var == variance == expected square of deviation from mean * == E(S(v - mv)) = E(S(v) - 2 * S(v * mv) + S(mv)) * = E(S(v)) - S(mv) * = ms - mv * mv * s == standard deviation = sqrt(var) * So for evaluating the standard deviation in the Sauvola * threshold, we take * s = sqrt(ms - mv * mv) * </pre> */ PIX * pixSauvolaGetThreshold(PIX *pixm, PIX *pixms, l_float32 factor, PIX **ppixsd) { l_int32 i, j, w, h, tabsize, wplm, wplms, wplsd, wpld, usetab; l_int32 mv, ms, var, thresh; l_uint32 *datam, *datams, *datasd, *datad; l_uint32 *linem, *linems, *linesd, *lined; l_float32 sd; l_float32 *tab; /* of 2^16 square roots */ PIX *pixsd, *pixd; PROCNAME("pixSauvolaGetThreshold"); if (ppixsd) *ppixsd = NULL; if (!pixm || pixGetDepth(pixm) != 8) return (PIX *)ERROR_PTR("pixm undefined or not 8 bpp", procName, NULL); if (pixGetColormap(pixm)) return (PIX *)ERROR_PTR("pixm is colormapped", procName, NULL); if (!pixms || pixGetDepth(pixms) != 32) return (PIX *)ERROR_PTR("pixms undefined or not 32 bpp", procName, NULL); if (factor < 0.0) return (PIX *)ERROR_PTR("factor must be >= 0", procName, NULL); /* Only make a table of 2^16 square roots if there * are enough pixels to justify it. */ pixGetDimensions(pixm, &w, &h, NULL); usetab = (w * h > 100000) ? 1 : 0; if (usetab) { tabsize = 1 << 16; tab = (l_float32 *)LEPT_CALLOC(tabsize, sizeof(l_float32)); for (i = 0; i < tabsize; i++) tab[i] = sqrtf((l_float32)i); } pixd = pixCreate(w, h, 8); if (ppixsd) { pixsd = pixCreate(w, h, 8); *ppixsd = pixsd; } datam = pixGetData(pixm); datams = pixGetData(pixms); if (ppixsd) datasd = pixGetData(pixsd); datad = pixGetData(pixd); wplm = pixGetWpl(pixm); wplms = pixGetWpl(pixms); if (ppixsd) wplsd = pixGetWpl(pixsd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { linem = datam + i * wplm; linems = datams + i * wplms; if (ppixsd) linesd = datasd + i * wplsd; lined = datad + i * wpld; for (j = 0; j < w; j++) { mv = GET_DATA_BYTE(linem, j); ms = linems[j]; var = ms - mv * mv; if (usetab) sd = tab[var]; else sd = sqrtf((l_float32)var); if (ppixsd) SET_DATA_BYTE(linesd, j, (l_int32)sd); thresh = (l_int32)(mv * (1.0 - factor * (1.0 - sd / 128.))); SET_DATA_BYTE(lined, j, thresh); } } if (usetab) LEPT_FREE(tab); return pixd; }
/*! * gplotGenCommandFile() * * Input: gplot * Return: 0 if OK, 1 on error */ l_int32 gplotGenCommandFile(GPLOT *gplot) { char buf[L_BUF_SIZE]; char *cmdstr, *plottitle, *dataname; l_int32 i, plotstyle, nplots; FILE *fp; PROCNAME("gplotGenCommandFile"); if (!gplot) return ERROR_INT("gplot not defined", procName, 1); /* Remove any previous command data */ sarrayClear(gplot->cmddata); /* Generate command data instructions */ if (gplot->title) { /* set title */ snprintf(buf, L_BUF_SIZE, "set title '%s'", gplot->title); sarrayAddString(gplot->cmddata, buf, L_COPY); } if (gplot->xlabel) { /* set xlabel */ snprintf(buf, L_BUF_SIZE, "set xlabel '%s'", gplot->xlabel); sarrayAddString(gplot->cmddata, buf, L_COPY); } if (gplot->ylabel) { /* set ylabel */ snprintf(buf, L_BUF_SIZE, "set ylabel '%s'", gplot->ylabel); sarrayAddString(gplot->cmddata, buf, L_COPY); } if (gplot->outformat == GPLOT_PNG) /* set terminal type and output */ snprintf(buf, L_BUF_SIZE, "set terminal png; set output '%s'", gplot->outname); else if (gplot->outformat == GPLOT_PS) snprintf(buf, L_BUF_SIZE, "set terminal postscript; set output '%s'", gplot->outname); else if (gplot->outformat == GPLOT_EPS) snprintf(buf, L_BUF_SIZE, "set terminal postscript eps; set output '%s'", gplot->outname); else if (gplot->outformat == GPLOT_LATEX) snprintf(buf, L_BUF_SIZE, "set terminal latex; set output '%s'", gplot->outname); else /* gplot->outformat == GPLOT_X11 */ #ifndef _WIN32 snprintf(buf, L_BUF_SIZE, "set terminal x11"); #else snprintf(buf, L_BUF_SIZE, "set terminal windows"); #endif /* _WIN32 */ sarrayAddString(gplot->cmddata, buf, L_COPY); if (gplot->scaling == GPLOT_LOG_SCALE_X || gplot->scaling == GPLOT_LOG_SCALE_X_Y) { snprintf(buf, L_BUF_SIZE, "set logscale x"); sarrayAddString(gplot->cmddata, buf, L_COPY); } if (gplot->scaling == GPLOT_LOG_SCALE_Y || gplot->scaling == GPLOT_LOG_SCALE_X_Y) { snprintf(buf, L_BUF_SIZE, "set logscale y"); sarrayAddString(gplot->cmddata, buf, L_COPY); } nplots = sarrayGetCount(gplot->datanames); for (i = 0; i < nplots; i++) { plottitle = sarrayGetString(gplot->plottitles, i, L_NOCOPY); dataname = sarrayGetString(gplot->datanames, i, L_NOCOPY); numaGetIValue(gplot->plotstyles, i, &plotstyle); if (nplots == 1) { snprintf(buf, L_BUF_SIZE, "plot '%s' title '%s' %s", dataname, plottitle, gplotstylenames[plotstyle]); } else { if (i == 0) snprintf(buf, L_BUF_SIZE, "plot '%s' title '%s' %s, \\", dataname, plottitle, gplotstylenames[plotstyle]); else if (i < nplots - 1) snprintf(buf, L_BUF_SIZE, " '%s' title '%s' %s, \\", dataname, plottitle, gplotstylenames[plotstyle]); else snprintf(buf, L_BUF_SIZE, " '%s' title '%s' %s", dataname, plottitle, gplotstylenames[plotstyle]); } sarrayAddString(gplot->cmddata, buf, L_COPY); } /* Write command data to file */ cmdstr = sarrayToString(gplot->cmddata, 1); if ((fp = fopenWriteStream(gplot->cmdname, "w")) == NULL) return ERROR_INT("cmd stream not opened", procName, 1); fwrite(cmdstr, 1, strlen(cmdstr), fp); fclose(fp); FREE(cmdstr); return 0; }
/*! * pixGenerateSelWithRuns() * * Input: pix (1 bpp, typically small, to be used as a pattern) * nhlines (number of hor lines along which elements are found) * nvlines (number of vert lines along which elements are found) * distance (min distance from boundary pixel; use 0 for default) * minlength (min runlength to set hit or miss; use 0 for default) * toppix (number of extra pixels of bg added above) * botpix (number of extra pixels of bg added below) * leftpix (number of extra pixels of bg added to left) * rightpix (number of extra pixels of bg added to right) * &pixe (<optional return> input pix expanded by extra pixels) * Return: sel (hit-miss for input pattern), or null on error * * Notes: * (1) The horizontal and vertical lines along which elements are * selected are roughly equally spaced. The actual locations of * the hits and misses are the centers of respective run-lengths. * (2) No elements are selected that are less than 'distance' pixels away * from a boundary pixel of the same color. This makes the * match much more robust to edge noise. Valid inputs of * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or * greater than 4, we reset it to the default value. * (3) The 4 numbers for adding rectangles of pixels outside the fg * can be use if the pattern is expected to be surrounded by bg * (white) pixels. On the other hand, if the pattern may be near * other fg (black) components on some sides, use 0 for those sides. * (4) The pixels added to a side allow you to have miss elements there. * There is a constraint between distance, minlength, and * the added pixels for this to work. We illustrate using the * default values. If you add 5 pixels to the top, and use a * distance of 1, then you end up with a vertical run of at least * 4 bg pixels along the top edge of the image. If you use a * minimum runlength of 3, each vertical line will always find * a miss near the center of its run. However, if you use a * minimum runlength of 5, you will not get a miss on every vertical * line. As another example, if you have 7 added pixels and a * distance of 2, you can use a runlength up to 5 to guarantee * that the miss element is recorded. We give a warning if the * contraint does not guarantee a miss element outside the * image proper. * (5) The input pix, as extended by the extra pixels on selected sides, * can optionally be returned. For debugging, call * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed * on the generating bitmap. */ SEL * pixGenerateSelWithRuns(PIX *pixs, l_int32 nhlines, l_int32 nvlines, l_int32 distance, l_int32 minlength, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, xval, yval, i, j, nh, nm; l_float32 delh, delw; NUMA *nah, *nam; PIX *pixt1, *pixt2, *pixfg, *pixbg; PTA *ptah, *ptam; SEL *seld, *sel; PROCNAME("pixGenerateSelWithRuns"); if (ppixe) *ppixe = NULL; if (!pixs) return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (nhlines < 1 && nvlines < 1) return (SEL *)ERROR_PTR("nvlines and nhlines both < 1", procName, NULL); if (distance <= 0) distance = DEFAULT_DISTANCE_TO_BOUNDARY; if (minlength <= 0) minlength = DEFAULT_MIN_RUNLENGTH; if (distance > MAX_DISTANCE_TO_BOUNDARY) { L_WARNING("distance too large; setting to max value", procName); distance = MAX_DISTANCE_TO_BOUNDARY; } /* Locate the foreground */ pixClipToForeground(pixs, &pixt1, NULL); if (!pixt1) return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL); ws = pixGetWidth(pixt1); hs = pixGetHeight(pixt1); w = ws; h = hs; /* Crop out a region including the foreground, and add pixels * on sides depending on the side flags */ if (toppix || botpix || leftpix || rightpix) { x = y = 0; if (toppix) { h += toppix; y = toppix; if (toppix < distance + minlength) L_WARNING("no miss elements in added top pixels", procName); } if (botpix) { h += botpix; if (botpix < distance + minlength) L_WARNING("no miss elements in added bot pixels", procName); } if (leftpix) { w += leftpix; x = leftpix; if (leftpix < distance + minlength) L_WARNING("no miss elements in added left pixels", procName); } if (rightpix) { w += rightpix; if (rightpix < distance + minlength) L_WARNING("no miss elements in added right pixels", procName); } pixt2 = pixCreate(w, h, 1); pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0); } else pixt2 = pixClone(pixt1); if (ppixe) *ppixe = pixClone(pixt2); pixDestroy(&pixt1); /* Identify fg and bg pixels that are at least 'distance' pixels * away from the boundary pixels in their set */ seld = selCreateBrick(2 * distance + 1, 2 * distance + 1, distance, distance, SEL_HIT); pixfg = pixErode(NULL, pixt2, seld); pixbg = pixDilate(NULL, pixt2, seld); pixInvert(pixbg, pixbg); selDestroy(&seld); pixDestroy(&pixt2); /* Accumulate hit and miss points */ ptah = ptaCreate(0); ptam = ptaCreate(0); if (nhlines >= 1) { delh = (l_float32)h / (l_float32)(nhlines + 1); for (i = 0, y = 0; i < nhlines; i++) { y += (l_int32)(delh + 0.5); nah = pixGetRunCentersOnLine(pixfg, -1, y, minlength); nam = pixGetRunCentersOnLine(pixbg, -1, y, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &xval); ptaAddPt(ptah, xval, y); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &xval); ptaAddPt(ptam, xval, y); } numaDestroy(&nah); numaDestroy(&nam); } } if (nvlines >= 1) { delw = (l_float32)w / (l_float32)(nvlines + 1); for (i = 0, x = 0; i < nvlines; i++) { x += (l_int32)(delw + 0.5); nah = pixGetRunCentersOnLine(pixfg, x, -1, minlength); nam = pixGetRunCentersOnLine(pixbg, x, -1, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &yval); ptaAddPt(ptah, x, yval); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &yval); ptaAddPt(ptam, x, yval); } numaDestroy(&nah); numaDestroy(&nam); } } /* Make the Sel with those points */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); nh = ptaGetCount(ptah); for (i = 0; i < nh; i++) { ptaGetIPt(ptah, i, &x, &y); selSetElement(sel, y, x, SEL_HIT); } nm = ptaGetCount(ptam); for (i = 0; i < nm; i++) { ptaGetIPt(ptam, i, &x, &y); selSetElement(sel, y, x, SEL_MISS); } pixDestroy(&pixfg); pixDestroy(&pixbg); ptaDestroy(&ptah); ptaDestroy(&ptam); return sel; }
/*! * \brief pixGetSortedNeighborValues() * * \param[in] pixs 8, 16 or 32 bpp, with pixels labeled by c.c. * \param[in] x, y location of pixel * \param[in] conn 4 or 8 connected neighbors * \param[out] pneigh array of integers, to be filled with * the values of the neighbors, if any * \param[out] pnvals the number of unique neighbor values found * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) The returned %neigh array is the unique set of neighboring * pixel values, of size nvals, sorted from smallest to largest. * The value 0, which represents background pixels that do * not belong to any set of connected components, is discarded. * (2) If there are no neighbors, this returns %neigh = NULL; otherwise, * the caller must free the array. * (3) For either 4 or 8 connectivity, the maximum number of unique * neighbor values is 4. * </pre> */ l_int32 pixGetSortedNeighborValues(PIX *pixs, l_int32 x, l_int32 y, l_int32 conn, l_int32 **pneigh, l_int32 *pnvals) { l_int32 i, npt, index; l_int32 neigh[4]; l_uint32 val; l_float32 fx, fy; L_ASET *aset; L_ASET_NODE *node; PTA *pta; RB_TYPE key; PROCNAME("pixGetSortedNeighborValues"); if (pneigh) *pneigh = NULL; if (pnvals) *pnvals = 0; if (!pneigh || !pnvals) return ERROR_INT("&neigh and &nvals not both defined", procName, 1); if (!pixs || pixGetDepth(pixs) < 8) return ERROR_INT("pixs not defined or depth < 8", procName, 1); /* Identify the locations of nearest neighbor pixels */ if ((pta = ptaGetNeighborPixLocs(pixs, x, y, conn)) == NULL) return ERROR_INT("pta of neighbors not made", procName, 1); /* Find the pixel values and insert into a set as keys */ aset = l_asetCreate(L_UINT_TYPE); npt = ptaGetCount(pta); for (i = 0; i < npt; i++) { ptaGetPt(pta, i, &fx, &fy); pixGetPixel(pixs, (l_int32)fx, (l_int32)fy, &val); key.utype = val; l_asetInsert(aset, key); } /* Extract the set keys and put them into the %neigh array. * Omit the value 0, which indicates the pixel doesn't * belong to one of the sets of connected components. */ node = l_asetGetFirst(aset); index = 0; while (node) { val = node->key.utype; if (val > 0) neigh[index++] = (l_int32)val; node = l_asetGetNext(node); } *pnvals = index; if (index > 0) { *pneigh = (l_int32 *)LEPT_CALLOC(index, sizeof(l_int32)); for (i = 0; i < index; i++) (*pneigh)[i] = neigh[i]; } ptaDestroy(&pta); l_asetDestroy(&aset); return 0; }