/*! * pixBilinearColor() * * Input: pixs (32 bpp) * vc (vector of 8 coefficients for bilinear transformation) * colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE) * Return: pixd, or null on error */ PIX * pixBilinearColor(PIX *pixs, l_float32 *vc, l_uint32 colorval) { l_int32 i, j, w, h, d, wpls, wpld; l_uint32 val; l_uint32 *datas, *datad, *lined; l_float32 x, y; PIX *pix1, *pix2, *pixd; PROCNAME("pixBilinearColor"); if (!pixs) return (PIX *) ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if (d != 32) return (PIX *) ERROR_PTR("pixs must be 32 bpp", procName, NULL); if (!vc) return (PIX *) ERROR_PTR("vc not defined", procName, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); pixd = pixCreateTemplate(pixs); pixSetAllArbitrary(pixd, colorval); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); /* Iterate over destination pixels */ for (i = 0; i < h; i++) { lined = datad + i * wpld; for (j = 0; j < w; j++) { /* Compute float src pixel location corresponding to (i,j) */ bilinearXformPt(vc, j, i, &x, &y); linearInterpolatePixelColor(datas, wpls, w, h, x, y, colorval, &val); *(lined + j) = val; } } /* If rgba, transform the pixs alpha channel and insert in pixd */ if (pixGetSpp(pixs) == 4) { pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); pix2 = pixBilinearGray(pix1, vc, 255); /* bring in opaque */ pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); pixDestroy(&pix1); pixDestroy(&pix2); } return pixd; }
/*! * \brief pixRotate3Shear() * * \param[in] pixs * \param[in] xcen, ycen center of rotation * \param[in] angle radians * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK; * \return pixd, or NULL on error. * * <pre> * Notes: * (1) This rotates the image about the given point, using the 3-shear * method. It should only be used for angles smaller than * LIMIT_SHEAR_ANGLE. For larger angles, a warning is issued. * (2) A positive angle gives a clockwise rotation. * (3) 3-shear rotation by a specified angle is equivalent * to the sequential transformations * y' = y + tan(angle/2) * (x - xcen) for first y-shear * x' = x + sin(angle) * (y - ycen) for x-shear * y' = y + tan(angle/2) * (x - xcen) for second y-shear * (4) Computation of tan(angle) is performed in the shear operations. * (5) This brings in 'incolor' pixels from outside the image. * (6) If the image has an alpha layer, it is rotated separately by * two shears. * (7) The algorithm was published by Alan Paeth: "A Fast Algorithm * for General Raster Rotation," Graphics Interface '86, * pp. 77-81, May 1986. A description of the method, along with * an implementation, can be found in Graphics Gems, p. 179, * edited by Andrew Glassner, published by Academic Press, 1990. * </pre> */ PIX * pixRotate3Shear(PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor) { l_float32 hangle; PIX *pix1, *pix2, *pixd; PROCNAME("pixRotate3Shear"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); if (L_ABS(angle) > LIMIT_SHEAR_ANGLE) { L_WARNING("%6.2f radians; large angle for 3-shear rotation\n", procName, L_ABS(angle)); } hangle = atan(sin(angle)); if ((pixd = pixVShear(NULL, pixs, xcen, angle / 2., incolor)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); if ((pix1 = pixHShear(NULL, pixd, ycen, hangle, incolor)) == NULL) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("pix1 not made", procName, NULL); } pixVShear(pixd, pix1, xcen, angle / 2., incolor); pixDestroy(&pix1); if (pixGetDepth(pixs) == 32 && pixGetSpp(pixs) == 4) { pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); /* L_BRING_IN_WHITE brings in opaque for the alpha component */ pix2 = pixRotate3Shear(pix1, xcen, ycen, angle, L_BRING_IN_WHITE); pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); pixDestroy(&pix1); pixDestroy(&pix2); } return pixd; }
/*! * pixRotateAMColorCorner() * * Input: pixs * angle (radians; clockwise is positive) * colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE) * Return: pixd, or null on error * * Notes: * (1) Rotates the image about the UL corner. * (2) A positive angle gives a clockwise rotation. * (3) Specify the color to be brought in from outside the image. */ PIX * pixRotateAMColorCorner(PIX *pixs, l_float32 angle, l_uint32 fillval) { l_int32 w, h, wpls, wpld; l_uint32 *datas, *datad; PIX *pix1, *pix2, *pixd; PROCNAME("pixRotateAMColorCorner"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); pixd = pixCreateTemplate(pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); rotateAMColorCornerLow(datad, w, h, wpld, datas, wpls, angle, fillval); if (pixGetSpp(pixs) == 4) { pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); pix2 = pixRotateAMGrayCorner(pix1, angle, 255); /* bring in opaque */ pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); pixDestroy(&pix1); pixDestroy(&pix2); } return pixd; }
/*! * 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; }
/*! * pixDisplayWithTitle() * * Input: pix (1, 2, 4, 8, 16, 32 bpp) * x, y (location of display frame) * title (<optional> on frame; can be NULL); * dispflag (1 to write, else disabled) * Return: 0 if OK; 1 on error * * Notes: * (1) See notes for pixDisplay(). * (2) This displays the image if dispflag == 1. */ l_int32 pixDisplayWithTitle(PIX *pixs, l_int32 x, l_int32 y, const char *title, l_int32 dispflag) { char *tempname; char buffer[L_BUF_SIZE]; static l_int32 index = 0; /* caution: not .so or thread safe */ l_int32 w, h, d, spp, maxheight, opaque, threeviews, ignore; l_float32 ratw, rath, ratmin; PIX *pix0, *pix1, *pix2; PIXCMAP *cmap; #ifndef _WIN32 l_int32 wt, ht; #else char *pathname; char fullpath[_MAX_PATH]; #endif /* _WIN32 */ PROCNAME("pixDisplayWithTitle"); if (dispflag != 1) return 0; if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (var_DISPLAY_PROG != L_DISPLAY_WITH_XZGV && var_DISPLAY_PROG != L_DISPLAY_WITH_XLI && var_DISPLAY_PROG != L_DISPLAY_WITH_XV && var_DISPLAY_PROG != L_DISPLAY_WITH_IV && var_DISPLAY_PROG != L_DISPLAY_WITH_OPEN) { return ERROR_INT("no program chosen for display", procName, 1); } /* Display with three views if either spp = 4 or if colormapped * and the alpha component is not fully opaque */ opaque = TRUE; if ((cmap = pixGetColormap(pixs)) != NULL) pixcmapIsOpaque(cmap, &opaque); spp = pixGetSpp(pixs); threeviews = (spp == 4 || !opaque) ? TRUE : FALSE; /* If colormapped and not opaque, remove the colormap to RGBA */ if (!opaque) pix0 = pixRemoveColormap(pixs, REMOVE_CMAP_WITH_ALPHA); else pix0 = pixClone(pixs); /* Scale if necessary; this will also remove a colormap */ pixGetDimensions(pix0, &w, &h, &d); maxheight = (threeviews) ? MAX_DISPLAY_HEIGHT / 3 : MAX_DISPLAY_HEIGHT; if (w <= MAX_DISPLAY_WIDTH && h <= maxheight) { if (d == 16) /* take MSB */ pix1 = pixConvert16To8(pix0, 1); else pix1 = pixClone(pix0); } else { ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w; rath = (l_float32)maxheight / (l_float32)h; ratmin = L_MIN(ratw, rath); if (ratmin < 0.125 && d == 1) pix1 = pixScaleToGray8(pix0); else if (ratmin < 0.25 && d == 1) pix1 = pixScaleToGray4(pix0); else if (ratmin < 0.33 && d == 1) pix1 = pixScaleToGray3(pix0); else if (ratmin < 0.5 && d == 1) pix1 = pixScaleToGray2(pix0); else pix1 = pixScale(pix0, ratmin, ratmin); } pixDestroy(&pix0); if (!pix1) return ERROR_INT("pix1 not made", procName, 1); /* Generate the three views if required */ if (threeviews) pix2 = pixDisplayLayersRGBA(pix1, 0xffffff00, 0); else pix2 = pixClone(pix1); if (index == 0) { lept_rmdir("disp"); lept_mkdir("disp"); } index++; if (pixGetDepth(pix2) < 8 || (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) { snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.png", index); pixWrite(buffer, pix2, IFF_PNG); } else { snprintf(buffer, L_BUF_SIZE, "/tmp/disp/write.%03d.jpg", index); pixWrite(buffer, pix2, IFF_JFIF_JPEG); } tempname = stringNew(buffer); #ifndef _WIN32 /* Unix */ if (var_DISPLAY_PROG == L_DISPLAY_WITH_XZGV) { /* no way to display title */ pixGetDimensions(pix2, &wt, &ht, NULL); snprintf(buffer, L_BUF_SIZE, "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10, x, y, tempname); } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XLI) { if (title) { snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d -title \"%s\" %s &", x, y, title, tempname); } else { snprintf(buffer, L_BUF_SIZE, "xli -dispgamma 1.0 -quiet -geometry +%d+%d %s &", x, y, tempname); } } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XV) { if (title) { snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d -name \"%s\" %s &", x, y, title, tempname); } else { snprintf(buffer, L_BUF_SIZE, "xv -quit -geometry +%d+%d %s &", x, y, tempname); } } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_OPEN) { snprintf(buffer, L_BUF_SIZE, "open %s &", tempname); } ignore = system(buffer); #else /* _WIN32 */ /* Windows: L_DISPLAY_WITH_IV */ pathname = genPathname(tempname, NULL); _fullpath(fullpath, pathname, sizeof(fullpath)); if (title) { snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"", fullpath, x, y, title); } else { snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d)", fullpath, x, y); } ignore = system(buffer); FREE(pathname); #endif /* _WIN32 */ pixDestroy(&pix1); pixDestroy(&pix2); FREE(tempname); return 0; }
bool TessPDFRenderer::imageToPDFObj(Pix *pix, char *filename, long int objnum, char **pdf_object, long int *pdf_object_size) { size_t n; char b0[kBasicBufSize]; char b1[kBasicBufSize]; char b2[kBasicBufSize]; if (!pdf_object_size || !pdf_object) return false; *pdf_object = NULL; *pdf_object_size = 0; if (!filename) return false; L_COMP_DATA *cid = NULL; const int kJpegQuality = 85; // TODO(jbreiden) Leptonica 1.71 doesn't correctly handle certain // types of PNG files, especially if there are 2 samples per pixel. // We can get rid of this logic after Leptonica 1.72 is released and // has propagated everywhere. Bug discussion as follows. // https://code.google.com/p/tesseract-ocr/issues/detail?id=1300 int format, sad; findFileFormat(filename, &format); if (pixGetSpp(pix) == 4 && format == IFF_PNG) { pixSetSpp(pix, 3); sad = pixGenerateCIData(pix, L_FLATE_ENCODE, 0, 0, &cid); } else { sad = l_generateCIDataForPdf(filename, pix, kJpegQuality, &cid); } if (sad || !cid) { l_CIDataDestroy(&cid); return false; } const char *group4 = ""; const char *filter; switch (cid->type) { case L_FLATE_ENCODE: filter = "/FlateDecode"; break; case L_JPEG_ENCODE: filter = "/DCTDecode"; break; case L_G4_ENCODE: filter = "/CCITTFaxDecode"; group4 = " /K -1\n"; break; case L_JP2K_ENCODE: filter = "/JPXDecode"; break; default: l_CIDataDestroy(&cid); return false; } // Maybe someday we will accept RGBA but today is not that day. // It requires creating an /SMask for the alpha channel. // http://stackoverflow.com/questions/14220221 const char *colorspace; if (cid->ncolors > 0) { n = snprintf(b0, sizeof(b0), " /ColorSpace [ /Indexed /DeviceRGB %d %s ]\n", cid->ncolors - 1, cid->cmapdatahex); if (n >= sizeof(b0)) { l_CIDataDestroy(&cid); return false; } colorspace = b0; } else { switch (cid->spp) { case 1: colorspace = " /ColorSpace /DeviceGray\n"; break; case 3: colorspace = " /ColorSpace /DeviceRGB\n"; break; default: l_CIDataDestroy(&cid); return false; } } int predictor = (cid->predictor) ? 14 : 1; // IMAGE n = snprintf(b1, sizeof(b1), "%ld 0 obj\n" "<<\n" " /Length %ld\n" " /Subtype /Image\n", objnum, (unsigned long) cid->nbytescomp); if (n >= sizeof(b1)) { l_CIDataDestroy(&cid); return false; } n = snprintf(b2, sizeof(b2), " /Width %d\n" " /Height %d\n" " /BitsPerComponent %d\n" " /Filter %s\n" " /DecodeParms\n" " <<\n" " /Predictor %d\n" " /Colors %d\n" "%s" " /Columns %d\n" " /BitsPerComponent %d\n" " >>\n" ">>\n" "stream\n", cid->w, cid->h, cid->bps, filter, predictor, cid->spp, group4, cid->w, cid->bps); if (n >= sizeof(b2)) { l_CIDataDestroy(&cid); return false; } const char *b3 = "endstream\n" "endobj\n"; size_t b1_len = strlen(b1); size_t b2_len = strlen(b2); size_t b3_len = strlen(b3); size_t colorspace_len = strlen(colorspace); *pdf_object_size = b1_len + colorspace_len + b2_len + cid->nbytescomp + b3_len; *pdf_object = new char[*pdf_object_size]; if (!pdf_object) { l_CIDataDestroy(&cid); return false; } char *p = *pdf_object; memcpy(p, b1, b1_len); p += b1_len; memcpy(p, colorspace, colorspace_len); p += colorspace_len; memcpy(p, b2, b2_len); p += b2_len; memcpy(p, cid->datacomp, cid->nbytescomp); p += cid->nbytescomp; memcpy(p, b3, b3_len); l_CIDataDestroy(&cid); return true; }
/*! * ioFormatTest() * * Input: filename (input file) * Return: 0 if OK; 1 on error or if the test fails * * Notes: * (1) This writes and reads a set of output files losslessly * in different formats to /tmp/format/, and tests that the * result before and after is unchanged. * (2) This should work properly on input images of any depth, * with and without colormaps. * (3) All supported formats are tested for bmp, png, tiff and * non-ascii pnm. Ascii pnm also works (but who'd ever want * to use it?) We allow 2 bpp bmp, although it's not * supported elsewhere. And we don't support reading * 16 bpp png, although this can be turned on in pngio.c. * (4) This silently skips png or tiff testing if HAVE_LIBPNG * or HAVE_LIBTIFF are 0, respectively. */ l_int32 ioFormatTest(const char *filename) { l_int32 d, equal, problems; PIX *pixs, *pixc, *pix1, *pix2; PIXCMAP *cmap; PROCNAME("ioFormatTest"); if (!filename) return ERROR_INT("filename not defined", procName, 1); if ((pixs = pixRead(filename)) == NULL) return ERROR_INT("pixs not made", procName, 1); lept_mkdir("lept"); /* Note that the reader automatically removes colormaps * from 1 bpp BMP images, but not from 8 bpp BMP images. * Therefore, if our 8 bpp image initially doesn't have a * colormap, we are going to need to remove it from any * pix read from a BMP file. */ pixc = pixClone(pixs); /* laziness */ /* This does not test the alpha layer pixels, because most * formats don't support it. Remove any alpha. */ if (pixGetSpp(pixc) == 4) pixSetSpp(pixc, 3); cmap = pixGetColormap(pixc); /* colormap; can be NULL */ d = pixGetDepth(pixc); problems = FALSE; /* ----------------------- BMP -------------------------- */ /* BMP works for 1, 2, 4, 8 and 32 bpp images. * It always writes colormaps for 1 and 8 bpp, so we must * remove it after readback if the input image doesn't have * a colormap. Although we can write/read 2 bpp BMP, nobody * else can read them! */ if (d == 1 || d == 8) { L_INFO("write/read bmp\n", procName); pixWrite(FILE_BMP, pixc, IFF_BMP); pix1 = pixRead(FILE_BMP); if (!cmap) pix2 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC); else pix2 = pixClone(pix1); pixEqual(pixc, pix2, &equal); if (!equal) { L_INFO(" **** bad bmp image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); } if (d == 2 || d == 4 || d == 32) { L_INFO("write/read bmp\n", procName); pixWrite(FILE_BMP, pixc, IFF_BMP); pix1 = pixRead(FILE_BMP); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad bmp image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } /* ----------------------- PNG -------------------------- */ #if HAVE_LIBPNG /* PNG works for all depths, but here, because we strip * 16 --> 8 bpp on reading, we don't test png for 16 bpp. */ if (d != 16) { L_INFO("write/read png\n", procName); pixWrite(FILE_PNG, pixc, IFF_PNG); pix1 = pixRead(FILE_PNG); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad png image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } #endif /* HAVE_LIBPNG */ /* ----------------------- TIFF -------------------------- */ #if HAVE_LIBTIFF /* TIFF works for 1, 2, 4, 8, 16 and 32 bpp images. * Because 8 bpp tiff always writes 256 entry colormaps, the * colormap sizes may be different for 8 bpp images with * colormap; we are testing if the image content is the same. * Likewise, the 2 and 4 bpp tiff images with colormaps * have colormap sizes 4 and 16, rsp. This test should * work properly on the content, regardless of the number * of color entries in pixc. */ /* tiff uncompressed works for all pixel depths */ L_INFO("write/read uncompressed tiff\n", procName); pixWrite(FILE_TIFF, pixc, IFF_TIFF); pix1 = pixRead(FILE_TIFF); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff uncompressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff lzw works for all pixel depths */ L_INFO("write/read lzw compressed tiff\n", procName); pixWrite(FILE_LZW, pixc, IFF_TIFF_LZW); pix1 = pixRead(FILE_LZW); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff lzw compressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff adobe deflate (zip) works for all pixel depths */ L_INFO("write/read zip compressed tiff\n", procName); pixWrite(FILE_ZIP, pixc, IFF_TIFF_ZIP); pix1 = pixRead(FILE_ZIP); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff zip compressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff g4, g3, rle and packbits work for 1 bpp */ if (d == 1) { L_INFO("write/read g4 compressed tiff\n", procName); pixWrite(FILE_G4, pixc, IFF_TIFF_G4); pix1 = pixRead(FILE_G4); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff g4 image ****\n", procName); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read g3 compressed tiff\n", procName); pixWrite(FILE_G3, pixc, IFF_TIFF_G3); pix1 = pixRead(FILE_G3); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff g3 image ****\n", procName); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read rle compressed tiff\n", procName); pixWrite(FILE_RLE, pixc, IFF_TIFF_RLE); pix1 = pixRead(FILE_RLE); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff rle image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read packbits compressed tiff\n", procName); pixWrite(FILE_PB, pixc, IFF_TIFF_PACKBITS); pix1 = pixRead(FILE_PB); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff packbits image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } #endif /* HAVE_LIBTIFF */ /* ----------------------- PNM -------------------------- */ /* pnm works for 1, 2, 4, 8, 16 and 32 bpp. * pnm doesn't have colormaps, so when we write colormapped * pix out as pnm, the colormap is removed. Thus for the test, * we must remove the colormap from pixc before testing. */ L_INFO("write/read pnm\n", procName); pixWrite(FILE_PNM, pixc, IFF_PNM); pix1 = pixRead(FILE_PNM); if (cmap) pix2 = pixRemoveColormap(pixc, REMOVE_CMAP_BASED_ON_SRC); else pix2 = pixClone(pixc); pixEqual(pix1, pix2, &equal); if (!equal) { L_INFO(" **** bad pnm image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); if (problems == FALSE) L_INFO("All formats read and written OK!\n", procName); pixDestroy(&pixc); pixDestroy(&pixs); return problems; }
bool TessPDFRenderer::imageToPDFObj(Pix *pix, char *filename, long int objnum, char **pdf_object, long int *pdf_object_size) { size_t n; char b0[kBasicBufSize]; char b1[kBasicBufSize]; char b2[kBasicBufSize]; if (!pdf_object_size || !pdf_object) return false; *pdf_object = nullptr; *pdf_object_size = 0; if (!filename) return false; L_Compressed_Data *cid = nullptr; const int kJpegQuality = 85; int format, sad; findFileFormat(filename, &format); if (pixGetSpp(pix) == 4 && format == IFF_PNG) { Pix *p1 = pixAlphaBlendUniform(pix, 0xffffff00); sad = pixGenerateCIData(p1, L_FLATE_ENCODE, 0, 0, &cid); pixDestroy(&p1); } else { sad = l_generateCIDataForPdf(filename, pix, kJpegQuality, &cid); } if (sad || !cid) { l_CIDataDestroy(&cid); return false; } const char *group4 = ""; const char *filter; switch(cid->type) { case L_FLATE_ENCODE: filter = "/FlateDecode"; break; case L_JPEG_ENCODE: filter = "/DCTDecode"; break; case L_G4_ENCODE: filter = "/CCITTFaxDecode"; group4 = " /K -1\n"; break; case L_JP2K_ENCODE: filter = "/JPXDecode"; break; default: l_CIDataDestroy(&cid); return false; } // Maybe someday we will accept RGBA but today is not that day. // It requires creating an /SMask for the alpha channel. // http://stackoverflow.com/questions/14220221 const char *colorspace; if (cid->ncolors > 0) { n = snprintf(b0, sizeof(b0), " /ColorSpace [ /Indexed /DeviceRGB %d %s ]\n", cid->ncolors - 1, cid->cmapdatahex); if (n >= sizeof(b0)) { l_CIDataDestroy(&cid); return false; } colorspace = b0; } else { switch (cid->spp) { case 1: colorspace = " /ColorSpace /DeviceGray\n"; break; case 3: colorspace = " /ColorSpace /DeviceRGB\n"; break; default: l_CIDataDestroy(&cid); return false; } } int predictor = (cid->predictor) ? 14 : 1; // IMAGE n = snprintf(b1, sizeof(b1), "%ld 0 obj\n" "<<\n" " /Length %ld\n" " /Subtype /Image\n", objnum, (unsigned long) cid->nbytescomp); if (n >= sizeof(b1)) { l_CIDataDestroy(&cid); return false; } n = snprintf(b2, sizeof(b2), " /Width %d\n" " /Height %d\n" " /BitsPerComponent %d\n" " /Filter %s\n" " /DecodeParms\n" " <<\n" " /Predictor %d\n" " /Colors %d\n" "%s" " /Columns %d\n" " /BitsPerComponent %d\n" " >>\n" ">>\n" "stream\n", cid->w, cid->h, cid->bps, filter, predictor, cid->spp, group4, cid->w, cid->bps); if (n >= sizeof(b2)) { l_CIDataDestroy(&cid); return false; } const char *b3 = "endstream\n" "endobj\n"; size_t b1_len = strlen(b1); size_t b2_len = strlen(b2); size_t b3_len = strlen(b3); size_t colorspace_len = strlen(colorspace); *pdf_object_size = b1_len + colorspace_len + b2_len + cid->nbytescomp + b3_len; *pdf_object = new char[*pdf_object_size]; char *p = *pdf_object; memcpy(p, b1, b1_len); p += b1_len; memcpy(p, colorspace, colorspace_len); p += colorspace_len; memcpy(p, b2, b2_len); p += b2_len; memcpy(p, cid->datacomp, cid->nbytescomp); p += cid->nbytescomp; memcpy(p, b3, b3_len); l_CIDataDestroy(&cid); return true; }
/*! * ioFormatTest() * * Input: filename (input file) * Return: 0 if OK; 1 on error or if the test fails * * Notes: * (1) This writes and reads a set of output files losslessly * in different formats to /tmp/format/, and tests that the * result before and after is unchanged. * (2) This should work properly on input images of any depth, * with and without colormaps. * (3) All supported formats are tested for bmp, png, tiff and * non-ascii pnm. Ascii pnm also works (but who'd ever want * to use it?) We allow 2 bpp bmp, although it's not * supported elsewhere. And we don't support reading * 16 bpp png, although this can be turned on in pngio.c. * (4) This silently skips png or tiff testing if HAVE_LIBPNG * or HAVE_LIBTIFF are 0, respectively. */ l_int32 ioFormatTest(const char *filename) { l_int32 w, h, d, depth, equal, problems; l_float32 diff; BOX *box; PIX *pixs, *pixc, *pix1, *pix2; PIXCMAP *cmap; PROCNAME("ioFormatTest"); if (!filename) return ERROR_INT("filename not defined", procName, 1); /* Read the input file and limit the size */ if ((pix1 = pixRead(filename)) == NULL) return ERROR_INT("pix1 not made", procName, 1); pixGetDimensions(pix1, &w, &h, NULL); if (w > 250 && h > 250) { /* take the central 250 x 250 region */ box = boxCreate(w / 2 - 125, h / 2 - 125, 250, 250); pixs = pixClipRectangle(pix1, box, NULL); boxDestroy(&box); } else { pixs = pixClone(pix1); } pixDestroy(&pix1); lept_mkdir("lept"); /* Note that the reader automatically removes colormaps * from 1 bpp BMP images, but not from 8 bpp BMP images. * Therefore, if our 8 bpp image initially doesn't have a * colormap, we are going to need to remove it from any * pix read from a BMP file. */ pixc = pixClone(pixs); /* laziness */ /* This does not test the alpha layer pixels, because most * formats don't support it. Remove any alpha. */ if (pixGetSpp(pixc) == 4) pixSetSpp(pixc, 3); cmap = pixGetColormap(pixc); /* colormap; can be NULL */ d = pixGetDepth(pixc); problems = FALSE; /* ----------------------- BMP -------------------------- */ /* BMP works for 1, 2, 4, 8 and 32 bpp images. * It always writes colormaps for 1 and 8 bpp, so we must * remove it after readback if the input image doesn't have * a colormap. Although we can write/read 2 bpp BMP, nobody * else can read them! */ if (d == 1 || d == 8) { L_INFO("write/read bmp\n", procName); pixWrite(FILE_BMP, pixc, IFF_BMP); pix1 = pixRead(FILE_BMP); if (!cmap) pix2 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC); else pix2 = pixClone(pix1); pixEqual(pixc, pix2, &equal); if (!equal) { L_INFO(" **** bad bmp image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); } if (d == 2 || d == 4 || d == 32) { L_INFO("write/read bmp\n", procName); pixWrite(FILE_BMP, pixc, IFF_BMP); pix1 = pixRead(FILE_BMP); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad bmp image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } /* ----------------------- PNG -------------------------- */ #if HAVE_LIBPNG /* PNG works for all depths, but here, because we strip * 16 --> 8 bpp on reading, we don't test png for 16 bpp. */ if (d != 16) { L_INFO("write/read png\n", procName); pixWrite(FILE_PNG, pixc, IFF_PNG); pix1 = pixRead(FILE_PNG); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad png image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } #endif /* HAVE_LIBPNG */ /* ----------------------- TIFF -------------------------- */ #if HAVE_LIBTIFF /* TIFF works for 1, 2, 4, 8, 16 and 32 bpp images. * Because 8 bpp tiff always writes 256 entry colormaps, the * colormap sizes may be different for 8 bpp images with * colormap; we are testing if the image content is the same. * Likewise, the 2 and 4 bpp tiff images with colormaps * have colormap sizes 4 and 16, rsp. This test should * work properly on the content, regardless of the number * of color entries in pixc. */ /* tiff uncompressed works for all pixel depths */ L_INFO("write/read uncompressed tiff\n", procName); pixWrite(FILE_TIFF, pixc, IFF_TIFF); pix1 = pixRead(FILE_TIFF); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff uncompressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff lzw works for all pixel depths */ L_INFO("write/read lzw compressed tiff\n", procName); pixWrite(FILE_LZW, pixc, IFF_TIFF_LZW); pix1 = pixRead(FILE_LZW); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff lzw compressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff adobe deflate (zip) works for all pixel depths */ L_INFO("write/read zip compressed tiff\n", procName); pixWrite(FILE_ZIP, pixc, IFF_TIFF_ZIP); pix1 = pixRead(FILE_ZIP); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff zip compressed image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); /* tiff g4, g3, rle and packbits work for 1 bpp */ if (d == 1) { L_INFO("write/read g4 compressed tiff\n", procName); pixWrite(FILE_G4, pixc, IFF_TIFF_G4); pix1 = pixRead(FILE_G4); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff g4 image ****\n", procName); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read g3 compressed tiff\n", procName); pixWrite(FILE_G3, pixc, IFF_TIFF_G3); pix1 = pixRead(FILE_G3); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff g3 image ****\n", procName); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read rle compressed tiff\n", procName); pixWrite(FILE_RLE, pixc, IFF_TIFF_RLE); pix1 = pixRead(FILE_RLE); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff rle image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); L_INFO("write/read packbits compressed tiff\n", procName); pixWrite(FILE_PB, pixc, IFF_TIFF_PACKBITS); pix1 = pixRead(FILE_PB); pixEqual(pixc, pix1, &equal); if (!equal) { L_INFO(" **** bad tiff packbits image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); } #endif /* HAVE_LIBTIFF */ /* ----------------------- PNM -------------------------- */ /* pnm works for 1, 2, 4, 8, 16 and 32 bpp. * pnm doesn't have colormaps, so when we write colormapped * pix out as pnm, the colormap is removed. Thus for the test, * we must remove the colormap from pixc before testing. */ L_INFO("write/read pnm\n", procName); pixWrite(FILE_PNM, pixc, IFF_PNM); pix1 = pixRead(FILE_PNM); if (cmap) pix2 = pixRemoveColormap(pixc, REMOVE_CMAP_BASED_ON_SRC); else pix2 = pixClone(pixc); pixEqual(pix1, pix2, &equal); if (!equal) { L_INFO(" **** bad pnm image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); /* ----------------------- GIF -------------------------- */ #if HAVE_LIBGIF /* GIF works for only 1 and 8 bpp, colormapped */ if (d != 8 || !cmap) pix1 = pixConvertTo8(pixc, 1); else pix1 = pixClone(pixc); L_INFO("write/read gif\n", procName); pixWrite(FILE_GIF, pix1, IFF_GIF); pix2 = pixRead(FILE_GIF); pixEqual(pix1, pix2, &equal); if (!equal) { L_INFO(" **** bad gif image: d = %d ****\n", procName, d); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); #endif /* HAVE_LIBGIF */ /* ----------------------- JPEG ------------------------- */ #if HAVE_LIBJPEG /* JPEG works for only 8 bpp gray and rgb */ if (cmap || d > 8) pix1 = pixConvertTo32(pixc); else pix1 = pixConvertTo8(pixc, 0); depth = pixGetDepth(pix1); L_INFO("write/read jpeg\n", procName); pixWrite(FILE_JPG, pix1, IFF_JFIF_JPEG); pix2 = pixRead(FILE_JPG); if (depth == 8) { pixCompareGray(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff, NULL, NULL); } else { pixCompareRGB(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff, NULL, NULL); } if (diff > 8.0) { L_INFO(" **** bad jpeg image: d = %d, diff = %5.2f ****\n", procName, depth, diff); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); #endif /* HAVE_LIBJPEG */ /* ----------------------- WEBP ------------------------- */ #if HAVE_LIBWEBP /* WEBP works for rgb and rgba */ if (cmap || d <= 16) pix1 = pixConvertTo32(pixc); else pix1 = pixClone(pixc); depth = pixGetDepth(pix1); L_INFO("write/read webp\n", procName); pixWrite(FILE_WEBP, pix1, IFF_WEBP); pix2 = pixRead(FILE_WEBP); pixCompareRGB(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff, NULL, NULL); if (diff > 5.0) { L_INFO(" **** bad webp image: d = %d, diff = %5.2f ****\n", procName, depth, diff); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); #endif /* HAVE_LIBWEBP */ /* ----------------------- JP2K ------------------------- */ #if HAVE_LIBJP2K /* JP2K works for only 8 bpp gray, rgb and rgba */ if (cmap || d > 8) pix1 = pixConvertTo32(pixc); else pix1 = pixConvertTo8(pixc, 0); depth = pixGetDepth(pix1); L_INFO("write/read jp2k\n", procName); pixWrite(FILE_JP2K, pix1, IFF_JP2); pix2 = pixRead(FILE_JP2K); if (depth == 8) { pixCompareGray(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff, NULL, NULL); } else { pixCompareRGB(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff, NULL, NULL); } fprintf(stderr, "diff = %7.3f\n", diff); if (diff > 7.0) { L_INFO(" **** bad jp2k image: d = %d, diff = %5.2f ****\n", procName, depth, diff); problems = TRUE; } pixDestroy(&pix1); pixDestroy(&pix2); #endif /* HAVE_LIBJP2K */ if (problems == FALSE) L_INFO("All formats read and written OK!\n", procName); pixDestroy(&pixc); pixDestroy(&pixs); return problems; }
/*! * \brief pixWriteMemWebP() * * \param[out] pencdata webp encoded data of pixs * \param[out] pencsize size of webp encoded data * \param[in] pixs any depth, cmapped OK * \param[in] quality 0 - 100; default ~80 * \param[in] lossless use 1 for lossless; 0 for lossy * \return 0 if OK, 1 on error * * <pre> * 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. * </pre> */ l_ok 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); /* The WebP API expects data in RGBA order. The pix stores * in host-dependent order with R as the MSB and A as the LSB. * On little-endian machines, the bytes in the word must * be swapped; e.g., R goes from byte 0 (LSB) to byte 3 (MSB). * No swapping is necessary for big-endians. */ 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; }
int main(int argc, char **argv) { l_int32 i, spp; l_uint32 bval, wval; PIX *pixs, *pix1, *pix2, *pix3, *pixd; PIXA *pixa; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; /* Scale each image and add a white boundary */ pixa = pixaCreate(setsize); for (i = 0; i < setsize; i++) { pixs = pixRead(fnames[i]); spp = pixGetSpp(pixs); pixGetBlackOrWhiteVal(pixs, L_GET_WHITE_VAL, &wval); pixGetBlackOrWhiteVal(pixs, L_GET_BLACK_VAL, &bval); fprintf(stderr, "d = %d, spp = %d, bval = %x, wval = %x\n", pixGetDepth(pixs), spp, bval, wval); if (spp == 4) /* remove alpha, using white background */ pix1 = pixAlphaBlendUniform(pixs, wval); else pix1 = pixClone(pixs); pix2 = pixScaleToSize(pix1, 150, 150); pixGetBlackOrWhiteVal(pix2, L_GET_WHITE_VAL, &wval); pix3 = pixAddBorderGeneral(pix2, 30, 30, 20, 20, wval); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pixs); pixDestroy(&pix1); pixDestroy(&pix2); } pixd = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 1, 30, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); pixDisplayWithTitle(pixd, 0, 100, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixa); /* Scale each image and add a black boundary */ pixa = pixaCreate(setsize); for (i = 0; i < setsize; i++) { pixs = pixRead(fnames[i]); spp = pixGetSpp(pixs); pixGetBlackOrWhiteVal(pixs, L_GET_WHITE_VAL, &wval); pixGetBlackOrWhiteVal(pixs, L_GET_BLACK_VAL, &bval); fprintf(stderr, "d = %d, spp = %d, bval = %x, wval = %x\n", pixGetDepth(pixs), spp, bval, wval); if (spp == 4) /* remove alpha, using white background */ pix1 = pixAlphaBlendUniform(pixs, wval); else pix1 = pixClone(pixs); pix2 = pixScaleToSize(pix1, 150, 150); pixGetBlackOrWhiteVal(pixs, L_GET_BLACK_VAL, &bval); pix3 = pixAddBorderGeneral(pix2, 30, 30, 20, 20, bval); pixaAddPix(pixa, pix3, L_INSERT); pixDestroy(&pixs); pixDestroy(&pix1); pixDestroy(&pix2); } pixd = pixaDisplayTiledInRows(pixa, 32, 1200, 1.0, 0, 30, 0); regTestWritePixAndCheck(rp, pixd, IFF_PNG); pixDisplayWithTitle(pixd, 1000, 100, NULL, rp->display); pixDestroy(&pixd); pixaDestroy(&pixa); return regTestCleanup(rp); }
/*! * pixRotateWithAlpha() * * Input: pixs (32 bpp rgb or cmapped) * angle (radians; clockwise is positive) * pixg (<optional> 8 bpp, can be null) * fract (between 0.0 and 1.0, with 0.0 fully transparent * and 1.0 fully opaque) * Return: pixd (32 bpp rgba), or null on error * * Notes: * (1) The alpha channel is transformed separately from pixs, * and aligns with it, being fully transparent outside the * boundary of the transformed pixs. For pixels that are fully * transparent, a blending function like pixBlendWithGrayMask() * will give zero weight to corresponding pixels in pixs. * (2) Rotation is about the center of the image; for very small * rotations, just return a clone. The dest is automatically * expanded so that no image pixels are lost. * (3) Rotation is by area mapping. It doesn't matter what * color is brought in because the alpha channel will * be transparent (black) there. * (4) If pixg is NULL, it is generated as an alpha layer that is * partially opaque, using @fract. Otherwise, it is cropped * to pixs if required and @fract is ignored. The alpha * channel in pixs is never used. * (4) Colormaps are removed to 32 bpp. * (5) The default setting for the border values in the alpha channel * is 0 (transparent) for the outermost ring of pixels and * (0.5 * fract * 255) for the second ring. When blended over * a second image, this * (a) shrinks the visible image to make a clean overlap edge * with an image below, and * (b) softens the edges by weakening the aliasing there. * Use l_setAlphaMaskBorder() to change these values. * (6) A subtle use of gamma correction is to remove gamma correction * before rotation and restore it afterwards. This is done * by sandwiching this function between a gamma/inverse-gamma * photometric transform: * pixt = pixGammaTRCWithAlpha(NULL, pixs, 1.0 / gamma, 0, 255); * pixd = pixRotateWithAlpha(pixt, angle, NULL, fract); * pixGammaTRCWithAlpha(pixd, pixd, gamma, 0, 255); * pixDestroy(&pixt); * This has the side-effect of producing artifacts in the very * dark regions. * * *** Warning: implicit assumption about RGB component ordering *** */ PIX * pixRotateWithAlpha(PIX *pixs, l_float32 angle, PIX *pixg, l_float32 fract) { l_int32 ws, hs, d, spp; PIX *pixd, *pix32, *pixg2, *pixgr; PROCNAME("pixRotateWithAlpha"); if (!pixs) return (PIX *) ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &ws, &hs, &d); if (d != 32 && pixGetColormap(pixs) == NULL) return (PIX *) ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL); if (pixg && pixGetDepth(pixg) != 8) { L_WARNING("pixg not 8 bpp; using @fract transparent alpha\n", procName); pixg = NULL; } if (!pixg && (fract < 0.0 || fract > 1.0)) { L_WARNING("invalid fract; using fully opaque\n", procName); fract = 1.0; } if (!pixg && fract == 0.0) L_WARNING("transparent alpha; image will not be blended\n", procName); /* Make sure input to rotation is 32 bpp rgb, and rotate it */ if (d != 32) pix32 = pixConvertTo32(pixs); else pix32 = pixClone(pixs); spp = pixGetSpp(pix32); pixSetSpp(pix32, 3); /* ignore the alpha channel for the rotation */ pixd = pixRotate(pix32, angle, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, ws, hs); pixSetSpp(pix32, spp); /* restore initial value in case it's a clone */ pixDestroy(&pix32); /* Set up alpha layer with a fading border and rotate it */ if (!pixg) { pixg2 = pixCreate(ws, hs, 8); if (fract == 1.0) pixSetAll(pixg2); else if (fract > 0.0) pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract)); } else { pixg2 = pixResizeToMatch(pixg, NULL, ws, hs); } if (ws > 10 && hs > 10) { /* see note 8 */ pixSetBorderRingVal(pixg2, 1, (l_int32)(255.0 * fract * AlphaMaskBorderVals[0])); pixSetBorderRingVal(pixg2, 2, (l_int32)(255.0 * fract * AlphaMaskBorderVals[1])); } pixgr = pixRotate(pixg2, angle, L_ROTATE_AREA_MAP, L_BRING_IN_BLACK, ws, hs); /* Combine into a 4 spp result */ pixSetRGBComponent(pixd, pixgr, L_ALPHA_CHANNEL); pixDestroy(&pixg2); pixDestroy(&pixgr); return pixd; }
/*! * pixConvertToOpjImage() * * Input: pix (8 or 32 bpp) * Return: opj_image, or NULL on error * * Notes: * (1) Input pix is 8 bpp grayscale, 32 bpp rgb, or 32 bpp rgba. * (2) Gray + alpha pix are all represented as rgba. */ static opj_image_t * pixConvertToOpjImage(PIX *pix) { l_int32 i, j, k, w, h, d, spp, wpl; OPJ_COLOR_SPACE colorspace; l_int32 *ir = NULL; l_int32 *ig = NULL; l_int32 *ib = NULL; l_int32 *ia = NULL; l_uint32 *line, *data; opj_image_t *image; opj_image_cmptparm_t cmptparm[4]; PROCNAME("pixConvertToOpjImage"); if (!pix) return (opj_image_t *)ERROR_PTR("pix not defined", procName, NULL); pixGetDimensions(pix, &w, &h, &d); if (d != 8 && d != 32) { L_ERROR("invalid depth: %d\n", procName, d); return NULL; } /* Allocate the opj_image. */ spp = pixGetSpp(pix); memset(&cmptparm[0], 0, 4 * sizeof(opj_image_cmptparm_t)); for (i = 0; i < spp; i++) { cmptparm[i].prec = 8; cmptparm[i].bpp = 8; cmptparm[i].sgnd = 0; cmptparm[i].dx = 1; cmptparm[i].dy = 1; cmptparm[i].w = w; cmptparm[i].h = h; } colorspace = (spp == 1) ? OPJ_CLRSPC_GRAY : OPJ_CLRSPC_SRGB; if ((image = opj_image_create(spp, &cmptparm[0], colorspace)) == NULL) return (opj_image_t *)ERROR_PTR("image not made", procName, NULL); image->x0 = 0; image->y0 = 0; image->x1 = w; image->y1 = h; /* Set the component pointers */ ir = image->comps[0].data; if (spp > 1) { ig = image->comps[1].data; ib = image->comps[2].data; } if(spp == 4) ia = image->comps[3].data; /* Transfer the data from the pix */ data = pixGetData(pix); wpl = pixGetWpl(pix); for (i = 0, k = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++, k++) { if (spp == 1) { ir[k] = GET_DATA_BYTE(line, j); } else if (spp > 1) { ir[k] = GET_DATA_BYTE(line + j, COLOR_RED); ig[k] = GET_DATA_BYTE(line + j, COLOR_GREEN); ib[k] = GET_DATA_BYTE(line + j, COLOR_BLUE); } if (spp == 4) ia[k] = GET_DATA_BYTE(line + j, L_ALPHA_CHANNEL); } } return image; }