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