/*! * \brief pixConnCompTransform() * * \param[in] pixs 1 bpp * \param[in] connect connectivity: 4 or 8 * \param[in] depth of pixd: 8 or 16 bpp; use 0 for auto determination * \return pixd 8, 16 or 32 bpp, or NULL on error * * <pre> * Notes: * (1) pixd is 8, 16 or 32 bpp, and the pixel values label the * fg component, starting with 1. Pixels in the bg are labelled 0. * (2) If %depth = 0, the depth of pixd is 8 if the number of c.c. * is less than 254, 16 if the number of c.c is less than 0xfffe, * and 32 otherwise. * (3) If %depth = 8, the assigned label for the n-th component is * 1 + n % 254. We use mod 254 because 0 is uniquely assigned * to black: e.g., see pixcmapCreateRandom(). Likewise, * if %depth = 16, the assigned label uses mod(2^16 - 2), and * if %depth = 32, no mod is taken. * </pre> */ PIX * pixConnCompTransform(PIX *pixs, l_int32 connect, l_int32 depth) { l_int32 i, n, index, w, h, xb, yb, wb, hb; BOXA *boxa; PIX *pix1, *pix2, *pixd; PIXA *pixa; PROCNAME("pixConnCompTransform"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connect != 4 && connect != 8) return (PIX *)ERROR_PTR("connectivity must be 4 or 8", procName, NULL); if (depth != 0 && depth != 8 && depth != 16 && depth != 32) return (PIX *)ERROR_PTR("depth must be 0, 8, 16 or 32", procName, NULL); boxa = pixConnComp(pixs, &pixa, connect); n = pixaGetCount(pixa); boxaDestroy(&boxa); pixGetDimensions(pixs, &w, &h, NULL); if (depth == 0) { if (n < 254) depth = 8; else if (n < 0xfffe) depth = 16; else depth = 32; } pixd = pixCreate(w, h, depth); pixSetSpp(pixd, 1); if (n == 0) { /* no fg */ pixaDestroy(&pixa); return pixd; } /* Label each component and blit it in */ for (i = 0; i < n; i++) { pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb); pix1 = pixaGetPix(pixa, i, L_CLONE); if (depth == 8) { index = 1 + (i % 254); pix2 = pixConvert1To8(NULL, pix1, 0, index); } else if (depth == 16) { index = 1 + (i % 0xfffe); pix2 = pixConvert1To16(NULL, pix1, 0, index); } else { /* depth == 32 */ index = 1 + i; pix2 = pixConvert1To32(NULL, pix1, 0, index); } pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix2, 0, 0); pixDestroy(&pix1); pixDestroy(&pix2); } pixaDestroy(&pixa); return pixd; }
char* ocr_bitmap(void* arg, png_color *palette,png_byte *alpha, unsigned char* indata,int w, int h) { PIX *pix = NULL; PIX *cpix = NULL; char*text_out= NULL; int i,j,index; unsigned int wpl; unsigned int *data,*ppixel; BOOL tess_ret = FALSE; struct ocrCtx* ctx = arg; pix = pixCreate(w, h, 32); if(pix == NULL) { return NULL; } wpl = pixGetWpl(pix); data = pixGetData(pix); #if LEPTONICA_VERSION > 69 pixSetSpp(pix, 4); #endif for (i = 0; i < h; i++) { ppixel = data + i * wpl; for (j = 0; j < w; j++) { index = indata[i * w + (j)]; composeRGBPixel(palette[index].red, palette[index].green,palette[index].blue, ppixel); SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL,alpha[index]); ppixel++; } } ignore_alpha_at_edge(alpha, indata, w, h, pix, &cpix); #ifdef OCR_DEBUG { char str[128] = ""; static int i = 0; sprintf(str,"temp/file_c_%d.png",i); pixWrite(str, cpix, IFF_PNG); i++; } #endif TessBaseAPISetImage2(ctx->api, cpix); tess_ret = TessBaseAPIRecognize(ctx->api, NULL); if( tess_ret != 0) printf("\nsomething messy\n"); text_out = TessBaseAPIGetUTF8Text(ctx->api); pixDestroy(&pix); pixDestroy(&cpix); return text_out; }
/*! * \brief pixConnCompIncrInit() * * \param[in] pixs 1 bpp * \param[in] conn connectivity: 4 or 8 * \param[out] ppixd 32 bpp, with c.c. labelled * \param[out] pptaa with pixel locations indexed by c.c. * \param[out] pncc initial number of c.c. * \return 0 if OK, 1 on error * * <pre> * Notes: * (1) This labels the connected components in a 1 bpp pix, and * additionally sets up a ptaa that lists the locations of pixels * in each of the components. * (2) It can be used to initialize the output image and arrays for * an application that maintains information about connected * components incrementally as pixels are added. * (3) pixs can be empty or have some foreground pixels. * (4) The connectivity is stored in pixd->special. * (5) Always initialize with the first pta in ptaa being empty * and representing the background value (index 0) in the pix. * </pre> */ l_int32 pixConnCompIncrInit(PIX *pixs, l_int32 conn, PIX **ppixd, PTAA **pptaa, l_int32 *pncc) { l_int32 empty, w, h, ncc; PIX *pixd; PTA *pta; PTAA *ptaa; PROCNAME("pixConnCompIncrInit"); if (ppixd) *ppixd = NULL; if (pptaa) *pptaa = NULL; if (pncc) *pncc = 0; if (!ppixd || !pptaa || !pncc) return ERROR_INT("&pixd, &ptaa, &ncc not all defined", procName, 1); if (!pixs || pixGetDepth(pixs) != 1) return ERROR_INT("pixs undefined or not 1 bpp", procName, 1); if (conn != 4 && conn != 8) return ERROR_INT("connectivity must be 4 or 8", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); pixZero(pixs, &empty); if (empty) { *ppixd = pixCreate(w, h, 32); pixSetSpp(*ppixd, 1); pixSetSpecial(*ppixd, conn); *pptaa = ptaaCreate(0); pta = ptaCreate(1); ptaaAddPta(*pptaa, pta, L_INSERT); /* reserve index 0 for background */ return 0; } /* Set up the initial labeled image and indexed pixel arrays */ if ((pixd = pixConnCompTransform(pixs, conn, 32)) == NULL) return ERROR_INT("pixd not made", procName, 1); pixSetSpecial(pixd, conn); *ppixd = pixd; if ((ptaa = ptaaIndexLabeledPixels(pixd, &ncc)) == NULL) return ERROR_INT("ptaa not made", procName, 1); *pptaa = ptaa; *pncc = ncc; return 0; }
/*! * \brief pixReadMemWebP() * * \param[in] filedata webp compressed data in memory * \param[in] filesize number of bytes in data * \return pix 32 bpp, or NULL on error * * <pre> * Notes: * (1) When the encoded data only has 3 channels (no alpha), * WebPDecodeRGBAInto() generates a raster of 32-bit pixels, with * the alpha channel set to opaque (255). * (2) We don't need to use the gnu runtime functions like fmemopen() * for redirecting data from a stream to memory, because * the webp library has been written with memory-to-memory * functions at the lowest level (which is good!). And, in * any event, fmemopen() doesn't work with l_binaryReadStream(). * </pre> */ PIX * pixReadMemWebP(const l_uint8 *filedata, size_t filesize) { l_uint8 *out = NULL; l_int32 w, h, has_alpha, wpl, stride; l_uint32 *data; size_t size; PIX *pix; WebPBitstreamFeatures features; PROCNAME("pixReadMemWebP"); if (!filedata) return (PIX *)ERROR_PTR("filedata not defined", procName, NULL); if (WebPGetFeatures(filedata, filesize, &features)) return (PIX *)ERROR_PTR("Invalid WebP file", procName, NULL); w = features.width; h = features.height; has_alpha = features.has_alpha; /* Write from compressed Y,U,V arrays to pix raster data */ pix = pixCreate(w, h, 32); pixSetInputFormat(pix, IFF_WEBP); if (has_alpha) pixSetSpp(pix, 4); data = pixGetData(pix); wpl = pixGetWpl(pix); stride = wpl * 4; size = stride * h; out = WebPDecodeRGBAInto(filedata, filesize, (uint8_t *)data, size, stride); if (out == NULL) { /* error: out should also point to data */ pixDestroy(&pix); return (PIX *)ERROR_PTR("WebP decode failed", procName, NULL); } /* 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(pix); return pix; }
/*! * \brief pixConnCompAreaTransform() * * \param[in] pixs 1 bpp * \param[in] connect connectivity: 4 or 8 * \return pixd 32 bpp, 1 spp, or NULL on error * * <pre> * Notes: * (1) The pixel values in pixd label the area of the fg component * to which the pixel belongs. Pixels in the bg are labelled 0. * (2) For purposes of visualization, the output can be converted * to 8 bpp, using pixConvert32To8() or pixMaxDynamicRange(). * </pre> */ PIX * pixConnCompAreaTransform(PIX *pixs, l_int32 connect) { l_int32 i, n, npix, w, h, xb, yb, wb, hb; l_int32 *tab8; BOXA *boxa; PIX *pix1, *pix2, *pixd; PIXA *pixa; PROCNAME("pixConnCompAreaTransform"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (connect != 4 && connect != 8) return (PIX *)ERROR_PTR("connectivity must be 4 or 8", procName, NULL); boxa = pixConnComp(pixs, &pixa, connect); n = pixaGetCount(pixa); boxaDestroy(&boxa); pixGetDimensions(pixs, &w, &h, NULL); pixd = pixCreate(w, h, 32); pixSetSpp(pixd, 1); if (n == 0) { /* no fg */ pixaDestroy(&pixa); return pixd; } /* Label each component and blit it in */ tab8 = makePixelSumTab8(); for (i = 0; i < n; i++) { pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb); pix1 = pixaGetPix(pixa, i, L_CLONE); pixCountPixels(pix1, &npix, tab8); pix2 = pixConvert1To32(NULL, pix1, 0, npix); pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix2, 0, 0); pixDestroy(&pix1); pixDestroy(&pix2); } pixaDestroy(&pixa); LEPT_FREE(tab8); return pixd; }
/*! * pixReadMemWebP() * * Input: filedata (webp compressed data in memory) * filesize (number of bytes in data) * Return: pix (32 bpp), or null on error * * Notes: * (1) When the encoded data only has 3 channels (no alpha), * WebPDecodeRGBAInto() generates a raster of 32-bit pixels, with * the alpha channel set to opaque (255). * (2) We don't need to use the gnu runtime functions like fmemopen() * for redirecting data from a stream to memory, because * the webp library has been written with memory-to-memory * functions at the lowest level (which is good!). And, in * any event, fmemopen() doesn't work with l_binaryReadStream(). */ PIX * pixReadMemWebP(const l_uint8 *filedata, size_t filesize) { l_uint8 *out = NULL; l_int32 w, h, has_alpha, wpl, stride; l_uint32 *data; size_t size; PIX *pix; WebPBitstreamFeatures features; PROCNAME("pixReadMemWebP"); if (!filedata) return (PIX *)ERROR_PTR("filedata not defined", procName, NULL); if (WebPGetFeatures(filedata, filesize, &features)) return (PIX *)ERROR_PTR("Invalid WebP file", procName, NULL); w = features.width; h = features.height; has_alpha = features.has_alpha; /* Write from compressed Y,U,V arrays to pix raster data */ pix = pixCreate(w, h, 32); if (has_alpha) pixSetSpp(pix, 4); data = pixGetData(pix); wpl = pixGetWpl(pix); stride = wpl * 4; size = stride * h; out = WebPDecodeRGBAInto(filedata, filesize, (uint8_t *)data, size, stride); if (out == NULL) { /* error: out should also point to data */ pixDestroy(&pix); return (PIX *)ERROR_PTR("WebP decode failed", procName, NULL); } /* WebP decoder emits opposite byte order for RGBA components */ pixEndianByteSwap(pix); return pix; }
int main(int argc, char **argv) { l_uint8 *data; l_int32 w, h, n1, n2, n, i, minval, maxval; l_int32 ncolors, rval, gval, bval, equal; l_int32 *rmap, *gmap, *bmap; l_uint32 color; l_float32 gamma; BOX *box; FILE *fp; PIX *pix1, *pix2, *pix3, *pix4, *pix5, *pix6; PIX *pixs, *pixb, *pixg, *pixc, *pixd; PIX *pixg2, *pixcs1, *pixcs2, *pixd1, *pixd2; PIXA *pixa, *pixa2, *pixa3; PIXCMAP *cmap, *cmap2; RGBA_QUAD *cta; L_REGPARAMS *rp; if (regTestSetup(argc, argv, &rp)) return 1; /* ------------------------ (1) ----------------------------*/ /* Blend with a white background */ pix1 = pixRead("books_logo.png"); pixDisplayWithTitle(pix1, 100, 0, NULL, rp->display); pix2 = pixAlphaBlendUniform(pix1, 0xffffff00); pixDisplayWithTitle(pix2, 100, 150, NULL, rp->display); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 0 */ regTestWritePixAndCheck(rp, pix2, IFF_PNG); /* 1 */ /* Generate an alpha layer based on the white background */ pix3 = pixSetAlphaOverWhite(pix2); pixSetSpp(pix3, 3); pixWrite("/tmp/alphaops.2.png", pix3, IFF_PNG); /* without alpha */ regTestCheckFile(rp, "/tmp/alphaops.2.png"); /* 2 */ pixSetSpp(pix3, 4); regTestWritePixAndCheck(rp, pix3, IFF_PNG); /* 3, with alpha */ pixDisplayWithTitle(pix3, 100, 300, NULL, rp->display); /* Render on a light yellow background */ pix4 = pixAlphaBlendUniform(pix3, 0xffffe000); regTestWritePixAndCheck(rp, pix4, IFF_PNG); /* 4 */ pixDisplayWithTitle(pix4, 100, 450, NULL, rp->display); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); /* ------------------------ (2) ----------------------------*/ lept_rmdir("alpha"); lept_mkdir("alpha"); /* Make the transparency (alpha) layer. * pixs is the mask. We turn it into a transparency (alpha) * layer by converting to 8 bpp. A small convolution fuzzes * the mask edges so that you don't see the pixels. */ pixs = pixRead("feyn-fract.tif"); pixGetDimensions(pixs, &w, &h, NULL); pixg = pixConvert1To8(NULL, pixs, 0, 255); pixg2 = pixBlockconvGray(pixg, NULL, 1, 1); regTestWritePixAndCheck(rp, pixg2, IFF_JFIF_JPEG); /* 5 */ pixDisplayWithTitle(pixg2, 0, 0, "alpha", rp->display); /* Make the viewable image. * pixc is the image that we see where the alpha layer is * opaque -- i.e., greater than 0. Scale it to the same * size as the mask. To visualize what this will look like * when displayed over a black background, create the black * background image, pixb, and do the blending with pixcs1 * explicitly using the alpha layer pixg2. */ pixc = pixRead("tetons.jpg"); pixcs1 = pixScaleToSize(pixc, w, h); regTestWritePixAndCheck(rp, pixcs1, IFF_JFIF_JPEG); /* 6 */ pixDisplayWithTitle(pixcs1, 300, 0, "viewable", rp->display); pixb = pixCreateTemplate(pixcs1); /* black */ pixd1 = pixBlendWithGrayMask(pixb, pixcs1, pixg2, 0, 0); regTestWritePixAndCheck(rp, pixd1, IFF_JFIF_JPEG); /* 7 */ pixDisplayWithTitle(pixd1, 600, 0, "alpha-blended 1", rp->display); /* Embed the alpha layer pixg2 into the color image pixc. * Write it out as is. Then clean pixcs1 (to 0) under the fully * transparent part of the alpha layer, and write that result * out as well. */ pixSetRGBComponent(pixcs1, pixg2, L_ALPHA_CHANNEL); pixWrite("/tmp/alpha/pixcs1.png", pixcs1, IFF_PNG); pixcs2 = pixSetUnderTransparency(pixcs1, 0, 0); pixWrite("/tmp/alpha/pixcs2.png", pixcs2, IFF_PNG); /* What will this look like over a black background? * Do the blending explicitly and display. It should * look identical to the blended result pixd1 before cleaning. */ pixd2 = pixBlendWithGrayMask(pixb, pixcs2, pixg2, 0, 0); regTestWritePixAndCheck(rp, pixd2, IFF_JFIF_JPEG); /* 8 */ pixDisplayWithTitle(pixd2, 0, 400, "alpha blended 2", rp->display); /* Read the two images back, ignoring the transparency layer. * The uncleaned image will come back identical to pixcs1. * However, the cleaned image will be black wherever * the alpha layer was fully transparent. It will * look the same when viewed through the alpha layer, * but have much better compression. */ pix1 = pixRead("/tmp/alpha/pixcs1.png"); /* just pixcs1 */ pix2 = pixRead("/tmp/alpha/pixcs2.png"); /* cleaned under transparent */ n1 = nbytesInFile("/tmp/alpha/pixcs1.png"); n2 = nbytesInFile("/tmp/alpha/pixcs2.png"); fprintf(stderr, " Original: %d bytes\n Cleaned: %d bytes\n", n1, n2); regTestWritePixAndCheck(rp, pix1, IFF_JFIF_JPEG); /* 9 */ regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 10 */ pixDisplayWithTitle(pix1, 300, 400, "without alpha", rp->display); pixDisplayWithTitle(pix2, 600, 400, "cleaned under transparent", rp->display); pixa = pixaCreate(0); pixSaveTiled(pixg2, pixa, 1.0, 1, 20, 32); pixSaveTiled(pixcs1, pixa, 1.0, 1, 20, 0); pixSaveTiled(pix1, pixa, 1.0, 0, 20, 0); pixSaveTiled(pixd1, pixa, 1.0, 1, 20, 0); pixSaveTiled(pixd2, pixa, 1.0, 0, 20, 0); pixSaveTiled(pix2, pixa, 1.0, 1, 20, 0); pixd = pixaDisplay(pixa, 0, 0); regTestWritePixAndCheck(rp, pixd, IFF_JFIF_JPEG); /* 11 */ pixDisplayWithTitle(pixd, 200, 200, "composite", rp->display); pixWrite("/tmp/alpha/alpha.png", pixd, IFF_JFIF_JPEG); pixDestroy(&pixd); pixaDestroy(&pixa); pixDestroy(&pixs); pixDestroy(&pixb); pixDestroy(&pixg); pixDestroy(&pixg2); pixDestroy(&pixc); pixDestroy(&pixcs1); pixDestroy(&pixcs2); pixDestroy(&pixd); pixDestroy(&pixd1); pixDestroy(&pixd2); pixDestroy(&pix1); pixDestroy(&pix2); /* ------------------------ (3) ----------------------------*/ color = 0xffffa000; gamma = 1.0; minval = 0; maxval = 200; box = boxCreate(0, 85, 600, 100); pixa = pixaCreate(6); pix1 = pixRead("blend-green1.jpg"); pixaAddPix(pixa, pix1, L_INSERT); pix1 = pixRead("blend-green2.png"); pixaAddPix(pixa, pix1, L_INSERT); pix1 = pixRead("blend-green3.png"); pixaAddPix(pixa, pix1, L_INSERT); pix1 = pixRead("blend-orange.jpg"); pixaAddPix(pixa, pix1, L_INSERT); pix1 = pixRead("blend-yellow.jpg"); pixaAddPix(pixa, pix1, L_INSERT); pix1 = pixRead("blend-red.png"); pixaAddPix(pixa, pix1, L_INSERT); n = pixaGetCount(pixa); pixa2 = pixaCreate(n); pixa3 = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixa, i, L_CLONE); pix2 = DoBlendTest(pix1, box, color, gamma, minval, maxval, 1); regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 12, 14, ... 22 */ pixDisplayWithTitle(pix2, 150 * i, 0, NULL, rp->display); pixaAddPix(pixa2, pix2, L_INSERT); pix2 = DoBlendTest(pix1, box, color, gamma, minval, maxval, 2); regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG); /* 13, 15, ... 23 */ pixDisplayWithTitle(pix2, 150 * i, 200, NULL, rp->display); pixaAddPix(pixa3, pix2, L_INSERT); pixDestroy(&pix1); } if (rp->display) { pixaConvertToPdf(pixa2, 0, 0.75, L_FLATE_ENCODE, 0, "blend 1 test", "/tmp/alpha/blending1.pdf"); pixaConvertToPdf(pixa3, 0, 0.75, L_FLATE_ENCODE, 0, "blend 2 test", "/tmp/alpha/blending2.pdf"); } pixaDestroy(&pixa); pixaDestroy(&pixa2); pixaDestroy(&pixa3); boxDestroy(&box); /* ------------------------ (4) ----------------------------*/ /* Use one image as the alpha component for a second image */ pix1 = pixRead("test24.jpg"); pix2 = pixRead("marge.jpg"); pix3 = pixScale(pix2, 1.9, 2.2); pix4 = pixConvertTo8(pix3, 0); pixSetRGBComponent(pix1, pix4, L_ALPHA_CHANNEL); regTestWritePixAndCheck(rp, pix1, IFF_PNG); /* 24 */ pixDisplayWithTitle(pix1, 600, 0, NULL, rp->display); /* Set the alpha value in a colormap to bval */ pix5 = pixOctreeColorQuant(pix1, 128, 0); cmap = pixGetColormap(pix5); pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL); n = pixcmapGetCount(cmap); for (i = 0; i < n; i++) { pixcmapGetColor(cmap, i, &rval, &gval, &bval); cta = (RGBA_QUAD *)cmap->array; cta[i].alpha = bval; } /* Test binary serialization/deserialization of colormap with alpha */ pixcmapSerializeToMemory(cmap, 4, &ncolors, &data); cmap2 = pixcmapDeserializeFromMemory(data, 4, ncolors); CmapEqual(cmap, cmap2, &equal); regTestCompareValues(rp, TRUE, equal, 0.0); /* 25 */ pixcmapDestroy(&cmap2); lept_free(data); /* Test ascii serialization/deserialization of colormap with alpha */ fp = fopenWriteStream("/tmp/alpha/cmap.4", "w"); pixcmapWriteStream(fp, cmap); fclose(fp); fp = fopenReadStream("/tmp/alpha/cmap.4"); cmap2 = pixcmapReadStream(fp); fclose(fp); CmapEqual(cmap, cmap2, &equal); regTestCompareValues(rp, TRUE, equal, 0.0); /* 26 */ pixcmapDestroy(&cmap2); /* Test r/w for cmapped pix with non-opaque alpha */ pixDisplayWithTitle(pix5, 900, 0, NULL, rp->display); regTestWritePixAndCheck(rp, pix5, IFF_PNG); /* 27 */ pixWrite("/tmp/alpha/fourcomp.png", pix5, IFF_PNG); pix6 = pixRead("/tmp/alpha/fourcomp.png"); regTestComparePix(rp, pix5, pix6); /* 28 */ pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); pixDestroy(&pix5); pixDestroy(&pix6); lept_free(rmap); lept_free(gmap); lept_free(bmap); return regTestCleanup(rp); }
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; }
/*! * 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; }
/*! * 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; }
/*! * pixBilinearPtaWithAlpha() * * Input: pixs (32 bpp rgb) * ptad (4 pts of final coordinate space) * ptas (4 pts of initial coordinate space) * pixg (<optional> 8 bpp, can be null) * fract (between 0.0 and 1.0, with 0.0 fully transparent * and 1.0 fully opaque) * border (of pixels added to capture transformed source pixels) * Return: pixd, 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) 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. * (3) Colormaps are removed. * (4) When pixs is transformed, it doesn't matter what color is brought * in because the alpha channel will be transparent (0) there. * (5) To avoid losing source pixels in the destination, it may be * necessary to add a border to the source pix before doing * the bilinear transformation. This can be any non-negative number. * (6) The input @ptad and @ptas are in a coordinate space before * the border is added. Internally, we compensate for this * before doing the bilinear transform on the image after * the border is added. * (7) 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. */ PIX * pixBilinearPtaWithAlpha(PIX *pixs, PTA *ptad, PTA *ptas, PIX *pixg, l_float32 fract, l_int32 border) { l_int32 ws, hs, d; PIX *pixd, *pixb1, *pixb2, *pixg2, *pixga; PTA *ptad2, *ptas2; PROCNAME("pixBilinearPtaWithAlpha"); 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 1.0 (fully transparent)\n", procName); fract = 1.0; } if (!pixg && fract == 0.0) L_WARNING("fully opaque alpha; image cannot be blended\n", procName); if (!ptad) return (PIX *) ERROR_PTR("ptad not defined", procName, NULL); if (!ptas) return (PIX *) ERROR_PTR("ptas not defined", procName, NULL); /* Add border; the color doesn't matter */ pixb1 = pixAddBorder(pixs, border, 0); /* Transform the ptr arrays to work on the bordered image */ ptad2 = ptaTransform(ptad, border, border, 1.0, 1.0); ptas2 = ptaTransform(ptas, border, border, 1.0, 1.0); /* Do separate bilinear transform of rgb channels of pixs and of pixg */ pixd = pixBilinearPtaColor(pixb1, ptad2, ptas2, 0); if (!pixg) { pixg2 = pixCreate(ws, hs, 8); if (fract == 1.0) pixSetAll(pixg2); else pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract)); } else { pixg2 = pixResizeToMatch(pixg, NULL, ws, hs); } if (ws > 10 && hs > 10) { /* see note 7 */ pixSetBorderRingVal(pixg2, 1, (l_int32)(255.0 * fract * AlphaMaskBorderVals[0])); pixSetBorderRingVal(pixg2, 2, (l_int32)(255.0 * fract * AlphaMaskBorderVals[1])); } pixb2 = pixAddBorder(pixg2, border, 0); /* must be black border */ pixga = pixBilinearPtaGray(pixb2, ptad2, ptas2, 0); pixSetRGBComponent(pixd, pixga, L_ALPHA_CHANNEL); pixSetSpp(pixd, 4); pixDestroy(&pixg2); pixDestroy(&pixb1); pixDestroy(&pixb2); pixDestroy(&pixga); ptaDestroy(&ptad2); ptaDestroy(&ptas2); return pixd; }