Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/rotateam.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/rotateam.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1118 @@ +/*====================================================================* + - 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 rotateam.c + * <pre> + * + * Grayscale and color rotation for area mapping (== interpolation) + * + * Rotation about the image center + * PIX *pixRotateAM() + * PIX *pixRotateAMColor() + * PIX *pixRotateAMGray() + * static void rotateAMColorLow() + * static void rotateAMGrayLow() + * + * Rotation about the UL corner of the image + * PIX *pixRotateAMCorner() + * PIX *pixRotateAMColorCorner() + * PIX *pixRotateAMGrayCorner() + * static void rotateAMColorCornerLow() + * static void rotateAMGrayCornerLow() + * + * Faster color rotation about the image center + * PIX *pixRotateAMColorFast() + * static void rotateAMColorFastLow() + * + * Rotations are measured in radians; clockwise is positive. + * + * The basic area mapping grayscale rotation works on 8 bpp images. + * For color, the same method is applied to each color separately. + * This can be done in two ways: (1) as here, computing each dest + * rgb pixel from the appropriate four src rgb pixels, or (2) separating + * the color image into three 8 bpp images, rotate each of these, + * and then combine the result. Method (1) is about 2.5x faster. + * We have also implemented a fast approximation for color area-mapping + * rotation (pixRotateAMColorFast()), which is about 25% faster + * than the standard color rotator. If you need the extra speed, + * use it. + * + * Area mapping works as follows. For each dest + * pixel you find the 4 source pixels that it partially + * covers. You then compute the dest pixel value as + * the area-weighted average of those 4 source pixels. + * We make two simplifying approximations: + * + * ~ For simplicity, compute the areas as if the dest + * pixel were translated but not rotated. + * + * ~ Compute area overlaps on a discrete sub-pixel grid. + * Because we are using 8 bpp images with 256 levels, + * it is convenient to break each pixel into a + * 16x16 sub-pixel grid, and count the number of + * overlapped sub-pixels. + * + * It is interesting to note that the digital filter that + * implements the area mapping algorithm for rotation + * is identical to the digital filter used for linear + * interpolation when arbitrarily scaling grayscale images. + * + * The advantage of area mapping over pixel sampling + * in grayscale rotation is that the former naturally + * blurs sharp edges ("anti-aliasing"), so that stair-step + * artifacts are not introduced. The disadvantage is that + * it is significantly slower. + * + * But it is still pretty fast. With standard 3 GHz hardware, + * the anti-aliased (area-mapped) color rotation speed is + * about 15 million pixels/sec. + * + * The function pixRotateAMColorFast() is about 10-20% faster + * than pixRotateAMColor(). The quality is slightly worse, + * and if you make many successive small rotations, with a + * total angle of 360 degrees, it has been noted that the + * center wanders -- it seems to be doing a 1 pixel translation + * in addition to the rotation. + * + * Consider again the comparison of image quality between sampling + * and area mapping. With sampling, sharp edges such as found in + * text images remain sharp. However, sampling artifacts such as + * characters randomly bouncing up and down by one pixel, or + * one pixel horizontal shear lines going through a line of text + * (causing the characters to look like badly rendered italic), + * are highly visible. It does not help to sample the source pixel + * with the largest area covering each dest pixel; the result has + * the same ugly sampling artifacts. + * + * With area mapping, these annoying artifacts are avoided, but the + * blurring of edges makes small text a bit more difficult to read. + * However, if you are willing to do more computation, you can have + * the best of both worlds: no sampling artifacts and sharp edges. + * Use area mapping to avoid sampling issues, and follow it with + * unsharp masking. Experiment with the sharpening parameters. + * I have found that a small amount of sharpening is sufficient to + * restore the sharp edges in text; e.g., + * pix2 = pixUnsharpMasking(pix1, 1, 0.3); + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <math.h> /* required for sin and tan */ +#include "allheaders.h" + +static void rotateAMColorLow(l_uint32 *datad, l_int32 w, l_int32 h, + l_int32 wpld, l_uint32 *datas, l_int32 wpls, + l_float32 angle, l_uint32 colorval); +static void rotateAMGrayLow(l_uint32 *datad, l_int32 w, l_int32 h, + l_int32 wpld, l_uint32 *datas, l_int32 wpls, + l_float32 angle, l_uint8 grayval); +static void rotateAMColorCornerLow(l_uint32 *datad, l_int32 w, l_int32 h, + l_int32 wpld, l_uint32 *datas, + l_int32 wpls, l_float32 angle, + l_uint32 colorval); +static void rotateAMGrayCornerLow(l_uint32 *datad, l_int32 w, l_int32 h, + l_int32 wpld, l_uint32 *datas, l_int32 wpls, + l_float32 angle, l_uint8 grayval); + +static void rotateAMColorFastLow(l_uint32 *datad, l_int32 w, l_int32 h, + l_int32 wpld, l_uint32 *datas, l_int32 wpls, + l_float32 angle, l_uint32 colorval); + +static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */ + + +/*------------------------------------------------------------------* + * Rotation about the center * + *------------------------------------------------------------------*/ +/*! + * \brief pixRotateAM() + * + * \param[in] pixs 2, 4, 8 bpp gray or colormapped, or 32 bpp RGB + * \param[in] angle radians; clockwise is positive + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Rotates about image center. + * (2) A positive angle gives a clockwise rotation. + * (3) Brings in either black or white pixels from the boundary. + * </pre> + */ +PIX * +pixRotateAM(PIX *pixs, + l_float32 angle, + l_int32 incolor) +{ +l_int32 d; +l_uint32 fillval; +PIX *pixt1, *pixt2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) == 1) + return (PIX *)ERROR_PTR("pixs is 1 bpp", __func__, NULL); + + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + /* Remove cmap if it exists, and unpack to 8 bpp if necessary */ + pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); + d = pixGetDepth(pixt1); + if (d < 8) + pixt2 = pixConvertTo8(pixt1, FALSE); + else + pixt2 = pixClone(pixt1); + d = pixGetDepth(pixt2); + + /* Compute actual incoming color */ + fillval = 0; + if (incolor == L_BRING_IN_WHITE) { + if (d == 8) + fillval = 255; + else /* d == 32 */ + fillval = 0xffffff00; + } + + if (d == 8) + pixd = pixRotateAMGray(pixt2, angle, fillval); + else /* d == 32 */ + pixd = pixRotateAMColor(pixt2, angle, fillval); + + pixDestroy(&pixt1); + pixDestroy(&pixt2); + return pixd; +} + + +/*! + * \brief pixRotateAMColor() + * + * \param[in] pixs 32 bpp + * \param[in] angle radians; clockwise is positive + * \param[in] colorval e.g., 0 to bring in BLACK, 0xffffff00 for WHITE + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Rotates about image center. + * (2) A positive angle gives a clockwise rotation. + * (3) Specify the color to be brought in from outside the image. + * </pre> + */ +PIX * +pixRotateAMColor(PIX *pixs, + l_float32 angle, + l_uint32 colorval) +{ +l_int32 w, h, wpls, wpld; +l_uint32 *datas, *datad; +PIX *pix1, *pix2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs must be 32 bpp", __func__, NULL); + + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + pixGetDimensions(pixs, &w, &h, NULL); + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + pixd = pixCreateTemplate(pixs); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + + rotateAMColorLow(datad, w, h, wpld, datas, wpls, angle, colorval); + if (pixGetSpp(pixs) == 4) { + pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); + pix2 = pixRotateAMGray(pix1, angle, 255); /* bring in opaque */ + pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); + pixDestroy(&pix1); + pixDestroy(&pix2); + } + + return pixd; +} + + +/*! + * \brief pixRotateAMGray() + * + * \param[in] pixs 8 bpp + * \param[in] angle radians; clockwise is positive + * \param[in] grayval 0 to bring in BLACK, 255 for WHITE + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Rotates about image center. + * (2) A positive angle gives a clockwise rotation. + * (3) Specify the grayvalue to be brought in from outside the image. + * </pre> + */ +PIX * +pixRotateAMGray(PIX *pixs, + l_float32 angle, + l_uint8 grayval) +{ +l_int32 w, h, wpls, wpld; +l_uint32 *datas, *datad; +PIX *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 8) + return (PIX *)ERROR_PTR("pixs must be 8 bpp", __func__, NULL); + + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + pixGetDimensions(pixs, &w, &h, NULL); + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + pixd = pixCreateTemplate(pixs); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + + rotateAMGrayLow(datad, w, h, wpld, datas, wpls, angle, grayval); + + return pixd; +} + + +static void +rotateAMColorLow(l_uint32 *datad, + l_int32 w, + l_int32 h, + l_int32 wpld, + l_uint32 *datas, + l_int32 wpls, + l_float32 angle, + l_uint32 colorval) +{ +l_int32 i, j, xcen, ycen, wm2, hm2; +l_int32 xdif, ydif, xpm, ypm, xp, yp, xf, yf; +l_int32 rval, gval, bval; +l_uint32 word00, word01, word10, word11; +l_uint32 *lines, *lined; +l_float32 sina, cosa; + + xcen = w / 2; + wm2 = w - 2; + ycen = h / 2; + hm2 = h - 2; + sina = 16.f * sin(angle); + cosa = 16.f * cos(angle); + + for (i = 0; i < h; i++) { + ydif = ycen - i; + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + xdif = xcen - j; + xpm = (l_int32)(-xdif * cosa - ydif * sina); + ypm = (l_int32)(-ydif * cosa + xdif * sina); + xp = xcen + (xpm >> 4); + yp = ycen + (ypm >> 4); + xf = xpm & 0x0f; + yf = ypm & 0x0f; + + /* if off the edge, write input colorval */ + if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) { + *(lined + j) = colorval; + continue; + } + + lines = datas + yp * wpls; + + /* do area weighting. Without this, we would + * simply do: + * *(lined + j) = *(lines + xp); + * which is faster but gives lousy results! + */ + word00 = *(lines + xp); + word10 = *(lines + xp + 1); + word01 = *(lines + wpls + xp); + word11 = *(lines + wpls + xp + 1); + rval = ((16 - xf) * (16 - yf) * ((word00 >> L_RED_SHIFT) & 0xff) + + xf * (16 - yf) * ((word10 >> L_RED_SHIFT) & 0xff) + + (16 - xf) * yf * ((word01 >> L_RED_SHIFT) & 0xff) + + xf * yf * ((word11 >> L_RED_SHIFT) & 0xff) + 128) / 256; + gval = ((16 - xf) * (16 - yf) * ((word00 >> L_GREEN_SHIFT) & 0xff) + + xf * (16 - yf) * ((word10 >> L_GREEN_SHIFT) & 0xff) + + (16 - xf) * yf * ((word01 >> L_GREEN_SHIFT) & 0xff) + + xf * yf * ((word11 >> L_GREEN_SHIFT) & 0xff) + 128) / 256; + bval = ((16 - xf) * (16 - yf) * ((word00 >> L_BLUE_SHIFT) & 0xff) + + xf * (16 - yf) * ((word10 >> L_BLUE_SHIFT) & 0xff) + + (16 - xf) * yf * ((word01 >> L_BLUE_SHIFT) & 0xff) + + xf * yf * ((word11 >> L_BLUE_SHIFT) & 0xff) + 128) / 256; + composeRGBPixel(rval, gval, bval, lined + j); + } + } +} + + +static void +rotateAMGrayLow(l_uint32 *datad, + l_int32 w, + l_int32 h, + l_int32 wpld, + l_uint32 *datas, + l_int32 wpls, + l_float32 angle, + l_uint8 grayval) +{ +l_int32 i, j, xcen, ycen, wm2, hm2; +l_int32 xdif, ydif, xpm, ypm, xp, yp, xf, yf; +l_int32 v00, v01, v10, v11; +l_uint8 val; +l_uint32 *lines, *lined; +l_float32 sina, cosa; + + xcen = w / 2; + wm2 = w - 2; + ycen = h / 2; + hm2 = h - 2; + sina = 16.f * sin(angle); + cosa = 16.f * cos(angle); + + for (i = 0; i < h; i++) { + ydif = ycen - i; + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + xdif = xcen - j; + xpm = (l_int32)(-xdif * cosa - ydif * sina); + ypm = (l_int32)(-ydif * cosa + xdif * sina); + xp = xcen + (xpm >> 4); + yp = ycen + (ypm >> 4); + xf = xpm & 0x0f; + yf = ypm & 0x0f; + + /* if off the edge, write input grayval */ + if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) { + SET_DATA_BYTE(lined, j, grayval); + continue; + } + + lines = datas + yp * wpls; + + /* do area weighting. Without this, we would + * simply do: + * SET_DATA_BYTE(lined, j, GET_DATA_BYTE(lines, xp)); + * which is faster but gives lousy results! + */ + v00 = (16 - xf) * (16 - yf) * GET_DATA_BYTE(lines, xp); + v10 = xf * (16 - yf) * GET_DATA_BYTE(lines, xp + 1); + v01 = (16 - xf) * yf * GET_DATA_BYTE(lines + wpls, xp); + v11 = xf * yf * GET_DATA_BYTE(lines + wpls, xp + 1); + val = (l_uint8)((v00 + v01 + v10 + v11 + 128) / 256); + SET_DATA_BYTE(lined, j, val); + } + } +} + + +/*------------------------------------------------------------------* + * Rotation about the UL corner * + *------------------------------------------------------------------*/ +/*! + * \brief pixRotateAMCorner() + * + * \param[in] pixs 1, 2, 4, 8 bpp gray or colormapped, or 32 bpp RGB + * \param[in] angle radians; clockwise is positive + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Rotates about the UL corner of the image. + * (2) A positive angle gives a clockwise rotation. + * (3) Brings in either black or white pixels from the boundary. + * </pre> + */ +PIX * +pixRotateAMCorner(PIX *pixs, + l_float32 angle, + l_int32 incolor) +{ +l_int32 d; +l_uint32 fillval; +PIX *pixt1, *pixt2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + /* Remove cmap if it exists, and unpack to 8 bpp if necessary */ + pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); + d = pixGetDepth(pixt1); + if (d < 8) + pixt2 = pixConvertTo8(pixt1, FALSE); + else + pixt2 = pixClone(pixt1); + d = pixGetDepth(pixt2); + + /* Compute actual incoming color */ + fillval = 0; + if (incolor == L_BRING_IN_WHITE) { + if (d == 8) + fillval = 255; + else /* d == 32 */ + fillval = 0xffffff00; + } + + if (d == 8) + pixd = pixRotateAMGrayCorner(pixt2, angle, fillval); + else /* d == 32 */ + pixd = pixRotateAMColorCorner(pixt2, angle, fillval); + + pixDestroy(&pixt1); + pixDestroy(&pixt2); + return pixd; +} + + +/*! + * \brief pixRotateAMColorCorner() + * + * \param[in] pixs + * \param[in] angle radians; clockwise is positive + * \param[in] fillval e.g., 0 to bring in BLACK, 0xffffff00 for WHITE + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Rotates the image about the UL corner. + * (2) A positive angle gives a clockwise rotation. + * (3) Specify the color to be brought in from outside the image. + * </pre> + */ +PIX * +pixRotateAMColorCorner(PIX *pixs, + l_float32 angle, + l_uint32 fillval) +{ +l_int32 w, h, wpls, wpld; +l_uint32 *datas, *datad; +PIX *pix1, *pix2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs must be 32 bpp", __func__, NULL); + + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + pixGetDimensions(pixs, &w, &h, NULL); + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + pixd = pixCreateTemplate(pixs); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + + rotateAMColorCornerLow(datad, w, h, wpld, datas, wpls, angle, fillval); + if (pixGetSpp(pixs) == 4) { + pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); + pix2 = pixRotateAMGrayCorner(pix1, angle, 255); /* bring in opaque */ + pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); + pixDestroy(&pix1); + pixDestroy(&pix2); + } + + return pixd; +} + + +/*! + * \brief pixRotateAMGrayCorner() + * + * \param[in] pixs + * \param[in] angle radians; clockwise is positive + * \param[in] grayval 0 to bring in BLACK, 255 for WHITE + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Rotates the image about the UL corner. + * (2) A positive angle gives a clockwise rotation. + * (3) Specify the grayvalue to be brought in from outside the image. + * </pre> + */ +PIX * +pixRotateAMGrayCorner(PIX *pixs, + l_float32 angle, + l_uint8 grayval) +{ +l_int32 w, h, wpls, wpld; +l_uint32 *datas, *datad; +PIX *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 8) + return (PIX *)ERROR_PTR("pixs must be 8 bpp", __func__, NULL); + + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + pixGetDimensions(pixs, &w, &h, NULL); + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + pixd = pixCreateTemplate(pixs); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + + rotateAMGrayCornerLow(datad, w, h, wpld, datas, wpls, angle, grayval); + + return pixd; +} + + +static void +rotateAMColorCornerLow(l_uint32 *datad, + l_int32 w, + l_int32 h, + l_int32 wpld, + l_uint32 *datas, + l_int32 wpls, + l_float32 angle, + l_uint32 colorval) +{ +l_int32 i, j, wm2, hm2; +l_int32 xpm, ypm, xp, yp, xf, yf; +l_int32 rval, gval, bval; +l_uint32 word00, word01, word10, word11; +l_uint32 *lines, *lined; +l_float32 sina, cosa; + + wm2 = w - 2; + hm2 = h - 2; + sina = 16.f * sin(angle); + cosa = 16.f * cos(angle); + + for (i = 0; i < h; i++) { + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + xpm = (l_int32)(j * cosa + i * sina); + ypm = (l_int32)(i * cosa - j * sina); + xp = xpm >> 4; + yp = ypm >> 4; + xf = xpm & 0x0f; + yf = ypm & 0x0f; + + /* if off the edge, write input colorval */ + if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) { + *(lined + j) = colorval; + continue; + } + + lines = datas + yp * wpls; + + /* do area weighting. Without this, we would + * simply do: + * *(lined + j) = *(lines + xp); + * which is faster but gives lousy results! + */ + word00 = *(lines + xp); + word10 = *(lines + xp + 1); + word01 = *(lines + wpls + xp); + word11 = *(lines + wpls + xp + 1); + rval = ((16 - xf) * (16 - yf) * ((word00 >> L_RED_SHIFT) & 0xff) + + xf * (16 - yf) * ((word10 >> L_RED_SHIFT) & 0xff) + + (16 - xf) * yf * ((word01 >> L_RED_SHIFT) & 0xff) + + xf * yf * ((word11 >> L_RED_SHIFT) & 0xff) + 128) / 256; + gval = ((16 - xf) * (16 - yf) * ((word00 >> L_GREEN_SHIFT) & 0xff) + + xf * (16 - yf) * ((word10 >> L_GREEN_SHIFT) & 0xff) + + (16 - xf) * yf * ((word01 >> L_GREEN_SHIFT) & 0xff) + + xf * yf * ((word11 >> L_GREEN_SHIFT) & 0xff) + 128) / 256; + bval = ((16 - xf) * (16 - yf) * ((word00 >> L_BLUE_SHIFT) & 0xff) + + xf * (16 - yf) * ((word10 >> L_BLUE_SHIFT) & 0xff) + + (16 - xf) * yf * ((word01 >> L_BLUE_SHIFT) & 0xff) + + xf * yf * ((word11 >> L_BLUE_SHIFT) & 0xff) + 128) / 256; + composeRGBPixel(rval, gval, bval, lined + j); + } + } +} + + +static void +rotateAMGrayCornerLow(l_uint32 *datad, + l_int32 w, + l_int32 h, + l_int32 wpld, + l_uint32 *datas, + l_int32 wpls, + l_float32 angle, + l_uint8 grayval) +{ +l_int32 i, j, wm2, hm2; +l_int32 xpm, ypm, xp, yp, xf, yf; +l_int32 v00, v01, v10, v11; +l_uint8 val; +l_uint32 *lines, *lined; +l_float32 sina, cosa; + + wm2 = w - 2; + hm2 = h - 2; + sina = 16.f * sin(angle); + cosa = 16.f * cos(angle); + + for (i = 0; i < h; i++) { + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + xpm = (l_int32)(j * cosa + i * sina); + ypm = (l_int32)(i * cosa - j * sina); + xp = xpm >> 4; + yp = ypm >> 4; + xf = xpm & 0x0f; + yf = ypm & 0x0f; + + /* if off the edge, write input grayval */ + if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) { + SET_DATA_BYTE(lined, j, grayval); + continue; + } + + lines = datas + yp * wpls; + + /* do area weighting. Without this, we would + * simply do: + * SET_DATA_BYTE(lined, j, GET_DATA_BYTE(lines, xp)); + * which is faster but gives lousy results! + */ + v00 = (16 - xf) * (16 - yf) * GET_DATA_BYTE(lines, xp); + v10 = xf * (16 - yf) * GET_DATA_BYTE(lines, xp + 1); + v01 = (16 - xf) * yf * GET_DATA_BYTE(lines + wpls, xp); + v11 = xf * yf * GET_DATA_BYTE(lines + wpls, xp + 1); + val = (l_uint8)((v00 + v01 + v10 + v11 + 128) / 256); + SET_DATA_BYTE(lined, j, val); + } + } +} + + +/*------------------------------------------------------------------* + * Fast RGB color rotation about center * + *------------------------------------------------------------------*/ +/*! + * \brief pixRotateAMColorFast() + * + * \param[in] pixs + * \param[in] angle radians; clockwise is positive + * \param[in] colorval e.g., 0 to bring in BLACK, 0xffffff00 for WHITE + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) This rotates a color image about the image center. + * (2) A positive angle gives a clockwise rotation. + * (3) It uses area mapping, dividing each pixel into + * 16 subpixels. + * (4) It is about 10% to 20% faster than the more accurate linear + * interpolation function pixRotateAMColor(), + * which uses 256 subpixels. + * (5) For some reason it shifts the image center. + * No attempt is made to rotate the alpha component. + * </pre> + */ +PIX * +pixRotateAMColorFast(PIX *pixs, + l_float32 angle, + l_uint32 colorval) +{ +l_int32 w, h, wpls, wpld; +l_uint32 *datas, *datad; +PIX *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs must be 32 bpp", __func__, NULL); + + if (L_ABS(angle) < MinAngleToRotate) + return pixClone(pixs); + + pixGetDimensions(pixs, &w, &h, NULL); + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + pixd = pixCreateTemplate(pixs); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + + rotateAMColorFastLow(datad, w, h, wpld, datas, wpls, angle, colorval); + return pixd; +} + + +/*! + * \brief rotateAMColorFastLow() + * + * This is a special simplification of area mapping with division + * of each pixel into 16 sub-pixels. The exact coefficients that + * should be used are the same as for the 4x linear interpolation + * scaling case, and are given there. I tried to approximate these + * as weighted coefficients with a maximum sum of 4, which + * allows us to do the arithmetic in parallel for the R, G and B + * components in a 32 bit pixel. However, there are three reasons + * for not doing that: + * (1) the loss of accuracy in the parallel implementation + * is visually significant + * (2) the parallel implementation (described below) is slower + * (3) the parallel implementation requires allocation of + * a temporary color image + * + * There are 16 cases for the choice of the subpixel, and + * for each, the mapping to the relevant source + * pixels is as follows: + * + * subpixel src pixel weights + * -------- ----------------- + * 0 sp1 + * 1 (3 * sp1 + sp2) / 4 + * 2 (sp1 + sp2) / 2 + * 3 (sp1 + 3 * sp2) / 4 + * 4 (3 * sp1 + sp3) / 4 + * 5 (9 * sp1 + 3 * sp2 + 3 * sp3 + sp4) / 16 + * 6 (3 * sp1 + 3 * sp2 + sp3 + sp4) / 8 + * 7 (3 * sp1 + 9 * sp2 + sp3 + 3 * sp4) / 16 + * 8 (sp1 + sp3) / 2 + * 9 (3 * sp1 + sp2 + 3 * sp3 + sp4) / 8 + * 10 (sp1 + sp2 + sp3 + sp4) / 4 + * 11 (sp1 + 3 * sp2 + sp3 + 3 * sp4) / 8 + * 12 (sp1 + 3 * sp3) / 4 + * 13 (3 * sp1 + sp2 + 9 * sp3 + 3 * sp4) / 16 + * 14 (sp1 + sp2 + 3 * sp3 + 3 * sp4) / 8 + * 15 (sp1 + 3 * sp2 + 3 * sp3 + 9 * sp4) / 16 + * + * Another way to visualize this is to consider the area mapping + * (or linear interpolation) coefficients for the pixel sp1. + * Expressed in fourths, they can be written as asymmetric matrix: + * + * 4 3 2 1 + * 3 2.25 1.5 0.75 + * 2 1.5 1 0.5 + * 1 0.75 0.5 0.25 + * + * The coefficients for the three neighboring pixels can be + * similarly written. + * + * This is implemented here, where, for each color component, + * we inline its extraction from each participating word, + * construct the linear combination, and combine the results + * into the destination 32 bit RGB pixel, using the appropriate shifts. + * + * It is interesting to note that an alternative method, where + * we do the arithmetic on the 32 bit pixels directly (after + * shifting the components so they won't overflow into each other) + * is significantly inferior. Because we have only 8 bits for + * internal overflows, which can be distributed as 2, 3, 3, it + * is impossible to add these with the correct linear + * interpolation coefficients, which require a sum of up to 16. + * Rounding off to a sum of 4 causes appreciable visual artifacts + * in the rotated image. The code for the inferior method + * can be found in prog/rotatefastalt.c, for reference. + */ +static void +rotateAMColorFastLow(l_uint32 *datad, + l_int32 w, + l_int32 h, + l_int32 wpld, + l_uint32 *datas, + l_int32 wpls, + l_float32 angle, + l_uint32 colorval) +{ +l_int32 i, j, xcen, ycen, wm2, hm2; +l_int32 xdif, ydif, xpm, ypm, xp, yp, xf, yf; +l_uint32 word1, word2, word3, word4, red, blue, green; +l_uint32 *pword, *lines, *lined; +l_float32 sina, cosa; + + xcen = w / 2; + wm2 = w - 2; + ycen = h / 2; + hm2 = h - 2; + sina = 4.f * sin(angle); + cosa = 4.f * cos(angle); + + for (i = 0; i < h; i++) { + ydif = ycen - i; + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + xdif = xcen - j; + xpm = (l_int32)(-xdif * cosa - ydif * sina); + ypm = (l_int32)(-ydif * cosa + xdif * sina); + xp = xcen + (xpm >> 2); + yp = ycen + (ypm >> 2); + xf = xpm & 0x03; + yf = ypm & 0x03; + + /* if off the edge, write input grayval */ + if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) { + *(lined + j) = colorval; + continue; + } + + lines = datas + yp * wpls; + pword = lines + xp; + + switch (xf + 4 * yf) + { + case 0: + *(lined + j) = *pword; + break; + case 1: + word1 = *pword; + word2 = *(pword + 1); + red = 3 * (word1 >> 24) + (word2 >> 24); + green = 3 * ((word1 >> 16) & 0xff) + + ((word2 >> 16) & 0xff); + blue = 3 * ((word1 >> 8) & 0xff) + + ((word2 >> 8) & 0xff); + *(lined + j) = ((red << 22) & 0xff000000) | + ((green << 14) & 0x00ff0000) | + ((blue << 6) & 0x0000ff00); + break; + case 2: + word1 = *pword; + word2 = *(pword + 1); + red = (word1 >> 24) + (word2 >> 24); + green = ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff); + *(lined + j) = ((red << 23) & 0xff000000) | + ((green << 15) & 0x00ff0000) | + ((blue << 7) & 0x0000ff00); + break; + case 3: + word1 = *pword; + word2 = *(pword + 1); + red = (word1 >> 24) + 3 * (word2 >> 24); + green = ((word1 >> 16) & 0xff) + + 3 * ((word2 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + + 3 * ((word2 >> 8) & 0xff); + *(lined + j) = ((red << 22) & 0xff000000) | + ((green << 14) & 0x00ff0000) | + ((blue << 6) & 0x0000ff00); + break; + case 4: + word1 = *pword; + word3 = *(pword + wpls); + red = 3 * (word1 >> 24) + (word3 >> 24); + green = 3 * ((word1 >> 16) & 0xff) + + ((word3 >> 16) & 0xff); + blue = 3 * ((word1 >> 8) & 0xff) + + ((word3 >> 8) & 0xff); + *(lined + j) = ((red << 22) & 0xff000000) | + ((green << 14) & 0x00ff0000) | + ((blue << 6) & 0x0000ff00); + break; + case 5: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = 9 * (word1 >> 24) + 3 * (word2 >> 24) + + 3 * (word3 >> 24) + (word4 >> 24); + green = 9 * ((word1 >> 16) & 0xff) + + 3 * ((word2 >> 16) & 0xff) + + 3 * ((word3 >> 16) & 0xff) + + ((word4 >> 16) & 0xff); + blue = 9 * ((word1 >> 8) & 0xff) + + 3 * ((word2 >> 8) & 0xff) + + 3 * ((word3 >> 8) & 0xff) + + ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 20) & 0xff000000) | + ((green << 12) & 0x00ff0000) | + ((blue << 4) & 0x0000ff00); + break; + case 6: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = 3 * (word1 >> 24) + 3 * (word2 >> 24) + + (word3 >> 24) + (word4 >> 24); + green = 3 * ((word1 >> 16) & 0xff) + + 3 * ((word2 >> 16) & 0xff) + + ((word3 >> 16) & 0xff) + + ((word4 >> 16) & 0xff); + blue = 3 * ((word1 >> 8) & 0xff) + + 3 * ((word2 >> 8) & 0xff) + + ((word3 >> 8) & 0xff) + + ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 21) & 0xff000000) | + ((green << 13) & 0x00ff0000) | + ((blue << 5) & 0x0000ff00); + break; + case 7: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = 3 * (word1 >> 24) + 9 * (word2 >> 24) + + (word3 >> 24) + 3 * (word4 >> 24); + green = 3 * ((word1 >> 16) & 0xff) + + 9 * ((word2 >> 16) & 0xff) + + ((word3 >> 16) & 0xff) + + 3 * ((word4 >> 16) & 0xff); + blue = 3 * ((word1 >> 8) & 0xff) + + 9 * ((word2 >> 8) & 0xff) + + ((word3 >> 8) & 0xff) + + 3 * ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 20) & 0xff000000) | + ((green << 12) & 0x00ff0000) | + ((blue << 4) & 0x0000ff00); + break; + case 8: + word1 = *pword; + word3 = *(pword + wpls); + red = (word1 >> 24) + (word3 >> 24); + green = ((word1 >> 16) & 0xff) + ((word3 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + ((word3 >> 8) & 0xff); + *(lined + j) = ((red << 23) & 0xff000000) | + ((green << 15) & 0x00ff0000) | + ((blue << 7) & 0x0000ff00); + break; + case 9: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = 3 * (word1 >> 24) + (word2 >> 24) + + 3 * (word3 >> 24) + (word4 >> 24); + green = 3 * ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff) + + 3 * ((word3 >> 16) & 0xff) + ((word4 >> 16) & 0xff); + blue = 3 * ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) + + 3 * ((word3 >> 8) & 0xff) + ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 21) & 0xff000000) | + ((green << 13) & 0x00ff0000) | + ((blue << 5) & 0x0000ff00); + break; + case 10: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = (word1 >> 24) + (word2 >> 24) + + (word3 >> 24) + (word4 >> 24); + green = ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff) + + ((word3 >> 16) & 0xff) + ((word4 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) + + ((word3 >> 8) & 0xff) + ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 22) & 0xff000000) | + ((green << 14) & 0x00ff0000) | + ((blue << 6) & 0x0000ff00); + break; + case 11: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = (word1 >> 24) + 3 * (word2 >> 24) + + (word3 >> 24) + 3 * (word4 >> 24); + green = ((word1 >> 16) & 0xff) + 3 * ((word2 >> 16) & 0xff) + + ((word3 >> 16) & 0xff) + 3 * ((word4 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + 3 * ((word2 >> 8) & 0xff) + + ((word3 >> 8) & 0xff) + 3 * ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 21) & 0xff000000) | + ((green << 13) & 0x00ff0000) | + ((blue << 5) & 0x0000ff00); + break; + case 12: + word1 = *pword; + word3 = *(pword + wpls); + red = (word1 >> 24) + 3 * (word3 >> 24); + green = ((word1 >> 16) & 0xff) + + 3 * ((word3 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + + 3 * ((word3 >> 8) & 0xff); + *(lined + j) = ((red << 22) & 0xff000000) | + ((green << 14) & 0x00ff0000) | + ((blue << 6) & 0x0000ff00); + break; + case 13: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = 3 * (word1 >> 24) + (word2 >> 24) + + 9 * (word3 >> 24) + 3 * (word4 >> 24); + green = 3 * ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff) + + 9 * ((word3 >> 16) & 0xff) + 3 * ((word4 >> 16) & 0xff); + blue = 3 *((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) + + 9 * ((word3 >> 8) & 0xff) + 3 * ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 20) & 0xff000000) | + ((green << 12) & 0x00ff0000) | + ((blue << 4) & 0x0000ff00); + break; + case 14: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = (word1 >> 24) + (word2 >> 24) + + 3 * (word3 >> 24) + 3 * (word4 >> 24); + green = ((word1 >> 16) & 0xff) +((word2 >> 16) & 0xff) + + 3 * ((word3 >> 16) & 0xff) + 3 * ((word4 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) + + 3 * ((word3 >> 8) & 0xff) + 3 * ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 21) & 0xff000000) | + ((green << 13) & 0x00ff0000) | + ((blue << 5) & 0x0000ff00); + break; + case 15: + word1 = *pword; + word2 = *(pword + 1); + word3 = *(pword + wpls); + word4 = *(pword + wpls + 1); + red = (word1 >> 24) + 3 * (word2 >> 24) + + 3 * (word3 >> 24) + 9 * (word4 >> 24); + green = ((word1 >> 16) & 0xff) + 3 * ((word2 >> 16) & 0xff) + + 3 * ((word3 >> 16) & 0xff) + 9 * ((word4 >> 16) & 0xff); + blue = ((word1 >> 8) & 0xff) + 3 * ((word2 >> 8) & 0xff) + + 3 * ((word3 >> 8) & 0xff) + 9 * ((word4 >> 8) & 0xff); + *(lined + j) = ((red << 20) & 0xff000000) | + ((green << 12) & 0x00ff0000) | + ((blue << 4) & 0x0000ff00); + break; + default: + lept_stderr("shouldn't get here\n"); + break; + } + } + } +}
