/*! * boxaIntersectsBox() * * Input: boxas * box (for intersecting) * Return boxad (boxa with all boxes in boxas that intersect box), * or null on error * * Notes: * (1) All boxes in boxa that intersect with box (i.e., are completely * or partially contained in box) are retained. */ BOXA * boxaIntersectsBox(BOXA *boxas, BOX *box) { l_int32 i, n, val; BOX *boxt; BOXA *boxad; PROCNAME("boxaIntersectsBox"); if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (!box) return (BOXA *)ERROR_PTR("box not defined", procName, NULL); if ((n = boxaGetCount(boxas)) == 0) return boxaCreate(1); /* empty */ boxad = boxaCreate(0); for (i = 0; i < n; i++) { boxt = boxaGetBox(boxas, i, L_CLONE); boxIntersects(box, boxt, &val); if (val == 1) boxaAddBox(boxad, boxt, L_COPY); boxDestroy(&boxt); /* destroy the clone */ } return boxad; }
/*! * boxaClipToBox() * * Input: boxas * box (for clipping) * Return boxad (boxa with boxes in boxas clipped to box), * or null on error * * Notes: * (1) All boxes in boxa not intersecting with box are removed, and * the remaining boxes are clipped to box. */ BOXA * boxaClipToBox(BOXA *boxas, BOX *box) { l_int32 i, n; BOX *boxt, *boxo; BOXA *boxad; PROCNAME("boxaClipToBox"); if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (!box) return (BOXA *)ERROR_PTR("box not defined", procName, NULL); if ((n = boxaGetCount(boxas)) == 0) return boxaCreate(1); /* empty */ boxad = boxaCreate(0); for (i = 0; i < n; i++) { boxt = boxaGetBox(boxas, i, L_CLONE); if ((boxo = boxOverlapRegion(box, boxt)) != NULL) boxaAddBox(boxad, boxo, L_INSERT); boxDestroy(&boxt); } return boxad; }
/*! * boxaSplitEvenOdd() * * Input: boxa * &boxae, &boxao (<return> save even and odd boxes in their * separate boxa, setting the other type to invalid boxes.) * Return: 0 if OK, 1 on error * * Notes: * (1) For example, boxae copies of the even boxes, in their original * location, that are in boxa. Invalid boxes are placed * in the odd array locations. * */ l_int32 boxaSplitEvenOdd(BOXA *boxa, BOXA **pboxae, BOXA **pboxao) { l_int32 i, n; BOX *box, *boxt; PROCNAME("boxaSplitEvenOdd"); if (!pboxae || !pboxao) return ERROR_INT("&boxae and &boxao not defined", procName, 1); *pboxae = *pboxao = NULL; if (!boxa) return ERROR_INT("boxa not defined", procName, 1); n = boxaGetCount(boxa); *pboxae = boxaCreate(n); *pboxao = boxaCreate(n); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_COPY); boxt = boxCreate(0, 0, 0, 0); /* empty placeholder */ if ((i & 1) == 0) { boxaAddBox(*pboxae, box, L_INSERT); boxaAddBox(*pboxao, boxt, L_INSERT); } else { boxaAddBox(*pboxae, boxt, L_INSERT); boxaAddBox(*pboxao, box, L_INSERT); } } return 0; }
/*! * boxaReadStream() * * Input: stream * Return: boxa, or null on error */ BOXA * boxaReadStream(FILE *fp) { l_int32 n, i, x, y, w, h, version; l_int32 ignore; BOX *box; BOXA *boxa; PROCNAME("boxaReadStream"); if (!fp) return (BOXA *)ERROR_PTR("stream not defined", procName, NULL); if (fscanf(fp, "\nBoxa Version %d\n", &version) != 1) return (BOXA *)ERROR_PTR("not a boxa file", procName, NULL); if (version != BOXA_VERSION_NUMBER) return (BOXA *)ERROR_PTR("invalid boxa version", procName, NULL); if (fscanf(fp, "Number of boxes = %d\n", &n) != 1) return (BOXA *)ERROR_PTR("not a boxa file", procName, NULL); if ((boxa = boxaCreate(n)) == NULL) return (BOXA *)ERROR_PTR("boxa not made", procName, NULL); for (i = 0; i < n; i++) { if (fscanf(fp, " Box[%d]: x = %d, y = %d, w = %d, h = %d\n", &ignore, &x, &y, &w, &h) != 5) return (BOXA *)ERROR_PTR("box descr not valid", procName, NULL); if ((box = boxCreate(x, y, w, h)) == NULL) return (BOXA *)ERROR_PTR("box not made", procName, NULL); boxaAddBox(boxa, box, L_INSERT); } return boxa; }
/*! * boxaCopy() * * Input: boxa * copyflag (L_COPY, L_CLONE, L_COPY_CLONE) * Return: new boxa, or null on error * * Notes: * (1) See pix.h for description of the copyflag. * (2) The copy-clone makes a new boxa that holds clones of each box. */ BOXA * boxaCopy(BOXA *boxa, l_int32 copyflag) { l_int32 i; BOX *boxc; BOXA *boxac; PROCNAME("boxaCopy"); if (!boxa) return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL); if (copyflag == L_CLONE) { boxa->refcount++; return boxa; } if (copyflag != L_COPY && copyflag != L_COPY_CLONE) return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL); if ((boxac = boxaCreate(boxa->nalloc)) == NULL) return (BOXA *)ERROR_PTR("boxac not made", procName, NULL); for (i = 0; i < boxa->n; i++) { if (copyflag == L_COPY) boxc = boxaGetBox(boxa, i, L_COPY); else /* copy-clone */ boxc = boxaGetBox(boxa, i, L_CLONE); boxaAddBox(boxac, boxc, L_INSERT); } return boxac; }
/*! * boxaMergeEvenOdd() * * Input: boxae (boxes to go in even positions in merged boxa) * boxao (boxes to go in odd positions in merged boxa) * Return: boxad (merged), or null on error * * Notes: * (1) Boxes are alternatingly selected from boxae and boxao. * Both boxae and boxao are of the same size. */ BOXA * boxaMergeEvenOdd(BOXA *boxae, BOXA *boxao) { l_int32 i, n; BOX *box; BOXA *boxad; PROCNAME("boxaMergeEvenOdd"); if (!boxae || !boxao) return (BOXA *)ERROR_PTR("boxae and boxao not defined", procName, NULL); n = boxaGetCount(boxae); if (n != boxaGetCount(boxao)) return (BOXA *)ERROR_PTR("boxa sizes differ", procName, NULL); boxad = boxaCreate(n); for (i = 0; i < n; i++) { if ((i & 1) == 0) box = boxaGetBox(boxae, i, L_COPY); else box = boxaGetBox(boxao, i, L_COPY); boxaAddBox(boxad, box, L_INSERT); } return boxad; }
/*! * boxaTransform() * * Input: boxa * shiftx, shifty * scalex, scaley * Return: boxad, or null on error * * Notes: * (1) This is a very simple function that first shifts, then scales. */ BOXA * boxaTransform(BOXA *boxas, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley) { l_int32 i, n; BOX *boxs, *boxd; BOXA *boxad; PROCNAME("boxaTransform"); if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); n = boxaGetCount(boxas); if ((boxad = boxaCreate(n)) == NULL) return (BOXA *)ERROR_PTR("boxad not made", procName, NULL); for (i = 0; i < n; i++) { if ((boxs = boxaGetBox(boxas, i, L_CLONE)) == NULL) return (BOXA *)ERROR_PTR("boxs not found", procName, NULL); boxd = boxTransform(boxs, shiftx, shifty, scalex, scaley); boxDestroy(&boxs); boxaAddBox(boxad, boxd, L_INSERT); } return boxad; }
/*! * boxaSortByIndex() * * Input: boxas * naindex (na that maps from the new boxa to the input boxa) * Return: boxad (sorted), or null on error */ BOXA * boxaSortByIndex(BOXA *boxas, NUMA *naindex) { l_int32 i, n, index; BOX *box; BOXA *boxad; PROCNAME("boxaSortByIndex"); if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (!naindex) return (BOXA *)ERROR_PTR("naindex not defined", procName, NULL); n = boxaGetCount(boxas); boxad = boxaCreate(n); for (i = 0; i < n; i++) { numaGetIValue(naindex, i, &index); box = boxaGetBox(boxas, index, L_COPY); boxaAddBox(boxad, box, L_INSERT); } return boxad; }
/*! * boxaRotateOrth() * * Input: boxa * w, h (of image in which the boxa is embedded) * rotation (0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg; * all rotations are clockwise) * Return: boxad, or null on error * * Notes: * (1) See boxRotateOrth() for details. */ BOXA * boxaRotateOrth(BOXA *boxas, l_int32 w, l_int32 h, l_int32 rotation) { l_int32 i, n; BOX *boxs, *boxd; BOXA *boxad; PROCNAME("boxaRotateOrth"); if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (rotation == 0) return boxaCopy(boxas, L_COPY); if (rotation < 1 || rotation > 3) return (BOXA *)ERROR_PTR("rotation not in {0,1,2,3}", procName, NULL); n = boxaGetCount(boxas); if ((boxad = boxaCreate(n)) == NULL) return (BOXA *)ERROR_PTR("boxad not made", procName, NULL); for (i = 0; i < n; i++) { if ((boxs = boxaGetBox(boxas, i, L_CLONE)) == NULL) return (BOXA *)ERROR_PTR("boxs not found", procName, NULL); boxd = boxRotateOrth(boxs, w, h, rotation); boxDestroy(&boxs); boxaAddBox(boxad, boxd, L_INSERT); } return boxad; }
/*! * boxaEncapsulateAligned() * * Input: boxa * num (number put into each boxa in the baa) * copyflag (L_COPY or L_CLONE) * Return: boxaa, or null on error * * Notes: * (1) This puts @num boxes from the input @boxa into each of a * set of boxa within an output boxaa. * (2) This assumes that the boxes in @boxa are in sets of @num each. */ BOXAA * boxaEncapsulateAligned(BOXA *boxa, l_int32 num, l_int32 copyflag) { l_int32 i, j, n, nbaa, index; BOX *box; BOXA *boxat; BOXAA *baa; PROCNAME("boxaEncapsulateAligned"); if (!boxa) return (BOXAA *)ERROR_PTR("boxa not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (BOXAA *)ERROR_PTR("invalid copyflag", procName, NULL); n = boxaGetCount(boxa); nbaa = (n + num - 1) / num; if (n / num != nbaa) L_ERROR("inconsistent alignment: n / num not an integer", procName); baa = boxaaCreate(nbaa); for (i = 0, index = 0; i < nbaa; i++) { boxat = boxaCreate(num); for (j = 0; j < num; j++, index++) { box = boxaGetBox(boxa, index, copyflag); boxaAddBox(boxat, box, L_INSERT); } boxaaAddBoxa(baa, boxat, L_INSERT); } return baa; }
/*! * \brief boxaaQuadtreeRegions() * * \param[in] w, h size of pix that is being quadtree-ized * \param[in] nlevels number of levels in quadtree * \return baa for quadtree regions at each level, or NULL on error * * <pre> * Notes: * (1) The returned boxaa has %nlevels of boxa, each containing * the set of rectangles at that level. The rectangle at * level 0 is the entire region; at level 1 the region is * divided into 4 rectangles, and at level n there are n^4 * rectangles. * (2) At each level, the rectangles in the boxa are in "raster" * order, with LR (fast scan) and TB (slow scan). * </pre> */ BOXAA * boxaaQuadtreeRegions(l_int32 w, l_int32 h, l_int32 nlevels) { l_int32 i, j, k, maxpts, nside, nbox, bw, bh; l_int32 *xstart, *xend, *ystart, *yend; BOX *box; BOXA *boxa; BOXAA *baa; PROCNAME("boxaaQuadtreeRegions"); if (nlevels < 1) return (BOXAA *)ERROR_PTR("nlevels must be >= 1", procName, NULL); if (w < (1 << (nlevels - 1))) return (BOXAA *)ERROR_PTR("w doesn't support nlevels", procName, NULL); if (h < (1 << (nlevels - 1))) return (BOXAA *)ERROR_PTR("h doesn't support nlevels", procName, NULL); baa = boxaaCreate(nlevels); maxpts = 1 << (nlevels - 1); xstart = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32)); xend = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32)); ystart = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32)); yend = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32)); for (k = 0; k < nlevels; k++) { nside = 1 << k; /* number of boxes in each direction */ for (i = 0; i < nside; i++) { xstart[i] = (w - 1) * i / nside; if (i > 0) xstart[i]++; xend[i] = (w - 1) * (i + 1) / nside; ystart[i] = (h - 1) * i / nside; if (i > 0) ystart[i]++; yend[i] = (h - 1) * (i + 1) / nside; #if DEBUG_BOXES fprintf(stderr, "k = %d, xs[%d] = %d, xe[%d] = %d, ys[%d] = %d, ye[%d] = %d\n", k, i, xstart[i], i, xend[i], i, ystart[i], i, yend[i]); #endif /* DEBUG_BOXES */ } nbox = 1 << (2 * k); boxa = boxaCreate(nbox); for (i = 0; i < nside; i++) { bh = yend[i] - ystart[i] + 1; for (j = 0; j < nside; j++) { bw = xend[j] - xstart[j] + 1; box = boxCreate(xstart[j], ystart[i], bw, bh); boxaAddBox(boxa, box, L_INSERT); } } boxaaAddBoxa(baa, boxa, L_INSERT); } LEPT_FREE(xstart); LEPT_FREE(xend); LEPT_FREE(ystart); LEPT_FREE(yend); return baa; }
main(int argc, char **argv) { char *filein, *fileout; l_int32 d; BOX *box1, *box2, *box3, *box4; BOXA *boxa; PIX *pixs, *pixt1, *pixt2, *pixt3; PTA *pta; static char mainName[] = "graphicstest"; if (argc != 3) exit(ERROR_INT(" Syntax: graphicstest filein fileout", mainName, 1)); filein = argv[1]; fileout = argv[2]; if ((pixs = pixRead(filein)) == NULL) exit(ERROR_INT(" Syntax: pixs not made", mainName, 1)); d = pixGetDepth(pixs); if (d <= 8) pixt1 = pixConvertTo32(pixs); else pixt1 = pixClone(pixs); /* Paint on RGB */ pixRenderLineArb(pixt1, 450, 20, 850, 320, 5, 200, 50, 125); pixRenderLineArb(pixt1, 30, 40, 440, 40, 5, 100, 200, 25); pixRenderLineBlend(pixt1, 30, 60, 440, 70, 5, 115, 200, 120, 0.3); pixRenderLineBlend(pixt1, 30, 600, 440, 670, 9, 215, 115, 30, 0.5); pixRenderLineBlend(pixt1, 130, 700, 540, 770, 9, 255, 255, 250, 0.4); pixRenderLineBlend(pixt1, 130, 800, 540, 870, 9, 0, 0, 0, 0.4); box1 = boxCreate(70, 80, 300, 245); box2 = boxCreate(470, 180, 150, 205); box3 = boxCreate(520, 220, 160, 220); box4 = boxCreate(570, 260, 160, 220); boxa = boxaCreate(3); boxaAddBox(boxa, box2, L_INSERT); boxaAddBox(boxa, box3, L_INSERT); boxaAddBox(boxa, box4, L_INSERT); pixRenderBoxArb(pixt1, box1, 3, 200, 200, 25); pixRenderBoxaBlend(pixt1, boxa, 17, 200, 200, 25, 0.4, 1); pta = ptaCreate(5); ptaAddPt(pta, 250, 300); ptaAddPt(pta, 350, 450); ptaAddPt(pta, 400, 600); ptaAddPt(pta, 212, 512); ptaAddPt(pta, 180, 375); pixRenderPolylineBlend(pixt1, pta, 17, 25, 200, 200, 0.5, 1, 1); pixWrite(fileout, pixt1, IFF_JFIF_JPEG); pixDisplay(pixt1, 200, 200); pixDestroy(&pixs); pixDestroy(&pixt1); boxDestroy(&box1); boxaDestroy(&boxa); ptaDestroy(&pta); pixDestroy(&pixs); return 0; }
main(int argc, char **argv) { l_int32 i, k, x, y, w, h; BOX *box; BOXA *boxa1, *boxa2; PIX *pix1, *pix2, *pixd; PIXA *pixa; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; for (k = 0; k < 7; k++) { srand(45617); pixa = pixaCreate(2); boxa1 = boxaCreate(0); for (i = 0; i < 500; i++) { x = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX); y = (l_int32)(600.0 * (l_float64)rand() / (l_float64)RAND_MAX); w = (l_int32) (1.0 + maxsize[k] * (l_float64)rand() / (l_float64)RAND_MAX); h = (l_int32) (1.0 + maxsize[k] * (l_float64)rand() / (l_float64)RAND_MAX); box = boxCreate(x, y, w, h); boxaAddBox(boxa1, box, L_INSERT); } pix1 = pixCreate(660, 660, 1); pixRenderBoxa(pix1, boxa1, 1, L_SET_PIXELS); pixaAddPix(pixa, pix1, L_INSERT); boxa2 = boxaCombineOverlaps(boxa1); pix2 = pixCreate(660, 660, 1); pixRenderBoxa(pix2, boxa2, 1, L_SET_PIXELS); pixaAddPix(pixa, pix2, L_INSERT); pixd = pixaDisplayTiledInRows(pixa, 1, 1500, 1.0, 0, 50, 2); pixDisplayWithTitle(pixd, 100, 100 + 100 * k, NULL, rp->display); regTestWritePixAndCheck(rp, pixd, IFF_PNG); fprintf(stderr, "%d: n_init = %d, n_final = %d\n", k, boxaGetCount(boxa1), boxaGetCount(boxa2)); pixDestroy(&pixd); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixaDestroy(&pixa); } regTestCleanup(rp); return 0; }
/*! * boxaaFlattenToBoxa() * * Input: boxaa * &naindex (<optional return> the boxa index in the boxaa) * copyflag (L_COPY or L_CLONE) * Return: boxa, or null on error * * Notes: * (1) This 'flattens' the boxaa to a boxa, taking the boxes in * order in the first boxa, then the second, etc. * (2) If a boxa is empty, we generate an invalid, placeholder box * of zero size. This is useful when converting from a boxaa * where each boxa has either 0 or 1 boxes, and it is necessary * to maintain a 1:1 correspondence between the initial * boxa array and the resulting box array. * (3) If &naindex is defined, we generate a Numa that gives, for * each box in the boxaa, the index of the boxa to which it belongs. */ BOXA * boxaaFlattenToBoxa(BOXAA *baa, NUMA **pnaindex, l_int32 copyflag) { l_int32 i, j, m, n; BOXA *boxa, *boxat; BOX *box; NUMA *naindex; PROCNAME("boxaaFlattenToBoxa"); if (pnaindex) *pnaindex = NULL; if (!baa) return (BOXA *)ERROR_PTR("baa not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL); if (pnaindex) { naindex = numaCreate(0); *pnaindex = naindex; } n = boxaaGetCount(baa); boxa = boxaCreate(n); for (i = 0; i < n; i++) { boxat = boxaaGetBoxa(baa, i, L_CLONE); m = boxaGetCount(boxat); if (m == 0) { /* placeholder box */ box = boxCreate(0, 0, 0, 0); boxaAddBox(boxa, box, L_INSERT); if (pnaindex) numaAddNumber(naindex, i); /* save 'row' number */ } else { for (j = 0; j < m; j++) { box = boxaGetBox(boxat, j, copyflag); boxaAddBox(boxa, box, L_INSERT); if (pnaindex) numaAddNumber(naindex, i); /* save 'row' number */ } } boxaDestroy(&boxat); } return boxa; }
/* static */ void BoxChar::RotateBoxes(float rotation, int xcenter, int ycenter, int start_box, int end_box, vector<BoxChar*>* boxes) { Boxa* orig = boxaCreate(0); for (int i = start_box; i < end_box; ++i) { BOX* box = (*boxes)[i]->box_; if (box) boxaAddBox(orig, box, L_CLONE); } Boxa* rotated = boxaRotate(orig, xcenter, ycenter, rotation); boxaDestroy(&orig); for (int i = start_box, box_ind = 0; i < end_box; ++i) { if ((*boxes)[i]->box_) { boxDestroy(&((*boxes)[i]->box_)); (*boxes)[i]->box_ = boxaGetBox(rotated, box_ind++, L_CLONE); } } boxaDestroy(&rotated); }
/*! * pixColorGrayCmap() * * Input: pixs (2, 4 or 8 bpp, with colormap) * box (<optional> region to set color; can be NULL) * type (L_PAINT_LIGHT, L_PAINT_DARK) * rval, gval, bval (target color) * Return: 0 if OK, 1 on error * * Notes: * (1) This is an in-place operation. * (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels, * preserving antialiasing. * If type == L_PAINT_DARK, it colorizes non-white pixels, * preserving antialiasing. * (3) box gives the region to apply color; if NULL, this * colorizes the entire image. * (4) If the cmap is only 2 or 4 bpp, pixs is converted in-place * to an 8 bpp cmap. A 1 bpp cmap is not a valid input pix. * (5) This can also be called through pixColorGray(). * (6) This operation increases the colormap size by the number of * different gray (non-black or non-white) colors in the * input colormap. If there is not enough room in the colormap * for this expansion, it returns 1 (error), and the caller * should check the return value. * (7) Using the darkness of each original pixel in the rect, * it generates a new color (based on the input rgb values). * If type == L_PAINT_LIGHT, the new color is a (generally) * darken-to-black version of the input rgb color, where the * amount of darkening increases with the darkness of the * original pixel color. * If type == L_PAINT_DARK, the new color is a (generally) * faded-to-white version of the input rgb color, where the * amount of fading increases with the brightness of the * original pixel color. */ l_int32 pixColorGrayCmap(PIX *pixs, BOX *box, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 w, h, d, ret; PIX *pixt; BOXA *boxa; PIXCMAP *cmap; PROCNAME("pixColorGrayCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {2, 4, 8}", procName, 1); if (type != L_PAINT_DARK && type != L_PAINT_LIGHT) return ERROR_INT("invalid type", procName, 1); /* If 2 bpp or 4 bpp, convert in-place to 8 bpp. */ if (d == 2 || d == 4) { pixt = pixConvertTo8(pixs, 1); pixTransferAllData(pixs, &pixt, 0, 0); } /* If box == NULL, color the entire image */ boxa = boxaCreate(1); if (box) { boxaAddBox(boxa, box, L_COPY); } else { box = boxCreate(0, 0, w, h); boxaAddBox(boxa, box, L_INSERT); } ret = pixColorGrayRegionsCmap(pixs, boxa, type, rval, gval, bval); boxaDestroy(&boxa); return ret; }
/*! * pixaaCreate() * * Input: n (initial number of pixa ptrs) * Return: pixaa, or null on error * * Notes: * (1) A pixaa provides a 2-level hierarchy of images. * A common use is for segmentation masks, which are * inexpensive to store in png format. * (2) For example, suppose you want a mask for each textline * in a two-column page. The textline masks for each column * can be represented by a pixa, of which there are 2 in the pixaa. * The boxes for the textline mask components within a column * can have their origin referred to the column rather than the page. * Then the boxa field can be used to represent the two box (regions) * for the columns, and the (x,y) components of each box can * be used to get the absolute position of the textlines on * the page. */ PIXAA * pixaaCreate(l_int32 n) { PIXAA *pixaa; PROCNAME("pixaaCreate"); if (n <= 0) n = INITIAL_PTR_ARRAYSIZE; if ((pixaa = (PIXAA *)CALLOC(1, sizeof(PIXAA))) == NULL) return (PIXAA *)ERROR_PTR("pixaa not made", procName, NULL); pixaa->n = 0; pixaa->nalloc = n; if ((pixaa->pixa = (PIXA **)CALLOC(n, sizeof(PIXA *))) == NULL) return (PIXAA *)ERROR_PTR("pixa ptrs not made", procName, NULL); pixaa->boxa = boxaCreate(n); return pixaa; }
/*! * \brief ptaConvertToBoxa() * * \param[in] pta * \param[in] ncorners 2 or 4 for the representation of each box * \return boxa with one box for each 2 or 4 points in the pta, * or NULL on error * * <pre> * Notes: * (1) For 2 corners, the order of the 2 points is UL, LR. * For 4 corners, the order of points is UL, UR, LL, LR. * (2) Each derived box is the minimum size containing all corners. * </pre> */ BOXA * ptaConvertToBoxa(PTA *pta, l_int32 ncorners) { l_int32 i, n, nbox, x1, y1, x2, y2, x3, y3, x4, y4, x, y, xmax, ymax; BOX *box; BOXA *boxa; PROCNAME("ptaConvertToBoxa"); if (!pta) return (BOXA *)ERROR_PTR("pta not defined", procName, NULL); if (ncorners != 2 && ncorners != 4) return (BOXA *)ERROR_PTR("ncorners not 2 or 4", procName, NULL); n = ptaGetCount(pta); if (n % ncorners != 0) return (BOXA *)ERROR_PTR("size % ncorners != 0", procName, NULL); nbox = n / ncorners; if ((boxa = boxaCreate(nbox)) == NULL) return (BOXA *)ERROR_PTR("boxa not made", procName, NULL); for (i = 0; i < n; i += ncorners) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); if (ncorners == 2) { box = boxCreate(x1, y1, x2 - x1 + 1, y2 - y1 + 1); boxaAddBox(boxa, box, L_INSERT); continue; } ptaGetIPt(pta, i + 2, &x3, &y3); ptaGetIPt(pta, i + 3, &x4, &y4); x = L_MIN(x1, x3); y = L_MIN(y1, y2); xmax = L_MAX(x2, x4); ymax = L_MAX(y3, y4); box = boxCreate(x, y, xmax - x + 1, ymax - y + 1); boxaAddBox(boxa, box, L_INSERT); } return boxa; }
/*! * boxaSort2dByIndex() * * Input: boxas * naa (numaa that maps from the new baa to the input boxa) * Return: baa (sorted boxaa), or null on error */ BOXAA * boxaSort2dByIndex(BOXA *boxas, NUMAA *naa) { l_int32 ntot, boxtot, i, j, n, nn, index; BOX *box; BOXA *boxa; BOXAA *baa; NUMA *na; PROCNAME("boxaSort2dByIndex"); if (!boxas) return (BOXAA *)ERROR_PTR("boxas not defined", procName, NULL); if (!naa) return (BOXAA *)ERROR_PTR("naindex not defined", procName, NULL); /* Check counts */ ntot = numaaGetNumberCount(naa); boxtot = boxaGetCount(boxas); if (ntot != boxtot) return (BOXAA *)ERROR_PTR("element count mismatch", procName, NULL); n = numaaGetCount(naa); baa = boxaaCreate(n); for (i = 0; i < n; i++) { na = numaaGetNuma(naa, i, L_CLONE); nn = numaGetCount(na); boxa = boxaCreate(nn); for (j = 0; j < nn; j++) { numaGetIValue(na, i, &index); box = boxaGetBox(boxas, index, L_COPY); boxaAddBox(boxa, box, L_INSERT); } boxaaAddBoxa(baa, boxa, L_INSERT); numaDestroy(&na); } return baa; }
/*! * \brief boxaSelectRange() * * \param[in] boxas * \param[in] first use 0 to select from the beginning * \param[in] last use -1 to select to the end * \param[in] copyflag L_COPY, L_CLONE * \return boxad, or NULL on error * * <pre> * Notes: * (1) The copyflag specifies what we do with each box from boxas. * Specifically, L_CLONE inserts a clone into boxad of each * selected box from boxas. * </pre> */ BOXA * boxaSelectRange(BOXA *boxas, l_int32 first, l_int32 last, l_int32 copyflag) { l_int32 n, nbox, i; BOX *box; BOXA *boxad; PROCNAME("boxaSelectRange"); if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL); if ((n = boxaGetCount(boxas)) == 0) { L_WARNING("boxas is empty\n", procName); return boxaCopy(boxas, copyflag); } first = L_MAX(0, first); if (last < 0) last = n - 1; if (first >= n) return (BOXA *)ERROR_PTR("invalid first", procName, NULL); if (last >= n) { L_WARNING("last = %d is beyond max index = %d; adjusting\n", procName, last, n - 1); last = n - 1; } if (first > last) return (BOXA *)ERROR_PTR("first > last", procName, NULL); nbox = last - first + 1; boxad = boxaCreate(nbox); for (i = first; i <= last; i++) { box = boxaGetBox(boxas, i, copyflag); boxaAddBox(boxad, box, L_INSERT); } return boxad; }
/*! * \brief boxaSelectWithIndicator() * * \param[in] boxas * \param[in] na indicator numa * \param[out] pchanged [optional] 1 if changed; 0 if clone returned * \return boxad, or NULL on error * * <pre> * Notes: * (1) Returns a copy of the boxa if no components are removed. * (2) Uses box copies in the new boxa. * (3) The indicator numa has values 0 (ignore) and 1 (accept). * (4) If all indicator values are 0, the returned boxa is empty. * </pre> */ BOXA * boxaSelectWithIndicator(BOXA *boxas, NUMA *na, l_int32 *pchanged) { l_int32 i, n, ival, nsave; BOX *box; BOXA *boxad; PROCNAME("boxaSelectWithIndicator"); if (pchanged) *pchanged = FALSE; if (!boxas) return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL); if (!na) return (BOXA *)ERROR_PTR("na not defined", procName, NULL); nsave = 0; n = numaGetCount(na); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) nsave++; } if (nsave == n) { if (pchanged) *pchanged = FALSE; return boxaCopy(boxas, L_COPY); } if (pchanged) *pchanged = TRUE; boxad = boxaCreate(nsave); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 0) continue; box = boxaGetBox(boxas, i, L_COPY); boxaAddBox(boxad, box, L_INSERT); } return boxad; }
/*! * boxaaFlattenAligned() * * Input: boxaa * num (number extracted from each) * copyflag (L_COPY or L_CLONE) * Return: boxa, or null on error * * Notes: * (1) This 'flattens' the boxaa to a boxa, taking the first @num * boxes from each boxa. * (2) If less than @num boxes are in a boxa, we add invalid placeholder * boxes to preserve the alignment between the input boxaa * and the output boxa. */ BOXA * boxaaFlattenAligned(BOXAA *baa, l_int32 num, l_int32 copyflag) { l_int32 i, j, m, n, mval, nshort; BOXA *boxat, *boxad; BOX *box; PROCNAME("boxaaFlattenAligned"); if (!baa) return (BOXA *)ERROR_PTR("baa not defined", procName, NULL); if (copyflag != L_COPY && copyflag != L_CLONE) return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL); n = boxaaGetCount(baa); boxad = boxaCreate(n); for (i = 0; i < n; i++) { boxat = boxaaGetBoxa(baa, i, L_CLONE); m = boxaGetCount(boxat); mval = L_MIN(m, num); nshort = num - mval; for (j = 0; j < mval; j++) { /* take the first @num if possible */ box = boxaGetBox(boxat, j, copyflag); boxaAddBox(boxad, box, L_INSERT); } for (j = 0; j < nshort; j++) { /* add placeholders if necessary */ box = boxCreate(0, 0, 0, 0); boxaAddBox(boxad, box, L_INSERT); } boxaDestroy(&boxat); } return boxad; }
/*! * \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); boxa = NULL; pixt = NULL; stack = 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) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pixt, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); boxaDestroy(&boxa); goto cleanup; } 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 */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pixt); return boxa; }
/*! * \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 *pix1, *pix2, *pix3, *pix4; 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); boxa = NULL; pix1 = pix2 = pix3 = pix4 = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ pix1 = pixCopy(NULL, pixs); pix2 = pixCopy(NULL, pixs); if (!pix1 || !pix2) { L_ERROR("pix1 or pix2 not made\n", procName); goto cleanup; } h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; pixa = pixaCreate(0); boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pix1, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); pixaDestroy(&pixa); boxaDestroy(&boxa); goto cleanup; } boxaAddBox(boxa, box, L_INSERT); /* Save the c.c. and remove from pix2 as well */ pix3 = pixClipRectangle(pix1, box, NULL); pix4 = pixClipRectangle(pix2, box, NULL); pixXor(pix3, pix3, pix4); pixRasterop(pix2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST, pix3, 0, 0); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix4); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pix1, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pix1, IFF_PNG); #endif /* DEBUG */ /* Remove old boxa of pixa and replace with a clone copy */ boxaDestroy(&pixa->boxa); pixa->boxa = boxaCopy(boxa, L_CLONE); *ppixa = pixa; /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pix1); pixDestroy(&pix2); return boxa; }
// Top-level method to perform splitting based on current settings. // Returns true if a split was actually performed. // split_for_pageseg should be true if the splitting is being done prior to // page segmentation. This mode uses the flag // pageseg_devanagari_split_strategy to determine the splitting strategy. bool ShiroRekhaSplitter::Split(bool split_for_pageseg) { SplitStrategy split_strategy = split_for_pageseg ? pageseg_split_strategy_ : ocr_split_strategy_; if (split_strategy == NO_SPLIT) { return false; // Nothing to do. } ASSERT_HOST(split_strategy == MINIMAL_SPLIT || split_strategy == MAXIMAL_SPLIT); ASSERT_HOST(orig_pix_); if (devanagari_split_debuglevel > 0) { tprintf("Splitting shiro-rekha ...\n"); tprintf("Split strategy = %s\n", split_strategy == MINIMAL_SPLIT ? "Minimal" : "Maximal"); tprintf("Initial pageseg available = %s\n", segmentation_block_list_ ? "yes" : "no"); } // Create a copy of original image to store the splitting output. pixDestroy(&splitted_image_); splitted_image_ = pixCopy(NULL, orig_pix_); // Initialize debug image if required. if (devanagari_split_debugimage) { pixDestroy(&debug_image_); debug_image_ = pixConvertTo32(orig_pix_); } // Determine all connected components in the input image. A close operation // may be required prior to this, depending on the current settings. Pix* pix_for_ccs = pixClone(orig_pix_); if (perform_close_ && global_xheight_ != kUnspecifiedXheight && !segmentation_block_list_) { if (devanagari_split_debuglevel > 0) { tprintf("Performing a global close operation..\n"); } // A global measure is available for xheight, but no local information // exists. pixDestroy(&pix_for_ccs); pix_for_ccs = pixCopy(NULL, orig_pix_); PerformClose(pix_for_ccs, global_xheight_); } Pixa* ccs; Boxa* tmp_boxa = pixConnComp(pix_for_ccs, &ccs, 8); boxaDestroy(&tmp_boxa); pixDestroy(&pix_for_ccs); // Iterate over all connected components. Get their bounding boxes and clip // out the image regions corresponding to these boxes from the original image. // Conditionally run splitting on each of them. Boxa* regions_to_clear = boxaCreate(0); for (int i = 0; i < pixaGetCount(ccs); ++i) { Box* box = ccs->boxa->box[i]; Pix* word_pix = pixClipRectangle(orig_pix_, box, NULL); ASSERT_HOST(word_pix); int xheight = GetXheightForCC(box); if (xheight == kUnspecifiedXheight && segmentation_block_list_ && devanagari_split_debugimage) { pixRenderBoxArb(debug_image_, box, 1, 255, 0, 0); } // If some xheight measure is available, attempt to pre-eliminate small // blobs from the shiro-rekha process. This is primarily to save the CCs // corresponding to punctuation marks/small dots etc which are part of // larger graphemes. if (xheight == kUnspecifiedXheight || (box->w > xheight / 3 && box->h > xheight / 2)) { SplitWordShiroRekha(split_strategy, word_pix, xheight, box->x, box->y, regions_to_clear); } else if (devanagari_split_debuglevel > 0) { tprintf("CC dropped from splitting: %d,%d (%d, %d)\n", box->x, box->y, box->w, box->h); } pixDestroy(&word_pix); } // Actually clear the boxes now. for (int i = 0; i < boxaGetCount(regions_to_clear); ++i) { Box* box = boxaGetBox(regions_to_clear, i, L_CLONE); pixClearInRect(splitted_image_, box); boxDestroy(&box); } boxaDestroy(®ions_to_clear); pixaDestroy(&ccs); if (devanagari_split_debugimage) { DumpDebugImage(split_for_pageseg ? "pageseg_split_debug.png" : "ocr_split_debug.png"); } return true; }
int main(int argc, char **argv) { l_uint8 *data1, *data2; l_int32 i, same, w, h, width, success, nba; size_t size1, size2; l_float32 diffarea, diffxor, scalefact; BOX *box; BOXA *boxa1, *boxa2, *boxa3; BOXAA *baa1, *baa2, *baa3; PIX *pix1, *pixdb; PIXA *pixa1, *pixa2; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; lept_mkdir("lept/boxa"); /* Make a boxa and display its contents */ boxa1 = boxaCreate(6); box = boxCreate(60, 60, 40, 20); boxaAddBox(boxa1, box, L_INSERT); box = boxCreate(120, 50, 20, 50); boxaAddBox(boxa1, box, L_INSERT); box = boxCreate(50, 140, 46, 60); boxaAddBox(boxa1, box, L_INSERT); box = boxCreate(166, 130, 64, 28); boxaAddBox(boxa1, box, L_INSERT); box = boxCreate(64, 224, 44, 34); boxaAddBox(boxa1, box, L_INSERT); box = boxCreate(117, 206, 26, 74); boxaAddBox(boxa1, box, L_INSERT); pix1 = DisplayBoxa(boxa1); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */ pixDisplayWithTitle(pix1, 0, 0, NULL, rp->display); pixDestroy(&pix1); boxaCompareRegions(boxa1, boxa1, 100, &same, &diffarea, &diffxor, NULL); regTestCompareValues(rp, 1, same, 0.0); /* 1 */ regTestCompareValues(rp, 0.0, diffarea, 0.0); /* 2 */ regTestCompareValues(rp, 0.0, diffxor, 0.0); /* 3 */ boxa2 = boxaTransform(boxa1, -13, -13, 1.0, 1.0); boxaCompareRegions(boxa1, boxa2, 10, &same, &diffarea, &diffxor, NULL); regTestCompareValues(rp, 1, same, 0.0); /* 4 */ regTestCompareValues(rp, 0.0, diffarea, 0.0); /* 5 */ regTestCompareValues(rp, 0.0, diffxor, 0.0); /* 6 */ boxaDestroy(&boxa2); boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP_AND_BOT, 6, L_ADJUST_CHOOSE_MIN, 1.0, 0); pix1 = DisplayBoxa(boxa2); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 7 */ pixDisplayWithTitle(pix1, 200, 0, NULL, rp->display); pixDestroy(&pix1); boxaCompareRegions(boxa1, boxa2, 10, &same, &diffarea, &diffxor, &pixdb); regTestCompareValues(rp, 1, same, 0.0); /* 8 */ regTestCompareValues(rp, 0.053, diffarea, 0.002); /* 9 */ regTestCompareValues(rp, 0.240, diffxor, 0.002); /* 10 */ regTestWritePixAndCheck(rp, pixdb, IFF_PNG); /* 11 */ pixDisplayWithTitle(pixdb, 400, 0, NULL, rp->display); pixDestroy(&pixdb); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* Input is a fairly clean boxa */ boxa1 = boxaRead("boxa1.ba"); boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP, 80, L_ADJUST_CHOOSE_MIN, 1.05, 1); width = 100; boxaGetExtent(boxa2, &w, &h, NULL); scalefact = (l_float32)width / (l_float32)w; boxa3 = boxaTransform(boxa2, 0, 0, scalefact, scalefact); pix1 = boxaDisplayTiled(boxa3, NULL, 1500, 2, 1.0, 0, 3, 2); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 12 */ pixDisplayWithTitle(pix1, 600, 0, NULL, rp->display); pixDestroy(&pix1); boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); /* Input is an unsmoothed and noisy boxa */ boxa1 = boxaRead("boxa2.ba"); boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP, 80, L_ADJUST_CHOOSE_MIN, 1.05, 1); width = 100; boxaGetExtent(boxa2, &w, &h, NULL); scalefact = (l_float32)width / (l_float32)w; boxa3 = boxaTransform(boxa2, 0, 0, scalefact, scalefact); pix1 = boxaDisplayTiled(boxa3, NULL, 1500, 2, 1.0, 0, 3, 2); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 13 */ pixDisplayWithTitle(pix1, 800, 0, NULL, rp->display); pixDestroy(&pix1); boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); /* Input is a boxa smoothed with a median window filter */ boxa1 = boxaRead("boxa3.ba"); boxa2 = boxaReconcileEvenOddHeight(boxa1, L_ADJUST_TOP, 80, L_ADJUST_CHOOSE_MIN, 1.05, 1); width = 100; boxaGetExtent(boxa2, &w, &h, NULL); scalefact = (l_float32)width / (l_float32)w; boxa3 = boxaTransform(boxa2, 0, 0, scalefact, scalefact); pix1 = boxaDisplayTiled(boxa3, NULL, 1500, 2, 1.0, 0, 3, 2); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 14 */ pixDisplayWithTitle(pix1, 1000, 0, NULL, rp->display); pixDestroy(&pix1); boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); /* Test serialized boxa I/O to and from memory */ data1 = l_binaryRead("boxa2.ba", &size1); boxa1 = boxaReadMem(data1, size1); boxaWriteMem(&data2, &size2, boxa1); boxa2 = boxaReadMem(data2, size2); boxaWrite("/tmp/lept/boxa/boxa1.ba", boxa1); boxaWrite("/tmp/lept/boxa/boxa2.ba", boxa2); filesAreIdentical("/tmp/lept/boxa/boxa1.ba", "/tmp/lept/boxa/boxa2.ba", &same); regTestCompareValues(rp, 1, same, 0.0); /* 15 */ boxaDestroy(&boxa1); boxaDestroy(&boxa2); lept_free(data1); lept_free(data2); /* ----------- Test pixaDisplayBoxaa() ------------ */ pixa1 = pixaReadBoth("showboxes.pac"); baa1 = boxaaRead("showboxes1.baa"); baa2 = boxaaTranspose(baa1); baa3 = boxaaTranspose(baa2); nba = boxaaGetCount(baa1); success = TRUE; for (i = 0; i < nba; i++) { boxa1 = boxaaGetBoxa(baa1, i, L_CLONE); boxa2 = boxaaGetBoxa(baa3, i, L_CLONE); boxaEqual(boxa1, boxa2, 0, NULL, &same); boxaDestroy(&boxa1); boxaDestroy(&boxa2); if (!same) success = FALSE; } /* Check that the transpose is reversible */ regTestCompareValues(rp, 1, success, 0.0); /* 16 */ pixa2 = pixaDisplayBoxaa(pixa1, baa2, L_DRAW_RGB, 2); pix1 = pixaDisplayTiledInRows(pixa2, 32, 1400, 1.0, 0, 10, 0); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 17 */ pixDisplayWithTitle(pix1, 0, 600, NULL, rp->display); fprintf(stderr, "Writing to: /tmp/lept/boxa/show.pdf\n"); l_pdfSetDateAndVersion(FALSE); pixaConvertToPdf(pixa2, 75, 1.0, 0, 0, NULL, "/tmp/lept/boxa/show.pdf"); regTestCheckFile(rp, "/tmp/lept/boxa/show.pdf"); /* 18 */ pixDestroy(&pix1); pixaDestroy(&pixa1); pixaDestroy(&pixa2); boxaaDestroy(&baa1); boxaaDestroy(&baa2); boxaaDestroy(&baa3); return regTestCleanup(rp); }
/*! * 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; }
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; }
std::vector<Figure> extractFigures(PIX *original, PageRegions &pageRegions, DocumentStatistics &docStats, bool verbose, bool showSteps, std::vector<Figure> &errors) { BOXA *bodytext = pageRegions.bodytext; BOXA *graphics = pageRegions.graphics; BOXA *captions = pageRegions.getCaptionsBoxa(); std::vector<Caption> unassigned_captions = pageRegions.captions; int total_captions = captions->n; PIXA *steps = showSteps ? pixaCreate(4) : NULL; // Add bodyText boxes to fill up the margin BOX *margin; BOX *foreground; pixClipToForeground(original, NULL, &foreground); BOX *extent; boxaGetExtent(graphics, NULL, NULL, &extent); margin = boxBoundingRegion(extent, foreground); boxDestroy(&extent); boxaGetExtent(bodytext, NULL, NULL, &extent); margin = boxBoundingRegion(margin, extent); boxDestroy(&extent); boxaGetExtent(pageRegions.other, NULL, NULL, &extent); margin = boxBoundingRegion(margin, extent); int x = margin->x - 2, y = margin->y - 2, h = margin->h + 4, w = margin->w + 4; x = std::max(x, 0); y = std::max(y, 0); h = std::min((int)original->h, h); w = std::min((int)original->w, w); boxDestroy(&margin); boxaAddBox(bodytext, boxCreate(0, 0, original->w, y), L_CLONE); boxaAddBox(bodytext, boxCreate(0, y + h, original->w, original->h - y - h), L_CLONE); boxaAddBox(bodytext, boxCreate(0, 0, x, original->h), L_CLONE); boxaAddBox(bodytext, boxCreate(x + w, 0, original->w - x - w, original->h), L_CLONE); // Add captions to body text boxaJoin(bodytext, captions, 0, captions->n); if (showSteps) pixaAddPix(steps, original, L_CLONE); // Generate proposed regions for each caption box double center = original->w / 2.0; BOXAA *allProposals = boxaaCreate(captions->n); BOXA *claimedImages = boxaCreate(captions->n); for (int i = 0; i < captions->n; i++) { BOX *captBox = boxaGetBox(captions, i, L_CLONE); BOXA *proposals = boxaCreate(4); for (int j = 0; j < bodytext->n; j++) { BOX *txtBox = boxaGetBox(bodytext, j, L_CLONE); BOX *proposal = NULL; int tolerance = 2; int horizontal = 0; int vertical = 0; boxAlignment(captBox, txtBox, tolerance, &horizontal, &vertical); if (vertical * horizontal != 0 or (vertical == 0 and horizontal == 0)) { continue; } if (vertical == 0) { if (horizontal == 1) { proposal = boxRelocateOneSide(NULL, captBox, txtBox->x + txtBox->w + 2, L_FROM_LEFT); } else if (horizontal == -1) { proposal = boxRelocateOneSide(NULL, captBox, txtBox->x - 2, L_FROM_RIGHT); } boxExpandUD(proposal, bodytext); if (horizontal == -1) { proposal->w -= captBox->w + 1; proposal->x = captBox->x + captBox->w + 1; } else if (horizontal == 1) { proposal->w -= captBox->w + 1; } } else { if (vertical == 1) { proposal = boxRelocateOneSide(NULL, captBox, txtBox->y + txtBox->h + 3, L_FROM_TOP); } else if (vertical == -1) { proposal = boxRelocateOneSide(NULL, captBox, txtBox->y - 3, L_FROM_BOT); } boxExpandLR(proposal, bodytext); if (vertical == -1) { proposal->h -= captBox->h + 1; proposal->y = captBox->y + captBox->h + 1; } else if (vertical == 1) { proposal->h -= captBox->h + 1; } } // For two columns document, captions that do not // cross the center should not have regions pass the center if (docStats.documentIsTwoColumn()) { if (captBox->x + captBox->w <= center and proposal->x + proposal->w > center) { boxRelocateOneSide(proposal, proposal, center - 1, L_FROM_RIGHT); } else if (captBox->x >= center and proposal->x < center) { boxRelocateOneSide(proposal, proposal, center + 1, L_FROM_LEFT); } } BOX *clippedProposal; pixClipBoxToForeground(original, proposal, NULL, &clippedProposal); if (clippedProposal != NULL and scoreBox(clippedProposal, pageRegions.captions.at(i).type, bodytext, graphics, claimedImages, original) > 0) { boxaAddBox(proposals, clippedProposal, L_CLONE); } } if (proposals->n > 0) { boxaaAddBoxa(allProposals, proposals, L_CLONE); } else { // Give up on this caption int on_caption = i - (total_captions - unassigned_captions.size()); errors.push_back(Figure(unassigned_captions.at(on_caption), NULL)); unassigned_captions.erase(unassigned_captions.begin() + on_caption); } } std::vector<Figure> figures = std::vector<Figure>(); if (unassigned_captions.size() == 0) { return figures; } // Now go through every possible assignment of captions // to proposals pick the highest scorign one int numConfigurations = 1; for (int i = 0; i < allProposals->n; ++i) { numConfigurations *= allProposals->boxa[i]->n; } if (verbose) printf("Found %d possible configurations\n", numConfigurations); BOXA *bestProposals = NULL; std::vector<bool> bestKeep; int bestFound = -1; double bestScore = -1; for (int onConfig = 0; onConfig < numConfigurations; ++onConfig) { // Gather the proposed regions based on the configuration number int configNum = onConfig; BOXA *proposals = boxaCreate(allProposals->n); std::vector<bool> keep; for (int i = 0; i < allProposals->n; ++i) { int numProposals = allProposals->boxa[i]->n; int selected = configNum % numProposals; configNum = configNum / numProposals; boxaAddBox(proposals, allProposals->boxa[i]->box[selected], L_COPY); } // Attempt to split any overlapping regions for (int i = 0; i < proposals->n; ++i) { for (int j = i; j < proposals->n; ++j) { BOX *p1 = proposals->box[i]; BOX *p2 = proposals->box[j]; int eq; boxEqual(p1, p2, &eq); if (not eq) continue; int vertical, horizontal; boxAlignment(unassigned_captions.at(i).boundingBox, unassigned_captions.at(j).boundingBox, 2, &horizontal, &vertical); if (vertical == 0 or horizontal != 0) continue; double split = splitBoxVertical(original, p1); if (split > 0) { BOX *topClipped; BOX *botClipped; BOX *top = boxRelocateOneSide(NULL, p1, split - 1, L_FROM_BOT); pixClipBoxToForeground(original, top, NULL, &topClipped); BOX *bot = boxRelocateOneSide(NULL, p1, split + 1, L_FROM_TOP); pixClipBoxToForeground(original, bot, NULL, &botClipped); if (vertical == -1) { proposals->box[i] = topClipped; proposals->box[j] = botClipped; } else { proposals->box[i] = botClipped; proposals->box[j] = topClipped; } if (verbose) printf("Split a region vertically\n"); } } } if (showSteps) { pixaAddPix(steps, pixDrawBoxa(original, proposals, 4, 0xff000000), L_CLONE); } // Score the proposals int numFound = 0; double totalScore = 0; for (int i = 0; i < proposals->n; ++i) { double score = scoreBox(proposals->box[i], pageRegions.captions.at(i).type, bodytext, graphics, proposals, original); totalScore += score; if (score > 0) { numFound += 1; keep.push_back(true); } else { keep.push_back(false); } } // Switch in for the current best needed if (numFound > bestFound or (numFound == bestFound and totalScore > bestScore)) { bestFound = numFound; bestScore = totalScore; bestProposals = proposals; bestKeep = keep; } } if (showSteps) { BOX *clip; PIXA *show = pixaCreate(4); pixClipBoxToForeground(original, NULL, NULL, &clip); int pad = 10; clip->x -= 10; clip->y -= 10; clip->w += pad * 2; clip->h += pad * 2; for (int i = 0; i < steps->n; ++i) { pixaAddPix(show, pixClipRectangle(steps->pix[i], clip, NULL), L_CLONE); } pixDisplay(pixaDisplayTiled(pixaConvertTo32(show), 4000, 1, 30), 0, 0); } for (int i = 0; i < bestProposals->n; ++i) { if (bestKeep.at(i)) { BOX *imageBox = bestProposals->box[i]; int pad = 2; imageBox->x -= pad; imageBox->y -= pad; imageBox->w += pad * 2; imageBox->h += pad * 2; figures.push_back(Figure(unassigned_captions.at(i), imageBox)); } else { errors.push_back(Figure(unassigned_captions.at(i), NULL)); } } return figures; }
void k2pdfopt_reflow_bmp(KOPTContext *kctx) { K2PDFOPT_SETTINGS _k2settings, *k2settings; MASTERINFO _masterinfo, *masterinfo; WILLUSBITMAP _srcgrey, *srcgrey; WILLUSBITMAP *src, *dst; BMPREGION region; int i, bw, marbot, marleft; src = &kctx->src; srcgrey = &_srcgrey; bmp_init(srcgrey); k2settings = &_k2settings; masterinfo = &_masterinfo; /* Initialize settings */ k2pdfopt_settings_init_from_koptcontext(k2settings, kctx); k2pdfopt_settings_quick_sanity_check(k2settings); /* Init for new source doc */ k2pdfopt_settings_new_source_document_init(k2settings); /* Init master output structure */ masterinfo_init(masterinfo, k2settings); wrapbmp_init(&masterinfo->wrapbmp, k2settings->dst_color); /* Init new source bitmap */ bmpregion_init(®ion); masterinfo_new_source_page_init(masterinfo, k2settings, src, srcgrey, NULL, ®ion, k2settings->src_rot, NULL, NULL, 1, -1, NULL ); /* Set output size */ k2pdfopt_settings_set_margins_and_devsize(k2settings,®ion,masterinfo,-1.,0); /* Process single source page */ bmpregion_source_page_add(®ion, k2settings, masterinfo, 1, 0); wrapbmp_flush(masterinfo, k2settings, 0); if (fabs(k2settings->dst_gamma - 1.0) > .001) bmp_gamma_correct(&masterinfo->bmp, &masterinfo->bmp, k2settings->dst_gamma); /* copy master bitmap to context dst bitmap */ dst = &kctx->dst; marbot = (int) (k2settings->dst_dpi * k2settings->dstmargins.box[1] + .5); marleft = (int) (k2settings->dst_dpi * k2settings->dstmargins.box[0] + .5); dst->bpp = masterinfo->bmp.bpp; dst->width = masterinfo->bmp.width; dst->height = masterinfo->rows > kctx->page_height ? masterinfo->rows + marbot : kctx->page_height; bmp_alloc(dst); bmp_fill(dst, 255, 255, 255); bw = bmp_bytewidth(&masterinfo->bmp); for (i = 0; i < masterinfo->rows; i++) memcpy(bmp_rowptr_from_top(dst, i), bmp_rowptr_from_top(&masterinfo->bmp, i), bw); kctx->page_width = kctx->dst.width; kctx->page_height = kctx->dst.height; kctx->precache = 0; int j; BOXA *rboxa = boxaCreate(masterinfo->rectmaps.n); BOXA *nboxa = boxaCreate(masterinfo->rectmaps.n); for (j = 0; j < masterinfo->rectmaps.n; j++) { WRECTMAP * rectmap = &masterinfo->rectmaps.wrectmap[j]; rectmap->coords[1].x += marleft; BOX* rlbox = boxCreate(rectmap->coords[1].x, rectmap->coords[1].y, rectmap->coords[2].x, rectmap->coords[2].y); BOX* nlbox = boxCreate(rectmap->coords[0].x*k2settings->src_dpi/rectmap->srcdpiw/kctx->zoom + kctx->bbox.x0, rectmap->coords[0].y*k2settings->src_dpi/rectmap->srcdpih/kctx->zoom + kctx->bbox.y0, rectmap->coords[2].x*k2settings->src_dpi/rectmap->srcdpiw/kctx->zoom, rectmap->coords[2].y*k2settings->src_dpi/rectmap->srcdpih/kctx->zoom); boxaAddBox(rboxa, rlbox, L_INSERT); boxaAddBox(nboxa, nlbox, L_INSERT); wrectmaps_add_wrectmap(&kctx->rectmaps, rectmap); /*printf("rectmap:coords:\t%.1f %.1f\t%.1f %.1f\t%.1f %.1f\t%.1f %.1f\n", rectmap->coords[0].x, rectmap->coords[0].y, rectmap->coords[1].x, rectmap->coords[1].y, rectmap->coords[2].x, rectmap->coords[2].y, rectmap->srcdpiw, rectmap->srcdpih);*/ } /* 2D sort the bounding boxes of these words. */ BOXAA *rbaa = boxaSort2d(rboxa, NULL, 3, -5, 5); BOXAA *nbaa = boxaSort2d(nboxa, NULL, 3, -5, 5); /* Flatten the boxaa, saving the boxa index for each box */ kctx->rboxa = boxaaFlattenToBoxa(rbaa, &kctx->rnai, L_CLONE); kctx->nboxa = boxaaFlattenToBoxa(nbaa, &kctx->nnai, L_CLONE); boxaDestroy(&rboxa); boxaaDestroy(&rbaa); boxaDestroy(&nboxa); boxaaDestroy(&nbaa); bmp_free(src); bmp_free(srcgrey); bmpregion_free(®ion); masterinfo_free(masterinfo, k2settings); }