| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| |
| /* |
| * shear.c |
| * |
| * About arbitrary lines |
| * PIX *pixHShear() |
| * PIX *pixVShear() |
| * |
| * About special 'points': UL corner and center |
| * PIX *pixHShearCorner() |
| * PIX *pixVShearCorner() |
| * PIX *pixHShearCenter() |
| * PIX *pixVShearCenter() |
| * |
| * In place about arbitrary lines |
| * l_int32 pixHShearIP() |
| * l_int32 pixVShearIP() |
| * |
| * Static helper |
| * static l_float32 normalizeAngleForShear() |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <math.h> |
| #include "allheaders.h" |
| |
| /* Shear angle must not get too close to -pi/2 or pi/2 */ |
| static const l_float32 MIN_DIFF_FROM_HALF_PI = 0.04; |
| |
| static l_float32 normalizeAngleForShear(l_float32 radang, l_float32 mindist); |
| |
| |
| #ifndef NO_CONSOLE_IO |
| #define DEBUG 0 |
| #endif /* ~NO_CONSOLE_IO */ |
| |
| |
| /*-------------------------------------------------------------* |
| * About arbitrary lines * |
| *-------------------------------------------------------------*/ |
| /*! |
| * pixHShear() |
| * |
| * Input: pixd (<optional>, this can be null, equal to pixs, |
| * or different from pixs) |
| * pixs (no restrictions on depth) |
| * liney (location of horizontal line, measured from origin) |
| * angle (in radians) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: pixd, always |
| * |
| * Notes: |
| * (1) There are 3 cases: |
| * (a) pixd == null (make a new pixd) |
| * (b) pixd == pixs (in-place) |
| * (c) pixd != pixs |
| * (2) For these three cases, use these patterns, respectively: |
| * pixd = pixHShear(NULL, pixs, ...); |
| * pixHShear(pixs, pixs, ...); |
| * pixHShear(pixd, pixs, ...); |
| * (3) This shear leaves the horizontal line of pixels at y = liney |
| * invariant. For a positive shear angle, pixels above this |
| * line are shoved to the right, and pixels below this line |
| * move to the left. |
| * (4) With positive shear angle, this can be used, along with |
| * pixVShear(), to perform a cw rotation, either with 2 shears |
| * (for small angles) or in the general case with 3 shears. |
| * (5) Changing the value of liney is equivalent to translating |
| * the result horizontally. |
| * (6) This brings in 'incolor' pixels from outside the image. |
| * (7) For in-place operation, pixs cannot be colormapped, |
| * because the in-place operation only blits in 0 or 1 bits, |
| * not an arbitrary colormap index. |
| * (8) The angle is brought into the range [-pi, -pi]. It is |
| * not permitted to be within MIN_DIFF_FROM_HALF_PI radians |
| * from either -pi/2 or pi/2. |
| */ |
| PIX * |
| pixHShear(PIX *pixd, |
| PIX *pixs, |
| l_int32 liney, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| l_int32 sign, w, h; |
| l_int32 y, yincr, inityincr, hshift; |
| l_float32 tanangle, invangle; |
| |
| PROCNAME("pixHShear"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); |
| if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) |
| return (PIX *)ERROR_PTR("invalid incolor value", procName, pixd); |
| |
| if (pixd == pixs) { /* in place */ |
| if (pixGetColormap(pixs) != NULL) |
| return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd); |
| pixHShearIP(pixd, liney, radang, incolor); |
| return pixd; |
| } |
| |
| /* Make sure pixd exists and is same size as pixs */ |
| if (!pixd) { |
| if ((pixd = pixCreateTemplate(pixs)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| } |
| else /* pixd != pixs */ |
| pixResizeImageData(pixd, pixs); |
| |
| /* Normalize angle. If no rotation, return a copy */ |
| radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI); |
| if (radang == 0.0 || tan(radang) == 0.0) |
| return pixCopy(pixd, pixs); |
| |
| /* Initialize to value of incoming pixels */ |
| pixSetBlackOrWhite(pixd, incolor); |
| |
| pixGetDimensions(pixs, &w, &h, NULL); |
| sign = L_SIGN(radang); |
| tanangle = tan(radang); |
| invangle = L_ABS(1. / tanangle); |
| inityincr = (l_int32)(invangle / 2.); |
| yincr = (l_int32)invangle; |
| pixRasterop(pixd, 0, liney - inityincr, w, 2 * inityincr, PIX_SRC, |
| pixs, 0, liney - inityincr); |
| |
| for (hshift = 1, y = liney + inityincr; y < h; hshift++) { |
| yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - liney); |
| if (h - y < yincr) /* reduce for last one if req'd */ |
| yincr = h - y; |
| pixRasterop(pixd, -sign*hshift, y, w, yincr, PIX_SRC, pixs, 0, y); |
| #if DEBUG |
| fprintf(stderr, "y = %d, hshift = %d, yincr = %d\n", y, hshift, yincr); |
| #endif /* DEBUG */ |
| y += yincr; |
| } |
| |
| for (hshift = -1, y = liney - inityincr; y > 0; hshift--) { |
| yincr = (y - liney) - (l_int32)(invangle * (hshift - 0.5) + 0.5); |
| if (y < yincr) /* reduce for last one if req'd */ |
| yincr = y; |
| pixRasterop(pixd, -sign*hshift, y - yincr, w, yincr, PIX_SRC, |
| pixs, 0, y - yincr); |
| #if DEBUG |
| fprintf(stderr, "y = %d, hshift = %d, yincr = %d\n", |
| y - yincr, hshift, yincr); |
| #endif /* DEBUG */ |
| y -= yincr; |
| } |
| |
| return pixd; |
| } |
| |
| |
| /*! |
| * pixVShear() |
| * |
| * Input: pixd (<optional>, this can be null, equal to pixs, |
| * or different from pixs) |
| * pixs (no restrictions on depth) |
| * linex (location of vertical line, measured from origin) |
| * angle (in radians; not too close to +-(pi / 2)) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: pixd, or null on error |
| * |
| * Notes: |
| * (1) There are 3 cases: |
| * (a) pixd == null (make a new pixd) |
| * (b) pixd == pixs (in-place) |
| * (c) pixd != pixs |
| * (2) For these three cases, use these patterns, respectively: |
| * pixd = pixVShear(NULL, pixs, ...); |
| * pixVShear(pixs, pixs, ...); |
| * pixVShear(pixd, pixs, ...); |
| * (3) This shear leaves the vertical line of pixels at x = linex |
| * invariant. For a positive shear angle, pixels to the right |
| * of this line are shoved downward, and pixels to the left |
| * of the line move upward. |
| * (4) With positive shear angle, this can be used, along with |
| * pixHShear(), to perform a cw rotation, either with 2 shears |
| * (for small angles) or in the general case with 3 shears. |
| * (5) Changing the value of linex is equivalent to translating |
| * the result vertically. |
| * (6) This brings in 'incolor' pixels from outside the image. |
| * (7) For in-place operation, pixs cannot be colormapped, |
| * because the in-place operation only blits in 0 or 1 bits, |
| * not an arbitrary colormap index. |
| * (8) The angle is brought into the range [-pi, -pi]. It is |
| * not permitted to be within MIN_DIFF_FROM_HALF_PI radians |
| * from either -pi/2 or pi/2. |
| */ |
| PIX * |
| pixVShear(PIX *pixd, |
| PIX *pixs, |
| l_int32 linex, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| l_int32 sign, w, h; |
| l_int32 x, xincr, initxincr, vshift; |
| l_float32 tanangle, invangle; |
| |
| PROCNAME("pixVShear"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); |
| if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) |
| return (PIX *)ERROR_PTR("invalid incolor value", procName, NULL); |
| |
| if (pixd == pixs) { /* in place */ |
| if (pixGetColormap(pixs) != NULL) |
| return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd); |
| pixVShearIP(pixd, linex, radang, incolor); |
| return pixd; |
| } |
| |
| /* Make sure pixd exists and is same size as pixs */ |
| if (!pixd) { |
| if ((pixd = pixCreateTemplate(pixs)) == NULL) |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| } |
| else /* pixd != pixs */ |
| pixResizeImageData(pixd, pixs); |
| |
| /* Normalize angle. If no rotation, return a copy */ |
| radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI); |
| if (radang == 0.0 || tan(radang) == 0.0) |
| return pixCopy(pixd, pixs); |
| |
| /* Initialize to value of incoming pixels */ |
| pixSetBlackOrWhite(pixd, incolor); |
| |
| pixGetDimensions(pixs, &w, &h, NULL); |
| sign = L_SIGN(radang); |
| tanangle = tan(radang); |
| invangle = L_ABS(1. / tanangle); |
| initxincr = (l_int32)(invangle / 2.); |
| xincr = (l_int32)invangle; |
| pixRasterop(pixd, linex - initxincr, 0, 2 * initxincr, h, PIX_SRC, |
| pixs, linex - initxincr, 0); |
| |
| for (vshift = 1, x = linex + initxincr; x < w; vshift++) { |
| xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - linex); |
| if (w - x < xincr) /* reduce for last one if req'd */ |
| xincr = w - x; |
| pixRasterop(pixd, x, sign*vshift, xincr, h, PIX_SRC, pixs, x, 0); |
| #if DEBUG |
| fprintf(stderr, "x = %d, vshift = %d, xincr = %d\n", x, vshift, xincr); |
| #endif /* DEBUG */ |
| x += xincr; |
| } |
| |
| for (vshift = -1, x = linex - initxincr; x > 0; vshift--) { |
| xincr = (x - linex) - (l_int32)(invangle * (vshift - 0.5) + 0.5); |
| if (x < xincr) /* reduce for last one if req'd */ |
| xincr = x; |
| pixRasterop(pixd, x - xincr, sign*vshift, xincr, h, PIX_SRC, |
| pixs, x - xincr, 0); |
| #if DEBUG |
| fprintf(stderr, "x = %d, vshift = %d, xincr = %d\n", |
| x - xincr, vshift, xincr); |
| #endif /* DEBUG */ |
| x -= xincr; |
| } |
| |
| return pixd; |
| } |
| |
| |
| |
| /*-------------------------------------------------------------* |
| * Shears about UL corner and center * |
| *-------------------------------------------------------------*/ |
| /*! |
| * pixHShearCorner() |
| * |
| * Input: pixd (<optional>, if not null, must be equal to pixs) |
| * pixs |
| * angle (in radians) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: pixd, or null on error. |
| * |
| * Notes: |
| * (1) See pixHShear() for usage. |
| * (2) This does a horizontal shear about the UL corner, with (+) shear |
| * pushing increasingly leftward (-x) with increasing y. |
| */ |
| PIX * |
| pixHShearCorner(PIX *pixd, |
| PIX *pixs, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| PROCNAME("pixHShearCorner"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); |
| |
| return pixHShear(pixd, pixs, 0, radang, incolor); |
| } |
| |
| |
| /*! |
| * pixVShearCorner() |
| * |
| * Input: pixd (<optional>, if not null, must be equal to pixs) |
| * pixs |
| * angle (in radians) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: pixd, or null on error. |
| * |
| * Notes: |
| * (1) See pixVShear() for usage. |
| * (2) This does a vertical shear about the UL corner, with (+) shear |
| * pushing increasingly downward (+y) with increasing x. |
| */ |
| PIX * |
| pixVShearCorner(PIX *pixd, |
| PIX *pixs, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| PROCNAME("pixVShearCorner"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); |
| |
| return pixVShear(pixd, pixs, 0, radang, incolor); |
| } |
| |
| |
| /*! |
| * pixHShearCenter() |
| * |
| * Input: pixd (<optional>, if not null, must be equal to pixs) |
| * pixs |
| * angle (in radians) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: pixd, or null on error. |
| * |
| * Notes: |
| * (1) See pixHShear() for usage. |
| * (2) This does a horizontal shear about the center, with (+) shear |
| * pushing increasingly leftward (-x) with increasing y. |
| */ |
| PIX * |
| pixHShearCenter(PIX *pixd, |
| PIX *pixs, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| PROCNAME("pixHShearCenter"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); |
| |
| return pixHShear(pixd, pixs, pixGetHeight(pixs) / 2, radang, incolor); |
| } |
| |
| |
| /*! |
| * pixVShearCenter() |
| * |
| * Input: pixd (<optional>, if not null, must be equal to pixs) |
| * pixs |
| * angle (in radians) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: pixd, or null on error. |
| * |
| * Notes: |
| * (1) See pixVShear() for usage. |
| * (2) This does a vertical shear about the center, with (+) shear |
| * pushing increasingly downward (+y) with increasing x. |
| */ |
| PIX * |
| pixVShearCenter(PIX *pixd, |
| PIX *pixs, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| PROCNAME("pixVShearCenter"); |
| |
| if (!pixs) |
| return (PIX *)ERROR_PTR("pixs not defined", procName, pixd); |
| |
| return pixVShear(pixd, pixs, pixGetWidth(pixs) / 2, radang, incolor); |
| } |
| |
| |
| |
| /*--------------------------------------------------------------------------* |
| * In place about arbitrary lines * |
| *--------------------------------------------------------------------------*/ |
| /*! |
| * pixHShearIP() |
| * |
| * Input: pixs |
| * liney (location of horizontal line, measured from origin) |
| * angle (in radians) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: 0 if OK; 1 on error |
| * |
| * Notes: |
| * (1) This is an in-place version of pixHShear(); see comments there. |
| * (2) This brings in 'incolor' pixels from outside the image. |
| * (3) pixs cannot be colormapped, because the in-place operation |
| * only blits in 0 or 1 bits, not an arbitrary colormap index. |
| * (4) Does a horizontal full-band shear about the line with (+) shear |
| * pushing increasingly leftward (-x) with increasing y. |
| */ |
| l_int32 |
| pixHShearIP(PIX *pixs, |
| l_int32 liney, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| l_int32 sign, w, h; |
| l_int32 y, yincr, inityincr, hshift; |
| l_float32 tanangle, invangle; |
| |
| PROCNAME("pixHShearIP"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) |
| return ERROR_INT("invalid incolor value", procName, 1); |
| if (pixGetColormap(pixs) != NULL) |
| return ERROR_INT("pixs is colormapped", procName, 1); |
| |
| /* Normalize angle */ |
| radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI); |
| if (radang == 0.0 || tan(radang) == 0.0) |
| return 0; |
| |
| sign = L_SIGN(radang); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| tanangle = tan(radang); |
| invangle = L_ABS(1. / tanangle); |
| inityincr = (l_int32)(invangle / 2.); |
| yincr = (l_int32)invangle; |
| |
| pixRasteropHip(pixs, liney - inityincr, 2 * inityincr, 0, incolor); |
| |
| for (hshift = 1, y = liney + inityincr; y < h; hshift++) { |
| yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - liney); |
| if (h - y < yincr) /* reduce for last one if req'd */ |
| yincr = h - y; |
| pixRasteropHip(pixs, y, yincr, -sign*hshift, incolor); |
| y += yincr; |
| } |
| |
| for (hshift = -1, y = liney - inityincr; y > 0; hshift--) { |
| yincr = (y - liney) - (l_int32)(invangle * (hshift - 0.5) + 0.5); |
| if (y < yincr) /* reduce for last one if req'd */ |
| yincr = y; |
| pixRasteropHip(pixs, y - yincr, yincr, -sign*hshift, incolor); |
| y -= yincr; |
| } |
| |
| return 0; |
| } |
| |
| |
| /*! |
| * pixVShearIP() |
| * |
| * Input: pixs (all depths; not colormapped) |
| * linex (location of vertical line, measured from origin) |
| * angle (in radians) |
| * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK); |
| * Return: 0 if OK; 1 on error |
| * |
| * Notes: |
| * (1) This is an in-place version of pixVShear(); see comments there. |
| * (2) This brings in 'incolor' pixels from outside the image. |
| * (3) pixs cannot be colormapped, because the in-place operation |
| * only blits in 0 or 1 bits, not an arbitrary colormap index. |
| * (4) Does a vertical full-band shear about the line with (+) shear |
| * pushing increasingly downward (+y) with increasing x. |
| */ |
| l_int32 |
| pixVShearIP(PIX *pixs, |
| l_int32 linex, |
| l_float32 radang, |
| l_int32 incolor) |
| { |
| l_int32 sign, w, h; |
| l_int32 x, xincr, initxincr, vshift; |
| l_float32 tanangle, invangle; |
| |
| PROCNAME("pixVShearIP"); |
| |
| if (!pixs) |
| return ERROR_INT("pixs not defined", procName, 1); |
| if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) |
| return ERROR_INT("invalid incolor value", procName, 1); |
| if (pixGetColormap(pixs) != NULL) |
| return ERROR_INT("pixs is colormapped", procName, 1); |
| |
| /* Normalize angle */ |
| radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI); |
| if (radang == 0.0 || tan(radang) == 0.0) |
| return 0; |
| |
| sign = L_SIGN(radang); |
| pixGetDimensions(pixs, &w, &h, NULL); |
| tanangle = tan(radang); |
| invangle = L_ABS(1. / tanangle); |
| initxincr = (l_int32)(invangle / 2.); |
| xincr = (l_int32)invangle; |
| |
| pixRasteropVip(pixs, linex - initxincr, 2 * initxincr, 0, incolor); |
| |
| for (vshift = 1, x = linex + initxincr; x < w; vshift++) { |
| xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - linex); |
| if (w - x < xincr) /* reduce for last one if req'd */ |
| xincr = w - x; |
| pixRasteropVip(pixs, x, xincr, sign*vshift, incolor); |
| x += xincr; |
| } |
| |
| for (vshift = -1, x = linex - initxincr; x > 0; vshift--) { |
| xincr = (x - linex) - (l_int32)(invangle * (vshift - 0.5) + 0.5); |
| if (x < xincr) /* reduce for last one if req'd */ |
| xincr = x; |
| pixRasteropVip(pixs, x - xincr, xincr, sign*vshift, incolor); |
| x -= xincr; |
| } |
| |
| return 0; |
| } |
| |
| |
| /*-------------------------------------------------------------------------* |
| * Angle normalization * |
| *-------------------------------------------------------------------------*/ |
| static l_float32 |
| normalizeAngleForShear(l_float32 radang, |
| l_float32 mindist) |
| { |
| l_float32 pi, diff90; |
| |
| PROCNAME("normalizeAngleForShear"); |
| |
| /* Bring angle into range from [-pi, pi] */ |
| pi = 3.14159265; |
| if (radang < -pi || radang > pi) |
| radang = radang - (l_int32)(radang / pi) * pi; |
| |
| /* If angle is too close to pi/2 or -pi/2, move away and issue warning */ |
| diff90 = radang - pi / 2.0; |
| if (L_ABS(diff90) < mindist) |
| L_WARNING("angle close to pi/2; shifting away", procName); |
| if (diff90 > -mindist && diff90 < 0.0) |
| radang = pi / 2.0 - mindist; |
| else if (diff90 >= 0.0 && diff90 < mindist) |
| radang = pi / 2.0 + mindist; |
| diff90 = radang + pi / 2.0; |
| if (L_ABS(diff90) < mindist) |
| L_WARNING("angle close to -pi/2; shifting away", procName); |
| if (diff90 > -mindist && diff90 < 0.0) |
| radang = -pi / 2.0 - mindist; |
| else if (diff90 >= 0.0 && diff90 < mindist) |
| radang = -pi / 2.0 + mindist; |
| |
| return radang; |
| } |
| |