int main(int argc, char **argv) { l_int32 i, w, h, d, rotflag; PIX *pixs, *pixt, *pixd; l_float32 angle, deg2rad, pops, ang; char *filein, *fileout; static char mainName[] = "rotatetest1"; if (argc != 4) return ERROR_INT(" Syntax: rotatetest1 filein angle fileout", mainName, 1); filein = argv[1]; angle = atof(argv[2]); fileout = argv[3]; deg2rad = 3.1415926535 / 180.; lept_mkdir("lept/rotate"); if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pix not made", mainName, 1); if (pixGetDepth(pixs) == 1) { pixt = pixScaleToGray3(pixs); pixDestroy(&pixs); pixs = pixAddBorderGeneral(pixt, 1, 0, 1, 0, 255); pixDestroy(&pixt); } pixGetDimensions(pixs, &w, &h, &d); fprintf(stderr, "w = %d, h = %d\n", w, h); #if 0 /* repertory of rotation operations to choose from */ pixd = pixRotateAM(pixs, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixRotateAMColor(pixs, deg2rad * angle, 0xffffff00); pixd = pixRotateAMColorFast(pixs, deg2rad * angle, 255); pixd = pixRotateAMCorner(pixs, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixRotateShear(pixs, w /2, h / 2, deg2rad * angle, L_BRING_IN_WHITE); pixd = pixRotate3Shear(pixs, w /2, h / 2, deg2rad * angle, L_BRING_IN_WHITE); pixRotateShearIP(pixs, w / 2, h / 2, deg2rad * angle); pixd = pixs; #endif #if 0 /* timing of shear rotation */ for (i = 0; i < NITERS; i++) { pixd = pixRotateShear(pixs, (i * w) / NITERS, (i * h) / NITERS, deg2rad * angle, L_BRING_IN_WHITE); pixDisplay(pixd, 100 + 20 * i, 100 + 20 * i); pixDestroy(&pixd); } #endif #if 0 /* timing of in-place shear rotation */ for (i = 0; i < NITERS; i++) { pixRotateShearIP(pixs, w/2, h/2, deg2rad * angle, L_BRING_IN_WHITE); /* pixRotateShearCenterIP(pixs, deg2rad * angle, L_BRING_IN_WHITE); */ pixDisplay(pixs, 100 + 20 * i, 100 + 20 * i); } pixd = pixs; if (pixGetDepth(pixd) == 1) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixs); #endif #if 0 /* timing of various rotation operations (choose) */ startTimer(); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < NTIMES; i++) { pixd = pixRotateShearCenter(pixs, deg2rad * angle, L_BRING_IN_WHITE); pixDestroy(&pixd); } pops = (l_float32)(w * h * NTIMES / 1000000.) / stopTimer(); fprintf(stderr, "vers. 1, mpops: %f\n", pops); startTimer(); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < NTIMES; i++) { pixRotateShearIP(pixs, w/2, h/2, deg2rad * angle, L_BRING_IN_WHITE); } pops = (l_float32)(w * h * NTIMES / 1000000.) / stopTimer(); fprintf(stderr, "shear, mpops: %f\n", pops); pixWrite(fileout, pixs, IFF_PNG); for (i = 0; i < NTIMES; i++) { pixRotateShearIP(pixs, w/2, h/2, -deg2rad * angle, L_BRING_IN_WHITE); } pixWrite("/usr/tmp/junkout", pixs, IFF_PNG); #endif #if 0 /* area-mapping rotation operations */ pixd = pixRotateAM(pixs, deg2rad * angle, L_BRING_IN_WHITE); /* pixd = pixRotateAMColorFast(pixs, deg2rad * angle, 255); */ if (pixGetDepth(pixd) == 1) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); #endif #if 0 /* compare the standard area-map color rotation with * the fast area-map color rotation, on a pixel basis */ { PIX *pix1, *pix2; NUMA *nar, *nag, *nab, *naseq; GPLOT *gplot; startTimer(); pix1 = pixRotateAMColor(pixs, 0.12, 0xffffff00); fprintf(stderr, " standard color rotate: %7.2f sec\n", stopTimer()); pixWrite("/tmp/lept/rotate/color1.jpg", pix1, IFF_JFIF_JPEG); startTimer(); pix2 = pixRotateAMColorFast(pixs, 0.12, 0xffffff00); fprintf(stderr, " fast color rotate: %7.2f sec\n", stopTimer()); pixWrite("/tmp/lept/rotate/color2.jpg", pix2, IFF_JFIF_JPEG); pixd = pixAbsDifference(pix1, pix2); pixGetColorHistogram(pixd, 1, &nar, &nag, &nab); naseq = numaMakeSequence(0., 1., 256); gplot = gplotCreate("/tmp/lept/rotate/absdiff", GPLOT_PNG, "Number vs diff", "diff", "number"); gplotAddPlot(gplot, naseq, nar, GPLOT_POINTS, "red"); gplotAddPlot(gplot, naseq, nag, GPLOT_POINTS, "green"); gplotAddPlot(gplot, naseq, nab, GPLOT_POINTS, "blue"); gplotMakeOutput(gplot); l_fileDisplay("/tmp/lept/rotate/absdiff.png", 100, 100, 1.0); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pixd); numaDestroy(&nar); numaDestroy(&nag); numaDestroy(&nab); numaDestroy(&naseq); gplotDestroy(&gplot); } #endif /* Do a succession of 180 7-degree rotations in a cw * direction, and unwind the result with another set in * a ccw direction. Although there is a considerable amount * of distortion after successive rotations, after all * 360 rotations, the resulting image is restored to * its original pristine condition! */ #if 1 rotflag = L_ROTATE_AREA_MAP; /* rotflag = L_ROTATE_SHEAR; */ /* rotflag = L_ROTATE_SAMPLING; */ ang = 7.0 * deg2rad; pixGetDimensions(pixs, &w, &h, NULL); pixd = pixRotate(pixs, ang, rotflag, L_BRING_IN_WHITE, w, h); pixWrite("/tmp/lept/rotate/rot7.png", pixd, IFF_PNG); for (i = 1; i < 180; i++) { pixs = pixd; pixd = pixRotate(pixs, ang, rotflag, L_BRING_IN_WHITE, w, h); if ((i % 30) == 0) pixDisplay(pixd, 600, 0); pixDestroy(&pixs); } pixWrite("/tmp/lept/rotate/spin.png", pixd, IFF_PNG); pixDisplay(pixd, 0, 0); for (i = 0; i < 180; i++) { pixs = pixd; pixd = pixRotate(pixs, -ang, rotflag, L_BRING_IN_WHITE, w, h); if (i && (i % 30) == 0) pixDisplay(pixd, 600, 500); pixDestroy(&pixs); } pixWrite("/tmp/lept/rotate/unspin.png", pixd, IFF_PNG); pixDisplay(pixd, 0, 500); pixDestroy(&pixd); #endif return 0; }
/*! * pixFMorphopGen_3() * * Input: pixd (usual 3 choices: null, == pixs, != pixs) * pixs (1 bpp) * operation (L_MORPH_DILATE, L_MORPH_ERODE, * L_MORPH_OPEN, L_MORPH_CLOSE) * sel name * Return: pixd * * Notes: * (1) This is a dwa operation, and the Sels must be limited in * size to not more than 31 pixels about the origin. * (2) A border of appropriate size (32 pixels, or 64 pixels * for safe closing with asymmetric b.c.) must be added before * this function is called. * (3) This handles all required setting of the border pixels * before erosion and dilation. * (4) The closing operation is safe; no pixels can be removed * near the boundary. */ PIX * pixFMorphopGen_3(PIX *pixd, PIX *pixs, l_int32 operation, char *selname) { l_int32 i, index, found, w, h, wpls, wpld, bordercolor, erodeop, borderop; l_uint32 *datad, *datas, *datat; PIX *pixt; PROCNAME("pixFMorphopGen_3"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd); /* Get boundary colors to use */ bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1); if (bordercolor == 1) erodeop = PIX_SET; else erodeop = PIX_CLR; found = FALSE; for (i = 0; i < NUM_SELS_GENERATED; i++) { if (strcmp(selname, SEL_NAMES[i]) == 0) { found = TRUE; index = 2 * i; break; } } if (found == FALSE) return (PIX *)ERROR_PTR("sel index not found", procName, pixd); if (!pixd) { if ((pixd = pixCreateTemplate(pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } else /* for in-place or pre-allocated */ pixResizeImageData(pixd, pixs); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); /* The images must be surrounded, in advance, with a border of * size 32 pixels (or 64, for closing), that we'll read from. * Fabricate a "proper" image as the subimage within the 32 * pixel border, having the following parameters: */ w = pixGetWidth(pixs) - 64; h = pixGetHeight(pixs) - 64; datas = pixGetData(pixs) + 32 * wpls + 1; datad = pixGetData(pixd) + 32 * wpld + 1; if (operation == L_MORPH_DILATE || operation == L_MORPH_ERODE) { borderop = PIX_CLR; if (operation == L_MORPH_ERODE) { borderop = erodeop; index++; } if (pixd == pixs) { /* in-place; generate a temp image */ if ((pixt = pixCopy(NULL, pixs)) == NULL) return (PIX *)ERROR_PTR("pixt not made", procName, pixd); datat = pixGetData(pixt) + 32 * wpls + 1; pixSetOrClearBorder(pixt, 32, 32, 32, 32, borderop); fmorphopgen_low_3(datad, w, h, wpld, datat, wpls, index); pixDestroy(&pixt); } else { /* not in-place */ pixSetOrClearBorder(pixs, 32, 32, 32, 32, borderop); fmorphopgen_low_3(datad, w, h, wpld, datas, wpls, index); } } else { /* opening or closing; generate a temp image */ if ((pixt = pixCreateTemplate(pixs)) == NULL) return (PIX *)ERROR_PTR("pixt not made", procName, pixd); datat = pixGetData(pixt) + 32 * wpls + 1; if (operation == L_MORPH_OPEN) { pixSetOrClearBorder(pixs, 32, 32, 32, 32, erodeop); fmorphopgen_low_3(datat, w, h, wpls, datas, wpls, index+1); pixSetOrClearBorder(pixt, 32, 32, 32, 32, PIX_CLR); fmorphopgen_low_3(datad, w, h, wpld, datat, wpls, index); } else { /* closing */ pixSetOrClearBorder(pixs, 32, 32, 32, 32, PIX_CLR); fmorphopgen_low_3(datat, w, h, wpls, datas, wpls, index); pixSetOrClearBorder(pixt, 32, 32, 32, 32, erodeop); fmorphopgen_low_3(datad, w, h, wpld, datat, wpls, index+1); } pixDestroy(&pixt); } return pixd; }
/*! * 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; }
main(int argc, char **argv) { char *text; l_int32 w, h, d, wpl, count, npages, color, format; FILE *fp; PIX *pix; PIXCMAP *cmap; char *filein; static char mainName[] = "fileinfo"; if (argc != 2) exit(ERROR_INT(" Syntax: fileinfo filein", mainName, 1)); filein = argv[1]; l_pngSetStrip16To8(0); /* to preserve 16 bpp if format is png */ if ((pix = pixRead(filein)) == NULL) exit(ERROR_INT("image not returned from file", mainName, 1)); format = pixGetInputFormat(pix); fprintf(stderr, "Input image format type: %s\n", ImageFileFormatExtensions[format]); pixGetDimensions(pix, &w, &h, &d); wpl = pixGetWpl(pix); fprintf(stderr, "w = %d, h = %d, d = %d, wpl = %d\n", w, h, d, wpl); fprintf(stderr, "xres = %d, yres = %d\n", pixGetXRes(pix), pixGetYRes(pix)); text = pixGetText(pix); if (text) /* not null */ fprintf(stderr, "Text: %s\n", text); cmap = pixGetColormap(pix); if (cmap) { pixcmapHasColor(cmap, &color); if (color) fprintf(stderr, "Colormap exists and has color values:"); else fprintf(stderr, "Colormap exists and has only gray values:"); pixcmapWriteStream(stderr, pixGetColormap(pix)); } else fprintf(stderr, "Colormap does not exist.\n"); if (format == IFF_TIFF || format == IFF_TIFF_G4 || format == IFF_TIFF_G3 || format == IFF_TIFF_PACKBITS) { fprintf(stderr, "Tiff header information:\n"); fp = fopen(filein, "r"); tiffGetCount(fp, &npages); fclose(fp); if (npages == 1) fprintf(stderr, "One page in file\n", npages); else fprintf(stderr, "%d pages in file\n", npages); fprintTiffInfo(stderr, filein); } 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))); } pixDestroy(&pix); exit(0); }
bool MakeIndividualGlyphs(Pix* pix, const vector<BoxChar*>& vbox, const int input_tiff_page) { // If checks fail, return false without exiting text2image if (!pix) { tprintf("ERROR: MakeIndividualGlyphs(): Input Pix* is nullptr\n"); return false; } else if (FLAGS_glyph_resized_size <= 0) { tprintf("ERROR: --glyph_resized_size must be positive\n"); return false; } else if (FLAGS_glyph_num_border_pixels_to_pad < 0) { tprintf("ERROR: --glyph_num_border_pixels_to_pad must be 0 or positive\n"); return false; } const int n_boxes = vbox.size(); int n_boxes_saved = 0; int current_tiff_page = 0; int y_previous = 0; static int glyph_count = 0; for (int i = 0; i < n_boxes; i++) { // Get one bounding box Box* b = vbox[i]->mutable_box(); if (!b) continue; const int x = b->x; const int y = b->y; const int w = b->w; const int h = b->h; // Check present tiff page (for multipage tiff) if (y < y_previous-pixGetHeight(pix)/10) { tprintf("ERROR: Wrap-around encountered, at i=%d\n", i); current_tiff_page++; } if (current_tiff_page < input_tiff_page) continue; else if (current_tiff_page > input_tiff_page) break; // Check box validity if (x < 0 || y < 0 || (x+w-1) >= pixGetWidth(pix) || (y+h-1) >= pixGetHeight(pix)) { tprintf("ERROR: MakeIndividualGlyphs(): Index out of range, at i=%d" " (x=%d, y=%d, w=%d, h=%d\n)", i, x, y, w, h); continue; } else if (w < FLAGS_glyph_num_border_pixels_to_pad && h < FLAGS_glyph_num_border_pixels_to_pad) { tprintf("ERROR: Input image too small to be a character, at i=%d\n", i); continue; } // Crop the boxed character Pix* pix_glyph = pixClipRectangle(pix, b, nullptr); if (!pix_glyph) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to clip, at i=%d\n", i); continue; } // Resize to square Pix* pix_glyph_sq = pixScaleToSize(pix_glyph, FLAGS_glyph_resized_size, FLAGS_glyph_resized_size); if (!pix_glyph_sq) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to resize, at i=%d\n", i); continue; } // Zero-pad Pix* pix_glyph_sq_pad = pixAddBorder(pix_glyph_sq, FLAGS_glyph_num_border_pixels_to_pad, 0); if (!pix_glyph_sq_pad) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to zero-pad, at i=%d\n", i); continue; } // Write out Pix* pix_glyph_sq_pad_8 = pixConvertTo8(pix_glyph_sq_pad, false); char filename[1024]; snprintf(filename, 1024, "%s_%d.jpg", FLAGS_outputbase.c_str(), glyph_count++); if (pixWriteJpeg(filename, pix_glyph_sq_pad_8, 100, 0)) { tprintf("ERROR: MakeIndividualGlyphs(): Failed to write JPEG to %s," " at i=%d\n", filename, i); continue; } pixDestroy(&pix_glyph); pixDestroy(&pix_glyph_sq); pixDestroy(&pix_glyph_sq_pad); pixDestroy(&pix_glyph_sq_pad_8); n_boxes_saved++; y_previous = y; } if (n_boxes_saved == 0) { return false; } else { tprintf("Total number of characters saved = %d\n", n_boxes_saved); return true; } }
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); }
bool TessPDFRenderer::AddImageHandler(TessBaseAPI* api) { size_t n; char buf[kBasicBufSize]; char buf2[kBasicBufSize]; Pix *pix = api->GetInputImage(); char *filename = (char *)api->GetInputName(); int ppi = api->GetSourceYResolution(); if (!pix || ppi <= 0) return false; double width = pixGetWidth(pix) * 72.0 / ppi; double height = pixGetHeight(pix) * 72.0 / ppi; snprintf(buf2, sizeof(buf2), "/XObject << /Im1 %ld 0 R >>\n", obj_ + 2); const char *xobject = (textonly_) ? "" : buf2; // PAGE n = snprintf(buf, sizeof(buf), "%ld 0 obj\n" "<<\n" " /Type /Page\n" " /Parent %ld 0 R\n" " /MediaBox [0 0 %.2f %.2f]\n" " /Contents %ld 0 R\n" " /Resources\n" " <<\n" " %s" " /ProcSet [ /PDF /Text /ImageB /ImageI /ImageC ]\n" " /Font << /f-0-0 %ld 0 R >>\n" " >>\n" ">>\n" "endobj\n", obj_, 2L, // Pages object width, height, obj_ + 1, // Contents object xobject, // Image object 3L); // Type0 Font if (n >= sizeof(buf)) return false; pages_.push_back(obj_); AppendPDFObject(buf); // CONTENTS const std::unique_ptr<char[]> pdftext(GetPDFTextObjects(api, width, height)); const size_t pdftext_len = strlen(pdftext.get()); size_t len; unsigned char *comp_pdftext = zlibCompress( reinterpret_cast<unsigned char *>(pdftext.get()), pdftext_len, &len); long comp_pdftext_len = len; n = snprintf(buf, sizeof(buf), "%ld 0 obj\n" "<<\n" " /Length %ld /Filter /FlateDecode\n" ">>\n" "stream\n", obj_, comp_pdftext_len); if (n >= sizeof(buf)) { lept_free(comp_pdftext); return false; } AppendString(buf); long objsize = strlen(buf); AppendData(reinterpret_cast<char *>(comp_pdftext), comp_pdftext_len); objsize += comp_pdftext_len; lept_free(comp_pdftext); const char *b2 = "endstream\n" "endobj\n"; AppendString(b2); objsize += strlen(b2); AppendPDFObjectDIY(objsize); if (!textonly_) { char *pdf_object = nullptr; if (!imageToPDFObj(pix, filename, obj_, &pdf_object, &objsize)) { return false; } AppendData(pdf_object, objsize); AppendPDFObjectDIY(objsize); delete[] pdf_object; } return true; }
/*! * \brief pixGenerateSelWithRuns() * * \param[in] pixs 1 bpp, typically small, to be used as a pattern * \param[in] nhlines number of hor lines along which elements are found * \param[in] nvlines number of vert lines along which elements are found * \param[in] distance min distance from boundary pixel; use 0 for default * \param[in] minlength min runlength to set hit or miss; use 0 for default * \param[in] toppix number of extra pixels of bg added above * \param[in] botpix number of extra pixels of bg added below * \param[in] leftpix number of extra pixels of bg added to left * \param[in] rightpix number of extra pixels of bg added to right * \param[out] ppixe [optional] input pix expanded by extra pixels * \return sel hit-miss for input pattern, or NULL on error * * <pre> * Notes: * (1) The horizontal and vertical lines along which elements are * selected are roughly equally spaced. The actual locations of * the hits and misses are the centers of respective run-lengths. * (2) No elements are selected that are less than 'distance' pixels away * from a boundary pixel of the same color. This makes the * match much more robust to edge noise. Valid inputs of * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or * greater than 4, we reset it to the default value. * (3) The 4 numbers for adding rectangles of pixels outside the fg * can be use if the pattern is expected to be surrounded by bg * (white) pixels. On the other hand, if the pattern may be near * other fg (black) components on some sides, use 0 for those sides. * (4) The pixels added to a side allow you to have miss elements there. * There is a constraint between distance, minlength, and * the added pixels for this to work. We illustrate using the * default values. If you add 5 pixels to the top, and use a * distance of 1, then you end up with a vertical run of at least * 4 bg pixels along the top edge of the image. If you use a * minimum runlength of 3, each vertical line will always find * a miss near the center of its run. However, if you use a * minimum runlength of 5, you will not get a miss on every vertical * line. As another example, if you have 7 added pixels and a * distance of 2, you can use a runlength up to 5 to guarantee * that the miss element is recorded. We give a warning if the * contraint does not guarantee a miss element outside the * image proper. * (5) The input pix, as extended by the extra pixels on selected sides, * can optionally be returned. For debugging, call * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed * on the generating bitmap. * </pre> */ SEL * pixGenerateSelWithRuns(PIX *pixs, l_int32 nhlines, l_int32 nvlines, l_int32 distance, l_int32 minlength, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, xval, yval, i, j, nh, nm; l_float32 delh, delw; NUMA *nah, *nam; PIX *pixt1, *pixt2, *pixfg, *pixbg; PTA *ptah, *ptam; SEL *seld, *sel; PROCNAME("pixGenerateSelWithRuns"); if (ppixe) *ppixe = NULL; if (!pixs) return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (nhlines < 1 && nvlines < 1) return (SEL *)ERROR_PTR("nvlines and nhlines both < 1", procName, NULL); if (distance <= 0) distance = DEFAULT_DISTANCE_TO_BOUNDARY; if (minlength <= 0) minlength = DEFAULT_MIN_RUNLENGTH; if (distance > MAX_DISTANCE_TO_BOUNDARY) { L_WARNING("distance too large; setting to max value\n", procName); distance = MAX_DISTANCE_TO_BOUNDARY; } /* Locate the foreground */ pixClipToForeground(pixs, &pixt1, NULL); if (!pixt1) return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL); ws = pixGetWidth(pixt1); hs = pixGetHeight(pixt1); w = ws; h = hs; /* Crop out a region including the foreground, and add pixels * on sides depending on the side flags */ if (toppix || botpix || leftpix || rightpix) { x = y = 0; if (toppix) { h += toppix; y = toppix; if (toppix < distance + minlength) L_WARNING("no miss elements in added top pixels\n", procName); } if (botpix) { h += botpix; if (botpix < distance + minlength) L_WARNING("no miss elements in added bot pixels\n", procName); } if (leftpix) { w += leftpix; x = leftpix; if (leftpix < distance + minlength) L_WARNING("no miss elements in added left pixels\n", procName); } if (rightpix) { w += rightpix; if (rightpix < distance + minlength) L_WARNING("no miss elements in added right pixels\n", procName); } pixt2 = pixCreate(w, h, 1); pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0); } else { pixt2 = pixClone(pixt1); } if (ppixe) *ppixe = pixClone(pixt2); pixDestroy(&pixt1); /* Identify fg and bg pixels that are at least 'distance' pixels * away from the boundary pixels in their set */ seld = selCreateBrick(2 * distance + 1, 2 * distance + 1, distance, distance, SEL_HIT); pixfg = pixErode(NULL, pixt2, seld); pixbg = pixDilate(NULL, pixt2, seld); pixInvert(pixbg, pixbg); selDestroy(&seld); pixDestroy(&pixt2); /* Accumulate hit and miss points */ ptah = ptaCreate(0); ptam = ptaCreate(0); if (nhlines >= 1) { delh = (l_float32)h / (l_float32)(nhlines + 1); for (i = 0, y = 0; i < nhlines; i++) { y += (l_int32)(delh + 0.5); nah = pixGetRunCentersOnLine(pixfg, -1, y, minlength); nam = pixGetRunCentersOnLine(pixbg, -1, y, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &xval); ptaAddPt(ptah, xval, y); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &xval); ptaAddPt(ptam, xval, y); } numaDestroy(&nah); numaDestroy(&nam); } } if (nvlines >= 1) { delw = (l_float32)w / (l_float32)(nvlines + 1); for (i = 0, x = 0; i < nvlines; i++) { x += (l_int32)(delw + 0.5); nah = pixGetRunCentersOnLine(pixfg, x, -1, minlength); nam = pixGetRunCentersOnLine(pixbg, x, -1, minlength); nh = numaGetCount(nah); nm = numaGetCount(nam); for (j = 0; j < nh; j++) { numaGetIValue(nah, j, &yval); ptaAddPt(ptah, x, yval); } for (j = 0; j < nm; j++) { numaGetIValue(nam, j, &yval); ptaAddPt(ptam, x, yval); } numaDestroy(&nah); numaDestroy(&nam); } } /* Make the Sel with those points */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); nh = ptaGetCount(ptah); for (i = 0; i < nh; i++) { ptaGetIPt(ptah, i, &x, &y); selSetElement(sel, y, x, SEL_HIT); } nm = ptaGetCount(ptam); for (i = 0; i < nm; i++) { ptaGetIPt(ptam, i, &x, &y); selSetElement(sel, y, x, SEL_MISS); } pixDestroy(&pixfg); pixDestroy(&pixbg); ptaDestroy(&ptah); ptaDestroy(&ptam); return sel; }
/*! * \brief pixGenerateSelRandom() * * \param[in] pixs 1 bpp, typically small, to be used as a pattern * \param[in] hitfract fraction of allowable fg pixels that are hits * \param[in] missfract fraction of allowable bg pixels that are misses * \param[in] distance min distance from boundary pixel; use 0 for default * \param[in] toppix number of extra pixels of bg added above * \param[in] botpix number of extra pixels of bg added below * \param[in] leftpix number of extra pixels of bg added to left * \param[in] rightpix number of extra pixels of bg added to right * \param[out] ppixe [optional] input pix expanded by extra pixels * \return sel hit-miss for input pattern, or NULL on error * * <pre> * Notes: * (1) Either of hitfract and missfract can be zero. If both are zero, * the sel would be empty, and NULL is returned. * (2) No elements are selected that are less than 'distance' pixels away * from a boundary pixel of the same color. This makes the * match much more robust to edge noise. Valid inputs of * 'distance' are 0, 1, 2, 3 and 4. If distance is either 0 or * greater than 4, we reset it to the default value. * (3) The 4 numbers for adding rectangles of pixels outside the fg * can be use if the pattern is expected to be surrounded by bg * (white) pixels. On the other hand, if the pattern may be near * other fg (black) components on some sides, use 0 for those sides. * (4) The input pix, as extended by the extra pixels on selected sides, * can optionally be returned. For debugging, call * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed * on the generating bitmap. * </pre> */ SEL * pixGenerateSelRandom(PIX *pixs, l_float32 hitfract, l_float32 missfract, l_int32 distance, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, i, j, thresh; l_uint32 val; PIX *pixt1, *pixt2, *pixfg, *pixbg; SEL *seld, *sel; PROCNAME("pixGenerateSelRandom"); if (ppixe) *ppixe = NULL; if (!pixs) return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (hitfract <= 0.0 && missfract <= 0.0) return (SEL *)ERROR_PTR("no hits or misses", procName, NULL); if (hitfract > 1.0 || missfract > 1.0) return (SEL *)ERROR_PTR("fraction can't be > 1.0", procName, NULL); if (distance <= 0) distance = DEFAULT_DISTANCE_TO_BOUNDARY; if (distance > MAX_DISTANCE_TO_BOUNDARY) { L_WARNING("distance too large; setting to max value\n", procName); distance = MAX_DISTANCE_TO_BOUNDARY; } /* Locate the foreground */ pixClipToForeground(pixs, &pixt1, NULL); if (!pixt1) return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL); ws = pixGetWidth(pixt1); hs = pixGetHeight(pixt1); w = ws; h = hs; /* Crop out a region including the foreground, and add pixels * on sides depending on the side flags */ if (toppix || botpix || leftpix || rightpix) { x = y = 0; if (toppix) { h += toppix; y = toppix; } if (botpix) h += botpix; if (leftpix) { w += leftpix; x = leftpix; } if (rightpix) w += rightpix; pixt2 = pixCreate(w, h, 1); pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0); } else { pixt2 = pixClone(pixt1); } if (ppixe) *ppixe = pixClone(pixt2); pixDestroy(&pixt1); /* Identify fg and bg pixels that are at least 'distance' pixels * away from the boundary pixels in their set */ seld = selCreateBrick(2 * distance + 1, 2 * distance + 1, distance, distance, SEL_HIT); pixfg = pixErode(NULL, pixt2, seld); pixbg = pixDilate(NULL, pixt2, seld); pixInvert(pixbg, pixbg); selDestroy(&seld); pixDestroy(&pixt2); /* Generate the sel from a random selection of these points */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); if (hitfract > 0.0) { thresh = (l_int32)(hitfract * (l_float64)RAND_MAX); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixfg, j, i, &val); if (val) { if (rand() < thresh) selSetElement(sel, i, j, SEL_HIT); } } } } if (missfract > 0.0) { thresh = (l_int32)(missfract * (l_float64)RAND_MAX); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixbg, j, i, &val); if (val) { if (rand() < thresh) selSetElement(sel, i, j, SEL_MISS); } } } } pixDestroy(&pixfg); pixDestroy(&pixbg); return sel; }
/*! * pixFindSkewSweepAndSearchScorePivot() * * Input: pixs (1 bpp) * &angle (<return> angle required to deskew; in degrees) * &conf (<return> confidence given by ratio of max/min score) * &endscore (<optional return> max score; use NULL to ignore) * redsweep (sweep reduction factor = 1, 2, 4 or 8) * redsearch (binary search reduction factor = 1, 2, 4 or 8; * and must not exceed redsweep) * sweepcenter (angle about which sweep is performed; in degrees) * sweeprange (half the full range, taken about sweepcenter; * in degrees) * sweepdelta (angle increment of sweep; in degrees) * minbsdelta (min binary search increment angle; in degrees) * pivot (L_SHEAR_ABOUT_CORNER, L_SHEAR_ABOUT_CENTER) * Return: 0 if OK, 1 on error or if angle measurment not valid * * Notes: * (1) See notes in pixFindSkewSweepAndSearchScore(). * (2) This allows choice of shear pivoting from either the UL corner * or the center. For small angles, the ability to discriminate * angles is better with shearing from the UL corner. However, * for large angles (say, greater than 20 degrees), it is better * to shear about the center because a shear from the UL corner * loses too much of the image. */ l_int32 pixFindSkewSweepAndSearchScorePivot(PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_float32 *pendscore, l_int32 redsweep, l_int32 redsearch, l_float32 sweepcenter, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_int32 pivot) { l_int32 ret, bzero, i, nangles, n, ratio, maxindex, minloc; l_int32 width, height; l_float32 deg2rad, theta, delta; l_float32 sum, maxscore, maxangle; l_float32 centerangle, leftcenterangle, rightcenterangle; l_float32 lefttemp, righttemp; l_float32 bsearchscore[5]; l_float32 minscore, minthresh; l_float32 rangeleft; NUMA *natheta, *nascore; PIX *pixsw, *pixsch, *pixt1, *pixt2; PROCNAME("pixFindSkewSweepAndSearchScorePivot"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 1); if (!pangle) return ERROR_INT("&angle not defined", procName, 1); if (!pconf) return ERROR_INT("&conf not defined", procName, 1); if (redsweep != 1 && redsweep != 2 && redsweep != 4 && redsweep != 8) return ERROR_INT("redsweep must be in {1,2,4,8}", procName, 1); if (redsearch != 1 && redsearch != 2 && redsearch != 4 && redsearch != 8) return ERROR_INT("redsearch must be in {1,2,4,8}", procName, 1); if (redsearch > redsweep) return ERROR_INT("redsearch must not exceed redsweep", procName, 1); if (pivot != L_SHEAR_ABOUT_CORNER && pivot != L_SHEAR_ABOUT_CENTER) return ERROR_INT("invalid pivot", procName, 1); *pangle = 0.0; *pconf = 0.0; deg2rad = 3.1415926535 / 180.; ret = 0; /* Generate reduced image for binary search, if requested */ if (redsearch == 1) pixsch = pixClone(pixs); else if (redsearch == 2) pixsch = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0); else if (redsearch == 4) pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0); else /* redsearch == 8 */ pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0); pixZero(pixsch, &bzero); if (bzero) { pixDestroy(&pixsch); return 1; } /* Generate reduced image for sweep, if requested */ ratio = redsweep / redsearch; if (ratio == 1) { pixsw = pixClone(pixsch); } else { /* ratio > 1 */ if (ratio == 2) pixsw = pixReduceRankBinaryCascade(pixsch, 1, 0, 0, 0); else if (ratio == 4) pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 0, 0); else /* ratio == 8 */ pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 2, 0); } pixt1 = pixCreateTemplate(pixsw); if (ratio == 1) pixt2 = pixClone(pixt1); else pixt2 = pixCreateTemplate(pixsch); nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1); natheta = numaCreate(nangles); nascore = numaCreate(nangles); if (!pixsch || !pixsw) { ret = ERROR_INT("pixsch and pixsw not both made", procName, 1); goto cleanup; } if (!pixt1 || !pixt2) { ret = ERROR_INT("pixt1 and pixt2 not both made", procName, 1); goto cleanup; } if (!natheta || !nascore) { ret = ERROR_INT("natheta and nascore not both made", procName, 1); goto cleanup; } /* Do sweep */ rangeleft = sweepcenter - sweeprange; for (i = 0; i < nangles; i++) { theta = rangeleft + i * sweepdelta; /* degrees */ /* Shear pix and put the result in pixt1 */ if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE); else pixVShearCenter(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE); /* Get score */ pixFindDifferentialSquareSum(pixt1, &sum); #if DEBUG_PRINT_SCORES L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum); #endif /* DEBUG_PRINT_SCORES */ /* Save the result in the output arrays */ numaAddNumber(nascore, sum); numaAddNumber(natheta, theta); } /* Find the largest of the set (maxscore at maxangle) */ numaGetMax(nascore, &maxscore, &maxindex); numaGetFValue(natheta, maxindex, &maxangle); #if DEBUG_PRINT_SWEEP L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName, maxangle, maxscore); #endif /* DEBUG_PRINT_SWEEP */ #if DEBUG_PLOT_SCORES /* Plot the sweep result -- the scores versus rotation angle -- * using gnuplot with GPLOT_LINES (lines connecting data points). */ {GPLOT *gplot; gplot = gplotCreate("sweep_output", GPLOT_PNG, "Sweep. Variance of difference of ON pixels vs. angle", "angle (deg)", "score"); gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1"); gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } #endif /* DEBUG_PLOT_SCORES */ /* Check if the max is at the end of the sweep. */ n = numaGetCount(natheta); if (maxindex == 0 || maxindex == n - 1) { L_WARNING("max found at sweep edge\n", procName); goto cleanup; } /* Empty the numas for re-use */ numaEmpty(nascore); numaEmpty(natheta); /* Do binary search to find skew angle. * First, set up initial three points. */ centerangle = maxangle; if (pivot == L_SHEAR_ABOUT_CORNER) { pixVShearCorner(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]); pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle - sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]); pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle + sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]); } else { pixVShearCenter(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]); pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle - sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]); pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle + sweepdelta), L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]); } numaAddNumber(nascore, bsearchscore[2]); numaAddNumber(natheta, centerangle); numaAddNumber(nascore, bsearchscore[0]); numaAddNumber(natheta, centerangle - sweepdelta); numaAddNumber(nascore, bsearchscore[4]); numaAddNumber(natheta, centerangle + sweepdelta); /* Start the search */ delta = 0.5 * sweepdelta; while (delta >= minbsdelta) { /* Get the left intermediate score */ leftcenterangle = centerangle - delta; if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt2, pixsch, deg2rad * leftcenterangle, L_BRING_IN_WHITE); else pixVShearCenter(pixt2, pixsch, deg2rad * leftcenterangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[1]); numaAddNumber(nascore, bsearchscore[1]); numaAddNumber(natheta, leftcenterangle); /* Get the right intermediate score */ rightcenterangle = centerangle + delta; if (pivot == L_SHEAR_ABOUT_CORNER) pixVShearCorner(pixt2, pixsch, deg2rad * rightcenterangle, L_BRING_IN_WHITE); else pixVShearCenter(pixt2, pixsch, deg2rad * rightcenterangle, L_BRING_IN_WHITE); pixFindDifferentialSquareSum(pixt2, &bsearchscore[3]); numaAddNumber(nascore, bsearchscore[3]); numaAddNumber(natheta, rightcenterangle); /* Find the maximum of the five scores and its location. * Note that the maximum must be in the center * three values, not in the end two. */ maxscore = bsearchscore[1]; maxindex = 1; for (i = 2; i < 4; i++) { if (bsearchscore[i] > maxscore) { maxscore = bsearchscore[i]; maxindex = i; } } /* Set up score array to interpolate for the next iteration */ lefttemp = bsearchscore[maxindex - 1]; righttemp = bsearchscore[maxindex + 1]; bsearchscore[2] = maxscore; bsearchscore[0] = lefttemp; bsearchscore[4] = righttemp; /* Get new center angle and delta for next iteration */ centerangle = centerangle + delta * (maxindex - 2); delta = 0.5 * delta; } *pangle = centerangle; #if DEBUG_PRINT_SCORES L_INFO(" Binary search score = %7.3f\n", procName, bsearchscore[2]); #endif /* DEBUG_PRINT_SCORES */ if (pendscore) /* save if requested */ *pendscore = bsearchscore[2]; /* Return the ratio of Max score over Min score * as a confidence value. Don't trust if the Min score * is too small, which can happen if the image is all black * with only a few white pixels interspersed. In that case, * we get a contribution from the top and bottom edges when * vertically sheared, but this contribution becomes zero when * the shear angle is zero. For zero shear angle, the only * contribution will be from the white pixels. We expect that * the signal goes as the product of the (height * width^2), * so we compute a (hopefully) normalized minimum threshold as * a function of these dimensions. */ numaGetMin(nascore, &minscore, &minloc); width = pixGetWidth(pixsch); height = pixGetHeight(pixsch); minthresh = MINSCORE_THRESHOLD_CONSTANT * width * width * height; #if DEBUG_THRESHOLD L_INFO(" minthresh = %10.2f, minscore = %10.2f\n", procName, minthresh, minscore); L_INFO(" maxscore = %10.2f\n", procName, maxscore); #endif /* DEBUG_THRESHOLD */ if (minscore > minthresh) *pconf = maxscore / minscore; else *pconf = 0.0; /* Don't trust it if too close to the edge of the sweep * range or if maxscore is small */ if ((centerangle > rangeleft + 2 * sweeprange - sweepdelta) || (centerangle < rangeleft + sweepdelta) || (maxscore < MIN_VALID_MAXSCORE)) *pconf = 0.0; #if DEBUG_PRINT_BINARY fprintf(stderr, "Binary search: angle = %7.3f, score ratio = %6.2f\n", *pangle, *pconf); fprintf(stderr, " max score = %8.0f\n", maxscore); #endif /* DEBUG_PRINT_BINARY */ #if DEBUG_PLOT_SCORES /* Plot the result -- the scores versus rotation angle -- * using gnuplot with GPLOT_POINTS. Because the data * points are not ordered by theta (increasing or decreasing), * using GPLOT_LINES would be confusing! */ {GPLOT *gplot; gplot = gplotCreate("search_output", GPLOT_PNG, "Binary search. Variance of difference of ON pixels vs. angle", "angle (deg)", "score"); gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot1"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } #endif /* DEBUG_PLOT_SCORES */ cleanup: pixDestroy(&pixsw); pixDestroy(&pixsch); pixDestroy(&pixt1); pixDestroy(&pixt2); numaDestroy(&nascore); numaDestroy(&natheta); return ret; }
/*! * pixWriteStreamPng() * * Input: stream * pix * gamma (use 0.0 if gamma is not defined) * Return: 0 if OK; 1 on error * * Notes: * (1) If called from pixWriteStream(), the stream is positioned * at the beginning of the file. * (2) To do sequential writes of png format images to a stream, * use pixWriteStreamPng() directly. * (3) gamma is an optional png chunk. If no gamma value is to be * placed into the file, use gamma = 0.0. Otherwise, if * gamma > 0.0, its value is written into the header. * (4) The use of gamma in png is highly problematic. For an illuminating * discussion, see: http://hsivonen.iki.fi/png-gamma/ * (5) What is the effect/meaning of gamma in the png file? This * gamma, which we can call the 'source' gamma, is the * inverse of the gamma that was used in enhance.c to brighten * or darken images. The 'source' gamma is supposed to indicate * the intensity mapping that was done at the time the * image was captured. Display programs typically apply a * 'display' gamma of 2.2 to the output, which is intended * to linearize the intensity based on the response of * thermionic tubes (CRTs). Flat panel LCDs have typically * been designed to give a similar response as CRTs (call it * "backward compatibility"). The 'display' gamma is * in some sense the inverse of the 'source' gamma. * jpeg encoders attached to scanners and cameras will lighten * the pixels, applying a gamma corresponding to approximately * a square-root relation of output vs input: * output = input^(gamma) * where gamma is often set near 0.4545 (1/gamma is 2.2). * This is stored in the image file. Then if the display * program reads the gamma, it will apply a display gamma, * typically about 2.2; the product is 1.0, and the * display program produces a linear output. This works because * the dark colors were appropriately boosted by the scanner, * as described by the 'source' gamma, so they should not * be further boosted by the display program. * (6) As an example, with xv and display, if no gamma is stored, * the program acts as if gamma were 0.4545, multiplies this by 2.2, * and does a linear rendering. Taking this as a baseline * brightness, if the stored gamma is: * > 0.4545, the image is rendered lighter than baseline * < 0.4545, the image is rendered darker than baseline * In contrast, gqview seems to ignore the gamma chunk in png. * (7) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16 * and 32. However, it is possible, and in some cases desirable, * to write out a png file using an rgb pix that has 24 bpp. * For example, the open source xpdf SplashBitmap class generates * 24 bpp rgb images. Consequently, we anble writing 24 bpp pix. * To generate such a pix, you can make a 24 bpp pix without data * and assign the data array to the pix; e.g., * pix = pixCreateHeader(w, h, 24); * pixSetData(pix, rgbdata); * See pixConvert32To24() for an example, where we get rgbdata * from the 32 bpp pix. Caution: do not call pixSetPadBits(), * because the alignment is wrong and you may erase part of the * last pixel on each line. */ l_int32 pixWriteStreamPng(FILE *fp, PIX *pix, l_float32 gamma) { char commentstring[] = "Comment"; l_int32 i, j, k; l_int32 wpl, d, cmflag; l_int32 ncolors; l_int32 *rmap, *gmap, *bmap; l_uint32 *data, *ppixel; png_byte bit_depth, color_type; png_uint_32 w, h; png_uint_32 xres, yres; png_bytep *row_pointers; png_bytep rowbuffer; png_structp png_ptr; png_infop info_ptr; png_colorp palette; PIX *pixt; PIXCMAP *cmap; char *text; PROCNAME("pixWriteStreamPng"); if (!fp) return ERROR_INT("stream not open", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); /* Allocate the 2 data structures */ if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, NULL, NULL)) == NULL) return ERROR_INT("png_ptr not made", procName, 1); if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); return ERROR_INT("info_ptr not made", procName, 1); } /* Set up png setjmp error handling */ if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return ERROR_INT("internal png error", procName, 1); } png_init_io(png_ptr, fp); /* With best zlib compression (9), get between 1 and 10% improvement * over default (5), but the compression is 3 to 10 times slower. * Our default compression is the zlib default (5). */ png_set_compression_level(png_ptr, var_ZLIB_COMPRESSION); w = pixGetWidth(pix); h = pixGetHeight(pix); d = pixGetDepth(pix); if ((cmap = pixGetColormap(pix))) cmflag = 1; else cmflag = 0; /* Set the color type and bit depth. */ if (d == 32 && var_PNG_WRITE_ALPHA == 1) { bit_depth = 8; color_type = PNG_COLOR_TYPE_RGBA; /* 6 */ cmflag = 0; /* ignore if it exists */ } else if (d == 24 || d == 32) { bit_depth = 8; color_type = PNG_COLOR_TYPE_RGB; /* 2 */ cmflag = 0; /* ignore if it exists */ } else { bit_depth = d; color_type = PNG_COLOR_TYPE_GRAY; /* 0 */ } if (cmflag) color_type = PNG_COLOR_TYPE_PALETTE; /* 3 */ #if DEBUG fprintf(stderr, "cmflag = %d, bit_depth = %d, color_type = %d\n", cmflag, bit_depth, color_type); #endif /* DEBUG */ png_set_IHDR(png_ptr, info_ptr, w, h, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); /* Store resolution in ppm, if known */ xres = (png_uint_32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5); yres = (png_uint_32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5); if ((xres == 0) || (yres == 0)) png_set_pHYs(png_ptr, info_ptr, 0, 0, PNG_RESOLUTION_UNKNOWN); else png_set_pHYs(png_ptr, info_ptr, xres, yres, PNG_RESOLUTION_METER); if (cmflag) { pixcmapToArrays(cmap, &rmap, &gmap, &bmap); ncolors = pixcmapGetCount(cmap); /* Make and save the palette */ if ((palette = (png_colorp)(CALLOC(ncolors, sizeof(png_color)))) == NULL) return ERROR_INT("palette not made", procName, 1); for (i = 0; i < ncolors; i++) { palette[i].red = (png_byte)rmap[i]; palette[i].green = (png_byte)gmap[i]; palette[i].blue = (png_byte)bmap[i]; } png_set_PLTE(png_ptr, info_ptr, palette, (int)ncolors); FREE(rmap); FREE(gmap); FREE(bmap); } /* 0.4545 is treated as the default by some image * display programs (not gqview). A value > 0.4545 will * lighten an image as displayed by xv, display, etc. */ if (gamma > 0.0) png_set_gAMA(png_ptr, info_ptr, (l_float64)gamma); if ((text = pixGetText(pix))) { png_text text_chunk; text_chunk.compression = PNG_TEXT_COMPRESSION_NONE; text_chunk.key = commentstring; text_chunk.text = text; text_chunk.text_length = strlen(text); #ifdef PNG_ITXT_SUPPORTED text_chunk.itxt_length = 0; text_chunk.lang = NULL; text_chunk.lang_key = NULL; #endif png_set_text(png_ptr, info_ptr, &text_chunk, 1); } /* Write header and palette info */ png_write_info(png_ptr, info_ptr); if ((d != 32) && (d != 24)) { /* not rgb color */ /* Generate a temporary pix with bytes swapped. * For a binary image, there are two conditions in * which you must first invert the data for writing png: * (a) no colormap * (b) colormap with BLACK set to 0 * png writes binary with BLACK = 0, unless contradicted * by a colormap. If the colormap has BLACK = "1" * (typ. about 255), do not invert the data. If there * is no colormap, you must invert the data to store * in default BLACK = 0 state. */ if (d == 1 && (!cmap || (cmap && ((l_uint8 *)(cmap->array))[0] == 0x0))) { pixt = pixInvert(NULL, pix); pixEndianByteSwap(pixt); } else pixt = pixEndianByteSwapNew(pix); if (!pixt) { png_destroy_write_struct(&png_ptr, &info_ptr); return ERROR_INT("pixt not made", procName, 1); } /* Make and assign array of image row pointers */ if ((row_pointers = (png_bytep *)CALLOC(h, sizeof(png_bytep))) == NULL) return ERROR_INT("row-pointers not made", procName, 1); wpl = pixGetWpl(pixt); data = pixGetData(pixt); for (i = 0; i < h; i++) row_pointers[i] = (png_bytep)(data + i * wpl); png_set_rows(png_ptr, info_ptr, row_pointers); /* Transfer the data */ png_write_image(png_ptr, row_pointers); png_write_end(png_ptr, info_ptr); if (cmflag) FREE(palette); FREE(row_pointers); pixDestroy(&pixt); png_destroy_write_struct(&png_ptr, &info_ptr); return 0; } /* For rgb, compose and write a row at a time */ data = pixGetData(pix); wpl = pixGetWpl(pix); if (d == 24) { /* See note 7 above: special case of 24 bpp rgb */ for (i = 0; i < h; i++) { ppixel = data + i * wpl; png_write_rows(png_ptr, (png_bytepp)&ppixel, 1); } } else { /* 32 bpp rgb and rgba */ if ((rowbuffer = (png_bytep)CALLOC(w, 4)) == NULL) return ERROR_INT("rowbuffer not made", procName, 1); for (i = 0; i < h; i++) { ppixel = data + i * wpl; for (j = k = 0; j < w; j++) { rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_RED); rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN); rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE); if (var_PNG_WRITE_ALPHA == 1) rowbuffer[k++] = GET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL); ppixel++; } png_write_rows(png_ptr, &rowbuffer, 1); } FREE(rowbuffer); } png_write_end(png_ptr, info_ptr); if (cmflag) FREE(palette); png_destroy_write_struct(&png_ptr, &info_ptr); return 0; }
/*! * pixGetLocalSkewAngles() * * Input: pixs * nslices (the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; use 0 for default) * redsweep (sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value) * redsearch (search reduction factor: 1, 2, 4 or 8, and * not larger than redsweep; use 0 for default value) * sweeprange (half the full range, assumed about 0; in degrees; * use 0.0 for default value) * sweepdelta (angle increment of sweep; in degrees; * use 0.0 for default value) * minbsdelta (min binary search increment angle; in degrees; * use 0.0 for default value) * &a (<optional return> slope of skew as fctn of y) * &b (<optional return> intercept at y=0 of skew as fctn of y) * Return: naskew, or null on error * * Notes: * (1) The local skew is measured in a set of overlapping strips. * We then do a least square linear fit parameters to get * the slope and intercept parameters a and b in * skew-angle = a * y + b (degrees) * for the local skew as a function of raster line y. * This is then used to make naskew, which can be interpreted * as the computed skew angle (in degrees) at the left edge * of each raster line. * (2) naskew can then be used to find the baselines of text, because * each text line has a baseline that should intersect * the left edge of the image with the angle given by this * array, evaluated at the raster line of intersection. */ NUMA * pixGetLocalSkewAngles(PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_float32 *pa, l_float32 *pb) { l_int32 w, h, hs, i, ystart, yend, ovlap, npts; l_float32 angle, conf, ycenter, a, b; BOX *box; NUMA *naskew; PIX *pix; PTA *pta; PROCNAME("pixGetLocalSkewAngles"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (nslices < 2 || nslices > 20) nslices = DEFAULT_SLICES; if (redsweep < 1 || redsweep > 8) redsweep = DEFAULT_SWEEP_REDUCTION; if (redsearch < 1 || redsearch > redsweep) redsearch = DEFAULT_BS_REDUCTION; if (sweeprange == 0.0) sweeprange = DEFAULT_SWEEP_RANGE; if (sweepdelta == 0.0) sweepdelta = DEFAULT_SWEEP_DELTA; if (minbsdelta == 0.0) minbsdelta = DEFAULT_MINBS_DELTA; h = pixGetHeight(pixs); w = pixGetWidth(pixs); hs = h / nslices; ovlap = (l_int32)(OVERLAP_FRACTION * hs); pta = ptaCreate(nslices); for (i = 0; i < nslices; i++) { ystart = L_MAX(0, hs * i - ovlap); yend = L_MIN(h - 1, hs * (i + 1) + ovlap); ycenter = (ystart + yend) / 2; box = boxCreate(0, ystart, w, yend - ystart + 1); pix = pixClipRectangle(pixs, box, NULL); pixFindSkewSweepAndSearch(pix, &angle, &conf, redsweep, redsearch, sweeprange, sweepdelta, minbsdelta); if (conf > MIN_ALLOWED_CONFIDENCE) ptaAddPt(pta, ycenter, angle); pixDestroy(&pix); boxDestroy(&box); } /* ptaWriteStream(stderr, pta, 0); */ /* Do linear least squares fit */ if ((npts = ptaGetCount(pta)) < 2) { ptaDestroy(&pta); return (NUMA *)ERROR_PTR("can't fit skew", procName, NULL); } ptaGetLinearLSF(pta, &a, &b, NULL); if (pa) *pa = a; if (pb) *pb = b; /* Make skew angle array as function of raster line */ naskew = numaCreate(h); for (i = 0; i < h; i++) { angle = a * i + b; numaAddNumber(naskew, angle); } #if DEBUG_PLOT { NUMA *nax, *nay; GPLOT *gplot; ptaGetArrays(pta, &nax, &nay); gplot = gplotCreate("junkskew", GPLOT_X11, "skew as fctn of y", "y (in raster lines from top)", "angle (in degrees)"); gplotAddPlot(gplot, NULL, naskew, GPLOT_POINTS, "linear lsf"); gplotAddPlot(gplot, nax, nay, GPLOT_POINTS, "actual data pts"); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&nax); numaDestroy(&nay); } #endif /* DEBUG_PLOT */ ptaDestroy(&pta); return naskew; }
/*! * pixGetLocalSkewTransform() * * Input: pixs * nslices (the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; use 0 for default) * redsweep (sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value) * redsearch (search reduction factor: 1, 2, 4 or 8, and * not larger than redsweep; use 0 for default value) * sweeprange (half the full range, assumed about 0; in degrees; * use 0.0 for default value) * sweepdelta (angle increment of sweep; in degrees; * use 0.0 for default value) * minbsdelta (min binary search increment angle; in degrees; * use 0.0 for default value) * &ptas (<return> 4 points in the source) * &ptad (<return> the corresponding 4 pts in the dest) * Return: 0 if OK, 1 on error * * Notes: * (1) This generates two pairs of points in the src, each pair * corresponding to a pair of points that would lie along * the same raster line in a transformed (dewarped) image. * (2) The sets of 4 src and 4 dest points returned by this function * can then be used, in a projective or bilinear transform, * to remove keystoning in the src. */ l_int32 pixGetLocalSkewTransform(PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, PTA **pptas, PTA **pptad) { l_int32 w, h, i; l_float32 deg2rad, angr, angd, dely; NUMA *naskew; PTA *ptas, *ptad; PROCNAME("pixGetLocalSkewTransform"); if (!pptas || !pptad) return ERROR_INT("&ptas and &ptad not defined", procName, 1); *pptas = *pptad = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (nslices < 2 || nslices > 20) nslices = DEFAULT_SLICES; if (redsweep < 1 || redsweep > 8) redsweep = DEFAULT_SWEEP_REDUCTION; if (redsearch < 1 || redsearch > redsweep) redsearch = DEFAULT_BS_REDUCTION; if (sweeprange == 0.0) sweeprange = DEFAULT_SWEEP_RANGE; if (sweepdelta == 0.0) sweepdelta = DEFAULT_SWEEP_DELTA; if (minbsdelta == 0.0) minbsdelta = DEFAULT_MINBS_DELTA; naskew = pixGetLocalSkewAngles(pixs, nslices, redsweep, redsearch, sweeprange, sweepdelta, minbsdelta, NULL, NULL); if (!naskew) return ERROR_INT("naskew not made", procName, 1); deg2rad = 3.14159265 / 180.; w = pixGetWidth(pixs); h = pixGetHeight(pixs); ptas = ptaCreate(4); ptad = ptaCreate(4); *pptas = ptas; *pptad = ptad; /* Find i for skew line that intersects LHS at i and RHS at h / 20 */ for (i = 0; i < h; i++) { numaGetFValue(naskew, i, &angd); angr = angd * deg2rad; dely = w * tan(angr); if (i - dely > 0.05 * h) break; } ptaAddPt(ptas, 0, i); ptaAddPt(ptas, w - 1, i - dely); ptaAddPt(ptad, 0, i); ptaAddPt(ptad, w - 1, i); /* Find i for skew line that intersects LHS at i and RHS at 19h / 20 */ for (i = h - 1; i > 0; i--) { numaGetFValue(naskew, i, &angd); angr = angd * deg2rad; dely = w * tan(angr); if (i - dely < 0.95 * h) break; } ptaAddPt(ptas, 0, i); ptaAddPt(ptas, w - 1, i - dely); ptaAddPt(ptad, 0, i); ptaAddPt(ptad, w - 1, i); numaDestroy(&naskew); return 0; }
/*! * pixFindBaselines() * * Input: pixs (1 bpp) * &pta (<optional return> pairs of pts corresponding to * approx. ends of each text line) * debug (usually 0; set to 1 for debugging output) * Return: na (of baseline y values), or null on error * * Notes: * (1) Input binary image must have text lines already aligned * horizontally. This can be done by either rotating the * image with pixDeskew(), or, if a projective transform * is required, by doing pixDeskewLocal() first. * (2) Input null for &pta if you don't want this returned. * The pta will come in pairs of points (left and right end * of each baseline). * (3) Caution: this will not work properly on text with multiple * columns, where the lines are not aligned between columns. * If there are multiple columns, they should be extracted * separately before finding the baselines. * (4) This function constructs different types of output * for baselines; namely, a set of raster line values and * a set of end points of each baseline. * (5) This function was designed to handle short and long text lines * without using dangerous thresholds on the peak heights. It does * this by combining the differential signal with a morphological * analysis of the locations of the text lines. One can also * combine this data to normalize the peak heights, by weighting * the differential signal in the region of each baseline * by the inverse of the width of the text line found there. * (6) There are various debug sections that can be turned on * with the debug flag. */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, l_int32 debug) { l_int32 w, h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh; l_int32 imaxloc, peakthresh, zerothresh, inpeak; l_int32 mintosearch, max, maxloc, nloc, locval; l_int32 *array; l_float32 maxval; BOXA *boxa1, *boxa2, *boxa3; GPLOT *gplot; NUMA *nasum, *nadiff, *naloc, *naval; PIX *pixt1, *pixt2; PTA *pta; PROCNAME("pixFindBaselines"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } /* Close up the text characters, removing noise */ pixt1 = pixMorphSequence(pixs, "c25.1 + e3.1", 0); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pixt1, NULL)) == NULL) return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); w = pixGetWidth(pixs); h = pixGetHeight(pixs); nadiff = numaCreate(h); numaGetIValue(nasum, 0, &val2); for (i = 0; i < h - 1; i++) { val1 = val2; numaGetIValue(nasum, i + 1, &val2); numaAddNumber(nadiff, val1 - val2); } if (debug) /* show the difference signal */ gplotSimple1(nadiff, GPLOT_X11, "junkdiff", "difference"); /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); /* Use this to begin locating a new peak: */ peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO; /* Use this to begin a region between peaks: */ zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO; naloc = numaCreate(0); naval = numaCreate(0); inpeak = FALSE; for (i = 0; i < ndiff; i++) { if (inpeak == FALSE) { if (array[i] > peakthresh) { /* transition to in-peak */ inpeak = TRUE; mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros * between i and mintosearch */ max = array[i]; maxloc = i; } } else { /* inpeak == TRUE; look for max */ if (array[i] > max) { max = array[i]; maxloc = i; mintosearch = i + MIN_DIST_IN_PEAK; } else if (i > mintosearch && array[i] <= zerothresh) { /* leave */ inpeak = FALSE; numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } } } /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } FREE(array); if (debug) { /* show the raster locations for the peaks */ gplot = gplotCreate("junkloc", GPLOT_X11, "Peak locations", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); } /* Generate an approximate profile of text line width. * First, filter the boxes of text, where there may be * more than one box for a given textline. */ pixt2 = pixMorphSequence(pixt1, "r11 + c25.1 + o7.1 +c1.3", 0); boxa1 = pixConnComp(pixt2, NULL, 4); boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); /* Then find the baseline segments */ if (pta) { nloc = numaGetCount(naloc); nbox = boxaGetCount(boxa3); for (i = 0; i < nbox; i++) { boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh); for (j = 0; j < nloc; j++) { numaGetIValue(naloc, j, &locval); if (L_ABS(locval - (by + bh)) > 25) continue; ptaAddPt(pta, bx, locval); ptaAddPt(pta, bx + bw, locval); break; } } } if (debug) { /* display baselines */ PIX *pixd; l_int32 npts, x1, y1, x2, y2; if (pta) { pixd = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pixd, x1, y1, x2, y2, 1, 255, 0, 0); } pixDisplay(pixd, 200, 200); pixWrite("junkbaselines", pixd, IFF_PNG); pixDestroy(&pixd); } } boxaDestroy(&boxa1); boxaDestroy(&boxa2); boxaDestroy(&boxa3); pixDestroy(&pixt1); pixDestroy(&pixt2); numaDestroy(&nasum); numaDestroy(&nadiff); numaDestroy(&naval); return naloc; }
/*! * 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; }
/*! * \brief pixGenerateSelBoundary() * * \param[in] pixs 1 bpp, typically small, to be used as a pattern * \param[in] hitdist min distance from fg boundary pixel * \param[in] missdist min distance from bg boundary pixel * \param[in] hitskip number of boundary pixels skipped between hits * \param[in] missskip number of boundary pixels skipped between misses * \param[in] topflag flag for extra pixels of bg added above * \param[in] botflag flag for extra pixels of bg added below * \param[in] leftflag flag for extra pixels of bg added to left * \param[in] rightflag flag for extra pixels of bg added to right * \param[out] ppixe [optional] input pix expanded by extra pixels * \return sel hit-miss for input pattern, or NULL on error * * <pre> * Notes: * (1) All fg elements selected are exactly hitdist pixels away from * the nearest fg boundary pixel, and ditto for bg elements. * Valid inputs of hitdist and missdist are 0, 1, 2, 3 and 4. * For example, a hitdist of 0 puts the hits at the fg boundary. * Usually, the distances should be > 0 avoid the effect of * noise at the boundary. * (2) Set hitskip < 0 if no hits are to be used. Ditto for missskip. * If both hitskip and missskip are < 0, the sel would be empty, * and NULL is returned. * (3) The 4 flags determine whether the sel is increased on that side * to allow bg misses to be placed all along that boundary. * The increase in sel size on that side is the minimum necessary * to allow the misses to be placed at mindist. For text characters, * the topflag and botflag are typically set to 1, and the leftflag * and rightflag to 0. * (4) The input pix, as extended by the extra pixels on selected sides, * can optionally be returned. For debugging, call * pixDisplayHitMissSel() to visualize the hit-miss sel superimposed * on the generating bitmap. * (5) This is probably the best of the three sel generators, in the * sense that you have the most flexibility with the smallest number * of hits and misses. * </pre> */ SEL * pixGenerateSelBoundary(PIX *pixs, l_int32 hitdist, l_int32 missdist, l_int32 hitskip, l_int32 missskip, l_int32 topflag, l_int32 botflag, l_int32 leftflag, l_int32 rightflag, PIX **ppixe) { l_int32 ws, hs, w, h, x, y, ix, iy, i, npt; PIX *pixt1, *pixt2, *pixt3, *pixfg, *pixbg; SEL *selh, *selm, *sel_3, *sel; PTA *ptah, *ptam; PROCNAME("pixGenerateSelBoundary"); if (ppixe) *ppixe = NULL; if (!pixs) return (SEL *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (hitdist < 0 || hitdist > 4 || missdist < 0 || missdist > 4) return (SEL *)ERROR_PTR("dist not in {0 .. 4}", procName, NULL); if (hitskip < 0 && missskip < 0) return (SEL *)ERROR_PTR("no hits or misses", procName, NULL); /* Locate the foreground */ pixClipToForeground(pixs, &pixt1, NULL); if (!pixt1) return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL); ws = pixGetWidth(pixt1); hs = pixGetHeight(pixt1); w = ws; h = hs; /* Crop out a region including the foreground, and add pixels * on sides depending on the side flags */ if (topflag || botflag || leftflag || rightflag) { x = y = 0; if (topflag) { h += missdist + 1; y = missdist + 1; } if (botflag) h += missdist + 1; if (leftflag) { w += missdist + 1; x = missdist + 1; } if (rightflag) w += missdist + 1; pixt2 = pixCreate(w, h, 1); pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0); } else { pixt2 = pixClone(pixt1); } if (ppixe) *ppixe = pixClone(pixt2); pixDestroy(&pixt1); /* Identify fg and bg pixels that are exactly hitdist and * missdist (rsp) away from the boundary pixels in their set. * Then get a subsampled set of these points. */ sel_3 = selCreateBrick(3, 3, 1, 1, SEL_HIT); if (hitskip >= 0) { selh = selCreateBrick(2 * hitdist + 1, 2 * hitdist + 1, hitdist, hitdist, SEL_HIT); pixt3 = pixErode(NULL, pixt2, selh); pixfg = pixErode(NULL, pixt3, sel_3); pixXor(pixfg, pixfg, pixt3); ptah = pixSubsampleBoundaryPixels(pixfg, hitskip); pixDestroy(&pixt3); pixDestroy(&pixfg); selDestroy(&selh); } if (missskip >= 0) { selm = selCreateBrick(2 * missdist + 1, 2 * missdist + 1, missdist, missdist, SEL_HIT); pixt3 = pixDilate(NULL, pixt2, selm); pixbg = pixDilate(NULL, pixt3, sel_3); pixXor(pixbg, pixbg, pixt3); ptam = pixSubsampleBoundaryPixels(pixbg, missskip); pixDestroy(&pixt3); pixDestroy(&pixbg); selDestroy(&selm); } selDestroy(&sel_3); pixDestroy(&pixt2); /* Generate the hit-miss sel from these point */ sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE); if (hitskip >= 0) { npt = ptaGetCount(ptah); for (i = 0; i < npt; i++) { ptaGetIPt(ptah, i, &ix, &iy); selSetElement(sel, iy, ix, SEL_HIT); } } if (missskip >= 0) { npt = ptaGetCount(ptam); for (i = 0; i < npt; i++) { ptaGetIPt(ptam, i, &ix, &iy); selSetElement(sel, iy, ix, SEL_MISS); } } ptaDestroy(&ptah); ptaDestroy(&ptam); return sel; }
/*! * pixaaDisplayByPixa() * * Input: pixaa * xspace between pix in pixa * yspace between pixa * max width of output pix * Return: pix, or null on error * * Notes: * (1) Displays each pixa on a line (or set of lines), * in order from top to bottom. Within each pixa, * the pix are displayed in order from left to right. * (2) The size of each pix in each pixa is assumed to be * approximately equal to the size of the first pix in * the pixa. If this assumption is not correct, this * function will not work properly. * (3) This ignores the boxa of the pixaa. */ PIX * pixaaDisplayByPixa(PIXAA *pixaa, l_int32 xspace, l_int32 yspace, l_int32 maxw) { l_int32 i, j, npixa, npix; l_int32 width, height, depth, nlines, lwidth; l_int32 x, y, w, h, w0, h0; PIX *pixt, *pixd; PIXA *pixa; PROCNAME("pixaaDisplayByPixa"); if (!pixaa) return (PIX *)ERROR_PTR("pixaa not defined", procName, NULL); if ((npixa = pixaaGetCount(pixaa)) == 0) return (PIX *)ERROR_PTR("no components", procName, NULL); /* Get size of output pix. The width is the minimum of the * maxw and the largest pixa line width. The height is whatever * it needs to be to accommodate all pixa. */ height = 2 * yspace; width = 0; for (i = 0; i < npixa; i++) { pixa = pixaaGetPixa(pixaa, i, L_CLONE); npix = pixaGetCount(pixa); pixt = pixaGetPix(pixa, 0, L_CLONE); if (i == 0) depth = pixGetDepth(pixt); w = pixGetWidth(pixt); lwidth = npix * (w + xspace); nlines = (lwidth + maxw - 1) / maxw; if (nlines > 1) width = maxw; else width = L_MAX(lwidth, width); height += nlines * (pixGetHeight(pixt) + yspace); pixDestroy(&pixt); pixaDestroy(&pixa); } if ((pixd = pixCreate(width, height, depth)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); /* Now layout the pix by pixa */ y = yspace; for (i = 0; i < npixa; i++) { x = 0; pixa = pixaaGetPixa(pixaa, i, L_CLONE); npix = pixaGetCount(pixa); for (j = 0; j < npix; j++) { pixt = pixaGetPix(pixa, j, L_CLONE); if (j == 0) { w0 = pixGetWidth(pixt); h0 = pixGetHeight(pixt); } w = pixGetWidth(pixt); if (width == maxw && x + w >= maxw) { x = 0; y += h0 + yspace; } h = pixGetHeight(pixt); pixRasterop(pixd, x, y, w, h, PIX_PAINT, pixt, 0, 0); pixDestroy(&pixt); x += w0 + xspace; } y += h0 + yspace; pixaDestroy(&pixa); } return pixd; }
/*! * \brief pixGetRunsOnLine() * * \param[in] pixs 1 bpp * \param[in] x1, y1, x2, y2 * \return numa, or NULL on error * * <pre> * Notes: * (1) Action: this function uses the bresenham algorithm to compute * the pixels along the specified line. It returns a Numa of the * runlengths of the fg (black) and bg (white) runs, always * starting with a white run. * (2) If the first pixel on the line is black, the length of the * first returned run (which is white) is 0. * </pre> */ NUMA * pixGetRunsOnLine(PIX *pixs, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2) { l_int32 w, h, x, y, npts; l_int32 i, runlen, preval; l_uint32 val; NUMA *numa; PTA *pta; PROCNAME("pixGetRunsOnLine"); if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs not 1 bpp", procName, NULL); w = pixGetWidth(pixs); h = pixGetHeight(pixs); if (x1 < 0 || x1 >= w) return (NUMA *)ERROR_PTR("x1 not valid", procName, NULL); if (x2 < 0 || x2 >= w) return (NUMA *)ERROR_PTR("x2 not valid", procName, NULL); if (y1 < 0 || y1 >= h) return (NUMA *)ERROR_PTR("y1 not valid", procName, NULL); if (y2 < 0 || y2 >= h) return (NUMA *)ERROR_PTR("y2 not valid", procName, NULL); if ((pta = generatePtaLine(x1, y1, x2, y2)) == NULL) return (NUMA *)ERROR_PTR("pta not made", procName, NULL); if ((npts = ptaGetCount(pta)) == 0) { ptaDestroy(&pta); return (NUMA *)ERROR_PTR("pta has no pts", procName, NULL); } if ((numa = numaCreate(0)) == NULL) { ptaDestroy(&pta); return (NUMA *)ERROR_PTR("numa not made", procName, NULL); } for (i = 0; i < npts; i++) { ptaGetIPt(pta, i, &x, &y); pixGetPixel(pixs, x, y, &val); if (i == 0) { if (val == 1) { /* black pixel; append white run of size 0 */ numaAddNumber(numa, 0); } preval = val; runlen = 1; continue; } if (val == preval) { /* extend current run */ preval = val; runlen++; } else { /* end previous run */ numaAddNumber(numa, runlen); preval = val; runlen = 1; } } numaAddNumber(numa, runlen); /* append last run */ ptaDestroy(&pta); return numa; }
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) exit(ERROR_INT(" Syntax: rasterop_reg", mainName, 1)); if ((pixs = pixRead("feyn.tif")) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); for (width = MINW; width <= MAXW; width++) { for (height = MINH; height <= MAXH; height++) { 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: results for (%d,%d) are identical!\n", width, height); else { fprintf(stderr, "Error: results are different!\n"); fprintf(stderr, "SE: width = %d, height = %d\n", width, height); pixWrite("/tmp/junkout1.png", pixd1, IFF_PNG); pixWrite("/tmp/junkout2.png", pixd2, IFF_PNG); exit(1); } pixDestroy(&pixse); pixDestroy(&pixd1); pixDestroy(&pixd2); selDestroy(&sel); } } pixDestroy(&pixs); exit(0); }
// Adds sub-pixel resolution EdgeOffsets for the outline if the supplied // pix is 8-bit. Does nothing otherwise. // Operation: Consider the following near-horizontal line: // _________ // |________ // |________ // At *every* position along this line, the gradient direction will be close // to vertical. Extrapoaltion/interpolation of the position of the threshold // that was used to binarize the image gives a more precise vertical position // for each horizontal step, and the conflict in step direction and gradient // direction can be used to ignore the vertical steps. void C_OUTLINE::ComputeEdgeOffsets(int threshold, Pix* pix) { if (pixGetDepth(pix) != 8) return; const l_uint32* data = pixGetData(pix); int wpl = pixGetWpl(pix); int width = pixGetWidth(pix); int height = pixGetHeight(pix); bool negative = flag(COUT_INVERSE); delete [] offsets; offsets = new EdgeOffset[stepcount]; ICOORD pos = start; ICOORD prev_gradient; ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &prev_gradient); for (int s = 0; s < stepcount; ++s) { ICOORD step_vec = step(s); TPOINT pt1(pos); pos += step_vec; TPOINT pt2(pos); ICOORD next_gradient; ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &next_gradient); // Use the sum of the prev and next as the working gradient. ICOORD gradient = prev_gradient + next_gradient; // best_diff will be manipulated to be always positive. int best_diff = 0; // offset will be the extrapolation of the location of the greyscale // threshold from the edge with the largest difference, relative to the // location of the binary edge. int offset = 0; if (pt1.y == pt2.y && abs(gradient.y()) * 2 >= abs(gradient.x())) { // Horizontal step. diff_sign == 1 indicates black above. int diff_sign = (pt1.x > pt2.x) == negative ? 1 : -1; int x = MIN(pt1.x, pt2.x); int y = height - pt1.y; int best_sum = 0; int best_y = y; EvaluateVerticalDiff(data, wpl, diff_sign, x, y, height, &best_diff, &best_sum, &best_y); // Find the strongest edge. int test_y = y; do { ++test_y; } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, &best_y)); test_y = y; do { --test_y; } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, &best_y)); offset = diff_sign * (best_sum / 2 - threshold) + (y - best_y) * best_diff; } else if (pt1.x == pt2.x && abs(gradient.x()) * 2 >= abs(gradient.y())) { // Vertical step. diff_sign == 1 indicates black on the left. int diff_sign = (pt1.y > pt2.y) == negative ? 1 : -1; int x = pt1.x; int y = height - MAX(pt1.y, pt2.y); const l_uint32* line = pixGetData(pix) + y * wpl; int best_sum = 0; int best_x = x; EvaluateHorizontalDiff(line, diff_sign, x, width, &best_diff, &best_sum, &best_x); // Find the strongest edge. int test_x = x; do { ++test_x; } while (EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); test_x = x; do { --test_x; } while (EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); offset = diff_sign * (threshold - best_sum / 2) + (best_x - x) * best_diff; } offsets[s].offset_numerator = static_cast<inT8>(ClipToRange(offset, -MAX_INT8, MAX_INT8)); offsets[s].pixel_diff = static_cast<uinT8>(ClipToRange(best_diff, 0 , MAX_UINT8)); if (negative) gradient = -gradient; // Compute gradient angle quantized to 256 directions, rotated by 64 (pi/2) // to convert from gradient direction to edge direction. offsets[s].direction = Modulo(FCOORD::binary_angle_plus_pi(gradient.angle()) + 64, 256); prev_gradient = next_gradient; } }
/** * Segment the page according to the current value of tessedit_pageseg_mode. * If the pix_binary_ member is not NULL, it is used as the source image, * and copied to image, otherwise it just uses image as the input. * On return the blocks list owns all the constructed page layout. */ int Tesseract::SegmentPage(const STRING* input_file, IMAGE* image, BLOCK_LIST* blocks) { int width = image->get_xsize(); int height = image->get_ysize(); int resolution = image->get_res(); #ifdef HAVE_LIBLEPT if (pix_binary_ != NULL) { width = pixGetWidth(pix_binary_); height = pixGetHeight(pix_binary_); resolution = pixGetXRes(pix_binary_); } #endif // Zero resolution messes up the algorithms, so make sure it is credible. if (resolution < kMinCredibleResolution) resolution = kDefaultResolution; // Get page segmentation mode. PageSegMode pageseg_mode = static_cast<PageSegMode>( static_cast<int>(tessedit_pageseg_mode)); // If a UNLV zone file can be found, use that instead of segmentation. if (pageseg_mode != tesseract::PSM_AUTO && input_file != NULL && input_file->length() > 0) { STRING name = *input_file; const char* lastdot = strrchr(name.string(), '.'); if (lastdot != NULL) name[lastdot - name.string()] = '\0'; read_unlv_file(name, width, height, blocks); } bool single_column = pageseg_mode > PSM_AUTO; if (blocks->empty()) { // No UNLV file present. Work according to the PageSegMode. // First make a single block covering the whole image. BLOCK_IT block_it(blocks); BLOCK* block = new BLOCK("", TRUE, 0, 0, 0, 0, width, height); block_it.add_to_end(block); } else { // UNLV file present. Use PSM_SINGLE_COLUMN. pageseg_mode = PSM_SINGLE_COLUMN; } TO_BLOCK_LIST land_blocks, port_blocks; TBOX page_box; if (pageseg_mode <= PSM_SINGLE_COLUMN) { if (AutoPageSeg(width, height, resolution, single_column, image, blocks, &port_blocks) < 0) { return -1; } // To create blobs from the image region bounds uncomment this line: // port_blocks.clear(); // Uncomment to go back to the old mode. } else { #if HAVE_LIBLEPT image->FromPix(pix_binary_); #endif deskew_ = FCOORD(1.0f, 0.0f); reskew_ = FCOORD(1.0f, 0.0f); } if (blocks->empty()) { tprintf("Empty page\n"); return 0; // AutoPageSeg found an empty page. } if (port_blocks.empty()) { // AutoPageSeg was not used, so we need to find_components first. find_components(blocks, &land_blocks, &port_blocks, &page_box); } else { // AutoPageSeg does not need to find_components as it did that already. page_box.set_left(0); page_box.set_bottom(0); page_box.set_right(width); page_box.set_top(height); // Filter_blobs sets up the TO_BLOCKs the same as find_components does. filter_blobs(page_box.topright(), &port_blocks, true); } TO_BLOCK_IT to_block_it(&port_blocks); ASSERT_HOST(!port_blocks.empty()); TO_BLOCK* to_block = to_block_it.data(); if (pageseg_mode <= PSM_SINGLE_BLOCK || to_block->line_size < 2) { // For now, AUTO, SINGLE_COLUMN and SINGLE_BLOCK all map to the old // textord. The difference is the number of blocks and how the are made. textord_page(page_box.topright(), blocks, &land_blocks, &port_blocks, this); } else { // SINGLE_LINE, SINGLE_WORD and SINGLE_CHAR all need a single row. float gradient = make_single_row(page_box.topright(), to_block, &port_blocks, this); if (pageseg_mode == PSM_SINGLE_LINE) { // SINGLE_LINE uses the old word maker on the single line. make_words(page_box.topright(), gradient, blocks, &land_blocks, &port_blocks, this); } else { // SINGLE_WORD and SINGLE_CHAR cram all the blobs into a // single word, and in SINGLE_CHAR mode, all the outlines // go in a single blob. make_single_word(pageseg_mode == PSM_SINGLE_CHAR, to_block->get_rows(), to_block->block->row_list()); } } return 0; }
int main(int argc, char **argv) { char textstr[256]; l_int32 w, h, d, i; l_uint32 srcval, dstval; l_float32 scalefact, sat, fract; L_BMF *bmf8; L_KERNEL *kel; NUMA *na; PIX *pix, *pixs, *pixs1, *pixs2, *pixd; PIX *pixt0, *pixt1, *pixt2, *pixt3, *pixt4; PIXA *pixa, *pixaf; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pix = pixRead(filein); pixGetDimensions(pix, &w, &h, &d); if (d != 32) return ERROR_INT("file not 32 bpp", argv[0], 1); scalefact = (l_float32)WIDTH / (l_float32)w; pixs = pixScale(pix, scalefact, scalefact); w = pixGetWidth(pixs); pixaf = pixaCreate(5); /* TRC: vary gamma */ pixa = pixaCreate(20); for (i = 0; i < 20; i++) { pixt0 = pixGammaTRC(NULL, pixs, 0.3 + 0.15 * i, 0, 255); pixaAddPix(pixa, pixt0, L_INSERT); } pixt1 = pixaDisplayTiledAndScaled(pixa, 32, w, 5, 0, 10, 2); pixSaveTiled(pixt1, pixaf, 1.0, 1, 20, 32); regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* 0 */ pixDisplayWithTitle(pixt1, 0, 100, "TRC Gamma", rp->display); pixDestroy(&pixt1); pixaDestroy(&pixa); /* TRC: vary black point */ pixa = pixaCreate(20); for (i = 0; i < 20; i++) { pixt0 = pixGammaTRC(NULL, pixs, 1.0, 5 * i, 255); pixaAddPix(pixa, pixt0, L_INSERT); } pixt1 = pixaDisplayTiledAndScaled(pixa, 32, w, 5, 0, 10, 2); pixSaveTiled(pixt1, pixaf, 1.0, 1, 20, 0); regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* 1 */ pixDisplayWithTitle(pixt1, 300, 100, "TRC", rp->display); pixDestroy(&pixt1); pixaDestroy(&pixa); /* Vary hue */ pixa = pixaCreate(20); for (i = 0; i < 20; i++) { pixt0 = pixModifyHue(NULL, pixs, 0.01 + 0.05 * i); pixaAddPix(pixa, pixt0, L_INSERT); } pixt1 = pixaDisplayTiledAndScaled(pixa, 32, w, 5, 0, 10, 2); pixSaveTiled(pixt1, pixaf, 1.0, 1, 20, 0); regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* 2 */ pixDisplayWithTitle(pixt1, 600, 100, "Hue", rp->display); pixDestroy(&pixt1); pixaDestroy(&pixa); /* Vary saturation */ pixa = pixaCreate(20); na = numaCreate(20); for (i = 0; i < 20; i++) { pixt0 = pixModifySaturation(NULL, pixs, -0.9 + 0.1 * i); pixMeasureSaturation(pixt0, 1, &sat); pixaAddPix(pixa, pixt0, L_INSERT); numaAddNumber(na, sat); } pixt1 = pixaDisplayTiledAndScaled(pixa, 32, w, 5, 0, 10, 2); pixSaveTiled(pixt1, pixaf, 1.0, 1, 20, 0); gplotSimple1(na, GPLOT_PNG, "/tmp/regout/enhance.7", "Average Saturation"); regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* 3 */ pixDisplayWithTitle(pixt1, 900, 100, "Saturation", rp->display); numaDestroy(&na); pixDestroy(&pixt1); pixaDestroy(&pixa); /* Vary contrast */ pixa = pixaCreate(20); for (i = 0; i < 20; i++) { pixt0 = pixContrastTRC(NULL, pixs, 0.1 * i); pixaAddPix(pixa, pixt0, L_INSERT); } pixt1 = pixaDisplayTiledAndScaled(pixa, 32, w, 5, 0, 10, 2); pixSaveTiled(pixt1, pixaf, 1.0, 1, 20, 0); regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* 4 */ pixDisplayWithTitle(pixt1, 0, 400, "Contrast", rp->display); pixDestroy(&pixt1); pixaDestroy(&pixa); /* Vary sharpening */ pixa = pixaCreate(20); for (i = 0; i < 20; i++) { pixt0 = pixUnsharpMasking(pixs, 3, 0.01 + 0.15 * i); pixaAddPix(pixa, pixt0, L_INSERT); } pixt1 = pixaDisplayTiledAndScaled(pixa, 32, w, 5, 0, 10, 2); pixSaveTiled(pixt1, pixaf, 1.0, 1, 20, 0); regTestWritePixAndCheck(rp, pixt1, IFF_PNG); /* 5 */ pixDisplayWithTitle(pixt1, 300, 400, "Sharp", rp->display); pixDestroy(&pixt1); pixaDestroy(&pixa); /* Hue constant mapping to lighter background */ pixa = pixaCreate(11); bmf8 = bmfCreate("fonts", 8); pixt0 = pixRead("candelabrum-11.jpg"); composeRGBPixel(230, 185, 144, &srcval); /* select typical bg pixel */ for (i = 0; i <= 10; i++) { fract = 0.10 * i; pixelFractionalShift(230, 185, 144, fract, &dstval); pixt1 = pixLinearMapToTargetColor(NULL, pixt0, srcval, dstval); snprintf(textstr, 50, "Fract = %5.1f", fract); pixt2 = pixAddSingleTextblock(pixt1, bmf8, textstr, 0xff000000, L_ADD_BELOW, NULL); pixSaveTiledOutline(pixt2, pixa, 1.0, (i % 4 == 0) ? 1 : 0, 30, 2, 32); pixDestroy(&pixt1); pixDestroy(&pixt2); } pixDestroy(&pixt0); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 6 */ pixDisplayWithTitle(pixd, 600, 400, "Constant hue", rp->display); bmfDestroy(&bmf8); pixaDestroy(&pixa); pixDestroy(&pixd); /* Delayed testing of saturation plot */ regTestCheckFile(rp, "/tmp/regout/enhance.7.png"); /* 7 */ /* Display results */ pixd = pixaDisplay(pixaf, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 8 */ pixDisplayWithTitle(pixd, 100, 100, "All", rp->display); pixDestroy(&pixd); pixaDestroy(&pixaf); pixDestroy(&pix); pixDestroy(&pixs); /* -----------------------------------------------* * Test global color transforms * * -----------------------------------------------*/ /* Make identical cmap and rgb images */ pix = pixRead("wet-day.jpg"); pixs1 = pixOctreeColorQuant(pix, 200, 0); pixs2 = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR); regTestComparePix(rp, pixs1, pixs2); /* 9 */ /* Make a diagonal color transform matrix */ kel = kernelCreate(3, 3); kernelSetElement(kel, 0, 0, 0.7); kernelSetElement(kel, 1, 1, 0.4); kernelSetElement(kel, 2, 2, 1.3); /* Apply to both cmap and rgb images. */ pixt1 = pixMultMatrixColor(pixs1, kel); pixt2 = pixMultMatrixColor(pixs2, kel); regTestComparePix(rp, pixt1, pixt2); /* 10 */ kernelDestroy(&kel); /* Apply the same transform in the simpler interface */ pixt3 = pixMultConstantColor(pixs1, 0.7, 0.4, 1.3); pixt4 = pixMultConstantColor(pixs2, 0.7, 0.4, 1.3); regTestComparePix(rp, pixt3, pixt4); /* 11 */ regTestComparePix(rp, pixt1, pixt3); /* 12 */ regTestWritePixAndCheck(rp, pixt1, IFF_JFIF_JPEG); /* 13 */ pixDestroy(&pix); pixDestroy(&pixs1); pixDestroy(&pixs2); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); return regTestCleanup(rp); }
bool TessPDFRenderer::AddImageHandler(TessBaseAPI* api) { char buf[kBasicBufSize]; Pix *pix = api->GetInputImage(); char *filename = (char *)api->GetInputName(); int ppi = api->GetSourceYResolution(); if (!pix || ppi <= 0) return false; double width = pixGetWidth(pix) * 72.0 / ppi; double height = pixGetHeight(pix) * 72.0 / ppi; // PAGE snprintf(buf, sizeof(buf), "%ld 0 obj\n" "<<\n" " /Type /Page\n" " /Parent %ld 0 R\n" " /MediaBox [0 0 %.2f %.2f]\n" " /Contents %ld 0 R\n" " /Resources\n" " <<\n" " /XObject << /Im1 %ld 0 R >>\n" " /ProcSet [ /PDF /Text /ImageB /ImageI /ImageC ]\n" " /Font << /f-0-0 %ld 0 R >>\n" " >>\n" ">>\n" "endobj\n", obj_, 2L, // Pages object width, height, obj_ + 1, // Contents object obj_ + 2, // Image object 3L); // Type0 Font pages_.push_back(obj_); AppendPDFObject(buf); // CONTENTS char* pdftext = GetPDFTextObjects(api, width, height, imagenum()); long pdftext_len = strlen(pdftext); unsigned char *pdftext_casted = reinterpret_cast<unsigned char *>(pdftext); size_t len; unsigned char *comp_pdftext = zlibCompress(pdftext_casted, pdftext_len, &len); long comp_pdftext_len = len; snprintf(buf, sizeof(buf), "%ld 0 obj\n" "<<\n" " /Length %ld /Filter /FlateDecode\n" ">>\n" "stream\n", obj_, comp_pdftext_len); AppendString(buf); long objsize = strlen(buf); AppendData(reinterpret_cast<char *>(comp_pdftext), comp_pdftext_len); objsize += comp_pdftext_len; lept_free(comp_pdftext); delete[] pdftext; snprintf(buf, sizeof(buf), "endstream\n" "endobj\n"); AppendString(buf); objsize += strlen(buf); AppendPDFObjectDIY(objsize); char *pdf_object; if (!fileToPDFObj(filename, obj_, &pdf_object, &objsize)) { if (!pixToPDFObj(pix, obj_, &pdf_object, &objsize)) { return false; } } AppendData(pdf_object, objsize); AppendPDFObjectDIY(objsize); delete[] pdf_object; return true; }
main(int argc, char **argv) { l_int32 i, w, h, dir; PIX *pixs, *pixd, *pixt; l_float32 pops; char *filein, *fileout; static char mainName[] = "rotateorthtest1"; if (argc != 3 && argc != 4) exit(ERROR_INT(" Syntax: rotateorthtest1 filein fileout [direction]", mainName, 1)); filein = argv[1]; fileout = argv[2]; if (argc == 4) dir = atoi(argv[3]); else dir = 1; if ((pixs = pixRead(filein)) == NULL) exit(ERROR_INT("pix not made", mainName, 1)); /* Do a single operation */ #if 1 pixd = pixRotate90(pixs, dir); #elif 0 pixd = pixRotate180(NULL, pixs); #elif 0 pixd = pixRotateLR(NULL, pixs); #elif 0 pixd = pixRotateTB(NULL, pixs); #endif /* Time rotate 90, allocating & destroying each time */ #if 0 startTimer(); w = pixGetWidth(pixs); h = pixGetHeight(pixs); for (i = 0; i < NTIMES; i++) { pixd = pixRotate90(pixs, dir); pixDestroy(&pixd); } pops = (l_float32)(w * h * NTIMES) / stopTimer(); fprintf(stderr, "MPops for 90 rotation: %7.3f\n", pops / 1000000.); pixd = pixRotate90(pixs, dir); #endif /* Time rotate 180, with no alloc/destroy */ #if 0 startTimer(); w = pixGetWidth(pixs); h = pixGetHeight(pixs); pixd = pixCreateTemplate(pixs); for (i = 0; i < NTIMES; i++) pixRotate180(pixd, pixs); pops = (l_float32)(w * h * NTIMES) / stopTimer(); fprintf(stderr, "MPops for 180 rotation: %7.3f\n", pops / 1000000.); #endif /* Test rotate 180 not in-place */ #if 0 pixt = pixRotate180(NULL, pixs); pixd = pixRotate180(NULL, pixt); pixEqual(pixs, pixd, &eq); if (eq) fprintf(stderr, "2 rots gives I\n"); else fprintf(stderr, "2 rots fail to give I\n"); pixDestroy(&pixt); #endif /* Test rotate 180 in-place */ #if 0 pixd = pixCopy(NULL, pixs); pixRotate180(pixd, pixd); pixRotate180(pixd, pixd); pixEqual(pixs, pixd, &eq); if (eq) fprintf(stderr, "2 rots gives I\n"); else fprintf(stderr, "2 rots fail to give I\n"); #endif /* Mix rotate 180 with LR/TB */ #if 0 pixd = pixRotate180(NULL, pixs); pixRotateLR(pixd, pixd); pixRotateTB(pixd, pixd); pixEqual(pixs, pixd, &eq); if (eq) fprintf(stderr, "180 rot OK\n"); else fprintf(stderr, "180 rot error\n"); #endif if (pixGetDepth(pixd) < 8) pixWrite(fileout, pixd, IFF_PNG); else pixWrite(fileout, pixd, IFF_JFIF_JPEG); pixDestroy(&pixs); pixDestroy(&pixd); return 0; }
/*! * \brief pixItalicWords() * * \param[in] pixs 1 bpp * \param[in] boxaw [optional] word bounding boxes; can be NULL * \param[in] pixw [optional] word box mask; can be NULL * \param[out] pboxa boxa of italic words * \param[in] debugflag 1 for debug output; 0 otherwise * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) You can input the bounding boxes for the words in one of * two forms: as bounding boxes (%boxaw) or as a word mask with * the word bounding boxes filled (%pixw). For example, * to compute %pixw, you can use pixWordMaskByDilation(). * (2) Alternatively, you can set both of these inputs to NULL, * in which case the word mask is generated here. This is * done by dilating and closing the input image to connect * letters within a word, while leaving the words separated. * The parameters are chosen under the assumption that the * input is 10 to 12 pt text, scanned at about 300 ppi. * (3) sel_ital1 and sel_ital2 detect the right edges that are * nearly vertical, at approximately the angle of italic * strokes. We use the right edge to avoid getting seeds * from lower-case 'y'. The typical italic slant has a smaller * angle with the vertical than the 'W', so in most cases we * will not trigger on the slanted lines in the 'W'. * (4) Note that sel_ital2 is shorter than sel_ital1. It is * more appropriate for a typical font scanned at 200 ppi. * </pre> */ l_int32 pixItalicWords(PIX *pixs, BOXA *boxaw, PIX *pixw, BOXA **pboxa, l_int32 debugflag) { char opstring[32]; l_int32 size; BOXA *boxa; PIX *pixsd, *pixm, *pixd; SEL *sel_ital1, *sel_ital2, *sel_ital3; PROCNAME("pixItalicWords"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pboxa) return ERROR_INT("&boxa not defined", procName, 1); if (boxaw && pixw) return ERROR_INT("both boxaw and pixw are defined", procName, 1); sel_ital1 = selCreateFromString(str_ital1, 13, 6, NULL); sel_ital2 = selCreateFromString(str_ital2, 10, 6, NULL); sel_ital3 = selCreateFromString(str_ital3, 4, 2, NULL); /* Make the italic seed: extract with HMT; remove noise. * The noise removal close/open is important to exclude * situations where a small slanted line accidentally * matches sel_ital1. */ pixsd = pixHMT(NULL, pixs, sel_ital1); pixClose(pixsd, pixsd, sel_ital3); pixOpen(pixsd, pixsd, sel_ital3); /* Make the word mask. Use input boxes or mask if given. */ size = 0; /* init */ if (boxaw) { pixm = pixCreateTemplate(pixs); pixMaskBoxa(pixm, pixm, boxaw, L_SET_PIXELS); } else if (pixw) { pixm = pixClone(pixw); } else { pixWordMaskByDilation(pixs, NULL, &size, NULL); L_INFO("dilation size = %d\n", procName, size); snprintf(opstring, sizeof(opstring), "d1.5 + c%d.1", size); pixm = pixMorphSequence(pixs, opstring, 0); } /* Binary reconstruction to fill in those word mask * components for which there is at least one seed pixel. */ pixd = pixSeedfillBinary(NULL, pixsd, pixm, 8); boxa = pixConnComp(pixd, NULL, 8); *pboxa = boxa; if (debugflag) { /* Save results at at 2x reduction */ lept_mkdir("lept/ital"); l_int32 res, upper; BOXA *boxat; GPLOT *gplot; NUMA *na; PIXA *pad; PIX *pix1, *pix2, *pix3; pad = pixaCreate(0); boxat = pixConnComp(pixm, NULL, 8); boxaWrite("/tmp/lept/ital/ital.ba", boxat); pixSaveTiledOutline(pixs, pad, 0.5, 1, 20, 2, 32); /* orig */ pixSaveTiledOutline(pixsd, pad, 0.5, 1, 20, 2, 0); /* seed */ pix1 = pixConvertTo32(pixm); pixRenderBoxaArb(pix1, boxat, 3, 255, 0, 0); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* mask + outline */ pixDestroy(&pix1); pixSaveTiledOutline(pixd, pad, 0.5, 1, 20, 2, 0); /* ital mask */ pix1 = pixConvertTo32(pixs); pixRenderBoxaArb(pix1, boxa, 3, 255, 0, 0); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* orig + outline */ pixDestroy(&pix1); pix1 = pixCreateTemplate(pixs); pix2 = pixSetBlackOrWhiteBoxa(pix1, boxa, L_SET_BLACK); pixCopy(pix1, pixs); pix3 = pixDilateBrick(NULL, pixs, 3, 3); pixCombineMasked(pix1, pix3, pix2); pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0); /* ital bolded */ pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pix2 = pixaDisplay(pad, 0, 0); pixWrite("/tmp/lept/ital/ital.png", pix2, IFF_PNG); pixDestroy(&pix2); /* Assuming the image represents 6 inches of actual page width, * the pixs resolution is approximately * (width of pixs in pixels) / 6 * and the images have been saved at half this resolution. */ res = pixGetWidth(pixs) / 12; L_INFO("resolution = %d\n", procName, res); l_pdfSetDateAndVersion(0); pixaConvertToPdf(pad, res, 1.0, L_FLATE_ENCODE, 75, "Italic Finder", "/tmp/lept/ital/ital.pdf"); l_pdfSetDateAndVersion(1); pixaDestroy(&pad); boxaDestroy(&boxat); /* Plot histogram of horizontal white run sizes. A small * initial vertical dilation removes most runs that are neither * inter-character nor inter-word. The larger first peak is * from inter-character runs, and the smaller second peak is * from inter-word runs. */ pix1 = pixDilateBrick(NULL, pixs, 1, 15); upper = L_MAX(30, 3 * size); na = pixRunHistogramMorph(pix1, L_RUN_OFF, L_HORIZ, upper); pixDestroy(&pix1); gplot = gplotCreate("/tmp/lept/ital/runhisto", GPLOT_PNG, "Histogram of horizontal runs of white pixels, vs length", "run length", "number of runs"); gplotAddPlot(gplot, NULL, na, GPLOT_LINES, "plot1"); gplotMakeOutput(gplot); gplotDestroy(&gplot); numaDestroy(&na); } selDestroy(&sel_ital1); selDestroy(&sel_ital2); selDestroy(&sel_ital3); pixDestroy(&pixsd); pixDestroy(&pixm); pixDestroy(&pixd); return 0; }
main(int argc, char **argv) { l_int32 h; l_float32 scalefactor; BOX *box; BOXA *boxa1, *boxa2; BOXAA *baa; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; lept_rmdir("segtest"); lept_mkdir("segtest"); baa = boxaaCreate(5); /* Image region input. */ pix1 = pixRead("wet-day.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pixWrite("/tmp/segtest/0.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/0.jpg"); /* 0 */ box = boxCreate(105, 161, 620, 872); /* image region */ boxa1 = boxaCreate(1); boxaAddBox(boxa1, box, L_INSERT); boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); /* Compute image region at w = 2 * WIDTH */ pix1 = pixRead("candelabrum-11.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pix3 = pixConvertTo1(pix2, 100); pix4 = pixExpandBinaryPower2(pix3, 2); /* w = 2 * WIDTH */ pix5 = pixGenHalftoneMask(pix4, NULL, NULL, 1); pix6 = pixMorphSequence(pix5, "c20.1 + c1.20", 0); pix7 = pixMaskConnComp(pix6, 8, &boxa1); pix8 = pixReduceBinary2(pix7, NULL); /* back to w = WIDTH */ pix9 = pixBackgroundNormSimple(pix2, pix8, NULL); pixWrite("/tmp/segtest/1.jpg", pix9, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/1.jpg"); /* 1 */ boxa2 = boxaTransform(boxa1, 0, 0, 0.5, 0.5); /* back to w = WIDTH */ boxaaAddBoxa(baa, boxa2, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); pixDestroy(&pix7); pixDestroy(&pix8); pixDestroy(&pix9); boxaDestroy(&boxa1); /* Use mask to find image region */ pix1 = pixRead("lion-page.00016.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pixWrite("/tmp/segtest/2.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/2.jpg"); /* 2 */ pix3 = pixRead("lion-mask.00016.tif"); pix4 = pixScaleToSize(pix3, WIDTH, 0); boxa1 = pixConnComp(pix4, NULL, 8); boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); /* Compute image region at full res */ pix1 = pixRead("rabi.png"); scalefactor = (l_float32)WIDTH / (l_float32)pixGetWidth(pix1); pix2 = pixScaleToGray(pix1, scalefactor); pixWrite("/tmp/segtest/3.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/3.jpg"); /* 3 */ pix3 = pixGenHalftoneMask(pix1, NULL, NULL, 0); pix4 = pixMorphSequence(pix3, "c20.1 + c1.20", 0); boxa1 = pixConnComp(pix4, NULL, 8); boxa2 = boxaTransform(boxa1, 0, 0, scalefactor, scalefactor); boxaaAddBoxa(baa, boxa2, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); boxaDestroy(&boxa1); /* Page with no image regions */ pix1 = pixRead("lucasta-47.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); boxa1 = boxaCreate(1); pixWrite("/tmp/segtest/4.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/4.jpg"); /* 4 */ boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); /* Page that is all image */ pix1 = pixRead("map1.jpg"); pix2 = pixScaleToSize(pix1, WIDTH, 0); pixWrite("/tmp/segtest/5.jpg", pix2, IFF_JFIF_JPEG); regTestCheckFile(rp, "/tmp/segtest/5.jpg"); /* 5 */ h = pixGetHeight(pix2); box = boxCreate(0, 0, WIDTH, h); boxa1 = boxaCreate(1); boxaAddBox(boxa1, box, L_INSERT); boxaaAddBoxa(baa, boxa1, L_INSERT); pixDestroy(&pix1); pixDestroy(&pix2); /* Save the boxaa file */ boxaaWrite("/tmp/segtest/seg.baa", baa); regTestCheckFile(rp, "/tmp/segtest/seg.baa"); /* 6 */ /* Do the conversion */ l_pdfSetDateAndVersion(FALSE); convertSegmentedFilesToPdf("/tmp/segtest", ".jpg", 100, L_G4_ENCODE, 140, baa, 75, 0.6, "Segmentation Test", "/tmp/pdfseg.7.pdf"); regTestCheckFile(rp, "/tmp/pdfseg.7.pdf"); /* 7 */ boxaaDestroy(&baa); return regTestCleanup(rp); }
int main(int argc, char **argv) { char *filein, *fileout; l_int32 i; l_uint32 val; l_float32 size; PIX *pixs, *pixd, *pixm, *pixmi, *pixt1, *pixt2, *pixt3; static char mainName[] = "seedfilltest"; if (argc != 3) return ERROR_INT(" Syntax: seedfilltest filein fileout", mainName, 1); filein = argv[1]; fileout = argv[2]; pixd = NULL; if ((pixm = pixRead(filein)) == NULL) return ERROR_INT("pixm not made", mainName, 1); pixmi = pixInvert(NULL, pixm); size = pixGetWidth(pixm) * pixGetHeight(pixm); pixs = pixCreateTemplate(pixm); for (i = 0; i < 100; i++) { pixGetPixel(pixm, XS + 5 * i, YS + 5 * i, &val); if (val == 0) break; } if (i == 100) return ERROR_INT("no seed pixel found", mainName, 1); pixSetPixel(pixs, XS + 5 * i, YS + 5 * i, 1); #if 0 /* hole filling; use "hole-filler.png" */ pixt1 = pixHDome(pixmi, 100, 4); pixt2 = pixThresholdToBinary(pixt1, 10); /* pixInvert(pixt1, pixt1); */ pixDisplay(pixt1, 100, 500); pixDisplay(pixt2, 600, 500); pixt3 = pixHolesByFilling(pixt2, 4); pixDilateBrick(pixt3, pixt3, 7, 7); pixd = pixConvertTo8(pixt3, FALSE); pixDisplay(pixd, 0, 100); pixSeedfillGray(pixd, pixmi, CONNECTIVITY); pixInvert(pixd, pixd); pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* hole filling; use "hole-filler.png" */ pixt1 = pixThresholdToBinary(pixm, 110); pixInvert(pixt1, pixt1); pixDisplay(pixt1, 100, 500); pixt2 = pixHolesByFilling(pixt1, 4); pixd = pixConvertTo8(pixt2, FALSE); pixDisplay(pixd, 0, 100); pixSeedfillGray(pixd, pixmi, CONNECTIVITY); pixInvert(pixd, pixd); pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* hole filling; use "hole-filler.png" */ pixd = pixInvert(NULL, pixm); pixAddConstantGray(pixd, -50); pixDisplay(pixd, 0, 100); /* pixt1 = pixThresholdToBinary(pixd, 20); pixDisplayWithTitle(pixt1, 600, 600, "pixt1", DFLAG); */ pixSeedfillGray(pixd, pixmi, CONNECTIVITY); /* pixInvert(pixd, pixd); */ pixDisplay(pixmi, 500, 100); pixDisplay(pixd, 1000, 100); pixWrite("/tmp/junkpixm.png", pixmi, IFF_PNG); pixWrite("/tmp/junkpixd.png", pixd, IFF_PNG); #endif #if 0 /* test in-place seedfill for speed */ pixd = pixClone(pixs); startTimer(); pixSeedfillBinary(pixs, pixs, pixmi, CONNECTIVITY); fprintf(stderr, "Filling rate: %7.4f Mpix/sec\n", (size/1000000.) / stopTimer()); pixWrite(fileout, pixd, IFF_PNG); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout1.png", pixd, IFF_PNG); #endif #if 0 /* test seedfill to dest for speed */ pixd = pixCreateTemplate(pixm); startTimer(); for (i = 0; i < NTIMES; i++) { pixSeedfillBinary(pixd, pixs, pixmi, CONNECTIVITY); } fprintf(stderr, "Filling rate: %7.4f Mpix/sec\n", (size/1000000.) * NTIMES / stopTimer()); pixWrite(fileout, pixd, IFF_PNG); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout1.png", pixd, IFF_PNG); #endif /* use same connectivity to compare with the result of the * slow parallel operation */ #if 1 pixDestroy(&pixd); pixd = pixSeedfillMorph(pixs, pixmi, 100, CONNECTIVITY); pixOr(pixd, pixd, pixm); pixWrite("/tmp/junkout2.png", pixd, IFF_PNG); #endif pixDestroy(&pixs); pixDestroy(&pixm); pixDestroy(&pixmi); pixDestroy(&pixd); return 0; }
/*! * pixHtmlViewer() * * Input: dirin: directory of input image files * dirout: directory for output files * rootname: root name for output files * thumbwidth: width of thumb images * (in pixels; use 0 for default) * viewwidth: maximum width of view images (no up-scaling) * (in pixels; use 0 for default) * copyorig: 1 to copy originals to dirout; 0 otherwise * Return: 0 if OK; 1 on error * * Notes: * (1) The thumb and view reduced images are generated, * along with two html files: * <rootname>.html and <rootname>-links.html * (2) The thumb and view files are named * <rootname>_thumb_xxx.jpg * <rootname>_view_xxx.jpg * With this naming scheme, any number of input directories * of images can be processed into views and thumbs * and placed in the same output directory. */ l_int32 pixHtmlViewer(const char *dirin, const char *dirout, const char *rootname, l_int32 thumbwidth, l_int32 viewwidth, l_int32 copyorig) { char *fname, *fullname, *outname; char *mainname, *linkname, *linknameshort; char *viewfile, *thumbfile; char *shtml, *slink; char charbuf[L_BUF_SIZE]; char htmlstring[] = "<html>"; char framestring[] = "</frameset></html>"; l_int32 i, nfiles, index, w, nimages, ret; l_float32 factor; PIX *pix, *pixthumb, *pixview; SARRAY *safiles, *sathumbs, *saviews, *sahtml, *salink; PROCNAME("pixHtmlViewer"); if (!dirin) return ERROR_INT("dirin not defined", procName, 1); if (!dirout) return ERROR_INT("dirout not defined", procName, 1); if (!rootname) return ERROR_INT("rootname not defined", procName, 1); if (thumbwidth == 0) thumbwidth = DEFAULT_THUMB_WIDTH; if (thumbwidth < MIN_THUMB_WIDTH) { L_WARNING("thumbwidth too small; using min value\n", procName); thumbwidth = MIN_THUMB_WIDTH; } if (viewwidth == 0) viewwidth = DEFAULT_VIEW_WIDTH; if (viewwidth < MIN_VIEW_WIDTH) { L_WARNING("viewwidth too small; using min value\n", procName); viewwidth = MIN_VIEW_WIDTH; } /* Make the output directory if it doesn't already exist */ #ifndef _WIN32 sprintf(charbuf, "mkdir -p %s", dirout); ret = system(charbuf); #else ret = CreateDirectory(dirout, NULL) ? 0 : 1; #endif /* !_WIN32 */ if (ret) { L_ERROR("output directory %s not made\n", procName, dirout); return 1; } /* Capture the filenames in the input directory */ if ((safiles = getFilenamesInDirectory(dirin)) == NULL) return ERROR_INT("safiles not made", procName, 1); /* Generate output text file names */ sprintf(charbuf, "%s/%s.html", dirout, rootname); mainname = stringNew(charbuf); sprintf(charbuf, "%s/%s-links.html", dirout, rootname); linkname = stringNew(charbuf); linknameshort = stringJoin(rootname, "-links.html"); if ((sathumbs = sarrayCreate(0)) == NULL) return ERROR_INT("sathumbs not made", procName, 1); if ((saviews = sarrayCreate(0)) == NULL) return ERROR_INT("saviews not made", procName, 1); /* Generate the thumbs and views */ nfiles = sarrayGetCount(safiles); index = 0; for (i = 0; i < nfiles; i++) { fname = sarrayGetString(safiles, i, L_NOCOPY); fullname = genPathname(dirin, fname); fprintf(stderr, "name: %s\n", fullname); if ((pix = pixRead(fullname)) == NULL) { fprintf(stderr, "file %s not a readable image\n", fullname); FREE(fullname); continue; } FREE(fullname); if (copyorig) { outname = genPathname(dirout, fname); pixWrite(outname, pix, IFF_JFIF_JPEG); FREE(outname); } /* Make and store the thumb */ w = pixGetWidth(pix); factor = (l_float32)thumbwidth / (l_float32)w; if ((pixthumb = pixScale(pix, factor, factor)) == NULL) return ERROR_INT("pixthumb not made", procName, 1); sprintf(charbuf, "%s_thumb_%03d.jpg", rootname, index); sarrayAddString(sathumbs, charbuf, L_COPY); outname = genPathname(dirout, charbuf); pixWrite(outname, pixthumb, IFF_JFIF_JPEG); FREE(outname); pixDestroy(&pixthumb); /* Make and store the view */ factor = (l_float32)viewwidth / (l_float32)w; if (factor >= 1.0) { pixview = pixClone(pix); /* no upscaling */ } else { if ((pixview = pixScale(pix, factor, factor)) == NULL) return ERROR_INT("pixview not made", procName, 1); } sprintf(charbuf, "%s_view_%03d.jpg", rootname, index); sarrayAddString(saviews, charbuf, L_COPY); outname = genPathname(dirout, charbuf); pixWrite(outname, pixview, IFF_JFIF_JPEG); FREE(outname); pixDestroy(&pixview); pixDestroy(&pix); index++; } /* Generate the main html file */ if ((sahtml = sarrayCreate(0)) == NULL) return ERROR_INT("sahtml not made", procName, 1); sarrayAddString(sahtml, htmlstring, L_COPY); sprintf(charbuf, "<frameset cols=\"%d, *\">", thumbwidth + 30); sarrayAddString(sahtml, charbuf, L_COPY); sprintf(charbuf, "<frame name=\"thumbs\" src=\"%s\">", linknameshort); sarrayAddString(sahtml, charbuf, L_COPY); sprintf(charbuf, "<frame name=\"views\" src=\"%s\">", sarrayGetString(saviews, 0, L_NOCOPY)); sarrayAddString(sahtml, charbuf, L_COPY); sarrayAddString(sahtml, framestring, L_COPY); shtml = sarrayToString(sahtml, 1); l_binaryWrite(mainname, "w", shtml, strlen(shtml)); FREE(shtml); FREE(mainname); /* Generate the link html file */ nimages = sarrayGetCount(saviews); fprintf(stderr, "num. images = %d\n", nimages); if ((salink = sarrayCreate(0)) == NULL) return ERROR_INT("salink not made", procName, 1); for (i = 0; i < nimages; i++) { viewfile = sarrayGetString(saviews, i, L_NOCOPY); thumbfile = sarrayGetString(sathumbs, i, L_NOCOPY); sprintf(charbuf, "<a href=\"%s\" TARGET=views><img src=\"%s\"></a>", viewfile, thumbfile); sarrayAddString(salink, charbuf, L_COPY); } slink = sarrayToString(salink, 1); l_binaryWrite(linkname, "w", slink, strlen(slink)); FREE(slink); FREE(linkname); FREE(linknameshort); sarrayDestroy(&safiles); sarrayDestroy(&sathumbs); sarrayDestroy(&saviews); sarrayDestroy(&sahtml); sarrayDestroy(&salink); 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); } } FREE(cmaptab); FREE(rtab); FREE(gtab); FREE(btab); return 0; }
int main(int argc, char **argv) { l_uint8 *array1, *array2; l_int32 i, n1, n2, n3; size_t size1, size2; FILE *fp; BOXA *boxa1, *boxa2; PIX *pixs, *pix1, *pix2, *pix3; PIXA *pixa1; PIXCMAP *cmap; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("feyn.tif"); /* --------------------------------------------------------------- * * Test pixConnComp() and pixCountConnComp(), * * with output to both boxa and pixa * * --------------------------------------------------------------- */ /* First, test with 4-cc */ boxa1= pixConnComp(pixs, &pixa1, 4); n1 = boxaGetCount(boxa1); boxa2= pixConnComp(pixs, NULL, 4); n2 = boxaGetCount(boxa2); pixCountConnComp(pixs, 4, &n3); fprintf(stderr, "Number of 4 c.c.: n1 = %d; n2 = %d, n3 = %d\n", n1, n2, n3); regTestCompareValues(rp, n1, n2, 0); /* 0 */ regTestCompareValues(rp, n1, n3, 0); /* 1 */ regTestCompareValues(rp, n1, 4452, 0); /* 2 */ pix1 = pixaDisplay(pixa1, pixGetWidth(pixs), pixGetHeight(pixs)); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 3 */ regTestComparePix(rp, pixs, pix1); /* 4 */ pixaDestroy(&pixa1); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixDestroy(&pix1); /* Test with 8-cc */ boxa1= pixConnComp(pixs, &pixa1, 8); n1 = boxaGetCount(boxa1); boxa2= pixConnComp(pixs, NULL, 8); n2 = boxaGetCount(boxa2); pixCountConnComp(pixs, 8, &n3); fprintf(stderr, "Number of 8 c.c.: n1 = %d; n2 = %d, n3 = %d\n", n1, n2, n3); regTestCompareValues(rp, n1, n2, 0); /* 5 */ regTestCompareValues(rp, n1, n3, 0); /* 6 */ regTestCompareValues(rp, n1, 4305, 0); /* 7 */ pix1 = pixaDisplay(pixa1, pixGetWidth(pixs), pixGetHeight(pixs)); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 8 */ regTestComparePix(rp, pixs, pix1); /* 9 */ pixaDestroy(&pixa1); boxaDestroy(&boxa1); boxaDestroy(&boxa2); pixDestroy(&pix1); /* --------------------------------------------------------------- * * Test boxa I/O * * --------------------------------------------------------------- */ lept_mkdir("lept/conn"); boxa1 = pixConnComp(pixs, NULL, 4); fp = lept_fopen("/tmp/lept/conn/boxa1.ba", "wb+"); boxaWriteStream(fp, boxa1); lept_fclose(fp); fp = lept_fopen("/tmp/lept/conn/boxa1.ba", "rb"); boxa2 = boxaReadStream(fp); lept_fclose(fp); fp = lept_fopen("/tmp/lept/conn/boxa2.ba", "wb+"); boxaWriteStream(fp, boxa2); lept_fclose(fp); array1 = l_binaryRead("/tmp/lept/conn/boxa1.ba", &size1); array2 = l_binaryRead("/tmp/lept/conn/boxa2.ba", &size2); regTestCompareStrings(rp, array1, size1, array2, size2); /* 10 */ lept_free(array1); lept_free(array2); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* --------------------------------------------------------------- * * Just for fun, display each component as a random color in * * cmapped 8 bpp. Background is color 0; it is set to white. * * --------------------------------------------------------------- */ boxa1 = pixConnComp(pixs, &pixa1, 4); pix1 = pixaDisplayRandomCmap(pixa1, pixGetWidth(pixs), pixGetHeight(pixs)); cmap = pixGetColormap(pix1); pixcmapResetColor(cmap, 0, 255, 255, 255); /* reset background to white */ regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 11 */ if (rp->display) pixDisplay(pix1, 100, 0); boxaDestroy(&boxa1); pixDestroy(&pix1); pixaDestroy(&pixa1); pixDestroy(&pixs); /* --------------------------------------------------------------- * * Test iterative covering of connected components by rectangles * * --------------------------------------------------------------- */ pixa1 = pixaCreate(0); pix1 = pixRead("rabi.png"); pix2 = pixReduceRankBinaryCascade(pix1, 1, 1, 1, 0); regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 12 - */ pixaAddPix(pixa1, pix2, L_INSERT); for (i = 1; i < 6; i++) { pix3 = pixMakeCoveringOfRectangles(pix2, i); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 13 - 17 */ pixaAddPix(pixa1, pix3, L_INSERT); } pix3 = pixaDisplayTiledInRows(pixa1, 1, 2500, 1.0, 0, 30, 0); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 18 */ pixDisplayWithTitle(pix3, 100, 900, NULL, rp->display); pixDestroy(&pix1); pixDestroy(&pix3); pixaDestroy(&pixa1); return regTestCleanup(rp); }