/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 | } |