MagickCore  7.1.1-43
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 @ 1999 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/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 _CensusInfo
128 {
129  double
130  left,
131  right,
132  top,
133  bottom;
134 } CensusInfo;
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  const Quantum
148  *p;
149 
150  double
151  census;
152 
153  Image
154  *edge_image;
155 
156  PixelInfo
157  background,
158  pixel;
159 
161  edge_geometry;
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+=(ptrdiff_t) 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 CensusInfo *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  CensusInfo
258  edge,
259  vertex;
260 
261  const char
262  *artifact;
263 
264  double
265  background_census,
266  percent_background;
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  const Quantum
401  *p;
402 
403  MagickBooleanType
404  status;
405 
406  PixelInfo
407  target[4],
408  zero;
409 
411  bounds;
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  GetPixelInfo(image,&target[3]);
477  p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,(ssize_t)
478  image->rows-1,1,1,exception);
479  if (p != (const Quantum *) NULL)
480  GetPixelInfoPixel(image,p,&target[3]);
481  status=MagickTrue;
482  GetPixelInfo(image,&zero);
483 #if defined(MAGICKCORE_OPENMP_SUPPORT)
484  #pragma omp parallel for schedule(static) shared(status) \
485  magick_number_threads(image,image,image->rows,2)
486 #endif
487  for (y=0; y < (ssize_t) image->rows; y++)
488  {
489  const Quantum
490  *magick_restrict q;
491 
492  PixelInfo
493  pixel;
494 
496  bounding_box;
497 
498  ssize_t
499  x;
500 
501  if (status == MagickFalse)
502  continue;
503 #if defined(MAGICKCORE_OPENMP_SUPPORT)
504 # pragma omp critical (MagickCore_GetImageBoundingBox)
505 #endif
506  bounding_box=bounds;
507  q=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
508  if (q == (const Quantum *) NULL)
509  {
510  status=MagickFalse;
511  continue;
512  }
513  pixel=zero;
514  for (x=0; x < (ssize_t) image->columns; x++)
515  {
516  GetPixelInfoPixel(image,q,&pixel);
517  if ((x < bounding_box.x) &&
518  (IsFuzzyEquivalencePixelInfo(&pixel,&target[0]) == MagickFalse))
519  bounding_box.x=x;
520  if ((x > (ssize_t) bounding_box.width) &&
521  (IsFuzzyEquivalencePixelInfo(&pixel,&target[1]) == MagickFalse))
522  bounding_box.width=(size_t) x;
523  if ((y < bounding_box.y) &&
524  (IsFuzzyEquivalencePixelInfo(&pixel,&target[0]) == MagickFalse))
525  bounding_box.y=y;
526  if ((y > (ssize_t) bounding_box.height) &&
527  (IsFuzzyEquivalencePixelInfo(&pixel,&target[2]) == MagickFalse))
528  bounding_box.height=(size_t) y;
529  if ((x < (ssize_t) bounding_box.width) &&
530  (y > (ssize_t) bounding_box.height) &&
531  (IsFuzzyEquivalencePixelInfo(&pixel,&target[3]) == MagickFalse))
532  {
533  bounding_box.width=(size_t) x;
534  bounding_box.height=(size_t) y;
535  }
536  q+=(ptrdiff_t) GetPixelChannels(image);
537  }
538 #if defined(MAGICKCORE_OPENMP_SUPPORT)
539 # pragma omp critical (MagickCore_GetImageBoundingBox)
540 #endif
541  {
542  if (bounding_box.x < bounds.x)
543  bounds.x=bounding_box.x;
544  if (bounding_box.y < bounds.y)
545  bounds.y=bounding_box.y;
546  if (bounding_box.width > bounds.width)
547  bounds.width=bounding_box.width;
548  if (bounding_box.height > bounds.height)
549  bounds.height=bounding_box.height;
550  }
551  }
552  image_view=DestroyCacheView(image_view);
553  if ((bounds.width == 0) || (bounds.height == 0))
554  (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
555  "GeometryDoesNotContainImage","`%s'",image->filename);
556  else
557  {
558  bounds.width-=(size_t) (bounds.x-1);
559  bounds.height-=(size_t) (bounds.y-1);
560  }
561  return(bounds);
562 }
563 
564 /*
565 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
566 % %
567 % %
568 % %
569 % G e t I m a g e C o n v e x H u l l %
570 % %
571 % %
572 % %
573 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
574 %
575 % GetImageConvexHull() returns the convex hull points of an image canvas.
576 %
577 % The format of the GetImageConvexHull method is:
578 %
579 % PointInfo *GetImageConvexHull(const Image *image,
580 % size_t number_vertices,ExceptionInfo *exception)
581 %
582 % A description of each parameter follows:
583 %
584 % o image: the image.
585 %
586 % o number_vertices: the number of vertices in the convex hull.
587 %
588 % o exception: return any errors or warnings in this structure.
589 %
590 */
591 
592 static double LexicographicalOrder(PointInfo *a,PointInfo *b,PointInfo *c)
593 {
594  /*
595  Order by x-coordinate, and in case of a tie, by y-coordinate.
596  */
597  return((b->x-a->x)*(c->y-a->y)-(b->y-a->y)*(c->x-a->x));
598 }
599 
600 static PixelInfo GetEdgeBackgroundColor(const Image *image,
601  const CacheView *image_view,ExceptionInfo *exception)
602 {
603  const char
604  *artifact;
605 
606  double
607  census[4],
608  edge_census;
609 
610  PixelInfo
611  background[4],
612  edge_background;
613 
614  ssize_t
615  i;
616 
617  /*
618  Most dominant color of edges/corners is the background color of the image.
619  */
620  memset(&edge_background,0,sizeof(edge_background));
621  artifact=GetImageArtifact(image,"convex-hull:background-color");
622  if (artifact == (const char *) NULL)
623  artifact=GetImageArtifact(image,"background");
624 #if defined(MAGICKCORE_OPENMP_SUPPORT)
625  #pragma omp parallel for schedule(static)
626 #endif
627  for (i=0; i < 4; i++)
628  {
629  CacheView
630  *edge_view;
631 
632  GravityType
633  gravity;
634 
635  Image
636  *edge_image;
637 
638  PixelInfo
639  pixel;
640 
642  edge_geometry;
643 
644  const Quantum
645  *p;
646 
647  ssize_t
648  y;
649 
650  census[i]=0.0;
651  (void) memset(&edge_geometry,0,sizeof(edge_geometry));
652  switch (i)
653  {
654  case 0:
655  default:
656  {
657  p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1,
658  exception);
659  gravity=WestGravity;
660  edge_geometry.width=1;
661  edge_geometry.height=0;
662  break;
663  }
664  case 1:
665  {
666  p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1,
667  exception);
668  gravity=EastGravity;
669  edge_geometry.width=1;
670  edge_geometry.height=0;
671  break;
672  }
673  case 2:
674  {
675  p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception);
676  gravity=NorthGravity;
677  edge_geometry.width=0;
678  edge_geometry.height=1;
679  break;
680  }
681  case 3:
682  {
683  p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,
684  (ssize_t) image->rows-1,1,1,exception);
685  gravity=SouthGravity;
686  edge_geometry.width=0;
687  edge_geometry.height=1;
688  break;
689  }
690  }
691  GetPixelInfoPixel(image,p,background+i);
692  if (artifact != (const char *) NULL)
693  (void) QueryColorCompliance(artifact,AllCompliance,background+i,
694  exception);
695  GravityAdjustGeometry(image->columns,image->rows,gravity,&edge_geometry);
696  edge_image=CropImage(image,&edge_geometry,exception);
697  if (edge_image == (Image *) NULL)
698  continue;
699  edge_view=AcquireVirtualCacheView(edge_image,exception);
700  for (y=0; y < (ssize_t) edge_image->rows; y++)
701  {
702  ssize_t
703  x;
704 
705  p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,
706  exception);
707  if (p == (const Quantum *) NULL)
708  break;
709  for (x=0; x < (ssize_t) edge_image->columns; x++)
710  {
711  GetPixelInfoPixel(edge_image,p,&pixel);
712  if (IsFuzzyEquivalencePixelInfo(&pixel,background+i) == MagickFalse)
713  census[i]++;
714  p+=(ptrdiff_t) GetPixelChannels(edge_image);
715  }
716  }
717  edge_view=DestroyCacheView(edge_view);
718  edge_image=DestroyImage(edge_image);
719  }
720  edge_census=(-1.0);
721  for (i=0; i < 4; i++)
722  if (census[i] > edge_census)
723  {
724  edge_background=background[i];
725  edge_census=census[i];
726  }
727  return(edge_background);
728 }
729 
730 void TraceConvexHull(PointInfo *vertices,size_t number_vertices,
731  PointInfo ***monotone_chain,size_t *chain_length)
732 {
733  PointInfo
734  **chain;
735 
736  size_t
737  demark,
738  n;
739 
740  ssize_t
741  i;
742 
743  /*
744  Construct the upper and lower hulls: rightmost to leftmost counterclockwise.
745  */
746  chain=(*monotone_chain);
747  n=0;
748  for (i=0; i < (ssize_t) number_vertices; i++)
749  {
750  while ((n >= 2) &&
751  (LexicographicalOrder(chain[n-2],chain[n-1],&vertices[i]) <= 0.0))
752  n--;
753  chain[n++]=(&vertices[i]);
754  }
755  demark=n+1;
756  for (i=(ssize_t) number_vertices-2; i >= 0; i--)
757  {
758  while ((n >= demark) &&
759  (LexicographicalOrder(chain[n-2],chain[n-1],&vertices[i]) <= 0.0))
760  n--;
761  chain[n++]=(&vertices[i]);
762  }
763  *chain_length=n;
764 }
765 
766 MagickExport PointInfo *GetImageConvexHull(const Image *image,
767  size_t *number_vertices,ExceptionInfo *exception)
768 {
769  CacheView
770  *image_view;
771 
772  MagickBooleanType
773  status;
774 
775  MemoryInfo
776  *monotone_info,
777  *vertices_info;
778 
779  PixelInfo
780  background;
781 
782  PointInfo
783  *convex_hull,
784  **monotone_chain,
785  *vertices;
786 
787  size_t
788  n;
789 
790  ssize_t
791  y;
792 
793  /*
794  Identify convex hull vertices of image foreground object(s).
795  */
796  assert(image != (Image *) NULL);
797  assert(image->signature == MagickCoreSignature);
798  if (IsEventLogging() != MagickFalse)
799  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
800  *number_vertices=0;
801  vertices_info=AcquireVirtualMemory(image->columns,image->rows*
802  sizeof(*vertices));
803  monotone_info=AcquireVirtualMemory(2*image->columns,2*
804  image->rows*sizeof(*monotone_chain));
805  if ((vertices_info == (MemoryInfo *) NULL) ||
806  (monotone_info == (MemoryInfo *) NULL))
807  {
808  if (monotone_info != (MemoryInfo *) NULL)
809  monotone_info=(MemoryInfo *) RelinquishVirtualMemory(monotone_info);
810  if (vertices_info != (MemoryInfo *) NULL)
811  vertices_info=RelinquishVirtualMemory(vertices_info);
812  return((PointInfo *) NULL);
813  }
814  vertices=(PointInfo *) GetVirtualMemoryBlob(vertices_info);
815  monotone_chain=(PointInfo **) GetVirtualMemoryBlob(monotone_info);
816  image_view=AcquireVirtualCacheView(image,exception);
817  background=GetEdgeBackgroundColor(image,image_view,exception);
818  status=MagickTrue;
819  n=0;
820  for (y=0; y < (ssize_t) image->rows; y++)
821  {
822  const Quantum
823  *p;
824 
825  ssize_t
826  x;
827 
828  if (status == MagickFalse)
829  continue;
830  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
831  if (p == (const Quantum *) NULL)
832  {
833  status=MagickFalse;
834  continue;
835  }
836  for (x=0; x < (ssize_t) image->columns; x++)
837  {
838  PixelInfo
839  pixel;
840 
841  GetPixelInfoPixel(image,p,&pixel);
842  if (IsFuzzyEquivalencePixelInfo(&pixel,&background) == MagickFalse)
843  {
844  vertices[n].x=(double) x;
845  vertices[n].y=(double) y;
846  n++;
847  }
848  p+=(ptrdiff_t) GetPixelChannels(image);
849  }
850  }
851  image_view=DestroyCacheView(image_view);
852  /*
853  Return the convex hull of the image foreground object(s).
854  */
855  TraceConvexHull(vertices,n,&monotone_chain,number_vertices);
856  convex_hull=(PointInfo *) AcquireQuantumMemory(*number_vertices,
857  sizeof(*convex_hull));
858  if (convex_hull != (PointInfo *) NULL)
859  for (n=0; n < *number_vertices; n++)
860  convex_hull[n]=(*monotone_chain[n]);
861  monotone_info=RelinquishVirtualMemory(monotone_info);
862  vertices_info=RelinquishVirtualMemory(vertices_info);
863  return(convex_hull);
864 }
865 
866 /*
867 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
868 % %
869 % %
870 % %
871 % G e t I m a g e D e p t h %
872 % %
873 % %
874 % %
875 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
876 %
877 % GetImageDepth() returns the depth of a particular image channel.
878 %
879 % The format of the GetImageDepth method is:
880 %
881 % size_t GetImageDepth(const Image *image,ExceptionInfo *exception)
882 %
883 % A description of each parameter follows:
884 %
885 % o image: the image.
886 %
887 % o exception: return any errors or warnings in this structure.
888 %
889 */
890 MagickExport size_t GetImageDepth(const Image *image,ExceptionInfo *exception)
891 {
892  CacheView
893  *image_view;
894 
895  MagickBooleanType
896  status;
897 
898  ssize_t
899  i;
900 
901  size_t
902  *current_depth,
903  depth,
904  number_threads;
905 
906  ssize_t
907  y;
908 
909  /*
910  Compute image depth.
911  */
912  assert(image != (Image *) NULL);
913  assert(image->signature == MagickCoreSignature);
914  if (IsEventLogging() != MagickFalse)
915  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
916  number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
917  current_depth=(size_t *) AcquireQuantumMemory(number_threads,
918  sizeof(*current_depth));
919  if (current_depth == (size_t *) NULL)
920  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
921  status=MagickTrue;
922  for (i=0; i < (ssize_t) number_threads; i++)
923  current_depth[i]=1;
924  if ((image->storage_class == PseudoClass) &&
925  ((image->alpha_trait & BlendPixelTrait) == 0))
926  {
927  for (i=0; i < (ssize_t) image->colors; i++)
928  {
929  const int
930  id = GetOpenMPThreadId();
931 
932  while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
933  {
934  MagickBooleanType
935  atDepth;
936 
937  QuantumAny
938  range;
939 
940  atDepth=MagickTrue;
941  range=GetQuantumRange(current_depth[id]);
942  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
943  if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].red),range) == MagickFalse)
944  atDepth=MagickFalse;
945  if ((atDepth != MagickFalse) &&
946  (GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
947  if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].green),range) == MagickFalse)
948  atDepth=MagickFalse;
949  if ((atDepth != MagickFalse) &&
950  (GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
951  if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].blue),range) == MagickFalse)
952  atDepth=MagickFalse;
953  if ((atDepth != MagickFalse))
954  break;
955  current_depth[id]++;
956  }
957  }
958  depth=current_depth[0];
959  for (i=1; i < (ssize_t) number_threads; i++)
960  if (depth < current_depth[i])
961  depth=current_depth[i];
962  current_depth=(size_t *) RelinquishMagickMemory(current_depth);
963  return(depth);
964  }
965  image_view=AcquireVirtualCacheView(image,exception);
966 #if !defined(MAGICKCORE_HDRI_SUPPORT)
967  DisableMSCWarning(4127)
968  if ((1UL*QuantumRange) <= MaxMap)
969  RestoreMSCWarning
970  {
971  size_t
972  *depth_map;
973 
974  /*
975  Scale pixels to desired (optimized with depth map).
976  */
977  depth_map=(size_t *) AcquireQuantumMemory(MaxMap+1,sizeof(*depth_map));
978  if (depth_map == (size_t *) NULL)
979  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
980  for (i=0; i <= (ssize_t) MaxMap; i++)
981  {
982  for (depth=1; depth < (size_t) MAGICKCORE_QUANTUM_DEPTH; depth++)
983  {
984  Quantum
985  pixel;
986 
987  QuantumAny
988  range;
989 
990  range=GetQuantumRange(depth);
991  pixel=(Quantum) i;
992  if (pixel == ScaleAnyToQuantum(ScaleQuantumToAny(pixel,range),range))
993  break;
994  }
995  depth_map[i]=depth;
996  }
997 #if defined(MAGICKCORE_OPENMP_SUPPORT)
998  #pragma omp parallel for schedule(static) shared(status) \
999  magick_number_threads(image,image,image->rows,1)
1000 #endif
1001  for (y=0; y < (ssize_t) image->rows; y++)
1002  {
1003  const int
1004  id = GetOpenMPThreadId();
1005 
1006  const Quantum
1007  *magick_restrict p;
1008 
1009  ssize_t
1010  x;
1011 
1012  if (status == MagickFalse)
1013  continue;
1014  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1015  if (p == (const Quantum *) NULL)
1016  continue;
1017  for (x=0; x < (ssize_t) image->columns; x++)
1018  {
1019  ssize_t
1020  j;
1021 
1022  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1023  {
1024  PixelChannel channel = GetPixelChannelChannel(image,j);
1025  PixelTrait traits = GetPixelChannelTraits(image,channel);
1026  if ((traits & UpdatePixelTrait) == 0)
1027  continue;
1028  if (depth_map[ScaleQuantumToMap(p[j])] > current_depth[id])
1029  current_depth[id]=depth_map[ScaleQuantumToMap(p[j])];
1030  }
1031  p+=(ptrdiff_t) GetPixelChannels(image);
1032  }
1033  if (current_depth[id] == MAGICKCORE_QUANTUM_DEPTH)
1034  status=MagickFalse;
1035  }
1036  image_view=DestroyCacheView(image_view);
1037  depth=current_depth[0];
1038  for (i=1; i < (ssize_t) number_threads; i++)
1039  if (depth < current_depth[i])
1040  depth=current_depth[i];
1041  depth_map=(size_t *) RelinquishMagickMemory(depth_map);
1042  current_depth=(size_t *) RelinquishMagickMemory(current_depth);
1043  return(depth);
1044  }
1045 #endif
1046  /*
1047  Compute pixel depth.
1048  */
1049 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1050  #pragma omp parallel for schedule(static) shared(status) \
1051  magick_number_threads(image,image,image->rows,1)
1052 #endif
1053  for (y=0; y < (ssize_t) image->rows; y++)
1054  {
1055  const int
1056  id = GetOpenMPThreadId();
1057 
1058  const Quantum
1059  *magick_restrict p;
1060 
1061  ssize_t
1062  x;
1063 
1064  if (status == MagickFalse)
1065  continue;
1066  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1067  if (p == (const Quantum *) NULL)
1068  continue;
1069  for (x=0; x < (ssize_t) image->columns; x++)
1070  {
1071  ssize_t
1072  j;
1073 
1074  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1075  {
1076  PixelChannel
1077  channel;
1078 
1079  PixelTrait
1080  traits;
1081 
1082  channel=GetPixelChannelChannel(image,j);
1083  traits=GetPixelChannelTraits(image,channel);
1084  if ((traits & UpdatePixelTrait) == 0)
1085  continue;
1086  while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
1087  {
1088  QuantumAny
1089  range;
1090 
1091  range=GetQuantumRange(current_depth[id]);
1092  if (p[j] == ScaleAnyToQuantum(ScaleQuantumToAny(p[j],range),range))
1093  break;
1094  current_depth[id]++;
1095  }
1096  }
1097  p+=(ptrdiff_t) GetPixelChannels(image);
1098  }
1099  if (current_depth[id] == MAGICKCORE_QUANTUM_DEPTH)
1100  status=MagickFalse;
1101  }
1102  image_view=DestroyCacheView(image_view);
1103  depth=current_depth[0];
1104  for (i=1; i < (ssize_t) number_threads; i++)
1105  if (depth < current_depth[i])
1106  depth=current_depth[i];
1107  current_depth=(size_t *) RelinquishMagickMemory(current_depth);
1108  return(depth);
1109 }
1110 
1111 /*
1112 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1113 % %
1114 % %
1115 % %
1116 % 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 %
1117 % %
1118 % %
1119 % %
1120 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1121 %
1122 % GetImageMinimumBoundingBox() returns the points that form the minimum
1123 % bounding box around the image foreground objects with the "Rotating
1124 % Calipers" algorithm. The method also returns these properties:
1125 % minimum-bounding-box:area, minimum-bounding-box:width,
1126 % minimum-bounding-box:height, and minimum-bounding-box:angle.
1127 %
1128 % The format of the GetImageMinimumBoundingBox method is:
1129 %
1130 % PointInfo *GetImageMinimumBoundingBox(Image *image,
1131 % size_t number_vertices,ExceptionInfo *exception)
1132 %
1133 % A description of each parameter follows:
1134 %
1135 % o image: the image.
1136 %
1137 % o number_vertices: the number of vertices in the bounding box.
1138 %
1139 % o exception: return any errors or warnings in this structure.
1140 %
1141 */
1142 
1143 typedef struct _CaliperInfo
1144 {
1145  double
1146  area,
1147  width,
1148  height,
1149  projection;
1150 
1151  ssize_t
1152  p,
1153  q,
1154  v;
1155 } CaliperInfo;
1156 
1157 static inline double getAngle(PointInfo *p,PointInfo *q)
1158 {
1159  /*
1160  Get the angle between line (p,q) and horizontal axis, in degrees.
1161  */
1162  return(RadiansToDegrees(atan2(q->y-p->y,q->x-p->x)));
1163 }
1164 
1165 static inline double getDistance(PointInfo *p,PointInfo *q)
1166 {
1167  double
1168  distance;
1169 
1170  distance=hypot(p->x-q->x,p->y-q->y);
1171  return(distance*distance);
1172 }
1173 
1174 static inline double getProjection(PointInfo *p,PointInfo *q,PointInfo *v)
1175 {
1176  double
1177  distance;
1178 
1179  /*
1180  Projection of vector (x,y) - p into a line passing through p and q.
1181  */
1182  distance=getDistance(p,q);
1183  if (distance < MagickEpsilon)
1184  return(INFINITY);
1185  return((q->x-p->x)*(v->x-p->x)+(v->y-p->y)*(q->y-p->y))/sqrt(distance);
1186 }
1187 
1188 static inline double getFeretDiameter(PointInfo *p,PointInfo *q,PointInfo *v)
1189 {
1190  double
1191  distance;
1192 
1193  /*
1194  Distance from a point (x,y) to a line passing through p and q.
1195  */
1196  distance=getDistance(p,q);
1197  if (distance < MagickEpsilon)
1198  return(INFINITY);
1199  return((q->x-p->x)*(v->y-p->y)-(v->x-p->x)*(q->y-p->y))/sqrt(distance);
1200 }
1201 
1202 MagickExport PointInfo *GetImageMinimumBoundingBox(Image *image,
1203  size_t *number_vertices,ExceptionInfo *exception)
1204 {
1205  CaliperInfo
1206  caliper_info;
1207 
1208  const char
1209  *artifact;
1210 
1211  double
1212  angle,
1213  diameter,
1214  distance;
1215 
1216  PointInfo
1217  *bounding_box,
1218  *vertices;
1219 
1220  size_t
1221  number_hull_vertices;
1222 
1223  ssize_t
1224  i;
1225 
1226  /*
1227  Generate the minimum bounding box with the "Rotating Calipers" algorithm.
1228  */
1229  assert(image != (Image *) NULL);
1230  assert(image->signature == MagickCoreSignature);
1231  if (IsEventLogging() != MagickFalse)
1232  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1233  *number_vertices=0;
1234  vertices=GetImageConvexHull(image,&number_hull_vertices,exception);
1235  if (vertices == (PointInfo *) NULL)
1236  return((PointInfo *) NULL);
1237  *number_vertices=4;
1238  bounding_box=(PointInfo *) AcquireQuantumMemory(*number_vertices,
1239  sizeof(*bounding_box));
1240  if (bounding_box == (PointInfo *) NULL)
1241  {
1242  vertices=(PointInfo *) RelinquishMagickMemory(vertices);
1243  return((PointInfo *) NULL);
1244  }
1245  caliper_info.area=2.0*image->columns*image->rows;
1246  caliper_info.width=(double) image->columns+image->rows;
1247  caliper_info.height=0.0;
1248  caliper_info.projection=0.0;
1249  caliper_info.p=(-1);
1250  caliper_info.q=(-1);
1251  caliper_info.v=(-1);
1252  for (i=0; i < (ssize_t) number_hull_vertices; i++)
1253  {
1254  double
1255  area = 0.0,
1256  max_projection = 0.0,
1257  min_diameter = -1.0,
1258  min_projection = 0.0;
1259 
1260  ssize_t
1261  j,
1262  k;
1263 
1264  ssize_t
1265  p = -1,
1266  q = -1,
1267  v = -1;
1268 
1269  for (j=0; j < (ssize_t) number_hull_vertices; j++)
1270  {
1271  diameter=fabs(getFeretDiameter(&vertices[i],
1272  &vertices[(i+1) % (ssize_t) number_hull_vertices],&vertices[j]));
1273  if (min_diameter < diameter)
1274  {
1275  min_diameter=diameter;
1276  p=i;
1277  q=(i+1) % (ssize_t) 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 */
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 */
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 & BlendPixelTrait) == 0)
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 if 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 */
1564 MagickExport ImageType IdentifyImageGray(const Image *image,
1565  ExceptionInfo *exception)
1566 {
1567  CacheView
1568  *image_view;
1569 
1570  ImageType
1571  type = BilevelType;
1572 
1573  MagickBooleanType
1574  status = MagickTrue;
1575 
1576  ssize_t
1577  y;
1578 
1579  assert(image != (Image *) NULL);
1580  assert(image->signature == MagickCoreSignature);
1581  if (IsEventLogging() != MagickFalse)
1582  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1583  if (IsImageGray(image) != MagickFalse)
1584  return(image->type);
1585  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1586  return(UndefinedType);
1587  image_view=AcquireVirtualCacheView(image,exception);
1588 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1589  #pragma omp parallel for schedule(static) shared(status,type) \
1590  magick_number_threads(image,image,image->rows,2)
1591 #endif
1592  for (y=0; y < (ssize_t) image->rows; y++)
1593  {
1594  const Quantum
1595  *p;
1596 
1597  ssize_t
1598  x;
1599 
1600  if (status == MagickFalse)
1601  continue;
1602  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1603  if (p == (const Quantum *) NULL)
1604  {
1605  status=MagickFalse;
1606  continue;
1607  }
1608  for (x=0; x < (ssize_t) image->columns; x++)
1609  {
1610  if (IsPixelGray(image,p) == MagickFalse)
1611  {
1612  status=MagickFalse;
1613  break;
1614  }
1615  if ((type == BilevelType) && (IsPixelMonochrome(image,p) == MagickFalse))
1616  type=GrayscaleType;
1617  p+=(ptrdiff_t) GetPixelChannels(image);
1618  }
1619  }
1620  image_view=DestroyCacheView(image_view);
1621  if ((type == GrayscaleType) && (image->alpha_trait != UndefinedPixelTrait))
1622  type=GrayscaleAlphaType;
1623  if (status == MagickFalse)
1624  return(UndefinedType);
1625  return(type);
1626 }
1627 
1628 /*
1629 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1630 % %
1631 % %
1632 % %
1633 % I d e n t i f y I m a g e M o n o c h r o m e %
1634 % %
1635 % %
1636 % %
1637 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1638 %
1639 % IdentifyImageMonochrome() returns MagickTrue if all the pixels in the image
1640 % have the same red, green, and blue intensities and the intensity is either
1641 % 0 or QuantumRange.
1642 %
1643 % The format of the IdentifyImageMonochrome method is:
1644 %
1645 % MagickBooleanType IdentifyImageMonochrome(const Image *image,
1646 % ExceptionInfo *exception)
1647 %
1648 % A description of each parameter follows:
1649 %
1650 % o image: the image.
1651 %
1652 % o exception: return any errors or warnings in this structure.
1653 %
1654 */
1655 MagickExport MagickBooleanType IdentifyImageMonochrome(const Image *image,
1656  ExceptionInfo *exception)
1657 {
1658  CacheView
1659  *image_view;
1660 
1661  ImageType
1662  type = BilevelType;
1663 
1664  ssize_t
1665  y;
1666 
1667  assert(image != (Image *) NULL);
1668  assert(image->signature == MagickCoreSignature);
1669  if (IsEventLogging() != MagickFalse)
1670  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1671  if (image->type == BilevelType)
1672  return(MagickTrue);
1673  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1674  return(MagickFalse);
1675  image_view=AcquireVirtualCacheView(image,exception);
1676 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1677  #pragma omp parallel for schedule(static) shared(type) \
1678  magick_number_threads(image,image,image->rows,2)
1679 #endif
1680  for (y=0; y < (ssize_t) image->rows; y++)
1681  {
1682  const Quantum
1683  *p;
1684 
1685  ssize_t
1686  x;
1687 
1688  if (type == UndefinedType)
1689  continue;
1690  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1691  if (p == (const Quantum *) NULL)
1692  {
1693  type=UndefinedType;
1694  continue;
1695  }
1696  for (x=0; x < (ssize_t) image->columns; x++)
1697  {
1698  if (IsPixelMonochrome(image,p) == MagickFalse)
1699  {
1700  type=UndefinedType;
1701  break;
1702  }
1703  p+=(ptrdiff_t) GetPixelChannels(image);
1704  }
1705  }
1706  image_view=DestroyCacheView(image_view);
1707  return(type == BilevelType ? MagickTrue : MagickFalse);
1708 }
1709 
1710 /*
1711 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1712 % %
1713 % %
1714 % %
1715 % I d e n t i f y I m a g e T y p e %
1716 % %
1717 % %
1718 % %
1719 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1720 %
1721 % IdentifyImageType() returns the potential type of image:
1722 %
1723 % Bilevel Grayscale GrayscaleMatte
1724 % Palette PaletteMatte TrueColor
1725 % TrueColorMatte ColorSeparation ColorSeparationMatte
1726 %
1727 % To ensure the image type matches its potential, use SetImageType():
1728 %
1729 % (void) SetImageType(image,IdentifyImageType(image,exception),exception);
1730 %
1731 % The format of the IdentifyImageType method is:
1732 %
1733 % ImageType IdentifyImageType(const Image *image,ExceptionInfo *exception)
1734 %
1735 % A description of each parameter follows:
1736 %
1737 % o image: the image.
1738 %
1739 % o exception: return any errors or warnings in this structure.
1740 %
1741 */
1742 MagickExport ImageType IdentifyImageType(const Image *image,
1743  ExceptionInfo *exception)
1744 {
1745  ImageType
1746  type;
1747 
1748  assert(image != (Image *) NULL);
1749  assert(image->signature == MagickCoreSignature);
1750  if (IsEventLogging() != MagickFalse)
1751  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1752  if (image->colorspace == CMYKColorspace)
1753  {
1754  if ((image->alpha_trait & BlendPixelTrait) == 0)
1755  return(ColorSeparationType);
1756  return(ColorSeparationAlphaType);
1757  }
1758  type=IdentifyImageGray(image,exception);
1759  if (IsGrayImageType(type))
1760  return(type);
1761  if (IdentifyPaletteImage(image,exception) != MagickFalse)
1762  {
1763  if (image->alpha_trait != UndefinedPixelTrait)
1764  return(PaletteAlphaType);
1765  return(PaletteType);
1766  }
1767  if (image->alpha_trait != UndefinedPixelTrait)
1768  return(TrueColorAlphaType);
1769  return(TrueColorType);
1770 }
1771 
1772 /*
1773 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1774 % %
1775 % %
1776 % %
1777 % I s I m a g e G r a y %
1778 % %
1779 % %
1780 % %
1781 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1782 %
1783 % IsImageGray() returns MagickTrue if the type of the image is grayscale or
1784 % bi-level.
1785 %
1786 % The format of the IsImageGray method is:
1787 %
1788 % MagickBooleanType IsImageGray(const Image *image)
1789 %
1790 % A description of each parameter follows:
1791 %
1792 % o image: the image.
1793 %
1794 */
1795 MagickExport MagickBooleanType IsImageGray(const Image *image)
1796 {
1797  assert(image != (Image *) NULL);
1798  assert(image->signature == MagickCoreSignature);
1799  if (IsGrayImageType(image->type) != MagickFalse)
1800  return(MagickTrue);
1801  return(MagickFalse);
1802 }
1803 
1804 /*
1805 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1806 % %
1807 % %
1808 % %
1809 % I s I m a g e M o n o c h r o m e %
1810 % %
1811 % %
1812 % %
1813 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1814 %
1815 % IsImageMonochrome() returns MagickTrue if type of the image is bi-level.
1816 %
1817 % The format of the IsImageMonochrome method is:
1818 %
1819 % MagickBooleanType IsImageMonochrome(const Image *image)
1820 %
1821 % A description of each parameter follows:
1822 %
1823 % o image: the image.
1824 %
1825 */
1826 MagickExport MagickBooleanType IsImageMonochrome(const Image *image)
1827 {
1828  assert(image != (Image *) NULL);
1829  assert(image->signature == MagickCoreSignature);
1830  if (image->type == BilevelType)
1831  return(MagickTrue);
1832  return(MagickFalse);
1833 }
1834 
1835 /*
1836 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1837 % %
1838 % %
1839 % %
1840 % I s I m a g e O p a q u e %
1841 % %
1842 % %
1843 % %
1844 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1845 %
1846 % IsImageOpaque() returns MagickTrue if none of the pixels in the image have
1847 % an alpha value other than OpaqueAlpha (QuantumRange).
1848 %
1849 % Will return true immediately is alpha channel is not available.
1850 %
1851 % The format of the IsImageOpaque method is:
1852 %
1853 % MagickBooleanType IsImageOpaque(const Image *image,
1854 % ExceptionInfo *exception)
1855 %
1856 % A description of each parameter follows:
1857 %
1858 % o image: the image.
1859 %
1860 % o exception: return any errors or warnings in this structure.
1861 %
1862 */
1863 MagickExport MagickBooleanType IsImageOpaque(const Image *image,
1864  ExceptionInfo *exception)
1865 {
1866  CacheView
1867  *image_view;
1868 
1869  MagickBooleanType
1870  opaque = MagickTrue;
1871 
1872  ssize_t
1873  y;
1874 
1875  /*
1876  Determine if image is opaque.
1877  */
1878  assert(image != (Image *) NULL);
1879  assert(image->signature == MagickCoreSignature);
1880  if (IsEventLogging() != MagickFalse)
1881  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1882  if ((image->alpha_trait & BlendPixelTrait) == 0)
1883  return(MagickTrue);
1884  image_view=AcquireVirtualCacheView(image,exception);
1885 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1886  #pragma omp parallel for schedule(static) shared(opaque) \
1887  magick_number_threads(image,image,image->rows,2)
1888 #endif
1889  for (y=0; y < (ssize_t) image->rows; y++)
1890  {
1891  const Quantum
1892  *p;
1893 
1894  ssize_t
1895  x;
1896 
1897  if (opaque == MagickFalse)
1898  continue;
1899  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1900  if (p == (const Quantum *) NULL)
1901  {
1902  opaque=MagickFalse;
1903  continue;
1904  }
1905  for (x=0; x < (ssize_t) image->columns; x++)
1906  {
1907  if (GetPixelAlpha(image,p) != OpaqueAlpha)
1908  {
1909  opaque=MagickFalse;
1910  break;
1911  }
1912  p+=(ptrdiff_t) GetPixelChannels(image);
1913  }
1914  }
1915  image_view=DestroyCacheView(image_view);
1916  return(opaque);
1917 }
1918 
1919 /*
1920 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1921 % %
1922 % %
1923 % %
1924 % S e t I m a g e D e p t h %
1925 % %
1926 % %
1927 % %
1928 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1929 %
1930 % SetImageDepth() sets the depth of the image.
1931 %
1932 % The format of the SetImageDepth method is:
1933 %
1934 % MagickBooleanType SetImageDepth(Image *image,const size_t depth,
1935 % ExceptionInfo *exception)
1936 %
1937 % A description of each parameter follows:
1938 %
1939 % o image: the image.
1940 %
1941 % o channel: the channel.
1942 %
1943 % o depth: the image depth.
1944 %
1945 % o exception: return any errors or warnings in this structure.
1946 %
1947 */
1948 
1949 static MagickBooleanType FloydSteinbergImageDepth(Image *image,
1950  const size_t depth,ExceptionInfo *exception)
1951 {
1952  CacheView
1953  *image_view;
1954 
1955  double
1956  *distortion;
1957 
1958  MagickBooleanType
1959  status;
1960 
1961  QuantumAny
1962  range;
1963 
1964  size_t
1965  channels;
1966 
1967  ssize_t
1968  y;
1969 
1970  /*
1971  Dither pixels with Floyd Steinberg algorithm.
1972  */
1973  status=SetImageStorageClass(image,DirectClass,exception);
1974  if (status == MagickFalse)
1975  return(MagickFalse);
1976  channels=GetPixelChannels(image);
1977  distortion=(double *) AcquireQuantumMemory(image->columns,3*channels*
1978  sizeof(*distortion));
1979  if (distortion == (double *) NULL)
1980  return(MagickFalse);
1981  (void) memset(distortion,0,3*image->columns*channels*sizeof(*distortion));
1982  range=GetQuantumRange(depth);
1983  image_view=AcquireAuthenticCacheView(image,exception);
1984  for (y=0; y < (ssize_t) image->rows; y++)
1985  {
1986  Quantum
1987  *magick_restrict q;
1988 
1989  ssize_t
1990  u,
1991  v,
1992  x;
1993 
1994  if (status == MagickFalse)
1995  continue;
1996  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1997  if (q == (Quantum *) NULL)
1998  {
1999  status=MagickFalse;
2000  continue;
2001  }
2002  /*
2003  Reset pixel distortion for current row.
2004  */
2005  u=(y % 3)*(ssize_t) (image->columns*channels);
2006  (void) memset(distortion+u,0,image->columns*channels*sizeof(*distortion));
2007  v=((y+1) % 3)*(ssize_t) (image->columns*channels);
2008  for (x=0; x < (ssize_t) image->columns; x++)
2009  {
2010  ssize_t
2011  i;
2012 
2013  for (i=0; i < (ssize_t) channels; i++)
2014  {
2015  double
2016  error,
2017  pixel;
2018 
2019  PixelChannel
2020  channel;
2021 
2022  PixelTrait
2023  traits;
2024 
2025  /*
2026  Add distortion to current pixel then distribute new distortion.
2027  */
2028  channel=GetPixelChannelChannel(image,i);
2029  traits=GetPixelChannelTraits(image,channel);
2030  if ((traits & UpdatePixelTrait) == 0)
2031  continue;
2032  pixel=(double) q[i]+distortion[u];
2033  q[i]=ScaleAnyToQuantum(ScaleQuantumToAny(ClampPixel((MagickRealType)
2034  pixel),range),range);
2035  /*
2036  Distribute distortion for right.
2037  */
2038  error=pixel-(double) q[i];
2039  if ((x+1) < (ssize_t) image->columns)
2040  distortion[u+(ssize_t) channels]+=7.0*error/16.0;
2041  if ((y+1) < (ssize_t) image->rows)
2042  {
2043  /*
2044  Distribute distortion for bottom left, bottom, and bottom right.
2045  */
2046  if (x > 0)
2047  distortion[v-(ssize_t) channels]+=3.0*error/16.0;
2048  distortion[v]+=5.0*error/16.0;
2049  if ((x+1) < (ssize_t) image->columns)
2050  distortion[v+(ssize_t) channels]+=1.0*error/16.0;
2051  }
2052  u++;
2053  v++;
2054  }
2055  q+=(ptrdiff_t) GetPixelChannels(image);
2056  }
2057  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2058  {
2059  status=MagickFalse;
2060  continue;
2061  }
2062  }
2063  image_view=DestroyCacheView(image_view);
2064  distortion=(double *) RelinquishMagickMemory(distortion);
2065  if (status != MagickFalse)
2066  image->depth=depth;
2067  return(status);
2068 }
2069 
2070 MagickExport MagickBooleanType SetImageDepth(Image *image,
2071  const size_t depth,ExceptionInfo *exception)
2072 {
2073  CacheView
2074  *image_view;
2075 
2076  const char
2077  *artifact;
2078 
2079  MagickBooleanType
2080  status;
2081 
2082  QuantumAny
2083  range;
2084 
2085  ssize_t
2086  y;
2087 
2088  assert(image != (Image *) NULL);
2089  if (IsEventLogging() != MagickFalse)
2090  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2091  assert(image->signature == MagickCoreSignature);
2092  if (depth >= MAGICKCORE_QUANTUM_DEPTH)
2093  {
2094  image->depth=depth;
2095  return(MagickTrue);
2096  }
2097  artifact=GetImageArtifact(image,"dither");
2098  if ((artifact != (const char *) NULL) &&
2099  (LocaleCompare(artifact,"FloydSteinberg") == 0))
2100  return(FloydSteinbergImageDepth(image,depth,exception));
2101  range=GetQuantumRange(depth);
2102  if (image->storage_class == PseudoClass)
2103  {
2104  ssize_t
2105  i;
2106 
2107 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2108  #pragma omp parallel for schedule(static) shared(status) \
2109  magick_number_threads(image,image,image->colors,1)
2110 #endif
2111  for (i=0; i < (ssize_t) image->colors; i++)
2112  {
2113  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2114  image->colormap[i].red=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2115  ClampPixel(image->colormap[i].red),range),range);
2116  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2117  image->colormap[i].green=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2118  ClampPixel(image->colormap[i].green),range),range);
2119  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2120  image->colormap[i].blue=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2121  ClampPixel(image->colormap[i].blue),range),range);
2122  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2123  image->colormap[i].alpha=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2124  ClampPixel(image->colormap[i].alpha),range),range);
2125  }
2126  }
2127  status=MagickTrue;
2128  image_view=AcquireAuthenticCacheView(image,exception);
2129 #if !defined(MAGICKCORE_HDRI_SUPPORT)
2130  DisableMSCWarning(4127)
2131  if ((1UL*QuantumRange) <= MaxMap)
2132  RestoreMSCWarning
2133  {
2134  Quantum
2135  *depth_map;
2136 
2137  ssize_t
2138  i;
2139 
2140  /*
2141  Scale pixels to desired (optimized with depth map).
2142  */
2143  depth_map=(Quantum *) AcquireQuantumMemory(MaxMap+1,sizeof(*depth_map));
2144  if (depth_map == (Quantum *) NULL)
2145  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2146  for (i=0; i <= (ssize_t) MaxMap; i++)
2147  depth_map[i]=ScaleAnyToQuantum(ScaleQuantumToAny((Quantum) i,range),
2148  range);
2149 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2150  #pragma omp parallel for schedule(static) shared(status) \
2151  magick_number_threads(image,image,image->rows,2)
2152 #endif
2153  for (y=0; y < (ssize_t) image->rows; y++)
2154  {
2155  ssize_t
2156  x;
2157 
2158  Quantum
2159  *magick_restrict q;
2160 
2161  if (status == MagickFalse)
2162  continue;
2163  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
2164  exception);
2165  if (q == (Quantum *) NULL)
2166  {
2167  status=MagickFalse;
2168  continue;
2169  }
2170  for (x=0; x < (ssize_t) image->columns; x++)
2171  {
2172  ssize_t
2173  j;
2174 
2175  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2176  {
2177  PixelChannel
2178  channel;
2179 
2180  PixelTrait
2181  traits;
2182 
2183  channel=GetPixelChannelChannel(image,j);
2184  traits=GetPixelChannelTraits(image,channel);
2185  if ((traits & UpdatePixelTrait) == 0)
2186  continue;
2187  q[j]=depth_map[ScaleQuantumToMap(q[j])];
2188  }
2189  q+=(ptrdiff_t) GetPixelChannels(image);
2190  }
2191  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2192  {
2193  status=MagickFalse;
2194  continue;
2195  }
2196  }
2197  image_view=DestroyCacheView(image_view);
2198  depth_map=(Quantum *) RelinquishMagickMemory(depth_map);
2199  if (status != MagickFalse)
2200  image->depth=depth;
2201  return(status);
2202  }
2203 #endif
2204  /*
2205  Scale pixels to desired depth.
2206  */
2207 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2208  #pragma omp parallel for schedule(static) shared(status) \
2209  magick_number_threads(image,image,image->rows,2)
2210 #endif
2211  for (y=0; y < (ssize_t) image->rows; y++)
2212  {
2213  ssize_t
2214  x;
2215 
2216  Quantum
2217  *magick_restrict q;
2218 
2219  if (status == MagickFalse)
2220  continue;
2221  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2222  if (q == (Quantum *) NULL)
2223  {
2224  status=MagickFalse;
2225  continue;
2226  }
2227  for (x=0; x < (ssize_t) image->columns; x++)
2228  {
2229  ssize_t
2230  i;
2231 
2232  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2233  {
2234  PixelChannel
2235  channel;
2236 
2237  PixelTrait
2238  traits;
2239 
2240  channel=GetPixelChannelChannel(image,i);
2241  traits=GetPixelChannelTraits(image,channel);
2242  if ((traits & UpdatePixelTrait) == 0)
2243  continue;
2244  q[i]=ScaleAnyToQuantum(ScaleQuantumToAny(ClampPixel((MagickRealType)
2245  q[i]),range),range);
2246  }
2247  q+=(ptrdiff_t) GetPixelChannels(image);
2248  }
2249  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2250  {
2251  status=MagickFalse;
2252  continue;
2253  }
2254  }
2255  image_view=DestroyCacheView(image_view);
2256  if (status != MagickFalse)
2257  image->depth=depth;
2258  return(status);
2259 }
2260 
2261 /*
2262 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2263 % %
2264 % %
2265 % %
2266 % S e t I m a g e T y p e %
2267 % %
2268 % %
2269 % %
2270 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2271 %
2272 % SetImageType() sets the type of image. Choose from these types:
2273 %
2274 % Bilevel Grayscale GrayscaleMatte
2275 % Palette PaletteMatte TrueColor
2276 % TrueColorMatte ColorSeparation ColorSeparationMatte
2277 % OptimizeType
2278 %
2279 % The format of the SetImageType method is:
2280 %
2281 % MagickBooleanType SetImageType(Image *image,const ImageType type,
2282 % ExceptionInfo *exception)
2283 %
2284 % A description of each parameter follows:
2285 %
2286 % o image: the image.
2287 %
2288 % o type: Image type.
2289 %
2290 % o exception: return any errors or warnings in this structure.
2291 %
2292 */
2293 MagickExport MagickBooleanType SetImageType(Image *image,const ImageType type,
2294  ExceptionInfo *exception)
2295 {
2296  const char
2297  *artifact;
2298 
2299  ImageInfo
2300  *image_info;
2301 
2302  MagickBooleanType
2303  status;
2304 
2305  QuantizeInfo
2306  *quantize_info;
2307 
2308  assert(image != (Image *) NULL);
2309  if (IsEventLogging() != MagickFalse)
2310  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2311  assert(image->signature == MagickCoreSignature);
2312  status=MagickTrue;
2313  image_info=AcquireImageInfo();
2314  image_info->dither=image->dither;
2315  artifact=GetImageArtifact(image,"dither");
2316  if (artifact != (const char *) NULL)
2317  (void) SetImageOption(image_info,"dither",artifact);
2318  switch (type)
2319  {
2320  case BilevelType:
2321  {
2322  if (IsGrayImageType(image->type) == MagickFalse)
2323  status=TransformImageColorspace(image,GRAYColorspace,exception);
2324  (void) NormalizeImage(image,exception);
2325  (void) BilevelImage(image,(double) QuantumRange/2.0,exception);
2326  quantize_info=AcquireQuantizeInfo(image_info);
2327  quantize_info->number_colors=2;
2328  quantize_info->colorspace=GRAYColorspace;
2329  status=QuantizeImage(quantize_info,image,exception);
2330  quantize_info=DestroyQuantizeInfo(quantize_info);
2331  image->alpha_trait=UndefinedPixelTrait;
2332  break;
2333  }
2334  case GrayscaleType:
2335  {
2336  if (IsGrayImageType(image->type) == MagickFalse)
2337  status=TransformImageColorspace(image,GRAYColorspace,exception);
2338  image->alpha_trait=UndefinedPixelTrait;
2339  break;
2340  }
2341  case GrayscaleAlphaType:
2342  {
2343  if (IsGrayImageType(image->type) == MagickFalse)
2344  status=TransformImageColorspace(image,GRAYColorspace,exception);
2345  if ((image->alpha_trait & BlendPixelTrait) == 0)
2346  (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2347  break;
2348  }
2349  case PaletteType:
2350  {
2351  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2352  status=TransformImageColorspace(image,sRGBColorspace,exception);
2353  if ((image->storage_class == DirectClass) || (image->colors > 256))
2354  {
2355  quantize_info=AcquireQuantizeInfo(image_info);
2356  quantize_info->number_colors=256;
2357  status=QuantizeImage(quantize_info,image,exception);
2358  quantize_info=DestroyQuantizeInfo(quantize_info);
2359  }
2360  image->alpha_trait=UndefinedPixelTrait;
2361  break;
2362  }
2363  case PaletteBilevelAlphaType:
2364  {
2365  ChannelType
2366  channel_mask;
2367 
2368  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2369  status=TransformImageColorspace(image,sRGBColorspace,exception);
2370  if ((image->alpha_trait & BlendPixelTrait) == 0)
2371  (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2372  channel_mask=SetImageChannelMask(image,AlphaChannel);
2373  (void) BilevelImage(image,(double) QuantumRange/2.0,exception);
2374  (void) SetImageChannelMask(image,channel_mask);
2375  quantize_info=AcquireQuantizeInfo(image_info);
2376  status=QuantizeImage(quantize_info,image,exception);
2377  quantize_info=DestroyQuantizeInfo(quantize_info);
2378  break;
2379  }
2380  case PaletteAlphaType:
2381  {
2382  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2383  status=TransformImageColorspace(image,sRGBColorspace,exception);
2384  if ((image->alpha_trait & BlendPixelTrait) == 0)
2385  (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2386  quantize_info=AcquireQuantizeInfo(image_info);
2387  quantize_info->colorspace=TransparentColorspace;
2388  status=QuantizeImage(quantize_info,image,exception);
2389  quantize_info=DestroyQuantizeInfo(quantize_info);
2390  break;
2391  }
2392  case TrueColorType:
2393  {
2394  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2395  status=TransformImageColorspace(image,sRGBColorspace,exception);
2396  if (image->storage_class != DirectClass)
2397  status=SetImageStorageClass(image,DirectClass,exception);
2398  image->alpha_trait=UndefinedPixelTrait;
2399  break;
2400  }
2401  case TrueColorAlphaType:
2402  {
2403  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2404  status=TransformImageColorspace(image,sRGBColorspace,exception);
2405  if (image->storage_class != DirectClass)
2406  status=SetImageStorageClass(image,DirectClass,exception);
2407  if ((image->alpha_trait & BlendPixelTrait) == 0)
2408  (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2409  break;
2410  }
2411  case ColorSeparationType:
2412  {
2413  if (image->colorspace != CMYKColorspace)
2414  status=TransformImageColorspace(image,CMYKColorspace,exception);
2415  if (image->storage_class != DirectClass)
2416  status=SetImageStorageClass(image,DirectClass,exception);
2417  image->alpha_trait=UndefinedPixelTrait;
2418  break;
2419  }
2420  case ColorSeparationAlphaType:
2421  {
2422  if (image->colorspace != CMYKColorspace)
2423  status=TransformImageColorspace(image,CMYKColorspace,exception);
2424  if (image->storage_class != DirectClass)
2425  status=SetImageStorageClass(image,DirectClass,exception);
2426  if ((image->alpha_trait & BlendPixelTrait) == 0)
2427  status=SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2428  break;
2429  }
2430  case OptimizeType:
2431  case UndefinedType:
2432  break;
2433  }
2434  image_info=DestroyImageInfo(image_info);
2435  if (status == MagickFalse)
2436  return(status);
2437  image->type=type;
2438  return(MagickTrue);
2439 }
_RectangleInfo
Definition: geometry.h:129
_CacheView
Definition: cache-view.c:65
_MemoryInfo
Definition: memory.c:163
_Image
Definition: image.h:131
_CensusInfo
Definition: attribute.c:127
_PixelInfo
Definition: pixel.h:181
_QuantizeInfo
Definition: quantize.h:35
_ImageInfo
Definition: image.h:358
_CaliperInfo
Definition: attribute.c:1143
_ExceptionInfo
Definition: exception.h:101
_PointInfo
Definition: geometry.h:122