/*! * pixThresholdToValue() * * Input: pixd (<optional>; if not null, must be equal to pixs) * pixs (8, 16, 32 bpp) * threshval * setval * Return: pixd always * * Notes: * - operation can be in-place (pixs == pixd) or to a new pixd * - if setval > threshval, sets pixels with a value >= threshval to setval * - if setval < threshval, sets pixels with a value <= threshval to setval * - if setval == threshval, no-op */ PIX * pixThresholdToValue(PIX *pixd, PIX *pixs, l_int32 threshval, l_int32 setval) { l_int32 w, h, d, wpld; l_uint32 *datad; PROCNAME("pixThresholdToValue"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); d = pixGetDepth(pixs); if (d != 8 && d != 16 && d != 32) return (PIX *)ERROR_PTR("pixs not 8, 16 or 32 bpp", procName, pixd); if (pixd && (pixs != pixd)) return (PIX *)ERROR_PTR("pixd exists and is not pixs", procName, pixd); if (threshval < 0 || setval < 0) return (PIX *)ERROR_PTR("threshval & setval not < 0", procName, pixd); if (d == 8 && setval > 255) return (PIX *)ERROR_PTR("setval > 255 for 8 bpp", procName, pixd); if (d == 16 && setval > 0xffff) return (PIX *)ERROR_PTR("setval > 0xffff for 16 bpp", procName, pixd); if (!pixd) pixd = pixCopy(NULL, pixs); if (setval == threshval) { L_WARNING("setval == threshval; no operation", procName); return pixd; } datad = pixGetData(pixd); pixGetDimensions(pixd, &w, &h, NULL); wpld = pixGetWpl(pixd); thresholdToValueLow(datad, w, h, d, wpld, threshval, setval); return pixd; }
// Sends each pixel as hex value like html, e.g. #00FF00 for green. void ScrollView::Transfer32bppImage(Pix* image) { #ifdef HAVE_LIBLEPT int ppL = pixGetWidth(image); int h = pixGetHeight(image); int wpl = pixGetWpl(image); int transfer_size= ppL * 7 + 2; char* pixel_data = new char[transfer_size]; for (int y = 0; y < h; ++y) { l_uint32* data = pixGetData(image) + y*wpl; for (int x = 0; x < ppL; ++x, ++data) { snprintf(&pixel_data[x*7], 7, "#%.2x%.2x%.2x", GET_DATA_BYTE(data, COLOR_RED), GET_DATA_BYTE(data, COLOR_GREEN), GET_DATA_BYTE(data, COLOR_BLUE)); } pixel_data[transfer_size - 2] = '\n'; pixel_data[transfer_size - 1] = '\0'; SendRawMessage(pixel_data); } delete[] pixel_data; #endif }
/*! * pixSetPadBits() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * val (0 or 1) * Return: 0 if OK; 1 on error * * Notes: * (1) The pad bits are the bits that expand each scanline to a * multiple of 32 bits. They are usually not used in * image processing operations. When boundary conditions * are important, as in seedfill, they must be set properly. * (2) This sets the value of the pad bits (if any) in the last * 32-bit word in each scanline. * (3) For 32 bpp pix, there are no pad bits, so this is a no-op. */ LEPTONICA_REAL_EXPORT l_int32 pixSetPadBits(PIX *pix, l_int32 val) { l_int32 i, w, h, d, wpl, endbits, fullwords; l_uint32 mask; l_uint32 *data, *pword; PROCNAME("pixSetPadBits"); if (!pix) return ERROR_INT("pix not defined", procName, 1); pixGetDimensions(pix, &w, &h, &d); if (d == 32) /* no padding exists for 32 bpp */ return 0; data = pixGetData(pix); wpl = pixGetWpl(pix); endbits = 32 - ((w * d) % 32); if (endbits == 32) /* no partial word */ return 0; fullwords = w * d / 32; mask = rmask32[endbits]; if (val == 0) mask = ~mask; for (i = 0; i < h; i++) { pword = data + i * wpl + fullwords; if (val == 0) /* clear */ *pword = *pword & mask; else /* set */ *pword = *pword | mask; } return 0; }
// NOTE: Opposite to SetImage for raw images, SetImage for Pix clones its // input, so the source pix may be pixDestroyed immediately after. void ImageThresholder::SetImage(const Pix* pix) { image_data_ = NULL; if (pix_ != NULL) pixDestroy(&pix_); Pix* src = const_cast<Pix*>(pix); int depth; pixGetDimensions(src, &image_width_, &image_height_, &depth); // Convert the image as necessary so it is one of binary, plain RGB, or // 8 bit with no colormap. if (depth > 1 && depth < 8) { pix_ = pixConvertTo8(src, false); } else if (pixGetColormap(src)) { pix_ = pixRemoveColormap(src, REMOVE_CMAP_BASED_ON_SRC); } else { pix_ = pixClone(src); } depth = pixGetDepth(pix_); image_bytespp_ = depth / 8; image_bytespl_ = pixGetWpl(pix_) * sizeof(l_uint32); scale_ = 1; estimated_res_ = yres_ = pixGetYRes(src); Init(); }
/*! * pixMultConstantGray() * * Input: pixs (8, 16 or 32 bpp) * val (>= 0.0; amount to multiply by each pixel) * Return: 0 if OK, 1 on error * * Notes: * (1) In-place operation; val must be >= 0. * (2) No clipping for 32 bpp. * (3) For 8 and 16 bpp, the result is clipped to 0xff and 0xffff, rsp. */ l_int32 pixMultConstantGray(PIX *pixs, l_float32 val) { l_int32 w, h, d, wpl; l_uint32 *data; PROCNAME("pixMultConstantGray"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d != 8 && d != 16 && d != 32) return ERROR_INT("pixs not 8, 16 or 32 bpp", procName, 1); if (val < 0.0) return ERROR_INT("val < 0.0", procName, 1); data = pixGetData(pixs); wpl = pixGetWpl(pixs); multConstantGrayLow(data, w, h, d, wpl, val); return 0; }
/*! * 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; }
/*! * \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; }
int main(int argc, char **argv) { l_int32 i, j, k, w, h, w2, w4, w8, w16, w32, wpl; l_int32 count1, count2, count3; l_uint32 val32, val1, val2; l_uint32 *data1, *line1, *data2, *line2; void **lines1, **linet1, **linet2; PIX *pixs, *pix1, *pix2; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; pixs = pixRead("feyn-fract.tif"); pixGetDimensions(pixs, &w, &h, NULL); data1 = pixGetData(pixs); wpl = pixGetWpl(pixs); lines1 = pixGetLinePtrs(pixs, NULL); /* Get timing for the 3 different methods */ startTimer(); for (k = 0; k < 10; k++) { count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (GET_DATA_BIT(lines1[i], j)) count1++; } } } fprintf(stderr, "Time with line ptrs = %5.3f sec, count1 = %d\n", stopTimer(), count1); startTimer(); for (k = 0; k < 10; k++) { count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; for (j = 0; j < w; j++) { if (l_getDataBit(line1, j)) count2++; } } } fprintf(stderr, "Time with l_get* = %5.3f sec, count2 = %d\n", stopTimer(), count2); startTimer(); for (k = 0; k < 10; k++) { count3 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val32); count3 += val32; } } } fprintf(stderr, "Time with pixGetPixel() = %5.3f sec, count3 = %d\n", stopTimer(), count3); pix1 = pixCreateTemplate(pixs); linet1 = pixGetLinePtrs(pix1, NULL); pix2 = pixCreateTemplate(pixs); data2 = pixGetData(pix2); linet2 = pixGetLinePtrs(pix2, NULL); /* ------------------------------------------------- */ /* Test different methods for 1 bpp */ /* ------------------------------------------------- */ count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { val1 = GET_DATA_BIT(lines1[i], j); count1 += val1; if (val1) SET_DATA_BIT(linet1[i], j); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w; j++) { val2 = l_getDataBit(line1, j); count2 += val2; if (val2) l_setDataBit(line2, j); } } CompareResults(pixs, pix1, pix2, count1, count2, "1 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 2 bpp */ /* ------------------------------------------------- */ count1 = 0; w2 = w / 2; for (i = 0; i < h; i++) { for (j = 0; j < w2; j++) { val1 = GET_DATA_DIBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbbc; SET_DATA_DIBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w2; j++) { val2 = l_getDataDibit(line1, j); count2 += val2; val2 += 0xbbbbbbbc; l_setDataDibit(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "2 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 4 bpp */ /* ------------------------------------------------- */ count1 = 0; w4 = w / 4; for (i = 0; i < h; i++) { for (j = 0; j < w4; j++) { val1 = GET_DATA_QBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbb0; SET_DATA_QBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w4; j++) { val2 = l_getDataQbit(line1, j); count2 += val2; val2 += 0xbbbbbbb0; l_setDataQbit(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "4 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 8 bpp */ /* ------------------------------------------------- */ count1 = 0; w8 = w / 8; for (i = 0; i < h; i++) { for (j = 0; j < w8; j++) { val1 = GET_DATA_BYTE(lines1[i], j); count1 += val1; val1 += 0xbbbbbb00; SET_DATA_BYTE(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w8; j++) { val2 = l_getDataByte(line1, j); count2 += val2; val2 += 0xbbbbbb00; l_setDataByte(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "8 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 16 bpp */ /* ------------------------------------------------- */ count1 = 0; w16 = w / 16; for (i = 0; i < h; i++) { for (j = 0; j < w16; j++) { val1 = GET_DATA_TWO_BYTES(lines1[i], j); count1 += val1; val1 += 0xbbbb0000; SET_DATA_TWO_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w16; j++) { val2 = l_getDataTwoBytes(line1, j); count2 += val2; val2 += 0xbbbb0000; l_setDataTwoBytes(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "16 bpp", rp); /* ------------------------------------------------- */ /* Test different methods for 32 bpp */ /* ------------------------------------------------- */ count1 = 0; w32 = w / 32; for (i = 0; i < h; i++) { for (j = 0; j < w32; j++) { val1 = GET_DATA_FOUR_BYTES(lines1[i], j); count1 += val1 & 0xfff; SET_DATA_FOUR_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line1 = data1 + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w32; j++) { val2 = l_getDataFourBytes(line1, j); count2 += val2 & 0xfff; l_setDataFourBytes(line2, j, val2); } } CompareResults(pixs, pix1, pix2, count1, count2, "32 bpp", rp); pixDestroy(&pixs); pixDestroy(&pix1); pixDestroy(&pix2); lept_free(lines1); lept_free(linet1); lept_free(linet2); return regTestCleanup(rp); }
/*! * pixReadStreamPng() * * Input: stream * Return: pix, or null on error * * Notes: * (1) If called from pixReadStream(), the stream is positioned * at the beginning of the file. * (2) To do sequential reads of png format images from a stream, * use pixReadStreamPng() */ PIX * pixReadStreamPng(FILE *fp) { l_uint8 rval, gval, bval; l_int32 i, j, k; l_int32 wpl, d, spp, cindex; l_uint32 png_transforms; l_uint32 *data, *line, *ppixel; int num_palette, num_text; png_byte bit_depth, color_type, channels; png_uint_32 w, h, rowbytes; png_uint_32 xres, yres; png_bytep rowptr; png_bytep *row_pointers; png_structp png_ptr; png_infop info_ptr, end_info; png_colorp palette; png_textp text_ptr; /* ptr to text_chunk */ PIX *pix; PIXCMAP *cmap; PROCNAME("pixReadStreamPng"); if (!fp) return (PIX *)ERROR_PTR("fp not defined", procName, NULL); pix = NULL; /* Allocate the 3 data structures */ if ((png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, NULL, NULL)) == NULL) return (PIX *)ERROR_PTR("png_ptr not made", procName, NULL); if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) { png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); return (PIX *)ERROR_PTR("info_ptr not made", procName, NULL); } if ((end_info = png_create_info_struct(png_ptr)) == NULL) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); return (PIX *)ERROR_PTR("end_info not made", procName, NULL); } /* Set up png setjmp error handling */ if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return (PIX *)ERROR_PTR("internal png error", procName, NULL); } png_init_io(png_ptr, fp); /* ---------------------------------------------------------- * * Set the transforms flags. Whatever happens here, * NEVER invert 1 bpp using PNG_TRANSFORM_INVERT_MONO. * ---------------------------------------------------------- */ /* To strip 16 --> 8 bit depth, use PNG_TRANSFORM_STRIP_16 */ if (var_PNG_STRIP_16_TO_8 == 1) /* our default */ png_transforms = PNG_TRANSFORM_STRIP_16; else png_transforms = PNG_TRANSFORM_IDENTITY; /* To remove alpha channel, use PNG_TRANSFORM_STRIP_ALPHA */ if (var_PNG_STRIP_ALPHA == 1) /* our default */ png_transforms |= PNG_TRANSFORM_STRIP_ALPHA; /* Read it */ png_read_png(png_ptr, info_ptr, png_transforms, NULL); row_pointers = png_get_rows(png_ptr, info_ptr); w = png_get_image_width(png_ptr, info_ptr); h = png_get_image_height(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); rowbytes = png_get_rowbytes(png_ptr, info_ptr); color_type = png_get_color_type(png_ptr, info_ptr); channels = png_get_channels(png_ptr, info_ptr); spp = channels; if (spp == 1) d = bit_depth; else if (spp == 2) { d = 2 * bit_depth; L_WARNING("there shouldn't be 2 spp!", procName); } else /* spp == 3 (rgb), spp == 4 (rgba) */ d = 4 * bit_depth; /* Remove if/when this is implemented for all bit_depths */ if (spp == 3 && bit_depth != 8) { fprintf(stderr, "Help: spp = 3 and depth = %d != 8\n!!", bit_depth); png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return (PIX *)ERROR_PTR("not implemented for this depth", procName, NULL); } if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_MASK_PALETTE) { /* generate a colormap */ png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); cmap = pixcmapCreate(d); /* spp == 1 */ for (cindex = 0; cindex < num_palette; cindex++) { rval = palette[cindex].red; gval = palette[cindex].green; bval = palette[cindex].blue; pixcmapAddColor(cmap, rval, gval, bval); } } else cmap = NULL; if ((pix = pixCreate(w, h, d)) == NULL) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return (PIX *)ERROR_PTR("pix not made", procName, NULL); } wpl = pixGetWpl(pix); data = pixGetData(pix); pixSetColormap(pix, cmap); if (spp == 1) { /* copy straight from buffer to pix */ for (i = 0; i < h; i++) { line = data + i * wpl; rowptr = row_pointers[i]; for (j = 0; j < rowbytes; j++) { SET_DATA_BYTE(line, j, rowptr[j]); } } } else { /* spp == 3 or spp == 4 */ for (i = 0; i < h; i++) { ppixel = data + i * wpl; rowptr = row_pointers[i]; for (j = k = 0; j < w; j++) { SET_DATA_BYTE(ppixel, COLOR_RED, rowptr[k++]); SET_DATA_BYTE(ppixel, COLOR_GREEN, rowptr[k++]); SET_DATA_BYTE(ppixel, COLOR_BLUE, rowptr[k++]); if (spp == 4) SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, rowptr[k++]); ppixel++; } } } #if DEBUG if (cmap) { for (i = 0; i < 16; i++) { fprintf(stderr, "[%d] = %d\n", i, ((l_uint8 *)(cmap->array))[i]); } } #endif /* DEBUG */ /* If there is no colormap, PNG defines black = 0 and * white = 1 by default for binary monochrome. Therefore, * since we use the opposite definition, we must invert * the image colors in either of these cases: * (i) there is no colormap (default) * (ii) there is a colormap which defines black to * be 0 and white to be 1. * We cannot use the PNG_TRANSFORM_INVERT_MONO flag * because that flag (since version 1.0.9) inverts 8 bpp * grayscale as well, which we don't want to do. * (It also doesn't work if there is a colormap.) * If there is a colormap that defines black = 1 and * white = 0, we don't need to do anything. * * How do we check the polarity of the colormap? * The colormap determines the values of black and * white pixels in the following way: * if black = 1 (255), white = 0 * 255, 255, 255, 0, 0, 0, 0, 0, 0 * if black = 0, white = 1 (255) * 0, 0, 0, 0, 255, 255, 255, 0 * So we test the first byte to see if it is 0; * if so, invert the colors. * * If there is a colormap, after inverting the pixels it is * necessary to destroy the colormap. Otherwise, if someone were * to call pixRemoveColormap(), this would cause the pixel * values to be inverted again! */ if (d == 1 && (!cmap || (cmap && ((l_uint8 *)(cmap->array))[0] == 0x0))) { /* fprintf(stderr, "Inverting binary data on png read\n"); */ pixInvert(pix, pix); pixDestroyColormap(pix); } xres = png_get_x_pixels_per_meter(png_ptr, info_ptr); yres = png_get_y_pixels_per_meter(png_ptr, info_ptr); pixSetXRes(pix, (l_int32)((l_float32)xres / 39.37 + 0.5)); /* to ppi */ pixSetYRes(pix, (l_int32)((l_float32)yres / 39.37 + 0.5)); /* to ppi */ /* Get the text if there is any */ png_get_text(png_ptr, info_ptr, &text_ptr, &num_text); if (num_text && text_ptr) pixSetText(pix, text_ptr->text); png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return pix; }
/*! * \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; }
/*! * \brief pixReadMemBmp() * * \param[in] cdata bmp data * \param[in] size number of bytes of bmp-formatted data * \return pix, or NULL on error */ PIX * pixReadMemBmp(const l_uint8 *cdata, size_t size) { l_uint8 pel[4]; l_uint8 *cmapBuf, *fdata, *data; l_int16 bftype, offset, depth, d; l_int32 width, height, xres, yres, compression, imagebytes; l_int32 cmapbytes, cmapEntries; l_int32 fdatabpl, extrabytes, pixWpl, pixBpl, i, j, k; l_uint32 *line, *pixdata, *pword; l_int64 npixels; BMP_FH *bmpfh; BMP_IH *bmpih; PIX *pix, *pix1; PIXCMAP *cmap; PROCNAME("pixReadMemBmp"); if (!cdata) return (PIX *)ERROR_PTR("cdata not defined", procName, NULL); if (size < sizeof(BMP_FH) + sizeof(BMP_IH)) return (PIX *)ERROR_PTR("bmf size error", procName, NULL); /* Verify this is an uncompressed bmp */ bmpfh = (BMP_FH *)cdata; bftype = convertOnBigEnd16(bmpfh->bfType); if (bftype != BMP_ID) return (PIX *)ERROR_PTR("not bmf format", procName, NULL); bmpih = (BMP_IH *)(cdata + BMP_FHBYTES); if (!bmpih) return (PIX *)ERROR_PTR("bmpih not defined", procName, NULL); compression = convertOnBigEnd32(bmpih->biCompression); if (compression != 0) return (PIX *)ERROR_PTR("cannot read compressed BMP files", procName, NULL); /* Read the rest of the useful header information */ offset = convertOnBigEnd16(bmpfh->bfOffBits); width = convertOnBigEnd32(bmpih->biWidth); height = convertOnBigEnd32(bmpih->biHeight); depth = convertOnBigEnd16(bmpih->biBitCount); imagebytes = convertOnBigEnd32(bmpih->biSizeImage); xres = convertOnBigEnd32(bmpih->biXPelsPerMeter); yres = convertOnBigEnd32(bmpih->biYPelsPerMeter); /* Some sanity checking. We impose limits on the image * dimensions and number of pixels. We make sure the file * is the correct size to hold the amount of uncompressed data * that is specified in the header. The number of colormap * entries is checked: it can be either 0 (no cmap) or some * number between 2 and 256. * Note that the imagebytes for uncompressed images is either * 0 or the size of the file data. (The fact that it can * be 0 is perhaps some legacy glitch). */ if (width < 1) return (PIX *)ERROR_PTR("width < 1", procName, NULL); if (width > L_MAX_ALLOWED_WIDTH) return (PIX *)ERROR_PTR("width too large", procName, NULL); if (height < 1) return (PIX *)ERROR_PTR("height < 1", procName, NULL); if (height > L_MAX_ALLOWED_HEIGHT) return (PIX *)ERROR_PTR("height too large", procName, NULL); npixels = 1LL * width * height; if (npixels > L_MAX_ALLOWED_PIXELS) return (PIX *)ERROR_PTR("npixels too large", procName, NULL); if (depth != 1 && depth != 2 && depth != 4 && depth != 8 && depth != 16 && depth != 24 && depth != 32) return (PIX *)ERROR_PTR("depth not in {1, 2, 4, 8, 16, 24, 32}", procName,NULL); fdatabpl = 4 * ((1LL * width * depth + 31)/32); if (imagebytes != 0 && imagebytes != fdatabpl * height) return (PIX *)ERROR_PTR("invalid imagebytes", procName, NULL); cmapbytes = offset - BMP_FHBYTES - BMP_IHBYTES; cmapEntries = cmapbytes / sizeof(RGBA_QUAD); if (cmapEntries < 0 || cmapEntries == 1) return (PIX *)ERROR_PTR("invalid: cmap size < 0 or 1", procName, NULL); if (cmapEntries > L_MAX_ALLOWED_NUM_COLORS) return (PIX *)ERROR_PTR("invalid cmap: too large", procName,NULL); if (size != 1LL * offset + 1LL * fdatabpl * height) return (PIX *)ERROR_PTR("size incommensurate with image data", procName,NULL); /* Handle the colormap */ cmapBuf = NULL; if (cmapEntries > 0) { if ((cmapBuf = (l_uint8 *)LEPT_CALLOC(cmapEntries, sizeof(RGBA_QUAD))) == NULL) return (PIX *)ERROR_PTR("cmapBuf alloc fail", procName, NULL ); /* Read the colormap entry data from bmp. The RGBA_QUAD colormap * entries are used for both bmp and leptonica colormaps. */ memcpy(cmapBuf, cdata + BMP_FHBYTES + BMP_IHBYTES, sizeof(RGBA_QUAD) * cmapEntries); } /* Make a 32 bpp pix if depth is 24 bpp */ d = (depth == 24) ? 32 : depth; if ((pix = pixCreate(width, height, d)) == NULL) { LEPT_FREE(cmapBuf); return (PIX *)ERROR_PTR( "pix not made", procName, NULL); } pixSetXRes(pix, (l_int32)((l_float32)xres / 39.37 + 0.5)); /* to ppi */ pixSetYRes(pix, (l_int32)((l_float32)yres / 39.37 + 0.5)); /* to ppi */ pixSetInputFormat(pix, IFF_BMP); pixWpl = pixGetWpl(pix); pixBpl = 4 * pixWpl; /* Convert the bmp colormap to a pixcmap */ cmap = NULL; if (cmapEntries > 0) { /* import the colormap to the pix cmap */ cmap = pixcmapCreate(L_MIN(d, 8)); LEPT_FREE(cmap->array); /* remove generated cmap array */ cmap->array = (void *)cmapBuf; /* and replace */ cmap->n = L_MIN(cmapEntries, 256); for (i = 0; i < cmap->n; i++) /* set all colors opaque */ pixcmapSetAlpha (cmap, i, 255); } pixSetColormap(pix, cmap); /* Acquire the image data. Image origin for bmp is at lower right. */ fdata = (l_uint8 *)cdata + offset; /* start of the bmp image data */ pixdata = pixGetData(pix); if (depth != 24) { /* typ. 1 or 8 bpp */ data = (l_uint8 *)pixdata + pixBpl * (height - 1); for (i = 0; i < height; i++) { memcpy(data, fdata, fdatabpl); fdata += fdatabpl; data -= pixBpl; } } else { /* 24 bpp file; 32 bpp pix * Note: for bmp files, pel[0] is blue, pel[1] is green, * and pel[2] is red. This is opposite to the storage * in the pix, which puts the red pixel in the 0 byte, * the green in the 1 byte and the blue in the 2 byte. * Note also that all words are endian flipped after * assignment on L_LITTLE_ENDIAN platforms. * * We can then make these assignments for little endians: * SET_DATA_BYTE(pword, 1, pel[0]); blue * SET_DATA_BYTE(pword, 2, pel[1]); green * SET_DATA_BYTE(pword, 3, pel[2]); red * This looks like: * 3 (R) 2 (G) 1 (B) 0 * |-----------|------------|-----------|-----------| * and after byte flipping: * 3 2 (B) 1 (G) 0 (R) * |-----------|------------|-----------|-----------| * * For big endians we set: * SET_DATA_BYTE(pword, 2, pel[0]); blue * SET_DATA_BYTE(pword, 1, pel[1]); green * SET_DATA_BYTE(pword, 0, pel[2]); red * This looks like: * 0 (R) 1 (G) 2 (B) 3 * |-----------|------------|-----------|-----------| * so in both cases we get the correct assignment in the PIX. * * Can we do a platform-independent assignment? * Yes, set the bytes without using macros: * *((l_uint8 *)pword) = pel[2]; red * *((l_uint8 *)pword + 1) = pel[1]; green * *((l_uint8 *)pword + 2) = pel[0]; blue * For little endians, before flipping, this looks again like: * 3 (R) 2 (G) 1 (B) 0 * |-----------|------------|-----------|-----------| */ extrabytes = fdatabpl - 3 * width; line = pixdata + pixWpl * (height - 1); for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { pword = line + j; memcpy(&pel, fdata, 3); fdata += 3; *((l_uint8 *)pword + COLOR_RED) = pel[2]; *((l_uint8 *)pword + COLOR_GREEN) = pel[1]; *((l_uint8 *)pword + COLOR_BLUE) = pel[0]; } if (extrabytes) { for (k = 0; k < extrabytes; k++) { memcpy(&pel, fdata, 1); fdata++; } } line -= pixWpl; } } pixEndianByteSwap(pix); /* ---------------------------------------------- * The bmp colormap determines the values of black * and white pixels for binary in the following way: * (a) white = 0 [255], black = 1 [0] * 255, 255, 255, 255, 0, 0, 0, 255 * (b) black = 0 [0], white = 1 [255] * 0, 0, 0, 255, 255, 255, 255, 255 * We have no need for a 1 bpp pix with a colormap! * Note: the alpha component here is 255 (opaque) * ---------------------------------------------- */ if (depth == 1 && cmap) { pix1 = pixRemoveColormap(pix, REMOVE_CMAP_TO_BINARY); pixDestroy(&pix); pix = pix1; /* rename */ } return pix; }
/*! * pixSauvolaGetThreshold() * * Input: pixm (8 bpp grayscale; not colormapped) * pixms (32 bpp) * factor (factor for reducing threshold due to variance; >= 0) * &pixsd (<optional return> local standard deviation) * Return: pixd (8 bpp, sauvola threshold values), or null on error * * Notes: * (1) The Sauvola threshold is determined from the formula: * t = m * (1 - k * (1 - s / 128)) * where: * t = local threshold * m = local mean * k = @factor (>= 0) [ typ. 0.35 ] * s = local standard deviation, which is maximized at * 127.5 when half the samples are 0 and half are 255. * (2) See pixSauvolaBinarize() for other details. * (3) Important definitions and relations for computing averages: * v == pixel value * E(p) == expected value of p == average of p over some pixel set * S(v) == square of v == v * v * mv == E(v) == expected pixel value == mean value * ms == E(S(v)) == expected square of pixel values * == mean square value * var == variance == expected square of deviation from mean * == E(S(v - mv)) = E(S(v) - 2 * S(v * mv) + S(mv)) * = E(S(v)) - S(mv) * = ms - mv * mv * s == standard deviation = sqrt(var) * So for evaluating the standard deviation in the Sauvola * threshold, we take * s = sqrt(ms - mv * mv) */ PIX * pixSauvolaGetThreshold(PIX *pixm, PIX *pixms, l_float32 factor, PIX **ppixsd) { l_int32 i, j, w, h, tabsize, wplm, wplms, wplsd, wpld, usetab; l_int32 mv, ms, var, thresh; l_uint32 *datam, *datams, *datasd, *datad; l_uint32 *linem, *linems, *linesd, *lined; l_float32 sd; l_float32 *tab; /* of 2^16 square roots */ PIX *pixsd, *pixd; PROCNAME("pixSauvolaGetThreshold"); if (ppixsd) *ppixsd = NULL; if (!pixm || pixGetDepth(pixm) != 8) return (PIX *)ERROR_PTR("pixm undefined or not 8 bpp", procName, NULL); if (pixGetColormap(pixm)) return (PIX *)ERROR_PTR("pixm is colormapped", procName, NULL); if (!pixms || pixGetDepth(pixms) != 32) return (PIX *)ERROR_PTR("pixms undefined or not 32 bpp", procName, NULL); if (factor < 0.0) return (PIX *)ERROR_PTR("factor must be >= 0", procName, NULL); /* Only make a table of 2^16 square roots if there * are enough pixels to justify it. */ pixGetDimensions(pixm, &w, &h, NULL); usetab = (w * h > 100000) ? 1 : 0; if (usetab) { tabsize = 1 << 16; tab = (l_float32 *)CALLOC(tabsize, sizeof(l_float32)); for (i = 0; i < tabsize; i++) tab[i] = (l_float32)sqrt((l_float64)i); } pixd = pixCreate(w, h, 8); if (ppixsd) { pixsd = pixCreate(w, h, 8); *ppixsd = pixsd; } datam = pixGetData(pixm); datams = pixGetData(pixms); if (ppixsd) datasd = pixGetData(pixsd); datad = pixGetData(pixd); wplm = pixGetWpl(pixm); wplms = pixGetWpl(pixms); if (ppixsd) wplsd = pixGetWpl(pixsd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { linem = datam + i * wplm; linems = datams + i * wplms; if (ppixsd) linesd = datasd + i * wplsd; lined = datad + i * wpld; for (j = 0; j < w; j++) { mv = GET_DATA_BYTE(linem, j); ms = linems[j]; var = ms - mv * mv; if (usetab) sd = tab[var]; else sd = (l_float32)sqrt((l_float32)var); if (ppixsd) SET_DATA_BYTE(linesd, j, (l_int32)sd); thresh = (l_int32)(mv * (1.0 - factor * (1.0 - sd / 128.))); SET_DATA_BYTE(lined, j, thresh); } } if (usetab) FREE(tab); return pixd; }
/*! * pixFindLargestRectangle() * * Input: pixs (1 bpp) * polarity (0 within background, 1 within foreground) * &box (<return> largest rectangle, either by area or * by perimeter) * debugflag (1 to output image with rectangle drawn on it) * Return: 0 if OK, 1 on error * * Notes: * (1) Why is this here? This is a simple and elegant solution to * a problem in computational geometry that at first appears * quite difficult: what is the largest rectangle that can * be placed in the image, covering only pixels of one polarity * (bg or fg)? The solution is O(n), where n is the number * of pixels in the image, and it requires nothing more than * using a simple recursion relation in a single sweep of the image. * (2) In a sweep from UL to LR with left-to-right being the fast * direction, calculate the largest white rectangle at (x, y), * using previously calculated values at pixels #1 and #2: * #1: (x, y - 1) * #2: (x - 1, y) * We also need the most recent "black" pixels that were seen * in the current row and column. * Consider the largest area. There are only two possibilities: * (a) Min(w(1), horizdist) * (h(1) + 1) * (b) Min(h(2), vertdist) * (w(2) + 1) * where * horizdist: the distance from the rightmost "black" pixel seen * in the current row across to the current pixel * vertdist: the distance from the lowest "black" pixel seen * in the current column down to the current pixel * and we choose the Max of (a) and (b). * (3) To convince yourself that these recursion relations are correct, * it helps to draw the maximum rectangles at #1 and #2. * Then for #1, you try to extend the rectangle down one line, * so that the height is h(1) + 1. Do you get the full * width of #1, w(1)? It depends on where the black pixels are * in the current row. You know the final width is bounded by w(1) * and w(2) + 1, but the actual value depends on the distribution * of black pixels in the current row that are at a distance * from the current pixel that is between these limits. * We call that value "horizdist", and the area is then given * by the expression (a) above. Using similar reasoning for #2, * where you attempt to extend the rectangle to the right * by 1 pixel, you arrive at (b). The largest rectangle is * then found by taking the Max. */ l_int32 pixFindLargestRectangle(PIX *pixs, l_int32 polarity, BOX **pbox, const char *debugfile) { l_int32 i, j, w, h, d, wpls, val; l_int32 wp, hp, w1, w2, h1, h2, wmin, hmin, area1, area2; l_int32 xmax, ymax; /* LR corner of the largest rectangle */ l_int32 maxarea, wmax, hmax, vertdist, horizdist, prevfg; l_int32 *lowestfg; l_uint32 *datas, *lines; l_uint32 **linew, **lineh; BOX *box; PIX *pixw, *pixh; /* keeps the width and height for the largest */ /* rectangles whose LR corner is located there. */ PROCNAME("pixFindLargestRectangle"); if (!pbox) return ERROR_INT("&box not defined", procName, 1); *pbox = NULL; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d != 1) return ERROR_INT("pixs not 1 bpp", procName, 1); if (polarity != 0 && polarity != 1) return ERROR_INT("invalid polarity", procName, 1); /* Initialize lowest "fg" seen so far for each column */ lowestfg = (l_int32 *)CALLOC(w, sizeof(l_int32)); for (i = 0; i < w; i++) lowestfg[i] = -1; /* The combination (val ^ polarity) is the color for which we * are searching for the maximum rectangle. For polarity == 0, * we search in the bg (white). */ pixw = pixCreate(w, h, 32); /* stores width */ pixh = pixCreate(w, h, 32); /* stores height */ linew = (l_uint32 **)pixGetLinePtrs(pixw, NULL); lineh = (l_uint32 **)pixGetLinePtrs(pixh, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); maxarea = xmax = ymax = wmax = hmax = 0; for (i = 0; i < h; i++) { lines = datas + i * wpls; prevfg = -1; for (j = 0; j < w; j++) { val = GET_DATA_BIT(lines, j); if ((val ^ polarity) == 0) { /* bg (0) if polarity == 0, etc. */ if (i == 0 && j == 0) { wp = hp = 1; } else if (i == 0) { wp = linew[i][j - 1] + 1; hp = 1; } else if (j == 0) { wp = 1; hp = lineh[i - 1][j] + 1; } else { /* Expand #1 prev rectangle down */ w1 = linew[i - 1][j]; h1 = lineh[i - 1][j]; horizdist = j - prevfg; wmin = L_MIN(w1, horizdist); /* width of new rectangle */ area1 = wmin * (h1 + 1); /* Expand #2 prev rectangle to right */ w2 = linew[i][j - 1]; h2 = lineh[i][j - 1]; vertdist = i - lowestfg[j]; hmin = L_MIN(h2, vertdist); /* height of new rectangle */ area2 = hmin * (w2 + 1); if (area1 > area2) { wp = wmin; hp = h1 + 1; } else { wp = w2 + 1; hp = hmin; } } } else { /* fg (1) if polarity == 0; bg (0) if polarity == 1 */ prevfg = j; lowestfg[j] = i; wp = hp = 0; } linew[i][j] = wp; lineh[i][j] = hp; if (wp * hp > maxarea) { maxarea = wp * hp; xmax = j; ymax = i; wmax = wp; hmax = hp; } } } /* Translate from LR corner to Box coords (UL corner, w, h) */ box = boxCreate(xmax - wmax + 1, ymax - hmax + 1, wmax, hmax); *pbox = box; if (debugfile) { PIX *pixdb; pixdb = pixConvertTo8(pixs, TRUE); pixRenderHashBoxArb(pixdb, box, 6, 2, L_NEG_SLOPE_LINE, 1, 255, 0, 0); pixWrite(debugfile, pixdb, IFF_PNG); pixDestroy(&pixdb); } FREE(linew); FREE(lineh); FREE(lowestfg); pixDestroy(&pixw); pixDestroy(&pixh); return 0; }
/********************************************************************** * SetBlobStrokeWidth * * Set the horizontal and vertical stroke widths in the blob. **********************************************************************/ void SetBlobStrokeWidth(Pix* pix, BLOBNBOX* blob) { // Cut the blob rectangle into a Pix. int pix_height = pixGetHeight(pix); const TBOX& box = blob->bounding_box(); int width = box.width(); int height = box.height(); Box* blob_pix_box = boxCreate(box.left(), pix_height - box.top(), width, height); Pix* pix_blob = pixClipRectangle(pix, blob_pix_box, nullptr); boxDestroy(&blob_pix_box); Pix* dist_pix = pixDistanceFunction(pix_blob, 4, 8, L_BOUNDARY_BG); pixDestroy(&pix_blob); // Compute the stroke widths. uint32_t* data = pixGetData(dist_pix); int wpl = pixGetWpl(dist_pix); // Horizontal width of stroke. STATS h_stats(0, width + 1); for (int y = 0; y < height; ++y) { uint32_t* pixels = data + y*wpl; int prev_pixel = 0; int pixel = GET_DATA_BYTE(pixels, 0); for (int x = 1; x < width; ++x) { int next_pixel = GET_DATA_BYTE(pixels, x); // We are looking for a pixel that is equal to its vertical neighbours, // yet greater than its left neighbour. if (prev_pixel < pixel && (y == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) && (y == height - 1 || pixel == GET_DATA_BYTE(pixels + wpl, x - 1))) { if (pixel > next_pixel) { // Single local max, so an odd width. h_stats.add(pixel * 2 - 1, 1); } else if (pixel == next_pixel && x + 1 < width && pixel > GET_DATA_BYTE(pixels, x + 1)) { // Double local max, so an even width. h_stats.add(pixel * 2, 1); } } prev_pixel = pixel; pixel = next_pixel; } } // Vertical width of stroke. STATS v_stats(0, height + 1); for (int x = 0; x < width; ++x) { int prev_pixel = 0; int pixel = GET_DATA_BYTE(data, x); for (int y = 1; y < height; ++y) { uint32_t* pixels = data + y*wpl; int next_pixel = GET_DATA_BYTE(pixels, x); // We are looking for a pixel that is equal to its horizontal neighbours, // yet greater than its upper neighbour. if (prev_pixel < pixel && (x == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) && (x == width - 1 || pixel == GET_DATA_BYTE(pixels - wpl, x + 1))) { if (pixel > next_pixel) { // Single local max, so an odd width. v_stats.add(pixel * 2 - 1, 1); } else if (pixel == next_pixel && y + 1 < height && pixel > GET_DATA_BYTE(pixels + wpl, x)) { // Double local max, so an even width. v_stats.add(pixel * 2, 1); } } prev_pixel = pixel; pixel = next_pixel; } } pixDestroy(&dist_pix); // Store the horizontal and vertical width in the blob, keeping both // widths if there is enough information, otherwse only the one with // the most samples. // If there are insufficient samples, store zero, rather than using // 2*area/perimeter, as the numbers that gives do not match the numbers // from the distance method. if (h_stats.get_total() >= (width + height) / 4) { blob->set_horz_stroke_width(h_stats.ile(0.5f)); if (v_stats.get_total() >= (width + height) / 4) blob->set_vert_stroke_width(v_stats.ile(0.5f)); else blob->set_vert_stroke_width(0.0f); } else { if (v_stats.get_total() >= (width + height) / 4 || v_stats.get_total() > h_stats.get_total()) { blob->set_horz_stroke_width(0.0f); blob->set_vert_stroke_width(v_stats.ile(0.5f)); } else { blob->set_horz_stroke_width(h_stats.get_total() > 2 ? h_stats.ile(0.5f) : 0.0f); blob->set_vert_stroke_width(0.0f); } } }
/*! * pixApplyHorizontalDisparity() * * Input: pixs (1, 8 or 32 bpp) * fpix (horizontal disparity array) * extraw (extra width added to pixd) * Return: pixd (modified by fpix), or null on error * * Notes: * (1) This applies the horizontal disparity array to the specified * image. */ PIX * pixApplyHorizontalDisparity(PIX *pixs, FPIX *fpix, l_int32 extraw) { l_int32 i, j, w, h, d, wd, fw, fh, wpls, wpld, wplf, jsrc, val8; l_uint32 *datas, *lines, *datad, *lined; l_float32 *dataf, *linef; PIX *pixd; PROCNAME("pixApplyHorizontalDisparity"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (!fpix) return (PIX *)ERROR_PTR("fpix not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 1 && d != 8 && d != 32) return (PIX *)ERROR_PTR("pix not 1, 8 or 32 bpp", procName, NULL); fpixGetDimensions(fpix, &fw, &fh); if (fw < w + extraw || fh < h) { fprintf(stderr, "fw = %d, w = %d, fh = %d, h = %d\n", fw, w, fh, h); return (PIX *)ERROR_PTR("invalid fpix size", procName, NULL); } wd = w + extraw; pixd = pixCreate(wd, h, d); datas = pixGetData(pixs); datad = pixGetData(pixd); dataf = fpixGetData(fpix); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); wplf = fpixGetWpl(fpix); if (d == 1) { for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < wd; j++) { jsrc = (l_int32)(j - linef[j] + 0.5); if (jsrc < 0) jsrc = 0; if (jsrc > w - 1) jsrc = w - 1; if (GET_DATA_BIT(lines, jsrc)) SET_DATA_BIT(lined, j); } } } else if (d == 8) { for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < wd; j++) { jsrc = (l_int32)(j - linef[j] + 0.5); if (jsrc < 0) jsrc = 0; if (jsrc > w - 1) jsrc = w - 1; val8 = GET_DATA_BYTE(lines, jsrc); SET_DATA_BYTE(lined, j, val8); } } } else { /* d == 32 */ for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; linef = dataf + i * wplf; for (j = 0; j < wd; j++) { jsrc = (l_int32)(j - linef[j] + 0.5); if (jsrc < 0) jsrc = 0; if (jsrc > w - 1) jsrc = w - 1; lined[j] = lines[jsrc]; } } } return pixd; }
// Adds sub-pixel resolution EdgeOffsets for the outline if the supplied // pix is 8-bit. Does nothing otherwise. // Operation: Consider the following near-horizontal line: // _________ // |________ // |________ // At *every* position along this line, the gradient direction will be close // to vertical. Extrapoaltion/interpolation of the position of the threshold // that was used to binarize the image gives a more precise vertical position // for each horizontal step, and the conflict in step direction and gradient // direction can be used to ignore the vertical steps. void C_OUTLINE::ComputeEdgeOffsets(int threshold, Pix* pix) { if (pixGetDepth(pix) != 8) return; const l_uint32* data = pixGetData(pix); int wpl = pixGetWpl(pix); int width = pixGetWidth(pix); int height = pixGetHeight(pix); bool negative = flag(COUT_INVERSE); delete [] offsets; offsets = new EdgeOffset[stepcount]; ICOORD pos = start; ICOORD prev_gradient; ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &prev_gradient); for (int s = 0; s < stepcount; ++s) { ICOORD step_vec = step(s); TPOINT pt1(pos); pos += step_vec; TPOINT pt2(pos); ICOORD next_gradient; ComputeGradient(data, wpl, pos.x(), height - pos.y(), width, height, &next_gradient); // Use the sum of the prev and next as the working gradient. ICOORD gradient = prev_gradient + next_gradient; // best_diff will be manipulated to be always positive. int best_diff = 0; // offset will be the extrapolation of the location of the greyscale // threshold from the edge with the largest difference, relative to the // location of the binary edge. int offset = 0; if (pt1.y == pt2.y && abs(gradient.y()) * 2 >= abs(gradient.x())) { // Horizontal step. diff_sign == 1 indicates black above. int diff_sign = (pt1.x > pt2.x) == negative ? 1 : -1; int x = MIN(pt1.x, pt2.x); int y = height - pt1.y; int best_sum = 0; int best_y = y; EvaluateVerticalDiff(data, wpl, diff_sign, x, y, height, &best_diff, &best_sum, &best_y); // Find the strongest edge. int test_y = y; do { ++test_y; } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, &best_y)); test_y = y; do { --test_y; } while (EvaluateVerticalDiff(data, wpl, diff_sign, x, test_y, height, &best_diff, &best_sum, &best_y)); offset = diff_sign * (best_sum / 2 - threshold) + (y - best_y) * best_diff; } else if (pt1.x == pt2.x && abs(gradient.x()) * 2 >= abs(gradient.y())) { // Vertical step. diff_sign == 1 indicates black on the left. int diff_sign = (pt1.y > pt2.y) == negative ? 1 : -1; int x = pt1.x; int y = height - MAX(pt1.y, pt2.y); const l_uint32* line = pixGetData(pix) + y * wpl; int best_sum = 0; int best_x = x; EvaluateHorizontalDiff(line, diff_sign, x, width, &best_diff, &best_sum, &best_x); // Find the strongest edge. int test_x = x; do { ++test_x; } while (EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); test_x = x; do { --test_x; } while (EvaluateHorizontalDiff(line, diff_sign, test_x, width, &best_diff, &best_sum, &best_x)); offset = diff_sign * (threshold - best_sum / 2) + (best_x - x) * best_diff; } offsets[s].offset_numerator = static_cast<inT8>(ClipToRange(offset, -MAX_INT8, MAX_INT8)); offsets[s].pixel_diff = static_cast<uinT8>(ClipToRange(best_diff, 0 , MAX_UINT8)); if (negative) gradient = -gradient; // Compute gradient angle quantized to 256 directions, rotated by 64 (pi/2) // to convert from gradient direction to edge direction. offsets[s].direction = Modulo(FCOORD::binary_angle_plus_pi(gradient.angle()) + 64, 256); prev_gradient = next_gradient; } }
jboolean Java_com_googlecode_leptonica_android_WriteFile_nativeWriteBitmap(JNIEnv *env, jclass clazz, jlong nativePix, jobject bitmap) { PIX *pixs = (PIX *) nativePix; l_int32 w, h, d; AndroidBitmapInfo info; void* pixels; int ret; if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); return JNI_FALSE; } if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { LOGE("Bitmap format is not RGBA_8888 !"); return JNI_FALSE; } pixGetDimensions(pixs, &w, &h, &d); if (w != info.width || h != info.height) { LOGE("Bitmap width and height do not match Pix dimensions!"); return JNI_FALSE; } if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return JNI_FALSE; } pixEndianByteSwap(pixs); l_uint8 *dst = (l_uint8 *) pixels; l_uint8 *src = (l_uint8 *) pixGetData(pixs); l_int32 dstBpl = info.stride; l_int32 srcBpl = 4 * pixGetWpl(pixs); LOGE("Writing 32bpp RGBA bitmap (w=%d, h=%d, stride=%d) from %dbpp Pix (wpl=%d)", info.width, info.height, info.stride, d, pixGetWpl(pixs)); for (int dy = 0; dy < info.height; dy++) { l_uint8 *dstx = dst; l_uint8 *srcx = src; if (d == 32) { memcpy(dst, src, 4 * info.width); } else if (d == 8) { for (int dw = 0; dw < info.width; dw++) { dstx[0] = dstx[1] = dstx[2] = srcx[0]; dstx[3] = 0xFF; dstx += 4; srcx += 1; } } else if (d == 1) { for (int dw = 0; dw < info.width; dw++) { dstx[0] = dstx[1] = dstx[2] = (1 << (7 - (dw & 7)) & srcx[0]) ? 0x00 : 0xFF; dstx[3] = 0xFF; dstx += 4; srcx += ((dw % 8) == 7) ? 1 : 0; } } dst += dstBpl; src += srcBpl; } AndroidBitmap_unlockPixels(env, bitmap); return JNI_TRUE; }
int main(int argc, char **argv) { l_int32 i, j; l_int32 w, h, bw, bh, wpls, rval, gval, bval, same; l_uint32 pixel; l_uint32 *lines, *datas; l_float32 sum1, sum2, ave1, ave2, ave3, ave4, diff1, diff2; l_float32 var1, var2, var3; BOX *box1, *box2; NUMA *na, *na1, *na2, *na3, *na4; PIX *pix, *pixs, *pix1, *pix2, *pix3, *pix4, *pix5, *pixg, *pixd; PIXA *pixa; static char mainName[] = "numa2_reg"; if (argc != 1) return ERROR_INT(" Syntax: numa2_reg", mainName, 1); lept_mkdir("lept/numa2"); /* -------------------------------------------------------------------* * Numa-windowed stats * * -------------------------------------------------------------------*/ #if DO_ALL na = numaRead("lyra.5.na"); numaWindowedStats(na, 5, &na1, &na2, &na3, &na4); gplotSimple1(na, GPLOT_PNG, "/tmp/lept/numa2/lyra6", "Original"); gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/numa2/lyra7", "Mean"); gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/numa2/lyra8", "Mean Square"); gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/numa2/lyra9", "Variance"); gplotSimple1(na4, GPLOT_PNG, "/tmp/lept/numa2/lyra10", "RMS Difference"); pixa = pixaCreate(5); pix1 = pixRead("/tmp/lept/numa2/lyra6.png"); pix2 = pixRead("/tmp/lept/numa2/lyra7.png"); pix3 = pixRead("/tmp/lept/numa2/lyra8.png"); pix4 = pixRead("/tmp/lept/numa2/lyra9.png"); pix5 = pixRead("/tmp/lept/numa2/lyra10.png"); pixaAddPix(pixa, pix1, L_INSERT); pixaAddPix(pixa, pix2, L_INSERT); pixaAddPix(pixa, pix3, L_INSERT); pixaAddPix(pixa, pix4, L_INSERT); pixaAddPix(pixa, pix5, L_INSERT); pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2); pixDisplay(pixd, 100, 0); pixWrite("/tmp/lept/numa2/window.png", pixd, IFF_PNG); pixDestroy(&pixd); pixaDestroy(&pixa); numaDestroy(&na); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); numaDestroy(&na4); #endif /* -------------------------------------------------------------------* * Extraction on a line * * -------------------------------------------------------------------*/ #if DO_ALL /* First, make a pretty image */ w = h = 200; pixs = pixCreate(w, h, 32); wpls = pixGetWpl(pixs); datas = pixGetData(pixs); for (i = 0; i < 200; i++) { lines = datas + i * wpls; for (j = 0; j < 200; j++) { rval = (l_int32)((255. * j) / w + (255. * i) / h); gval = (l_int32)((255. * 2 * j) / w + (255. * 2 * i) / h) % 255; bval = (l_int32)((255. * 4 * j) / w + (255. * 4 * i) / h) % 255; composeRGBPixel(rval, gval, bval, &pixel); lines[j] = pixel; } } pixg = pixConvertTo8(pixs, 0); /* and a grayscale version */ pixWrite("/tmp/lept/numa_pixg.png", pixg, IFF_PNG); pixDisplay(pixg, 450, 100); na1 = pixExtractOnLine(pixg, 20, 20, 180, 20, 1); na2 = pixExtractOnLine(pixg, 40, 30, 40, 170, 1); na3 = pixExtractOnLine(pixg, 20, 170, 180, 30, 1); na4 = pixExtractOnLine(pixg, 20, 190, 180, 10, 1); gplotSimple1(na1, GPLOT_PNG, "/tmp/lept/numa2/ext1", "Horizontal"); gplotSimple1(na2, GPLOT_PNG, "/tmp/lept/numa2/ext2", "Vertical"); gplotSimple1(na3, GPLOT_PNG, "/tmp/lept/numa2/ext3", "Slightly more horizontal than vertical"); gplotSimple1(na4, GPLOT_PNG, "/tmp/lept/numa2/ext4", "Slightly more vertical than horizontal"); pixa = pixaCreate(4); pix1 = pixRead("/tmp/lept/numa2/ext1.png"); pix2 = pixRead("/tmp/lept/numa2/ext2.png"); pix3 = pixRead("/tmp/lept/numa2/ext3.png"); pix4 = pixRead("/tmp/lept/numa2/ext4.png"); pixaAddPix(pixa, pix1, L_INSERT); pixaAddPix(pixa, pix2, L_INSERT); pixaAddPix(pixa, pix3, L_INSERT); pixaAddPix(pixa, pix4, L_INSERT); pixd = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 20, 2); pixDisplay(pixd, 100, 450); pixWrite("/tmp/lept/numa2/extract.png", pixd, IFF_PNG); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixg); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); numaDestroy(&na4); #endif /* -------------------------------------------------------------------* * Row and column pixel sums * * -------------------------------------------------------------------*/ #if DO_ALL /* Sum by columns in two halves (left and right) */ pixs = pixRead("test8.jpg"); pixGetDimensions(pixs, &w, &h, NULL); box1 = boxCreate(0, 0, w / 2, h); box2 = boxCreate(w / 2, 0, w - 2 / 2, h); na1 = pixAverageByColumn(pixs, box1, L_BLACK_IS_MAX); na2 = pixAverageByColumn(pixs, box2, L_BLACK_IS_MAX); numaJoin(na1, na2, 0, -1); na3 = pixAverageByColumn(pixs, NULL, L_BLACK_IS_MAX); numaSimilar(na1, na3, 0.0, &same); if (same) fprintf(stderr, "Same for columns\n"); else fprintf(stderr, "Error for columns\n"); pix = pixConvertTo32(pixs); pixRenderPlotFromNumaGen(&pix, na3, L_HORIZONTAL_LINE, 3, h / 2, 80, 1, 0xff000000); pixRenderPlotFromNuma(&pix, na3, L_PLOT_AT_BOT, 3, 80, 0xff000000); boxDestroy(&box1); boxDestroy(&box2); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); /* Sum by rows in two halves (top and bottom) */ box1 = boxCreate(0, 0, w, h / 2); box2 = boxCreate(0, h / 2, w, h - h / 2); na1 = pixAverageByRow(pixs, box1, L_WHITE_IS_MAX); na2 = pixAverageByRow(pixs, box2, L_WHITE_IS_MAX); numaJoin(na1, na2, 0, -1); na3 = pixAverageByRow(pixs, NULL, L_WHITE_IS_MAX); numaSimilar(na1, na3, 0.0, &same); if (same) fprintf(stderr, "Same for rows\n"); else fprintf(stderr, "Error for rows\n"); pixRenderPlotFromNumaGen(&pix, na3, L_VERTICAL_LINE, 3, w / 2, 80, 1, 0x00ff0000); pixRenderPlotFromNuma(&pix, na3, L_PLOT_AT_RIGHT, 3, 80, 0x00ff0000); pixDisplay(pix, 500, 200); boxDestroy(&box1); boxDestroy(&box2); numaDestroy(&na1); numaDestroy(&na2); numaDestroy(&na3); pixDestroy(&pix); /* Average left by rows; right by columns; compare totals */ box1 = boxCreate(0, 0, w / 2, h); box2 = boxCreate(w / 2, 0, w - 2 / 2, h); na1 = pixAverageByRow(pixs, box1, L_WHITE_IS_MAX); na2 = pixAverageByColumn(pixs, box2, L_WHITE_IS_MAX); numaGetSum(na1, &sum1); /* sum of averages of left box */ numaGetSum(na2, &sum2); /* sum of averages of right box */ ave1 = sum1 / h; ave2 = 2.0 * sum2 / w; ave3 = 0.5 * (ave1 + ave2); /* average over both halves */ fprintf(stderr, "ave1 = %8.4f\n", sum1 / h); fprintf(stderr, "ave2 = %8.4f\n", 2.0 * sum2 / w); pixAverageInRect(pixs, NULL, &ave4); /* entire image */ diff1 = ave4 - ave3; diff2 = w * h * ave4 - (0.5 * w * sum1 + h * sum2); if (diff1 < 0.001) fprintf(stderr, "Average diffs are correct\n"); else fprintf(stderr, "Average diffs are wrong: diff1 = %7.5f\n", diff1); if (diff2 < 20) /* float-to-integer roundoff */ fprintf(stderr, "Pixel sums are correct\n"); else fprintf(stderr, "Pixel sums are in error: diff = %7.0f\n", diff2); /* Variance left and right halves. Variance doesn't average * in a simple way, unlike pixel sums. */ pixVarianceInRect(pixs, box1, &var1); /* entire image */ pixVarianceInRect(pixs, box2, &var2); /* entire image */ pixVarianceInRect(pixs, NULL, &var3); /* entire image */ fprintf(stderr, "0.5 * (var1 + var2) = %7.3f, var3 = %7.3f\n", 0.5 * (var1 + var2), var3); boxDestroy(&box1); boxDestroy(&box2); numaDestroy(&na1); numaDestroy(&na2); #endif /* -------------------------------------------------------------------* * Row and column variances * * -------------------------------------------------------------------*/ #if DO_ALL /* Display variance by rows and columns */ box1 = boxCreate(415, 0, 130, 425); boxGetGeometry(box1, NULL, NULL, &bw, &bh); na1 = pixVarianceByRow(pixs, box1); na2 = pixVarianceByColumn(pixs, box1); pix = pixConvertTo32(pixs); pix1 = pixCopy(NULL, pix); pixRenderPlotFromNumaGen(&pix, na1, L_VERTICAL_LINE, 3, 415, 100, 1, 0xff000000); pixRenderPlotFromNumaGen(&pix, na2, L_HORIZONTAL_LINE, 3, bh / 2, 100, 1, 0x00ff0000); pixRenderPlotFromNuma(&pix1, na1, L_PLOT_AT_LEFT, 3, 60, 0x00ff0000); pixRenderPlotFromNuma(&pix1, na1, L_PLOT_AT_MID_VERT, 3, 60, 0x0000ff00); pixRenderPlotFromNuma(&pix1, na1, L_PLOT_AT_RIGHT, 3, 60, 0xff000000); pixRenderPlotFromNuma(&pix1, na2, L_PLOT_AT_TOP, 3, 60, 0x0000ff00); pixRenderPlotFromNuma(&pix1, na2, L_PLOT_AT_MID_HORIZ, 3, 60, 0xff000000); pixRenderPlotFromNuma(&pix1, na2, L_PLOT_AT_BOT, 3, 60, 0x00ff0000); pixDisplay(pix, 500, 900); pixDisplay(pix1, 500, 1000); boxDestroy(&box1); numaDestroy(&na1); numaDestroy(&na2); pixDestroy(&pix); pixDestroy(&pix1); pixDestroy(&pixs); /* Again on a different image */ pix1 = pixRead("boxedpage.jpg"); pix2 = pixConvertTo8(pix1, 0); pixGetDimensions(pix2, &w, &h, NULL); na1 = pixVarianceByRow(pix2, NULL); pix3 = pixConvertTo32(pix1); pixRenderPlotFromNumaGen(&pix3, na1, L_VERTICAL_LINE, 3, 0, 70, 1, 0xff000000); na2 = pixVarianceByColumn(pix2, NULL); pixRenderPlotFromNumaGen(&pix3, na2, L_HORIZONTAL_LINE, 3, bh - 1, 70, 1, 0x00ff0000); pixDisplay(pix3, 1000, 0); numaDestroy(&na1); numaDestroy(&na2); pixDestroy(&pix3); /* Again, with an erosion */ pix3 = pixErodeGray(pix2, 3, 21); pixDisplay(pix3, 1400, 0); na1 = pixVarianceByRow(pix3, NULL); pix4 = pixConvertTo32(pix1); pixRenderPlotFromNumaGen(&pix4, na1, L_VERTICAL_LINE, 3, 30, 70, 1, 0xff000000); na2 = pixVarianceByColumn(pix3, NULL); pixRenderPlotFromNumaGen(&pix4, na2, L_HORIZONTAL_LINE, 3, bh - 1, 70, 1, 0x00ff0000); pixDisplay(pix4, 1000, 550); numaDestroy(&na1); numaDestroy(&na2); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); #endif /* -------------------------------------------------------------------* * Windowed variance along a line * * -------------------------------------------------------------------*/ #if DO_ALL pix1 = pixRead("boxedpage.jpg"); pix2 = pixConvertTo8(pix1, 0); pixGetDimensions(pix2, &w, &h, NULL); pix3 = pixCopy(NULL, pix1); /* Plot along horizontal line */ pixWindowedVarianceOnLine(pix2, L_HORIZONTAL_LINE, h / 2 - 30, 0, w, 5, &na1); pixRenderPlotFromNumaGen(&pix1, na1, L_HORIZONTAL_LINE, 3, h / 2 - 30, 80, 1, 0xff000000); pixRenderPlotFromNuma(&pix3, na1, L_PLOT_AT_TOP, 3, 60, 0x00ff0000); pixRenderPlotFromNuma(&pix3, na1, L_PLOT_AT_BOT, 3, 60, 0x0000ff00); /* Plot along vertical line */ pixWindowedVarianceOnLine(pix2, L_VERTICAL_LINE, 0.78 * w, 0, h, 5, &na2); pixRenderPlotFromNumaGen(&pix1, na2, L_VERTICAL_LINE, 3, 0.78 * w, 60, 1, 0x00ff0000); pixRenderPlotFromNuma(&pix3, na2, L_PLOT_AT_LEFT, 3, 60, 0xff000000); pixRenderPlotFromNuma(&pix3, na2, L_PLOT_AT_RIGHT, 3, 60, 0x00ff0000); pixDisplay(pix1, 1000, 1000); pixDisplay(pix3, 1500, 1000); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); numaDestroy(&na1); numaDestroy(&na2); #endif 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; }
/*! * pixColorGrayRegionsCmap() * * Input: pixs (8 bpp, with colormap) * boxa (of regions in which to apply color) * type (L_PAINT_LIGHT, L_PAINT_DARK) * rval, gval, bval (target color) * Return: 0 if OK, 1 on error * * Notes: * (1) This is an in-place operation. * (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels, * preserving antialiasing. * If type == L_PAINT_DARK, it colorizes non-white pixels, * preserving antialiasing. See pixColorGrayCmap() for details. * (3) This can also be called through pixColorGrayRegions(). * (4) This increases the colormap size by the number of * different gray (non-black or non-white) colors in the * selected regions of pixs. If there is not enough room in * the colormap for this expansion, it returns 1 (error), * and the caller should check the return value. * (5) Because two boxes in the boxa can overlap, pixels that * are colorized in the first box must be excluded in the * second because their value exceeds the size of the map. */ l_int32 pixColorGrayRegionsCmap(PIX *pixs, BOXA *boxa, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, k, w, h, n, nc, x1, y1, x2, y2, bw, bh, wpl; l_int32 val, nval; l_int32 *map; l_uint32 *line, *data; BOX *box; NUMA *na; PIXCMAP *cmap; PROCNAME("pixColorGrayRegionsCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!boxa) return ERROR_INT("boxa not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); if (pixGetDepth(pixs) != 8) return ERROR_INT("depth not 8 bpp", procName, 1); if (type != L_PAINT_DARK && type != L_PAINT_LIGHT) return ERROR_INT("invalid type", procName, 1); nc = pixcmapGetCount(cmap); if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na)) return ERROR_INT("no room; cmap full", procName, 1); map = numaGetIArray(na); numaDestroy(&na); if (!map) return ERROR_INT("map not made", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); data = pixGetData(pixs); wpl = pixGetWpl(pixs); n = boxaGetCount(boxa); for (k = 0; k < n; k++) { box = boxaGetBox(boxa, k, L_CLONE); boxGetGeometry(box, &x1, &y1, &bw, &bh); x2 = x1 + bw - 1; y2 = y1 + bh - 1; /* Remap gray pixels in the region */ for (i = y1; i <= y2; i++) { if (i < 0 || i >= h) /* clip */ continue; line = data + i * wpl; for (j = x1; j <= x2; j++) { if (j < 0 || j >= w) /* clip */ continue; val = GET_DATA_BYTE(line, j); if (val >= nc) continue; /* from overlapping b.b. */ nval = map[val]; if (nval != 256) SET_DATA_BYTE(line, j, nval); } } boxDestroy(&box); } FREE(map); return 0; }
/*! * \brief pixWriteMemBmp() * * \param[out] pfdata data of bmp formatted image * \param[out] pfsize size of returned data * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) 2 bpp bmp files are not valid in the spec, and are * written as 8 bpp. * (2) pix with depth <= 8 bpp are written with a colormap. * 16 bpp gray and 32 bpp rgb pix are written without a colormap. * (3) The transparency component in an rgb pix is ignored. * All 32 bpp pix have the bmp alpha component set to 255 (opaque). * (4) The bmp colormap entries, RGBA_QUAD, are the same as * the ones used for colormaps in leptonica. This allows * a simple memcpy for bmp output. * </pre> */ l_int32 pixWriteMemBmp(l_uint8 **pfdata, size_t *pfsize, PIX *pixs) { l_uint8 pel[4]; l_uint8 *cta = NULL; /* address of the bmp color table array */ l_uint8 *fdata, *data, *fmdata; l_int32 cmaplen; /* number of bytes in the bmp colormap */ l_int32 ncolors, val, stepsize; l_int32 w, h, d, fdepth, xres, yres; l_int32 pixWpl, pixBpl, extrabytes, fBpl, fWpl, i, j, k; l_int32 heapcm; /* extra copy of cta on the heap ? 1 : 0 */ l_uint32 offbytes, fimagebytes; l_uint32 *line, *pword; size_t fsize; BMP_FH *bmpfh; BMP_IH *bmpih; PIX *pix; PIXCMAP *cmap; RGBA_QUAD *pquad; PROCNAME("pixWriteMemBmp"); if (pfdata) *pfdata = NULL; if (pfsize) *pfsize = 0; if (!pfdata) return ERROR_INT("&fdata not defined", procName, 1 ); if (!pfsize) return ERROR_INT("&fsize not defined", procName, 1 ); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); pixGetDimensions(pixs, &w, &h, &d); if (d == 2) { L_WARNING("2 bpp files can't be read; converting to 8 bpp\n", procName); pix = pixConvert2To8(pixs, 0, 85, 170, 255, 1); d = 8; } else { pix = pixCopy(NULL, pixs); } fdepth = (d == 32) ? 24 : d; /* Resolution is given in pixels/meter */ xres = (l_int32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5); yres = (l_int32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5); pixWpl = pixGetWpl(pix); pixBpl = 4 * pixWpl; fWpl = (w * fdepth + 31) / 32; fBpl = 4 * fWpl; fimagebytes = h * fBpl; if (fimagebytes > 4LL * L_MAX_ALLOWED_PIXELS) { pixDestroy(&pix); return ERROR_INT("image data is too large", procName, 1); } /* If not rgb or 16 bpp, the bmp data is required to have a colormap */ heapcm = 0; if (d == 32 || d == 16) { /* 24 bpp rgb or 16 bpp: no colormap */ ncolors = 0; cmaplen = 0; } else if ((cmap = pixGetColormap(pix))) { /* existing colormap */ ncolors = pixcmapGetCount(cmap); cmaplen = ncolors * sizeof(RGBA_QUAD); cta = (l_uint8 *)cmap->array; } else { /* no existing colormap; d <= 8; make a binary or gray one */ if (d == 1) { cmaplen = sizeof(bwmap); ncolors = 2; cta = (l_uint8 *)bwmap; } else { /* d = 2,4,8; use a grayscale output colormap */ ncolors = 1 << fdepth; cmaplen = ncolors * sizeof(RGBA_QUAD); heapcm = 1; cta = (l_uint8 *)LEPT_CALLOC(cmaplen, 1); stepsize = 255 / (ncolors - 1); for (i = 0, val = 0, pquad = (RGBA_QUAD *)cta; i < ncolors; i++, val += stepsize, pquad++) { pquad->blue = pquad->green = pquad->red = val; pquad->alpha = 255; /* opaque */ } } } #if DEBUG {l_uint8 *pcmptr; pcmptr = (l_uint8 *)pixGetColormap(pix)->array; fprintf(stderr, "Pix colormap[0] = %c%c%c%d\n", pcmptr[0], pcmptr[1], pcmptr[2], pcmptr[3]); fprintf(stderr, "Pix colormap[1] = %c%c%c%d\n", pcmptr[4], pcmptr[5], pcmptr[6], pcmptr[7]); } #endif /* DEBUG */ offbytes = BMP_FHBYTES + BMP_IHBYTES + cmaplen; fsize = offbytes + fimagebytes; fdata = (l_uint8 *)LEPT_CALLOC(fsize, 1); *pfdata = fdata; *pfsize = fsize; /* Convert to little-endian and write the file header data */ bmpfh = (BMP_FH *)fdata; bmpfh->bfType = convertOnBigEnd16(BMP_ID); bmpfh->bfSize = convertOnBigEnd16(fsize & 0x0000ffff); bmpfh->bfFill1 = convertOnBigEnd16((fsize >> 16) & 0x0000ffff); bmpfh->bfOffBits = convertOnBigEnd16(offbytes & 0x0000ffff); bmpfh->bfFill2 = convertOnBigEnd16((offbytes >> 16) & 0x0000ffff); /* Convert to little-endian and write the info header data */ bmpih = (BMP_IH *)(fdata + BMP_FHBYTES); bmpih->biSize = convertOnBigEnd32(BMP_IHBYTES); bmpih->biWidth = convertOnBigEnd32(w); bmpih->biHeight = convertOnBigEnd32(h); bmpih->biPlanes = convertOnBigEnd16(1); bmpih->biBitCount = convertOnBigEnd16(fdepth); bmpih->biSizeImage = convertOnBigEnd32(fimagebytes); bmpih->biXPelsPerMeter = convertOnBigEnd32(xres); bmpih->biYPelsPerMeter = convertOnBigEnd32(yres); bmpih->biClrUsed = convertOnBigEnd32(ncolors); bmpih->biClrImportant = convertOnBigEnd32(ncolors); /* Copy the colormap data and free the cta if necessary */ if (ncolors > 0) { memcpy(fdata + BMP_FHBYTES + BMP_IHBYTES, cta, cmaplen); if (heapcm) LEPT_FREE(cta); } /* When you write a binary image with a colormap * that sets BLACK to 0, you must invert the data */ if (fdepth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) { pixInvert(pix, pix); } /* An endian byte swap is also required */ pixEndianByteSwap(pix); /* Transfer the image data. Image origin for bmp is at lower right. */ fmdata = fdata + offbytes; if (fdepth != 24) { /* typ 1 or 8 bpp */ data = (l_uint8 *)pixGetData(pix) + pixBpl * (h - 1); for (i = 0; i < h; i++) { memcpy(fmdata, data, fBpl); data -= pixBpl; fmdata += fBpl; } } else { /* 32 bpp pix; 24 bpp file * See the comments in pixReadStreamBmp() to * understand the logic behind the pixel ordering below. * Note that we have again done an endian swap on * little endian machines before arriving here, so that * the bytes are ordered on both platforms as: Red Green Blue -- |-----------|------------|-----------|-----------| */ extrabytes = fBpl - 3 * w; line = pixGetData(pix) + pixWpl * (h - 1); for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pword = line + j; pel[2] = *((l_uint8 *)pword + COLOR_RED); pel[1] = *((l_uint8 *)pword + COLOR_GREEN); pel[0] = *((l_uint8 *)pword + COLOR_BLUE); memcpy(fmdata, &pel, 3); fmdata += 3; } if (extrabytes) { for (k = 0; k < extrabytes; k++) { memcpy(fmdata, &pel, 1); fmdata++; } } line -= pixWpl; } } pixDestroy(&pix); return 0; }
/*! * pixColorGrayMaskedCmap() * * Input: pixs (8 bpp, with colormap) * pixm (1 bpp mask, through which to apply color) * type (L_PAINT_LIGHT, L_PAINT_DARK) * rval, gval, bval (target color) * Return: 0 if OK, 1 on error * * Notes: * (1) This is an in-place operation. * (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels, * preserving antialiasing. * If type == L_PAINT_DARK, it colorizes non-white pixels, * preserving antialiasing. See pixColorGrayCmap() for details. * (3) This increases the colormap size by the number of * different gray (non-black or non-white) colors in the * input colormap. If there is not enough room in the colormap * for this expansion, it returns 1 (error). */ l_int32 pixColorGrayMaskedCmap(PIX *pixs, PIX *pixm, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, wm, hm, wmin, hmin, wpl, wplm; l_int32 val, nval; l_int32 *map; l_uint32 *line, *data, *linem, *datam; NUMA *na; PIXCMAP *cmap; PROCNAME("pixColorGrayMaskedCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!pixm || pixGetDepth(pixm) != 1) return ERROR_INT("pixm undefined or not 1 bpp", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); if (pixGetDepth(pixs) != 8) return ERROR_INT("depth not 8 bpp", procName, 1); if (type != L_PAINT_DARK && type != L_PAINT_LIGHT) return ERROR_INT("invalid type", procName, 1); if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na)) return ERROR_INT("no room; cmap full", procName, 1); map = numaGetIArray(na); numaDestroy(&na); if (!map) return ERROR_INT("map not made", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); pixGetDimensions(pixm, &wm, &hm, NULL); if (wm != w) L_WARNING("wm = %d differs from w = %d\n", procName, wm, w); if (hm != h) L_WARNING("hm = %d differs from h = %d\n", procName, hm, h); wmin = L_MIN(w, wm); hmin = L_MIN(h, hm); data = pixGetData(pixs); wpl = pixGetWpl(pixs); datam = pixGetData(pixm); wplm = pixGetWpl(pixm); /* Remap gray pixels in the region */ for (i = 0; i < hmin; i++) { line = data + i * wpl; linem = datam + i * wplm; for (j = 0; j < wmin; j++) { if (GET_DATA_BIT(linem, j) == 0) continue; val = GET_DATA_BYTE(line, j); nval = map[val]; if (nval != 256) SET_DATA_BYTE(line, j, nval); } } FREE(map); 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; }
/*! * pixSetSelectMaskedCmap() * * Input: pixs (2, 4 or 8 bpp, with colormap) * pixm (<optional> 1 bpp mask; no-op if NULL) * x, y (UL corner of mask relative to pixs) * sindex (colormap index of pixels in pixs to be changed) * rval, gval, bval (new color to substitute) * Return: 0 if OK, 1 on error * * Note: * (1) This is an in-place operation. * (2) This paints through the fg of pixm and replaces all pixels * in pixs that have a particular value (sindex) with the new color. * (3) If pixm == NULL, a warning is given. * (4) sindex must be in the existing colormap; otherwise an * error is returned. * (5) If the new color exists in the colormap, it is used; * otherwise, it is added to the colormap. If the colormap * is full, an error is returned. */ l_int32 pixSetSelectMaskedCmap(PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, d, n, wm, hm, wpls, wplm, val; l_int32 index; /* of new color to be set */ l_uint32 *lines, *linem, *datas, *datam; PIXCMAP *cmap; PROCNAME("pixSetSelectMaskedCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); if (!pixm) { L_WARNING("no mask; nothing to do\n", procName); return 0; } d = pixGetDepth(pixs); if (d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {2, 4, 8}", procName, 1); /* add new color if necessary; get index of this color in cmap */ n = pixcmapGetCount(cmap); if (sindex >= n) return ERROR_INT("sindex too large; no cmap entry", procName, 1); if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */ if (pixcmapAddColor(cmap, rval, gval, bval)) return ERROR_INT("error adding cmap entry", procName, 1); else index = n; /* we've added one color */ } /* replace pixel value sindex by index when fg pixel in pixmc * overlays it */ w = pixGetWidth(pixs); h = pixGetHeight(pixs); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); wm = pixGetWidth(pixm); hm = pixGetHeight(pixm); datam = pixGetData(pixm); wplm = pixGetWpl(pixm); for (i = 0; i < hm; i++) { if (i + y < 0 || i + y >= h) continue; lines = datas + (y + i) * wpls; linem = datam + i * wplm; for (j = 0; j < wm; j++) { if (j + x < 0 || j + x >= w) continue; if (GET_DATA_BIT(linem, j)) { switch (d) { case 1: val = GET_DATA_BIT(lines, x + j); if (val == sindex) { if (index == 0) CLEAR_DATA_BIT(lines, x + j); else SET_DATA_BIT(lines, x + j); } break; case 2: val = GET_DATA_DIBIT(lines, x + j); if (val == sindex) SET_DATA_DIBIT(lines, x + j, index); break; case 4: val = GET_DATA_QBIT(lines, x + j); if (val == sindex) SET_DATA_QBIT(lines, x + j, index); break; case 8: val = GET_DATA_BYTE(lines, x + j); if (val == sindex) SET_DATA_BYTE(lines, x + j, index); break; default: return ERROR_INT("depth not in {1,2,4,8}", procName, 1); } } } } 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; }
/*! * pixSetMaskedCmap() * * Input: pixs (2, 4 or 8 bpp, colormapped) * pixm (<optional> 1 bpp mask; no-op if NULL) * x, y (origin of pixm relative to pixs; can be negative) * rval, gval, bval (new color to set at each masked pixel) * Return: 0 if OK; 1 on error * * Notes: * (1) This is an in-place operation. * (2) It paints a single color through the mask (as a stencil). * (3) The mask origin is placed at (x,y) on pixs, and the * operation is clipped to the intersection of the mask and pixs. * (4) If pixm == NULL, a warning is given. * (5) Typically, pixm is a small binary mask located somewhere * on the larger pixs. * (6) If the color is in the colormap, it is used. Otherwise, * it is added if possible; an error is returned if the * colormap is already full. */ l_int32 pixSetMaskedCmap(PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 w, h, d, wpl, wm, hm, wplm; l_int32 i, j, index; l_uint32 *data, *datam, *line, *linem; PIXCMAP *cmap; PROCNAME("pixSetMaskedCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap in pixs", procName, 1); if (!pixm) { L_WARNING("no mask; nothing to do\n", procName); return 0; } d = pixGetDepth(pixs); if (d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {2,4,8}", procName, 1); if (pixGetDepth(pixm) != 1) return ERROR_INT("pixm not 1 bpp", procName, 1); /* Add new color if necessary; store in 'index' */ if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */ if (pixcmapAddColor(cmap, rval, gval, bval)) return ERROR_INT("no room in cmap", procName, 1); index = pixcmapGetCount(cmap) - 1; } pixGetDimensions(pixs, &w, &h, NULL); wpl = pixGetWpl(pixs); data = pixGetData(pixs); pixGetDimensions(pixm, &wm, &hm, NULL); wplm = pixGetWpl(pixm); datam = pixGetData(pixm); for (i = 0; i < hm; i++) { if (i + y < 0 || i + y >= h) continue; line = data + (i + y) * wpl; linem = datam + i * wplm; for (j = 0; j < wm; j++) { if (j + x < 0 || j + x >= w) continue; if (GET_DATA_BIT(linem, j)) { /* paint color */ switch (d) { case 2: SET_DATA_DIBIT(line, j + x, index); break; case 4: SET_DATA_QBIT(line, j + x, index); break; case 8: SET_DATA_BYTE(line, j + x, index); break; default: return ERROR_INT("depth not in {2,4,8}", procName, 1); } } } } return 0; }
/*! * \brief gifToPix() * * \param[in] gif opened gif stream * \return pix, or NULL on error * * <pre> * Notes: * (1) This decodes the pix from the compressed gif stream and * closes the stream. * (2) It is static so that the stream is not exposed to clients. * </pre> */ static PIX * gifToPix(GifFileType *gif) { l_int32 wpl, i, j, w, h, d, cindex, ncolors; l_int32 rval, gval, bval; l_uint32 *data, *line; PIX *pixd, *pixdi; PIXCMAP *cmap; ColorMapObject *gif_cmap; SavedImage si; #if (GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1) || GIFLIB_MAJOR > 5 int giferr; #endif /* 5.1 and beyond */ PROCNAME("gifToPix"); /* Read all the data, but use only the first image found */ if (DGifSlurp(gif) != GIF_OK) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("failed to read GIF data", procName, NULL); } if (gif->SavedImages == NULL) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("no images found in GIF", procName, NULL); } si = gif->SavedImages[0]; w = si.ImageDesc.Width; h = si.ImageDesc.Height; if (w <= 0 || h <= 0) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("invalid image dimensions", procName, NULL); } if (si.RasterBits == NULL) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("no raster data in GIF", procName, NULL); } if (si.ImageDesc.ColorMap) { /* private cmap for this image */ gif_cmap = si.ImageDesc.ColorMap; } else if (gif->SColorMap) { /* global cmap for whole picture */ gif_cmap = gif->SColorMap; } else { /* don't know where to take cmap from */ DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("color map is missing", procName, NULL); } ncolors = gif_cmap->ColorCount; if (ncolors <= 2) d = 1; else if (ncolors <= 4) d = 2; else if (ncolors <= 16) d = 4; else d = 8; if ((cmap = pixcmapCreate(d)) == NULL) { DGifCloseFile(gif, &giferr); return (PIX *)ERROR_PTR("cmap creation failed", procName, NULL); } for (cindex = 0; cindex < ncolors; cindex++) { rval = gif_cmap->Colors[cindex].Red; gval = gif_cmap->Colors[cindex].Green; bval = gif_cmap->Colors[cindex].Blue; pixcmapAddColor(cmap, rval, gval, bval); } if ((pixd = pixCreate(w, h, d)) == NULL) { DGifCloseFile(gif, &giferr); pixcmapDestroy(&cmap); return (PIX *)ERROR_PTR("failed to allocate pixd", procName, NULL); } pixSetInputFormat(pixd, IFF_GIF); pixSetColormap(pixd, cmap); wpl = pixGetWpl(pixd); data = pixGetData(pixd); for (i = 0; i < h; i++) { line = data + i * wpl; if (d == 1) { for (j = 0; j < w; j++) { if (si.RasterBits[i * w + j]) SET_DATA_BIT(line, j); } } else if (d == 2) { for (j = 0; j < w; j++) SET_DATA_DIBIT(line, j, si.RasterBits[i * w + j]); } else if (d == 4) { for (j = 0; j < w; j++) SET_DATA_QBIT(line, j, si.RasterBits[i * w + j]); } else { /* d == 8 */ for (j = 0; j < w; j++) SET_DATA_BYTE(line, j, si.RasterBits[i * w + j]); } } /* If the image has been interlaced (for viewing in a browser), * this restores the raster lines to normal order. */ if (gif->Image.Interlace) { pixdi = pixUninterlaceGIF(pixd); pixTransferAllData(pixd, &pixdi, 0, 0); } DGifCloseFile(gif, &giferr); return pixd; }
/*! * pixSetSelectCmap() * * Input: pixs (1, 2, 4 or 8 bpp, with colormap) * box (<optional> region to set color; can be NULL) * sindex (colormap index of pixels to be changed) * rval, gval, bval (new color to paint) * Return: 0 if OK, 1 on error * * Note: * (1) This is an in-place operation. * (2) It sets all pixels in region that have the color specified * by the colormap index 'sindex' to the new color. * (3) sindex must be in the existing colormap; otherwise an * error is returned. * (4) If the new color exists in the colormap, it is used; * otherwise, it is added to the colormap. If it cannot be * added because the colormap is full, an error is returned. * (5) If box is NULL, applies function to the entire image; otherwise, * clips the operation to the intersection of the box and pix. * (6) An DC of use would be to set to a specific color all * the light (background) pixels within a certain region of * a 3-level 2 bpp image, while leaving light pixels outside * this region unchanged. */ l_int32 pixSetSelectCmap(PIX *pixs, BOX *box, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval) { l_int32 i, j, w, h, d, n, x1, y1, x2, y2, bw, bh, val, wpls; l_int32 index; /* of new color to be set */ l_uint32 *lines, *datas; PIXCMAP *cmap; PROCNAME("pixSetSelectCmap"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return ERROR_INT("no colormap", procName, 1); d = pixGetDepth(pixs); if (d != 1 && d != 2 && d != 4 && d != 8) return ERROR_INT("depth not in {1,2,4,8}", procName, 1); /* Add new color if necessary; get index of this color in cmap */ n = pixcmapGetCount(cmap); if (sindex >= n) return ERROR_INT("sindex too large; no cmap entry", procName, 1); if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */ if (pixcmapAddColor(cmap, rval, gval, bval)) return ERROR_INT("error adding cmap entry", procName, 1); else index = n; /* we've added one color */ } /* Determine the region of substitution */ pixGetDimensions(pixs, &w, &h, NULL); if (!box) { x1 = y1 = 0; x2 = w; y2 = h; } else { boxGetGeometry(box, &x1, &y1, &bw, &bh); x2 = x1 + bw - 1; y2 = y1 + bh - 1; } /* Replace pixel value sindex by index in the region */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); for (i = y1; i <= y2; i++) { if (i < 0 || i >= h) /* clip */ continue; lines = datas + i * wpls; for (j = x1; j <= x2; j++) { if (j < 0 || j >= w) /* clip */ continue; switch (d) { case 1: val = GET_DATA_BIT(lines, j); if (val == sindex) { if (index == 0) CLEAR_DATA_BIT(lines, j); else SET_DATA_BIT(lines, j); } break; case 2: val = GET_DATA_DIBIT(lines, j); if (val == sindex) SET_DATA_DIBIT(lines, j, index); break; case 4: val = GET_DATA_QBIT(lines, j); if (val == sindex) SET_DATA_QBIT(lines, j, index); break; case 8: val = GET_DATA_BYTE(lines, j); if (val == sindex) SET_DATA_BYTE(lines, j, index); break; default: return ERROR_INT("depth not in {1,2,4,8}", procName, 1); } } } 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; }
main(int argc, char **argv) { l_int32 x, y, i, j, k, w, h, w2, w4, w8, w16, w32, wpl, nerrors; l_int32 count1, count2, count3, ret, val1, val2; l_uint32 val32; l_uint32 *data, *line, *line1, *line2, *data1, *data2; void **lines1, **linet1, **linet2; PIX *pixs, *pixt1, *pixt2; static char mainName[] = "lowaccess_reg"; pixs = pixRead("feyn.tif"); /* width divisible by 16 */ pixGetDimensions(pixs, &w, &h, NULL); data = pixGetData(pixs); wpl = pixGetWpl(pixs); lines1 = pixGetLinePtrs(pixs, NULL); /* Get timing for the 3 different methods */ startTimer(); for (k = 0; k < 10; k++) { count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { if (GET_DATA_BIT(lines1[i], j)) count1++; } } } fprintf(stderr, "Time with line ptrs = %5.3f sec, count1 = %d\n", stopTimer(), count1); startTimer(); for (k = 0; k < 10; k++) { count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { if (l_getDataBit(line, j)) count2++; } } } fprintf(stderr, "Time with l_get* = %5.3f sec, count2 = %d\n", stopTimer(), count2); startTimer(); for (k = 0; k < 10; k++) { count3 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { pixGetPixel(pixs, j, i, &val32); count3 += val32; } } } fprintf(stderr, "Time with pixGetPixel() = %5.3f sec, count3 = %d\n", stopTimer(), count3); pixt1 = pixCreateTemplate(pixs); data1 = pixGetData(pixt1); linet1 = pixGetLinePtrs(pixt1, NULL); pixt2 = pixCreateTemplate(pixs); data2 = pixGetData(pixt2); linet2 = pixGetLinePtrs(pixt2, NULL); nerrors = 0; /* Test different methods for 1 bpp */ count1 = 0; for (i = 0; i < h; i++) { for (j = 0; j < w; j++) { val1 = GET_DATA_BIT(lines1[i], j); count1 += val1; if (val1) SET_DATA_BIT(linet1[i], j); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w; j++) { val2 = l_getDataBit(line, j); count2 += val2; if (val2) l_setDataBit(line2, j); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "1 bpp"); nerrors += ret; /* Test different methods for 2 bpp */ count1 = 0; w2 = w / 2; for (i = 0; i < h; i++) { for (j = 0; j < w2; j++) { val1 = GET_DATA_DIBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbbc; SET_DATA_DIBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w2; j++) { val2 = l_getDataDibit(line, j); count2 += val2; val2 += 0xbbbbbbbc; l_setDataDibit(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "2 bpp"); nerrors += ret; /* Test different methods for 4 bpp */ count1 = 0; w4 = w / 4; for (i = 0; i < h; i++) { for (j = 0; j < w4; j++) { val1 = GET_DATA_QBIT(lines1[i], j); count1 += val1; val1 += 0xbbbbbbb0; SET_DATA_QBIT(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w4; j++) { val2 = l_getDataQbit(line, j); count2 += val2; val2 += 0xbbbbbbb0; l_setDataQbit(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "4 bpp"); nerrors += ret; /* Test different methods for 8 bpp */ count1 = 0; w8 = w / 8; for (i = 0; i < h; i++) { for (j = 0; j < w8; j++) { val1 = GET_DATA_BYTE(lines1[i], j); count1 += val1; val1 += 0xbbbbbb00; SET_DATA_BYTE(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w8; j++) { val2 = l_getDataByte(line, j); count2 += val2; val2 += 0xbbbbbb00; l_setDataByte(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "8 bpp"); nerrors += ret; /* Test different methods for 16 bpp */ count1 = 0; w16 = w / 16; for (i = 0; i < h; i++) { for (j = 0; j < w16; j++) { val1 = GET_DATA_TWO_BYTES(lines1[i], j); count1 += val1; val1 += 0xbbbb0000; SET_DATA_TWO_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w16; j++) { val2 = l_getDataTwoBytes(line, j); count2 += val2; val2 += 0xbbbb0000; l_setDataTwoBytes(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "16 bpp"); nerrors += ret; /* Test different methods for 32 bpp */ count1 = 0; w32 = w / 32; for (i = 0; i < h; i++) { for (j = 0; j < w32; j++) { val1 = GET_DATA_FOUR_BYTES(lines1[i], j); count1 += val1 & 0xfff; SET_DATA_FOUR_BYTES(linet1[i], j, val1); } } count2 = 0; for (i = 0; i < h; i++) { line = data + i * wpl; line2 = data2 + i * wpl; for (j = 0; j < w32; j++) { val2 = l_getDataFourBytes(line, j); count2 += val2 & 0xfff; l_setDataFourBytes(line2, j, val2); } } ret = compareResults(pixs, pixt1, pixt2, count1, count2, "32 bpp"); nerrors += ret; if (!nerrors) fprintf(stderr, "**** No errors ****\n"); else fprintf(stderr, "**** %d errors found! ****\n", nerrors); pixDestroy(&pixs); pixDestroy(&pixt1); pixDestroy(&pixt2); lept_free(lines1); lept_free(linet1); lept_free(linet2); return 0; }