/* static */ void Input::PreparePixInput(const StaticShape& shape, const Pix* pix, TRand* randomizer, NetworkIO* input) { bool color = shape.depth() == 3; Pix* var_pix = const_cast<Pix*>(pix); int depth = pixGetDepth(var_pix); Pix* normed_pix = nullptr; // On input to BaseAPI, an image is forced to be 1, 8 or 24 bit, without // colormap, so we just have to deal with depth conversion here. if (color) { // Force RGB. if (depth == 32) normed_pix = pixClone(var_pix); else normed_pix = pixConvertTo32(var_pix); } else { // Convert non-8-bit images to 8 bit. if (depth == 8) normed_pix = pixClone(var_pix); else normed_pix = pixConvertTo8(var_pix, false); } int width = pixGetWidth(normed_pix); int height = pixGetHeight(normed_pix); int target_height = shape.height(); if (target_height == 1) target_height = shape.depth(); if (target_height == 0) target_height = height; float im_factor = static_cast<float>(target_height) / height; if (im_factor != 1.0f) { // Get the scaled image. Pix* scaled_pix = pixScale(normed_pix, im_factor, im_factor); pixDestroy(&normed_pix); normed_pix = scaled_pix; } input->FromPix(shape, normed_pix, randomizer); pixDestroy(&normed_pix); }
/*! * adjacentOnPixelInRaster() * * Input: pixs (1 bpp) * x, y (current pixel) * xa, ya (adjacent ON pixel, found by simple CCW search) * Return: 1 if a pixel is found; 0 otherwise or on error * * Notes: * (1) Search is in 4-connected directions first; then on diagonals. * This allows traversal along a 4-connected boundary. */ l_int32 adjacentOnPixelInRaster(PIX *pixs, l_int32 x, l_int32 y, l_int32 *pxa, l_int32 *pya) { l_int32 w, h, i, xa, ya, found; l_int32 xdel[] = {-1, 0, 1, 0, -1, 1, 1, -1}; l_int32 ydel[] = {0, 1, 0, -1, 1, 1, -1, -1}; l_uint32 val; PROCNAME("adjacentOnPixelInRaster"); if (!pixs) return ERROR_INT("pixs not defined", procName, 0); if (pixGetDepth(pixs) != 1) return ERROR_INT("pixs not 1 bpp", procName, 0); w = pixGetWidth(pixs); h = pixGetHeight(pixs); found = 0; for (i = 0; i < 8; i++) { xa = x + xdel[i]; ya = y + ydel[i]; if (xa < 0 || xa >= w || ya < 0 || ya >= h) continue; pixGetPixel(pixs, xa, ya, &val); if (val == 1) { found = 1; *pxa = xa; *pya = ya; break; } } return found; }
static PIX * FakeReconstructByBand(L_REGPARAMS *rp, const char *fname) { l_int32 i, jlow, jup, n, nbands; l_int32 rval1, gval1, bval1, rval2, gval2, bval2, rval, gval, bval; PIX *pixs, *pixm, *pixd; PIXCMAP *cmaps, *cmapd; pixs = pixRead(fname); cmaps = pixGetColormap(pixs); n = pixcmapGetCount(cmaps); nbands = (n + 1) / 2; pixd = pixCreateTemplate(pixs); cmapd = pixcmapCreate(pixGetDepth(pixs)); pixSetColormap(pixd, cmapd); for (i = 0; i < nbands; i++) { jlow = 2 * i; jup = L_MIN(jlow + 1, n - 1); pixm = pixGenerateMaskByBand(pixs, jlow, jup, 1, 1); /* Get average color in the band */ pixcmapGetColor(cmaps, jlow, &rval1, &gval1, &bval1); pixcmapGetColor(cmaps, jup, &rval2, &gval2, &bval2); rval = (rval1 + rval2) / 2; gval = (gval1 + gval2) / 2; bval = (bval1 + bval2) / 2; pixcmapAddColor(cmapd, rval, gval, bval); pixSetMaskedCmap(pixd, pixm, 0, 0, rval, gval, bval); pixDestroy(&pixm); } pixDestroy(&pixs); return pixd; }
/*! * pixRotate90() * * Input: pixs (all depths) * direction (1 = clockwise, -1 = counter-clockwise) * Return: pixd, or null on error * * Notes: * (1) This does a 90 degree rotation of the image about the center, * either cw or ccw, returning a new pix. * (2) The direction must be either 1 (cw) or -1 (ccw). */ PIX * pixRotate90(PIX *pixs, l_int32 direction) { l_int32 wd, hd, d, wpls, wpld; l_uint32 *datas, *datad; PIX *pixd; PROCNAME("pixRotate90"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); d = pixGetDepth(pixs); if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) return (PIX *)ERROR_PTR("pixs not in {1,2,4,8,16,32} bpp", procName, NULL); if (direction != 1 && direction != -1) return (PIX *)ERROR_PTR("invalid direction", procName, NULL); hd = pixGetWidth(pixs); wd = pixGetHeight(pixs); if ((pixd = pixCreate(wd, hd, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixCopyColormap(pixd, pixs); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); rotate90Low(datad, wd, hd, d, wpld, datas, wpls, direction); return pixd; }
/*! * pixMultConstAccumulate() * * Input: pixs (32 bpp) * factor * offset (same as used for initialization) * Return: 0 if OK; 1 on error * * Notes: * (1) The offset must be >= 0 and should not exceed 0x40000000. * (2) This multiplies each pixel, relative to offset, by the input factor * (3) The result is returned with the offset back in place. */ l_int32 pixMultConstAccumulate(PIX *pixs, l_float32 factor, l_uint32 offset) { l_int32 w, h, wpl; l_uint32 *data; PROCNAME("pixMultConstAccumulate"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (pixGetDepth(pixs) != 32) return ERROR_INT("pixs not 32 bpp", procName, 1); if (offset > 0x40000000) offset = 0x40000000; pixGetDimensions(pixs, &w, &h, NULL); data = pixGetData(pixs); wpl = pixGetWpl(pixs); multConstAccumulateLow(data, w, h, wpl, factor, offset); return 0; }
/*! * pixRotateBinaryNice() * * Input: pixs (1 bpp) * angle (radians; clockwise is positive; about the center) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * Return: pixd, or null on error * * Notes: * (1) For very small rotations, just return a clone. * (2) This does a computationally expensive rotation of 1 bpp images. * The fastest rotators (using shears or subsampling) leave * visible horizontal and vertical shear lines across which * the image shear changes by one pixel. To ameliorate the * visual effect one can introduce random dithering. One * way to do this in a not-too-random fashion is given here. * We convert to 8 bpp, do a very small blur, rotate using * linear interpolation (same as area mapping), do a * small amount of sharpening to compensate for the initial * blur, and threshold back to binary. The shear lines * are magically removed. * (3) This operation is about 5x slower than rotation by sampling. */ PIX * pixRotateBinaryNice(PIX *pixs, l_float32 angle, l_int32 incolor) { PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixd; PROCNAME("pixRotateBinaryNice"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *) ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *) ERROR_PTR("invalid incolor", procName, NULL); pixt1 = pixConvertTo8(pixs, 0); pixt2 = pixBlockconv(pixt1, 1, 1); /* smallest blur allowed */ pixt3 = pixRotateAM(pixt2, angle, incolor); pixt4 = pixUnsharpMasking(pixt3, 1, 1.0); /* sharpen a bit */ pixd = pixThresholdToBinary(pixt4, 128); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); return pixd; }
/*! * pixVarianceInRectangle() * * Input: pix (8 bpp) * box (region to compute variance and/or root variance) * pix_ma (mean accumulator) * dpix_msa (mean square accumulator) * &var (<optional return> variance) * &rvar (<optional return> root variance) * Return: 0 if OK, 1 on error * * Notes: * (1) This function is intended to be used for many rectangles * on the same image. It can find the variance and/or the * square root of the variance within a rectangle in O(1), * independent of the size of the rectangle. */ l_int32 pixVarianceInRectangle(PIX *pixs, BOX *box, PIX *pix_ma, DPIX *dpix_msa, l_float32 *pvar, l_float32 *prvar) { l_int32 w, h, bx, by, bw, bh; l_uint32 val00, val01, val10, val11; l_float64 dval00, dval01, dval10, dval11, mval, msval, var, norm; BOX *boxc; PROCNAME("pixVarianceInRectangle"); if (!pvar && !prvar) return ERROR_INT("neither &var nor &rvar defined", procName, 1); if (pvar) *pvar = 0.0; if (prvar) *prvar = 0.0; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs not defined", procName, 1); if (!box) return ERROR_INT("box not defined", procName, 1); if (!pix_ma) return ERROR_INT("pix_ma not defined", procName, 1); if (!dpix_msa) return ERROR_INT("dpix_msa not defined", procName, 1); /* Clip rectangle to image */ pixGetDimensions(pixs, &w, &h, NULL); boxc = boxClipToRectangle(box, w, h); boxGetGeometry(boxc, &bx, &by, &bw, &bh); boxDestroy(&boxc); if (bw == 0 || bh == 0) return ERROR_INT("no pixels in box", procName, 1); /* Use up to 4 points in the accumulators */ norm = 1.0 / (bw * bh); if (bx > 0 && by > 0) { pixGetPixel(pix_ma, bx + bw - 1, by + bh - 1, &val11); pixGetPixel(pix_ma, bx + bw - 1, by - 1, &val10); pixGetPixel(pix_ma, bx - 1, by + bh - 1, &val01); pixGetPixel(pix_ma, bx - 1, by - 1, &val00); dpixGetPixel(dpix_msa, bx + bw - 1, by + bh - 1, &dval11); dpixGetPixel(dpix_msa, bx + bw - 1, by - 1, &dval10); dpixGetPixel(dpix_msa, bx - 1, by + bh - 1, &dval01); dpixGetPixel(dpix_msa, bx - 1, by - 1, &dval00); mval = norm * (val11 - val01 + val00 - val10); msval = norm * (dval11 - dval01 + dval00 - dval10); var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } else if (by > 0) { /* bx == 0 */ pixGetPixel(pix_ma, bw - 1, by + bh - 1, &val11); pixGetPixel(pix_ma, bw - 1, by - 1, &val10); dpixGetPixel(dpix_msa, bw - 1, by + bh - 1, &dval11); dpixGetPixel(dpix_msa, bw - 1, by - 1, &dval10); mval = norm * (val11 - val10); msval = norm * (dval11 - dval10); var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } else if (bx > 0) { /* by == 0 */ pixGetPixel(pix_ma, bx + bw - 1, bh - 1, &val11); pixGetPixel(pix_ma, bx - 1, bh - 1, &val01); dpixGetPixel(dpix_msa, bx + bw - 1, bh - 1, &dval11); dpixGetPixel(dpix_msa, bx - 1, bh - 1, &dval01); mval = norm * (val11 - val01); msval = norm * (dval11 - dval01); var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } else { /* bx == 0 && by == 0 */ pixGetPixel(pix_ma, bw - 1, bh - 1, &val11); dpixGetPixel(dpix_msa, bw - 1, bh - 1, &dval11); mval = norm * val11; msval = norm * dval11; var = (msval - mval * mval); if (pvar) *pvar = (l_float32) var; if (prvar) *prvar = (l_float32)(sqrt(var)); } return 0; }
/*! * \brief pixToGif() * * \param[in] pix 1, 2, 4, 8, 16 or 32 bpp * \param[in] gif opened gif stream * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This encodes the pix to the gif stream. The stream is not * closes by this function. * (2) It is static to make this function private. * </pre> */ static l_int32 pixToGif(PIX *pix, GifFileType *gif) { char *text; l_int32 wpl, i, j, w, h, d, ncolor, rval, gval, bval; l_int32 gif_ncolor = 0; l_uint32 *data, *line; PIX *pixd; PIXCMAP *cmap; ColorMapObject *gif_cmap; GifByteType *gif_line; #if (GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) || GIFLIB_MAJOR > 5 int giferr; #endif /* 5.1 and beyond */ PROCNAME("pixToGif"); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (!gif) return ERROR_INT("gif not defined", procName, 1); d = pixGetDepth(pix); if (d == 32) { pixd = pixConvertRGBToColormap(pix, 1); } else if (d > 1) { pixd = pixConvertTo8(pix, TRUE); } else { /* d == 1; make sure there's a colormap */ pixd = pixClone(pix); if (!pixGetColormap(pixd)) { cmap = pixcmapCreate(1); pixcmapAddColor(cmap, 255, 255, 255); pixcmapAddColor(cmap, 0, 0, 0); pixSetColormap(pixd, cmap); } } if (!pixd) return ERROR_INT("failed to convert image to indexed", procName, 1); d = pixGetDepth(pixd); if ((cmap = pixGetColormap(pixd)) == NULL) { pixDestroy(&pixd); return ERROR_INT("cmap is missing", procName, 1); } /* 'Round' the number of gif colors up to a power of 2 */ ncolor = pixcmapGetCount(cmap); for (i = 0; i <= 8; i++) { if ((1 << i) >= ncolor) { gif_ncolor = (1 << i); break; } } if (gif_ncolor < 1) { pixDestroy(&pixd); return ERROR_INT("number of colors is invalid", procName, 1); } /* Save the cmap colors in a gif_cmap */ if ((gif_cmap = GifMakeMapObject(gif_ncolor, NULL)) == NULL) { pixDestroy(&pixd); return ERROR_INT("failed to create GIF color map", procName, 1); } for (i = 0; i < gif_ncolor; i++) { rval = gval = bval = 0; if (ncolor > 0) { if (pixcmapGetColor(cmap, i, &rval, &gval, &bval) != 0) { pixDestroy(&pixd); GifFreeMapObject(gif_cmap); return ERROR_INT("failed to get color from color map", procName, 1); } ncolor--; } gif_cmap->Colors[i].Red = rval; gif_cmap->Colors[i].Green = gval; gif_cmap->Colors[i].Blue = bval; } pixGetDimensions(pixd, &w, &h, NULL); if (EGifPutScreenDesc(gif, w, h, gif_cmap->BitsPerPixel, 0, gif_cmap) != GIF_OK) { pixDestroy(&pixd); GifFreeMapObject(gif_cmap); return ERROR_INT("failed to write screen description", procName, 1); } GifFreeMapObject(gif_cmap); /* not needed after this point */ if (EGifPutImageDesc(gif, 0, 0, w, h, FALSE, NULL) != GIF_OK) { pixDestroy(&pixd); return ERROR_INT("failed to image screen description", procName, 1); } data = pixGetData(pixd); wpl = pixGetWpl(pixd); if (d != 1 && d != 2 && d != 4 && d != 8) { pixDestroy(&pixd); return ERROR_INT("image depth is not in {1, 2, 4, 8}", procName, 1); } if ((gif_line = (GifByteType *)LEPT_CALLOC(sizeof(GifByteType), w)) == NULL) { pixDestroy(&pixd); return ERROR_INT("mem alloc fail for data line", procName, 1); } for (i = 0; i < h; i++) { line = data + i * wpl; /* Gif's way of setting the raster line up for compression */ for (j = 0; j < w; j++) { switch(d) { case 8: gif_line[j] = GET_DATA_BYTE(line, j); break; case 4: gif_line[j] = GET_DATA_QBIT(line, j); break; case 2: gif_line[j] = GET_DATA_DIBIT(line, j); break; case 1: gif_line[j] = GET_DATA_BIT(line, j); break; } } /* Compress and save the line */ if (EGifPutLine(gif, gif_line, w) != GIF_OK) { LEPT_FREE(gif_line); pixDestroy(&pixd); return ERROR_INT("failed to write data line into GIF", procName, 1); } } /* Write a text comment. This must be placed after writing the * data (!!) Note that because libgif does not provide a function * for reading comments from file, you will need another way * to read comments. */ if ((text = pixGetText(pix)) != NULL) { if (EGifPutComment(gif, text) != GIF_OK) L_WARNING("gif comment not written\n", procName); } LEPT_FREE(gif_line); pixDestroy(&pixd); return 0; }
/*! * 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; }
/*! * \brief pixGetLocalSkewTransform() * * \param[in] pixs * \param[in] nslices the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; use 0 for default * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and * not larger than redsweep; use 0 for default value * \param[in] sweeprange half the full range, assumed about 0; in degrees; * use 0.0 for default value * \param[in] sweepdelta angle increment of sweep; in degrees; * use 0.0 for default value * \param[in] minbsdelta min binary search increment angle; in degrees; * use 0.0 for default value * \param[out] pptas 4 points in the source * \param[out] pptad the corresponding 4 pts in the dest * \return 0 if OK, 1 on error * * <pre> * 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. * </pre> */ 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 || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", 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, 0); 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; }
/*! * pixThinExamples() * * Input: pixs (1 bpp) * type (L_THIN_FG, L_THIN_BG) * index (into specific examples; valid 1-9; see notes) * maxiters (max number of iters allowed; use 0 to iterate * until completion) * selfile (<optional> filename for output sel display) * Return: pixd, or null on error * * Notes: * (1) See notes in pixThin(). The examples are taken from * the paper referenced there. * (2) Here we allow specific sets of HMTs to be used in * parallel for thinning from each of four directions. * One iteration consists of four such parallel thins. * (3) The examples are indexed as follows: * Thinning (e.g., run to completion): * index = 1 sel_4_1, sel_4_5, sel_4_6 * index = 2 sel_4_1, sel_4_7, sel_4_7_rot * index = 3 sel_48_1, sel_48_1_rot, sel_48_2 * index = 4 sel_8_2, sel_8_3, sel_48_2 * index = 5 sel_8_1, sel_8_5, sel_8_6 * index = 6 sel_8_2, sel_8_3, sel_8_8, sel_8_9 * index = 7 sel_8_5, sel_8_6, sel_8_7, sel_8_7_rot * Thickening: * index = 8 sel_4_2, sel_4_3 (e.g,, do just a few iterations) * index = 9 sel_8_4 (e.g., do just a few iterations) */ PIX * pixThinExamples(PIX *pixs, l_int32 type, l_int32 index, l_int32 maxiters, const char *selfile) { PIX *pixd, *pixt; SEL *sel; SELA *sela; PROCNAME("pixThinExamples"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (type != L_THIN_FG && type != L_THIN_BG) return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); if (index < 1 || index > 9) return (PIX *)ERROR_PTR("invalid index", procName, NULL); if (maxiters == 0) maxiters = 10000; switch(index) { case 1: sela = selaCreate(3); sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_5, 3, 3, "sel_4_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_6, 3, 3, "sel_4_6"); selaAddSel(sela, sel, NULL, 0); break; case 2: sela = selaCreate(3); sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_7, 3, 3, "sel_4_7"); selaAddSel(sela, sel, NULL, 0); sel = selRotateOrth(sel, 1); selaAddSel(sela, sel, "sel_4_7_rot", 0); break; case 3: sela = selaCreate(3); sel = selCreateFromString(sel_48_1, 3, 3, "sel_48_1"); selaAddSel(sela, sel, NULL, 0); sel = selRotateOrth(sel, 1); selaAddSel(sela, sel, "sel_48_1_rot", 0); sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); selaAddSel(sela, sel, NULL, 0); break; case 4: sela = selaCreate(3); sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2"); selaAddSel(sela, sel, NULL, 0); break; case 5: sela = selaCreate(3); sel = selCreateFromString(sel_8_1, 3, 3, "sel_8_1"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); selaAddSel(sela, sel, NULL, 0); break; case 6: sela = selaCreate(4); sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_8, 3, 3, "sel_8_8"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_9, 3, 3, "sel_8_9"); selaAddSel(sela, sel, NULL, 0); break; case 7: sela = selaCreate(4); sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_8_7, 3, 3, "sel_8_7"); selaAddSel(sela, sel, NULL, 0); sel = selRotateOrth(sel, 1); selaAddSel(sela, sel, "sel_8_7_rot", 0); break; case 8: /* thicken for this one; just a few iterations */ sela = selaCreate(2); sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2"); selaAddSel(sela, sel, NULL, 0); sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3"); selaAddSel(sela, sel, NULL, 0); pixt = pixThinGeneral(pixs, type, sela, maxiters); pixd = pixRemoveBorderConnComps(pixt, 4); pixDestroy(&pixt); break; case 9: /* thicken for this one; just a few iterations */ sela = selaCreate(1); sel = selCreateFromString(sel_8_4, 3, 3, "sel_8_4"); selaAddSel(sela, sel, NULL, 0); pixt = pixThinGeneral(pixs, type, sela, maxiters); pixd = pixRemoveBorderConnComps(pixt, 4); pixDestroy(&pixt); break; default: return (PIX *)ERROR_PTR("invalid index", procName, NULL); } if (index <= 7) pixd = pixThinGeneral(pixs, type, sela, maxiters); /* Optionally display the sels */ if (selfile) { pixt = selaDisplayInPix(sela, 35, 3, 15, 4); pixWrite(selfile, pixt, IFF_PNG); pixDestroy(&pixt); } selaDestroy(&sela); return pixd; }
int main(int argc, char **argv) { PIX *pixs; char *filein, *fileout, *base, *ext; const char *formatstr; l_int32 format; l_int32 d; static char mainName[] = "convertformat"; if (argc != 3 && argc != 4) { fprintf(stderr, "Syntax: convertformat filein fileout [format]\n" "If you don't specify a format, the output file\n" "needs one of these seven extensions:\n" " bmp, jpg, png, tif, pnm, gif, webp\n"); return 1; } filein = argv[1]; fileout = argv[2]; if (argc == 3) { splitPathAtExtension(fileout, NULL, &ext); if (!strcmp(ext, ".bmp")) format = IFF_BMP; else if (!strcmp(ext, ".jpg")) format = IFF_JFIF_JPEG; else if (!strcmp(ext, ".png")) format = IFF_PNG; else if (!strcmp(ext, ".tif")) /* requesting g4-tiff binary comp */ format = IFF_TIFF_G4; else if (!strcmp(ext, ".pnm")) format = IFF_PNM; else if (!strcmp(ext, ".gif")) format = IFF_GIF; else if (!strcmp(ext, ".webp")) format = IFF_WEBP; else { return ERROR_INT( "Valid extensions: bmp, jpg, png, tif, pnm, gif, webp", mainName, 1); } lept_free(ext); } else { formatstr = argv[3]; if (!strcmp(formatstr, "BMP")) format = IFF_BMP; else if (!strcmp(formatstr, "JPEG")) format = IFF_JFIF_JPEG; else if (!strcmp(formatstr, "PNG")) format = IFF_PNG; else if (!strcmp(formatstr, "TIFF")) format = IFF_TIFF_G4; else if (!strcmp(formatstr, "PNM")) format = IFF_PNM; else if (!strcmp(formatstr, "GIF")) format = IFF_GIF; else if (!strcmp(formatstr, "WEBP")) format = IFF_WEBP; else { return ERROR_INT( "Valid formats: BMP, JPEG, PNG, TIFF, PNM, GIF, WEBP", mainName, 1); } } if ((pixs = pixRead(filein)) == NULL) { L_ERROR("read fail for %s\n", mainName, filein); return 1; } d = pixGetDepth(pixs); if (d != 1 && format == IFF_TIFF_G4) { L_WARNING("can't convert to tiff_g4; converting to png\n", mainName); format = IFF_PNG; } if (d < 8 && format == IFF_JFIF_JPEG) { L_WARNING("can't convert to jpeg; converting to png\n", mainName); splitPathAtExtension(fileout, &base, &ext); fileout = stringJoin(base, ".png"); format = IFF_PNG; } if (d < 8 && format == IFF_WEBP) { L_WARNING("can't convert to webp; converting to png\n", mainName); splitPathAtExtension(fileout, &base, &ext); fileout = stringJoin(base, ".png"); format = IFF_PNG; } pixWrite(fileout, pixs, format); return 0; }
/*! * pixRunlengthTransform() * * Input: pixs (1 bpp) * color (0 for white runs, 1 for black runs) * direction (L_HORIZONTAL_RUNS, L_VERTICAL_RUNS) * depth (8 or 16 bpp) * Return: pixd (8 or 16 bpp), or null on error * * Notes: * (1) The dest Pix is 8 or 16 bpp, with the pixel values * equal to the runlength in which it is a member. * The length is clipped to the max pixel value if necessary. * (2) The color determines if we're labelling white or black runs. * (3) A pixel that is not a member of the chosen color gets * value 0; it belongs to a run of length 0 of the * chosen color. * (4) To convert for maximum dynamic range, either linear or * log, use pixMaxDynamicRange(). */ PIX * pixRunlengthTransform(PIX *pixs, l_int32 color, l_int32 direction, l_int32 depth) { l_int32 i, j, w, h, wpld, bufsize, maxsize, n; l_int32 *start, *end, *buffer; l_uint32 *datad, *lined; PIX *pixt, *pixd; PROCNAME("pixRunlengthTransform"); if (!pixs) return (PIX *) ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *) ERROR_PTR("pixs not 1 bpp", procName, NULL); if (depth != 8 && depth != 16) return (PIX *) ERROR_PTR("depth must be 8 or 16 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); if (direction == L_HORIZONTAL_RUNS) maxsize = 1 + w / 2; else if (direction == L_VERTICAL_RUNS) maxsize = 1 + h / 2; else return (PIX *) ERROR_PTR("invalid direction", procName, NULL); bufsize = L_MAX(w, h); if ((pixd = pixCreate(w, h, depth)) == NULL) return (PIX *) ERROR_PTR("pixd not made", procName, NULL); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); if ((start = (l_int32 *) CALLOC(maxsize, sizeof(l_int32))) == NULL) return (PIX *) ERROR_PTR("start not made", procName, NULL); if ((end = (l_int32 *) CALLOC(maxsize, sizeof(l_int32))) == NULL) return (PIX *) ERROR_PTR("end not made", procName, NULL); if ((buffer = (l_int32 *) CALLOC(bufsize, sizeof(l_int32))) == NULL) return (PIX *) ERROR_PTR("buffer not made", procName, NULL); /* Use fg runs for evaluation */ if (color == 0) pixt = pixInvert(NULL, pixs); else pixt = pixClone(pixs); if (direction == L_HORIZONTAL_RUNS) { for (i = 0; i < h; i++) { pixFindHorizontalRuns(pixt, i, start, end, &n); runlengthMembershipOnLine(buffer, w, depth, start, end, n); lined = datad + i * wpld; if (depth == 8) { for (j = 0; j < w; j++) SET_DATA_BYTE(lined, j, buffer[j]); } else { /* depth == 16 */ for (j = 0; j < w; j++) SET_DATA_TWO_BYTES(lined, j, buffer[j]); } } } else { /* L_VERTICAL_RUNS */ for (j = 0; j < w; j++) { pixFindVerticalRuns(pixt, j, start, end, &n); runlengthMembershipOnLine(buffer, h, depth, start, end, n); if (depth == 8) { for (i = 0; i < h; i++) { lined = datad + i * wpld; SET_DATA_BYTE(lined, j, buffer[i]); } } else { /* depth == 16 */ for (i = 0; i < h; i++) { lined = datad + i * wpld; SET_DATA_TWO_BYTES(lined, j, buffer[i]); } } } } pixDestroy(&pixt); FREE(start); FREE(end); FREE(buffer); return pixd; }
int main(int argc, char **argv) { char *filein, *fileout, *fname; char buffer[512]; const char *psfile = "/tmp/junk_split_image.ps"; l_int32 nx, ny, i, w, h, d, ws, hs, n, res, ignore; l_float32 scale; PIX *pixs, *pixt, *pixr; PIXA *pixa; static char mainName[] = "splitimage2pdf"; if (argc != 5) return ERROR_INT(" Syntax: splitimage2pdf filein nx ny fileout", mainName, 1); filein = argv[1]; nx = atoi(argv[2]); ny = atoi(argv[3]); fileout = argv[4]; lept_rm(NULL, "junk_split_image.ps"); if ((pixs = pixRead(filein)) == NULL) return ERROR_INT("pixs not made", mainName, 1); d = pixGetDepth(pixs); if (d == 1 ) lept_rm(NULL, "junk_split_image.tif"); else if (d == 8 || d == 32) lept_rm(NULL, "junk_split_image.jpg"); else return ERROR_INT("d not in {1,8,32} bpp", mainName, 1); pixGetDimensions(pixs, &ws, &hs, NULL); if (ny * ws > nx * hs) pixr = pixRotate90(pixs, 1); else pixr = pixClone(pixs); pixa = pixaSplitPix(pixr, nx, ny, 0, 0); n = pixaGetCount(pixa); res = 300; for (i = 0; i < n; i++) { pixt = pixaGetPix(pixa, i, L_CLONE); pixGetDimensions(pixt, &w, &h, NULL); scale = L_MIN(FILL_FACTOR * 2550 / w, FILL_FACTOR * 3300 / h); fname = NULL; if (d == 1) { fname = genPathname("/tmp", "junk_split_image.tif"); pixWrite(fname, pixt, IFF_TIFF_G4); if (i == 0) { convertG4ToPS(fname, psfile, "w", 0, 0, 300, scale, 1, FALSE, TRUE); } else { convertG4ToPS(fname, psfile, "a", 0, 0, 300, scale, 1, FALSE, TRUE); } } else { fname = genPathname("/tmp", "junk_split_image.jpg"); pixWrite(fname, pixt, IFF_JFIF_JPEG); if (i == 0) { convertJpegToPS(fname, psfile, "w", 0, 0, 300, scale, 1, TRUE); } else { convertJpegToPS(fname, psfile, "a", 0, 0, 300, scale, 1, TRUE); } } lept_free(fname); pixDestroy(&pixt); } snprintf(buffer, sizeof(buffer), "ps2pdf %s %s", psfile, fileout); ignore = system(buffer); /* ps2pdf */ pixaDestroy(&pixa); pixDestroy(&pixr); pixDestroy(&pixs); return 0; }
main(int argc, char **argv) { #if HAVE_FMEMOPEN char psname[256]; #endif /* HAVE_FMEMOPEN */ char *tempname; l_uint8 *data; l_int32 i, d, n, success, failure, same; l_int32 w, h, bps, spp; size_t size, nbytes; PIX *pix1, *pix2, *pix4, *pix8, *pix16, *pix32; PIX *pix, *pixt, *pixd; PIXA *pixa; L_REGPARAMS *rp; #if !HAVE_LIBJPEG fprintf(stderr, "Omitting libjpeg tests in ioformats_reg\n"); #endif /* !HAVE_LIBJPEG */ #if !HAVE_LIBTIFF fprintf(stderr, "Omitting libtiff tests in ioformats_reg\n"); #endif /* !HAVE_LIBTIFF */ #if !HAVE_LIBPNG || !HAVE_LIBZ fprintf(stderr, "Omitting libpng tests in ioformats_reg\n"); #endif /* !HAVE_LIBPNG || !HAVE_LIBZ */ if (regTestSetup(argc, argv, &rp)) return 1; /* --------- Part 1: Test all lossless formats for r/w to file ---------*/ failure = FALSE; success = TRUE; fprintf(stderr, "Test bmp 1 bpp file:\n"); if (ioFormatTest(BMP_FILE)) success = FALSE; #if HAVE_LIBTIFF fprintf(stderr, "\nTest other 1 bpp file:\n"); if (ioFormatTest(FILE_1BPP)) success = FALSE; #endif /* HAVE_LIBTIFF */ #if HAVE_LIBPNG fprintf(stderr, "\nTest 2 bpp file:\n"); if (ioFormatTest(FILE_2BPP)) success = FALSE; fprintf(stderr, "\nTest 2 bpp file with cmap:\n"); if (ioFormatTest(FILE_2BPP_C)) success = FALSE; fprintf(stderr, "\nTest 4 bpp file:\n"); if (ioFormatTest(FILE_4BPP)) success = FALSE; fprintf(stderr, "\nTest 4 bpp file with cmap:\n"); if (ioFormatTest(FILE_4BPP_C)) success = FALSE; fprintf(stderr, "\nTest 8 bpp grayscale file with cmap:\n"); if (ioFormatTest(FILE_8BPP_1)) success = FALSE; fprintf(stderr, "\nTest 8 bpp color file with cmap:\n"); if (ioFormatTest(FILE_8BPP_2)) success = FALSE; #endif /* HAVE_LIBPNG */ #if HAVE_LIBJPEG fprintf(stderr, "\nTest 8 bpp file without cmap:\n"); if (ioFormatTest(FILE_8BPP_3)) success = FALSE; #endif /* HAVE_LIBJPEG */ #if HAVE_LIBTIFF fprintf(stderr, "\nTest 16 bpp file:\n"); if (ioFormatTest(FILE_16BPP)) success = FALSE; #endif /* HAVE_LIBTIFF */ #if HAVE_LIBJPEG fprintf(stderr, "\nTest 32 bpp file:\n"); if (ioFormatTest(FILE_32BPP)) success = FALSE; #endif /* HAVE_LIBJPEG */ if (success) fprintf(stderr, "\n ********** Success on all i/o format tests *********\n"); else fprintf(stderr, "\n ******* Failure on at least one i/o format test ******\n"); if (!success) failure = TRUE; /* ------------------ Part 2: Test tiff r/w to file ------------------- */ #if !HAVE_LIBTIFF goto part6; #endif /* !HAVE_LIBTIFF */ fprintf(stderr, "\nTest tiff r/w and format extraction\n"); pixa = pixaCreate(6); pix1 = pixRead(BMP_FILE); pix2 = pixConvert1To2(NULL, pix1, 3, 0); pix4 = pixConvert1To4(NULL, pix1, 15, 0); pix16 = pixRead(FILE_16BPP); fprintf(stderr, "Input format: %d\n", pixGetInputFormat(pix16)); pix8 = pixConvert16To8(pix16, 1); pix32 = pixRead(FILE_32BPP); pixaAddPix(pixa, pix1, L_INSERT); pixaAddPix(pixa, pix2, L_INSERT); pixaAddPix(pixa, pix4, L_INSERT); pixaAddPix(pixa, pix8, L_INSERT); pixaAddPix(pixa, pix16, L_INSERT); pixaAddPix(pixa, pix32, L_INSERT); n = pixaGetCount(pixa); success = TRUE; for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); d = pixGetDepth(pix); fprintf(stderr, "%d bpp\n", d); if (i == 0) { /* 1 bpp */ pixWrite("/tmp/junkg3.tif", pix, IFF_TIFF_G3); pixWrite("/tmp/junkg4.tif", pix, IFF_TIFF_G4); pixWrite("/tmp/junkrle.tif", pix, IFF_TIFF_RLE); pixWrite("/tmp/junkpb.tif", pix, IFF_TIFF_PACKBITS); if (testcomp("/tmp/junkg3.tif", pix, IFF_TIFF_G3)) success = FALSE; if (testcomp("/tmp/junkg4.tif", pix, IFF_TIFF_G4)) success = FALSE; if (testcomp("/tmp/junkrle.tif", pix, IFF_TIFF_RLE)) success = FALSE; if (testcomp("/tmp/junkpb.tif", pix, IFF_TIFF_PACKBITS)) success = FALSE; } pixWrite("/tmp/junklzw.tif", pix, IFF_TIFF_LZW); pixWrite("/tmp/junkzip.tif", pix, IFF_TIFF_ZIP); pixWrite("/tmp/junknon.tif", pix, IFF_TIFF); if (testcomp("/tmp/junklzw.tif", pix, IFF_TIFF_LZW)) success = FALSE; if (testcomp("/tmp/junkzip.tif", pix, IFF_TIFF_ZIP)) success = FALSE; if (testcomp("/tmp/junknon.tif", pix, IFF_TIFF)) success = FALSE; pixDestroy(&pix); } if (success) fprintf(stderr, "\n ********** Success on tiff r/w to file *********\n\n"); else fprintf(stderr, "\n ******* Failure on at least one tiff r/w to file ******\n\n"); if (!success) failure = TRUE; /* ------------------ Part 3: Test tiff r/w to memory ----------------- */ success = TRUE; for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); d = pixGetDepth(pix); fprintf(stderr, "%d bpp\n", d); if (i == 0) { /* 1 bpp */ pixWriteMemTiff(&data, &size, pix, IFF_TIFF_G3); nbytes = nbytesInFile("/tmp/junkg3.tif"); fprintf(stderr, "nbytes = %ld, size = %ld\n", nbytes, size); pixt = pixReadMemTiff(data, size, 0); if (testcomp_mem(pix, &pixt, i, IFF_TIFF_G3)) success = FALSE; lept_free(data); pixWriteMemTiff(&data, &size, pix, IFF_TIFF_G4); nbytes = nbytesInFile("/tmp/junkg4.tif"); fprintf(stderr, "nbytes = %ld, size = %ld\n", nbytes, size); pixt = pixReadMemTiff(data, size, 0); if (testcomp_mem(pix, &pixt, i, IFF_TIFF_G4)) success = FALSE; readHeaderMemTiff(data, size, 0, &w, &h, &bps, &spp, NULL, NULL, NULL); fprintf(stderr, "(w,h,bps,spp) = (%d,%d,%d,%d)\n", w, h, bps, spp); lept_free(data); pixWriteMemTiff(&data, &size, pix, IFF_TIFF_RLE); nbytes = nbytesInFile("/tmp/junkrle.tif"); fprintf(stderr, "nbytes = %ld, size = %ld\n", nbytes, size); pixt = pixReadMemTiff(data, size, 0); if (testcomp_mem(pix, &pixt, i, IFF_TIFF_RLE)) success = FALSE; lept_free(data); pixWriteMemTiff(&data, &size, pix, IFF_TIFF_PACKBITS); nbytes = nbytesInFile("/tmp/junkpb.tif"); fprintf(stderr, "nbytes = %ld, size = %ld\n", nbytes, size); pixt = pixReadMemTiff(data, size, 0); if (testcomp_mem(pix, &pixt, i, IFF_TIFF_PACKBITS)) success = FALSE; lept_free(data); } pixWriteMemTiff(&data, &size, pix, IFF_TIFF_LZW); pixt = pixReadMemTiff(data, size, 0); if (testcomp_mem(pix, &pixt, i, IFF_TIFF_LZW)) success = FALSE; lept_free(data); pixWriteMemTiff(&data, &size, pix, IFF_TIFF_ZIP); pixt = pixReadMemTiff(data, size, 0); if (testcomp_mem(pix, &pixt, i, IFF_TIFF_ZIP)) success = FALSE; readHeaderMemTiff(data, size, 0, &w, &h, &bps, &spp, NULL, NULL, NULL); fprintf(stderr, "(w,h,bps,spp) = (%d,%d,%d,%d)\n", w, h, bps, spp); lept_free(data); pixWriteMemTiff(&data, &size, pix, IFF_TIFF); pixt = pixReadMemTiff(data, size, 0); if (testcomp_mem(pix, &pixt, i, IFF_TIFF)) success = FALSE; lept_free(data); pixDestroy(&pix); } if (success) fprintf(stderr, "\n ********** Success on tiff r/w to memory *********\n\n"); else fprintf(stderr, "\n ******* Failure on at least one tiff r/w to memory ******\n\n"); if (!success) failure = TRUE; /* ---------------- Part 4: Test non-tiff r/w to memory ---------------- */ #if HAVE_FMEMOPEN pixDisplayWrite(NULL, -1); success = TRUE; for (i = 0; i < n; i++) { pix = pixaGetPix(pixa, i, L_CLONE); d = pixGetDepth(pix); sprintf(psname, "/tmp/junkps.%d", d); fprintf(stderr, "%d bpp\n", d); if (d != 16) { if (test_writemem(pix, IFF_PNG, NULL)) success = FALSE; if (test_writemem(pix, IFF_BMP, NULL)) success = FALSE; } if (test_writemem(pix, IFF_PNM, NULL)) success = FALSE; if (test_writemem(pix, IFF_PS, psname)) success = FALSE; if (d == 8 || d == 32) if (test_writemem(pix, IFF_JFIF_JPEG, NULL)) success = FALSE; pixDestroy(&pix); } if (success) fprintf(stderr, "\n ********** Success on non-tiff r/w to memory *********\n\n"); else fprintf(stderr, "\n **** Failure on at least one non-tiff r/w to memory *****\n\n"); if (!success) failure = TRUE; #else fprintf(stderr, "\n ***** Non-tiff r/w to memory not enabled *****\n\n"); #endif /* HAVE_FMEMOPEN */ pixaDestroy(&pixa); /* ------------ Part 5: Test multipage tiff r/w to memory ------------ */ /* Make a multipage tiff file, and read it back into memory */ success = TRUE; pix = pixRead("feyn.tif"); pixa = pixaSplitPix(pix, 3, 3, 0, 0); for (i = 0; i < 9; i++) { pixt = pixaGetPix(pixa, i, L_CLONE); if (i == 0) pixWriteTiff("/tmp/junktiffmpage.tif", pixt, IFF_TIFF_G4, "w"); else pixWriteTiff("/tmp/junktiffmpage.tif", pixt, IFF_TIFF_G4, "a"); pixDestroy(&pixt); } data = l_binaryRead("/tmp/junktiffmpage.tif", &nbytes); pixaDestroy(&pixa); /* Read the individual pages from memory to a pix */ pixa = pixaCreate(9); for (i = 0; i < 9; i++) { pixt = pixReadMemTiff(data, nbytes, i); pixaAddPix(pixa, pixt, L_INSERT); } lept_free(data); /* Un-tile the pix in the pixa back to the original image */ pixt = pixaDisplayUnsplit(pixa, 3, 3, 0, 0); pixaDestroy(&pixa); /* Clip to foreground to remove any extra rows or columns */ pixClipToForeground(pix, &pix1, NULL); pixClipToForeground(pixt, &pix2, NULL); pixEqual(pix1, pix2, &same); if (same) fprintf(stderr, "\n ******* Success on tiff multipage read from memory ******\n\n"); else fprintf(stderr, "\n ******* Failure on tiff multipage read from memory ******\n\n"); if (!same) failure = TRUE; pixDestroy(&pix); pixDestroy(&pixt); pixDestroy(&pix1); pixDestroy(&pix2); /* ------------ Part 6: Test 24 bpp writing ------------ */ #if !HAVE_LIBTIFF part6: #endif /* !HAVE_LIBTIFF */ #if !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF goto finish; #endif /* !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF */ /* Generate a 24 bpp (not 32 bpp !!) rgb pix and write it out */ success = TRUE; pix = pixRead("marge.jpg"); pixt = make_24_bpp_pix(pix); pixWrite("/tmp/junk24.png", pixt, IFF_PNG); pixWrite("/tmp/junk24.jpg", pixt, IFF_JFIF_JPEG); pixWrite("/tmp/junk24.tif", pixt, IFF_TIFF); pixd = pixRead("/tmp/junk24.png"); pixEqual(pix, pixd, &same); if (!same) success = FALSE; pixDestroy(&pixd); pixd = pixRead("/tmp/junk24.jpg"); regTestCompareSimilarPix(rp, pix, pixd, 10, 0.0002, 0); pixDestroy(&pixd); pixd = pixRead("/tmp/junk24.tif"); pixEqual(pix, pixd, &same); if (!same) success = FALSE; pixDestroy(&pixd); if (success) fprintf(stderr, "\n ******* Success on 24 bpp rgb writing *******\n\n"); else fprintf(stderr, "\n ******* Failure on 24 bpp rgb writing *******\n\n"); if (!success) failure = TRUE; pixDestroy(&pix); pixDestroy(&pixt); /* -------------- Part 7: Read header information -------------- */ success = TRUE; if (get_header_data(FILE_1BPP, IFF_TIFF_G4)) success = FALSE; if (get_header_data(FILE_2BPP, IFF_PNG)) success = FALSE; if (get_header_data(FILE_2BPP_C, IFF_PNG)) success = FALSE; if (get_header_data(FILE_4BPP, IFF_PNG)) success = FALSE; if (get_header_data(FILE_4BPP_C, IFF_PNG)) success = FALSE; if (get_header_data(FILE_8BPP_1, IFF_PNG)) success = FALSE; if (get_header_data(FILE_8BPP_2, IFF_PNG)) success = FALSE; if (get_header_data(FILE_8BPP_3, IFF_JFIF_JPEG)) success = FALSE; if (get_header_data(FILE_16BPP, IFF_TIFF_ZIP)) success = FALSE; if (get_header_data(FILE_32BPP, IFF_JFIF_JPEG)) success = FALSE; #if HAVE_FMEMOPEN pix = pixRead(FILE_8BPP_1); tempname = genTempFilename((const char *)"/tmp", (const char *)".pnm", 1, 1); pixWrite(tempname, pix, IFF_PNM); if (get_header_data(tempname, IFF_PNM)) success = FALSE; pixDestroy(&pix); lept_free(tempname); #endif /* HAVE_FMEMOPEN */ pix = pixRead(FILE_1BPP); tempname = genTempFilename((const char *)"/tmp", (const char *)".tif", 1, 1); pixWrite(tempname, pix, IFF_TIFF_G3); if (get_header_data(tempname, IFF_TIFF_G3)) success = FALSE; pixWrite(tempname, pix, IFF_TIFF_G4); if (get_header_data(tempname, IFF_TIFF_G4)) success = FALSE; pixWrite(tempname, pix, IFF_TIFF_PACKBITS); if (get_header_data(tempname, IFF_TIFF_PACKBITS)) success = FALSE; pixWrite(tempname, pix, IFF_TIFF_RLE); if (get_header_data(tempname, IFF_TIFF_RLE)) success = FALSE; pixWrite(tempname, pix, IFF_TIFF_LZW); if (get_header_data(tempname, IFF_TIFF_LZW)) success = FALSE; pixWrite(tempname, pix, IFF_TIFF_ZIP); if (get_header_data(tempname, IFF_TIFF_ZIP)) success = FALSE; pixWrite(tempname, pix, IFF_TIFF); if (get_header_data(tempname, IFF_TIFF)) success = FALSE; pixDestroy(&pix); lept_free(tempname); if (success) fprintf(stderr, "\n ******* Success on reading headers *******\n\n"); else fprintf(stderr, "\n ******* Failure on reading headers *******\n\n"); if (!success) failure = TRUE; #if !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF finish: #endif /* !HAVE_LIBPNG || !HAVE_LIBJPEG || !HAVE_LIBTIFF */ if (!failure) fprintf(stderr, " ******* Success on all tests *******\n\n"); else fprintf(stderr, " ******* Failure on at least one test *******\n\n"); return regTestCleanup(rp); }
/*! * pixDisplayWithTitle() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * x, y (location of display frame) * title (<optional> on frame; can be NULL); * dispflag (1 to write, else disabled) * Return: 0 if OK; 1 on error * * Notes: * (1) See notes for pixDisplay(). * (2) This displays the image if dispflag == 1. */ l_int32 pixDisplayWithTitle(PIX *pixs, l_int32 x, l_int32 y, const char *title, l_int32 dispflag) { char *tempname; char buffer[L_BUF_SIZE]; static l_int32 index = 0; /* caution: not .so or thread safe */ l_int32 w, h, d, spp, maxheight, opaque, threeviews, ignore; l_float32 ratw, rath, ratmin; PIX *pix0, *pix1, *pix2; PIXCMAP *cmap; #ifndef _WIN32 l_int32 wt, ht; #else char *pathname; char fullpath[_MAX_PATH]; #endif /* _WIN32 */ PROCNAME("pixDisplayWithTitle"); if (dispflag != 1) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (var_DISPLAY_PROG != L_DISPLAY_WITH_XZGV && var_DISPLAY_PROG != L_DISPLAY_WITH_XLI && var_DISPLAY_PROG != L_DISPLAY_WITH_XV && var_DISPLAY_PROG != L_DISPLAY_WITH_IV && var_DISPLAY_PROG != L_DISPLAY_WITH_OPEN) { return ERROR_INT("no program chosen for display", procName, 1); } /* Display with three views if either spp = 4 or if colormapped * and the alpha component is not fully opaque */ opaque = TRUE; if ((cmap = pixGetColormap(pixs)) != NULL) pixcmapIsOpaque(cmap, &opaque); spp = pixGetSpp(pixs); threeviews = (spp == 4 || !opaque) ? TRUE : FALSE; /* If colormapped and not opaque, remove the colormap to RGBA */ if (!opaque) pix0 = pixRemoveColormap(pixs, REMOVE_CMAP_WITH_ALPHA); else pix0 = pixClone(pixs); /* Scale if necessary; this will also remove a colormap */ pixGetDimensions(pix0, &w, &h, &d); maxheight = (threeviews) ? MAX_DISPLAY_HEIGHT / 3 : MAX_DISPLAY_HEIGHT; if (w <= MAX_DISPLAY_WIDTH && h <= maxheight) { if (d == 16) /* take MSB */ pix1 = pixConvert16To8(pix0, 1); else pix1 = pixClone(pix0); } else { ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w; rath = (l_float32)maxheight / (l_float32)h; ratmin = L_MIN(ratw, rath); if (ratmin < 0.125 && d == 1) pix1 = pixScaleToGray8(pix0); else if (ratmin < 0.25 && d == 1) pix1 = pixScaleToGray4(pix0); else if (ratmin < 0.33 && d == 1) pix1 = pixScaleToGray3(pix0); else if (ratmin < 0.5 && d == 1) pix1 = pixScaleToGray2(pix0); else pix1 = pixScale(pix0, ratmin, ratmin); } pixDestroy(&pix0); if (!pix1) return ERROR_INT("pix1 not made", procName, 1); /* Generate the three views if required */ if (threeviews) pix2 = pixDisplayLayersRGBA(pix1, 0xffffff00, 0); else pix2 = pixClone(pix1); if (index == 0) { lept_rmdir("disp"); lept_mkdir("disp"); } index++; if (pixGetDepth(pix2) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.png", index); pixWrite(buffer, pix2, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.jpg", index); pixWrite(buffer, pix2, IFF_JFIF_JPEG); } tempname = stringNew(buffer); #ifndef _WIN32 /* Unix */ if (var_DISPLAY_PROG == L_DISPLAY_WITH_XZGV) { /* no way to display title */ pixGetDimensions(pix2, &wt, &ht, NULL); snprintf(buffer, L_BUF_SIZE, "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10, x, y, tempname); } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XLI) { if (title) { snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d -title \"%s\" %s &", x, y, title, tempname); } else { snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d %s &", x, y, tempname); } } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XV) { if (title) { snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d -name \"%s\" %s &", x, y, title, tempname); } else { snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d %s &", x, y, tempname); } } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_OPEN) { snprintf(buffer, L_BUF_SIZE, "open %s &", tempname); } ignore = system(buffer); #else /* _WIN32 */ /* Windows: L_DISPLAY_WITH_IV */ pathname = genPathname(tempname, NULL); _fullpath(fullpath, pathname, sizeof(fullpath)); if (title) { snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"", fullpath, x, y, title); } else { snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d)", fullpath, x, y); } ignore = system(buffer); FREE(pathname); #endif /* _WIN32 */ pixDestroy(&pix1); pixDestroy(&pix2); FREE(tempname); return 0; }
/*! * pixDisplayWriteFormat() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * reduction (-1 to reset/erase; 0 to disable; * otherwise this is a reduction factor) * format (IFF_PNG or IFF_JFIF_JPEG) * Return: 0 if OK; 1 on error * * Notes: * (1) This writes files if reduction > 0. These can be displayed using * pixDisplayMultiple("/tmp/display/file*"); * (2) All previously written files can be erased by calling with * reduction < 0; the value of pixs is ignored. * (3) If reduction > 1 and depth == 1, this does a scale-to-gray * reduction. * (4) This function uses a static internal variable to number * output files written by a single process. Behavior * with a shared library may be unpredictable. * (5) Output file format is as follows: * format == IFF_JFIF_JPEG: * png if d < 8 or d == 16 or if the output pix * has a colormap. Otherwise, output is jpg. * format == IFF_PNG: * png (lossless) on all images. * (6) For 16 bpp, the choice of full dynamic range with log scale * is the best for displaying these images. Alternative outputs are * pix8 = pixMaxDynamicRange(pixt, L_LINEAR_SCALE); * pix8 = pixConvert16To8(pixt, 0); // low order byte * pix8 = pixConvert16To8(pixt, 1); // high order byte */ l_int32 pixDisplayWriteFormat(PIX *pixs, l_int32 reduction, l_int32 format) { char buf[L_BUF_SIZE]; char *fname; l_float32 scale; PIX *pixt, *pix8; static l_int32 index = 0; /* caution: not .so or thread safe */ PROCNAME("pixDisplayWriteFormat"); if (reduction == 0) return 0; if (reduction < 0) { index = 0; /* reset; this will cause erasure at next call to write */ return 0; } if (format != IFF_JFIF_JPEG && format != IFF_PNG) return ERROR_INT("invalid format", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (index == 0) { lept_rmdir("display"); lept_mkdir("display"); } index++; if (reduction == 1) { pixt = pixClone(pixs); } else { scale = 1. / (l_float32)reduction; if (pixGetDepth(pixs) == 1) pixt = pixScaleToGray(pixs, scale); else pixt = pixScale(pixs, scale, scale); } if (pixGetDepth(pixt) == 16) { pix8 = pixMaxDynamicRange(pixt, L_LOG_SCALE); snprintf(buf, L_BUF_SIZE, "file.%03d.png", index); fname = genPathname("/tmp/display", buf); pixWrite(fname, pix8, IFF_PNG); pixDestroy(&pix8); } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt) || format == IFF_PNG) { snprintf(buf, L_BUF_SIZE, "file.%03d.png", index); fname = genPathname("/tmp/display", buf); pixWrite(fname, pixt, IFF_PNG); } else { snprintf(buf, L_BUF_SIZE, "file.%03d.jpg", index); fname = genPathname("/tmp/display", buf); pixWrite(fname, pixt, format); } FREE(fname); pixDestroy(&pixt); return 0; }
/*! * pixStrokeWidthTransform() * * Input: pixs (1 bpp) * color (0 for white runs, 1 for black runs) * depth (of pixd: 8 or 16 bpp) * nangles (2, 4, 6 or 8) * Return: pixd (8 or 16 bpp), or null on error * * Notes: * (1) The dest Pix is 8 or 16 bpp, with the pixel values * equal to the stroke width in which it is a member. * The values are clipped to the max pixel value if necessary. * (2) The color determines if we're labelling white or black strokes. * (3) A pixel that is not a member of the chosen color gets * value 0; it belongs to a width of length 0 of the * chosen color. * (4) This chooses, for each dest pixel, the minimum of sets * of runlengths through each pixel. Here are the sets: * nangles increment set * ------- --------- -------------------------------- * 2 90 {0, 90} * 4 45 {0, 45, 90, 135} * 6 30 {0, 30, 60, 90, 120, 150} * 8 22.5 {0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5} * (5) Runtime scales linearly with (nangles - 2). */ PIX * pixStrokeWidthTransform(PIX *pixs, l_int32 color, l_int32 depth, l_int32 nangles) { l_float32 angle, pi; PIX *pixh, *pixv, *pixt, *pixg1, *pixg2, *pixg3, *pixg4; PROCNAME("pixStrokeWidthTransform"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *) ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (depth != 8 && depth != 16) return (PIX *) ERROR_PTR("depth must be 8 or 16 bpp", procName, NULL); if (nangles != 2 && nangles != 4 && nangles != 6 && nangles != 8) return (PIX *) ERROR_PTR("nangles not in {2,4,6,8}", procName, NULL); /* Use fg runs for evaluation */ if (color == 0) pixt = pixInvert(NULL, pixs); else pixt = pixClone(pixs); /* Find min length at 0 and 90 degrees */ pixh = pixRunlengthTransform(pixt, 1, L_HORIZONTAL_RUNS, depth); pixv = pixRunlengthTransform(pixt, 1, L_VERTICAL_RUNS, depth); pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN); pixDestroy(&pixh); pixDestroy(&pixv); pixg2 = pixg3 = pixg4 = NULL; pi = 3.1415926535; if (nangles == 4 || nangles == 8) { /* Find min length at +45 and -45 degrees */ angle = pi / 4.0; pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth); } if (nangles == 6) { /* Find min length at +30 and -60 degrees */ angle = pi / 6.0; pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth); /* Find min length at +60 and -30 degrees */ angle = pi / 3.0; pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth); } if (nangles == 8) { /* Find min length at +22.5 and -67.5 degrees */ angle = pi / 8.0; pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth); /* Find min length at +67.5 and -22.5 degrees */ angle = 3.0 * pi / 8.0; pixg4 = pixFindMinRunsOrthogonal(pixt, angle, depth); } pixDestroy(&pixt); if (nangles > 2) pixMinOrMax(pixg1, pixg1, pixg2, L_CHOOSE_MIN); if (nangles > 4) pixMinOrMax(pixg1, pixg1, pixg3, L_CHOOSE_MIN); if (nangles > 6) pixMinOrMax(pixg1, pixg1, pixg4, L_CHOOSE_MIN); pixDestroy(&pixg2); pixDestroy(&pixg3); pixDestroy(&pixg4); return pixg1; }
/*! * pixThinGeneral() * * Input: pixs (1 bpp) * type (L_THIN_FG, L_THIN_BG) * sela (of Sels for parallel composite HMTs) * maxiters (max number of iters allowed; use 0 to iterate * until completion) * Return: pixd, or null on error * * Notes: * (1) See notes in pixThin(). That function chooses among * the best of the Sels for thinning. * (2) This is a general function that takes a Sela of HMTs * that are used in parallel for thinning from each * of four directions. One iteration consists of four * such parallel thins. */ PIX * pixThinGeneral(PIX *pixs, l_int32 type, SELA *sela, l_int32 maxiters) { l_int32 i, j, r, nsels, same; PIXA *pixahmt; PIX **pixhmt; /* array owned by pixahmt; do not destroy! */ PIX *pixd, *pixt; SEL *sel, *selr; PROCNAME("pixThinGeneral"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL); if (type != L_THIN_FG && type != L_THIN_BG) return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL); if (!sela) return (PIX *)ERROR_PTR("sela not defined", procName, NULL); if (maxiters == 0) maxiters = 10000; /* Set up array of temp pix to hold hmts */ nsels = selaGetCount(sela); pixahmt = pixaCreate(nsels); for (i = 0; i < nsels; i++) { pixt = pixCreateTemplate(pixs); pixaAddPix(pixahmt, pixt, L_INSERT); } pixhmt = pixaGetPixArray(pixahmt); if (!pixhmt) return (PIX *)ERROR_PTR("pixhmt array not made", procName, NULL); #if DEBUG_SELS pixt = selaDisplayInPix(sela, 35, 3, 15, 4); pixDisplayWithTitle(pixt, 100, 100, "allsels", 1); pixDestroy(&pixt); #endif /* DEBUG_SELS */ /* Set up initial image for fg thinning */ if (type == L_THIN_FG) pixd = pixCopy(NULL, pixs); else /* bg thinning */ pixd = pixInvert(NULL, pixs); /* Thin the fg, with up to maxiters iterations */ for (i = 0; i < maxiters; i++) { pixt = pixCopy(NULL, pixd); /* test for completion */ for (r = 0; r < 4; r++) { /* over 90 degree rotations of Sels */ for (j = 0; j < nsels; j++) { /* over individual sels in sela */ sel = selaGetSel(sela, j); /* not a copy */ selr = selRotateOrth(sel, r); pixHMT(pixhmt[j], pixd, selr); selDestroy(&selr); if (j > 0) pixOr(pixhmt[0], pixhmt[0], pixhmt[j]); /* accum result */ } pixSubtract(pixd, pixd, pixhmt[0]); /* remove result */ } pixEqual(pixd, pixt, &same); pixDestroy(&pixt); if (same) { L_INFO("%d iterations to completion\n", procName, i); break; } } if (type == L_THIN_BG) pixInvert(pixd, pixd); pixaDestroy(&pixahmt); return pixd; }
main(int argc, char **argv) { char *file1, *file2, *fileout; l_int32 d; l_float32 fract; PIX *pixs1, *pixs2, *pixt1, *pixt2, *pixt3, *pixt4, *pixd; static char mainName[] = "blendtest1"; if (argc != 5) exit(ERROR_INT(" Syntax: blendtest1 file1 file2 fract fileout", mainName, 1)); file1 = argv[1]; file2 = argv[2]; fract = atof(argv[3]); fileout = argv[4]; if ((pixs1 = pixRead(file1)) == NULL) exit(ERROR_INT("pixs1 not made", mainName, 1)); if ((pixs2 = pixRead(file2)) == NULL) exit(ERROR_INT("pixs2 not made", mainName, 1)); #if 0 d = pixGetDepth(pixs2); if (d == 1) { pixt1 = pixBlend(pixs1, pixs2, X, Y, fract); pixt2 = pixBlend(pixt1, pixs2, X, Y + 60, fract); pixt3 = pixBlend(pixt2, pixs2, X, Y + 120, fract); pixt4 = pixBlend(pixt3, pixs2, X, Y + 180, fract); pixt5 = pixBlend(pixt4, pixs2, X, Y + 240, fract); pixd = pixBlend(pixt5, pixs2, X, Y + 300, fract); pixWrite(fileout, pixd, IFF_DEFAULT); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixt5); } else { pixt1 = pixBlend(pixs1, pixs2, X, Y, fract); pixt2 = pixBlend(pixt1, pixs2, X + 80, Y + 80, fract); pixt3 = pixBlend(pixt2, pixs2, X + 160, Y + 160, fract); pixt4 = pixBlend(pixt3, pixs2, X + 240, Y + 240, fract); pixt5 = pixBlend(pixt4, pixs2, X + 320, Y + 320, fract); pixd = pixBlend(pixt5, pixs2, X + 360, Y + 360, fract); pixWrite(fileout, pixd, IFF_DEFAULT); pixDestroy(&pixt1); pixDestroy(&pixt2); pixDestroy(&pixt3); pixDestroy(&pixt4); pixDestroy(&pixt5); } pixDestroy(&pixd); #endif #if 1 /* e.g., weasel8.png with fract = 0.3 */ pixSnapColor(pixs2, pixs2, 0xff, 0xff, 50); pixBlendGray(pixs1, pixs1, pixs2, 200, 100, fract, L_BLEND_GRAY, 1, 0xff); pixBlendGray(pixs1, pixs1, pixs2, 200, 200, fract, L_BLEND_GRAY, 1, 0xff); pixBlendGray(pixs1, pixs1, pixs2, 200, 260, fract, L_BLEND_GRAY, 1, 0xff); pixBlendGray(pixs1, pixs1, pixs2, 200, 340, fract, L_BLEND_GRAY, 1, 0xff); pixWrite(fileout, pixs1, IFF_JFIF_JPEG); pixDisplay(pixs1, 200, 200); #endif #if 0 /* e.g., weasel8.png with fract = 0.5 */ pixSnapColor(pixs2, pixs2, 0xff, 0xff, 50); pixBlendGray(pixs1, pixs1, pixs2, 200, 100, fract, L_BLEND_GRAY_WITH_INVERSE, 1, 0xff); pixBlendGray(pixs1, pixs1, pixs2, 200, 200, fract, L_BLEND_GRAY_WITH_INVERSE, 1, 0xff); pixBlendGray(pixs1, pixs1, pixs2, 200, 260, fract, L_BLEND_GRAY_WITH_INVERSE, 1, 0xff); pixBlendGray(pixs1, pixs1, pixs2, 200, 340, fract, L_BLEND_GRAY_WITH_INVERSE, 1, 0xff); pixWrite(fileout, pixs1, IFF_JFIF_JPEG); pixDisplay(pixs1, 200, 200); #endif #if 0 /* e.g., weasel32.png with fract = 0.2 */ pixSnapColor(pixs2, pixs2, 0xffffff00, 0xffffff00, 50); pixBlendColor(pixs1, pixs1, pixs2, 200, 100, fract, 1, 0xffffff00); pixBlendColor(pixs1, pixs1, pixs2, 200, 200, fract, 1, 0xffffff00); pixBlendColor(pixs1, pixs1, pixs2, 200, 260, fract, 1, 0xffffff00); pixBlendColor(pixs1, pixs1, pixs2, 200, 340, fract, 1, 0xffffff00); pixWrite(fileout, pixs1, IFF_JFIF_JPEG); pixDisplay(pixs1, 200, 200); #endif pixDestroy(&pixs1); pixDestroy(&pixs2); exit(0); }
/*! * \brief pixFindBaselines() * * \param[in] pixs 1 bpp, 300 ppi * \param[out] ppta [optional] pairs of pts corresponding to * approx. ends of each text line * \param[in] pixadb for debug output; use NULL to skip * \return na of baseline y values, or NULL on error * * <pre> * 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. * </pre> */ NUMA * pixFindBaselines(PIX *pixs, PTA **ppta, PIXA *pixadb) { l_int32 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 *pix1, *pix2; PTA *pta; PROCNAME("pixFindBaselines"); if (ppta) *ppta = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); /* Close up the text characters, removing noise */ pix1 = pixMorphSequence(pixs, "c25.1 + e15.1", 0); /* Estimate the resolution */ if (pixadb) pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); /* Save the difference of adjacent row sums. * The high positive-going peaks are the baselines */ if ((nasum = pixCountPixelsByRow(pix1, NULL)) == NULL) { pixDestroy(&pix1); return (NUMA *)ERROR_PTR("nasum not made", procName, NULL); } 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); } numaDestroy(&nasum); if (pixadb) { /* show the difference signal */ lept_mkdir("lept/baseline"); gplotSimple1(nadiff, GPLOT_PNG, "/tmp/lept/baseline/diff", "Diff Sig"); pix2 = pixRead("/tmp/lept/baseline/diff.png"); pixaAddPix(pixadb, pix2, L_INSERT); } /* Use the zeroes of the profile to locate each baseline. */ array = numaGetIArray(nadiff); ndiff = numaGetCount(nadiff); numaGetMax(nadiff, &maxval, &imaxloc); numaDestroy(&nadiff); /* 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); } } } LEPT_FREE(array); /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */ if (inpeak) { numaAddNumber(naval, max); numaAddNumber(naloc, maxloc); } if (pixadb) { /* show the raster locations for the peaks */ gplot = gplotCreate("/tmp/lept/baseline/loc", GPLOT_PNG, "Peak locs", "rasterline", "height"); gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs"); gplotMakeOutput(gplot); gplotDestroy(&gplot); pix2 = pixRead("/tmp/lept/baseline/loc.png"); pixaAddPix(pixadb, pix2, L_INSERT); } numaDestroy(&naval); /* 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. */ pix2 = pixMorphSequence(pix1, "r11 + c20.1 + o30.1 +c1.3", 0); if (pixadb) pixaAddPix(pixadb, pix2, L_COPY); boxa1 = pixConnComp(pix2, NULL, 4); pixDestroy(&pix1); pixDestroy(&pix2); if (boxaGetCount(boxa1) == 0) { numaDestroy(&naloc); boxaDestroy(&boxa1); L_INFO("no compnents after filtering\n", procName); return NULL; } boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.); boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL); boxaDestroy(&boxa1); boxaDestroy(&boxa2); /* Optionally, find the baseline segments */ pta = NULL; if (ppta) { pta = ptaCreate(0); *ppta = pta; } 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; } } } boxaDestroy(&boxa3); if (pixadb && pta) { /* display baselines */ l_int32 npts, x1, y1, x2, y2; pix1 = pixConvertTo32(pixs); npts = ptaGetCount(pta); for (i = 0; i < npts; i += 2) { ptaGetIPt(pta, i, &x1, &y1); ptaGetIPt(pta, i + 1, &x2, &y2); pixRenderLineArb(pix1, x1, y1, x2, y2, 2, 255, 0, 0); } pixWrite("/tmp/lept/baseline/baselines.png", pix1, IFF_PNG); pixaAddPix(pixadb, pixScale(pix1, 0.25, 0.25), L_INSERT); pixDestroy(&pix1); } return naloc; }
/*! * \brief pixConnCompPixa() * * \param[in] pixs 1 bpp * \param[out] ppixa pixa of each c.c. * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) This finds bounding boxes of 4- or 8-connected components * in a binary image, and saves images of each c.c * in a pixa array. * (2) It sets up 2 temporary pix, and for each c.c. that is * located in raster order, it erases the c.c. from one pix, * then uses the b.b. to extract the c.c. from the two pix using * an XOR, and finally erases the c.c. from the second pix. * (3) A clone of the returned boxa (where all boxes in the array * are clones) is inserted into the pixa. * (4) If the input is valid, this always returns a boxa and a pixa. * If pixs is empty, the boxa and pixa will be empty. * </pre> */ BOXA * pixConnCompPixa(PIX *pixs, PIXA **ppixa, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pix1, *pix2, *pix3, *pix4; PIXA *pixa; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompPixa"); if (!ppixa) return (BOXA *)ERROR_PTR("&pixa not defined", procName, NULL); *ppixa = NULL; if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); boxa = NULL; pix1 = pix2 = pix3 = pix4 = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ pix1 = pixCopy(NULL, pixs); pix2 = pixCopy(NULL, pixs); if (!pix1 || !pix2) { L_ERROR("pix1 or pix2 not made\n", procName); goto cleanup; } h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; pixa = pixaCreate(0); boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pix1, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pix1, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); pixaDestroy(&pixa); boxaDestroy(&boxa); goto cleanup; } boxaAddBox(boxa, box, L_INSERT); /* Save the c.c. and remove from pix2 as well */ pix3 = pixClipRectangle(pix1, box, NULL); pix4 = pixClipRectangle(pix2, box, NULL); pixXor(pix3, pix3, pix4); pixRasterop(pix2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST, pix3, 0, 0); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pix4); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pix1, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pix1, IFF_PNG); #endif /* DEBUG */ /* Remove old boxa of pixa and replace with a clone copy */ boxaDestroy(&pixa->boxa); pixa->boxa = boxaCopy(boxa, L_CLONE); *ppixa = pixa; /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pix1); pixDestroy(&pix2); return boxa; }
/*! * \brief pixGetLocalSkewAngles() * * \param[in] pixs 1 bpp * \param[in] nslices the number of horizontal overlapping slices; must * be larger than 1 and not exceed 20; 0 for default * \param[in] redsweep sweep reduction factor: 1, 2, 4 or 8; * use 0 for default value * \param[in] redsearch search reduction factor: 1, 2, 4 or 8, and not * larger than redsweep; use 0 for default value * \param[in] sweeprange half the full range, assumed about 0; in degrees; * use 0.0 for default value * \param[in] sweepdelta angle increment of sweep; in degrees; * use 0.0 for default value * \param[in] minbsdelta min binary search increment angle; in degrees; * use 0.0 for default value * \param[out] pa [optional] slope of skew as fctn of y * \param[out] pb [optional] intercept at y=0 of skew as fctn of y * \param[in] debug 1 for generating plot of skew angle vs. y; 0 otherwise * \return naskew, or NULL on error * * <pre> * 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. * </pre> */ 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 debug) { l_int32 w, h, hs, i, ystart, yend, ovlap, npts; l_float32 angle, conf, ycenter, a, b; BOX *box; GPLOT *gplot; NUMA *naskew, *nax, *nay; PIX *pix; PTA *pta; PROCNAME("pixGetLocalSkewAngles"); if (!pixs || pixGetDepth(pixs) != 1) return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", 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; pixGetDimensions(pixs, &w, &h, NULL); 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); } /* 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) { lept_mkdir("lept/baseline"); ptaGetArrays(pta, &nax, &nay); gplot = gplotCreate("/tmp/lept/baseline/skew", GPLOT_PNG, "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); } ptaDestroy(&pta); return naskew; }
/*! * \brief pixConnCompBB() * * \param[in] pixs 1 bpp * \param[in] connectivity 4 or 8 * \return boxa, or NULL on error * * <pre> * Notes: * (1) Finds bounding boxes of 4- or 8-connected components * in a binary image. * (2) This works on a copy of the input pix. The c.c. are located * in raster order and erased one at a time. In the process, * the b.b. is computed and saved. * </pre> */ BOXA * pixConnCompBB(PIX *pixs, l_int32 connectivity) { l_int32 h, iszero; l_int32 x, y, xstart, ystart; PIX *pixt; BOX *box; BOXA *boxa; L_STACK *stack, *auxstack; PROCNAME("pixConnCompBB"); if (!pixs || pixGetDepth(pixs) != 1) return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connectivity != 4 && connectivity != 8) return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); boxa = NULL; pixt = NULL; stack = NULL; pixZero(pixs, &iszero); if (iszero) return boxaCreate(1); /* return empty boxa */ if ((pixt = pixCopy(NULL, pixs)) == NULL) return (BOXA *)ERROR_PTR("pixt not made", procName, NULL); h = pixGetHeight(pixs); if ((stack = lstackCreate(h)) == NULL) { L_ERROR("stack not made\n", procName); goto cleanup; } auxstack = lstackCreate(0); stack->auxstack = auxstack; boxa = boxaCreate(0); xstart = 0; ystart = 0; while (1) { if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y)) break; if ((box = pixSeedfillBB(pixt, stack, x, y, connectivity)) == NULL) { L_ERROR("box not made\n", procName); boxaDestroy(&boxa); goto cleanup; } boxaAddBox(boxa, box, L_INSERT); xstart = x; ystart = y; } #if DEBUG pixCountPixels(pixt, &iszero, NULL); fprintf(stderr, "Number of remaining pixels = %d\n", iszero); pixWrite("junkremain", pixt1, IFF_PNG); #endif /* DEBUG */ /* Cleanup, freeing the fillsegs on each stack */ cleanup: lstackDestroy(&stack, TRUE); pixDestroy(&pixt); return boxa; }
/*! * convertFilesTo1bpp() * * Input: dirin * substr (<optional> substring filter on filenames; can be NULL) * upscaling (1, 2 or 4; only for input color or grayscale) * thresh (global threshold for binarization; use 0 for default) * firstpage * npages (use 0 to do all from @firstpage to the end) * dirout * outformat (IFF_PNG, IFF_TIFF_G4) * Return: 0 if OK, 1 on error * * Notes: * (1) Images are sorted lexicographically, and the names in the * output directory are retained except for the extension. */ l_int32 convertFilesTo1bpp(const char *dirin, const char *substr, l_int32 upscaling, l_int32 thresh, l_int32 firstpage, l_int32 npages, const char *dirout, l_int32 outformat) { l_int32 i, nfiles; char buf[512]; char *fname, *tail, *basename; PIX *pixs, *pixg1, *pixg2, *pixb; SARRAY *safiles; PROCNAME("convertFilesTo1bpp"); if (!dirin) return ERROR_INT("dirin", procName, 1); if (!dirout) return ERROR_INT("dirout", procName, 1); if (upscaling != 1 && upscaling != 2 && upscaling != 4) return ERROR_INT("invalid upscaling factor", procName, 1); if (thresh <= 0) thresh = 180; if (firstpage < 0) firstpage = 0; if (npages < 0) npages = 0; if (outformat != IFF_TIFF_G4) outformat = IFF_PNG; safiles = getSortedPathnamesInDirectory(dirin, substr, firstpage, npages); if (!safiles) return ERROR_INT("safiles not made", procName, 1); if ((nfiles = sarrayGetCount(safiles)) == 0) { sarrayDestroy(&safiles); return ERROR_INT("no matching files in the directory", procName, 1); } for (i = 0; i < nfiles; i++) { fname = sarrayGetString(safiles, i, L_NOCOPY); if ((pixs = pixRead(fname)) == NULL) { L_WARNING("Couldn't read file %s\n", procName, fname); continue; } if (pixGetDepth(pixs) == 32) pixg1 = pixConvertRGBToLuminance(pixs); else pixg1 = pixClone(pixs); pixg2 = pixRemoveColormap(pixg1, REMOVE_CMAP_TO_GRAYSCALE); if (pixGetDepth(pixg2) == 1) { pixb = pixClone(pixg2); } else { if (upscaling == 1) pixb = pixThresholdToBinary(pixg2, thresh); else if (upscaling == 2) pixb = pixScaleGray2xLIThresh(pixg2, thresh); else /* upscaling == 4 */ pixb = pixScaleGray4xLIThresh(pixg2, thresh); } pixDestroy(&pixs); pixDestroy(&pixg1); pixDestroy(&pixg2); splitPathAtDirectory(fname, NULL, &tail); splitPathAtExtension(tail, &basename, NULL); if (outformat == IFF_TIFF_G4) { snprintf(buf, sizeof(buf), "%s/%s.tif", dirout, basename); pixWrite(buf, pixb, IFF_TIFF_G4); } else { snprintf(buf, sizeof(buf), "%s/%s.png", dirout, basename); pixWrite(buf, pixb, IFF_PNG); } pixDestroy(&pixb); FREE(tail); FREE(basename); } sarrayDestroy(&safiles); return 0; }
/*! * \brief pixSeedfill8BB() * * \param[in] pixs 1 bpp * \param[in] stack for holding fillsegs * \param[in] x,y location of seed pixel * \return box or NULL on error. * * <pre> * Notes: * (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm. * (2) This operates on the input 1 bpp pix to remove the fg seed * pixel, at (x,y), and all pixels that are 8-connected to it. * The seed pixel at (x,y) must initially be ON. * (3) Returns the bounding box of the erased 8-cc component. * (4) Reference: see Paul Heckbert's stack-based seed fill algorithm * in "Graphic Gems", ed. Andrew Glassner, Academic * Press, 1990. The algorithm description is given * on pp. 275-277; working C code is on pp. 721-722.) * The code here follows Heckbert's closely, except * the leak checks are changed for 8 connectivity. * See comments on pixSeedfill4BB() for more details. * </pre> */ BOX * pixSeedfill8BB(PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y) { l_int32 w, h, xstart, wpl, x1, x2, dy; l_int32 xmax, ymax; l_int32 minx, maxx, miny, maxy; /* for bounding box of this c.c. */ l_uint32 *data, *line; BOX *box; PROCNAME("pixSeedfill8BB"); if (!pixs || pixGetDepth(pixs) != 1) return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (!stack) return (BOX *)ERROR_PTR("stack not defined", procName, NULL); if (!stack->auxstack) stack->auxstack = lstackCreate(0); pixGetDimensions(pixs, &w, &h, NULL); xmax = w - 1; ymax = h - 1; data = pixGetData(pixs); wpl = pixGetWpl(pixs); line = data + y * wpl; /* Check pix value of seed; must be ON */ if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0)) return NULL; /* Init stack to seed: * Must first init b.b. values to prevent valgrind from complaining; * then init b.b. boundaries correctly to seed. */ minx = miny = 100000; maxx = maxy = 0; pushFillsegBB(stack, x, x, y, 1, ymax, &minx, &maxx, &miny, &maxy); pushFillsegBB(stack, x, x, y + 1, -1, ymax, &minx, &maxx, &miny, &maxy); minx = maxx = x; miny = maxy = y; while (lstackGetCount(stack) > 0) { /* Pop segment off stack and fill a neighboring scan line */ popFillseg(stack, &x1, &x2, &y, &dy); line = data + y * wpl; /* A segment of scanline y - dy for x1 <= x <= x2 was * previously filled. We now explore adjacent pixels * in scan line y. There are three regions: to the * left of x1, between x1 and x2, and to the right of x2. * These regions are handled differently. Leaks are * possible expansions beyond the previous segment and * going back in the -dy direction. These can happen * for x < x1 and for x > x2. Any "leak" segments * are plugged with a push in the -dy (opposite) direction. * And any segments found anywhere are always extended * in the +dy direction. */ for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--) CLEAR_DATA_BIT(line,x); if (x >= x1 - 1) /* pix at x1 - 1 was off and was not cleared */ goto skip; xstart = x + 1; if (xstart < x1) /* leak on left? */ pushFillsegBB(stack, xstart, x1 - 1, y, -dy, ymax, &minx, &maxx, &miny, &maxy); x = x1; do { for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++) CLEAR_DATA_BIT(line, x); pushFillsegBB(stack, xstart, x - 1, y, dy, ymax, &minx, &maxx, &miny, &maxy); if (x > x2) /* leak on right? */ pushFillsegBB(stack, x2 + 1, x - 1, y, -dy, ymax, &minx, &maxx, &miny, &maxy); skip: for (x++; x <= x2 + 1 && x <= xmax && (GET_DATA_BIT(line, x) == 0); x++) ; xstart = x; } while (x <= x2 + 1 && x <= xmax); } if ((box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1)) == NULL) return (BOX *)ERROR_PTR("box not made", procName, NULL); return box; }
/*! * pixQuadtreeVariance() * * Input: pixs (8 bpp, no colormap) * nlevels (in quadtree) * *pix_ma (input mean accumulator; can be null) * *dpix_msa (input mean square accumulator; can be null) * *pfpixa_v (<optional return> variance values in quadtree) * *pfpixa_rv (<optional return> root variance values in quadtree) * Return: 0 if OK, 1 on error * * Notes: * (1) The returned fpixav and fpixarv have @nlevels of fpix, * each containing at the respective levels the variance * and root variance values. */ l_int32 pixQuadtreeVariance(PIX *pixs, l_int32 nlevels, PIX *pix_ma, DPIX *dpix_msa, FPIXA **pfpixa_v, FPIXA **pfpixa_rv) { l_int32 i, j, w, h, size, n; l_float32 var, rvar; BOX *box; BOXA *boxa; BOXAA *baa; FPIX *fpixv, *fpixrv; PIX *pix_mac; /* copy of mean accumulator */ DPIX *dpix_msac; /* msa clone */ PROCNAME("pixQuadtreeVariance"); if (!pfpixa_v && !pfpixa_rv) return ERROR_INT("neither &fpixav nor &fpixarv defined", procName, 1); if (pfpixa_v) *pfpixa_v = NULL; if (pfpixa_rv) *pfpixa_rv = NULL; if (!pixs || pixGetDepth(pixs) != 8) return ERROR_INT("pixs not defined or not 8 bpp", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); if (nlevels > quadtreeMaxLevels(w, h)) return ERROR_INT("nlevels too large for image", procName, 1); if (!pix_ma) pix_mac = pixBlockconvAccum(pixs); else pix_mac = pixClone(pix_ma); if (!pix_mac) return ERROR_INT("pix_mac not made", procName, 1); if (!dpix_msa) dpix_msac = pixMeanSquareAccum(pixs); else dpix_msac = dpixClone(dpix_msa); if (!dpix_msac) return ERROR_INT("dpix_msac not made", procName, 1); if ((baa = boxaaQuadtreeRegions(w, h, nlevels)) == NULL) { pixDestroy(&pix_mac); dpixDestroy(&dpix_msac); return ERROR_INT("baa not made", procName, 1); } if (pfpixa_v) *pfpixa_v = fpixaCreate(nlevels); if (pfpixa_rv) *pfpixa_rv = fpixaCreate(nlevels); for (i = 0; i < nlevels; i++) { boxa = boxaaGetBoxa(baa, i, L_CLONE); size = 1 << i; n = boxaGetCount(boxa); /* n == size * size */ if (pfpixa_v) fpixv = fpixCreate(size, size); if (pfpixa_rv) fpixrv = fpixCreate(size, size); for (j = 0; j < n; j++) { box = boxaGetBox(boxa, j, L_CLONE); pixVarianceInRectangle(pixs, box, pix_mac, dpix_msac, &var, &rvar); if (pfpixa_v) fpixSetPixel(fpixv, j % size, j / size, var); if (pfpixa_rv) fpixSetPixel(fpixrv, j % size, j / size, rvar); boxDestroy(&box); } if (pfpixa_v) fpixaAddFPix(*pfpixa_v, fpixv, L_INSERT); if (pfpixa_rv) fpixaAddFPix(*pfpixa_rv, fpixrv, L_INSERT); boxaDestroy(&boxa); } pixDestroy(&pix_mac); dpixDestroy(&dpix_msac); boxaaDestroy(&baa); return 0; }
/*! * \brief pixSeedfill8() * * \param[in] pixs 1 bpp * \param[in] stack for holding fillsegs * \param[in] x,y location of seed pixel * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm. * (2) This operates on the input 1 bpp pix to remove the fg seed * pixel, at (x,y), and all pixels that are 8-connected to it. * The seed pixel at (x,y) must initially be ON. * (3) Reference: see pixSeedFill8BB() * </pre> */ l_int32 pixSeedfill8(PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y) { l_int32 w, h, xstart, wpl, x1, x2, dy; l_int32 xmax, ymax; l_uint32 *data, *line; PROCNAME("pixSeedfill8"); if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs not defined or not 1 bpp", procName, 1); if (!stack) return ERROR_INT("stack not defined", procName, 1); if (!stack->auxstack) stack->auxstack = lstackCreate(0); pixGetDimensions(pixs, &w, &h, NULL); xmax = w - 1; ymax = h - 1; data = pixGetData(pixs); wpl = pixGetWpl(pixs); line = data + y * wpl; /* Check pix value of seed; must be ON */ if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0)) return 0; /* Init stack to seed */ pushFillseg(stack, x, x, y, 1, ymax); pushFillseg(stack, x, x, y + 1, -1, ymax); while (lstackGetCount(stack) > 0) { /* Pop segment off stack and fill a neighboring scan line */ popFillseg(stack, &x1, &x2, &y, &dy); line = data + y * wpl; /* A segment of scanline y - dy for x1 <= x <= x2 was * previously filled. We now explore adjacent pixels * in scan line y. There are three regions: to the * left of x1, between x1 and x2, and to the right of x2. * These regions are handled differently. Leaks are * possible expansions beyond the previous segment and * going back in the -dy direction. These can happen * for x < x1 and for x > x2. Any "leak" segments * are plugged with a push in the -dy (opposite) direction. * And any segments found anywhere are always extended * in the +dy direction. */ for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--) CLEAR_DATA_BIT(line,x); if (x >= x1 - 1) /* pix at x1 - 1 was off and was not cleared */ goto skip; xstart = x + 1; if (xstart < x1) /* leak on left? */ pushFillseg(stack, xstart, x1 - 1, y, -dy, ymax); x = x1; do { for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++) CLEAR_DATA_BIT(line, x); pushFillseg(stack, xstart, x - 1, y, dy, ymax); if (x > x2) /* leak on right? */ pushFillseg(stack, x2 + 1, x - 1, y, -dy, ymax); skip: for (x++; x <= x2 + 1 && x <= xmax && (GET_DATA_BIT(line, x) == 0); x++) ; xstart = x; } while (x <= x2 + 1 && x <= xmax); } return 0; }
/*! * pixWriteMemWebP() * * Input: &encdata (<return> webp encoded data of pixs) * &encsize (<return> size of webp encoded data) * pixs (any depth, cmapped OK) * quality (0 - 100; default ~80) * lossless (use 1 for lossless; 0 for lossy) * Return: 0 if OK, 1 on error * * Notes: * (1) Lossless and lossy encoding are entirely different in webp. * @quality applies to lossy, and is ignored for lossless. * (2) The input image is converted to RGB if necessary. If spp == 3, * we set the alpha channel to fully opaque (255), and * WebPEncodeRGBA() then removes the alpha chunk when encoding, * setting the internal header field has_alpha to 0. */ l_int32 pixWriteMemWebP(l_uint8 **pencdata, size_t *pencsize, PIX *pixs, l_int32 quality, l_int32 lossless) { l_int32 w, h, d, wpl, stride; l_uint32 *data; PIX *pix1, *pix2; PROCNAME("pixWriteMemWebP"); if (!pencdata) return ERROR_INT("&encdata not defined", procName, 1); *pencdata = NULL; if (!pencsize) return ERROR_INT("&encsize not defined", procName, 1); *pencsize = 0; if (!pixs) return ERROR_INT("&pixs not defined", procName, 1); if (lossless == 0 && (quality < 0 || quality > 100)) return ERROR_INT("quality not in [0 ... 100]", procName, 1); if ((pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR)) == NULL) return ERROR_INT("failure to remove color map", procName, 1); /* Convert to rgb if not 32 bpp; pix2 must not be a clone of pixs. */ if (pixGetDepth(pix1) != 32) pix2 = pixConvertTo32(pix1); else pix2 = pixCopy(NULL, pix1); pixDestroy(&pix1); pixGetDimensions(pix2, &w, &h, &d); if (w <= 0 || h <= 0 || d != 32) { pixDestroy(&pix2); return ERROR_INT("pix2 not 32 bpp or of 0 size", procName, 1); } /* If spp == 3, need to set alpha layer to opaque (all 1s). */ if (pixGetSpp(pix2) == 3) pixSetComponentArbitrary(pix2, L_ALPHA_CHANNEL, 255); /* Webp encoder assumes big-endian byte order for RGBA components */ pixEndianByteSwap(pix2); wpl = pixGetWpl(pix2); data = pixGetData(pix2); stride = wpl * 4; if (lossless) { *pencsize = WebPEncodeLosslessRGBA((uint8_t *)data, w, h, stride, pencdata); } else { *pencsize = WebPEncodeRGBA((uint8_t *)data, w, h, stride, quality, pencdata); } pixDestroy(&pix2); if (*pencsize == 0) { free(pencdata); *pencdata = NULL; return ERROR_INT("webp encoding failed", procName, 1); } return 0; }
/*! * pixSaveTiledOutline() * * Input: pixs (1, 2, 4, 8, 32 bpp) * pixa (the pix are accumulated here) * scalefactor (0.0 to disable; otherwise this is a scale factor) * newrow (0 if placed on the same row as previous; 1 otherwise) * space (horizontal and vertical spacing, in pixels) * linewidth (width of added outline for image; 0 for no outline) * dp (depth of pixa; 8 or 32 bpp; only used on first call) * Return: 0 if OK, 1 on error. * * Notes: * (1) Before calling this function for the first time, use * pixaCreate() to make the @pixa that will accumulate the pix. * This is passed in each time pixSaveTiled() is called. * (2) @scalefactor scales the input image. After scaling and * possible depth conversion, the image is saved in the input * pixa, along with a box that specifies the location to * place it when tiled later. Disable saving the pix by * setting @scalefactor == 0.0. * (3) @newrow and @space specify the location of the new pix * with respect to the last one(s) that were entered. * (4) @dp specifies the depth at which all pix are saved. It can * be only 8 or 32 bpp. Any colormap is removed. This is only * used at the first invocation. * (5) This function uses two variables from call to call. * If they were static, the function would not be .so or thread * safe, and furthermore, there would be interference with two or * more pixa accumulating images at a time. Consequently, * we use the first pix in the pixa to store and obtain both * the depth and the current position of the bottom (one pixel * below the lowest image raster line when laid out using * the boxa). The bottom variable is stored in the input format * field, which is the only field available for storing an int. */ l_int32 pixSaveTiledOutline(PIX *pixs, PIXA *pixa, l_float32 scalefactor, l_int32 newrow, l_int32 space, l_int32 linewidth, l_int32 dp) { l_int32 n, top, left, bx, by, bw, w, h, depth, bottom; BOX *box; PIX *pix1, *pix2, *pix3, *pix4; PROCNAME("pixSaveTiledOutline"); if (scalefactor == 0.0) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); n = pixaGetCount(pixa); if (n == 0) { bottom = 0; if (dp != 8 && dp != 32) { L_WARNING("dp not 8 or 32 bpp; using 32\n", procName); depth = 32; } else { depth = dp; } } else { /* extract the depth and bottom params from the first pix */ pix1 = pixaGetPix(pixa, 0, L_CLONE); depth = pixGetDepth(pix1); bottom = pixGetInputFormat(pix1); /* not typical usage! */ pixDestroy(&pix1); } /* Remove colormap if it exists; otherwise a copy. This * guarantees that pix4 is not a clone of pixs. */ pix1 = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY); /* Scale and convert to output depth */ if (scalefactor == 1.0) { pix2 = pixClone(pix1); } else if (scalefactor > 1.0) { pix2 = pixScale(pix1, scalefactor, scalefactor); } else if (scalefactor < 1.0) { if (pixGetDepth(pix1) == 1) pix2 = pixScaleToGray(pix1, scalefactor); else pix2 = pixScale(pix1, scalefactor, scalefactor); } pixDestroy(&pix1); if (depth == 8) pix3 = pixConvertTo8(pix2, 0); else pix3 = pixConvertTo32(pix2); pixDestroy(&pix2); /* Add black outline */ if (linewidth > 0) pix4 = pixAddBorder(pix3, linewidth, 0); else pix4 = pixClone(pix3); pixDestroy(&pix3); /* Find position of current pix (UL corner plus size) */ if (n == 0) { top = 0; left = 0; } else if (newrow == 1) { top = bottom + space; left = 0; } else if (n > 0) { pixaGetBoxGeometry(pixa, n - 1, &bx, &by, &bw, NULL); top = by; left = bx + bw + space; } pixGetDimensions(pix4, &w, &h, NULL); bottom = L_MAX(bottom, top + h); box = boxCreate(left, top, w, h); pixaAddPix(pixa, pix4, L_INSERT); pixaAddBox(pixa, box, L_INSERT); /* Save the new bottom value */ pix1 = pixaGetPix(pixa, 0, L_CLONE); pixSetInputFormat(pix1, bottom); /* not typical usage! */ pixDestroy(&pix1); return 0; }