Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/thirdparty/leptonica/src/rotate.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 |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 /*====================================================================* | |
| 2 - Copyright (C) 2001 Leptonica. All rights reserved. | |
| 3 - | |
| 4 - Redistribution and use in source and binary forms, with or without | |
| 5 - modification, are permitted provided that the following conditions | |
| 6 - are met: | |
| 7 - 1. Redistributions of source code must retain the above copyright | |
| 8 - notice, this list of conditions and the following disclaimer. | |
| 9 - 2. Redistributions in binary form must reproduce the above | |
| 10 - copyright notice, this list of conditions and the following | |
| 11 - disclaimer in the documentation and/or other materials | |
| 12 - provided with the distribution. | |
| 13 - | |
| 14 - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY | |
| 18 - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 19 - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 20 - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 21 - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 22 - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 23 - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 24 - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 *====================================================================*/ | |
| 26 | |
| 27 /*! | |
| 28 * \file rotate.c | |
| 29 * <pre> | |
| 30 * | |
| 31 * General rotation about image center | |
| 32 * PIX *pixRotate() | |
| 33 * PIX *pixEmbedForRotation() | |
| 34 * | |
| 35 * General rotation by sampling | |
| 36 * PIX *pixRotateBySampling() | |
| 37 * | |
| 38 * Nice (slow) rotation of 1 bpp image | |
| 39 * PIX *pixRotateBinaryNice() | |
| 40 * | |
| 41 * Rotation including alpha (blend) component | |
| 42 * PIX *pixRotateWithAlpha() | |
| 43 * | |
| 44 * Rotations are measured in radians; clockwise is positive. | |
| 45 * | |
| 46 * The general rotation pixRotate() does the best job for | |
| 47 * rotating about the image center. For 1 bpp, it uses shear; | |
| 48 * for others, it uses either shear or area mapping. | |
| 49 * If requested, it expands the output image so that no pixels are lost | |
| 50 * in the rotation, and this can be done on multiple successive shears | |
| 51 * without expanding beyond the maximum necessary size. | |
| 52 * </pre> | |
| 53 */ | |
| 54 | |
| 55 #ifdef HAVE_CONFIG_H | |
| 56 #include <config_auto.h> | |
| 57 #endif /* HAVE_CONFIG_H */ | |
| 58 | |
| 59 #include <math.h> | |
| 60 #include "allheaders.h" | |
| 61 | |
| 62 extern l_float32 AlphaMaskBorderVals[2]; | |
| 63 static const l_float32 MinAngleToRotate = 0.001f; /* radians; ~0.06 deg */ | |
| 64 static const l_float32 Max1BppShearAngle = 0.06f; /* radians; ~3 deg */ | |
| 65 static const l_float32 LimitShearAngle = 0.35f; /* radians; ~20 deg */ | |
| 66 | |
| 67 /*------------------------------------------------------------------* | |
| 68 * General rotation about the center * | |
| 69 *------------------------------------------------------------------*/ | |
| 70 /*! | |
| 71 * \brief pixRotate() | |
| 72 * | |
| 73 * \param[in] pixs 1, 2, 4, 8, 32 bpp rgb | |
| 74 * \param[in] angle radians; clockwise is positive | |
| 75 * \param[in] type L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING | |
| 76 * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK | |
| 77 * \param[in] width original width; use 0 to avoid embedding | |
| 78 * \param[in] height original height; use 0 to avoid embedding | |
| 79 * \return pixd, or NULL on error | |
| 80 * | |
| 81 * <pre> | |
| 82 * Notes: | |
| 83 * (1) This is a high-level, simple interface for rotating images | |
| 84 * about their center. | |
| 85 * (2) For very small rotations, just return a clone. | |
| 86 * (3) Rotation brings either white or black pixels in | |
| 87 * from outside the image. | |
| 88 * (4) The rotation type is adjusted if necessary for the image | |
| 89 * depth and size of rotation angle. For 1 bpp images, we | |
| 90 * rotate either by shear or sampling. | |
| 91 * (5) Colormaps are removed for rotation by area mapping. | |
| 92 * (6) The dest can be expanded so that no image pixels | |
| 93 * are lost. To invoke expansion, input the original | |
| 94 * width and height. For repeated rotation, use of the | |
| 95 * original width and height allows the expansion to | |
| 96 * stop at the maximum required size, which is a square | |
| 97 * with side = sqrt(w*w + h*h). | |
| 98 * </pre> | |
| 99 */ | |
| 100 PIX * | |
| 101 pixRotate(PIX *pixs, | |
| 102 l_float32 angle, | |
| 103 l_int32 type, | |
| 104 l_int32 incolor, | |
| 105 l_int32 width, | |
| 106 l_int32 height) | |
| 107 { | |
| 108 l_int32 w, h, d; | |
| 109 l_uint32 fillval; | |
| 110 PIX *pix1, *pix2, *pix3, *pixd; | |
| 111 PIXCMAP *cmap; | |
| 112 | |
| 113 if (!pixs) | |
| 114 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 115 if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP && | |
| 116 type != L_ROTATE_SAMPLING) | |
| 117 return (PIX *)ERROR_PTR("invalid type", __func__, NULL); | |
| 118 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) | |
| 119 return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL); | |
| 120 | |
| 121 if (L_ABS(angle) < MinAngleToRotate) | |
| 122 return pixClone(pixs); | |
| 123 | |
| 124 /* Adjust rotation type if necessary: | |
| 125 * - If d == 1 bpp and the angle is more than about 6 degrees, | |
| 126 * rotate by sampling; otherwise rotate by shear. | |
| 127 * - If d > 1, only allow shear rotation up to about 20 degrees; | |
| 128 * beyond that, default a shear request to sampling. */ | |
| 129 if (pixGetDepth(pixs) == 1) { | |
| 130 if (L_ABS(angle) > Max1BppShearAngle) { | |
| 131 if (type != L_ROTATE_SAMPLING) | |
| 132 L_INFO("1 bpp, large angle; rotate by sampling\n", __func__); | |
| 133 type = L_ROTATE_SAMPLING; | |
| 134 } else if (type != L_ROTATE_SHEAR) { | |
| 135 L_INFO("1 bpp; rotate by shear\n", __func__); | |
| 136 type = L_ROTATE_SHEAR; | |
| 137 } | |
| 138 } else if (L_ABS(angle) > LimitShearAngle && type == L_ROTATE_SHEAR) { | |
| 139 L_INFO("large angle; rotate by sampling\n", __func__); | |
| 140 type = L_ROTATE_SAMPLING; | |
| 141 } | |
| 142 | |
| 143 /* Remove colormap if we rotate by area mapping. */ | |
| 144 cmap = pixGetColormap(pixs); | |
| 145 if (cmap && type == L_ROTATE_AREA_MAP) | |
| 146 pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC); | |
| 147 else | |
| 148 pix1 = pixClone(pixs); | |
| 149 cmap = pixGetColormap(pix1); | |
| 150 | |
| 151 /* Otherwise, if there is a colormap and we're not embedding, | |
| 152 * add white color if it doesn't exist. */ | |
| 153 if (cmap && width == 0) { /* no embedding; generate %incolor */ | |
| 154 if (incolor == L_BRING_IN_BLACK) | |
| 155 pixcmapAddBlackOrWhite(cmap, 0, NULL); | |
| 156 else /* L_BRING_IN_WHITE */ | |
| 157 pixcmapAddBlackOrWhite(cmap, 1, NULL); | |
| 158 } | |
| 159 | |
| 160 /* Request to embed in a larger image; do if necessary */ | |
| 161 pix2 = pixEmbedForRotation(pix1, angle, incolor, width, height); | |
| 162 | |
| 163 /* Area mapping requires 8 or 32 bpp. If less than 8 bpp and | |
| 164 * area map rotation is requested, convert to 8 bpp. */ | |
| 165 d = pixGetDepth(pix2); | |
| 166 if (type == L_ROTATE_AREA_MAP && d < 8) | |
| 167 pix3 = pixConvertTo8(pix2, FALSE); | |
| 168 else | |
| 169 pix3 = pixClone(pix2); | |
| 170 | |
| 171 /* Do the rotation: shear, sampling or area mapping */ | |
| 172 pixGetDimensions(pix3, &w, &h, &d); | |
| 173 if (type == L_ROTATE_SHEAR) { | |
| 174 pixd = pixRotateShearCenter(pix3, angle, incolor); | |
| 175 } else if (type == L_ROTATE_SAMPLING) { | |
| 176 pixd = pixRotateBySampling(pix3, w / 2, h / 2, angle, incolor); | |
| 177 } else { /* rotate by area mapping */ | |
| 178 fillval = 0; | |
| 179 if (incolor == L_BRING_IN_WHITE) { | |
| 180 if (d == 8) | |
| 181 fillval = 255; | |
| 182 else /* d == 32 */ | |
| 183 fillval = 0xffffff00; | |
| 184 } | |
| 185 if (d == 8) | |
| 186 pixd = pixRotateAMGray(pix3, angle, fillval); | |
| 187 else /* d == 32 */ | |
| 188 pixd = pixRotateAMColor(pix3, angle, fillval); | |
| 189 } | |
| 190 | |
| 191 pixDestroy(&pix1); | |
| 192 pixDestroy(&pix2); | |
| 193 pixDestroy(&pix3); | |
| 194 return pixd; | |
| 195 } | |
| 196 | |
| 197 | |
| 198 /*! | |
| 199 * \brief pixEmbedForRotation() | |
| 200 * | |
| 201 * \param[in] pixs 1, 2, 4, 8, 32 bpp rgb | |
| 202 * \param[in] angle radians; clockwise is positive | |
| 203 * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK | |
| 204 * \param[in] width original width; use 0 to avoid embedding | |
| 205 * \param[in] height original height; use 0 to avoid embedding | |
| 206 * \return pixd, or NULL on error | |
| 207 * | |
| 208 * <pre> | |
| 209 * Notes: | |
| 210 * (1) For very small rotations, just return a clone. | |
| 211 * (2) Generate larger image to embed pixs if necessary, and | |
| 212 * place the center of the input image in the center. | |
| 213 * (3) Rotation brings either white or black pixels in | |
| 214 * from outside the image. For colormapped images where | |
| 215 * there is no white or black, a new color is added if | |
| 216 * possible for these pixels; otherwise, either the | |
| 217 * lightest or darkest color is used. In most cases, | |
| 218 * the colormap will be removed prior to rotation. | |
| 219 * (4) The dest is to be expanded so that no image pixels | |
| 220 * are lost after rotation. Input of the original width | |
| 221 * and height allows the expansion to stop at the maximum | |
| 222 * required size, which is a square with side equal to | |
| 223 * sqrt(w*w + h*h). | |
| 224 * (5) For an arbitrary angle, the expansion can be found by | |
| 225 * considering the UL and UR corners. As the image is | |
| 226 * rotated, these move in an arc centered at the center of | |
| 227 * the image. Normalize to a unit circle by dividing by half | |
| 228 * the image diagonal. After a rotation of T radians, the UL | |
| 229 * and UR corners are at points T radians along the unit | |
| 230 * circle. Compute the x and y coordinates of both these | |
| 231 * points and take the max of absolute values; these represent | |
| 232 * the half width and half height of the containing rectangle. | |
| 233 * The arithmetic is done using formulas for sin(a+b) and cos(a+b), | |
| 234 * where b = T. For the UR corner, sin(a) = h/d and cos(a) = w/d. | |
| 235 * For the UL corner, replace a by (pi - a), and you have | |
| 236 * sin(pi - a) = h/d, cos(pi - a) = -w/d. The equations | |
| 237 * given below follow directly. | |
| 238 * </pre> | |
| 239 */ | |
| 240 PIX * | |
| 241 pixEmbedForRotation(PIX *pixs, | |
| 242 l_float32 angle, | |
| 243 l_int32 incolor, | |
| 244 l_int32 width, | |
| 245 l_int32 height) | |
| 246 { | |
| 247 l_int32 w, h, d, w1, h1, w2, h2, maxside, wnew, hnew, xoff, yoff, setcolor; | |
| 248 l_float64 sina, cosa, fw, fh; | |
| 249 PIX *pixd; | |
| 250 | |
| 251 if (!pixs) | |
| 252 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 253 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) | |
| 254 return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL); | |
| 255 if (L_ABS(angle) < MinAngleToRotate) | |
| 256 return pixClone(pixs); | |
| 257 | |
| 258 /* Test if big enough to hold any rotation of the original image */ | |
| 259 pixGetDimensions(pixs, &w, &h, &d); | |
| 260 maxside = (l_int32)(sqrt((l_float64)(width * width) + | |
| 261 (l_float64)(height * height)) + 0.5); | |
| 262 if (w >= maxside && h >= maxside) /* big enough */ | |
| 263 return pixClone(pixs); | |
| 264 | |
| 265 /* Find the new sizes required to hold the image after rotation. | |
| 266 * Note that the new dimensions must be at least as large as those | |
| 267 * of pixs, because we're rasterop-ing into it before rotation. */ | |
| 268 cosa = cos(angle); | |
| 269 sina = sin(angle); | |
| 270 fw = (l_float64)w; | |
| 271 fh = (l_float64)h; | |
| 272 w1 = (l_int32)(L_ABS(fw * cosa - fh * sina) + 0.5); | |
| 273 w2 = (l_int32)(L_ABS(-fw * cosa - fh * sina) + 0.5); | |
| 274 h1 = (l_int32)(L_ABS(fw * sina + fh * cosa) + 0.5); | |
| 275 h2 = (l_int32)(L_ABS(-fw * sina + fh * cosa) + 0.5); | |
| 276 wnew = L_MAX(w, L_MAX(w1, w2)); | |
| 277 hnew = L_MAX(h, L_MAX(h1, h2)); | |
| 278 | |
| 279 if ((pixd = pixCreate(wnew, hnew, d)) == NULL) | |
| 280 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 281 pixCopyResolution(pixd, pixs); | |
| 282 pixCopyColormap(pixd, pixs); | |
| 283 pixCopySpp(pixd, pixs); | |
| 284 pixCopyText(pixd, pixs); | |
| 285 xoff = (wnew - w) / 2; | |
| 286 yoff = (hnew - h) / 2; | |
| 287 | |
| 288 /* Set background to color to be rotated in */ | |
| 289 setcolor = (incolor == L_BRING_IN_BLACK) ? L_SET_BLACK : L_SET_WHITE; | |
| 290 pixSetBlackOrWhite(pixd, setcolor); | |
| 291 | |
| 292 /* Rasterop automatically handles all 4 channels for rgba */ | |
| 293 pixRasterop(pixd, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0); | |
| 294 return pixd; | |
| 295 } | |
| 296 | |
| 297 | |
| 298 /*------------------------------------------------------------------* | |
| 299 * General rotation by sampling * | |
| 300 *------------------------------------------------------------------*/ | |
| 301 /*! | |
| 302 * \brief pixRotateBySampling() | |
| 303 * | |
| 304 * \param[in] pixs 1, 2, 4, 8, 16, 32 bpp rgb; can be cmapped | |
| 305 * \param[in] xcen x value of center of rotation | |
| 306 * \param[in] ycen y value of center of rotation | |
| 307 * \param[in] angle radians; clockwise is positive | |
| 308 * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK | |
| 309 * \return pixd, or NULL on error | |
| 310 * | |
| 311 * <pre> | |
| 312 * Notes: | |
| 313 * (1) For very small rotations, just return a clone. | |
| 314 * (2) Rotation brings either white or black pixels in | |
| 315 * from outside the image. | |
| 316 * (3) Colormaps are retained. | |
| 317 * </pre> | |
| 318 */ | |
| 319 PIX * | |
| 320 pixRotateBySampling(PIX *pixs, | |
| 321 l_int32 xcen, | |
| 322 l_int32 ycen, | |
| 323 l_float32 angle, | |
| 324 l_int32 incolor) | |
| 325 { | |
| 326 l_int32 w, h, d, i, j, x, y, xdif, ydif, wm1, hm1, wpld; | |
| 327 l_uint32 val; | |
| 328 l_float32 sina, cosa; | |
| 329 l_uint32 *datad, *lined; | |
| 330 void **lines; | |
| 331 PIX *pixd; | |
| 332 | |
| 333 if (!pixs) | |
| 334 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 335 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) | |
| 336 return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL); | |
| 337 pixGetDimensions(pixs, &w, &h, &d); | |
| 338 if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) | |
| 339 return (PIX *)ERROR_PTR("invalid depth", __func__, NULL); | |
| 340 | |
| 341 if (L_ABS(angle) < MinAngleToRotate) | |
| 342 return pixClone(pixs); | |
| 343 | |
| 344 if ((pixd = pixCreateTemplate(pixs)) == NULL) | |
| 345 return (PIX *)ERROR_PTR("pixd not made", __func__, NULL); | |
| 346 pixSetBlackOrWhite(pixd, incolor); | |
| 347 | |
| 348 sina = sin(angle); | |
| 349 cosa = cos(angle); | |
| 350 datad = pixGetData(pixd); | |
| 351 wpld = pixGetWpl(pixd); | |
| 352 wm1 = w - 1; | |
| 353 hm1 = h - 1; | |
| 354 lines = pixGetLinePtrs(pixs, NULL); | |
| 355 | |
| 356 /* Treat 1 bpp case specially */ | |
| 357 if (d == 1) { | |
| 358 for (i = 0; i < h; i++) { /* scan over pixd */ | |
| 359 lined = datad + i * wpld; | |
| 360 ydif = ycen - i; | |
| 361 for (j = 0; j < w; j++) { | |
| 362 xdif = xcen - j; | |
| 363 x = xcen + (l_int32)(-xdif * cosa - ydif * sina); | |
| 364 if (x < 0 || x > wm1) continue; | |
| 365 y = ycen + (l_int32)(-ydif * cosa + xdif * sina); | |
| 366 if (y < 0 || y > hm1) continue; | |
| 367 if (incolor == L_BRING_IN_WHITE) { | |
| 368 if (GET_DATA_BIT(lines[y], x)) | |
| 369 SET_DATA_BIT(lined, j); | |
| 370 } else { | |
| 371 if (!GET_DATA_BIT(lines[y], x)) | |
| 372 CLEAR_DATA_BIT(lined, j); | |
| 373 } | |
| 374 } | |
| 375 } | |
| 376 LEPT_FREE(lines); | |
| 377 return pixd; | |
| 378 } | |
| 379 | |
| 380 for (i = 0; i < h; i++) { /* scan over pixd */ | |
| 381 lined = datad + i * wpld; | |
| 382 ydif = ycen - i; | |
| 383 for (j = 0; j < w; j++) { | |
| 384 xdif = xcen - j; | |
| 385 x = xcen + (l_int32)(-xdif * cosa - ydif * sina); | |
| 386 if (x < 0 || x > wm1) continue; | |
| 387 y = ycen + (l_int32)(-ydif * cosa + xdif * sina); | |
| 388 if (y < 0 || y > hm1) continue; | |
| 389 switch (d) | |
| 390 { | |
| 391 case 8: | |
| 392 val = GET_DATA_BYTE(lines[y], x); | |
| 393 SET_DATA_BYTE(lined, j, val); | |
| 394 break; | |
| 395 case 32: | |
| 396 val = GET_DATA_FOUR_BYTES(lines[y], x); | |
| 397 SET_DATA_FOUR_BYTES(lined, j, val); | |
| 398 break; | |
| 399 case 2: | |
| 400 val = GET_DATA_DIBIT(lines[y], x); | |
| 401 SET_DATA_DIBIT(lined, j, val); | |
| 402 break; | |
| 403 case 4: | |
| 404 val = GET_DATA_QBIT(lines[y], x); | |
| 405 SET_DATA_QBIT(lined, j, val); | |
| 406 break; | |
| 407 case 16: | |
| 408 val = GET_DATA_TWO_BYTES(lines[y], x); | |
| 409 SET_DATA_TWO_BYTES(lined, j, val); | |
| 410 break; | |
| 411 default: | |
| 412 return (PIX *)ERROR_PTR("invalid depth", __func__, NULL); | |
| 413 } | |
| 414 } | |
| 415 } | |
| 416 | |
| 417 LEPT_FREE(lines); | |
| 418 return pixd; | |
| 419 } | |
| 420 | |
| 421 | |
| 422 /*------------------------------------------------------------------* | |
| 423 * Nice (slow) rotation of 1 bpp image * | |
| 424 *------------------------------------------------------------------*/ | |
| 425 /*! | |
| 426 * \brief pixRotateBinaryNice() | |
| 427 * | |
| 428 * \param[in] pixs 1 bpp | |
| 429 * \param[in] angle radians; clockwise is positive; about the center | |
| 430 * \param[in] incolor L_BRING_IN_WHITE, L_BRING_IN_BLACK | |
| 431 * \return pixd, or NULL on error | |
| 432 * | |
| 433 * <pre> | |
| 434 * Notes: | |
| 435 * (1) For very small rotations, just return a clone. | |
| 436 * (2) This does a computationally expensive rotation of 1 bpp images. | |
| 437 * The fastest rotators (using shears or subsampling) leave | |
| 438 * visible horizontal and vertical shear lines across which | |
| 439 * the image shear changes by one pixel. To ameliorate the | |
| 440 * visual effect one can introduce random dithering. One | |
| 441 * way to do this in a not-too-random fashion is given here. | |
| 442 * We convert to 8 bpp, do a very small blur, rotate using | |
| 443 * linear interpolation (same as area mapping), do a | |
| 444 * small amount of sharpening to compensate for the initial | |
| 445 * blur, and threshold back to binary. The shear lines | |
| 446 * are magically removed. | |
| 447 * (3) This operation is about 5x slower than rotation by sampling. | |
| 448 * </pre> | |
| 449 */ | |
| 450 PIX * | |
| 451 pixRotateBinaryNice(PIX *pixs, | |
| 452 l_float32 angle, | |
| 453 l_int32 incolor) | |
| 454 { | |
| 455 PIX *pix1, *pix2, *pix3, *pix4, *pixd; | |
| 456 | |
| 457 if (!pixs || pixGetDepth(pixs) != 1) | |
| 458 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL); | |
| 459 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK) | |
| 460 return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL); | |
| 461 | |
| 462 pix1 = pixConvertTo8(pixs, 0); | |
| 463 pix2 = pixBlockconv(pix1, 1, 1); /* smallest blur allowed */ | |
| 464 pix3 = pixRotateAM(pix2, angle, incolor); | |
| 465 pix4 = pixUnsharpMasking(pix3, 1, 1.0); /* sharpen a bit */ | |
| 466 pixd = pixThresholdToBinary(pix4, 128); | |
| 467 pixDestroy(&pix1); | |
| 468 pixDestroy(&pix2); | |
| 469 pixDestroy(&pix3); | |
| 470 pixDestroy(&pix4); | |
| 471 return pixd; | |
| 472 } | |
| 473 | |
| 474 | |
| 475 /*------------------------------------------------------------------* | |
| 476 * Rotation including alpha (blend) component * | |
| 477 *------------------------------------------------------------------*/ | |
| 478 /*! | |
| 479 * \brief pixRotateWithAlpha() | |
| 480 * | |
| 481 * \param[in] pixs 32 bpp rgb or cmapped | |
| 482 * \param[in] angle radians; clockwise is positive | |
| 483 * \param[in] pixg [optional] 8 bpp, can be null | |
| 484 * \param[in] fract between 0.0 and 1.0, with 0.0 fully transparent | |
| 485 * and 1.0 fully opaque | |
| 486 * \return pixd 32 bpp rgba, or NULL on error | |
| 487 * | |
| 488 * <pre> | |
| 489 * Notes: | |
| 490 * (1) The alpha channel is transformed separately from pixs, | |
| 491 * and aligns with it, being fully transparent outside the | |
| 492 * boundary of the transformed pixs. For pixels that are fully | |
| 493 * transparent, a blending function like pixBlendWithGrayMask() | |
| 494 * will give zero weight to corresponding pixels in pixs. | |
| 495 * (2) Rotation is about the center of the image; for very small | |
| 496 * rotations, just return a clone. The dest is automatically | |
| 497 * expanded so that no image pixels are lost. | |
| 498 * (3) Rotation is by area mapping. It doesn't matter what | |
| 499 * color is brought in because the alpha channel will | |
| 500 * be transparent (black) there. | |
| 501 * (4) If pixg is NULL, it is generated as an alpha layer that is | |
| 502 * partially opaque, using %fract. Otherwise, it is cropped | |
| 503 * to pixs if required and %fract is ignored. The alpha | |
| 504 * channel in pixs is never used. | |
| 505 * (4) Colormaps are removed to 32 bpp. | |
| 506 * (5) The default setting for the border values in the alpha channel | |
| 507 * is 0 (transparent) for the outermost ring of pixels and | |
| 508 * (0.5 * fract * 255) for the second ring. When blended over | |
| 509 * a second image, this | |
| 510 * (a) shrinks the visible image to make a clean overlap edge | |
| 511 * with an image below, and | |
| 512 * (b) softens the edges by weakening the aliasing there. | |
| 513 * Use l_setAlphaMaskBorder() to change these values. | |
| 514 * (6) A subtle use of gamma correction is to remove gamma correction | |
| 515 * before rotation and restore it afterwards. This is done | |
| 516 * by sandwiching this function between a gamma/inverse-gamma | |
| 517 * photometric transform: | |
| 518 * pixt = pixGammaTRCWithAlpha(NULL, pixs, 1.0 / gamma, 0, 255); | |
| 519 * pixd = pixRotateWithAlpha(pixt, angle, NULL, fract); | |
| 520 * pixGammaTRCWithAlpha(pixd, pixd, gamma, 0, 255); | |
| 521 * pixDestroy(&pixt); | |
| 522 * This has the side-effect of producing artifacts in the very | |
| 523 * dark regions. | |
| 524 * </pre> | |
| 525 */ | |
| 526 PIX * | |
| 527 pixRotateWithAlpha(PIX *pixs, | |
| 528 l_float32 angle, | |
| 529 PIX *pixg, | |
| 530 l_float32 fract) | |
| 531 { | |
| 532 l_int32 ws, hs, d, spp; | |
| 533 PIX *pixd, *pix32, *pixg2, *pixgr; | |
| 534 | |
| 535 if (!pixs) | |
| 536 return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); | |
| 537 pixGetDimensions(pixs, &ws, &hs, &d); | |
| 538 if (d != 32 && pixGetColormap(pixs) == NULL) | |
| 539 return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", __func__, NULL); | |
| 540 if (pixg && pixGetDepth(pixg) != 8) { | |
| 541 L_WARNING("pixg not 8 bpp; using 'fract' transparent alpha\n", | |
| 542 __func__); | |
| 543 pixg = NULL; | |
| 544 } | |
| 545 if (!pixg && (fract < 0.0 || fract > 1.0)) { | |
| 546 L_WARNING("invalid fract; using fully opaque\n", __func__); | |
| 547 fract = 1.0; | |
| 548 } | |
| 549 if (!pixg && fract == 0.0) | |
| 550 L_WARNING("transparent alpha; image will not be blended\n", __func__); | |
| 551 | |
| 552 /* Make sure input to rotation is 32 bpp rgb, and rotate it */ | |
| 553 if (d != 32) | |
| 554 pix32 = pixConvertTo32(pixs); | |
| 555 else | |
| 556 pix32 = pixClone(pixs); | |
| 557 spp = pixGetSpp(pix32); | |
| 558 pixSetSpp(pix32, 3); /* ignore the alpha channel for the rotation */ | |
| 559 pixd = pixRotate(pix32, angle, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, ws, hs); | |
| 560 pixSetSpp(pix32, spp); /* restore initial value in case it's a clone */ | |
| 561 pixDestroy(&pix32); | |
| 562 | |
| 563 /* Set up alpha layer with a fading border and rotate it */ | |
| 564 if (!pixg) { | |
| 565 pixg2 = pixCreate(ws, hs, 8); | |
| 566 if (fract == 1.0) | |
| 567 pixSetAll(pixg2); | |
| 568 else if (fract > 0.0) | |
| 569 pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract)); | |
| 570 } else { | |
| 571 pixg2 = pixResizeToMatch(pixg, NULL, ws, hs); | |
| 572 } | |
| 573 if (ws > 10 && hs > 10) { /* see note 8 */ | |
| 574 pixSetBorderRingVal(pixg2, 1, | |
| 575 (l_int32)(255.0 * fract * AlphaMaskBorderVals[0])); | |
| 576 pixSetBorderRingVal(pixg2, 2, | |
| 577 (l_int32)(255.0 * fract * AlphaMaskBorderVals[1])); | |
| 578 } | |
| 579 pixgr = pixRotate(pixg2, angle, L_ROTATE_AREA_MAP, | |
| 580 L_BRING_IN_BLACK, ws, hs); | |
| 581 | |
| 582 /* Combine into a 4 spp result */ | |
| 583 pixSetRGBComponent(pixd, pixgr, L_ALPHA_CHANNEL); | |
| 584 | |
| 585 pixDestroy(&pixg2); | |
| 586 pixDestroy(&pixgr); | |
| 587 return pixd; | |
| 588 } |
