Coverage Report

Created: 2024-07-27 06:27

/src/leptonica/src/rotate.c
Line
Count
Source (jump to first uncovered line)
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
0
{
108
0
l_int32    w, h, d;
109
0
l_uint32   fillval;
110
0
PIX       *pix1, *pix2, *pix3, *pixd;
111
0
PIXCMAP   *cmap;
112
113
0
    if (!pixs)
114
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
115
0
    if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP &&
116
0
        type != L_ROTATE_SAMPLING)
117
0
        return (PIX *)ERROR_PTR("invalid type", __func__, NULL);
118
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
119
0
        return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL);
120
121
0
    if (L_ABS(angle) < MinAngleToRotate)
122
0
        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
0
    if (pixGetDepth(pixs) == 1) {
130
0
        if (L_ABS(angle) > Max1BppShearAngle) {
131
0
            if (type != L_ROTATE_SAMPLING)
132
0
                L_INFO("1 bpp, large angle; rotate by sampling\n", __func__);
133
0
            type = L_ROTATE_SAMPLING;
134
0
        } else if (type != L_ROTATE_SHEAR) {
135
0
            L_INFO("1 bpp; rotate by shear\n", __func__);
136
0
            type = L_ROTATE_SHEAR;
137
0
        }
138
0
    } else if (L_ABS(angle) > LimitShearAngle && type == L_ROTATE_SHEAR) {
139
0
        L_INFO("large angle; rotate by sampling\n", __func__);
140
0
        type = L_ROTATE_SAMPLING;
141
0
    }
142
143
        /* Remove colormap if we rotate by area mapping. */
144
0
    cmap = pixGetColormap(pixs);
145
0
    if (cmap && type == L_ROTATE_AREA_MAP)
146
0
        pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
147
0
    else
148
0
        pix1 = pixClone(pixs);
149
0
    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
0
    if (cmap && width == 0) {  /* no embedding; generate %incolor */
154
0
        if (incolor == L_BRING_IN_BLACK)
155
0
            pixcmapAddBlackOrWhite(cmap, 0, NULL);
156
0
        else  /* L_BRING_IN_WHITE */
157
0
            pixcmapAddBlackOrWhite(cmap, 1, NULL);
158
0
    }
159
160
        /* Request to embed in a larger image; do if necessary */
161
0
    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
0
    d = pixGetDepth(pix2);
166
0
    if (type == L_ROTATE_AREA_MAP && d < 8)
167
0
        pix3 = pixConvertTo8(pix2, FALSE);
168
0
    else
169
0
        pix3 = pixClone(pix2);
170
171
        /* Do the rotation: shear, sampling or area mapping */
172
0
    pixGetDimensions(pix3, &w, &h, &d);
173
0
    if (type == L_ROTATE_SHEAR) {
174
0
        pixd = pixRotateShearCenter(pix3, angle, incolor);
175
0
    } else if (type == L_ROTATE_SAMPLING) {
176
0
        pixd = pixRotateBySampling(pix3, w / 2, h / 2, angle, incolor);
177
0
    } else {  /* rotate by area mapping */
178
0
        fillval = 0;
179
0
        if (incolor == L_BRING_IN_WHITE) {
180
0
            if (d == 8)
181
0
                fillval = 255;
182
0
            else  /* d == 32 */
183
0
                fillval = 0xffffff00;
184
0
        }
185
0
        if (d == 8)
186
0
            pixd = pixRotateAMGray(pix3, angle, fillval);
187
0
        else  /* d == 32 */
188
0
            pixd = pixRotateAMColor(pix3, angle, fillval);
189
0
    }
190
191
0
    pixDestroy(&pix1);
192
0
    pixDestroy(&pix2);
193
0
    pixDestroy(&pix3);
194
0
    return pixd;
195
0
}
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
0
{
247
0
l_int32    w, h, d, w1, h1, w2, h2, maxside, wnew, hnew, xoff, yoff, setcolor;
248
0
l_float64  sina, cosa, fw, fh;
249
0
PIX       *pixd;
250
251
0
    if (!pixs)
252
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
253
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
254
0
        return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL);
255
0
    if (L_ABS(angle) < MinAngleToRotate)
256
0
        return pixClone(pixs);
257
258
        /* Test if big enough to hold any rotation of the original image */
259
0
    pixGetDimensions(pixs, &w, &h, &d);
260
0
    maxside = (l_int32)(sqrt((l_float64)(width * width) +
261
0
                             (l_float64)(height * height)) + 0.5);
262
0
    if (w >= maxside && h >= maxside)  /* big enough */
263
0
        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
0
    cosa = cos(angle);
269
0
    sina = sin(angle);
270
0
    fw = (l_float64)w;
271
0
    fh = (l_float64)h;
272
0
    w1 = (l_int32)(L_ABS(fw * cosa - fh * sina) + 0.5);
273
0
    w2 = (l_int32)(L_ABS(-fw * cosa - fh * sina) + 0.5);
274
0
    h1 = (l_int32)(L_ABS(fw * sina + fh * cosa) + 0.5);
275
0
    h2 = (l_int32)(L_ABS(-fw * sina + fh * cosa) + 0.5);
276
0
    wnew = L_MAX(w, L_MAX(w1, w2));
277
0
    hnew = L_MAX(h, L_MAX(h1, h2));
278
279
0
    if ((pixd = pixCreate(wnew, hnew, d)) == NULL)
280
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
281
0
    pixCopyResolution(pixd, pixs);
282
0
    pixCopyColormap(pixd, pixs);
283
0
    pixCopySpp(pixd, pixs);
284
0
    pixCopyText(pixd, pixs);
285
0
    xoff = (wnew - w) / 2;
286
0
    yoff = (hnew - h) / 2;
287
288
        /* Set background to color to be rotated in */
289
0
    setcolor = (incolor == L_BRING_IN_BLACK) ? L_SET_BLACK : L_SET_WHITE;
290
0
    pixSetBlackOrWhite(pixd, setcolor);
291
292
        /* Rasterop automatically handles all 4 channels for rgba */
293
0
    pixRasterop(pixd, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0);
294
0
    return pixd;
295
0
}
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
0
{
326
0
l_int32    w, h, d, i, j, x, y, xdif, ydif, wm1, hm1, wpld;
327
0
l_uint32   val;
328
0
l_float32  sina, cosa;
329
0
l_uint32  *datad, *lined;
330
0
void     **lines;
331
0
PIX       *pixd;
332
333
0
    if (!pixs)
334
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
335
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
336
0
        return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL);
337
0
    pixGetDimensions(pixs, &w, &h, &d);
338
0
    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
339
0
        return (PIX *)ERROR_PTR("invalid depth", __func__, NULL);
340
341
0
    if (L_ABS(angle) < MinAngleToRotate)
342
0
        return pixClone(pixs);
343
344
0
    if ((pixd = pixCreateTemplate(pixs)) == NULL)
345
0
        return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
346
0
    pixSetBlackOrWhite(pixd, incolor);
347
348
0
    sina = sin(angle);
349
0
    cosa = cos(angle);
350
0
    datad = pixGetData(pixd);
351
0
    wpld = pixGetWpl(pixd);
352
0
    wm1 = w - 1;
353
0
    hm1 = h - 1;
354
0
    lines = pixGetLinePtrs(pixs, NULL);
355
356
        /* Treat 1 bpp case specially */
357
0
    if (d == 1) {
358
0
        for (i = 0; i < h; i++) {  /* scan over pixd */
359
0
            lined = datad + i * wpld;
360
0
            ydif = ycen - i;
361
0
            for (j = 0; j < w; j++) {
362
0
                xdif = xcen - j;
363
0
                x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
364
0
                if (x < 0 || x > wm1) continue;
365
0
                y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
366
0
                if (y < 0 || y > hm1) continue;
367
0
                if (incolor == L_BRING_IN_WHITE) {
368
0
                    if (GET_DATA_BIT(lines[y], x))
369
0
                        SET_DATA_BIT(lined, j);
370
0
                } else {
371
0
                    if (!GET_DATA_BIT(lines[y], x))
372
0
                        CLEAR_DATA_BIT(lined, j);
373
0
                }
374
0
            }
375
0
        }
376
0
        LEPT_FREE(lines);
377
0
        return pixd;
378
0
    }
379
380
0
    for (i = 0; i < h; i++) {  /* scan over pixd */
381
0
        lined = datad + i * wpld;
382
0
        ydif = ycen - i;
383
0
        for (j = 0; j < w; j++) {
384
0
            xdif = xcen - j;
385
0
            x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
386
0
            if (x < 0 || x > wm1) continue;
387
0
            y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
388
0
            if (y < 0 || y > hm1) continue;
389
0
            switch (d)
390
0
            {
391
0
            case 8:
392
0
                val = GET_DATA_BYTE(lines[y], x);
393
0
                SET_DATA_BYTE(lined, j, val);
394
0
                break;
395
0
            case 32:
396
0
                val = GET_DATA_FOUR_BYTES(lines[y], x);
397
0
                SET_DATA_FOUR_BYTES(lined, j, val);
398
0
                break;
399
0
            case 2:
400
0
                val = GET_DATA_DIBIT(lines[y], x);
401
0
                SET_DATA_DIBIT(lined, j, val);
402
0
                break;
403
0
            case 4:
404
0
                val = GET_DATA_QBIT(lines[y], x);
405
0
                SET_DATA_QBIT(lined, j, val);
406
0
                break;
407
0
            case 16:
408
0
                val = GET_DATA_TWO_BYTES(lines[y], x);
409
0
                SET_DATA_TWO_BYTES(lined, j, val);
410
0
                break;
411
0
            default:
412
0
                return (PIX *)ERROR_PTR("invalid depth", __func__, NULL);
413
0
            }
414
0
        }
415
0
    }
416
417
0
    LEPT_FREE(lines);
418
0
    return pixd;
419
0
}
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
0
{
455
0
PIX  *pix1, *pix2, *pix3, *pix4, *pixd;
456
457
0
    if (!pixs || pixGetDepth(pixs) != 1)
458
0
        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", __func__, NULL);
459
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
460
0
        return (PIX *)ERROR_PTR("invalid incolor", __func__, NULL);
461
462
0
    pix1 = pixConvertTo8(pixs, 0);
463
0
    pix2 = pixBlockconv(pix1, 1, 1);  /* smallest blur allowed */
464
0
    pix3 = pixRotateAM(pix2, angle, incolor);
465
0
    pix4 = pixUnsharpMasking(pix3, 1, 1.0);  /* sharpen a bit */
466
0
    pixd = pixThresholdToBinary(pix4, 128);
467
0
    pixDestroy(&pix1);
468
0
    pixDestroy(&pix2);
469
0
    pixDestroy(&pix3);
470
0
    pixDestroy(&pix4);
471
0
    return pixd;
472
0
}
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
0
{
532
0
l_int32  ws, hs, d, spp;
533
0
PIX     *pixd, *pix32, *pixg2, *pixgr;
534
535
0
    if (!pixs)
536
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
537
0
    pixGetDimensions(pixs, &ws, &hs, &d);
538
0
    if (d != 32 && pixGetColormap(pixs) == NULL)
539
0
        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", __func__, NULL);
540
0
    if (pixg && pixGetDepth(pixg) != 8) {
541
0
        L_WARNING("pixg not 8 bpp; using 'fract' transparent alpha\n",
542
0
                  __func__);
543
0
        pixg = NULL;
544
0
    }
545
0
    if (!pixg && (fract < 0.0 || fract > 1.0)) {
546
0
        L_WARNING("invalid fract; using fully opaque\n", __func__);
547
0
        fract = 1.0;
548
0
    }
549
0
    if (!pixg && fract == 0.0)
550
0
        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
0
    if (d != 32)
554
0
        pix32 = pixConvertTo32(pixs);
555
0
    else
556
0
        pix32 = pixClone(pixs);
557
0
    spp = pixGetSpp(pix32);
558
0
    pixSetSpp(pix32, 3);  /* ignore the alpha channel for the rotation */
559
0
    pixd = pixRotate(pix32, angle, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, ws, hs);
560
0
    pixSetSpp(pix32, spp);  /* restore initial value in case it's a clone */
561
0
    pixDestroy(&pix32);
562
563
        /* Set up alpha layer with a fading border and rotate it */
564
0
    if (!pixg) {
565
0
        pixg2 = pixCreate(ws, hs, 8);
566
0
        if (fract == 1.0)
567
0
            pixSetAll(pixg2);
568
0
        else if (fract > 0.0)
569
0
            pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract));
570
0
    } else {
571
0
        pixg2 = pixResizeToMatch(pixg, NULL, ws, hs);
572
0
    }
573
0
    if (ws > 10 && hs > 10) {  /* see note 8 */
574
0
        pixSetBorderRingVal(pixg2, 1,
575
0
                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[0]));
576
0
        pixSetBorderRingVal(pixg2, 2,
577
0
                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[1]));
578
0
    }
579
0
    pixgr = pixRotate(pixg2, angle, L_ROTATE_AREA_MAP,
580
0
                      L_BRING_IN_BLACK, ws, hs);
581
582
        /* Combine into a 4 spp result */
583
0
    pixSetRGBComponent(pixd, pixgr, L_ALPHA_CHANNEL);
584
585
0
    pixDestroy(&pixg2);
586
0
    pixDestroy(&pixgr);
587
0
    return pixd;
588
0
}