Coverage Report

Created: 2026-03-31 06:56

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