Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/affine.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/affine.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1587 @@ +/*====================================================================* + - 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 affine.c + * <pre> + * + * Affine (3 pt) image transformation using a sampled + * (to nearest integer) transform on each dest point + * PIX *pixAffineSampledPta() + * PIX *pixAffineSampled() + * + * Affine (3 pt) image transformation using interpolation + * (or area mapping) for anti-aliasing images that are + * 2, 4, or 8 bpp gray, or colormapped, or 32 bpp RGB + * PIX *pixAffinePta() + * PIX *pixAffine() + * PIX *pixAffinePtaColor() + * PIX *pixAffineColor() + * PIX *pixAffinePtaGray() + * PIX *pixAffineGray() + * + * Affine transform including alpha (blend) component + * PIX *pixAffinePtaWithAlpha() + * + * Affine coordinate transformation + * l_int32 getAffineXformCoeffs() + * l_int32 affineInvertXform() + * l_int32 affineXformSampledPt() + * l_int32 affineXformPt() + * + * Interpolation helper functions + * l_int32 linearInterpolatePixelGray() + * l_int32 linearInterpolatePixelColor() + * + * Gauss-jordan linear equation solver + * l_int32 gaussjordan() + * + * Affine image transformation using a sequence of + * shear/scale/translation operations + * PIX *pixAffineSequential() + * + * One can define a coordinate space by the location of the origin, + * the orientation of x and y axes, and the unit scaling along + * each axis. An affine transform is a general linear + * transformation from one coordinate space to another. + * + * For the general case, we can define the affine transform using + * two sets of three (noncollinear) points in a plane. One set + * corresponds to the input (src) coordinate space; the other to the + * transformed (dest) coordinate space. Each point in the + * src corresponds to one of the points in the dest. With two + * sets of three points, we get a set of 6 equations in 6 unknowns + * that specifies the mapping between the coordinate spaces. + * The interface here allows you to specify either the corresponding + * sets of 3 points, or the transform itself (as a vector of 6 + * coefficients). + * + * Given the transform as a vector of 6 coefficients, we can compute + * both a a pointwise affine coordinate transformation and an + * affine image transformation. + * + * To compute the coordinate transform, we need the coordinate + * value (x',y') in the transformed space for any point (x,y) + * in the original space. To derive this transform from the + * three corresponding points, it is convenient to express the affine + * coordinate transformation using an LU decomposition of + * a set of six linear equations that express the six coordinates + * of the three points in the transformed space as a function of + * the six coordinates in the original space. Once we have + * this transform matrix , we can transform an image by + * finding, for each destination pixel, the pixel (or pixels) + * in the source that give rise to it. + * + * This 'pointwise' transformation can be done either by sampling + * and picking a single pixel in the src to replicate into the dest, + * or by interpolating (or averaging) over four src pixels to + * determine the value of the dest pixel. The first method is + * implemented by pixAffineSampled() and the second method by + * pixAffine(). The interpolated method can only be used for + * images with more than 1 bpp, but for these, the image quality + * is significantly better than the sampled method, due to + * the 'antialiasing' effect of weighting the src pixels. + * + * Interpolation works well when there is relatively little scaling, + * or if there is image expansion in general. However, if there + * is significant image reduction, one should apply a low-pass + * filter before subsampling to avoid aliasing the high frequencies. + * + * A typical application might be to align two images, which + * may be scaled, rotated and translated versions of each other. + * Through some pre-processing, three corresponding points are + * located in each of the two images. One of the images is + * then to be (affine) transformed to align with the other. + * As mentioned, the standard way to do this is to use three + * sets of points, compute the 6 transformation coefficients + * from these points that describe the linear transformation, + * + * x' = ax + by + c + * y' = dx + ey + f + * + * and use this in a pointwise manner to transform the image. + * + * N.B. Be sure to see the comment in getAffineXformCoeffs(), + * regarding using the inverse of the affine transform for points + * to transform images. + * + * There is another way to do this transformation; namely, + * by doing a sequence of simple affine transforms, without + * computing directly the affine coordinate transformation. + * We have at our disposal (1) translations (using rasterop), + * (2) horizontal and vertical shear about any horizontal and vertical + * line, respectively, and (3) non-isotropic scaling by two + * arbitrary x and y scaling factors. We also have rotation + * about an arbitrary point, but this is equivalent to a set + * of three shears so we do not need to use it. + * + * Why might we do this? For binary images, it is usually + * more efficient to do such transformations by a sequence + * of word parallel operations. Shear and translation can be + * done in-place and word parallel; arbitrary scaling is + * mostly pixel-wise. + * + * Suppose that we are transforming image 1 to correspond to image 2. + * We have a set of three points, describing the coordinate space + * embedded in image 1, and we need to transform image 1 until + * those three points exactly correspond to the new coordinate space + * defined by the second set of three points. In our image + * matching application, the latter set of three points was + * found to be the corresponding points in image 2. + * + * The most elegant way I can think of to do such a sequential + * implementation is to imagine that we're going to transform + * BOTH images until they're aligned. (We don't really want + * to transform both, because in fact we may only have one image + * that is undergoing a general affine transformation.) + * + * Choose the 3 corresponding points as follows: + * ~ The 1st point is an origin + * ~ The 2nd point gives the orientation and scaling of the + * "x" axis with respect to the origin + * ~ The 3rd point does likewise for the "y" axis. + * These "axes" must not be collinear; otherwise they are + * arbitrary (although some strange things will happen if + * the handedness sweeping through the minimum angle between + * the axes is opposite). + * + * An important constraint is that we have shear operations + * about an arbitrary horizontal or vertical line, but always + * parallel to the x or y axis. If we continue to pretend that + * we have an unprimed coordinate space embedded in image 1 and + * a primed coordinate space embedded in image 2, we imagine + * (a) transforming image 1 by horizontal and vertical shears about + * point 1 to align points 3 and 2 along the y and x axes, + * respectively, and (b) transforming image 2 by horizontal and + * vertical shears about point 1' to align points 3' and 2' along + * the y and x axes. Then we scale image 1 so that the distances + * from 1 to 2 and from 1 to 3 are equal to the distances in + * image 2 from 1' to 2' and from 1' to 3'. This scaling operation + * leaves the true image origin, at (0,0) invariant, and will in + * general translate point 1. The original points 1 and 1' will + * typically not coincide in any event, so we must translate + * the origin of image 1, at its current point 1, to the origin + * of image 2 at 1'. The images should now be aligned. But + * because we never really transformed image 2 (and image 2 may + * not even exist), we now perform on image 1 the reverse of + * the shear transforms that we imagined doing on image 2; + * namely, the negative vertical shear followed by the negative + * horizontal shear. Image 1 should now have its transformed + * unprimed coordinates aligned with the original primed + * coordinates. In all this, it is only necessary to keep track + * of the shear angles and translations of points during the shears. + * What has been accomplished is a general affine transformation + * on image 1. + * + * Having described all this, if you are going to use an + * affine transformation in an application, this is what you + * need to know: + * + * (1) You should NEVER use the sequential method, because + * the image quality for 1 bpp text is much poorer + * (even though it is about 2x faster than the pointwise sampled + * method), and for images with depth greater than 1, it is + * nearly 20x slower than the pointwise sampled method + * and over 10x slower than the pointwise interpolated method! + * The sequential method is given here for purely + * pedagogical reasons. + * + * (2) For 1 bpp images, use the pointwise sampled function + * pixAffineSampled(). For all other images, the best + * quality results result from using the pointwise + * interpolated function pixAffinePta() or pixAffine(); + * the cost is less than a doubling of the computation time + * with respect to the sampled function. If you use + * interpolation on colormapped images, the colormap will + * be removed, resulting in either a grayscale or color + * image, depending on the values in the colormap. + * If you want to retain the colormap, use pixAffineSampled(). + * + * Typical relative timing of pointwise transforms (sampled = 1.0): + * 8 bpp: sampled 1.0 + * interpolated 1.6 + * 32 bpp: sampled 1.0 + * interpolated 1.8 + * Additionally, the computation time/pixel is nearly the same + * for 8 bpp and 32 bpp, for both sampled and interpolated. + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include <math.h> +#include "allheaders.h" + +extern l_float32 AlphaMaskBorderVals[2]; + +#ifndef NO_CONSOLE_IO +#define DEBUG 0 +#endif /* ~NO_CONSOLE_IO */ + +/*-------------------------------------------------------------* + * Sampled affine image transformation * + *-------------------------------------------------------------*/ +/*! + * \brief pixAffineSampledPta() + * + * \param[in] pixs all depths + * \param[in] ptad 3 pts of final coordinate space + * \param[in] ptas 3 pts of initial coordinate space + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Brings in either black or white pixels from the boundary. + * (2) Retains colormap, which you can do for a sampled transform.. + * (3) The 3 points must not be collinear. + * (4) The order of the 3 points is arbitrary; however, to compare + * with the sequential transform they must be in these locations + * and in this order: origin, x-axis, y-axis. + * (5) For 1 bpp images, this has much better quality results + * than pixAffineSequential(), particularly for text. + * It is about 3x slower, but does not require additional + * border pixels. The poor quality of pixAffineSequential() + * is due to repeated quantized transforms. It is strongly + * recommended that pixAffineSampled() be used for 1 bpp images. + * (6) For 8 or 32 bpp, much better quality is obtained by the + * somewhat slower pixAffinePta(). See that function + * for relative timings between sampled and interpolated. + * (7) To repeat, use of the sequential transform, + * pixAffineSequential(), for any images, is discouraged. + * </pre> + */ +PIX * +pixAffineSampledPta(PIX *pixs, + PTA *ptad, + PTA *ptas, + l_int32 incolor) +{ +l_float32 *vc; +PIX *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!ptas) + return (PIX *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!ptad) + return (PIX *)ERROR_PTR("ptad not defined", __func__, NULL); + if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) + return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL); + if (ptaGetCount(ptas) != 3) + return (PIX *)ERROR_PTR("ptas count not 3", __func__, NULL); + if (ptaGetCount(ptad) != 3) + return (PIX *)ERROR_PTR("ptad count not 3", __func__, NULL); + + /* Get backwards transform from dest to src, and apply it */ + getAffineXformCoeffs(ptad, ptas, &vc); + pixd = pixAffineSampled(pixs, vc, incolor); + LEPT_FREE(vc); + + return pixd; +} + + +/*! + * \brief pixAffineSampled() + * + * \param[in] pixs all depths + * \param[in] vc vector of 6 coefficients for affine transformation + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Brings in either black or white pixels from the boundary. + * (2) Retains colormap, which you can do for a sampled transform.. + * (3) For 8 or 32 bpp, much better quality is obtained by the + * somewhat slower pixAffine(). See that function + * for relative timings between sampled and interpolated. + * </pre> + */ +PIX * +pixAffineSampled(PIX *pixs, + l_float32 *vc, + l_int32 incolor) +{ +l_int32 i, j, w, h, d, x, y, wpls, wpld, color, cmapindex; +l_uint32 val; +l_uint32 *datas, *datad, *lines, *lined; +PIX *pixd; +PIXCMAP *cmap; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!vc) + return (PIX *)ERROR_PTR("vc not defined", __func__, NULL); + if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) + return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL); + pixGetDimensions(pixs, &w, &h, &d); + if (d != 1 && d != 2 && d != 4 && d != 8 && d != 32) + return (PIX *)ERROR_PTR("depth not 1, 2, 4, 8 or 16", __func__, NULL); + + /* Init all dest pixels to color to be brought in from outside */ + pixd = pixCreateTemplate(pixs); + if ((cmap = pixGetColormap(pixs)) != NULL) { + if (incolor == L_BRING_IN_WHITE) + color = 1; + else + color = 0; + pixcmapAddBlackOrWhite(cmap, color, &cmapindex); + pixSetAllArbitrary(pixd, cmapindex); + } else { + if ((d == 1 && incolor == L_BRING_IN_WHITE) || + (d > 1 && incolor == L_BRING_IN_BLACK)) { + pixClearAll(pixd); + } else { + pixSetAll(pixd); + } + } + + /* Scan over the dest pixels */ + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + for (i = 0; i < h; i++) { + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + affineXformSampledPt(vc, j, i, &x, &y); + if (x < 0 || y < 0 || x >=w || y >= h) + continue; + lines = datas + y * wpls; + if (d == 1) { + val = GET_DATA_BIT(lines, x); + SET_DATA_BIT_VAL(lined, j, val); + } else if (d == 8) { + val = GET_DATA_BYTE(lines, x); + SET_DATA_BYTE(lined, j, val); + } else if (d == 32) { + lined[j] = lines[x]; + } else if (d == 2) { + val = GET_DATA_DIBIT(lines, x); + SET_DATA_DIBIT(lined, j, val); + } else if (d == 4) { + val = GET_DATA_QBIT(lines, x); + SET_DATA_QBIT(lined, j, val); + } + } + } + + return pixd; +} + + +/*---------------------------------------------------------------------* + * Interpolated affine image transformation * + *---------------------------------------------------------------------*/ +/*! + * \brief pixAffinePta() + * + * \param[in] pixs all depths; colormap ok + * \param[in] ptad 3 pts of final coordinate space + * \param[in] ptas 3 pts of initial coordinate space + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Brings in either black or white pixels from the boundary + * (2) Removes any existing colormap, if necessary, before transforming + * </pre> + */ +PIX * +pixAffinePta(PIX *pixs, + PTA *ptad, + PTA *ptas, + l_int32 incolor) +{ +l_int32 d; +l_uint32 colorval; +PIX *pixt1, *pixt2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!ptas) + return (PIX *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!ptad) + return (PIX *)ERROR_PTR("ptad not defined", __func__, NULL); + if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) + return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL); + if (ptaGetCount(ptas) != 3) + return (PIX *)ERROR_PTR("ptas count not 3", __func__, NULL); + if (ptaGetCount(ptad) != 3) + return (PIX *)ERROR_PTR("ptad count not 3", __func__, NULL); + + if (pixGetDepth(pixs) == 1) + return pixAffineSampledPta(pixs, ptad, ptas, incolor); + + /* 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 color to bring in from edges */ + colorval = 0; + if (incolor == L_BRING_IN_WHITE) { + if (d == 8) + colorval = 255; + else /* d == 32 */ + colorval = 0xffffff00; + } + + if (d == 8) + pixd = pixAffinePtaGray(pixt2, ptad, ptas, colorval); + else /* d == 32 */ + pixd = pixAffinePtaColor(pixt2, ptad, ptas, colorval); + pixDestroy(&pixt1); + pixDestroy(&pixt2); + return pixd; +} + + +/*! + * \brief pixAffine() + * + * \param[in] pixs all depths; colormap ok + * \param[in] vc vector of 6 coefficients for affine transformation + * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) Brings in either black or white pixels from the boundary + * (2) Removes any existing colormap, if necessary, before transforming + * </pre> + */ +PIX * +pixAffine(PIX *pixs, + l_float32 *vc, + l_int32 incolor) +{ +l_int32 d; +l_uint32 colorval; +PIX *pixt1, *pixt2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!vc) + return (PIX *)ERROR_PTR("vc not defined", __func__, NULL); + + if (pixGetDepth(pixs) == 1) + return pixAffineSampled(pixs, vc, incolor); + + /* 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 color to bring in from edges */ + colorval = 0; + if (incolor == L_BRING_IN_WHITE) { + if (d == 8) + colorval = 255; + else /* d == 32 */ + colorval = 0xffffff00; + } + + if (d == 8) + pixd = pixAffineGray(pixt2, vc, colorval); + else /* d == 32 */ + pixd = pixAffineColor(pixt2, vc, colorval); + pixDestroy(&pixt1); + pixDestroy(&pixt2); + return pixd; +} + + +/*! + * \brief pixAffinePtaColor() + * + * \param[in] pixs 32 bpp + * \param[in] ptad 3 pts of final coordinate space + * \param[in] ptas 3 pts of initial coordinate space + * \param[in] colorval e.g.: 0 to bring in BLACK, 0xffffff00 for WHITE + * \return pixd, or NULL on error + */ +PIX * +pixAffinePtaColor(PIX *pixs, + PTA *ptad, + PTA *ptas, + l_uint32 colorval) +{ +l_float32 *vc; +PIX *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!ptas) + return (PIX *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!ptad) + return (PIX *)ERROR_PTR("ptad not defined", __func__, NULL); + if (pixGetDepth(pixs) != 32) + return (PIX *)ERROR_PTR("pixs must be 32 bpp", __func__, NULL); + if (ptaGetCount(ptas) != 3) + return (PIX *)ERROR_PTR("ptas count not 3", __func__, NULL); + if (ptaGetCount(ptad) != 3) + return (PIX *)ERROR_PTR("ptad count not 3", __func__, NULL); + + /* Get backwards transform from dest to src, and apply it */ + getAffineXformCoeffs(ptad, ptas, &vc); + pixd = pixAffineColor(pixs, vc, colorval); + LEPT_FREE(vc); + + return pixd; +} + + +/*! + * \brief pixAffineColor() + * + * \param[in] pixs 32 bpp + * \param[in] vc vector of 6 coefficients for affine transformation + * \param[in] colorval e.g.: 0 to bring in BLACK, 0xffffff00 for WHITE + * \return pixd, or NULL on error + */ +PIX * +pixAffineColor(PIX *pixs, + l_float32 *vc, + l_uint32 colorval) +{ +l_int32 i, j, w, h, d, wpls, wpld; +l_uint32 val; +l_uint32 *datas, *datad, *lined; +l_float32 x, y; +PIX *pix1, *pix2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + pixGetDimensions(pixs, &w, &h, &d); + if (d != 32) + return (PIX *)ERROR_PTR("pixs must be 32 bpp", __func__, NULL); + if (!vc) + return (PIX *)ERROR_PTR("vc not defined", __func__, NULL); + + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + pixd = pixCreateTemplate(pixs); + pixSetAllArbitrary(pixd, colorval); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + + /* Iterate over destination pixels */ + for (i = 0; i < h; i++) { + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + /* Compute float src pixel location corresponding to (i,j) */ + affineXformPt(vc, j, i, &x, &y); + linearInterpolatePixelColor(datas, wpls, w, h, x, y, colorval, + &val); + *(lined + j) = val; + } + } + + /* If rgba, transform the pixs alpha channel and insert in pixd */ + if (pixGetSpp(pixs) == 4) { + pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL); + pix2 = pixAffineGray(pix1, vc, 255); /* bring in opaque */ + pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); + pixDestroy(&pix1); + pixDestroy(&pix2); + } + + return pixd; +} + + +/*! + * \brief pixAffinePtaGray() + * + * \param[in] pixs 8 bpp + * \param[in] ptad 3 pts of final coordinate space + * \param[in] ptas 3 pts of initial coordinate space + * \param[in] grayval e.g.: 0 to bring in BLACK, 255 for WHITE + * \return pixd, or NULL on error + */ +PIX * +pixAffinePtaGray(PIX *pixs, + PTA *ptad, + PTA *ptas, + l_uint8 grayval) +{ +l_float32 *vc; +PIX *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!ptas) + return (PIX *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!ptad) + return (PIX *)ERROR_PTR("ptad not defined", __func__, NULL); + if (pixGetDepth(pixs) != 8) + return (PIX *)ERROR_PTR("pixs must be 8 bpp", __func__, NULL); + if (ptaGetCount(ptas) != 3) + return (PIX *)ERROR_PTR("ptas count not 3", __func__, NULL); + if (ptaGetCount(ptad) != 3) + return (PIX *)ERROR_PTR("ptad count not 3", __func__, NULL); + + /* Get backwards transform from dest to src, and apply it */ + getAffineXformCoeffs(ptad, ptas, &vc); + pixd = pixAffineGray(pixs, vc, grayval); + LEPT_FREE(vc); + + return pixd; +} + + + +/*! + * \brief pixAffineGray() + * + * \param[in] pixs 8 bpp + * \param[in] vc vector of 6 coefficients for affine transformation + * \param[in] grayval e.g.: 0 to bring in BLACK, 255 for WHITE + * \return pixd, or NULL on error + */ +PIX * +pixAffineGray(PIX *pixs, + l_float32 *vc, + l_uint8 grayval) +{ +l_int32 i, j, w, h, wpls, wpld, val; +l_uint32 *datas, *datad, *lined; +l_float32 x, y; +PIX *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + pixGetDimensions(pixs, &w, &h, NULL); + if (pixGetDepth(pixs) != 8) + return (PIX *)ERROR_PTR("pixs must be 8 bpp", __func__, NULL); + if (!vc) + return (PIX *)ERROR_PTR("vc not defined", __func__, NULL); + + datas = pixGetData(pixs); + wpls = pixGetWpl(pixs); + pixd = pixCreateTemplate(pixs); + pixSetAllArbitrary(pixd, grayval); + datad = pixGetData(pixd); + wpld = pixGetWpl(pixd); + + /* Iterate over destination pixels */ + for (i = 0; i < h; i++) { + lined = datad + i * wpld; + for (j = 0; j < w; j++) { + /* Compute float src pixel location corresponding to (i,j) */ + affineXformPt(vc, j, i, &x, &y); + linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val); + SET_DATA_BYTE(lined, j, val); + } + } + + return pixd; +} + + +/*---------------------------------------------------------------------------* + * Affine transform including alpha (blend) component * + *---------------------------------------------------------------------------*/ +/*! + * \brief pixAffinePtaWithAlpha() + * + * \param[in] pixs 32 bpp rgb + * \param[in] ptad 3 pts of final coordinate space + * \param[in] ptas 3 pts of initial coordinate space + * \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 + * \param[in] border of pixels added to capture transformed source pixels + * \return pixd, or NULL on error + * + * <pre> + * 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 affine 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 affine 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. + * </pre> + */ +PIX * +pixAffinePtaWithAlpha(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; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + pixGetDimensions(pixs, &ws, &hs, &d); + if (d != 32 && pixGetColormap(pixs) == NULL) + return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", __func__, NULL); + if (pixg && pixGetDepth(pixg) != 8) { + L_WARNING("pixg not 8 bpp; using 'fract' transparent alpha\n", + __func__); + pixg = NULL; + } + if (!pixg && (fract < 0.0 || fract > 1.0)) { + L_WARNING("invalid fract; using 1.0 (fully transparent)\n", __func__); + fract = 1.0; + } + if (!pixg && fract == 0.0) + L_WARNING("fully opaque alpha; image will not be blended\n", __func__); + if (!ptad) + return (PIX *)ERROR_PTR("ptad not defined", __func__, NULL); + if (!ptas) + return (PIX *)ERROR_PTR("ptas not defined", __func__, 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 affine transform of rgb channels of pixs and of pixg */ + pixd = pixAffinePtaColor(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 = pixAffinePtaGray(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; +} + + +/*-------------------------------------------------------------* + * Affine coordinate transformation * + *-------------------------------------------------------------*/ +/*! + * \brief getAffineXformCoeffs() + * + * \param[in] ptas source 3 points; unprimed + * \param[in] ptad transformed 3 points; primed + * \param[out] pvc vector of coefficients of transform + * \return 0 if OK; 1 on error + * + * <pre> + * We have a set of six equations, describing the affine + * transformation that takes 3 points ptas into 3 other + * points ptad. These equations are: + * + * x1' = c[0]*x1 + c[1]*y1 + c[2] + * y1' = c[3]*x1 + c[4]*y1 + c[5] + * x2' = c[0]*x2 + c[1]*y2 + c[2] + * y2' = c[3]*x2 + c[4]*y2 + c[5] + * x3' = c[0]*x3 + c[1]*y3 + c[2] + * y3' = c[3]*x3 + c[4]*y3 + c[5] + * + * This can be represented as + * + * AC = B + * + * where B and C are column vectors + * + * B = [ x1' y1' x2' y2' x3' y3' ] + * C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] ] + * + * and A is the 6x6 matrix + * + * x1 y1 1 0 0 0 + * 0 0 0 x1 y1 1 + * x2 y2 1 0 0 0 + * 0 0 0 x2 y2 1 + * x3 y3 1 0 0 0 + * 0 0 0 x3 y3 1 + * + * These six equations are solved here for the coefficients C. + * + * These six coefficients can then be used to find the dest + * point x',y') corresponding to any src point (x,y, according + * to the equations + * + * x' = c[0]x + c[1]y + c[2] + * y' = c[3]x + c[4]y + c[5] + * + * that are implemented in affineXformPt. + * + * !!!!!!!!!!!!!!!!!! Very important !!!!!!!!!!!!!!!!!!!!!! + * + * When the affine transform is composed from a set of simple + * operations such as translation, scaling and rotation, + * it is built in a form to convert from the un-transformed src + * point to the transformed dest point. However, when an + * affine transform is used on images, it is used in an inverted + * way: it converts from the transformed dest point to the + * un-transformed src point. So, for example, if you transform + * a boxa using transform A, to transform an image in the same + * way you must use the inverse of A. + * + * For example, if you transform a boxa with a 3x3 affine matrix + * 'mat', the analogous image transformation must use 'matinv': + * \code + * boxad = boxaAffineTransform(boxas, mat); + * affineInvertXform(mat, &matinv); + * pixd = pixAffine(pixs, matinv, L_BRING_IN_WHITE); + * \endcode + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * </pre> + */ +l_ok +getAffineXformCoeffs(PTA *ptas, + PTA *ptad, + l_float32 **pvc) +{ +l_int32 i; +l_float32 x1, y1, x2, y2, x3, y3; +l_float32 *b; /* rhs vector of primed coords X'; coeffs returned in *pvc */ +l_float32 *a[6]; /* 6x6 matrix A */ + + if (!ptas) + return ERROR_INT("ptas not defined", __func__, 1); + if (!ptad) + return ERROR_INT("ptad not defined", __func__, 1); + if (!pvc) + return ERROR_INT("&vc not defined", __func__, 1); + + b = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32)); + *pvc = b; + + ptaGetPt(ptas, 0, &x1, &y1); + ptaGetPt(ptas, 1, &x2, &y2); + ptaGetPt(ptas, 2, &x3, &y3); + ptaGetPt(ptad, 0, &b[0], &b[1]); + ptaGetPt(ptad, 1, &b[2], &b[3]); + ptaGetPt(ptad, 2, &b[4], &b[5]); + + for (i = 0; i < 6; i++) + a[i] = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32)); + a[0][0] = x1; + a[0][1] = y1; + a[0][2] = 1.; + a[1][3] = x1; + a[1][4] = y1; + a[1][5] = 1.; + a[2][0] = x2; + a[2][1] = y2; + a[2][2] = 1.; + a[3][3] = x2; + a[3][4] = y2; + a[3][5] = 1.; + a[4][0] = x3; + a[4][1] = y3; + a[4][2] = 1.; + a[5][3] = x3; + a[5][4] = y3; + a[5][5] = 1.; + + gaussjordan(a, b, 6); + + for (i = 0; i < 6; i++) + LEPT_FREE(a[i]); + + return 0; +} + + +/*! + * \brief affineInvertXform() + * + * \param[in] vc vector of 6 coefficients + * \param[out] pvci inverted transform + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) The 6 affine transform coefficients are the first + * two rows of a 3x3 matrix where the last row has + * only a 1 in the third column. We invert this + * using gaussjordan(), and select the first 2 rows + * as the coefficients of the inverse affine transform. + * (2) Alternatively, we can find the inverse transform + * coefficients by inverting the 2x2 submatrix, + * and treating the top 2 coefficients in the 3rd column as + * a RHS vector for that 2x2 submatrix. Then the + * 6 inverted transform coefficients are composed of + * the inverted 2x2 submatrix and the negative of the + * transformed RHS vector. Why is this so? We have + * Y = AX + R (2 equations in 6 unknowns) + * Then + * X = A'Y - A'R + * Gauss-jordan solves + * AF = R + * and puts the solution for F, which is A'R, + * into the input R vector. + * + * </pre> + */ +l_ok +affineInvertXform(l_float32 *vc, + l_float32 **pvci) +{ +l_int32 i; +l_float32 *vci; +l_float32 *a[3]; +l_float32 b[3] = {1.0, 1.0, 1.0}; /* anything; results ignored */ + + if (!pvci) + return ERROR_INT("&vci not defined", __func__, 1); + *pvci = NULL; + if (!vc) + return ERROR_INT("vc not defined", __func__, 1); + +#if 1 + for (i = 0; i < 3; i++) + a[i] = (l_float32 *)LEPT_CALLOC(3, sizeof(l_float32)); + a[0][0] = vc[0]; + a[0][1] = vc[1]; + a[0][2] = vc[2]; + a[1][0] = vc[3]; + a[1][1] = vc[4]; + a[1][2] = vc[5]; + a[2][2] = 1.0; + gaussjordan(a, b, 3); /* this inverts matrix a */ + vci = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32)); + *pvci = vci; + vci[0] = a[0][0]; + vci[1] = a[0][1]; + vci[2] = a[0][2]; + vci[3] = a[1][0]; + vci[4] = a[1][1]; + vci[5] = a[1][2]; + for (i = 0; i < 3; i++) + LEPT_FREE(a[i]); + +#else + + /* Alternative version, inverting a 2x2 matrix */ + { l_float32 *a2[2]; + for (i = 0; i < 2; i++) + a2[i] = (l_float32 *)LEPT_CALLOC(2, sizeof(l_float32)); + a2[0][0] = vc[0]; + a2[0][1] = vc[1]; + a2[1][0] = vc[3]; + a2[1][1] = vc[4]; + b[0] = vc[2]; + b[1] = vc[5]; + gaussjordan(a2, b, 2); /* this inverts matrix a2 */ + vci = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32)); + *pvci = vci; + vci[0] = a2[0][0]; + vci[1] = a2[0][1]; + vci[2] = -b[0]; /* note sign */ + vci[3] = a2[1][0]; + vci[4] = a2[1][1]; + vci[5] = -b[1]; /* note sign */ + for (i = 0; i < 2; i++) + LEPT_FREE(a2[i]); + } +#endif + + return 0; +} + + +/*! + * \brief affineXformSampledPt() + * + * \param[in] vc vector of 6 coefficients + * \param[in] x, y initial point + * \param[out] pxp, pyp transformed point + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This finds the nearest pixel coordinates of the transformed point. + * (2) It does not check ptrs for returned data! + * </pre> + */ +l_ok +affineXformSampledPt(l_float32 *vc, + l_int32 x, + l_int32 y, + l_int32 *pxp, + l_int32 *pyp) +{ + if (!vc) + return ERROR_INT("vc not defined", __func__, 1); + + *pxp = (l_int32)(vc[0] * x + vc[1] * y + vc[2] + 0.5); + *pyp = (l_int32)(vc[3] * x + vc[4] * y + vc[5] + 0.5); + return 0; +} + + +/*! + * \brief affineXformPt() + * + * \param[in] vc vector of 6 coefficients + * \param[in] x, y initial point + * \param[out] pxp, pyp transformed point + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This computes the floating point location of the transformed point. + * (2) It does not check ptrs for returned data! + * </pre> + */ +l_ok +affineXformPt(l_float32 *vc, + l_int32 x, + l_int32 y, + l_float32 *pxp, + l_float32 *pyp) +{ + if (!vc) + return ERROR_INT("vc not defined", __func__, 1); + + *pxp = vc[0] * x + vc[1] * y + vc[2]; + *pyp = vc[3] * x + vc[4] * y + vc[5]; + return 0; +} + + +/*-------------------------------------------------------------* + * Interpolation helper functions * + *-------------------------------------------------------------*/ +/*! + * \brief linearInterpolatePixelColor() + * + * \param[in] datas ptr to beginning of image data + * \param[in] wpls 32-bit word/line for this data array + * \param[in] w, h of image + * \param[in] x, y floating pt location for evaluation + * \param[in] colorval color brought in from the outside when the + * input x,y location is outside the image; + * in 0xrrggbb00 format) + * \param[out] pval interpolated color value + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This is a standard linear interpolation function. It is + * equivalent to area weighting on each component, and + * avoids "jaggies" when rendering sharp edges. + * </pre> + */ +l_ok +linearInterpolatePixelColor(l_uint32 *datas, + l_int32 wpls, + l_int32 w, + l_int32 h, + l_float32 x, + l_float32 y, + l_uint32 colorval, + l_uint32 *pval) +{ +l_int32 valid, xpm, ypm, xp, xp2, yp, xf, yf; +l_int32 rval, gval, bval; +l_uint32 word00, word01, word10, word11; +l_uint32 *lines; + + if (!pval) + return ERROR_INT("&val not defined", __func__, 1); + *pval = colorval; + if (!datas) + return ERROR_INT("datas not defined", __func__, 1); + + /* Skip if x or y are invalid. (x,y) must be in the source image. + * Failure to detect an invalid point will cause a mem address fault. + * Occasionally, x or y will be a nan, and relational checks always + * fail for nans. Therefore we check if the point is inside the pix */ + valid = (x >= 0.0 && y >= 0.0 && x < w && y < h); + if (!valid) return 0; + + xpm = (l_int32)(16.0 * x); + ypm = (l_int32)(16.0 * y); + xp = xpm >> 4; + xp2 = xp + 1 < w ? xp + 1 : xp; + yp = ypm >> 4; + if (yp + 1 >= h) wpls = 0; + xf = xpm & 0x0f; + yf = ypm & 0x0f; + +#if DEBUG + if (xf < 0 || yf < 0) + lept_stderr("xp = %d, yp = %d, xf = %d, yf = %d\n", xp, yp, xf, yf); +#endif /* DEBUG */ + + /* Do area weighting (eqiv. to linear interpolation) */ + lines = datas + yp * wpls; + word00 = *(lines + xp); + word10 = *(lines + xp2); + word01 = *(lines + wpls + xp); + word11 = *(lines + wpls + xp2); + 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)) / 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)) / 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)) / 256; + composeRGBPixel(rval, gval, bval, pval); + return 0; +} + + +/*! + * \brief linearInterpolatePixelGray() + * + * \param[in] datas ptr to beginning of image data + * \param[in] wpls 32-bit word/line for this data array + * \param[in] w, h of image + * \param[in] x, y floating pt location for evaluation + * \param[in] grayval color brought in from the outside when the + * input x,y location is outside the image + * \param[out] pval interpolated gray value + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This is a standard linear interpolation function. It is + * equivalent to area weighting on each component, and + * avoids "jaggies" when rendering sharp edges. + * </pre> + */ +l_ok +linearInterpolatePixelGray(l_uint32 *datas, + l_int32 wpls, + l_int32 w, + l_int32 h, + l_float32 x, + l_float32 y, + l_int32 grayval, + l_int32 *pval) +{ +l_int32 valid, xpm, ypm, xp, xp2, yp, xf, yf, v00, v10, v01, v11; +l_uint32 *lines; + + if (!pval) + return ERROR_INT("&val not defined", __func__, 1); + *pval = grayval; + if (!datas) + return ERROR_INT("datas not defined", __func__, 1); + + /* Skip if x or y is invalid. (x,y) must be in the source image. + * Failure to detect an invalid point will cause a mem address fault. + * Occasionally, x or y will be a nan, and relational checks always + * fail for nans. Therefore we check if the point is inside the pix */ + valid = (x >= 0.0 && y >= 0.0 && x < w && y < h); + if (!valid) return 0; + + xpm = (l_int32)(16.0 * x); + ypm = (l_int32)(16.0 * y); + xp = xpm >> 4; + xp2 = xp + 1 < w ? xp + 1 : xp; + yp = ypm >> 4; + if (yp + 1 >= h) wpls = 0; + xf = xpm & 0x0f; + yf = ypm & 0x0f; + +#if DEBUG + if (xf < 0 || yf < 0) + lept_stderr("xp = %d, yp = %d, xf = %d, yf = %d\n", xp, yp, xf, yf); +#endif /* DEBUG */ + + /* Interpolate by area weighting. */ + lines = datas + yp * wpls; + v00 = (16 - xf) * (16 - yf) * GET_DATA_BYTE(lines, xp); + v10 = xf * (16 - yf) * GET_DATA_BYTE(lines, xp2); + v01 = (16 - xf) * yf * GET_DATA_BYTE(lines + wpls, xp); + v11 = xf * yf * GET_DATA_BYTE(lines + wpls, xp2); + *pval = (v00 + v01 + v10 + v11) / 256; + return 0; +} + + + +/*-------------------------------------------------------------* + * Gauss-jordan linear equation solver * + *-------------------------------------------------------------*/ +#define SWAP(a,b) {temp = (a); (a) = (b); (b) = temp;} + +/*! + * \brief gaussjordan() + * + * \param[in] a n x n matrix + * \param[in] b n x 1 right-hand side column vector + * \param[in] n dimension + * \return 0 if ok, 1 on error + * + * <pre> + * Notes: + * (1) There are two side-effects: + * * The matrix a is transformed to its inverse A + * * The rhs vector b is transformed to the solution x + * of the linear equation ax = b + * (2) The inverse A can then be used to solve the same equation with + * different rhs vectors c by multiplication: x = Ac + * (3) Adapted from "Numerical Recipes in C, Second Edition", 1992, + * pp. 36-41 (gauss-jordan elimination) + * </pre> + */ +l_int32 +gaussjordan(l_float32 **a, + l_float32 *b, + l_int32 n) +{ +l_int32 i, icol, irow, j, k, col, row, success; +l_int32 *indexc, *indexr, *ipiv; +l_float32 maxval, val, pivinv, temp; + + if (!a) + return ERROR_INT("a not defined", __func__, 1); + if (!b) + return ERROR_INT("b not defined", __func__, 1); + + success = TRUE; + indexc = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32)); + indexr = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32)); + ipiv = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32)); + if (!indexc || !indexr || !ipiv) { + L_ERROR("array not made\n", __func__); + success = FALSE; + goto cleanup_arrays; + } + + icol = irow = 0; /* silence static checker */ + for (i = 0; i < n; i++) { + maxval = 0.0; + for (j = 0; j < n; j++) { + if (ipiv[j] != 1) { + for (k = 0; k < n; k++) { + if (ipiv[k] == 0) { + if (fabs(a[j][k]) >= maxval) { + maxval = fabs(a[j][k]); + irow = j; + icol = k; + } + } else if (ipiv[k] > 1) { + L_ERROR("singular matrix\n", __func__); + success = FALSE; + goto cleanup_arrays; + } + } + } + } + ++(ipiv[icol]); + + if (irow != icol) { + for (col = 0; col < n; col++) + SWAP(a[irow][col], a[icol][col]); + SWAP(b[irow], b[icol]); + } + + indexr[i] = irow; + indexc[i] = icol; + if (a[icol][icol] == 0.0) { + L_ERROR("singular matrix\n", __func__); + success = FALSE; + goto cleanup_arrays; + } + pivinv = 1.0 / a[icol][icol]; + a[icol][icol] = 1.0; + for (col = 0; col < n; col++) + a[icol][col] *= pivinv; + b[icol] *= pivinv; + + for (row = 0; row < n; row++) { + if (row != icol) { + val = a[row][icol]; + a[row][icol] = 0.0; + for (col = 0; col < n; col++) + a[row][col] -= a[icol][col] * val; + b[row] -= b[icol] * val; + } + } + } + + for (col = n - 1; col >= 0; col--) { + if (indexr[col] != indexc[col]) { + for (k = 0; k < n; k++) + SWAP(a[k][indexr[col]], a[k][indexc[col]]); + } + } + +cleanup_arrays: + LEPT_FREE(indexr); + LEPT_FREE(indexc); + LEPT_FREE(ipiv); + return (success) ? 0 : 1; +} + + +/*-------------------------------------------------------------* + * Sequential affine image transformation * + *-------------------------------------------------------------*/ +/*! + * \brief pixAffineSequential() + * + * \param[in] pixs + * \param[in] ptad 3 pts of final coordinate space + * \param[in] ptas 3 pts of initial coordinate space + * \param[in] bw pixels of additional border width during computation + * \param[in] bh pixels of additional border height during computation + * \return pixd, or NULL on error + * + * <pre> + * Notes: + * (1) The 3 pts must not be collinear. + * (2) The 3 pts must be given in this order: + * ~ origin + * ~ a location along the x-axis + * ~ a location along the y-axis. + * (3) You must guess how much border must be added so that no + * pixels are lost in the transformations from src to + * dest coordinate space. (This can be calculated but it + * is a lot of work!) For coordinate spaces that are nearly + * at right angles, on a 300 ppi scanned page, the addition + * of 1000 pixels on each side is usually sufficient. + * (4) This is here for pedagogical reasons. It is about 3x faster + * on 1 bpp images than pixAffineSampled(), but the results + * on text are much inferior. + * </pre> + */ +PIX * +pixAffineSequential(PIX *pixs, + PTA *ptad, + PTA *ptas, + l_int32 bw, + l_int32 bh) +{ +l_int32 x1, y1, x2, y2, x3, y3; /* ptas */ +l_int32 x1p, y1p, x2p, y2p, x3p, y3p; /* ptad */ +l_int32 x1sc, y1sc; /* scaled origin */ +l_float32 x2s, x2sp, scalex, scaley; +l_float32 th3, th3p, ph2, ph2p; +#if DEBUG +l_float32 rad2deg; +#endif /* DEBUG */ +PIX *pix1, *pix2, *pixd; + + if (!pixs) + return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); + if (!ptas) + return (PIX *)ERROR_PTR("ptas not defined", __func__, NULL); + if (!ptad) + return (PIX *)ERROR_PTR("ptad not defined", __func__, NULL); + + if (ptaGetCount(ptas) != 3) + return (PIX *)ERROR_PTR("ptas count not 3", __func__, NULL); + if (ptaGetCount(ptad) != 3) + return (PIX *)ERROR_PTR("ptad count not 3", __func__, NULL); + ptaGetIPt(ptas, 0, &x1, &y1); + ptaGetIPt(ptas, 1, &x2, &y2); + ptaGetIPt(ptas, 2, &x3, &y3); + ptaGetIPt(ptad, 0, &x1p, &y1p); + ptaGetIPt(ptad, 1, &x2p, &y2p); + ptaGetIPt(ptad, 2, &x3p, &y3p); + + pix1 = pix2 = pixd = NULL; + + if (y1 == y3) + return (PIX *)ERROR_PTR("y1 == y3!", __func__, NULL); + if (y1p == y3p) + return (PIX *)ERROR_PTR("y1p == y3p!", __func__, NULL); + + if (bw != 0 || bh != 0) { + /* resize all points and add border to pixs */ + x1 = x1 + bw; + y1 = y1 + bh; + x2 = x2 + bw; + y2 = y2 + bh; + x3 = x3 + bw; + y3 = y3 + bh; + x1p = x1p + bw; + y1p = y1p + bh; + x2p = x2p + bw; + y2p = y2p + bh; + x3p = x3p + bw; + y3p = y3p + bh; + + if ((pix1 = pixAddBorderGeneral(pixs, bw, bw, bh, bh, 0)) == NULL) + return (PIX *)ERROR_PTR("pix1 not made", __func__, NULL); + } else { + pix1 = pixCopy(NULL, pixs); + } + + /*-------------------------------------------------------------* + The horizontal shear is done to move the 3rd point to the + y axis. This moves the 2nd point either towards or away + from the y axis, depending on whether it is above or below + the x axis. That motion must be computed so that we know + the angle of vertical shear to use to get the 2nd point + on the x axis. We must also know the x coordinate of the + 2nd point in order to compute how much scaling is required + to match points on the axis. + *-------------------------------------------------------------*/ + + /* Shear angles required to put src points on x and y axes */ + th3 = atan2((l_float64)(x1 - x3), (l_float64)(y1 - y3)); + x2s = (l_float32)(x2 - ((l_float32)(y1 - y2) * (x3 - x1)) / (y1 - y3)); + if (x2s == (l_float32)x1) { + L_ERROR("x2s == x1!\n", __func__); + goto cleanup_pix; + } + ph2 = atan2((l_float64)(y1 - y2), (l_float64)(x2s - x1)); + + /* Shear angles required to put dest points on x and y axes. + * Use the negative of these values to instead move the + * src points from the axes to the actual dest position. + * These values are also needed to scale the image. */ + th3p = atan2((l_float64)(x1p - x3p), (l_float64)(y1p - y3p)); + x2sp = (l_float32)(x2p - + ((l_float32)(y1p - y2p) * (x3p - x1p)) / (y1p - y3p)); + if (x2sp == (l_float32)x1p) { + L_ERROR("x2sp == x1p!\n", __func__); + goto cleanup_pix; + } + ph2p = atan2((l_float64)(y1p - y2p), (l_float64)(x2sp - x1p)); + + /* Shear image to first put src point 3 on the y axis, + * and then to put src point 2 on the x axis */ + pixHShearIP(pix1, y1, th3, L_BRING_IN_WHITE); + pixVShearIP(pix1, x1, ph2, L_BRING_IN_WHITE); + + /* Scale image to match dest scale. The dest scale + * is calculated above from the angles th3p and ph2p + * that would be required to move the dest points to + * the x and y axes. */ + scalex = (l_float32)(x2sp - x1p) / (x2s - x1); + scaley = (l_float32)(y3p - y1p) / (y3 - y1); + if ((pix2 = pixScale(pix1, scalex, scaley)) == NULL) { + L_ERROR("pix2 not made\n", __func__); + goto cleanup_pix; + } + +#if DEBUG + rad2deg = 180. / 3.1415926535; + lept_stderr("th3 = %5.1f deg, ph2 = %5.1f deg\n", + rad2deg * th3, rad2deg * ph2); + lept_stderr("th3' = %5.1f deg, ph2' = %5.1f deg\n", + rad2deg * th3p, rad2deg * ph2p); + lept_stderr("scalex = %6.3f, scaley = %6.3f\n", scalex, scaley); +#endif /* DEBUG */ + + /*-------------------------------------------------------------* + Scaling moves the 1st src point, which is the origin. + It must now be moved again to coincide with the origin + (1st point) of the dest. After this is done, the 2nd + and 3rd points must be sheared back to the original + positions of the 2nd and 3rd dest points. We use the + negative of the angles that were previously computed + for shearing those points in the dest image to x and y + axes, and take the shears in reverse order as well. + *-------------------------------------------------------------*/ + /* Shift image to match dest origin. */ + x1sc = (l_int32)(scalex * x1 + 0.5); /* x comp of origin after scaling */ + y1sc = (l_int32)(scaley * y1 + 0.5); /* y comp of origin after scaling */ + pixRasteropIP(pix2, x1p - x1sc, y1p - y1sc, L_BRING_IN_WHITE); + + /* Shear image to take points 2 and 3 off the axis and + * put them in the original dest position */ + pixVShearIP(pix2, x1p, -ph2p, L_BRING_IN_WHITE); + pixHShearIP(pix2, y1p, -th3p, L_BRING_IN_WHITE); + + if (bw != 0 || bh != 0) { + if ((pixd = pixRemoveBorderGeneral(pix2, bw, bw, bh, bh)) == NULL) + L_ERROR("pixd not made\n", __func__); + } else { + pixd = pixClone(pix2); + } + +cleanup_pix: + pixDestroy(&pix1); + pixDestroy(&pix2); + return pixd; +}
