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; }
// 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; } }
/*! * pixRasterop() * * Input: pixd (dest pix) * dx (x val of UL corner of dest rectangle) * dy (y val of UL corner of dest rectangle) * dw (width of dest rectangle) * dh (height of dest rectangle) * op (op code) * pixs (src pix) * sx (x val of UL corner of src rectangle) * sy (y val of UL corner of src rectangle) * Return: 0 if OK; 1 on error. * * Notes: * (1) This has the standard set of 9 args for rasterop. * This function is your friend; it is worth memorizing! * (2) If the operation involves only dest, this calls * rasteropUniLow(). Otherwise, checks depth of the * src and dest, and if they match, calls rasteropLow(). * (3) For the two-image operation, where both pixs and pixd * are defined, they are typically different images. However * there are cases, such as pixSetMirroredBorder(), where * in-place operations can be done, blitting pixels from * one part of pixd to another. Consequently, we permit * such operations. If you use them, be sure that there * is no overlap between the source and destination rectangles * in pixd (!) * * Background: * ----------- * * There are 18 operations, described by the op codes in pix.h. * * One, PIX_DST, is a no-op. * * Three, PIX_CLR, PIX_SET, and PIX_NOT(PIX_DST) operate only on the dest. * These are handled by the low-level rasteropUniLow(). * * The other 14 involve the both the src and the dest, and depend on * the bit values of either just the src or the bit values of both * src and dest. They are handled by rasteropLow(): * * PIX_SRC s * PIX_NOT(PIX_SRC) ~s * PIX_SRC | PIX_DST s | d * PIX_SRC & PIX_DST s & d * PIX_SRC ^ PIX_DST s ^ d * PIX_NOT(PIX_SRC) | PIX_DST ~s | d * PIX_NOT(PIX_SRC) & PIX_DST ~s & d * PIX_NOT(PIX_SRC) ^ PIX_DST ~s ^ d * PIX_SRC | PIX_NOT(PIX_DST) s | ~d * PIX_SRC & PIX_NOT(PIX_DST) s & ~d * PIX_SRC ^ PIX_NOT(PIX_DST) s ^ ~d * PIX_NOT(PIX_SRC | PIX_DST) ~(s | d) * PIX_NOT(PIX_SRC & PIX_DST) ~(s & d) * PIX_NOT(PIX_SRC ^ PIX_DST) ~(s ^ d) * * Each of these is implemented with one of three low-level * functions, depending on the alignment of the left edge * of the src and dest rectangles: * * a fastest implementation if both left edges are * (32-bit) word aligned * * a very slightly slower implementation if both left * edges have the same relative (32-bit) word alignment * * the general routine that is invoked when * both left edges have different word alignment * * Of the 14 binary rasterops above, only 12 are unique * logical combinations (out of a possible 16) of src * and dst bits: * * (sd) (11) (10) (01) (00) * ----------------------------------------------- * s 1 1 0 0 * ~s 0 1 0 1 * s | d 1 1 1 0 * s & d 1 0 0 0 * s ^ d 0 1 1 0 * ~s | d 1 0 1 1 * ~s & d 0 0 1 0 * ~s ^ d 1 0 0 1 * s | ~d 1 1 0 1 * s & ~d 0 1 0 0 * s ^ ~d 1 0 0 1 * ~(s | d) 0 0 0 1 * ~(s & d) 0 1 1 1 * ~(s ^ d) 1 0 0 1 * * Note that the following three operations are equivalent: * ~(s ^ d) * ~s ^ d * s ^ ~d * and in the implementation, we call them out with the first form; * namely, ~(s ^ d). * * Of the 16 possible binary combinations of src and dest bits, * the remaining 4 unique ones are independent of the src bit. * They depend on either just the dest bit or on neither * the src nor dest bits: * * d 1 0 1 0 (indep. of s) * ~d 0 1 0 1 (indep. of s) * CLR 0 0 0 0 (indep. of both s & d) * SET 1 1 1 1 (indep. of both s & d) * * As mentioned above, three of these are implemented by * rasteropUniLow(), and one is a no-op. * * How can these operation codes be represented by bits * in such a way that when the basic operations are performed * on the bits the results are unique for unique * operations, and mimic the logic table given above? * * The answer is to choose a particular order of the pairings: * (sd) (11) (10) (01) (00) * (which happens to be the same as in the above table) * and to translate the result into 4-bit representations * of s and d. For example, the Sun rasterop choice * (omitting the extra bit for clipping) is * * PIX_SRC 0xc * PIX_DST 0xa * * This corresponds to our pairing order given above: * (sd) (11) (10) (01) (00) * where for s = 1 we get the bit pattern * PIX_SRC: 1 1 0 0 (0xc) * and for d = 1 we get the pattern * PIX_DST: 1 0 1 0 (0xa) * * OK, that's the pairing order that Sun chose. How many different * ways can we assign bit patterns to PIX_SRC and PIX_DST to get * the boolean ops to work out? Any of the 4 pairs can be put * in the first position, any of the remaining 3 pairs can go * in the second; and one of the remaining 2 pairs can go the the third. * There is a total of 4*3*2 = 24 ways these pairs can be permuted. */ l_int32 pixRasterop(PIX *pixd, l_int32 dx, l_int32 dy, l_int32 dw, l_int32 dh, l_int32 op, PIX *pixs, l_int32 sx, l_int32 sy) { l_int32 dd; PROCNAME("pixRasterop"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (op == PIX_DST) /* no-op */ return 0; /* Check if operation is only on dest */ dd = pixGetDepth(pixd); if (op == PIX_CLR || op == PIX_SET || op == PIX_NOT(PIX_DST)) { rasteropUniLow(pixGetData(pixd), pixGetWidth(pixd), pixGetHeight(pixd), dd, pixGetWpl(pixd), dx, dy, dw, dh, op); return 0; } if (!pixs) return ERROR_INT("pixs not defined", procName, 1); /* Check depth of src and dest; these must agree */ if (dd != pixGetDepth(pixs)) return ERROR_INT("depths of pixs and pixd differ", procName, 1); rasteropLow(pixGetData(pixd), pixGetWidth(pixd), pixGetHeight(pixd), dd, pixGetWpl(pixd), dx, dy, dw, dh, op, pixGetData(pixs), pixGetWidth(pixs), pixGetHeight(pixs), pixGetWpl(pixs), sx, sy); return 0; }
/*! * pixRemoveWithIndicator() * * Input: pixs (1 bpp pix from which components are removed; in-place) * pixa (of connected components in pixs) * na (numa indicator: remove components corresponding to 1s) * Return: 0 if OK, 1 on error * * Notes: * (1) This complements pixAddWithIndicator(). Here, the selected * components are set subtracted from pixs. */ l_int32 pixRemoveWithIndicator(PIX *pixs, PIXA *pixa, NUMA *na) { l_int32 i, n, ival, x, y, w, h; BOX *box; PIX *pix; PROCNAME("pixRemoveWithIndicator"); 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_DST & PIX_NOT(PIX_SRC), pix, 0, 0); boxDestroy(&box); pixDestroy(&pix); } } return 0; }
/*! * kernelDisplayInPix() * * Input: kernel * size (of grid interiors; odd; either 1 or a minimum size * of 17 is enforced) * gthick (grid thickness; either 0 or a minimum size of 2 * is enforced) * Return: pix (display of kernel), or null on error * * Notes: * (1) This gives a visual representation of a kernel. * (2) There are two modes of display: * (a) Grid lines of minimum width 2, surrounding regions * representing kernel elements of minimum size 17, * with a "plus" mark at the kernel origin, or * (b) A pix without grid lines and using 1 pixel per kernel element. * (3) For both cases, the kernel absolute value is displayed, * normalized such that the maximum absolute value is 255. * (4) Large 2D separable kernels should be used for convolution * with two 1D kernels. However, for the bilateral filter, * the computation time is independent of the size of the * 2D content kernel. */ PIX * kernelDisplayInPix(L_KERNEL *kel, l_int32 size, l_int32 gthick) { l_int32 i, j, w, h, sx, sy, cx, cy, width, x0, y0; l_int32 normval; l_float32 minval, maxval, max, val, norm; PIX *pixd, *pixt0, *pixt1; PROCNAME("kernelDisplayInPix"); if (!kel) return (PIX *)ERROR_PTR("kernel not defined", procName, NULL); /* Normalize the max value to be 255 for display */ kernelGetParameters(kel, &sy, &sx, &cy, &cx); kernelGetMinMax(kel, &minval, &maxval); max = L_MAX(maxval, -minval); if (max == 0.0) return (PIX *)ERROR_PTR("kernel elements all 0.0", procName, NULL); norm = 255. / (l_float32)max; /* Handle the 1 element/pixel case; typically with large kernels */ if (size == 1 && gthick == 0) { pixd = pixCreate(sx, sy, 8); for (i = 0; i < sy; i++) { for (j = 0; j < sx; j++) { kernelGetElement(kel, i, j, &val); normval = (l_int32)(norm * L_ABS(val)); pixSetPixel(pixd, j, i, normval); } } return pixd; } /* Enforce the constraints for the grid line version */ if (size < 17) { L_WARNING("size < 17; setting to 17\n", procName); size = 17; } if (size % 2 == 0) size++; if (gthick < 2) { L_WARNING("grid thickness < 2; setting to 2\n", procName); gthick = 2; } w = size * sx + gthick * (sx + 1); h = size * sy + gthick * (sy + 1); pixd = pixCreate(w, h, 8); /* Generate grid lines */ for (i = 0; i <= sy; i++) pixRenderLine(pixd, 0, gthick / 2 + i * (size + gthick), w - 1, gthick / 2 + i * (size + gthick), gthick, L_SET_PIXELS); for (j = 0; j <= sx; j++) pixRenderLine(pixd, gthick / 2 + j * (size + gthick), 0, gthick / 2 + j * (size + gthick), h - 1, gthick, L_SET_PIXELS); /* Generate mask for each element */ pixt0 = pixCreate(size, size, 1); pixSetAll(pixt0); /* Generate crossed lines for origin pattern */ pixt1 = pixCreate(size, size, 1); width = size / 8; pixRenderLine(pixt1, size / 2, (l_int32)(0.12 * size), size / 2, (l_int32)(0.88 * size), width, L_SET_PIXELS); pixRenderLine(pixt1, (l_int32)(0.15 * size), size / 2, (l_int32)(0.85 * size), size / 2, width, L_FLIP_PIXELS); pixRasterop(pixt1, size / 2 - width, size / 2 - width, 2 * width, 2 * width, PIX_NOT(PIX_DST), NULL, 0, 0); /* Paste the patterns in */ y0 = gthick; for (i = 0; i < sy; i++) { x0 = gthick; for (j = 0; j < sx; j++) { kernelGetElement(kel, i, j, &val); normval = (l_int32)(norm * L_ABS(val)); pixSetMaskedGeneral(pixd, pixt0, normval, x0, y0); if (i == cy && j == cx) pixPaintThroughMask(pixd, pixt1, x0, y0, 255 - normval); x0 += size + gthick; } y0 += size + gthick; } pixDestroy(&pixt0); pixDestroy(&pixt1); return pixd; }
// Finds image regions within the source pix (page image) and returns // the image regions as a Boxa, Pixa pair, analgous to pixConnComp. // The returned boxa, pixa may be NULL, meaning no images found. // If not NULL, they must be destroyed by the caller. void ImageFinder::FindImages(Pix* pix, Boxa** boxa, Pixa** pixa) { *boxa = NULL; *pixa = NULL; #ifdef HAVE_LIBLEPT if (pixGetWidth(pix) < kMinImageFindSize || pixGetHeight(pix) < kMinImageFindSize) return; // Not worth looking at small images. // Reduce by factor 2. Pix *pixr = pixReduceRankBinaryCascade(pix, 1, 0, 0, 0); pixDisplayWrite(pixr, textord_tabfind_show_images); // Get the halftone mask directly from Leptonica. Pix *pixht2 = pixGenHalftoneMask(pixr, NULL, NULL, textord_tabfind_show_images); pixDestroy(&pixr); if (pixht2 == NULL) return; // Expand back up again. Pix *pixht = pixExpandReplicate(pixht2, 2); pixDisplayWrite(pixht, textord_tabfind_show_images); pixDestroy(&pixht2); // Fill to capture pixels near the mask edges that were missed Pix *pixt = pixSeedfillBinary(NULL, pixht, pix, 8); pixOr(pixht, pixht, pixt); pixDestroy(&pixt); // Eliminate lines and bars that may be joined to images. Pix* pixfinemask = pixReduceRankBinaryCascade(pixht, 1, 1, 3, 3); pixDilateBrick(pixfinemask, pixfinemask, 5, 5); pixDisplayWrite(pixfinemask, textord_tabfind_show_images); Pix* pixreduced = pixReduceRankBinaryCascade(pixht, 1, 1, 1, 1); Pix* pixreduced2 = pixReduceRankBinaryCascade(pixreduced, 3, 3, 3, 0); pixDestroy(&pixreduced); pixDilateBrick(pixreduced2, pixreduced2, 5, 5); Pix* pixcoarsemask = pixExpandReplicate(pixreduced2, 8); pixDestroy(&pixreduced2); pixDisplayWrite(pixcoarsemask, textord_tabfind_show_images); // Combine the coarse and fine image masks. pixAnd(pixcoarsemask, pixcoarsemask, pixfinemask); pixDestroy(&pixfinemask); // Dilate a bit to make sure we get everything. pixDilateBrick(pixcoarsemask, pixcoarsemask, 3, 3); Pix* pixmask = pixExpandReplicate(pixcoarsemask, 16); pixDestroy(&pixcoarsemask); pixDisplayWrite(pixmask, textord_tabfind_show_images); // And the image mask with the line and bar remover. pixAnd(pixht, pixht, pixmask); pixDestroy(&pixmask); pixDisplayWrite(pixht, textord_tabfind_show_images); // Find the individual image regions in the mask image. *boxa = pixConnComp(pixht, pixa, 8); pixDestroy(&pixht); // Rectangularize the individual images. If a sharp edge in vertical and/or // horizontal occupancy can be found, it indicates a probably rectangular // image with unwanted bits merged on, so clip to the approximate rectangle. int npixes = pixaGetCount(*pixa); for (int i = 0; i < npixes; ++i) { int x_start, x_end, y_start, y_end; Pix* img_pix = pixaGetPix(*pixa, i, L_CLONE); pixDisplayWrite(img_pix, textord_tabfind_show_images); if (pixNearlyRectangular(img_pix, kMinRectangularFraction, kMaxRectangularFraction, kMaxRectangularGradient, &x_start, &y_start, &x_end, &y_end)) { // Add 1 to the size as a kludgy flag to indicate to the later stages // of processing that it is a clipped rectangular image . Pix* simple_pix = pixCreate(pixGetWidth(img_pix) + 1, pixGetHeight(img_pix), 1); pixDestroy(&img_pix); pixRasterop(simple_pix, x_start, y_start, x_end - x_start, y_end - y_start, PIX_SET, NULL, 0, 0); // pixaReplacePix takes ownership of the simple_pix. pixaReplacePix(*pixa, i, simple_pix, NULL); img_pix = pixaGetPix(*pixa, i, L_CLONE); } // Subtract the pix from the correct location in the master image. l_int32 x, y, width, height; pixDisplayWrite(img_pix, textord_tabfind_show_images); boxaGetBoxGeometry(*boxa, i, &x, &y, &width, &height); pixRasterop(pix, x, y, width, height, PIX_NOT(PIX_SRC) & PIX_DST, img_pix, 0, 0); pixDestroy(&img_pix); } #endif }