/*! * pixEmbedForRotation() * * Input: pixs (1, 2, 4, 8, 32 bpp rgb) * angle (radians; clockwise is positive) * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK) * width (original width; use 0 to avoid embedding) * height (original height; use 0 to avoid embedding) * Return: pixd, or null on error * * Notes: * (1) For very small rotations, just return a clone. * (2) Generate larger image to embed pixs if necessary, and * place the center of the input image in the center. * (3) Rotation brings either white or black pixels in * from outside the image. For colormapped images where * there is no white or black, a new color is added if * possible for these pixels; otherwise, either the * lightest or darkest color is used. In most cases, * the colormap will be removed prior to rotation. * (4) The dest is to be expanded so that no image pixels * are lost after rotation. Input of the original width * and height allows the expansion to stop at the maximum * required size, which is a square with side equal to * sqrt(w*w + h*h). * (5) For an arbitrary angle, the expansion can be found by * considering the UL and UR corners. As the image is * rotated, these move in an arc centered at the center of * the image. Normalize to a unit circle by dividing by half * the image diagonal. After a rotation of T radians, the UL * and UR corners are at points T radians along the unit * circle. Compute the x and y coordinates of both these * points and take the max of absolute values; these represent * the half width and half height of the containing rectangle. * The arithmetic is done using formulas for sin(a+b) and cos(a+b), * where b = T. For the UR corner, sin(a) = h/d and cos(a) = w/d. * For the UL corner, replace a by (pi - a), and you have * sin(pi - a) = h/d, cos(pi - a) = -w/d. The equations * given below follow directly. */ PIX * pixEmbedForRotation(PIX *pixs, l_float32 angle, l_int32 incolor, l_int32 width, l_int32 height) { l_int32 w, h, d, w1, h1, w2, h2, maxside, wnew, hnew, xoff, yoff, setcolor; l_float64 sina, cosa, fw, fh; PIX *pixd; PROCNAME("pixEmbedForRotation"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *)ERROR_PTR("invalid incolor", procName, NULL); if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE) return pixClone(pixs); /* Test if big enough to hold any rotation of the original image */ pixGetDimensions(pixs, &w, &h, &d); maxside = (l_int32)(sqrt((l_float64)(width * width) + (l_float64)(height * height)) + 0.5); if (w >= maxside && h >= maxside) /* big enough */ return pixClone(pixs); /* Find the new sizes required to hold the image after rotation. * Note that the new dimensions must be at least as large as those * of pixs, because we're rasterop-ing into it before rotation. */ cosa = cos(angle); sina = sin(angle); fw = (l_float64)w; fh = (l_float64)h; w1 = (l_int32)(L_ABS(fw * cosa - fh * sina) + 0.5); w2 = (l_int32)(L_ABS(-fw * cosa - fh * sina) + 0.5); h1 = (l_int32)(L_ABS(fw * sina + fh * cosa) + 0.5); h2 = (l_int32)(L_ABS(-fw * sina + fh * cosa) + 0.5); wnew = L_MAX(w, L_MAX(w1, w2)); hnew = L_MAX(h, L_MAX(h1, h2)); if ((pixd = pixCreate(wnew, hnew, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopySpp(pixd, pixs); pixCopyText(pixd, pixs); xoff = (wnew - w) / 2; yoff = (hnew - h) / 2; /* Set background to color to be rotated in */ setcolor = (incolor == L_BRING_IN_BLACK) ? L_SET_BLACK : L_SET_WHITE; pixSetBlackOrWhite(pixd, setcolor); /* Rasterop automatically handles all 4 channels for rgba */ pixRasterop(pixd, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0); return pixd; }
/*! * pixSelectByWidthHeightRatio() * * Input: pixs (1 bpp) * thresh (threshold ratio of width/height) * connectivity (4 or 8) * type (L_SELECT_IF_LT, L_SELECT_IF_GT, * L_SELECT_IF_LTE, L_SELECT_IF_GTE) * &changed (<optional return> 1 if changed; 0 if clone returned) * Return: pixd, or null on error * * Notes: * (1) The args specify constraints on the width-to-height ratio * for components that are kept. * (2) If unchanged, returns a copy of pixs. Otherwise, * returns a new pix with the filtered components. * (3) This filters components based on the width-to-height ratios. * (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components * with less than the threshold ratio, and * L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them. */ PIX * pixSelectByWidthHeightRatio(PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged) { l_int32 w, h, empty, changed, count; BOXA *boxa; PIX *pixd; PIXA *pixas, *pixad; PROCNAME("pixSelectByWidthHeightRatio"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT && type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE) return (PIX *)ERROR_PTR("invalid type", procName, NULL); if (pchanged) *pchanged = FALSE; /* Check if any components exist */ pixZero(pixs, &empty); if (empty) return pixCopy(NULL, pixs); /* Filter components */ boxa = pixConnComp(pixs, &pixas, connectivity); pixad = pixaSelectByWidthHeightRatio(pixas, thresh, type, &changed); boxaDestroy(&boxa); pixaDestroy(&pixas); /* Render the result */ if (!changed) { pixaDestroy(&pixad); return pixCopy(NULL, pixs); } else { if (pchanged) *pchanged = TRUE; pixGetDimensions(pixs, &w, &h, NULL); count = pixaGetCount(pixad); if (count == 0) /* return empty pix */ pixd = pixCreateTemplate(pixs); else { pixd = pixaDisplay(pixad, w, h); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); } pixaDestroy(&pixad); return pixd; } }
/*! * pixTransferAllData() * * Input: pixd (must be different from pixs) * &pixs (will be nulled if refcount goes to 0) * copytext (1 to copy the text field; 0 to skip) * copyformat (1 to copy the informat field; 0 to skip) * Return: 0 if OK, 1 on error * * Notes: * (1) This does a complete data transfer from pixs to pixd, * followed by the destruction of pixs (refcount permitting). * (2) If the refcount of pixs is 1, pixs is destroyed. Otherwise, * the data in pixs is copied (rather than transferred) to pixd. * (3) This operation, like all others with a pre-existing pixd, * will side-effect any existing clones of pixd. The pixd * refcount does not change. * (4) When might you use this? Suppose you have an in-place Pix * function (returning void) with the typical signature: * void function-inplace(PIX *pix, ...) * where "..." are non-pointer input parameters, and suppose * further that you sometimes want to return an arbitrary Pix * in place of the input Pix. There are two ways you can do this: * (a) The straightforward way is to change the function * signature to take the address of the Pix ptr: * void function-inplace(PIX **ppix, ...) { * PIX *pixt = function-makenew(*ppix); * pixDestroy(ppix); * *ppix = pixt; * return; * } * Here, the input and returned pix are different, as viewed * by the calling function, and the inplace function is * expected to destroy the input pix to avoid a memory leak. * (b) Keep the signature the same and use pixTransferAllData() * to return the new Pix in the input Pix struct: * void function-inplace(PIX *pix, ...) { * PIX *pixt = function-makenew(pix); * pixTransferAllData(pix, &pixt); // pixt is destroyed * return; * } * Here, the input and returned pix are the same, as viewed * by the calling function, and the inplace function must * never destroy the input pix, because the calling function * maintains an unchanged handle to it. */ l_int32 pixTransferAllData(PIX *pixd, PIX **ppixs, l_int32 copytext, l_int32 copyformat) { l_int32 nbytes; PIX *pixs; PROCNAME("pixTransferAllData"); if (!ppixs) return ERROR_INT("&pixs not defined", procName, 1); if ((pixs = *ppixs) == NULL) return ERROR_INT("pixs not defined", procName, 1); if (!pixd) return ERROR_INT("pixd not defined", procName, 1); if (pixs == pixd) /* no-op */ return ERROR_INT("pixd == pixs", procName, 1); if (pixGetRefcount(pixs) == 1) { /* transfer the data, cmap, text */ pixFreeData(pixd); /* dealloc any existing data */ pixSetData(pixd, pixGetData(pixs)); /* transfer new data from pixs */ pixs->data = NULL; /* pixs no longer owns data */ pixSetColormap(pixd, pixGetColormap(pixs)); /* frees old; sets new */ pixs->colormap = NULL; /* pixs no longer owns colormap */ if (copytext) { pixSetText(pixd, pixGetText(pixs)); pixSetText(pixs, NULL); } } else { /* preserve pixs by making a copy of the data, cmap, text */ pixResizeImageData(pixd, pixs); nbytes = 4 * pixGetWpl(pixs) * pixGetHeight(pixs); memcpy((char *)pixGetData(pixd), (char *)pixGetData(pixs), nbytes); pixCopyColormap(pixd, pixs); if (copytext) pixCopyText(pixd, pixs); } pixCopyResolution(pixd, pixs); pixCopyDimensions(pixd, pixs); if (copyformat) pixCopyInputFormat(pixd, pixs); /* This will destroy pixs if data was transferred; * otherwise, it just decrements its refcount. */ pixDestroy(ppixs); return 0; }
/*! * pixCopy() * * Input: pixd (<optional>; can be null, or equal to pixs, * or different from pixs) * pixs * Return: pixd, or null on error * * Notes: * (1) There are three cases: * (a) pixd == null (makes a new pix; refcount = 1) * (b) pixd == pixs (no-op) * (c) pixd != pixs (data copy; no change in refcount) * If the refcount of pixd > 1, case (c) will side-effect * these handles. * (2) The general pattern of use is: * pixd = pixCopy(pixd, pixs); * This will work for all three cases. * For clarity when the case is known, you can use: * (a) pixd = pixCopy(NULL, pixs); * (c) pixCopy(pixd, pixs); * (3) For case (c), we check if pixs and pixd are the same * size (w,h,d). If so, the data is copied directly. * Otherwise, the data is reallocated to the correct size * and the copy proceeds. The refcount of pixd is unchanged. * (4) This operation, like all others that may involve a pre-existing * pixd, will side-effect any existing clones of pixd. */ PIX * pixCopy(PIX *pixd, /* can be null */ PIX *pixs) { l_int32 bytes; l_uint32 *datas, *datad; PROCNAME("pixCopy"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixs == pixd) return pixd; /* Total bytes in image data */ bytes = 4 * pixGetWpl(pixs) * pixGetHeight(pixs); /* If we're making a new pix ... */ if (!pixd) { if ((pixd = pixCreateTemplate(pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); datas = pixGetData(pixs); datad = pixGetData(pixd); memcpy((char *)datad, (char *)datas, bytes); return pixd; } /* Reallocate image data if sizes are different */ if (pixResizeImageData(pixd, pixs) == 1) return (PIX *)ERROR_PTR("reallocation of data failed", procName, NULL); /* Copy non-image data fields */ pixCopyColormap(pixd, pixs); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); pixCopyText(pixd, pixs); /* Copy image data */ datas = pixGetData(pixs); datad = pixGetData(pixd); memcpy((char*)datad, (char*)datas, bytes); return pixd; }
/*! * pixCreateTemplateNoInit() * * Input: pixs * Return: pixd, or null on error * * Notes: * (1) Makes a Pix of the same size as the input Pix, with * the data array allocated but not initialized to 0. * (2) Copies the other fields, including colormap if it exists. */ PIX * pixCreateTemplateNoInit(PIX *pixs) { l_int32 w, h, d; PIX *pixd; PROCNAME("pixCreateTemplateNoInit"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); pixGetDimensions(pixs, &w, &h, &d); if ((pixd = pixCreateNoInit(w, h, d)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixCopyResolution(pixd, pixs); pixCopyColormap(pixd, pixs); pixCopyText(pixd, pixs); pixCopyInputFormat(pixd, pixs); return pixd; }
/*! * \brief pixSetStrokeWidth() * * \param[in] pixs 1 bpp pix * \param[in] width set stroke width to this value, in [1 ... 100]. * \param[in] thinfirst 1 to thin all pix to a skeleton first; 0 to skip * \param[in] connectivity 4 or 8, to be used if %thinfirst == 1 * \return pixd with stroke width set to %width, or NULL on error * * <pre> * Notes: * (1) See notes in pixaSetStrokeWidth(). * (2) A white border of sufficient width to avoid boundary * artifacts in the thickening step is added before thinning. * (3) %connectivity == 8 usually gives a slightly smoother result. * </pre> */ PIX * pixSetStrokeWidth(PIX *pixs, l_int32 width, l_int32 thinfirst, l_int32 connectivity) { char buf[16]; l_int32 border; PIX *pix1, *pix2, *pixd; PROCNAME("pixSetStrokeWidth"); if (!pixs || (pixGetDepth(pixs) != 1)) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (width < 1 || width > 100) return (PIX *)ERROR_PTR("width not in [1 ... 100]", procName, NULL); if (connectivity != 4 && connectivity != 8) return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL); if (!thinfirst && width == 1) /* nothing to do */ return pixCopy(NULL, pixs); /* Add a white border */ border = width / 2; pix1 = pixAddBorder(pixs, border, 0); /* Thin to a skeleton */ if (thinfirst) pix2 = pixThinConnected(pix1, L_THIN_FG, connectivity, 0); else pix2 = pixClone(pix1); pixDestroy(&pix1); /* Dilate */ snprintf(buf, sizeof(buf), "D%d.%d", width, width); pixd = pixMorphSequence(pix2, buf, 0); pixCopyText(pixd, pixs); pixDestroy(&pix2); return pixd; }