/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file rotate.c *
 *
 *     General rotation about image center
 *              PIX     *pixRotate()
 *              PIX     *pixEmbedForRotation()
 *
 *     General rotation by sampling
 *              PIX     *pixRotateBySampling()
 *
 *     Nice (slow) rotation of 1 bpp image
 *              PIX     *pixRotateBinaryNice()
 *
 *     Rotation including alpha (blend) component
 *              PIX     *pixRotateWithAlpha()
 *
 *     Rotations are measured in radians; clockwise is positive.
 *
 *     The general rotation pixRotate() does the best job for
 *     rotating about the image center.  For 1 bpp, it uses shear;
 *     for others, it uses either shear or area mapping.
 *     If requested, it expands the output image so that no pixels are lost
 *     in the rotation, and this can be done on multiple successive shears
 *     without expanding beyond the maximum necessary size.
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" extern l_float32 AlphaMaskBorderVals[2]; static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */ static const l_float32 Max1BppShearAngle = 0.06f; /* radians; ~3 deg */ static const l_float32 LimitShearAngle = 0.35f; /* radians; ~20 deg */ /*------------------------------------------------------------------* * General rotation about the center * *------------------------------------------------------------------*/ /*! * \brief pixRotate() * * \param[in] pixs 1, 2, 4, 8, 32 bpp rgb * \param[in] angle radians; clockwise is positive * \param[in] type L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK * \param[in] width original width; use 0 to avoid embedding * \param[in] height original height; use 0 to avoid embedding * \return pixd, or NULL on error * *
 * Notes:
 *      (1) This is a high-level, simple interface for rotating images
 *          about their center.
 *      (2) For very small rotations, just return a clone.
 *      (3) Rotation brings either white or black pixels in
 *          from outside the image.
 *      (4) The rotation type is adjusted if necessary for the image
 *          depth and size of rotation angle.  For 1 bpp images, we
 *          rotate either by shear or sampling.
 *      (5) Colormaps are removed for rotation by area mapping.
 *      (6) The dest can be expanded so that no image pixels
 *          are lost.  To invoke expansion, input the original
 *          width and height.  For repeated rotation, use of the
 *          original width and height allows the expansion to
 *          stop at the maximum required size, which is a square
 *          with side = sqrt(w*w + h*h).
 * 
*/ PIX * pixRotate(PIX *pixs, l_float32 angle, l_int32 type, l_int32 incolor, l_int32 width, l_int32 height) { l_int32 w, h, d; l_uint32 fillval; PIX *pix1, *pix2, *pix3, *pixd; PIXCMAP *cmap; PROCNAME("pixRotate"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP && type != L_ROTATE_SAMPLING) return (PIX *)ERROR_PTR("invalid type", 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) < MinAngleToRotate) return pixClone(pixs); /* Adjust rotation type if necessary: * - If d == 1 bpp and the angle is more than about 6 degrees, * rotate by sampling; otherwise rotate by shear. * - If d > 1, only allow shear rotation up to about 20 degrees; * beyond that, default a shear request to sampling. */ if (pixGetDepth(pixs) == 1) { if (L_ABS(angle) > Max1BppShearAngle) { if (type != L_ROTATE_SAMPLING) L_INFO("1 bpp, large angle; rotate by sampling\n", procName); type = L_ROTATE_SAMPLING; } else if (type != L_ROTATE_SHEAR) { L_INFO("1 bpp; rotate by shear\n", procName); type = L_ROTATE_SHEAR; } } else if (L_ABS(angle) > LimitShearAngle && type == L_ROTATE_SHEAR) { L_INFO("large angle; rotate by sampling\n", procName); type = L_ROTATE_SAMPLING; } /* Remove colormap if we rotate by area mapping. */ cmap = pixGetColormap(pixs); if (cmap && type == L_ROTATE_AREA_MAP) pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); else pix1 = pixClone(pixs); cmap = pixGetColormap(pix1); /* Otherwise, if there is a colormap and we're not embedding, * add white color if it doesn't exist. */ if (cmap && width == 0) { /* no embedding; generate %incolor */ if (incolor == L_BRING_IN_BLACK) pixcmapAddBlackOrWhite(cmap, 0, NULL); else /* L_BRING_IN_WHITE */ pixcmapAddBlackOrWhite(cmap, 1, NULL); } /* Request to embed in a larger image; do if necessary */ pix2 = pixEmbedForRotation(pix1, angle, incolor, width, height); /* Area mapping requires 8 or 32 bpp. If less than 8 bpp and * area map rotation is requested, convert to 8 bpp. */ d = pixGetDepth(pix2); if (type == L_ROTATE_AREA_MAP && d < 8) pix3 = pixConvertTo8(pix2, FALSE); else pix3 = pixClone(pix2); /* Do the rotation: shear, sampling or area mapping */ pixGetDimensions(pix3, &w, &h, &d); if (type == L_ROTATE_SHEAR) { pixd = pixRotateShearCenter(pix3, angle, incolor); } else if (type == L_ROTATE_SAMPLING) { pixd = pixRotateBySampling(pix3, w / 2, h / 2, angle, incolor); } else { /* rotate by area mapping */ fillval = 0; if (incolor == L_BRING_IN_WHITE) { if (d == 8) fillval = 255; else /* d == 32 */ fillval = 0xffffff00; } if (d == 8) pixd = pixRotateAMGray(pix3, angle, fillval); else /* d == 32 */ pixd = pixRotateAMColor(pix3, angle, fillval); } pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); return pixd; } /*! * \brief pixEmbedForRotation() * * \param[in] pixs 1, 2, 4, 8, 32 bpp rgb * \param[in] angle radians; clockwise is positive * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK * \param[in] width original width; use 0 to avoid embedding * \param[in] 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) < MinAngleToRotate) 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; } /*------------------------------------------------------------------* * General rotation by sampling * *------------------------------------------------------------------*/ /*! * \brief pixRotateBySampling() * * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp rgb; can be cmapped * \param[in] xcen x value of center of rotation * \param[in] ycen y value of center of rotation * \param[in] angle radians; clockwise is positive * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK * \return pixd, or NULL on error * *
 * Notes:
 *      (1) For very small rotations, just return a clone.
 *      (2) Rotation brings either white or black pixels in
 *          from outside the image.
 *      (3) Colormaps are retained.
 * 
*/ PIX * pixRotateBySampling(PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor) { l_int32 w, h, d, i, j, x, y, xdif, ydif, wm1, hm1, wpld; l_uint32 val; l_float32 sina, cosa; l_uint32 *datad, *lined; void **lines; PIX *pixd; PROCNAME("pixRotateBySampling"); 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); pixGetDimensions(pixs, &w, &h, &d); if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) return (PIX *)ERROR_PTR("invalid depth", procName, NULL); if (L_ABS(angle) < MinAngleToRotate) return pixClone(pixs); if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixSetBlackOrWhite(pixd, incolor); sina = sin(angle); cosa = cos(angle); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); wm1 = w - 1; hm1 = h - 1; lines = pixGetLinePtrs(pixs, NULL); /* Treat 1 bpp case specially */ if (d == 1) { for (i = 0; i < h; i++) { /* scan over pixd */ lined = datad + i * wpld; ydif = ycen - i; for (j = 0; j < w; j++) { xdif = xcen - j; x = xcen + (l_int32)(-xdif * cosa - ydif * sina); if (x < 0 || x > wm1) continue; y = ycen + (l_int32)(-ydif * cosa + xdif * sina); if (y < 0 || y > hm1) continue; if (incolor == L_BRING_IN_WHITE) { if (GET_DATA_BIT(lines[y], x)) SET_DATA_BIT(lined, j); } else { if (!GET_DATA_BIT(lines[y], x)) CLEAR_DATA_BIT(lined, j); } } } LEPT_FREE(lines); return pixd; } for (i = 0; i < h; i++) { /* scan over pixd */ lined = datad + i * wpld; ydif = ycen - i; for (j = 0; j < w; j++) { xdif = xcen - j; x = xcen + (l_int32)(-xdif * cosa - ydif * sina); if (x < 0 || x > wm1) continue; y = ycen + (l_int32)(-ydif * cosa + xdif * sina); if (y < 0 || y > hm1) continue; switch (d) { case 8: val = GET_DATA_BYTE(lines[y], x); SET_DATA_BYTE(lined, j, val); break; case 32: val = GET_DATA_FOUR_BYTES(lines[y], x); SET_DATA_FOUR_BYTES(lined, j, val); break; case 2: val = GET_DATA_DIBIT(lines[y], x); SET_DATA_DIBIT(lined, j, val); break; case 4: val = GET_DATA_QBIT(lines[y], x); SET_DATA_QBIT(lined, j, val); break; case 16: val = GET_DATA_TWO_BYTES(lines[y], x); SET_DATA_TWO_BYTES(lined, j, val); break; default: return (PIX *)ERROR_PTR("invalid depth", procName, NULL); } } } LEPT_FREE(lines); return pixd; } /*------------------------------------------------------------------* * Nice (slow) rotation of 1 bpp image * *------------------------------------------------------------------*/ /*! * \brief pixRotateBinaryNice() * * \param[in] pixs 1 bpp * \param[in] angle radians; clockwise is positive; about the center * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK * \return pixd, or NULL on error * *
 * Notes:
 *      (1) For very small rotations, just return a clone.
 *      (2) This does a computationally expensive rotation of 1 bpp images.
 *          The fastest rotators (using shears or subsampling) leave
 *          visible horizontal and vertical shear lines across which
 *          the image shear changes by one pixel.  To ameliorate the
 *          visual effect one can introduce random dithering.  One
 *          way to do this in a not-too-random fashion is given here.
 *          We convert to 8 bpp, do a very small blur, rotate using
 *          linear interpolation (same as area mapping), do a
 *          small amount of sharpening to compensate for the initial
 *          blur, and threshold back to binary.  The shear lines
 *          are magically removed.
 *      (3) This operation is about 5x slower than rotation by sampling.
 * 
*/ PIX * pixRotateBinaryNice(PIX *pixs, l_float32 angle, l_int32 incolor) { PIX *pix1, *pix2, *pix3, *pix4, *pixd; PROCNAME("pixRotateBinaryNice"); if (!pixs || pixGetDepth(pixs) != 1) return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL); if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) return (PIX *)ERROR_PTR("invalid incolor", procName, NULL); pix1 = pixConvertTo8(pixs, 0); pix2 = pixBlockconv(pix1, 1, 1); /* smallest blur allowed */ pix3 = pixRotateAM(pix2, angle, incolor); pix4 = pixUnsharpMasking(pix3, 1, 1.0); /* sharpen a bit */ pixd = pixThresholdToBinary(pix4, 128); pixDestroy(&pix1); pixDestroy(&pix2); pixDestroy(&pix3); pixDestroy(&pix4); return pixd; } /*------------------------------------------------------------------* * Rotation including alpha (blend) component * *------------------------------------------------------------------*/ /*! * \brief pixRotateWithAlpha() * * \param[in] pixs 32 bpp rgb or cmapped * \param[in] angle radians; clockwise is positive * \param[in] pixg [optional] 8 bpp, can be null * \param[in] 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.
 * 
*/ 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; }