MagickCore 7.1.2-32
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/license/ %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
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{
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
160 RectangleInfo
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{
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
271 RectangleInfo
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{
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
410 RectangleInfo
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
495 RectangleInfo
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 (bounds.x > (ssize_t) bounds.width) ||
555 (bounds.y > (ssize_t) bounds.height))
556 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
557 "GeometryDoesNotContainImage","`%s'",image->filename);
558 else
559 {
560 bounds.width-=(size_t) (bounds.x-1);
561 bounds.height-=(size_t) (bounds.y-1);
562 }
563 return(bounds);
564}
565
566/*
567%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
568% %
569% %
570% %
571% G e t I m a g e C o n v e x H u l l %
572% %
573% %
574% %
575%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
576%
577% GetImageConvexHull() returns the convex hull points of an image canvas.
578%
579% The format of the GetImageConvexHull method is:
580%
581% PointInfo *GetImageConvexHull(const Image *image,
582% size_t number_vertices,ExceptionInfo *exception)
583%
584% A description of each parameter follows:
585%
586% o image: the image.
587%
588% o number_vertices: the number of vertices in the convex hull.
589%
590% o exception: return any errors or warnings in this structure.
591%
592*/
593
594static double LexicographicalOrder(PointInfo *a,PointInfo *b,PointInfo *c)
595{
596 /*
597 Order by x-coordinate, and in case of a tie, by y-coordinate.
598 */
599 return((b->x-a->x)*(c->y-a->y)-(b->y-a->y)*(c->x-a->x));
600}
601
602static PixelInfo GetEdgeBackgroundColor(const Image *image,
603 const CacheView *image_view,ExceptionInfo *exception)
604{
605 const char
606 *artifact;
607
608 double
609 census[4],
610 edge_census;
611
612 PixelInfo
613 background[4],
614 edge_background;
615
616 ssize_t
617 i;
618
619 /*
620 Most dominant color of edges/corners is the background color of the image.
621 */
622 memset(&edge_background,0,sizeof(edge_background));
623 artifact=GetImageArtifact(image,"convex-hull:background-color");
624 if (artifact == (const char *) NULL)
625 artifact=GetImageArtifact(image,"background");
626#if defined(MAGICKCORE_OPENMP_SUPPORT)
627 #pragma omp parallel for schedule(static)
628#endif
629 for (i=0; i < 4; i++)
630 {
631 CacheView
632 *edge_view;
633
634 GravityType
635 gravity;
636
637 Image
638 *edge_image;
639
640 PixelInfo
641 pixel;
642
643 RectangleInfo
644 edge_geometry;
645
646 const Quantum
647 *p;
648
649 ssize_t
650 y;
651
652 census[i]=0.0;
653 (void) memset(&edge_geometry,0,sizeof(edge_geometry));
654 switch (i)
655 {
656 case 0:
657 default:
658 {
659 p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1,
660 exception);
661 gravity=WestGravity;
662 edge_geometry.width=1;
663 edge_geometry.height=0;
664 break;
665 }
666 case 1:
667 {
668 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1,
669 exception);
670 gravity=EastGravity;
671 edge_geometry.width=1;
672 edge_geometry.height=0;
673 break;
674 }
675 case 2:
676 {
677 p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception);
678 gravity=NorthGravity;
679 edge_geometry.width=0;
680 edge_geometry.height=1;
681 break;
682 }
683 case 3:
684 {
685 p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,
686 (ssize_t) image->rows-1,1,1,exception);
687 gravity=SouthGravity;
688 edge_geometry.width=0;
689 edge_geometry.height=1;
690 break;
691 }
692 }
693 GetPixelInfoPixel(image,p,background+i);
694 if (artifact != (const char *) NULL)
695 (void) QueryColorCompliance(artifact,AllCompliance,background+i,
696 exception);
697 GravityAdjustGeometry(image->columns,image->rows,gravity,&edge_geometry);
698 edge_image=CropImage(image,&edge_geometry,exception);
699 if (edge_image == (Image *) NULL)
700 continue;
701 edge_view=AcquireVirtualCacheView(edge_image,exception);
702 for (y=0; y < (ssize_t) edge_image->rows; y++)
703 {
704 ssize_t
705 x;
706
707 p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,
708 exception);
709 if (p == (const Quantum *) NULL)
710 break;
711 for (x=0; x < (ssize_t) edge_image->columns; x++)
712 {
713 GetPixelInfoPixel(edge_image,p,&pixel);
714 if (IsFuzzyEquivalencePixelInfo(&pixel,background+i) == MagickFalse)
715 census[i]++;
716 p+=(ptrdiff_t) GetPixelChannels(edge_image);
717 }
718 }
719 edge_view=DestroyCacheView(edge_view);
720 edge_image=DestroyImage(edge_image);
721 }
722 edge_census=(-1.0);
723 for (i=0; i < 4; i++)
724 if (census[i] > edge_census)
725 {
726 edge_background=background[i];
727 edge_census=census[i];
728 }
729 return(edge_background);
730}
731
732void TraceConvexHull(PointInfo *vertices,size_t number_vertices,
733 PointInfo ***monotone_chain,size_t *chain_length)
734{
735 PointInfo
736 **chain;
737
738 size_t
739 demark,
740 n;
741
742 ssize_t
743 i;
744
745 /*
746 Construct the upper and lower hulls: rightmost to leftmost counterclockwise.
747 */
748 chain=(*monotone_chain);
749 n=0;
750 for (i=0; i < (ssize_t) number_vertices; i++)
751 {
752 while ((n >= 2) &&
753 (LexicographicalOrder(chain[n-2],chain[n-1],&vertices[i]) <= 0.0))
754 n--;
755 chain[n++]=(&vertices[i]);
756 }
757 demark=n+1;
758 for (i=(ssize_t) number_vertices-2; i >= 0; i--)
759 {
760 while ((n >= demark) &&
761 (LexicographicalOrder(chain[n-2],chain[n-1],&vertices[i]) <= 0.0))
762 n--;
763 chain[n++]=(&vertices[i]);
764 }
765 *chain_length=n;
766}
767
768MagickExport PointInfo *GetImageConvexHull(const Image *image,
769 size_t *number_vertices,ExceptionInfo *exception)
770{
771 CacheView
772 *image_view;
773
774 MagickBooleanType
775 status;
776
777 MemoryInfo
778 *monotone_info,
779 *vertices_info;
780
781 PixelInfo
782 background;
783
784 PointInfo
785 *convex_hull,
786 **monotone_chain,
787 *vertices;
788
789 size_t
790 n;
791
792 ssize_t
793 y;
794
795 /*
796 Identify convex hull vertices of image foreground object(s).
797 */
798 assert(image != (Image *) NULL);
799 assert(image->signature == MagickCoreSignature);
800 if (IsEventLogging() != MagickFalse)
801 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
802 *number_vertices=0;
803 vertices_info=AcquireVirtualMemory(image->columns,image->rows*
804 sizeof(*vertices));
805 monotone_info=AcquireVirtualMemory(2*image->columns,2*
806 image->rows*sizeof(*monotone_chain));
807 if ((vertices_info == (MemoryInfo *) NULL) ||
808 (monotone_info == (MemoryInfo *) NULL))
809 {
810 if (monotone_info != (MemoryInfo *) NULL)
811 monotone_info=(MemoryInfo *) RelinquishVirtualMemory(monotone_info);
812 if (vertices_info != (MemoryInfo *) NULL)
813 vertices_info=RelinquishVirtualMemory(vertices_info);
814 return((PointInfo *) NULL);
815 }
816 vertices=(PointInfo *) GetVirtualMemoryBlob(vertices_info);
817 monotone_chain=(PointInfo **) GetVirtualMemoryBlob(monotone_info);
818 image_view=AcquireVirtualCacheView(image,exception);
819 background=GetEdgeBackgroundColor(image,image_view,exception);
820 status=MagickTrue;
821 n=0;
822 for (y=0; y < (ssize_t) image->rows; y++)
823 {
824 const Quantum
825 *p;
826
827 ssize_t
828 x;
829
830 if (status == MagickFalse)
831 continue;
832 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
833 if (p == (const Quantum *) NULL)
834 {
835 status=MagickFalse;
836 continue;
837 }
838 for (x=0; x < (ssize_t) image->columns; x++)
839 {
840 PixelInfo
841 pixel;
842
843 GetPixelInfoPixel(image,p,&pixel);
844 if (IsFuzzyEquivalencePixelInfo(&pixel,&background) == MagickFalse)
845 {
846 vertices[n].x=(double) x;
847 vertices[n].y=(double) y;
848 n++;
849 }
850 p+=(ptrdiff_t) GetPixelChannels(image);
851 }
852 }
853 image_view=DestroyCacheView(image_view);
854 /*
855 Return the convex hull of the image foreground object(s).
856 */
857 TraceConvexHull(vertices,n,&monotone_chain,number_vertices);
858 convex_hull=(PointInfo *) AcquireQuantumMemory(*number_vertices,
859 sizeof(*convex_hull));
860 if (convex_hull != (PointInfo *) NULL)
861 for (n=0; n < *number_vertices; n++)
862 convex_hull[n]=(*monotone_chain[n]);
863 monotone_info=RelinquishVirtualMemory(monotone_info);
864 vertices_info=RelinquishVirtualMemory(vertices_info);
865 return(convex_hull);
866}
867
868/*
869%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
870% %
871% %
872% %
873% G e t I m a g e D e p t h %
874% %
875% %
876% %
877%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
878%
879% GetImageDepth() returns the depth of a particular image channel.
880%
881% The format of the GetImageDepth method is:
882%
883% size_t GetImageDepth(const Image *image,ExceptionInfo *exception)
884%
885% A description of each parameter follows:
886%
887% o image: the image.
888%
889% o exception: return any errors or warnings in this structure.
890%
891*/
892MagickExport size_t GetImageDepth(const Image *image,ExceptionInfo *exception)
893{
894 CacheView
895 *image_view;
896
897 MagickBooleanType
898 status;
899
900 ssize_t
901 i;
902
903 size_t
904 *current_depth,
905 depth,
906 number_threads;
907
908 ssize_t
909 y;
910
911 /*
912 Compute image depth.
913 */
914 assert(image != (Image *) NULL);
915 assert(image->signature == MagickCoreSignature);
916 if (IsEventLogging() != MagickFalse)
917 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
918 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
919 current_depth=(size_t *) AcquireQuantumMemory(number_threads,
920 sizeof(*current_depth));
921 if (current_depth == (size_t *) NULL)
922 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
923 status=MagickTrue;
924 for (i=0; i < (ssize_t) number_threads; i++)
925 current_depth[i]=1;
926 if ((image->storage_class == PseudoClass) &&
927 ((image->alpha_trait & BlendPixelTrait) == 0))
928 {
929 for (i=0; i < (ssize_t) image->colors; i++)
930 {
931 const int
932 id = GetOpenMPThreadId();
933
934 while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
935 {
936 MagickBooleanType
937 atDepth;
938
939 QuantumAny
940 range;
941
942 atDepth=MagickTrue;
943 range=GetQuantumRange(current_depth[id]);
944 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
945 if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].red),range) == MagickFalse)
946 atDepth=MagickFalse;
947 if ((atDepth != MagickFalse) &&
948 (GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
949 if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].green),range) == MagickFalse)
950 atDepth=MagickFalse;
951 if ((atDepth != MagickFalse) &&
952 (GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
953 if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].blue),range) == MagickFalse)
954 atDepth=MagickFalse;
955 if ((atDepth != MagickFalse))
956 break;
957 current_depth[id]++;
958 }
959 }
960 depth=current_depth[0];
961 for (i=1; i < (ssize_t) number_threads; i++)
962 if (depth < current_depth[i])
963 depth=current_depth[i];
964 current_depth=(size_t *) RelinquishMagickMemory(current_depth);
965 return(depth);
966 }
967 image_view=AcquireVirtualCacheView(image,exception);
968#if !defined(MAGICKCORE_HDRI_SUPPORT)
969 DisableMSCWarning(4127)
970 if ((1UL*QuantumRange) <= MaxMap)
971 RestoreMSCWarning
972 {
973 size_t
974 *depth_map;
975
976 /*
977 Scale pixels to desired (optimized with depth map).
978 */
979 depth_map=(size_t *) AcquireQuantumMemory(MaxMap+1,sizeof(*depth_map));
980 if (depth_map == (size_t *) NULL)
981 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
982 for (i=0; i <= (ssize_t) MaxMap; i++)
983 {
984 for (depth=1; depth < (size_t) MAGICKCORE_QUANTUM_DEPTH; depth++)
985 {
986 Quantum
987 pixel;
988
989 QuantumAny
990 range;
991
992 range=GetQuantumRange(depth);
993 pixel=(Quantum) i;
994 if (pixel == ScaleAnyToQuantum(ScaleQuantumToAny(pixel,range),range))
995 break;
996 }
997 depth_map[i]=depth;
998 }
999#if defined(MAGICKCORE_OPENMP_SUPPORT)
1000 #pragma omp parallel for schedule(static) shared(status) \
1001 magick_number_threads(image,image,image->rows,1)
1002#endif
1003 for (y=0; y < (ssize_t) image->rows; y++)
1004 {
1005 const int
1006 id = GetOpenMPThreadId();
1007
1008 const Quantum
1009 *magick_restrict p;
1010
1011 ssize_t
1012 x;
1013
1014 if (status == MagickFalse)
1015 continue;
1016 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1017 if (p == (const Quantum *) NULL)
1018 continue;
1019 for (x=0; x < (ssize_t) image->columns; x++)
1020 {
1021 ssize_t
1022 j;
1023
1024 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1025 {
1026 PixelChannel channel = GetPixelChannelChannel(image,j);
1027 PixelTrait traits = GetPixelChannelTraits(image,channel);
1028 if ((traits & UpdatePixelTrait) == 0)
1029 continue;
1030 if (depth_map[ScaleQuantumToMap(p[j])] > current_depth[id])
1031 current_depth[id]=depth_map[ScaleQuantumToMap(p[j])];
1032 }
1033 p+=(ptrdiff_t) GetPixelChannels(image);
1034 }
1035 if (current_depth[id] == MAGICKCORE_QUANTUM_DEPTH)
1036 status=MagickFalse;
1037 }
1038 image_view=DestroyCacheView(image_view);
1039 depth=current_depth[0];
1040 for (i=1; i < (ssize_t) number_threads; i++)
1041 if (depth < current_depth[i])
1042 depth=current_depth[i];
1043 depth_map=(size_t *) RelinquishMagickMemory(depth_map);
1044 current_depth=(size_t *) RelinquishMagickMemory(current_depth);
1045 return(depth);
1046 }
1047#endif
1048 /*
1049 Compute pixel depth.
1050 */
1051#if defined(MAGICKCORE_OPENMP_SUPPORT)
1052 #pragma omp parallel for schedule(static) shared(status) \
1053 magick_number_threads(image,image,image->rows,1)
1054#endif
1055 for (y=0; y < (ssize_t) image->rows; y++)
1056 {
1057 const int
1058 id = GetOpenMPThreadId();
1059
1060 const Quantum
1061 *magick_restrict p;
1062
1063 ssize_t
1064 x;
1065
1066 if (status == MagickFalse)
1067 continue;
1068 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1069 if (p == (const Quantum *) NULL)
1070 continue;
1071 for (x=0; x < (ssize_t) image->columns; x++)
1072 {
1073 ssize_t
1074 j;
1075
1076 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1077 {
1078 PixelChannel
1079 channel;
1080
1081 PixelTrait
1082 traits;
1083
1084 channel=GetPixelChannelChannel(image,j);
1085 traits=GetPixelChannelTraits(image,channel);
1086 if ((traits & UpdatePixelTrait) == 0)
1087 continue;
1088 while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
1089 {
1090 QuantumAny
1091 range;
1092
1093 range=GetQuantumRange(current_depth[id]);
1094 if (p[j] == ScaleAnyToQuantum(ScaleQuantumToAny(p[j],range),range))
1095 break;
1096 current_depth[id]++;
1097 }
1098 }
1099 p+=(ptrdiff_t) GetPixelChannels(image);
1100 }
1101 if (current_depth[id] == MAGICKCORE_QUANTUM_DEPTH)
1102 status=MagickFalse;
1103 }
1104 image_view=DestroyCacheView(image_view);
1105 depth=current_depth[0];
1106 for (i=1; i < (ssize_t) number_threads; i++)
1107 if (depth < current_depth[i])
1108 depth=current_depth[i];
1109 current_depth=(size_t *) RelinquishMagickMemory(current_depth);
1110 return(depth);
1111}
1112
1113/*
1114%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1115% %
1116% %
1117% %
1118% 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 %
1119% %
1120% %
1121% %
1122%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1123%
1124% GetImageMinimumBoundingBox() returns the points that form the minimum
1125% bounding box around the image foreground objects with the "Rotating
1126% Calipers" algorithm. The method also returns these properties:
1127% minimum-bounding-box:area, minimum-bounding-box:width,
1128% minimum-bounding-box:height, and minimum-bounding-box:angle.
1129%
1130% The format of the GetImageMinimumBoundingBox method is:
1131%
1132% PointInfo *GetImageMinimumBoundingBox(Image *image,
1133% size_t number_vertices,ExceptionInfo *exception)
1134%
1135% A description of each parameter follows:
1136%
1137% o image: the image.
1138%
1139% o number_vertices: the number of vertices in the bounding box.
1140%
1141% o exception: return any errors or warnings in this structure.
1142%
1143*/
1144
1145typedef struct _CaliperInfo
1146{
1147 double
1148 area,
1149 width,
1150 height,
1151 projection;
1152
1153 ssize_t
1154 p,
1155 q,
1156 v;
1157} CaliperInfo;
1158
1159static inline double getAngle(PointInfo *p,PointInfo *q)
1160{
1161 /*
1162 Get the angle between line (p,q) and horizontal axis, in degrees.
1163 */
1164 return(RadiansToDegrees(atan2(q->y-p->y,q->x-p->x)));
1165}
1166
1167static inline double getDistance(PointInfo *p,PointInfo *q)
1168{
1169 double
1170 distance;
1171
1172 distance=hypot(p->x-q->x,p->y-q->y);
1173 return(distance*distance);
1174}
1175
1176static inline double getProjection(PointInfo *p,PointInfo *q,PointInfo *v)
1177{
1178 double
1179 distance;
1180
1181 /*
1182 Projection of vector (x,y) - p into a line passing through p and q.
1183 */
1184 distance=getDistance(p,q);
1185 if (distance < MagickEpsilon)
1186 return(INFINITY);
1187 return((q->x-p->x)*(v->x-p->x)+(v->y-p->y)*(q->y-p->y))/sqrt(distance);
1188}
1189
1190static inline double getFeretDiameter(PointInfo *p,PointInfo *q,PointInfo *v)
1191{
1192 double
1193 distance;
1194
1195 /*
1196 Distance from a point (x,y) to a line passing through p and q.
1197 */
1198 distance=getDistance(p,q);
1199 if (distance < MagickEpsilon)
1200 return(INFINITY);
1201 return((q->x-p->x)*(v->y-p->y)-(v->x-p->x)*(q->y-p->y))/sqrt(distance);
1202}
1203
1204MagickExport PointInfo *GetImageMinimumBoundingBox(Image *image,
1205 size_t *number_vertices,ExceptionInfo *exception)
1206{
1207 CaliperInfo
1208 caliper_info;
1209
1210 const char
1211 *artifact;
1212
1213 double
1214 angle,
1215 diameter,
1216 distance;
1217
1218 PointInfo
1219 *bounding_box,
1220 *vertices;
1221
1222 size_t
1223 number_hull_vertices;
1224
1225 ssize_t
1226 i;
1227
1228 /*
1229 Generate the minimum bounding box with the "Rotating Calipers" algorithm.
1230 */
1231 assert(image != (Image *) NULL);
1232 assert(image->signature == MagickCoreSignature);
1233 if (IsEventLogging() != MagickFalse)
1234 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1235 *number_vertices=0;
1236 vertices=GetImageConvexHull(image,&number_hull_vertices,exception);
1237 if (vertices == (PointInfo *) NULL)
1238 return((PointInfo *) NULL);
1239 *number_vertices=4;
1240 bounding_box=(PointInfo *) AcquireQuantumMemory(*number_vertices,
1241 sizeof(*bounding_box));
1242 if (bounding_box == (PointInfo *) NULL)
1243 {
1244 vertices=(PointInfo *) RelinquishMagickMemory(vertices);
1245 return((PointInfo *) NULL);
1246 }
1247 caliper_info.area=2.0*image->columns*image->rows;
1248 caliper_info.width=(double) image->columns+image->rows;
1249 caliper_info.height=0.0;
1250 caliper_info.projection=0.0;
1251 caliper_info.p=(-1);
1252 caliper_info.q=(-1);
1253 caliper_info.v=(-1);
1254 for (i=0; i < (ssize_t) number_hull_vertices; i++)
1255 {
1256 double
1257 area = 0.0,
1258 max_projection = 0.0,
1259 min_diameter = -1.0,
1260 min_projection = 0.0;
1261
1262 ssize_t
1263 j,
1264 k;
1265
1266 ssize_t
1267 p = -1,
1268 q = -1,
1269 v = -1;
1270
1271 for (j=0; j < (ssize_t) number_hull_vertices; j++)
1272 {
1273 diameter=fabs(getFeretDiameter(&vertices[i],
1274 &vertices[(i+1) % (ssize_t) number_hull_vertices],&vertices[j]));
1275 if (min_diameter < diameter)
1276 {
1277 min_diameter=diameter;
1278 p=i;
1279 q=(i+1) % (ssize_t) number_hull_vertices;
1280 v=j;
1281 }
1282 }
1283 for (k=0; k < (ssize_t) number_hull_vertices; k++)
1284 {
1285 double
1286 projection;
1287
1288 /*
1289 Rotating calipers.
1290 */
1291 projection=getProjection(&vertices[p],&vertices[q],&vertices[k]);
1292 min_projection=MagickMin(min_projection,projection);
1293 max_projection=MagickMax(max_projection,projection);
1294 }
1295 area=min_diameter*(max_projection-min_projection);
1296 if (caliper_info.area > area)
1297 {
1298 caliper_info.area=area;
1299 caliper_info.width=min_diameter;
1300 caliper_info.height=max_projection-min_projection;
1301 caliper_info.projection=max_projection;
1302 caliper_info.p=p;
1303 caliper_info.q=q;
1304 caliper_info.v=v;
1305 }
1306 }
1307 /*
1308 Initialize minimum bounding box.
1309 */
1310 diameter=getFeretDiameter(&vertices[caliper_info.p],
1311 &vertices[caliper_info.q],&vertices[caliper_info.v]);
1312 angle=atan2(vertices[caliper_info.q].y-vertices[caliper_info.p].y,
1313 vertices[caliper_info.q].x-vertices[caliper_info.p].x);
1314 bounding_box[0].x=vertices[caliper_info.p].x+cos(angle)*
1315 caliper_info.projection;
1316 bounding_box[0].y=vertices[caliper_info.p].y+sin(angle)*
1317 caliper_info.projection;
1318 bounding_box[1].x=floor(bounding_box[0].x+cos(angle+MagickPI/2.0)*diameter+
1319 0.5);
1320 bounding_box[1].y=floor(bounding_box[0].y+sin(angle+MagickPI/2.0)*diameter+
1321 0.5);
1322 bounding_box[2].x=floor(bounding_box[1].x+cos(angle)*(-caliper_info.height)+
1323 0.5);
1324 bounding_box[2].y=floor(bounding_box[1].y+sin(angle)*(-caliper_info.height)+
1325 0.5);
1326 bounding_box[3].x=floor(bounding_box[2].x+cos(angle+MagickPI/2.0)*(-diameter)+
1327 0.5);
1328 bounding_box[3].y=floor(bounding_box[2].y+sin(angle+MagickPI/2.0)*(-diameter)+
1329 0.5);
1330 /*
1331 Export minimum bounding box properties.
1332 */
1333 (void) FormatImageProperty(image,"minimum-bounding-box:area","%.*g",
1334 GetMagickPrecision(),caliper_info.area);
1335 (void) FormatImageProperty(image,"minimum-bounding-box:width","%.*g",
1336 GetMagickPrecision(),caliper_info.width);
1337 (void) FormatImageProperty(image,"minimum-bounding-box:height","%.*g",
1338 GetMagickPrecision(),caliper_info.height);
1339 (void) FormatImageProperty(image,"minimum-bounding-box:_p","%.*g,%.*g",
1340 GetMagickPrecision(),vertices[caliper_info.p].x,
1341 GetMagickPrecision(),vertices[caliper_info.p].y);
1342 (void) FormatImageProperty(image,"minimum-bounding-box:_q","%.*g,%.*g",
1343 GetMagickPrecision(),vertices[caliper_info.q].x,
1344 GetMagickPrecision(),vertices[caliper_info.q].y);
1345 (void) FormatImageProperty(image,"minimum-bounding-box:_v","%.*g,%.*g",
1346 GetMagickPrecision(),vertices[caliper_info.v].x,
1347 GetMagickPrecision(),vertices[caliper_info.v].y);
1348 /*
1349 Find smallest angle to origin.
1350 */
1351 distance=hypot(bounding_box[0].x,bounding_box[0].y);
1352 angle=getAngle(&bounding_box[0],&bounding_box[1]);
1353 for (i=1; i < 4; i++)
1354 {
1355 double d = hypot(bounding_box[i].x,bounding_box[i].y);
1356 if (d < distance)
1357 {
1358 distance=d;
1359 angle=getAngle(&bounding_box[i],&bounding_box[(i+1) % 4]);
1360 }
1361 }
1362 artifact=GetImageArtifact(image,"minimum-bounding-box:orientation");
1363 if (artifact != (const char *) NULL)
1364 {
1365 double
1366 length,
1367 q_length,
1368 p_length;
1369
1370 PointInfo
1371 delta,
1372 point;
1373
1374 /*
1375 Find smallest perpendicular distance from edge to origin.
1376 */
1377 point=bounding_box[0];
1378 for (i=1; i < 4; i++)
1379 {
1380 if (bounding_box[i].x < point.x)
1381 point.x=bounding_box[i].x;
1382 if (bounding_box[i].y < point.y)
1383 point.y=bounding_box[i].y;
1384 }
1385 for (i=0; i < 4; i++)
1386 {
1387 bounding_box[i].x-=point.x;
1388 bounding_box[i].y-=point.y;
1389 }
1390 for (i=0; i < 4; i++)
1391 {
1392 double
1393 d,
1394 intercept,
1395 slope;
1396
1397 delta.x=bounding_box[(i+1) % 4].x-bounding_box[i].x;
1398 delta.y=bounding_box[(i+1) % 4].y-bounding_box[i].y;
1399 slope=delta.y*MagickSafeReciprocal(delta.x);
1400 intercept=bounding_box[(i+1) % 4].y-slope*bounding_box[i].x;
1401 d=fabs((slope*bounding_box[i].x-bounding_box[i].y+intercept)*
1402 MagickSafeReciprocal(sqrt(slope*slope+1.0)));
1403 if ((i == 0) || (d < distance))
1404 {
1405 distance=d;
1406 point=delta;
1407 }
1408 }
1409 angle=RadiansToDegrees(atan(point.y*MagickSafeReciprocal(point.x)));
1410 length=hypot(point.x,point.y);
1411 p_length=fabs((double) MagickMax(caliper_info.width,caliper_info.height)-
1412 length);
1413 q_length=fabs(length-(double) MagickMin(caliper_info.width,
1414 caliper_info.height));
1415 if (LocaleCompare(artifact,"landscape") == 0)
1416 {
1417 if (p_length > q_length)
1418 angle+=(angle < 0.0) ? 90.0 : -90.0;
1419 }
1420 else
1421 if (LocaleCompare(artifact,"portrait") == 0)
1422 {
1423 if (p_length < q_length)
1424 angle+=(angle >= 0.0) ? 90.0 : -90.0;
1425 }
1426 }
1427 (void) FormatImageProperty(image,"minimum-bounding-box:angle","%.*g",
1428 GetMagickPrecision(),angle);
1429 (void) FormatImageProperty(image,"minimum-bounding-box:unrotate","%.*g",
1430 GetMagickPrecision(),-angle);
1431 vertices=(PointInfo *) RelinquishMagickMemory(vertices);
1432 return(bounding_box);
1433}
1434
1435/*
1436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1437% %
1438% %
1439% %
1440% G e t I m a g e Q u a n t u m D e p t h %
1441% %
1442% %
1443% %
1444%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1445%
1446% GetImageQuantumDepth() returns the depth of the image rounded to a legal
1447% quantum depth: 8, 16, or 32.
1448%
1449% The format of the GetImageQuantumDepth method is:
1450%
1451% size_t GetImageQuantumDepth(const Image *image,
1452% const MagickBooleanType constrain)
1453%
1454% A description of each parameter follows:
1455%
1456% o image: the image.
1457%
1458% o constrain: A value other than MagickFalse, constrains the depth to
1459% a maximum of MAGICKCORE_QUANTUM_DEPTH.
1460%
1461*/
1462MagickExport size_t GetImageQuantumDepth(const Image *image,
1463 const MagickBooleanType constrain)
1464{
1465 size_t
1466 depth;
1467
1468 depth=image->depth;
1469 if (depth <= 8)
1470 depth=8;
1471 else
1472 if (depth <= 16)
1473 depth=16;
1474 else
1475 if (depth <= 32)
1476 depth=32;
1477 else
1478 if (depth <= 64)
1479 depth=64;
1480 if (constrain != MagickFalse)
1481 depth=(size_t) MagickMin((double) depth,(double) MAGICKCORE_QUANTUM_DEPTH);
1482 return(depth);
1483}
1484
1485/*
1486%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1487% %
1488% %
1489% %
1490% G e t I m a g e T y p e %
1491% %
1492% %
1493% %
1494%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1495%
1496% GetImageType() returns the type of image:
1497%
1498% Bilevel Grayscale GrayscaleMatte
1499% Palette PaletteMatte TrueColor
1500% TrueColorMatte ColorSeparation ColorSeparationMatte
1501%
1502% The format of the GetImageType method is:
1503%
1504% ImageType GetImageType(const Image *image)
1505%
1506% A description of each parameter follows:
1507%
1508% o image: the image.
1509%
1510*/
1511MagickExport ImageType GetImageType(const Image *image)
1512{
1513 assert(image != (Image *) NULL);
1514 assert(image->signature == MagickCoreSignature);
1515 if (image->colorspace == CMYKColorspace)
1516 {
1517 if ((image->alpha_trait & BlendPixelTrait) == 0)
1518 return(ColorSeparationType);
1519 return(ColorSeparationAlphaType);
1520 }
1521 if (IsImageMonochrome(image) != MagickFalse)
1522 return(BilevelType);
1523 if (IsImageGray(image) != MagickFalse)
1524 {
1525 if (image->alpha_trait != UndefinedPixelTrait)
1526 return(GrayscaleAlphaType);
1527 return(GrayscaleType);
1528 }
1529 if (IsPaletteImage(image) != MagickFalse)
1530 {
1531 if (image->alpha_trait != UndefinedPixelTrait)
1532 return(PaletteAlphaType);
1533 return(PaletteType);
1534 }
1535 if (image->alpha_trait != UndefinedPixelTrait)
1536 return(TrueColorAlphaType);
1537 return(TrueColorType);
1538}
1539
1540/*
1541%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1542% %
1543% %
1544% %
1545% I d e n t i f y I m a g e G r a y %
1546% %
1547% %
1548% %
1549%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1550%
1551% IdentifyImageGray() returns grayscale if all the pixels in the image have
1552% the same red, green, and blue intensities, and bi-level if the intensity is
1553% either 0 or QuantumRange. Otherwise undefined is returned.
1554%
1555% The format of the IdentifyImageGray method is:
1556%
1557% ImageType IdentifyImageGray(const Image *image,ExceptionInfo *exception)
1558%
1559% A description of each parameter follows:
1560%
1561% o image: the image.
1562%
1563% o exception: return any errors or warnings in this structure.
1564%
1565*/
1566MagickExport ImageType IdentifyImageGray(const Image *image,
1567 ExceptionInfo *exception)
1568{
1569 CacheView
1570 *image_view;
1571
1572 ImageType
1573 type = BilevelType;
1574
1575 MagickBooleanType
1576 status = MagickTrue;
1577
1578 ssize_t
1579 y;
1580
1581 assert(image != (Image *) NULL);
1582 assert(image->signature == MagickCoreSignature);
1583 if (IsEventLogging() != MagickFalse)
1584 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1585 if (IsImageGray(image) != MagickFalse)
1586 return(image->type);
1587 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1588 return(UndefinedType);
1589 image_view=AcquireVirtualCacheView(image,exception);
1590#if defined(MAGICKCORE_OPENMP_SUPPORT)
1591 #pragma omp parallel for schedule(static) shared(status,type) \
1592 magick_number_threads(image,image,image->rows,2)
1593#endif
1594 for (y=0; y < (ssize_t) image->rows; y++)
1595 {
1596 const Quantum
1597 *p;
1598
1599 ssize_t
1600 x;
1601
1602 if (status == MagickFalse)
1603 continue;
1604 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1605 if (p == (const Quantum *) NULL)
1606 {
1607 status=MagickFalse;
1608 continue;
1609 }
1610 for (x=0; x < (ssize_t) image->columns; x++)
1611 {
1612 if (IsPixelGray(image,p) == MagickFalse)
1613 {
1614 status=MagickFalse;
1615 break;
1616 }
1617 if ((type == BilevelType) && (IsPixelMonochrome(image,p) == MagickFalse))
1618 type=GrayscaleType;
1619 p+=(ptrdiff_t) GetPixelChannels(image);
1620 }
1621 }
1622 image_view=DestroyCacheView(image_view);
1623 if ((type == GrayscaleType) && (image->alpha_trait != UndefinedPixelTrait))
1624 type=GrayscaleAlphaType;
1625 if (status == MagickFalse)
1626 return(UndefinedType);
1627 return(type);
1628}
1629
1630/*
1631%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1632% %
1633% %
1634% %
1635% I d e n t i f y I m a g e M o n o c h r o m e %
1636% %
1637% %
1638% %
1639%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1640%
1641% IdentifyImageMonochrome() returns MagickTrue if all the pixels in the image
1642% have the same red, green, and blue intensities and the intensity is either
1643% 0 or QuantumRange.
1644%
1645% The format of the IdentifyImageMonochrome method is:
1646%
1647% MagickBooleanType IdentifyImageMonochrome(const Image *image,
1648% ExceptionInfo *exception)
1649%
1650% A description of each parameter follows:
1651%
1652% o image: the image.
1653%
1654% o exception: return any errors or warnings in this structure.
1655%
1656*/
1657MagickExport MagickBooleanType IdentifyImageMonochrome(const Image *image,
1658 ExceptionInfo *exception)
1659{
1660 CacheView
1661 *image_view;
1662
1663 ImageType
1664 type = BilevelType;
1665
1666 ssize_t
1667 y;
1668
1669 assert(image != (Image *) NULL);
1670 assert(image->signature == MagickCoreSignature);
1671 if (IsEventLogging() != MagickFalse)
1672 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1673 if (image->type == BilevelType)
1674 return(MagickTrue);
1675 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1676 return(MagickFalse);
1677 image_view=AcquireVirtualCacheView(image,exception);
1678#if defined(MAGICKCORE_OPENMP_SUPPORT)
1679 #pragma omp parallel for schedule(static) shared(type) \
1680 magick_number_threads(image,image,image->rows,2)
1681#endif
1682 for (y=0; y < (ssize_t) image->rows; y++)
1683 {
1684 const Quantum
1685 *p;
1686
1687 ssize_t
1688 x;
1689
1690 if (type == UndefinedType)
1691 continue;
1692 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1693 if (p == (const Quantum *) NULL)
1694 {
1695 type=UndefinedType;
1696 continue;
1697 }
1698 for (x=0; x < (ssize_t) image->columns; x++)
1699 {
1700 if (IsPixelMonochrome(image,p) == MagickFalse)
1701 {
1702 type=UndefinedType;
1703 break;
1704 }
1705 p+=(ptrdiff_t) GetPixelChannels(image);
1706 }
1707 }
1708 image_view=DestroyCacheView(image_view);
1709 return(type == BilevelType ? MagickTrue : MagickFalse);
1710}
1711
1712/*
1713%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1714% %
1715% %
1716% %
1717% I d e n t i f y I m a g e T y p e %
1718% %
1719% %
1720% %
1721%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1722%
1723% IdentifyImageType() returns the potential type of image:
1724%
1725% Bilevel Grayscale GrayscaleMatte
1726% Palette PaletteMatte TrueColor
1727% TrueColorMatte ColorSeparation ColorSeparationMatte
1728%
1729% To ensure the image type matches its potential, use SetImageType():
1730%
1731% (void) SetImageType(image,IdentifyImageType(image,exception),exception);
1732%
1733% The format of the IdentifyImageType method is:
1734%
1735% ImageType IdentifyImageType(const Image *image,ExceptionInfo *exception)
1736%
1737% A description of each parameter follows:
1738%
1739% o image: the image.
1740%
1741% o exception: return any errors or warnings in this structure.
1742%
1743*/
1744MagickExport ImageType IdentifyImageType(const Image *image,
1745 ExceptionInfo *exception)
1746{
1747 ImageType
1748 type;
1749
1750 assert(image != (Image *) NULL);
1751 assert(image->signature == MagickCoreSignature);
1752 if (IsEventLogging() != MagickFalse)
1753 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1754 if (image->colorspace == CMYKColorspace)
1755 {
1756 if ((image->alpha_trait & BlendPixelTrait) == 0)
1757 return(ColorSeparationType);
1758 return(ColorSeparationAlphaType);
1759 }
1760 type=IdentifyImageGray(image,exception);
1761 if (IsGrayImageType(type))
1762 return(type);
1763 if (IdentifyPaletteImage(image,exception) != MagickFalse)
1764 {
1765 if (image->alpha_trait != UndefinedPixelTrait)
1766 return(PaletteAlphaType);
1767 return(PaletteType);
1768 }
1769 if (image->alpha_trait != UndefinedPixelTrait)
1770 return(TrueColorAlphaType);
1771 return(TrueColorType);
1772}
1773
1774/*
1775%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1776% %
1777% %
1778% %
1779% I s I m a g e G r a y %
1780% %
1781% %
1782% %
1783%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1784%
1785% IsImageGray() returns MagickTrue if the type of the image is grayscale or
1786% bi-level.
1787%
1788% The format of the IsImageGray method is:
1789%
1790% MagickBooleanType IsImageGray(const Image *image)
1791%
1792% A description of each parameter follows:
1793%
1794% o image: the image.
1795%
1796*/
1797MagickExport MagickBooleanType IsImageGray(const Image *image)
1798{
1799 assert(image != (Image *) NULL);
1800 assert(image->signature == MagickCoreSignature);
1801 if (IsGrayImageType(image->type) != MagickFalse)
1802 return(MagickTrue);
1803 return(MagickFalse);
1804}
1805
1806/*
1807%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1808% %
1809% %
1810% %
1811% I s I m a g e M o n o c h r o m e %
1812% %
1813% %
1814% %
1815%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1816%
1817% IsImageMonochrome() returns MagickTrue if type of the image is bi-level.
1818%
1819% The format of the IsImageMonochrome method is:
1820%
1821% MagickBooleanType IsImageMonochrome(const Image *image)
1822%
1823% A description of each parameter follows:
1824%
1825% o image: the image.
1826%
1827*/
1828MagickExport MagickBooleanType IsImageMonochrome(const Image *image)
1829{
1830 assert(image != (Image *) NULL);
1831 assert(image->signature == MagickCoreSignature);
1832 if (image->type == BilevelType)
1833 return(MagickTrue);
1834 return(MagickFalse);
1835}
1836
1837/*
1838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1839% %
1840% %
1841% %
1842% I s I m a g e O p a q u e %
1843% %
1844% %
1845% %
1846%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1847%
1848% IsImageOpaque() returns MagickTrue if none of the pixels in the image have
1849% an alpha value other than OpaqueAlpha (QuantumRange).
1850%
1851% Will return true immediately is alpha channel is not available.
1852%
1853% The format of the IsImageOpaque method is:
1854%
1855% MagickBooleanType IsImageOpaque(const Image *image,
1856% ExceptionInfo *exception)
1857%
1858% A description of each parameter follows:
1859%
1860% o image: the image.
1861%
1862% o exception: return any errors or warnings in this structure.
1863%
1864*/
1865MagickExport MagickBooleanType IsImageOpaque(const Image *image,
1866 ExceptionInfo *exception)
1867{
1868 CacheView
1869 *image_view;
1870
1871 MagickBooleanType
1872 opaque = MagickTrue;
1873
1874 ssize_t
1875 y;
1876
1877 /*
1878 Determine if image is opaque.
1879 */
1880 assert(image != (Image *) NULL);
1881 assert(image->signature == MagickCoreSignature);
1882 if (IsEventLogging() != MagickFalse)
1883 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1884 if ((image->alpha_trait & BlendPixelTrait) == 0)
1885 return(MagickTrue);
1886 image_view=AcquireVirtualCacheView(image,exception);
1887#if defined(MAGICKCORE_OPENMP_SUPPORT)
1888 #pragma omp parallel for schedule(static) shared(opaque) \
1889 magick_number_threads(image,image,image->rows,2)
1890#endif
1891 for (y=0; y < (ssize_t) image->rows; y++)
1892 {
1893 const Quantum
1894 *p;
1895
1896 ssize_t
1897 x;
1898
1899 if (opaque == MagickFalse)
1900 continue;
1901 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1902 if (p == (const Quantum *) NULL)
1903 {
1904 opaque=MagickFalse;
1905 continue;
1906 }
1907 for (x=0; x < (ssize_t) image->columns; x++)
1908 {
1909 if (GetPixelAlpha(image,p) != OpaqueAlpha)
1910 {
1911 opaque=MagickFalse;
1912 break;
1913 }
1914 p+=(ptrdiff_t) GetPixelChannels(image);
1915 }
1916 }
1917 image_view=DestroyCacheView(image_view);
1918 return(opaque);
1919}
1920
1921/*
1922%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1923% %
1924% %
1925% %
1926% S e t I m a g e D e p t h %
1927% %
1928% %
1929% %
1930%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1931%
1932% SetImageDepth() sets the depth of the image.
1933%
1934% The format of the SetImageDepth method is:
1935%
1936% MagickBooleanType SetImageDepth(Image *image,const size_t depth,
1937% ExceptionInfo *exception)
1938%
1939% A description of each parameter follows:
1940%
1941% o image: the image.
1942%
1943% o channel: the channel.
1944%
1945% o depth: the image depth.
1946%
1947% o exception: return any errors or warnings in this structure.
1948%
1949*/
1950
1951static MagickBooleanType FloydSteinbergImageDepth(Image *image,
1952 const size_t depth,ExceptionInfo *exception)
1953{
1954 CacheView
1955 *image_view;
1956
1957 double
1958 *distortion;
1959
1960 MagickBooleanType
1961 status;
1962
1963 QuantumAny
1964 range;
1965
1966 size_t
1967 channels;
1968
1969 ssize_t
1970 y;
1971
1972 /*
1973 Dither pixels with Floyd Steinberg algorithm.
1974 */
1975 status=SetImageStorageClass(image,DirectClass,exception);
1976 if (status == MagickFalse)
1977 return(MagickFalse);
1978 channels=GetPixelChannels(image);
1979 distortion=(double *) AcquireQuantumMemory(image->columns,3*channels*
1980 sizeof(*distortion));
1981 if (distortion == (double *) NULL)
1982 return(MagickFalse);
1983 (void) memset(distortion,0,3*image->columns*channels*sizeof(*distortion));
1984 range=GetQuantumRange(depth);
1985 image_view=AcquireAuthenticCacheView(image,exception);
1986 for (y=0; y < (ssize_t) image->rows; y++)
1987 {
1988 Quantum
1989 *magick_restrict q;
1990
1991 ssize_t
1992 u,
1993 v,
1994 x;
1995
1996 if (status == MagickFalse)
1997 continue;
1998 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1999 if (q == (Quantum *) NULL)
2000 {
2001 status=MagickFalse;
2002 continue;
2003 }
2004 /*
2005 Reset pixel distortion for current row.
2006 */
2007 u=(y % 3)*(ssize_t) (image->columns*channels);
2008 (void) memset(distortion+u,0,image->columns*channels*sizeof(*distortion));
2009 v=((y+1) % 3)*(ssize_t) (image->columns*channels);
2010 for (x=0; x < (ssize_t) image->columns; x++)
2011 {
2012 ssize_t
2013 i;
2014
2015 for (i=0; i < (ssize_t) channels; i++)
2016 {
2017 double
2018 error,
2019 pixel;
2020
2021 PixelChannel
2022 channel;
2023
2024 PixelTrait
2025 traits;
2026
2027 /*
2028 Add distortion to current pixel then distribute new distortion.
2029 */
2030 channel=GetPixelChannelChannel(image,i);
2031 traits=GetPixelChannelTraits(image,channel);
2032 if ((traits & UpdatePixelTrait) == 0)
2033 {
2034 u++;
2035 v++;
2036 continue;
2037 }
2038 pixel=(double) q[i]+distortion[u];
2039 q[i]=ScaleAnyToQuantum(ScaleQuantumToAny(ClampPixel((MagickRealType)
2040 pixel),range),range);
2041 /*
2042 Distribute distortion for right.
2043 */
2044 error=pixel-(double) q[i];
2045 if ((x+1) < (ssize_t) image->columns)
2046 distortion[u+(ssize_t) channels]+=7.0*error/16.0;
2047 if ((y+1) < (ssize_t) image->rows)
2048 {
2049 /*
2050 Distribute distortion for bottom left, bottom, and bottom right.
2051 */
2052 if (x > 0)
2053 distortion[v-(ssize_t) channels]+=3.0*error/16.0;
2054 distortion[v]+=5.0*error/16.0;
2055 if ((x+1) < (ssize_t) image->columns)
2056 distortion[v+(ssize_t) channels]+=1.0*error/16.0;
2057 }
2058 u++;
2059 v++;
2060 }
2061 q+=(ptrdiff_t) GetPixelChannels(image);
2062 }
2063 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2064 {
2065 status=MagickFalse;
2066 continue;
2067 }
2068 }
2069 image_view=DestroyCacheView(image_view);
2070 distortion=(double *) RelinquishMagickMemory(distortion);
2071 if (status != MagickFalse)
2072 image->depth=depth;
2073 return(status);
2074}
2075
2076MagickExport MagickBooleanType SetImageDepth(Image *image,
2077 const size_t depth,ExceptionInfo *exception)
2078{
2079 CacheView
2080 *image_view;
2081
2082 const char
2083 *artifact;
2084
2085 MagickBooleanType
2086 status;
2087
2088 QuantumAny
2089 range;
2090
2091 ssize_t
2092 y;
2093
2094 assert(image != (Image *) NULL);
2095 if (IsEventLogging() != MagickFalse)
2096 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2097 assert(image->signature == MagickCoreSignature);
2098 if (depth >= MAGICKCORE_QUANTUM_DEPTH)
2099 {
2100 image->depth=depth;
2101 return(MagickTrue);
2102 }
2103 artifact=GetImageArtifact(image,"dither");
2104 if ((artifact != (const char *) NULL) &&
2105 (LocaleCompare(artifact,"FloydSteinberg") == 0))
2106 return(FloydSteinbergImageDepth(image,depth,exception));
2107 range=GetQuantumRange(depth);
2108 if (image->storage_class == PseudoClass)
2109 {
2110 ssize_t
2111 i;
2112
2113#if defined(MAGICKCORE_OPENMP_SUPPORT)
2114 #pragma omp parallel for schedule(static) shared(status) \
2115 magick_number_threads(image,image,image->colors,1)
2116#endif
2117 for (i=0; i < (ssize_t) image->colors; i++)
2118 {
2119 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2120 image->colormap[i].red=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2121 ClampPixel(image->colormap[i].red),range),range);
2122 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2123 image->colormap[i].green=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2124 ClampPixel(image->colormap[i].green),range),range);
2125 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2126 image->colormap[i].blue=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2127 ClampPixel(image->colormap[i].blue),range),range);
2128 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2129 image->colormap[i].alpha=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2130 ClampPixel(image->colormap[i].alpha),range),range);
2131 }
2132 }
2133 status=MagickTrue;
2134 image_view=AcquireAuthenticCacheView(image,exception);
2135#if !defined(MAGICKCORE_HDRI_SUPPORT)
2136 DisableMSCWarning(4127)
2137 if ((1UL*QuantumRange) <= MaxMap)
2138 RestoreMSCWarning
2139 {
2140 Quantum
2141 *depth_map;
2142
2143 ssize_t
2144 i;
2145
2146 /*
2147 Scale pixels to desired (optimized with depth map).
2148 */
2149 depth_map=(Quantum *) AcquireQuantumMemory(MaxMap+1,sizeof(*depth_map));
2150 if (depth_map == (Quantum *) NULL)
2151 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2152 for (i=0; i <= (ssize_t) MaxMap; i++)
2153 depth_map[i]=ScaleAnyToQuantum(ScaleQuantumToAny((Quantum) i,range),
2154 range);
2155#if defined(MAGICKCORE_OPENMP_SUPPORT)
2156 #pragma omp parallel for schedule(static) shared(status) \
2157 magick_number_threads(image,image,image->rows,2)
2158#endif
2159 for (y=0; y < (ssize_t) image->rows; y++)
2160 {
2161 ssize_t
2162 x;
2163
2164 Quantum
2165 *magick_restrict q;
2166
2167 if (status == MagickFalse)
2168 continue;
2169 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
2170 exception);
2171 if (q == (Quantum *) NULL)
2172 {
2173 status=MagickFalse;
2174 continue;
2175 }
2176 for (x=0; x < (ssize_t) image->columns; x++)
2177 {
2178 ssize_t
2179 j;
2180
2181 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2182 {
2183 PixelChannel
2184 channel;
2185
2186 PixelTrait
2187 traits;
2188
2189 channel=GetPixelChannelChannel(image,j);
2190 traits=GetPixelChannelTraits(image,channel);
2191 if ((traits & UpdatePixelTrait) == 0)
2192 continue;
2193 q[j]=depth_map[ScaleQuantumToMap(q[j])];
2194 }
2195 q+=(ptrdiff_t) GetPixelChannels(image);
2196 }
2197 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2198 {
2199 status=MagickFalse;
2200 continue;
2201 }
2202 }
2203 image_view=DestroyCacheView(image_view);
2204 depth_map=(Quantum *) RelinquishMagickMemory(depth_map);
2205 if (status != MagickFalse)
2206 image->depth=depth;
2207 return(status);
2208 }
2209#endif
2210 /*
2211 Scale pixels to desired depth.
2212 */
2213#if defined(MAGICKCORE_OPENMP_SUPPORT)
2214 #pragma omp parallel for schedule(static) shared(status) \
2215 magick_number_threads(image,image,image->rows,2)
2216#endif
2217 for (y=0; y < (ssize_t) image->rows; y++)
2218 {
2219 ssize_t
2220 x;
2221
2222 Quantum
2223 *magick_restrict q;
2224
2225 if (status == MagickFalse)
2226 continue;
2227 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2228 if (q == (Quantum *) NULL)
2229 {
2230 status=MagickFalse;
2231 continue;
2232 }
2233 for (x=0; x < (ssize_t) image->columns; x++)
2234 {
2235 ssize_t
2236 i;
2237
2238 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2239 {
2240 PixelChannel
2241 channel;
2242
2243 PixelTrait
2244 traits;
2245
2246 channel=GetPixelChannelChannel(image,i);
2247 traits=GetPixelChannelTraits(image,channel);
2248 if ((traits & UpdatePixelTrait) == 0)
2249 continue;
2250 q[i]=ScaleAnyToQuantum(ScaleQuantumToAny(ClampPixel((MagickRealType)
2251 q[i]),range),range);
2252 }
2253 q+=(ptrdiff_t) GetPixelChannels(image);
2254 }
2255 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2256 {
2257 status=MagickFalse;
2258 continue;
2259 }
2260 }
2261 image_view=DestroyCacheView(image_view);
2262 if (status != MagickFalse)
2263 image->depth=depth;
2264 return(status);
2265}
2266
2267/*
2268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2269% %
2270% %
2271% %
2272% S e t I m a g e T y p e %
2273% %
2274% %
2275% %
2276%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2277%
2278% SetImageType() sets the type of image. Choose from these types:
2279%
2280% Bilevel Grayscale GrayscaleMatte
2281% Palette PaletteMatte TrueColor
2282% TrueColorMatte ColorSeparation ColorSeparationMatte
2283% OptimizeType
2284%
2285% The format of the SetImageType method is:
2286%
2287% MagickBooleanType SetImageType(Image *image,const ImageType type,
2288% ExceptionInfo *exception)
2289%
2290% A description of each parameter follows:
2291%
2292% o image: the image.
2293%
2294% o type: Image type.
2295%
2296% o exception: return any errors or warnings in this structure.
2297%
2298*/
2299MagickExport MagickBooleanType SetImageType(Image *image,const ImageType type,
2300 ExceptionInfo *exception)
2301{
2302 const char
2303 *artifact;
2304
2305 ImageInfo
2306 *image_info;
2307
2308 MagickBooleanType
2309 status;
2310
2311 QuantizeInfo
2312 *quantize_info;
2313
2314 assert(image != (Image *) NULL);
2315 if (IsEventLogging() != MagickFalse)
2316 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2317 assert(image->signature == MagickCoreSignature);
2318 status=MagickTrue;
2319 image_info=AcquireImageInfo();
2320 image_info->dither=image->dither;
2321 artifact=GetImageArtifact(image,"dither");
2322 if (artifact != (const char *) NULL)
2323 (void) SetImageOption(image_info,"dither",artifact);
2324 switch (type)
2325 {
2326 case BilevelType:
2327 {
2328 if (IsGrayImageType(image->type) == MagickFalse)
2329 status=TransformImageColorspace(image,GRAYColorspace,exception);
2330 (void) NormalizeImage(image,exception);
2331 (void) BilevelImage(image,(double) QuantumRange/2.0,exception);
2332 quantize_info=AcquireQuantizeInfo(image_info);
2333 quantize_info->number_colors=2;
2334 quantize_info->colorspace=GRAYColorspace;
2335 status=QuantizeImage(quantize_info,image,exception);
2336 quantize_info=DestroyQuantizeInfo(quantize_info);
2337 image->alpha_trait=UndefinedPixelTrait;
2338 break;
2339 }
2340 case GrayscaleType:
2341 {
2342 if (IsGrayImageType(image->type) == MagickFalse)
2343 status=TransformImageColorspace(image,GRAYColorspace,exception);
2344 image->alpha_trait=UndefinedPixelTrait;
2345 break;
2346 }
2347 case GrayscaleAlphaType:
2348 {
2349 if (IsGrayImageType(image->type) == MagickFalse)
2350 status=TransformImageColorspace(image,GRAYColorspace,exception);
2351 if ((image->alpha_trait & BlendPixelTrait) == 0)
2352 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2353 break;
2354 }
2355 case PaletteType:
2356 {
2357 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2358 status=TransformImageColorspace(image,sRGBColorspace,exception);
2359 if ((image->storage_class == DirectClass) || (image->colors > 256))
2360 {
2361 quantize_info=AcquireQuantizeInfo(image_info);
2362 quantize_info->number_colors=256;
2363 status=QuantizeImage(quantize_info,image,exception);
2364 quantize_info=DestroyQuantizeInfo(quantize_info);
2365 }
2366 image->alpha_trait=UndefinedPixelTrait;
2367 break;
2368 }
2369 case PaletteBilevelAlphaType:
2370 {
2371 ChannelType
2372 channel_mask;
2373
2374 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2375 status=TransformImageColorspace(image,sRGBColorspace,exception);
2376 if ((image->alpha_trait & BlendPixelTrait) == 0)
2377 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2378 channel_mask=SetImageChannelMask(image,AlphaChannel);
2379 (void) BilevelImage(image,(double) QuantumRange/2.0,exception);
2380 (void) SetImageChannelMask(image,channel_mask);
2381 quantize_info=AcquireQuantizeInfo(image_info);
2382 status=QuantizeImage(quantize_info,image,exception);
2383 quantize_info=DestroyQuantizeInfo(quantize_info);
2384 break;
2385 }
2386 case PaletteAlphaType:
2387 {
2388 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2389 status=TransformImageColorspace(image,sRGBColorspace,exception);
2390 if ((image->alpha_trait & BlendPixelTrait) == 0)
2391 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2392 quantize_info=AcquireQuantizeInfo(image_info);
2393 status=QuantizeImage(quantize_info,image,exception);
2394 quantize_info=DestroyQuantizeInfo(quantize_info);
2395 break;
2396 }
2397 case TrueColorType:
2398 {
2399 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2400 status=TransformImageColorspace(image,sRGBColorspace,exception);
2401 if (image->storage_class != DirectClass)
2402 status=SetImageStorageClass(image,DirectClass,exception);
2403 image->alpha_trait=UndefinedPixelTrait;
2404 break;
2405 }
2406 case TrueColorAlphaType:
2407 {
2408 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2409 status=TransformImageColorspace(image,sRGBColorspace,exception);
2410 if (image->storage_class != DirectClass)
2411 status=SetImageStorageClass(image,DirectClass,exception);
2412 if ((image->alpha_trait & BlendPixelTrait) == 0)
2413 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2414 break;
2415 }
2416 case ColorSeparationType:
2417 {
2418 if (image->colorspace != CMYKColorspace)
2419 status=TransformImageColorspace(image,CMYKColorspace,exception);
2420 if (image->storage_class != DirectClass)
2421 status=SetImageStorageClass(image,DirectClass,exception);
2422 image->alpha_trait=UndefinedPixelTrait;
2423 break;
2424 }
2425 case ColorSeparationAlphaType:
2426 {
2427 if (image->colorspace != CMYKColorspace)
2428 status=TransformImageColorspace(image,CMYKColorspace,exception);
2429 if (image->storage_class != DirectClass)
2430 status=SetImageStorageClass(image,DirectClass,exception);
2431 if ((image->alpha_trait & BlendPixelTrait) == 0)
2432 status=SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2433 break;
2434 }
2435 case OptimizeType:
2436 case UndefinedType:
2437 break;
2438 }
2439 image_info=DestroyImageInfo(image_info);
2440 if (status == MagickFalse)
2441 return(status);
2442 image->type=type;
2443 return(MagickTrue);
2444}