int main(int argc, char **argv) { char buf[32]; char *filein, *fileout, *fontdir, *textstr; l_int32 n, i, maxdepth, ntext, border, lossless, display, showtext; l_float32 scalefact; L_BMF *bmf; PIX *pix1, *pix2, *pix3, *pix4, *pixd; PIXA *pixa, *pixad; static char mainName[] = "displaypixa"; if (argc != 3 && argc != 4 && argc != 7 && argc != 8) { fprintf(stderr, "Syntax error in displaypixa:\n" " displaypixa filein fileout [showtext]\n" " displaypixa filein scalefact border" " lossless disp fileout [showtext]\n"); return 1; } filein = argv[1]; if ((pixa = pixaRead(filein)) == NULL) return ERROR_INT("pixa not made", mainName, 1); pixaCountText(pixa, &ntext); if (argc == 3 || argc == 4) fileout = argv[2]; if (argc == 4) showtext = atoi(argv[3]); /* Simple specification; no output text */ if (argc == 3 || (argc == 4 && (ntext == 0 || showtext == 0))) { /* no text output */ pixaVerifyDepth(pixa, &maxdepth); pixd = pixaDisplayTiledInRows(pixa, maxdepth, 1400, 1.0, 0, 10, 0); pixDisplay(pixd, 100, 100); if (pixGetDepth(pixd) == 1) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixaDestroy(&pixa); return 0; } /* Simple specification with output text */ if (argc == 4) { /* showtext == 1 && ntext > 0 */ n = pixaGetCount(pixa); bmf = bmfCreate(NULL, 6); pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixa, i, L_CLONE); pix2 = pixConvertTo32(pix1); pix3 = pixAddBorderGeneral(pix2, 10, 10, 5, 5, 0xffffff00); textstr = pixGetText(pix1); if (textstr && strlen(textstr) > 0) { snprintf(buf, sizeof(buf), "%s", textstr); pix4 = pixAddSingleTextblock(pix3, bmf, buf, 0xff000000, L_ADD_BELOW, NULL); } else { pix4 = pixClone(pix3); } pixaAddPix(pixad, pix4, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); } bmfDestroy(&bmf); pixaVerifyDepth(pixad, &maxdepth); pixd = pixaDisplayTiledInRows(pixad, maxdepth, 1400, 1.0, 0, 10, 0); pixDisplay(pixd, 100, 100); if (pixGetDepth(pixd) == 1) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixaDestroy(&pixa); pixaDestroy(&pixad); return 0; } /* Full specification */ scalefact = atof(argv[2]); border = atoi(argv[3]); lossless = atoi(argv[4]); display = atoi(argv[5]); fileout = argv[6]; showtext = (argc == 8) ? atoi(argv[7]) : 0; if (showtext && ntext == 0) L_INFO("No text found in any of the pix\n", mainName); bmf = (showtext && ntext > 0) ? bmfCreate(NULL, 6) : NULL; n = pixaGetCount(pixa); pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixa, i, L_CLONE); pix2 = pixConvertTo32(pix1); pix3 = pixAddBorderGeneral(pix2, 10, 10, 5, 5, 0xffffff00); textstr = pixGetText(pix1); if (bmf && textstr && strlen(textstr) > 0) { snprintf(buf, sizeof(buf), "%s", textstr); pix4 = pixAddSingleTextblock(pix3, bmf, buf, 0xff000000, L_ADD_BELOW, NULL); } else { pix4 = pixClone(pix3); } pixaAddPix(pixad, pix4, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); } bmfDestroy(&bmf); pixaVerifyDepth(pixad, &maxdepth); pixd = pixaDisplayTiledInRows(pixad, maxdepth, 1400, scalefact, 0, 10, border); if (display) pixDisplay(pixd, 20, 20); if (pixGetDepth(pixd) == 1 || lossless) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixaDestroy(&pixa); pixaDestroy(&pixad); return 0; }
int main(int argc, char **argv) { char *filein, *fileout; l_int32 ret; l_float32 deg2rad; l_float32 angle, conf, score; PIX *pix, *pixs, *pixd; static char mainName[] = "skewtest"; if (argc != 3) return ERROR_INT(" Syntax: skewtest filein fileout", mainName, 1); filein = argv[1]; fileout = argv[2]; setLeptDebugOK(1); pixd = NULL; deg2rad = 3.1415926535 / 180.; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); /* Find the skew angle various ways */ pix = pixConvertTo1(pixs, 130); pixWrite("/tmp/binarized.tif", pix, IFF_TIFF_G4); pixFindSkew(pix, &angle, &conf); fprintf(stderr, "pixFindSkew():\n" " conf = %5.3f, angle = %7.3f degrees\n", conf, angle); pixFindSkewSweepAndSearchScorePivot(pix, &angle, &conf, &score, SWEEP_REDUCTION2, SEARCH_REDUCTION, 0.0, SWEEP_RANGE2, SWEEP_DELTA2, SEARCH_MIN_DELTA, L_SHEAR_ABOUT_CORNER); fprintf(stderr, "pixFind...Pivot(about corner):\n" " conf = %5.3f, angle = %7.3f degrees, score = %f\n", conf, angle, score); pixFindSkewSweepAndSearchScorePivot(pix, &angle, &conf, &score, SWEEP_REDUCTION2, SEARCH_REDUCTION, 0.0, SWEEP_RANGE2, SWEEP_DELTA2, SEARCH_MIN_DELTA, L_SHEAR_ABOUT_CENTER); fprintf(stderr, "pixFind...Pivot(about center):\n" " conf = %5.3f, angle = %7.3f degrees, score = %f\n", conf, angle, score); /* Use top-level */ pixd = pixDeskew(pixs, 0); pixWriteImpliedFormat(fileout, pixd, 0, 0); #if 0 /* Do it piecemeal; fails if outside the range */ if (pixGetDepth(pixs) == 1) { pixd = pixDeskew(pix, DESKEW_REDUCTION); pixWrite(fileout, pixd, IFF_PNG); } else { ret = pixFindSkewSweepAndSearch(pix, &angle, &conf, SWEEP_REDUCTION2, SEARCH_REDUCTION, SWEEP_RANGE2, SWEEP_DELTA2, SEARCH_MIN_DELTA); if (ret) L_WARNING("skew angle not valid\n", mainName); else { fprintf(stderr, "conf = %5.3f, angle = %7.3f degrees\n", conf, angle); if (conf > 2.5) pixd = pixRotate(pixs, angle * deg2rad, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, 0, 0); else pixd = pixClone(pixs); pixWrite(fileout, pixd, IFF_PNG); pixDestroy(&pixd); } } #endif pixDestroy(&pixs); pixDestroy(&pix); pixDestroy(&pixd); return 0; }
/*! * pixRunlengthTransform() * * Input: pixs (1 bpp) * color (0 for white runs, 1 for black runs) * direction (L_HORIZONTAL_RUNS, L_VERTICAL_RUNS) * depth (8 or 16 bpp) * Return: pixd (8 or 16 bpp), or null on error * * Notes: * (1) The dest Pix is 8 or 16 bpp, with the pixel values * equal to the runlength in which it is a member. * The length is clipped to the max pixel value if necessary. * (2) The color determines if we're labelling white or black runs. * (3) A pixel that is not a member of the chosen color gets * value 0; it belongs to a run of length 0 of the * chosen color. * (4) To convert for maximum dynamic range, either linear or * log, use pixMaxDynamicRange(). */ PIX * pixRunlengthTransform(PIX *pixs, l_int32 color, l_int32 direction, l_int32 depth) { l_int32 i, j, w, h, wpld, bufsize, maxsize, n; l_int32 *start, *end, *buffer; l_uint32 *datad, *lined; PIX *pixt, *pixd; PROCNAME("pixRunlengthTransform"); if (!pixs) return (PIX *) ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *) ERROR_PTR("pixs not 1 bpp", procName, NULL); if (depth != 8 && depth != 16) return (PIX *) ERROR_PTR("depth must be 8 or 16 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); if (direction == L_HORIZONTAL_RUNS) maxsize = 1 + w / 2; else if (direction == L_VERTICAL_RUNS) maxsize = 1 + h / 2; else return (PIX *) ERROR_PTR("invalid direction", procName, NULL); bufsize = L_MAX(w, h); if ((pixd = pixCreate(w, h, depth)) == NULL) return (PIX *) ERROR_PTR("pixd not made", procName, NULL); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); if ((start = (l_int32 *) CALLOC(maxsize, sizeof(l_int32))) == NULL) return (PIX *) ERROR_PTR("start not made", procName, NULL); if ((end = (l_int32 *) CALLOC(maxsize, sizeof(l_int32))) == NULL) return (PIX *) ERROR_PTR("end not made", procName, NULL); if ((buffer = (l_int32 *) CALLOC(bufsize, sizeof(l_int32))) == NULL) return (PIX *) ERROR_PTR("buffer not made", procName, NULL); /* Use fg runs for evaluation */ if (color == 0) pixt = pixInvert(NULL, pixs); else pixt = pixClone(pixs); if (direction == L_HORIZONTAL_RUNS) { for (i = 0; i < h; i++) { pixFindHorizontalRuns(pixt, i, start, end, &n); runlengthMembershipOnLine(buffer, w, depth, start, end, n); lined = datad + i * wpld; if (depth == 8) { for (j = 0; j < w; j++) SET_DATA_BYTE(lined, j, buffer[j]); } else { /* depth == 16 */ for (j = 0; j < w; j++) SET_DATA_TWO_BYTES(lined, j, buffer[j]); } } } else { /* L_VERTICAL_RUNS */ for (j = 0; j < w; j++) { pixFindVerticalRuns(pixt, j, start, end, &n); runlengthMembershipOnLine(buffer, h, depth, start, end, n); if (depth == 8) { for (i = 0; i < h; i++) { lined = datad + i * wpld; SET_DATA_BYTE(lined, j, buffer[i]); } } else { /* depth == 16 */ for (i = 0; i < h; i++) { lined = datad + i * wpld; SET_DATA_TWO_BYTES(lined, j, buffer[i]); } } } } pixDestroy(&pixt); FREE(start); FREE(end); FREE(buffer); return pixd; }
/*! * pixGenerateSelRandom() * * Input: pix (1 bpp, typically small, to be used as a pattern) * hitfract (fraction of allowable fg pixels that are hits) * missfract (fraction of allowable bg pixels that are misses) * distance (min distance from boundary pixel; 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) Either of hitfract and missfract can be zero. If both are zero, * the sel would be empty, and NULL is returned. * (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 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 * pixGenerateSelRandom(PIX *pixs, l_float32 hitfract, l_float32 missfract, l_int32 distance, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, i, j, thresh; l_uint32 val; PIX *pixt1, *pixt2, *pixfg, *pixbg; SEL *seld, *sel; PROCNAME("pixGenerateSelRandom"); 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 (hitfract <= 0.0 && missfract <= 0.0) return (SEL *)ERROR_PTR("no hits or misses", procName, NULL); if (hitfract > 1.0 || missfract > 1.0) return (SEL *)ERROR_PTR("fraction can't be > 1.0", procName, NULL); if (distance <= 0) distance = DEFAULT_DISTANCE_TO_BOUNDARY; 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 (botpix) h += botpix; if (leftpix) { w += leftpix; x = leftpix; } if (rightpix) w += rightpix; 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); /* Generate the sel from a random selection of these points */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); if (hitfract > 0.0) { thresh = (l_int32)(hitfract * (l_float64)RAND_MAX); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixfg, j, i, &val); if (val) { if (rand() < thresh) selSetElement(sel, i, j, SEL_HIT); } } } } if (missfract > 0.0) { thresh = (l_int32)(missfract * (l_float64)RAND_MAX); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixbg, j, i, &val); if (val) { if (rand() < thresh) selSetElement(sel, i, j, SEL_MISS); } } } } pixDestroy(&pixfg); pixDestroy(&pixbg); return sel; }
/*! * pixOtsuAdaptiveThreshold() * * Input: pixs (8 bpp) * sx, sy (desired tile dimensions; actual size may vary) * smoothx, smoothy (half-width of convolution kernel applied to * threshold array: use 0 for no smoothing) * scorefract (fraction of the max Otsu score; typ. 0.1; * use 0.0 for standard Otsu) * &pixth (<optional return> array of threshold values * found for each tile) * &pixd (<optional return> thresholded input pixs, based on * the threshold array) * Return: 0 if OK, 1 on error * * 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. */ 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); LOGE("neither &pixth nor &pixd defined"); } 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); LOGE("pixs not defined or not 8 bpp"); } if (sx < 16 || sy < 16){ return ERROR_INT("sx and sy must be >= 16", procName, 1); LOGE("sx and sy must be >= 16"); } /* 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, 0); 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); 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; }
/*! * \brief pixSaveTiledOutline() * * \param[in] pixs 1, 2, 4, 8, 32 bpp * \param[in] pixa the pix are accumulated here * \param[in] scalefactor 0.0 to disable; otherwise this is a scale factor * \param[in] newrow 0 if placed on the same row as previous; 1 otherwise * \param[in] space horizontal and vertical spacing, in pixels * \param[in] linewidth width of added outline for image; 0 for no outline * \param[in] dp depth of pixa; 8 or 32 bpp; only used on first call * \return 0 if OK, 1 on error. * * <pre> * 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. * </pre> */ 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; }
/*! * dewarpShowResults() * * Input: dewa * sarray (of indexed input images) * boxa (crop boxes for input images; can be null) * firstpage, lastpage * fontdir (for text bitmap fonts) * pdfout (filename) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates a pdf of image pairs (before, after) for * the designated set of input pages. * (2) If the boxa exists, its elements are aligned with numbers * in the filenames in @sa. It is used to crop the input images. * It is assumed that the dewa was generated from the cropped * images. No undercropping is applied before rendering. */ l_int32 dewarpShowResults(L_DEWARPA *dewa, SARRAY *sa, BOXA *boxa, l_int32 firstpage, l_int32 lastpage, const char *fontdir, const char *pdfout) { char bufstr[256]; char *outpath; l_int32 i, modelpage; L_BMF *bmf; BOX *box; L_DEWARP *dew; PIX *pixs, *pixc, *pixd, *pixt1, *pixt2; PIXA *pixa; PROCNAME("dewarpShowResults"); if (!dewa) return ERROR_INT("dewa not defined", procName, 1); if (!sa) return ERROR_INT("sa not defined", procName, 1); if (!pdfout) return ERROR_INT("pdfout not defined", procName, 1); if (firstpage > lastpage) return ERROR_INT("invalid first/last page numbers", procName, 1); lept_rmdir("dewarp_pdfout"); lept_mkdir("dewarp_pdfout"); if ((bmf = bmfCreate(fontdir, 6)) == NULL) L_ERROR("bmf not made; page info not displayed", procName); fprintf(stderr, "Dewarping and generating s/by/s view\n"); for (i = firstpage; i <= lastpage; i++) { if (i && (i % 10 == 0)) fprintf(stderr, ".. %d ", i); pixs = pixReadIndexed(sa, i); if (boxa) { box = boxaGetBox(boxa, i, L_CLONE); pixc = pixClipRectangle(pixs, box, NULL); boxDestroy(&box); } else pixc = pixClone(pixs); dew = dewarpaGetDewarp(dewa, i); pixd = NULL; if (dew) { dewarpaApplyDisparity(dewa, dew->pageno, pixc, GRAYIN_VALUE, 0, 0, &pixd, NULL); dewarpMinimize(dew); } pixa = pixaCreate(2); pixaAddPix(pixa, pixc, L_INSERT); if (pixd) pixaAddPix(pixa, pixd, L_INSERT); pixt1 = pixaDisplayTiledAndScaled(pixa, 32, 500, 2, 0, 35, 2); if (dew) { modelpage = (dew->hasref) ? dew->refpage : dew->pageno; snprintf(bufstr, sizeof(bufstr), "Page %d; using %d\n", i, modelpage); } else snprintf(bufstr, sizeof(bufstr), "Page %d; no dewarp\n", i); pixt2 = pixAddSingleTextblock(pixt1, bmf, bufstr, 0x0000ff00, L_ADD_BELOW, 0); snprintf(bufstr, sizeof(bufstr), "/tmp/dewarp_pdfout/%05d", i); pixWrite(bufstr, pixt2, IFF_JFIF_JPEG); pixaDestroy(&pixa); pixDestroy(&pixs); pixDestroy(&pixt1); pixDestroy(&pixt2); } fprintf(stderr, "\n"); fprintf(stderr, "Generating pdf of result\n"); convertFilesToPdf("/tmp/dewarp_pdfout", NULL, 100, 1.0, L_JPEG_ENCODE, 0, "Dewarp sequence", pdfout); outpath = genPathname(pdfout, NULL); fprintf(stderr, "Output written to: %s\n", outpath); FREE(outpath); bmfDestroy(&bmf); return 0; }
/*! * 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; }
/*! * ioFormatTest() * * Input: filename (input file) * Return: 0 if OK; 1 on error or if the test fails * * Notes: * (1) This writes and reads a set of output files losslessly * in different formats to /tmp/format/, and tests that the * result before and after is unchanged. * (2) This should work properly on input images of any depth, * with and without colormaps. * (3) All supported formats are tested for bmp, png, tiff and * non-ascii pnm. Ascii pnm also works (but who'd ever want * to use it?) We allow 2 bpp bmp, although it's not * supported elsewhere. And we don't support reading * 16 bpp png, although this can be turned on in pngio.c. * (4) This silently skips png or tiff testing if HAVE_LIBPNG * or HAVE_LIBTIFF are 0, respectively. */ l_int32 ioFormatTest(const char *filename) { l_int32 d, equal, problems; PIX *pixs, *pixc, *pix1, *pix2; PIXCMAP *cmap; PROCNAME("ioFormatTest"); if (!filename) return ERROR_INT("filename not defined", procName, 1); if ((pixs = pixRead(filename)) == NULL) return ERROR_INT("pixs not made", procName, 1); lept_mkdir("lept"); /* Note that the reader automatically removes colormaps * from 1 bpp BMP images, but not from 8 bpp BMP images. * Therefore, if our 8 bpp image initially doesn't have a * colormap, we are going to need to remove it from any * pix read from a BMP file. */ pixc = pixClone(pixs); /* laziness */ /* This does not test the alpha layer pixels, because most * formats don't support it. Remove any alpha. */ if (pixGetSpp(pixc) == 4) pixSetSpp(pixc, 3); cmap = pixGetColormap(pixc); /* colormap; can be NULL */ d = pixGetDepth(pixc); problems = FALSE; /* ----------------------- BMP -------------------------- */ /* BMP works for 1, 2, 4, 8 and 32 bpp images. * It always writes colormaps for 1 and 8 bpp, so we must * remove it after readback if the input image doesn't have * a colormap. Although we can write/read 2 bpp BMP, nobody * else can read them! */ if (d == 1 || d == 8) { L_INFO("write/read bmp\n", procName); pixWrite(FILE_BMP, pixc, IFF_BMP); pix1 = pixRead(FILE_BMP); if (!cmap) pix2 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC); else pix2 = pixClone(pix1); pixEqual(pixc, pix2, &equal); if (!equal) { L_INFO(" **** bad bmp image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); } if (d == 2 || d == 4 || d == 32) { L_INFO("write/read bmp\n", procName); pixWrite(FILE_BMP, pixc, IFF_BMP); pix1 = pixRead(FILE_BMP); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad bmp image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } /* ----------------------- PNG -------------------------- */ #if HAVE_LIBPNG /* PNG works for all depths, but here, because we strip * 16 --> 8 bpp on reading, we don't test png for 16 bpp. */ if (d != 16) { L_INFO("write/read png\n", procName); pixWrite(FILE_PNG, pixc, IFF_PNG); pix1 = pixRead(FILE_PNG); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad png image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } #endif /* HAVE_LIBPNG */ /* ----------------------- TIFF -------------------------- */ #if HAVE_LIBTIFF /* TIFF works for 1, 2, 4, 8, 16 and 32 bpp images. * Because 8 bpp tiff always writes 256 entry colormaps, the * colormap sizes may be different for 8 bpp images with * colormap; we are testing if the image content is the same. * Likewise, the 2 and 4 bpp tiff images with colormaps * have colormap sizes 4 and 16, rsp. This test should * work properly on the content, regardless of the number * of color entries in pixc. */ /* tiff uncompressed works for all pixel depths */ L_INFO("write/read uncompressed tiff\n", procName); pixWrite(FILE_TIFF, pixc, IFF_TIFF); pix1 = pixRead(FILE_TIFF); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff uncompressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff lzw works for all pixel depths */ L_INFO("write/read lzw compressed tiff\n", procName); pixWrite(FILE_LZW, pixc, IFF_TIFF_LZW); pix1 = pixRead(FILE_LZW); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff lzw compressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff adobe deflate (zip) works for all pixel depths */ L_INFO("write/read zip compressed tiff\n", procName); pixWrite(FILE_ZIP, pixc, IFF_TIFF_ZIP); pix1 = pixRead(FILE_ZIP); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff zip compressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff g4, g3, rle and packbits work for 1 bpp */ if (d == 1) { L_INFO("write/read g4 compressed tiff\n", procName); pixWrite(FILE_G4, pixc, IFF_TIFF_G4); pix1 = pixRead(FILE_G4); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff g4 image ****\n", procName); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read g3 compressed tiff\n", procName); pixWrite(FILE_G3, pixc, IFF_TIFF_G3); pix1 = pixRead(FILE_G3); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff g3 image ****\n", procName); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read rle compressed tiff\n", procName); pixWrite(FILE_RLE, pixc, IFF_TIFF_RLE); pix1 = pixRead(FILE_RLE); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff rle image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read packbits compressed tiff\n", procName); pixWrite(FILE_PB, pixc, IFF_TIFF_PACKBITS); pix1 = pixRead(FILE_PB); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff packbits image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } #endif /* HAVE_LIBTIFF */ /* ----------------------- PNM -------------------------- */ /* pnm works for 1, 2, 4, 8, 16 and 32 bpp. * pnm doesn't have colormaps, so when we write colormapped * pix out as pnm, the colormap is removed. Thus for the test, * we must remove the colormap from pixc before testing. */ L_INFO("write/read pnm\n", procName); pixWrite(FILE_PNM, pixc, IFF_PNM); pix1 = pixRead(FILE_PNM); if (cmap) pix2 = pixRemoveColormap(pixc, REMOVE_CMAP_BASED_ON_SRC); else pix2 = pixClone(pixc); pixEqual(pix1, pix2, &equal); if (!equal) { L_INFO(" **** bad pnm image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); if (problems == FALSE) L_INFO("All formats read and written OK!\n", procName); pixDestroy(&pixc); pixDestroy(&pixs); return problems; }
/*! * \brief jbWordsInTextlines() * * \param[in] dirin directory of input pages * \param[in] reduction 1 for full res; 2 for half-res * \param[in] maxwidth of word mask components, to be kept * \param[in] maxheight of word mask components, to be kept * \param[in] thresh on correlation; 0.80 is reasonable * \param[in] weight for handling thick text; 0.6 is reasonable * \param[out] pnatl numa with textline index for each component * \param[in] firstpage 0-based * \param[in] npages use 0 for all pages in dirin * \return classer for the set of pages * * <pre> * Notes: * (1) This is a high-level function. See prog/jbwords for example * of usage. * (2) Typically, use input of 75 - 150 ppi for finding words. * </pre> */ JBCLASSER * jbWordsInTextlines(const char *dirin, l_int32 reduction, l_int32 maxwidth, l_int32 maxheight, l_float32 thresh, l_float32 weight, NUMA **pnatl, l_int32 firstpage, l_int32 npages) { char *fname; l_int32 nfiles, i, w, h; BOXA *boxa; JBCLASSER *classer; NUMA *nai, *natl; PIX *pix1, *pix2; PIXA *pixa; SARRAY *safiles; PROCNAME("jbWordsInTextlines"); if (!pnatl) return (JBCLASSER *)ERROR_PTR("&natl not defined", procName, NULL); *pnatl = NULL; if (!dirin) return (JBCLASSER *)ERROR_PTR("dirin not defined", procName, NULL); if (reduction != 1 && reduction != 2) return (JBCLASSER *)ERROR_PTR("reduction not in {1,2}", procName, NULL); safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages); nfiles = sarrayGetCount(safiles); /* Classify components */ classer = jbCorrelationInit(JB_WORDS, maxwidth, maxheight, thresh, weight); classer->safiles = sarrayCopy(safiles); natl = numaCreate(0); *pnatl = natl; for (i = 0; i < nfiles; i++) { fname = sarrayGetString(safiles, i, L_NOCOPY); if ((pix1 = pixRead(fname)) == NULL) { L_WARNING("image file %d not read\n", procName, i); continue; } if (reduction == 1) pix2 = pixClone(pix1); else /* reduction == 2 */ pix2 = pixReduceRankBinaryCascade(pix1, 1, 0, 0, 0); pixGetWordsInTextlines(pix2, JB_WORDS_MIN_WIDTH, JB_WORDS_MIN_HEIGHT, maxwidth, maxheight, &boxa, &pixa, &nai); pixGetDimensions(pix2, &w, &h, NULL); classer->w = w; classer->h = h; jbAddPageComponents(classer, pix2, boxa, pixa); numaJoin(natl, nai, 0, -1); pixDestroy(&pix1); pixDestroy(&pix2); numaDestroy(&nai); boxaDestroy(&boxa); pixaDestroy(&pixa); } sarrayDestroy(&safiles); return classer; }
/*! * 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; }
void RotateTest(PIX *pixs, l_int32 reduction, L_REGPARAMS *rp) { l_int32 w, h, d, outformat; PIX *pixt1, *pixt2, *pixt3, *pixd; PIXA *pixa; pixGetDimensions(pixs, &w, &h, &d); outformat = (d == 8 || d == 32) ? IFF_JFIF_JPEG : IFF_PNG; pixa = pixaCreate(0); pixt1 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_WHITE, w, h); pixSaveTiled(pixt1, pixa, reduction, 1, 20, 32); pixt2 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_BLACK, w, h); pixSaveTiled(pixt2, pixa, reduction, 0, 20, 0); pixDestroy(&pixt1); pixDestroy(&pixt2); pixt1 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_WHITE, 0, 0); pixSaveTiled(pixt1, pixa, reduction, 1, 20, 0); pixt2 = pixRotate(pixs, ANGLE1, L_ROTATE_SHEAR, L_BRING_IN_BLACK, 0, 0); pixSaveTiled(pixt2, pixa, reduction, 0, 20, 0); pixDestroy(&pixt1); pixDestroy(&pixt2); pixt1 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_WHITE, w, h); pixSaveTiled(pixt1, pixa, reduction, 1, 20, 0); pixt2 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_BLACK, w, h); pixSaveTiled(pixt2, pixa, reduction, 0, 20, 0); pixDestroy(&pixt1); pixDestroy(&pixt2); pixt1 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_WHITE, 0, 0); pixSaveTiled(pixt1, pixa, reduction, 1, 20, 0); pixt2 = pixRotate(pixs, ANGLE2, L_ROTATE_SHEAR, L_BRING_IN_BLACK, 0, 0); pixSaveTiled(pixt2, pixa, reduction, 0, 20, 0); pixDestroy(&pixt1); pixDestroy(&pixt2); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, outformat); pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixa); pixa = pixaCreate(0); pixt1 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_WHITE, w, h); pixSaveTiled(pixt1, pixa, reduction, 1, 20, 32); pixt2 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_BLACK, w, h); pixSaveTiled(pixt2, pixa, reduction, 0, 20, 0); pixDestroy(&pixt1); pixDestroy(&pixt2); pixt1 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_WHITE, 0, 0); pixSaveTiled(pixt1, pixa, reduction, 1, 20, 0); pixt2 = pixRotate(pixs, ANGLE2, L_ROTATE_SAMPLING, L_BRING_IN_BLACK, 0, 0); pixSaveTiled(pixt2, pixa, reduction, 0, 20, 0); pixDestroy(&pixt1); pixDestroy(&pixt2); if (pixGetDepth(pixs) == 1) pixt1 = pixScaleToGray2(pixs); else pixt1 = pixClone(pixs); pixt2 = pixRotate(pixt1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, w, h); pixSaveTiled(pixt2, pixa, reduction, 1, 20, 0); pixt3 = pixRotate(pixt1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_BLACK, w, h); pixSaveTiled(pixt3, pixa, reduction, 0, 20, 0); pixDestroy(&pixt2); pixDestroy(&pixt3); pixt2 = pixRotate(pixt1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, 0, 0); pixSaveTiled(pixt2, pixa, reduction, 1, 20, 0); pixt3 = pixRotate(pixt1, ANGLE2, L_ROTATE_AREA_MAP, L_BRING_IN_BLACK, 0, 0); pixSaveTiled(pixt3, pixa, reduction, 0, 20, 0); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt1); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, outformat); pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixa); return; }
/* * pixWriteSegmentedPageToPS() * * Input: pixs (all depths; colormap ok) * pixm (<optional> 1 bpp segmentation mask over image region) * textscale (scale of text output relative to pixs) * imagescale (scale of image output relative to pixs) * threshold (threshold for binarization; typ. 190) * pageno (page number in set; use 1 for new output file) * fileout (output ps file) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates the PS string for a mixed text/image page, * and adds it to an existing file if @pageno > 1. * The PS output is determined by fitting the result to * a letter-size (8.5 x 11 inch) page. * (2) The two images (pixs and pixm) are at the same resolution * (typically 300 ppi). They are used to generate two compressed * images, pixb and pixc, that are put directly into the output * PS file. * (3) pixb is the text component. In the PostScript world, we think of * it as a mask through which we paint black. It is produced by * scaling pixs by @textscale, and thresholding to 1 bpp. * (4) pixc is the image component, which is that part of pixs under * the mask pixm. It is scaled from pixs by @imagescale. * (5) Typical values are textscale = 2.0 and imagescale = 0.5. * (6) If pixm == NULL, the page has only text. If it is all black, * the page is all image and has no text. * (7) This can be used to write a multi-page PS file, by using * sequential page numbers with the same output file. It can * also be used to write separate PS files for each page, * by using different output files with @pageno = 0 or 1. */ l_int32 pixWriteSegmentedPageToPS(PIX *pixs, PIX *pixm, l_float32 textscale, l_float32 imagescale, l_int32 threshold, l_int32 pageno, const char *fileout) { l_int32 alltext, notext, d, ret; l_uint32 val; l_float32 scaleratio; PIX *pixmi, *pixmis, *pixt, *pixg, *pixsc, *pixb, *pixc; PROCNAME("pixWriteSegmentedPageToPS"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!fileout) return ERROR_INT("fileout not defined", procName, 1); if (imagescale <= 0.0 || textscale <= 0.0) return ERROR_INT("relative scales must be > 0.0", procName, 1); /* Analyze the page. Determine the ratio by which the * binary text mask is scaled relative to the image part. * If there is no image region (alltext == TRUE), the * text mask will be rendered directly to fit the page, * and scaleratio = 1.0. */ alltext = TRUE; notext = FALSE; scaleratio = 1.0; if (pixm) { pixZero(pixm, &alltext); /* pixm empty: all text */ if (alltext) pixm = NULL; /* treat it as not existing here */ else { pixmi = pixInvert(NULL, pixm); pixZero(pixmi, ¬ext); /* pixm full; no text */ pixDestroy(&pixmi); scaleratio = textscale / imagescale; } } if (pixGetDepth(pixs) == 1) { /* render tiff g4 */ pixb = pixClone(pixs); pixc = NULL; } else { pixt = pixConvertTo8Or32(pixs, 0, 0); /* this can be a clone of pixs */ /* Get the binary text mask. Note that pixg cannot be a * clone of pixs, because it may be altered by pixSetMasked(). */ pixb = NULL; if (notext == FALSE) { d = pixGetDepth(pixt); if (d == 8) pixg = pixCopy(NULL, pixt); else /* d == 32 */ pixg = pixConvertRGBToLuminance(pixt); if (pixm) /* clear out the image parts */ pixSetMasked(pixg, pixm, 255); if (textscale == 1.0) pixsc = pixClone(pixg); else if (textscale >= 0.7) pixsc = pixScaleGrayLI(pixg, textscale, textscale); else pixsc = pixScaleAreaMap(pixg, textscale, textscale); pixb = pixThresholdToBinary(pixsc, threshold); pixDestroy(&pixg); pixDestroy(&pixsc); } /* Get the scaled image region */ pixc = NULL; if (pixm) { if (imagescale == 1.0) pixsc = pixClone(pixt); /* can possibly be a clone of pixs */ else pixsc = pixScale(pixt, imagescale, imagescale); /* If pixm is not full, clear the pixels in pixsc * corresponding to bg in pixm, where there can be text * that is written through the mask pixb. Note that * we could skip this and use pixsc directly in * pixWriteMixedToPS(); however, clearing these * non-image regions to a white background will reduce * the size of pixc (relative to pixsc), and hence * reduce the size of the PS file that is generated. * Use a copy so that we don't accidentally alter pixs. */ if (notext == FALSE) { pixmis = pixScale(pixm, imagescale, imagescale); pixmi = pixInvert(NULL, pixmis); val = (d == 8) ? 0xff : 0xffffff00; pixc = pixCopy(NULL, pixsc); pixSetMasked(pixc, pixmi, val); /* clear non-image part */ pixDestroy(&pixmis); pixDestroy(&pixmi); } else pixc = pixClone(pixsc); pixDestroy(&pixsc); } pixDestroy(&pixt); } ret = pixWriteMixedToPS(pixb, pixc, scaleratio, pageno, fileout); pixDestroy(&pixb); pixDestroy(&pixc); return ret; }
main(int argc, char **argv) { char *filein, *fileout; l_int32 w, h, d, w2, h2, i, ncols; l_float32 angle, conf; BOX *box; BOXA *boxa, *boxas, *boxad, *boxa2; NUMA *numa; PIX *pixs, *pixt, *pixb, *pixb2, *pixd; PIX *pixtlm, *pixvws; PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixt5, *pixt6; PIXA *pixam, *pixac, *pixad, *pixat; PIXAA *pixaa, *pixaa2; PTA *pta; SEL *selsplit; static char mainName[] = "textlinemask"; if (argc != 3) exit(ERROR_INT(" Syntax: textlinemask filein fileout", mainName, 1)); filein = argv[1]; fileout = argv[2]; pixDisplayWrite(NULL, -1); /* init debug output */ if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); pixGetDimensions(pixs, &w, &h, &d); /* Binarize input */ if (d == 8) pixt = pixThresholdToBinary(pixs, 128); else if (d == 1) pixt = pixClone(pixs); else { fprintf(stderr, "depth is %d\n", d); exit(1); } /* Deskew */ pixb = pixFindSkewAndDeskew(pixt, 1, &angle, &conf); pixDestroy(&pixt); fprintf(stderr, "Skew angle: %7.2f degrees; %6.2f conf\n", angle, conf); pixDisplayWrite(pixb, DEBUG_OUTPUT); #if 1 /* Use full image morphology to find columns, at 2x reduction. * This only works for very simple layouts where each column * of text extends the full height of the input image. * pixam has a pix component over each column. */ pixb2 = pixReduceRankBinary2(pixb, 2, NULL); pixt1 = pixMorphCompSequence(pixb2, "c5.500", 0); boxa = pixConnComp(pixt1, &pixam, 8); ncols = boxaGetCount(boxa); fprintf(stderr, "Num columns: %d\n", ncols); pixDisplayWrite(pixt1, DEBUG_OUTPUT); /* Use selective region-based morphology to get the textline mask. */ pixad = pixaMorphSequenceByRegion(pixb2, pixam, "c100.3", 0, 0); pixGetDimensions(pixb2, &w2, &h2, NULL); if (DEBUG_OUTPUT) { pixt2 = pixaDisplay(pixad, w2, h2); pixDisplayWrite(pixt2, DEBUG_OUTPUT); pixDestroy(&pixt2); } /* Some of the lines may be touching, so use a HMT to split the * lines in each column, and use a pixaa to save the results. */ selsplit = selCreateFromString(seltext, 17, 7, "selsplit"); pixaa = pixaaCreate(ncols); for (i = 0; i < ncols; i++) { pixt3 = pixaGetPix(pixad, i, L_CLONE); box = pixaGetBox(pixad, i, L_COPY); pixt4 = pixHMT(NULL, pixt3, selsplit); pixXor(pixt4, pixt4, pixt3); boxa2 = pixConnComp(pixt4, &pixac, 8); pixaaAddPixa(pixaa, pixac, L_INSERT); pixaaAddBox(pixaa, box, L_INSERT); if (DEBUG_OUTPUT) { pixt5 = pixaDisplayRandomCmap(pixac, 0, 0); pixDisplayWrite(pixt5, DEBUG_OUTPUT); fprintf(stderr, "Num textlines in col %d: %d\n", i, boxaGetCount(boxa2)); pixDestroy(&pixt5); } pixDestroy(&pixt3); pixDestroy(&pixt4); boxaDestroy(&boxa2); } /* Visual output */ if (DEBUG_OUTPUT) { pixDisplayMultiple("/tmp/junk_write_display*"); pixat = pixaReadFiles("/tmp", "junk_write_display"); pixt5 = selDisplayInPix(selsplit, 31, 2); pixaAddPix(pixat, pixt5, L_INSERT); pixt6 = pixaDisplayTiledAndScaled(pixat, 32, 400, 3, 0, 35, 3); pixWrite(fileout, pixt6, IFF_PNG); pixaDestroy(&pixat); pixDestroy(&pixt6); } /* Test pixaa I/O */ pixaaWrite("/tmp/junkpixaa", pixaa); pixaa2 = pixaaRead("/tmp/junkpixaa"); pixaaWrite("/tmp/junkpixaa2", pixaa2); /* Test pixaa display */ pixd = pixaaDisplay(pixaa, w2, h2); pixWrite("/tmp/junkdisplay", pixd, IFF_PNG); pixDestroy(&pixd); /* Cleanup */ pixDestroy(&pixb2); pixDestroy(&pixt1); pixaDestroy(&pixam); pixaDestroy(&pixad); pixaaDestroy(&pixaa); pixaaDestroy(&pixaa2); boxaDestroy(&boxa); selDestroy(&selsplit); #endif #if 0 /* Use the baseline finder; not really what is needed */ numa = pixFindBaselines(pixb, &pta, 1); #endif #if 0 /* Use the textline mask function; parameters are not quite right */ pixb2 = pixReduceRankBinary2(pixb, 2, NULL); pixtlm = pixGenTextlineMask(pixb2, &pixvws, NULL, 1); pixDisplay(pixtlm, 0, 100); pixDisplay(pixvws, 500, 100); pixDestroy(&pixb2); pixDestroy(&pixtlm); pixDestroy(&pixvws); #endif #if 0 /* Use the Breuel whitespace partition method; slow and we would * still need to work to extract the fg regions. */ pixb2 = pixReduceRankBinary2(pixb, 2, NULL); boxas = pixConnComp(pixb2, NULL, 8); boxad = boxaGetWhiteblocks(boxas, NULL, L_SORT_BY_HEIGHT, 3, 0.1, 200, 0.2, 0); pixd = pixDrawBoxa(pixb2, boxad, 7, 0xe0708000); pixDisplay(pixd, 100, 500); pixDestroy(&pixb2); pixDestroy(&pixd); boxaDestroy(&boxas); boxaDestroy(&boxad); #endif #if 0 /* Use morphology to find columns and then selective * region-based morphology to get the textline mask. * This is for display; we really want to get a pixa of the * specific textline masks. */ startTimer(); pixb2 = pixReduceRankBinary2(pixb, 2, NULL); pixt1 = pixMorphCompSequence(pixb2, "c5.500", 0); /* column mask */ pixt2 = pixMorphSequenceByRegion(pixb2, pixt1, "c100.3", 8, 0, 0, &boxa); fprintf(stderr, "time = %7.3f sec\n", stopTimer()); pixDisplay(pixt1, 100, 500); pixDisplay(pixt2, 800, 500); pixDestroy(&pixb2); pixDestroy(&pixt1); pixDestroy(&pixt2); boxaDestroy(&boxa); #endif pixDestroy(&pixs); pixDestroy(&pixb); exit(0); }
/*! * \brief pixGetWordsInTextlines() * * \param[in] pixs 1 bpp, typ. 300 ppi * \param[in] reduction 1 for input res; 2 for 2x reduction of input res * \param[in] minwidth, minheight of saved components; smaller are discarded * \param[in] maxwidth, maxheight of saved components; larger are discarded * \param[out] pboxad word boxes sorted in textline line order * \param[out] ppixad word images sorted in textline line order * \param[out] pnai index of textline for each word * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) The input should be at a resolution of about 300 ppi. * The word masks and word images can be computed at either * 150 ppi or 300 ppi. For the former, set reduction = 2. * (2) The four size constraints on saved components are all * scaled by %reduction. * (3) The result are word images (and their b.b.), extracted in * textline order, at either full res or 2x reduction, * and with a numa giving the textline index for each word. * (4) The pixa and boxa interfaces should make this type of * application simple to put together. The steps are: * ~ optionally reduce by 2x * ~ generate first estimate of word masks * ~ get b.b. of these, and remove the small and big ones * ~ extract pixa of the word images, using the b.b. * ~ sort actual word images in textline order (2d) * ~ flatten them to a pixa (1d), saving the textline index * for each pix * (5) In an actual application, it may be desirable to pre-filter * the input image to remove large components, to extract * single columns of text, and to deskew them. For example, * to remove both large components and small noisy components * that can interfere with the statistics used to estimate * parameters for segmenting by words, but still retain text lines, * the following image preprocessing can be done: * Pix *pixt = pixMorphSequence(pixs, "c40.1", 0); * Pix *pixf = pixSelectBySize(pixt, 0, 60, 8, * L_SELECT_HEIGHT, L_SELECT_IF_LT, NULL); * pixAnd(pixf, pixf, pixs); // the filtered image * The closing turns text lines into long blobs, but does not * significantly increase their height. But if there are many * small connected components in a dense texture, this is likely * to generate tall components that will be eliminated in pixf. * </pre> */ l_int32 pixGetWordsInTextlines(PIX *pixs, l_int32 reduction, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad, NUMA **pnai) { l_int32 maxdil; BOXA *boxa1, *boxad; BOXAA *baa; NUMA *nai; NUMAA *naa; PIXA *pixa1, *pixad; PIX *pix1; PIXAA *paa; PROCNAME("pixGetWordsInTextlines"); if (!pboxad || !ppixad || !pnai) return ERROR_INT("&boxad, &pixad, &nai not all defined", procName, 1); *pboxad = NULL; *ppixad = NULL; *pnai = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (reduction != 1 && reduction != 2) return ERROR_INT("reduction not in {1,2}", procName, 1); if (reduction == 1) { pix1 = pixClone(pixs); maxdil = 18; } else { /* reduction == 2 */ pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); maxdil = 9; } /* Get the bounding boxes of the words from the word mask. */ pixWordBoxesByDilation(pix1, maxdil, minwidth, minheight, maxwidth, maxheight, &boxa1, NULL); /* Generate a pixa of the word images */ pixa1 = pixaCreateFromBoxa(pix1, boxa1, NULL); /* mask over each word */ /* Sort the bounding boxes of these words by line. We use the * index mapping to allow identical sorting of the pixa. */ baa = boxaSort2d(boxa1, &naa, -1, -1, 4); paa = pixaSort2dByIndex(pixa1, naa, L_CLONE); /* Flatten the word paa */ pixad = pixaaFlattenToPixa(paa, &nai, L_CLONE); boxad = pixaGetBoxa(pixad, L_COPY); *pnai = nai; *pboxad = boxad; *ppixad = pixad; pixDestroy(&pix1); pixaDestroy(&pixa1); boxaDestroy(&boxa1); boxaaDestroy(&baa); pixaaDestroy(&paa); numaaDestroy(&naa); return 0; }
l_int32 main(int argc, char **argv) { char buf[256], dirname[256]; char *dirin, *pattern, *subdirout, *fname, *tail, *basename; l_int32 thresh, i, n; l_float32 scalefactor; PIX *pix1, *pix2, *pix3, *pix4; SARRAY *sa; static char mainName[] = "binarizefiles.c"; if (argc != 6) { fprintf(stderr, "Syntax: binarizefiles dirin pattern thresh scalefact dirout\n" " dirin: input directory for image files\n" " pattern: use 'allfiles' to convert all files\n" " in the directory\n" " thresh: 0 for adaptive; > 0 for global thresh (e.g., 128)\n" " scalefactor: in (0.0 ... 4.0]; use 1.0 to prevent scaling\n" " subdirout: subdirectory of /tmp for output files\n"); return 1; } dirin = argv[1]; pattern = argv[2]; thresh = atoi(argv[3]); scalefactor = atof(argv[4]); subdirout = argv[5]; if (!strcmp(pattern, "allfiles")) pattern = NULL; if (scalefactor <= 0.0 || scalefactor > 4.0) { L_WARNING("invalid scalefactor: setting to 1.0\n", mainName); scalefactor = 1.0; } /* Get the input filenames */ sa = getSortedPathnamesInDirectory(dirin, pattern, 0, 0); sarrayWriteStream(stderr, sa); n = sarrayGetCount(sa); /* Write the output files */ makeTempDirname(dirname, 256, subdirout); fprintf(stderr, "dirname: %s\n", dirname); lept_mkdir(subdirout); for (i = 0; i < n; i++) { fname = sarrayGetString(sa, i, L_NOCOPY); if ((pix1 = pixRead(fname)) == NULL) { L_ERROR("file %s not read as image", mainName, fname); continue; } splitPathAtDirectory(fname, NULL, &tail); splitPathAtExtension(tail, &basename, NULL); snprintf(buf, sizeof(buf), "%s/%s.tif", dirname, basename); FREE(tail); FREE(basename); fprintf(stderr, "fileout: %s\n", buf); if (scalefactor != 1.0) pix2 = pixScale(pix1, scalefactor, scalefactor); else pix2 = pixClone(pix1); if (thresh == 0) { pix4 = pixConvertTo8(pix2, 0); pix3 = pixAdaptThresholdToBinary(pix4, NULL, 1.0); pixDestroy(&pix4); } else { pix3 = pixConvertTo1(pix2, thresh); } pixWrite(buf, pix3, IFF_TIFF_G4); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); } sarrayDestroy(&sa); return 0; }
/*! * \brief pixDisplayWriteFormat() * * \param[in] pix 1, 2, 4, 8, 16, 32 bpp * \param[in] reduction -1 to erase and reset; 0 to disable; * otherwise this is a reduction factor * \param[in] format IFF_DEFAULT or IFF_PNG * \return 0 if OK; 1 on error * * <pre> * Notes: * (1) This writes files with pathnames "/tmp/lept/display/file.*" * if reduction \> 0. These can be collected into a pdf using * pixDisplayMultiple(); * (2) Before writing a set of files, call * pixDisplayWrite(NULL, -1); * This erases any previously written files in that directory. * (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_DEFAULT: * 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 * </pre> */ l_int32 pixDisplayWriteFormat(PIX *pixs, l_int32 reduction, l_int32 format) { char buf[L_BUF_SIZE]; char *fname; l_float32 scale; PIX *pix1, *pix2; static l_int32 index = 0; /* caution: not .so or thread safe */ PROCNAME("pixDisplayWriteFormat"); if (reduction == 0) return 0; if (reduction < 0) { /* initialize */ lept_rmdir("lept/display"); index = 0; return 0; } if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (format != IFF_DEFAULT && format != IFF_PNG) { L_INFO("invalid format; using default\n", procName); format = IFF_DEFAULT; } if (index == 0) lept_mkdir("lept/display"); index++; if (reduction == 1) { pix1 = pixClone(pixs); } else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pix1 = pixScaleToGray(pixs, scale); else pix1 = pixScale(pixs, scale, scale); } if (pixGetDepth(pix1) == 16) { pix2 = pixMaxDynamicRange(pix1, L_LOG_SCALE); snprintf(buf, L_BUF_SIZE, "file.%03d.png", index); fname = genPathname("/tmp/lept/display", buf); pixWrite(fname, pix2, IFF_PNG); pixDestroy(&pix2); } else if (pixGetDepth(pix1) < 8 || pixGetColormap(pix1) || format == IFF_PNG) { snprintf(buf, L_BUF_SIZE, "file.%03d.png", index); fname = genPathname("/tmp/lept/display", buf); pixWrite(fname, pix1, IFF_PNG); } else { snprintf(buf, L_BUF_SIZE, "file.%03d.jpg", index); fname = genPathname("/tmp/lept/display", buf); pixWrite(fname, pix1, format); } LEPT_FREE(fname); pixDestroy(&pix1); return 0; }
// On setting the input image, a clone of it is owned by this class. void ShiroRekhaSplitter::set_orig_pix(Pix* pix) { if (orig_pix_) { pixDestroy(&orig_pix_); } orig_pix_ = pixClone(pix); }
/*! * \brief pixDisplayWithTitle() * * \param[in] pix 1, 2, 4, 8, 16, 32 bpp * \param[in] x, y location of display frame * \param[in] title [optional] on frame; can be NULL; * \param[in] dispflag 1 to write, else disabled * \return 0 if OK; 1 on error * * <pre> * Notes: * (1) See notes for pixDisplay(). * (2) This displays the image if dispflag == 1. * </pre> */ 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("lept/disp"); lept_mkdir("lept/disp"); } index++; if (pixGetDepth(pix2) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "/tmp/lept/disp/write.%03d.png", index); pixWrite(buffer, pix2, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/lept/disp/write.%03d.jpg", index); pixWrite(buffer, pix2, IFF_JFIF_JPEG); } tempname = genPathname(buffer, NULL); #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); LEPT_FREE(pathname); #endif /* _WIN32 */ pixDestroy(&pix1); pixDestroy(&pix2); LEPT_FREE(tempname); return 0; }
// Top-level method to perform splitting based on current settings. // Returns true if a split was actually performed. // split_for_pageseg should be true if the splitting is being done prior to // page segmentation. This mode uses the flag // pageseg_devanagari_split_strategy to determine the splitting strategy. bool ShiroRekhaSplitter::Split(bool split_for_pageseg) { SplitStrategy split_strategy = split_for_pageseg ? pageseg_split_strategy_ : ocr_split_strategy_; if (split_strategy == NO_SPLIT) { return false; // Nothing to do. } ASSERT_HOST(split_strategy == MINIMAL_SPLIT || split_strategy == MAXIMAL_SPLIT); ASSERT_HOST(orig_pix_); if (devanagari_split_debuglevel > 0) { tprintf("Splitting shiro-rekha ...\n"); tprintf("Split strategy = %s\n", split_strategy == MINIMAL_SPLIT ? "Minimal" : "Maximal"); tprintf("Initial pageseg available = %s\n", segmentation_block_list_ ? "yes" : "no"); } // Create a copy of original image to store the splitting output. pixDestroy(&splitted_image_); splitted_image_ = pixCopy(NULL, orig_pix_); // Initialize debug image if required. if (devanagari_split_debugimage) { pixDestroy(&debug_image_); debug_image_ = pixConvertTo32(orig_pix_); } // Determine all connected components in the input image. A close operation // may be required prior to this, depending on the current settings. Pix* pix_for_ccs = pixClone(orig_pix_); if (perform_close_ && global_xheight_ != kUnspecifiedXheight && !segmentation_block_list_) { if (devanagari_split_debuglevel > 0) { tprintf("Performing a global close operation..\n"); } // A global measure is available for xheight, but no local information // exists. pixDestroy(&pix_for_ccs); pix_for_ccs = pixCopy(NULL, orig_pix_); PerformClose(pix_for_ccs, global_xheight_); } Pixa* ccs; Boxa* tmp_boxa = pixConnComp(pix_for_ccs, &ccs, 8); boxaDestroy(&tmp_boxa); pixDestroy(&pix_for_ccs); // Iterate over all connected components. Get their bounding boxes and clip // out the image regions corresponding to these boxes from the original image. // Conditionally run splitting on each of them. Boxa* regions_to_clear = boxaCreate(0); for (int i = 0; i < pixaGetCount(ccs); ++i) { Box* box = ccs->boxa->box[i]; Pix* word_pix = pixClipRectangle(orig_pix_, box, NULL); ASSERT_HOST(word_pix); int xheight = GetXheightForCC(box); if (xheight == kUnspecifiedXheight && segmentation_block_list_ && devanagari_split_debugimage) { pixRenderBoxArb(debug_image_, box, 1, 255, 0, 0); } // If some xheight measure is available, attempt to pre-eliminate small // blobs from the shiro-rekha process. This is primarily to save the CCs // corresponding to punctuation marks/small dots etc which are part of // larger graphemes. if (xheight == kUnspecifiedXheight || (box->w > xheight / 3 && box->h > xheight / 2)) { SplitWordShiroRekha(split_strategy, word_pix, xheight, box->x, box->y, regions_to_clear); } else if (devanagari_split_debuglevel > 0) { tprintf("CC dropped from splitting: %d,%d (%d, %d)\n", box->x, box->y, box->w, box->h); } pixDestroy(&word_pix); } // Actually clear the boxes now. for (int i = 0; i < boxaGetCount(regions_to_clear); ++i) { Box* box = boxaGetBox(regions_to_clear, i, L_CLONE); pixClearInRect(splitted_image_, box); boxDestroy(&box); } boxaDestroy(®ions_to_clear); pixaDestroy(&ccs); if (devanagari_split_debugimage) { DumpDebugImage(split_for_pageseg ? "pageseg_split_debug.png" : "ocr_split_debug.png"); } return true; }
/*! * 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; }
/*! * pixSaveTiledOutline() * * Input: pixs (1, 2, 4, 8, 32 bpp) * pixa (the pix are accumulated here) * reduction (0 to disable; otherwise this is a reduction 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) @reduction is the integer reduction factor for the input * image. After reduction 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 reduction == 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_int32 reduction, l_int32 newrow, l_int32 space, l_int32 linewidth, l_int32 dp) { l_int32 n, top, left, bx, by, bw, w, h, depth, bottom; l_float32 scale; BOX *box; PIX *pix, *pixt1, *pixt2, *pixt3; PROCNAME("pixSaveTiledOutline"); if (reduction == 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", procName); depth = 32; } else depth = dp; } else { /* extract the depth and bottom params from the first pix */ pix = pixaGetPix(pixa, 0, L_CLONE); depth = pixGetDepth(pix); bottom = pixGetInputFormat(pix); /* not typical usage! */ pixDestroy(&pix); } /* Scale and convert to output depth */ if (reduction == 1) pixt1 = pixClone(pixs); else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pixt1 = pixScaleToGray(pixs, scale); else pixt1 = pixScale(pixs, scale, scale); } if (depth == 8) pixt2 = pixConvertTo8(pixt1, 0); else pixt2 = pixConvertTo32(pixt1); pixDestroy(&pixt1); /* Add black outline */ if (linewidth > 0) pixt3 = pixAddBorder(pixt2, linewidth, 0); else pixt3 = pixClone(pixt2); pixDestroy(&pixt2); /* 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(pixt3, &w, &h, NULL); bottom = L_MAX(bottom, top + h); box = boxCreate(left, top, w, h); pixaAddPix(pixa, pixt3, L_INSERT); pixaAddBox(pixa, box, L_INSERT); /* Save the new bottom value */ pix = pixaGetPix(pixa, 0, L_CLONE); pixSetInputFormat(pix, bottom); /* not typical usage! */ pixDestroy(&pix); return 0; }
/*! * pixGenerateSelBoundary() * * Input: pix (1 bpp, typically small, to be used as a pattern) * hitdist (min distance from fg boundary pixel) * missdist (min distance from bg boundary pixel) * hitskip (number of boundary pixels skipped between hits) * missskip (number of boundary pixels skipped between misses) * topflag (flag for extra pixels of bg added above) * botflag (flag for extra pixels of bg added below) * leftflag (flag for extra pixels of bg added to left) * rightflag (flag for 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) All fg elements selected are exactly hitdist pixels away from * the nearest fg boundary pixel, and ditto for bg elements. * Valid inputs of hitdist and missdist are 0, 1, 2, 3 and 4. * For example, a hitdist of 0 puts the hits at the fg boundary. * Usually, the distances should be > 0 avoid the effect of * noise at the boundary. * (2) Set hitskip < 0 if no hits are to be used. Ditto for missskip. * If both hitskip and missskip are < 0, the sel would be empty, * and NULL is returned. * (3) The 4 flags determine whether the sel is increased on that side * to allow bg misses to be placed all along that boundary. * The increase in sel size on that side is the minimum necessary * to allow the misses to be placed at mindist. For text characters, * the topflag and botflag are typically set to 1, and the leftflag * and rightflag to 0. * (4) 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. * (5) This is probably the best of the three sel generators, in the * sense that you have the most flexibility with the smallest number * of hits and misses. */ SEL * pixGenerateSelBoundary(PIX *pixs, l_int32 hitdist, l_int32 missdist, l_int32 hitskip, l_int32 missskip, l_int32 topflag, l_int32 botflag, l_int32 leftflag, l_int32 rightflag, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, ix, iy, i, npt; PIX *pixt1, *pixt2, *pixt3, *pixfg, *pixbg; SEL *selh, *selm, *sel_3, *sel; PTA *ptah, *ptam; PROCNAME("pixGenerateSelBoundary"); 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 (hitdist < 0 || hitdist > 4 || missdist < 0 || missdist > 4) return (SEL *)ERROR_PTR("dist not in {0 .. 4}", procName, NULL); if (hitskip < 0 && missskip < 0) return (SEL *)ERROR_PTR("no hits or misses", procName, NULL); /* 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 (topflag || botflag || leftflag || rightflag) { x = y = 0; if (topflag) { h += missdist + 1; y = missdist + 1; } if (botflag) h += missdist + 1; if (leftflag) { w += missdist + 1; x = missdist + 1; } if (rightflag) w += missdist + 1; 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 exactly hitdist and * missdist (rsp) away from the boundary pixels in their set. * Then get a subsampled set of these points. */ sel_3 = selCreateBrick(3, 3, 1, 1, SEL_HIT); if (hitskip >= 0) { selh = selCreateBrick(2 * hitdist + 1, 2 * hitdist + 1, hitdist, hitdist, SEL_HIT); pixt3 = pixErode(NULL, pixt2, selh); pixfg = pixErode(NULL, pixt3, sel_3); pixXor(pixfg, pixfg, pixt3); ptah = pixSubsampleBoundaryPixels(pixfg, hitskip); pixDestroy(&pixt3); pixDestroy(&pixfg); selDestroy(&selh); } if (missskip >= 0) { selm = selCreateBrick(2 * missdist + 1, 2 * missdist + 1, missdist, missdist, SEL_HIT); pixt3 = pixDilate(NULL, pixt2, selm); pixbg = pixDilate(NULL, pixt3, sel_3); pixXor(pixbg, pixbg, pixt3); ptam = pixSubsampleBoundaryPixels(pixbg, missskip); pixDestroy(&pixt3); pixDestroy(&pixbg); selDestroy(&selm); } selDestroy(&sel_3); pixDestroy(&pixt2); /* Generate the hit-miss sel from these point */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); if (hitskip >= 0) { npt = ptaGetCount(ptah); for (i = 0; i < npt; i++) { ptaGetIPt(ptah, i, &ix, &iy); selSetElement(sel, iy, ix, SEL_HIT); } } if (missskip >= 0) { npt = ptaGetCount(ptam); for (i = 0; i < npt; i++) { ptaGetIPt(ptam, i, &ix, &iy); selSetElement(sel, iy, ix, SEL_MISS); } } ptaDestroy(&ptah); ptaDestroy(&ptam); return sel; }
/*! * 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, ignore; l_float32 ratw, rath, ratmin; PIX *pixt; #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_XV && var_DISPLAY_PROG != L_DISPLAY_WITH_XLI && var_DISPLAY_PROG != L_DISPLAY_WITH_XZGV && var_DISPLAY_PROG != L_DISPLAY_WITH_IV) return ERROR_INT("no program chosen for display", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (w <= MAX_DISPLAY_WIDTH && h <= MAX_DISPLAY_HEIGHT) { if (d == 16) /* take MSB */ pixt = pixConvert16To8(pixs, 1); else pixt = pixClone(pixs); } else { ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w; rath = (l_float32)MAX_DISPLAY_HEIGHT / (l_float32)h; ratmin = L_MIN(ratw, rath); if (ratmin < 0.125 && d == 1) pixt = pixScaleToGray8(pixs); else if (ratmin < 0.25 && d == 1) pixt = pixScaleToGray4(pixs); else if (ratmin < 0.33 && d == 1) pixt = pixScaleToGray3(pixs); else if (ratmin < 0.5 && d == 1) pixt = pixScaleToGray2(pixs); else pixt = pixScale(pixs, ratmin, ratmin); if (!pixt) return ERROR_INT("pixt not made", procName, 1); } if (index == 0) { lept_rmdir("display"); lept_mkdir("display"); } index++; if (pixGetDepth(pixt) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "/tmp/display/write.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/display/write.%03d.jpg", index); pixWrite(buffer, pixt, IFF_JFIF_JPEG); } tempname = stringNew(buffer); #ifndef _WIN32 /* Unix */ 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_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_XZGV) { /* no way to display title */ pixGetDimensions(pixt, &wt, &ht, NULL); snprintf(buffer, L_BUF_SIZE, "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10, x, y, 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(&pixt); FREE(tempname); return 0; }
/*! * pixSauvolaBinarize() * * Input: pixs (8 bpp grayscale; not colormapped) * whsize (window half-width for measuring local statistics) * factor (factor for reducing threshold due to variance; >= 0) * addborder (1 to add border of width (@whsize + 1) on all sides) * &pixm (<optional return> local mean values) * &pixsd (<optional return> local standard deviation values) * &pixth (<optional return> threshold values) * &pixd (<optional return> thresholded image) * Return: 0 if OK, 1 on error * * 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. */ 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 && !ppixsd && !ppixth && !ppixd) return ERROR_INT("no outputs", procName, 1); if (ppixm) *ppixm = NULL; if (ppixsd) *ppixsd = NULL; 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 (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); 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; }
/*! * 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/junk_write_display*"); * (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 buffer[L_BUF_SIZE]; l_int32 ignore; 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) { snprintf(buffer, L_BUF_SIZE, "rm -f /tmp/junk_write_display.*.png /tmp/junk_write_display.*.jpg"); ignore = system(buffer); } 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(buffer, L_BUF_SIZE, "/tmp/junk_write_display.%03d.png", index); pixWrite(buffer, pix8, IFF_PNG); pixDestroy(&pix8); } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt) || format == IFF_PNG) { snprintf(buffer, L_BUF_SIZE, "/tmp/junk_write_display.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/junk_write_display.%03d.jpg", index); pixWrite(buffer, pixt, format); } pixDestroy(&pixt); return 0; }
int main(int argc, char **argv) { char *filein, *fileout, *fname; char buffer[512]; const char *psfile = "/tmp/junk_split_image.ps"; l_int32 nx, ny, i, w, h, d, ws, hs, n, res, ignore; l_float32 scale; PIX *pixs, *pixt, *pixr; PIXA *pixa; static char mainName[] = "splitimage2pdf"; if (argc != 5) return ERROR_INT(" Syntax: splitimage2pdf filein nx ny fileout", mainName, 1); filein = argv[1]; nx = atoi(argv[2]); ny = atoi(argv[3]); fileout = argv[4]; lept_rm(NULL, "junk_split_image.ps"); if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); d = pixGetDepth(pixs); if (d == 1 ) lept_rm(NULL, "junk_split_image.tif"); else if (d == 8 || d == 32) lept_rm(NULL, "junk_split_image.jpg"); else return ERROR_INT("d not in {1,8,32} bpp", mainName, 1); pixGetDimensions(pixs, &ws, &hs, NULL); if (ny * ws > nx * hs) pixr = pixRotate90(pixs, 1); else pixr = pixClone(pixs); pixa = pixaSplitPix(pixr, nx, ny, 0, 0); n = pixaGetCount(pixa); res = 300; for (i = 0; i < n; i++) { pixt = pixaGetPix(pixa, i, L_CLONE); pixGetDimensions(pixt, &w, &h, NULL); scale = L_MIN(FILL_FACTOR * 2550 / w, FILL_FACTOR * 3300 / h); fname = NULL; if (d == 1) { fname = genPathname("/tmp", "junk_split_image.tif"); pixWrite(fname, pixt, IFF_TIFF_G4); if (i == 0) { convertG4ToPS(fname, psfile, "w", 0, 0, 300, scale, 1, FALSE, TRUE); } else { convertG4ToPS(fname, psfile, "a", 0, 0, 300, scale, 1, FALSE, TRUE); } } else { fname = genPathname("/tmp", "junk_split_image.jpg"); pixWrite(fname, pixt, IFF_JFIF_JPEG); if (i == 0) { convertJpegToPS(fname, psfile, "w", 0, 0, 300, scale, 1, TRUE); } else { convertJpegToPS(fname, psfile, "a", 0, 0, 300, scale, 1, TRUE); } } lept_free(fname); pixDestroy(&pixt); } snprintf(buffer, sizeof(buffer), "ps2pdf %s %s", psfile, fileout); ignore = system(buffer); /* ps2pdf */ pixaDestroy(&pixa); pixDestroy(&pixr); pixDestroy(&pixs); return 0; }
/*! * deskew() * * Input: pixs * redsearch (for binary search: reduction factor = 1, 2 or 4) * Return: deskewed pix, or NULL on error */ PIX * deskew(PIX *pixs, l_int32 redsearch) { l_float32 angle, conf, deg2rad; PIX *pixg; /* gray version */ PIX *pixb; /* binary version */ PIX *pixd; /* destination image */ PROCNAME("deskew"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); /* Calculate a skew angle. We may need to make a binary version of the * image for this calculation. */ if (pixGetDepth(pixs) != 1) { /* FIX ME: We should probably pick a threshold value with more care. */ /* Create a grayscale image if we need one. */ if (pixGetDepth(pixs) >= 24) { pixg = pixConvertRGBToGray(pixs, 0.0, 0.0, 0.0); } else { pixg = pixs; } pixb = pixThresholdToBinary(pixg, 127); if (pixg != pixs) { pixDestroy(&pixg); } /* Assert: We are done with any gray image. */ } else { pixb = pixs; } /* Assert: We have a valid binary image. */ if (redsearch != 1 && redsearch != 2 && redsearch != 4) return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL); deg2rad = 3.1415926535 / 180.; if (pixFindSkewSweepAndSearch(pixb, &angle, &conf, DEFAULT_SWEEP_REDUCTION, redsearch, DEFAULT_SWEEP_RANGE, DEFAULT_SWEEP_DELTA, DEFAULT_MINBS_DELTA)) { pixd = pixClone(pixs); goto finish; } if (L_ABS(angle) < MIN_DESKEW_ANGLE || conf < MIN_ALLOWED_CONFIDENCE) { pixd = pixClone(pixs); goto finish; } /* If the pixel depth of pixs is 1, we need to use a bit-depth * independent rotate instead of the more accurate area mapping rotate. */ if (pixGetDepth(pixs) == 1) { if ((pixd = pixRotateShear(pixs, 0, 0, deg2rad * angle, 0xffffff00)) == NULL) { pixd = pixClone(pixs); } } else { #if defined(COLOR_ROTATE) if ((pixd = pixRotateAMColorFast(pixs, deg2rad * angle)) == NULL) { pixd = pixClone(pixs); } #else if ((pixd = pixRotateAM(pixs, deg2rad * angle, 0xffffff00)) == NULL) { pixd = pixClone(pixs); } #endif } finish: if (pixb != pixs) { pixDestroy(&pixb); } return pixd; }
/*! * pixStrokeWidthTransform() * * Input: pixs (1 bpp) * color (0 for white runs, 1 for black runs) * depth (of pixd: 8 or 16 bpp) * nangles (2, 4, 6 or 8) * Return: pixd (8 or 16 bpp), or null on error * * Notes: * (1) The dest Pix is 8 or 16 bpp, with the pixel values * equal to the stroke width in which it is a member. * The values are clipped to the max pixel value if necessary. * (2) The color determines if we're labelling white or black strokes. * (3) A pixel that is not a member of the chosen color gets * value 0; it belongs to a width of length 0 of the * chosen color. * (4) This chooses, for each dest pixel, the minimum of sets * of runlengths through each pixel. Here are the sets: * nangles increment set * ------- --------- -------------------------------- * 2 90 {0, 90} * 4 45 {0, 45, 90, 135} * 6 30 {0, 30, 60, 90, 120, 150} * 8 22.5 {0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5} * (5) Runtime scales linearly with (nangles - 2). */ PIX * pixStrokeWidthTransform(PIX *pixs, l_int32 color, l_int32 depth, l_int32 nangles) { l_float32 angle, pi; PIX *pixh, *pixv, *pixt, *pixg1, *pixg2, *pixg3, *pixg4; PROCNAME("pixStrokeWidthTransform"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *) ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (depth != 8 && depth != 16) return (PIX *) ERROR_PTR("depth must be 8 or 16 bpp", procName, NULL); if (nangles != 2 && nangles != 4 && nangles != 6 && nangles != 8) return (PIX *) ERROR_PTR("nangles not in {2,4,6,8}", procName, NULL); /* Use fg runs for evaluation */ if (color == 0) pixt = pixInvert(NULL, pixs); else pixt = pixClone(pixs); /* Find min length at 0 and 90 degrees */ pixh = pixRunlengthTransform(pixt, 1, L_HORIZONTAL_RUNS, depth); pixv = pixRunlengthTransform(pixt, 1, L_VERTICAL_RUNS, depth); pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN); pixDestroy(&pixh); pixDestroy(&pixv); pixg2 = pixg3 = pixg4 = NULL; pi = 3.1415926535; if (nangles == 4 || nangles == 8) { /* Find min length at +45 and -45 degrees */ angle = pi / 4.0; pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth); } if (nangles == 6) { /* Find min length at +30 and -60 degrees */ angle = pi / 6.0; pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth); /* Find min length at +60 and -30 degrees */ angle = pi / 3.0; pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth); } if (nangles == 8) { /* Find min length at +22.5 and -67.5 degrees */ angle = pi / 8.0; pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth); /* Find min length at +67.5 and -22.5 degrees */ angle = 3.0 * pi / 8.0; pixg4 = pixFindMinRunsOrthogonal(pixt, angle, depth); } pixDestroy(&pixt); if (nangles > 2) pixMinOrMax(pixg1, pixg1, pixg2, L_CHOOSE_MIN); if (nangles > 4) pixMinOrMax(pixg1, pixg1, pixg3, L_CHOOSE_MIN); if (nangles > 6) pixMinOrMax(pixg1, pixg1, pixg4, L_CHOOSE_MIN); pixDestroy(&pixg2); pixDestroy(&pixg3); pixDestroy(&pixg4); return pixg1; }
/*! * pixQuadtreeVariance() * * Input: pixs (8 bpp, no colormap) * nlevels (in quadtree) * *pix_ma (input mean accumulator; can be null) * *dpix_msa (input mean square accumulator; can be null) * *pfpixa_v (<optional return> variance values in quadtree) * *pfpixa_rv (<optional return> root variance values in quadtree) * Return: 0 if OK, 1 on error * * Notes: * (1) The returned fpixav and fpixarv have @nlevels of fpix, * each containing at the respective levels the variance * and root variance values. */ l_int32 pixQuadtreeVariance(PIX *pixs, l_int32 nlevels, PIX *pix_ma, DPIX *dpix_msa, FPIXA **pfpixa_v, FPIXA **pfpixa_rv) { l_int32 i, j, w, h, size, n; l_float32 var, rvar; BOX *box; BOXA *boxa; BOXAA *baa; FPIX *fpixv, *fpixrv; PIX *pix_mac; /* copy of mean accumulator */ DPIX *dpix_msac; /* msa clone */ PROCNAME("pixQuadtreeVariance"); if (!pfpixa_v && !pfpixa_rv) return ERROR_INT("neither &fpixav nor &fpixarv defined", procName, 1); if (pfpixa_v) *pfpixa_v = NULL; if (pfpixa_rv) *pfpixa_rv = NULL; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs not defined or not 8 bpp", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); if (nlevels > quadtreeMaxLevels(w, h)) return ERROR_INT("nlevels too large for image", procName, 1); if (!pix_ma) pix_mac = pixBlockconvAccum(pixs); else pix_mac = pixClone(pix_ma); if (!pix_mac) return ERROR_INT("pix_mac not made", procName, 1); if (!dpix_msa) dpix_msac = pixMeanSquareAccum(pixs); else dpix_msac = dpixClone(dpix_msa); if (!dpix_msac) return ERROR_INT("dpix_msac not made", procName, 1); if ((baa = boxaaQuadtreeRegions(w, h, nlevels)) == NULL) { pixDestroy(&pix_mac); dpixDestroy(&dpix_msac); return ERROR_INT("baa not made", procName, 1); } if (pfpixa_v) *pfpixa_v = fpixaCreate(nlevels); if (pfpixa_rv) *pfpixa_rv = fpixaCreate(nlevels); for (i = 0; i < nlevels; i++) { boxa = boxaaGetBoxa(baa, i, L_CLONE); size = 1 << i; n = boxaGetCount(boxa); /* n == size * size */ if (pfpixa_v) fpixv = fpixCreate(size, size); if (pfpixa_rv) fpixrv = fpixCreate(size, size); for (j = 0; j < n; j++) { box = boxaGetBox(boxa, j, L_CLONE); pixVarianceInRectangle(pixs, box, pix_mac, dpix_msac, &var, &rvar); if (pfpixa_v) fpixSetPixel(fpixv, j % size, j / size, var); if (pfpixa_rv) fpixSetPixel(fpixrv, j % size, j / size, rvar); boxDestroy(&box); } if (pfpixa_v) fpixaAddFPix(*pfpixa_v, fpixv, L_INSERT); if (pfpixa_rv) fpixaAddFPix(*pfpixa_rv, fpixrv, L_INSERT); boxaDestroy(&boxa); } pixDestroy(&pix_mac); dpixDestroy(&dpix_msac); boxaaDestroy(&baa); return 0; }