Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/rotateshear.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/thirdparty/leptonica/src/rotateshear.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,486 @@ +/*====================================================================* + - 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 rotateshear.c + * <pre> + * + * Shear rotation about arbitrary point using 2 and 3 shears + * + * PIX *pixRotateShear() + * PIX *pixRotate2Shear() + * PIX *pixRotate3Shear() + * + * Shear rotation in-place about arbitrary point using 3 shears + * l_int32 pixRotateShearIP() + * + * Shear rotation around the image center + * PIX *pixRotateShearCenter() (2 or 3 shears) + * l_int32 pixRotateShearCenterIP() (3 shears) + * + * Rotation is measured in radians; clockwise rotations are positive. + * + * Rotation by shear works on images of any depth, + * including 8 bpp color paletted images and 32 bpp + * rgb images. It works by translating each src pixel + * value to the appropriate pixel in the rotated dest. + * For 8 bpp grayscale images, it is about 10-15x faster + * than rotation by area-mapping. + * + * This speed and flexibility comes at the following cost, + * relative to area-mapped rotation: + * + * ~ Jaggies are created on edges of straight lines + * + * ~ For large angles, where you must use 3 shears, + * there is some extra clipping from the shears. + * + * For small angles, typically less than 0.05 radians, + * rotation can be done with 2 orthogonal shears. + * Two such continuous shears (as opposed to the discrete + * shears on a pixel lattice that we have here) give + * a rotated image that has a distortion in the lengths + * of the two rotated and still-perpendicular axes. The + * length/width ratio changes by a fraction + * + * 0.5 * (angle)**2 + * + * For an angle of 0.05 radians, this is about 1 part in + * a thousand. This distortion is absent when you use + * 3 continuous shears with the correct angles (see below). + * + * Of course, the image is on a discrete pixel lattice. + * Rotation by shear gives an approximation to a continuous + * rotation, leaving pixel jaggies at sharp boundaries. + * For very small rotations, rotating from a corner gives + * better sensitivity than rotating from the image center. + * Here's why. Define the shear "center" to be the line such + * that the image is sheared in opposite directions on + * each side of and parallel to the line. For small + * rotations there is a "dead space" on each side of the + * shear center of width equal to half the shear angle, + * in radians. Thus, when the image is sheared about the center, + * the dead space width equals the shear angle, but when + * the image is sheared from a corner, the dead space + * width is only half the shear angle. + * + * All horizontal and vertical shears are implemented by + * rasterop. The in-place rotation uses special in-place + * shears that copy rows sideways or columns vertically + * without buffering, and then rewrite old pixels that are + * no longer covered by sheared pixels. For that rewriting, + * you have the choice of using white or black pixels. + * When not in-place, the new pix is initialized with white or black + * pixels by pixSetBlackOrWhite(), which also works for cmapped pix. + * But for in-place, this initialization is not possible, so + * in-place shear operations on cmapped pix are not allowed. + * + * Rotation by shear is fast and depth-independent. However, it + * does not work well for large rotation angles. In fact, for + * rotation angles greater than about 7 degrees, more pixels are + * lost at the edges than when using pixRotationBySampling(), which + * only loses pixels because they are rotated out of the image. + * For larger rotations, use pixRotationBySampling() or, for + * more accuracy when d > 1 bpp, pixRotateAM(). + * + * For small angles, when comparing the quality of rotation by + * sampling and by shear, you can see that rotation by sampling + * is slightly more accurate. However, the difference in + * accuracy of rotation by sampling when compared to 3-shear and + * (for angles less than 2 degrees, when compared to 2-shear) is + * less than 1 pixel at any point. For very small angles, rotation by + * sampling is much slower than rotation by shear. The speed difference + * depends on the pixel depth and the rotation angle. Rotation + * by shear is very fast for small angles and for small depth (esp. 1 bpp). + * Rotation by sampling speed is independent of angle and relatively + * more efficient for 8 and 32 bpp images. Here are some timings + * for the ratio of rotation times: (time for sampling)/ (time for shear) + * + * depth (bpp) ratio (2 deg) ratio (10 deg) + * ----------------------------------------------------- + * 1 25 6 + * 8 5 2.6 + * 32 1.6 1.0 + * + * In summary: + * * For d == 1 and small angles, use rotation by shear. By default + * this will use 2-shear rotations, because 3-shears cause more + * visible artifacts in straight lines and, for small angles, the + * distortion in asperity ratio is small. + * * For d > 1, shear is faster than sampling, which is faster than + * area mapping. However, area mapping gives the best results. + * These results are used in selecting the rotation methods in + * pixRotateShear(). + * + * There has been some work on what is called a "quasishear + * rotation" ("The Quasi-Shear Rotation, Eric Andres, + * DGCI 1996, pp. 307-314). I believe they use a 3-shear + * approximation to the continuous rotation, exactly as + * we do here. The approximation is due to being on + * a square pixel lattice. They also use integers to specify + * the rotation angle and center offset, but that makes + * little sense on a machine where you have a few GFLOPS + * and only a few hundred floating point operations to do (!) + * They also allow subpixel specification of the center of + * rotation, which I haven't bothered with, and claim that + * better results are possible if each of the 4 quadrants is + * handled separately. + * + * But the bottom line is that you are going to see shear lines when + * you rotate 1 bpp images. Although the 3-shear rotation is + * mathematically exact in the limit of infinitesimal pixels, artifacts + * will be evident in real images. One might imagine using dithering + * to break up the horizontal and vertical shear lines, but this + * is hard with block shears, where you need to dither on the block + * boundaries. Dithering (by accumulation of 'error') with sampling + * makes more sense, but I haven't tried to do this. There is only + * so much you can do with 1 bpp images! + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <math.h> +#include <string.h> +#include "allheaders.h" + + /* Angle limits: + * angle < MinAngleToRotate ==> clone + * angle > MaxTwoShearAngle ==> warning for 2-angle shears + * angle > MaxThreeShearAngle ==> warning for 3-angle shears + * angle > MaxShearAngle ==> error + */ +static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */ +static const l_float32 MaxTwoShearAngle = 0.06f; /* radians; ~3 deg */ +static const l_float32 MaxThreeShearAngle = 0.35f; /* radians; ~20 deg */ +static const l_float32 MaxShearAngle = 0.50f; /* radians; ~29 deg */ + +/*------------------------------------------------------------------* + * Rotations about an arbitrary point * + *------------------------------------------------------------------*/ +/*! + * \brief pixRotateShear() + * + * \param[in] pixs any depth; cmap ok + * \param[in] xcen x value for which there is no horizontal shear + * \param[in] ycen y value for which there is no vertical shear + * \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 an image about the given point, using + * either 2 or 3 shears. + * (2) A positive angle gives a clockwise rotation. + * (3) This brings in 'incolor' pixels from outside the image. + * (4) For rotation angles larger than about 0.35 radians, we issue + * a warning because you should probably be using another method + * (either sampling or area mapping) + * </pre> + */ +PIX * +pixRotateShear(PIX *pixs, + l_int32 xcen, + l_int32 ycen, + l_float32 angle, + l_int32 incolor) +{ + if (!pixs) + return (PIX *)(PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) + return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", __func__, NULL); + + if (L_ABS(angle) > MaxShearAngle) { + L_ERROR("%6.2f radians; too large for shear rotation\n", __func__, + L_ABS(angle)); + return NULL; + } + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + if (L_ABS(angle) <= MaxTwoShearAngle) + return pixRotate2Shear(pixs, xcen, ycen, angle, incolor); + else + return pixRotate3Shear(pixs, xcen, ycen, angle, incolor); +} + + +/*! + * \brief pixRotate2Shear() + * + * \param[in] pixs any depth; cmap ok + * \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 2-shear + * method. It should only be used for angles no larger than + * MaxTwoShearAngle. For larger angles, a warning is issued. + * (2) A positive angle gives a clockwise rotation. + * (3) 2-shear rotation by a specified angle is equivalent + * to the sequential transformations + * x' = x + tan(angle) * (y - ycen) for x-shear + * y' = y + tan(angle) * (x - xcen) for y-shear + * (4) Computation of tan(angle) is performed within the shear operation. + * (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. + * </pre> + */ +PIX * +pixRotate2Shear(PIX *pixs, + l_int32 xcen, + l_int32 ycen, + l_float32 angle, + l_int32 incolor) +{ +PIX *pix1, *pix2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) + return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", __func__, NULL); + + if (L_ABS(angle) > MaxShearAngle) { + L_ERROR("%6.2f radians; too large for shear rotation\n", __func__, + L_ABS(angle)); + return NULL; + } + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + if (L_ABS(angle) > MaxTwoShearAngle) + L_WARNING("%6.2f radians; large angle for 2-shear rotation\n", + __func__, L_ABS(angle)); + + if ((pix1 = pixHShear(NULL, pixs, ycen, angle, incolor)) == NULL) + return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL); + pixd = pixVShear(NULL, pix1, xcen, angle, incolor); + pixDestroy(&pix1); + if (!pixd) + return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); + + 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 = pixRotate2Shear(pix1, xcen, ycen, angle, L_BRING_IN_WHITE); + pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); + pixDestroy(&pix1); + pixDestroy(&pix2); + } + return pixd; +} + + +/*! + * \brief pixRotate3Shear() + * + * \param[in] pixs any depth; cmap ok + * \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 + * MaxThreeShearAngle. 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; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) + return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", __func__, NULL); + + if (L_ABS(angle) > MaxShearAngle) { + L_ERROR("%6.2f radians; too large for shear rotation\n", __func__, + L_ABS(angle)); + return NULL; + } + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + if (L_ABS(angle) > MaxThreeShearAngle) { + L_WARNING("%6.2f radians; large angle for 3-shear rotation\n", + __func__, L_ABS(angle)); + } + + hangle = atan(sin(angle)); + if ((pixd = pixVShear(NULL, pixs, xcen, angle / 2.f, incolor)) == NULL) + return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); + if ((pix1 = pixHShear(NULL, pixd, ycen, hangle, incolor)) == NULL) { + pixDestroy(&pixd); + return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL); + } + pixVShear(pixd, pix1, xcen, angle / 2.f, 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; +} + + +/*------------------------------------------------------------------* + * Rotations in-place about an arbitrary point * + *------------------------------------------------------------------*/ +/*! + * \brief pixRotateShearIP() + * + * \param[in] pixs any depth; no cmap + * \param[in] xcen, ycen center of rotation + * \param[in] angle radians + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This does an in-place rotation of the image about the + * specified point, using the 3-shear method. It should only + * be used for angles smaller than MaxThreeShearAngle. + * 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) The pix cannot be colormapped, because the in-place operation + * only blits in 0 or 1 bits, not an arbitrary colormap index. + * </pre> + */ +l_ok +pixRotateShearIP(PIX *pixs, + l_int32 xcen, + l_int32 ycen, + l_float32 angle, + l_int32 incolor) +{ +l_float32 hangle; + + if (!pixs) + return ERROR_INT("pixs not defined", __func__, 1); + if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) + return ERROR_INT("invalid value for incolor", __func__, 1); + if (pixGetColormap(pixs) != NULL) + return ERROR_INT("pixs is colormapped", __func__, 1); + + if (angle == 0.0) + return 0; + if (L_ABS(angle) > MaxThreeShearAngle) { + L_WARNING("%6.2f radians; large angle for in-place 3-shear rotation\n", + __func__, L_ABS(angle)); + } + + hangle = atan(sin(angle)); + pixHShearIP(pixs, ycen, angle / 2.f, incolor); + pixVShearIP(pixs, xcen, hangle, incolor); + pixHShearIP(pixs, ycen, angle / 2.f, incolor); + return 0; +} + + +/*------------------------------------------------------------------* + * Rotations about the image center * + *------------------------------------------------------------------*/ +/*! + * \brief pixRotateShearCenter() + * + * \param[in] pixs any depth; cmap ok + * \param[in] angle radians + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return pixd, or NULL on error + */ +PIX * +pixRotateShearCenter(PIX *pixs, + l_float32 angle, + l_int32 incolor) +{ + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + + return pixRotateShear(pixs, pixGetWidth(pixs) / 2, + pixGetHeight(pixs) / 2, angle, incolor); +} + + +/*! + * \brief pixRotateShearCenterIP() + * + * \param[in] pixs any depth; no cmap + * \param[in] angle radians + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return 0 if OK, 1 on error + */ +l_ok +pixRotateShearCenterIP(PIX *pixs, + l_float32 angle, + l_int32 incolor) +{ + if (!pixs) + return ERROR_INT("pixs not defined", __func__, 1); + + return pixRotateShearIP(pixs, pixGetWidth(pixs) / 2, + pixGetHeight(pixs) / 2, angle, incolor); +}
