// create a union of two arbitrary pix Pix *CubeLineSegmenter::PixUnion(Pix *dest_pix, Box *dest_box, Pix *src_pix, Box *src_box) { // compute dimensions of union rect BOX *union_box = boxBoundingRegion(src_box, dest_box); // create the union pix Pix *union_pix = pixCreate(union_box->w, union_box->h, src_pix->d); if (union_pix == NULL) { return NULL; } // blt the src and dest pix pixRasterop(union_pix, src_box->x - union_box->x, src_box->y - union_box->y, src_box->w, src_box->h, PIX_SRC | PIX_DST, src_pix, 0, 0); pixRasterop(union_pix, dest_box->x - union_box->x, dest_box->y - union_box->y, dest_box->w, dest_box->h, PIX_SRC | PIX_DST, dest_pix, 0, 0); // replace the dest_box *dest_box = *union_box; boxDestroy(&union_box); return union_pix; }
/*! * \brief pixTilingPaintTile() * * \param[in] pixd dest: paint tile onto this, without overlap * \param[in] i tile row index * \param[in] j tile column index * \param[in] pixs source: tile to be painted from * \param[in] pt pixtiling struct * \return 0 if OK, 1 on error */ l_int32 pixTilingPaintTile(PIX *pixd, l_int32 i, l_int32 j, PIX *pixs, PIXTILING *pt) { l_int32 w, h; PROCNAME("pixTilingPaintTile"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pt) return ERROR_INT("pt not defined", procName, 1); if (i < 0 || i >= pt->ny) return ERROR_INT("invalid row index i", procName, 1); if (j < 0 || j >= pt->nx) return ERROR_INT("invalid column index j", procName, 1); /* Strip added border pixels off if requested */ pixGetDimensions(pixs, &w, &h, NULL); if (pt->strip == TRUE) { pixRasterop(pixd, j * pt->w, i * pt->h, w - 2 * pt->xoverlap, h - 2 * pt->yoverlap, PIX_SRC, pixs, pt->xoverlap, pt->yoverlap); } else { pixRasterop(pixd, j * pt->w, i * pt->h, w, h, PIX_SRC, pixs, 0, 0); } return 0; }
void Java_com_example_ocr_Pixa_nativeMergeAndReplacePix(JNIEnv *env, jclass clazz, jint nativePixa, jint indexA, jint indexB) { PIXA *pixa = (PIXA *) nativePixa; l_int32 op; l_int32 x, y, w, h; l_int32 dx, dy, dw, dh; PIX *pixs, *pixd; BOX *boxA, *boxB, *boxd; boxA = pixaGetBox(pixa, indexA, L_CLONE); boxB = pixaGetBox(pixa, indexB, L_CLONE); boxd = boxBoundingRegion(boxA, boxB); boxGetGeometry(boxd, &x, &y, &w, &h); pixd = pixCreate(w, h, 1); op = PIX_SRC | PIX_DST; pixs = pixaGetPix(pixa, indexA, L_CLONE); boxGetGeometry(boxA, &dx, &dy, &dw, &dh); pixRasterop(pixd, dx - x, dy - y, dw, dh, op, pixs, 0, 0); pixDestroy(&pixs); boxDestroy(&boxA); pixs = pixaGetPix(pixa, indexB, L_CLONE); boxGetGeometry(boxB, &dx, &dy, &dw, &dh); pixRasterop(pixd, dx - x, dy - y, dw, dh, op, pixs, 0, 0); pixDestroy(&pixs); boxDestroy(&boxB); pixaReplacePix(pixa, indexA, pixd, boxd); }
/*! * pixRasteropHip() * * Input: pixd (in-place operation) * by (top of horizontal band) * bh (height of horizontal band) * hshift (horizontal shift of band; hshift > 0 is to right) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * Return: 0 if OK; 1 on error * * Notes: * (1) This rasterop translates a horizontal band of the * image either left or right, bringing in either white * or black pixels from outside the image. * (2) The horizontal band extends the full width of pixd. * (3) If a colormap exists, the nearest color to white or black * is brought in. */ l_int32 pixRasteropHip(PIX *pixd, l_int32 by, l_int32 bh, l_int32 hshift, l_int32 incolor) { l_int32 w, h, d, index, op; PIX *pixt; PIXCMAP *cmap; PROCNAME("pixRasteropHip"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return ERROR_INT("invalid value for incolor", procName, 1); if (bh <= 0) return ERROR_INT("bh must be > 0", procName, 1); if (hshift == 0) return 0; pixGetDimensions(pixd, &w, &h, &d); rasteropHipLow(pixGetData(pixd), h, d, pixGetWpl(pixd), by, bh, hshift); cmap = pixGetColormap(pixd); if (!cmap) { if ((d == 1 && incolor == L_BRING_IN_BLACK) || (d > 1 && incolor == L_BRING_IN_WHITE)) op = PIX_SET; else op = PIX_CLR; /* Set the pixels brought in at left or right */ if (hshift > 0) pixRasterop(pixd, 0, by, hshift, bh, op, NULL, 0, 0); else /* hshift < 0 */ pixRasterop(pixd, w + hshift, by, -hshift, bh, op, NULL, 0, 0); return 0; } /* Get the nearest index and fill with that */ if (incolor == L_BRING_IN_BLACK) pixcmapGetRankIntensity(cmap, 0.0, &index); else /* white */ pixcmapGetRankIntensity(cmap, 1.0, &index); pixt = pixCreate(L_ABS(hshift), bh, d); pixSetAllArbitrary(pixt, index); if (hshift > 0) pixRasterop(pixd, 0, by, hshift, bh, PIX_SRC, pixt, 0, 0); else /* hshift < 0 */ pixRasterop(pixd, w + hshift, by, -hshift, bh, PIX_SRC, pixt, 0, 0); pixDestroy(&pixt); return 0; }
main(int argc, char **argv) { l_int32 w, h, d, n; char *filein1, *filein2, *fileout; PIX *pixs1, *pixs2, *pixd; static char mainName[] = "bincompare"; if (argc != 4) exit(ERROR_INT(" Syntax: bincompare filein1 filein2 fileout", mainName, 1)); filein1 = argv[1]; filein2 = argv[2]; fileout = argv[3]; if ((pixs1 = pixRead(filein1)) == NULL) exit(ERROR_INT("pixs1 not made", mainName, 1)); if ((pixs2 = pixRead(filein2)) == NULL) exit(ERROR_INT("pixs2 not made", mainName, 1)); w = pixGetWidth(pixs1); h = pixGetHeight(pixs1); d = pixGetDepth(pixs1); if (d != 1) exit(ERROR_INT("pixs1 not binary", mainName, 1)); pixCountPixels(pixs1, &n, NULL); fprintf(stderr, "Number of fg pixels in file1 = %d\n", n); pixCountPixels(pixs2, &n, NULL); fprintf(stderr, "Number of fg pixels in file2 = %d\n", n); #if XOR fprintf(stderr, "xor: 1 ^ 2\n"); pixRasterop(pixs1, 0, 0, w, h, PIX_SRC ^ PIX_DST, pixs2, 0, 0); pixCountPixels(pixs1, &n, NULL); fprintf(stderr, "Number of fg pixels in XOR = %d\n", n); pixWrite(fileout, pixs1, IFF_PNG); #elif SUBTRACT_1_FROM_2 fprintf(stderr, "subtract: 2 - 1\n"); pixRasterop(pixs1, 0, 0, w, h, PIX_SRC & PIX_NOT(PIX_DST), pixs2, 0, 0); pixCountPixels(pixs1, &n, NULL); fprintf(stderr, "Number of fg pixels in 2 - 1 = %d\n", n); pixWrite(fileout, pixs1, IFF_PNG); #elif SUBTRACT_2_FROM_1 fprintf(stderr, "subtract: 1 - 2\n"); pixRasterop(pixs1, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC), pixs2, 0, 0); pixCountPixels(pixs1, &n, NULL); fprintf(stderr, "Number of fg pixels in 1 - 2 = %d\n", n); pixWrite(fileout, pixs1, IFF_PNG); #else fprintf(stderr, "no comparison selected\n"); #endif return 0; }
// Tests each blob in the list to see if it is certain non-text using 2 // conditions: // 1. blob overlaps a cell with high value in noise_density_ (previously set // by ComputeNoiseDensity). // OR 2. The blob overlaps more than max_blob_overlaps in *this grid. This // condition is disabled with max_blob_overlaps == -1. // If it does, the blob is declared non-text, and is used to mark up the // nontext_mask. Such blobs are fully deleted, and non-noise blobs have their // neighbours reset, as they may now point to deleted data. // WARNING: The blobs list blobs may be in the *this grid, but they are // not removed. If any deleted blobs might be in *this, then this must be // Clear()ed immediately after MarkAndDeleteNonTextBlobs is called. // If the win is not NULL, deleted blobs are drawn on it in red, and kept // blobs are drawn on it in ok_color. void CCNonTextDetect::MarkAndDeleteNonTextBlobs(BLOBNBOX_LIST* blobs, int max_blob_overlaps, ScrollView* win, ScrollView::Color ok_color, Pix* nontext_mask) { int imageheight = tright().y() - bleft().x(); BLOBNBOX_IT blob_it(blobs); BLOBNBOX_LIST dead_blobs; BLOBNBOX_IT dead_it(&dead_blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); TBOX box = blob->bounding_box(); if (!noise_density_->RectMostlyOverThreshold(box, max_noise_count_) && (max_blob_overlaps < 0 || !BlobOverlapsTooMuch(blob, max_blob_overlaps))) { blob->ClearNeighbours(); #ifndef GRAPHICS_DISABLED if (win != NULL) blob->plot(win, ok_color, ok_color); #endif // GRAPHICS_DISABLED } else { if (noise_density_->AnyZeroInRect(box)) { // There is a danger that the bounding box may overlap real text, so // we need to render the outline. Pix* blob_pix = blob->cblob()->render_outline(); pixRasterop(nontext_mask, box.left(), imageheight - box.top(), box.width(), box.height(), PIX_SRC | PIX_DST, blob_pix, 0, 0); pixDestroy(&blob_pix); } else { if (box.area() < gridsize() * gridsize()) { // It is a really bad idea to make lots of small components in the // photo mask, so try to join it to a bigger area by expanding the // box in a way that does not touch any zero noise density cell. box = AttemptBoxExpansion(box, *noise_density_, gridsize()); } // All overlapped cells are non-zero, so just mark the rectangle. pixRasterop(nontext_mask, box.left(), imageheight - box.top(), box.width(), box.height(), PIX_SET, NULL, 0, 0); } #ifndef GRAPHICS_DISABLED if (win != NULL) blob->plot(win, ScrollView::RED, ScrollView::RED); #endif // GRAPHICS_DISABLED // It is safe to delete the cblob now, as it isn't used by the grid // or BlobOverlapsTooMuch, and the BLOBNBOXes will go away with the // dead_blobs list. // TODO(rays) delete the delete when the BLOBNBOX destructor deletes // the cblob. delete blob->cblob(); dead_it.add_to_end(blob_it.extract()); } } }
/*! * pixaDisplay() * * Input: pixa * w, h (if set to 0, determines the size from the * b.b. of the components in pixa) * Return: pix, or null on error * * Notes: * (1) This uses the boxes to place each pix in the rendered composite. * (2) Set w = h = 0 to use the b.b. of the components to determine * the size of the returned pix. * (3) Uses the first pix in pixa to determine the depth. * (4) The background is written "white". On 1 bpp, each successive * pix is "painted" (adding foreground), whereas for grayscale * or color each successive pix is blitted with just the src. * (5) If the pixa is empty, returns an empty 1 bpp pix. */ PIX * pixaDisplay(PIXA *pixa, l_int32 w, l_int32 h) { l_int32 i, n, d, xb, yb, wb, hb; BOXA *boxa; PIX *pixt, *pixd; PROCNAME("pixaDisplay"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); n = pixaGetCount(pixa); if (n == 0 && w == 0 && h == 0) return (PIX *)ERROR_PTR("no components; no size", procName, NULL); if (n == 0) { L_WARNING("no components; returning empty 1 bpp pix", procName); return pixCreate(w, h, 1); } /* If w and h not input, determine the minimum size required * to contain the origin and all c.c. */ if (w == 0 || h == 0) { boxa = pixaGetBoxa(pixa, L_CLONE); boxaGetExtent(boxa, &w, &h, NULL); boxaDestroy(&boxa); } /* Use the first pix in pixa to determine the depth. */ pixt = pixaGetPix(pixa, 0, L_CLONE); d = pixGetDepth(pixt); pixDestroy(&pixt); if ((pixd = pixCreate(w, h, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); if (d > 1) pixSetAll(pixd); for (i = 0; i < n; i++) { if (pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb)) { L_WARNING("no box found!", procName); continue; } pixt = pixaGetPix(pixa, i, L_CLONE); if (d == 1) pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pixt, 0, 0); else pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pixt, 0, 0); pixDestroy(&pixt); } return pixd; }
// Renders the outline to the given pix, with left and top being // the coords of the upper-left corner of the pix. void C_OUTLINE::render(int left, int top, Pix* pix) const { ICOORD pos = start; for (int stepindex = 0; stepindex < stepcount; ++stepindex) { ICOORD next_step = step(stepindex); if (next_step.y() < 0) { pixRasterop(pix, 0, top - pos.y(), pos.x() - left, 1, PIX_NOT(PIX_DST), NULL, 0, 0); } else if (next_step.y() > 0) { pixRasterop(pix, 0, top - pos.y() - 1, pos.x() - left, 1, PIX_NOT(PIX_DST), NULL, 0, 0); } pos += next_step; } }
/*! * \brief pixConnCompTransform() * * \param[in] pixs 1 bpp * \param[in] connect connectivity: 4 or 8 * \param[in] depth of pixd: 8 or 16 bpp; use 0 for auto determination * \return pixd 8, 16 or 32 bpp, or NULL on error * * <pre> * Notes: * (1) pixd is 8, 16 or 32 bpp, and the pixel values label the * fg component, starting with 1. Pixels in the bg are labelled 0. * (2) If %depth = 0, the depth of pixd is 8 if the number of c.c. * is less than 254, 16 if the number of c.c is less than 0xfffe, * and 32 otherwise. * (3) If %depth = 8, the assigned label for the n-th component is * 1 + n % 254. We use mod 254 because 0 is uniquely assigned * to black: e.g., see pixcmapCreateRandom(). Likewise, * if %depth = 16, the assigned label uses mod(2^16 - 2), and * if %depth = 32, no mod is taken. * </pre> */ PIX * pixConnCompTransform(PIX *pixs, l_int32 connect, l_int32 depth) { l_int32 i, n, index, w, h, xb, yb, wb, hb; BOXA *boxa; PIX *pix1, *pix2, *pixd; PIXA *pixa; PROCNAME("pixConnCompTransform"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connect != 4 && connect != 8) return (PIX *)ERROR_PTR("connectivity must be 4 or 8", procName, NULL); if (depth != 0 && depth != 8 && depth != 16 && depth != 32) return (PIX *)ERROR_PTR("depth must be 0, 8, 16 or 32", procName, NULL); boxa = pixConnComp(pixs, &pixa, connect); n = pixaGetCount(pixa); boxaDestroy(&boxa); pixGetDimensions(pixs, &w, &h, NULL); if (depth == 0) { if (n < 254) depth = 8; else if (n < 0xfffe) depth = 16; else depth = 32; } pixd = pixCreate(w, h, depth); pixSetSpp(pixd, 1); if (n == 0) { /* no fg */ pixaDestroy(&pixa); return pixd; } /* Label each component and blit it in */ for (i = 0; i < n; i++) { pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb); pix1 = pixaGetPix(pixa, i, L_CLONE); if (depth == 8) { index = 1 + (i % 254); pix2 = pixConvert1To8(NULL, pix1, 0, index); } else if (depth == 16) { index = 1 + (i % 0xfffe); pix2 = pixConvert1To16(NULL, pix1, 0, index); } else { /* depth == 32 */ index = 1 + i; pix2 = pixConvert1To32(NULL, pix1, 0, index); } pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix2, 0, 0); pixDestroy(&pix1); pixDestroy(&pix2); } pixaDestroy(&pixa); return pixd; }
/** * Returns an image of the current object at the given level in greyscale * if available in the input. To guarantee a binary image use BinaryImage. * NOTE that in order to give the best possible image, the bounds are * expanded slightly over the binary connected component, by the supplied * padding, so the top-left position of the returned image is returned * in (left,top). These will most likely not match the coordinates * returned by BoundingBox. * Use pixDestroy to delete the image after use. */ Pix* PageIterator::GetImage(PageIteratorLevel level, int padding, int* left, int* top) const { int right, bottom; if (!BoundingBox(level, left, top, &right, &bottom)) return NULL; Pix* pix = tesseract_->pix_grey(); if (pix == NULL) return GetBinaryImage(level); // Expand the box. *left = MAX(*left - padding, 0); *top = MAX(*top - padding, 0); right = MIN(right + padding, rect_width_); bottom = MIN(bottom + padding, rect_height_); Box* box = boxCreate(*left, *top, right - *left, bottom - *top); Pix* grey_pix = pixClipRectangle(pix, box, NULL); boxDestroy(&box); if (level == RIL_BLOCK) { Pix* mask = it_->block()->block->render_mask(); Pix* expanded_mask = pixCreate(right - *left, bottom - *top, 1); pixRasterop(expanded_mask, padding, padding, pixGetWidth(mask), pixGetHeight(mask), PIX_SRC, mask, 0, 0); pixDestroy(&mask); pixDilateBrick(expanded_mask, expanded_mask, 2*padding + 1, 2*padding + 1); pixInvert(expanded_mask, expanded_mask); pixSetMasked(grey_pix, expanded_mask, 255); pixDestroy(&expanded_mask); } return grey_pix; }
/*! * pixAddWithIndicator() * * Input: pixs (1 bpp pix from which components are added; in-place) * pixa (of connected components, some of which will be put * into pixs) * na (numa indicator: add components corresponding to 1s) * Return: 0 if OK, 1 on error * * Notes: * (1) This complements pixRemoveWithIndicator(). Here, the selected * components are added to pixs. */ l_int32 pixAddWithIndicator(PIX *pixs, PIXA *pixa, NUMA *na) { l_int32 i, n, ival, x, y, w, h; BOX *box; PIX *pix; PROCNAME("pixAddWithIndicator"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!na) return ERROR_INT("na not defined", procName, 1); n = pixaGetCount(pixa); if (n != numaGetCount(na)) return ERROR_INT("pixa and na sizes not equal", procName, 1); for (i = 0; i < n; i++) { numaGetIValue(na, i, &ival); if (ival == 1) { pix = pixaGetPix(pixa, i, L_CLONE); box = pixaGetBox(pixa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); pixRasterop(pixs, x, y, w, h, PIX_SRC | PIX_DST, pix, 0, 0); boxDestroy(&box); pixDestroy(&pix); } } return 0; }
// Compute the connected components in a line Boxa * CubeLineSegmenter::ComputeLineConComps(Pix *line_mask_pix, Box *line_box, Pixa **con_comps_pixa) { // clone the line mask Pix *line_pix = pixClone(line_mask_pix); if (line_pix == NULL) { return NULL; } // AND with the image to get the actual line pixRasterop(line_pix, 0, 0, line_pix->w, line_pix->h, PIX_SRC & PIX_DST, img_, line_box->x, line_box->y); // compute the connected components of the line to be merged Boxa *line_con_comps = pixConnComp(line_pix, con_comps_pixa, 8); pixDestroy(&line_pix); // offset boxes by the bbox of the line for (int con = 0; con < line_con_comps->n; con++) { line_con_comps->box[con]->x += line_box->x; line_con_comps->box[con]->y += line_box->y; } return line_con_comps; }
main(int argc, char **argv) { l_int32 i, j, equal; PIX *pixs, *pixt, *pixd; static char mainName[] = "rasteropip_reg"; pixs = pixRead("test8.jpg"); pixt = pixCopy(NULL, pixs); /* Copy, in-place and one COLUMN at a time, from the right side to the left side. */ for (j = 0; j < 200; j++) pixRasterop(pixs, 20 + j, 20, 1, 250, PIX_SRC, pixs, 250 + j, 20); pixDisplay(pixs, 50, 50); /* Copy, in-place and one ROW at a time, from the right side to the left side. */ for (i = 0; i < 250; i++) pixRasterop(pixt, 20, 20 + i, 200, 1, PIX_SRC, pixt, 250, 20 + i); pixDisplay(pixt, 620, 50); /* Test */ pixEqual(pixs, pixt, &equal); if (equal) fprintf(stderr, "OK: images are the same\n"); else fprintf(stderr, "Error: images are different\n"); pixWrite("/tmp/junkpix.png", pixs, IFF_PNG); pixDestroy(&pixs); pixDestroy(&pixt); /* Show the mirrored border, which uses the general pixRasterop() on an image in-place. */ pixs = pixRead("test8.jpg"); pixt = pixRemoveBorder(pixs, 40); pixd = pixAddMirroredBorder(pixt, 25, 25, 25, 25); pixDisplay(pixd, 50, 550); pixDestroy(&pixs); pixDestroy(&pixt); pixDestroy(&pixd); return 0; }
main(int argc, char **argv) { l_int32 i, j, w, h, same, width, height, cx, cy; l_uint32 val; PIX *pixs, *pixse, *pixd1, *pixd2; SEL *sel; static char mainName[] = "rasterop_reg"; if (argc != 1) return ERROR_INT(" Syntax: rasterop_reg", mainName, 1); pixs = pixRead("feyn.tif"); for (width = 1; width <= 25; width += 3) { for (height = 1; height <= 25; height += 4) { cx = width / 2; cy = height / 2; /* Dilate using an actual sel */ sel = selCreateBrick(height, width, cy, cx, SEL_HIT); pixd1 = pixDilate(NULL, pixs, sel); /* Dilate using a pix as a sel */ pixse = pixCreate(width, height, 1); pixSetAll(pixse); pixd2 = pixCopy(NULL, pixs); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val); if (val) pixRasterop(pixd2, j - cx, i - cy, width, height, PIX_SRC | PIX_DST, pixse, 0, 0); } } pixEqual(pixd1, pixd2, &same); if (same == 1) fprintf(stderr, "Correct for (%d,%d)\n", width, height); else { fprintf(stderr, "Error: results are different!\n"); fprintf(stderr, "SE: width = %d, height = %d\n", width, height); pixWrite("/tmp/junkout1", pixd1, IFF_PNG); pixWrite("/tmp/junkout2", pixd2, IFF_PNG); return 1; } pixDestroy(&pixse); pixDestroy(&pixd1); pixDestroy(&pixd2); selDestroy(&sel); } } pixDestroy(&pixs); return 0; }
/*! * pixEmbedForRotation() * * Input: pixs (1, 2, 4, 8, 32 bpp rgb) * angle (radians; clockwise is positive) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * width (original width; use 0 to avoid embedding) * height (original height; use 0 to avoid embedding) * Return: pixd, or null on error * * Notes: * (1) For very small rotations, just return a clone. * (2) Generate larger image to embed pixs if necessary, and * place the center of the input image in the center. * (3) Rotation brings either white or black pixels in * from outside the image. For colormapped images where * there is no white or black, a new color is added if * possible for these pixels; otherwise, either the * lightest or darkest color is used. In most cases, * the colormap will be removed prior to rotation. * (4) The dest is to be expanded so that no image pixels * are lost after rotation. Input of the original width * and height allows the expansion to stop at the maximum * required size, which is a square with side equal to * sqrt(w*w + h*h). * (5) For an arbitrary angle, the expansion can be found by * considering the UL and UR corners. As the image is * rotated, these move in an arc centered at the center of * the image. Normalize to a unit circle by dividing by half * the image diagonal. After a rotation of T radians, the UL * and UR corners are at points T radians along the unit * circle. Compute the x and y coordinates of both these * points and take the max of absolute values; these represent * the half width and half height of the containing rectangle. * The arithmetic is done using formulas for sin(a+b) and cos(a+b), * where b = T. For the UR corner, sin(a) = h/d and cos(a) = w/d. * For the UL corner, replace a by (pi - a), and you have * sin(pi - a) = h/d, cos(pi - a) = -w/d. The equations * given below follow directly. */ PIX * pixEmbedForRotation(PIX *pixs, l_float32 angle, l_int32 incolor, l_int32 width, l_int32 height) { l_int32 w, h, d, w1, h1, w2, h2, maxside, wnew, hnew, xoff, yoff, setcolor; l_float64 sina, cosa, fw, fh; PIX *pixd; PROCNAME("pixEmbedForRotation"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *)ERROR_PTR("invalid incolor", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); /* Test if big enough to hold any rotation of the original image */ pixGetDimensions(pixs, &w, &h, &d); maxside = (l_int32)(sqrt((l_float64)(width * width) + (l_float64)(height * height)) + 0.5); if (w >= maxside && h >= maxside) /* big enough */ return pixClone(pixs); /* Find the new sizes required to hold the image after rotation. * Note that the new dimensions must be at least as large as those * of pixs, because we're rasterop-ing into it before rotation. */ cosa = cos(angle); sina = sin(angle); fw = (l_float64)w; fh = (l_float64)h; w1 = (l_int32)(L_ABS(fw * cosa - fh * sina) + 0.5); w2 = (l_int32)(L_ABS(-fw * cosa - fh * sina) + 0.5); h1 = (l_int32)(L_ABS(fw * sina + fh * cosa) + 0.5); h2 = (l_int32)(L_ABS(-fw * sina + fh * cosa) + 0.5); wnew = L_MAX(w, L_MAX(w1, w2)); hnew = L_MAX(h, L_MAX(h1, h2)); if ((pixd = pixCreate(wnew, hnew, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopySpp(pixd, pixs); pixCopyText(pixd, pixs); xoff = (wnew - w) / 2; yoff = (hnew - h) / 2; /* Set background to color to be rotated in */ setcolor = (incolor == L_BRING_IN_BLACK) ? L_SET_BLACK : L_SET_WHITE; pixSetBlackOrWhite(pixd, setcolor); /* Rasterop automatically handles all 4 channels for rgba */ pixRasterop(pixd, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0); return pixd; }
/*! * \brief recogGetWindowedArea() * * \param[in] recog * \param[in] index of template * \param[in] x pixel position of left hand edge of template * \param[out] pdely y shift of template relative to pix1 * \param[out] pwsum number of fg pixels in window of pixs * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This is called after the best path has been found through * the trellis, in order to produce a correlation that can be used * to evaluate the confidence we have in the identification. * The correlation is |1 \& 2|^2 / (|1| * |2|). * |1 \& 2| is given by the count array, |2| is found from * nasum_u[], and |1| is wsum returned from this function. * </pre> */ static l_int32 recogGetWindowedArea(L_RECOG *recog, l_int32 index, l_int32 x, l_int32 *pdely, l_int32 *pwsum) { l_int32 w1, h1, w2, h2; PIX *pix1, *pix2, *pixt; L_RDID *did; PROCNAME("recogGetWindowedArea"); if (pdely) *pdely = 0; if (pwsum) *pwsum = 0; if (!pdely || !pwsum) return ERROR_INT("&dely and &wsum not both defined", procName, 1); if (!recog) return ERROR_INT("recog not defined", procName, 1); if ((did = recogGetDid(recog)) == NULL) return ERROR_INT("did not defined", procName, 1); if (index < 0 || index >= did->narray) return ERROR_INT("invalid index", procName, 1); pix1 = did->pixs; pixGetDimensions(pix1, &w1, &h1, NULL); if (x >= w1) return ERROR_INT("invalid x position", procName, 1); pix2 = pixaGetPix(recog->pixa_u, index, L_CLONE); pixGetDimensions(pix2, &w2, &h2, NULL); if (w1 < w2) { L_INFO("template %d too small\n", procName, index); pixDestroy(&pix2); return 0; } *pdely = did->delya[index][x]; pixt = pixCreate(w2, h1, 1); pixRasterop(pixt, 0, *pdely, w2, h2, PIX_SRC, pix2, 0, 0); pixRasterop(pixt, 0, 0, w2, h1, PIX_SRC & PIX_DST, pix1, x, 0); pixCountPixels(pixt, pwsum, recog->sumtab); pixDestroy(&pix2); pixDestroy(&pixt); return 0; }
main(int argc, char **argv) { l_int32 i, j; PIX *pixs, *pixt, *pixd; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("test8.jpg"); pixt = pixCopy(NULL, pixs); /* Copy, in-place and one COLUMN at a time, from the right side to the left side. */ for (j = 0; j < 200; j++) pixRasterop(pixs, 20 + j, 20, 1, 250, PIX_SRC, pixs, 250 + j, 20); pixDisplayWithTitle(pixs, 50, 50, "in-place copy", rp->display); /* Copy, in-place and one ROW at a time, from the right side to the left side. */ for (i = 0; i < 250; i++) pixRasterop(pixt, 20, 20 + i, 200, 1, PIX_SRC, pixt, 250, 20 + i); /* Test */ regTestComparePix(rp, pixs, pixt); /* 0 */ pixDestroy(&pixs); pixDestroy(&pixt); /* Show the mirrored border, which uses the general pixRasterop() on an image in-place. */ pixs = pixRead("test8.jpg"); pixt = pixRemoveBorder(pixs, 40); pixd = pixAddMirroredBorder(pixt, 40, 40, 40, 40); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */ pixDisplayWithTitle(pixd, 650, 50, "mirrored border", rp->display); pixDestroy(&pixs); pixDestroy(&pixt); pixDestroy(&pixd); return regTestCleanup(rp); }
/*! * pixaDisplayRandomCmap() * * Input: pixa (of 1 bpp components, with boxa) * w, h (if set to 0, determines the size from the * b.b. of the components in pixa) * Return: pix (8 bpp, cmapped, with random colors on the components), * or null on error * * Notes: * (1) This uses the boxes to place each pix in the rendered composite. * (2) By default, the background color is: black, cmap index 0. * This can be changed by pixcmapResetColor() */ PIX * pixaDisplayRandomCmap(PIXA *pixa, l_int32 w, l_int32 h) { l_int32 i, n, d, index, xb, yb, wb, hb; BOXA *boxa; PIX *pixs, *pixt, *pixd; PIXCMAP *cmap; PROCNAME("pixaDisplayRandomCmap"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); n = pixaGetCount(pixa); if (n == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Use the first pix in pixa to verify depth is 1 bpp */ pixs = pixaGetPix(pixa, 0, L_CLONE); d = pixGetDepth(pixs); pixDestroy(&pixs); if (d != 1) return (PIX *)ERROR_PTR("components not 1 bpp", procName, NULL); /* If w and h not input, determine the minimum size required * to contain the origin and all c.c. */ if (w == 0 || h == 0) { boxa = pixaGetBoxa(pixa, L_CLONE); boxaGetExtent(boxa, &w, &h, NULL); boxaDestroy(&boxa); } /* Set up an 8 bpp dest pix, with a colormap with 254 random colors */ if ((pixd = pixCreate(w, h, 8)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); cmap = pixcmapCreateRandom(8, 1, 1); pixSetColormap(pixd, cmap); /* Color each component and blit it in */ for (i = 0; i < n; i++) { index = 1 + (i % 254); pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb); pixs = pixaGetPix(pixa, i, L_CLONE); pixt = pixConvert1To8(NULL, pixs, 0, index); pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pixt, 0, 0); pixDestroy(&pixs); pixDestroy(&pixt); } return pixd; }
// Returns a binary Pix mask with a 1 pixel for every pixel within the // block. Rotates the coordinate system by rerotation prior to rendering. Pix* PDBLK::render_mask(const FCOORD& rerotation, TBOX* mask_box) { TBOX rotated_box(box); rotated_box.rotate(rerotation); Pix* pix = pixCreate(rotated_box.width(), rotated_box.height(), 1); if (hand_poly != nullptr) { // We are going to rotate, so get a deep copy of the points and // make a new POLY_BLOCK with it. ICOORDELT_LIST polygon; polygon.deep_copy(hand_poly->points(), ICOORDELT::deep_copy); POLY_BLOCK image_block(&polygon, hand_poly->isA()); image_block.rotate(rerotation); // Block outline is a polygon, so use a PB_LINE_IT to get the // rasterized interior. (Runs of interior pixels on a line.) auto *lines = new PB_LINE_IT(&image_block); for (int y = box.bottom(); y < box.top(); ++y) { const std::unique_ptr</*non-const*/ ICOORDELT_LIST> segments( lines->get_line(y)); if (!segments->empty()) { ICOORDELT_IT s_it(segments.get()); // Each element of segments is a start x and x size of the // run of interior pixels. for (s_it.mark_cycle_pt(); !s_it.cycled_list(); s_it.forward()) { int start = s_it.data()->x(); int xext = s_it.data()->y(); // Set the run of pixels to 1. pixRasterop(pix, start - rotated_box.left(), rotated_box.height() - 1 - (y - rotated_box.bottom()), xext, 1, PIX_SET, nullptr, 0, 0); } } } delete lines; } else { // Just fill the whole block as there is only a bounding box. pixRasterop(pix, 0, 0, rotated_box.width(), rotated_box.height(), PIX_SET, nullptr, 0, 0); } if (mask_box != nullptr) *mask_box = rotated_box; return pix; }
/*! * boxaGetCoverage() * * Input: boxa * wc, hc (dimensions of overall clipping rectangle with UL * corner at (0, 0) that is covered by the boxes. * exactflag (1 for guaranteeing an exact result; 0 for getting * an exact result only if the boxes do not overlap) * &fract (<return> sum of box area as fraction of w * h) * Return: 0 if OK, 1 on error * * Notes: * (1) The boxes in boxa are clipped to the input rectangle. * (2) * When @exactflag == 1, we generate a 1 bpp pix of size * wc x hc, paint all the boxes black, and count the fg pixels. * This can take 1 msec on a large page with many boxes. * * When @exactflag == 0, we clip each box to the wc x hc region * and sum the resulting areas. This is faster. * * The results are the same when none of the boxes overlap * within the wc x hc region. */ l_int32 boxaGetCoverage(BOXA *boxa, l_int32 wc, l_int32 hc, l_int32 exactflag, l_float32 *pfract) { l_int32 i, n, x, y, w, h, sum; BOX *box, *boxc; PIX *pixt; PROCNAME("boxaGetCoverage"); if (!pfract) return ERROR_INT("&fract not defined", procName, 1); *pfract = 0.0; if (!boxa) return ERROR_INT("boxa not defined", procName, 1); n = boxaGetCount(boxa); if (n == 0) return ERROR_INT("no boxes in boxa", procName, 1); if (exactflag == 0) { /* quick and dirty */ sum = 0; for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); if ((boxc = boxClipToRectangle(box, wc, hc)) != NULL) { boxGetGeometry(boxc, NULL, NULL, &w, &h); sum += w * h; boxDestroy(&boxc); } boxDestroy(&box); } } else { /* slower and exact */ pixt = pixCreate(wc, hc, 1); for (i = 0; i < n; i++) { box = boxaGetBox(boxa, i, L_CLONE); boxGetGeometry(box, &x, &y, &w, &h); pixRasterop(pixt, x, y, w, h, PIX_SET, NULL, 0, 0); boxDestroy(&box); } pixCountPixels(pixt, &sum, NULL); pixDestroy(&pixt); } *pfract = (l_float32)sum / (l_float32)(wc * hc); return 0; }
/*! * pixaDisplayUnsplit() * * Input: pixa * nx (number of mosaic cells horizontally) * ny (number of mosaic cells vertically) * borderwidth (of added border on all sides) * bordercolor (in our RGBA format: 0xrrggbbaa) * Return: pix of tiled images, or null on error * * Notes: * (1) This is a logical inverse of pixaSplitPix(). It * constructs a pix from a mosaic of tiles, all of equal size. * (2) For added generality, a border of arbitrary color can * be added to each of the tiles. * (3) In use, pixa will typically have either been generated * from pixaSplitPix() or will derived from a pixa that * was so generated. * (4) All pix in the pixa must be of equal depth, and, if * colormapped, have the same colormap. */ PIX * pixaDisplayUnsplit(PIXA *pixa, l_int32 nx, l_int32 ny, l_int32 borderwidth, l_uint32 bordercolor) { l_int32 w, h, d, wt, ht; l_int32 i, j, k, x, y, n; PIX *pixt, *pixd; PROCNAME("pixaDisplayUnsplit"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); if (nx <= 0 || ny <= 0) return (PIX *)ERROR_PTR("nx and ny must be > 0", procName, NULL); if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); if (n != nx * ny) return (PIX *)ERROR_PTR("n != nx * ny", procName, NULL); borderwidth = L_MAX(0, borderwidth); pixaGetPixDimensions(pixa, 0, &wt, &ht, &d); w = nx * (wt + 2 * borderwidth); h = ny * (ht + 2 * borderwidth); if ((pixd = pixCreate(w, h, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixt = pixaGetPix(pixa, 0, L_CLONE); pixCopyColormap(pixd, pixt); pixDestroy(&pixt); if (borderwidth > 0) pixSetAllArbitrary(pixd, bordercolor); y = borderwidth; for (i = 0, k = 0; i < ny; i++) { x = borderwidth; for (j = 0; j < nx; j++, k++) { pixt = pixaGetPix(pixa, k, L_CLONE); pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pixt, 0, 0); pixDestroy(&pixt); x += wt + 2 * borderwidth; } y += ht + 2 * borderwidth; } return pixd; }
/*! * pixRasteropFullImage() * * Input: pixd * pixs * op (any of the op-codes) * Return: 0 if OK; 1 on error * * Notes: * - this is a wrapper for a common 2-image raster operation * - both pixs and pixd must be defined * - the operation is performed with aligned UL corners of pixs and pixd * - the operation clips to the smallest pix; if the width or height * of pixd is larger than pixs, some pixels in pixd will be unchanged */ l_int32 pixRasteropFullImage(PIX *pixd, PIX *pixs, l_int32 op) { PROCNAME("pixRasteropFullImage"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd), op, pixs, 0, 0); return 0; }
// Returns a full-resolution binary pix in which each cell over the given // threshold is filled as a black square. pixDestroy after use. // Edge cells, which have a zero 4-neighbour, are not marked. Pix* IntGrid::ThresholdToPix(int threshold) const { Pix* pix = pixCreate(tright().x() - bleft().x(), tright().y() - bleft().y(), 1); int cellsize = gridsize(); for (int y = 0; y < gridheight(); ++y) { for (int x = 0; x < gridwidth(); ++x) { if (GridCellValue(x, y) > threshold && GridCellValue(x - 1, y) > 0 && GridCellValue(x + 1, y) > 0 && GridCellValue(x, y - 1) > 0 && GridCellValue(x, y + 1) > 0) { pixRasterop(pix, x * cellsize, tright().y() - ((y + 1) * cellsize), cellsize, cellsize, PIX_SET, NULL, 0, 0); } } } return pix; }
/*! * pixaSplitPix() * * Input: pixs (with individual components on a lattice) * nx (number of mosaic cells horizontally) * ny (number of mosaic cells vertically) * borderwidth (of added border on all sides) * bordercolor (in our RGBA format: 0xrrggbbaa) * Return: pixa, or null on error * * Notes: * (1) This is a variant on pixaCreateFromPix(), where we * simply divide the image up into (approximately) equal * subunits. If you want the subimages to have essentially * the same aspect ratio as the input pix, use nx = ny. * (2) If borderwidth is 0, we ignore the input bordercolor and * redefine it to white. * (3) The bordercolor is always used to initialize each tiled pix, * so that if the src is clipped, the unblitted part will * be this color. This avoids 1 pixel wide black stripes at the * left and lower edges. */ PIXA * pixaSplitPix(PIX *pixs, l_int32 nx, l_int32 ny, l_int32 borderwidth, l_uint32 bordercolor) { l_int32 w, h, d, cellw, cellh, i, j; PIX *pixt; PIXA *pixa; PROCNAME("pixaSplitPix"); if (!pixs) return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL); if (nx <= 0 || ny <= 0) return (PIXA *)ERROR_PTR("nx and ny must be > 0", procName, NULL); borderwidth = L_MAX(0, borderwidth); if ((pixa = pixaCreate(nx * ny)) == NULL) return (PIXA *)ERROR_PTR("pixa not made", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); cellw = (w + nx - 1) / nx; /* round up */ cellh = (h + ny - 1) / ny; for (i = 0; i < ny; i++) { for (j = 0; j < nx; j++) { if ((pixt = pixCreate(cellw + 2 * borderwidth, cellh + 2 * borderwidth, d)) == NULL) return (PIXA *)ERROR_PTR("pixt not made", procName, NULL); pixCopyColormap(pixt, pixs); if (borderwidth == 0) { /* initialize full image to white */ if (d == 1) pixClearAll(pixt); else pixSetAll(pixt); } else pixSetAllArbitrary(pixt, bordercolor); pixRasterop(pixt, borderwidth, borderwidth, cellw, cellh, PIX_SRC, pixs, j * cellw, i * cellh); pixaAddPix(pixa, pixt, L_INSERT); } } return pixa; }
/*! * pixConnCompAreaTransform() * * Input: pixs (1 bpp) * connect (connectivity: 4 or 8) * Return: pixd (16 bpp), or null on error * * Notes: * (1) The pixel values in pixd label the area of the fg component * to which the pixel belongs. Pixels in the bg are labelled 0. * (2) The pixel values cannot exceed 2^16 - 1, even if the area * of the c.c. is larger. * (3) For purposes of visualization, the output can be converted * to 8 bpp, using pixConvert16To8() or pixMaxDynamicRange(). */ PIX * pixConnCompAreaTransform(PIX *pixs, l_int32 connect) { l_int32 i, n, npix, w, h, xb, yb, wb, hb; l_int32 *tab8; BOXA *boxa; PIX *pix1, *pix2, *pixd; PIXA *pixa; PROCNAME("pixConnCompTransform"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connect != 4 && connect != 8) return (PIX *)ERROR_PTR("connectivity must be 4 or 8", procName, NULL); boxa = pixConnComp(pixs, &pixa, connect); n = pixaGetCount(pixa); boxaDestroy(&boxa); pixGetDimensions(pixs, &w, &h, NULL); pixd = pixCreate(w, h, 16); if (n == 0) { /* no fg */ pixaDestroy(&pixa); return pixd; } /* Label each component and blit it in */ tab8 = makePixelSumTab8(); for (i = 0; i < n; i++) { pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb); pix1 = pixaGetPix(pixa, i, L_CLONE); pixCountPixels(pix1, &npix, tab8); npix = L_MIN(npix, 0xffff); pix2 = pixConvert1To16(NULL, pix1, 0, npix); pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix2, 0, 0); pixDestroy(&pix1); pixDestroy(&pix2); } pixaDestroy(&pixa); FREE(tab8); return pixd; }
/*! * pixFindMinRunsOrthogonal() * * Input: pixs (1 bpp) * angle (in radians) * depth (of pixd: 8 or 16 bpp) * Return: pixd (8 or 16 bpp), or null on error * * Notes: * (1) This computes, for each fg pixel in pixs, the minimum of * the runlengths going through that pixel in two orthogonal * directions: at @angle and at (90 + @angle). * (2) We use rotation by shear because the forward and backward * rotations by the same angle are exact inverse operations. * As a result, the nonzero pixels in pixd correspond exactly * to the fg pixels in pixs. This is not the case with * sampled rotation, due to spatial quantization. Nevertheless, * the result suffers from lack of exact correspondence * between original and rotated pixels, also due to spatial * quantization, causing some boundary pixels to be * shifted from bg to fg or v.v. */ static PIX * pixFindMinRunsOrthogonal(PIX *pixs, l_float32 angle, l_int32 depth) { l_int32 w, h, diag, xoff, yoff; PIX *pixb, *pixr, *pixh, *pixv, *pixg1, *pixg2, *pixd; BOX *box; PROCNAME("pixFindMinRunsOrthogonal"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Rasterop into the center of a sufficiently large image * so we don't lose pixels for any rotation angle. */ pixGetDimensions(pixs, &w, &h, NULL); diag = (l_int32)(sqrt((l_float64)(w * w + h * h)) + 2.5); xoff = (diag - w) / 2; yoff = (diag - h) / 2; pixb = pixCreate(diag, diag, 1); pixRasterop(pixb, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0); /* Rotate about the 'center', get the min of orthogonal transforms, * rotate back, and crop the part corresponding to pixs. */ pixr = pixRotateShear(pixb, diag / 2, diag / 2, angle, L_BRING_IN_WHITE); pixh = pixRunlengthTransform(pixr, 1, L_HORIZONTAL_RUNS, depth); pixv = pixRunlengthTransform(pixr, 1, L_VERTICAL_RUNS, depth); pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN); pixg2 = pixRotateShear(pixg1, diag / 2, diag / 2, -angle, L_BRING_IN_WHITE); box = boxCreate(xoff, yoff, w, h); pixd = pixClipRectangle(pixg2, box, NULL); pixDestroy(&pixb); pixDestroy(&pixr); pixDestroy(&pixh); pixDestroy(&pixv); pixDestroy(&pixg1); pixDestroy(&pixg2); boxDestroy(&box); return pixd; }
/** * Returns a binary image of the current object at the given level. * The position and size match the return from BoundingBoxInternal, and so this * could be upscaled with respect to the original input image. * Use pixDestroy to delete the image after use. * The following methods are used to generate the images: * RIL_BLOCK: mask the page image with the block polygon. * RIL_TEXTLINE: Clip the rectangle of the line box from the page image. * TODO(rays) fix this to generate and use a line polygon. * RIL_WORD: Clip the rectangle of the word box from the page image. * RIL_SYMBOL: Render the symbol outline to an image for cblobs (prior * to recognition) or the bounding box otherwise. * A reconstruction of the original image (using xor to check for double * representation) should be reasonably accurate, * apart from removed noise, at the block level. Below the block level, the * reconstruction will be missing images and line separators. * At the symbol level, kerned characters will be invade the bounding box * if rendered after recognition, making an xor reconstruction inaccurate, but * an or construction better. Before recognition, symbol-level reconstruction * should be good, even with xor, since the images come from the connected * components. */ Pix* PageIterator::GetBinaryImage(PageIteratorLevel level) const { int left, top, right, bottom; if (!BoundingBoxInternal(level, &left, &top, &right, &bottom)) return NULL; Pix* pix = NULL; switch (level) { case RIL_BLOCK: case RIL_PARA: int bleft, btop, bright, bbottom; BoundingBoxInternal(RIL_BLOCK, &bleft, &btop, &bright, &bbottom); pix = it_->block()->block->render_mask(); // AND the mask and the image. pixRasterop(pix, 0, 0, pixGetWidth(pix), pixGetHeight(pix), PIX_SRC & PIX_DST, tesseract_->pix_binary(), bleft, btop); if (level == RIL_PARA) { // RIL_PARA needs further attention: // clip the paragraph from the block mask. Box* box = boxCreate(left - bleft, top - btop, right - left, bottom - top); Pix* pix2 = pixClipRectangle(pix, box, NULL); boxDestroy(&box); pixDestroy(&pix); pix = pix2; } break; case RIL_TEXTLINE: case RIL_WORD: case RIL_SYMBOL: if (level == RIL_SYMBOL && cblob_it_ != NULL && cblob_it_->data()->area() != 0) return cblob_it_->data()->render(); // Just clip from the bounding box. Box* box = boxCreate(left, top, right - left, bottom - top); pix = pixClipRectangle(tesseract_->pix_binary(), box, NULL); boxDestroy(&box); break; } return pix; }
// create a union of a number of arbitrary pix Pix *CubeLineSegmenter::Pixa2Pix(Pixa *pixa, Box **dest_box, int start_pix, int pix_cnt) { // compute union_box int min_x = INT_MAX, max_x = INT_MIN, min_y = INT_MAX, max_y = INT_MIN; for (int pix_idx = start_pix; pix_idx < (start_pix + pix_cnt); pix_idx++) { Box *pix_box = pixa->boxa->box[pix_idx]; UpdateRange(pix_box->x, pix_box->x + pix_box->w, &min_x, &max_x); UpdateRange(pix_box->y, pix_box->y + pix_box->h, &min_y, &max_y); } (*dest_box) = boxCreate(min_x, min_y, max_x - min_x, max_y - min_y); if ((*dest_box) == NULL) { return NULL; } // create the union pix Pix *union_pix = pixCreate((*dest_box)->w, (*dest_box)->h, img_->d); if (union_pix == NULL) { boxDestroy(dest_box); return NULL; } // create a pix corresponding to the union of all pixs // blt the src and dest pix for (int pix_idx = start_pix; pix_idx < (start_pix + pix_cnt); pix_idx++) { Box *pix_box = pixa->boxa->box[pix_idx]; Pix *con_pix = pixa->pix[pix_idx]; pixRasterop(union_pix, pix_box->x - (*dest_box)->x, pix_box->y - (*dest_box)->y, pix_box->w, pix_box->h, PIX_SRC | PIX_DST, con_pix, 0, 0); } return union_pix; }
/*! * pixaCreateFromPix() * * Input: pixs (with individual components on a lattice) * n (number of components) * cellw (width of each cell) * cellh (height of each cell) * Return: pixa, or null on error * * Note: for bpp = 1, we truncate each retrieved pix to * the ON pixels, which we assume for now start at (0,0) */ PIXA * pixaCreateFromPix(PIX *pixs, l_int32 n, l_int32 cellw, l_int32 cellh) { l_int32 w, h, d, nw, nh, i, j, index; PIX *pix, *pixt; PIXA *pixa; PROCNAME("pixaCreateFromPix"); if (!pixs) return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL); if (n <= 0) return (PIXA *)ERROR_PTR("n must be > 0", procName, NULL); if ((pixa = pixaCreate(n)) == NULL) return (PIXA *)ERROR_PTR("pixa not made", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if ((pixt = pixCreate(cellw, cellh, d)) == NULL) return (PIXA *)ERROR_PTR("pixt not made", procName, NULL); nw = (w + cellw - 1) / cellw; nh = (h + cellh - 1) / cellh; for (i = 0, index = 0; i < nh; i++) { for (j = 0; j < nw && index < n; j++, index++) { pixRasterop(pixt, 0, 0, cellw, cellh, PIX_SRC, pixs, j * cellw, i * cellh); if (d == 1 && !pixClipToForeground(pixt, &pix, NULL)) pixaAddPix(pixa, pix, L_INSERT); else pixaAddPix(pixa, pixt, L_COPY); } } pixDestroy(&pixt); return pixa; }
// clean up the image Pix *CubeLineSegmenter::CleanUp(Pix *orig_img) { // get rid of long horizontal lines Pix *pix_temp0 = pixMorphCompSequence(orig_img, "o300.2", 0); pixXor(pix_temp0, pix_temp0, orig_img); // get rid of long vertical lines Pix *pix_temp1 = pixMorphCompSequence(pix_temp0, "o2.300", 0); pixXor(pix_temp1, pix_temp1, pix_temp0); pixDestroy(&pix_temp0); // detect connected components Pixa *con_comps; Boxa *boxa = pixConnComp(pix_temp1, &con_comps, 8); if (boxa == NULL) { return NULL; } // detect and remove suspicious conn comps for (int con = 0; con < con_comps->n; con++) { Box *box = boxa->box[con]; // remove if suspc. conn comp if ((box->w > (box->h * kMaxHorzAspectRatio)) || (box->h > (box->w * kMaxVertAspectRatio)) || (box->w < kMinWid && box->h < kMinHgt)) { pixRasterop(pix_temp1, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST, con_comps->pix[con], 0, 0); } } pixaDestroy(&con_comps); boxaDestroy(&boxa); return pix_temp1; }