/src/imagemagick/MagickCore/shear.c
Line | Count | Source |
1 | | /* |
2 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3 | | % % |
4 | | % % |
5 | | % % |
6 | | % SSSSS H H EEEEE AAA RRRR % |
7 | | % SS H H E A A R R % |
8 | | % SSS HHHHH EEE AAAAA RRRR % |
9 | | % SS H H E A A R R % |
10 | | % SSSSS H H EEEEE A A R R % |
11 | | % % |
12 | | % % |
13 | | % MagickCore Methods to Shear or Rotate an Image by an Arbitrary Angle % |
14 | | % % |
15 | | % Software Design % |
16 | | % Cristy % |
17 | | % July 1992 % |
18 | | % % |
19 | | % % |
20 | | % Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization % |
21 | | % dedicated to making software imaging solutions freely available. % |
22 | | % % |
23 | | % You may not use this file except in compliance with the License. You may % |
24 | | % obtain a copy of the License at % |
25 | | % % |
26 | | % https://imagemagick.org/license/ % |
27 | | % % |
28 | | % Unless required by applicable law or agreed to in writing, software % |
29 | | % distributed under the License is distributed on an "AS IS" BASIS, % |
30 | | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % |
31 | | % See the License for the specific language governing permissions and % |
32 | | % limitations under the License. % |
33 | | % % |
34 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
35 | | % |
36 | | % The XShearImage() and YShearImage() methods are based on the paper "A Fast |
37 | | % Algorithm for General Raster Rotation" by Alan W. Paeth, Graphics |
38 | | % Interface '86 (Vancouver). ShearRotateImage() is adapted from a similar |
39 | | % method based on the Paeth paper written by Michael Halle of the Spatial |
40 | | % Imaging Group, MIT Media Lab. |
41 | | % |
42 | | */ |
43 | | |
44 | | /* |
45 | | Include declarations. |
46 | | */ |
47 | | #include "MagickCore/studio.h" |
48 | | #include "MagickCore/artifact.h" |
49 | | #include "MagickCore/attribute.h" |
50 | | #include "MagickCore/blob-private.h" |
51 | | #include "MagickCore/cache-private.h" |
52 | | #include "MagickCore/channel.h" |
53 | | #include "MagickCore/color-private.h" |
54 | | #include "MagickCore/colorspace-private.h" |
55 | | #include "MagickCore/composite.h" |
56 | | #include "MagickCore/composite-private.h" |
57 | | #include "MagickCore/decorate.h" |
58 | | #include "MagickCore/distort.h" |
59 | | #include "MagickCore/draw.h" |
60 | | #include "MagickCore/exception.h" |
61 | | #include "MagickCore/exception-private.h" |
62 | | #include "MagickCore/gem.h" |
63 | | #include "MagickCore/geometry.h" |
64 | | #include "MagickCore/image.h" |
65 | | #include "MagickCore/image-private.h" |
66 | | #include "MagickCore/matrix.h" |
67 | | #include "MagickCore/memory_.h" |
68 | | #include "MagickCore/list.h" |
69 | | #include "MagickCore/monitor.h" |
70 | | #include "MagickCore/monitor-private.h" |
71 | | #include "MagickCore/nt-base-private.h" |
72 | | #include "MagickCore/pixel-accessor.h" |
73 | | #include "MagickCore/profile-private.h" |
74 | | #include "MagickCore/quantum.h" |
75 | | #include "MagickCore/resource_.h" |
76 | | #include "MagickCore/shear.h" |
77 | | #include "MagickCore/statistic.h" |
78 | | #include "MagickCore/string_.h" |
79 | | #include "MagickCore/string-private.h" |
80 | | #include "MagickCore/thread-private.h" |
81 | | #include "MagickCore/threshold.h" |
82 | | #include "MagickCore/transform.h" |
83 | | |
84 | | /* |
85 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
86 | | % % |
87 | | % % |
88 | | % % |
89 | | % C r o p T o F i t I m a g e % |
90 | | % % |
91 | | % % |
92 | | % % |
93 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
94 | | % |
95 | | % CropToFitImage() crops the sheared image as determined by the bounding box |
96 | | % as defined by width and height and shearing angles. |
97 | | % |
98 | | % The format of the CropToFitImage method is: |
99 | | % |
100 | | % MagickBooleanType CropToFitImage(Image **image, |
101 | | % const double x_shear,const double x_shear, |
102 | | % const double width,const double height, |
103 | | % const MagickBooleanType rotate,ExceptionInfo *exception) |
104 | | % |
105 | | % A description of each parameter follows. |
106 | | % |
107 | | % o image: the image. |
108 | | % |
109 | | % o x_shear, y_shear, width, height: Defines a region of the image to crop. |
110 | | % |
111 | | % o exception: return any errors or warnings in this structure. |
112 | | % |
113 | | */ |
114 | | static MagickBooleanType CropToFitImage(Image **image, |
115 | | const double x_shear,const double y_shear, |
116 | | const double width,const double height, |
117 | | const MagickBooleanType rotate,ExceptionInfo *exception) |
118 | 0 | { |
119 | 0 | Image |
120 | 0 | *crop_image; |
121 | |
|
122 | 0 | PointInfo |
123 | 0 | extent[4], |
124 | 0 | min, |
125 | 0 | max; |
126 | |
|
127 | 0 | RectangleInfo |
128 | 0 | geometry, |
129 | 0 | page; |
130 | |
|
131 | 0 | ssize_t |
132 | 0 | i; |
133 | | |
134 | | /* |
135 | | Calculate the rotated image size. |
136 | | */ |
137 | 0 | extent[0].x=(double) (-width/2.0); |
138 | 0 | extent[0].y=(double) (-height/2.0); |
139 | 0 | extent[1].x=(double) width/2.0; |
140 | 0 | extent[1].y=(double) (-height/2.0); |
141 | 0 | extent[2].x=(double) (-width/2.0); |
142 | 0 | extent[2].y=(double) height/2.0; |
143 | 0 | extent[3].x=(double) width/2.0; |
144 | 0 | extent[3].y=(double) height/2.0; |
145 | 0 | for (i=3; i >= 0; i--) |
146 | 0 | { |
147 | 0 | extent[i].x+=x_shear*extent[i].y; |
148 | 0 | extent[i].y+=y_shear*extent[i].x; |
149 | 0 | if (rotate != MagickFalse) |
150 | 0 | extent[i].x+=x_shear*extent[i].y; |
151 | 0 | extent[i].x+=(double) (*image)->columns/2.0; |
152 | 0 | extent[i].y+=(double) (*image)->rows/2.0; |
153 | 0 | } |
154 | 0 | min=extent[0]; |
155 | 0 | max=extent[0]; |
156 | 0 | for (i=1; i < 4; i++) |
157 | 0 | { |
158 | 0 | if (min.x > extent[i].x) |
159 | 0 | min.x=extent[i].x; |
160 | 0 | if (min.y > extent[i].y) |
161 | 0 | min.y=extent[i].y; |
162 | 0 | if (max.x < extent[i].x) |
163 | 0 | max.x=extent[i].x; |
164 | 0 | if (max.y < extent[i].y) |
165 | 0 | max.y=extent[i].y; |
166 | 0 | } |
167 | 0 | geometry.x=CastDoubleToSsizeT(ceil(min.x-0.5)); |
168 | 0 | geometry.y=CastDoubleToSsizeT(ceil(min.y-0.5)); |
169 | 0 | geometry.width=(size_t) CastDoubleToSsizeT(floor(max.x-min.x+0.5)); |
170 | 0 | geometry.height=(size_t) CastDoubleToSsizeT(floor(max.y-min.y+0.5)); |
171 | 0 | page=(*image)->page; |
172 | 0 | (void) ParseAbsoluteGeometry("0x0+0+0",&(*image)->page); |
173 | 0 | crop_image=CropImage(*image,&geometry,exception); |
174 | 0 | if (crop_image == (Image *) NULL) |
175 | 0 | return(MagickFalse); |
176 | 0 | crop_image->page=page; |
177 | 0 | *image=DestroyImage(*image); |
178 | 0 | *image=crop_image; |
179 | 0 | return(MagickTrue); |
180 | 0 | } |
181 | | |
182 | | /* |
183 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
184 | | % % |
185 | | % % |
186 | | % % |
187 | | % D e s k e w I m a g e % |
188 | | % % |
189 | | % % |
190 | | % % |
191 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
192 | | % |
193 | | % DeskewImage() removes skew from the image. Skew is an artifact that |
194 | | % occurs in scanned images because of the camera being misaligned, |
195 | | % imperfections in the scanning or surface, or simply because the paper was |
196 | | % not placed completely flat when scanned. |
197 | | % |
198 | | % The result will be auto-cropped if the artifact "deskew:auto-crop" is |
199 | | % defined, while the amount the image is to be deskewed, in degrees is also |
200 | | % saved as the artifact "deskew:angle". |
201 | | % |
202 | | % The format of the DeskewImage method is: |
203 | | % |
204 | | % Image *DeskewImage(const Image *image,const double threshold, |
205 | | % ExceptionInfo *exception) |
206 | | % |
207 | | % A description of each parameter follows: |
208 | | % |
209 | | % o image: the image. |
210 | | % |
211 | | % o threshold: separate background from foreground. |
212 | | % |
213 | | % o exception: return any errors or warnings in this structure. |
214 | | % |
215 | | */ |
216 | | |
217 | | static void RadonProjection(MatrixInfo *source_matrices, |
218 | | MatrixInfo *destination_matrices,const ssize_t sign,size_t *projection) |
219 | 0 | { |
220 | 0 | MatrixInfo |
221 | 0 | *p, |
222 | 0 | *q, |
223 | 0 | *swap; |
224 | |
|
225 | 0 | size_t |
226 | 0 | step; |
227 | |
|
228 | 0 | ssize_t |
229 | 0 | x; |
230 | |
|
231 | 0 | p=source_matrices; |
232 | 0 | q=destination_matrices; |
233 | 0 | for (step=1; step < GetMatrixColumns(p); step*=2) |
234 | 0 | { |
235 | 0 | for (x=0; x < (ssize_t) GetMatrixColumns(p); x+=2*(ssize_t) step) |
236 | 0 | { |
237 | 0 | ssize_t |
238 | 0 | i, |
239 | 0 | y; |
240 | |
|
241 | 0 | unsigned short |
242 | 0 | element, |
243 | 0 | neighbor; |
244 | |
|
245 | 0 | for (i=0; i < (ssize_t) step; i++) |
246 | 0 | { |
247 | 0 | for (y=0; y < ((ssize_t) GetMatrixRows(p)-i-1); y++) |
248 | 0 | { |
249 | 0 | if (GetMatrixElement(p,x+i,y,&element) == MagickFalse) |
250 | 0 | continue; |
251 | 0 | if (GetMatrixElement(p,x+i+(ssize_t) step,y+i,&neighbor) == MagickFalse) |
252 | 0 | continue; |
253 | 0 | neighbor+=element; |
254 | 0 | if (SetMatrixElement(q,x+2*i,y,&neighbor) == MagickFalse) |
255 | 0 | continue; |
256 | 0 | if (GetMatrixElement(p,x+i+(ssize_t) step,y+i+1,&neighbor) == MagickFalse) |
257 | 0 | continue; |
258 | 0 | neighbor+=element; |
259 | 0 | if (SetMatrixElement(q,x+2*i+1,y,&neighbor) == MagickFalse) |
260 | 0 | continue; |
261 | 0 | } |
262 | 0 | for ( ; y < ((ssize_t) GetMatrixRows(p)-i); y++) |
263 | 0 | { |
264 | 0 | if (GetMatrixElement(p,x+i,y,&element) == MagickFalse) |
265 | 0 | continue; |
266 | 0 | if (GetMatrixElement(p,x+i+(ssize_t) step,y+i,&neighbor) == MagickFalse) |
267 | 0 | continue; |
268 | 0 | neighbor+=element; |
269 | 0 | if (SetMatrixElement(q,x+2*i,y,&neighbor) == MagickFalse) |
270 | 0 | continue; |
271 | 0 | if (SetMatrixElement(q,x+2*i+1,y,&element) == MagickFalse) |
272 | 0 | continue; |
273 | 0 | } |
274 | 0 | for ( ; y < (ssize_t) GetMatrixRows(p); y++) |
275 | 0 | { |
276 | 0 | if (GetMatrixElement(p,x+i,y,&element) == MagickFalse) |
277 | 0 | continue; |
278 | 0 | if (SetMatrixElement(q,x+2*i,y,&element) == MagickFalse) |
279 | 0 | continue; |
280 | 0 | if (SetMatrixElement(q,x+2*i+1,y,&element) == MagickFalse) |
281 | 0 | continue; |
282 | 0 | } |
283 | 0 | } |
284 | 0 | } |
285 | 0 | swap=p; |
286 | 0 | p=q; |
287 | 0 | q=swap; |
288 | 0 | } |
289 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
290 | | #pragma omp parallel for schedule(static) \ |
291 | | num_threads((int) GetMagickResourceLimit(ThreadResource)) |
292 | | #endif |
293 | 0 | for (x=0; x < (ssize_t) GetMatrixColumns(p); x++) |
294 | 0 | { |
295 | 0 | size_t |
296 | 0 | sum; |
297 | |
|
298 | 0 | ssize_t |
299 | 0 | y; |
300 | |
|
301 | 0 | sum=0; |
302 | 0 | for (y=0; y < (ssize_t) (GetMatrixRows(p)-1); y++) |
303 | 0 | { |
304 | 0 | ssize_t |
305 | 0 | delta; |
306 | |
|
307 | 0 | unsigned short |
308 | 0 | element, |
309 | 0 | neighbor; |
310 | |
|
311 | 0 | if (GetMatrixElement(p,x,y,&element) == MagickFalse) |
312 | 0 | continue; |
313 | 0 | if (GetMatrixElement(p,x,y+1,&neighbor) == MagickFalse) |
314 | 0 | continue; |
315 | 0 | delta=(ssize_t) element-(ssize_t) neighbor; |
316 | 0 | sum+=(size_t) (delta*delta); |
317 | 0 | } |
318 | 0 | projection[(ssize_t) GetMatrixColumns(p)+sign*x-1]=sum; |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | | static MagickBooleanType RadonTransform(const Image *image, |
323 | | const double threshold,size_t *projection,ExceptionInfo *exception) |
324 | 0 | { |
325 | 0 | CacheView |
326 | 0 | *image_view; |
327 | |
|
328 | 0 | MatrixInfo |
329 | 0 | *destination_matrices, |
330 | 0 | *source_matrices; |
331 | |
|
332 | 0 | MagickBooleanType |
333 | 0 | status; |
334 | |
|
335 | 0 | size_t |
336 | 0 | count, |
337 | 0 | width; |
338 | |
|
339 | 0 | ssize_t |
340 | 0 | j, |
341 | 0 | y; |
342 | |
|
343 | 0 | unsigned char |
344 | 0 | c; |
345 | |
|
346 | 0 | unsigned short |
347 | 0 | bits[256]; |
348 | |
|
349 | 0 | for (width=1; width < ((image->columns+7)/8); width<<=1) ; |
350 | 0 | source_matrices=AcquireMatrixInfo(width,image->rows,sizeof(unsigned short), |
351 | 0 | exception); |
352 | 0 | destination_matrices=AcquireMatrixInfo(width,image->rows, |
353 | 0 | sizeof(unsigned short),exception); |
354 | 0 | if ((source_matrices == (MatrixInfo *) NULL) || |
355 | 0 | (destination_matrices == (MatrixInfo *) NULL)) |
356 | 0 | { |
357 | 0 | if (destination_matrices != (MatrixInfo *) NULL) |
358 | 0 | destination_matrices=DestroyMatrixInfo(destination_matrices); |
359 | 0 | if (source_matrices != (MatrixInfo *) NULL) |
360 | 0 | source_matrices=DestroyMatrixInfo(source_matrices); |
361 | 0 | return(MagickFalse); |
362 | 0 | } |
363 | 0 | if (NullMatrix(source_matrices) == MagickFalse) |
364 | 0 | { |
365 | 0 | destination_matrices=DestroyMatrixInfo(destination_matrices); |
366 | 0 | source_matrices=DestroyMatrixInfo(source_matrices); |
367 | 0 | return(MagickFalse); |
368 | 0 | } |
369 | 0 | for (j=0; j < 256; j++) |
370 | 0 | { |
371 | 0 | c=(unsigned char) j; |
372 | 0 | for (count=0; c != 0; c>>=1) |
373 | 0 | count+=c & 0x01; |
374 | 0 | bits[j]=(unsigned short) count; |
375 | 0 | } |
376 | 0 | status=MagickTrue; |
377 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
378 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
379 | | #pragma omp parallel for schedule(static) shared(status) \ |
380 | | magick_number_threads(image,image,image->rows,2) |
381 | | #endif |
382 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
383 | 0 | { |
384 | 0 | const Quantum |
385 | 0 | *magick_restrict p; |
386 | |
|
387 | 0 | size_t |
388 | 0 | bit, |
389 | 0 | byte; |
390 | |
|
391 | 0 | ssize_t |
392 | 0 | i, |
393 | 0 | x; |
394 | |
|
395 | 0 | unsigned short |
396 | 0 | value; |
397 | |
|
398 | 0 | if (status == MagickFalse) |
399 | 0 | continue; |
400 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
401 | 0 | if (p == (const Quantum *) NULL) |
402 | 0 | { |
403 | 0 | status=MagickFalse; |
404 | 0 | continue; |
405 | 0 | } |
406 | 0 | bit=0; |
407 | 0 | byte=0; |
408 | 0 | i=(ssize_t) (image->columns+7)/8; |
409 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
410 | 0 | { |
411 | 0 | byte<<=1; |
412 | 0 | if (((MagickRealType) GetPixelRed(image,p) < threshold) || |
413 | 0 | ((MagickRealType) GetPixelGreen(image,p) < threshold) || |
414 | 0 | ((MagickRealType) GetPixelBlue(image,p) < threshold)) |
415 | 0 | byte|=0x01; |
416 | 0 | bit++; |
417 | 0 | if (bit == 8) |
418 | 0 | { |
419 | 0 | value=bits[byte]; |
420 | 0 | (void) SetMatrixElement(source_matrices,--i,y,&value); |
421 | 0 | bit=0; |
422 | 0 | byte=0; |
423 | 0 | } |
424 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
425 | 0 | } |
426 | 0 | if (bit != 0) |
427 | 0 | { |
428 | 0 | byte<<=(8-bit); |
429 | 0 | value=bits[byte]; |
430 | 0 | (void) SetMatrixElement(source_matrices,--i,y,&value); |
431 | 0 | } |
432 | 0 | } |
433 | 0 | RadonProjection(source_matrices,destination_matrices,-1,projection); |
434 | 0 | (void) NullMatrix(source_matrices); |
435 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
436 | | #pragma omp parallel for schedule(static) shared(status) \ |
437 | | magick_number_threads(image,image,image->rows,2) |
438 | | #endif |
439 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
440 | 0 | { |
441 | 0 | const Quantum |
442 | 0 | *magick_restrict p; |
443 | |
|
444 | 0 | size_t |
445 | 0 | bit, |
446 | 0 | byte; |
447 | |
|
448 | 0 | ssize_t |
449 | 0 | i, |
450 | 0 | x; |
451 | |
|
452 | 0 | unsigned short |
453 | 0 | value; |
454 | |
|
455 | 0 | if (status == MagickFalse) |
456 | 0 | continue; |
457 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
458 | 0 | if (p == (const Quantum *) NULL) |
459 | 0 | { |
460 | 0 | status=MagickFalse; |
461 | 0 | continue; |
462 | 0 | } |
463 | 0 | bit=0; |
464 | 0 | byte=0; |
465 | 0 | i=0; |
466 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
467 | 0 | { |
468 | 0 | byte<<=1; |
469 | 0 | if (((MagickRealType) GetPixelRed(image,p) < threshold) || |
470 | 0 | ((MagickRealType) GetPixelGreen(image,p) < threshold) || |
471 | 0 | ((MagickRealType) GetPixelBlue(image,p) < threshold)) |
472 | 0 | byte|=0x01; |
473 | 0 | bit++; |
474 | 0 | if (bit == 8) |
475 | 0 | { |
476 | 0 | value=bits[byte]; |
477 | 0 | (void) SetMatrixElement(source_matrices,i++,y,&value); |
478 | 0 | bit=0; |
479 | 0 | byte=0; |
480 | 0 | } |
481 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
482 | 0 | } |
483 | 0 | if (bit != 0) |
484 | 0 | { |
485 | 0 | byte<<=(8-bit); |
486 | 0 | value=bits[byte]; |
487 | 0 | (void) SetMatrixElement(source_matrices,i++,y,&value); |
488 | 0 | } |
489 | 0 | } |
490 | 0 | RadonProjection(source_matrices,destination_matrices,1,projection); |
491 | 0 | image_view=DestroyCacheView(image_view); |
492 | 0 | destination_matrices=DestroyMatrixInfo(destination_matrices); |
493 | 0 | source_matrices=DestroyMatrixInfo(source_matrices); |
494 | 0 | return(MagickTrue); |
495 | 0 | } |
496 | | |
497 | | static void GetImageBackgroundColor(Image *image,const ssize_t offset, |
498 | | ExceptionInfo *exception) |
499 | 0 | { |
500 | 0 | CacheView |
501 | 0 | *image_view; |
502 | |
|
503 | 0 | double |
504 | 0 | count; |
505 | |
|
506 | 0 | PixelInfo |
507 | 0 | background; |
508 | |
|
509 | 0 | ssize_t |
510 | 0 | y; |
511 | | |
512 | | /* |
513 | | Compute average background color. |
514 | | */ |
515 | 0 | if (offset <= 0) |
516 | 0 | return; |
517 | 0 | GetPixelInfo(image,&background); |
518 | 0 | count=0.0; |
519 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
520 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
521 | 0 | { |
522 | 0 | const Quantum |
523 | 0 | *magick_restrict p; |
524 | |
|
525 | 0 | ssize_t |
526 | 0 | x; |
527 | |
|
528 | 0 | if ((y >= offset) && (y < ((ssize_t) image->rows-offset))) |
529 | 0 | continue; |
530 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
531 | 0 | if (p == (const Quantum *) NULL) |
532 | 0 | continue; |
533 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
534 | 0 | { |
535 | 0 | if ((x >= offset) && (x < ((ssize_t) image->columns-offset))) |
536 | 0 | continue; |
537 | 0 | background.red+=QuantumScale*(double) GetPixelRed(image,p); |
538 | 0 | background.green+=QuantumScale*(double) GetPixelGreen(image,p); |
539 | 0 | background.blue+=QuantumScale*(double) GetPixelBlue(image,p); |
540 | 0 | if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
541 | 0 | background.alpha+=QuantumScale*(double) GetPixelAlpha(image,p); |
542 | 0 | count++; |
543 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
544 | 0 | } |
545 | 0 | } |
546 | 0 | image_view=DestroyCacheView(image_view); |
547 | 0 | image->background_color.red=(double) ClampToQuantum((double) QuantumRange* |
548 | 0 | (double) background.red/count); |
549 | 0 | image->background_color.green=(double) ClampToQuantum((double) QuantumRange* |
550 | 0 | (double) background.green/count); |
551 | 0 | image->background_color.blue=(double) ClampToQuantum((double) QuantumRange* |
552 | 0 | (double) background.blue/count); |
553 | 0 | if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
554 | 0 | image->background_color.alpha=(double) ClampToQuantum((double) QuantumRange* |
555 | 0 | (double) background.alpha/count); |
556 | 0 | } |
557 | | |
558 | | MagickExport Image *DeskewImage(const Image *image,const double threshold, |
559 | | ExceptionInfo *exception) |
560 | 0 | { |
561 | 0 | AffineMatrix |
562 | 0 | affine_matrix; |
563 | |
|
564 | 0 | const char |
565 | 0 | *artifact; |
566 | |
|
567 | 0 | double |
568 | 0 | degrees; |
569 | |
|
570 | 0 | Image |
571 | 0 | *clone_image, |
572 | 0 | *crop_image, |
573 | 0 | *deskew_image, |
574 | 0 | *median_image; |
575 | |
|
576 | 0 | MagickBooleanType |
577 | 0 | status; |
578 | |
|
579 | 0 | RectangleInfo |
580 | 0 | geometry; |
581 | |
|
582 | 0 | size_t |
583 | 0 | max_projection, |
584 | 0 | *projection, |
585 | 0 | width; |
586 | |
|
587 | 0 | ssize_t |
588 | 0 | i, |
589 | 0 | skew; |
590 | | |
591 | | /* |
592 | | Compute deskew angle. |
593 | | */ |
594 | 0 | for (width=1; width < ((image->columns+7)/8); width<<=1) ; |
595 | 0 | projection=(size_t *) AcquireQuantumMemory((size_t) (2*width-1), |
596 | 0 | sizeof(*projection)); |
597 | 0 | if (projection == (size_t *) NULL) |
598 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
599 | 0 | status=RadonTransform(image,threshold,projection,exception); |
600 | 0 | if (status == MagickFalse) |
601 | 0 | { |
602 | 0 | projection=(size_t *) RelinquishMagickMemory(projection); |
603 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
604 | 0 | } |
605 | 0 | max_projection=0; |
606 | 0 | skew=0; |
607 | 0 | for (i=0; i < (ssize_t) (2*width-1); i++) |
608 | 0 | { |
609 | 0 | if (projection[i] > max_projection) |
610 | 0 | { |
611 | 0 | skew=i-(ssize_t) width+1; |
612 | 0 | max_projection=projection[i]; |
613 | 0 | } |
614 | 0 | } |
615 | 0 | projection=(size_t *) RelinquishMagickMemory(projection); |
616 | 0 | degrees=RadiansToDegrees(-atan((double) skew/width/8)); |
617 | 0 | if (image->debug != MagickFalse) |
618 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
619 | 0 | " Deskew angle: %g",degrees); |
620 | | /* |
621 | | Deskew image. |
622 | | */ |
623 | 0 | clone_image=CloneImage(image,0,0,MagickTrue,exception); |
624 | 0 | if (clone_image == (Image *) NULL) |
625 | 0 | return((Image *) NULL); |
626 | 0 | { |
627 | 0 | char |
628 | 0 | angle[MagickPathExtent]; |
629 | |
|
630 | 0 | (void) FormatLocaleString(angle,MagickPathExtent,"%.20g",degrees); |
631 | 0 | (void) SetImageArtifact(clone_image,"deskew:angle",angle); |
632 | 0 | } |
633 | 0 | (void) SetImageVirtualPixelMethod(clone_image,BackgroundVirtualPixelMethod, |
634 | 0 | exception); |
635 | 0 | affine_matrix.sx=cos(DegreesToRadians(fmod((double) degrees,360.0))); |
636 | 0 | affine_matrix.rx=sin(DegreesToRadians(fmod((double) degrees,360.0))); |
637 | 0 | affine_matrix.ry=(-sin(DegreesToRadians(fmod((double) degrees,360.0)))); |
638 | 0 | affine_matrix.sy=cos(DegreesToRadians(fmod((double) degrees,360.0))); |
639 | 0 | affine_matrix.tx=0.0; |
640 | 0 | affine_matrix.ty=0.0; |
641 | 0 | artifact=GetImageArtifact(image,"deskew:auto-crop"); |
642 | 0 | if (IsStringTrue(artifact) == MagickFalse) |
643 | 0 | { |
644 | 0 | deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); |
645 | 0 | clone_image=DestroyImage(clone_image); |
646 | 0 | return(deskew_image); |
647 | 0 | } |
648 | | /* |
649 | | Auto-crop image. |
650 | | */ |
651 | 0 | GetImageBackgroundColor(clone_image,(ssize_t) StringToLong(artifact), |
652 | 0 | exception); |
653 | 0 | deskew_image=AffineTransformImage(clone_image,&affine_matrix,exception); |
654 | 0 | clone_image=DestroyImage(clone_image); |
655 | 0 | if (deskew_image == (Image *) NULL) |
656 | 0 | return((Image *) NULL); |
657 | 0 | median_image=StatisticImage(deskew_image,MedianStatistic,3,3,exception); |
658 | 0 | if (median_image == (Image *) NULL) |
659 | 0 | { |
660 | 0 | deskew_image=DestroyImage(deskew_image); |
661 | 0 | return((Image *) NULL); |
662 | 0 | } |
663 | 0 | geometry=GetImageBoundingBox(median_image,exception); |
664 | 0 | median_image=DestroyImage(median_image); |
665 | 0 | if (image->debug != MagickFalse) |
666 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule()," Deskew geometry: " |
667 | 0 | "%.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double) |
668 | 0 | geometry.height,(double) geometry.x,(double) geometry.y); |
669 | 0 | crop_image=CropImage(deskew_image,&geometry,exception); |
670 | 0 | deskew_image=DestroyImage(deskew_image); |
671 | 0 | return(crop_image); |
672 | 0 | } |
673 | | |
674 | | /* |
675 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
676 | | % % |
677 | | % % |
678 | | % % |
679 | | % I n t e g r a l R o t a t e I m a g e % |
680 | | % % |
681 | | % % |
682 | | % % |
683 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
684 | | % |
685 | | % IntegralRotateImage() rotates the image an integral of 90 degrees. It |
686 | | % allocates the memory necessary for the new Image structure and returns a |
687 | | % pointer to the rotated image. |
688 | | % |
689 | | % The format of the IntegralRotateImage method is: |
690 | | % |
691 | | % Image *IntegralRotateImage(const Image *image,size_t rotations, |
692 | | % ExceptionInfo *exception) |
693 | | % |
694 | | % A description of each parameter follows. |
695 | | % |
696 | | % o image: the image. |
697 | | % |
698 | | % o rotations: Specifies the number of 90 degree rotations. |
699 | | % |
700 | | */ |
701 | | MagickExport Image *IntegralRotateImage(const Image *image,size_t rotations, |
702 | | ExceptionInfo *exception) |
703 | 4.98k | { |
704 | 4.98k | #define RotateImageTag "Rotate/Image" |
705 | | |
706 | 4.98k | CacheView |
707 | 4.98k | *image_view, |
708 | 4.98k | *rotate_view; |
709 | | |
710 | 4.98k | Image |
711 | 4.98k | *rotate_image; |
712 | | |
713 | 4.98k | MagickBooleanType |
714 | 4.98k | status; |
715 | | |
716 | 4.98k | MagickOffsetType |
717 | 4.98k | progress; |
718 | | |
719 | 4.98k | RectangleInfo |
720 | 4.98k | page; |
721 | | |
722 | | /* |
723 | | Initialize rotated image attributes. |
724 | | */ |
725 | 4.98k | assert(image != (Image *) NULL); |
726 | 4.98k | page=image->page; |
727 | 4.98k | rotations%=4; |
728 | 4.98k | switch (rotations) |
729 | 4.98k | { |
730 | 0 | case 0: |
731 | 0 | default: |
732 | 0 | { |
733 | 0 | rotate_image=CloneImage(image,0,0,MagickTrue,exception); |
734 | 0 | break; |
735 | 0 | } |
736 | 0 | case 2: |
737 | 0 | { |
738 | 0 | rotate_image=CloneImage(image,image->columns,image->rows,MagickTrue, |
739 | 0 | exception); |
740 | 0 | break; |
741 | 0 | } |
742 | 4.98k | case 1: |
743 | 4.98k | case 3: |
744 | 4.98k | { |
745 | 4.98k | rotate_image=CloneImage(image,image->rows,image->columns,MagickTrue, |
746 | 4.98k | exception); |
747 | 4.98k | break; |
748 | 4.98k | } |
749 | 4.98k | } |
750 | 4.98k | if (rotate_image == (Image *) NULL) |
751 | 0 | return((Image *) NULL); |
752 | 4.98k | if (rotations == 0) |
753 | 0 | return(rotate_image); |
754 | | /* |
755 | | Integral rotate the image. |
756 | | */ |
757 | 4.98k | status=MagickTrue; |
758 | 4.98k | progress=0; |
759 | 4.98k | image_view=AcquireVirtualCacheView(image,exception); |
760 | 4.98k | rotate_view=AcquireAuthenticCacheView(rotate_image,exception); |
761 | 4.98k | switch (rotations) |
762 | 4.98k | { |
763 | 4.98k | case 1: |
764 | 4.98k | { |
765 | 4.98k | size_t |
766 | 4.98k | tile_height, |
767 | 4.98k | tile_width; |
768 | | |
769 | 4.98k | ssize_t |
770 | 4.98k | tile_y; |
771 | | |
772 | | /* |
773 | | Rotate 90 degrees. |
774 | | */ |
775 | 4.98k | GetPixelCacheTileSize(image,&tile_width,&tile_height); |
776 | 4.98k | tile_width=image->columns; |
777 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
778 | | #pragma omp parallel for schedule(static) shared(status) \ |
779 | | magick_number_threads(image,rotate_image,image->rows/tile_height,2) |
780 | | #endif |
781 | 10.0k | for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) |
782 | 5.11k | { |
783 | 5.11k | ssize_t |
784 | 5.11k | tile_x; |
785 | | |
786 | 5.11k | if (status == MagickFalse) |
787 | 0 | continue; |
788 | 5.11k | tile_x=0; |
789 | 10.2k | for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) |
790 | 5.11k | { |
791 | 5.11k | const Quantum |
792 | 5.11k | *magick_restrict p; |
793 | | |
794 | 5.11k | MagickBooleanType |
795 | 5.11k | sync; |
796 | | |
797 | 5.11k | Quantum |
798 | 5.11k | *magick_restrict q; |
799 | | |
800 | 5.11k | size_t |
801 | 5.11k | height, |
802 | 5.11k | width; |
803 | | |
804 | 5.11k | ssize_t |
805 | 5.11k | y; |
806 | | |
807 | 5.11k | width=tile_width; |
808 | 5.11k | if ((tile_width+(size_t) tile_x) > image->columns) |
809 | 0 | width=(size_t) ((ssize_t) tile_width-(tile_x+(ssize_t) tile_width- |
810 | 0 | (ssize_t) image->columns)); |
811 | 5.11k | height=tile_height; |
812 | 5.11k | if ((tile_height+(size_t) tile_y) > image->rows) |
813 | 4.97k | height=(size_t) ((ssize_t) tile_height-(tile_y+(ssize_t) |
814 | 4.97k | tile_height-(ssize_t) image->rows)); |
815 | 5.11k | p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, |
816 | 5.11k | exception); |
817 | 5.11k | if (p == (const Quantum *) NULL) |
818 | 0 | { |
819 | 0 | status=MagickFalse; |
820 | 0 | break; |
821 | 0 | } |
822 | 57.9k | for (y=0; y < (ssize_t) width; y++) |
823 | 52.7k | { |
824 | 52.7k | const Quantum |
825 | 52.7k | *magick_restrict tile_pixels; |
826 | | |
827 | 52.7k | ssize_t |
828 | 52.7k | x; |
829 | | |
830 | 52.7k | if (status == MagickFalse) |
831 | 0 | continue; |
832 | 52.7k | q=QueueCacheViewAuthenticPixels(rotate_view,(ssize_t) |
833 | 52.7k | rotate_image->columns-(tile_y+(ssize_t) height),y+tile_x,height, |
834 | 52.7k | 1,exception); |
835 | 52.7k | if (q == (Quantum *) NULL) |
836 | 0 | { |
837 | 0 | status=MagickFalse; |
838 | 0 | continue; |
839 | 0 | } |
840 | 52.7k | tile_pixels=p+(((ssize_t) height-1)*(ssize_t) width+y)*(ssize_t) |
841 | 52.7k | GetPixelChannels(image); |
842 | 212k | for (x=0; x < (ssize_t) height; x++) |
843 | 159k | { |
844 | 159k | ssize_t |
845 | 159k | i; |
846 | | |
847 | 446k | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
848 | 287k | { |
849 | 287k | PixelChannel channel = GetPixelChannelChannel(image,i); |
850 | 287k | PixelTrait traits = GetPixelChannelTraits(image,channel); |
851 | 287k | PixelTrait rotate_traits = GetPixelChannelTraits(rotate_image, |
852 | 287k | channel); |
853 | 287k | if ((traits == UndefinedPixelTrait) || |
854 | 287k | (rotate_traits == UndefinedPixelTrait)) |
855 | 0 | continue; |
856 | 287k | SetPixelChannel(rotate_image,channel,tile_pixels[i],q); |
857 | 287k | } |
858 | 159k | tile_pixels-=width*GetPixelChannels(image); |
859 | 159k | q+=(ptrdiff_t) GetPixelChannels(rotate_image); |
860 | 159k | } |
861 | 52.7k | sync=SyncCacheViewAuthenticPixels(rotate_view,exception); |
862 | 52.7k | if (sync == MagickFalse) |
863 | 0 | status=MagickFalse; |
864 | 52.7k | } |
865 | 5.11k | } |
866 | 5.11k | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
867 | 0 | { |
868 | 0 | MagickBooleanType |
869 | 0 | proceed; |
870 | |
|
871 | 0 | proceed=SetImageProgress(image,RotateImageTag, |
872 | 0 | progress+=(MagickOffsetType) tile_height,image->rows); |
873 | 0 | if (proceed == MagickFalse) |
874 | 0 | status=MagickFalse; |
875 | 0 | } |
876 | 5.11k | } |
877 | 4.98k | (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) |
878 | 4.98k | image->rows-1,image->rows); |
879 | 4.98k | Swap(page.width,page.height); |
880 | 4.98k | Swap(page.x,page.y); |
881 | 4.98k | if (page.width != 0) |
882 | 0 | page.x=(ssize_t) page.width-(ssize_t) rotate_image->columns-page.x; |
883 | 4.98k | break; |
884 | 0 | } |
885 | 0 | case 2: |
886 | 0 | { |
887 | 0 | ssize_t |
888 | 0 | y; |
889 | | |
890 | | /* |
891 | | Rotate 180 degrees. |
892 | | */ |
893 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
894 | | #pragma omp parallel for schedule(static) shared(status) \ |
895 | | magick_number_threads(image,rotate_image,image->rows,2) |
896 | | #endif |
897 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
898 | 0 | { |
899 | 0 | const Quantum |
900 | 0 | *magick_restrict p; |
901 | |
|
902 | 0 | MagickBooleanType |
903 | 0 | sync; |
904 | |
|
905 | 0 | Quantum |
906 | 0 | *magick_restrict q; |
907 | |
|
908 | 0 | ssize_t |
909 | 0 | x; |
910 | |
|
911 | 0 | if (status == MagickFalse) |
912 | 0 | continue; |
913 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
914 | 0 | q=QueueCacheViewAuthenticPixels(rotate_view,0,(ssize_t) image->rows-y-1, |
915 | 0 | image->columns,1,exception); |
916 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
917 | 0 | { |
918 | 0 | status=MagickFalse; |
919 | 0 | continue; |
920 | 0 | } |
921 | 0 | q+=(ptrdiff_t) GetPixelChannels(rotate_image)*image->columns; |
922 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
923 | 0 | { |
924 | 0 | ssize_t |
925 | 0 | i; |
926 | |
|
927 | 0 | q-=GetPixelChannels(rotate_image); |
928 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
929 | 0 | { |
930 | 0 | PixelChannel channel = GetPixelChannelChannel(image,i); |
931 | 0 | PixelTrait traits = GetPixelChannelTraits(image,channel); |
932 | 0 | PixelTrait rotate_traits = GetPixelChannelTraits(rotate_image, |
933 | 0 | channel); |
934 | 0 | if ((traits == UndefinedPixelTrait) || |
935 | 0 | (rotate_traits == UndefinedPixelTrait)) |
936 | 0 | continue; |
937 | 0 | SetPixelChannel(rotate_image,channel,p[i],q); |
938 | 0 | } |
939 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
940 | 0 | } |
941 | 0 | sync=SyncCacheViewAuthenticPixels(rotate_view,exception); |
942 | 0 | if (sync == MagickFalse) |
943 | 0 | status=MagickFalse; |
944 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
945 | 0 | { |
946 | 0 | MagickBooleanType |
947 | 0 | proceed; |
948 | |
|
949 | 0 | proceed=SetImageProgress(image,RotateImageTag,progress++, |
950 | 0 | image->rows); |
951 | 0 | if (proceed == MagickFalse) |
952 | 0 | status=MagickFalse; |
953 | 0 | } |
954 | 0 | } |
955 | 0 | (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) |
956 | 0 | image->rows-1,image->rows); |
957 | 0 | if (page.width != 0) |
958 | 0 | page.x=(ssize_t) page.width-(ssize_t) rotate_image->columns-page.x; |
959 | 0 | if (page.height != 0) |
960 | 0 | page.y=(ssize_t) page.height-(ssize_t) rotate_image->rows-page.y; |
961 | 0 | break; |
962 | 0 | } |
963 | 0 | case 3: |
964 | 0 | { |
965 | 0 | size_t |
966 | 0 | tile_height, |
967 | 0 | tile_width; |
968 | |
|
969 | 0 | ssize_t |
970 | 0 | tile_y; |
971 | | |
972 | | /* |
973 | | Rotate 270 degrees. |
974 | | */ |
975 | 0 | GetPixelCacheTileSize(image,&tile_width,&tile_height); |
976 | 0 | tile_width=image->columns; |
977 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
978 | | #pragma omp parallel for schedule(static) shared(status) \ |
979 | | magick_number_threads(image,rotate_image,image->rows/tile_height,2) |
980 | | #endif |
981 | 0 | for (tile_y=0; tile_y < (ssize_t) image->rows; tile_y+=(ssize_t) tile_height) |
982 | 0 | { |
983 | 0 | ssize_t |
984 | 0 | tile_x; |
985 | |
|
986 | 0 | if (status == MagickFalse) |
987 | 0 | continue; |
988 | 0 | tile_x=0; |
989 | 0 | for ( ; tile_x < (ssize_t) image->columns; tile_x+=(ssize_t) tile_width) |
990 | 0 | { |
991 | 0 | MagickBooleanType |
992 | 0 | sync; |
993 | |
|
994 | 0 | const Quantum |
995 | 0 | *magick_restrict p; |
996 | |
|
997 | 0 | Quantum |
998 | 0 | *magick_restrict q; |
999 | |
|
1000 | 0 | size_t |
1001 | 0 | height, |
1002 | 0 | width; |
1003 | |
|
1004 | 0 | ssize_t |
1005 | 0 | y; |
1006 | |
|
1007 | 0 | width=tile_width; |
1008 | 0 | if ((tile_width+(size_t) tile_x) > image->columns) |
1009 | 0 | width=(size_t) ((ssize_t) tile_width-(tile_x+(ssize_t) tile_width- |
1010 | 0 | (ssize_t) image->columns)); |
1011 | 0 | height=tile_height; |
1012 | 0 | if ((tile_height+(size_t) tile_y) > image->rows) |
1013 | 0 | height=(size_t) ((ssize_t) tile_height-(tile_y+(ssize_t) |
1014 | 0 | tile_height-(ssize_t) image->rows)); |
1015 | 0 | p=GetCacheViewVirtualPixels(image_view,tile_x,tile_y,width,height, |
1016 | 0 | exception); |
1017 | 0 | if (p == (const Quantum *) NULL) |
1018 | 0 | { |
1019 | 0 | status=MagickFalse; |
1020 | 0 | break; |
1021 | 0 | } |
1022 | 0 | for (y=0; y < (ssize_t) width; y++) |
1023 | 0 | { |
1024 | 0 | const Quantum |
1025 | 0 | *magick_restrict tile_pixels; |
1026 | |
|
1027 | 0 | ssize_t |
1028 | 0 | x; |
1029 | |
|
1030 | 0 | if (status == MagickFalse) |
1031 | 0 | continue; |
1032 | 0 | q=QueueCacheViewAuthenticPixels(rotate_view,tile_y,y+(ssize_t) |
1033 | 0 | rotate_image->rows-(tile_x+(ssize_t) width),height,1,exception); |
1034 | 0 | if (q == (Quantum *) NULL) |
1035 | 0 | { |
1036 | 0 | status=MagickFalse; |
1037 | 0 | continue; |
1038 | 0 | } |
1039 | 0 | tile_pixels=p+(((ssize_t) width-1)-y)*(ssize_t) |
1040 | 0 | GetPixelChannels(image); |
1041 | 0 | for (x=0; x < (ssize_t) height; x++) |
1042 | 0 | { |
1043 | 0 | ssize_t |
1044 | 0 | i; |
1045 | |
|
1046 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
1047 | 0 | { |
1048 | 0 | PixelChannel channel = GetPixelChannelChannel(image,i); |
1049 | 0 | PixelTrait traits = GetPixelChannelTraits(image,channel); |
1050 | 0 | PixelTrait rotate_traits = GetPixelChannelTraits(rotate_image, |
1051 | 0 | channel); |
1052 | 0 | if ((traits == UndefinedPixelTrait) || |
1053 | 0 | (rotate_traits == UndefinedPixelTrait)) |
1054 | 0 | continue; |
1055 | 0 | SetPixelChannel(rotate_image,channel,tile_pixels[i],q); |
1056 | 0 | } |
1057 | 0 | tile_pixels+=width*GetPixelChannels(image); |
1058 | 0 | q+=(ptrdiff_t) GetPixelChannels(rotate_image); |
1059 | 0 | } |
1060 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1061 | | #pragma omp critical (MagickCore_IntegralRotateImage) |
1062 | | #endif |
1063 | 0 | sync=SyncCacheViewAuthenticPixels(rotate_view,exception); |
1064 | 0 | if (sync == MagickFalse) |
1065 | 0 | status=MagickFalse; |
1066 | 0 | } |
1067 | 0 | } |
1068 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1069 | 0 | { |
1070 | 0 | MagickBooleanType |
1071 | 0 | proceed; |
1072 | |
|
1073 | 0 | proceed=SetImageProgress(image,RotateImageTag, |
1074 | 0 | progress+=(MagickOffsetType) tile_height,image->rows); |
1075 | 0 | if (proceed == MagickFalse) |
1076 | 0 | status=MagickFalse; |
1077 | 0 | } |
1078 | 0 | } |
1079 | 0 | (void) SetImageProgress(image,RotateImageTag,(MagickOffsetType) |
1080 | 0 | image->rows-1,image->rows); |
1081 | 0 | Swap(page.width,page.height); |
1082 | 0 | Swap(page.x,page.y); |
1083 | 0 | if (page.height != 0) |
1084 | 0 | page.y=(ssize_t) page.height-(ssize_t) rotate_image->rows-page.y; |
1085 | 0 | break; |
1086 | 0 | } |
1087 | 0 | default: |
1088 | 0 | break; |
1089 | 4.98k | } |
1090 | 4.98k | rotate_view=DestroyCacheView(rotate_view); |
1091 | 4.98k | image_view=DestroyCacheView(image_view); |
1092 | 4.98k | rotate_image->type=image->type; |
1093 | 4.98k | rotate_image->page=page; |
1094 | 4.98k | if (status != MagickFalse) |
1095 | 4.98k | { |
1096 | 4.98k | char |
1097 | 4.98k | transform[MagickPathExtent]; |
1098 | | |
1099 | 4.98k | (void) FormatLocaleString(transform,MagickPathExtent, |
1100 | 4.98k | "rotate %.20gx%.20g %.20g",(double) image->columns, |
1101 | 4.98k | (double) image->rows,(double) rotations); |
1102 | 4.98k | AppendImageProfileProperty(rotate_image,"hdrgm","hdrgm:Transform", |
1103 | 4.98k | transform,exception); |
1104 | 4.98k | } |
1105 | 4.98k | if (status == MagickFalse) |
1106 | 0 | rotate_image=DestroyImage(rotate_image); |
1107 | 4.98k | return(rotate_image); |
1108 | 4.98k | } |
1109 | | |
1110 | | /* |
1111 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1112 | | % % |
1113 | | % % |
1114 | | % % |
1115 | | + X S h e a r I m a g e % |
1116 | | % % |
1117 | | % % |
1118 | | % % |
1119 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1120 | | % |
1121 | | % XShearImage() shears the image in the X direction with a shear angle of |
1122 | | % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and |
1123 | | % negative angles shear clockwise. Angles are measured relative to a vertical |
1124 | | % Y-axis. X shears will widen an image creating 'empty' triangles on the left |
1125 | | % and right sides of the source image. |
1126 | | % |
1127 | | % The format of the XShearImage method is: |
1128 | | % |
1129 | | % MagickBooleanType XShearImage(Image *image,const double degrees, |
1130 | | % const size_t width,const size_t height, |
1131 | | % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) |
1132 | | % |
1133 | | % A description of each parameter follows. |
1134 | | % |
1135 | | % o image: the image. |
1136 | | % |
1137 | | % o degrees: A double representing the shearing angle along the X |
1138 | | % axis. |
1139 | | % |
1140 | | % o width, height, x_offset, y_offset: Defines a region of the image |
1141 | | % to shear. |
1142 | | % |
1143 | | % o exception: return any errors or warnings in this structure. |
1144 | | % |
1145 | | */ |
1146 | | static MagickBooleanType XShearImage(Image *image,const double degrees, |
1147 | | const size_t width,const size_t height,const ssize_t x_offset, |
1148 | | const ssize_t y_offset,ExceptionInfo *exception) |
1149 | 0 | { |
1150 | 0 | #define XShearImageTag "XShear/Image" |
1151 | |
|
1152 | 0 | typedef enum |
1153 | 0 | { |
1154 | 0 | LEFT, |
1155 | 0 | RIGHT |
1156 | 0 | } ShearDirection; |
1157 | |
|
1158 | 0 | CacheView |
1159 | 0 | *image_view; |
1160 | |
|
1161 | 0 | MagickBooleanType |
1162 | 0 | status; |
1163 | |
|
1164 | 0 | MagickOffsetType |
1165 | 0 | progress; |
1166 | |
|
1167 | 0 | PixelInfo |
1168 | 0 | background; |
1169 | |
|
1170 | 0 | ssize_t |
1171 | 0 | y; |
1172 | | |
1173 | | /* |
1174 | | X shear image. |
1175 | | */ |
1176 | 0 | assert(image != (Image *) NULL); |
1177 | 0 | assert(image->signature == MagickCoreSignature); |
1178 | 0 | if (IsEventLogging() != MagickFalse) |
1179 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1180 | 0 | status=MagickTrue; |
1181 | 0 | background=image->background_color; |
1182 | 0 | progress=0; |
1183 | 0 | image_view=AcquireAuthenticCacheView(image,exception); |
1184 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1185 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
1186 | | magick_number_threads(image,image,height,1) |
1187 | | #endif |
1188 | 0 | for (y=0; y < (ssize_t) height; y++) |
1189 | 0 | { |
1190 | 0 | double |
1191 | 0 | area, |
1192 | 0 | displacement; |
1193 | |
|
1194 | 0 | PixelInfo |
1195 | 0 | pixel, |
1196 | 0 | source, |
1197 | 0 | destination; |
1198 | |
|
1199 | 0 | Quantum |
1200 | 0 | *magick_restrict p, |
1201 | 0 | *magick_restrict q; |
1202 | |
|
1203 | 0 | ShearDirection |
1204 | 0 | direction; |
1205 | |
|
1206 | 0 | ssize_t |
1207 | 0 | i, |
1208 | 0 | step; |
1209 | |
|
1210 | 0 | if (status == MagickFalse) |
1211 | 0 | continue; |
1212 | 0 | p=GetCacheViewAuthenticPixels(image_view,0,y_offset+y,image->columns,1, |
1213 | 0 | exception); |
1214 | 0 | if (p == (Quantum *) NULL) |
1215 | 0 | { |
1216 | 0 | status=MagickFalse; |
1217 | 0 | continue; |
1218 | 0 | } |
1219 | 0 | p+=(ptrdiff_t) x_offset*(ssize_t) GetPixelChannels(image); |
1220 | 0 | displacement=degrees*(double) (y-height/2.0); |
1221 | 0 | if (displacement == 0.0) |
1222 | 0 | continue; |
1223 | 0 | if (displacement > 0.0) |
1224 | 0 | direction=RIGHT; |
1225 | 0 | else |
1226 | 0 | { |
1227 | 0 | displacement*=(-1.0); |
1228 | 0 | direction=LEFT; |
1229 | 0 | } |
1230 | 0 | step=CastDoubleToSsizeT(floor((double) displacement)); |
1231 | 0 | area=(double) (displacement-step); |
1232 | 0 | step++; |
1233 | 0 | pixel=background; |
1234 | 0 | GetPixelInfo(image,&source); |
1235 | 0 | GetPixelInfo(image,&destination); |
1236 | 0 | switch (direction) |
1237 | 0 | { |
1238 | 0 | case LEFT: |
1239 | 0 | { |
1240 | | /* |
1241 | | Transfer pixels left-to-right. |
1242 | | */ |
1243 | 0 | if (step > x_offset) |
1244 | 0 | break; |
1245 | 0 | q=p-step*(ssize_t) GetPixelChannels(image); |
1246 | 0 | for (i=0; i < (ssize_t) width; i++) |
1247 | 0 | { |
1248 | 0 | if ((x_offset+i) < step) |
1249 | 0 | { |
1250 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
1251 | 0 | GetPixelInfoPixel(image,p,&pixel); |
1252 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1253 | 0 | continue; |
1254 | 0 | } |
1255 | 0 | GetPixelInfoPixel(image,p,&source); |
1256 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1257 | 0 | &source,(double) GetPixelAlpha(image,p),area,&destination); |
1258 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1259 | 0 | GetPixelInfoPixel(image,p,&pixel); |
1260 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
1261 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1262 | 0 | } |
1263 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1264 | 0 | &background,(double) background.alpha,area,&destination); |
1265 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1266 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1267 | 0 | for (i=0; i < (step-1); i++) |
1268 | 0 | { |
1269 | 0 | SetPixelViaPixelInfo(image,&background,q); |
1270 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1271 | 0 | } |
1272 | 0 | break; |
1273 | 0 | } |
1274 | 0 | case RIGHT: |
1275 | 0 | { |
1276 | | /* |
1277 | | Transfer pixels right-to-left. |
1278 | | */ |
1279 | 0 | p+=(ptrdiff_t) width*GetPixelChannels(image); |
1280 | 0 | q=p+step*(ssize_t) GetPixelChannels(image); |
1281 | 0 | for (i=0; i < (ssize_t) width; i++) |
1282 | 0 | { |
1283 | 0 | p-=(ptrdiff_t)GetPixelChannels(image); |
1284 | 0 | q-=GetPixelChannels(image); |
1285 | 0 | if ((size_t) (x_offset+(ssize_t) width+step-i) > image->columns) |
1286 | 0 | continue; |
1287 | 0 | GetPixelInfoPixel(image,p,&source); |
1288 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1289 | 0 | &source,(double) GetPixelAlpha(image,p),area,&destination); |
1290 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1291 | 0 | GetPixelInfoPixel(image,p,&pixel); |
1292 | 0 | } |
1293 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1294 | 0 | &background,(double) background.alpha,area,&destination); |
1295 | 0 | q-=GetPixelChannels(image); |
1296 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1297 | 0 | for (i=0; i < (step-1); i++) |
1298 | 0 | { |
1299 | 0 | q-=GetPixelChannels(image); |
1300 | 0 | SetPixelViaPixelInfo(image,&background,q); |
1301 | 0 | } |
1302 | 0 | break; |
1303 | 0 | } |
1304 | 0 | } |
1305 | 0 | if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
1306 | 0 | status=MagickFalse; |
1307 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1308 | 0 | { |
1309 | 0 | MagickBooleanType |
1310 | 0 | proceed; |
1311 | |
|
1312 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1313 | | #pragma omp atomic |
1314 | | #endif |
1315 | 0 | progress++; |
1316 | 0 | proceed=SetImageProgress(image,XShearImageTag,progress,height); |
1317 | 0 | if (proceed == MagickFalse) |
1318 | 0 | status=MagickFalse; |
1319 | 0 | } |
1320 | 0 | } |
1321 | 0 | image_view=DestroyCacheView(image_view); |
1322 | 0 | return(status); |
1323 | 0 | } |
1324 | | |
1325 | | /* |
1326 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1327 | | % % |
1328 | | % % |
1329 | | % % |
1330 | | + Y S h e a r I m a g e % |
1331 | | % % |
1332 | | % % |
1333 | | % % |
1334 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1335 | | % |
1336 | | % YShearImage shears the image in the Y direction with a shear angle of |
1337 | | % 'degrees'. Positive angles shear counter-clockwise (right-hand rule), and |
1338 | | % negative angles shear clockwise. Angles are measured relative to a |
1339 | | % horizontal X-axis. Y shears will increase the height of an image creating |
1340 | | % 'empty' triangles on the top and bottom of the source image. |
1341 | | % |
1342 | | % The format of the YShearImage method is: |
1343 | | % |
1344 | | % MagickBooleanType YShearImage(Image *image,const double degrees, |
1345 | | % const size_t width,const size_t height, |
1346 | | % const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) |
1347 | | % |
1348 | | % A description of each parameter follows. |
1349 | | % |
1350 | | % o image: the image. |
1351 | | % |
1352 | | % o degrees: A double representing the shearing angle along the Y |
1353 | | % axis. |
1354 | | % |
1355 | | % o width, height, x_offset, y_offset: Defines a region of the image |
1356 | | % to shear. |
1357 | | % |
1358 | | % o exception: return any errors or warnings in this structure. |
1359 | | % |
1360 | | */ |
1361 | | static MagickBooleanType YShearImage(Image *image,const double degrees, |
1362 | | const size_t width,const size_t height,const ssize_t x_offset, |
1363 | | const ssize_t y_offset,ExceptionInfo *exception) |
1364 | 0 | { |
1365 | 0 | #define YShearImageTag "YShear/Image" |
1366 | |
|
1367 | 0 | typedef enum |
1368 | 0 | { |
1369 | 0 | UP, |
1370 | 0 | DOWN |
1371 | 0 | } ShearDirection; |
1372 | |
|
1373 | 0 | CacheView |
1374 | 0 | *image_view; |
1375 | |
|
1376 | 0 | MagickBooleanType |
1377 | 0 | status; |
1378 | |
|
1379 | 0 | MagickOffsetType |
1380 | 0 | progress; |
1381 | |
|
1382 | 0 | PixelInfo |
1383 | 0 | background; |
1384 | |
|
1385 | 0 | ssize_t |
1386 | 0 | x; |
1387 | | |
1388 | | /* |
1389 | | Y Shear image. |
1390 | | */ |
1391 | 0 | assert(image != (Image *) NULL); |
1392 | 0 | assert(image->signature == MagickCoreSignature); |
1393 | 0 | if (IsEventLogging() != MagickFalse) |
1394 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1395 | 0 | status=MagickTrue; |
1396 | 0 | progress=0; |
1397 | 0 | background=image->background_color; |
1398 | 0 | image_view=AcquireAuthenticCacheView(image,exception); |
1399 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1400 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
1401 | | magick_number_threads(image,image,width,1) |
1402 | | #endif |
1403 | 0 | for (x=0; x < (ssize_t) width; x++) |
1404 | 0 | { |
1405 | 0 | double |
1406 | 0 | area, |
1407 | 0 | displacement; |
1408 | |
|
1409 | 0 | PixelInfo |
1410 | 0 | pixel, |
1411 | 0 | source, |
1412 | 0 | destination; |
1413 | |
|
1414 | 0 | Quantum |
1415 | 0 | *magick_restrict p, |
1416 | 0 | *magick_restrict q; |
1417 | |
|
1418 | 0 | ShearDirection |
1419 | 0 | direction; |
1420 | |
|
1421 | 0 | ssize_t |
1422 | 0 | i, |
1423 | 0 | step; |
1424 | |
|
1425 | 0 | if (status == MagickFalse) |
1426 | 0 | continue; |
1427 | 0 | p=GetCacheViewAuthenticPixels(image_view,x_offset+x,0,1,image->rows, |
1428 | 0 | exception); |
1429 | 0 | if (p == (Quantum *) NULL) |
1430 | 0 | { |
1431 | 0 | status=MagickFalse; |
1432 | 0 | continue; |
1433 | 0 | } |
1434 | 0 | p+=(ptrdiff_t) y_offset*(ssize_t) GetPixelChannels(image); |
1435 | 0 | displacement=degrees*(double) (x-width/2.0); |
1436 | 0 | if (displacement == 0.0) |
1437 | 0 | continue; |
1438 | 0 | if (displacement > 0.0) |
1439 | 0 | direction=DOWN; |
1440 | 0 | else |
1441 | 0 | { |
1442 | 0 | displacement*=(-1.0); |
1443 | 0 | direction=UP; |
1444 | 0 | } |
1445 | 0 | step=CastDoubleToSsizeT(floor((double) displacement)); |
1446 | 0 | area=(double) (displacement-step); |
1447 | 0 | step++; |
1448 | 0 | pixel=background; |
1449 | 0 | GetPixelInfo(image,&source); |
1450 | 0 | GetPixelInfo(image,&destination); |
1451 | 0 | switch (direction) |
1452 | 0 | { |
1453 | 0 | case UP: |
1454 | 0 | { |
1455 | | /* |
1456 | | Transfer pixels top-to-bottom. |
1457 | | */ |
1458 | 0 | if (step > y_offset) |
1459 | 0 | break; |
1460 | 0 | q=p-step*(ssize_t) GetPixelChannels(image); |
1461 | 0 | for (i=0; i < (ssize_t) height; i++) |
1462 | 0 | { |
1463 | 0 | if ((y_offset+i) < step) |
1464 | 0 | { |
1465 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
1466 | 0 | GetPixelInfoPixel(image,p,&pixel); |
1467 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1468 | 0 | continue; |
1469 | 0 | } |
1470 | 0 | GetPixelInfoPixel(image,p,&source); |
1471 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1472 | 0 | &source,(double) GetPixelAlpha(image,p),area, |
1473 | 0 | &destination); |
1474 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1475 | 0 | GetPixelInfoPixel(image,p,&pixel); |
1476 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
1477 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1478 | 0 | } |
1479 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1480 | 0 | &background,(double) background.alpha,area,&destination); |
1481 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1482 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1483 | 0 | for (i=0; i < (step-1); i++) |
1484 | 0 | { |
1485 | 0 | SetPixelViaPixelInfo(image,&background,q); |
1486 | 0 | q+=(ptrdiff_t) GetPixelChannels(image); |
1487 | 0 | } |
1488 | 0 | break; |
1489 | 0 | } |
1490 | 0 | case DOWN: |
1491 | 0 | { |
1492 | | /* |
1493 | | Transfer pixels bottom-to-top. |
1494 | | */ |
1495 | 0 | p+=(ptrdiff_t) height*GetPixelChannels(image); |
1496 | 0 | q=p+step*(ssize_t) GetPixelChannels(image); |
1497 | 0 | for (i=0; i < (ssize_t) height; i++) |
1498 | 0 | { |
1499 | 0 | p-=(ptrdiff_t)GetPixelChannels(image); |
1500 | 0 | q-=GetPixelChannels(image); |
1501 | 0 | if ((size_t) (y_offset+(ssize_t) height+step-i) > image->rows) |
1502 | 0 | continue; |
1503 | 0 | GetPixelInfoPixel(image,p,&source); |
1504 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1505 | 0 | &source,(double) GetPixelAlpha(image,p),area, |
1506 | 0 | &destination); |
1507 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1508 | 0 | GetPixelInfoPixel(image,p,&pixel); |
1509 | 0 | } |
1510 | 0 | CompositePixelInfoAreaBlend(&pixel,(double) pixel.alpha, |
1511 | 0 | &background,(double) background.alpha,area,&destination); |
1512 | 0 | q-=GetPixelChannels(image); |
1513 | 0 | SetPixelViaPixelInfo(image,&destination,q); |
1514 | 0 | for (i=0; i < (step-1); i++) |
1515 | 0 | { |
1516 | 0 | q-=GetPixelChannels(image); |
1517 | 0 | SetPixelViaPixelInfo(image,&background,q); |
1518 | 0 | } |
1519 | 0 | break; |
1520 | 0 | } |
1521 | 0 | } |
1522 | 0 | if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
1523 | 0 | status=MagickFalse; |
1524 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1525 | 0 | { |
1526 | 0 | MagickBooleanType |
1527 | 0 | proceed; |
1528 | |
|
1529 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1530 | | #pragma omp atomic |
1531 | | #endif |
1532 | 0 | progress++; |
1533 | 0 | proceed=SetImageProgress(image,YShearImageTag,progress,image->rows); |
1534 | 0 | if (proceed == MagickFalse) |
1535 | 0 | status=MagickFalse; |
1536 | 0 | } |
1537 | 0 | } |
1538 | 0 | image_view=DestroyCacheView(image_view); |
1539 | 0 | return(status); |
1540 | 0 | } |
1541 | | |
1542 | | /* |
1543 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1544 | | % % |
1545 | | % % |
1546 | | % % |
1547 | | % S h e a r I m a g e % |
1548 | | % % |
1549 | | % % |
1550 | | % % |
1551 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1552 | | % |
1553 | | % ShearImage() creates a new image that is a shear_image copy of an existing |
1554 | | % one. Shearing slides one edge of an image along the X or Y axis, creating |
1555 | | % a parallelogram. An X direction shear slides an edge along the X axis, |
1556 | | % while a Y direction shear slides an edge along the Y axis. The amount of |
1557 | | % the shear is controlled by a shear angle. For X direction shears, x_shear |
1558 | | % is measured relative to the Y axis, and similarly, for Y direction shears |
1559 | | % y_shear is measured relative to the X axis. Empty triangles left over from |
1560 | | % shearing the image are filled with the background color defined by member |
1561 | | % 'background_color' of the image.. ShearImage() allocates the memory |
1562 | | % necessary for the new Image structure and returns a pointer to the new image. |
1563 | | % |
1564 | | % ShearImage() is based on the paper "A Fast Algorithm for General Raster |
1565 | | % Rotation" by Alan W. Paeth. |
1566 | | % |
1567 | | % The format of the ShearImage method is: |
1568 | | % |
1569 | | % Image *ShearImage(const Image *image,const double x_shear, |
1570 | | % const double y_shear,ExceptionInfo *exception) |
1571 | | % |
1572 | | % A description of each parameter follows. |
1573 | | % |
1574 | | % o image: the image. |
1575 | | % |
1576 | | % o x_shear, y_shear: Specifies the number of degrees to shear the image. |
1577 | | % |
1578 | | % o exception: return any errors or warnings in this structure. |
1579 | | % |
1580 | | */ |
1581 | | MagickExport Image *ShearImage(const Image *image,const double x_shear, |
1582 | | const double y_shear,ExceptionInfo *exception) |
1583 | 0 | { |
1584 | 0 | Image |
1585 | 0 | *integral_image, |
1586 | 0 | *shear_image; |
1587 | |
|
1588 | 0 | MagickBooleanType |
1589 | 0 | status; |
1590 | |
|
1591 | 0 | PointInfo |
1592 | 0 | shear; |
1593 | |
|
1594 | 0 | RectangleInfo |
1595 | 0 | border_info, |
1596 | 0 | bounds; |
1597 | |
|
1598 | 0 | assert(image != (Image *) NULL); |
1599 | 0 | assert(image->signature == MagickCoreSignature); |
1600 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1601 | 0 | assert(exception->signature == MagickCoreSignature); |
1602 | 0 | if (IsEventLogging() != MagickFalse) |
1603 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1604 | 0 | if ((x_shear != 0.0) && (fmod(x_shear,90.0) == 0.0)) |
1605 | 0 | ThrowImageException(ImageError,"AngleIsDiscontinuous"); |
1606 | 0 | if ((y_shear != 0.0) && (fmod(y_shear,90.0) == 0.0)) |
1607 | 0 | ThrowImageException(ImageError,"AngleIsDiscontinuous"); |
1608 | | /* |
1609 | | Initialize shear angle. |
1610 | | */ |
1611 | 0 | integral_image=CloneImage(image,0,0,MagickTrue,exception); |
1612 | 0 | if (integral_image == (Image *) NULL) |
1613 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1614 | 0 | shear.x=(-tan(DegreesToRadians(fmod(x_shear,360.0)))); |
1615 | 0 | shear.y=tan(DegreesToRadians(fmod(y_shear,360.0))); |
1616 | 0 | if ((shear.x == 0.0) && (shear.y == 0.0)) |
1617 | 0 | return(integral_image); |
1618 | 0 | if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) |
1619 | 0 | { |
1620 | 0 | integral_image=DestroyImage(integral_image); |
1621 | 0 | return(integral_image); |
1622 | 0 | } |
1623 | 0 | if (integral_image->alpha_trait == UndefinedPixelTrait) |
1624 | 0 | (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); |
1625 | | /* |
1626 | | Compute image size. |
1627 | | */ |
1628 | 0 | bounds.width=(size_t) ((ssize_t) image->columns+CastDoubleToSsizeT(floor( |
1629 | 0 | fabs(shear.x)*image->rows+0.5))); |
1630 | 0 | bounds.x=CastDoubleToSsizeT(ceil((double) image->columns+((fabs(shear.x)* |
1631 | 0 | image->rows)-image->columns)/2.0-0.5)); |
1632 | 0 | bounds.y=CastDoubleToSsizeT(ceil((double) image->rows+((fabs(shear.y)* |
1633 | 0 | bounds.width)-image->rows)/2.0-0.5)); |
1634 | | /* |
1635 | | Surround image with border. |
1636 | | */ |
1637 | 0 | integral_image->border_color=integral_image->background_color; |
1638 | 0 | integral_image->compose=CopyCompositeOp; |
1639 | 0 | border_info.width=(size_t) bounds.x; |
1640 | 0 | border_info.height=(size_t) bounds.y; |
1641 | 0 | shear_image=BorderImage(integral_image,&border_info,image->compose,exception); |
1642 | 0 | integral_image=DestroyImage(integral_image); |
1643 | 0 | if (shear_image == (Image *) NULL) |
1644 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1645 | | /* |
1646 | | Shear the image. |
1647 | | */ |
1648 | 0 | if (shear_image->alpha_trait == UndefinedPixelTrait) |
1649 | 0 | (void) SetImageAlphaChannel(shear_image,OpaqueAlphaChannel,exception); |
1650 | 0 | status=XShearImage(shear_image,shear.x,image->columns,image->rows,bounds.x, |
1651 | 0 | (ssize_t) (shear_image->rows-image->rows)/2,exception); |
1652 | 0 | if (status == MagickFalse) |
1653 | 0 | { |
1654 | 0 | shear_image=DestroyImage(shear_image); |
1655 | 0 | return((Image *) NULL); |
1656 | 0 | } |
1657 | 0 | status=YShearImage(shear_image,shear.y,bounds.width,image->rows,(ssize_t) |
1658 | 0 | (shear_image->columns-bounds.width)/2,bounds.y,exception); |
1659 | 0 | if (status == MagickFalse) |
1660 | 0 | { |
1661 | 0 | shear_image=DestroyImage(shear_image); |
1662 | 0 | return((Image *) NULL); |
1663 | 0 | } |
1664 | 0 | status=CropToFitImage(&shear_image,shear.x,shear.y,(MagickRealType) |
1665 | 0 | image->columns,(MagickRealType) image->rows,MagickFalse,exception); |
1666 | 0 | shear_image->alpha_trait=image->alpha_trait; |
1667 | 0 | shear_image->compose=image->compose; |
1668 | 0 | shear_image->page.width=0; |
1669 | 0 | shear_image->page.height=0; |
1670 | 0 | if (status == MagickFalse) |
1671 | 0 | shear_image=DestroyImage(shear_image); |
1672 | 0 | return(shear_image); |
1673 | 0 | } |
1674 | | |
1675 | | /* |
1676 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1677 | | % % |
1678 | | % % |
1679 | | % % |
1680 | | % S h e a r R o t a t e I m a g e % |
1681 | | % % |
1682 | | % % |
1683 | | % % |
1684 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1685 | | % |
1686 | | % ShearRotateImage() creates a new image that is a rotated copy of an existing |
1687 | | % one. Positive angles rotate counter-clockwise (right-hand rule), while |
1688 | | % negative angles rotate clockwise. Rotated images are usually larger than |
1689 | | % the originals and have 'empty' triangular corners. X axis. Empty |
1690 | | % triangles left over from shearing the image are filled with the background |
1691 | | % color defined by member 'background_color' of the image. ShearRotateImage |
1692 | | % allocates the memory necessary for the new Image structure and returns a |
1693 | | % pointer to the new image. |
1694 | | % |
1695 | | % ShearRotateImage() is based on the paper "A Fast Algorithm for General |
1696 | | % Raster Rotation" by Alan W. Paeth. ShearRotateImage is adapted from a |
1697 | | % similar method based on the Paeth paper written by Michael Halle of the |
1698 | | % Spatial Imaging Group, MIT Media Lab. |
1699 | | % |
1700 | | % The format of the ShearRotateImage method is: |
1701 | | % |
1702 | | % Image *ShearRotateImage(const Image *image,const double degrees, |
1703 | | % ExceptionInfo *exception) |
1704 | | % |
1705 | | % A description of each parameter follows. |
1706 | | % |
1707 | | % o image: the image. |
1708 | | % |
1709 | | % o degrees: Specifies the number of degrees to rotate the image. |
1710 | | % |
1711 | | % o exception: return any errors or warnings in this structure. |
1712 | | % |
1713 | | */ |
1714 | | MagickExport Image *ShearRotateImage(const Image *image,const double degrees, |
1715 | | ExceptionInfo *exception) |
1716 | 0 | { |
1717 | 0 | Image |
1718 | 0 | *integral_image, |
1719 | 0 | *rotate_image; |
1720 | |
|
1721 | 0 | MagickBooleanType |
1722 | 0 | status; |
1723 | |
|
1724 | 0 | MagickRealType |
1725 | 0 | angle; |
1726 | |
|
1727 | 0 | PointInfo |
1728 | 0 | shear; |
1729 | |
|
1730 | 0 | RectangleInfo |
1731 | 0 | border_info, |
1732 | 0 | bounds; |
1733 | |
|
1734 | 0 | size_t |
1735 | 0 | height, |
1736 | 0 | rotations, |
1737 | 0 | shear_width, |
1738 | 0 | width; |
1739 | | |
1740 | | /* |
1741 | | Adjust rotation angle. |
1742 | | */ |
1743 | 0 | assert(image != (Image *) NULL); |
1744 | 0 | assert(image->signature == MagickCoreSignature); |
1745 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1746 | 0 | assert(exception->signature == MagickCoreSignature); |
1747 | 0 | if (IsEventLogging() != MagickFalse) |
1748 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1749 | 0 | angle=fmod(degrees,360.0); |
1750 | 0 | if (angle < -45.0) |
1751 | 0 | angle+=360.0; |
1752 | 0 | for (rotations=0; angle > 45.0; rotations++) |
1753 | 0 | angle-=90.0; |
1754 | 0 | rotations%=4; |
1755 | | /* |
1756 | | Calculate shear equations. |
1757 | | */ |
1758 | 0 | integral_image=IntegralRotateImage(image,rotations,exception); |
1759 | 0 | if (integral_image == (Image *) NULL) |
1760 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1761 | 0 | shear.x=(-tan((double) DegreesToRadians(angle)/2.0)); |
1762 | 0 | shear.y=sin((double) DegreesToRadians(angle)); |
1763 | 0 | if ((shear.x == 0.0) && (shear.y == 0.0)) |
1764 | 0 | return(integral_image); |
1765 | 0 | if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse) |
1766 | 0 | { |
1767 | 0 | integral_image=DestroyImage(integral_image); |
1768 | 0 | return(integral_image); |
1769 | 0 | } |
1770 | 0 | if (integral_image->alpha_trait == UndefinedPixelTrait) |
1771 | 0 | (void) SetImageAlphaChannel(integral_image,OpaqueAlphaChannel,exception); |
1772 | | /* |
1773 | | Compute maximum bounds for 3 shear operations. |
1774 | | */ |
1775 | 0 | width=integral_image->columns; |
1776 | 0 | height=integral_image->rows; |
1777 | 0 | bounds.width=CastDoubleToSizeT(fabs((double) height*shear.x)+width+0.5); |
1778 | 0 | bounds.height=CastDoubleToSizeT(fabs((double) bounds.width*shear.y)+height+0.5); |
1779 | 0 | shear_width=CastDoubleToSizeT(fabs((double) bounds.height*shear.x)+ |
1780 | 0 | bounds.width+0.5); |
1781 | 0 | bounds.x=CastDoubleToSsizeT(floor((double) ((shear_width > bounds.width) ? |
1782 | 0 | width : bounds.width-shear_width+2)/2.0+0.5)); |
1783 | 0 | bounds.y=CastDoubleToSsizeT(floor(((double) bounds.height-height+2)/2.0+0.5)); |
1784 | | /* |
1785 | | Surround image with a border. |
1786 | | */ |
1787 | 0 | integral_image->border_color=integral_image->background_color; |
1788 | 0 | integral_image->compose=CopyCompositeOp; |
1789 | 0 | border_info.width=(size_t) bounds.x; |
1790 | 0 | border_info.height=(size_t) bounds.y; |
1791 | 0 | rotate_image=BorderImage(integral_image,&border_info,image->compose, |
1792 | 0 | exception); |
1793 | 0 | integral_image=DestroyImage(integral_image); |
1794 | 0 | if (rotate_image == (Image *) NULL) |
1795 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1796 | | /* |
1797 | | Rotate the image. |
1798 | | */ |
1799 | 0 | status=XShearImage(rotate_image,shear.x,width,height,bounds.x,(ssize_t) |
1800 | 0 | (rotate_image->rows-height)/2,exception); |
1801 | 0 | if (status == MagickFalse) |
1802 | 0 | { |
1803 | 0 | rotate_image=DestroyImage(rotate_image); |
1804 | 0 | return((Image *) NULL); |
1805 | 0 | } |
1806 | 0 | status=YShearImage(rotate_image,shear.y,bounds.width,height,(ssize_t) |
1807 | 0 | (rotate_image->columns-bounds.width)/2,bounds.y,exception); |
1808 | 0 | if (status == MagickFalse) |
1809 | 0 | { |
1810 | 0 | rotate_image=DestroyImage(rotate_image); |
1811 | 0 | return((Image *) NULL); |
1812 | 0 | } |
1813 | 0 | status=XShearImage(rotate_image,shear.x,bounds.width,bounds.height,(ssize_t) |
1814 | 0 | (rotate_image->columns-bounds.width)/2,(ssize_t) (rotate_image->rows- |
1815 | 0 | bounds.height)/2,exception); |
1816 | 0 | if (status == MagickFalse) |
1817 | 0 | { |
1818 | 0 | rotate_image=DestroyImage(rotate_image); |
1819 | 0 | return((Image *) NULL); |
1820 | 0 | } |
1821 | 0 | status=CropToFitImage(&rotate_image,shear.x,shear.y,(MagickRealType) width, |
1822 | 0 | (MagickRealType) height,MagickTrue,exception); |
1823 | 0 | rotate_image->alpha_trait=image->alpha_trait; |
1824 | 0 | rotate_image->compose=image->compose; |
1825 | 0 | rotate_image->page.width=0; |
1826 | 0 | rotate_image->page.height=0; |
1827 | 0 | if (status == MagickFalse) |
1828 | 0 | rotate_image=DestroyImage(rotate_image); |
1829 | 0 | return(rotate_image); |
1830 | 0 | } |