1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % AAA TTTTT TTTTT RRRR IIIII BBBB U U TTTTT EEEEE %
7 % A A T T R R I B B U U T E %
8 % AAAAA T T RRRR I BBBB U U T EEE %
9 % A A T T R R I B B U U T E %
10 % A A T T R R IIIII BBBB UUU T EEEEE %
11 % %
12 % %
13 % MagickCore Get / Set Image Attributes %
14 % %
15 % Software Design %
16 % Cristy %
17 % October 2002 %
18 % %
19 % %
20 % Copyright 1999-2021 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/script/license.php %
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 %
37 %
38 */
39
40 /*
41 Include declarations.
42 */
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/attribute.h"
46 #include "MagickCore/blob.h"
47 #include "MagickCore/blob-private.h"
48 #include "MagickCore/cache.h"
49 #include "MagickCore/cache-private.h"
50 #include "MagickCore/cache-view.h"
51 #include "MagickCore/channel.h"
52 #include "MagickCore/client.h"
53 #include "MagickCore/color.h"
54 #include "MagickCore/color-private.h"
55 #include "MagickCore/colormap.h"
56 #include "MagickCore/colormap-private.h"
57 #include "MagickCore/colorspace.h"
58 #include "MagickCore/colorspace-private.h"
59 #include "MagickCore/composite.h"
60 #include "MagickCore/composite-private.h"
61 #include "MagickCore/constitute.h"
62 #include "MagickCore/draw.h"
63 #include "MagickCore/draw-private.h"
64 #include "MagickCore/effect.h"
65 #include "MagickCore/enhance.h"
66 #include "MagickCore/exception.h"
67 #include "MagickCore/exception-private.h"
68 #include "MagickCore/geometry.h"
69 #include "MagickCore/histogram.h"
70 #include "MagickCore/identify.h"
71 #include "MagickCore/image.h"
72 #include "MagickCore/image-private.h"
73 #include "MagickCore/list.h"
74 #include "MagickCore/log.h"
75 #include "MagickCore/memory_.h"
76 #include "MagickCore/magick.h"
77 #include "MagickCore/monitor.h"
78 #include "MagickCore/monitor-private.h"
79 #include "MagickCore/option.h"
80 #include "MagickCore/paint.h"
81 #include "MagickCore/pixel.h"
82 #include "MagickCore/pixel-accessor.h"
83 #include "MagickCore/property.h"
84 #include "MagickCore/quantize.h"
85 #include "MagickCore/quantum-private.h"
86 #include "MagickCore/random_.h"
87 #include "MagickCore/resource_.h"
88 #include "MagickCore/semaphore.h"
89 #include "MagickCore/segment.h"
90 #include "MagickCore/splay-tree.h"
91 #include "MagickCore/string_.h"
92 #include "MagickCore/string-private.h"
93 #include "MagickCore/thread-private.h"
94 #include "MagickCore/threshold.h"
95 #include "MagickCore/transform.h"
96 #include "MagickCore/utility.h"
97
98 /*
99 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
100 % %
101 % %
102 % %
103 + G e t I m a g e B o u n d i n g B o x %
104 % %
105 % %
106 % %
107 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
108 %
109 % GetImageBoundingBox() returns the bounding box of an image canvas.
110 %
111 % The format of the GetImageBoundingBox method is:
112 %
113 % RectangleInfo GetImageBoundingBox(const Image *image,
114 % ExceptionInfo *exception)
115 %
116 % A description of each parameter follows:
117 %
118 % o bounds: Method GetImageBoundingBox returns the bounding box of an
119 % image canvas.
120 %
121 % o image: the image.
122 %
123 % o exception: return any errors or warnings in this structure.
124 %
125 */
126
127 typedef struct _EdgeInfo
128 {
129 double
130 left,
131 right,
132 top,
133 bottom;
134 } EdgeInfo;
135
GetEdgeBackgroundCensus(const Image * image,const CacheView * image_view,const GravityType gravity,const size_t width,const size_t height,const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo * exception)136 static double GetEdgeBackgroundCensus(const Image *image,
137 const CacheView *image_view,const GravityType gravity,const size_t width,
138 const size_t height,const ssize_t x_offset,const ssize_t y_offset,
139 ExceptionInfo *exception)
140 {
141 CacheView
142 *edge_view;
143
144 const char
145 *artifact;
146
147 double
148 census;
149
150 Image
151 *edge_image;
152
153 PixelInfo
154 background,
155 pixel;
156
157 RectangleInfo
158 edge_geometry;
159
160 const Quantum
161 *p;
162
163 ssize_t
164 y;
165
166 /*
167 Determine the percent of image background for this edge.
168 */
169 switch (gravity)
170 {
171 case NorthWestGravity:
172 case NorthGravity:
173 default:
174 {
175 p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception);
176 break;
177 }
178 case NorthEastGravity:
179 case EastGravity:
180 {
181 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1,
182 exception);
183 break;
184 }
185 case SouthEastGravity:
186 case SouthGravity:
187 {
188 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,
189 (ssize_t) image->rows-1,1,1,exception);
190 break;
191 }
192 case SouthWestGravity:
193 case WestGravity:
194 {
195 p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1,
196 exception);
197 break;
198 }
199 }
200 GetPixelInfoPixel(image,p,&background);
201 artifact=GetImageArtifact(image,"background");
202 if (artifact != (const char *) NULL)
203 (void) QueryColorCompliance(artifact,AllCompliance,&background,exception);
204 artifact=GetImageArtifact(image,"trim:background-color");
205 if (artifact != (const char *) NULL)
206 (void) QueryColorCompliance(artifact,AllCompliance,&background,exception);
207 edge_geometry.width=width;
208 edge_geometry.height=height;
209 edge_geometry.x=x_offset;
210 edge_geometry.y=y_offset;
211 GravityAdjustGeometry(image->columns,image->rows,gravity,&edge_geometry);
212 edge_image=CropImage(image,&edge_geometry,exception);
213 if (edge_image == (Image *) NULL)
214 return(0.0);
215 census=0.0;
216 edge_view=AcquireVirtualCacheView(edge_image,exception);
217 for (y=0; y < (ssize_t) edge_image->rows; y++)
218 {
219 ssize_t
220 x;
221
222 p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
223 if (p == (const Quantum *) NULL)
224 break;
225 for (x=0; x < (ssize_t) edge_image->columns; x++)
226 {
227 GetPixelInfoPixel(edge_image,p,&pixel);
228 if (IsFuzzyEquivalencePixelInfo(&pixel,&background) == MagickFalse)
229 census++;
230 p+=GetPixelChannels(edge_image);
231 }
232 }
233 census/=((double) edge_image->columns*edge_image->rows);
234 edge_view=DestroyCacheView(edge_view);
235 edge_image=DestroyImage(edge_image);
236 return(census);
237 }
238
GetMinEdgeBackgroundCensus(const EdgeInfo * edge)239 static inline double GetMinEdgeBackgroundCensus(const EdgeInfo *edge)
240 {
241 double
242 census;
243
244 census=MagickMin(MagickMin(MagickMin(edge->left,edge->right),edge->top),
245 edge->bottom);
246 return(census);
247 }
248
GetEdgeBoundingBox(const Image * image,ExceptionInfo * exception)249 static RectangleInfo GetEdgeBoundingBox(const Image *image,
250 ExceptionInfo *exception)
251 {
252 CacheView
253 *edge_view;
254
255 const char
256 *artifact;
257
258 double
259 background_census,
260 percent_background;
261
262 EdgeInfo
263 edge,
264 vertex;
265
266 Image
267 *edge_image;
268
269 RectangleInfo
270 bounds;
271
272 /*
273 Get the image bounding box.
274 */
275 assert(image != (Image *) NULL);
276 assert(image->signature == MagickCoreSignature);
277 if (image->debug != MagickFalse)
278 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
279 SetGeometry(image,&bounds);
280 edge_image=CloneImage(image,0,0,MagickTrue,exception);
281 if (edge_image == (Image *) NULL)
282 return(bounds);
283 (void) ParseAbsoluteGeometry("0x0+0+0",&edge_image->page);
284 (void) memset(&vertex,0,sizeof(vertex));
285 edge_view=AcquireVirtualCacheView(edge_image,exception);
286 edge.left=GetEdgeBackgroundCensus(edge_image,edge_view,WestGravity,
287 1,0,0,0,exception);
288 edge.right=GetEdgeBackgroundCensus(edge_image,edge_view,EastGravity,
289 1,0,0,0,exception);
290 edge.top=GetEdgeBackgroundCensus(edge_image,edge_view,NorthGravity,
291 0,1,0,0,exception);
292 edge.bottom=GetEdgeBackgroundCensus(edge_image,edge_view,SouthGravity,
293 0,1,0,0,exception);
294 percent_background=1.0;
295 artifact=GetImageArtifact(edge_image,"trim:percent-background");
296 if (artifact != (const char *) NULL)
297 percent_background=StringToDouble(artifact,(char **) NULL)/100.0;
298 percent_background=MagickMin(MagickMax(1.0-percent_background,MagickEpsilon),
299 1.0);
300 background_census=GetMinEdgeBackgroundCensus(&edge);
301 for ( ; background_census < percent_background;
302 background_census=GetMinEdgeBackgroundCensus(&edge))
303 {
304 if ((bounds.width == 0) || (bounds.height == 0))
305 break;
306 if (fabs(edge.left-background_census) < MagickEpsilon)
307 {
308 /*
309 Trim left edge.
310 */
311 vertex.left++;
312 bounds.width--;
313 edge.left=GetEdgeBackgroundCensus(edge_image,edge_view,
314 NorthWestGravity,1,bounds.height,(ssize_t) vertex.left,(ssize_t)
315 vertex.top,exception);
316 edge.top=GetEdgeBackgroundCensus(edge_image,edge_view,
317 NorthWestGravity,bounds.width,1,(ssize_t) vertex.left,(ssize_t)
318 vertex.top,exception);
319 edge.bottom=GetEdgeBackgroundCensus(edge_image,edge_view,
320 SouthWestGravity,bounds.width,1,(ssize_t) vertex.left,(ssize_t)
321 vertex.bottom,exception);
322 continue;
323 }
324 if (fabs(edge.right-background_census) < MagickEpsilon)
325 {
326 /*
327 Trim right edge.
328 */
329 vertex.right++;
330 bounds.width--;
331 edge.right=GetEdgeBackgroundCensus(edge_image,edge_view,
332 NorthEastGravity,1,bounds.height,(ssize_t) vertex.right,(ssize_t)
333 vertex.top,exception);
334 edge.top=GetEdgeBackgroundCensus(edge_image,edge_view,
335 NorthWestGravity,bounds.width,1,(ssize_t) vertex.left,(ssize_t)
336 vertex.top,exception);
337 edge.bottom=GetEdgeBackgroundCensus(edge_image,edge_view,
338 SouthWestGravity,bounds.width,1,(ssize_t) vertex.left,(ssize_t)
339 vertex.bottom,exception);
340 continue;
341 }
342 if (fabs(edge.top-background_census) < MagickEpsilon)
343 {
344 /*
345 Trim top edge.
346 */
347 vertex.top++;
348 bounds.height--;
349 edge.left=GetEdgeBackgroundCensus(edge_image,edge_view,
350 NorthWestGravity,1,bounds.height,(ssize_t) vertex.left,(ssize_t)
351 vertex.top,exception);
352 edge.right=GetEdgeBackgroundCensus(edge_image,edge_view,
353 NorthEastGravity,1,bounds.height,(ssize_t) vertex.right,(ssize_t)
354 vertex.top,exception);
355 edge.top=GetEdgeBackgroundCensus(edge_image,edge_view,
356 NorthWestGravity,bounds.width,1,(ssize_t) vertex.left,(ssize_t)
357 vertex.top,exception);
358 continue;
359 }
360 if (fabs(edge.bottom-background_census) < MagickEpsilon)
361 {
362 /*
363 Trim bottom edge.
364 */
365 vertex.bottom++;
366 bounds.height--;
367 edge.left=GetEdgeBackgroundCensus(edge_image,edge_view,
368 NorthWestGravity,1,bounds.height,(ssize_t) vertex.left,(ssize_t)
369 vertex.top,exception);
370 edge.right=GetEdgeBackgroundCensus(edge_image,edge_view,
371 NorthEastGravity,1,bounds.height,(ssize_t) vertex.right,(ssize_t)
372 vertex.top,exception);
373 edge.bottom=GetEdgeBackgroundCensus(edge_image,edge_view,
374 SouthWestGravity,bounds.width,1,(ssize_t) vertex.left,(ssize_t)
375 vertex.bottom,exception);
376 continue;
377 }
378 }
379 edge_view=DestroyCacheView(edge_view);
380 edge_image=DestroyImage(edge_image);
381 bounds.x=(ssize_t) vertex.left;
382 bounds.y=(ssize_t) vertex.top;
383 if ((bounds.width == 0) || (bounds.height == 0))
384 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
385 "GeometryDoesNotContainImage","`%s'",image->filename);
386 return(bounds);
387 }
388
GetImageBoundingBox(const Image * image,ExceptionInfo * exception)389 MagickExport RectangleInfo GetImageBoundingBox(const Image *image,
390 ExceptionInfo *exception)
391 {
392 CacheView
393 *image_view;
394
395 const char
396 *artifact;
397
398 MagickBooleanType
399 status;
400
401 PixelInfo
402 target[4],
403 zero;
404
405 RectangleInfo
406 bounds;
407
408 const Quantum
409 *p;
410
411 ssize_t
412 y;
413
414 assert(image != (Image *) NULL);
415 assert(image->signature == MagickCoreSignature);
416 if (image->debug != MagickFalse)
417 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
418 artifact=GetImageArtifact(image,"trim:percent-background");
419 if (artifact != (const char *) NULL)
420 return(GetEdgeBoundingBox(image,exception));
421 artifact=GetImageArtifact(image, "trim:edges");
422 if (artifact == (const char *) NULL)
423 {
424 bounds.width=0;
425 bounds.height=0;
426 bounds.x=(ssize_t) image->columns;
427 bounds.y=(ssize_t) image->rows;
428 }
429 else
430 {
431 char
432 *edges,
433 *p,
434 *q;
435
436 bounds.width=(size_t) image->columns;
437 bounds.height=(size_t) image->rows;
438 bounds.x=0;
439 bounds.y=0;
440 edges=AcquireString(artifact);
441 q=edges;
442 while ((p=StringToken(",",&q)) != (char *) NULL)
443 {
444 if (LocaleCompare(p,"north") == 0)
445 bounds.y=(ssize_t) image->rows;
446 if (LocaleCompare(p,"east") == 0)
447 bounds.width=0;
448 if (LocaleCompare(p,"south") == 0)
449 bounds.height=0;
450 if (LocaleCompare(p,"west") == 0)
451 bounds.x=(ssize_t) image->columns;
452 }
453 edges=DestroyString(edges);
454 }
455 GetPixelInfo(image,&target[0]);
456 image_view=AcquireVirtualCacheView(image,exception);
457 p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception);
458 if (p == (const Quantum *) NULL)
459 {
460 image_view=DestroyCacheView(image_view);
461 return(bounds);
462 }
463 GetPixelInfoPixel(image,p,&target[0]);
464 GetPixelInfo(image,&target[1]);
465 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1,
466 exception);
467 if (p != (const Quantum *) NULL)
468 GetPixelInfoPixel(image,p,&target[1]);
469 GetPixelInfo(image,&target[2]);
470 p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1,
471 exception);
472 if (p != (const Quantum *) NULL)
473 GetPixelInfoPixel(image,p,&target[2]);
474 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,(ssize_t)
475 image->rows-1,1,1,exception);
476 if (p != (const Quantum *) NULL)
477 GetPixelInfoPixel(image,p,&target[3]);
478 status=MagickTrue;
479 GetPixelInfo(image,&zero);
480 #if defined(MAGICKCORE_OPENMP_SUPPORT)
481 #pragma omp parallel for schedule(static) shared(status) \
482 magick_number_threads(image,image,image->rows,1)
483 #endif
484 for (y=0; y < (ssize_t) image->rows; y++)
485 {
486 PixelInfo
487 pixel;
488
489 RectangleInfo
490 bounding_box;
491
492 const Quantum
493 *magick_restrict p;
494
495 ssize_t
496 x;
497
498 if (status == MagickFalse)
499 continue;
500 #if defined(MAGICKCORE_OPENMP_SUPPORT)
501 # pragma omp critical (MagickCore_GetImageBoundingBox)
502 #endif
503 bounding_box=bounds;
504 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
505 if (p == (const Quantum *) NULL)
506 {
507 status=MagickFalse;
508 continue;
509 }
510 pixel=zero;
511 for (x=0; x < (ssize_t) image->columns; x++)
512 {
513 GetPixelInfoPixel(image,p,&pixel);
514 if ((x < bounding_box.x) &&
515 (IsFuzzyEquivalencePixelInfo(&pixel,&target[0]) == MagickFalse))
516 bounding_box.x=x;
517 if ((x > (ssize_t) bounding_box.width) &&
518 (IsFuzzyEquivalencePixelInfo(&pixel,&target[1]) == MagickFalse))
519 bounding_box.width=(size_t) x;
520 if ((y < bounding_box.y) &&
521 (IsFuzzyEquivalencePixelInfo(&pixel,&target[0]) == MagickFalse))
522 bounding_box.y=y;
523 if ((y > (ssize_t) bounding_box.height) &&
524 (IsFuzzyEquivalencePixelInfo(&pixel,&target[2]) == MagickFalse))
525 bounding_box.height=(size_t) y;
526 if ((x < (ssize_t) bounding_box.width) &&
527 (y > (ssize_t) bounding_box.height) &&
528 (IsFuzzyEquivalencePixelInfo(&pixel,&target[3]) == MagickFalse))
529 {
530 bounding_box.width=(size_t) x;
531 bounding_box.height=(size_t) y;
532 }
533 p+=GetPixelChannels(image);
534 }
535 #if defined(MAGICKCORE_OPENMP_SUPPORT)
536 # pragma omp critical (MagickCore_GetImageBoundingBox)
537 #endif
538 {
539 if (bounding_box.x < bounds.x)
540 bounds.x=bounding_box.x;
541 if (bounding_box.y < bounds.y)
542 bounds.y=bounding_box.y;
543 if (bounding_box.width > bounds.width)
544 bounds.width=bounding_box.width;
545 if (bounding_box.height > bounds.height)
546 bounds.height=bounding_box.height;
547 }
548 }
549 image_view=DestroyCacheView(image_view);
550 if ((bounds.width == 0) || (bounds.height == 0))
551 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
552 "GeometryDoesNotContainImage","`%s'",image->filename);
553 else
554 {
555 bounds.width-=(bounds.x-1);
556 bounds.height-=(bounds.y-1);
557 }
558 return(bounds);
559 }
560
561 /*
562 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
563 % %
564 % %
565 % %
566 % G e t I m a g e C o n v e x H u l l %
567 % %
568 % %
569 % %
570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
571 %
572 % GetImageConvexHull() returns the convex hull points of an image canvas.
573 %
574 % The format of the GetImageConvexHull method is:
575 %
576 % PointInfo *GetImageConvexHull(const Image *image,
577 % size_t number_vertices,ExceptionInfo *exception)
578 %
579 % A description of each parameter follows:
580 %
581 % o image: the image.
582 %
583 % o number_vertices: the number of vertices in the convex hull.
584 %
585 % o exception: return any errors or warnings in this structure.
586 %
587 */
588
LexicographicalOrder(PointInfo * a,PointInfo * b,PointInfo * c)589 static double LexicographicalOrder(PointInfo *a,PointInfo *b,PointInfo *c)
590 {
591 /*
592 Order by x-coordinate, and in case of a tie, by y-coordinate.
593 */
594 return((b->x-a->x)*(c->y-a->y)-(b->y-a->y)*(c->x-a->x));
595 }
596
GetEdgeBackgroundColor(const Image * image,const CacheView * image_view,ExceptionInfo * exception)597 static PixelInfo GetEdgeBackgroundColor(const Image *image,
598 const CacheView *image_view,ExceptionInfo *exception)
599 {
600 const char
601 *artifact;
602
603 double
604 census[4],
605 edge_census;
606
607 PixelInfo
608 background[4],
609 edge_background;
610
611 ssize_t
612 i;
613
614 /*
615 Most dominant color of edges/corners is the background color of the image.
616 */
617 artifact=GetImageArtifact(image,"convex-hull:background-color");
618 if (artifact == (const char *) NULL)
619 artifact=GetImageArtifact(image,"background");
620 #if defined(MAGICKCORE_OPENMP_SUPPORT)
621 #pragma omp parallel for schedule(static)
622 #endif
623 for (i=0; i < 4; i++)
624 {
625 CacheView
626 *edge_view;
627
628 GravityType
629 gravity;
630
631 Image
632 *edge_image;
633
634 PixelInfo
635 pixel;
636
637 RectangleInfo
638 edge_geometry;
639
640 const Quantum
641 *p;
642
643 ssize_t
644 y;
645
646 census[i]=0.0;
647 (void) memset(&edge_geometry,0,sizeof(edge_geometry));
648 switch (i)
649 {
650 case 0:
651 default:
652 {
653 p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1,
654 exception);
655 gravity=WestGravity;
656 edge_geometry.width=1;
657 edge_geometry.height=0;
658 break;
659 }
660 case 1:
661 {
662 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1,
663 exception);
664 gravity=EastGravity;
665 edge_geometry.width=1;
666 edge_geometry.height=0;
667 break;
668 }
669 case 2:
670 {
671 p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception);
672 gravity=NorthGravity;
673 edge_geometry.width=0;
674 edge_geometry.height=1;
675 break;
676 }
677 case 3:
678 {
679 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,
680 (ssize_t) image->rows-1,1,1,exception);
681 gravity=SouthGravity;
682 edge_geometry.width=0;
683 edge_geometry.height=1;
684 break;
685 }
686 }
687 GetPixelInfoPixel(image,p,background+i);
688 if (artifact != (const char *) NULL)
689 (void) QueryColorCompliance(artifact,AllCompliance,background+i,
690 exception);
691 GravityAdjustGeometry(image->columns,image->rows,gravity,&edge_geometry);
692 edge_image=CropImage(image,&edge_geometry,exception);
693 if (edge_image == (Image *) NULL)
694 continue;
695 edge_view=AcquireVirtualCacheView(edge_image,exception);
696 for (y=0; y < (ssize_t) edge_image->rows; y++)
697 {
698 ssize_t
699 x;
700
701 p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,
702 exception);
703 if (p == (const Quantum *) NULL)
704 break;
705 for (x=0; x < (ssize_t) edge_image->columns; x++)
706 {
707 GetPixelInfoPixel(edge_image,p,&pixel);
708 if (IsFuzzyEquivalencePixelInfo(&pixel,background+i) == MagickFalse)
709 census[i]++;
710 p+=GetPixelChannels(edge_image);
711 }
712 }
713 edge_view=DestroyCacheView(edge_view);
714 edge_image=DestroyImage(edge_image);
715 }
716 edge_census=(-1.0);
717 for (i=0; i < 4; i++)
718 if (census[i] > edge_census)
719 {
720 edge_background=background[i];
721 edge_census=census[i];
722 }
723 return(edge_background);
724 }
725
TraceConvexHull(PointInfo * vertices,size_t number_vertices,PointInfo *** monotone_chain,size_t * chain_length)726 void TraceConvexHull(PointInfo *vertices,size_t number_vertices,
727 PointInfo ***monotone_chain,size_t *chain_length)
728 {
729 PointInfo
730 **chain;
731
732 ssize_t
733 i;
734
735 size_t
736 demark,
737 n;
738
739 /*
740 Construct the upper and lower hulls: rightmost to leftmost counterclockwise.
741 */
742 chain=(*monotone_chain);
743 n=0;
744 for (i=0; i < (ssize_t) number_vertices; i++)
745 {
746 while ((n >= 2) &&
747 (LexicographicalOrder(chain[n-2],chain[n-1],&vertices[i]) <= 0.0))
748 n--;
749 chain[n++]=(&vertices[i]);
750 }
751 demark=n+1;
752 for (i=(ssize_t) number_vertices-2; i >= 0; i--)
753 {
754 while ((n >= demark) &&
755 (LexicographicalOrder(chain[n-2],chain[n-1],&vertices[i]) <= 0.0))
756 n--;
757 chain[n++]=(&vertices[i]);
758 }
759 *chain_length=n;
760 }
761
GetImageConvexHull(const Image * image,size_t * number_vertices,ExceptionInfo * exception)762 MagickExport PointInfo *GetImageConvexHull(const Image *image,
763 size_t *number_vertices,ExceptionInfo *exception)
764 {
765 CacheView
766 *image_view;
767
768 MagickBooleanType
769 status;
770
771 MemoryInfo
772 *monotone_info,
773 *vertices_info;
774
775 PixelInfo
776 background;
777
778 PointInfo
779 *convex_hull,
780 **monotone_chain,
781 *vertices;
782
783 size_t
784 n;
785
786 ssize_t
787 y;
788
789 /*
790 Identify convex hull vertices of image foreground object(s).
791 */
792 assert(image != (Image *) NULL);
793 assert(image->signature == MagickCoreSignature);
794 if (image->debug != MagickFalse)
795 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
796 *number_vertices=0;
797 vertices_info=AcquireVirtualMemory(image->columns,image->rows*
798 sizeof(*vertices));
799 monotone_info=AcquireVirtualMemory(2*image->columns,2*
800 image->rows*sizeof(*monotone_chain));
801 if ((vertices_info == (MemoryInfo *) NULL) ||
802 (monotone_info == (MemoryInfo *) NULL))
803 {
804 if (monotone_info != (MemoryInfo *) NULL)
805 monotone_info=(MemoryInfo *) RelinquishVirtualMemory(monotone_info);
806 if (vertices_info != (MemoryInfo *) NULL)
807 vertices_info=RelinquishVirtualMemory(vertices_info);
808 return((PointInfo *) NULL);
809 }
810 vertices=(PointInfo *) GetVirtualMemoryBlob(vertices_info);
811 monotone_chain=(PointInfo **) GetVirtualMemoryBlob(monotone_info);
812 image_view=AcquireVirtualCacheView(image,exception);
813 background=GetEdgeBackgroundColor(image,image_view,exception);
814 status=MagickTrue;
815 n=0;
816 for (y=0; y < (ssize_t) image->rows; y++)
817 {
818 const Quantum
819 *p;
820
821 ssize_t
822 x;
823
824 if (status == MagickFalse)
825 continue;
826 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
827 if (p == (const Quantum *) NULL)
828 {
829 status=MagickFalse;
830 continue;
831 }
832 for (x=0; x < (ssize_t) image->columns; x++)
833 {
834 PixelInfo
835 pixel;
836
837 GetPixelInfoPixel(image,p,&pixel);
838 if (IsFuzzyEquivalencePixelInfo(&pixel,&background) == MagickFalse)
839 {
840 vertices[n].x=(double) x;
841 vertices[n].y=(double) y;
842 n++;
843 }
844 p+=GetPixelChannels(image);
845 }
846 }
847 image_view=DestroyCacheView(image_view);
848 /*
849 Return the convex hull of the image foreground object(s).
850 */
851 TraceConvexHull(vertices,n,&monotone_chain,number_vertices);
852 convex_hull=(PointInfo *) AcquireQuantumMemory(*number_vertices,
853 sizeof(*convex_hull));
854 if (convex_hull != (PointInfo *) NULL)
855 for (n=0; n < *number_vertices; n++)
856 convex_hull[n]=(*monotone_chain[n]);
857 monotone_info=RelinquishVirtualMemory(monotone_info);
858 vertices_info=RelinquishVirtualMemory(vertices_info);
859 return(convex_hull);
860 }
861
862 /*
863 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
864 % %
865 % %
866 % %
867 % G e t I m a g e D e p t h %
868 % %
869 % %
870 % %
871 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
872 %
873 % GetImageDepth() returns the depth of a particular image channel.
874 %
875 % The format of the GetImageDepth method is:
876 %
877 % size_t GetImageDepth(const Image *image,ExceptionInfo *exception)
878 %
879 % A description of each parameter follows:
880 %
881 % o image: the image.
882 %
883 % o exception: return any errors or warnings in this structure.
884 %
885 */
GetImageDepth(const Image * image,ExceptionInfo * exception)886 MagickExport size_t GetImageDepth(const Image *image,ExceptionInfo *exception)
887 {
888 CacheView
889 *image_view;
890
891 MagickBooleanType
892 status;
893
894 ssize_t
895 i;
896
897 size_t
898 *current_depth,
899 depth,
900 number_threads;
901
902 ssize_t
903 y;
904
905 /*
906 Compute image depth.
907 */
908 assert(image != (Image *) NULL);
909 assert(image->signature == MagickCoreSignature);
910 if (image->debug != MagickFalse)
911 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
912 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
913 current_depth=(size_t *) AcquireQuantumMemory(number_threads,
914 sizeof(*current_depth));
915 if (current_depth == (size_t *) NULL)
916 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
917 status=MagickTrue;
918 for (i=0; i < (ssize_t) number_threads; i++)
919 current_depth[i]=1;
920 if ((image->storage_class == PseudoClass) &&
921 (image->alpha_trait == UndefinedPixelTrait))
922 {
923 for (i=0; i < (ssize_t) image->colors; i++)
924 {
925 const int
926 id = GetOpenMPThreadId();
927
928 while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
929 {
930 MagickBooleanType
931 atDepth;
932
933 QuantumAny
934 range;
935
936 atDepth=MagickTrue;
937 range=GetQuantumRange(current_depth[id]);
938 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
939 if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].red),range) == MagickFalse)
940 atDepth=MagickFalse;
941 if ((atDepth != MagickFalse) &&
942 (GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
943 if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].green),range) == MagickFalse)
944 atDepth=MagickFalse;
945 if ((atDepth != MagickFalse) &&
946 (GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
947 if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].blue),range) == MagickFalse)
948 atDepth=MagickFalse;
949 if ((atDepth != MagickFalse))
950 break;
951 current_depth[id]++;
952 }
953 }
954 depth=current_depth[0];
955 for (i=1; i < (ssize_t) number_threads; i++)
956 if (depth < current_depth[i])
957 depth=current_depth[i];
958 current_depth=(size_t *) RelinquishMagickMemory(current_depth);
959 return(depth);
960 }
961 image_view=AcquireVirtualCacheView(image,exception);
962 #if !defined(MAGICKCORE_HDRI_SUPPORT)
963 if ((1UL*QuantumRange) <= MaxMap)
964 {
965 size_t
966 *depth_map;
967
968 /*
969 Scale pixels to desired (optimized with depth map).
970 */
971 depth_map=(size_t *) AcquireQuantumMemory(MaxMap+1,sizeof(*depth_map));
972 if (depth_map == (size_t *) NULL)
973 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
974 for (i=0; i <= (ssize_t) MaxMap; i++)
975 {
976 unsigned int
977 depth;
978
979 for (depth=1; depth < MAGICKCORE_QUANTUM_DEPTH; depth++)
980 {
981 Quantum
982 pixel;
983
984 QuantumAny
985 range;
986
987 range=GetQuantumRange(depth);
988 pixel=(Quantum) i;
989 if (pixel == ScaleAnyToQuantum(ScaleQuantumToAny(pixel,range),range))
990 break;
991 }
992 depth_map[i]=depth;
993 }
994 #if defined(MAGICKCORE_OPENMP_SUPPORT)
995 #pragma omp parallel for schedule(static) shared(status) \
996 magick_number_threads(image,image,image->rows,1)
997 #endif
998 for (y=0; y < (ssize_t) image->rows; y++)
999 {
1000 const int
1001 id = GetOpenMPThreadId();
1002
1003 const Quantum
1004 *magick_restrict p;
1005
1006 ssize_t
1007 x;
1008
1009 if (status == MagickFalse)
1010 continue;
1011 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1012 if (p == (const Quantum *) NULL)
1013 continue;
1014 for (x=0; x < (ssize_t) image->columns; x++)
1015 {
1016 ssize_t
1017 i;
1018
1019 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1020 {
1021 PixelChannel channel = GetPixelChannelChannel(image,i);
1022 PixelTrait traits = GetPixelChannelTraits(image,channel);
1023 if ((traits & UpdatePixelTrait) == 0)
1024 continue;
1025 if (depth_map[ScaleQuantumToMap(p[i])] > current_depth[id])
1026 current_depth[id]=depth_map[ScaleQuantumToMap(p[i])];
1027 }
1028 p+=GetPixelChannels(image);
1029 }
1030 if (current_depth[id] == MAGICKCORE_QUANTUM_DEPTH)
1031 status=MagickFalse;
1032 }
1033 image_view=DestroyCacheView(image_view);
1034 depth=current_depth[0];
1035 for (i=1; i < (ssize_t) number_threads; i++)
1036 if (depth < current_depth[i])
1037 depth=current_depth[i];
1038 depth_map=(size_t *) RelinquishMagickMemory(depth_map);
1039 current_depth=(size_t *) RelinquishMagickMemory(current_depth);
1040 return(depth);
1041 }
1042 #endif
1043 /*
1044 Compute pixel depth.
1045 */
1046 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1047 #pragma omp parallel for schedule(static) shared(status) \
1048 magick_number_threads(image,image,image->rows,1)
1049 #endif
1050 for (y=0; y < (ssize_t) image->rows; y++)
1051 {
1052 const int
1053 id = GetOpenMPThreadId();
1054
1055 const Quantum
1056 *magick_restrict p;
1057
1058 ssize_t
1059 x;
1060
1061 if (status == MagickFalse)
1062 continue;
1063 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1064 if (p == (const Quantum *) NULL)
1065 continue;
1066 for (x=0; x < (ssize_t) image->columns; x++)
1067 {
1068 ssize_t
1069 i;
1070
1071 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1072 {
1073 PixelChannel
1074 channel;
1075
1076 PixelTrait
1077 traits;
1078
1079 channel=GetPixelChannelChannel(image,i);
1080 traits=GetPixelChannelTraits(image,channel);
1081 if ((traits & UpdatePixelTrait) == 0)
1082 continue;
1083 while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
1084 {
1085 QuantumAny
1086 range;
1087
1088 range=GetQuantumRange(current_depth[id]);
1089 if (p[i] == ScaleAnyToQuantum(ScaleQuantumToAny(p[i],range),range))
1090 break;
1091 current_depth[id]++;
1092 }
1093 }
1094 p+=GetPixelChannels(image);
1095 }
1096 if (current_depth[id] == MAGICKCORE_QUANTUM_DEPTH)
1097 status=MagickFalse;
1098 }
1099 image_view=DestroyCacheView(image_view);
1100 depth=current_depth[0];
1101 for (i=1; i < (ssize_t) number_threads; i++)
1102 if (depth < current_depth[i])
1103 depth=current_depth[i];
1104 current_depth=(size_t *) RelinquishMagickMemory(current_depth);
1105 return(depth);
1106 }
1107
1108 /*
1109 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1110 % %
1111 % %
1112 % %
1113 % G e t I m a g e M i n i m u m B o u n d i n g B o x %
1114 % %
1115 % %
1116 % %
1117 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1118 %
1119 % GetImageMinimumBoundingBox() returns the points that form the minimum
1120 % bounding box around the image foreground objects with the "Rotating
1121 % Calipers" algorithm. The method also returns these properties:
1122 % minimum-bounding-box:area, minimum-bounding-box:width,
1123 % minimum-bounding-box:height, and minimum-bounding-box:angle.
1124 %
1125 % The format of the GetImageMinimumBoundingBox method is:
1126 %
1127 % PointInfo *GetImageMinimumBoundingBox(Image *image,
1128 % size_t number_vertices,ExceptionInfo *exception)
1129 %
1130 % A description of each parameter follows:
1131 %
1132 % o image: the image.
1133 %
1134 % o number_vertices: the number of vertices in the bounding box.
1135 %
1136 % o exception: return any errors or warnings in this structure.
1137 %
1138 */
1139
1140 typedef struct _CaliperInfo
1141 {
1142 double
1143 area,
1144 width,
1145 height,
1146 projection;
1147
1148 ssize_t
1149 p,
1150 q,
1151 v;
1152 } CaliperInfo;
1153
getAngle(PointInfo * p,PointInfo * q)1154 static inline double getAngle(PointInfo *p,PointInfo *q)
1155 {
1156 /*
1157 Get the angle between line (p,q) and horizontal axis, in degrees.
1158 */
1159 return(RadiansToDegrees(atan2(q->y-p->y,q->x-p->x)));
1160 }
1161
getDistance(PointInfo * p,PointInfo * q)1162 static inline double getDistance(PointInfo *p,PointInfo *q)
1163 {
1164 double
1165 distance;
1166
1167 distance=hypot(p->x-q->x,p->y-q->y);
1168 return(distance*distance);
1169 }
1170
getProjection(PointInfo * p,PointInfo * q,PointInfo * v)1171 static inline double getProjection(PointInfo *p,PointInfo *q,PointInfo *v)
1172 {
1173 double
1174 distance;
1175
1176 /*
1177 Projection of vector (x,y) - p into a line passing through p and q.
1178 */
1179 distance=getDistance(p,q);
1180 if (distance < MagickEpsilon)
1181 return(INFINITY);
1182 return((q->x-p->x)*(v->x-p->x)+(v->y-p->y)*(q->y-p->y))/sqrt(distance);
1183 }
1184
getFeretDiameter(PointInfo * p,PointInfo * q,PointInfo * v)1185 static inline double getFeretDiameter(PointInfo *p,PointInfo *q,PointInfo *v)
1186 {
1187 double
1188 distance;
1189
1190 /*
1191 Distance from a point (x,y) to a line passing through p and q.
1192 */
1193 distance=getDistance(p,q);
1194 if (distance < MagickEpsilon)
1195 return(INFINITY);
1196 return((q->x-p->x)*(v->y-p->y)-(v->x-p->x)*(q->y-p->y))/sqrt(distance);
1197 }
1198
GetImageMinimumBoundingBox(Image * image,size_t * number_vertices,ExceptionInfo * exception)1199 MagickExport PointInfo *GetImageMinimumBoundingBox(Image *image,
1200 size_t *number_vertices,ExceptionInfo *exception)
1201 {
1202 CaliperInfo
1203 caliper_info;
1204
1205 const char
1206 *artifact;
1207
1208 double
1209 angle,
1210 diameter,
1211 distance;
1212
1213 PointInfo
1214 *bounding_box,
1215 *vertices;
1216
1217 ssize_t
1218 i;
1219
1220 size_t
1221 number_hull_vertices;
1222
1223 /*
1224 Generate the minimum bounding box with the "Rotating Calipers" algorithm.
1225 */
1226 assert(image != (Image *) NULL);
1227 assert(image->signature == MagickCoreSignature);
1228 if (image->debug != MagickFalse)
1229 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1230 *number_vertices=0;
1231 vertices=GetImageConvexHull(image,&number_hull_vertices,exception);
1232 if (vertices == (PointInfo *) NULL)
1233 return((PointInfo *) NULL);
1234 *number_vertices=4;
1235 bounding_box=(PointInfo *) AcquireQuantumMemory(*number_vertices,
1236 sizeof(*bounding_box));
1237 if (bounding_box == (PointInfo *) NULL)
1238 {
1239 vertices=(PointInfo *) RelinquishMagickMemory(vertices);
1240 return((PointInfo *) NULL);
1241 }
1242 caliper_info.area=2.0*image->columns*image->rows;
1243 caliper_info.width=(double) image->columns+image->rows;
1244 caliper_info.height=0.0;
1245 caliper_info.projection=0.0;
1246 caliper_info.p=(-1);
1247 caliper_info.q=(-1);
1248 caliper_info.v=(-1);
1249 for (i=0; i < (ssize_t) number_hull_vertices; i++)
1250 {
1251 double
1252 area = 0.0,
1253 max_projection = 0.0,
1254 min_diameter = -1.0,
1255 min_projection = 0.0;
1256
1257 ssize_t
1258 j,
1259 k;
1260
1261 ssize_t
1262 p = -1,
1263 q = -1,
1264 v = -1;
1265
1266 for (j=0; j < (ssize_t) number_hull_vertices; j++)
1267 {
1268 double
1269 diameter;
1270
1271 diameter=fabs(getFeretDiameter(&vertices[i],
1272 &vertices[(i+1) % number_hull_vertices],&vertices[j]));
1273 if (min_diameter < diameter)
1274 {
1275 min_diameter=diameter;
1276 p=i;
1277 q=(i+1) % number_hull_vertices;
1278 v=j;
1279 }
1280 }
1281 for (k=0; k < (ssize_t) number_hull_vertices; k++)
1282 {
1283 double
1284 projection;
1285
1286 /*
1287 Rotating calipers.
1288 */
1289 projection=getProjection(&vertices[p],&vertices[q],&vertices[k]);
1290 min_projection=MagickMin(min_projection,projection);
1291 max_projection=MagickMax(max_projection,projection);
1292 }
1293 area=min_diameter*(max_projection-min_projection);
1294 if (caliper_info.area > area)
1295 {
1296 caliper_info.area=area;
1297 caliper_info.width=min_diameter;
1298 caliper_info.height=max_projection-min_projection;
1299 caliper_info.projection=max_projection;
1300 caliper_info.p=p;
1301 caliper_info.q=q;
1302 caliper_info.v=v;
1303 }
1304 }
1305 /*
1306 Initialize minimum bounding box.
1307 */
1308 diameter=getFeretDiameter(&vertices[caliper_info.p],
1309 &vertices[caliper_info.q],&vertices[caliper_info.v]);
1310 angle=atan2(vertices[caliper_info.q].y-vertices[caliper_info.p].y,
1311 vertices[caliper_info.q].x-vertices[caliper_info.p].x);
1312 bounding_box[0].x=vertices[caliper_info.p].x+cos(angle)*
1313 caliper_info.projection;
1314 bounding_box[0].y=vertices[caliper_info.p].y+sin(angle)*
1315 caliper_info.projection;
1316 bounding_box[1].x=floor(bounding_box[0].x+cos(angle+MagickPI/2.0)*diameter+
1317 0.5);
1318 bounding_box[1].y=floor(bounding_box[0].y+sin(angle+MagickPI/2.0)*diameter+
1319 0.5);
1320 bounding_box[2].x=floor(bounding_box[1].x+cos(angle)*(-caliper_info.height)+
1321 0.5);
1322 bounding_box[2].y=floor(bounding_box[1].y+sin(angle)*(-caliper_info.height)+
1323 0.5);
1324 bounding_box[3].x=floor(bounding_box[2].x+cos(angle+MagickPI/2.0)*(-diameter)+
1325 0.5);
1326 bounding_box[3].y=floor(bounding_box[2].y+sin(angle+MagickPI/2.0)*(-diameter)+
1327 0.5);
1328 /*
1329 Export minimum bounding box properties.
1330 */
1331 (void) FormatImageProperty(image,"minimum-bounding-box:area","%.*g",
1332 GetMagickPrecision(),caliper_info.area);
1333 (void) FormatImageProperty(image,"minimum-bounding-box:width","%.*g",
1334 GetMagickPrecision(),caliper_info.width);
1335 (void) FormatImageProperty(image,"minimum-bounding-box:height","%.*g",
1336 GetMagickPrecision(),caliper_info.height);
1337 (void) FormatImageProperty(image,"minimum-bounding-box:_p","%.*g,%.*g",
1338 GetMagickPrecision(),vertices[caliper_info.p].x,
1339 GetMagickPrecision(),vertices[caliper_info.p].y);
1340 (void) FormatImageProperty(image,"minimum-bounding-box:_q","%.*g,%.*g",
1341 GetMagickPrecision(),vertices[caliper_info.q].x,
1342 GetMagickPrecision(),vertices[caliper_info.q].y);
1343 (void) FormatImageProperty(image,"minimum-bounding-box:_v","%.*g,%.*g",
1344 GetMagickPrecision(),vertices[caliper_info.v].x,
1345 GetMagickPrecision(),vertices[caliper_info.v].y);
1346 /*
1347 Find smallest angle to origin.
1348 */
1349 distance=hypot(bounding_box[0].x,bounding_box[0].y);
1350 angle=getAngle(&bounding_box[0],&bounding_box[1]);
1351 for (i=1; i < 4; i++)
1352 {
1353 double d = hypot(bounding_box[i].x,bounding_box[i].y);
1354 if (d < distance)
1355 {
1356 distance=d;
1357 angle=getAngle(&bounding_box[i],&bounding_box[(i+1) % 4]);
1358 }
1359 }
1360 artifact=GetImageArtifact(image,"minimum-bounding-box:orientation");
1361 if (artifact != (const char *) NULL)
1362 {
1363 double
1364 length,
1365 q_length,
1366 p_length;
1367
1368 PointInfo
1369 delta,
1370 point;
1371
1372 /*
1373 Find smallest perpendicular distance from edge to origin.
1374 */
1375 point=bounding_box[0];
1376 for (i=1; i < 4; i++)
1377 {
1378 if (bounding_box[i].x < point.x)
1379 point.x=bounding_box[i].x;
1380 if (bounding_box[i].y < point.y)
1381 point.y=bounding_box[i].y;
1382 }
1383 for (i=0; i < 4; i++)
1384 {
1385 bounding_box[i].x-=point.x;
1386 bounding_box[i].y-=point.y;
1387 }
1388 for (i=0; i < 4; i++)
1389 {
1390 double
1391 d,
1392 intercept,
1393 slope;
1394
1395 delta.x=bounding_box[(i+1) % 4].x-bounding_box[i].x;
1396 delta.y=bounding_box[(i+1) % 4].y-bounding_box[i].y;
1397 slope=delta.y*PerceptibleReciprocal(delta.x);
1398 intercept=bounding_box[(i+1) % 4].y-slope*bounding_box[i].x;
1399 d=fabs((slope*bounding_box[i].x-bounding_box[i].y+intercept)*
1400 PerceptibleReciprocal(sqrt(slope*slope+1.0)));
1401 if ((i == 0) || (d < distance))
1402 {
1403 distance=d;
1404 point=delta;
1405 }
1406 }
1407 angle=RadiansToDegrees(atan(point.y*PerceptibleReciprocal(point.x)));
1408 length=hypot(point.x,point.y);
1409 p_length=fabs((double) MagickMax(caliper_info.width,caliper_info.height)-
1410 length);
1411 q_length=fabs(length-(double) MagickMin(caliper_info.width,
1412 caliper_info.height));
1413 if (LocaleCompare(artifact,"landscape") == 0)
1414 {
1415 if (p_length > q_length)
1416 angle+=(angle < 0.0) ? 90.0 : -90.0;
1417 }
1418 else
1419 if (LocaleCompare(artifact,"portrait") == 0)
1420 {
1421 if (p_length < q_length)
1422 angle+=(angle >= 0.0) ? 90.0 : -90.0;
1423 }
1424 }
1425 (void) FormatImageProperty(image,"minimum-bounding-box:angle","%.*g",
1426 GetMagickPrecision(),angle);
1427 (void) FormatImageProperty(image,"minimum-bounding-box:unrotate","%.*g",
1428 GetMagickPrecision(),-angle);
1429 vertices=(PointInfo *) RelinquishMagickMemory(vertices);
1430 return(bounding_box);
1431 }
1432
1433 /*
1434 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1435 % %
1436 % %
1437 % %
1438 % G e t I m a g e Q u a n t u m D e p t h %
1439 % %
1440 % %
1441 % %
1442 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1443 %
1444 % GetImageQuantumDepth() returns the depth of the image rounded to a legal
1445 % quantum depth: 8, 16, or 32.
1446 %
1447 % The format of the GetImageQuantumDepth method is:
1448 %
1449 % size_t GetImageQuantumDepth(const Image *image,
1450 % const MagickBooleanType constrain)
1451 %
1452 % A description of each parameter follows:
1453 %
1454 % o image: the image.
1455 %
1456 % o constrain: A value other than MagickFalse, constrains the depth to
1457 % a maximum of MAGICKCORE_QUANTUM_DEPTH.
1458 %
1459 */
GetImageQuantumDepth(const Image * image,const MagickBooleanType constrain)1460 MagickExport size_t GetImageQuantumDepth(const Image *image,
1461 const MagickBooleanType constrain)
1462 {
1463 size_t
1464 depth;
1465
1466 depth=image->depth;
1467 if (depth <= 8)
1468 depth=8;
1469 else
1470 if (depth <= 16)
1471 depth=16;
1472 else
1473 if (depth <= 32)
1474 depth=32;
1475 else
1476 if (depth <= 64)
1477 depth=64;
1478 if (constrain != MagickFalse)
1479 depth=(size_t) MagickMin((double) depth,(double) MAGICKCORE_QUANTUM_DEPTH);
1480 return(depth);
1481 }
1482
1483 /*
1484 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1485 % %
1486 % %
1487 % %
1488 % G e t I m a g e T y p e %
1489 % %
1490 % %
1491 % %
1492 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1493 %
1494 % GetImageType() returns the type of image:
1495 %
1496 % Bilevel Grayscale GrayscaleMatte
1497 % Palette PaletteMatte TrueColor
1498 % TrueColorMatte ColorSeparation ColorSeparationMatte
1499 %
1500 % The format of the GetImageType method is:
1501 %
1502 % ImageType GetImageType(const Image *image)
1503 %
1504 % A description of each parameter follows:
1505 %
1506 % o image: the image.
1507 %
1508 */
GetImageType(const Image * image)1509 MagickExport ImageType GetImageType(const Image *image)
1510 {
1511 assert(image != (Image *) NULL);
1512 assert(image->signature == MagickCoreSignature);
1513 if (image->colorspace == CMYKColorspace)
1514 {
1515 if (image->alpha_trait == UndefinedPixelTrait)
1516 return(ColorSeparationType);
1517 return(ColorSeparationAlphaType);
1518 }
1519 if (IsImageMonochrome(image) != MagickFalse)
1520 return(BilevelType);
1521 if (IsImageGray(image) != MagickFalse)
1522 {
1523 if (image->alpha_trait != UndefinedPixelTrait)
1524 return(GrayscaleAlphaType);
1525 return(GrayscaleType);
1526 }
1527 if (IsPaletteImage(image) != MagickFalse)
1528 {
1529 if (image->alpha_trait != UndefinedPixelTrait)
1530 return(PaletteAlphaType);
1531 return(PaletteType);
1532 }
1533 if (image->alpha_trait != UndefinedPixelTrait)
1534 return(TrueColorAlphaType);
1535 return(TrueColorType);
1536 }
1537
1538 /*
1539 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1540 % %
1541 % %
1542 % %
1543 % I d e n t i f y I m a g e G r a y %
1544 % %
1545 % %
1546 % %
1547 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1548 %
1549 % IdentifyImageGray() returns grayscale if all the pixels in the image have
1550 % the same red, green, and blue intensities, and bi-level is the intensity is
1551 % either 0 or QuantumRange. Otherwise undefined is returned.
1552 %
1553 % The format of the IdentifyImageGray method is:
1554 %
1555 % ImageType IdentifyImageGray(const Image *image,ExceptionInfo *exception)
1556 %
1557 % A description of each parameter follows:
1558 %
1559 % o image: the image.
1560 %
1561 % o exception: return any errors or warnings in this structure.
1562 %
1563 */
IdentifyImageGray(const Image * image,ExceptionInfo * exception)1564 MagickExport ImageType IdentifyImageGray(const Image *image,
1565 ExceptionInfo *exception)
1566 {
1567 CacheView
1568 *image_view;
1569
1570 ImageType
1571 type;
1572
1573 const Quantum
1574 *p;
1575
1576 ssize_t
1577 x;
1578
1579 ssize_t
1580 y;
1581
1582 assert(image != (Image *) NULL);
1583 assert(image->signature == MagickCoreSignature);
1584 if (image->debug != MagickFalse)
1585 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1586 if ((image->type == BilevelType) || (image->type == GrayscaleType) ||
1587 (image->type == GrayscaleAlphaType))
1588 return(image->type);
1589 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1590 return(UndefinedType);
1591 type=BilevelType;
1592 image_view=AcquireVirtualCacheView(image,exception);
1593 for (y=0; y < (ssize_t) image->rows; y++)
1594 {
1595 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1596 if (p == (const Quantum *) NULL)
1597 break;
1598 for (x=0; x < (ssize_t) image->columns; x++)
1599 {
1600 if (IsPixelGray(image,p) == MagickFalse)
1601 {
1602 type=UndefinedType;
1603 break;
1604 }
1605 if ((type == BilevelType) &&
1606 (IsPixelMonochrome(image,p) == MagickFalse))
1607 type=GrayscaleType;
1608 p+=GetPixelChannels(image);
1609 }
1610 if (type == UndefinedType)
1611 break;
1612 }
1613 image_view=DestroyCacheView(image_view);
1614 if ((type == GrayscaleType) && (image->alpha_trait != UndefinedPixelTrait))
1615 type=GrayscaleAlphaType;
1616 return(type);
1617 }
1618
1619 /*
1620 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1621 % %
1622 % %
1623 % %
1624 % I d e n t i f y I m a g e M o n o c h r o m e %
1625 % %
1626 % %
1627 % %
1628 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1629 %
1630 % IdentifyImageMonochrome() returns MagickTrue if all the pixels in the image
1631 % have the same red, green, and blue intensities and the intensity is either
1632 % 0 or QuantumRange.
1633 %
1634 % The format of the IdentifyImageMonochrome method is:
1635 %
1636 % MagickBooleanType IdentifyImageMonochrome(const Image *image,
1637 % ExceptionInfo *exception)
1638 %
1639 % A description of each parameter follows:
1640 %
1641 % o image: the image.
1642 %
1643 % o exception: return any errors or warnings in this structure.
1644 %
1645 */
IdentifyImageMonochrome(const Image * image,ExceptionInfo * exception)1646 MagickExport MagickBooleanType IdentifyImageMonochrome(const Image *image,
1647 ExceptionInfo *exception)
1648 {
1649 CacheView
1650 *image_view;
1651
1652 MagickBooleanType
1653 bilevel;
1654
1655 ssize_t
1656 x;
1657
1658 const Quantum
1659 *p;
1660
1661 ssize_t
1662 y;
1663
1664 assert(image != (Image *) NULL);
1665 assert(image->signature == MagickCoreSignature);
1666 if (image->debug != MagickFalse)
1667 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1668 if (image->type == BilevelType)
1669 return(MagickTrue);
1670 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1671 return(MagickFalse);
1672 bilevel=MagickTrue;
1673 image_view=AcquireVirtualCacheView(image,exception);
1674 for (y=0; y < (ssize_t) image->rows; y++)
1675 {
1676 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1677 if (p == (const Quantum *) NULL)
1678 break;
1679 for (x=0; x < (ssize_t) image->columns; x++)
1680 {
1681 if (IsPixelMonochrome(image,p) == MagickFalse)
1682 {
1683 bilevel=MagickFalse;
1684 break;
1685 }
1686 p+=GetPixelChannels(image);
1687 }
1688 if (bilevel == MagickFalse)
1689 break;
1690 }
1691 image_view=DestroyCacheView(image_view);
1692 return(bilevel);
1693 }
1694
1695 /*
1696 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1697 % %
1698 % %
1699 % %
1700 % I d e n t i f y I m a g e T y p e %
1701 % %
1702 % %
1703 % %
1704 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1705 %
1706 % IdentifyImageType() returns the potential type of image:
1707 %
1708 % Bilevel Grayscale GrayscaleMatte
1709 % Palette PaletteMatte TrueColor
1710 % TrueColorMatte ColorSeparation ColorSeparationMatte
1711 %
1712 % To ensure the image type matches its potential, use SetImageType():
1713 %
1714 % (void) SetImageType(image,IdentifyImageType(image,exception),exception);
1715 %
1716 % The format of the IdentifyImageType method is:
1717 %
1718 % ImageType IdentifyImageType(const Image *image,ExceptionInfo *exception)
1719 %
1720 % A description of each parameter follows:
1721 %
1722 % o image: the image.
1723 %
1724 % o exception: return any errors or warnings in this structure.
1725 %
1726 */
IdentifyImageType(const Image * image,ExceptionInfo * exception)1727 MagickExport ImageType IdentifyImageType(const Image *image,
1728 ExceptionInfo *exception)
1729 {
1730 assert(image != (Image *) NULL);
1731 assert(image->signature == MagickCoreSignature);
1732 if (image->debug != MagickFalse)
1733 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1734 if (image->colorspace == CMYKColorspace)
1735 {
1736 if (image->alpha_trait == UndefinedPixelTrait)
1737 return(ColorSeparationType);
1738 return(ColorSeparationAlphaType);
1739 }
1740 if (IdentifyImageMonochrome(image,exception) != MagickFalse)
1741 return(BilevelType);
1742 if (IdentifyImageGray(image,exception) != UndefinedType)
1743 {
1744 if (image->alpha_trait != UndefinedPixelTrait)
1745 return(GrayscaleAlphaType);
1746 return(GrayscaleType);
1747 }
1748 if (IdentifyPaletteImage(image,exception) != MagickFalse)
1749 {
1750 if (image->alpha_trait != UndefinedPixelTrait)
1751 return(PaletteAlphaType);
1752 return(PaletteType);
1753 }
1754 if (image->alpha_trait != UndefinedPixelTrait)
1755 return(TrueColorAlphaType);
1756 return(TrueColorType);
1757 }
1758
1759 /*
1760 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1761 % %
1762 % %
1763 % %
1764 % I s I m a g e G r a y %
1765 % %
1766 % %
1767 % %
1768 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1769 %
1770 % IsImageGray() returns MagickTrue if the type of the image is grayscale or
1771 % bi-level.
1772 %
1773 % The format of the IsImageGray method is:
1774 %
1775 % MagickBooleanType IsImageGray(const Image *image)
1776 %
1777 % A description of each parameter follows:
1778 %
1779 % o image: the image.
1780 %
1781 */
IsImageGray(const Image * image)1782 MagickExport MagickBooleanType IsImageGray(const Image *image)
1783 {
1784 assert(image != (Image *) NULL);
1785 assert(image->signature == MagickCoreSignature);
1786 if ((image->type == BilevelType) || (image->type == GrayscaleType) ||
1787 (image->type == GrayscaleAlphaType))
1788 return(MagickTrue);
1789 return(MagickFalse);
1790 }
1791
1792 /*
1793 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1794 % %
1795 % %
1796 % %
1797 % I s I m a g e M o n o c h r o m e %
1798 % %
1799 % %
1800 % %
1801 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1802 %
1803 % IsImageMonochrome() returns MagickTrue if type of the image is bi-level.
1804 %
1805 % The format of the IsImageMonochrome method is:
1806 %
1807 % MagickBooleanType IsImageMonochrome(const Image *image)
1808 %
1809 % A description of each parameter follows:
1810 %
1811 % o image: the image.
1812 %
1813 */
IsImageMonochrome(const Image * image)1814 MagickExport MagickBooleanType IsImageMonochrome(const Image *image)
1815 {
1816 assert(image != (Image *) NULL);
1817 assert(image->signature == MagickCoreSignature);
1818 if (image->type == BilevelType)
1819 return(MagickTrue);
1820 return(MagickFalse);
1821 }
1822
1823 /*
1824 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1825 % %
1826 % %
1827 % %
1828 % I s I m a g e O p a q u e %
1829 % %
1830 % %
1831 % %
1832 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1833 %
1834 % IsImageOpaque() returns MagickTrue if none of the pixels in the image have
1835 % an alpha value other than OpaqueAlpha (QuantumRange).
1836 %
1837 % Will return true immediatally is alpha channel is not available.
1838 %
1839 % The format of the IsImageOpaque method is:
1840 %
1841 % MagickBooleanType IsImageOpaque(const Image *image,
1842 % ExceptionInfo *exception)
1843 %
1844 % A description of each parameter follows:
1845 %
1846 % o image: the image.
1847 %
1848 % o exception: return any errors or warnings in this structure.
1849 %
1850 */
IsImageOpaque(const Image * image,ExceptionInfo * exception)1851 MagickExport MagickBooleanType IsImageOpaque(const Image *image,
1852 ExceptionInfo *exception)
1853 {
1854 CacheView
1855 *image_view;
1856
1857 const Quantum
1858 *p;
1859
1860 ssize_t
1861 x;
1862
1863 ssize_t
1864 y;
1865
1866 /*
1867 Determine if image is opaque.
1868 */
1869 assert(image != (Image *) NULL);
1870 assert(image->signature == MagickCoreSignature);
1871 if (image->debug != MagickFalse)
1872 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1873 if (image->alpha_trait == UndefinedPixelTrait)
1874 return(MagickTrue);
1875 image_view=AcquireVirtualCacheView(image,exception);
1876 for (y=0; y < (ssize_t) image->rows; y++)
1877 {
1878 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1879 if (p == (const Quantum *) NULL)
1880 break;
1881 for (x=0; x < (ssize_t) image->columns; x++)
1882 {
1883 if (GetPixelAlpha(image,p) != OpaqueAlpha)
1884 break;
1885 p+=GetPixelChannels(image);
1886 }
1887 if (x < (ssize_t) image->columns)
1888 break;
1889 }
1890 image_view=DestroyCacheView(image_view);
1891 return(y < (ssize_t) image->rows ? MagickFalse : MagickTrue);
1892 }
1893
1894 /*
1895 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1896 % %
1897 % %
1898 % %
1899 % S e t I m a g e D e p t h %
1900 % %
1901 % %
1902 % %
1903 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1904 %
1905 % SetImageDepth() sets the depth of the image.
1906 %
1907 % The format of the SetImageDepth method is:
1908 %
1909 % MagickBooleanType SetImageDepth(Image *image,const size_t depth,
1910 % ExceptionInfo *exception)
1911 %
1912 % A description of each parameter follows:
1913 %
1914 % o image: the image.
1915 %
1916 % o channel: the channel.
1917 %
1918 % o depth: the image depth.
1919 %
1920 % o exception: return any errors or warnings in this structure.
1921 %
1922 */
SetImageDepth(Image * image,const size_t depth,ExceptionInfo * exception)1923 MagickExport MagickBooleanType SetImageDepth(Image *image,
1924 const size_t depth,ExceptionInfo *exception)
1925 {
1926 CacheView
1927 *image_view;
1928
1929 MagickBooleanType
1930 status;
1931
1932 QuantumAny
1933 range;
1934
1935 ssize_t
1936 y;
1937
1938 assert(image != (Image *) NULL);
1939 if (image->debug != MagickFalse)
1940 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
1941 assert(image->signature == MagickCoreSignature);
1942 if (depth >= MAGICKCORE_QUANTUM_DEPTH)
1943 {
1944 image->depth=depth;
1945 return(MagickTrue);
1946 }
1947 range=GetQuantumRange(depth);
1948 if (image->storage_class == PseudoClass)
1949 {
1950 ssize_t
1951 i;
1952
1953 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1954 #pragma omp parallel for schedule(static) shared(status) \
1955 magick_number_threads(image,image,image->colors,1)
1956 #endif
1957 for (i=0; i < (ssize_t) image->colors; i++)
1958 {
1959 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1960 image->colormap[i].red=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
1961 ClampPixel(image->colormap[i].red),range),range);
1962 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1963 image->colormap[i].green=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
1964 ClampPixel(image->colormap[i].green),range),range);
1965 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1966 image->colormap[i].blue=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
1967 ClampPixel(image->colormap[i].blue),range),range);
1968 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1969 image->colormap[i].alpha=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
1970 ClampPixel(image->colormap[i].alpha),range),range);
1971 }
1972 }
1973 status=MagickTrue;
1974 image_view=AcquireAuthenticCacheView(image,exception);
1975 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1976 if ((1UL*QuantumRange) <= MaxMap)
1977 {
1978 Quantum
1979 *depth_map;
1980
1981 ssize_t
1982 i;
1983
1984 /*
1985 Scale pixels to desired (optimized with depth map).
1986 */
1987 depth_map=(Quantum *) AcquireQuantumMemory(MaxMap+1,sizeof(*depth_map));
1988 if (depth_map == (Quantum *) NULL)
1989 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1990 for (i=0; i <= (ssize_t) MaxMap; i++)
1991 depth_map[i]=ScaleAnyToQuantum(ScaleQuantumToAny((Quantum) i,range),
1992 range);
1993 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1994 #pragma omp parallel for schedule(static) shared(status) \
1995 magick_number_threads(image,image,image->rows,1)
1996 #endif
1997 for (y=0; y < (ssize_t) image->rows; y++)
1998 {
1999 ssize_t
2000 x;
2001
2002 Quantum
2003 *magick_restrict q;
2004
2005 if (status == MagickFalse)
2006 continue;
2007 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
2008 exception);
2009 if (q == (Quantum *) NULL)
2010 {
2011 status=MagickFalse;
2012 continue;
2013 }
2014 for (x=0; x < (ssize_t) image->columns; x++)
2015 {
2016 ssize_t
2017 i;
2018
2019 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2020 {
2021 PixelChannel
2022 channel;
2023
2024 PixelTrait
2025 traits;
2026
2027 channel=GetPixelChannelChannel(image,i);
2028 traits=GetPixelChannelTraits(image,channel);
2029 if ((traits & UpdatePixelTrait) == 0)
2030 continue;
2031 q[i]=depth_map[ScaleQuantumToMap(q[i])];
2032 }
2033 q+=GetPixelChannels(image);
2034 }
2035 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2036 {
2037 status=MagickFalse;
2038 continue;
2039 }
2040 }
2041 image_view=DestroyCacheView(image_view);
2042 depth_map=(Quantum *) RelinquishMagickMemory(depth_map);
2043 if (status != MagickFalse)
2044 image->depth=depth;
2045 return(status);
2046 }
2047 #endif
2048 /*
2049 Scale pixels to desired depth.
2050 */
2051 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2052 #pragma omp parallel for schedule(static) shared(status) \
2053 magick_number_threads(image,image,image->rows,1)
2054 #endif
2055 for (y=0; y < (ssize_t) image->rows; y++)
2056 {
2057 ssize_t
2058 x;
2059
2060 Quantum
2061 *magick_restrict q;
2062
2063 if (status == MagickFalse)
2064 continue;
2065 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2066 if (q == (Quantum *) NULL)
2067 {
2068 status=MagickFalse;
2069 continue;
2070 }
2071 for (x=0; x < (ssize_t) image->columns; x++)
2072 {
2073 ssize_t
2074 i;
2075
2076 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2077 {
2078 PixelChannel
2079 channel;
2080
2081 PixelTrait
2082 traits;
2083
2084 channel=GetPixelChannelChannel(image,i);
2085 traits=GetPixelChannelTraits(image,channel);
2086 if ((traits & UpdatePixelTrait) == 0)
2087 continue;
2088 q[i]=ScaleAnyToQuantum(ScaleQuantumToAny(ClampPixel((MagickRealType)
2089 q[i]),range),range);
2090 }
2091 q+=GetPixelChannels(image);
2092 }
2093 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2094 {
2095 status=MagickFalse;
2096 continue;
2097 }
2098 }
2099 image_view=DestroyCacheView(image_view);
2100 if (status != MagickFalse)
2101 image->depth=depth;
2102 return(status);
2103 }
2104
2105 /*
2106 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2107 % %
2108 % %
2109 % %
2110 % S e t I m a g e T y p e %
2111 % %
2112 % %
2113 % %
2114 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2115 %
2116 % SetImageType() sets the type of image. Choose from these types:
2117 %
2118 % Bilevel Grayscale GrayscaleMatte
2119 % Palette PaletteMatte TrueColor
2120 % TrueColorMatte ColorSeparation ColorSeparationMatte
2121 % OptimizeType
2122 %
2123 % The format of the SetImageType method is:
2124 %
2125 % MagickBooleanType SetImageType(Image *image,const ImageType type,
2126 % ExceptionInfo *exception)
2127 %
2128 % A description of each parameter follows:
2129 %
2130 % o image: the image.
2131 %
2132 % o type: Image type.
2133 %
2134 % o exception: return any errors or warnings in this structure.
2135 %
2136 */
SetImageType(Image * image,const ImageType type,ExceptionInfo * exception)2137 MagickExport MagickBooleanType SetImageType(Image *image,const ImageType type,
2138 ExceptionInfo *exception)
2139 {
2140 const char
2141 *artifact;
2142
2143 ImageInfo
2144 *image_info;
2145
2146 MagickBooleanType
2147 status;
2148
2149 QuantizeInfo
2150 *quantize_info;
2151
2152 assert(image != (Image *) NULL);
2153 if (image->debug != MagickFalse)
2154 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2155 assert(image->signature == MagickCoreSignature);
2156 status=MagickTrue;
2157 image_info=AcquireImageInfo();
2158 image_info->dither=image->dither;
2159 artifact=GetImageArtifact(image,"dither");
2160 if (artifact != (const char *) NULL)
2161 (void) SetImageOption(image_info,"dither",artifact);
2162 switch (type)
2163 {
2164 case BilevelType:
2165 {
2166 status=TransformImageColorspace(image,GRAYColorspace,exception);
2167 (void) NormalizeImage(image,exception);
2168 quantize_info=AcquireQuantizeInfo(image_info);
2169 quantize_info->number_colors=2;
2170 quantize_info->colorspace=GRAYColorspace;
2171 status=QuantizeImage(quantize_info,image,exception);
2172 quantize_info=DestroyQuantizeInfo(quantize_info);
2173 image->alpha_trait=UndefinedPixelTrait;
2174 break;
2175 }
2176 case GrayscaleType:
2177 {
2178 status=TransformImageColorspace(image,GRAYColorspace,exception);
2179 image->alpha_trait=UndefinedPixelTrait;
2180 break;
2181 }
2182 case GrayscaleAlphaType:
2183 {
2184 status=TransformImageColorspace(image,GRAYColorspace,exception);
2185 if (image->alpha_trait == UndefinedPixelTrait)
2186 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2187 break;
2188 }
2189 case PaletteType:
2190 {
2191 status=TransformImageColorspace(image,sRGBColorspace,exception);
2192 if ((image->storage_class == DirectClass) || (image->colors > 256))
2193 {
2194 quantize_info=AcquireQuantizeInfo(image_info);
2195 quantize_info->number_colors=256;
2196 status=QuantizeImage(quantize_info,image,exception);
2197 quantize_info=DestroyQuantizeInfo(quantize_info);
2198 }
2199 image->alpha_trait=UndefinedPixelTrait;
2200 break;
2201 }
2202 case PaletteBilevelAlphaType:
2203 {
2204 ChannelType
2205 channel_mask;
2206
2207 status=TransformImageColorspace(image,sRGBColorspace,exception);
2208 if (image->alpha_trait == UndefinedPixelTrait)
2209 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2210 channel_mask=SetImageChannelMask(image,AlphaChannel);
2211 (void) BilevelImage(image,(double) QuantumRange/2.0,exception);
2212 (void) SetImageChannelMask(image,channel_mask);
2213 quantize_info=AcquireQuantizeInfo(image_info);
2214 status=QuantizeImage(quantize_info,image,exception);
2215 quantize_info=DestroyQuantizeInfo(quantize_info);
2216 break;
2217 }
2218 case PaletteAlphaType:
2219 {
2220 status=TransformImageColorspace(image,sRGBColorspace,exception);
2221 if (image->alpha_trait == UndefinedPixelTrait)
2222 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2223 quantize_info=AcquireQuantizeInfo(image_info);
2224 quantize_info->colorspace=TransparentColorspace;
2225 status=QuantizeImage(quantize_info,image,exception);
2226 quantize_info=DestroyQuantizeInfo(quantize_info);
2227 break;
2228 }
2229 case TrueColorType:
2230 {
2231 status=TransformImageColorspace(image,sRGBColorspace,exception);
2232 if (image->storage_class != DirectClass)
2233 status=SetImageStorageClass(image,DirectClass,exception);
2234 image->alpha_trait=UndefinedPixelTrait;
2235 break;
2236 }
2237 case TrueColorAlphaType:
2238 {
2239 status=TransformImageColorspace(image,sRGBColorspace,exception);
2240 if (image->storage_class != DirectClass)
2241 status=SetImageStorageClass(image,DirectClass,exception);
2242 if (image->alpha_trait == UndefinedPixelTrait)
2243 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2244 break;
2245 }
2246 case ColorSeparationType:
2247 {
2248 status=TransformImageColorspace(image,CMYKColorspace,exception);
2249 if (image->storage_class != DirectClass)
2250 status=SetImageStorageClass(image,DirectClass,exception);
2251 image->alpha_trait=UndefinedPixelTrait;
2252 break;
2253 }
2254 case ColorSeparationAlphaType:
2255 {
2256 status=TransformImageColorspace(image,CMYKColorspace,exception);
2257 if (image->storage_class != DirectClass)
2258 status=SetImageStorageClass(image,DirectClass,exception);
2259 if (image->alpha_trait == UndefinedPixelTrait)
2260 status=SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2261 break;
2262 }
2263 case OptimizeType:
2264 case UndefinedType:
2265 break;
2266 }
2267 image_info=DestroyImageInfo(image_info);
2268 if (status == MagickFalse)
2269 return(status);
2270 image->type=type;
2271 return(MagickTrue);
2272 }
2273