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