Coverage Report

Created: 2024-06-18 06:05

/src/leptonica/src/shear.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
/*!
29
 * \file shear.c
30
 * <pre>
31
 *
32
 *    About arbitrary lines
33
 *           PIX      *pixHShear()
34
 *           PIX      *pixVShear()
35
 *
36
 *    About special 'points': UL corner and center
37
 *           PIX      *pixHShearCorner()
38
 *           PIX      *pixVShearCorner()
39
 *           PIX      *pixHShearCenter()
40
 *           PIX      *pixVShearCenter()
41
 *
42
 *    In place about arbitrary lines
43
 *           l_int32   pixHShearIP()
44
 *           l_int32   pixVShearIP()
45
 *
46
 *    Linear interpolated shear about arbitrary lines
47
 *           PIX      *pixHShearLI()
48
 *           PIX      *pixVShearLI()
49
 *
50
 *    Static helper
51
 *      static l_float32  normalizeAngleForShear()
52
 * </pre>
53
 */
54
55
#ifdef HAVE_CONFIG_H
56
#include <config_auto.h>
57
#endif  /* HAVE_CONFIG_H */
58
59
#include <string.h>
60
#include <math.h>
61
#include "allheaders.h"
62
63
    /* Shear angle must not get too close to -pi/2 or pi/2 */
64
static const l_float32   MinDiffFromHalfPi = 0.04f;
65
66
static l_float32 normalizeAngleForShear(l_float32 radang, l_float32 mindif);
67
68
69
#ifndef  NO_CONSOLE_IO
70
#define  DEBUG     0
71
#endif  /* ~NO_CONSOLE_IO */
72
73
74
/*-------------------------------------------------------------*
75
 *                    About arbitrary lines                    *
76
 *-------------------------------------------------------------*/
77
/*!
78
 * \brief   pixHShear()
79
 *
80
 * \param[in]    pixd      [optional] this can be null, equal to pixs,
81
 *                         or different from pixs
82
 * \param[in]    pixs      any depth; cmap ok
83
 * \param[in]    yloc      location of horizontal line, measured from origin
84
 * \param[in]    radang    angle in radians
85
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
86
 * \return  pixd, always
87
 *
88
 * <pre>
89
 * Notes:
90
 *      (1) There are 3 cases:
91
 *            (a) pixd == null (make a new pixd)
92
 *            (b) pixd == pixs (in-place)
93
 *            (c) pixd != pixs
94
 *      (2) For these three cases, use these patterns, respectively:
95
 *              pixd = pixHShear(NULL, pixs, ...);
96
 *              pixHShear(pixs, pixs, ...);
97
 *              pixHShear(pixd, pixs, ...);
98
 *      (3) This shear leaves the horizontal line of pixels at y = yloc
99
 *          invariant.  For a positive shear angle, pixels above this
100
 *          line are shoved to the right, and pixels below this line
101
 *          move to the left.
102
 *      (4) With positive shear angle, this can be used, along with
103
 *          pixVShear(), to perform a cw rotation, either with 2 shears
104
 *          (for small angles) or in the general case with 3 shears.
105
 *      (5) Changing the value of yloc is equivalent to translating
106
 *          the result horizontally.
107
 *      (6) This brings in %incolor pixels from outside the image.
108
 *      (7) In-place shears do not work on cmapped pix, because the
109
 *          in-place operation cannot initialize to the requested %incolor,
110
 *          so we shear from a copy.
111
 *      (8) The angle is brought into the range [-pi, -pi].  It is
112
 *          not permitted to be within MinDiffFromHalfPi radians
113
 *          from either -pi/2 or pi/2.
114
 * </pre>
115
 */
116
PIX *
117
pixHShear(PIX       *pixd,
118
          PIX       *pixs,
119
          l_int32    yloc,
120
          l_float32  radang,
121
          l_int32    incolor)
122
131
{
123
131
l_int32    sign, w, h;
124
131
l_int32    y, yincr, inityincr, hshift;
125
131
l_float32  tanangle, invangle;
126
127
131
    if (!pixs)
128
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
129
131
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
130
0
        return (PIX *)ERROR_PTR("invalid incolor value", __func__, pixd);
131
132
131
    if (pixd == pixs) {  /* in place */
133
0
        if (!pixGetColormap(pixs)) {
134
0
            pixHShearIP(pixd, yloc, radang, incolor);
135
0
        } else {  /* can't do in-place with a colormap */
136
0
            PIX *pix1 = pixCopy(NULL, pixs);
137
0
            pixHShear(pixd, pix1, yloc, radang, incolor);
138
0
            pixDestroy(&pix1);
139
0
        }
140
0
        return pixd;
141
0
    }
142
143
        /* Make sure pixd exists and is same size as pixs */
144
131
    if (!pixd) {
145
131
        if ((pixd = pixCreateTemplate(pixs)) == NULL)
146
0
            return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
147
131
    } else {  /* pixd != pixs */
148
0
        pixResizeImageData(pixd, pixs);
149
0
    }
150
151
        /* Normalize angle.  If no rotation, return a copy */
152
131
    radang = normalizeAngleForShear(radang, MinDiffFromHalfPi);
153
131
    if (radang == 0.0 || tan(radang) == 0.0)
154
0
        return pixCopy(pixd, pixs);
155
156
        /* Initialize to value of incoming pixels */
157
131
    pixSetBlackOrWhite(pixd, incolor);
158
159
131
    pixGetDimensions(pixs, &w, &h, NULL);
160
131
    sign = L_SIGN(radang);
161
131
    tanangle = tan(radang);
162
131
    invangle = L_ABS(1. / tanangle);
163
131
    inityincr = (l_int32)(invangle / 2.);
164
131
    yincr = (l_int32)invangle;
165
131
    pixRasterop(pixd, 0, yloc - inityincr, w, 2 * inityincr, PIX_SRC,
166
131
                pixs, 0, yloc - inityincr);
167
168
240
    for (hshift = 1, y = yloc + inityincr; y < h; hshift++) {
169
109
        yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - yloc);
170
109
        if (h - y < yincr)  /* reduce for last one if req'd */
171
25
            yincr = h - y;
172
109
        pixRasterop(pixd, -sign*hshift, y, w, yincr, PIX_SRC, pixs, 0, y);
173
#if DEBUG
174
        lept_stderr("y = %d, hshift = %d, yincr = %d\n", y, hshift, yincr);
175
#endif /* DEBUG */
176
109
        y += yincr;
177
109
    }
178
179
240
    for (hshift = -1, y = yloc - inityincr; y > 0; hshift--) {
180
109
        yincr = (y - yloc) - (l_int32)(invangle * (hshift - 0.5) + 0.5);
181
109
        if (y < yincr)  /* reduce for last one if req'd */
182
25
            yincr = y;
183
109
        pixRasterop(pixd, -sign*hshift, y - yincr, w, yincr, PIX_SRC,
184
109
            pixs, 0, y - yincr);
185
#if DEBUG
186
        lept_stderr("y = %d, hshift = %d, yincr = %d\n",
187
                y - yincr, hshift, yincr);
188
#endif /* DEBUG */
189
109
        y -= yincr;
190
109
    }
191
192
131
    return pixd;
193
131
}
194
195
196
/*!
197
 * \brief   pixVShear()
198
 *
199
 * \param[in]    pixd      [optional], this can be null, equal to pixs,
200
 *                         or different from pixs
201
 * \param[in]    pixs      any depth; cmap ok
202
 * \param[in]    xloc      location of vertical line, measured from origin
203
 * \param[in]    radang    angle in radians; not too close to +-(pi / 2)
204
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
205
 * \return  pixd, or NULL on error
206
 *
207
 * <pre>
208
 * Notes:
209
 *      (1) There are 3 cases:
210
 *            (a) pixd == null (make a new pixd)
211
 *            (b) pixd == pixs (in-place)
212
 *            (c) pixd != pixs
213
 *      (2) For these three cases, use these patterns, respectively:
214
 *              pixd = pixVShear(NULL, pixs, ...);
215
 *              pixVShear(pixs, pixs, ...);
216
 *              pixVShear(pixd, pixs, ...);
217
 *      (3) This shear leaves the vertical line of pixels at x = xloc
218
 *          invariant.  For a positive shear angle, pixels to the right
219
 *          of this line are shoved downward, and pixels to the left
220
 *          of the line move upward.
221
 *      (4) With positive shear angle, this can be used, along with
222
 *          pixHShear(), to perform a cw rotation, either with 2 shears
223
 *          (for small angles) or in the general case with 3 shears.
224
 *      (5) Changing the value of xloc is equivalent to translating
225
 *          the result vertically.
226
 *      (6) This brings in %incolor pixels from outside the image.
227
 *      (7) In-place shears do not work on cmapped pix, because the
228
 *          in-place operation cannot initialize to the requested %incolor,
229
 *          so we shear from a copy.
230
 *      (8) The angle is brought into the range [-pi, -pi].  It is
231
 *          not permitted to be within MinDiffFromHalfPi radians
232
 *          from either -pi/2 or pi/2.
233
 * </pre>
234
 */
235
PIX *
236
pixVShear(PIX       *pixd,
237
          PIX       *pixs,
238
          l_int32    xloc,
239
          l_float32  radang,
240
          l_int32    incolor)
241
88.0k
{
242
88.0k
l_int32    sign, w, h;
243
88.0k
l_int32    x, xincr, initxincr, vshift;
244
88.0k
l_float32  tanangle, invangle;
245
246
88.0k
    if (!pixs)
247
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
248
88.0k
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
249
0
        return (PIX *)ERROR_PTR("invalid incolor value", __func__, NULL);
250
251
88.0k
    if (pixd == pixs) {  /* in place */
252
0
        if (!pixGetColormap(pixs)) {
253
0
            pixVShearIP(pixd, xloc, radang, incolor);
254
0
        } else {  /* can't do in-place with a colormap */
255
0
            PIX *pix1 = pixCopy(NULL, pixs);
256
0
            pixVShear(pixd, pix1, xloc, radang, incolor);
257
0
            pixDestroy(&pix1);
258
0
        }
259
0
        return pixd;
260
0
    }
261
262
        /* Make sure pixd exists and is same size as pixs */
263
88.0k
    if (!pixd) {
264
131
        if ((pixd = pixCreateTemplate(pixs)) == NULL)
265
0
            return (PIX *)ERROR_PTR("pixd not made", __func__, NULL);
266
87.9k
    } else {  /* pixd != pixs */
267
87.9k
        pixResizeImageData(pixd, pixs);
268
87.9k
    }
269
270
        /* Normalize angle.  If no rotation, return a copy */
271
88.0k
    radang = normalizeAngleForShear(radang, MinDiffFromHalfPi);
272
88.0k
    if (radang == 0.0 || tan(radang) == 0.0)
273
4.12k
        return pixCopy(pixd, pixs);
274
275
        /* Initialize to value of incoming pixels */
276
83.9k
    pixSetBlackOrWhite(pixd, incolor);
277
278
83.9k
    pixGetDimensions(pixs, &w, &h, NULL);
279
83.9k
    sign = L_SIGN(radang);
280
83.9k
    tanangle = tan(radang);
281
83.9k
    invangle = L_ABS(1. / tanangle);
282
83.9k
    initxincr = (l_int32)(invangle / 2.);
283
83.9k
    xincr = (l_int32)invangle;
284
83.9k
    pixRasterop(pixd, xloc - initxincr, 0, 2 * initxincr, h, PIX_SRC,
285
83.9k
                pixs, xloc - initxincr, 0);
286
287
381k
    for (vshift = 1, x = xloc + initxincr; x < w; vshift++) {
288
297k
        xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - xloc);
289
297k
        if (w - x < xincr)  /* reduce for last one if req'd */
290
49.5k
            xincr = w - x;
291
297k
        pixRasterop(pixd, x, sign*vshift, xincr, h, PIX_SRC, pixs, x, 0);
292
#if DEBUG
293
        lept_stderr("x = %d, vshift = %d, xincr = %d\n", x, vshift, xincr);
294
#endif /* DEBUG */
295
297k
        x += xincr;
296
297k
    }
297
298
84.1k
    for (vshift = -1, x = xloc - initxincr; x > 0; vshift--) {
299
222
        xincr = (x - xloc) - (l_int32)(invangle * (vshift - 0.5) + 0.5);
300
222
        if (x < xincr)  /* reduce for last one if req'd */
301
68
            xincr = x;
302
222
        pixRasterop(pixd, x - xincr, sign*vshift, xincr, h, PIX_SRC,
303
222
            pixs, x - xincr, 0);
304
#if DEBUG
305
        lept_stderr("x = %d, vshift = %d, xincr = %d\n",
306
                x - xincr, vshift, xincr);
307
#endif /* DEBUG */
308
222
        x -= xincr;
309
222
    }
310
311
83.9k
    return pixd;
312
88.0k
}
313
314
315
316
/*-------------------------------------------------------------*
317
 *             Shears about UL corner and center               *
318
 *-------------------------------------------------------------*/
319
/*!
320
 * \brief   pixHShearCorner()
321
 *
322
 * \param[in]    pixd      [optional], if not null, must be equal to pixs
323
 * \param[in]    pixs      any depth
324
 * \param[in]    radang    angle in radians
325
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
326
 * \return  pixd, or NULL on error.
327
 *
328
 * <pre>
329
 * Notes:
330
 *      (1) See pixHShear() for usage.
331
 *      (2) This does a horizontal shear about the UL corner, with (+) shear
332
 *          pushing increasingly leftward (-x) with increasing y.
333
 * </pre>
334
 */
335
PIX *
336
pixHShearCorner(PIX       *pixd,
337
                PIX       *pixs,
338
                l_float32  radang,
339
                l_int32    incolor)
340
0
{
341
0
    if (!pixs)
342
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
343
344
0
    return pixHShear(pixd, pixs, 0, radang, incolor);
345
0
}
346
347
348
/*!
349
 * \brief   pixVShearCorner()
350
 *
351
 * \param[in]    pixd      [optional], if not null, must be equal to pixs
352
 * \param[in]    pixs      any depth
353
 * \param[in]    radang    angle in radians
354
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
355
 * \return  pixd, or NULL on error.
356
 *
357
 * <pre>
358
 * Notes:
359
 *      (1) See pixVShear() for usage.
360
 *      (2) This does a vertical shear about the UL corner, with (+) shear
361
 *          pushing increasingly downward (+y) with increasing x.
362
 * </pre>
363
 */
364
PIX *
365
pixVShearCorner(PIX       *pixd,
366
                PIX       *pixs,
367
                l_float32  radang,
368
                l_int32    incolor)
369
87.9k
{
370
87.9k
    if (!pixs)
371
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
372
373
87.9k
    return pixVShear(pixd, pixs, 0, radang, incolor);
374
87.9k
}
375
376
377
/*!
378
 * \brief   pixHShearCenter()
379
 *
380
 * \param[in]    pixd      [optional] if not null, must be equal to pixs
381
 * \param[in]    pixs      any depth
382
 * \param[in]    radang    angle in radians
383
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
384
 * \return  pixd, or NULL on error.
385
 *
386
 * <pre>
387
 * Notes:
388
 *      (1) See pixHShear() for usage.
389
 *      (2) This does a horizontal shear about the center, with (+) shear
390
 *          pushing increasingly leftward (-x) with increasing y.
391
 * </pre>
392
 */
393
PIX *
394
pixHShearCenter(PIX       *pixd,
395
                PIX       *pixs,
396
                l_float32  radang,
397
                l_int32    incolor)
398
0
{
399
0
    if (!pixs)
400
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
401
402
0
    return pixHShear(pixd, pixs, pixGetHeight(pixs) / 2, radang, incolor);
403
0
}
404
405
406
/*!
407
 * \brief   pixVShearCenter()
408
 *
409
 * \param[in]    pixd      [optional] if not null, must be equal to pixs
410
 * \param[in]    pixs      any depth
411
 * \param[in]    radang    angle in radians
412
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
413
 * \return  pixd, or NULL on error.
414
 *
415
 * <pre>
416
 * Notes:
417
 *      (1) See pixVShear() for usage.
418
 *      (2) This does a vertical shear about the center, with (+) shear
419
 *          pushing increasingly downward (+y) with increasing x.
420
 * </pre>
421
 */
422
PIX *
423
pixVShearCenter(PIX       *pixd,
424
                PIX       *pixs,
425
                l_float32  radang,
426
                l_int32    incolor)
427
0
{
428
0
    if (!pixs)
429
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd);
430
431
0
    return pixVShear(pixd, pixs, pixGetWidth(pixs) / 2, radang, incolor);
432
0
}
433
434
435
436
/*--------------------------------------------------------------------------*
437
 *                       In place about arbitrary lines                     *
438
 *--------------------------------------------------------------------------*/
439
/*!
440
 * \brief   pixHShearIP()
441
 *
442
 * \param[in]    pixs      any depth; no cmap
443
 * \param[in]    yloc      location of horizontal line, measured from origin
444
 * \param[in]    radang    angle in radians
445
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
446
 * \return  0 if OK; 1 on error
447
 *
448
 * <pre>
449
 * Notes:
450
 *      (1) This is an in-place version of pixHShear(); see comments there.
451
 *      (2) This brings in 'incolor' pixels from outside the image.
452
 *      (3) pixs cannot be colormapped, because the in-place operation
453
 *          only blits in 0 or 1 bits, not an arbitrary colormap index.
454
 *      (4) Does a horizontal full-band shear about the line with (+) shear
455
 *          pushing increasingly leftward (-x) with increasing y.
456
 * </pre>
457
 */
458
l_ok
459
pixHShearIP(PIX       *pixs,
460
            l_int32    yloc,
461
            l_float32  radang,
462
            l_int32    incolor)
463
0
{
464
0
l_int32    sign, w, h;
465
0
l_int32    y, yincr, inityincr, hshift;
466
0
l_float32  tanangle, invangle;
467
468
0
    if (!pixs)
469
0
        return ERROR_INT("pixs not defined", __func__, 1);
470
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
471
0
        return ERROR_INT("invalid incolor value", __func__, 1);
472
0
    if (pixGetColormap(pixs))
473
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
474
475
        /* Normalize angle */
476
0
    radang = normalizeAngleForShear(radang, MinDiffFromHalfPi);
477
0
    if (radang == 0.0 || tan(radang) == 0.0)
478
0
        return 0;
479
480
0
    sign = L_SIGN(radang);
481
0
    pixGetDimensions(pixs, &w, &h, NULL);
482
0
    tanangle = tan(radang);
483
0
    invangle = L_ABS(1. / tanangle);
484
0
    inityincr = (l_int32)(invangle / 2.);
485
0
    yincr = (l_int32)invangle;
486
487
0
    if (inityincr > 0)
488
0
        pixRasteropHip(pixs, yloc - inityincr, 2 * inityincr, 0, incolor);
489
490
0
    for (hshift = 1, y = yloc + inityincr; y < h; hshift++) {
491
0
        yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - yloc);
492
0
        if (yincr == 0) continue;
493
0
        if (h - y < yincr)  /* reduce for last one if req'd */
494
0
            yincr = h - y;
495
0
        pixRasteropHip(pixs, y, yincr, -sign*hshift, incolor);
496
0
        y += yincr;
497
0
    }
498
499
0
    for (hshift = -1, y = yloc - inityincr; y > 0; hshift--) {
500
0
        yincr = (y - yloc) - (l_int32)(invangle * (hshift - 0.5) + 0.5);
501
0
        if (yincr == 0) continue;
502
0
        if (y < yincr)  /* reduce for last one if req'd */
503
0
            yincr = y;
504
0
        pixRasteropHip(pixs, y - yincr, yincr, -sign*hshift, incolor);
505
0
        y -= yincr;
506
0
    }
507
508
0
    return 0;
509
0
}
510
511
512
/*!
513
 * \brief   pixVShearIP()
514
 *
515
 * \param[in]    pixs      any depth; no cmap
516
 * \param[in]    xloc      location of vertical line, measured from origin
517
 * \param[in]    radang    angle in radians
518
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
519
 * \return  0 if OK; 1 on error
520
 *
521
 * <pre>
522
 * Notes:
523
 *      (1) This is an in-place version of pixVShear(); see comments there.
524
 *      (2) This brings in 'incolor' pixels from outside the image.
525
 *      (3) pixs cannot be colormapped, because the in-place operation
526
 *          only blits in 0 or 1 bits, not an arbitrary colormap index.
527
 *      (4) Does a vertical full-band shear about the line with (+) shear
528
 *          pushing increasingly downward (+y) with increasing x.
529
 * </pre>
530
 */
531
l_ok
532
pixVShearIP(PIX       *pixs,
533
            l_int32    xloc,
534
            l_float32  radang,
535
            l_int32    incolor)
536
0
{
537
0
l_int32    sign, w, h;
538
0
l_int32    x, xincr, initxincr, vshift;
539
0
l_float32  tanangle, invangle;
540
541
0
    if (!pixs)
542
0
        return ERROR_INT("pixs not defined", __func__, 1);
543
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
544
0
        return ERROR_INT("invalid incolor value", __func__, 1);
545
0
    if (pixGetColormap(pixs))
546
0
        return ERROR_INT("pixs is colormapped", __func__, 1);
547
548
        /* Normalize angle */
549
0
    radang = normalizeAngleForShear(radang, MinDiffFromHalfPi);
550
0
    if (radang == 0.0 || tan(radang) == 0.0)
551
0
        return 0;
552
553
0
    sign = L_SIGN(radang);
554
0
    pixGetDimensions(pixs, &w, &h, NULL);
555
0
    tanangle = tan(radang);
556
0
    invangle = L_ABS(1. / tanangle);
557
0
    initxincr = (l_int32)(invangle / 2.);
558
0
    xincr = (l_int32)invangle;
559
560
0
    if (initxincr > 0)
561
0
        pixRasteropVip(pixs, xloc - initxincr, 2 * initxincr, 0, incolor);
562
563
0
    for (vshift = 1, x = xloc + initxincr; x < w; vshift++) {
564
0
        xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - xloc);
565
0
        if (xincr == 0) continue;
566
0
        if (w - x < xincr)  /* reduce for last one if req'd */
567
0
            xincr = w - x;
568
0
        pixRasteropVip(pixs, x, xincr, sign*vshift, incolor);
569
0
        x += xincr;
570
0
    }
571
572
0
    for (vshift = -1, x = xloc - initxincr; x > 0; vshift--) {
573
0
        xincr = (x - xloc) - (l_int32)(invangle * (vshift - 0.5) + 0.5);
574
0
        if (xincr == 0) continue;
575
0
        if (x < xincr)  /* reduce for last one if req'd */
576
0
            xincr = x;
577
0
        pixRasteropVip(pixs, x - xincr, xincr, sign*vshift, incolor);
578
0
        x -= xincr;
579
0
    }
580
581
0
    return 0;
582
0
}
583
584
585
/*-------------------------------------------------------------------------*
586
 *              Linear interpolated shear about arbitrary lines            *
587
 *-------------------------------------------------------------------------*/
588
/*!
589
 * \brief   pixHShearLI()
590
 *
591
 * \param[in]    pixs      8 bpp or 32 bpp, or colormapped
592
 * \param[in]    yloc      location of horizontal line, measured from origin
593
 * \param[in]    radang    angle in radians, in range (-pi/2 ... pi/2)
594
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
595
 * \return  pixd sheared, or NULL on error
596
 *
597
 * <pre>
598
 * Notes:
599
 *      (1) This does horizontal shear with linear interpolation for
600
 *          accurate results on 8 bpp gray, 32 bpp rgb, or cmapped images.
601
 *          It is relatively slow compared to the sampled version
602
 *          implemented by rasterop, but the result is much smoother.
603
 *      (2) This shear leaves the horizontal line of pixels at y = yloc
604
 *          invariant.  For a positive shear angle, pixels above this
605
 *          line are shoved to the right, and pixels below this line
606
 *          move to the left.
607
 *      (3) Any colormap is removed.
608
 *      (4) The angle is brought into the range [-pi/2 + del, pi/2 - del],
609
 *          where del == MinDiffFromHalfPi.
610
 * </pre>
611
 */
612
PIX *
613
pixHShearLI(PIX       *pixs,
614
            l_int32    yloc,
615
            l_float32  radang,
616
            l_int32    incolor)
617
0
{
618
0
l_int32    i, jd, x, xp, xf, w, h, d, wm, wpls, wpld, val, rval, gval, bval;
619
0
l_uint32   word0, word1;
620
0
l_uint32  *datas, *datad, *lines, *lined;
621
0
l_float32  tanangle, xshift;
622
0
PIX       *pix, *pixd;
623
624
0
    if (!pixs)
625
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
626
0
    pixGetDimensions(pixs, &w, &h, &d);
627
0
    if (d != 8 && d != 32 && !pixGetColormap(pixs))
628
0
        return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", __func__, NULL);
629
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
630
0
        return (PIX *)ERROR_PTR("invalid incolor value", __func__, NULL);
631
0
    if (yloc < 0 || yloc >= h)
632
0
        return (PIX *)ERROR_PTR("yloc not in [0 ... h-1]", __func__, NULL);
633
634
0
    if (pixGetColormap(pixs))
635
0
        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
636
0
    else
637
0
        pix = pixClone(pixs);
638
639
        /* Normalize angle.  If no rotation, return a copy */
640
0
    radang = normalizeAngleForShear(radang, MinDiffFromHalfPi);
641
0
    if (radang == 0.0 || tan(radang) == 0.0) {
642
0
        pixDestroy(&pix);
643
0
        return pixCopy(NULL, pixs);
644
0
    }
645
646
        /* Initialize to value of incoming pixels */
647
0
    pixd = pixCreateTemplate(pix);
648
0
    pixSetBlackOrWhite(pixd, incolor);
649
650
        /* Standard linear interp: subdivide each pixel into 64 parts */
651
0
    d = pixGetDepth(pixd);  /* 8 or 32 */
652
0
    datas = pixGetData(pix);
653
0
    datad = pixGetData(pixd);
654
0
    wpls = pixGetWpl(pix);
655
0
    wpld = pixGetWpl(pixd);
656
0
    tanangle = tan(radang);
657
0
    for (i = 0; i < h; i++) {
658
0
        lines = datas + i * wpls;
659
0
        lined = datad + i * wpld;
660
0
        xshift = (yloc - i) * tanangle;
661
0
        for (jd = 0; jd < w; jd++) {
662
0
            x = (l_int32)(64.0 * (-xshift + jd) + 0.5);
663
0
            xp = x / 64;
664
0
            xf = x & 63;
665
0
            wm = w - 1;
666
0
            if (xp < 0 || xp > wm) continue;
667
0
            if (d == 8) {
668
0
                if (xp < wm) {
669
0
                    val = ((63 - xf) * GET_DATA_BYTE(lines, xp) +
670
0
                           xf * GET_DATA_BYTE(lines, xp + 1) + 31) / 63;
671
0
                } else {  /* xp == wm */
672
0
                    val = GET_DATA_BYTE(lines, xp);
673
0
                }
674
0
                SET_DATA_BYTE(lined, jd, val);
675
0
            } else {  /* d == 32 */
676
0
                if (xp < wm) {
677
0
                    word0 = *(lines + xp);
678
0
                    word1 = *(lines + xp + 1);
679
0
                    rval = ((63 - xf) * ((word0 >> L_RED_SHIFT) & 0xff) +
680
0
                           xf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
681
0
                    gval = ((63 - xf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
682
0
                           xf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
683
0
                    bval = ((63 - xf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
684
0
                           xf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
685
0
                    composeRGBPixel(rval, gval, bval, lined + jd);
686
0
                } else {  /* xp == wm */
687
0
                    lined[jd] = lines[xp];
688
0
                }
689
0
            }
690
0
        }
691
0
    }
692
693
0
    pixDestroy(&pix);
694
0
    return pixd;
695
0
}
696
697
698
/*!
699
 * \brief   pixVShearLI()
700
 *
701
 * \param[in]    pixs      8 bpp or 32 bpp, or colormapped
702
 * \param[in]    xloc      location of vertical line, measured from origin
703
 * \param[in]    radang    angle in radians, in range (-pi/2 ... pi/2)
704
 * \param[in]    incolor   L_BRING_IN_WHITE, L_BRING_IN_BLACK;
705
 * \return  pixd sheared, or NULL on error
706
 *
707
 * <pre>
708
 * Notes:
709
 *      (1) This does vertical shear with linear interpolation for
710
 *          accurate results on 8 bpp gray, 32 bpp rgb, or cmapped images.
711
 *          It is relatively slow compared to the sampled version
712
 *          implemented by rasterop, but the result is much smoother.
713
 *      (2) This shear leaves the vertical line of pixels at x = xloc
714
 *          invariant.  For a positive shear angle, pixels to the right
715
 *          of this line are shoved downward, and pixels to the left
716
 *          of the line move upward.
717
 *      (3) Any colormap is removed.
718
 *      (4) The angle is brought into the range [-pi/2 + del, pi/2 - del],
719
 *          where del == MinDiffFromHalfPi.
720
 * </pre>
721
 */
722
PIX *
723
pixVShearLI(PIX       *pixs,
724
            l_int32    xloc,
725
            l_float32  radang,
726
            l_int32    incolor)
727
0
{
728
0
l_int32    id, y, yp, yf, j, w, h, d, hm, wpls, wpld, val, rval, gval, bval;
729
0
l_uint32   word0, word1;
730
0
l_uint32  *datas, *datad, *lines, *lined;
731
0
l_float32  tanangle, yshift;
732
0
PIX       *pix, *pixd;
733
734
0
    if (!pixs)
735
0
        return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL);
736
0
    pixGetDimensions(pixs, &w, &h, &d);
737
0
    if (d != 8 && d != 32 && !pixGetColormap(pixs))
738
0
        return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", __func__, NULL);
739
0
    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
740
0
        return (PIX *)ERROR_PTR("invalid incolor value", __func__, NULL);
741
0
    if (xloc < 0 || xloc >= w)
742
0
        return (PIX *)ERROR_PTR("xloc not in [0 ... w-1]", __func__, NULL);
743
744
0
    if (pixGetColormap(pixs))
745
0
        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
746
0
    else
747
0
        pix = pixClone(pixs);
748
749
        /* Normalize angle.  If no rotation, return a copy */
750
0
    radang = normalizeAngleForShear(radang, MinDiffFromHalfPi);
751
0
    if (radang == 0.0 || tan(radang) == 0.0) {
752
0
        pixDestroy(&pix);
753
0
        return pixCopy(NULL, pixs);
754
0
    }
755
756
        /* Initialize to value of incoming pixels */
757
0
    pixd = pixCreateTemplate(pix);
758
0
    pixSetBlackOrWhite(pixd, incolor);
759
760
        /* Standard linear interp: subdivide each pixel into 64 parts */
761
0
    d = pixGetDepth(pixd);  /* 8 or 32 */
762
0
    datas = pixGetData(pix);
763
0
    datad = pixGetData(pixd);
764
0
    wpls = pixGetWpl(pix);
765
0
    wpld = pixGetWpl(pixd);
766
0
    tanangle = tan(radang);
767
0
    for (j = 0; j < w; j++) {
768
0
        yshift = (j - xloc) * tanangle;
769
0
        for (id = 0; id < h; id++) {
770
0
            y = (l_int32)(64.0 * (-yshift + id) + 0.5);
771
0
            yp = y / 64;
772
0
            yf = y & 63;
773
0
            hm = h - 1;
774
0
            if (yp < 0 || yp > hm) continue;
775
0
            lines = datas + yp * wpls;
776
0
            lined = datad + id * wpld;
777
0
            if (d == 8) {
778
0
                if (yp < hm) {
779
0
                    val = ((63 - yf) * GET_DATA_BYTE(lines, j) +
780
0
                           yf * GET_DATA_BYTE(lines + wpls, j) + 31) / 63;
781
0
                } else {  /* yp == hm */
782
0
                    val = GET_DATA_BYTE(lines, j);
783
0
                }
784
0
                SET_DATA_BYTE(lined, j, val);
785
0
            } else {  /* d == 32 */
786
0
                if (yp < hm) {
787
0
                    word0 = *(lines + j);
788
0
                    word1 = *(lines + wpls + j);
789
0
                    rval = ((63 - yf) * ((word0 >> L_RED_SHIFT) & 0xff) +
790
0
                           yf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
791
0
                    gval = ((63 - yf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
792
0
                           yf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
793
0
                    bval = ((63 - yf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
794
0
                           yf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
795
0
                    composeRGBPixel(rval, gval, bval, lined + j);
796
0
                } else {  /* yp == hm */
797
0
                    lined[j] = lines[j];
798
0
                }
799
0
            }
800
0
        }
801
0
    }
802
803
0
    pixDestroy(&pix);
804
0
    return pixd;
805
0
}
806
807
808
/*-------------------------------------------------------------------------*
809
 *                           Angle normalization                           *
810
 *-------------------------------------------------------------------------*/
811
static l_float32
812
normalizeAngleForShear(l_float32  radang,
813
                       l_float32  mindif)
814
88.2k
{
815
88.2k
l_float32  pi2;
816
817
       /* Bring angle into range [-pi/2, pi/2] */
818
88.2k
    pi2 = 3.14159265f / 2.0f;
819
88.2k
    if (radang < -pi2 || radang > pi2)
820
0
        radang = radang - (l_int32)(radang / pi2) * pi2;
821
822
       /* If angle is too close to pi/2 or -pi/2, move it */
823
88.2k
    if (radang > pi2 - mindif) {
824
0
        L_WARNING("angle close to pi/2; shifting away\n", __func__);
825
0
        radang = pi2 - mindif;
826
88.2k
    } else if (radang < -pi2 + mindif) {
827
0
        L_WARNING("angle close to -pi/2; shifting away\n", __func__);
828
0
        radang = -pi2 + mindif;
829
0
    }
830
831
88.2k
    return radang;
832
88.2k
}