/*! * numaaCompareImagesByBoxes() * * Input: naa1 (for image 1, formatted by boxaExtractSortedPattern()) * naa2 (ditto; for image 2) * nperline (number of box regions to be used in each textline) * nreq (number of complete row matches required) * maxshiftx (max allowed x shift between two patterns, in pixels) * maxshifty (max allowed y shift between two patterns, in pixels) * delx (max allowed difference in x data, after alignment) * dely (max allowed difference in y data, after alignment) * &same (<return> 1 if @nreq row matches are found; 0 otherwise) * debugflag (1 for debug output) * Return: 0 if OK, 1 on error * * Notes: * (1) Each input numaa describes a set of sorted bounding boxes * (sorted by textline and, within each textline, from * left to right) in the images from which they are derived. * See boxaExtractSortedPattern() for a description of the data * format in each of the input numaa. * (2) This function does an alignment between the input * descriptions of bounding boxes for two images. The * input parameter @nperline specifies the number of boxes * to consider in each line when testing for a match, and * @nreq is the required number of lines that must be well-aligned * to get a match. * (3) Testing by alignment has 3 steps: * (a) Generating the location of word bounding boxes from the * images (prior to calling this function). * (b) Listing all possible pairs of aligned rows, based on * tolerances in horizontal and vertical positions of * the boxes. Specifically, all pairs of rows are enumerated * whose first @nperline boxes can be brought into close * alignment, based on the delx parameter for boxes in the * line and within the overall the @maxshiftx and @maxshifty * constraints. * (c) Each pair, starting with the first, is used to search * for a set of @nreq - 1 other pairs that can all be aligned * with a difference in global translation of not more * than (@delx, @dely). */ l_int32 numaaCompareImagesByBoxes(NUMAA *naa1, NUMAA *naa2, l_int32 nperline, l_int32 nreq, l_int32 maxshiftx, l_int32 maxshifty, l_int32 delx, l_int32 dely, l_int32 *psame, l_int32 debugflag) { l_int32 n1, n2, i, j, nbox, y1, y2, xl1, xl2; l_int32 shiftx, shifty, match; l_int32 *line1, *line2; /* indicator for sufficient boxes in a line */ l_int32 *yloc1, *yloc2; /* arrays of y value for first box in a line */ l_int32 *xleft1, *xleft2; /* arrays of x value for left side of first box */ NUMA *na1, *na2, *nai1, *nai2, *nasx, *nasy; PROCNAME("numaaCompareImagesByBoxes"); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; if (!naa1) return ERROR_INT("naa1 not defined", procName, 1); if (!naa2) return ERROR_INT("naa2 not defined", procName, 1); if (nperline < 1) return ERROR_INT("nperline < 1", procName, 1); if (nreq < 1) return ERROR_INT("nreq < 1", procName, 1); n1 = numaaGetCount(naa1); n2 = numaaGetCount(naa2); if (n1 < nreq || n2 < nreq) return 0; /* Find the lines in naa1 and naa2 with sufficient boxes. * Also, find the y-values for each of the lines, and the * LH x-values of the first box in each line. */ line1 = (l_int32 *) CALLOC(n1, sizeof(l_int32)); line2 = (l_int32 *) CALLOC(n2, sizeof(l_int32)); yloc1 = (l_int32 *) CALLOC(n1, sizeof(l_int32)); yloc2 = (l_int32 *) CALLOC(n2, sizeof(l_int32)); xleft1 = (l_int32 *) CALLOC(n1, sizeof(l_int32)); xleft2 = (l_int32 *) CALLOC(n2, sizeof(l_int32)); for (i = 0; i < n1; i++) { na1 = numaaGetNuma(naa1, i, L_CLONE); numaGetIValue(na1, 0, yloc1 + i); numaGetIValue(na1, 1, xleft1 + i); nbox = (numaGetCount(na1) - 1) / 2; if (nbox >= nperline) line1[i] = 1; numaDestroy(&na1); } for (i = 0; i < n2; i++) { na2 = numaaGetNuma(naa2, i, L_CLONE); numaGetIValue(na2, 0, yloc2 + i); numaGetIValue(na2, 1, xleft2 + i); nbox = (numaGetCount(na2) - 1) / 2; if (nbox >= nperline) line2[i] = 1; numaDestroy(&na2); } /* Enumerate all possible line matches. A 'possible' line * match is one where the x and y shifts for the first box * in each line are within the maxshiftx and maxshifty * constraints, and the left and right sides of the remaining * (nperline - 1) successive boxes are within delx of each other. * The result is a set of four numas giving parameters of * each set of matching lines. */ nai1 = numaCreate(0); /* line index 1 of match */ nai2 = numaCreate(0); /* line index 2 of match */ nasx = numaCreate(0); /* shiftx for match */ nasy = numaCreate(0); /* shifty for match */ for (i = 0; i < n1; i++) { if (line1[i] == 0) continue; y1 = yloc1[i]; xl1 = xleft1[i]; na1 = numaaGetNuma(naa1, i, L_CLONE); for (j = 0; j < n2; j++) { if (line2[j] == 0) continue; y2 = yloc2[j]; if (L_ABS(y1 - y2) > maxshifty) continue; xl2 = xleft2[j]; if (L_ABS(xl1 - xl2) > maxshiftx) continue; shiftx = xl1 - xl2; /* shift to add to x2 values */ shifty = y1 - y2; /* shift to add to y2 values */ na2 = numaaGetNuma(naa2, j, L_CLONE); /* Now check if 'nperline' boxes in the two lines match */ match = testLineAlignmentX(na1, na2, shiftx, delx, nperline); if (match) { numaAddNumber(nai1, i); numaAddNumber(nai2, j); numaAddNumber(nasx, shiftx); numaAddNumber(nasy, shifty); } numaDestroy(&na2); } numaDestroy(&na1); } /* Determine if there are a sufficient number of mutually * aligned matches. Mutually aligned matches place an additional * constraint on the 'possible' matches, where the relative * shifts must not exceed the (delx, dely) distances. */ countAlignedMatches(nai1, nai2, nasx, nasy, n1, n2, delx, dely, nreq, psame, debugflag); FREE(line1); FREE(line2); FREE(yloc1); FREE(yloc2); FREE(xleft1); FREE(xleft2); numaDestroy(&nai1); numaDestroy(&nai2); numaDestroy(&nasx); numaDestroy(&nasy); return 0; }
/*! * regTestCompareFiles() * * Input: rp (regtest parameters) * index1 (of one output file from reg test) * index2 (of another output file from reg test) * Return: 0 if OK, 1 on error (a failure in comparison is not an error) * * Notes: * (1) This only does something in "compare" mode. * (2) The canonical format of the golden filenames is: * /tmp/golden/<root of main name>_golden.<index>.<ext of localname> * e.g., * /tmp/golden/maze_golden.0.png */ l_int32 regTestCompareFiles(L_REGPARAMS *rp, l_int32 index1, l_int32 index2) { char *name1, *name2; char namebuf[256]; l_int32 same; SARRAY *sa; PROCNAME("regTestCompareFiles"); if (!rp) return ERROR_INT("rp not defined", procName, 1); if (index1 < 0 || index2 < 0) { rp->success = FALSE; return ERROR_INT("index1 and/or index2 is negative", procName, 1); } if (index1 == index2) { rp->success = FALSE; return ERROR_INT("index1 must differ from index2", procName, 1); } rp->index++; if (rp->mode != L_REG_COMPARE) return 0; /* Generate the golden file names */ snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", rp->testname, index1); sa = getSortedPathnamesInDirectory("/tmp/golden", namebuf, 0, 0); if (sarrayGetCount(sa) != 1) { sarrayDestroy(&sa); rp->success = FALSE; L_ERROR("golden file %s not found\n", procName, namebuf); return 1; } name1 = sarrayGetString(sa, 0, L_COPY); sarrayDestroy(&sa); snprintf(namebuf, sizeof(namebuf), "%s_golden.%d.", rp->testname, index2); sa = getSortedPathnamesInDirectory("/tmp/golden", namebuf, 0, 0); if (sarrayGetCount(sa) != 1) { sarrayDestroy(&sa); rp->success = FALSE; FREE(name1); L_ERROR("golden file %s not found\n", procName, namebuf); return 1; } name2 = sarrayGetString(sa, 0, L_COPY); sarrayDestroy(&sa); /* Test and record on failure */ filesAreIdentical(name1, name2, &same); if (!same) { fprintf(rp->fp, "Failure in %s_reg, index %d: comparing %s with %s\n", rp->testname, rp->index, name1, name2); fprintf(stderr, "Failure in %s_reg, index %d: comparing %s with %s\n", rp->testname, rp->index, name1, name2); rp->success = FALSE; } FREE(name1); FREE(name2); return 0; }
/*! * pixGetRegionsBinary() * * Input: pixs (1 bpp, assumed to be 300 to 400 ppi) * &pixhm (<optional return> halftone mask) * &pixtm (<optional return> textline mask) * &pixtb (<optional return> textblock mask) * debug (flag: set to 1 for debug output) * Return: 0 if OK, 1 on error * * Notes: * (1) It is best to deskew the image before segmenting. * (2) The debug flag enables a number of outputs. These * are included to show how to generate and save/display * these results. */ l_int32 pixGetRegionsBinary(PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, l_int32 debug) { l_int32 htfound, tlfound; PIX *pixr, *pixt1, *pixt2; PIX *pixtext; /* text pixels only */ PIX *pixhm2; /* halftone mask; 2x reduction */ PIX *pixhm; /* halftone mask; */ PIX *pixtm2; /* textline mask; 2x reduction */ PIX *pixtm; /* textline mask */ PIX *pixvws; /* vertical white space mask */ PIX *pixtb2; /* textblock mask; 2x reduction */ PIX *pixtbf2; /* textblock mask; 2x reduction; small comps filtered */ PIX *pixtb; /* textblock mask */ PROCNAME("pixGetRegionsBinary"); if (ppixhm) *ppixhm = NULL; if (ppixtm) *ppixtm = NULL; if (ppixtb) *ppixtb = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 1); /* 2x reduce, to 150 -200 ppi */ pixr = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); pixDisplayWrite(pixr, debug); /* Get the halftone mask */ pixhm2 = pixGenHalftoneMask(pixr, &pixtext, &htfound, debug); /* Get the textline mask from the text pixels */ pixtm2 = pixGenTextlineMask(pixtext, &pixvws, &tlfound, debug); /* Get the textblock mask from the textline mask */ pixtb2 = pixGenTextblockMask(pixtm2, pixvws, debug); pixDestroy(&pixr); pixDestroy(&pixtext); pixDestroy(&pixvws); /* Remove small components from the mask, where a small * component is defined as one with both width and height < 60 */ pixtbf2 = pixSelectBySize(pixtb2, 60, 60, 4, L_SELECT_IF_EITHER, L_SELECT_IF_GTE, NULL); pixDestroy(&pixtb2); pixDisplayWriteFormat(pixtbf2, debug, IFF_PNG); /* Expand all masks to full resolution, and do filling or * small dilations for better coverage. */ pixhm = pixExpandReplicate(pixhm2, 2); pixt1 = pixSeedfillBinary(NULL, pixhm, pixs, 8); pixOr(pixhm, pixhm, pixt1); pixDestroy(&pixt1); pixDisplayWriteFormat(pixhm, debug, IFF_PNG); pixt1 = pixExpandReplicate(pixtm2, 2); pixtm = pixDilateBrick(NULL, pixt1, 3, 3); pixDestroy(&pixt1); pixDisplayWriteFormat(pixtm, debug, IFF_PNG); pixt1 = pixExpandReplicate(pixtbf2, 2); pixtb = pixDilateBrick(NULL, pixt1, 3, 3); pixDestroy(&pixt1); pixDisplayWriteFormat(pixtb, debug, IFF_PNG); pixDestroy(&pixhm2); pixDestroy(&pixtm2); pixDestroy(&pixtbf2); /* Debug: identify objects that are neither text nor halftone image */ if (debug) { pixt1 = pixSubtract(NULL, pixs, pixtm); /* remove text pixels */ pixt2 = pixSubtract(NULL, pixt1, pixhm); /* remove halftone pixels */ pixDisplayWriteFormat(pixt2, 1, IFF_PNG); pixDestroy(&pixt1); pixDestroy(&pixt2); } /* Debug: display textline components with random colors */ if (debug) { l_int32 w, h; BOXA *boxa; PIXA *pixa; boxa = pixConnComp(pixtm, &pixa, 8); pixGetDimensions(pixtm, &w, &h, NULL); pixt1 = pixaDisplayRandomCmap(pixa, w, h); pixcmapResetColor(pixGetColormap(pixt1), 0, 255, 255, 255); pixDisplay(pixt1, 100, 100); pixDisplayWriteFormat(pixt1, 1, IFF_PNG); pixaDestroy(&pixa); boxaDestroy(&boxa); pixDestroy(&pixt1); } /* Debug: identify the outlines of each textblock */ if (debug) { PIXCMAP *cmap; PTAA *ptaa; ptaa = pixGetOuterBordersPtaa(pixtb); lept_mkdir("pageseg"); ptaaWrite("/tmp/pageseg/tb_outlines.ptaa", ptaa, 1); pixt1 = pixRenderRandomCmapPtaa(pixtb, ptaa, 1, 16, 1); cmap = pixGetColormap(pixt1); pixcmapResetColor(cmap, 0, 130, 130, 130); pixDisplay(pixt1, 500, 100); pixDisplayWriteFormat(pixt1, 1, IFF_PNG); pixDestroy(&pixt1); ptaaDestroy(&ptaa); } /* Debug: get b.b. for all mask components */ if (debug) { BOXA *bahm, *batm, *batb; bahm = pixConnComp(pixhm, NULL, 4); batm = pixConnComp(pixtm, NULL, 4); batb = pixConnComp(pixtb, NULL, 4); boxaWrite("/tmp/pageseg/htmask.boxa", bahm); boxaWrite("/tmp/pageseg/textmask.boxa", batm); boxaWrite("/tmp/pageseg/textblock.boxa", batb); boxaDestroy(&bahm); boxaDestroy(&batm); boxaDestroy(&batb); } if (ppixhm) *ppixhm = pixhm; else pixDestroy(&pixhm); if (ppixtm) *ppixtm = pixtm; else pixDestroy(&pixtm); if (ppixtb) *ppixtb = pixtb; else pixDestroy(&pixtb); return 0; }
main(int argc, char **argv) { l_int32 ws, hs; BOX *box; BOXA *boxa; PIX *pixs, *pixc, *pix32, *pixt, *pixd; PIXA *pixat, *pixas, *pixac; static char mainName[] = "pixadisp_reg"; if (argc != 1) exit(ERROR_INT(" Syntax: pixadisp_reg", mainName, 1)); if ((pixs = pixRead("feyn.tif")) == NULL) exit(ERROR_INT("pixs not made", mainName, 1)); box = boxCreate(683, 799, 970, 479); pixc = pixClipRectangle(pixs, box, NULL); boxDestroy(&box); pixDisplayWrite(pixc, 1); if ((pix32 = pixRead("marge.jpg")) == NULL) exit(ERROR_INT("pix32 not made", mainName, 1)); /* Generate pixas from pixs and pixac from pixc */ boxa = pixConnComp(pixs, &pixat, 8); pixas = pixaSelectBySize(pixat, 60, 60, L_SELECT_IF_BOTH, L_SELECT_IF_LTE, NULL); pixaDestroy(&pixat); boxaDestroy(&boxa); boxa = pixConnComp(pixc, &pixac, 8); boxaDestroy(&boxa); /* pixaDisplay() */ pixGetDimensions(pixs, &ws, &hs, NULL); pixd = pixaDisplay(pixas, ws, hs); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* pixaDisplayRandomCmap() */ pixd = pixaDisplayRandomCmap(pixas, ws, hs); /* black bg */ pixDisplayWrite(pixd, 1); pixcmapResetColor(pixGetColormap(pixd), 0, 255, 255, 255); /* white bg */ pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* pixaDisplayOnLattice() */ pixd = pixaDisplayOnLattice(pixac, 50, 50); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* pixaDisplayUnsplit() */ pixat = pixaSplitPix(pix32, 5, 7, 10, 0x0000ff00); pixd = pixaDisplayUnsplit(pixat, 5, 7, 10, 0x00ff0000); pixDisplayWrite(pixd, 1); pixaDestroy(&pixat); pixDestroy(&pixd); /* pixaDisplayTiled() */ pixd = pixaDisplayTiled(pixac, 1000, 0, 10); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* pixaDisplayTiledInRows() */ pixd = pixaDisplayTiledInRows(pixac, 1, 1000, 1.0, 0, 10, 2); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); /* pixaDisplayTiledAndScaled() */ pixd = pixaDisplayTiledAndScaled(pixac, 1, 25, 20, 0, 5, 0); pixDisplayWrite(pixd, 1); pixDestroy(&pixd); pixat = pixaCreate(10); pixd = pixRankFilter(pix32, 8, 8, 0.5); pixaAddPix(pixat, pixd, L_INSERT); pixt = pixScale(pix32, 0.5, 0.5); pixd = pixRankFilter(pixt, 8, 8, 0.5); pixaAddPix(pixat, pixd, L_INSERT); pixDestroy(&pixt); pixt = pixScale(pix32, 0.25, 0.25); pixd = pixRankFilter(pixt, 8, 8, 0.5); pixaAddPix(pixat, pixd, L_INSERT); pixDestroy(&pixt); pixd = pixaDisplayTiledAndScaled(pixat, 32, 500, 1, 0, 25, 0); pixDisplayWrite(pixd, 1); pixaDestroy(&pixat); pixDestroy(&pixd); pixaDestroy(&pixas); pixaDestroy(&pixac); pixDestroy(&pixs); pixDestroy(&pixc); pixDestroy(&pix32); pixDisplayMultiple("/tmp/junk_write_display*"); return 0; }
/*! * regTestSetup() * * Input: argc (from invocation; can be either 1 or 2) * argv (to regtest: @argv[1] is one of these: * "generate", "compare", "display") * &rp (<return> all regression params) * Return: 0 if OK, 1 on error * * Notes: * (1) Call this function with the args to the reg test. * There are three cases: * Case 1: * There is either only one arg, or the second arg is "compare". * This is the mode in which you run a regression test * (or a set of them), looking for failures and logging * the results to a file. The output, which includes * logging of all reg test failures plus a SUCCESS or * FAILURE summary for each test, is appended to the file * "/tmp/reg_results.txt. For this case, as in Case 2, * the display field in rp is set to FALSE, preventing * image display. * Case 2: * The second arg is "generate". This will cause * generation of new golden files for the reg test. * The results of the reg test are not recorded, and * the display field in rp is set to FALSE. * Case 3: * The second arg is "display". The test will run and * files will be written. Comparisons with golden files * will not be carried out, so the only notion of success * or failure is with tests that do not involve golden files. * The display field in rp is TRUE, and this is used by * pixDisplayWithTitle(). * (2) See regutils.h for examples of usage. */ l_int32 regTestSetup(l_int32 argc, char **argv, L_REGPARAMS **prp) { char *testname, *vers; char errormsg[64]; L_REGPARAMS *rp; PROCNAME("regTestSetup"); if (argc != 1 && argc != 2) { snprintf(errormsg, sizeof(errormsg), "Syntax: %s [ [generate] | compare | display ]", argv[0]); return ERROR_INT(errormsg, procName, 1); } if ((testname = getRootNameFromArgv0(argv[0])) == NULL) return ERROR_INT("invalid root", procName, 1); if ((rp = (L_REGPARAMS *)CALLOC(1, sizeof(L_REGPARAMS))) == NULL) return ERROR_INT("rp not made", procName, 1); *prp = rp; rp->testname = testname; rp->index = -1; /* increment before each test */ /* Initialize to true. A failure in any test is registered * as a failure of the regression test. */ rp->success = TRUE; /* Only open a stream to a temp file for the 'compare' case */ if (argc == 1 || !strcmp(argv[1], "compare")) { rp->mode = L_REG_COMPARE; rp->tempfile = genTempFilename("/tmp", "regtest_output.txt", 0, 1); rp->fp = fopenWriteStream(rp->tempfile, "wb"); if (rp->fp == NULL) { rp->success = FALSE; return ERROR_INT("stream not opened for tempfile", procName, 1); } } else if (!strcmp(argv[1], "generate")) { rp->mode = L_REG_GENERATE; lept_mkdir("golden"); } else if (!strcmp(argv[1], "display")) { rp->mode = L_REG_DISPLAY; rp->display = TRUE; } else { FREE(rp); snprintf(errormsg, sizeof(errormsg), "Syntax: %s [ [generate] | compare | display ]", argv[0]); return ERROR_INT(errormsg, procName, 1); } /* Print out test name and both the leptonica and * image libarary versions */ fprintf(stderr, "\n################ %s_reg ###############\n", rp->testname); vers = getLeptonicaVersion(); fprintf(stderr, "%s\n", vers); FREE(vers); vers = getImagelibVersions(); fprintf(stderr, "%s\n", vers); FREE(vers); rp->tstart = startTimerNested(); return 0; }
/*! * pixColorSegmentRemoveColors() * * Input: pixd (8 bpp, colormapped) * pixs (32 bpp rgb, with initial pixel values) * finalcolors (max number of colors to retain) * Return: 0 if OK, 1 on error * * Notes: * (1) This operation is in-place. * (2) This is phase 4 of color segmentation, and the second part * of the 2-step noise removal. Only 'finalcolors' different * colors are retained, with colors with smaller populations * being replaced by the nearest color of the remaining colors. * For highest accuracy, for pixels that are being replaced, * we find the nearest colormap color to the original rgb color. */ l_int32 pixColorSegmentRemoveColors(PIX *pixd, PIX *pixs, l_int32 finalcolors) { l_int32 i, ncolors, index, tempindex; l_int32 *tab; l_uint32 tempcolor; NUMA *na, *nasi; PIX *pixm; PIXCMAP *cmap; PROCNAME("pixColorSegmentRemoveColors"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (pixGetDepth(pixd) != 8) return ERROR_INT("pixd not 8 bpp", procName, 1); if ((cmap = pixGetColormap(pixd)) == NULL) return ERROR_INT("cmap not found", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); ncolors = pixcmapGetCount(cmap); if (finalcolors >= ncolors) /* few enough colors already; nothing to do */ return 0; /* Generate a mask over all pixels that are not in the * 'finalcolors' most populated colors. Save the colormap * index of any one of the retained colors in 'tempindex'. * The LUT has values 0 for the 'finalcolors' most populated colors, * which will be retained; and 1 for the rest, which are marked * by fg pixels in pixm and will be removed. */ na = pixGetCmapHistogram(pixd, 1); if ((nasi = numaGetSortIndex(na, L_SORT_DECREASING)) == NULL) { numaDestroy(&na); return ERROR_INT("nasi not made", procName, 1); } numaGetIValue(nasi, finalcolors - 1, &tempindex); /* retain down to this */ pixcmapGetColor32(cmap, tempindex, &tempcolor); /* use this color */ tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); for (i = finalcolors; i < ncolors; i++) { numaGetIValue(nasi, i, &index); tab[index] = 1; } pixm = pixMakeMaskFromLUT(pixd, tab); LEPT_FREE(tab); /* Reassign the masked pixels temporarily to the saved index * (tempindex). This guarantees that no pixels are labeled by * a colormap index of any colors that will be removed. * The actual value doesn't matter, as long as it's one * of the retained colors, because these pixels will later * be reassigned based on the full set of colors retained * in the colormap. */ pixSetMasked(pixd, pixm, tempcolor); /* Now remove unused colors from the colormap. This reassigns * image pixels as required. */ pixRemoveUnusedColors(pixd); /* Finally, reassign the pixels under the mask (those that were * given a 'tempindex' value) to the nearest color in the colormap. * This is the function used in phase 2 on all image pixels; here * it is only used on the masked pixels given by pixm. */ pixAssignToNearestColor(pixd, pixs, pixm, LEVEL_IN_OCTCUBE, NULL); pixDestroy(&pixm); numaDestroy(&na); numaDestroy(&nasi); return 0; }
/*! * bmfMakeAsciiTables * * Input: bmf * Return: 0 if OK, 1 on error * * Notes: * (1) This makes three tables, each of size 128, as follows: * - fonttab is a table containing the index of the Pix * that corresponds to each input ascii character; * it maps (ascii-index) --> Pixa index * - baselinetab is a table containing the baseline offset * for the Pix that corresponds to each input ascii character; * it maps (ascii-index) --> baseline offset * - widthtab is a table containing the character width in * pixels for the Pix that corresponds to that character; * it maps (ascii-index) --> bitmap width * (2) This also computes * - lineheight (sum of maximum character extensions above and * below the baseline) * - kernwidth (spacing between characters within a word) * - spacewidth (space between words) * - vertlinesep (extra vertical spacing between textlines) * (3) The baselines apply as follows: * baseline1 (ascii 32 - 57), ascii 92 * baseline2 (ascii 58 - 91) * baseline3 (ascii 93 - 126) * (4) The only array in bmf that is not ascii-based is the * array of bitmaps in the pixa, which starts at ascii 32. */ static l_int32 bmfMakeAsciiTables(L_BMF *bmf) { l_int32 i, maxh, height, charwidth, xwidth, kernwidth; l_int32 *fonttab, *baselinetab, *widthtab; PIX *pix; PROCNAME("bmfMakeAsciiTables"); if (!bmf) return ERROR_INT("bmf not defined", procName, 1); /* First get the fonttab; we use this later for the char widths */ if ((fonttab = (l_int32 *)CALLOC(128, sizeof(l_int32))) == NULL) return ERROR_INT("fonttab not made", procName, 1); bmf->fonttab = fonttab; for (i = 0; i < 128; i++) fonttab[i] = UNDEF; for (i = 32; i < 127; i++) fonttab[i] = i - 32; if ((baselinetab = (l_int32 *)CALLOC(128, sizeof(l_int32))) == NULL) return ERROR_INT("baselinetab not made", procName, 1); bmf->baselinetab = baselinetab; for (i = 0; i < 128; i++) baselinetab[i] = UNDEF; for (i = 32; i <= 57; i++) baselinetab[i] = bmf->baseline1; for (i = 58; i <= 91; i++) baselinetab[i] = bmf->baseline2; baselinetab[92] = bmf->baseline1; /* the '\' char */ for (i = 93; i < 127; i++) baselinetab[i] = bmf->baseline3; /* Generate array of character widths; req's fonttab to exist */ if ((widthtab = (l_int32 *)CALLOC(128, sizeof(l_int32))) == NULL) return ERROR_INT("widthtab not made", procName, 1); bmf->widthtab = widthtab; for (i = 0; i < 128; i++) widthtab[i] = UNDEF; for (i = 32; i < 127; i++) { bmfGetWidth(bmf, i, &charwidth); widthtab[i] = charwidth; } /* Get the line height of text characters, from the highest * ascender to the lowest descender; req's fonttab to exist. */ pix = bmfGetPix(bmf, 32); maxh = pixGetHeight(pix); pixDestroy(&pix); pix = bmfGetPix(bmf, 58); height = pixGetHeight(pix); pixDestroy(&pix); maxh = L_MAX(maxh, height); pix = bmfGetPix(bmf, 93); height = pixGetHeight(pix); pixDestroy(&pix); maxh = L_MAX(maxh, height); bmf->lineheight = maxh; /* Get the kern width (distance between characters). * We let it be the same for all characters in a given * font size, and scale it linearly with the size; * req's fonttab to be built first. */ bmfGetWidth(bmf, 120, &xwidth); kernwidth = (l_int32)(0.08 * (l_float32)xwidth + 0.5); bmf->kernwidth = L_MAX(1, kernwidth); /* Save the space width (between words) */ bmfGetWidth(bmf, 32, &charwidth); bmf->spacewidth = charwidth; /* Save the extra vertical space between lines */ bmf->vertlinesep = (l_int32)(VERT_FRACT_SEP * bmf->lineheight + 0.5); return 0; }
main(int argc, char **argv) { char *filein, *fileout, *str, *fname, *filename; char buffer[512]; l_int32 i, count, npages, length; FILE *fp; NUMA *naflags, *nasizes; PIX *pix, *pixd; PIXA *pixa; PIXCMAP *cmap; SARRAY *savals, *satypes, *sa; static char mainName[] = "mtifftest"; if (argc != 3) exit(ERROR_INT(" Syntax: mtifftest filein fileout", mainName, 1)); filein = argv[1]; fileout = argv[2]; #if 1 /* ------------------ Test multipage I/O -------------------*/ /* This puts every image file in the directory with a string * match to "weasel" into a multipage tiff file. * Images with 1 bpp are coded as g4; the others as zip. * It then reads back into a pix and displays. */ writeMultipageTiff(".", "weasel", "/tmp/junkout.tif"); pixa = pixaReadMultipageTiff("/tmp/junkout.tif"); pixd = pixaDisplayTiledInRows(pixa, 1, 1200, 0.5, 0, 15, 4); pixDisplay(pixd, 100, 0); pixDestroy(&pixd); pixd = pixaDisplayTiledInRows(pixa, 8, 1200, 0.8, 0, 15, 4); pixDisplay(pixd, 100, 200); pixDestroy(&pixd); pixd = pixaDisplayTiledInRows(pixa, 32, 1200, 1.2, 0, 15, 4); pixDisplay(pixd, 100, 400); pixDestroy(&pixd); pixaDestroy(&pixa); #endif #if 0 /* ------------ Test single-to-multipage I/O -------------------*/ /* Use 'filein' to specify a directory of tiff files. * Read them in and generate a multipage tiff file. * Then convert that to a G4 compressed and ascii85 encoded * PS file. */ sa = getFilenamesInDirectory(filein); sarrayWriteStream(stderr, sa); sarraySort(sa, sa, L_SORT_INCREASING); sarrayWriteStream(stderr, sa); npages = sarrayGetCount(sa); for (i = 0; i < npages; i++) { fname = sarrayGetString(sa, i, 0); filename = genPathname(filein, fname); pix = pixRead(filename); if (!pix) continue; if (i == 0) pixWriteTiff(tempmtiff, pix, IFF_TIFF_G4, "w+"); else pixWriteTiff(tempmtiff, pix, IFF_TIFF_G4, "a"); pixDestroy(&pix); lept_free(filename); } /* write it out as a PS file */ convertTiffMultipageToPS(tempmtiff, fileout, NULL, 0.95); sarrayDestroy(&sa); #endif #if 0 /* ------------------ Test multipage I/O -------------------*/ /* read count of tiff multipage */ fp = lept_fopen(filein, "rb"); if (fileFormatIsTiff(fp)) { tiffGetCount(fp, &npages); fprintf(stderr, " Tiff: %d page\n", npages); } else exit(ERROR_INT(" file not tiff", mainName, 1)); lept_fclose(fp); /* split into separate page files */ for (i = 0; i < npages + 1; i++) { /* read one beyond to catch error */ pix = pixReadTiff(filein, i); if (!pix) continue; sprintf(buffer, "/tmp/junkout.%d.tif", i); pixWrite(buffer, pix, IFF_TIFF_G4); pixDestroy(&pix); } /* read separate page files and write reversed file */ for (i = npages - 1; i >= 0; i--) { sprintf(buffer, "/tmp/junkout.%d.tif", i); pix = pixRead(buffer); if (!pix) continue; if (i == npages - 1) pixWriteTiff(tempmtiff, pix, IFF_TIFF_G4, "w+"); else pixWriteTiff(tempmtiff, pix, IFF_TIFF_G4, "a"); pixDestroy(&pix); } /* read reversed file and reverse again */ pixa = pixaCreate(npages); for (i = 0; i < 5; i++) { pix = pixReadTiff(tempmtiff, i); pixaAddPix(pixa, pix, L_INSERT); } for (i = npages - 1; i >= 0; i--) { pix = pixaGetPix(pixa, i, L_CLONE); if (i == npages - 1) pixWriteTiff(tempnewmtiff, pix, IFF_TIFF_G4, "w+"); else pixWriteTiff(tempnewmtiff, pix, IFF_TIFF_G4, "a"); pixDestroy(&pix); } pixaDestroy(&pixa); #endif #if 0 /* ----- test adding custom public tags to a tiff header ----- */ pix = pixRead(filein); naflags = numaCreate(10); savals = sarrayCreate(10); satypes = sarrayCreate(10); nasizes = numaCreate(10); /* numaAddNumber(naflags, TIFFTAG_XMLPACKET); */ /* XMP: 700 */ numaAddNumber(naflags, 700); str = "<xmp>This is a Fake XMP packet</xmp>\n<text>Guess what ...?</text>"; length = strlen(str); sarrayAddString(savals, str, 1); sarrayAddString(satypes, "char*", 1); numaAddNumber(nasizes, length); /* get it all */ numaAddNumber(naflags, 269); /* DOCUMENTNAME */ sarrayAddString(savals, "One silly title", 1); sarrayAddString(satypes, "char*", 1); numaAddNumber(naflags, 270); /* IMAGEDESCRIPTION */ sarrayAddString(savals, "One page of text", 1); sarrayAddString(satypes, "char*", 1); /* the max sample is used by rendering programs * to scale the dynamic range */ numaAddNumber(naflags, 281); /* MAXSAMPLEVALUE */ sarrayAddString(savals, "4", 1); sarrayAddString(satypes, "l_uint16", 1); /* note that date is required to be a 20 byte string */ numaAddNumber(naflags, 306); /* DATETIME */ sarrayAddString(savals, "2004:10:11 09:35:15", 1); sarrayAddString(satypes, "char*", 1); /* note that page number requires 2 l_uint16 input */ numaAddNumber(naflags, 297); /* PAGENUMBER */ sarrayAddString(savals, "1-412", 1); sarrayAddString(satypes, "l_uint16-l_uint16", 1); pixWriteTiffCustom(fileout, pix, IFF_TIFF_G4, "w", naflags, savals, satypes, nasizes); fprintTiffInfo(stderr, fileout); numaDestroy(&naflags); numaDestroy(&nasizes); sarrayDestroy(&savals); sarrayDestroy(&satypes); pixDestroy(&pix); #endif return 0; }
/*! * pixSaveTiledOutline() * * Input: pixs (1, 2, 4, 8, 32 bpp) * pixa (the pix are accumulated here) * reduction (0 to disable; otherwise this is a reduction factor) * newrow (0 if placed on the same row as previous; 1 otherwise) * space (horizontal and vertical spacing, in pixels) * linewidth (width of added outline for image; 0 for no outline) * dp (depth of pixa; 8 or 32 bpp; only used on first call) * Return: 0 if OK, 1 on error. * * Notes: * (1) Before calling this function for the first time, use * pixaCreate() to make the @pixa that will accumulate the pix. * This is passed in each time pixSaveTiled() is called. * (2) @reduction is the integer reduction factor for the input * image. After reduction and possible depth conversion, * the image is saved in the input pixa, along with a box * that specifies the location to place it when tiled later. * Disable saving the pix by setting reduction == 0. * (3) @newrow and @space specify the location of the new pix * with respect to the last one(s) that were entered. * (4) @dp specifies the depth at which all pix are saved. It can * be only 8 or 32 bpp. Any colormap is removed. This is only * used at the first invocation. * (5) This function uses two variables from call to call. * If they were static, the function would not be .so or thread * safe, and furthermore, there would be interference with two or * more pixa accumulating images at a time. Consequently, * we use the first pix in the pixa to store and obtain both * the depth and the current position of the bottom (one pixel * below the lowest image raster line when laid out using * the boxa). The bottom variable is stored in the input format * field, which is the only field available for storing an int. */ l_int32 pixSaveTiledOutline(PIX *pixs, PIXA *pixa, l_int32 reduction, l_int32 newrow, l_int32 space, l_int32 linewidth, l_int32 dp) { l_int32 n, top, left, bx, by, bw, w, h, depth, bottom; l_float32 scale; BOX *box; PIX *pix, *pixt1, *pixt2, *pixt3; PROCNAME("pixSaveTiledOutline"); if (reduction == 0) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); if (n == 0) { bottom = 0; if (dp != 8 && dp != 32) { L_WARNING("dp not 8 or 32 bpp; using 32", procName); depth = 32; } else depth = dp; } else { /* extract the depth and bottom params from the first pix */ pix = pixaGetPix(pixa, 0, L_CLONE); depth = pixGetDepth(pix); bottom = pixGetInputFormat(pix); /* not typical usage! */ pixDestroy(&pix); } /* Scale and convert to output depth */ if (reduction == 1) pixt1 = pixClone(pixs); else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pixt1 = pixScaleToGray(pixs, scale); else pixt1 = pixScale(pixs, scale, scale); } if (depth == 8) pixt2 = pixConvertTo8(pixt1, 0); else pixt2 = pixConvertTo32(pixt1); pixDestroy(&pixt1); /* Add black outline */ if (linewidth > 0) pixt3 = pixAddBorder(pixt2, linewidth, 0); else pixt3 = pixClone(pixt2); pixDestroy(&pixt2); /* Find position of current pix (UL corner plus size) */ if (n == 0) { top = 0; left = 0; } else if (newrow == 1) { top = bottom + space; left = 0; } else if (n > 0) { pixaGetBoxGeometry(pixa, n - 1, &bx, &by, &bw, NULL); top = by; left = bx + bw + space; } pixGetDimensions(pixt3, &w, &h, NULL); bottom = L_MAX(bottom, top + h); box = boxCreate(left, top, w, h); pixaAddPix(pixa, pixt3, L_INSERT); pixaAddBox(pixa, box, L_INSERT); /* Save the new bottom value */ pix = pixaGetPix(pixa, 0, L_CLONE); pixSetInputFormat(pix, bottom); /* not typical usage! */ pixDestroy(&pix); return 0; }
/* * pixaWriteCompressedToPS() * * Input: pixa (any set of images) * fileout (output ps file) * res (of input image) * level (compression: 2 or 3) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates a PS file of multiple page images, all * with bounding boxes. * (2) It compresses to: * cmap + level2: jpeg * cmap + level3: flate * 1 bpp: tiffg4 * 2 or 4 bpp + level2: jpeg * 2 or 4 bpp + level3: flate * 8 bpp: jpeg * 16 bpp: flate * 32 bpp: jpeg * (3) To generate a pdf, use: ps2pdf <infile.ps> <outfile.pdf> */ l_int32 pixaWriteCompressedToPS(PIXA *pixa, const char *fileout, l_int32 res, l_int32 level) { char *tname, *g4_name, *jpeg_name, *png_name; l_int32 i, n, firstfile, index, writeout, d; PIX *pix, *pixt; PIXCMAP *cmap; PROCNAME("pixaWriteCompressedToPS"); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!fileout) return ERROR_INT("fileout not defined", procName, 1); if (level != 2 && level != 3) { L_ERROR("only levels 2 and 3 permitted; using level 2\n", procName); level = 2; } n = pixaGetCount(pixa); firstfile = TRUE; index = 0; lept_mkdir("lept/comp"); g4_name = genTempFilename("/tmp/lept/comp", "temp.tif", 0, 0); jpeg_name = genTempFilename("/tmp/lept/comp", "temp.jpg", 0, 0); png_name = genTempFilename("/tmp/lept/comp", "temp.png", 0, 0); for (i = 0; i < n; i++) { writeout = TRUE; pix = pixaGetPix(pixa, i, L_CLONE); d = pixGetDepth(pix); cmap = pixGetColormap(pix); if (d == 1) { tname = g4_name; pixWrite(tname, pix, IFF_TIFF_G4); } else if (cmap) { if (level == 2) { pixt = pixConvertForPSWrap(pix); tname = jpeg_name; pixWrite(tname, pixt, IFF_JFIF_JPEG); pixDestroy(&pixt); } else { /* level == 3 */ tname = png_name; pixWrite(tname, pix, IFF_PNG); } } else if (d == 16) { if (level == 2) L_WARNING("d = 16; must write out flate\n", procName); tname = png_name; pixWrite(tname, pix, IFF_PNG); } else if (d == 2 || d == 4) { if (level == 2) { pixt = pixConvertTo8(pix, 0); tname = jpeg_name; pixWrite(tname, pixt, IFF_JFIF_JPEG); pixDestroy(&pixt); } else { /* level == 3 */ tname = png_name; pixWrite(tname, pix, IFF_PNG); } } else if (d == 8 || d == 32) { tname = jpeg_name; pixWrite(tname, pix, IFF_JFIF_JPEG); } else { /* shouldn't happen */ L_ERROR("invalid depth: %d\n", procName, d); writeout = FALSE; } pixDestroy(&pix); if (writeout) writeImageCompressedToPSFile(tname, fileout, res, &firstfile, &index); } LEPT_FREE(g4_name); LEPT_FREE(jpeg_name); LEPT_FREE(png_name); return 0; }
l_int32 main(int argc, char **argv) { l_int32 method, pageno; L_DEWARP *dew1; L_DEWARPA *dewa; PIX *pixs, *pixn, *pixg, *pixb, *pixd; static char mainName[] = "dewarptest2"; if (argc != 2 && argc != 4) return ERROR_INT("Syntax: dewarptest2 method [image pageno]", mainName, 1); if (argc == 2) { pixs = pixRead("cat.035.jpg"); pageno = 35; } else { pixs = pixRead(argv[2]); pageno = atoi(argv[3]); } if (!pixs) return ERROR_INT("image not read", mainName, 1); method = atoi(argv[1]); lept_mkdir("lept/dewarp"); if (method == 1) { /* Use single page dewarp function */ dewarpSinglePage(pixs, 0, 1, 1, 0, &pixd, NULL, 1); } else { /* Break down into multiple steps; require min of only 8 lines */ dewa = dewarpaCreate(40, 30, 1, 8, 50); dewarpaUseBothArrays(dewa, 1); dewarpaSetCheckColumns(dewa, 0); #if NORMALIZE /* 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); #else /* Don't normalize; just threshold and clean edges */ pixg = pixConvertTo8(pixs, 0); pixb = pixThresholdToBinary(pixg, 100); pixSetOrClearBorder(pixb, 30, 30, 40, 40, PIX_CLR); #endif /* Run the basic functions */ dew1 = dewarpCreate(pixb, pageno); dewarpaInsertDewarp(dewa, dew1); dewarpBuildPageModel(dew1, "/tmp/lept/dewarp/test2_model.pdf"); dewarpaApplyDisparity(dewa, pageno, pixg, -1, 0, 0, &pixd, "/tmp/lept/dewarp/test2_apply.pdf"); dewarpaInfo(stderr, dewa); dewarpaDestroy(&dewa); pixDestroy(&pixg); pixDestroy(&pixb); } pixDestroy(&pixs); pixDestroy(&pixd); return 0; }
/* * convertToPSEmbed() * * Input: filein (input image file -- any format) * fileout (output ps file) * level (compression: 1 (uncompressed), 2 or 3) * Return: 0 if OK, 1 on error * * Notes: * (1) This is a wrapper function that generates a PS file with * a bounding box, from any input image file. * (2) Do the best job of compression given the specified level. * %level=3 does flate compression on anything that is not * tiffg4 (1 bpp) or jpeg (8 bpp or rgb). * (3) If %level=2 and the file is not tiffg4 or jpeg, it will * first be written to file as jpeg with quality = 75. * This will remove the colormap and cause some degradation * in the image. * (4) The bounding box is required when a program such as TeX * (through epsf) places and rescales the image. It is * sized for fitting the image to an 8.5 x 11.0 inch page. */ l_int32 convertToPSEmbed(const char *filein, const char *fileout, l_int32 level) { const char nametif[] = "/tmp/junk_convert_ps_embed.tif"; const char namejpg[] = "/tmp/junk_convert_ps_embed.jpg"; l_int32 d, format; PIX *pix, *pixs; PROCNAME("convertToPSEmbed"); if (!filein) return ERROR_INT("filein not defined", procName, 1); if (!fileout) return ERROR_INT("fileout not defined", procName, 1); if (level != 1 && level != 2 && level != 3) { L_ERROR("invalid level specified; using level 2\n", procName); level = 2; } if (level == 1) { /* no compression */ pixWritePSEmbed(filein, fileout); return 0; } /* Find the format and write out directly if in jpeg or tiff g4 */ findFileFormat(filein, &format); if (format == IFF_JFIF_JPEG) { convertJpegToPSEmbed(filein, fileout); return 0; } else if (format == IFF_TIFF_G4) { convertG4ToPSEmbed(filein, fileout); return 0; } else if (format == IFF_UNKNOWN) { L_ERROR("format of %s not known\n", procName, filein); return 1; } /* If level 3, flate encode. */ if (level == 3) { convertFlateToPSEmbed(filein, fileout); return 0; } /* OK, it's level 2, so we must convert to jpeg or tiff g4 */ if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("image not read from file", procName, 1); d = pixGetDepth(pixs); if ((d == 2 || d == 4) && !pixGetColormap(pixs)) pix = pixConvertTo8(pixs, 0); else if (d == 16) pix = pixConvert16To8(pixs, 1); else pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); d = pixGetDepth(pix); if (d == 1) { pixWrite(nametif, pix, IFF_TIFF_G4); convertG4ToPSEmbed(nametif, fileout); } else { pixWrite(namejpg, pix, IFF_JFIF_JPEG); convertJpegToPSEmbed(namejpg, fileout); } pixDestroy(&pix); pixDestroy(&pixs); return 0; }
/* * pixWriteSegmentedPageToPS() * * Input: pixs (all depths; colormap ok) * pixm (<optional> 1 bpp segmentation mask over image region) * textscale (scale of text output relative to pixs) * imagescale (scale of image output relative to pixs) * threshold (threshold for binarization; typ. 190) * pageno (page number in set; use 1 for new output file) * fileout (output ps file) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates the PS string for a mixed text/image page, * and adds it to an existing file if %pageno > 1. * The PS output is determined by fitting the result to * a letter-size (8.5 x 11 inch) page. * (2) The two images (pixs and pixm) are at the same resolution * (typically 300 ppi). They are used to generate two compressed * images, pixb and pixc, that are put directly into the output * PS file. * (3) pixb is the text component. In the PostScript world, we think of * it as a mask through which we paint black. It is produced by * scaling pixs by %textscale, and thresholding to 1 bpp. * (4) pixc is the image component, which is that part of pixs under * the mask pixm. It is scaled from pixs by %imagescale. * (5) Typical values are textscale = 2.0 and imagescale = 0.5. * (6) If pixm == NULL, the page has only text. If it is all black, * the page is all image and has no text. * (7) This can be used to write a multi-page PS file, by using * sequential page numbers with the same output file. It can * also be used to write separate PS files for each page, * by using different output files with %pageno = 0 or 1. */ l_int32 pixWriteSegmentedPageToPS(PIX *pixs, PIX *pixm, l_float32 textscale, l_float32 imagescale, l_int32 threshold, l_int32 pageno, const char *fileout) { l_int32 alltext, notext, d, ret; l_uint32 val; l_float32 scaleratio; PIX *pixmi, *pixmis, *pixt, *pixg, *pixsc, *pixb, *pixc; PROCNAME("pixWriteSegmentedPageToPS"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!fileout) return ERROR_INT("fileout not defined", procName, 1); if (imagescale <= 0.0 || textscale <= 0.0) return ERROR_INT("relative scales must be > 0.0", procName, 1); /* Analyze the page. Determine the ratio by which the * binary text mask is scaled relative to the image part. * If there is no image region (alltext == TRUE), the * text mask will be rendered directly to fit the page, * and scaleratio = 1.0. */ alltext = TRUE; notext = FALSE; scaleratio = 1.0; if (pixm) { pixZero(pixm, &alltext); /* pixm empty: all text */ if (alltext) { pixm = NULL; /* treat it as not existing here */ } else { pixmi = pixInvert(NULL, pixm); pixZero(pixmi, ¬ext); /* pixm full; no text */ pixDestroy(&pixmi); scaleratio = textscale / imagescale; } } if (pixGetDepth(pixs) == 1) { /* render tiff g4 */ pixb = pixClone(pixs); pixc = NULL; } else { pixt = pixConvertTo8Or32(pixs, L_CLONE, 0); /* clone if possible */ /* Get the binary text mask. Note that pixg cannot be a * clone of pixs, because it may be altered by pixSetMasked(). */ pixb = NULL; if (notext == FALSE) { d = pixGetDepth(pixt); if (d == 8) pixg = pixCopy(NULL, pixt); else /* d == 32 */ pixg = pixConvertRGBToLuminance(pixt); if (pixm) /* clear out the image parts */ pixSetMasked(pixg, pixm, 255); if (textscale == 1.0) pixsc = pixClone(pixg); else if (textscale >= 0.7) pixsc = pixScaleGrayLI(pixg, textscale, textscale); else pixsc = pixScaleAreaMap(pixg, textscale, textscale); pixb = pixThresholdToBinary(pixsc, threshold); pixDestroy(&pixg); pixDestroy(&pixsc); } /* Get the scaled image region */ pixc = NULL; if (pixm) { if (imagescale == 1.0) pixsc = pixClone(pixt); /* can possibly be a clone of pixs */ else pixsc = pixScale(pixt, imagescale, imagescale); /* If pixm is not full, clear the pixels in pixsc * corresponding to bg in pixm, where there can be text * that is written through the mask pixb. Note that * we could skip this and use pixsc directly in * pixWriteMixedToPS(); however, clearing these * non-image regions to a white background will reduce * the size of pixc (relative to pixsc), and hence * reduce the size of the PS file that is generated. * Use a copy so that we don't accidentally alter pixs. */ if (notext == FALSE) { pixmis = pixScale(pixm, imagescale, imagescale); pixmi = pixInvert(NULL, pixmis); val = (d == 8) ? 0xff : 0xffffff00; pixc = pixCopy(NULL, pixsc); pixSetMasked(pixc, pixmi, val); /* clear non-image part */ pixDestroy(&pixmis); pixDestroy(&pixmi); } else { pixc = pixClone(pixsc); } pixDestroy(&pixsc); } pixDestroy(&pixt); } /* Generate the PS file. Don't use bounding boxes. */ l_psWriteBoundingBox(FALSE); ret = pixWriteMixedToPS(pixb, pixc, scaleratio, pageno, fileout); l_psWriteBoundingBox(TRUE); pixDestroy(&pixb); pixDestroy(&pixc); return ret; }
/* * countAlignedMatches() * Input: nai1, nai2 (numas of row pairs for matches) * nasx, nasy (numas of x and y shifts for the matches) * n1, n2 (number of rows in images 1 and 2) * delx, dely (allowed difference in shifts of the match, * compared to the reference match) * nreq (number of required aligned matches) * &same (<return> 1 if @nreq row matches are found; 0 otherwise) * Return: 0 if OK, 1 on error * * Notes: * (1) This takes 4 input arrays giving parameters of all the * line matches. It looks for the maximum set of aligned * matches (matches with approximately the same overall shifts) * that do not use rows from either image more than once. */ static l_int32 countAlignedMatches(NUMA *nai1, NUMA *nai2, NUMA *nasx, NUMA *nasy, l_int32 n1, l_int32 n2, l_int32 delx, l_int32 dely, l_int32 nreq, l_int32 *psame, l_int32 debugflag) { l_int32 i, j, nm, shiftx, shifty, nmatch, diffx, diffy; l_int32 *ia1, *ia2, *iasx, *iasy, *index1, *index2; PROCNAME("countAlignedMatches"); if (!nai1 || !nai2 || !nasx || !nasy) return ERROR_INT("4 input numas not defined", procName, 1); if (!psame) return ERROR_INT("&same not defined", procName, 1); *psame = 0; /* Check for sufficient aligned matches, doing a double iteration * over the set of raw matches. The row index arrays * are used to verify that the same rows in either image * are not used in more than one match. Whenever there * is a match that is properly aligned, those rows are * marked in the index arrays. */ nm = numaGetCount(nai1); /* number of matches */ if (nm < nreq) return 0; ia1 = numaGetIArray(nai1); ia2 = numaGetIArray(nai2); iasx = numaGetIArray(nasx); iasy = numaGetIArray(nasy); index1 = (l_int32 *) CALLOC(n1, sizeof(l_int32)); /* keep track of rows */ index2 = (l_int32 *) CALLOC(n2, sizeof(l_int32)); for (i = 0; i < nm; i++) { if (*psame == 1) break; /* Reset row index arrays */ memset(index1, 0, 4 * n1); memset(index2, 0, 4 * n2); nmatch = 1; index1[ia1[i]] = nmatch; /* mark these rows as taken */ index2[ia2[i]] = nmatch; shiftx = iasx[i]; /* reference shift between two rows */ shifty = iasy[i]; /* ditto */ if (nreq == 1) { *psame = 1; break; } for (j = 0; j < nm; j++) { if (j == i) continue; /* Rows must both be different from any previously seen */ if (index1[ia1[j]] > 0 || index2[ia2[j]] > 0) continue; /* Check the shift for this match */ diffx = L_ABS(shiftx - iasx[j]); diffy = L_ABS(shifty - iasy[j]); if (diffx > delx || diffy > dely) continue; /* We have a match */ nmatch++; index1[ia1[j]] = nmatch; /* mark the rows */ index2[ia2[j]] = nmatch; if (nmatch >= nreq) { *psame = 1; if (debugflag) printRowIndices(index1, n1, index2, n2); break; } } } FREE(ia1); FREE(ia2); FREE(iasx); FREE(iasy); FREE(index1); FREE(index2); return 0; }
/*! * pixColorSegmentTryCluster() * * Input: pixd * pixs * maxdist * maxcolors * Return: 0 if OK, 1 on error * * Note: This function should only be called from pixColorSegCluster() */ static l_int32 pixColorSegmentTryCluster(PIX *pixd, PIX *pixs, l_int32 maxdist, l_int32 maxcolors) { l_int32 rmap[256], gmap[256], bmap[256]; l_int32 w, h, wpls, wpld, i, j, k, found, ret, index, ncolors; l_int32 rval, gval, bval, dist2, maxdist2; l_int32 countarray[256]; l_int32 rsum[256], gsum[256], bsum[256]; l_uint32 *ppixel; l_uint32 *datas, *datad, *lines, *lined; PIXCMAP *cmap; PROCNAME("pixColorSegmentTryCluster"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); w = pixGetWidth(pixs); h = pixGetHeight(pixs); maxdist2 = maxdist * maxdist; cmap = pixGetColormap(pixd); pixcmapClear(cmap); for (k = 0; k < 256; k++) { rsum[k] = gsum[k] = bsum[k] = 0; rmap[k] = gmap[k] = bmap[k] = 0; } datas = pixGetData(pixs); datad = pixGetData(pixd); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { ppixel = lines + j; rval = GET_DATA_BYTE(ppixel, COLOR_RED); gval = GET_DATA_BYTE(ppixel, COLOR_GREEN); bval = GET_DATA_BYTE(ppixel, COLOR_BLUE); ncolors = pixcmapGetCount(cmap); found = FALSE; for (k = 0; k < ncolors; k++) { dist2 = (rval - rmap[k]) * (rval - rmap[k]) + (gval - gmap[k]) * (gval - gmap[k]) + (bval - bmap[k]) * (bval - bmap[k]); if (dist2 <= maxdist2) { /* take it; greedy */ found = TRUE; SET_DATA_BYTE(lined, j, k); countarray[k]++; rsum[k] += rval; gsum[k] += gval; bsum[k] += bval; break; } } if (!found) { /* Add a new color */ ret = pixcmapAddNewColor(cmap, rval, gval, bval, &index); /* fprintf(stderr, "index = %d, (i,j) = (%d,%d), rgb = (%d, %d, %d)\n", index, i, j, rval, gval, bval); */ if (ret == 0 && index < maxcolors) { countarray[index] = 1; SET_DATA_BYTE(lined, j, index); rmap[index] = rval; gmap[index] = gval; bmap[index] = bval; rsum[index] = rval; gsum[index] = gval; bsum[index] = bval; } else { L_INFO("maxcolors exceeded for maxdist = %d\n", procName, maxdist); return 1; } } } } /* Replace the colors in the colormap by the averages */ for (k = 0; k < ncolors; k++) { rval = rsum[k] / countarray[k]; gval = gsum[k] / countarray[k]; bval = bsum[k] / countarray[k]; pixcmapResetColor(cmap, k, rval, gval, bval); } return 0; }
/*! * pixWrite() * * Input: filename * pix * format (defined in imageio.h) * Return: 0 if OK; 1 on error * * Notes: * (1) Open for write using binary mode (with the "b" flag) * to avoid having Windows automatically translate the NL * into CRLF, which corrupts image files. On non-windows * systems this flag should be ignored, per ISO C90. * Thanks to Dave Bryan for pointing this out. * (2) If the default image format is requested, we use the input format; * if the input format is unknown, a lossless format is assigned. * (3) There are two modes with respect to file naming. * (a) The default code writes to @filename. * (b) If WRITE_AS_NAMED is defined to 0, it's a bit fancier. * Then, if @filename does not have a file extension, one is * automatically appended, depending on the requested format. * The original intent for providing option (b) was to insure * that filenames on Windows have an extension that matches * the image compression. However, this is not the default. */ l_int32 pixWrite(const char *filename, PIX *pix, l_int32 format) { char *fname; FILE *fp; PROCNAME("pixWrite"); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (!filename) return ERROR_INT("filename not defined", procName, 1); if (format == IFF_JP2) return ERROR_INT("jp2 not supported", procName, 1); fname = genPathname(filename, NULL); #if WRITE_AS_NAMED /* Default */ if ((fp = fopen(fname, "wb+")) == NULL) { FREE(fname); return ERROR_INT("stream not opened", procName, 1); } #else /* Add an extension to the output name if none exists */ {l_int32 extlen; char *extension, *filebuf; splitPathAtExtension(fname, NULL, &extension); extlen = strlen(extension); FREE(extension); if (extlen == 0) { if (format == IFF_DEFAULT || format == IFF_UNKNOWN) format = pixChooseOutputFormat(pix); filebuf = (char *)CALLOC(strlen(fname) + 10, sizeof(char)); if (!filebuf) { return ERROR_INT("filebuf not made", procName, 1); FREE(fname); } strncpy(filebuf, fname, strlen(fname)); strcat(filebuf, "."); strcat(filebuf, ImageFileFormatExtensions[format]); } else filebuf = (char *)fname; fp = fopen(filebuf, "wb+"); if (filebuf != fname) FREE(filebuf); if (fp == NULL) { FREE(fname); return ERROR_INT("stream not opened", procName, 1); } } #endif /* WRITE_AS_NAMED */ FREE(fname); if (pixWriteStream(fp, pix, format)) { fclose(fp); return ERROR_INT("pix not written to stream", procName, 1); } /* Close the stream except if GIF under windows, because * EGifCloseFile() closes the windows file stream! */ if (format != IFF_GIF) fclose(fp); #ifndef _WIN32 else /* gif file */ fclose(fp); #endif /* ! _WIN32 */ return 0; }
/*! * pixAssignToNearestColor() * * Input: pixd (8 bpp, colormapped) * pixs (32 bpp; 24-bit color) * pixm (<optional> 1 bpp) * level (of octcube used for finding nearest color in cmap) * countarray (<optional> ptr to array, in which we can store * the number of pixels found in each color in * the colormap in pixd) * Return: 0 if OK, 1 on error * * Notes: * (1) This is used in phase 2 of color segmentation, where pixs * is the original input image to pixColorSegment(), and * pixd is the colormapped image returned from * pixColorSegmentCluster(). It is also used, with a mask, * in phase 4. * (2) This is an in-place operation. * (3) The colormap in pixd is unchanged. * (4) pixs and pixd must be the same size (w, h). * (5) The selection mask pixm can be null. If it exists, it must * be the same size as pixs and pixd, and only pixels * corresponding to fg in pixm are assigned. Set to * NULL if all pixels in pixd are to be assigned. * (6) The countarray can be null. If it exists, it is pre-allocated * and of a size at least equal to the size of the colormap in pixd. * (7) This does a best-fit (non-greedy) assignment of pixels to * existing clusters. Specifically, it assigns each pixel * in pixd to the color index in the pixd colormap that has a * color closest to the corresponding rgb pixel in pixs. * (8) 'level' is the octcube level used to quickly find the nearest * color in the colormap for each pixel. For color segmentation, * this parameter is set to LEVEL_IN_OCTCUBE. * (9) We build a mapping table from octcube to colormap index so * that this function can run in a time (otherwise) independent * of the number of colors in the colormap. This avoids a * brute-force search for the closest colormap color to each * pixel in the image. */ l_int32 pixAssignToNearestColor(PIX *pixd, PIX *pixs, PIX *pixm, l_int32 level, l_int32 *countarray) { l_int32 w, h, wpls, wpld, wplm, i, j; l_int32 rval, gval, bval, index; l_int32 *cmaptab; l_uint32 octindex; l_uint32 *rtab, *gtab, *btab; l_uint32 *ppixel; l_uint32 *datas, *datad, *datam, *lines, *lined, *linem; PIXCMAP *cmap; PROCNAME("pixAssignToNearestColor"); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if ((cmap = pixGetColormap(pixd)) == NULL) return ERROR_INT("cmap not found", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 32) return ERROR_INT("pixs not 32 bpp", procName, 1); /* Set up the tables to map rgb to the nearest colormap index */ if (makeRGBToIndexTables(&rtab, >ab, &btab, level)) return ERROR_INT("index tables not made", procName, 1); if ((cmaptab = pixcmapToOctcubeLUT(cmap, level, L_MANHATTAN_DISTANCE)) == NULL) return ERROR_INT("cmaptab not made", procName, 1); w = pixGetWidth(pixs); h = pixGetHeight(pixs); datas = pixGetData(pixs); datad = pixGetData(pixd); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); if (pixm) { datam = pixGetData(pixm); wplm = pixGetWpl(pixm); } for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; if (pixm) linem = datam + i * wplm; for (j = 0; j < w; j++) { if (pixm) { if (!GET_DATA_BIT(linem, j)) continue; } ppixel = lines + j; rval = GET_DATA_BYTE(ppixel, COLOR_RED); gval = GET_DATA_BYTE(ppixel, COLOR_GREEN); bval = GET_DATA_BYTE(ppixel, COLOR_BLUE); /* Map from rgb to octcube index */ getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab, &octindex); /* Map from octcube index to nearest colormap index */ index = cmaptab[octindex]; if (countarray) countarray[index]++; SET_DATA_BYTE(lined, j, index); } } LEPT_FREE(cmaptab); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return 0; }
/*! * pixWriteStream() * * Input: stream * pix * format * Return: 0 if OK; 1 on error. */ l_int32 pixWriteStream(FILE *fp, PIX *pix, l_int32 format) { PROCNAME("pixWriteStream"); if (!fp) return ERROR_INT("stream not defined", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (format == IFF_DEFAULT) format = pixChooseOutputFormat(pix); switch(format) { case IFF_BMP: pixWriteStreamBmp(fp, pix); break; case IFF_JFIF_JPEG: /* default quality; baseline sequential */ return pixWriteStreamJpeg(fp, pix, 75, 0); break; case IFF_PNG: /* no gamma value stored */ return pixWriteStreamPng(fp, pix, 0.0); break; case IFF_TIFF: /* uncompressed */ case IFF_TIFF_PACKBITS: /* compressed, binary only */ case IFF_TIFF_RLE: /* compressed, binary only */ case IFF_TIFF_G3: /* compressed, binary only */ case IFF_TIFF_G4: /* compressed, binary only */ case IFF_TIFF_LZW: /* compressed, all depths */ case IFF_TIFF_ZIP: /* compressed, all depths */ return pixWriteStreamTiff(fp, pix, format); break; case IFF_PNM: return pixWriteStreamPnm(fp, pix); break; case IFF_GIF: return pixWriteStreamGif(fp, pix); break; case IFF_PS: return pixWriteStreamPS(fp, pix, NULL, 0, DEFAULT_SCALING); break; case IFF_JP2: return ERROR_INT("jp2 format not supported", procName, 1); break; case IFF_WEBP: return pixWriteStreamWebP(fp, pix, 80); break; case IFF_LPDF: return pixWriteStreamPdf(fp, pix, 0, NULL); break; case IFF_SPIX: return pixWriteStreamSpix(fp, pix); break; default: return ERROR_INT("unknown format", procName, 1); break; } return 0; }
/*! * pixWriteMemWebP() * * Input: &encdata (<return> webp encoded data of pixs) * &encsize (<return> size of webp encoded data) * pixs (any depth, cmapped OK) * quality (0 - 100; default ~80) * lossless (use 1 for lossless; 0 for lossy) * Return: 0 if OK, 1 on error * * Notes: * (1) Lossless and lossy encoding are entirely different in webp. * @quality applies to lossy, and is ignored for lossless. * (2) The input image is converted to RGB if necessary. If spp == 3, * we set the alpha channel to fully opaque (255), and * WebPEncodeRGBA() then removes the alpha chunk when encoding, * setting the internal header field has_alpha to 0. */ l_int32 pixWriteMemWebP(l_uint8 **pencdata, size_t *pencsize, PIX *pixs, l_int32 quality, l_int32 lossless) { l_int32 w, h, d, wpl, stride; l_uint32 *data; PIX *pix1, *pix2; PROCNAME("pixWriteMemWebP"); if (!pencdata) return ERROR_INT("&encdata not defined", procName, 1); *pencdata = NULL; if (!pencsize) return ERROR_INT("&encsize not defined", procName, 1); *pencsize = 0; if (!pixs) return ERROR_INT("&pixs not defined", procName, 1); if (lossless == 0 && (quality < 0 || quality > 100)) return ERROR_INT("quality not in [0 ... 100]", procName, 1); if ((pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR)) == NULL) return ERROR_INT("failure to remove color map", procName, 1); /* Convert to rgb if not 32 bpp; pix2 must not be a clone of pixs. */ if (pixGetDepth(pix1) != 32) pix2 = pixConvertTo32(pix1); else pix2 = pixCopy(NULL, pix1); pixDestroy(&pix1); pixGetDimensions(pix2, &w, &h, &d); if (w <= 0 || h <= 0 || d != 32) { pixDestroy(&pix2); return ERROR_INT("pix2 not 32 bpp or of 0 size", procName, 1); } /* If spp == 3, need to set alpha layer to opaque (all 1s). */ if (pixGetSpp(pix2) == 3) pixSetComponentArbitrary(pix2, L_ALPHA_CHANNEL, 255); /* Webp encoder assumes big-endian byte order for RGBA components */ pixEndianByteSwap(pix2); wpl = pixGetWpl(pix2); data = pixGetData(pix2); stride = wpl * 4; if (lossless) { *pencsize = WebPEncodeLosslessRGBA((uint8_t *)data, w, h, stride, pencdata); } else { *pencsize = WebPEncodeRGBA((uint8_t *)data, w, h, stride, quality, pencdata); } pixDestroy(&pix2); if (*pencsize == 0) { free(pencdata); *pencdata = NULL; return ERROR_INT("webp encoding failed", procName, 1); } return 0; }
/*! * pixWriteMem() * * Input: &data (<return> data of tiff compressed image) * &size (<return> size of returned data) * pix * format (defined in imageio.h) * Return: 0 if OK, 1 on error * * Notes: * (1) On windows, this will only write tiff and PostScript to memory. * For other formats, it requires open_memstream(3). * (2) PostScript output is uncompressed, in hex ascii. * Most printers support level 2 compression (tiff_g4 for 1 bpp, * jpeg for 8 and 32 bpp). */ l_int32 pixWriteMem(l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 format) { l_int32 ret; PROCNAME("pixWriteMem"); if (!pdata) return ERROR_INT("&data not defined", procName, 1 ); if (!psize) return ERROR_INT("&size not defined", procName, 1 ); if (!pix) return ERROR_INT("&pix not defined", procName, 1 ); if (format == IFF_DEFAULT) format = pixChooseOutputFormat(pix); switch(format) { case IFF_BMP: ret = pixWriteMemBmp(pdata, psize, pix); break; case IFF_JFIF_JPEG: /* default quality; baseline sequential */ ret = pixWriteMemJpeg(pdata, psize, pix, 75, 0); break; case IFF_PNG: /* no gamma value stored */ ret = pixWriteMemPng(pdata, psize, pix, 0.0); break; case IFF_TIFF: /* uncompressed */ case IFF_TIFF_PACKBITS: /* compressed, binary only */ case IFF_TIFF_RLE: /* compressed, binary only */ case IFF_TIFF_G3: /* compressed, binary only */ case IFF_TIFF_G4: /* compressed, binary only */ case IFF_TIFF_LZW: /* compressed, all depths */ case IFF_TIFF_ZIP: /* compressed, all depths */ ret = pixWriteMemTiff(pdata, psize, pix, format); break; case IFF_PNM: ret = pixWriteMemPnm(pdata, psize, pix); break; case IFF_PS: ret = pixWriteMemPS(pdata, psize, pix, NULL, 0, DEFAULT_SCALING); break; case IFF_GIF: ret = pixWriteMemGif(pdata, psize, pix); break; case IFF_JP2: return ERROR_INT("jp2 not supported", procName, 1); break; case IFF_SPIX: ret = pixWriteMemSpix(pdata, psize, pix); break; default: return ERROR_INT("unknown format", procName, 1); break; } return ret; }
main(int argc, char **argv) { l_int32 d; PIX *pixs, *pixc, *pixr, *pixg, *pixb, *pixsg, *pixsm, *pixd; PIXA *pixa; static char mainName[] = "livre_adapt"; if (argc != 1) exit(ERROR_INT(" Syntax: livre_adapt", mainName, 1)); /* Read the image in at 150 ppi. */ pixDisplayWrite(NULL, -1); if ((pixs = pixRead("brothers.150.jpg")) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); pixDisplayWriteFormat(pixs, 2, IFF_JFIF_JPEG); /* Normalize for uneven illumination on RGB image */ pixBackgroundNormRGBArraysMorph(pixs, NULL, 4, 5, 200, &pixr, &pixg, &pixb); pixd = pixApplyInvBackgroundRGBMap(pixs, pixr, pixg, pixb, 4, 4); pixDisplayWriteFormat(pixd, 2, IFF_JFIF_JPEG); pixDestroy(&pixr); pixDestroy(&pixg); pixDestroy(&pixb); pixDestroy(&pixd); /* Convert the RGB image to grayscale. */ pixsg = pixConvertRGBToLuminance(pixs); pixDisplayWriteFormat(pixsg, 2, IFF_JFIF_JPEG); /* Remove the text in the fg. */ pixc = pixCloseGray(pixsg, 25, 25); pixDisplayWriteFormat(pixc, 2, IFF_JFIF_JPEG); /* Smooth the bg with a convolution. */ pixsm = pixBlockconv(pixc, 15, 15); pixDisplayWriteFormat(pixsm, 2, IFF_JFIF_JPEG); pixDestroy(&pixc); /* Normalize for uneven illumination on gray image. */ pixBackgroundNormGrayArrayMorph(pixsg, NULL, 4, 5, 200, &pixg); pixc = pixApplyInvBackgroundGrayMap(pixsg, pixg, 4, 4); pixDisplayWriteFormat(pixc, 2, IFF_JFIF_JPEG); pixDestroy(&pixg); /* Increase the dynamic range. */ pixd = pixGammaTRC(NULL, pixc, 1.0, 30, 180); pixDisplayWriteFormat(pixd, 2, IFF_JFIF_JPEG); pixDestroy(&pixc); /* Threshold to 1 bpp. */ pixb = pixThresholdToBinary(pixd, 120); pixDisplayWriteFormat(pixb, 2, IFF_PNG); pixDestroy(&pixd); pixDestroy(&pixb); /* Generate the output image */ pixa = pixaReadFiles("/tmp", "junk_write_display"); pixd = pixaDisplayTiledAndScaled(pixa, 8, 350, 4, 0, 25, 2); pixWrite("/tmp/adapt.jpg", pixd, IFF_JFIF_JPEG); pixDisplayWithTitle(pixd, 100, 100, NULL, 1); pixDestroy(&pixd); pixDestroy(&pixs); pixDestroy(&pixsg); return 0; }
/*! * pixDisplayWithTitle() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * x, y (location of display frame) * title (<optional> on frame; can be NULL); * dispflag (1 to write, else disabled) * Return: 0 if OK; 1 on error * * Notes: * (1) See notes for pixDisplay(). * (2) This displays the image if dispflag == 1. */ l_int32 pixDisplayWithTitle(PIX *pixs, l_int32 x, l_int32 y, const char *title, l_int32 dispflag) { char *tempname; char buffer[L_BUF_SIZE]; static l_int32 index = 0; /* caution: not .so or thread safe */ l_int32 w, h, d, ignore; l_float32 ratw, rath, ratmin; PIX *pixt; #ifndef _WIN32 l_int32 wt, ht; #else char *pathname; char fullpath[_MAX_PATH]; #endif /* _WIN32 */ PROCNAME("pixDisplayWithTitle"); if (dispflag != 1) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (var_DISPLAY_PROG != L_DISPLAY_WITH_XV && var_DISPLAY_PROG != L_DISPLAY_WITH_XLI && var_DISPLAY_PROG != L_DISPLAY_WITH_XZGV && var_DISPLAY_PROG != L_DISPLAY_WITH_IV) return ERROR_INT("no program chosen for display", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (w <= MAX_DISPLAY_WIDTH && h <= MAX_DISPLAY_HEIGHT) { if (d == 16) /* take MSB */ pixt = pixConvert16To8(pixs, 1); else pixt = pixClone(pixs); } else { ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w; rath = (l_float32)MAX_DISPLAY_HEIGHT / (l_float32)h; ratmin = L_MIN(ratw, rath); if (ratmin < 0.125 && d == 1) pixt = pixScaleToGray8(pixs); else if (ratmin < 0.25 && d == 1) pixt = pixScaleToGray4(pixs); else if (ratmin < 0.33 && d == 1) pixt = pixScaleToGray3(pixs); else if (ratmin < 0.5 && d == 1) pixt = pixScaleToGray2(pixs); else pixt = pixScale(pixs, ratmin, ratmin); if (!pixt) return ERROR_INT("pixt not made", procName, 1); } if (index == 0) { lept_rmdir("display"); lept_mkdir("display"); } index++; if (pixGetDepth(pixt) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "/tmp/display/write.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/display/write.%03d.jpg", index); pixWrite(buffer, pixt, IFF_JFIF_JPEG); } tempname = stringNew(buffer); #ifndef _WIN32 /* Unix */ if (var_DISPLAY_PROG == L_DISPLAY_WITH_XV) { if (title) snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d -name \"%s\" %s &", x, y, title, tempname); else snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d %s &", x, y, tempname); } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XLI) { if (title) snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d -title \"%s\" %s &", x, y, title, tempname); else snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d %s &", x, y, tempname); } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XZGV) { /* no way to display title */ pixGetDimensions(pixt, &wt, &ht, NULL); snprintf(buffer, L_BUF_SIZE, "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10, x, y, tempname); } ignore = system(buffer); #else /* _WIN32 */ /* Windows: L_DISPLAY_WITH_IV */ pathname = genPathname(tempname, NULL); _fullpath(fullpath, pathname, sizeof(fullpath)); if (title) snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"", fullpath, x, y, title); else snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d)", fullpath, x, y); ignore = system(buffer); FREE(pathname); #endif /* _WIN32 */ pixDestroy(&pixt); FREE(tempname); return 0; }
/*! * getBilinearXformCoeffs() * * Input: ptas (source 4 points; unprimed) * ptad (transformed 4 points; primed) * &vc (<return> vector of coefficients of transform) * Return: 0 if OK; 1 on error * * We have a set of 8 equations, describing the bilinear * transformation that takes 4 points (ptas) into 4 other * points (ptad). These equations are: * * x1' = c[0]*x1 + c[1]*y1 + c[2]*x1*y1 + c[3] * y1' = c[4]*x1 + c[5]*y1 + c[6]*x1*y1 + c[7] * x2' = c[0]*x2 + c[1]*y2 + c[2]*x2*y2 + c[3] * y2' = c[4]*x2 + c[5]*y2 + c[6]*x2*y2 + c[7] * x3' = c[0]*x3 + c[1]*y3 + c[2]*x3*y3 + c[3] * y3' = c[4]*x3 + c[5]*y3 + c[6]*x3*y3 + c[7] * x4' = c[0]*x4 + c[1]*y4 + c[2]*x4*y4 + c[3] * y4' = c[4]*x4 + c[5]*y4 + c[6]*x4*y4 + c[7] * * This can be represented as * * AC = B * * where B and C are column vectors * * B = [ x1' y1' x2' y2' x3' y3' x4' y4' ] * C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] ] * * and A is the 8x8 matrix * * x1 y1 x1*y1 1 0 0 0 0 * 0 0 0 0 x1 y1 x1*y1 1 * x2 y2 x2*y2 1 0 0 0 0 * 0 0 0 0 x2 y2 x2*y2 1 * x3 y3 x3*y3 1 0 0 0 0 * 0 0 0 0 x3 y3 x3*y3 1 * x4 y4 x4*y4 1 0 0 0 0 * 0 0 0 0 x4 y4 x4*y4 1 * * These eight equations are solved here for the coefficients C. * * These eight coefficients can then be used to find the mapping * (x,y) --> (x',y'): * * x' = c[0]x + c[1]y + c[2]xy + c[3] * y' = c[4]x + c[5]y + c[6]xy + c[7] * * that are implemented in bilinearXformSampledPt() and * bilinearXFormPt(). */ l_int32 getBilinearXformCoeffs(PTA *ptas, PTA *ptad, l_float32 **pvc) { l_int32 i; l_float32 x1, y1, x2, y2, x3, y3, x4, y4; l_float32 *b; /* rhs vector of primed coords X'; coeffs returned in *pvc */ l_float32 *a[8]; /* 8x8 matrix A */ PROCNAME("getBilinearXformCoeffs"); if (!ptas) return ERROR_INT("ptas not defined", procName, 1); if (!ptad) return ERROR_INT("ptad not defined", procName, 1); if (!pvc) return ERROR_INT("&vc not defined", procName, 1); if ((b = (l_float32 *) CALLOC(8, sizeof(l_float32))) == NULL) return ERROR_INT("b not made", procName, 1); *pvc = b; ptaGetPt(ptas, 0, &x1, &y1); ptaGetPt(ptas, 1, &x2, &y2); ptaGetPt(ptas, 2, &x3, &y3); ptaGetPt(ptas, 3, &x4, &y4); ptaGetPt(ptad, 0, &b[0], &b[1]); ptaGetPt(ptad, 1, &b[2], &b[3]); ptaGetPt(ptad, 2, &b[4], &b[5]); ptaGetPt(ptad, 3, &b[6], &b[7]); for (i = 0; i < 8; i++) { if ((a[i] = (l_float32 *) CALLOC(8, sizeof(l_float32))) == NULL) return ERROR_INT("a[i] not made", procName, 1); } a[0][0] = x1; a[0][1] = y1; a[0][2] = x1 * y1; a[0][3] = 1.; a[1][4] = x1; a[1][5] = y1; a[1][6] = x1 * y1; a[1][7] = 1.; a[2][0] = x2; a[2][1] = y2; a[2][2] = x2 * y2; a[2][3] = 1.; a[3][4] = x2; a[3][5] = y2; a[3][6] = x2 * y2; a[3][7] = 1.; a[4][0] = x3; a[4][1] = y3; a[4][2] = x3 * y3; a[4][3] = 1.; a[5][4] = x3; a[5][5] = y3; a[5][6] = x3 * y3; a[5][7] = 1.; a[6][0] = x4; a[6][1] = y4; a[6][2] = x4 * y4; a[6][3] = 1.; a[7][4] = x4; a[7][5] = y4; a[7][6] = x4 * y4; a[7][7] = 1.; gaussjordan(a, b, 8); for (i = 0; i < 8; i++) FREE(a[i]); return 0; }
/*! * pixDisplayWriteFormat() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * reduction (-1 to reset/erase; 0 to disable; * otherwise this is a reduction factor) * format (IFF_PNG or IFF_JFIF_JPEG) * Return: 0 if OK; 1 on error * * Notes: * (1) This writes files if reduction > 0. These can be displayed using * pixDisplayMultiple("/tmp/junk_write_display*"); * (2) All previously written files can be erased by calling with * reduction < 0; the value of pixs is ignored. * (3) If reduction > 1 and depth == 1, this does a scale-to-gray * reduction. * (4) This function uses a static internal variable to number * output files written by a single process. Behavior * with a shared library may be unpredictable. * (5) Output file format is as follows: * format == IFF_JFIF_JPEG: * png if d < 8 or d == 16 or if the output pix * has a colormap. Otherwise, output is jpg. * format == IFF_PNG: * png (lossless) on all images. * (6) For 16 bpp, the choice of full dynamic range with log scale * is the best for displaying these images. Alternative outputs are * pix8 = pixMaxDynamicRange(pixt, L_LINEAR_SCALE); * pix8 = pixConvert16To8(pixt, 0); // low order byte * pix8 = pixConvert16To8(pixt, 1); // high order byte */ l_int32 pixDisplayWriteFormat(PIX *pixs, l_int32 reduction, l_int32 format) { char buffer[L_BUF_SIZE]; l_int32 ignore; l_float32 scale; PIX *pixt, *pix8; static l_int32 index = 0; /* caution: not .so or thread safe */ PROCNAME("pixDisplayWriteFormat"); if (reduction == 0) return 0; if (reduction < 0) { index = 0; /* reset; this will cause erasure at next call to write */ return 0; } if (format != IFF_JFIF_JPEG && format != IFF_PNG) return ERROR_INT("invalid format", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (index == 0) { snprintf(buffer, L_BUF_SIZE, "rm -f /tmp/junk_write_display.*.png /tmp/junk_write_display.*.jpg"); ignore = system(buffer); } index++; if (reduction == 1) pixt = pixClone(pixs); else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pixt = pixScaleToGray(pixs, scale); else pixt = pixScale(pixs, scale, scale); } if (pixGetDepth(pixt) == 16) { pix8 = pixMaxDynamicRange(pixt, L_LOG_SCALE); snprintf(buffer, L_BUF_SIZE, "/tmp/junk_write_display.%03d.png", index); pixWrite(buffer, pix8, IFF_PNG); pixDestroy(&pix8); } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt) || format == IFF_PNG) { snprintf(buffer, L_BUF_SIZE, "/tmp/junk_write_display.%03d.png", index); pixWrite(buffer, pixt, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/junk_write_display.%03d.jpg", index); pixWrite(buffer, pixt, format); } pixDestroy(&pixt); return 0; }
/*! * regTestCheckFile() * * Input: rp (regtest parameters) * localname (name of output file from reg test) * Return: 0 if OK, 1 on error (a failure in comparison is not an error) * * Notes: * (1) This function does one of three things, depending on the mode: * * "generate": makes a "golden" file as a copy @localname. * * "compare": compares @localname contents with the golden file * * "display": makes the @localname file but does no comparison * (2) The canonical format of the golden filenames is: * /tmp/golden/<root of main name>_golden.<index>.<ext of localname> * e.g., * /tmp/golden/maze_golden.0.png * It is important to add an extension to the local name, because * the extension is added to the name of the golden file. */ l_int32 regTestCheckFile(L_REGPARAMS *rp, const char *localname) { char *ext; char namebuf[256]; l_int32 ret, same, format; PIX *pix1, *pix2; PROCNAME("regTestCheckFile"); if (!rp) return ERROR_INT("rp not defined", procName, 1); if (!localname) { rp->success = FALSE; return ERROR_INT("local name not defined", procName, 1); } if (rp->mode != L_REG_GENERATE && rp->mode != L_REG_COMPARE && rp->mode != L_REG_DISPLAY) { rp->success = FALSE; return ERROR_INT("invalid mode", procName, 1); } rp->index++; /* If display mode, no generation and no testing */ if (rp->mode == L_REG_DISPLAY) return 0; /* Generate the golden file name; used in 'generate' and 'compare' */ splitPathAtExtension(localname, NULL, &ext); snprintf(namebuf, sizeof(namebuf), "/tmp/golden/%s_golden.%d%s", rp->testname, rp->index, ext); FREE(ext); /* Generate mode. No testing. */ if (rp->mode == L_REG_GENERATE) { /* Save the file as a golden file */ /* fprintf(stderr, "%d: %s\n", rp->index, namebuf); */ ret = fileCopy(localname, namebuf); if (!ret) fprintf(stderr, "Copy: %s to %s\n", localname, namebuf); return ret; } /* Compare mode: test and record on failure. GIF compression * is lossless for images with up to 8 bpp (but not for RGB * because it must generate a 256 color palette). Although * the read/write cycle for GIF is idempotent in the image * pixels for bpp <= 8, it is not idempotent in the actual * file bytes. Tests comparing file bytes before and after * a GIF read/write cycle will fail. So for GIF we uncompress * the two images and compare the actual pixels. From my tests, * PNG, in addition to being lossless, is idempotent in file * bytes on read/write, so comparing the pixels is not necessary. * (It also increases the regression test time by an an average * of about 8%.) JPEG is lossy and not idempotent in the image * pixels, so no tests are constructed that would require it. */ findFileFormat(localname, &format); if (format == IFF_GIF) { same = 0; pix1 = pixRead(localname); pix2 = pixRead(namebuf); pixEqual(pix1, pix2, &same); pixDestroy(&pix1); pixDestroy(&pix2); } else { filesAreIdentical(localname, namebuf, &same); } if (!same) { fprintf(rp->fp, "Failure in %s_reg, index %d: comparing %s with %s\n", rp->testname, rp->index, localname, namebuf); fprintf(stderr, "Failure in %s_reg, index %d: comparing %s with %s\n", rp->testname, rp->index, localname, namebuf); rp->success = FALSE; } return 0; }
main(int argc, char **argv) { l_int32 i, j; l_float32 f; l_uint32 redval, greenval; PIX *pixs, *pixd, *pixt0, *pixt1, *pixt2, *pixt3; static char mainName[] = "locminmax_reg"; if (argc != 1) exit(ERROR_INT("syntax: locminmax_reg", mainName, 1)); pixs = pixCreate(500, 500, 8); for (i = 0; i < 500; i++) { for (j = 0; j < 500; j++) { f = 128.0 + 26.3 * sin(0.0438 * (l_float32)i); f += 33.4 * cos(0.0712 * (l_float32)i); f += 18.6 * sin(0.0561 * (l_float32)j); f += 23.6 * cos(0.0327 * (l_float32)j); pixSetPixel(pixs, j, i, (l_int32)f); } } pixDisplay(pixs, 0, 0); pixWrite("/tmp/junkpattern.png", pixs, IFF_PNG); startTimer(); /* pixSelectedLocalExtrema(pixs, 1, &pixt1, &pixt2); */ pixLocalExtrema(pixs, 0, 0, &pixt1, &pixt2); fprintf(stderr, "Time for extrema: %7.3f\n", stopTimer()); composeRGBPixel(255, 0, 0, &redval); composeRGBPixel(0, 255, 0, &greenval); pixd = pixConvertTo32(pixs); pixPaintThroughMask(pixd, pixt2, 0, 0, greenval); pixPaintThroughMask(pixd, pixt1, 0, 0, redval); pixDisplay(pixd, 510, 0); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixs); pixDestroy(&pixd); pixt0 = pixRead("karen8.jpg"); pixs = pixBlockconv(pixt0, 10, 10); pixDisplay(pixs, 0, 400); pixWrite("/tmp/junkconv.png", pixs, IFF_PNG); startTimer(); /* pixSelectedLocalExtrema(pixs, 1, &pixt1, &pixt2); */ pixLocalExtrema(pixs, 50, 100, &pixt1, &pixt2); fprintf(stderr, "Time for extrema: %7.3f\n", stopTimer()); composeRGBPixel(255, 0, 0, &redval); composeRGBPixel(0, 255, 0, &greenval); pixd = pixConvertTo32(pixs); pixPaintThroughMask(pixd, pixt2, 0, 0, greenval); pixPaintThroughMask(pixd, pixt1, 0, 0, redval); pixDisplay(pixd, 350, 400); pixWrite("/tmp/junkpixd2.png", pixd, IFF_PNG); pixDestroy(&pixt0); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixs); pixDestroy(&pixd); return 0; }
/*! * pixSplitIntoCharacters() * * Input: pixs (1 bpp, contains only deskewed text) * minw (minimum component width for initial filtering; typ. 4) * minh (minimum component height for initial filtering; typ. 4) * &boxa (<optional return> character bounding boxes) * &pixa (<optional return> character images) * &pixdebug (<optional return> showing splittings) * * Return: 0 if OK, 1 on error * * Notes: * (1) This is a simple function that attempts to find split points * based on vertical pixel profiles. * (2) It should be given an image that has an arbitrary number * of text characters. * (3) The returned pixa includes the boxes from which the * (possibly split) components are extracted. */ l_int32 pixSplitIntoCharacters(PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug) { l_int32 ncomp, i, xoff, yoff; BOXA *boxa1, *boxa2, *boxat1, *boxat2, *boxad; BOXAA *baa; PIX *pix, *pix1, *pix2, *pixdb; PIXA *pixa1, *pixadb; PROCNAME("pixSplitIntoCharacters"); if (pboxa) *pboxa = NULL; if (ppixa) *ppixa = NULL; if (ppixdebug) *ppixdebug = NULL; if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); /* Remove the small stuff */ pix1 = pixSelectBySize(pixs, minw, minh, 8, L_SELECT_IF_BOTH, L_SELECT_IF_GT, NULL); /* Small vertical close for consolidation */ pix2 = pixMorphSequence(pix1, "c1.10", 0); pixDestroy(&pix1); /* Get the 8-connected components */ boxa1 = pixConnComp(pix2, &pixa1, 8); pixDestroy(&pix2); boxaDestroy(&boxa1); /* Split the components if obvious */ ncomp = pixaGetCount(pixa1); boxa2 = boxaCreate(ncomp); pixadb = (ppixdebug) ? pixaCreate(ncomp) : NULL; for (i = 0; i < ncomp; i++) { pix = pixaGetPix(pixa1, i, L_CLONE); if (ppixdebug) { boxat1 = pixSplitComponentWithProfile(pix, 10, 7, &pixdb); if (pixdb) pixaAddPix(pixadb, pixdb, L_INSERT); } else { boxat1 = pixSplitComponentWithProfile(pix, 10, 7, NULL); } pixaGetBoxGeometry(pixa1, i, &xoff, &yoff, NULL, NULL); boxat2 = boxaTransform(boxat1, xoff, yoff, 1.0, 1.0); boxaJoin(boxa2, boxat2, 0, -1); pixDestroy(&pix); boxaDestroy(&boxat1); boxaDestroy(&boxat2); } pixaDestroy(&pixa1); /* Generate the debug image */ if (ppixdebug) { if (pixaGetCount(pixadb) > 0) { *ppixdebug = pixaDisplayTiledInRows(pixadb, 32, 1500, 1.0, 0, 20, 1); } pixaDestroy(&pixadb); } /* Do a 2D sort on the bounding boxes, and flatten the result to 1D */ baa = boxaSort2d(boxa2, NULL, 0, 0, 5); boxad = boxaaFlattenToBoxa(baa, NULL, L_CLONE); boxaaDestroy(&baa); boxaDestroy(&boxa2); /* Optionally extract the pieces from the input image */ if (ppixa) *ppixa = pixClipRectangles(pixs, boxad); if (pboxa) *pboxa = boxad; else boxaDestroy(&boxad); return 0; }
main(int argc, char **argv) { l_int32 w, h, d, wpl, count, i, format, xres, yres; FILE *fp; PIX *pix, *pixt1, *pixt2; PIXCMAP *cmap; char *filein; char *fileout = NULL; static char mainName[] = "iotest"; if (argc != 2 && argc != 3) exit(ERROR_INT(" Syntax: iotest filein [fileout]", mainName, 1)); filein = argv[1]; if (argc == 3) fileout = argv[2]; #if 1 if ((pix = pixRead(filein)) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); #else if ((pix = pixReadJpeg(filein, 0, 4, NULL)) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); #endif pixGetDimensions(pix, &w, &h, &d); wpl = pixGetWpl(pix); fprintf(stderr, "w = %d, h = %d, d = %d, wpl = %d\n", w, h, d, wpl); xres = pixGetXRes(pix); yres = pixGetXRes(pix); if (xres != 0 && yres != 0) fprintf(stderr, "xres = %d, yres = %d\n", xres, yres); if (pixGetColormap(pix)) { /* Write and read back the colormap */ pixcmapWriteStream(stderr, pixGetColormap(pix)); fp = lept_fopen("/tmp/junkcmap1", "wb"); pixcmapWriteStream(fp, pixGetColormap(pix)); lept_fclose(fp); fp = lept_fopen("/tmp/junkcmap1", "rb"); cmap = pixcmapReadStream(fp); lept_fclose(fp); fp = lept_fopen("/tmp/junkcmap2", "wb"); pixcmapWriteStream(fp, cmap); lept_fclose(fp); pixcmapDestroy(&cmap); /* Remove and regenerate colormap */ pixt1 = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC); if (pixGetDepth(pixt1) == 8) { fprintf(stderr, "Colormap: represents grayscale image\n"); pixt2 = pixConvertGrayToColormap(pixt1); } else { /* 32 bpp */ fprintf(stderr, "Colormap: represents RGB image\n"); pixt2 = pixConvertRGBToColormap(pixt1, 1); } pixWrite("/tmp/junkpixt2.png", pixt2, IFF_PNG); pixDestroy(&pixt1); pixDestroy(&pixt2); } else { fprintf(stderr, "no colormap\n"); } format = pixGetInputFormat(pix); fprintf(stderr, "Input format extension: %s\n", ImageFileFormatExtensions[format]); if (format == IFF_JFIF_JPEG) fprintf(stderr, "Jpeg comment: %s\n", pixGetText(pix)); if (d == 1) { pixCountPixels(pix, &count, NULL); fprintf(stderr, "pixel ratio ON/OFF = %6.3f\n", (l_float32)count / (l_float32)(pixGetWidth(pix) * pixGetHeight(pix))); } if (argc == 3) { #if 1 d = pixGetDepth(pix); if (d == 16 || d < 8 || pixGetColormap(pix)) pixWrite(fileout, pix, IFF_PNG); else pixWriteJpeg(fileout, pix, 75, 0); #elif 0 pixWrite(fileout, pix, IFF_BMP); #elif 0 pixWrite(fileout, pix, IFF_PNG); #elif 0 pixWrite(fileout, pix, IFF_TIFF); fprintTiffInfo(stderr, fileout); #elif 0 pixWrite(fileout, pix, IFF_TIFF_PACKBITS); fprintTiffInfo(stderr, fileout); #elif 0 pixWrite(fileout, pix, IFF_TIFF_G3); fprintTiffInfo(stderr, fileout); #elif 0 pixWrite(fileout, pix, IFF_TIFF_G4); fprintTiffInfo(stderr, fileout); #elif 0 pixWrite(fileout, pix, IFF_JFIF_JPEG); #elif 0 pixWriteJpeg(fileout, pix, 75, 0); #elif 0 pixWrite(fileout, pix, IFF_PNM); #elif 0 pixWrite(fileout, pix, IFF_PS); #endif } pixDestroy(&pix); #if 0 /* test tiff header reader */ { l_int32 w, h, bps, spp, res, cmap; if (readHeaderTiff(filein, 0, &w, &h, &bps, &spp, &res, &cmap) == 0) fprintf(stderr, "w = %d, h = %d, bps = %d, spp = %d, res = %d, cmap = %d\n", w, h, bps, spp, res, cmap); } #endif return 0; }
/*! * \brief pixMirrorDetectDwa() * * \param[in] pixs 1 bpp, deskewed, English text * \param[out] pconf confidence that text is not LR mirror reversed * \param[in] mincount min number of left + right; use 0 for default * \param[in] debug 1 for debug output; 0 otherwise * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) We assume the text is horizontally oriented, with * ascenders going up. * (2) See notes in pixMirrorDetect(). * </pre> */ l_int32 pixMirrorDetectDwa(PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 debug) { char flipsel1[] = "flipsel1"; char flipsel2[] = "flipsel2"; l_int32 count1, count2, nmax; l_float32 nleft, nright; PIX *pix0, *pix1, *pix2, *pix3; PROCNAME("pixMirrorDetectDwa"); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); *pconf = 0.0; if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); if (mincount == 0) mincount = DEFAULT_MIN_MIRROR_FLIP_COUNT; /* Fill x-height characters but not space between them, sort of. */ pix3 = pixMorphSequenceDwa(pixs, "d1.30", 0); pixXor(pix3, pix3, pixs); pix0 = pixMorphSequenceDwa(pixs, "c15.1", 0); pixXor(pix0, pix0, pixs); pixAnd(pix0, pix0, pix3); pixOr(pix3, pix0, pixs); pixDestroy(&pix0); pix0 = pixAddBorderGeneral(pix3, ADDED_BORDER, ADDED_BORDER, ADDED_BORDER, ADDED_BORDER, 0); pixDestroy(&pix3); /* Filter the right-facing characters. */ pix1 = pixFlipFHMTGen(NULL, pix0, flipsel1); pix3 = pixReduceRankBinaryCascade(pix1, 1, 1, 0, 0); pixCountPixels(pix3, &count1, NULL); pixDestroy(&pix1); pixDestroy(&pix3); /* Filter the left-facing characters. */ pix2 = pixFlipFHMTGen(NULL, pix0, flipsel2); pix3 = pixReduceRankBinaryCascade(pix2, 1, 1, 0, 0); pixCountPixels(pix3, &count2, NULL); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix0); nright = (l_float32)count1; nleft = (l_float32)count2; nmax = L_MAX(count1, count2); if (nmax > mincount) *pconf = 2. * ((nright - nleft) / sqrt(nright + nleft)); if (debug) { fprintf(stderr, "nright = %f, nleft = %f\n", nright, nleft); if (*pconf > DEFAULT_MIN_MIRROR_FLIP_CONF) fprintf(stderr, "Text is not mirror reversed\n"); if (*pconf < -DEFAULT_MIN_MIRROR_FLIP_CONF) fprintf(stderr, "Text is mirror reversed\n"); } return 0; }
/*! * pixGetWordsInTextlines() * * Input: pixs (1 bpp, typ. 300 ppi) * reduction (1 for input res; 2 for 2x reduction of input res) * minwidth, minheight (of saved components; smaller are discarded) * maxwidth, maxheight (of saved components; larger are discarded) * &boxad (<return> word boxes sorted in textline line order) * &pixad (<return> word images sorted in textline line order) * &naindex (<return> index of textline for each word) * Return: 0 if OK, 1 on error * * Notes: * (1) The input should be at a resolution of about 300 ppi. * The word masks and word images can be computed at either * 150 ppi or 300 ppi. For the former, set reduction = 2. * (2) The four size constraints on saved components are all * scaled by @reduction. * (3) The result are word images (and their b.b.), extracted in * textline order, at either full res or 2x reduction, * and with a numa giving the textline index for each word. * (4) The pixa and boxa interfaces should make this type of * application simple to put together. The steps are: * - optionally reduce by 2x * - generate first estimate of word masks * - get b.b. of these, and remove the small and big ones * - extract pixa of the word images, using the b.b. * - sort actual word images in textline order (2d) * - flatten them to a pixa (1d), saving the textline index * for each pix * (5) In an actual application, it may be desirable to pre-filter * the input image to remove large components, to extract * single columns of text, and to deskew them. For example, * to remove both large components and small noisy components * that can interfere with the statistics used to estimate * parameters for segmenting by words, but still retain text lines, * the following image preprocessing can be done: * Pix *pixt = pixMorphSequence(pixs, "c40.1", 0); * Pix *pixf = pixSelectBySize(pixt, 0, 60, 8, * L_SELECT_HEIGHT, L_SELECT_IF_LT, NULL); * pixAnd(pixf, pixf, pixs); // the filtered image * The closing turns text lines into long blobs, but does not * significantly increase their height. But if there are many * small connected components in a dense texture, this is likely * to generate tall components that will be eliminated in pixf. */ l_int32 pixGetWordsInTextlines(PIX *pixs, l_int32 reduction, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad, NUMA **pnai) { l_int32 maxdil; BOXA *boxa1, *boxad; BOXAA *baa; NUMA *nai; NUMAA *naa; PIXA *pixa1, *pixad; PIX *pix1; PIXAA *paa; PROCNAME("pixGetWordsInTextlines"); if (!pboxad || !ppixad || !pnai) return ERROR_INT("&boxad, &pixad, &nai not all defined", procName, 1); *pboxad = NULL; *ppixad = NULL; *pnai = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (reduction != 1 && reduction != 2) return ERROR_INT("reduction not in {1,2}", procName, 1); if (reduction == 1) { pix1 = pixClone(pixs); maxdil = 18; } else { /* reduction == 2 */ pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); maxdil = 9; } /* Get the bounding boxes of the words from the word mask. */ pixWordBoxesByDilation(pix1, maxdil, minwidth, minheight, maxwidth, maxheight, &boxa1, NULL); /* Generate a pixa of the word images */ pixa1 = pixaCreateFromBoxa(pix1, boxa1, NULL); /* mask over each word */ /* Sort the bounding boxes of these words by line. We use the * index mapping to allow identical sorting of the pixa. */ baa = boxaSort2d(boxa1, &naa, -1, -1, 4); paa = pixaSort2dByIndex(pixa1, naa, L_CLONE); /* Flatten the word paa */ pixad = pixaaFlattenToPixa(paa, &nai, L_CLONE); boxad = pixaGetBoxa(pixad, L_COPY); *pnai = nai; *pboxad = boxad; *ppixad = pixad; pixDestroy(&pix1); pixaDestroy(&pixa1); boxaDestroy(&boxa1); boxaaDestroy(&baa); pixaaDestroy(&paa); numaaDestroy(&naa); return 0; }