mirror of http://192.168.1.51:8099/lmh188/twain3.0
597 lines
23 KiB
C
597 lines
23 KiB
C
|
/*====================================================================*
|
||
|
- Copyright (C) 2001 Leptonica. All rights reserved.
|
||
|
-
|
||
|
- Redistribution and use in source and binary forms, with or without
|
||
|
- modification, are permitted provided that the following conditions
|
||
|
- are met:
|
||
|
- 1. Redistributions of source code must retain the above copyright
|
||
|
- notice, this list of conditions and the following disclaimer.
|
||
|
- 2. Redistributions in binary form must reproduce the above
|
||
|
- copyright notice, this list of conditions and the following
|
||
|
- disclaimer in the documentation and/or other materials
|
||
|
- provided with the distribution.
|
||
|
-
|
||
|
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
|
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
|
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY
|
||
|
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||
|
- OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*====================================================================*/
|
||
|
|
||
|
|
||
|
/*!
|
||
|
* \file rotate.c
|
||
|
* <pre>
|
||
|
*
|
||
|
* General rotation about image center
|
||
|
* PIX *pixRotate()
|
||
|
* PIX *pixEmbedForRotation()
|
||
|
*
|
||
|
* General rotation by sampling
|
||
|
* PIX *pixRotateBySampling()
|
||
|
*
|
||
|
* Nice (slow) rotation of 1 bpp image
|
||
|
* PIX *pixRotateBinaryNice()
|
||
|
*
|
||
|
* Rotation including alpha (blend) component
|
||
|
* PIX *pixRotateWithAlpha()
|
||
|
*
|
||
|
* Rotations are measured in radians; clockwise is positive.
|
||
|
*
|
||
|
* The general rotation pixRotate() does the best job for
|
||
|
* rotating about the image center. For 1 bpp, it uses shear;
|
||
|
* for others, it uses either shear or area mapping.
|
||
|
* If requested, it expands the output image so that no pixels are lost
|
||
|
* in the rotation, and this can be done on multiple successive shears
|
||
|
* without expanding beyond the maximum necessary size.
|
||
|
* </pre>
|
||
|
*/
|
||
|
|
||
|
#include <math.h>
|
||
|
#include "allheaders.h"
|
||
|
|
||
|
extern l_float32 AlphaMaskBorderVals[2];
|
||
|
static const l_float32 MinAngleToRotate = 0.001; /* radians; ~0.06 deg */
|
||
|
static const l_float32 Max1BppShearAngle = 0.06; /* radians; ~3 deg */
|
||
|
static const l_float32 LimitShearAngle = 0.35; /* radians; ~20 deg */
|
||
|
|
||
|
|
||
|
/*------------------------------------------------------------------*
|
||
|
* General rotation about the center *
|
||
|
*------------------------------------------------------------------*/
|
||
|
/*!
|
||
|
* \brief pixRotate()
|
||
|
*
|
||
|
* \param[in] pixs 1, 2, 4, 8, 32 bpp rgb
|
||
|
* \param[in] angle radians; clockwise is positive
|
||
|
* \param[in] type L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING
|
||
|
* \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
|
||
|
* \param[in] width original width; use 0 to avoid embedding
|
||
|
* \param[in] height original height; use 0 to avoid embedding
|
||
|
* \return pixd, or NULL on error
|
||
|
*
|
||
|
* <pre>
|
||
|
* Notes:
|
||
|
* (1) This is a high-level, simple interface for rotating images
|
||
|
* about their center.
|
||
|
* (2) For very small rotations, just return a clone.
|
||
|
* (3) Rotation brings either white or black pixels in
|
||
|
* from outside the image.
|
||
|
* (4) The rotation type is adjusted if necessary for the image
|
||
|
* depth and size of rotation angle. For 1 bpp images, we
|
||
|
* rotate either by shear or sampling.
|
||
|
* (5) Colormaps are removed for rotation by area mapping.
|
||
|
* (6) The dest can be expanded so that no image pixels
|
||
|
* are lost. To invoke expansion, input the original
|
||
|
* width and height. For repeated rotation, use of the
|
||
|
* original width and height allows the expansion to
|
||
|
* stop at the maximum required size, which is a square
|
||
|
* with side = sqrt(w*w + h*h).
|
||
|
* </pre>
|
||
|
*/
|
||
|
PIX *
|
||
|
pixRotate(PIX *pixs,
|
||
|
l_float32 angle,
|
||
|
l_int32 type,
|
||
|
l_int32 incolor,
|
||
|
l_int32 width,
|
||
|
l_int32 height)
|
||
|
{
|
||
|
l_int32 w, h, d;
|
||
|
l_uint32 fillval;
|
||
|
PIX *pix1, *pix2, *pix3, *pixd;
|
||
|
PIXCMAP *cmap;
|
||
|
|
||
|
PROCNAME("pixRotate");
|
||
|
|
||
|
if (!pixs)
|
||
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
||
|
if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP &&
|
||
|
type != L_ROTATE_SAMPLING)
|
||
|
return (PIX *)ERROR_PTR("invalid type", procName, NULL);
|
||
|
if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
|
||
|
return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
|
||
|
|
||
|
if (L_ABS(angle) < MinAngleToRotate)
|
||
|
return pixClone(pixs);
|
||
|
|
||
|
/* Adjust rotation type if necessary:
|
||
|
* - If d == 1 bpp and the angle is more than about 6 degrees,
|
||
|
* rotate by sampling; otherwise rotate by shear.
|
||
|
* - If d > 1, only allow shear rotation up to about 20 degrees;
|
||
|
* beyond that, default a shear request to sampling. */
|
||
|
if (pixGetDepth(pixs) == 1) {
|
||
|
if (L_ABS(angle) > Max1BppShearAngle) {
|
||
|
if (type != L_ROTATE_SAMPLING)
|
||
|
L_INFO("1 bpp, large angle; rotate by sampling\n", procName);
|
||
|
type = L_ROTATE_SAMPLING;
|
||
|
} else if (type != L_ROTATE_SHEAR) {
|
||
|
L_INFO("1 bpp; rotate by shear\n", procName);
|
||
|
type = L_ROTATE_SHEAR;
|
||
|
}
|
||
|
} else if (L_ABS(angle) > LimitShearAngle && type == L_ROTATE_SHEAR) {
|
||
|
L_INFO("large angle; rotate by sampling\n", procName);
|
||
|
type = L_ROTATE_SAMPLING;
|
||
|
}
|
||
|
|
||
|
/* Remove colormap if we rotate by area mapping. */
|
||
|
cmap = pixGetColormap(pixs);
|
||
|
if (cmap && type == L_ROTATE_AREA_MAP)
|
||
|
pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
|
||
|
else
|
||
|
pix1 = pixClone(pixs);
|
||
|
cmap = pixGetColormap(pix1);
|
||
|
|
||
|
/* Otherwise, if there is a colormap and we're not embedding,
|
||
|
* add white color if it doesn't exist. */
|
||
|
if (cmap && width == 0) { /* no embedding; generate %incolor */
|
||
|
if (incolor == L_BRING_IN_BLACK)
|
||
|
pixcmapAddBlackOrWhite(cmap, 0, NULL);
|
||
|
else /* L_BRING_IN_WHITE */
|
||
|
pixcmapAddBlackOrWhite(cmap, 1, NULL);
|
||
|
}
|
||
|
|
||
|
/* Request to embed in a larger image; do if necessary */
|
||
|
pix2 = pixEmbedForRotation(pix1, angle, incolor, width, height);
|
||
|
|
||
|
/* Area mapping requires 8 or 32 bpp. If less than 8 bpp and
|
||
|
* area map rotation is requested, convert to 8 bpp. */
|
||
|
d = pixGetDepth(pix2);
|
||
|
if (type == L_ROTATE_AREA_MAP && d < 8)
|
||
|
pix3 = pixConvertTo8(pix2, FALSE);
|
||
|
else
|
||
|
pix3 = pixClone(pix2);
|
||
|
|
||
|
/* Do the rotation: shear, sampling or area mapping */
|
||
|
pixGetDimensions(pix3, &w, &h, &d);
|
||
|
if (type == L_ROTATE_SHEAR) {
|
||
|
pixd = pixRotateShearCenter(pix3, angle, incolor);
|
||
|
} else if (type == L_ROTATE_SAMPLING) {
|
||
|
pixd = pixRotateBySampling(pix3, w / 2, h / 2, angle, incolor);
|
||
|
} else { /* rotate by area mapping */
|
||
|
fillval = 0;
|
||
|
if (incolor == L_BRING_IN_WHITE) {
|
||
|
if (d == 8)
|
||
|
fillval = 255;
|
||
|
else /* d == 32 */
|
||
|
fillval = 0xffffff00;
|
||
|
}
|
||
|
if (d == 8)
|
||
|
pixd = pixRotateAMGray(pix3, angle, fillval);
|
||
|
else /* d == 32 */
|
||
|
pixd = pixRotateAMColor(pix3, angle, fillval);
|
||
|
}
|
||
|
|
||
|
pixDestroy(&pix1);
|
||
|
pixDestroy(&pix2);
|
||
|
pixDestroy(&pix3);
|
||
|
return pixd;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*!
|
||
|
* \brief pixEmbedForRotation()
|
||
|
*
|
||
|
* \param[in] pixs 1, 2, 4, 8, 32 bpp rgb
|
||
|
* \param[in] angle radians; clockwise is positive
|
||
|
* \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
|
||
|
* \param[in] width original width; use 0 to avoid embedding
|
||
|
* \param[in] height original height; use 0 to avoid embedding
|
||
|
* \return pixd, or NULL on error
|
||
|
*
|
||
|
* <pre>
|
||
|
* Notes:
|
||
|
* (1) For very small rotations, just return a clone.
|
||
|
* (2) Generate larger image to embed pixs if necessary, and
|
||
|
* place the center of the input image in the center.
|
||
|
* (3) Rotation brings either white or black pixels in
|
||
|
* from outside the image. For colormapped images where
|
||
|
* there is no white or black, a new color is added if
|
||
|
* possible for these pixels; otherwise, either the
|
||
|
* lightest or darkest color is used. In most cases,
|
||
|
* the colormap will be removed prior to rotation.
|
||
|
* (4) The dest is to be expanded so that no image pixels
|
||
|
* are lost after rotation. Input of the original width
|
||
|
* and height allows the expansion to stop at the maximum
|
||
|
* required size, which is a square with side equal to
|
||
|
* sqrt(w*w + h*h).
|
||
|
* (5) For an arbitrary angle, the expansion can be found by
|
||
|
* considering the UL and UR corners. As the image is
|
||
|
* rotated, these move in an arc centered at the center of
|
||
|
* the image. Normalize to a unit circle by dividing by half
|
||
|
* the image diagonal. After a rotation of T radians, the UL
|
||
|
* and UR corners are at points T radians along the unit
|
||
|
* circle. Compute the x and y coordinates of both these
|
||
|
* points and take the max of absolute values; these represent
|
||
|
* the half width and half height of the containing rectangle.
|
||
|
* The arithmetic is done using formulas for sin(a+b) and cos(a+b),
|
||
|
* where b = T. For the UR corner, sin(a) = h/d and cos(a) = w/d.
|
||
|
* For the UL corner, replace a by (pi - a), and you have
|
||
|
* sin(pi - a) = h/d, cos(pi - a) = -w/d. The equations
|
||
|
* given below follow directly.
|
||
|
* </pre>
|
||
|
*/
|
||
|
PIX *
|
||
|
pixEmbedForRotation(PIX *pixs,
|
||
|
l_float32 angle,
|
||
|
l_int32 incolor,
|
||
|
l_int32 width,
|
||
|
l_int32 height)
|
||
|
{
|
||
|
l_int32 w, h, d, w1, h1, w2, h2, maxside, wnew, hnew, xoff, yoff, setcolor;
|
||
|
l_float64 sina, cosa, fw, fh;
|
||
|
PIX *pixd;
|
||
|
|
||
|
PROCNAME("pixEmbedForRotation");
|
||
|
|
||
|
if (!pixs)
|
||
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
||
|
if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
|
||
|
return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
|
||
|
if (L_ABS(angle) < MinAngleToRotate)
|
||
|
return pixClone(pixs);
|
||
|
|
||
|
/* Test if big enough to hold any rotation of the original image */
|
||
|
pixGetDimensions(pixs, &w, &h, &d);
|
||
|
maxside = (l_int32)(sqrt((l_float64)(width * width) +
|
||
|
(l_float64)(height * height)) + 0.5);
|
||
|
if (w >= maxside && h >= maxside) /* big enough */
|
||
|
return pixClone(pixs);
|
||
|
|
||
|
/* Find the new sizes required to hold the image after rotation.
|
||
|
* Note that the new dimensions must be at least as large as those
|
||
|
* of pixs, because we're rasterop-ing into it before rotation. */
|
||
|
cosa = cos(angle);
|
||
|
sina = sin(angle);
|
||
|
fw = (l_float64)w;
|
||
|
fh = (l_float64)h;
|
||
|
w1 = (l_int32)(L_ABS(fw * cosa - fh * sina) + 0.5);
|
||
|
w2 = (l_int32)(L_ABS(-fw * cosa - fh * sina) + 0.5);
|
||
|
h1 = (l_int32)(L_ABS(fw * sina + fh * cosa) + 0.5);
|
||
|
h2 = (l_int32)(L_ABS(-fw * sina + fh * cosa) + 0.5);
|
||
|
wnew = L_MAX(w, L_MAX(w1, w2));
|
||
|
hnew = L_MAX(h, L_MAX(h1, h2));
|
||
|
|
||
|
if ((pixd = pixCreate(wnew, hnew, d)) == NULL)
|
||
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
||
|
pixCopyResolution(pixd, pixs);
|
||
|
pixCopyColormap(pixd, pixs);
|
||
|
pixCopySpp(pixd, pixs);
|
||
|
pixCopyText(pixd, pixs);
|
||
|
xoff = (wnew - w) / 2;
|
||
|
yoff = (hnew - h) / 2;
|
||
|
|
||
|
/* Set background to color to be rotated in */
|
||
|
setcolor = (incolor == L_BRING_IN_BLACK) ? L_SET_BLACK : L_SET_WHITE;
|
||
|
pixSetBlackOrWhite(pixd, setcolor);
|
||
|
|
||
|
/* Rasterop automatically handles all 4 channels for rgba */
|
||
|
pixRasterop(pixd, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0);
|
||
|
return pixd;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*------------------------------------------------------------------*
|
||
|
* General rotation by sampling *
|
||
|
*------------------------------------------------------------------*/
|
||
|
/*!
|
||
|
* \brief pixRotateBySampling()
|
||
|
*
|
||
|
* \param[in] pixs 1, 2, 4, 8, 16, 32 bpp rgb; can be cmapped
|
||
|
* \param[in] xcen x value of center of rotation
|
||
|
* \param[in] ycen y value of center of rotation
|
||
|
* \param[in] angle radians; clockwise is positive
|
||
|
* \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
|
||
|
* \return pixd, or NULL on error
|
||
|
*
|
||
|
* <pre>
|
||
|
* Notes:
|
||
|
* (1) For very small rotations, just return a clone.
|
||
|
* (2) Rotation brings either white or black pixels in
|
||
|
* from outside the image.
|
||
|
* (3) Colormaps are retained.
|
||
|
* </pre>
|
||
|
*/
|
||
|
PIX *
|
||
|
pixRotateBySampling(PIX *pixs,
|
||
|
l_int32 xcen,
|
||
|
l_int32 ycen,
|
||
|
l_float32 angle,
|
||
|
l_int32 incolor)
|
||
|
{
|
||
|
l_int32 w, h, d, i, j, x, y, xdif, ydif, wm1, hm1, wpld;
|
||
|
l_uint32 val;
|
||
|
l_float32 sina, cosa;
|
||
|
l_uint32 *datad, *lined;
|
||
|
void **lines;
|
||
|
PIX *pixd;
|
||
|
|
||
|
PROCNAME("pixRotateBySampling");
|
||
|
|
||
|
if (!pixs)
|
||
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
||
|
if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
|
||
|
return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
|
||
|
pixGetDimensions(pixs, &w, &h, &d);
|
||
|
if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
|
||
|
return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
|
||
|
|
||
|
if (L_ABS(angle) < MinAngleToRotate)
|
||
|
return pixClone(pixs);
|
||
|
|
||
|
if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL)
|
||
|
return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
|
||
|
pixSetBlackOrWhite(pixd, incolor);
|
||
|
|
||
|
sina = sin(angle);
|
||
|
cosa = cos(angle);
|
||
|
datad = pixGetData(pixd);
|
||
|
wpld = pixGetWpl(pixd);
|
||
|
wm1 = w - 1;
|
||
|
hm1 = h - 1;
|
||
|
lines = pixGetLinePtrs(pixs, NULL);
|
||
|
|
||
|
/* Treat 1 bpp case specially */
|
||
|
if (d == 1) {
|
||
|
for (i = 0; i < h; i++) { /* scan over pixd */
|
||
|
lined = datad + i * wpld;
|
||
|
ydif = ycen - i;
|
||
|
for (j = 0; j < w; j++) {
|
||
|
xdif = xcen - j;
|
||
|
x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
|
||
|
if (x < 0 || x > wm1) continue;
|
||
|
y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
|
||
|
if (y < 0 || y > hm1) continue;
|
||
|
if (incolor == L_BRING_IN_WHITE) {
|
||
|
if (GET_DATA_BIT(lines[y], x))
|
||
|
SET_DATA_BIT(lined, j);
|
||
|
} else {
|
||
|
if (!GET_DATA_BIT(lines[y], x))
|
||
|
CLEAR_DATA_BIT(lined, j);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
LEPT_FREE(lines);
|
||
|
return pixd;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < h; i++) { /* scan over pixd */
|
||
|
lined = datad + i * wpld;
|
||
|
ydif = ycen - i;
|
||
|
for (j = 0; j < w; j++) {
|
||
|
xdif = xcen - j;
|
||
|
x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
|
||
|
if (x < 0 || x > wm1) continue;
|
||
|
y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
|
||
|
if (y < 0 || y > hm1) continue;
|
||
|
switch (d)
|
||
|
{
|
||
|
case 8:
|
||
|
val = GET_DATA_BYTE(lines[y], x);
|
||
|
SET_DATA_BYTE(lined, j, val);
|
||
|
break;
|
||
|
case 32:
|
||
|
val = GET_DATA_FOUR_BYTES(lines[y], x);
|
||
|
SET_DATA_FOUR_BYTES(lined, j, val);
|
||
|
break;
|
||
|
case 2:
|
||
|
val = GET_DATA_DIBIT(lines[y], x);
|
||
|
SET_DATA_DIBIT(lined, j, val);
|
||
|
break;
|
||
|
case 4:
|
||
|
val = GET_DATA_QBIT(lines[y], x);
|
||
|
SET_DATA_QBIT(lined, j, val);
|
||
|
break;
|
||
|
case 16:
|
||
|
val = GET_DATA_TWO_BYTES(lines[y], x);
|
||
|
SET_DATA_TWO_BYTES(lined, j, val);
|
||
|
break;
|
||
|
default:
|
||
|
return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LEPT_FREE(lines);
|
||
|
return pixd;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*------------------------------------------------------------------*
|
||
|
* Nice (slow) rotation of 1 bpp image *
|
||
|
*------------------------------------------------------------------*/
|
||
|
/*!
|
||
|
* \brief pixRotateBinaryNice()
|
||
|
*
|
||
|
* \param[in] pixs 1 bpp
|
||
|
* \param[in] angle radians; clockwise is positive; about the center
|
||
|
* \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK
|
||
|
* \return pixd, or NULL on error
|
||
|
*
|
||
|
* <pre>
|
||
|
* Notes:
|
||
|
* (1) For very small rotations, just return a clone.
|
||
|
* (2) This does a computationally expensive rotation of 1 bpp images.
|
||
|
* The fastest rotators (using shears or subsampling) leave
|
||
|
* visible horizontal and vertical shear lines across which
|
||
|
* the image shear changes by one pixel. To ameliorate the
|
||
|
* visual effect one can introduce random dithering. One
|
||
|
* way to do this in a not-too-random fashion is given here.
|
||
|
* We convert to 8 bpp, do a very small blur, rotate using
|
||
|
* linear interpolation (same as area mapping), do a
|
||
|
* small amount of sharpening to compensate for the initial
|
||
|
* blur, and threshold back to binary. The shear lines
|
||
|
* are magically removed.
|
||
|
* (3) This operation is about 5x slower than rotation by sampling.
|
||
|
* </pre>
|
||
|
*/
|
||
|
PIX *
|
||
|
pixRotateBinaryNice(PIX *pixs,
|
||
|
l_float32 angle,
|
||
|
l_int32 incolor)
|
||
|
{
|
||
|
PIX *pix1, *pix2, *pix3, *pix4, *pixd;
|
||
|
|
||
|
PROCNAME("pixRotateBinaryNice");
|
||
|
|
||
|
if (!pixs || pixGetDepth(pixs) != 1)
|
||
|
return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
|
||
|
if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
|
||
|
return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
|
||
|
|
||
|
pix1 = pixConvertTo8(pixs, 0);
|
||
|
pix2 = pixBlockconv(pix1, 1, 1); /* smallest blur allowed */
|
||
|
pix3 = pixRotateAM(pix2, angle, incolor);
|
||
|
pix4 = pixUnsharpMasking(pix3, 1, 1.0); /* sharpen a bit */
|
||
|
pixd = pixThresholdToBinary(pix4, 128);
|
||
|
pixDestroy(&pix1);
|
||
|
pixDestroy(&pix2);
|
||
|
pixDestroy(&pix3);
|
||
|
pixDestroy(&pix4);
|
||
|
return pixd;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*------------------------------------------------------------------*
|
||
|
* Rotation including alpha (blend) component *
|
||
|
*------------------------------------------------------------------*/
|
||
|
/*!
|
||
|
* \brief pixRotateWithAlpha()
|
||
|
*
|
||
|
* \param[in] pixs 32 bpp rgb or cmapped
|
||
|
* \param[in] angle radians; clockwise is positive
|
||
|
* \param[in] pixg [optional] 8 bpp, can be null
|
||
|
* \param[in] fract between 0.0 and 1.0, with 0.0 fully transparent
|
||
|
* and 1.0 fully opaque
|
||
|
* \return pixd 32 bpp rgba, or NULL on error
|
||
|
*
|
||
|
* <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) Rotation is about the center of the image; for very small
|
||
|
* rotations, just return a clone. The dest is automatically
|
||
|
* expanded so that no image pixels are lost.
|
||
|
* (3) Rotation is by area mapping. It doesn't matter what
|
||
|
* color is brought in because the alpha channel will
|
||
|
* be transparent (black) there.
|
||
|
* (4) If pixg is NULL, it is generated as an alpha layer that is
|
||
|
* partially opaque, using %fract. Otherwise, it is cropped
|
||
|
* to pixs if required and %fract is ignored. The alpha
|
||
|
* channel in pixs is never used.
|
||
|
* (4) Colormaps are removed to 32 bpp.
|
||
|
* (5) The default setting for the border values in the alpha channel
|
||
|
* is 0 (transparent) for the outermost ring of pixels and
|
||
|
* (0.5 * fract * 255) for the second ring. When blended over
|
||
|
* a second image, this
|
||
|
* (a) shrinks the visible image to make a clean overlap edge
|
||
|
* with an image below, and
|
||
|
* (b) softens the edges by weakening the aliasing there.
|
||
|
* Use l_setAlphaMaskBorder() to change these values.
|
||
|
* (6) A subtle use of gamma correction is to remove gamma correction
|
||
|
* before rotation and restore it afterwards. This is done
|
||
|
* by sandwiching this function between a gamma/inverse-gamma
|
||
|
* photometric transform:
|
||
|
* pixt = pixGammaTRCWithAlpha(NULL, pixs, 1.0 / gamma, 0, 255);
|
||
|
* pixd = pixRotateWithAlpha(pixt, angle, NULL, fract);
|
||
|
* pixGammaTRCWithAlpha(pixd, pixd, gamma, 0, 255);
|
||
|
* pixDestroy(&pixt);
|
||
|
* This has the side-effect of producing artifacts in the very
|
||
|
* dark regions.
|
||
|
* </pre>
|
||
|
*/
|
||
|
PIX *
|
||
|
pixRotateWithAlpha(PIX *pixs,
|
||
|
l_float32 angle,
|
||
|
PIX *pixg,
|
||
|
l_float32 fract)
|
||
|
{
|
||
|
l_int32 ws, hs, d, spp;
|
||
|
PIX *pixd, *pix32, *pixg2, *pixgr;
|
||
|
|
||
|
PROCNAME("pixRotateWithAlpha");
|
||
|
|
||
|
if (!pixs)
|
||
|
return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
|
||
|
pixGetDimensions(pixs, &ws, &hs, &d);
|
||
|
if (d != 32 && pixGetColormap(pixs) == NULL)
|
||
|
return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
|
||
|
if (pixg && pixGetDepth(pixg) != 8) {
|
||
|
L_WARNING("pixg not 8 bpp; using 'fract' transparent alpha\n",
|
||
|
procName);
|
||
|
pixg = NULL;
|
||
|
}
|
||
|
if (!pixg && (fract < 0.0 || fract > 1.0)) {
|
||
|
L_WARNING("invalid fract; using fully opaque\n", procName);
|
||
|
fract = 1.0;
|
||
|
}
|
||
|
if (!pixg && fract == 0.0)
|
||
|
L_WARNING("transparent alpha; image will not be blended\n", procName);
|
||
|
|
||
|
/* Make sure input to rotation is 32 bpp rgb, and rotate it */
|
||
|
if (d != 32)
|
||
|
pix32 = pixConvertTo32(pixs);
|
||
|
else
|
||
|
pix32 = pixClone(pixs);
|
||
|
spp = pixGetSpp(pix32);
|
||
|
pixSetSpp(pix32, 3); /* ignore the alpha channel for the rotation */
|
||
|
pixd = pixRotate(pix32, angle, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, ws, hs);
|
||
|
pixSetSpp(pix32, spp); /* restore initial value in case it's a clone */
|
||
|
pixDestroy(&pix32);
|
||
|
|
||
|
/* Set up alpha layer with a fading border and rotate it */
|
||
|
if (!pixg) {
|
||
|
pixg2 = pixCreate(ws, hs, 8);
|
||
|
if (fract == 1.0)
|
||
|
pixSetAll(pixg2);
|
||
|
else if (fract > 0.0)
|
||
|
pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract));
|
||
|
} else {
|
||
|
pixg2 = pixResizeToMatch(pixg, NULL, ws, hs);
|
||
|
}
|
||
|
if (ws > 10 && hs > 10) { /* see note 8 */
|
||
|
pixSetBorderRingVal(pixg2, 1,
|
||
|
(l_int32)(255.0 * fract * AlphaMaskBorderVals[0]));
|
||
|
pixSetBorderRingVal(pixg2, 2,
|
||
|
(l_int32)(255.0 * fract * AlphaMaskBorderVals[1]));
|
||
|
}
|
||
|
pixgr = pixRotate(pixg2, angle, L_ROTATE_AREA_MAP,
|
||
|
L_BRING_IN_BLACK, ws, hs);
|
||
|
|
||
|
/* Combine into a 4 spp result */
|
||
|
pixSetRGBComponent(pixd, pixgr, L_ALPHA_CHANNEL);
|
||
|
|
||
|
pixDestroy(&pixg2);
|
||
|
pixDestroy(&pixgr);
|
||
|
return pixd;
|
||
|
}
|