/*! * 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; }
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; }
/*! * 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; }
/*! * 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; }
/*! * 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; }
/*! * 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; }
/*! * \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; }
/*! * 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; }
/*! * 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; }
/*! * 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; }
/*! * 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; }
/*! * pixaAddBorderGeneral() * * Input: pixad (can be null or equal to pixas) * pixas (containing pix of all depths; colormap ok) * left, right, top, bot (number of pixels added) * val (value of added border pixels) * Return: pixad (with border added to each pix), including on error * * Notes: * (1) For binary images: * white: val = 0 * black: val = 1 * For grayscale images: * white: val = 2 ** d - 1 * black: val = 0 * For rgb color images: * white: val = 0xffffff00 * black: val = 0 * For colormapped images, use 'index' found this way: * white: pixcmapGetRankIntensity(cmap, 1.0, &index); * black: pixcmapGetRankIntensity(cmap, 0.0, &index); * (2) For in-place replacement of each pix with a bordered version, * use @pixad = @pixas. To make a new pixa, use @pixad = NULL. * (3) In both cases, the boxa has sides adjusted as if it were * expanded by the border. */ PIXA * pixaAddBorderGeneral(PIXA *pixad, PIXA *pixas, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_uint32 val) { l_int32 i, n, nbox; BOX *box; BOXA *boxad; PIX *pixs, *pixd; PROCNAME("pixaAddBorderGeneral"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, pixad); if (left < 0 || right < 0 || top < 0 || bot < 0) return (PIXA *)ERROR_PTR("negative border added!", procName, pixad); if (pixad && (pixad != pixas)) return (PIXA *)ERROR_PTR("pixad defined but != pixas", procName, pixad); n = pixaGetCount(pixas); if (!pixad) pixad = pixaCreate(n); for (i = 0; i < n; i++) { pixs = pixaGetPix(pixas, i, L_CLONE); pixd = pixAddBorderGeneral(pixs, left, right, top, bot, val); if (pixad == pixas) /* replace */ pixaReplacePix(pixad, i, pixd, NULL); else pixaAddPix(pixad, pixd, L_INSERT); pixDestroy(&pixs); } nbox = pixaGetBoxaCount(pixas); boxad = pixaGetBoxa(pixad, L_CLONE); for (i = 0; i < nbox; i++) { if ((box = pixaGetBox(pixas, i, L_COPY)) == NULL) { L_WARNING_INT("box %d not found", procName, i); break; } boxAdjustSides(box, box, -left, right, -top, bot); if (pixad == pixas) /* replace */ boxaReplaceBox(boxad, i, box); else boxaAddBox(boxad, box, L_INSERT); } boxaDestroy(&boxad); return pixad; }
/*! * \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; }
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; }
/*! * 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; }
/* 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); }
/*! * pixaAddBox() * * Input: pixa * box * copyflag (L_INSERT, L_COPY, L_CLONE) * Return: 0 if OK, 1 on error */ l_int32 pixaAddBox(PIXA *pixa, BOX *box, l_int32 copyflag) { PROCNAME("pixaAddBox"); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!box) return ERROR_INT("box not defined", procName, 1); if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE) return ERROR_INT("invalid copyflag", procName, 1); boxaAddBox(pixa->boxa, box, copyflag); return 0; }
/*! * 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; }
/*! * boxaaAddBox() * * Input: boxaa * index (of boxa with boxaa) * box (to be added) * accessflag (L_INSERT, L_COPY or L_CLONE) * Return: 0 if OK, 1 on error * * Notes: * (1) Adds to an existing boxa only. */ l_int32 boxaaAddBox(BOXAA *baa, l_int32 index, BOX *box, l_int32 accessflag) { l_int32 n; BOXA *boxa; PROCNAME("boxaaAddBox"); if (!baa) return ERROR_INT("baa not defined", procName, 1); n = boxaaGetCount(baa); if (index < 0 || index >= n) return ERROR_INT("index not valid", procName, 1); if (accessflag != L_INSERT && accessflag != L_COPY && accessflag != L_CLONE) return ERROR_INT("invalid accessflag", procName, 1); boxa = boxaaGetBoxa(baa, index, L_CLONE); boxaAddBox(boxa, box, accessflag); boxaDestroy(&boxa); return 0; }
/*! * boxaJoin() * * Input: boxad (dest boxa; add to this one) * boxas (source boxa; add from this one) * istart (starting index in nas) * iend (ending index in nas; use 0 to cat all) * Return: 0 if OK, 1 on error * * Notes: * (1) This appends a clone of each indicated box in boxas to boxad * (2) istart < 0 is taken to mean 'read from the start' (istart = 0) * (3) iend <= 0 means 'read to the end' */ l_int32 boxaJoin(BOXA *boxad, BOXA *boxas, l_int32 istart, l_int32 iend) { l_int32 ns, i; BOX *box; PROCNAME("boxaJoin"); if (!boxad) return ERROR_INT("boxad not defined", procName, 1); if (!boxas) return ERROR_INT("boxas not defined", procName, 1); if ((ns = boxaGetCount(boxas)) == 0) { L_INFO("empty boxas", procName); return 0; } if (istart < 0) istart = 0; if (istart >= ns) return ERROR_INT("istart out of bounds", procName, 1); if (iend <= 0) iend = ns - 1; if (iend >= ns) return ERROR_INT("iend out of bounds", procName, 1); if (istart > iend) return ERROR_INT("istart > iend; nothing to add", procName, 1); for (i = istart; i <= iend; i++) { box = boxaGetBox(boxas, i, L_CLONE); boxaAddBox(boxad, box, L_INSERT); } 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; }
/*! * \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; }
// 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; } } }
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); }