static PIX * PtaDisplayRotate(PIX *pixs, l_float32 xc, l_float32 yc) { l_int32 i, w, h; PIX *pix1, *pix2; PTA *pta1, *pta2, *pta3, *pta4; PTAA *ptaa; /* Save rotated sets of pixels */ pta1 = ptaGetPixelsFromPix(pixs, NULL); ptaa = ptaaCreate(0); for (i = 0; i < 9; i++) { pta2 = ptaRotate(pta1, xc, yc, -0.8 + 0.2 * i); ptaaAddPta(ptaa, pta2, L_INSERT); } ptaDestroy(&pta1); /* Render them */ pixGetDimensions(pixs, &w, &h, NULL); pix1 = pixCreate(w, h, 32); pixSetAll(pix1); pta3 = generatePtaFilledCircle(4); pta4 = ptaTranslate(pta3, xc, yc); pixRenderPtaArb(pix1, pta4, 255, 0, 0); /* circle at rotation center */ pix2 = pixDisplayPtaa(pix1, ptaa); /* rotated sets */ pixDestroy(&pix1); ptaDestroy(&pta3); ptaDestroy(&pta4); ptaaDestroy(&ptaa); return pix2; }
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; }
/*! * 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; }
// Helper writes a grey image to a file for use by scrollviewer. // Normally for speed we don't display the image in the layout debug windows. // If textord_debug_images is true, we draw the image as a background to some // of the debug windows. printable determines whether these // images are optimized for printing instead of screen display. static void WriteDebugBackgroundImage(bool printable, Pix* pix_binary) { Pix* grey_pix = pixCreate(pixGetWidth(pix_binary), pixGetHeight(pix_binary), 8); // Printable images are light grey on white, but for screen display // they are black on dark grey so the other colors show up well. if (printable) { pixSetAll(grey_pix); pixSetMasked(grey_pix, pix_binary, 192); } else { pixSetAllArbitrary(grey_pix, 64); pixSetMasked(grey_pix, pix_binary, 0); } AlignedBlob::IncrementDebugPix(); pixWrite(AlignedBlob::textord_debug_pix().string(), grey_pix, IFF_PNG); pixDestroy(&grey_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; }
static PIX * DisplayBoxa(BOXA *boxa) { l_int32 w, h; BOX *box; PIX *pix1, *pix2, *pix3; PIXA *pixa; pixa = pixaCreate(2); boxaGetExtent(boxa, &w, &h, &box); pix1 = pixCreate(w, h, 1); pixMaskBoxa(pix1, pix1, boxa, L_SET_PIXELS); pixaAddPix(pixa, pix1, L_INSERT); pix2 = pixCreate(w, h, 32); pixSetAll(pix2); pixRenderBoxaArb(pix2, boxa, 2, 0, 255, 0); pixRenderBoxArb(pix2, box, 3, 255, 0, 0); pixaAddPix(pixa, pix2, L_INSERT); pix3 = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 30, 2); boxDestroy(&box); pixaDestroy(&pixa); return pix3; }
/*! * pixaDisplayTiledInRows() * * Input: pixa * outdepth (output depth: 1, 8 or 32 bpp) * maxwidth (of output image) * scalefactor (applied to every pix; use 1.0 for no scaling) * background (0 for white, 1 for black; this is the color * of the spacing between the images) * spacing (between images, and on outside) * border (width of black border added to each image; * use 0 for no border) * Return: pixd (of tiled images), or null on error * * Notes: * (1) This saves a pixa to a single image file of width not to * exceed maxwidth, with background color either white or black, * and with each row tiled such that the top of each pix is * aligned and separated by 'spacing' from the next one. * A black border can be added to each pix. * (2) All pix are converted to outdepth; existing colormaps are removed. * (3) This does a reasonably spacewise-efficient job of laying * out the individual pix images into a tiled composite. */ PIX * pixaDisplayTiledInRows(PIXA *pixa, l_int32 outdepth, l_int32 maxwidth, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border) { l_int32 h; /* cumulative height over all the rows */ l_int32 w; /* cumulative height in the current row */ l_int32 bordval, wtry, wt, ht; l_int32 irow; /* index of current pix in current row */ l_int32 wmaxrow; /* width of the largest row */ l_int32 maxh; /* max height in row */ l_int32 i, j, index, n, x, y, nrows, ninrow; NUMA *nainrow; /* number of pix in the row */ NUMA *namaxh; /* height of max pix in the row */ PIX *pix, *pixn, *pixt, *pixd; PIXA *pixan; PROCNAME("pixaDisplayTiledInRows"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); if (outdepth != 1 && outdepth != 8 && outdepth != 32) return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL); if (border < 0) border = 0; if (scalefactor <= 0.0) scalefactor = 1.0; if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Normalize depths, scale, remove colormaps; optionally add border */ pixan = pixaCreate(n); bordval = (outdepth == 1) ? 1 : 0; for (i = 0; i < n; i++) { if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) continue; if (outdepth == 1) pixn = pixConvertTo1(pix, 128); else if (outdepth == 8) pixn = pixConvertTo8(pix, FALSE); else /* outdepth == 32 */ pixn = pixConvertTo32(pix); pixDestroy(&pix); if (scalefactor != 1.0) pixt = pixScale(pixn, scalefactor, scalefactor); else pixt = pixClone(pixn); if (border) pixd = pixAddBorder(pixt, border, bordval); else pixd = pixClone(pixt); pixDestroy(&pixn); pixDestroy(&pixt); pixaAddPix(pixan, pixd, L_INSERT); } if (pixaGetCount(pixan) != n) { n = pixaGetCount(pixan); L_WARNING_INT("only got %d components", procName, n); if (n == 0) { pixaDestroy(&pixan); return (PIX *)ERROR_PTR("no components", procName, NULL); } } /* Compute parameters for layout */ nainrow = numaCreate(0); namaxh = numaCreate(0); wmaxrow = 0; w = h = spacing; maxh = 0; /* max height in row */ for (i = 0, irow = 0; i < n; i++, irow++) { pixaGetPixDimensions(pixan, i, &wt, &ht, NULL); wtry = w + wt + spacing; if (wtry > maxwidth) { /* end the current row and start next one */ numaAddNumber(nainrow, irow); numaAddNumber(namaxh, maxh); wmaxrow = L_MAX(wmaxrow, w); h += maxh + spacing; irow = 0; w = wt + 2 * spacing; maxh = ht; } else { w = wtry; maxh = L_MAX(maxh, ht); } } /* Enter the parameters for the last row */ numaAddNumber(nainrow, irow); numaAddNumber(namaxh, maxh); wmaxrow = L_MAX(wmaxrow, w); h += maxh + spacing; if ((pixd = pixCreate(wmaxrow, h, outdepth)) == NULL) { numaDestroy(&nainrow); numaDestroy(&namaxh); pixaDestroy(&pixan); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } /* Reset the background color if necessary */ if ((background == 1 && outdepth == 1) || (background == 0 && outdepth != 1)) pixSetAll(pixd); /* Blit the images to the dest */ nrows = numaGetCount(nainrow); y = spacing; for (i = 0, index = 0; i < nrows; i++) { /* over rows */ numaGetIValue(nainrow, i, &ninrow); numaGetIValue(namaxh, i, &maxh); x = spacing; for (j = 0; j < ninrow; j++, index++) { /* over pix in row */ pix = pixaGetPix(pixan, index, L_CLONE); pixGetDimensions(pix, &wt, &ht, NULL); pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pix, 0, 0); pixDestroy(&pix); x += wt + spacing; } y += maxh + spacing; } numaDestroy(&nainrow); numaDestroy(&namaxh); pixaDestroy(&pixan); return pixd; }
/*! * pixaDisplayTiledAndScaled() * * Input: pixa * outdepth (output depth: 1, 8 or 32 bpp) * tilewidth (each pix is scaled to this width) * ncols (number of tiles in each row) * background (0 for white, 1 for black; this is the color * of the spacing between the images) * spacing (between images, and on outside) * border (width of additional black border on each image; * use 0 for no border) * Return: pix of tiled images, or null on error * * Notes: * (1) This can be used to tile a number of renderings of * an image that are at different scales and depths. * (2) Each image, after scaling and optionally adding the * black border, has width 'tilewidth'. Thus, the border does * not affect the spacing between the image tiles. The * maximum allowed border width is tilewidth / 5. */ PIX * pixaDisplayTiledAndScaled(PIXA *pixa, l_int32 outdepth, l_int32 tilewidth, l_int32 ncols, l_int32 background, l_int32 spacing, l_int32 border) { l_int32 x, y, w, h, wd, hd, d; l_int32 i, n, nrows, maxht, ninrow, irow, bordval; l_int32 *rowht; l_float32 scalefact; PIX *pix, *pixn, *pixt, *pixb, *pixd; PIXA *pixan; PROCNAME("pixaDisplayTiledAndScaled"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); if (outdepth != 1 && outdepth != 8 && outdepth != 32) return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL); if (border < 0 || border > tilewidth / 5) border = 0; if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Normalize scale and depth for each pix; optionally add border */ pixan = pixaCreate(n); bordval = (outdepth == 1) ? 1 : 0; for (i = 0; i < n; i++) { if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) continue; pixGetDimensions(pix, &w, &h, &d); scalefact = (l_float32)(tilewidth - 2 * border) / (l_float32)w; if (d == 1 && outdepth > 1 && scalefact < 1.0) pixt = pixScaleToGray(pix, scalefact); else pixt = pixScale(pix, scalefact, scalefact); if (outdepth == 1) pixn = pixConvertTo1(pixt, 128); else if (outdepth == 8) pixn = pixConvertTo8(pixt, FALSE); else /* outdepth == 32 */ pixn = pixConvertTo32(pixt); pixDestroy(&pixt); if (border) pixb = pixAddBorder(pixn, border, bordval); else pixb = pixClone(pixn); pixaAddPix(pixan, pixb, L_INSERT); pixDestroy(&pix); pixDestroy(&pixn); } if ((n = pixaGetCount(pixan)) == 0) { /* should not have changed! */ pixaDestroy(&pixan); return (PIX *)ERROR_PTR("no components", procName, NULL); } /* Determine the size of each row and of pixd */ wd = tilewidth * ncols + spacing * (ncols + 1); nrows = (n + ncols - 1) / ncols; if ((rowht = (l_int32 *)CALLOC(nrows, sizeof(l_int32))) == NULL) return (PIX *)ERROR_PTR("rowht array not made", procName, NULL); maxht = 0; ninrow = 0; irow = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixan, i, L_CLONE); ninrow++; pixGetDimensions(pix, &w, &h, NULL); maxht = L_MAX(h, maxht); if (ninrow == ncols) { rowht[irow] = maxht; maxht = ninrow = 0; /* reset */ irow++; } pixDestroy(&pix); } if (ninrow > 0) { /* last fencepost */ rowht[irow] = maxht; irow++; /* total number of rows */ } nrows = irow; hd = spacing * (nrows + 1); for (i = 0; i < nrows; i++) hd += rowht[i]; pixd = pixCreate(wd, hd, outdepth); if ((background == 1 && outdepth == 1) || (background == 0 && outdepth != 1)) pixSetAll(pixd); /* Now blit images to pixd */ x = y = spacing; irow = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixan, i, L_CLONE); pixGetDimensions(pix, &w, &h, NULL); if (i && ((i % ncols) == 0)) { /* start new row */ x = spacing; y += spacing + rowht[irow]; irow++; } pixRasterop(pixd, x, y, w, h, PIX_SRC, pix, 0, 0); x += tilewidth + spacing; pixDestroy(&pix); } pixaDestroy(&pixan); FREE(rowht); return pixd; }
/*! * pixaDisplayOnColor() * * Input: pixa * w, h (if set to 0, determines the size from the * b.b. of the components in pixa) * color (background color to use) * 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) If any pix in @pixa are colormapped, or if the pix have * different depths, it returns a 32 bpp pix. Otherwise, * the depth of the returned pixa equals that of the pix in @pixa. * (4) If the pixa is empty, return null. */ PIX * pixaDisplayOnColor(PIXA *pixa, l_int32 w, l_int32 h, l_uint32 bgcolor) { l_int32 i, n, xb, yb, wb, hb, hascmap, maxdepth, same; BOXA *boxa; PIX *pixt1, *pixt2, *pixd; PIXA *pixat; PROCNAME("pixaDisplayOnColor"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* If w and h are 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); } /* If any pix have colormaps, or if they have different depths, * generate rgb */ pixaAnyColormaps(pixa, &hascmap); pixaGetDepthInfo(pixa, &maxdepth, &same); if (hascmap || !same) { maxdepth = 32; pixat = pixaCreate(n); for (i = 0; i < n; i++) { pixt1 = pixaGetPix(pixa, i, L_CLONE); pixt2 = pixConvertTo32(pixt1); pixaAddPix(pixat, pixt2, L_INSERT); pixDestroy(&pixt1); } } else pixat = pixaCopy(pixa, L_CLONE); /* Make the output pix and set the background color */ if ((pixd = pixCreate(w, h, maxdepth)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); if ((maxdepth == 1 && bgcolor > 0) || (maxdepth == 2 && bgcolor >= 0x3) || (maxdepth == 4 && bgcolor >= 0xf) || (maxdepth == 8 && bgcolor >= 0xff) || (maxdepth == 16 && bgcolor >= 0xffff) || (maxdepth == 32 && bgcolor >= 0xffffff00)) { pixSetAll(pixd); } else if (bgcolor > 0) pixSetAllArbitrary(pixd, bgcolor); /* Blit each pix into its place */ for (i = 0; i < n; i++) { if (pixaGetBoxGeometry(pixat, i, &xb, &yb, &wb, &hb)) { L_WARNING("no box found!", procName); continue; } pixt1 = pixaGetPix(pixat, i, L_CLONE); pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pixt1, 0, 0); pixDestroy(&pixt1); } pixaDestroy(&pixat); return pixd; }
/*! * pixaDisplayTiled() * * Input: pixa * maxwidth (of output image) * background (0 for white, 1 for black) * spacing * Return: pix of tiled images, or null on error * * Notes: * (1) This saves a pixa to a single image file of width not to * exceed maxwidth, with background color either white or black, * and with each subimage spaced on a regular lattice. * (2) The lattice size is determined from the largest width and height, * separately, of all pix in the pixa. * (3) All pix in the pixa must be of equal depth. * (4) If any pix has a colormap, all pix are rendered in rgb. * (5) Careful: because no components are omitted, this is * dangerous if there are thousands of small components and * one or more very large one, because the size of the * resulting pix can be huge! */ PIX * pixaDisplayTiled(PIXA *pixa, l_int32 maxwidth, l_int32 background, l_int32 spacing) { l_int32 w, h, wmax, hmax, wd, hd, d, hascmap; l_int32 i, j, n, ni, ncols, nrows; l_int32 ystart, xstart, wt, ht; PIX *pix, *pixt, *pixd; PIXA *pixat; PROCNAME("pixaDisplayTiled"); if (!pixa) return (PIX *)ERROR_PTR("pixa not defined", procName, NULL); /* If any pix have colormaps, generate rgb */ if ((n = pixaGetCount(pixa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); pixaAnyColormaps(pixa, &hascmap); if (hascmap) { pixat = pixaCreate(n); for (i = 0; i < n; i++) { pixt = pixaGetPix(pixa, i, L_CLONE); pix = pixConvertTo32(pixt); pixaAddPix(pixat, pix, L_INSERT); pixDestroy(&pixt); } } else pixat = pixaCopy(pixa, L_CLONE); /* Find the largest width and height of the subimages */ wmax = hmax = 0; for (i = 0; i < n; i++) { pix = pixaGetPix(pixat, i, L_CLONE); pixGetDimensions(pix, &w, &h, NULL); if (i == 0) d = pixGetDepth(pix); else if (d != pixGetDepth(pix)) { pixDestroy(&pix); pixaDestroy(&pixat); return (PIX *)ERROR_PTR("depths not equal", procName, NULL); } if (w > wmax) wmax = w; if (h > hmax) hmax = h; pixDestroy(&pix); } /* Get the number of rows and columns and the output image size */ spacing = L_MAX(spacing, 0); ncols = (l_int32)((l_float32)(maxwidth - spacing) / (l_float32)(wmax + spacing)); nrows = (n + ncols - 1) / ncols; wd = wmax * ncols + spacing * (ncols + 1); hd = hmax * nrows + spacing * (nrows + 1); if ((pixd = pixCreate(wd, hd, d)) == NULL) { pixaDestroy(&pixat); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } #if 0 fprintf(stderr, " nrows = %d, ncols = %d, wmax = %d, hmax = %d\n", nrows, ncols, wmax, hmax); fprintf(stderr, " space = %d, wd = %d, hd = %d, n = %d\n", space, wd, hd, n); #endif /* Reset the background color if necessary */ if ((background == 1 && d == 1) || (background == 0 && d != 1)) pixSetAll(pixd); /* Blit the images to the dest */ for (i = 0, ni = 0; i < nrows; i++) { ystart = spacing + i * (hmax + spacing); for (j = 0; j < ncols && ni < n; j++, ni++) { xstart = spacing + j * (wmax + spacing); pix = pixaGetPix(pixat, ni, L_CLONE); wt = pixGetWidth(pix); ht = pixGetHeight(pix); pixRasterop(pixd, xstart, ystart, wt, ht, PIX_SRC, pix, 0, 0); pixDestroy(&pix); } } pixaDestroy(&pixat); return pixd; }
/*! * pixSearchGrayMaze() * * Input: pixs (1 bpp, maze) * xi, yi (beginning point; use same initial point * that was used to generate the maze) * xf, yf (end point, or close to it) * &ppixd (<optional return> maze with path illustrated, or * if no path possible, the part of the maze * that was searched) * Return: pta (shortest path), or null if either no path * exists or on error * * Commentary: * Consider first a slight generalization of the binary maze * search problem. Suppose that you can go through walls, * but the cost is higher (say, an increment of 3 to go into * a wall pixel rather than 1)? You're still trying to find * the shortest path. One way to do this is with an ordered * queue, and a simple way to visualize an ordered queue is as * a set of stacks, each stack being marked with the distance * of each pixel in the stack from the start. We place the * start pixel in stack 0, pop it, and process its 4 children. * Each pixel is given a distance that is incremented from that * of its parent (0 in this case), depending on if it is a wall * pixel or not. That value may be recorded on a distance map, * according to the algorithm below. For children of the first * pixel, those not on a wall go in stack 1, and wall * children go in stack 3. Stack 0 being emptied, the process * then continues with pixels being popped from stack 1. * Here is the algorithm for each child pixel. The pixel's * distance value, were it to be placed on a stack, is compared * with the value for it that is on the distance map. There * are three possible cases: * (1) If the pixel has not yet been registered, it is pushed * on its stack and the distance is written to the map. * (2) If it has previously been registered with a higher distance, * the distance on the map is relaxed to that of the * current pixel, which is then placed on its stack. * (3) If it has previously been registered with an equal * or lower value, the pixel is discarded. * The pixels are popped and processed successively from * stack 1, and when stack 1 is empty, popping starts on stack 2. * This continues until the destination pixel is popped off * a stack. The minimum path is then derived from the distance map, * going back from the end point as before. This is just Dijkstra's * algorithm for a directed graph; here, the underlying graph * (consisting of the pixels and four edges connecting each pixel * to its 4-neighbor) is a special case of a directed graph, where * each edge is bi-directional. The implementation of this generalized * maze search is left as an exercise to the reader. * * Let's generalize a bit further. Suppose the "maze" is just * a grayscale image -- think of it as an elevation map. The cost * of moving on this surface depends on the height, or the gradient, * or whatever you want. All that is required is that the cost * is specified and non-negative on each link between adjacent * pixels. Now the problem becomes: find the least cost path * moving on this surface between two specified end points. * For example, if the cost across an edge between two pixels * depends on the "gradient", you can use: * cost = 1 + L_ABS(deltaV) * where deltaV is the difference in value between two adjacent * pixels. If the costs are all integers, we can still use an array * of stacks to avoid ordering the queue (e.g., by using a heap sort.) * This is a neat problem, because you don't even have to build a * maze -- you can can use it on any grayscale image! * * Rather than using an array of stacks, a more practical * approach is to implement with a priority queue, which is * a queue that is sorted so that the elements with the largest * (or smallest) key values always come off first. The * priority queue is efficiently implemented as a heap, and * this is how we do it. Suppose you run the algorithm * using a priority queue, doing the bookkeeping with an * auxiliary image data structure that saves the distance of * each pixel put on the queue as before, according to the method * described above. We implement it as a 2-way choice by * initializing the distance array to a large value and putting * a pixel on the queue if its distance is less than the value * found on the array. When you finally pop the end pixel from * the queue, you're done, and you can trace the path backward, * either always going downhill or using an auxiliary image to * give you the direction to go at each step. This is implemented * here in searchGrayMaze(). * * Do we really have to use a sorted queue? Can we solve this * generalized maze with an unsorted queue of pixels? (Or even * an unsorted stack, doing a depth-first search (DFS)?) * Consider a different algorithm for this generalized maze, where * we travel again breadth first, but this time use a single, * unsorted queue. An auxiliary image is used as before to * store the distances and to determine if pixels get pushed * on the stack or dropped. As before, we must allow pixels * to be revisited, with relaxation of the distance if a shorter * path arrives later. As a result, we will in general have * multiple instances of the same pixel on the stack with different * distances. However, because the queue is not ordered, some of * these pixels will be popped when another instance with a lower * distance is still on the stack. Here, we're just popping them * in the order they go on, rather than setting up a priority * based on minimum distance. Thus, unlike the priority queue, * when a pixel is popped we have to check the distance map to * see if a pixel with a lower distance has been put on the queue, * and, if so, we discard the pixel we just popped. So the * "while" loop looks like this: * - pop a pixel from the queue * - check its distance against the distance stored in the * distance map; if larger, discard * - otherwise, for each of its neighbors: * - compute its distance from the start pixel * - compare this distance with that on the distance map: * - if the distance map value higher, relax the distance * and push the pixel on the queue * - if the distance map value is lower, discard the pixel * * How does this loop terminate? Before, with an ordered queue, * it terminates when you pop the end pixel. But with an unordered * queue (or stack), the first time you hit the end pixel, the * distance is not guaranteed to be correct, because the pixels * along the shortest path may not have yet been visited and relaxed. * Because the shortest path can theoretically go anywhere, * we must keep going. How do we know when to stop? Dijkstra * uses an ordered queue to systematically remove nodes from * further consideration. (Each time a pixel is popped, we're * done with it; it's "finalized" in the Dijkstra sense because * we know the shortest path to it.) However, with an unordered * queue, the brute force answer is: stop when the queue * (or stack) is empty, because then every pixel in the image * has been assigned its minimum "distance" from the start pixel. * * This is similar to the situation when you use a stack for the * simpler uniform-step problem: with breadth-first search (BFS) * the pixels on the queue are automatically ordered, so you are * done when you locate the end pixel as a neighbor of a popped pixel; * whereas depth-first search (DFS), using a stack, requires, * in general, a search of every accessible pixel. Further, if * a pixel is revisited with a smaller distance, that distance is * recorded and the pixel is put on the stack again. * * But surely, you ask, can't we stop sooner? What if the * start and end pixels are very close to each other? * OK, suppose they are, and you have very high walls and a * long snaking level path that is actually the minimum cost. * That long path can wind back and forth across the entire * maze many times before ending up at the end point, which * could be just over a wall from the start. With the unordered * queue, you very quickly get a high distance for the end * pixel, which will be relaxed to the minimum distance only * after all the pixels of the path have been visited and placed * on the queue, multiple times for many of them. So that's the * price for not ordering the queue! */ PTA * pixSearchGrayMaze(PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd) { l_int32 x, y, w, h, d; l_uint32 val, valr, vals, rpixel, gpixel, bpixel; void **lines8, **liner32, **linep8; l_int32 cost, dist, distparent, sival, sivals; MAZEEL *el, *elp; PIX *pixd; /* optionally plot the path on this RGB version of pixs */ PIX *pixr; /* for bookkeeping, to indicate the minimum distance */ /* to pixels already visited */ PIX *pixp; /* for bookkeeping, to indicate direction to parent */ L_HEAP *lh; PTA *pta; PROCNAME("pixSearchGrayMaze"); if (ppixd) *ppixd = NULL; if (!pixs) return (PTA *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 8) return (PTA *)ERROR_PTR("pixs not 8 bpp", procName, NULL); if (xi <= 0 || xi >= w) return (PTA *)ERROR_PTR("xi not valid", procName, NULL); if (yi <= 0 || yi >= h) return (PTA *)ERROR_PTR("yi not valid", procName, NULL); pixd = NULL; pta = NULL; pixr = pixCreate(w, h, 32); pixSetAll(pixr); /* initialize to max value */ pixp = pixCreate(w, h, 8); /* direction to parent stored as enum val */ lines8 = pixGetLinePtrs(pixs, NULL); linep8 = pixGetLinePtrs(pixp, NULL); liner32 = pixGetLinePtrs(pixr, NULL); lh = lheapCreate(0, L_SORT_INCREASING); /* always remove closest pixels */ /* Prime the heap with the first pixel */ pixGetPixel(pixs, xi, yi, &val); el = mazeelCreate(xi, yi, 0); /* don't need direction here */ el->distance = 0; pixGetPixel(pixs, xi, yi, &val); el->val = val; pixSetPixel(pixr, xi, yi, 0); /* distance is 0 */ lheapAdd(lh, el); /* Breadth-first search with priority queue (implemented by a heap), labeling direction to parents in pixp and minimum distance to visited pixels in pixr. Stop when we pull the destination point (xf, yf) off the queue. */ while (lheapGetCount(lh) > 0) { elp = (MAZEEL *)lheapRemove(lh); if (!elp) return (PTA *)ERROR_PTR("heap broken!!", procName, NULL); x = elp->x; y = elp->y; if (x == xf && y == yf) { /* exit condition */ FREE(elp); break; } distparent = (l_int32)elp->distance; val = elp->val; sival = val; if (x > 0) { /* check to west */ vals = GET_DATA_BYTE(lines8[y], x - 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x - 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x - 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST); /* parent to E */ el = mazeelCreate(x - 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y > 0) { /* check north */ vals = GET_DATA_BYTE(lines8[y - 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y - 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y - 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH); /* parent to S */ el = mazeelCreate(x, y - 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (x < w - 1) { /* check east */ vals = GET_DATA_BYTE(lines8[y], x + 1); valr = GET_DATA_FOUR_BYTES(liner32[y], x + 1); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y], x + 1, dist); /* new dist */ SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST); /* parent to W */ el = mazeelCreate(x + 1, y, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } if (y < h - 1) { /* check south */ vals = GET_DATA_BYTE(lines8[y + 1], x); valr = GET_DATA_FOUR_BYTES(liner32[y + 1], x); sivals = (l_int32)vals; cost = 1 + L_ABS(sivals - sival); /* cost to move to this pixel */ dist = distparent + cost; if (dist < valr) { /* shortest path so far to this pixel */ SET_DATA_FOUR_BYTES(liner32[y + 1], x, dist); /* new dist */ SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH); /* parent to N */ el = mazeelCreate(x, y + 1, 0); el->val = vals; el->distance = dist; lheapAdd(lh, el); } } FREE(elp); } lheapDestroy(&lh, TRUE); if (ppixd) { pixd = pixConvert8To32(pixs); *ppixd = pixd; } composeRGBPixel(255, 0, 0, &rpixel); /* start point */ composeRGBPixel(0, 255, 0, &gpixel); composeRGBPixel(0, 0, 255, &bpixel); /* end point */ x = xf; y = yf; pta = ptaCreate(0); while (1) { /* write path onto pixd */ ptaAddPt(pta, x, y); if (x == xi && y == yi) break; if (pixd) pixSetPixel(pixd, x, y, gpixel); pixGetPixel(pixp, x, y, &val); if (val == DIR_NORTH) y--; else if (val == DIR_SOUTH) y++; else if (val == DIR_EAST) x++; else if (val == DIR_WEST) x--; pixGetPixel(pixr, x, y, &val); #if DEBUG_PATH fprintf(stderr, "(x,y) = (%d, %d); dist = %d\n", x, y, val); #endif /* DEBUG_PATH */ } if (pixd) { pixSetPixel(pixd, xi, yi, rpixel); pixSetPixel(pixd, xf, yf, bpixel); } pixDestroy(&pixp); pixDestroy(&pixr); FREE(lines8); FREE(linep8); FREE(liner32); return pta; }
/*! * pixRotateWithAlpha() * * Input: pixs (32 bpp rgb or cmapped) * angle (radians; clockwise is positive) * pixg (<optional> 8 bpp, can be null) * fract (between 0.0 and 1.0, with 0.0 fully transparent * and 1.0 fully opaque) * Return: pixd (32 bpp rgba), or null on error * * Notes: * (1) The alpha channel is transformed separately from pixs, * and aligns with it, being fully transparent outside the * boundary of the transformed pixs. For pixels that are fully * transparent, a blending function like pixBlendWithGrayMask() * will give zero weight to corresponding pixels in pixs. * (2) Rotation is about the center of the image; for very small * rotations, just return a clone. The dest is automatically * expanded so that no image pixels are lost. * (3) Rotation is by area mapping. It doesn't matter what * color is brought in because the alpha channel will * be transparent (black) there. * (4) If pixg is NULL, it is generated as an alpha layer that is * partially opaque, using @fract. Otherwise, it is cropped * to pixs if required and @fract is ignored. The alpha * channel in pixs is never used. * (4) Colormaps are removed to 32 bpp. * (5) The default setting for the border values in the alpha channel * is 0 (transparent) for the outermost ring of pixels and * (0.5 * fract * 255) for the second ring. When blended over * a second image, this * (a) shrinks the visible image to make a clean overlap edge * with an image below, and * (b) softens the edges by weakening the aliasing there. * Use l_setAlphaMaskBorder() to change these values. * (6) A subtle use of gamma correction is to remove gamma correction * before rotation and restore it afterwards. This is done * by sandwiching this function between a gamma/inverse-gamma * photometric transform: * pixt = pixGammaTRCWithAlpha(NULL, pixs, 1.0 / gamma, 0, 255); * pixd = pixRotateWithAlpha(pixt, angle, NULL, fract); * pixGammaTRCWithAlpha(pixd, pixd, gamma, 0, 255); * pixDestroy(&pixt); * This has the side-effect of producing artifacts in the very * dark regions. * * *** Warning: implicit assumption about RGB component ordering *** */ PIX * pixRotateWithAlpha(PIX *pixs, l_float32 angle, PIX *pixg, l_float32 fract) { l_int32 ws, hs, d, spp; PIX *pixd, *pix32, *pixg2, *pixgr; PROCNAME("pixRotateWithAlpha"); if (!pixs) return (PIX *) ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &ws, &hs, &d); if (d != 32 && pixGetColormap(pixs) == NULL) return (PIX *) ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL); if (pixg && pixGetDepth(pixg) != 8) { L_WARNING("pixg not 8 bpp; using @fract transparent alpha\n", procName); pixg = NULL; } if (!pixg && (fract < 0.0 || fract > 1.0)) { L_WARNING("invalid fract; using fully opaque\n", procName); fract = 1.0; } if (!pixg && fract == 0.0) L_WARNING("transparent alpha; image will not be blended\n", procName); /* Make sure input to rotation is 32 bpp rgb, and rotate it */ if (d != 32) pix32 = pixConvertTo32(pixs); else pix32 = pixClone(pixs); spp = pixGetSpp(pix32); pixSetSpp(pix32, 3); /* ignore the alpha channel for the rotation */ pixd = pixRotate(pix32, angle, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, ws, hs); pixSetSpp(pix32, spp); /* restore initial value in case it's a clone */ pixDestroy(&pix32); /* Set up alpha layer with a fading border and rotate it */ if (!pixg) { pixg2 = pixCreate(ws, hs, 8); if (fract == 1.0) pixSetAll(pixg2); else if (fract > 0.0) pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract)); } else { pixg2 = pixResizeToMatch(pixg, NULL, ws, hs); } if (ws > 10 && hs > 10) { /* see note 8 */ pixSetBorderRingVal(pixg2, 1, (l_int32)(255.0 * fract * AlphaMaskBorderVals[0])); pixSetBorderRingVal(pixg2, 2, (l_int32)(255.0 * fract * AlphaMaskBorderVals[1])); } pixgr = pixRotate(pixg2, angle, L_ROTATE_AREA_MAP, L_BRING_IN_BLACK, ws, hs); /* Combine into a 4 spp result */ pixSetRGBComponent(pixd, pixgr, L_ALPHA_CHANNEL); pixDestroy(&pixg2); pixDestroy(&pixgr); return pixd; }
l_int32 main(int argc, char **argv) { l_int32 i, n; l_float32 a, b, c, d, e; NUMA *nax, *nafit; PIX *pixs, *pixn, *pixg, *pixb, *pixt1, *pixt2; PIXA *pixa; PTA *pta, *ptad; PTAA *ptaa1, *ptaa2; pixs = pixRead("cat-35.jpg"); /* pixs = pixRead("zanotti-78.jpg"); */ /* Normalize for varying background and binarize */ pixn = pixBackgroundNormSimple(pixs, NULL, NULL); pixg = pixConvertRGBToGray(pixn, 0.5, 0.3, 0.2); pixb = pixThresholdToBinary(pixg, 130); pixDestroy(&pixn); pixDestroy(&pixg); /* Get the textline centers */ pixa = pixaCreate(6); ptaa1 = dewarpGetTextlineCenters(pixb, 0); pixt1 = pixCreateTemplate(pixs); pixSetAll(pixt1); pixt2 = pixDisplayPtaa(pixt1, ptaa1); pixWrite("/tmp/textline1.png", pixt2, IFF_PNG); pixDisplayWithTitle(pixt2, 0, 100, "textline centers 1", 1); pixaAddPix(pixa, pixt2, L_INSERT); pixDestroy(&pixt1); /* Remove short lines */ fprintf(stderr, "Num all lines = %d\n", ptaaGetCount(ptaa1)); ptaa2 = dewarpRemoveShortLines(pixb, ptaa1, 0.8, 0); pixt1 = pixCreateTemplate(pixs); pixSetAll(pixt1); pixt2 = pixDisplayPtaa(pixt1, ptaa2); pixWrite("/tmp/textline2.png", pixt2, IFF_PNG); pixDisplayWithTitle(pixt2, 300, 100, "textline centers 2", 1); pixaAddPix(pixa, pixt2, L_INSERT); pixDestroy(&pixt1); n = ptaaGetCount(ptaa2); fprintf(stderr, "Num long lines = %d\n", n); ptaaDestroy(&ptaa1); pixDestroy(&pixb); /* Long lines over input image */ pixt1 = pixCopy(NULL, pixs); pixt2 = pixDisplayPtaa(pixt1, ptaa2); pixWrite("/tmp/textline3.png", pixt2, IFF_PNG); pixDisplayWithTitle(pixt2, 600, 100, "textline centers 3", 1); pixaAddPix(pixa, pixt2, L_INSERT); pixDestroy(&pixt1); /* Quadratic fit to curve */ pixt1 = pixCopy(NULL, pixs); for (i = 0; i < n; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetQuadraticLSF(pta, &a, &b, &c, &nafit); fprintf(stderr, "Quadratic: a = %10.6f, b = %7.3f, c = %7.3f\n", a, b, c); ptad = ptaCreateFromNuma(nax, nafit); pixDisplayPta(pixt1, pixt1, ptad); ptaDestroy(&pta); ptaDestroy(&ptad); numaDestroy(&nax); numaDestroy(&nafit); } pixWrite("/tmp/textline4.png", pixt1, IFF_PNG); pixDisplayWithTitle(pixt1, 900, 100, "textline centers 4", 1); pixaAddPix(pixa, pixt1, L_INSERT); /* Cubic fit to curve */ pixt1 = pixCopy(NULL, pixs); for (i = 0; i < n; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetCubicLSF(pta, &a, &b, &c, &d, &nafit); fprintf(stderr, "Cubic: a = %10.6f, b = %10.6f, c = %7.3f, d = %7.3f\n", a, b, c, d); ptad = ptaCreateFromNuma(nax, nafit); pixDisplayPta(pixt1, pixt1, ptad); ptaDestroy(&pta); ptaDestroy(&ptad); numaDestroy(&nax); numaDestroy(&nafit); } pixWrite("/tmp/textline5.png", pixt1, IFF_PNG); pixDisplayWithTitle(pixt1, 1200, 100, "textline centers 5", 1); pixaAddPix(pixa, pixt1, L_INSERT); /* Quartic fit to curve */ pixt1 = pixCopy(NULL, pixs); for (i = 0; i < n; i++) { pta = ptaaGetPta(ptaa2, i, L_CLONE); ptaGetArrays(pta, &nax, NULL); ptaGetQuarticLSF(pta, &a, &b, &c, &d, &e, &nafit); fprintf(stderr, "Quartic: a = %7.3f, b = %7.3f, c = %9.5f, d = %7.3f, e = %7.3f\n", a, b, c, d, e); ptad = ptaCreateFromNuma(nax, nafit); pixDisplayPta(pixt1, pixt1, ptad); ptaDestroy(&pta); ptaDestroy(&ptad); numaDestroy(&nax); numaDestroy(&nafit); } pixWrite("/tmp/textline6.png", pixt1, IFF_PNG); pixDisplayWithTitle(pixt1, 1500, 100, "textline centers 6", 1); pixaAddPix(pixa, pixt1, L_INSERT); pixaConvertToPdf(pixa, 300, 0.5, L_JPEG_ENCODE, 75, "LS fittings to textlines", "/tmp/dewarp_fittings.pdf"); pixaDestroy(&pixa); pixDestroy(&pixs); ptaaDestroy(&ptaa2); return 0; }
/*! * \brief selaAddCrossJunctions() * * \param[in] sela [optional] * \param[in] hlsize length of each line of hits from origin * \param[in] mdist distance of misses from the origin * \param[in] norient number of orientations; max of 8 * \param[in] debugflag 1 for debug output * \return sela with additional sels, or NULL on error * * <pre> * Notes: * (1) Adds hitmiss Sels for the intersection of two lines. * If the lines are very thin, they must be nearly orthogonal * to register. * (2) The number of Sels generated is equal to %norient. * (3) If %norient == 2, this generates 2 Sels of crosses, each with * two perpendicular lines of hits. One Sel has horizontal and * vertical hits; the other has hits along lines at +-45 degrees. * Likewise, if %norient == 3, this generates 3 Sels of crosses * oriented at 30 degrees with each other. * (4) It is suggested that %hlsize be chosen at least 1 greater * than %mdist. Try values of (%hlsize, %mdist) such as * (6,5), (7,6), (8,7), (9,7), etc. * </pre> */ SELA * selaAddCrossJunctions(SELA *sela, l_float32 hlsize, l_float32 mdist, l_int32 norient, l_int32 debugflag) { char name[L_BUF_SIZE]; l_int32 i, j, w, xc, yc; l_float64 pi, halfpi, radincr, radang; l_float64 angle; PIX *pixc, *pixm, *pixt; PIXA *pixa; PTA *pta1, *pta2, *pta3, *pta4; SEL *sel; PROCNAME("selaAddCrossJunctions"); if (hlsize <= 0) return (SELA *)ERROR_PTR("hlsize not > 0", procName, NULL); if (norient < 1 || norient > 8) return (SELA *)ERROR_PTR("norient not in [1, ... 8]", procName, NULL); if (!sela) { if ((sela = selaCreate(0)) == NULL) return (SELA *)ERROR_PTR("sela not made", procName, NULL); } pi = 3.1415926535; halfpi = 3.1415926535 / 2.0; radincr = halfpi / (l_float64)norient; w = (l_int32)(2.2 * (L_MAX(hlsize, mdist) + 0.5)); if (w % 2 == 0) w++; xc = w / 2; yc = w / 2; pixa = pixaCreate(norient); for (i = 0; i < norient; i++) { /* Set the don't cares */ pixc = pixCreate(w, w, 32); pixSetAll(pixc); /* Add the green lines of hits */ pixm = pixCreate(w, w, 1); radang = (l_float32)i * radincr; pta1 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang); pta2 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang + halfpi); pta3 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang + pi); pta4 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang + pi + halfpi); ptaJoin(pta1, pta2, 0, -1); ptaJoin(pta1, pta3, 0, -1); ptaJoin(pta1, pta4, 0, -1); pixRenderPta(pixm, pta1, L_SET_PIXELS); pixPaintThroughMask(pixc, pixm, 0, 0, 0x00ff0000); ptaDestroy(&pta1); ptaDestroy(&pta2); ptaDestroy(&pta3); ptaDestroy(&pta4); /* Add red misses between the lines */ for (j = 0; j < 4; j++) { angle = radang + (j - 0.5) * halfpi; pixSetPixel(pixc, xc + (l_int32)(mdist * cos(angle)), yc + (l_int32)(mdist * sin(angle)), 0xff000000); } /* Add dark green for origin */ pixSetPixel(pixc, xc, yc, 0x00550000); /* Generate the sel */ sel = selCreateFromColorPix(pixc, NULL); sprintf(name, "sel_cross_%d", i); selaAddSel(sela, sel, name, 0); if (debugflag) { pixt = pixScaleBySampling(pixc, 10.0, 10.0); pixaAddPix(pixa, pixt, L_INSERT); } pixDestroy(&pixm); pixDestroy(&pixc); } if (debugflag) { l_int32 w; lept_mkdir("lept/sel"); pixaGetPixDimensions(pixa, 0, &w, NULL, NULL); pixt = pixaDisplayTiledAndScaled(pixa, 32, w, 1, 0, 10, 2); pixWrite("/tmp/lept/sel/xsel1.png", pixt, IFF_PNG); pixDisplay(pixt, 0, 100); pixDestroy(&pixt); pixt = selaDisplayInPix(sela, 15, 2, 20, 1); pixWrite("/tmp/lept/sel/xsel2.png", pixt, IFF_PNG); pixDisplay(pixt, 500, 100); pixDestroy(&pixt); selaWriteStream(stderr, sela); } pixaDestroy(&pixa); return sela; }
main(int argc, char **argv) { l_int32 i, j, w, h; l_int32 minsum[5] = { 2, 40, 50, 50, 70}; l_int32 skipdist[5] = { 5, 5, 10, 10, 30}; l_int32 delta[5] = { 2, 10, 10, 25, 40}; l_int32 maxbg[5] = {10, 15, 10, 20, 40}; BOX *box1, *box2, *box3, *box4; BOXA *boxa; PIX *pixs, *pixc, *pixt, *pixd, *pix32; PIXA *pixas, *pixad; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; /* Generate and save 1 bpp masks */ pixas = pixaCreate(0); pixs = pixCreate(300, 250, 1); pixSetAll(pixs); box1 = boxCreate(50, 0, 140, 25); box2 = boxCreate(120, 100, 100, 25); box3 = boxCreate(75, 170, 80, 20); box4 = boxCreate(150, 80, 25, 70); pixClearInRect(pixs, box1); pixaAddPix(pixas, pixs, L_COPY); pixt = pixRotateOrth(pixs, 1); pixaAddPix(pixas, pixt, L_INSERT); pixClearInRect(pixs, box2); pixaAddPix(pixas, pixs, L_COPY); pixt = pixRotateOrth(pixs, 1); pixaAddPix(pixas, pixt, L_INSERT); pixClearInRect(pixs, box3); pixaAddPix(pixas, pixs, L_COPY); pixt = pixRotateOrth(pixs, 1); pixaAddPix(pixas, pixt, L_INSERT); pixClearInRect(pixs, box4); pixaAddPix(pixas, pixs, L_COPY); pixt = pixRotateOrth(pixs, 1); pixaAddPix(pixas, pixt, L_INSERT); boxDestroy(&box1); boxDestroy(&box2); boxDestroy(&box3); boxDestroy(&box4); pixDestroy(&pixs); /* Do 5 splittings on each of the 8 masks */ pixad = pixaCreate(0); for (j = 0; j < 8; j++) { pixt = pixaGetPix(pixas, j, L_CLONE); pixGetDimensions(pixt, &w, &h, NULL); pix32 = pixCreate(w, h, 32); pixSetAll(pix32); pixPaintThroughMask(pix32, pixt, 0, 0, 0xc0c0c000); pixSaveTiled(pix32, pixad, 1, 1, 30, 32); for (i = 0; i < 5; i++) { pixc = pixCopy(NULL, pix32); boxa = pixSplitComponentIntoBoxa(pixt, NULL, minsum[i], skipdist[i], delta[i], maxbg[i], 0, 1); /* boxaWriteStream(stderr, boxa); */ pixd = pixBlendBoxaRandom(pixc, boxa, 0.4); pixRenderBoxaArb(pixd, boxa, 2, 255, 0, 0); pixSaveTiled(pixd, pixad, 1, 0, 30, 32); pixDestroy(&pixd); pixDestroy(&pixc); boxaDestroy(&boxa); } pixDestroy(&pixt); pixDestroy(&pix32); } /* Display results */ pixd = pixaDisplay(pixad, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 0 */ pixDisplayWithTitle(pixd, 100, 100, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixad); /* Put the 8 masks all together, and split 5 ways */ pixad = pixaCreate(0); pixs = pixaDisplayOnLattice(pixas, 325, 325); pixGetDimensions(pixs, &w, &h, NULL); pix32 = pixCreate(w, h, 32); pixSetAll(pix32); pixPaintThroughMask(pix32, pixs, 0, 0, 0xc0c0c000); pixSaveTiled(pix32, pixad, 1, 1, 30, 32); for (i = 0; i < 5; i++) { pixc = pixCopy(NULL, pix32); boxa = pixSplitIntoBoxa(pixs, minsum[i], skipdist[i], delta[i], maxbg[i], 0, 1); /* boxaWriteStream(stderr, boxa); */ pixd = pixBlendBoxaRandom(pixc, boxa, 0.4); pixRenderBoxaArb(pixd, boxa, 2, 255, 0, 0); pixSaveTiled(pixd, pixad, 1, 0, 30, 32); pixDestroy(&pixd); pixDestroy(&pixc); boxaDestroy(&boxa); } pixDestroy(&pix32); pixDestroy(&pixs); /* Display results */ pixd = pixaDisplay(pixad, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 1 */ pixDisplayWithTitle(pixd, 600, 100, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixad); pixaDestroy(&pixas); regTestCleanup(rp); return 0; }
main(int argc, char **argv) { l_int32 i, j, w1, h1, w2, h2, w, h, same; BOX *box1, *box2; PIX *pixs, *pixs1, *pixs2, *pix1, *pix2; PIX *pixg, *pixg1, *pixg2, *pixc2, *pixbl, *pixd; PIXA *pixa; static char mainName[] = "blend2_reg"; /* --- Set up the 8 bpp blending image --- */ pixg = pixCreate(660, 500, 8); for (i = 0; i < 500; i++) for (j = 0; j < 660; j++) pixSetPixel(pixg, j, i, (l_int32)(0.775 * j) % 256); /* --- Set up the initial color images to be blended together --- */ pixs1 = pixRead("wyom.jpg"); pixs2 = pixRead("fish24.jpg"); pixGetDimensions(pixs1, &w1, &h1, NULL); pixGetDimensions(pixs2, &w2, &h2, NULL); h = L_MIN(h1, h2); w = L_MIN(w1, w2); box1 = boxCreate(0, 0, w1, h1); box2 = boxCreate(0, 300, 660, 500); pix1 = pixClipRectangle(pixs1, box1, NULL); pix2 = pixClipRectangle(pixs2, box2, NULL); pixDestroy(&pixs1); pixDestroy(&pixs2); boxDestroy(&box1); boxDestroy(&box2); /* --- Blend 2 rgb images --- */ pixa = pixaCreate(0); pixSaveTiled(pixg, pixa, 1, 1, 40, 32); pixd = pixBlendWithGrayMask(pix1, pix2, pixg, 50, 50); pixSaveTiled(pix1, pixa, 1, 1, 40, 32); pixSaveTiled(pix2, pixa, 1, 0, 40, 32); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixd); /* --- Blend 2 grayscale images --- */ pixg1 = pixConvertRGBToLuminance(pix1); pixg2 = pixConvertRGBToLuminance(pix2); pixd = pixBlendWithGrayMask(pixg1, pixg2, pixg, 50, 50); pixSaveTiled(pixg1, pixa, 1, 1, 40, 32); pixSaveTiled(pixg2, pixa, 1, 0, 40, 32); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixg1); pixDestroy(&pixg2); pixDestroy(&pixd); /* --- Blend a colormap image and an rgb image --- */ pixc2 = pixFixedOctcubeQuantGenRGB(pix2, 2); pixd = pixBlendWithGrayMask(pix1, pixc2, pixg, 50, 50); pixSaveTiled(pix1, pixa, 1, 1, 40, 32); pixSaveTiled(pixc2, pixa, 1, 0, 40, 32); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixc2); pixDestroy(&pixd); /* --- Blend a colormap image and a grayscale image --- */ pixg1 = pixConvertRGBToLuminance(pix1); pixc2 = pixFixedOctcubeQuantGenRGB(pix2, 2); pixd = pixBlendWithGrayMask(pixg1, pixc2, pixg, 50, 50); pixSaveTiled(pixg1, pixa, 1, 1, 40, 32); pixSaveTiled(pixc2, pixa, 1, 0, 40, 32); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixd); pixd = pixBlendWithGrayMask(pixg1, pixc2, pixg, -100, -100); pixSaveTiled(pixg1, pixa, 1, 1, 40, 32); pixSaveTiled(pixc2, pixa, 1, 0, 40, 32); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixd); pixDestroy(&pixg1); pixDestroy(&pixc2); /* --- Test png read/write with alpha channel --- */ /* First make pixs1, using pixg as the alpha channel */ pixs = pixRead("fish24.jpg"); box1 = boxCreate(0, 300, 660, 500); pixs1 = pixClipRectangle(pixs, box1, NULL); pixSaveTiled(pixs1, pixa, 1, 1, 40, 32); pixSetRGBComponent(pixs1, pixg, L_ALPHA_CHANNEL); /* To see the alpha channel, blend with a black image */ pixbl = pixCreate(660, 500, 32); pixd = pixBlendWithGrayMask(pixbl, pixs1, NULL, 0, 0); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixd); /* Write out the RGBA image and read it back */ l_pngSetWriteAlpha(1); pixWrite("/tmp/junkpixs1.png", pixs1, IFF_PNG); l_pngSetStripAlpha(0); pixs2 = pixRead("/tmp/junkpixs1.png"); /* Make sure that the alpha channel image hasn't changed */ pixg2 = pixGetRGBComponent(pixs2, L_ALPHA_CHANNEL); pixEqual(pixg, pixg2, &same); if (same) fprintf(stderr, "PNG with alpha read/write OK\n"); else fprintf(stderr, "PNG with alpha read/write failed\n"); /* Blend again with a black image */ pixd = pixBlendWithGrayMask(pixbl, pixs2, NULL, 0, 0); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixd); /* Blend with a white image */ pixSetAll(pixbl); pixd = pixBlendWithGrayMask(pixbl, pixs2, NULL, 0, 0); pixSaveTiled(pixd, pixa, 1, 0, 40, 32); pixDestroy(&pixd); l_pngSetWriteAlpha(0); /* reset to default */ l_pngSetStripAlpha(1); /* reset to default */ pixDestroy(&pixbl); pixDestroy(&pixs); pixDestroy(&pixs1); pixDestroy(&pixs2); pixDestroy(&pixg2); boxDestroy(&box1); /* --- Display results --- */ pixd = pixaDisplay(pixa, 0, 0); pixDisplay(pixd, 100, 100); pixWrite("/tmp/junkblend2.jpg", pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixg); pixDestroy(&pix1); pixDestroy(&pix2); return 0; }
int main(int argc, char **argv) { l_int32 i, j, w, h, same, width, height, cx, cy; l_uint32 val; BOX *box; PIX *pix0, *pixs, *pixse, *pixd1, *pixd2; SEL *sel; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pix0 = pixRead("feyn-fract.tif"); box = boxCreate(293, 37, pixGetWidth(pix0) - 691, pixGetHeight(pix0) -145); pixs = pixClipRectangle(pix0, box, NULL); boxDestroy(&box); if (rp->display) pixDisplay(pixs, 100, 100); /* Test 63 different sizes */ for (width = 1; width <= 25; width += 3) { /* 9 values */ for (height = 1; height <= 25; height += 4) { /* 7 values */ 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); regTestCompareValues(rp, 1, same, 0.0); /* 0 - 62 */ if (same == 0) { fprintf(stderr, "Results differ for SE (width,height) = (%d,%d)\n", width, height); } pixDestroy(&pixse); pixDestroy(&pixd1); pixDestroy(&pixd2); selDestroy(&sel); } } pixDestroy(&pix0); pixDestroy(&pixs); return regTestCleanup(rp); }
int main(int argc, char **argv) { PIX *pixs2, *pixs3, *pixb1, *pixb2, *pixb3; PIX *pixr2, *pixr3; PIX *pixc1, *pixc2, *pixc3, *pixcs1, *pixcs2, *pixcs3; PIX *pixd, *pixt1, *pixt2, *pixt3; PTA *ptas1, *ptas2, *ptas3, *ptad1, *ptad2, *ptad3; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixc1 = pixRead("test24.jpg"); pixc2 = pixRead("wyom.jpg"); pixc3 = pixRead("marge.jpg"); /* Test alpha blend scaling */ pixd = pixCreate(900, 400, 32); pixSetAll(pixd); pixs2 = pixScaleWithAlpha(pixc2, 0.5, 0.5, NULL, 0.3); pixs3 = pixScaleWithAlpha(pixc3, 0.4, 0.4, NULL, 0.7); pixb1 = pixBlendWithGrayMask(pixd, pixs3, NULL, 100, 100); pixb2 = pixBlendWithGrayMask(pixb1, pixs2, NULL, 300, 130); pixb3 = pixBlendWithGrayMask(pixb2, pixs3, NULL, 600, 160); regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 0 */ pixDisplayWithTitle(pixb3, 900, 100, NULL, rp->display); pixDestroy(&pixd); pixDestroy(&pixs2); pixDestroy(&pixs3); pixDestroy(&pixb1); pixDestroy(&pixb2); pixDestroy(&pixb3); /* Test alpha blend rotation */ pixd = pixCreate(1200, 800, 32); pixSetAll(pixd); pixr3 = pixRotateWithAlpha(pixc3, -0.3, NULL, 1.0); pixr2 = pixRotateWithAlpha(pixc2, +0.3, NULL, 1.0); pixb3 = pixBlendWithGrayMask(pixd, pixr3, NULL, 100, 100); pixb2 = pixBlendWithGrayMask(pixb3, pixr2, NULL, 400, 100); regTestWritePixAndCheck(rp, pixb2, IFF_PNG); /* 1 */ pixDisplayWithTitle(pixb2, 500, 100, NULL, rp->display); pixDestroy(&pixd); pixDestroy(&pixr3); pixDestroy(&pixr2); pixDestroy(&pixb3); pixDestroy(&pixb2); pixcs1 = pixScale(pixc1, 0.35, 0.35); pixcs2 = pixScale(pixc2, 0.55, 0.55); pixcs3 = pixScale(pixc3, 0.65, 0.65); /* Test alpha blend affine */ pixd = pixCreate(800, 900, 32); pixSetAll(pixd); MakePtas(2, 3, &ptas1, &ptad1); MakePtas(4, 3, &ptas2, &ptad2); MakePtas(3, 3, &ptas3, &ptad3); pixt1 = pixAffinePtaWithAlpha(pixcs1, ptad1, ptas1, NULL, 1.0, 300); pixt2 = pixAffinePtaWithAlpha(pixcs2, ptad2, ptas2, NULL, 0.8, 400); pixt3 = pixAffinePtaWithAlpha(pixcs3, ptad3, ptas3, NULL, 0.7, 300); pixb1 = pixBlendWithGrayMask(pixd, pixt1, NULL, -250, 20); pixb2 = pixBlendWithGrayMask(pixb1, pixt2, NULL, -150, -250); pixb3 = pixBlendWithGrayMask(pixb2, pixt3, NULL, -100, 220); regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 2 */ pixDisplayWithTitle(pixb3, 100, 100, NULL, rp->display); pixDestroy(&pixd); pixDestroy(&pixb1); pixDestroy(&pixb2); pixDestroy(&pixb3); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); ptaDestroy(&ptas1); ptaDestroy(&ptas2); ptaDestroy(&ptas3); ptaDestroy(&ptad1); ptaDestroy(&ptad2); ptaDestroy(&ptad3); /* Test alpha blend projective */ pixd = pixCreate(900, 900, 32); pixSetAll(pixd); MakePtas(2, 4, &ptas1, &ptad1); MakePtas(4, 4, &ptas2, &ptad2); MakePtas(3, 4, &ptas3, &ptad3); pixt1 = pixProjectivePtaWithAlpha(pixcs1, ptad1, ptas1, NULL, 1.0, 300); pixt2 = pixProjectivePtaWithAlpha(pixcs2, ptad2, ptas2, NULL, 0.8, 400); pixt3 = pixProjectivePtaWithAlpha(pixcs3, ptad3, ptas3, NULL, 0.7, 400); pixb1 = pixBlendWithGrayMask(pixd, pixt1, NULL, -150, 20); pixb2 = pixBlendWithGrayMask(pixb1, pixt2, NULL, -50, -250); pixb3 = pixBlendWithGrayMask(pixb2, pixt3, NULL, -100, 220); regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 3 */ pixDisplayWithTitle(pixb3, 300, 100, NULL, rp->display); pixDestroy(&pixd); pixDestroy(&pixb1); pixDestroy(&pixb2); pixDestroy(&pixb3); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); ptaDestroy(&ptas1); ptaDestroy(&ptas2); ptaDestroy(&ptas3); ptaDestroy(&ptad1); ptaDestroy(&ptad2); ptaDestroy(&ptad3); /* Test alpha blend bilinear */ pixd = pixCreate(900, 900, 32); pixSetAll(pixd); MakePtas(2, 4, &ptas1, &ptad1); MakePtas(4, 4, &ptas2, &ptad2); MakePtas(3, 4, &ptas3, &ptad3); pixt1 = pixBilinearPtaWithAlpha(pixcs1, ptad1, ptas1, NULL, 1.0, 300); pixt2 = pixBilinearPtaWithAlpha(pixcs2, ptad2, ptas2, NULL, 0.8, 400); pixt3 = pixBilinearPtaWithAlpha(pixcs3, ptad3, ptas3, NULL, 0.7, 400); pixb1 = pixBlendWithGrayMask(pixd, pixt1, NULL, -150, 20); pixb2 = pixBlendWithGrayMask(pixb1, pixt2, NULL, -50, -250); pixb3 = pixBlendWithGrayMask(pixb2, pixt3, NULL, -100, 220); regTestWritePixAndCheck(rp, pixb3, IFF_PNG); /* 4 */ pixDisplayWithTitle(pixb3, 500, 100, NULL, rp->display); pixDestroy(&pixd); pixDestroy(&pixb1); pixDestroy(&pixb2); pixDestroy(&pixb3); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); ptaDestroy(&ptas1); ptaDestroy(&ptas2); ptaDestroy(&ptas3); ptaDestroy(&ptad1); ptaDestroy(&ptad2); ptaDestroy(&ptad3); pixDestroy(&pixc1); pixDestroy(&pixc2); pixDestroy(&pixc3); pixDestroy(&pixcs1); pixDestroy(&pixcs2); pixDestroy(&pixcs3); return regTestCleanup(rp); }
// Auto page segmentation. Divide the page image into blocks of uniform // text linespacing and images. // Width, height and resolution are derived from the input image. // If the pix is non-NULL, then it is assumed to be the input, and it is // copied to the image, otherwise the image is used directly. // The output goes in the blocks list with corresponding TO_BLOCKs in the // to_blocks list. // If single_column is true, then no attempt is made to divide the image // into columns, but multiple blocks are still made if the text is of // non-uniform linespacing. int Tesseract::AutoPageSeg(int width, int height, int resolution, bool single_column, IMAGE* image, BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks) { int vertical_x = 0; int vertical_y = 1; TabVector_LIST v_lines; TabVector_LIST h_lines; ICOORD bleft(0, 0); Boxa* boxa = NULL; Pixa* pixa = NULL; // The blocks made by the ColumnFinder. Moved to blocks before return. BLOCK_LIST found_blocks; #ifdef HAVE_LIBLEPT if (pix_binary_ != NULL) { if (textord_debug_images) { Pix* grey_pix = pixCreate(width, height, 8); // Printable images are light grey on white, but for screen display // they are black on dark grey so the other colors show up well. if (textord_debug_printable) { pixSetAll(grey_pix); pixSetMasked(grey_pix, pix_binary_, 192); } else { pixSetAllArbitrary(grey_pix, 64); pixSetMasked(grey_pix, pix_binary_, 0); } AlignedBlob::IncrementDebugPix(); pixWrite(AlignedBlob::textord_debug_pix().string(), grey_pix, IFF_PNG); pixDestroy(&grey_pix); } if (tessedit_dump_pageseg_images) pixWrite("tessinput.png", pix_binary_, IFF_PNG); // Leptonica is used to find the lines and image regions in the input. LineFinder::FindVerticalLines(resolution, pix_binary_, &vertical_x, &vertical_y, &v_lines); LineFinder::FindHorizontalLines(resolution, pix_binary_, &h_lines); if (tessedit_dump_pageseg_images) pixWrite("tessnolines.png", pix_binary_, IFF_PNG); ImageFinder::FindImages(pix_binary_, &boxa, &pixa); if (tessedit_dump_pageseg_images) pixWrite("tessnoimages.png", pix_binary_, IFF_PNG); // Copy the Pix to the IMAGE. image->FromPix(pix_binary_); if (single_column) v_lines.clear(); } #endif TO_BLOCK_LIST land_blocks, port_blocks; TBOX page_box; // The rest of the algorithm uses the usual connected components. find_components(blocks, &land_blocks, &port_blocks, &page_box); TO_BLOCK_IT to_block_it(&port_blocks); ASSERT_HOST(!to_block_it.empty()); for (to_block_it.mark_cycle_pt(); !to_block_it.cycled_list(); to_block_it.forward()) { TO_BLOCK* to_block = to_block_it.data(); TBOX blkbox = to_block->block->bounding_box(); if (to_block->line_size >= 2) { // Note: if there are multiple blocks, then v_lines, boxa, and pixa // are empty on the next iteration, but in this case, we assume // that there aren't any interesting line separators or images, since // it means that we have a pre-defined unlv zone file. ColumnFinder finder(static_cast<int>(to_block->line_size), blkbox.botleft(), blkbox.topright(), &v_lines, &h_lines, vertical_x, vertical_y); if (finder.FindBlocks(height, resolution, single_column, to_block, boxa, pixa, &found_blocks, to_blocks) < 0) return -1; finder.ComputeDeskewVectors(&deskew_, &reskew_); boxa = NULL; pixa = NULL; } } #ifdef HAVE_LIBLEPT boxaDestroy(&boxa); pixaDestroy(&pixa); #endif blocks->clear(); BLOCK_IT block_it(blocks); // Move the found blocks to the input/output blocks. block_it.add_list_after(&found_blocks); if (textord_debug_images) { // The debug image is no longer needed so delete it. unlink(AlignedBlob::textord_debug_pix().string()); } return 0; }
/*! * \brief boxaDisplayTiled() * * \param[in] boxas * \param[in] pixa [optional] background for each box * \param[in] first index of first box * \param[in] last index of last box; use -1 to go to end * \param[in] maxwidth of output image * \param[in] linewidth width of box outlines, before scaling * \param[in] scalefactor applied to every box; use 1.0 for no scaling * \param[in] background 0 for white, 1 for black; this is the color * of the spacing between the images * \param[in] spacing between images, and on outside * \param[in] border width of black border added to each image; * use 0 for no border * \return pixd of tiled images of boxes, or NULL on error * * <pre> * Notes: * (1) Displays each box separately in a tiled 32 bpp image. * (2) If pixa is defined, it must have the same count as the boxa, * and it will be a background over with each box is rendered. * If pixa is not defined, the boxes will be rendered over * blank images of identical size. * (3) See pixaDisplayTiledInRows() for other parameters. * </pre> */ PIX * boxaDisplayTiled(BOXA *boxas, PIXA *pixa, l_int32 first, l_int32 last, l_int32 maxwidth, l_int32 linewidth, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border) { char buf[32]; l_int32 i, n, npix, w, h, fontsize; L_BMF *bmf; BOX *box; BOXA *boxa; PIX *pix1, *pix2, *pixd; PIXA *pixat; PROCNAME("boxaDisplayTiled"); if (!boxas) return (PIX *)ERROR_PTR("boxas not defined", procName, NULL); boxa = boxaSaveValid(boxas, L_COPY); n = boxaGetCount(boxa); if (pixa) { npix = pixaGetCount(pixa); if (n != npix) { boxaDestroy(&boxa); return (PIX *)ERROR_PTR("boxa and pixa counts differ", procName, NULL); } } first = L_MAX(0, first); if (last < 0) last = n - 1; if (first >= n) { boxaDestroy(&boxa); return (PIX *)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) { boxaDestroy(&boxa); return (PIX *)ERROR_PTR("first > last", procName, NULL); } /* Because the bitmap font will be reduced when tiled, choose the * font size inversely with the scale factor. */ if (scalefactor > 0.8) fontsize = 6; else if (scalefactor > 0.6) fontsize = 10; else if (scalefactor > 0.4) fontsize = 14; else if (scalefactor > 0.3) fontsize = 18; else fontsize = 20; bmf = bmfCreate(NULL, fontsize); pixat = pixaCreate(n); boxaGetExtent(boxa, &w, &h, NULL); for (i = first; i <= last; i++) { box = boxaGetBox(boxa, i, L_CLONE); if (!pixa) { pix1 = pixCreate(w, h, 32); pixSetAll(pix1); } else { pix1 = pixaGetPix(pixa, i, L_COPY); } pixSetBorderVal(pix1, 0, 0, 0, 2, 0x0000ff00); snprintf(buf, sizeof(buf), "%d", i); pix2 = pixAddSingleTextblock(pix1, bmf, buf, 0x00ff0000, L_ADD_BELOW, NULL); pixDestroy(&pix1); pixRenderBoxArb(pix2, box, linewidth, 255, 0, 0); pixaAddPix(pixat, pix2, L_INSERT); boxDestroy(&box); } bmfDestroy(&bmf); boxaDestroy(&boxa); pixd = pixaDisplayTiledInRows(pixat, 32, maxwidth, scalefactor, background, spacing, border); pixaDestroy(&pixat); return pixd; }
/*! * Note: this method is generally inferior to pixHasColorRegions(); it * is retained as a reference only * * \brief pixFindColorRegionsLight() * * \param[in] pixs 32 bpp rgb * \param[in] pixm [optional] 1 bpp mask image * \param[in] factor subsample factor; integer >= 1 * \param[in] darkthresh threshold to eliminate dark pixels (e.g., text) * from consideration; typ. 70; -1 for default. * \param[in] lightthresh threshold for minimum gray value at 95% rank * near white; typ. 220; -1 for default * \param[in] mindiff minimum difference from 95% rank value, used * to count darker pixels; typ. 50; -1 for default * \param[in] colordiff minimum difference in (max - min) component to * qualify as a color pixel; typ. 40; -1 for default * \param[out] pcolorfract fraction of 'color' pixels found * \param[out] pcolormask1 [optional] mask over background color, if any * \param[out] pcolormask2 [optional] filtered mask over background color * \param[out] pixadb [optional] debug intermediate results * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This function tries to determine if there is a significant * color or darker region on a scanned page image where part * of the image is very close to "white". It will also allow * extraction of small regions of lightly colored pixels. * If the background is darker (and reddish), use instead * pixHasColorRegions2(). * (2) If %pixm exists, only pixels under fg are considered. Typically, * the inverse of %pixm would have fg pixels over a photograph. * (3) There are four thresholds. * * %darkthresh: ignore pixels darker than this (typ. fg text). * We make a 1 bpp mask of these pixels, and then dilate it to * remove all vestiges of fg from their vicinity. * * %lightthresh: let val95 be the pixel value for which 95% * of the non-masked pixels have a lower value (darker) of * their min component. Then if val95 is darker than * %lightthresh, the image is not considered to have a * light bg, and this returns 0.0 for %colorfract. * * %mindiff: we are interested in the fraction of pixels that * have two conditions. The first is that their min component * is at least %mindiff darker than val95. * * %colordiff: the second condition is that the max-min diff * of the pixel components exceeds %colordiff. * (4) This returns in %pcolorfract the fraction of pixels that have * both a min component that is at least %mindiff below that at the * 95% rank value (where 100% rank is the lightest value), and * a max-min diff that is at least %colordiff. Without the * %colordiff constraint, gray pixels of intermediate value * could get flagged by this function. * (5) No masks are returned unless light color pixels are found. * If colorfract > 0.0 and %pcolormask1 is defined, this returns * a 1 bpp mask with fg pixels over the color background. * This mask may have some holes in it. * (6) If colorfract > 0.0 and %pcolormask2 is defined, this returns * a filtered version of colormask1. The two changes are * (a) small holes have been filled * (b) components near the border have been removed. * The latter insures that dark pixels near the edge of the * image are not included. * (7) To generate a boxa of rectangular regions from the overlap * of components in the filtered mask: * boxa1 = pixConnCompBB(colormask2, 8); * boxa2 = boxaCombineOverlaps(boxa1); * This is done here in debug mode. * </pre> */ static l_int32 pixFindColorRegionsLight(PIX *pixs, PIX *pixm, l_int32 factor, l_int32 darkthresh, l_int32 lightthresh, l_int32 mindiff, l_int32 colordiff, l_float32 *pcolorfract, PIX **pcolormask1, PIX **pcolormask2, PIXA *pixadb) { l_int32 lightbg, w, h, count; l_float32 ratio, val95, rank; BOXA *boxa1, *boxa2; NUMA *nah; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pixm1, *pixm2, *pixm3; PROCNAME("pixFindColorRegionsLight"); if (pcolormask1) *pcolormask1 = NULL; if (pcolormask2) *pcolormask2 = NULL; if (!pcolorfract) return ERROR_INT("&colorfract not defined", procName, 1); *pcolorfract = 0.0; if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs not defined or not 32 bpp", procName, 1); if (factor < 1) factor = 1; if (darkthresh < 0) darkthresh = 70; /* defaults */ if (lightthresh < 0) lightthresh = 220; if (mindiff < 0) mindiff = 50; if (colordiff < 0) colordiff = 40; /* Check if pixm covers most of the image. If so, just return. */ pixGetDimensions(pixs, &w, &h, NULL); if (pixm) { pixCountPixels(pixm, &count, NULL); ratio = (l_float32)count / ((l_float32)(w) * h); if (ratio > 0.7) { if (pixadb) L_INFO("pixm has big fg: %f5.2\n", procName, ratio); return 0; } } /* Make a mask pixm1 over the dark pixels in the image: * convert to gray using the average of the components; * threshold using %darkthresh; do a small dilation; * combine with pixm. */ pix1 = pixConvertRGBToGray(pixs, 0.33, 0.34, 0.33); if (pixadb) pixaAddPix(pixadb, pixs, L_COPY); if (pixadb) pixaAddPix(pixadb, pix1, L_COPY); pixm1 = pixThresholdToBinary(pix1, darkthresh); pixDilateBrick(pixm1, pixm1, 7, 7); if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY); if (pixm) { pixOr(pixm1, pixm1, pixm); if (pixadb) pixaAddPix(pixadb, pixm1, L_COPY); } pixDestroy(&pix1); /* Convert to gray using the minimum component value and * find the gray value at rank 0.95, that represents the light * pixels in the image. If it is too dark, quit. */ pix1 = pixConvertRGBToGrayMinMax(pixs, L_SELECT_MIN); pix2 = pixInvert(NULL, pixm1); /* pixels that are not dark */ pixGetRankValueMasked(pix1, pix2, 0, 0, factor, 0.95, &val95, &nah); pixDestroy(&pix2); if (pixadb) { L_INFO("val at 0.95 rank = %5.1f\n", procName, val95); gplotSimple1(nah, GPLOT_PNG, "/tmp/lept/histo1", "gray histo"); pix3 = pixRead("/tmp/lept/histo1.png"); pix4 = pixExpandReplicate(pix3, 2); pixaAddPix(pixadb, pix4, L_INSERT); pixDestroy(&pix3); } lightbg = (l_int32)val95 >= lightthresh; numaDestroy(&nah); if (!lightbg) { pixDestroy(&pix1); pixDestroy(&pixm1); return 0; } /* Make mask pixm2 over pixels that are darker than val95 - mindiff. */ pixm2 = pixThresholdToBinary(pix1, val95 - mindiff); if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY); pixDestroy(&pix1); /* Make a mask pixm3 over pixels that have some color saturation, * with a (max - min) component difference >= %colordiff, * and combine using AND with pixm2. */ pix2 = pixConvertRGBToGrayMinMax(pixs, L_CHOOSE_MAXDIFF); pixm3 = pixThresholdToBinary(pix2, colordiff); pixDestroy(&pix2); pixInvert(pixm3, pixm3); /* need pixels above threshold */ if (pixadb) pixaAddPix(pixadb, pixm3, L_COPY); pixAnd(pixm2, pixm2, pixm3); if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY); pixDestroy(&pixm3); /* Subtract the dark pixels represented by pixm1. * pixm2 now holds all the color pixels of interest */ pixSubtract(pixm2, pixm2, pixm1); pixDestroy(&pixm1); if (pixadb) pixaAddPix(pixadb, pixm2, L_COPY); /* But we're not quite finished. Remove pixels from any component * that is touching the image border. False color pixels can * sometimes be found there if the image is much darker near * the border, due to oxidation or reduced illumination. */ pixm3 = pixRemoveBorderConnComps(pixm2, 8); pixDestroy(&pixm2); if (pixadb) pixaAddPix(pixadb, pixm3, L_COPY); /* Get the fraction of light color pixels */ pixCountPixels(pixm3, &count, NULL); *pcolorfract = (l_float32)count / (w * h); if (pixadb) { if (count == 0) L_INFO("no light color pixels found\n", procName); else L_INFO("fraction of light color pixels = %5.3f\n", procName, *pcolorfract); } /* Debug: extract the color pixels from pixs */ if (pixadb && count > 0) { /* Use pixm3 to extract the color pixels */ pix3 = pixCreateTemplate(pixs); pixSetAll(pix3); pixCombineMasked(pix3, pixs, pixm3); pixaAddPix(pixadb, pix3, L_INSERT); /* Use additional filtering to extract the color pixels */ pix3 = pixCloseSafeBrick(NULL, pixm3, 15, 15); pixaAddPix(pixadb, pix3, L_INSERT); pix5 = pixCreateTemplate(pixs); pixSetAll(pix5); pixCombineMasked(pix5, pixs, pix3); pixaAddPix(pixadb, pix5, L_INSERT); /* Get the combined bounding boxes of the mask components * in pix3, and extract those pixels from pixs. */ boxa1 = pixConnCompBB(pix3, 8); boxa2 = boxaCombineOverlaps(boxa1, NULL); pix4 = pixCreateTemplate(pix3); pixMaskBoxa(pix4, pix4, boxa2, L_SET_PIXELS); pixaAddPix(pixadb, pix4, L_INSERT); pix5 = pixCreateTemplate(pixs); pixSetAll(pix5); pixCombineMasked(pix5, pixs, pix4); pixaAddPix(pixadb, pix5, L_INSERT); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixaAddPix(pixadb, pixs, L_COPY); } /* Optional colormask returns */ if (pcolormask2 && count > 0) *pcolormask2 = pixCloseSafeBrick(NULL, pixm3, 15, 15); if (pcolormask1 && count > 0) *pcolormask1 = pixm3; else pixDestroy(&pixm3); return 0; }
l_int32 main(int argc, char **argv) { L_BMF *bmf; PIX *pixs1, *pixs2, *pixg, *pixt, *pixd; PIXA *pixa; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; bmf = bmfCreate("./fonts", 8); pixs1 = pixCreate(301, 301, 32); pixs2 = pixCreate(601, 601, 32); pixSetAll(pixs1); pixSetAll(pixs2); pixRenderLineArb(pixs1, 0, 20, 300, 20, 5, 0, 0, 255); pixRenderLineArb(pixs1, 0, 70, 300, 70, 5, 0, 255, 0); pixRenderLineArb(pixs1, 0, 120, 300, 120, 5, 0, 255, 255); pixRenderLineArb(pixs1, 0, 170, 300, 170, 5, 255, 0, 0); pixRenderLineArb(pixs1, 0, 220, 300, 220, 5, 255, 0, 255); pixRenderLineArb(pixs1, 0, 270, 300, 270, 5, 255, 255, 0); pixRenderLineArb(pixs2, 0, 20, 300, 20, 5, 0, 0, 255); pixRenderLineArb(pixs2, 0, 70, 300, 70, 5, 0, 255, 0); pixRenderLineArb(pixs2, 0, 120, 300, 120, 5, 0, 255, 255); pixRenderLineArb(pixs2, 0, 170, 300, 170, 5, 255, 0, 0); pixRenderLineArb(pixs2, 0, 220, 300, 220, 5, 255, 0, 255); pixRenderLineArb(pixs2, 0, 270, 300, 270, 5, 255, 255, 0); /* Color, small pix */ pixa = pixaCreate(0); pixt = pixQuadraticVShear(pixs1, L_WARP_TO_LEFT, 60, -20, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "sampled-left"); pixt = pixQuadraticVShear(pixs1, L_WARP_TO_RIGHT, 60, -20, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "sampled-right"); pixt = pixQuadraticVShear(pixs1, L_WARP_TO_LEFT, 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "interpolated-left"); pixt = pixQuadraticVShear(pixs1, L_WARP_TO_RIGHT, 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "interpolated-right"); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); pixDisplayWithTitle(pixd, 50, 50, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixa); /* Grayscale, small pix */ pixg = pixConvertTo8(pixs1, 0); pixa = pixaCreate(0); pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT, 60, -20, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "sampled-left"); pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT, 60, -20, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "sampled-right"); pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT, 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "interpolated-left"); pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT, 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "interpolated-right"); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); pixDisplayWithTitle(pixd, 250, 50, NULL, rp->display); pixDestroy(&pixg); pixDestroy(&pixd); pixaDestroy(&pixa); /* Color, larger pix */ pixa = pixaCreate(0); pixt = pixQuadraticVShear(pixs2, L_WARP_TO_LEFT, 120, -40, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "sampled-left"); pixt = pixQuadraticVShear(pixs2, L_WARP_TO_RIGHT, 120, -40, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "sampled-right"); pixt = pixQuadraticVShear(pixs2, L_WARP_TO_LEFT, 120, -40, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "interpolated-left"); pixt = pixQuadraticVShear(pixs2, L_WARP_TO_RIGHT, 120, -40, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "interpolated-right"); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); pixDisplayWithTitle(pixd, 550, 50, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixa); /* Grayscale, larger pix */ pixg = pixConvertTo8(pixs2, 0); pixa = pixaCreate(0); pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT, 60, -20, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "sampled-left"); pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT, 60, -20, L_SAMPLED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "sampled-right"); pixt = pixQuadraticVShear(pixg, L_WARP_TO_LEFT, 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 1, bmf, "interpolated-left"); pixt = pixQuadraticVShear(pixg, L_WARP_TO_RIGHT, 60, -20, L_INTERPOLATED, L_BRING_IN_WHITE); PixSave(&pixt, pixa, 0, bmf, "interpolated-right"); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); pixDisplayWithTitle(pixd, 850, 50, NULL, rp->display); pixDestroy(&pixg); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixs1); pixDestroy(&pixs2); bmfDestroy(&bmf); return regTestCleanup(rp); }
/*! * pixProjectiveSampled() * * Input: pixs (all depths) * vc (vector of 8 coefficients for projective transformation) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * Return: pixd, or null on error * * Notes: * (1) Brings in either black or white pixels from the boundary. * (2) Retains colormap, which you can do for a sampled transform.. * (3) For 8 or 32 bpp, much better quality is obtained by the * somewhat slower pixProjective(). See that function * for relative timings between sampled and interpolated. */ PIX * pixProjectiveSampled(PIX *pixs, l_float32 *vc, l_int32 incolor) { l_int32 i, j, w, h, d, x, y, wpls, wpld, color, cmapindex; l_uint32 val; l_uint32 *datas, *datad, *lines, *lined; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixProjectiveSampled"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!vc) return (PIX *)ERROR_PTR("vc 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 != 32) return (PIX *)ERROR_PTR("depth not 1, 2, 4, 8 or 16", procName, NULL); /* Init all dest pixels to color to be brought in from outside */ pixd = pixCreateTemplate(pixs); if ((cmap = pixGetColormap(pixs)) != NULL) { if (incolor == L_BRING_IN_WHITE) color = 1; else color = 0; pixcmapAddBlackOrWhite(cmap, color, &cmapindex); pixSetAllArbitrary(pixd, cmapindex); } else { if ((d == 1 && incolor == L_BRING_IN_WHITE) || (d > 1 && incolor == L_BRING_IN_BLACK)) pixClearAll(pixd); else pixSetAll(pixd); } /* Scan over the dest pixels */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lined = datad + i * wpld; for (j = 0; j < w; j++) { projectiveXformSampledPt(vc, j, i, &x, &y); if (x < 0 || y < 0 || x >=w || y >= h) continue; lines = datas + y * wpls; if (d == 1) { val = GET_DATA_BIT(lines, x); SET_DATA_BIT_VAL(lined, j, val); } else if (d == 8) { val = GET_DATA_BYTE(lines, x); SET_DATA_BYTE(lined, j, val); } else if (d == 32) { lined[j] = lines[x]; } else if (d == 2) { val = GET_DATA_DIBIT(lines, x); SET_DATA_DIBIT(lined, j, val); } else if (d == 4) { val = GET_DATA_QBIT(lines, x); SET_DATA_QBIT(lined, j, val); } } } return pixd; }
int main(int argc, char **argv) { char buf[256]; l_int32 w, h, i, j, k, index, op, dir, stretch; l_float32 del, angle, angledeg; BOX *box; L_BMF *bmf; PIX *pixs, *pix1, *pix2, *pixd; PIXA *pixa; static char mainName[] = "warpertest"; if (argc != 1) return ERROR_INT("syntax: warpertest", mainName, 1); bmf = bmfCreate(NULL, 6); /* -------- Stereoscopic warping --------------*/ #if RUN_WARP pixs = pixRead("german.png"); pixGetDimensions(pixs, &w, &h, NULL); pixa = pixaCreate(50); for (i = 0; i < 50; i++) { /* need to test > 2 widths ! */ j = 7 * i; box = boxCreate(0, 0, w - j, h - j); pix1 = pixClipRectangle(pixs, box, NULL); pixd = pixWarpStereoscopic(pix1, 15, 22, 8, 30, -20, 1); pixSetChromaSampling(pixd, 0); pixaAddPix(pixa, pixd, L_INSERT); pixDestroy(&pix1); boxDestroy(&box); } pixDestroy(&pixs); pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "warp.pdf", "/tmp/warp.pdf"); pixd = pixaDisplayTiledInRows(pixa, 32, 2000, 1.0, 0, 20, 2); pixWrite("/tmp/warp.jpg", pixd, IFF_JFIF_JPEG); pixaDestroy(&pixa); pixDestroy(&pixd); #endif /* -------- Quadratic Vertical Shear --------------*/ #if RUN_QUAD_VERT_SHEAR pixs = pixCreate(501, 501, 32); pixGetDimensions(pixs, &w, &h, NULL); pixSetAll(pixs); pixRenderLineArb(pixs, 0, 30, 500, 30, 5, 0, 0, 255); pixRenderLineArb(pixs, 0, 110, 500, 110, 5, 0, 255, 0); pixRenderLineArb(pixs, 0, 190, 500, 190, 5, 0, 255, 255); pixRenderLineArb(pixs, 0, 270, 500, 270, 5, 255, 0, 0); pixRenderLineArb(pixs, 0, 360, 500, 360, 5, 255, 0, 255); pixRenderLineArb(pixs, 0, 450, 500, 450, 5, 255, 255, 0); pixa = pixaCreate(50); for (i = 0; i < 50; i++) { j = 3 * i; dir = ((i / 2) & 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; box = boxCreate(0, 0, w - j, h - j); pix1 = pixClipRectangle(pixs, box, NULL); pix2 = pixQuadraticVShear(pix1, dir, 60, -20, op, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]); pixd = pixAddSingleTextblock(pix2, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pixd, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); boxDestroy(&box); } pixDestroy(&pixs); pixaConvertToPdf(pixa, 100, 1.0, L_FLATE_ENCODE, 0, "quad_vshear.pdf", "/tmp/quad_vshear.pdf"); pixd = pixaDisplayTiledInRows(pixa, 32, 2000, 1.0, 0, 20, 2); pixWrite("/tmp/quad_vshear.jpg", pixd, IFF_PNG); pixaDestroy(&pixa); pixDestroy(&pixd); #endif /* -------- Linear Horizontal stretching --------------*/ #if RUN_LIN_HORIZ_STRETCH pixs = pixRead("german.png"); pixa = pixaCreate(50); for (k = 0; k < 2; k++) { for (i = 0; i < 25; i++) { index = 25 * k + i; stretch = 10 + 4 * i; if (k == 0) stretch = -stretch; dir = (k == 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; pix1 = pixStretchHorizontal(pixs, dir, L_LINEAR_WARP, stretch, op, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]); pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pixd, L_INSERT); pixDestroy(&pix1); } } pixDestroy(&pixs); pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "linear_hstretch.pdf", "/tmp/linear_hstretch.pdf"); pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2); pixWrite("/tmp/linear_hstretch.jpg", pixd, IFF_JFIF_JPEG); pixaDestroy(&pixa); pixDestroy(&pixd); #endif /* -------- Quadratic Horizontal stretching --------------*/ #if RUN_QUAD_HORIZ_STRETCH pixs = pixRead("german.png"); pixa = pixaCreate(50); for (k = 0; k < 2; k++) { for (i = 0; i < 25; i++) { index = 25 * k + i; stretch = 10 + 4 * i; if (k == 0) stretch = -stretch; dir = (k == 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; pix1 = pixStretchHorizontal(pixs, dir, L_QUADRATIC_WARP, stretch, op, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]); pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pixd, L_INSERT); pixDestroy(&pix1); } } pixDestroy(&pixs); pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "quad_hstretch.pdf", "/tmp/quad_hstretch.pdf"); pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2); pixWrite("/tmp/quad_hstretch.jpg", pixd, IFF_JFIF_JPEG); pixaDestroy(&pixa); pixDestroy(&pixd); #endif /* -------- Horizontal Shear --------------*/ #if RUN_HORIZ_SHEAR pixs = pixRead("german.png"); pixGetDimensions(pixs, &w, &h, NULL); pixa = pixaCreate(50); for (i = 0; i < 25; i++) { del = 0.2 / 12.; angle = -0.2 + (i - (i & 1)) * del; angledeg = 180. * angle / 3.14159265; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; if (op == L_SAMPLED) pix1 = pixHShear(NULL, pixs, h / 2, angle, L_BRING_IN_WHITE); else pix1 = pixHShearLI(pixs, h / 2, angle, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%6.2f degree, %s", angledeg, opstr[op]); pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pixd, L_INSERT); pixDestroy(&pix1); } pixDestroy(&pixs); pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "hshear.pdf", "/tmp/hshear.pdf"); pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2); pixWrite("/tmp/hshear.jpg", pixd, IFF_JFIF_JPEG); pixaDestroy(&pixa); pixDestroy(&pixd); #endif /* -------- Vertical Shear --------------*/ #if RUN_VERT_SHEAR pixs = pixRead("german.png"); pixGetDimensions(pixs, &w, &h, NULL); pixa = pixaCreate(50); for (i = 0; i < 25; i++) { del = 0.2 / 12.; angle = -0.2 + (i - (i & 1)) * del; angledeg = 180. * angle / 3.14159265; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; if (op == L_SAMPLED) pix1 = pixVShear(NULL, pixs, w / 2, angle, L_BRING_IN_WHITE); else pix1 = pixVShearLI(pixs, w / 2, angle, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%6.2f degree, %s", angledeg, opstr[op]); pixd = pixAddSingleTextblock(pix1, bmf, buf, 0xff000000, L_ADD_BELOW, 0); pixaAddPix(pixa, pixd, L_INSERT); pixDestroy(&pix1); } pixDestroy(&pixs); pixaConvertToPdf(pixa, 100, 1.0, L_JPEG_ENCODE, 0, "vshear.pdf", "/tmp/vshear.pdf"); pixd = pixaDisplayTiledInRows(pixa, 32, 2500, 1.0, 0, 20, 2); pixWrite("/tmp/vshear.jpg", pixd, IFF_JFIF_JPEG); pixaDestroy(&pixa); pixDestroy(&pixd); #endif bmfDestroy(&bmf); return 0; }
/*! * pixProjectivePtaWithAlpha() * * Input: pixs (32 bpp rgb) * ptad (4 pts of final coordinate space) * ptas (4 pts of initial coordinate space) * pixg (<optional> 8 bpp, for alpha channel, can be null) * fract (between 0.0 and 1.0, with 0.0 fully transparent * and 1.0 fully opaque) * border (of pixels added to capture transformed source pixels) * Return: pixd, or null on error * * Notes: * (1) The alpha channel is transformed separately from pixs, * and aligns with it, being fully transparent outside the * boundary of the transformed pixs. For pixels that are fully * transparent, a blending function like pixBlendWithGrayMask() * will give zero weight to corresponding pixels in pixs. * (2) If pixg is NULL, it is generated as an alpha layer that is * partially opaque, using @fract. Otherwise, it is cropped * to pixs if required and @fract is ignored. The alpha channel * in pixs is never used. * (3) Colormaps are removed. * (4) When pixs is transformed, it doesn't matter what color is brought * in because the alpha channel will be transparent (0) there. * (5) To avoid losing source pixels in the destination, it may be * necessary to add a border to the source pix before doing * the projective transformation. This can be any non-negative * number. * (6) The input @ptad and @ptas are in a coordinate space before * the border is added. Internally, we compensate for this * before doing the projective transform on the image after * the border is added. * (7) The default setting for the border values in the alpha channel * is 0 (transparent) for the outermost ring of pixels and * (0.5 * fract * 255) for the second ring. When blended over * a second image, this * (a) shrinks the visible image to make a clean overlap edge * with an image below, and * (b) softens the edges by weakening the aliasing there. * Use l_setAlphaMaskBorder() to change these values. */ PIX * pixProjectivePtaWithAlpha(PIX *pixs, PTA *ptad, PTA *ptas, PIX *pixg, l_float32 fract, l_int32 border) { l_int32 ws, hs, d; PIX *pixd, *pixb1, *pixb2, *pixg2, *pixga; PTA *ptad2, *ptas2; PROCNAME("pixProjectivePtaWithAlpha"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &ws, &hs, &d); if (d != 32 && pixGetColormap(pixs) == NULL) return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL); if (pixg && pixGetDepth(pixg) != 8) { L_WARNING("pixg not 8 bpp; using @fract transparent alpha", procName); pixg = NULL; } if (!pixg && (fract < 0.0 || fract > 1.0)) { L_WARNING("invalid fract; using 1.0 (fully transparent)", procName); fract = 1.0; } if (!pixg && fract == 0.0) L_WARNING("fully opaque alpha; image will not be blended", procName); if (!ptad) return (PIX *)ERROR_PTR("ptad not defined", procName, NULL); if (!ptas) return (PIX *)ERROR_PTR("ptas not defined", procName, NULL); /* Add border; the color doesn't matter */ pixb1 = pixAddBorder(pixs, border, 0); /* Transform the ptr arrays to work on the bordered image */ ptad2 = ptaTransform(ptad, border, border, 1.0, 1.0); ptas2 = ptaTransform(ptas, border, border, 1.0, 1.0); /* Do separate projective transform of rgb channels of pixs * and of pixg */ pixd = pixProjectivePtaColor(pixb1, ptad2, ptas2, 0); if (!pixg) { pixg2 = pixCreate(ws, hs, 8); if (fract == 1.0) pixSetAll(pixg2); else pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract)); } else pixg2 = pixResizeToMatch(pixg, NULL, ws, hs); if (ws > 10 && hs > 10) { /* see note 7 */ pixSetBorderRingVal(pixg2, 1, (l_int32)(255.0 * fract * AlphaMaskBorderVals[0])); pixSetBorderRingVal(pixg2, 2, (l_int32)(255.0 * fract * AlphaMaskBorderVals[1])); } pixb2 = pixAddBorder(pixg2, border, 0); /* must be black border */ pixga = pixProjectivePtaGray(pixb2, ptad2, ptas2, 0); pixSetRGBComponent(pixd, pixga, L_ALPHA_CHANNEL); pixDestroy(&pixg2); pixDestroy(&pixb1); pixDestroy(&pixb2); pixDestroy(&pixga); ptaDestroy(&ptad2); ptaDestroy(&ptas2); return pixd; }
/*! * selaAddTJunctions() * * Input: sela (<optional>) * hlsize (length of each line of hits from origin) * mdist (distance of misses from the origin) * norient (number of orientations; max of 8) * debugflag (1 for debug output) * Return: sela with additional sels, or null on error * * Notes: * (1) Adds hitmiss Sels for the T-junction of two lines. * If the lines are very thin, they must be nearly orthogonal * to register. * (2) The number of Sels generated is 4 * @norient. * (3) It is suggested that @hlsize be chosen at least 1 greater * than @mdist. Try values of (@hlsize, @mdist) such as * (6,5), (7,6), (8,7), (9,7), etc. */ SELA * selaAddTJunctions(SELA *sela, l_float32 hlsize, l_float32 mdist, l_int32 norient, l_int32 debugflag) { char name[L_BUF_SIZE]; l_int32 i, j, k, w, xc, yc; l_float64 pi, halfpi, radincr, jang, radang; l_float64 angle[3], dist[3]; PIX *pixc, *pixm, *pixt; PIXA *pixa; PTA *pta1, *pta2, *pta3; SEL *sel; PROCNAME("selaAddTJunctions"); if (hlsize <= 2) return (SELA *)ERROR_PTR("hlsizel not > 1", procName, NULL); if (norient < 1 || norient > 8) return (SELA *)ERROR_PTR("norient not in [1, ... 8]", procName, NULL); if (!sela) { if ((sela = selaCreate(0)) == NULL) return (SELA *)ERROR_PTR("sela not made", procName, NULL); } pi = 3.1415926535; halfpi = 3.1415926535 / 2.0; radincr = halfpi / (l_float32)norient; w = (l_int32)(2.4 * (L_MAX(hlsize, mdist) + 0.5)); if (w % 2 == 0) w++; xc = w / 2; yc = w / 2; pixa = pixaCreate(4 * norient); for (i = 0; i < norient; i++) { for (j = 0; j < 4; j++) { /* 4 orthogonal orientations */ jang = (l_float32)j * halfpi; /* Set the don't cares */ pixc = pixCreate(w, w, 32); pixSetAll(pixc); /* Add the green lines of hits */ pixm = pixCreate(w, w, 1); radang = (l_float32)i * radincr; pta1 = generatePtaLineFromPt(xc, yc, hlsize + 1, jang + radang); pta2 = generatePtaLineFromPt(xc, yc, hlsize + 1, jang + radang + halfpi); pta3 = generatePtaLineFromPt(xc, yc, hlsize + 1, jang + radang + pi); ptaJoin(pta1, pta2, 0, -1); ptaJoin(pta1, pta3, 0, -1); pixRenderPta(pixm, pta1, L_SET_PIXELS); pixPaintThroughMask(pixc, pixm, 0, 0, 0x00ff0000); ptaDestroy(&pta1); ptaDestroy(&pta2); ptaDestroy(&pta3); /* Add red misses between the lines */ angle[0] = radang + jang - halfpi; angle[1] = radang + jang + 0.5 * halfpi; angle[2] = radang + jang + 1.5 * halfpi; dist[0] = 0.8 * mdist; dist[1] = dist[2] = mdist; for (k = 0; k < 3; k++) { pixSetPixel(pixc, xc + (l_int32)(dist[k] * cos(angle[k])), yc + (l_int32)(dist[k] * sin(angle[k])), 0xff000000); } /* Add dark green for origin */ pixSetPixel(pixc, xc, yc, 0x00550000); /* Generate the sel */ sel = selCreateFromColorPix(pixc, NULL); sprintf(name, "sel_cross_%d", 4 * i + j); selaAddSel(sela, sel, name, 0); if (debugflag) { pixt = pixScaleBySampling(pixc, 10.0, 10.0); pixaAddPix(pixa, pixt, L_INSERT); } pixDestroy(&pixm); pixDestroy(&pixc); } } if (debugflag) { l_int32 w; pixaGetPixDimensions(pixa, 0, &w, NULL, NULL); pixt = pixaDisplayTiledAndScaled(pixa, 32, w, 4, 0, 10, 2); pixWriteTempfile("/tmp", "tsel1.png", pixt, IFF_PNG, 0); pixDisplay(pixt, 0, 100); pixDestroy(&pixt); pixt = selaDisplayInPix(sela, 15, 2, 20, 4); pixWriteTempfile("/tmp", "tsel2.png", pixt, IFF_PNG, 0); pixDisplay(pixt, 500, 100); pixDestroy(&pixt); selaWriteStream(stderr, sela); } pixaDestroy(&pixa); return sela; }
main(int argc, char **argv) { l_int32 i, j, x, y, rval, gval, bval; l_uint32 pixel; l_float32 frval, fgval, fbval; NUMA *nahue, *nasat, *napk; PIX *pixs, *pixhsv, *pixh, *pixg, *pixf, *pixd; PIX *pixr, *pixt1, *pixt2, *pixt3; PIXA *pixa, *pixapk; PTA *ptapk; L_REGPARAMS *rp; l_chooseDisplayProg(L_DISPLAY_WITH_XV); if (regTestSetup(argc, argv, &rp)) return 1; /* Make a graded frame color */ pixs = pixCreate(650, 900, 32); for (i = 0; i < 900; i++) { rval = 40 + i / 30; for (j = 0; j < 650; j++) { gval = 255 - j / 30; bval = 70 + j / 30; composeRGBPixel(rval, gval, bval, &pixel); pixSetPixel(pixs, j, i, pixel); } } /* Place an image inside the frame and convert to HSV */ pixt1 = pixRead("1555-3.jpg"); pixt2 = pixScale(pixt1, 0.5, 0.5); pixRasterop(pixs, 100, 100, 2000, 2000, PIX_SRC, pixt2, 0, 0); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDisplayWithTitle(pixs, 400, 0, "Input image", rp->display); pixa = pixaCreate(0); pixhsv = pixConvertRGBToHSV(NULL, pixs); /* Work in the HS projection of HSV */ pixh = pixMakeHistoHS(pixhsv, 5, &nahue, &nasat); pixg = pixMaxDynamicRange(pixh, L_LOG_SCALE); pixf = pixConvertGrayToFalseColor(pixg, 1.0); regTestWritePixAndCheck(rp, pixf, IFF_PNG); /* 0 */ pixDisplayWithTitle(pixf, 100, 0, "False color HS histo", rp->display); pixaAddPix(pixa, pixs, L_COPY); pixaAddPix(pixa, pixhsv, L_INSERT); pixaAddPix(pixa, pixg, L_INSERT); pixaAddPix(pixa, pixf, L_INSERT); gplotSimple1(nahue, GPLOT_PNG, "/tmp/junkhue", "Histogram of hue values"); #ifndef _WIN32 sleep(1); #else Sleep(1000); #endif /* _WIN32 */ pixt3 = pixRead("/tmp/junkhue.png"); regTestWritePixAndCheck(rp, pixt3, IFF_PNG); /* 1 */ pixDisplayWithTitle(pixt3, 100, 300, "Histo of hue", rp->display); pixaAddPix(pixa, pixt3, L_INSERT); gplotSimple1(nasat, GPLOT_PNG, "/tmp/junksat", "Histogram of saturation values"); #ifndef _WIN32 sleep(1); #else Sleep(1000); #endif /* _WIN32 */ pixt3 = pixRead("/tmp/junksat.png"); regTestWritePixAndCheck(rp, pixt3, IFF_PNG); /* 2 */ pixDisplayWithTitle(pixt3, 100, 800, "Histo of saturation", rp->display); pixaAddPix(pixa, pixt3, L_INSERT); pixd = pixaDisplayTiledAndScaled(pixa, 32, 270, 7, 0, 30, 3); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 3 */ pixDisplayWithTitle(pixd, 0, 400, "Hue and Saturation Mosaic", rp->display); pixDestroy(&pixd); pixaDestroy(&pixa); numaDestroy(&nahue); numaDestroy(&nasat); /* Find all the peaks */ pixFindHistoPeaksHSV(pixh, L_HS_HISTO, 20, 20, 6, 2.0, &ptapk, &napk, &pixapk); numaWriteStream(stderr, napk); ptaWriteStream(stderr, ptapk, 1); pixd = pixaDisplayTiledInRows(pixapk, 32, 1400, 1.0, 0, 30, 2); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 4 */ pixDisplayWithTitle(pixd, 0, 550, "Peaks in HS", rp->display); pixDestroy(&pixh); pixDestroy(&pixd); pixaDestroy(&pixapk); /* Make masks for each of the peaks */ pixa = pixaCreate(0); pixr = pixScaleBySampling(pixs, 0.4, 0.4); for (i = 0; i < 6; i++) { ptaGetIPt(ptapk, i, &x, &y); pixt1 = pixMakeRangeMaskHS(pixr, y, 20, x, 20, L_INCLUDE_REGION); pixaAddPix(pixa, pixt1, L_INSERT); pixGetAverageMaskedRGB(pixr, pixt1, 0, 0, 1, L_MEAN_ABSVAL, &frval, &fgval, &fbval); composeRGBPixel((l_int32)frval, (l_int32)fgval, (l_int32)fbval, &pixel); pixt2 = pixCreateTemplate(pixr); pixSetAll(pixt2); pixPaintThroughMask(pixt2, pixt1, 0, 0, pixel); pixaAddPix(pixa, pixt2, L_INSERT); pixt3 = pixCreateTemplate(pixr); pixSetAllArbitrary(pixt3, pixel); pixaAddPix(pixa, pixt3, L_INSERT); } pixd = pixaDisplayTiledAndScaled(pixa, 32, 225, 3, 0, 30, 3); regTestWritePixAndCheck(rp, pixd, IFF_PNG); /* 5 */ pixDisplayWithTitle(pixd, 600, 0, "Masks over peaks", rp->display); pixDestroy(&pixs); pixDestroy(&pixr); pixDestroy(&pixd); pixaDestroy(&pixa); ptaDestroy(&ptapk); numaDestroy(&napk); regTestCleanup(rp); 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; }
main(int argc, char **argv) { char buf[256]; l_int32 w, h, i, j, k, index, op, dir, stretch; l_float32 del, angle, angledeg; BOX *box; L_BMF *bmf; PIX *pixs, *pixt, *pixt2, *pixd; static char mainName[] = "warpertest"; if (argc != 1) exit(ERROR_INT("syntax: warpertest", mainName, 1)); /* -------- Stereoscopic warping --------------*/ #if RUN_WARP pixs = pixRead("german.png"); pixGetDimensions(pixs, &w, &h, NULL); l_jpegSetNoChromaSampling(1); for (i = 0; i < 50; i++) { /* need to test > 2 widths ! */ j = 7 * i; box = boxCreate(0, 0, w - j, h - j); pixt = pixClipRectangle(pixs, box, NULL); pixd = pixWarpStereoscopic(pixt, 15, 22, 8, 30, -20, 1); snprintf(buf, sizeof(buf), "/tmp/junkpixw.%02d.jpg", i); pixWrite(buf, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixDestroy(&pixt); boxDestroy(&box); } pixDestroy(&pixs); pixDisplayMultiple("/tmp/junkpixw*.jpg"); #endif /* -------- Quadratic Vertical Shear --------------*/ #if RUN_QUAD_VERT_SHEAR pixs = pixCreate(501, 501, 32); pixGetDimensions(pixs, &w, &h, NULL); pixSetAll(pixs); pixRenderLineArb(pixs, 0, 30, 500, 30, 5, 0, 0, 255); pixRenderLineArb(pixs, 0, 110, 500, 110, 5, 0, 255, 0); pixRenderLineArb(pixs, 0, 190, 500, 190, 5, 0, 255, 255); pixRenderLineArb(pixs, 0, 270, 500, 270, 5, 255, 0, 0); pixRenderLineArb(pixs, 0, 360, 500, 360, 5, 255, 0, 255); pixRenderLineArb(pixs, 0, 450, 500, 450, 5, 255, 255, 0); bmf = bmfCreate("./fonts", 6); for (i = 0; i < 50; i++) { j = 3 * i; dir = ((i / 2) & 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; Box *box = boxCreate(0, 0, w - j, h - j); pixt = pixClipRectangle(pixs, box, NULL); pixt2 = pixQuadraticVShear(pixt, dir, 60, -20, op, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]); pixd = pixAddSingleTextblock(pixt2, bmf, buf, 0xff000000, L_ADD_BELOW, 0); snprintf(buf, sizeof(buf), "/tmp/junkpixvs.%02d.png", i); pixWrite(buf, pixd, IFF_PNG); pixDestroy(&pixd); pixDestroy(&pixt); pixDestroy(&pixt2); boxDestroy(&box); } pixDestroy(&pixs); bmfDestroy(&bmf); pixDisplayMultiple("/tmp/junkpixvs*.png"); #endif /* -------- Linear Horizontal stretching --------------*/ #if RUN_LIN_HORIZ_STRETCH pixs = pixRead("german.png"); bmf = bmfCreate("./fonts", 6); for (k = 0; k < 2; k++) { for (i = 0; i < 25; i++) { index = 25 * k + i; stretch = 10 + 4 * i; if (k == 0) stretch = -stretch; dir = (k == 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; pixt = pixStretchHorizontal(pixs, dir, L_LINEAR_WARP, stretch, op, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]); pixd = pixAddSingleTextblock(pixt, bmf, buf, 0xff000000, L_ADD_BELOW, 0); snprintf(buf, sizeof(buf), "/tmp/junkpixlhs.%02d.jpg", index); pixWrite(buf, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixDestroy(&pixt); } } pixDestroy(&pixs); bmfDestroy(&bmf); pixDisplayMultiple("/tmp/junkpixlhs*.jpg"); #endif /* -------- Quadratic Horizontal stretching --------------*/ #if RUN_QUAD_HORIZ_STRETCH pixs = pixRead("german.png"); bmf = bmfCreate("./fonts", 6); for (k = 0; k < 2; k++) { for (i = 0; i < 25; i++) { index = 25 * k + i; stretch = 10 + 4 * i; if (k == 0) stretch = -stretch; dir = (k == 1) ? L_WARP_TO_RIGHT : L_WARP_TO_LEFT; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; pixt = pixStretchHorizontal(pixs, dir, L_QUADRATIC_WARP, stretch, op, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%s, %s", dirstr[dir], opstr[op]); pixd = pixAddSingleTextblock(pixt, bmf, buf, 0xff000000, L_ADD_BELOW, 0); snprintf(buf, sizeof(buf), "/tmp/junkpixqhs.%02d.jpg", index); pixWrite(buf, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixDestroy(&pixt); } } pixDestroy(&pixs); bmfDestroy(&bmf); pixDisplayMultiple("/tmp/junkpixqhs*.jpg"); #endif /* -------- Horizontal Shear --------------*/ #if RUN_HORIZ_SHEAR pixs = pixRead("german.png"); pixGetDimensions(pixs, &w, &h, NULL); bmf = bmfCreate("./fonts", 6); for (i = 0; i < 25; i++) { del = 0.2 / 12.; angle = -0.2 + (i - (i & 1)) * del; angledeg = 180. * angle / 3.14159265; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; if (op == L_SAMPLED) pixt = pixHShear(NULL, pixs, h / 2, angle, L_BRING_IN_WHITE); else pixt = pixHShearLI(pixs, h / 2, angle, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%6.2f degree, %s", angledeg, opstr[op]); pixd = pixAddSingleTextblock(pixt, bmf, buf, 0xff000000, L_ADD_BELOW, 0); snprintf(buf, sizeof(buf), "/tmp/junkpixsh.%02d.jpg", i); pixWrite(buf, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixDestroy(&pixt); } pixDestroy(&pixs); bmfDestroy(&bmf); pixDisplayMultiple("/tmp/junkpixsh*.jpg"); #endif /* -------- Vertical Shear --------------*/ #if RUN_VERT_SHEAR pixs = pixRead("german.png"); pixGetDimensions(pixs, &w, &h, NULL); bmf = bmfCreate("./fonts", 6); for (i = 0; i < 25; i++) { del = 0.2 / 12.; angle = -0.2 + (i - (i & 1)) * del; angledeg = 180. * angle / 3.14159265; op = (i & 1) ? L_INTERPOLATED : L_SAMPLED; if (op == L_SAMPLED) pixt = pixVShear(NULL, pixs, w / 2, angle, L_BRING_IN_WHITE); else pixt = pixVShearLI(pixs, w / 2, angle, L_BRING_IN_WHITE); snprintf(buf, sizeof(buf), "%6.2f degree, %s", angledeg, opstr[op]); pixd = pixAddSingleTextblock(pixt, bmf, buf, 0xff000000, L_ADD_BELOW, 0); snprintf(buf, sizeof(buf), "/tmp/junkpixsv.%02d.jpg", i); pixWrite(buf, pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixDestroy(&pixt); } pixDestroy(&pixs); bmfDestroy(&bmf); pixDisplayMultiple("/tmp/junkpixsv*.jpg"); #endif return 0; }