// Threshold the rectangle, taking everything except the image buffer pointer // from the class, using thresholds/hi_values to the output IMAGE. void ImageThresholder::ThresholdRectToPix(const unsigned char* imagedata, int bytes_per_pixel, int bytes_per_line, const int* thresholds, const int* hi_values, Pix** pix) const { *pix = pixCreate(rect_width_, rect_height_, 1); uinT32* pixdata = pixGetData(*pix); int wpl = pixGetWpl(*pix); const unsigned char* srcdata = imagedata + rect_top_* bytes_per_line + rect_left_ * bytes_per_pixel; for (int y = 0; y < rect_height_; ++y) { const uinT8* linedata = srcdata; uinT32* pixline = pixdata + y * wpl; for (int x = 0; x < rect_width_; ++x, linedata += bytes_per_pixel) { bool white_result = true; for (int ch = 0; ch < bytes_per_pixel; ++ch) { if (hi_values[ch] >= 0 && (linedata[ch] > thresholds[ch]) == (hi_values[ch] == 0)) { white_result = false; break; } } if (white_result) CLEAR_DATA_BIT(pixline, x); else SET_DATA_BIT(pixline, x); } srcdata += bytes_per_line; } }
/*! * pixSetPixel() * * Input: pix * (x,y) pixel coords * val (value to be inserted) * Return: 0 if OK; 1 on error * * Notes: * (1) Warning: the input value is not checked for overflow with respect * the the depth of @pix, and the sign bit (if any) is ignored. * * For d == 1, @val > 0 sets the bit on. * * For d == 2, 4, 8 and 16, @val is masked to the maximum allowable * pixel value, and any (invalid) higher order bits are discarded. * (2) See pixGetPixel() for information on performance. */ LEPTONICA_EXPORT l_int32 pixSetPixel(PIX *pix, l_int32 x, l_int32 y, l_uint32 val) { l_int32 w, h, d, wpl; l_uint32 *line, *data; PROCNAME("pixSetPixel"); if (!pix) return ERROR_INT("pix not defined", procName, 1); pixGetDimensions(pix, &w, &h, &d); if (x < 0 || x >= w) return ERROR_INT("x out of bounds", procName, 1); if (y < 0 || y >= h) return ERROR_INT("y out of bounds", procName, 1); data = pixGetData(pix); wpl = pixGetWpl(pix); line = data + y * wpl; switch (d) { case 1: if (val) SET_DATA_BIT(line, x); else CLEAR_DATA_BIT(line, x); break; case 2: SET_DATA_DIBIT(line, x, val); break; case 4: SET_DATA_QBIT(line, x, val); break; case 8: SET_DATA_BYTE(line, x, val); break; case 16: SET_DATA_TWO_BYTES(line, x, val); break; case 32: line[x] = val; break; default: return ERROR_INT("depth must be in {1,2,4,8,16,32} bpp", procName, 1); } return 0; }
// Get a set of bounding boxes of possible horizontal lines in the image. // The input resolution overrides any resolution set in src_pix. // The output line_pix contains just all the detected lines. // The output boxes undergo the transformation (x,y)->(height-y,x) so the // lines can be found with a vertical line finder afterwards. // This transformation allows a simple x/y flip to reverse it in tesseract // coordinates and it is faster to flip the lines than rotate the image. Boxa* LineFinder::GetHLineBoxes(int resolution, Pix* src_pix, Pix** line_pix) { #ifdef HAVE_LIBLEPT // Remove any parts of 1 inch/kThinLineFraction high or more, by opening // away the thin lines and subtracting what's left. // This is very generous and will leave in even quite wide lines. Pix* pixt1 = pixOpenBrick(NULL, src_pix, 1, resolution / kThinLineFraction); pixSubtract(pixt1, src_pix, pixt1); // Spread vertically to allow for some skew. Pix* pixt2 = pixDilateBrick(NULL, pixt1, 1, 3); // Now keep only wide stuff of width at least 1 inch/kMinLineLengthFraction. pixOpenBrick(pixt1, pixt2, resolution / kMinLineLengthFraction, 1); pixDestroy(&pixt2); // Put a single pixel crack in every line at an arbitrary spacing, // so they break up and the bounding boxes can be used to get the // direction accurately enough without needing outlines. int wpl = pixGetWpl(pixt1); int width = pixGetWidth(pixt1); int height = pixGetHeight(pixt1); l_uint32* data = pixGetData(pixt1); for (int y = 0; y < height; ++y, data += wpl) { for (int x = kCrackSpacing; x < width; x += kCrackSpacing) { CLEAR_DATA_BIT(data, x); } } if (textord_tabfind_show_vlines) pixWrite("hlines.png", pixt1, IFF_PNG); Boxa* boxa = pixConnComp(pixt1, NULL, 8); *line_pix = pixt1; // Iterate the boxes to flip x and y. int nboxes = boxaGetCount(boxa); for (int i = 0; i < nboxes; ++i) { l_int32 x, y, box_width, box_height; boxaGetBoxGeometry(boxa, i, &x, &y, &box_width, &box_height); Box* box = boxCreate(height - (y + box_height), width - (x + box_width), box_height, box_width); boxaReplaceBox(boxa, i, box); } return boxa; #else return NULL; #endif }
/*! * \brief pixSeedfill8() * * \param[in] pixs 1 bpp * \param[in] stack for holding fillsegs * \param[in] x,y location of seed pixel * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm. * (2) This operates on the input 1 bpp pix to remove the fg seed * pixel, at (x,y), and all pixels that are 8-connected to it. * The seed pixel at (x,y) must initially be ON. * (3) Reference: see pixSeedFill8BB() * </pre> */ l_int32 pixSeedfill8(PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y) { l_int32 w, h, xstart, wpl, x1, x2, dy; l_int32 xmax, ymax; l_uint32 *data, *line; PROCNAME("pixSeedfill8"); if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); if (!stack) return ERROR_INT("stack not defined", procName, 1); if (!stack->auxstack) stack->auxstack = lstackCreate(0); pixGetDimensions(pixs, &w, &h, NULL); xmax = w - 1; ymax = h - 1; data = pixGetData(pixs); wpl = pixGetWpl(pixs); line = data + y * wpl; /* Check pix value of seed; must be ON */ if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0)) return 0; /* Init stack to seed */ pushFillseg(stack, x, x, y, 1, ymax); pushFillseg(stack, x, x, y + 1, -1, ymax); while (lstackGetCount(stack) > 0) { /* Pop segment off stack and fill a neighboring scan line */ popFillseg(stack, &x1, &x2, &y, &dy); line = data + y * wpl; /* A segment of scanline y - dy for x1 <= x <= x2 was * previously filled. We now explore adjacent pixels * in scan line y. There are three regions: to the * left of x1, between x1 and x2, and to the right of x2. * These regions are handled differently. Leaks are * possible expansions beyond the previous segment and * going back in the -dy direction. These can happen * for x < x1 and for x > x2. Any "leak" segments * are plugged with a push in the -dy (opposite) direction. * And any segments found anywhere are always extended * in the +dy direction. */ for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--) CLEAR_DATA_BIT(line,x); if (x >= x1 - 1) /* pix at x1 - 1 was off and was not cleared */ goto skip; xstart = x + 1; if (xstart < x1) /* leak on left? */ pushFillseg(stack, xstart, x1 - 1, y, -dy, ymax); x = x1; do { for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++) CLEAR_DATA_BIT(line, x); pushFillseg(stack, xstart, x - 1, y, dy, ymax); if (x > x2) /* leak on right? */ pushFillseg(stack, x2 + 1, x - 1, y, -dy, ymax); skip: for (x++; x <= x2 + 1 && x <= xmax && (GET_DATA_BIT(line, x) == 0); x++) ; xstart = x; } while (x <= x2 + 1 && x <= xmax); } return 0; }
/*! * \brief pixSeedfill8BB() * * \param[in] pixs 1 bpp * \param[in] stack for holding fillsegs * \param[in] x,y location of seed pixel * \return box or NULL on error. * * <pre> * Notes: * (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm. * (2) This operates on the input 1 bpp pix to remove the fg seed * pixel, at (x,y), and all pixels that are 8-connected to it. * The seed pixel at (x,y) must initially be ON. * (3) Returns the bounding box of the erased 8-cc component. * (4) Reference: see Paul Heckbert's stack-based seed fill algorithm * in "Graphic Gems", ed. Andrew Glassner, Academic * Press, 1990. The algorithm description is given * on pp. 275-277; working C code is on pp. 721-722.) * The code here follows Heckbert's closely, except * the leak checks are changed for 8 connectivity. * See comments on pixSeedfill4BB() for more details. * </pre> */ BOX * pixSeedfill8BB(PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y) { l_int32 w, h, xstart, wpl, x1, x2, dy; l_int32 xmax, ymax; l_int32 minx, maxx, miny, maxy; /* for bounding box of this c.c. */ l_uint32 *data, *line; BOX *box; PROCNAME("pixSeedfill8BB"); if (!pixs || pixGetDepth(pixs) != 1) return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (!stack) return (BOX *)ERROR_PTR("stack not defined", procName, NULL); if (!stack->auxstack) stack->auxstack = lstackCreate(0); pixGetDimensions(pixs, &w, &h, NULL); xmax = w - 1; ymax = h - 1; data = pixGetData(pixs); wpl = pixGetWpl(pixs); line = data + y * wpl; /* Check pix value of seed; must be ON */ if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0)) return NULL; /* Init stack to seed: * Must first init b.b. values to prevent valgrind from complaining; * then init b.b. boundaries correctly to seed. */ minx = miny = 100000; maxx = maxy = 0; pushFillsegBB(stack, x, x, y, 1, ymax, &minx, &maxx, &miny, &maxy); pushFillsegBB(stack, x, x, y + 1, -1, ymax, &minx, &maxx, &miny, &maxy); minx = maxx = x; miny = maxy = y; while (lstackGetCount(stack) > 0) { /* Pop segment off stack and fill a neighboring scan line */ popFillseg(stack, &x1, &x2, &y, &dy); line = data + y * wpl; /* A segment of scanline y - dy for x1 <= x <= x2 was * previously filled. We now explore adjacent pixels * in scan line y. There are three regions: to the * left of x1, between x1 and x2, and to the right of x2. * These regions are handled differently. Leaks are * possible expansions beyond the previous segment and * going back in the -dy direction. These can happen * for x < x1 and for x > x2. Any "leak" segments * are plugged with a push in the -dy (opposite) direction. * And any segments found anywhere are always extended * in the +dy direction. */ for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--) CLEAR_DATA_BIT(line,x); if (x >= x1 - 1) /* pix at x1 - 1 was off and was not cleared */ goto skip; xstart = x + 1; if (xstart < x1) /* leak on left? */ pushFillsegBB(stack, xstart, x1 - 1, y, -dy, ymax, &minx, &maxx, &miny, &maxy); x = x1; do { for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++) CLEAR_DATA_BIT(line, x); pushFillsegBB(stack, xstart, x - 1, y, dy, ymax, &minx, &maxx, &miny, &maxy); if (x > x2) /* leak on right? */ pushFillsegBB(stack, x2 + 1, x - 1, y, -dy, ymax, &minx, &maxx, &miny, &maxy); skip: for (x++; x <= x2 + 1 && x <= xmax && (GET_DATA_BIT(line, x) == 0); x++) ; xstart = x; } while (x <= x2 + 1 && x <= xmax); } if ((box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1)) == NULL) return (BOX *)ERROR_PTR("box not made", procName, NULL); return box; }
/*! * pixSetSelectCmap() * * Input: pixs (1, 2, 4 or 8 bpp, with colormap) * box (<optional> region to set color; can be NULL) * sindex (colormap index of pixels to be changed) * rval, gval, bval (new color to paint) * Return: 0 if OK, 1 on error * * Note: * (1) This is an in-place operation. * (2) It sets all pixels in region that have the color specified * by the colormap index 'sindex' to the new color. * (3) sindex must be in the existing colormap; otherwise an * error is returned. * (4) If the new color exists in the colormap, it is used; * otherwise, it is added to the colormap. If it cannot be * added because the colormap is full, an error is returned. * (5) If box is NULL, applies function to the entire image; otherwise, * clips the operation to the intersection of the box and pix. * (6) An DC of use would be to set to a specific color all * the light (background) pixels within a certain region of * a 3-level 2 bpp image, while leaving light pixels outside * this region unchanged. */ l_int32 pixSetSelectCmap(PIX *pixs, BOX *box, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, d, n, x1, y1, x2, y2, bw, bh, val, wpls; l_int32 index; /* of new color to be set */ l_uint32 *lines, *datas; PIXCMAP *cmap; PROCNAME("pixSetSelectCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); d = pixGetDepth(pixs); if (d != 1 && d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {1,2,4,8}", procName, 1); /* Add new color if necessary; get index of this color in cmap */ n = pixcmapGetCount(cmap); if (sindex >= n) return ERROR_INT("sindex too large; no cmap entry", procName, 1); if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */ if (pixcmapAddColor(cmap, rval, gval, bval)) return ERROR_INT("error adding cmap entry", procName, 1); else index = n; /* we've added one color */ } /* Determine the region of substitution */ pixGetDimensions(pixs, &w, &h, NULL); if (!box) { x1 = y1 = 0; x2 = w; y2 = h; } else { boxGetGeometry(box, &x1, &y1, &bw, &bh); x2 = x1 + bw - 1; y2 = y1 + bh - 1; } /* Replace pixel value sindex by index in the region */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); for (i = y1; i <= y2; i++) { if (i < 0 || i >= h) /* clip */ continue; lines = datas + i * wpls; for (j = x1; j <= x2; j++) { if (j < 0 || j >= w) /* clip */ continue; switch (d) { case 1: val = GET_DATA_BIT(lines, j); if (val == sindex) { if (index == 0) CLEAR_DATA_BIT(lines, j); else SET_DATA_BIT(lines, j); } break; case 2: val = GET_DATA_DIBIT(lines, j); if (val == sindex) SET_DATA_DIBIT(lines, j, index); break; case 4: val = GET_DATA_QBIT(lines, j); if (val == sindex) SET_DATA_QBIT(lines, j, index); break; case 8: val = GET_DATA_BYTE(lines, j); if (val == sindex) SET_DATA_BYTE(lines, j, index); break; default: return ERROR_INT("depth not in {1,2,4,8}", procName, 1); } } } return 0; }
/*! * pixSetSelectMaskedCmap() * * Input: pixs (2, 4 or 8 bpp, with colormap) * pixm (<optional> 1 bpp mask; no-op if NULL) * x, y (UL corner of mask relative to pixs) * sindex (colormap index of pixels in pixs to be changed) * rval, gval, bval (new color to substitute) * Return: 0 if OK, 1 on error * * Note: * (1) This is an in-place operation. * (2) This paints through the fg of pixm and replaces all pixels * in pixs that have a particular value (sindex) with the new color. * (3) If pixm == NULL, a warning is given. * (4) sindex must be in the existing colormap; otherwise an * error is returned. * (5) If the new color exists in the colormap, it is used; * otherwise, it is added to the colormap. If the colormap * is full, an error is returned. */ l_int32 pixSetSelectMaskedCmap(PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, d, n, wm, hm, wpls, wplm, val; l_int32 index; /* of new color to be set */ l_uint32 *lines, *linem, *datas, *datam; PIXCMAP *cmap; PROCNAME("pixSetSelectMaskedCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); if (!pixm) { L_WARNING("no mask; nothing to do\n", procName); return 0; } d = pixGetDepth(pixs); if (d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {2, 4, 8}", procName, 1); /* add new color if necessary; get index of this color in cmap */ n = pixcmapGetCount(cmap); if (sindex >= n) return ERROR_INT("sindex too large; no cmap entry", procName, 1); if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */ if (pixcmapAddColor(cmap, rval, gval, bval)) return ERROR_INT("error adding cmap entry", procName, 1); else index = n; /* we've added one color */ } /* replace pixel value sindex by index when fg pixel in pixmc * overlays it */ w = pixGetWidth(pixs); h = pixGetHeight(pixs); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); wm = pixGetWidth(pixm); hm = pixGetHeight(pixm); datam = pixGetData(pixm); wplm = pixGetWpl(pixm); for (i = 0; i < hm; i++) { if (i + y < 0 || i + y >= h) continue; lines = datas + (y + i) * wpls; linem = datam + i * wplm; for (j = 0; j < wm; j++) { if (j + x < 0 || j + x >= w) continue; if (GET_DATA_BIT(linem, j)) { switch (d) { case 1: val = GET_DATA_BIT(lines, x + j); if (val == sindex) { if (index == 0) CLEAR_DATA_BIT(lines, x + j); else SET_DATA_BIT(lines, x + j); } break; case 2: val = GET_DATA_DIBIT(lines, x + j); if (val == sindex) SET_DATA_DIBIT(lines, x + j, index); break; case 4: val = GET_DATA_QBIT(lines, x + j); if (val == sindex) SET_DATA_QBIT(lines, x + j, index); break; case 8: val = GET_DATA_BYTE(lines, x + j); if (val == sindex) SET_DATA_BYTE(lines, x + j, index); break; default: return ERROR_INT("depth not in {1,2,4,8}", procName, 1); } } } } return 0; }
/*! * pixRotateBySampling() * * Input: pixs (1, 2, 4, 8, 16, 32 bpp rgb; can be cmapped) * xcen (x value of center of rotation) * ycen (y value of center of rotation) * angle (radians; clockwise is positive) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * Return: pixd, or null on error * * Notes: * (1) For very small rotations, just return a clone. * (2) Rotation brings either white or black pixels in * from outside the image. * (3) Colormaps are retained. */ PIX * pixRotateBySampling(PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor) { l_int32 w, h, d, i, j, x, y, xdif, ydif, wm1, hm1, wpld; l_uint32 val; l_float32 sina, cosa; l_uint32 *datad, *lined; void **lines; PIX *pixd; PROCNAME("pixRotateBySampling"); 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); pixGetDimensions(pixs, &w, &h, &d); if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) return (PIX *) ERROR_PTR("invalid depth", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL) return (PIX *) ERROR_PTR("pixd not made", procName, NULL); pixSetBlackOrWhite(pixd, incolor); sina = sin(angle); cosa = cos(angle); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); wm1 = w - 1; hm1 = h - 1; lines = pixGetLinePtrs(pixs, NULL); /* Treat 1 bpp case specially */ if (d == 1) { for (i = 0; i < h; i++) { /* scan over pixd */ lined = datad + i * wpld; ydif = ycen - i; for (j = 0; j < w; j++) { xdif = xcen - j; x = xcen + (l_int32)(-xdif * cosa - ydif * sina); if (x < 0 || x > wm1) continue; y = ycen + (l_int32)(-ydif * cosa + xdif * sina); if (y < 0 || y > hm1) continue; if (incolor == L_BRING_IN_WHITE) { if (GET_DATA_BIT(lines[y], x)) SET_DATA_BIT(lined, j); } else { if (!GET_DATA_BIT(lines[y], x)) CLEAR_DATA_BIT(lined, j); } } } FREE(lines); return pixd; } for (i = 0; i < h; i++) { /* scan over pixd */ lined = datad + i * wpld; ydif = ycen - i; for (j = 0; j < w; j++) { xdif = xcen - j; x = xcen + (l_int32)(-xdif * cosa - ydif * sina); if (x < 0 || x > wm1) continue; y = ycen + (l_int32)(-ydif * cosa + xdif * sina); if (y < 0 || y > hm1) continue; switch (d) { case 8: val = GET_DATA_BYTE(lines[y], x); SET_DATA_BYTE(lined, j, val); break; case 32: val = GET_DATA_FOUR_BYTES(lines[y], x); SET_DATA_FOUR_BYTES(lined, j, val); break; case 2: val = GET_DATA_DIBIT(lines[y], x); SET_DATA_DIBIT(lined, j, val); break; case 4: val = GET_DATA_QBIT(lines[y], x); SET_DATA_QBIT(lined, j, val); break; case 16: val = GET_DATA_TWO_BYTES(lines[y], x); SET_DATA_TWO_BYTES(lined, j, val); break; default: return (PIX *) ERROR_PTR("invalid depth", procName, NULL); } } } FREE(lines); return pixd; }