1 /*====================================================================*
2 - Copyright (C) 2001 Leptonica. All rights reserved.
3 - This software is distributed in the hope that it will be
4 - useful, but with NO WARRANTY OF ANY KIND.
5 - No author or distributor accepts responsibility to anyone for the
6 - consequences of using this software, or for whether it serves any
7 - particular purpose or works at all, unless he or she says so in
8 - writing. Everyone is granted permission to copy, modify and
9 - redistribute this source code, for commercial or non-commercial
10 - purposes, with the following restrictions: (1) the origin of this
11 - source code must not be misrepresented; (2) modified versions must
12 - be plainly marked as such; and (3) this notice may not be removed
13 - or altered from any source or modified source distribution.
14 *====================================================================*/
15
16
17 /*
18 * rotate.c
19 *
20 * General rotation about image center
21 * PIX *pixRotate()
22 * PIX *pixEmbedForRotation()
23 *
24 * General rotation by sampling
25 * PIX *pixRotateBySampling()
26 *
27 * Nice (slow) rotation of 1 bpp image
28 * PIX *pixRotateBinaryNice()
29 *
30 * Rotations are measured in radians; clockwise is positive.
31 *
32 * The general rotation pixRotate() does the best job for
33 * rotating about the image center. For 1 bpp, it uses shear;
34 * for others, it uses either shear or area mapping.
35 * If requested, it expands the output image so that no pixels are lost
36 * in the rotation, and this can be done on multiple successive shears
37 * without expanding beyond the maximum necessary size.
38 */
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <math.h>
43 #include "allheaders.h"
44
45 static const l_float32 VERY_SMALL_ANGLE = 0.001; /* radians; ~0.06 degrees */
46
47
48 /*------------------------------------------------------------------*
49 * General rotation about the center *
50 *------------------------------------------------------------------*/
51 /*!
52 * pixRotate()
53 *
54 * Input: pixs (1, 2, 4, 8, 32 bpp rgb)
55 * angle (radians; clockwise is positive)
56 * type (L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING)
57 * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
58 * width (original width; use 0 to avoid embedding)
59 * height (original height; use 0 to avoid embedding)
60 * Return: pixd, or null on error
61 *
62 * Notes:
63 * (1) Rotation is about the center of the image.
64 * (2) For very small rotations, just return a clone.
65 * (3) Rotation brings either white or black pixels in
66 * from outside the image.
67 * (4) Above 20 degrees, if rotation by shear is requested, we rotate
68 * by sampling.
69 * (5) Colormaps are removed for rotation by area map and shear.
70 * (6) The dest can be expanded so that no image pixels
71 * are lost. To invoke expansion, input the original
72 * width and height. For repeated rotation, use of the
73 * original width and height allows the expansion to
74 * stop at the maximum required size, which is a square
75 * with side = sqrt(w*w + h*h).
76 *
77 * *** Warning: implicit assumption about RGB component ordering ***
78 */
79 PIX *
pixRotate(PIX * pixs,l_float32 angle,l_int32 type,l_int32 incolor,l_int32 width,l_int32 height)80 pixRotate(PIX *pixs,
81 l_float32 angle,
82 l_int32 type,
83 l_int32 incolor,
84 l_int32 width,
85 l_int32 height)
86 {
87 l_int32 w, h, d;
88 l_uint32 fillval;
89 PIX *pixt1, *pixt2, *pixt3, *pixd;
90 PIXCMAP *cmap;
91
92 PROCNAME("pixRotate");
93
94 if (!pixs)
95 return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
96 if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP &&
97 type != L_ROTATE_SAMPLING)
98 return (PIX *)ERROR_PTR("invalid type", procName, NULL);
99 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
100 return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
101
102 if (L_ABS(angle) < VERY_SMALL_ANGLE)
103 return pixClone(pixs);
104
105 /* Don't rotate by shear more than 20 degrees */
106 if (L_ABS(angle) > 0.35 && type == L_ROTATE_SHEAR) {
107 L_WARNING("large angle; rotating by sampling", procName);
108 type = L_ROTATE_SAMPLING;
109 }
110
111 /* If 1 bpp and area map is requested, rotate by sampling */
112 d = pixGetDepth(pixs);
113 if (d == 1 && type == L_ROTATE_AREA_MAP) {
114 L_WARNING("1 bpp; rotating by sampling", procName);
115 type = L_ROTATE_SAMPLING;
116 }
117
118 /* Remove colormap if we're rotating by area mapping. */
119 cmap = pixGetColormap(pixs);
120 if (cmap && type == L_ROTATE_AREA_MAP)
121 pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
122 else
123 pixt1 = pixClone(pixs);
124 cmap = pixGetColormap(pixt1);
125
126 /* Otherwise, if there is a colormap and we're not embedding,
127 * add white color if it doesn't exist. */
128 if (cmap && width == 0) { /* no embedding; generate @incolor */
129 if (incolor == L_BRING_IN_BLACK)
130 pixcmapAddBlackOrWhite(cmap, 0, NULL);
131 else /* L_BRING_IN_WHITE */
132 pixcmapAddBlackOrWhite(cmap, 1, NULL);
133 }
134
135 /* Request to embed in a larger image; do if necessary */
136 pixt2 = pixEmbedForRotation(pixt1, angle, incolor, width, height);
137
138 /* Area mapping requires 8 or 32 bpp.
139 * If 1 bpp, default to sampling. */
140 d = pixGetDepth(pixt2);
141 if (type == L_ROTATE_AREA_MAP && d < 8)
142 pixt3 = pixConvertTo8(pixt2, FALSE);
143 else
144 pixt3 = pixClone(pixt2);
145
146 /* Rotate by shear or area mapping */
147 pixGetDimensions(pixt3, &w, &h, &d);
148 if (type == L_ROTATE_SHEAR)
149 pixd = pixRotateShearCenter(pixt3, angle, incolor);
150 else if (type == L_ROTATE_SAMPLING)
151 pixd = pixRotateBySampling(pixt3, w / 2, h / 2, angle, incolor);
152 else { /* rotate by area mapping */
153 fillval = 0;
154 if (incolor == L_BRING_IN_WHITE) {
155 if (d == 8)
156 fillval = 255;
157 else /* d == 32 */
158 fillval = 0xffffff00;
159 }
160 if (d == 8)
161 pixd = pixRotateAMGray(pixt3, angle, fillval);
162 else /* d == 32 */
163 pixd = pixRotateAMColor(pixt3, angle, fillval);
164 }
165
166 pixDestroy(&pixt1);
167 pixDestroy(&pixt2);
168 pixDestroy(&pixt3);
169 return pixd;
170 }
171
172
173 /*!
174 * pixEmbedForRotation()
175 *
176 * Input: pixs (1, 2, 4, 8, 32 bpp rgb)
177 * angle (radians; clockwise is positive)
178 * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
179 * width (original width; use 0 to avoid embedding)
180 * height (original height; use 0 to avoid embedding)
181 * Return: pixd, or null on error
182 *
183 * Notes:
184 * (1) For very small rotations, just return a clone.
185 * (2) Generate larger image to embed pixs if necessary, and
186 * place in the center.
187 * (3) Rotation brings either white or black pixels in
188 * from outside the image. For colormapped images where
189 * there is no white or black, a new color is added if
190 * possible for these pixels; otherwise, either the
191 * lightest or darkest color is used. In most cases,
192 * the colormap will be removed prior to rotation.
193 * (4) The dest is to be expanded so that no image pixels
194 * are lost after rotation. Input of the original width
195 * and height allows the expansion to stop at the maximum
196 * required size, which is a square with side equal to
197 * sqrt(w*w + h*h).
198 * (5) For an arbitrary angle, the expansion can be found by
199 * considering the UL and UR corners. As the image is
200 * rotated, these move in an arc centered at the center of
201 * the image. Normalize to a unit circle by dividing by half
202 * the image diagonal. After a rotation of T radians, the UL
203 * and UR corners are at points T radians along the unit
204 * circle. Compute the x and y coordinates of both these
205 * points and take the max of absolute values; these represent
206 * the half width and half height of the containing rectangle.
207 * The arithmetic is done using formulas for sin(a+b) and cos(a+b),
208 * where b = T. For the UR corner, sin(a) = h/d and cos(a) = w/d.
209 * For the UL corner, replace a by (pi - a), and you have
210 * sin(pi - a) = h/d, cos(pi - a) = -w/d. The equations
211 * given below follow directly.
212 */
213 PIX *
pixEmbedForRotation(PIX * pixs,l_float32 angle,l_int32 incolor,l_int32 width,l_int32 height)214 pixEmbedForRotation(PIX *pixs,
215 l_float32 angle,
216 l_int32 incolor,
217 l_int32 width,
218 l_int32 height)
219 {
220 l_int32 w, h, d, w1, h1, w2, h2, maxside, wnew, hnew, xoff, yoff;
221 l_float64 sina, cosa, fw, fh;
222 PIX *pixd;
223
224 PROCNAME("pixEmbedForRotation");
225
226 if (!pixs)
227 return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
228 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
229 return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
230 if (L_ABS(angle) < VERY_SMALL_ANGLE)
231 return pixClone(pixs);
232
233 /* Test if big enough to hold any rotation of the original image */
234 pixGetDimensions(pixs, &w, &h, &d);
235 maxside = (l_int32)(sqrt((l_float64)(width * width) +
236 (l_float64)(height * height)) + 0.5);
237 if (w >= maxside && h >= maxside) /* big enough */
238 return pixClone(pixs);
239
240 /* Find the new sizes required to hold the image after rotation */
241 cosa = cos(angle);
242 sina = sin(angle);
243 fw = (l_float64)w;
244 fh = (l_float64)h;
245 w1 = (l_int32)L_ABS(fw * cosa - fh * sina);
246 w2 = (l_int32)L_ABS(-fw * cosa - fh * sina);
247 h1 = (l_int32)L_ABS(fw * sina + fh * cosa);
248 h2 = (l_int32)L_ABS(-fw * sina + fh * cosa);
249 wnew = L_MAX(w1, w2);
250 hnew = L_MAX(h1, h2);
251
252 if ((pixd = pixCreate(wnew, hnew, d)) == NULL)
253 return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
254 pixCopyResolution(pixd, pixs);
255 pixCopyColormap(pixd, pixs);
256 pixCopyText(pixd, pixs);
257 xoff = (wnew - w) / 2;
258 yoff = (hnew - h) / 2;
259
260 /* Set background to color to be rotated in */
261 pixSetBlackOrWhite(pixd, incolor);
262
263 pixRasterop(pixd, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0);
264 return pixd;
265 }
266
267
268 /*------------------------------------------------------------------*
269 * General rotation by sampling *
270 *------------------------------------------------------------------*/
271 /*!
272 * pixRotateBySampling()
273 *
274 * Input: pixs (1, 2, 4, 8, 16, 32 bpp rgb; can be cmapped)
275 * xcen (x value of center of rotation)
276 * ycen (y value of center of rotation)
277 * angle (radians; clockwise is positive)
278 * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
279 * Return: pixd, or null on error
280 *
281 * Notes:
282 * (1) For very small rotations, just return a clone.
283 * (2) Rotation brings either white or black pixels in
284 * from outside the image.
285 * (3) Colormaps are retained.
286 */
287 PIX *
pixRotateBySampling(PIX * pixs,l_int32 xcen,l_int32 ycen,l_float32 angle,l_int32 incolor)288 pixRotateBySampling(PIX *pixs,
289 l_int32 xcen,
290 l_int32 ycen,
291 l_float32 angle,
292 l_int32 incolor)
293 {
294 l_int32 w, h, d, i, j, x, y, xdif, ydif, wm1, hm1, wpld;
295 l_uint32 val;
296 l_float32 sina, cosa;
297 l_uint32 *datad, *lined;
298 void **lines;
299 PIX *pixd;
300
301 PROCNAME("pixRotateBySampling");
302
303 if (!pixs)
304 return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
305 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
306 return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
307 pixGetDimensions(pixs, &w, &h, &d);
308 if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
309 return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
310
311 if (L_ABS(angle) < VERY_SMALL_ANGLE)
312 return pixClone(pixs);
313
314 if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL)
315 return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
316 pixSetBlackOrWhite(pixd, incolor);
317
318 sina = sin(angle);
319 cosa = cos(angle);
320 datad = pixGetData(pixd);
321 wpld = pixGetWpl(pixd);
322 wm1 = w - 1;
323 hm1 = h - 1;
324 lines = pixGetLinePtrs(pixs, NULL);
325
326 /* Treat 1 bpp case specially */
327 if (d == 1) {
328 for (i = 0; i < h; i++) { /* scan over pixd */
329 lined = datad + i * wpld;
330 ydif = ycen - i;
331 for (j = 0; j < w; j++) {
332 xdif = xcen - j;
333 x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
334 if (x < 0 || x > wm1) continue;
335 y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
336 if (y < 0 || y > hm1) continue;
337 if (incolor == L_BRING_IN_WHITE) {
338 if (GET_DATA_BIT(lines[y], x))
339 SET_DATA_BIT(lined, j);
340 }
341 else {
342 if (!GET_DATA_BIT(lines[y], x))
343 CLEAR_DATA_BIT(lined, j);
344 }
345 }
346 }
347 FREE(lines);
348 return pixd;
349 }
350
351 for (i = 0; i < h; i++) { /* scan over pixd */
352 lined = datad + i * wpld;
353 ydif = ycen - i;
354 for (j = 0; j < w; j++) {
355 xdif = xcen - j;
356 x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
357 if (x < 0 || x > wm1) continue;
358 y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
359 if (y < 0 || y > hm1) continue;
360 switch (d)
361 {
362 case 8:
363 val = GET_DATA_BYTE(lines[y], x);
364 SET_DATA_BYTE(lined, j, val);
365 break;
366 case 32:
367 val = GET_DATA_FOUR_BYTES(lines[y], x);
368 SET_DATA_FOUR_BYTES(lined, j, val);
369 break;
370 case 2:
371 val = GET_DATA_DIBIT(lines[y], x);
372 SET_DATA_DIBIT(lined, j, val);
373 break;
374 case 4:
375 val = GET_DATA_QBIT(lines[y], x);
376 SET_DATA_QBIT(lined, j, val);
377 break;
378 case 16:
379 val = GET_DATA_TWO_BYTES(lines[y], x);
380 SET_DATA_TWO_BYTES(lined, j, val);
381 break;
382 default:
383 return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
384 }
385 }
386 }
387
388 FREE(lines);
389 return pixd;
390 }
391
392
393 /*------------------------------------------------------------------*
394 * Nice (slow) rotation of 1 bpp image *
395 *------------------------------------------------------------------*/
396 /*!
397 * pixRotateBinaryNice()
398 *
399 * Input: pixs (1 bpp)
400 * angle (radians; clockwise is positive; about the center)
401 * incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
402 * Return: pixd, or null on error
403 *
404 * Notes:
405 * (1) For very small rotations, just return a clone.
406 * (2) This does a computationally expensive rotation of 1 bpp images.
407 * The fastest rotators (using shears or subsampling) leave
408 * visible horizontal and vertical shear lines across which
409 * the image shear changes by one pixel. To ameliorate the
410 * visual effect one can introduce random dithering. One
411 * way to do this in a not-too-random fashion is given here.
412 * We convert to 8 bpp, do a very small blur, rotate using
413 * linear interpolation (same as area mapping), do a
414 * small amount of sharpening to compensate for the initial
415 * blur, and threshold back to binary. The shear lines
416 * are magically removed.
417 * (3) This operation is about 5x slower than rotation by sampling.
418 */
419 PIX *
pixRotateBinaryNice(PIX * pixs,l_float32 angle,l_int32 incolor)420 pixRotateBinaryNice(PIX *pixs,
421 l_float32 angle,
422 l_int32 incolor)
423 {
424 PIX *pixt1, *pixt2, *pixt3, *pixt4, *pixd;
425
426 PROCNAME("pixRotateBinaryNice");
427
428 if (!pixs || pixGetDepth(pixs) != 1)
429 return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
430 if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
431 return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
432
433 pixt1 = pixConvertTo8(pixs, 0);
434 pixt2 = pixBlockconv(pixt1, 1, 1); /* smallest blur allowed */
435 pixt3 = pixRotateAM(pixt2, angle, incolor);
436 pixt4 = pixUnsharpMasking(pixt3, 1, 1.0); /* sharpen a bit */
437 pixd = pixThresholdToBinary(pixt4, 128);
438 pixDestroy(&pixt1);
439 pixDestroy(&pixt2);
440 pixDestroy(&pixt3);
441 pixDestroy(&pixt4);
442 return pixd;
443 }
444
445
446