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 }