int main_find_pattern(int argc, char **argv) { char *filein, *fileout, *patternfile; l_int32 w, h, i, n; BOX *box, *boxe; BOXA *boxa1, *boxa2; PIX *pixs, *pixp, *pixpe; PIX *pixd, *pixt1, *pixt2, *pixhmt; SEL *sel_2h, *sel; static char mainName[] = "findpattern1"; filein = "feyn.tif"; patternfile = "char.tif"; fileout = "result.findpattern1"; if ((pixs = pixRead(filein)) == NULL) printf("pixs not made\n"); if ((pixp = pixRead(patternfile)) == NULL) printf("pixp not made\n"); w = pixGetWidth(pixp); h = pixGetHeight(pixp); /* generate the hit-miss Sel with runs */ sel = pixGenerateSelWithRuns(pixp, NumHorLines, NumVertLines, 0, MinRunlength, 7, 7, 0, 0, &pixpe); /* display the Sel two ways */ selWriteStream(stderr, sel); pixt1 = pixDisplayHitMissSel(pixpe, sel, 9, HitColor, MissColor); pixDisplay(pixt1, 200, 200); pixWrite("junkpixt", pixt1, IFF_PNG); /* use the Sel to find all instances in the page */ startTimer(); pixhmt = pixHMT(NULL, pixs, sel); fprintf(stderr, "Time to find patterns = %7.3f\n", stopTimer()); /* small erosion to remove noise; typically not necessary if * there are enough elements in the Sel */ sel_2h = selCreateBrick(1, 2, 0, 0, SEL_HIT); pixt2 = pixErode(NULL, pixhmt, sel_2h); /* display the result visually by placing the Sel at each * location found */ pixd = pixDilate(NULL, pixt2, sel); pixWrite(fileout, pixd, IFF_TIFF_G4); /* display outut with an outline around each located pattern */ boxa1 = pixConnCompBB(pixt2, 8); n = boxaGetCount(boxa1); boxa2 = boxaCreate(n); for (i = 0; i < n; i++) { box = boxaGetBox(boxa1, i, L_COPY); boxe = boxCreate(box->x - w / 2, box->y - h / 2, w + 4, h + 4); boxaAddBox(boxa2, boxe, L_INSERT); pixRenderBox(pixs, boxe, 4, L_FLIP_PIXELS); boxDestroy(&box); } pixWrite("junkoutline", pixs, IFF_TIFF_G4); //boxaWriteStream(stderr, boxa2); //TODO ??? pixDestroy(&pixs); pixDestroy(&pixp); pixDestroy(&pixpe); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixhmt); pixDestroy(&pixd); selDestroy(&sel); selDestroy(&sel_2h); boxaDestroy(&boxa1); boxaDestroy(&boxa2); printf("\n---\nEND\n"); getchar(); return 0; }
main(int argc, char **argv) { char dilateseq[BUF_SIZE], erodeseq[BUF_SIZE]; char openseq[BUF_SIZE], closeseq[BUF_SIZE]; char wtophatseq[BUF_SIZE], btophatseq[BUF_SIZE]; char *filein; l_int32 w, h, d; PIX *pixs, *pixt, *pixt2, *pixt3, *pixt3a, *pixt4; PIX *pixg, *pixd, *pixd1, *pixd2, *pixd3; PIXACC *pacc; PIXCMAP *cmap; static char mainName[] = "graymorph1_reg"; if (argc != 2) exit(ERROR_INT(" Syntax: graymorph1_reg filein", mainName, 1)); filein = argv[1]; if ((pixs = pixRead(filein)) == NULL) exit(ERROR_INT("pixs not made", mainName, 1)); pixGetDimensions(pixs, &w, &h, &d); if (d != 8) exit(ERROR_INT("pixs not 8 bpp", mainName, 1)); /* -------- Test gray morph, including interpreter ------------ */ pixd = pixDilateGray(pixs, WSIZE, HSIZE); sprintf(dilateseq, "D%d.%d", WSIZE, HSIZE); pixg = pixGrayMorphSequence(pixs, dilateseq, HORIZ_SEP, 0); pixCompare(pixd, pixg, "results are the same", "results are different" ); pixDestroy(&pixg); pixDestroy(&pixd); pixd = pixErodeGray(pixs, WSIZE, HSIZE); sprintf(erodeseq, "E%d.%d", WSIZE, HSIZE); pixg = pixGrayMorphSequence(pixs, erodeseq, HORIZ_SEP, 100); pixCompare(pixd, pixg, "results are the same", "results are different" ); pixDestroy(&pixg); pixDestroy(&pixd); pixd = pixOpenGray(pixs, WSIZE, HSIZE); sprintf(openseq, "O%d.%d", WSIZE, HSIZE); pixg = pixGrayMorphSequence(pixs, openseq, HORIZ_SEP, 200); pixCompare(pixd, pixg, "results are the same", "results are different" ); pixDestroy(&pixg); pixDestroy(&pixd); pixd = pixCloseGray(pixs, WSIZE, HSIZE); sprintf(closeseq, "C%d.%d", WSIZE, HSIZE); pixg = pixGrayMorphSequence(pixs, closeseq, HORIZ_SEP, 300); pixCompare(pixd, pixg, "results are the same", "results are different" ); pixDestroy(&pixg); pixDestroy(&pixd); pixd = pixTophat(pixs, WSIZE, HSIZE, L_TOPHAT_WHITE); sprintf(wtophatseq, "Tw%d.%d", WSIZE, HSIZE); pixg = pixGrayMorphSequence(pixs, wtophatseq, HORIZ_SEP, 400); pixCompare(pixd, pixg, "results are the same", "results are different" ); pixDestroy(&pixg); pixDestroy(&pixd); pixd = pixTophat(pixs, WSIZE, HSIZE, L_TOPHAT_BLACK); sprintf(btophatseq, "Tb%d.%d", WSIZE, HSIZE); pixg = pixGrayMorphSequence(pixs, btophatseq, HORIZ_SEP, 500); pixCompare(pixd, pixg, "results are the same", "results are different" ); pixDestroy(&pixg); /* ------------- Test erode/dilate duality -------------- */ pixd = pixDilateGray(pixs, WSIZE, HSIZE); pixInvert(pixs, pixs); pixd2 = pixErodeGray(pixs, WSIZE, HSIZE); pixInvert(pixd2, pixd2); pixCompare(pixd, pixd2, "results are the same", "results are different" ); pixDestroy(&pixd); pixDestroy(&pixd2); /* ------------- Test open/close duality -------------- */ pixd = pixOpenGray(pixs, WSIZE, HSIZE); pixInvert(pixs, pixs); pixd2 = pixCloseGray(pixs, WSIZE, HSIZE); pixInvert(pixd2, pixd2); pixCompare(pixd, pixd2, "results are the same", "results are different" ); pixDestroy(&pixd); pixDestroy(&pixd2); /* ------------- Test tophat duality -------------- */ pixd = pixTophat(pixs, WSIZE, HSIZE, L_TOPHAT_WHITE); pixInvert(pixs, pixs); pixd2 = pixTophat(pixs, WSIZE, HSIZE, L_TOPHAT_BLACK); pixCompare(pixd, pixd2, "Correct: images are duals", "Error: images are not duals" ); pixDestroy(&pixd); pixDestroy(&pixd2); pixInvert(pixs, pixs); pixd = pixGrayMorphSequence(pixs, "Tw9.5", HORIZ_SEP, 100); pixInvert(pixs, pixs); pixd2 = pixGrayMorphSequence(pixs, "Tb9.5", HORIZ_SEP, 300); pixCompare(pixd, pixd2, "Correct: images are duals", "Error: images are not duals" ); pixDestroy(&pixd); pixDestroy(&pixd2); /* ------------- Test opening/closing for large sels -------------- */ pixd = pixGrayMorphSequence(pixs, "C9.9 + C19.19 + C29.29 + C39.39 + C49.49", HORIZ_SEP, 100); pixDestroy(&pixd); pixd = pixGrayMorphSequence(pixs, "O9.9 + O19.19 + O29.29 + O39.39 + O49.49", HORIZ_SEP, 400); pixDestroy(&pixd); /* ---------- Closing plus white tophat result ------------ * * Parameters: wsize, hsize = 9, 29 * * ---------------------------------------------------------*/ pixd = pixCloseGray(pixs, 9, 9); pixd1 = pixTophat(pixd, 9, 9, L_TOPHAT_WHITE); pixd2 = pixGrayMorphSequence(pixs, "C9.9 + TW9.9", HORIZ_SEP, 0); pixCompare(pixd1, pixd2, "correct: same", "wrong: different"); pixd3 = pixMaxDynamicRange(pixd1, L_LINEAR_SCALE); pixDisplayWrite(pixd3, 1); pixDestroy(&pixd); pixDestroy(&pixd1); pixDestroy(&pixd2); pixDestroy(&pixd3); pixd = pixCloseGray(pixs, 29, 29); pixd1 = pixTophat(pixd, 29, 29, L_TOPHAT_WHITE); pixd2 = pixGrayMorphSequence(pixs, "C29.29 + Tw29.29", HORIZ_SEP, 0); pixCompare(pixd1, pixd2, "correct: same", "wrong: different"); pixd3 = pixMaxDynamicRange(pixd1, L_LINEAR_SCALE); pixDisplayWrite(pixd3, 1); pixDestroy(&pixd); pixDestroy(&pixd1); pixDestroy(&pixd2); pixDestroy(&pixd3); /* --------- hdome with parameter height = 100 ------------*/ pixd = pixHDome(pixs, 100, 4); pixd2 = pixMaxDynamicRange(pixd, L_LINEAR_SCALE); pixDisplayWrite(pixd2, 1); pixDestroy(&pixd2); /* ----- Contrast enhancement with morph parameters 9, 9 -------*/ pixd1 = pixInitAccumulate(w, h, 0x8000); pixAccumulate(pixd1, pixs, L_ARITH_ADD); pixMultConstAccumulate(pixd1, 3., 0x8000); pixd2 = pixOpenGray(pixs, 9, 9); pixAccumulate(pixd1, pixd2, L_ARITH_SUBTRACT); pixDestroy(&pixd2); pixd2 = pixCloseGray(pixs, 9, 9); pixAccumulate(pixd1, pixd2, L_ARITH_SUBTRACT); pixDestroy(&pixd2); pixd = pixFinalAccumulate(pixd1, 0x8000, 8); pixDisplayWrite(pixd, 1); pixDestroy(&pixd1); /* Do the same thing with the Pixacc */ pacc = pixaccCreate(w, h, 1); pixaccAdd(pacc, pixs); pixaccMultConst(pacc, 3.); pixd1 = pixOpenGray(pixs, 9, 9); pixaccSubtract(pacc, pixd1); pixDestroy(&pixd1); pixd1 = pixCloseGray(pixs, 9, 9); pixaccSubtract(pacc, pixd1); pixDestroy(&pixd1); pixd2 = pixaccFinal(pacc, 8); pixaccDestroy(&pacc); pixDisplayWrite(pixd2, 1); pixCompare(pixd, pixd2, "Correct: same", "Wrong: different"); pixDestroy(&pixd); pixDestroy(&pixd2); /* ---- Tophat result on feynman stamp, to extract diagrams ----- */ pixDestroy(&pixs); pixs = pixRead("feynman-stamp.jpg"); /* Make output image to hold five intermediate images */ w = pixGetWidth(pixs); h = pixGetHeight(pixs); pixd = pixCreate(5 * w + 18, h + 6, 32); /* composite output image */ pixSetAllArbitrary(pixd, 0x0000ff00); /* set to blue */ /* Paste in the input image */ pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR); pixRasterop(pixd, 3, 3, w, h, PIX_SRC, pixt, 0, 0); /* 1st one */ /* pixWrite("/tmp/junkgray.jpg", pixt, IFF_JFIF_JPEG); */ pixDestroy(&pixt); /* Paste in the grayscale version */ cmap = pixGetColormap(pixs); if (cmap) pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE); else pixt = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33); pixt2 = pixConvertTo32(pixt); /* 8 --> 32 bpp */ pixRasterop(pixd, w + 6, 3, w, h, PIX_SRC, pixt2, 0, 0); /* 2nd one */ pixDestroy(&pixt2); /* Paste in a log dynamic range scaled version of the white tophat */ pixt2 = pixTophat(pixt, 3, 3, L_TOPHAT_WHITE); pixt3a = pixMaxDynamicRange(pixt2, L_LOG_SCALE); pixt3 = pixConvertTo32(pixt3a); pixRasterop(pixd, 2 * w + 9, 3, w, h, PIX_SRC, pixt3, 0, 0); /* 3rd */ /* pixWrite("/tmp/junktophat.jpg", pixt2, IFF_JFIF_JPEG); */ pixDestroy(&pixt3); pixDestroy(&pixt3a); pixDestroy(&pixt); /* Stretch the range and threshold to binary; paste it in */ pixt3a = pixGammaTRC(NULL, pixt2, 1.0, 0, 80); pixt3 = pixThresholdToBinary(pixt3a, 70); pixt4 = pixConvertTo32(pixt3); pixRasterop(pixd, 3 * w + 12, 3, w, h, PIX_SRC, pixt4, 0, 0); /* 4th */ /* pixWrite("/tmp/junkbin.png", pixt3, IFF_PNG); */ pixDestroy(&pixt2); pixDestroy(&pixt3a); pixDestroy(&pixt4); /* Invert; this is the final result */ pixInvert(pixt3, pixt3); pixt4 = pixConvertTo32(pixt3); pixRasterop(pixd, 4 * w + 15, 3, w, h, PIX_SRC, pixt4, 0, 0); /* 5th */ pixWrite("/tmp/junkbininvert.png", pixt3, IFF_PNG); pixDisplayWrite(pixd, 1); /* pixWrite("/tmp/junkall.jpg", pixd, IFF_JFIF_JPEG); */ pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixd); pixDisplayMultiple("/tmp/junk_write_display*"); pixDestroy(&pixs); return 0; }
/*! * \brief pixConnCompPixa() * * \param[in] pixs 1 bpp * \param[out] ppixa pixa of each c.c. * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) This finds bounding boxes of 4- or 8-connected components * in a binary image, and saves images of each c.c * in a pixa array. * (2) It sets up 2 temporary pix, and for each c.c. that is * located in raster order, it erases the c.c. from one pix, * then uses the b.b. to extract the c.c. from the two pix using * an XOR, and finally erases the c.c. from the second pix. * (3) A clone of the returned boxa (where all boxes in the array * are clones) is inserted into the pixa. * (4) If the input is valid, this always returns a boxa and a pixa. * If pixs is empty, the boxa and pixa will be empty. * </pre> */ BOXA * pixConnCompPixa(PIX *pixs, PIXA **ppixa, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pixt1, *pixt2, *pixt3, *pixt4; PIXA *pixa; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompPixa"); if (!ppixa) return (BOXA *)ERROR_PTR("&pixa not defined", procName, NULL); *ppixa = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); pixa = pixaCreate(0); *ppixa = pixa; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ if ((pixt1 = pixCopy(NULL, pixs)) == NULL) return (BOXA *)ERROR_PTR("pixt1 not made", procName, NULL); if ((pixt2 = pixCopy(NULL, pixs)) == NULL) return (BOXA *)ERROR_PTR("pixt2 not made", procName, NULL); h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) return (BOXA *)ERROR_PTR("stack not made", procName, NULL); if ((auxstack = lstackCreate(0)) == NULL) return (BOXA *)ERROR_PTR("auxstack not made", procName, NULL); stack->auxstack = auxstack; if ((boxa = boxaCreate(0)) == NULL) return (BOXA *)ERROR_PTR("boxa not made", procName, NULL); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pixt1, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pixt1, stack, x, y, connectivity)) == NULL) return (BOXA *)ERROR_PTR("box not made", procName, NULL); boxaAddBox(boxa, box, L_INSERT); /* Save the c.c. and remove from pixt2 as well */ pixt3 = pixClipRectangle(pixt1, box, NULL); pixt4 = pixClipRectangle(pixt2, box, NULL); pixXor(pixt3, pixt3, pixt4); pixRasterop(pixt2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST, pixt3, 0, 0); pixaAddPix(pixa, pixt3, L_INSERT); pixDestroy(&pixt4); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pixt1, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pixt1, IFF_PNG); #endif /* DEBUG */ /* Remove old boxa of pixa and replace with a clone copy */ boxaDestroy(&pixa->boxa); pixa->boxa = boxaCopy(boxa, L_CLONE); /* Cleanup, freeing the fillsegs on each stack */ lstackDestroy(&stack, TRUE); pixDestroy(&pixt1); pixDestroy(&pixt2); return boxa; }
/*! * 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; }
// Adds sub-pixel resolution EdgeOffsets for the outline if the supplied // pix is 8-bit. Does nothing otherwise. // Operation: Consider the following near-horizontal line: // _________ // |________ // |________ // At *every* position along this line, the gradient direction will be close // to vertical. Extrapoaltion/interpolation of the position of the threshold // that was used to binarize the image gives a more precise vertical position // for each horizontal step, and the conflict in step direction and gradient // direction can be used to ignore the vertical steps. void C_OUTLINE::ComputeEdgeOffsets(int threshold, Pix* pix) { if (pixGetDepth(pix) != 8) return; const l_uint32* data = pixGetData(pix); int wpl = pixGetWpl(pix); int width = pixGetWidth(pix); int height = pixGetHeight(pix); bool negative = flag(COUT_INVERSE); delete [] offsets; offsets = new EdgeOffset[stepcount]; ICOORD pos = start; ICOORD prev_gradient; ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &prev_gradient); for (int s = 0; s < stepcount; ++s) { ICOORD step_vec = step(s); TPOINT pt1(pos); pos += step_vec; TPOINT pt2(pos); ICOORD next_gradient; ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &next_gradient); // Use the sum of the prev and next as the working gradient. ICOORD gradient = prev_gradient + next_gradient; // best_diff will be manipulated to be always positive. int best_diff = 0; // offset will be the extrapolation of the location of the greyscale // threshold from the edge with the largest difference, relative to the // location of the binary edge. int offset = 0; if (pt1.y == pt2.y && abs(gradient.y()) * 2 >= abs(gradient.x())) { // Horizontal step. diff_sign == 1 indicates black above. int diff_sign = (pt1.x > pt2.x) == negative ? 1 : -1; int x = MIN(pt1.x, pt2.x); int y = height - pt1.y; int best_sum = 0; int best_y = y; EvaluateVerticalDiff(data, wpl, diff_sign, x, y, height, &best_diff, &best_sum, &best_y); // Find the strongest edge. int test_y = y; do { ++test_y; } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, &best_y)); test_y = y; do { --test_y; } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, &best_y)); offset = diff_sign * (best_sum / 2 - threshold) + (y - best_y) * best_diff; } else if (pt1.x == pt2.x && abs(gradient.x()) * 2 >= abs(gradient.y())) { // Vertical step. diff_sign == 1 indicates black on the left. int diff_sign = (pt1.y > pt2.y) == negative ? 1 : -1; int x = pt1.x; int y = height - MAX(pt1.y, pt2.y); const l_uint32* line = pixGetData(pix) + y * wpl; int best_sum = 0; int best_x = x; EvaluateHorizontalDiff(line, diff_sign, x, width, &best_diff, &best_sum, &best_x); // Find the strongest edge. int test_x = x; do { ++test_x; } while (EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); test_x = x; do { --test_x; } while (EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); offset = diff_sign * (threshold - best_sum / 2) + (best_x - x) * best_diff; } offsets[s].offset_numerator = static_cast<inT8>(ClipToRange(offset, -MAX_INT8, MAX_INT8)); offsets[s].pixel_diff = static_cast<uinT8>(ClipToRange(best_diff, 0 , MAX_UINT8)); if (negative) gradient = -gradient; // Compute gradient angle quantized to 256 directions, rotated by 64 (pi/2) // to convert from gradient direction to edge direction. offsets[s].direction = Modulo(FCOORD::binary_angle_plus_pi(gradient.angle()) + 64, 256); prev_gradient = next_gradient; } }
int main(int argc, char **argv) { l_int32 i, j, sindex, wb, hb, ws, hs, delx, dely, x, y, y0; PIX *pixs, *pixb, *pix1, *pix2; PIXA *pixa; PIXCMAP *cmap; setLeptDebugOK(1); lept_mkdir("lept/blend"); pixa = pixaCreate(0); pixs = pixRead("rabi.png"); /* blendee */ pixb = pixRead("weasel4.11c.png"); /* blender */ /* Fade the blender */ pixcmapShiftIntensity(pixGetColormap(pixb), FADE_FRACTION); /* Downscale the input */ wb = pixGetWidth(pixb); hb = pixGetHeight(pixb); pix1 = pixScaleToGray4(pixs); /* Threshold to 5 levels, 4 bpp */ ws = pixGetWidth(pix1); hs = pixGetHeight(pix1); pix2 = pixThresholdTo4bpp(pix1, 5, 1); pixaAddPix(pixa, pix2, L_COPY); pixaAddPix(pixa, pixb, L_COPY); cmap = pixGetColormap(pix2); pixcmapWriteStream(stderr, cmap); /* Overwrite the white pixels (at sindex in pix2) */ pixcmapGetIndex(cmap, 255, 255, 255, &sindex); /* Blend the weasel 20 times */ delx = ws / NX; dely = hs / NY; for (i = 0; i < NY; i++) { y = 20 + i * dely; if (y >= hs + hb) continue; for (j = 0; j < NX; j++) { x = 30 + j * delx; y0 = y; if (j & 1) { y0 = y + dely / 2; if (y0 >= hs + hb) continue; } if (x >= ws + wb) continue; pixBlendCmap(pix2, pixb, x, y0, sindex); } } pixaAddPix(pixa, pix2, L_COPY); cmap = pixGetColormap(pix2); pixcmapWriteStream(stderr, cmap); fprintf(stderr, "Writing to: /tmp/lept/blend/blendcmap.pdf\n"); pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, "cmap-blendtest", "/tmp/lept/blend/blendcmap.pdf"); pixDestroy(&pixs); pixDestroy(&pixb); pixDestroy(&pix1); pixDestroy(&pix2); pixaDestroy(&pixa); return 0; }
/*! * pixGenerateSelWithRuns() * * Input: pix (1 bpp, typically small, to be used as a pattern) * nhlines (number of hor lines along which elements are found) * nvlines (number of vert lines along which elements are found) * distance (min distance from boundary pixel; use 0 for default) * minlength (min runlength to set hit or miss; use 0 for default) * toppix (number of extra pixels of bg added above) * botpix (number of extra pixels of bg added below) * leftpix (number of extra pixels of bg added to left) * rightpix (number of extra pixels of bg added to right) * &pixe (<optional return> input pix expanded by extra pixels) * Return: sel (hit-miss for input pattern), or null on error * * Notes: * (1) The horizontal and vertical lines along which elements are * selected are roughly equally spaced. The actual locations of * the hits and misses are the centers of respective run-lengths. * (2) No elements are selected that are less than 'distance' pixels away * from a boundary pixel of the same color. This makes the * match much more robust to edge noise. Valid inputs of * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or * greater than 4, we reset it to the default value. * (3) The 4 numbers for adding rectangles of pixels outside the fg * can be use if the pattern is expected to be surrounded by bg * (white) pixels. On the other hand, if the pattern may be near * other fg (black) components on some sides, use 0 for those sides. * (4) The pixels added to a side allow you to have miss elements there. * There is a constraint between distance, minlength, and * the added pixels for this to work. We illustrate using the * default values. If you add 5 pixels to the top, and use a * distance of 1, then you end up with a vertical run of at least * 4 bg pixels along the top edge of the image. If you use a * minimum runlength of 3, each vertical line will always find * a miss near the center of its run. However, if you use a * minimum runlength of 5, you will not get a miss on every vertical * line. As another example, if you have 7 added pixels and a * distance of 2, you can use a runlength up to 5 to guarantee * that the miss element is recorded. We give a warning if the * contraint does not guarantee a miss element outside the * image proper. * (5) The input pix, as extended by the extra pixels on selected sides, * can optionally be returned. For debugging, call * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed * on the generating bitmap. */ SEL * pixGenerateSelWithRuns(PIX *pixs, l_int32 nhlines, l_int32 nvlines, l_int32 distance, l_int32 minlength, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, xval, yval, i, j, nh, nm; l_float32 delh, delw; NUMA *nah, *nam; PIX *pixt1, *pixt2, *pixfg, *pixbg; PTA *ptah, *ptam; SEL *seld, *sel; PROCNAME("pixGenerateSelWithRuns"); if (ppixe) *ppixe = NULL; if (!pixs) return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (nhlines < 1 && nvlines < 1) return (SEL *)ERROR_PTR("nvlines and nhlines both < 1", procName, NULL); if (distance <= 0) distance = DEFAULT_DISTANCE_TO_BOUNDARY; if (minlength <= 0) minlength = DEFAULT_MIN_RUNLENGTH; if (distance > MAX_DISTANCE_TO_BOUNDARY) { L_WARNING("distance too large; setting to max value", procName); distance = MAX_DISTANCE_TO_BOUNDARY; } /* Locate the foreground */ pixClipToForeground(pixs, &pixt1, NULL); if (!pixt1) return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL); ws = pixGetWidth(pixt1); hs = pixGetHeight(pixt1); w = ws; h = hs; /* Crop out a region including the foreground, and add pixels * on sides depending on the side flags */ if (toppix || botpix || leftpix || rightpix) { x = y = 0; if (toppix) { h += toppix; y = toppix; if (toppix < distance + minlength) L_WARNING("no miss elements in added top pixels", procName); } if (botpix) { h += botpix; if (botpix < distance + minlength) L_WARNING("no miss elements in added bot pixels", procName); } if (leftpix) { w += leftpix; x = leftpix; if (leftpix < distance + minlength) L_WARNING("no miss elements in added left pixels", procName); } if (rightpix) { w += rightpix; if (rightpix < distance + minlength) L_WARNING("no miss elements in added right pixels", procName); } pixt2 = pixCreate(w, h, 1); pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0); } else pixt2 = pixClone(pixt1); if (ppixe) *ppixe = pixClone(pixt2); pixDestroy(&pixt1); /* Identify fg and bg pixels that are at least 'distance' pixels * away from the boundary pixels in their set */ seld = selCreateBrick(2 * distance + 1, 2 * distance + 1, distance, distance, SEL_HIT); pixfg = pixErode(NULL, pixt2, seld); pixbg = pixDilate(NULL, pixt2, seld); pixInvert(pixbg, pixbg); selDestroy(&seld); pixDestroy(&pixt2); /* Accumulate hit and miss points */ ptah = ptaCreate(0); ptam = ptaCreate(0); if (nhlines >= 1) { delh = (l_float32)h / (l_float32)(nhlines + 1); for (i = 0, y = 0; i < nhlines; i++) { y += (l_int32)(delh + 0.5); nah = pixGetRunCentersOnLine(pixfg, -1, y, minlength); nam = pixGetRunCentersOnLine(pixbg, -1, y, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &xval); ptaAddPt(ptah, xval, y); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &xval); ptaAddPt(ptam, xval, y); } numaDestroy(&nah); numaDestroy(&nam); } } if (nvlines >= 1) { delw = (l_float32)w / (l_float32)(nvlines + 1); for (i = 0, x = 0; i < nvlines; i++) { x += (l_int32)(delw + 0.5); nah = pixGetRunCentersOnLine(pixfg, x, -1, minlength); nam = pixGetRunCentersOnLine(pixbg, x, -1, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &yval); ptaAddPt(ptah, x, yval); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &yval); ptaAddPt(ptam, x, yval); } numaDestroy(&nah); numaDestroy(&nam); } } /* Make the Sel with those points */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); nh = ptaGetCount(ptah); for (i = 0; i < nh; i++) { ptaGetIPt(ptah, i, &x, &y); selSetElement(sel, y, x, SEL_HIT); } nm = ptaGetCount(ptam); for (i = 0; i < nm; i++) { ptaGetIPt(ptam, i, &x, &y); selSetElement(sel, y, x, SEL_MISS); } pixDestroy(&pixfg); pixDestroy(&pixbg); ptaDestroy(&ptah); ptaDestroy(&ptam); return sel; }
/** * Segment the page according to the current value of tessedit_pageseg_mode. * pix_binary_ is used as the source image and should not be NULL. * On return the blocks list owns all the constructed page layout. */ int Tesseract::SegmentPage(const STRING* input_file, BLOCK_LIST* blocks, Tesseract* osd_tess, OSResults* osr) { ASSERT_HOST(pix_binary_ != NULL); int width = pixGetWidth(pix_binary_); int height = pixGetHeight(pix_binary_); // Get page segmentation mode. PageSegMode pageseg_mode = static_cast<PageSegMode>( static_cast<int>(tessedit_pageseg_mode)); // If a UNLV zone file can be found, use that instead of segmentation. if (!PSM_COL_FIND_ENABLED(pageseg_mode) && input_file != NULL && input_file->length() > 0) { STRING name = *input_file; const char* lastdot = strrchr(name.string(), '.'); if (lastdot != NULL) name[lastdot - name.string()] = '\0'; read_unlv_file(name, width, height, blocks); } if (blocks->empty()) { // No UNLV file present. Work according to the PageSegMode. // First make a single block covering the whole image. BLOCK_IT block_it(blocks); BLOCK* block = new BLOCK("", TRUE, 0, 0, 0, 0, width, height); block->set_right_to_left(right_to_left()); block_it.add_to_end(block); } else { // UNLV file present. Use PSM_SINGLE_BLOCK. pageseg_mode = PSM_SINGLE_BLOCK; } // The diacritic_blobs holds noise blobs that may be diacritics. They // are separated out on areas of the image that seem noisy and short-circuit // the layout process, going straight from the initial partition creation // right through to after word segmentation, where they are added to the // rej_cblobs list of the most appropriate word. From there classification // will determine whether they are used. BLOBNBOX_LIST diacritic_blobs; int auto_page_seg_ret_val = 0; TO_BLOCK_LIST to_blocks; if (PSM_OSD_ENABLED(pageseg_mode) || PSM_BLOCK_FIND_ENABLED(pageseg_mode) || PSM_SPARSE(pageseg_mode)) { auto_page_seg_ret_val = AutoPageSeg( pageseg_mode, blocks, &to_blocks, enable_noise_removal ? &diacritic_blobs : NULL, osd_tess, osr); if (pageseg_mode == PSM_OSD_ONLY) return auto_page_seg_ret_val; // To create blobs from the image region bounds uncomment this line: // to_blocks.clear(); // Uncomment to go back to the old mode. } else { deskew_ = FCOORD(1.0f, 0.0f); reskew_ = FCOORD(1.0f, 0.0f); if (pageseg_mode == PSM_CIRCLE_WORD) { Pix* pixcleaned = RemoveEnclosingCircle(pix_binary_); if (pixcleaned != NULL) { pixDestroy(&pix_binary_); pix_binary_ = pixcleaned; } } } if (auto_page_seg_ret_val < 0) { return -1; } if (blocks->empty()) { if (textord_debug_tabfind) tprintf("Empty page\n"); return 0; // AutoPageSeg found an empty page. } bool splitting = pageseg_devanagari_split_strategy != ShiroRekhaSplitter::NO_SPLIT; bool cjk_mode = textord_use_cjk_fp_model; textord_.TextordPage(pageseg_mode, reskew_, width, height, pix_binary_, pix_thresholds_, pix_grey_, splitting || cjk_mode, &diacritic_blobs, blocks, &to_blocks); return auto_page_seg_ret_val; }
/********************************************************************** * SetBlobStrokeWidth * * Set the horizontal and vertical stroke widths in the blob. **********************************************************************/ void SetBlobStrokeWidth(Pix* pix, BLOBNBOX* blob) { // Cut the blob rectangle into a Pix. int pix_height = pixGetHeight(pix); const TBOX& box = blob->bounding_box(); int width = box.width(); int height = box.height(); Box* blob_pix_box = boxCreate(box.left(), pix_height - box.top(), width, height); Pix* pix_blob = pixClipRectangle(pix, blob_pix_box, nullptr); boxDestroy(&blob_pix_box); Pix* dist_pix = pixDistanceFunction(pix_blob, 4, 8, L_BOUNDARY_BG); pixDestroy(&pix_blob); // Compute the stroke widths. uint32_t* data = pixGetData(dist_pix); int wpl = pixGetWpl(dist_pix); // Horizontal width of stroke. STATS h_stats(0, width + 1); for (int y = 0; y < height; ++y) { uint32_t* pixels = data + y*wpl; int prev_pixel = 0; int pixel = GET_DATA_BYTE(pixels, 0); for (int x = 1; x < width; ++x) { int next_pixel = GET_DATA_BYTE(pixels, x); // We are looking for a pixel that is equal to its vertical neighbours, // yet greater than its left neighbour. if (prev_pixel < pixel && (y == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) && (y == height - 1 || pixel == GET_DATA_BYTE(pixels + wpl, x - 1))) { if (pixel > next_pixel) { // Single local max, so an odd width. h_stats.add(pixel * 2 - 1, 1); } else if (pixel == next_pixel && x + 1 < width && pixel > GET_DATA_BYTE(pixels, x + 1)) { // Double local max, so an even width. h_stats.add(pixel * 2, 1); } } prev_pixel = pixel; pixel = next_pixel; } } // Vertical width of stroke. STATS v_stats(0, height + 1); for (int x = 0; x < width; ++x) { int prev_pixel = 0; int pixel = GET_DATA_BYTE(data, x); for (int y = 1; y < height; ++y) { uint32_t* pixels = data + y*wpl; int next_pixel = GET_DATA_BYTE(pixels, x); // We are looking for a pixel that is equal to its horizontal neighbours, // yet greater than its upper neighbour. if (prev_pixel < pixel && (x == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) && (x == width - 1 || pixel == GET_DATA_BYTE(pixels - wpl, x + 1))) { if (pixel > next_pixel) { // Single local max, so an odd width. v_stats.add(pixel * 2 - 1, 1); } else if (pixel == next_pixel && y + 1 < height && pixel > GET_DATA_BYTE(pixels + wpl, x)) { // Double local max, so an even width. v_stats.add(pixel * 2, 1); } } prev_pixel = pixel; pixel = next_pixel; } } pixDestroy(&dist_pix); // Store the horizontal and vertical width in the blob, keeping both // widths if there is enough information, otherwse only the one with // the most samples. // If there are insufficient samples, store zero, rather than using // 2*area/perimeter, as the numbers that gives do not match the numbers // from the distance method. if (h_stats.get_total() >= (width + height) / 4) { blob->set_horz_stroke_width(h_stats.ile(0.5f)); if (v_stats.get_total() >= (width + height) / 4) blob->set_vert_stroke_width(v_stats.ile(0.5f)); else blob->set_vert_stroke_width(0.0f); } else { if (v_stats.get_total() >= (width + height) / 4 || v_stats.get_total() > h_stats.get_total()) { blob->set_horz_stroke_width(0.0f); blob->set_vert_stroke_width(v_stats.ile(0.5f)); } else { blob->set_horz_stroke_width(h_stats.get_total() > 2 ? h_stats.ile(0.5f) : 0.0f); blob->set_vert_stroke_width(0.0f); } } }
/*! * pixaGenerateFont() * * Input: pix (of 95 characters in 3 rows) * fontsize (4, 6, 8, ... , 20, in pts at 300 ppi) * &bl1 (<return> baseline of row 1) * &bl2 (<return> baseline of row 2) * &bl3 (<return> baseline of row 3) * Return: pixa of font bitmaps for 95 characters, or null on error * * Notes: * (1) This does all the work. See pixaGenerateFontFromFile() * for an overview. * (2) The pix is for one of the 9 fonts. @fontsize is only * used here for debugging. */ PIXA * pixaGenerateFont(PIX *pixs, l_int32 fontsize, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2) { l_int32 i, j, nrows, nrowchars, nchars, h, yval; l_int32 width, height; l_int32 baseline[3]; l_int32 *tab = NULL; BOX *box, *box1, *box2; BOXA *boxar, *boxac, *boxacs; PIX *pix1, *pix2, *pixr, *pixrc, *pixc; PIXA *pixa; l_int32 n, w, inrow, top; l_int32 *ia; NUMA *na; PROCNAME("pixaGenerateFont"); if (!pbl0 || !pbl1 || !pbl2) return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL); *pbl0 = *pbl1 = *pbl2 = 0; if (!pixs) return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL); /* Locate the 3 rows of characters */ w = pixGetWidth(pixs); na = pixCountPixelsByRow(pixs, NULL); boxar = boxaCreate(0); n = numaGetCount(na); ia = numaGetIArray(na); inrow = 0; for (i = 0; i < n; i++) { if (!inrow && ia[i] > 0) { inrow = 1; top = i; } else if (inrow && ia[i] == 0) { inrow = 0; box = boxCreate(0, top, w, i - top); boxaAddBox(boxar, box, L_INSERT); } } FREE(ia); numaDestroy(&na); nrows = boxaGetCount(boxar); #if DEBUG_FONT_GEN L_INFO("For fontsize %s, have %d rows\n", procName, fontsize, nrows); #endif /* DEBUG_FONT_GEN */ if (nrows != 3) { L_INFO("nrows = %d; skipping fontsize %d\n", procName, nrows, fontsize); return (PIXA *)ERROR_PTR("3 rows not generated", procName, NULL); } /* Grab the character images and baseline data */ #if DEBUG_BASELINE lept_rmdir("baseline"); lept_mkdir("baseline"); #endif /* DEBUG_BASELINE */ tab = makePixelSumTab8(); pixa = pixaCreate(95); for (i = 0; i < nrows; i++) { box = boxaGetBox(boxar, i, L_CLONE); pixr = pixClipRectangle(pixs, box, NULL); /* row of chars */ pixGetTextBaseline(pixr, tab, &yval); baseline[i] = yval; #if DEBUG_BASELINE L_INFO("Baseline info: row %d, yval = %d, h = %d\n", procName, i, yval, pixGetHeight(pixr)); pix1 = pixCopy(NULL, pixr); pixRenderLine(pix1, 0, yval, pixGetWidth(pix1), yval, 1, L_FLIP_PIXELS); if (i == 0 ) pixWrite("/tmp/baseline/row0.png", pix1, IFF_PNG); else if (i == 1) pixWrite("/tmp/baseline/row1.png", pix1, IFF_PNG); else pixWrite("/tmp/baseline/row2.png", pix1, IFF_PNG); pixDestroy(&pix1); #endif /* DEBUG_BASELINE */ boxDestroy(&box); pixrc = pixCloseSafeBrick(NULL, pixr, 1, 35); boxac = pixConnComp(pixrc, NULL, 8); boxacs = boxaSort(boxac, L_SORT_BY_X, L_SORT_INCREASING, NULL); if (i == 0) { /* consolidate the two components of '"' */ box1 = boxaGetBox(boxacs, 1, L_CLONE); box2 = boxaGetBox(boxacs, 2, L_CLONE); box1->w = box2->x + box2->w - box1->x; /* increase width */ boxDestroy(&box1); boxDestroy(&box2); boxaRemoveBox(boxacs, 2); } h = pixGetHeight(pixr); nrowchars = boxaGetCount(boxacs); for (j = 0; j < nrowchars; j++) { box = boxaGetBox(boxacs, j, L_COPY); if (box->w <= 2 && box->h == 1) { /* skip 1x1, 2x1 components */ boxDestroy(&box); continue; } box->y = 0; box->h = h - 1; pixc = pixClipRectangle(pixr, box, NULL); boxDestroy(&box); if (i == 0 && j == 0) /* add a pix for the space; change later */ pixaAddPix(pixa, pixc, L_COPY); if (i == 2 && j == 0) /* add a pix for the '\'; change later */ pixaAddPix(pixa, pixc, L_COPY); pixaAddPix(pixa, pixc, L_INSERT); } pixDestroy(&pixr); pixDestroy(&pixrc); boxaDestroy(&boxac); boxaDestroy(&boxacs); } FREE(tab); nchars = pixaGetCount(pixa); if (nchars != 95) return (PIXA *)ERROR_PTR("95 chars not generated", procName, NULL); *pbl0 = baseline[0]; *pbl1 = baseline[1]; *pbl2 = baseline[2]; /* Fix the space character up; it should have no ON pixels, * and be about twice as wide as the '!' character. */ pix1 = pixaGetPix(pixa, 0, L_CLONE); width = 2 * pixGetWidth(pix1); height = pixGetHeight(pix1); pixDestroy(&pix1); pix1 = pixCreate(width, height, 1); pixaReplacePix(pixa, 0, pix1, NULL); /* Fix up the '\' character; use a LR flip of the '/' char */ pix1 = pixaGetPix(pixa, 15, L_CLONE); pix2 = pixFlipLR(NULL, pix1); pixDestroy(&pix1); pixaReplacePix(pixa, 60, pix2, NULL); #if DEBUG_CHARS pix1 = pixaDisplayTiled(pixa, 1500, 0, 10); pixDisplay(pix1, 100 * i, 200); pixDestroy(&pix1); #endif /* DEBUG_CHARS */ boxaDestroy(&boxar); return pixa; }
/*! * bmfMakeAsciiTables * * Input: bmf * Return: 0 if OK, 1 on error * * Notes: * (1) This makes three tables, each of size 128, as follows: * - fonttab is a table containing the index of the Pix * that corresponds to each input ascii character; * it maps (ascii-index) --> Pixa index * - baselinetab is a table containing the baseline offset * for the Pix that corresponds to each input ascii character; * it maps (ascii-index) --> baseline offset * - widthtab is a table containing the character width in * pixels for the Pix that corresponds to that character; * it maps (ascii-index) --> bitmap width * (2) This also computes * - lineheight (sum of maximum character extensions above and * below the baseline) * - kernwidth (spacing between characters within a word) * - spacewidth (space between words) * - vertlinesep (extra vertical spacing between textlines) * (3) The baselines apply as follows: * baseline1 (ascii 32 - 57), ascii 92 * baseline2 (ascii 58 - 91) * baseline3 (ascii 93 - 126) * (4) The only array in bmf that is not ascii-based is the * array of bitmaps in the pixa, which starts at ascii 32. */ static l_int32 bmfMakeAsciiTables(L_BMF *bmf) { l_int32 i, maxh, height, charwidth, xwidth, kernwidth; l_int32 *fonttab, *baselinetab, *widthtab; PIX *pix; PROCNAME("bmfMakeAsciiTables"); if (!bmf) return ERROR_INT("bmf not defined", procName, 1); /* First get the fonttab; we use this later for the char widths */ if ((fonttab = (l_int32 *)CALLOC(128, sizeof(l_int32))) == NULL) return ERROR_INT("fonttab not made", procName, 1); bmf->fonttab = fonttab; for (i = 0; i < 128; i++) fonttab[i] = UNDEF; for (i = 32; i < 127; i++) fonttab[i] = i - 32; if ((baselinetab = (l_int32 *)CALLOC(128, sizeof(l_int32))) == NULL) return ERROR_INT("baselinetab not made", procName, 1); bmf->baselinetab = baselinetab; for (i = 0; i < 128; i++) baselinetab[i] = UNDEF; for (i = 32; i <= 57; i++) baselinetab[i] = bmf->baseline1; for (i = 58; i <= 91; i++) baselinetab[i] = bmf->baseline2; baselinetab[92] = bmf->baseline1; /* the '\' char */ for (i = 93; i < 127; i++) baselinetab[i] = bmf->baseline3; /* Generate array of character widths; req's fonttab to exist */ if ((widthtab = (l_int32 *)CALLOC(128, sizeof(l_int32))) == NULL) return ERROR_INT("widthtab not made", procName, 1); bmf->widthtab = widthtab; for (i = 0; i < 128; i++) widthtab[i] = UNDEF; for (i = 32; i < 127; i++) { bmfGetWidth(bmf, i, &charwidth); widthtab[i] = charwidth; } /* Get the line height of text characters, from the highest * ascender to the lowest descender; req's fonttab to exist. */ pix = bmfGetPix(bmf, 32); maxh = pixGetHeight(pix); pixDestroy(&pix); pix = bmfGetPix(bmf, 58); height = pixGetHeight(pix); pixDestroy(&pix); maxh = L_MAX(maxh, height); pix = bmfGetPix(bmf, 93); height = pixGetHeight(pix); pixDestroy(&pix); maxh = L_MAX(maxh, height); bmf->lineheight = maxh; /* Get the kern width (distance between characters). * We let it be the same for all characters in a given * font size, and scale it linearly with the size; * req's fonttab to be built first. */ bmfGetWidth(bmf, 120, &xwidth); kernwidth = (l_int32)(0.08 * (l_float32)xwidth + 0.5); bmf->kernwidth = L_MAX(1, kernwidth); /* Save the space width (between words) */ bmfGetWidth(bmf, 32, &charwidth); bmf->spacewidth = charwidth; /* Save the extra vertical space between lines */ bmf->vertlinesep = (l_int32)(VERT_FRACT_SEP * bmf->lineheight + 0.5); return 0; }
/*! * pixGetLocalSkewAngles() * * Input: pixs * nslices (the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; use 0 for default) * redsweep (sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value) * redsearch (search reduction factor: 1, 2, 4 or 8, and * not larger than redsweep; use 0 for default value) * sweeprange (half the full range, assumed about 0; in degrees; * use 0.0 for default value) * sweepdelta (angle increment of sweep; in degrees; * use 0.0 for default value) * minbsdelta (min binary search increment angle; in degrees; * use 0.0 for default value) * &a (<optional return> slope of skew as fctn of y) * &b (<optional return> intercept at y=0 of skew as fctn of y) * Return: naskew, or null on error * * Notes: * (1) The local skew is measured in a set of overlapping strips. * We then do a least square linear fit parameters to get * the slope and intercept parameters a and b in * skew-angle = a * y + b (degrees) * for the local skew as a function of raster line y. * This is then used to make naskew, which can be interpreted * as the computed skew angle (in degrees) at the left edge * of each raster line. * (2) naskew can then be used to find the baselines of text, because * each text line has a baseline that should intersect * the left edge of the image with the angle given by this * array, evaluated at the raster line of intersection. */ NUMA * pixGetLocalSkewAngles(PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_float32 *pa, l_float32 *pb) { l_int32 w, h, hs, i, ystart, yend, ovlap, npts; l_float32 angle, conf, ycenter, a, b; BOX *box; NUMA *naskew; PIX *pix; PTA *pta; PROCNAME("pixGetLocalSkewAngles"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (nslices < 2 || nslices > 20) nslices = DEFAULT_SLICES; if (redsweep < 1 || redsweep > 8) redsweep = DEFAULT_SWEEP_REDUCTION; if (redsearch < 1 || redsearch > redsweep) redsearch = DEFAULT_BS_REDUCTION; if (sweeprange == 0.0) sweeprange = DEFAULT_SWEEP_RANGE; if (sweepdelta == 0.0) sweepdelta = DEFAULT_SWEEP_DELTA; if (minbsdelta == 0.0) minbsdelta = DEFAULT_MINBS_DELTA; h = pixGetHeight(pixs); w = pixGetWidth(pixs); hs = h / nslices; ovlap = (l_int32)(OVERLAP_FRACTION * hs); pta = ptaCreate(nslices); for (i = 0; i < nslices; i++) { ystart = L_MAX(0, hs * i - ovlap); yend = L_MIN(h - 1, hs * (i + 1) + ovlap); ycenter = (ystart + yend) / 2; box = boxCreate(0, ystart, w, yend - ystart + 1); pix = pixClipRectangle(pixs, box, NULL); pixFindSkewSweepAndSearch(pix, &angle, &conf, redsweep, redsearch, sweeprange, sweepdelta, minbsdelta); if (conf > MIN_ALLOWED_CONFIDENCE) ptaAddPt(pta, ycenter, angle); pixDestroy(&pix); boxDestroy(&box); } /* ptaWriteStream(stderr, pta, 0); */ /* Do linear least squares fit */ if ((npts = ptaGetCount(pta)) < 2) { ptaDestroy(&pta); return (NUMA *)ERROR_PTR("can't fit skew", procName, NULL); } ptaGetLinearLSF(pta, &a, &b, NULL); if (pa) *pa = a; if (pb) *pb = b; /* Make skew angle array as function of raster line */ naskew = numaCreate(h); for (i = 0; i < h; i++) { angle = a * i + b; numaAddNumber(naskew, angle); } #if DEBUG_PLOT { NUMA *nax, *nay; GPLOT *gplot; ptaGetArrays(pta, &nax, &nay); gplot = gplotCreate("/tmp/lept/baseline/kew", GPLOT_PNG, "skew as fctn of y", "y (in raster lines from top)", "angle (in degrees)"); gplotAddPlot(gplot, NULL, naskew, GPLOT_POINTS, "linear lsf"); gplotAddPlot(gplot, nax, nay, GPLOT_POINTS, "actual data pts"); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&nax); numaDestroy(&nay); } #endif /* DEBUG_PLOT */ ptaDestroy(&pta); return naskew; }
/*! * pixGetLocalSkewTransform() * * Input: pixs * nslices (the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; use 0 for default) * redsweep (sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value) * redsearch (search reduction factor: 1, 2, 4 or 8, and * not larger than redsweep; use 0 for default value) * sweeprange (half the full range, assumed about 0; in degrees; * use 0.0 for default value) * sweepdelta (angle increment of sweep; in degrees; * use 0.0 for default value) * minbsdelta (min binary search increment angle; in degrees; * use 0.0 for default value) * &ptas (<return> 4 points in the source) * &ptad (<return> the corresponding 4 pts in the dest) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates two pairs of points in the src, each pair * corresponding to a pair of points that would lie along * the same raster line in a transformed (dewarped) image. * (2) The sets of 4 src and 4 dest points returned by this function * can then be used, in a projective or bilinear transform, * to remove keystoning in the src. */ l_int32 pixGetLocalSkewTransform(PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, PTA **pptas, PTA **pptad) { l_int32 w, h, i; l_float32 deg2rad, angr, angd, dely; NUMA *naskew; PTA *ptas, *ptad; PROCNAME("pixGetLocalSkewTransform"); if (!pptas || !pptad) return ERROR_INT("&ptas and &ptad not defined", procName, 1); *pptas = *pptad = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (nslices < 2 || nslices > 20) nslices = DEFAULT_SLICES; if (redsweep < 1 || redsweep > 8) redsweep = DEFAULT_SWEEP_REDUCTION; if (redsearch < 1 || redsearch > redsweep) redsearch = DEFAULT_BS_REDUCTION; if (sweeprange == 0.0) sweeprange = DEFAULT_SWEEP_RANGE; if (sweepdelta == 0.0) sweepdelta = DEFAULT_SWEEP_DELTA; if (minbsdelta == 0.0) minbsdelta = DEFAULT_MINBS_DELTA; naskew = pixGetLocalSkewAngles(pixs, nslices, redsweep, redsearch, sweeprange, sweepdelta, minbsdelta, NULL, NULL); if (!naskew) return ERROR_INT("naskew not made", procName, 1); deg2rad = 3.14159265 / 180.; w = pixGetWidth(pixs); h = pixGetHeight(pixs); ptas = ptaCreate(4); ptad = ptaCreate(4); *pptas = ptas; *pptad = ptad; /* Find i for skew line that intersects LHS at i and RHS at h / 20 */ for (i = 0; i < h; i++) { numaGetFValue(naskew, i, &angd); angr = angd * deg2rad; dely = w * tan(angr); if (i - dely > 0.05 * h) break; } ptaAddPt(ptas, 0, i); ptaAddPt(ptas, w - 1, i - dely); ptaAddPt(ptad, 0, i); ptaAddPt(ptad, w - 1, i); /* Find i for skew line that intersects LHS at i and RHS at 19h / 20 */ for (i = h - 1; i > 0; i--) { numaGetFValue(naskew, i, &angd); angr = angd * deg2rad; dely = w * tan(angr); if (i - dely < 0.95 * h) break; } ptaAddPt(ptas, 0, i); ptaAddPt(ptas, w - 1, i - dely); ptaAddPt(ptad, 0, i); ptaAddPt(ptad, w - 1, i); numaDestroy(&naskew); return 0; }
/*! * pixFindBaselines() * * Input: pixs (1 bpp) * &pta (<optional return> pairs of pts corresponding to * approx. ends of each text line) * debug (usually 0; set to 1 for debugging output) * Return: na (of baseline y values), or null on error * * Notes: * (1) Input binary image must have text lines already aligned * horizontally. This can be done by either rotating the * image with pixDeskew(), or, if a projective transform * is required, by doing pixDeskewLocal() first. * (2) Input null for &pta if you don't want this returned. * The pta will come in pairs of points (left and right end * of each baseline). * (3) Caution: this will not work properly on text with multiple * columns, where the lines are not aligned between columns. * If there are multiple columns, they should be extracted * separately before finding the baselines. * (4) This function constructs different types of output * for baselines; namely, a set of raster line values and * a set of end points of each baseline. * (5) This function was designed to handle short and long text lines * without using dangerous thresholds on the peak heights. It does * this by combining the differential signal with a morphological * analysis of the locations of the text lines. One can also * combine this data to normalize the peak heights, by weighting * the differential signal in the region of each baseline * by the inverse of the width of the text line found there. * (6) There are various debug sections that can be turned on * with the debug flag. */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, l_int32 debug) { l_int32 h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh; l_int32 imaxloc, peakthresh, zerothresh, inpeak; l_int32 mintosearch, max, maxloc, nloc, locval; l_int32 *array; l_float32 maxval; BOXA *boxa1, *boxa2, *boxa3; GPLOT *gplot; NUMA *nasum, *nadiff, *naloc, *naval; PIX *pixt1, *pixt2; PTA *pta; PROCNAME("pixFindBaselines"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } lept_mkdir("/lept/baseline"); /* Close up the text characters, removing noise */ pixt1 = pixMorphSequence(pixs, "c25.1 + e3.1", 0); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pixt1, NULL)) == NULL) return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); h = pixGetHeight(pixs); nadiff = numaCreate(h); numaGetIValue(nasum, 0, &val2); for (i = 0; i < h - 1; i++) { val1 = val2; numaGetIValue(nasum, i + 1, &val2); numaAddNumber(nadiff, val1 - val2); } if (debug) /* show the difference signal */ gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig"); /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); /* Use this to begin locating a new peak: */ peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO; /* Use this to begin a region between peaks: */ zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO; naloc = numaCreate(0); naval = numaCreate(0); inpeak = FALSE; for (i = 0; i < ndiff; i++) { if (inpeak == FALSE) { if (array[i] > peakthresh) { /* transition to in-peak */ inpeak = TRUE; mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros * between i and mintosearch */ max = array[i]; maxloc = i; } } else { /* inpeak == TRUE; look for max */ if (array[i] > max) { max = array[i]; maxloc = i; mintosearch = i + MIN_DIST_IN_PEAK; } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */ inpeak = FALSE; numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } } } /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } LEPT_FREE(array); if (debug) { /* show the raster locations for the peaks */ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } /* Generate an approximate profile of text line width. * First, filter the boxes of text, where there may be * more than one box for a given textline. */ pixt2 = pixMorphSequence(pixt1, "r11 + c25.1 + o7.1 +c1.3", 0); boxa1 = pixConnComp(pixt2, NULL, 4); boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); /* Then find the baseline segments */ if (pta) { nloc = numaGetCount(naloc); nbox = boxaGetCount(boxa3); for (i = 0; i < nbox; i++) { boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh); for (j = 0; j < nloc; j++) { numaGetIValue(naloc, j, &locval); if (L_ABS(locval - (by + bh)) > 25) continue; ptaAddPt(pta, bx, locval); ptaAddPt(pta, bx + bw, locval); break; } } } if (debug) { /* display baselines */ PIX *pixd; l_int32 npts, x1, y1, x2, y2; if (pta) { pixd = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pixd, x1, y1, x2, y2, 1, 255, 0, 0); } pixDisplay(pixd, 200, 200); pixWrite("/tmp/lept/baseline/baselines.png", pixd, IFF_PNG); pixDestroy(&pixd); } } boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); pixDestroy(&pixt1); pixDestroy(&pixt2); numaDestroy(&nasum); numaDestroy(&nadiff); numaDestroy(&naval); return naloc; }
main(int argc, char **argv) { char *filein, *fileout; l_int32 w, h; l_float32 scale; FILE *fp; PIX *pix, *pixs, *pixd; PIXCMAP *cmap; static char mainName[] = "dithertest"; if (argc != 3) exit(ERROR_INT(" Syntax: dithertest filein fileout", mainName, 1)); filein = argv[1]; fileout = argv[2]; if ((pix = pixRead(filein)) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); if (pixGetDepth(pix) != 8) exit(ERROR_INT("pix not 8 bpp", mainName, 1)); pixs = pixGammaTRC(NULL, pix, GAMMA, 0, 255); startTimer(); pixd = pixDitherToBinary(pixs); fprintf(stderr, " time for binarized dither = %7.3f sec\n", stopTimer()); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* Dither to 2 bpp, with colormap */ startTimer(); pixd = pixDitherTo2bpp(pixs, 1); fprintf(stderr, " time for dither = %7.3f sec\n", stopTimer()); pixDisplayWrite(pixd, 1); cmap = pixGetColormap(pixd); pixcmapWriteStream(stderr, cmap); pixDestroy(&pixd); /* Dither to 2 bpp, without colormap */ startTimer(); pixd = pixDitherTo2bpp(pixs, 0); fprintf(stderr, " time for dither = %7.3f sec\n", stopTimer()); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* Dither to 2 bpp, without colormap; output in PostScript */ pixd = pixDitherTo2bpp(pixs, 0); w = pixGetWidth(pixs); h = pixGetHeight(pixs); scale = L_MIN(FACTOR * 2550 / w, FACTOR * 3300 / h); fp = lept_fopen(fileout, "wb+"); pixWriteStreamPS(fp, pixd, NULL, 300, scale); lept_fclose(fp); pixDestroy(&pixd); /* Dither 2x upscale to 1 bpp */ startTimer(); pixd = pixScaleGray2xLIDither(pixs); fprintf(stderr, " time for scale/dither = %7.3f sec\n", stopTimer()); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* Dither 4x upscale to 1 bpp */ startTimer(); pixd = pixScaleGray4xLIDither(pixs); fprintf(stderr, " time for scale/dither = %7.3f sec\n", stopTimer()); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); pixDisplayMultiple("/tmp/junk_write_display*"); pixDestroy(&pix); pixDestroy(&pixs); return 0; }
int main(int argc, char **argv) { char *filein; l_int32 i, n, count; BOX *box; BOXA *boxa; PIX *pixs, *pixd; PIXA *pixa; PIXCMAP *cmap; static char mainName[] = "cctest1"; if (argc != 2) return ERROR_INT(" Syntax: cctest1 filein", mainName, 1); filein = argv[1]; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); if (pixGetDepth(pixs) != 1) exit(ERROR_INT("pixs not 1 bpp", mainName, 1)); /* Test speed of pixCountConnComp() */ startTimer(); for (i = 0; i < NTIMES; i++) pixCountConnComp(pixs, 4, &count); fprintf(stderr, "Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES); fprintf(stderr, "Number of 4-cc: %d\n", count); startTimer(); for (i = 0; i < NTIMES; i++) pixCountConnComp(pixs, 8, &count); fprintf(stderr, "Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES); fprintf(stderr, "Number of 8-cc: %d\n", count); /* Test speed of pixConnComp(), with only boxa output */ startTimer(); for (i = 0; i < NTIMES; i++) { boxa = pixConnComp(pixs, NULL, 4); boxaDestroy(&boxa); } fprintf(stderr, "Time to compute 4-cc: %6.3f sec\n", stopTimer()/NTIMES); startTimer(); for (i = 0; i < NTIMES; i++) { boxa = pixConnComp(pixs, NULL, 8); boxaDestroy(&boxa); } fprintf(stderr, "Time to compute 8-cc: %6.3f sec\n", stopTimer()/NTIMES); /* Draw outline of each c.c. box */ boxa = pixConnComp(pixs, NULL, 4); n = boxaGetCount(boxa); fprintf(stderr, "Num 4-cc boxes: %d\n", n); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); pixRenderBox(pixs, box, 3, L_FLIP_PIXELS); boxDestroy(&box); /* remember, clones need to be destroyed */ } pixDisplayWrite(pixs, 1); boxaDestroy(&boxa); /* Display each component as a random color in cmapped 8 bpp. * Background is color 0; it is set to white. */ boxa = pixConnComp(pixs, &pixa, 4); pixd = pixaDisplayRandomCmap(pixa, pixGetWidth(pixs), pixGetHeight(pixs)); cmap = pixGetColormap(pixd); pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */ pixDisplay(pixd, 100, 100); pixDisplayWrite(pixd, 1); boxaDestroy(&boxa); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixs); return 0; }
bool TessPDFRenderer::AddImageHandler(TessBaseAPI * api) { size_t n; char buf[kBasicBufSize]; Pix *pix = api->GetInputImage(); char *filename = (char *) api->GetInputName(); int ppi = api->GetSourceYResolution(); if (!pix || ppi <= 0) return false; double width = pixGetWidth(pix) * 72.0 / ppi; double height = pixGetHeight(pix) * 72.0 / ppi; // PAGE n = snprintf(buf, sizeof(buf), "%ld 0 obj\n" "<<\n" " /Type /Page\n" " /Parent %ld 0 R\n" " /MediaBox [0 0 %.2f %.2f]\n" " /Contents %ld 0 R\n" " /Resources\n" " <<\n" " /XObject << /Im1 %ld 0 R >>\n" " /ProcSet [ /PDF /Text /ImageB /ImageI /ImageC ]\n" " /Font << /f-0-0 %ld 0 R >>\n" " >>\n" ">>\n" "endobj\n", obj_, 2L, // Pages object width, height, obj_ + 1, // Contents object obj_ + 2, // Image object 3L); // Type0 Font if (n >= sizeof(buf)) return false; pages_.push_back(obj_); AppendPDFObject(buf); // CONTENTS char *pdftext = GetPDFTextObjects(api, width, height); long pdftext_len = strlen(pdftext); unsigned char *pdftext_casted = reinterpret_cast<unsigned char *>(pdftext); size_t len; unsigned char *comp_pdftext = zlibCompress(pdftext_casted, pdftext_len, &len); long comp_pdftext_len = len; n = snprintf(buf, sizeof(buf), "%ld 0 obj\n" "<<\n" " /Length %ld /Filter /FlateDecode\n" ">>\n" "stream\n", obj_, comp_pdftext_len); if (n >= sizeof(buf)) { delete[] pdftext; lept_free(comp_pdftext); return false; } AppendString(buf); long objsize = strlen(buf); AppendData(reinterpret_cast<char *>(comp_pdftext), comp_pdftext_len); objsize += comp_pdftext_len; lept_free(comp_pdftext); delete[] pdftext; const char *b2 = "endstream\n" "endobj\n"; AppendString(b2); objsize += strlen(b2); AppendPDFObjectDIY(objsize); char *pdf_object; if (!imageToPDFObj(pix, filename, obj_, &pdf_object, &objsize)) { return false; } AppendData(pdf_object, objsize); AppendPDFObjectDIY(objsize); delete[] pdf_object; return true; }
int main(int argc, char **argv) { l_int32 i, w, h, d, rotflag; PIX *pixs, *pixt, *pixd; l_float32 angle, deg2rad, pops, ang; char *filein, *fileout; static char mainName[] = "rotatetest1"; if (argc != 4) return ERROR_INT(" Syntax: rotatetest1 filein angle fileout", mainName, 1); filein = argv[1]; angle = atof(argv[2]); fileout = argv[3]; deg2rad = 3.1415926535 / 180.; if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pix not made", mainName, 1); if (pixGetDepth(pixs) == 1) { pixt = pixScaleToGray3(pixs); pixDestroy(&pixs); pixs = pixAddBorderGeneral(pixt, 1, 0, 1, 0, 255); pixDestroy(&pixt); } pixGetDimensions(pixs, &w, &h, &d); fprintf(stderr, "w = %d, h = %d\n", w, h); #if 0 /* repertory of rotation operations to choose from */ pixd = pixRotateAM(pixs, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixRotateAMColor(pixs, deg2rad * angle, 0xffffff00); pixd = pixRotateAMColorFast(pixs, deg2rad * angle, 255); pixd = pixRotateAMCorner(pixs, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixRotateShear(pixs, w /2, h / 2, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixRotate3Shear(pixs, w /2, h / 2, deg2rad * angle, L_BRING_IN_WHITE); pixRotateShearIP(pixs, w / 2, h / 2, deg2rad * angle); pixd = pixs; #endif #if 0 /* timing of shear rotation */ for (i = 0; i < NITERS; i++) { pixd = pixRotateShear(pixs, (i * w) / NITERS, (i * h) / NITERS, deg2rad * angle, L_BRING_IN_WHITE); pixDisplay(pixd, 100 + 20 * i, 100 + 20 * i); pixDestroy(&pixd); } #endif #if 0 /* timing of in-place shear rotation */ for (i = 0; i < NITERS; i++) { pixRotateShearIP(pixs, w/2, h/2, deg2rad * angle, L_BRING_IN_WHITE); /* pixRotateShearCenterIP(pixs, deg2rad * angle, L_BRING_IN_WHITE); */ pixDisplay(pixs, 100 + 20 * i, 100 + 20 * i); } pixd = pixs; if (pixGetDepth(pixd) == 1) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixs); #endif #if 0 /* timing of various rotation operations (choose) */ startTimer(); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < NTIMES; i++) { pixd = pixRotateShearCenter(pixs, deg2rad * angle, L_BRING_IN_WHITE); pixDestroy(&pixd); } pops = (l_float32)(w * h * NTIMES / 1000000.) / stopTimer(); fprintf(stderr, "vers. 1, mpops: %f\n", pops); startTimer(); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < NTIMES; i++) { pixRotateShearIP(pixs, w/2, h/2, deg2rad * angle, L_BRING_IN_WHITE); } pops = (l_float32)(w * h * NTIMES / 1000000.) / stopTimer(); fprintf(stderr, "shear, mpops: %f\n", pops); pixWrite(fileout, pixs, IFF_PNG); for (i = 0; i < NTIMES; i++) { pixRotateShearIP(pixs, w/2, h/2, -deg2rad * angle, L_BRING_IN_WHITE); } pixWrite("/usr/tmp/junkout", pixs, IFF_PNG); #endif #if 0 /* area-mapping rotation operations */ pixd = pixRotateAM(pixs, deg2rad * angle, L_BRING_IN_WHITE); /* pixd = pixRotateAMColorFast(pixs, deg2rad * angle, 255); */ if (pixGetDepth(pixd) == 1) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); #endif #if 0 /* compare the standard area-map color rotation with * the fast area-map color rotation, on a pixel basis */ { PIX *pix1, *pix2; NUMA *nar, *nag, *nab, *naseq; GPLOT *gplot; startTimer(); pix1 = pixRotateAMColor(pixs, 0.12, 0xffffff00); fprintf(stderr, " standard color rotate: %7.2f sec\n", stopTimer()); pixWrite("junkcolor1", pix1, IFF_JFIF_JPEG); startTimer(); pix2 = pixRotateAMColorFast(pixs, 0.12, 0xffffff00); fprintf(stderr, " fast color rotate: %7.2f sec\n", stopTimer()); pixWrite("junkcolor2", pix2, IFF_JFIF_JPEG); pixd = pixAbsDifference(pix1, pix2); pixGetColorHistogram(pixd, 1, &nar, &nag, &nab); naseq = numaMakeSequence(0., 1., 256); gplot = gplotCreate("junk_absdiff", GPLOT_X11, "Number vs diff", "diff", "number"); gplotAddPlot(gplot, naseq, nar, GPLOT_POINTS, "red"); gplotAddPlot(gplot, naseq, nag, GPLOT_POINTS, "green"); gplotAddPlot(gplot, naseq, nab, GPLOT_POINTS, "blue"); gplotMakeOutput(gplot); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pixd); numaDestroy(&nar); numaDestroy(&nag); numaDestroy(&nab); numaDestroy(&naseq); gplotDestroy(&gplot); } #endif /* Do a succession of 180 7-degree rotations in a cw * direction, and unwind the result with another set in * a ccw direction. Although there is a considerable amount * of distortion after successive rotations, after all * 360 rotations, the resulting image is restored to * its original pristine condition! */ #if 1 rotflag = L_ROTATE_AREA_MAP; /* rotflag = L_ROTATE_SHEAR; */ /* rotflag = L_ROTATE_SAMPLING; */ ang = 7.0 * deg2rad; pixGetDimensions(pixs, &w, &h, NULL); pixd = pixRotate(pixs, ang, rotflag, L_BRING_IN_WHITE, w, h); pixWrite("junkrot7", pixd, IFF_PNG); for (i = 1; i < 180; i++) { pixs = pixd; pixd = pixRotate(pixs, ang, rotflag, L_BRING_IN_WHITE, w, h); if ((i % 30) == 0) pixDisplay(pixd, 600, 0); pixDestroy(&pixs); } pixWrite("junkspin", pixd, IFF_PNG); pixDisplay(pixd, 0, 0); for (i = 0; i < 180; i++) { pixs = pixd; pixd = pixRotate(pixs, -ang, rotflag, L_BRING_IN_WHITE, w, h); if (i && (i % 30) == 0) pixDisplay(pixd, 600, 500); pixDestroy(&pixs); } pixWrite("junkunspin", pixd, IFF_PNG); pixDisplay(pixd, 0, 500); pixDestroy(&pixd); #endif return 0; }
/*! * pixSetSelectMaskedCmap() * * Input: pixs (2, 4 or 8 bpp, with colormap) * pixm (<optional> 1 bpp mask; no-op if NULL) * x, y (UL corner of mask relative to pixs) * sindex (colormap index of pixels in pixs to be changed) * rval, gval, bval (new color to substitute) * Return: 0 if OK, 1 on error * * Note: * (1) This is an in-place operation. * (2) This paints through the fg of pixm and replaces all pixels * in pixs that have a particular value (sindex) with the new color. * (3) If pixm == NULL, a warning is given. * (4) sindex must be in the existing colormap; otherwise an * error is returned. * (5) If the new color exists in the colormap, it is used; * otherwise, it is added to the colormap. If the colormap * is full, an error is returned. */ l_int32 pixSetSelectMaskedCmap(PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, d, n, wm, hm, wpls, wplm, val; l_int32 index; /* of new color to be set */ l_uint32 *lines, *linem, *datas, *datam; PIXCMAP *cmap; PROCNAME("pixSetSelectMaskedCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); if (!pixm) { L_WARNING("no mask; nothing to do\n", procName); return 0; } d = pixGetDepth(pixs); if (d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {2, 4, 8}", procName, 1); /* add new color if necessary; get index of this color in cmap */ n = pixcmapGetCount(cmap); if (sindex >= n) return ERROR_INT("sindex too large; no cmap entry", procName, 1); if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */ if (pixcmapAddColor(cmap, rval, gval, bval)) return ERROR_INT("error adding cmap entry", procName, 1); else index = n; /* we've added one color */ } /* replace pixel value sindex by index when fg pixel in pixmc * overlays it */ w = pixGetWidth(pixs); h = pixGetHeight(pixs); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); wm = pixGetWidth(pixm); hm = pixGetHeight(pixm); datam = pixGetData(pixm); wplm = pixGetWpl(pixm); for (i = 0; i < hm; i++) { if (i + y < 0 || i + y >= h) continue; lines = datas + (y + i) * wpls; linem = datam + i * wplm; for (j = 0; j < wm; j++) { if (j + x < 0 || j + x >= w) continue; if (GET_DATA_BIT(linem, j)) { switch (d) { case 1: val = GET_DATA_BIT(lines, x + j); if (val == sindex) { if (index == 0) CLEAR_DATA_BIT(lines, x + j); else SET_DATA_BIT(lines, x + j); } break; case 2: val = GET_DATA_DIBIT(lines, x + j); if (val == sindex) SET_DATA_DIBIT(lines, x + j, index); break; case 4: val = GET_DATA_QBIT(lines, x + j); if (val == sindex) SET_DATA_QBIT(lines, x + j, index); break; case 8: val = GET_DATA_BYTE(lines, x + j); if (val == sindex) SET_DATA_BYTE(lines, x + j, index); break; default: return ERROR_INT("depth not in {1,2,4,8}", procName, 1); } } } } return 0; }
/*! * pixaaDisplayByPixa() * * Input: pixaa * xspace between pix in pixa * yspace between pixa * max width of output pix * Return: pix, or null on error * * Notes: * (1) Displays each pixa on a line (or set of lines), * in order from top to bottom. Within each pixa, * the pix are displayed in order from left to right. * (2) The size of each pix in each pixa is assumed to be * approximately equal to the size of the first pix in * the pixa. If this assumption is not correct, this * function will not work properly. * (3) This ignores the boxa of the pixaa. */ PIX * pixaaDisplayByPixa(PIXAA *pixaa, l_int32 xspace, l_int32 yspace, l_int32 maxw) { l_int32 i, j, npixa, npix; l_int32 width, height, depth, nlines, lwidth; l_int32 x, y, w, h, w0, h0; PIX *pixt, *pixd; PIXA *pixa; PROCNAME("pixaaDisplayByPixa"); if (!pixaa) return (PIX *)ERROR_PTR("pixaa not defined", procName, NULL); if ((npixa = pixaaGetCount(pixaa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Get size of output pix. The width is the minimum of the * maxw and the largest pixa line width. The height is whatever * it needs to be to accommodate all pixa. */ height = 2 * yspace; width = 0; for (i = 0; i < npixa; i++) { pixa = pixaaGetPixa(pixaa, i, L_CLONE); npix = pixaGetCount(pixa); pixt = pixaGetPix(pixa, 0, L_CLONE); if (i == 0) depth = pixGetDepth(pixt); w = pixGetWidth(pixt); lwidth = npix * (w + xspace); nlines = (lwidth + maxw - 1) / maxw; if (nlines > 1) width = maxw; else width = L_MAX(lwidth, width); height += nlines * (pixGetHeight(pixt) + yspace); pixDestroy(&pixt); pixaDestroy(&pixa); } if ((pixd = pixCreate(width, height, depth)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); /* Now layout the pix by pixa */ y = yspace; for (i = 0; i < npixa; i++) { x = 0; pixa = pixaaGetPixa(pixaa, i, L_CLONE); npix = pixaGetCount(pixa); for (j = 0; j < npix; j++) { pixt = pixaGetPix(pixa, j, L_CLONE); if (j == 0) { w0 = pixGetWidth(pixt); h0 = pixGetHeight(pixt); } w = pixGetWidth(pixt); if (width == maxw && x + w >= maxw) { x = 0; y += h0 + yspace; } h = pixGetHeight(pixt); pixRasterop(pixd, x, y, w, h, PIX_PAINT, pixt, 0, 0); pixDestroy(&pixt); x += w0 + xspace; } y += h0 + yspace; pixaDestroy(&pixa); } return pixd; }
/*! * 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; }
/*! * pixaDisplayTiled() * * Input: pixa * maxwidth (of output image) * background (0 for white, 1 for black) * spacing * Return: pix of tiled images, or null on error * * Notes: * (1) This saves a pixa to a single image file of width not to * exceed maxwidth, with background color either white or black, * and with each subimage spaced on a regular lattice. * (2) The lattice size is determined from the largest width and height, * separately, of all pix in the pixa. * (3) All pix in the pixa must be of equal depth. * (4) If any pix has a colormap, all pix are rendered in rgb. * (5) Careful: because no components are omitted, this is * dangerous if there are thousands of small components and * one or more very large one, because the size of the * resulting pix can be huge! */ PIX * pixaDisplayTiled(PIXA *pixa, l_int32 maxwidth, l_int32 background, l_int32 spacing) { l_int32 w, h, wmax, hmax, wd, hd, d, hascmap; l_int32 i, j, n, ni, ncols, nrows; l_int32 ystart, xstart, wt, ht; PIX *pix, *pixt, *pixd; PIXA *pixat; PROCNAME("pixaDisplayTiled"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); /* If any pix have colormaps, generate rgb */ if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); pixaAnyColormaps(pixa, &hascmap); if (hascmap) { pixat = pixaCreate(n); for (i = 0; i < n; i++) { pixt = pixaGetPix(pixa, i, L_CLONE); pix = pixConvertTo32(pixt); pixaAddPix(pixat, pix, L_INSERT); pixDestroy(&pixt); } } else pixat = pixaCopy(pixa, L_CLONE); /* Find the largest width and height of the subimages */ wmax = hmax = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixat, i, L_CLONE); pixGetDimensions(pix, &w, &h, NULL); if (i == 0) d = pixGetDepth(pix); else if (d != pixGetDepth(pix)) { pixDestroy(&pix); pixaDestroy(&pixat); return (PIX *)ERROR_PTR("depths not equal", procName, NULL); } if (w > wmax) wmax = w; if (h > hmax) hmax = h; pixDestroy(&pix); } /* Get the number of rows and columns and the output image size */ spacing = L_MAX(spacing, 0); ncols = (l_int32)((l_float32)(maxwidth - spacing) / (l_float32)(wmax + spacing)); nrows = (n + ncols - 1) / ncols; wd = wmax * ncols + spacing * (ncols + 1); hd = hmax * nrows + spacing * (nrows + 1); if ((pixd = pixCreate(wd, hd, d)) == NULL) { pixaDestroy(&pixat); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } #if 0 fprintf(stderr, " nrows = %d, ncols = %d, wmax = %d, hmax = %d\n", nrows, ncols, wmax, hmax); fprintf(stderr, " space = %d, wd = %d, hd = %d, n = %d\n", space, wd, hd, n); #endif /* Reset the background color if necessary */ if ((background == 1 && d == 1) || (background == 0 && d != 1)) pixSetAll(pixd); /* Blit the images to the dest */ for (i = 0, ni = 0; i < nrows; i++) { ystart = spacing + i * (hmax + spacing); for (j = 0; j < ncols && ni < n; j++, ni++) { xstart = spacing + j * (wmax + spacing); pix = pixaGetPix(pixat, ni, L_CLONE); wt = pixGetWidth(pix); ht = pixGetHeight(pix); pixRasterop(pixd, xstart, ystart, wt, ht, PIX_SRC, pix, 0, 0); pixDestroy(&pix); } } pixaDestroy(&pixat); return pixd; }
/*! * pixGetRunsOnLine() * * Input: pixs (1 bpp) * x1, y1, x2, y2 * Return: numa, or null on error * * Notes: * (1) Action: this function uses the bresenham algorithm to compute * the pixels along the specified line. It returns a Numa of the * runlengths of the fg (black) and bg (white) runs, always * starting with a white run. * (2) If the first pixel on the line is black, the length of the * first returned run (which is white) is 0. */ NUMA * pixGetRunsOnLine(PIX *pixs, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2) { l_int32 w, h, x, y, npts; l_int32 i, runlen, preval; l_uint32 val; NUMA *numa; PTA *pta; PROCNAME("pixGetRunsOnLine"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs not 1 bpp", procName, NULL); w = pixGetWidth(pixs); h = pixGetHeight(pixs); if (x1 < 0 || x1 >= w) return (NUMA *)ERROR_PTR("x1 not valid", procName, NULL); if (x2 < 0 || x2 >= w) return (NUMA *)ERROR_PTR("x2 not valid", procName, NULL); if (y1 < 0 || y1 >= h) return (NUMA *)ERROR_PTR("y1 not valid", procName, NULL); if (y2 < 0 || y2 >= h) return (NUMA *)ERROR_PTR("y2 not valid", procName, NULL); if ((pta = generatePtaLine(x1, y1, x2, y2)) == NULL) return (NUMA *)ERROR_PTR("pta not made", procName, NULL); if ((npts = ptaGetCount(pta)) == 0) return (NUMA *)ERROR_PTR("pta has no pts", procName, NULL); if ((numa = numaCreate(0)) == NULL) return (NUMA *)ERROR_PTR("numa not made", procName, NULL); for (i = 0; i < npts; i++) { ptaGetIPt(pta, i, &x, &y); pixGetPixel(pixs, x, y, &val); if (i == 0) { if (val == 1) { /* black pixel; append white run of size 0 */ numaAddNumber(numa, 0); } preval = val; runlen = 1; continue; } if (val == preval) { /* extend current run */ preval = val; runlen++; } else { /* end previous run */ numaAddNumber(numa, runlen); preval = val; runlen = 1; } } numaAddNumber(numa, runlen); /* append last run */ ptaDestroy(&pta); return numa; }
// Returns a list of regions (boxes) which should be cleared in the original // image so as to perform shiro-rekha splitting. Pix is assumed to carry one // (or less) word only. Xheight measure could be the global estimate, the row // estimate, or unspecified. If unspecified, over splitting may occur, since a // conservative estimate of stroke width along with an associated multiplier // is used in its place. It is advisable to have a specified xheight when // splitting for classification/training. // A vertical projection histogram of all the on-pixels in the input pix is // computed. The maxima of this histogram is regarded as an approximate location // of the shiro-rekha. By descending on the maxima's peak on both sides, // stroke width of shiro-rekha is estimated. // A horizontal projection histogram is computed for a sub-image of the input // image, which extends from just below the shiro-rekha down to a certain // leeway. The leeway depends on the input xheight, if provided, else a // conservative multiplier on approximate stroke width is used (which may lead // to over-splitting). void ShiroRekhaSplitter::SplitWordShiroRekha(SplitStrategy split_strategy, Pix* pix, int xheight, int word_left, int word_top, Boxa* regions_to_clear) { if (split_strategy == NO_SPLIT) { return; } int width = pixGetWidth(pix); int height = pixGetHeight(pix); // Statistically determine the yextents of the shiro-rekha. int shirorekha_top, shirorekha_bottom, shirorekha_ylevel; GetShiroRekhaYExtents(pix, &shirorekha_top, &shirorekha_bottom, &shirorekha_ylevel); // Since the shiro rekha is also a stroke, its width is equal to the stroke // width. int stroke_width = shirorekha_bottom - shirorekha_top + 1; // Some safeguards to protect CCs we do not want to be split. // These are particularly useful when the word wasn't eliminated earlier // because xheight information was unavailable. if (shirorekha_ylevel > height / 2) { // Shirorekha shouldn't be in the bottom half of the word. if (devanagari_split_debuglevel > 0) { tprintf("Skipping splitting CC at (%d, %d): shirorekha in lower half..\n", word_left, word_top); } return; } if (stroke_width > height / 3) { // Even the boldest of fonts shouldn't do this. if (devanagari_split_debuglevel > 0) { tprintf("Skipping splitting CC at (%d, %d): stroke width too huge..\n", word_left, word_top); } return; } // Clear the ascender and descender regions of the word. // Obtain a vertical projection histogram for the resulting image. Box* box_to_clear = boxCreate(0, shirorekha_top - stroke_width / 3, width, 5 * stroke_width / 3); Pix* word_in_xheight = pixCopy(NULL, pix); pixClearInRect(word_in_xheight, box_to_clear); // Also clear any pixels which are below shirorekha_bottom + some leeway. // The leeway is set to xheight if the information is available, else it is a // multiplier applied to the stroke width. int leeway_to_keep = stroke_width * 3; if (xheight != kUnspecifiedXheight) { // This is because the xheight-region typically includes the shiro-rekha // inside it, i.e., the top of the xheight range corresponds to the top of // shiro-rekha. leeway_to_keep = xheight - stroke_width; } box_to_clear->y = shirorekha_bottom + leeway_to_keep; box_to_clear->h = height - box_to_clear->y; pixClearInRect(word_in_xheight, box_to_clear); boxDestroy(&box_to_clear); PixelHistogram vert_hist; vert_hist.ConstructVerticalCountHist(word_in_xheight); pixDestroy(&word_in_xheight); // If the number of black pixel in any column of the image is less than a // fraction of the stroke width, treat it as noise / a stray mark. Perform // these changes inside the vert_hist data itself, as that is used later on as // a bit vector for the final split decision at every column. for (int i = 0; i < width; ++i) { if (vert_hist.hist()[i] <= stroke_width / 4) vert_hist.hist()[i] = 0; else vert_hist.hist()[i] = 1; } // In order to split the line at any point, we make sure that the width of the // gap is atleast half the stroke width. int i = 0; int cur_component_width = 0; while (i < width) { if (!vert_hist.hist()[i]) { int j = 0; while (i + j < width && !vert_hist.hist()[i+j]) ++j; if (j >= stroke_width / 2 && cur_component_width >= stroke_width / 2) { // Perform a shiro-rekha split. The intervening region lies from i to // i+j-1. // A minimal single-pixel split makes the estimation of intra- and // inter-word spacing easier during page layout analysis, // whereas a maximal split may be needed for OCR, depending on // how the engine was trained. bool minimal_split = (split_strategy == MINIMAL_SPLIT); int split_width = minimal_split ? 1 : j; int split_left = minimal_split ? i + (j / 2) - (split_width / 2) : i; if (!minimal_split || (i != 0 && i + j != width)) { Box* box_to_clear = boxCreate(word_left + split_left, word_top + shirorekha_top - stroke_width / 3, split_width, 5 * stroke_width / 3); if (box_to_clear) { boxaAddBox(regions_to_clear, box_to_clear, L_CLONE); // Mark this in the debug image if needed. if (devanagari_split_debugimage) { pixRenderBoxArb(debug_image_, box_to_clear, 1, 128, 255, 128); } boxDestroy(&box_to_clear); cur_component_width = 0; } } } i += j; } else { ++i; ++cur_component_width; } } }
main(int argc, char **argv) { char *filein, *fileout; l_int32 wsize, hsize, w, h; PIX *pixs, *pixd; static char mainName[] = "graymorphtest"; if (argc != 5) exit(ERROR_INT(" Syntax: graymorphtest wsize, hsizefilein fileout", mainName, 1)); filein = argv[1]; wsize = atoi(argv[2]); hsize = atoi(argv[3]); fileout = argv[4]; if ((pixs = pixRead(filein)) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); w = pixGetWidth(pixs); h = pixGetHeight(pixs); /* ---------- Choose an operation ---------- */ #if 1 pixd = pixDilateGray(pixs, wsize, hsize); pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); #elif 0 pixd = pixErodeGray(pixs, wsize, hsize); pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); #elif 0 pixd = pixOpenGray(pixs, wsize, hsize); pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); #elif 0 pixd = pixCloseGray(pixs, wsize, hsize); pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); #elif 0 pixd = pixTophat(pixs, wsize, hsize, TOPHAT_WHITE); pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); #elif 0 pixd = pixTophat(pixs, wsize, hsize, TOPHAT_BLACK); pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); #endif /* ---------- Speed ---------- */ #if 0 startTimer(); pixd = pixCloseGray(pixs, wsize, hsize); fprintf(stderr, " Speed is %6.2f MPix/sec\n", (l_float32)(4 * w * h) / (1000000. * stopTimer())); pixWrite(fileout, pixd, IFF_PNG); #endif pixDestroy(&pixs); exit(0); }
// Returns a new box object for the corresponding TBOX, based on the original // image's coordinate system. Box* ShiroRekhaSplitter::GetBoxForTBOX(const TBOX& tbox) const { return boxCreate(tbox.left(), pixGetHeight(orig_pix_) - tbox.top() - 1, tbox.width(), tbox.height()); }
/*! * pixWriteStreamPng() * * Input: stream * pix * gamma (use 0.0 if gamma is not defined) * Return: 0 if OK; 1 on error * * Notes: * (1) If called from pixWriteStream(), the stream is positioned * at the beginning of the file. * (2) To do sequential writes of png format images to a stream, * use pixWriteStreamPng() directly. * (3) gamma is an optional png chunk. If no gamma value is to be * placed into the file, use gamma = 0.0. Otherwise, if * gamma > 0.0, its value is written into the header. * (4) The use of gamma in png is highly problematic. For an illuminating * discussion, see: http://hsivonen.iki.fi/png-gamma/ * (5) What is the effect/meaning of gamma in the png file? This * gamma, which we can call the 'source' gamma, is the * inverse of the gamma that was used in enhance.c to brighten * or darken images. The 'source' gamma is supposed to indicate * the intensity mapping that was done at the time the * image was captured. Display programs typically apply a * 'display' gamma of 2.2 to the output, which is intended * to linearize the intensity based on the response of * thermionic tubes (CRTs). Flat panel LCDs have typically * been designed to give a similar response as CRTs (call it * "backward compatibility"). The 'display' gamma is * in some sense the inverse of the 'source' gamma. * jpeg encoders attached to scanners and cameras will lighten * the pixels, applying a gamma corresponding to approximately * a square-root relation of output vs input: * output = input^(gamma) * where gamma is often set near 0.4545 (1/gamma is 2.2). * This is stored in the image file. Then if the display * program reads the gamma, it will apply a display gamma, * typically about 2.2; the product is 1.0, and the * display program produces a linear output. This works because * the dark colors were appropriately boosted by the scanner, * as described by the 'source' gamma, so they should not * be further boosted by the display program. * (6) As an example, with xv and display, if no gamma is stored, * the program acts as if gamma were 0.4545, multiplies this by 2.2, * and does a linear rendering. Taking this as a baseline * brightness, if the stored gamma is: * > 0.4545, the image is rendered lighter than baseline * < 0.4545, the image is rendered darker than baseline * In contrast, gqview seems to ignore the gamma chunk in png. * (7) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16 * and 32. However, it is possible, and in some cases desirable, * to write out a png file using an rgb pix that has 24 bpp. * For example, the open source xpdf SplashBitmap class generates * 24 bpp rgb images. Consequently, we anble writing 24 bpp pix. * To generate such a pix, you can make a 24 bpp pix without data * and assign the data array to the pix; e.g., * pix = pixCreateHeader(w, h, 24); * pixSetData(pix, rgbdata); * See pixConvert32To24() for an example, where we get rgbdata * from the 32 bpp pix. Caution: do not call pixSetPadBits(), * because the alignment is wrong and you may erase part of the * last pixel on each line. */ l_int32 pixWriteStreamPng(FILE *fp, PIX *pix, l_float32 gamma) { char commentstring[] = "Comment"; l_int32 i, j, k; l_int32 wpl, d, cmflag; l_int32 ncolors; l_int32 *rmap, *gmap, *bmap; l_uint32 *data, *ppixel; png_byte bit_depth, color_type; png_uint_32 w, h; png_uint_32 xres, yres; png_bytep *row_pointers; png_bytep rowbuffer; png_structp png_ptr; png_infop info_ptr; png_colorp palette; PIX *pixt; PIXCMAP *cmap; char *text; PROCNAME("pixWriteStreamPng"); if (!fp) return ERROR_INT("stream not open", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); /* Allocate the 2 data structures */ if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, NULL, NULL)) == NULL) return ERROR_INT("png_ptr not made", procName, 1); if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return ERROR_INT("info_ptr not made", procName, 1); } /* Set up png setjmp error handling */ if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return ERROR_INT("internal png error", procName, 1); } png_init_io(png_ptr, fp); /* With best zlib compression (9), get between 1 and 10% improvement * over default (5), but the compression is 3 to 10 times slower. * Our default compression is the zlib default (5). */ png_set_compression_level(png_ptr, var_ZLIB_COMPRESSION); w = pixGetWidth(pix); h = pixGetHeight(pix); d = pixGetDepth(pix); if ((cmap = pixGetColormap(pix))) cmflag = 1; else cmflag = 0; /* Set the color type and bit depth. */ if (d == 32 && var_PNG_WRITE_ALPHA == 1) { bit_depth = 8; color_type = PNG_COLOR_TYPE_RGBA; /* 6 */ cmflag = 0; /* ignore if it exists */ } else if (d == 24 || d == 32) { bit_depth = 8; color_type = PNG_COLOR_TYPE_RGB; /* 2 */ cmflag = 0; /* ignore if it exists */ } else { bit_depth = d; color_type = PNG_COLOR_TYPE_GRAY; /* 0 */ } if (cmflag) color_type = PNG_COLOR_TYPE_PALETTE; /* 3 */ #if DEBUG fprintf(stderr, "cmflag = %d, bit_depth = %d, color_type = %d\n", cmflag, bit_depth, color_type); #endif /* DEBUG */ png_set_IHDR(png_ptr, info_ptr, w, h, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); /* Store resolution in ppm, if known */ xres = (png_uint_32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5); yres = (png_uint_32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5); if ((xres == 0) || (yres == 0)) png_set_pHYs(png_ptr, info_ptr, 0, 0, PNG_RESOLUTION_UNKNOWN); else png_set_pHYs(png_ptr, info_ptr, xres, yres, PNG_RESOLUTION_METER); if (cmflag) { pixcmapToArrays(cmap, &rmap, &gmap, &bmap); ncolors = pixcmapGetCount(cmap); /* Make and save the palette */ if ((palette = (png_colorp)(CALLOC(ncolors, sizeof(png_color)))) == NULL) return ERROR_INT("palette not made", procName, 1); for (i = 0; i < ncolors; i++) { palette[i].red = (png_byte)rmap[i]; palette[i].green = (png_byte)gmap[i]; palette[i].blue = (png_byte)bmap[i]; } png_set_PLTE(png_ptr, info_ptr, palette, (int)ncolors); FREE(rmap); FREE(gmap); FREE(bmap); } /* 0.4545 is treated as the default by some image * display programs (not gqview). A value > 0.4545 will * lighten an image as displayed by xv, display, etc. */ if (gamma > 0.0) png_set_gAMA(png_ptr, info_ptr, (l_float64)gamma); if ((text = pixGetText(pix))) { png_text text_chunk; text_chunk.compression = PNG_TEXT_COMPRESSION_NONE; text_chunk.key = commentstring; text_chunk.text = text; text_chunk.text_length = strlen(text); #ifdef PNG_ITXT_SUPPORTED text_chunk.itxt_length = 0; text_chunk.lang = NULL; text_chunk.lang_key = NULL; #endif png_set_text(png_ptr, info_ptr, &text_chunk, 1); } /* Write header and palette info */ png_write_info(png_ptr, info_ptr); if ((d != 32) && (d != 24)) { /* not rgb color */ /* Generate a temporary pix with bytes swapped. * For a binary image, there are two conditions in * which you must first invert the data for writing png: * (a) no colormap * (b) colormap with BLACK set to 0 * png writes binary with BLACK = 0, unless contradicted * by a colormap. If the colormap has BLACK = "1" * (typ. about 255), do not invert the data. If there * is no colormap, you must invert the data to store * in default BLACK = 0 state. */ if (d == 1 && (!cmap || (cmap && ((l_uint8 *)(cmap->array))[0] == 0x0))) { pixt = pixInvert(NULL, pix); pixEndianByteSwap(pixt); } else pixt = pixEndianByteSwapNew(pix); if (!pixt) { png_destroy_write_struct(&png_ptr, &info_ptr); return ERROR_INT("pixt not made", procName, 1); } /* Make and assign array of image row pointers */ if ((row_pointers = (png_bytep *)CALLOC(h, sizeof(png_bytep))) == NULL) return ERROR_INT("row-pointers not made", procName, 1); wpl = pixGetWpl(pixt); data = pixGetData(pixt); for (i = 0; i < h; i++) row_pointers[i] = (png_bytep)(data + i * wpl); png_set_rows(png_ptr, info_ptr, row_pointers); /* Transfer the data */ png_write_image(png_ptr, row_pointers); png_write_end(png_ptr, info_ptr); if (cmflag) FREE(palette); FREE(row_pointers); pixDestroy(&pixt); png_destroy_write_struct(&png_ptr, &info_ptr); return 0; } /* For rgb, compose and write a row at a time */ data = pixGetData(pix); wpl = pixGetWpl(pix); if (d == 24) { /* See note 7 above: special case of 24 bpp rgb */ for (i = 0; i < h; i++) { ppixel = data + i * wpl; png_write_rows(png_ptr, (png_bytepp)&ppixel, 1); } } else { /* 32 bpp rgb and rgba */ if ((rowbuffer = (png_bytep)CALLOC(w, 4)) == NULL) return ERROR_INT("rowbuffer not made", procName, 1); for (i = 0; i < h; i++) { ppixel = data + i * wpl; for (j = k = 0; j < w; j++) { rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_RED); rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN); rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE); if (var_PNG_WRITE_ALPHA == 1) rowbuffer[k++] = GET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL); ppixel++; } png_write_rows(png_ptr, &rowbuffer, 1); } FREE(rowbuffer); } png_write_end(png_ptr, info_ptr); if (cmflag) FREE(palette); png_destroy_write_struct(&png_ptr, &info_ptr); return 0; }
bool MakeIndividualGlyphs(Pix* pix, const std::vector<BoxChar*>& vbox, const int input_tiff_page) { // If checks fail, return false without exiting text2image if (!pix) { tprintf("ERROR: MakeIndividualGlyphs(): Input Pix* is nullptr\n"); return false; } else if (FLAGS_glyph_resized_size <= 0) { tprintf("ERROR: --glyph_resized_size must be positive\n"); return false; } else if (FLAGS_glyph_num_border_pixels_to_pad < 0) { tprintf("ERROR: --glyph_num_border_pixels_to_pad must be 0 or positive\n"); return false; } const int n_boxes = vbox.size(); int n_boxes_saved = 0; int current_tiff_page = 0; int y_previous = 0; static int glyph_count = 0; for (int i = 0; i < n_boxes; i++) { // Get one bounding box Box* b = vbox[i]->mutable_box(); if (!b) continue; const int x = b->x; const int y = b->y; const int w = b->w; const int h = b->h; // Check present tiff page (for multipage tiff) if (y < y_previous-pixGetHeight(pix)/10) { tprintf("ERROR: Wrap-around encountered, at i=%d\n", i); current_tiff_page++; } if (current_tiff_page < input_tiff_page) continue; else if (current_tiff_page > input_tiff_page) break; // Check box validity if (x < 0 || y < 0 || (x+w-1) >= pixGetWidth(pix) || (y+h-1) >= pixGetHeight(pix)) { tprintf("ERROR: MakeIndividualGlyphs(): Index out of range, at i=%d" " (x=%d, y=%d, w=%d, h=%d\n)", i, x, y, w, h); continue; } else if (w < FLAGS_glyph_num_border_pixels_to_pad && h < FLAGS_glyph_num_border_pixels_to_pad) { tprintf("ERROR: Input image too small to be a character, at i=%d\n", i); continue; } // Crop the boxed character Pix* pix_glyph = pixClipRectangle(pix, b, nullptr); if (!pix_glyph) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to clip, at i=%d\n", i); continue; } // Resize to square Pix* pix_glyph_sq = pixScaleToSize(pix_glyph, FLAGS_glyph_resized_size, FLAGS_glyph_resized_size); if (!pix_glyph_sq) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to resize, at i=%d\n", i); continue; } // Zero-pad Pix* pix_glyph_sq_pad = pixAddBorder(pix_glyph_sq, FLAGS_glyph_num_border_pixels_to_pad, 0); if (!pix_glyph_sq_pad) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to zero-pad, at i=%d\n", i); continue; } // Write out Pix* pix_glyph_sq_pad_8 = pixConvertTo8(pix_glyph_sq_pad, false); char filename[1024]; snprintf(filename, 1024, "%s_%d.jpg", FLAGS_outputbase.c_str(), glyph_count++); if (pixWriteJpeg(filename, pix_glyph_sq_pad_8, 100, 0)) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to write JPEG to %s," " at i=%d\n", filename, i); continue; } pixDestroy(&pix_glyph); pixDestroy(&pix_glyph_sq); pixDestroy(&pix_glyph_sq_pad); pixDestroy(&pix_glyph_sq_pad_8); n_boxes_saved++; y_previous = y; } if (n_boxes_saved == 0) { return false; } else { tprintf("Total number of characters saved = %d\n", n_boxes_saved); return true; } }
/*! * \brief pixConnCompBB() * * \param[in] pixs 1 bpp * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) Finds bounding boxes of 4- or 8-connected components * in a binary image. * (2) This works on a copy of the input pix. The c.c. are located * in raster order and erased one at a time. In the process, * the b.b. is computed and saved. * </pre> */ BOXA * pixConnCompBB(PIX *pixs, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pixt; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompBB"); if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ if ((pixt = pixCopy(NULL, pixs)) == NULL) return (BOXA *)ERROR_PTR("pixt not made", procName, NULL); h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) return (BOXA *)ERROR_PTR("stack not made", procName, NULL); if ((auxstack = lstackCreate(0)) == NULL) return (BOXA *)ERROR_PTR("auxstack not made", procName, NULL); stack->auxstack = auxstack; if ((boxa = boxaCreate(0)) == NULL) return (BOXA *)ERROR_PTR("boxa not made", procName, NULL); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pixt, stack, x, y, connectivity)) == NULL) return (BOXA *)ERROR_PTR("box not made", procName, NULL); boxaAddBox(boxa, box, L_INSERT); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pixt, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pixt1, IFF_PNG); #endif /* DEBUG */ /* Cleanup, freeing the fillsegs on each stack */ lstackDestroy(&stack, TRUE); pixDestroy(&pixt); return boxa; }
main(int argc, char **argv) { char *filein, *fileout; 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) exit(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); ws = pixGetWidth(pixs); hs = pixGetHeight(pixs); 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); w = pixGetWidth(pixt); h = pixGetHeight(pixt); scale = L_MIN(FILL_FACTOR * 2550 / w, FILL_FACTOR * 3300 / h); if (d == 1) { pixWrite("/tmp/junk_split_image.tif", pixt, IFF_TIFF_G4); if (i == 0) convertG4ToPS("/tmp/junk_split_image.tif", psfile, "w", 0, 0, 300, scale, 1, FALSE, TRUE); else convertG4ToPS("/tmp/junk_split_image.tif", psfile, "a", 0, 0, 300, scale, 1, FALSE, TRUE); } else { pixWrite("/tmp/junk_split_image.jpg", pixt, IFF_JFIF_JPEG); if (i == 0) convertJpegToPS("/tmp/junk_split_image.jpg", psfile, "w", 0, 0, 300, scale, 1, TRUE); else convertJpegToPS("/tmp/junk_split_image.jpg", psfile, "a", 0, 0, 300, scale, 1, TRUE); } pixDestroy(&pixt); } sprintf(buffer, "ps2pdf %s %s", psfile, fileout); ignore = system(buffer); pixaDestroy(&pixa); pixDestroy(&pixr); pixDestroy(&pixs); return 0; }