MagickCore 7.1.1
Convert, Edit, Or Compose Bitmap Images
Loading...
Searching...
No Matches
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
127typedef struct _CensusInfo
128{
129 double
130 left,
131 right,
132 top,
133 bottom;
134} CensusInfo;
135
136static 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{
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
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
241static 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
251static RectangleInfo GetEdgeBoundingBox(const Image *image,
252 ExceptionInfo *exception)
253{
255 *edge_view;
256
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
391MagickExport RectangleInfo GetImageBoundingBox(const Image *image,
392 ExceptionInfo *exception)
393{
395 *image_view;
396
397 const char
398 *artifact;
399
400 const Quantum
401 *p;
402
403 MagickBooleanType
404 status;
405
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
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
592static 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
600static 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
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 {
630 *edge_view;
631
632 GravityType
633 gravity;
634
635 Image
636 *edge_image;
637
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
730void TraceConvexHull(PointInfo *vertices,size_t number_vertices,
731 PointInfo ***monotone_chain,size_t *chain_length)
732{
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
766MagickExport PointInfo *GetImageConvexHull(const Image *image,
767 size_t *number_vertices,ExceptionInfo *exception)
768{
770 *image_view;
771
772 MagickBooleanType
773 status;
774
776 *monotone_info,
777 *vertices_info;
778
780 background;
781
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 {
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*/
890MagickExport size_t GetImageDepth(const Image *image,ExceptionInfo *exception)
891{
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
1143typedef 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
1157static 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
1165static 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
1174static 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
1188static 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
1202MagickExport PointInfo *GetImageMinimumBoundingBox(Image *image,
1203 size_t *number_vertices,ExceptionInfo *exception)
1204{
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*/
1460MagickExport 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*/
1509MagickExport 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*/
1564MagickExport 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*/
1655MagickExport 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*/
1742MagickExport 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*/
1795MagickExport 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*/
1826MagickExport 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*/
1863MagickExport 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
1949static 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
2070MagickExport 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*/
2293MagickExport 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
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}