Coverage Report

Created: 2026-06-07 07:20

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
588
{
394
588
  CacheView
395
588
    *image_view;
396
397
588
  const char
398
588
    *artifact;
399
400
588
  const Quantum
401
588
    *p;
402
403
588
  MagickBooleanType
404
588
    status;
405
406
588
  PixelInfo
407
588
    target[4],
408
588
    zero;
409
410
588
  RectangleInfo
411
588
    bounds;
412
413
588
  ssize_t
414
588
    y;
415
416
588
  assert(image != (Image *) NULL);
417
588
  assert(image->signature == MagickCoreSignature);
418
588
  if (IsEventLogging() != MagickFalse)
419
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
420
588
  artifact=GetImageArtifact(image,"trim:percent-background");
421
588
  if (artifact != (const char *) NULL)
422
0
    return(GetEdgeBoundingBox(image,exception));
423
588
  artifact=GetImageArtifact(image,"trim:edges");
424
588
  if (artifact == (const char *) NULL)
425
588
    {
426
588
      bounds.width=(size_t) (image->columns == 1 ? 1 : 0);
427
588
      bounds.height=(size_t) (image->rows == 1 ? 1 : 0);
428
588
      bounds.x=(ssize_t) image->columns;
429
588
      bounds.y=(ssize_t) image->rows;
430
588
    }
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
588
  GetPixelInfo(image,&target[0]);
458
588
  image_view=AcquireVirtualCacheView(image,exception);
459
588
  p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception);
460
588
  if (p == (const Quantum *) NULL)
461
349
    {
462
349
      image_view=DestroyCacheView(image_view);
463
349
      return(bounds);
464
349
    }
465
239
  GetPixelInfoPixel(image,p,&target[0]);
466
239
  GetPixelInfo(image,&target[1]);
467
239
  p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1,
468
239
    exception);
469
239
  if (p != (const Quantum *) NULL)
470
239
    GetPixelInfoPixel(image,p,&target[1]);
471
239
  GetPixelInfo(image,&target[2]);
472
239
  p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1,
473
239
    exception);
474
239
  if (p != (const Quantum *) NULL)
475
239
    GetPixelInfoPixel(image,p,&target[2]);
476
239
  GetPixelInfo(image,&target[3]);
477
239
  p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,(ssize_t)
478
239
    image->rows-1,1,1,exception);
479
239
  if (p != (const Quantum *) NULL)
480
239
    GetPixelInfoPixel(image,p,&target[3]);
481
239
  status=MagickTrue;
482
239
  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
15.7k
  for (y=0; y < (ssize_t) image->rows; y++)
488
15.4k
  {
489
15.4k
    const Quantum
490
15.4k
      *magick_restrict q;
491
492
15.4k
    PixelInfo
493
15.4k
      pixel;
494
495
15.4k
    RectangleInfo
496
15.4k
      bounding_box;
497
498
15.4k
    ssize_t
499
15.4k
      x;
500
501
15.4k
    if (status == MagickFalse)
502
0
      continue;
503
#if defined(MAGICKCORE_OPENMP_SUPPORT)
504
#  pragma omp critical (MagickCore_GetImageBoundingBox)
505
#endif
506
15.4k
    bounding_box=bounds;
507
15.4k
    q=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
508
15.4k
    if (q == (const Quantum *) NULL)
509
0
      {
510
0
        status=MagickFalse;
511
0
        continue;
512
0
      }
513
15.4k
    pixel=zero;
514
13.2M
    for (x=0; x < (ssize_t) image->columns; x++)
515
13.2M
    {
516
13.2M
      GetPixelInfoPixel(image,q,&pixel);
517
13.2M
      if ((x < bounding_box.x) &&
518
13.2M
          (IsFuzzyEquivalencePixelInfo(&pixel,&target[0]) == MagickFalse))
519
0
        bounding_box.x=x;
520
13.2M
      if ((x > (ssize_t) bounding_box.width) &&
521
13.2M
          (IsFuzzyEquivalencePixelInfo(&pixel,&target[1]) == MagickFalse))
522
0
        bounding_box.width=(size_t) x;
523
13.2M
      if ((y < bounding_box.y) &&
524
13.2M
          (IsFuzzyEquivalencePixelInfo(&pixel,&target[0]) == MagickFalse))
525
0
        bounding_box.y=y;
526
13.2M
      if ((y > (ssize_t) bounding_box.height) &&
527
13.2M
          (IsFuzzyEquivalencePixelInfo(&pixel,&target[2]) == MagickFalse))
528
0
        bounding_box.height=(size_t) y;
529
13.2M
      if ((x < (ssize_t) bounding_box.width) &&
530
4.84k
          (y > (ssize_t) bounding_box.height) &&
531
4.69k
          (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
13.2M
      q+=(ptrdiff_t) GetPixelChannels(image);
537
13.2M
    }
538
#if defined(MAGICKCORE_OPENMP_SUPPORT)
539
#  pragma omp critical (MagickCore_GetImageBoundingBox)
540
#endif
541
15.4k
    {
542
15.4k
      if (bounding_box.x < bounds.x)
543
0
        bounds.x=bounding_box.x;
544
15.4k
      if (bounding_box.y < bounds.y)
545
0
        bounds.y=bounding_box.y;
546
15.4k
      if (bounding_box.width > bounds.width)
547
0
        bounds.width=bounding_box.width;
548
15.4k
      if (bounding_box.height > bounds.height)
549
0
        bounds.height=bounding_box.height;
550
15.4k
    }
551
15.4k
  }
552
239
  image_view=DestroyCacheView(image_view);
553
239
  if ((bounds.width == 0) || (bounds.height == 0))
554
141
    (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
555
141
      "GeometryDoesNotContainImage","`%s'",image->filename);
556
98
  else
557
98
    {
558
98
      bounds.width-=(size_t) (bounds.x-1);
559
98
      bounds.height-=(size_t) (bounds.y-1);
560
98
    }
561
239
  return(bounds);
562
588
}
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
121
{
603
121
  const char
604
121
    *artifact;
605
606
121
  double
607
121
    census[4],
608
121
    edge_census;
609
610
121
  PixelInfo
611
121
    background[4],
612
121
    edge_background;
613
614
121
  ssize_t
615
121
    i;
616
617
  /*
618
    Most dominant color of edges/corners is the background color of the image.
619
  */
620
121
  memset(&edge_background,0,sizeof(edge_background));
621
121
  artifact=GetImageArtifact(image,"convex-hull:background-color");
622
121
  if (artifact == (const char *) NULL)
623
121
    artifact=GetImageArtifact(image,"background");
624
#if defined(MAGICKCORE_OPENMP_SUPPORT)
625
  #pragma omp parallel for schedule(static)
626
#endif
627
605
  for (i=0; i < 4; i++)
628
484
  {
629
484
    CacheView
630
484
      *edge_view;
631
632
484
    GravityType
633
484
      gravity;
634
635
484
    Image
636
484
      *edge_image;
637
638
484
    PixelInfo
639
484
      pixel;
640
641
484
    RectangleInfo
642
484
      edge_geometry;
643
644
484
    const Quantum
645
484
      *p;
646
647
484
    ssize_t
648
484
      y;
649
650
484
    census[i]=0.0;
651
484
    (void) memset(&edge_geometry,0,sizeof(edge_geometry));
652
484
    switch (i)
653
484
    {
654
121
      case 0:
655
121
      default:
656
121
      {
657
121
        p=GetCacheViewVirtualPixels(image_view,0,(ssize_t) image->rows-1,1,1,
658
121
          exception);
659
121
        gravity=WestGravity;
660
121
        edge_geometry.width=1;
661
121
        edge_geometry.height=0;
662
121
        break;
663
121
      }
664
121
      case 1:
665
121
      {
666
121
        p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,0,1,1,
667
121
          exception);
668
121
        gravity=EastGravity;
669
121
        edge_geometry.width=1;
670
121
        edge_geometry.height=0;
671
121
        break;
672
121
      }
673
121
      case 2:
674
121
      {
675
121
        p=GetCacheViewVirtualPixels(image_view,0,0,1,1,exception);
676
121
        gravity=NorthGravity;
677
121
        edge_geometry.width=0;
678
121
        edge_geometry.height=1;
679
121
        break;
680
121
      }
681
121
      case 3:
682
121
      {
683
121
        p=GetCacheViewVirtualPixels(image_view,(ssize_t) image->columns-1,
684
121
          (ssize_t) image->rows-1,1,1,exception);
685
121
        gravity=SouthGravity;
686
121
        edge_geometry.width=0;
687
121
        edge_geometry.height=1;
688
121
        break;
689
121
      }
690
484
    }
691
484
    GetPixelInfoPixel(image,p,background+i);
692
484
    if (artifact != (const char *) NULL)
693
0
      (void) QueryColorCompliance(artifact,AllCompliance,background+i,
694
0
        exception);
695
484
    GravityAdjustGeometry(image->columns,image->rows,gravity,&edge_geometry);
696
484
    edge_image=CropImage(image,&edge_geometry,exception);
697
484
    if (edge_image == (Image *) NULL)
698
0
      continue;
699
484
    edge_view=AcquireVirtualCacheView(edge_image,exception);
700
85.9k
    for (y=0; y < (ssize_t) edge_image->rows; y++)
701
85.4k
    {
702
85.4k
      ssize_t
703
85.4k
        x;
704
705
85.4k
      p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,
706
85.4k
        exception);
707
85.4k
      if (p == (const Quantum *) NULL)
708
0
        break;
709
257k
      for (x=0; x < (ssize_t) edge_image->columns; x++)
710
172k
      {
711
172k
        GetPixelInfoPixel(edge_image,p,&pixel);
712
172k
        if (IsFuzzyEquivalencePixelInfo(&pixel,background+i) == MagickFalse)
713
0
          census[i]++;
714
172k
        p+=(ptrdiff_t) GetPixelChannels(edge_image);
715
172k
      }
716
85.4k
    }
717
484
    edge_view=DestroyCacheView(edge_view);
718
484
    edge_image=DestroyImage(edge_image);
719
484
  }
720
121
  edge_census=(-1.0);
721
605
  for (i=0; i < 4; i++)
722
484
    if (census[i] > edge_census)
723
121
      {
724
121
        edge_background=background[i];
725
121
        edge_census=census[i];
726
121
      }
727
121
  return(edge_background);
728
121
}
729
730
void TraceConvexHull(PointInfo *vertices,size_t number_vertices,
731
  PointInfo ***monotone_chain,size_t *chain_length)
732
121
{
733
121
  PointInfo
734
121
    **chain;
735
736
121
  size_t
737
121
    demark,
738
121
    n;
739
740
121
  ssize_t
741
121
    i;
742
743
  /*
744
    Construct the upper and lower hulls: rightmost to leftmost counterclockwise.
745
  */
746
121
  chain=(*monotone_chain);
747
121
  n=0;
748
121
  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
121
  demark=n+1;
756
121
  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
121
  *chain_length=n;
764
121
}
765
766
MagickExport PointInfo *GetImageConvexHull(const Image *image,
767
  size_t *number_vertices,ExceptionInfo *exception)
768
121
{
769
121
  CacheView
770
121
    *image_view;
771
772
121
  MagickBooleanType
773
121
    status;
774
775
121
  MemoryInfo
776
121
    *monotone_info,
777
121
    *vertices_info;
778
779
121
  PixelInfo
780
121
    background;
781
782
121
  PointInfo
783
121
    *convex_hull,
784
121
    **monotone_chain,
785
121
    *vertices;
786
787
121
  size_t
788
121
    n;
789
790
121
  ssize_t
791
121
    y;
792
793
  /*
794
    Identify convex hull vertices of image foreground object(s).
795
  */
796
121
  assert(image != (Image *) NULL);
797
121
  assert(image->signature == MagickCoreSignature);
798
121
  if (IsEventLogging() != MagickFalse)
799
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
800
121
  *number_vertices=0;
801
121
  vertices_info=AcquireVirtualMemory(image->columns,image->rows*
802
121
    sizeof(*vertices));
803
121
  monotone_info=AcquireVirtualMemory(2*image->columns,2*
804
121
    image->rows*sizeof(*monotone_chain));
805
121
  if ((vertices_info == (MemoryInfo *) NULL) ||
806
121
      (monotone_info == (MemoryInfo *) NULL))
807
0
    {
808
0
      if (monotone_info != (MemoryInfo *) NULL)
809
0
        monotone_info=(MemoryInfo *) RelinquishVirtualMemory(monotone_info);
810
0
      if (vertices_info != (MemoryInfo *) NULL)
811
0
        vertices_info=RelinquishVirtualMemory(vertices_info);
812
0
      return((PointInfo *) NULL);
813
0
    }
814
121
  vertices=(PointInfo *) GetVirtualMemoryBlob(vertices_info);
815
121
  monotone_chain=(PointInfo **) GetVirtualMemoryBlob(monotone_info);
816
121
  image_view=AcquireVirtualCacheView(image,exception);
817
121
  background=GetEdgeBackgroundColor(image,image_view,exception);
818
121
  status=MagickTrue;
819
121
  n=0;
820
42.7k
  for (y=0; y < (ssize_t) image->rows; y++)
821
42.6k
  {
822
42.6k
    const Quantum
823
42.6k
      *p;
824
825
42.6k
    ssize_t
826
42.6k
      x;
827
828
42.6k
    if (status == MagickFalse)
829
0
      continue;
830
42.6k
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
831
42.6k
    if (p == (const Quantum *) NULL)
832
0
      {
833
0
        status=MagickFalse;
834
0
        continue;
835
0
      }
836
77.1M
    for (x=0; x < (ssize_t) image->columns; x++)
837
77.1M
    {
838
77.1M
      PixelInfo
839
77.1M
        pixel;
840
841
77.1M
      GetPixelInfoPixel(image,p,&pixel);
842
77.1M
      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
77.1M
      p+=(ptrdiff_t) GetPixelChannels(image);
849
77.1M
    }
850
42.6k
  }
851
121
  image_view=DestroyCacheView(image_view);
852
  /*
853
    Return the convex hull of the image foreground object(s).
854
  */
855
121
  TraceConvexHull(vertices,n,&monotone_chain,number_vertices);
856
121
  convex_hull=(PointInfo *) AcquireQuantumMemory(*number_vertices,
857
121
    sizeof(*convex_hull));
858
121
  if (convex_hull != (PointInfo *) NULL)
859
0
    for (n=0; n < *number_vertices; n++)
860
0
      convex_hull[n]=(*monotone_chain[n]);
861
121
  monotone_info=RelinquishVirtualMemory(monotone_info);
862
121
  vertices_info=RelinquishVirtualMemory(vertices_info);
863
121
  return(convex_hull);
864
121
}
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
14.5k
{
892
14.5k
  CacheView
893
14.5k
    *image_view;
894
895
14.5k
  MagickBooleanType
896
14.5k
    status;
897
898
14.5k
  ssize_t
899
14.5k
    i;
900
901
14.5k
  size_t
902
14.5k
    *current_depth,
903
14.5k
    depth,
904
14.5k
    number_threads;
905
906
14.5k
  ssize_t
907
14.5k
    y;
908
909
  /*
910
    Compute image depth.
911
  */
912
14.5k
  assert(image != (Image *) NULL);
913
14.5k
  assert(image->signature == MagickCoreSignature);
914
14.5k
  if (IsEventLogging() != MagickFalse)
915
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
916
14.5k
  number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
917
14.5k
  current_depth=(size_t *) AcquireQuantumMemory(number_threads,
918
14.5k
    sizeof(*current_depth));
919
14.5k
  if (current_depth == (size_t *) NULL)
920
14.5k
    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
921
14.5k
  status=MagickTrue;
922
29.0k
  for (i=0; i < (ssize_t) number_threads; i++)
923
14.5k
    current_depth[i]=1;
924
14.5k
  if ((image->storage_class == PseudoClass) &&
925
336
      ((image->alpha_trait & BlendPixelTrait) == 0))
926
296
    {
927
10.2k
      for (i=0; i < (ssize_t) image->colors; i++)
928
9.93k
      {
929
9.93k
        const int
930
9.93k
          id = GetOpenMPThreadId();
931
932
10.6k
        while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
933
10.6k
        {
934
10.6k
          MagickBooleanType
935
10.6k
            atDepth;
936
937
10.6k
          QuantumAny
938
10.6k
            range;
939
940
10.6k
          atDepth=MagickTrue;
941
10.6k
          range=GetQuantumRange(current_depth[id]);
942
10.6k
          if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
943
10.6k
            if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].red),range) == MagickFalse)
944
477
              atDepth=MagickFalse;
945
10.6k
          if ((atDepth != MagickFalse) &&
946
10.1k
              (GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
947
10.1k
            if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].green),range) == MagickFalse)
948
145
              atDepth=MagickFalse;
949
10.6k
          if ((atDepth != MagickFalse) &&
950
9.98k
              (GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
951
9.98k
            if (IsPixelAtDepth(ClampToQuantum(image->colormap[i].blue),range) == MagickFalse)
952
42
              atDepth=MagickFalse;
953
10.6k
          if ((atDepth != MagickFalse))
954
9.93k
            break;
955
664
          current_depth[id]++;
956
664
        }
957
9.93k
      }
958
296
      depth=current_depth[0];
959
296
      for (i=1; i < (ssize_t) number_threads; i++)
960
0
        if (depth < current_depth[i])
961
0
          depth=current_depth[i];
962
296
      current_depth=(size_t *) RelinquishMagickMemory(current_depth);
963
296
      return(depth);
964
296
    }
965
14.2k
  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
79.4k
  for (y=0; y < (ssize_t) image->rows; y++)
1054
65.2k
  {
1055
65.2k
    const int
1056
65.2k
      id = GetOpenMPThreadId();
1057
1058
65.2k
    const Quantum
1059
65.2k
      *magick_restrict p;
1060
1061
65.2k
    ssize_t
1062
65.2k
      x;
1063
1064
65.2k
    if (status == MagickFalse)
1065
9.89k
      continue;
1066
55.3k
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1067
55.3k
    if (p == (const Quantum *) NULL)
1068
0
      continue;
1069
23.2M
    for (x=0; x < (ssize_t) image->columns; x++)
1070
23.1M
    {
1071
23.1M
      ssize_t
1072
23.1M
        j;
1073
1074
91.7M
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1075
68.5M
      {
1076
68.5M
        PixelChannel
1077
68.5M
          channel;
1078
1079
68.5M
        PixelTrait
1080
68.5M
          traits;
1081
1082
68.5M
        channel=GetPixelChannelChannel(image,j);
1083
68.5M
        traits=GetPixelChannelTraits(image,channel);
1084
68.5M
        if ((traits & UpdatePixelTrait) == 0)
1085
2.41M
          continue;
1086
66.1M
        while (current_depth[id] < MAGICKCORE_QUANTUM_DEPTH)
1087
66.1M
        {
1088
66.1M
          QuantumAny
1089
66.1M
            range;
1090
1091
66.1M
          range=GetQuantumRange(current_depth[id]);
1092
66.1M
          if (p[j] == ScaleAnyToQuantum(ScaleQuantumToAny(p[j],range),range))
1093
66.1M
            break;
1094
6.78k
          current_depth[id]++;
1095
6.78k
        }
1096
66.1M
      }
1097
23.1M
      p+=(ptrdiff_t) GetPixelChannels(image);
1098
23.1M
    }
1099
55.3k
    if (current_depth[id] == MAGICKCORE_QUANTUM_DEPTH)
1100
297
      status=MagickFalse;
1101
55.3k
  }
1102
14.2k
  image_view=DestroyCacheView(image_view);
1103
14.2k
  depth=current_depth[0];
1104
14.2k
  for (i=1; i < (ssize_t) number_threads; i++)
1105
0
    if (depth < current_depth[i])
1106
0
      depth=current_depth[i];
1107
14.2k
  current_depth=(size_t *) RelinquishMagickMemory(current_depth);
1108
14.2k
  return(depth);
1109
14.5k
}
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
4
{
1205
4
  CaliperInfo
1206
4
    caliper_info;
1207
1208
4
  const char
1209
4
    *artifact;
1210
1211
4
  double
1212
4
    angle,
1213
4
    diameter,
1214
4
    distance;
1215
1216
4
  PointInfo
1217
4
    *bounding_box,
1218
4
    *vertices;
1219
1220
4
  size_t
1221
4
    number_hull_vertices;
1222
1223
4
  ssize_t
1224
4
    i;
1225
1226
  /*
1227
    Generate the minimum bounding box with the "Rotating Calipers" algorithm.
1228
  */
1229
4
  assert(image != (Image *) NULL);
1230
4
  assert(image->signature == MagickCoreSignature);
1231
4
  if (IsEventLogging() != MagickFalse)
1232
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1233
4
  *number_vertices=0;
1234
4
  vertices=GetImageConvexHull(image,&number_hull_vertices,exception);
1235
4
  if (vertices == (PointInfo *) NULL)
1236
4
    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
53.7k
{
1463
53.7k
  size_t
1464
53.7k
    depth;
1465
1466
53.7k
  depth=image->depth;
1467
53.7k
  if (depth <= 8)
1468
38.1k
    depth=8;
1469
15.5k
  else
1470
15.5k
    if (depth <= 16)
1471
12.6k
      depth=16;
1472
2.89k
    else
1473
2.89k
      if (depth <= 32)
1474
1.57k
        depth=32;
1475
1.32k
      else
1476
1.32k
        if (depth <= 64)
1477
1.32k
          depth=64;
1478
53.7k
  if (constrain != MagickFalse)
1479
0
    depth=(size_t) MagickMin((double) depth,(double) MAGICKCORE_QUANTUM_DEPTH);
1480
53.7k
  return(depth);
1481
53.7k
}
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
28.5k
{
1567
28.5k
  CacheView
1568
28.5k
    *image_view;
1569
1570
28.5k
  ImageType
1571
28.5k
    type = BilevelType;
1572
1573
28.5k
  MagickBooleanType
1574
28.5k
    status = MagickTrue;
1575
1576
28.5k
  ssize_t
1577
28.5k
    y;
1578
1579
28.5k
  assert(image != (Image *) NULL);
1580
28.5k
  assert(image->signature == MagickCoreSignature);
1581
28.5k
  if (IsEventLogging() != MagickFalse)
1582
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1583
28.5k
  if (IsImageGray(image) != MagickFalse)
1584
10.4k
    return(image->type);
1585
18.1k
  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1586
933
    return(UndefinedType);
1587
17.2k
  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
3.46M
  for (y=0; y < (ssize_t) image->rows; y++)
1593
3.45M
  {
1594
3.45M
    const Quantum
1595
3.45M
      *p;
1596
1597
3.45M
    ssize_t
1598
3.45M
      x;
1599
1600
3.45M
    if (status == MagickFalse)
1601
2.88M
      continue;
1602
566k
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1603
566k
    if (p == (const Quantum *) NULL)
1604
0
      {
1605
0
        status=MagickFalse;
1606
0
        continue;
1607
0
      }
1608
312M
    for (x=0; x < (ssize_t) image->columns; x++)
1609
311M
    {
1610
311M
      if (IsPixelGray(image,p) == MagickFalse)
1611
9.37k
        {
1612
9.37k
          status=MagickFalse;
1613
9.37k
          break;
1614
9.37k
        }
1615
311M
      if ((type == BilevelType) && (IsPixelMonochrome(image,p) == MagickFalse))
1616
3.14k
        type=GrayscaleType;
1617
311M
      p+=(ptrdiff_t) GetPixelChannels(image);
1618
311M
    }
1619
566k
  }
1620
17.2k
  image_view=DestroyCacheView(image_view);
1621
17.2k
  if ((type == GrayscaleType) && (image->alpha_trait != UndefinedPixelTrait))
1622
584
    type=GrayscaleAlphaType;
1623
17.2k
  if (status == MagickFalse)
1624
9.37k
    return(UndefinedType);
1625
7.86k
  return(type);
1626
17.2k
}
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
4.42k
{
1658
4.42k
  CacheView
1659
4.42k
    *image_view;
1660
1661
4.42k
  ImageType
1662
4.42k
    type = BilevelType;
1663
1664
4.42k
  ssize_t
1665
4.42k
    y;
1666
1667
4.42k
  assert(image != (Image *) NULL);
1668
4.42k
  assert(image->signature == MagickCoreSignature);
1669
4.42k
  if (IsEventLogging() != MagickFalse)
1670
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1671
4.42k
  if (image->type == BilevelType)
1672
0
    return(MagickTrue);
1673
4.42k
  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
1674
0
    return(MagickFalse);
1675
4.42k
  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
815k
  for (y=0; y < (ssize_t) image->rows; y++)
1681
811k
  {
1682
811k
    const Quantum
1683
811k
      *p;
1684
1685
811k
    ssize_t
1686
811k
      x;
1687
1688
811k
    if (type == UndefinedType)
1689
305k
      continue;
1690
505k
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1691
505k
    if (p == (const Quantum *) NULL)
1692
0
      {
1693
0
        type=UndefinedType;
1694
0
        continue;
1695
0
      }
1696
261M
    for (x=0; x < (ssize_t) image->columns; x++)
1697
261M
    {
1698
261M
      if (IsPixelMonochrome(image,p) == MagickFalse)
1699
1.12k
        {
1700
1.12k
          type=UndefinedType;
1701
1.12k
          break;
1702
1.12k
        }
1703
261M
      p+=(ptrdiff_t) GetPixelChannels(image);
1704
261M
    }
1705
505k
  }
1706
4.42k
  image_view=DestroyCacheView(image_view);
1707
4.42k
  return(type == BilevelType ? MagickTrue : MagickFalse);
1708
4.42k
}
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
2.31k
{
1745
2.31k
  ImageType
1746
2.31k
    type;
1747
1748
2.31k
  assert(image != (Image *) NULL);
1749
2.31k
  assert(image->signature == MagickCoreSignature);
1750
2.31k
  if (IsEventLogging() != MagickFalse)
1751
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1752
2.31k
  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
2.31k
  type=IdentifyImageGray(image,exception);
1759
2.31k
  if (IsGrayImageType(type))
1760
2.31k
    return(type);
1761
0
  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
0
  if (image->alpha_trait != UndefinedPixelTrait)
1768
0
    return(TrueColorAlphaType);
1769
0
  return(TrueColorType);
1770
0
}
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
50.1k
{
1797
50.1k
  assert(image != (Image *) NULL);
1798
50.1k
  assert(image->signature == MagickCoreSignature);
1799
50.1k
  if (IsGrayImageType(image->type) != MagickFalse)
1800
14.1k
    return(MagickTrue);
1801
35.9k
  return(MagickFalse);
1802
50.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
27.6k
{
1828
27.6k
  assert(image != (Image *) NULL);
1829
27.6k
  assert(image->signature == MagickCoreSignature);
1830
27.6k
  if (image->type == BilevelType)
1831
5.79k
    return(MagickTrue);
1832
21.8k
  return(MagickFalse);
1833
27.6k
}
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.70k
{
1866
4.70k
  CacheView
1867
4.70k
    *image_view;
1868
1869
4.70k
  MagickBooleanType
1870
4.70k
    opaque = MagickTrue;
1871
1872
4.70k
  ssize_t
1873
4.70k
    y;
1874
1875
  /*
1876
    Determine if image is opaque.
1877
  */
1878
4.70k
  assert(image != (Image *) NULL);
1879
4.70k
  assert(image->signature == MagickCoreSignature);
1880
4.70k
  if (IsEventLogging() != MagickFalse)
1881
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1882
4.70k
  if ((image->alpha_trait & BlendPixelTrait) == 0)
1883
4.64k
    return(MagickTrue);
1884
63
  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
12.5k
  for (y=0; y < (ssize_t) image->rows; y++)
1890
12.4k
  {
1891
12.4k
    const Quantum
1892
12.4k
      *p;
1893
1894
12.4k
    ssize_t
1895
12.4k
      x;
1896
1897
12.4k
    if (opaque == MagickFalse)
1898
4.83k
      continue;
1899
7.65k
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1900
7.65k
    if (p == (const Quantum *) NULL)
1901
0
      {
1902
0
        opaque=MagickFalse;
1903
0
        continue;
1904
0
      }
1905
8.58M
    for (x=0; x < (ssize_t) image->columns; x++)
1906
8.57M
    {
1907
8.57M
      if (GetPixelAlpha(image,p) != OpaqueAlpha)
1908
36
        {
1909
36
          opaque=MagickFalse;
1910
36
          break;
1911
36
        }
1912
8.57M
      p+=(ptrdiff_t) GetPixelChannels(image);
1913
8.57M
    }
1914
7.65k
  }
1915
63
  image_view=DestroyCacheView(image_view);
1916
63
  return(opaque);
1917
4.70k
}
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
          {
2032
0
            u++;
2033
0
            v++;
2034
0
            continue;
2035
0
          }
2036
0
        pixel=(double) q[i]+distortion[u];
2037
0
        q[i]=ScaleAnyToQuantum(ScaleQuantumToAny(ClampPixel((MagickRealType)
2038
0
          pixel),range),range);
2039
        /*
2040
          Distribute distortion for right.
2041
        */
2042
0
        error=pixel-(double) q[i];
2043
0
        if ((x+1) < (ssize_t) image->columns)
2044
0
          distortion[u+(ssize_t) channels]+=7.0*error/16.0;
2045
0
        if ((y+1) < (ssize_t) image->rows)
2046
0
          {
2047
            /*
2048
              Distribute distortion for bottom left, bottom, and bottom right.
2049
            */
2050
0
            if (x > 0)
2051
0
              distortion[v-(ssize_t) channels]+=3.0*error/16.0;
2052
0
            distortion[v]+=5.0*error/16.0;
2053
0
            if ((x+1) < (ssize_t) image->columns)
2054
0
              distortion[v+(ssize_t) channels]+=1.0*error/16.0;
2055
0
          }
2056
0
        u++;
2057
0
        v++;
2058
0
      }
2059
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2060
0
    }
2061
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2062
0
      {
2063
0
        status=MagickFalse;
2064
0
        continue;
2065
0
      }
2066
0
  }
2067
0
  image_view=DestroyCacheView(image_view);
2068
0
  distortion=(double *) RelinquishMagickMemory(distortion);
2069
0
  if (status != MagickFalse)
2070
0
    image->depth=depth;
2071
0
  return(status);
2072
0
}
2073
2074
MagickExport MagickBooleanType SetImageDepth(Image *image,
2075
  const size_t depth,ExceptionInfo *exception)
2076
2.30k
{
2077
2.30k
  CacheView
2078
2.30k
    *image_view;
2079
2080
2.30k
  const char
2081
2.30k
    *artifact;
2082
2083
2.30k
  MagickBooleanType
2084
2.30k
    status;
2085
2086
2.30k
  QuantumAny
2087
2.30k
    range;
2088
2089
2.30k
  ssize_t
2090
2.30k
    y;
2091
2092
2.30k
  assert(image != (Image *) NULL);
2093
2.30k
  if (IsEventLogging() != MagickFalse)
2094
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2095
2.30k
  assert(image->signature == MagickCoreSignature);
2096
2.30k
  if (depth >= MAGICKCORE_QUANTUM_DEPTH)
2097
197
    {
2098
197
      image->depth=depth;
2099
197
      return(MagickTrue);
2100
197
    }
2101
2.10k
  artifact=GetImageArtifact(image,"dither");
2102
2.10k
  if ((artifact != (const char *) NULL) &&
2103
0
      (LocaleCompare(artifact,"FloydSteinberg") == 0))
2104
0
    return(FloydSteinbergImageDepth(image,depth,exception));
2105
2.10k
  range=GetQuantumRange(depth);
2106
2.10k
  if (image->storage_class == PseudoClass)
2107
244
    {
2108
244
      ssize_t
2109
244
        i;
2110
2111
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2112
      #pragma omp parallel for schedule(static) shared(status) \
2113
        magick_number_threads(image,image,image->colors,1)
2114
#endif
2115
1.24k
      for (i=0; i < (ssize_t) image->colors; i++)
2116
1.00k
      {
2117
1.00k
        if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2118
1.00k
          image->colormap[i].red=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2119
1.00k
            ClampPixel(image->colormap[i].red),range),range);
2120
1.00k
        if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2121
1.00k
          image->colormap[i].green=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2122
1.00k
            ClampPixel(image->colormap[i].green),range),range);
2123
1.00k
        if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2124
1.00k
          image->colormap[i].blue=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2125
1.00k
            ClampPixel(image->colormap[i].blue),range),range);
2126
1.00k
        if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2127
88
          image->colormap[i].alpha=(double) ScaleAnyToQuantum(ScaleQuantumToAny(
2128
88
            ClampPixel(image->colormap[i].alpha),range),range);
2129
1.00k
      }
2130
244
    }
2131
2.10k
  status=MagickTrue;
2132
2.10k
  image_view=AcquireAuthenticCacheView(image,exception);
2133
#if !defined(MAGICKCORE_HDRI_SUPPORT)
2134
  DisableMSCWarning(4127)
2135
  if ((1UL*QuantumRange) <= MaxMap)
2136
  RestoreMSCWarning
2137
    {
2138
      Quantum
2139
        *depth_map;
2140
2141
      ssize_t
2142
        i;
2143
2144
      /*
2145
        Scale pixels to desired (optimized with depth map).
2146
      */
2147
      depth_map=(Quantum *) AcquireQuantumMemory(MaxMap+1,sizeof(*depth_map));
2148
      if (depth_map == (Quantum *) NULL)
2149
        ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
2150
      for (i=0; i <= (ssize_t) MaxMap; i++)
2151
        depth_map[i]=ScaleAnyToQuantum(ScaleQuantumToAny((Quantum) i,range),
2152
          range);
2153
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2154
      #pragma omp parallel for schedule(static) shared(status) \
2155
        magick_number_threads(image,image,image->rows,2)
2156
#endif
2157
      for (y=0; y < (ssize_t) image->rows; y++)
2158
      {
2159
        ssize_t
2160
          x;
2161
2162
        Quantum
2163
          *magick_restrict q;
2164
2165
        if (status == MagickFalse)
2166
          continue;
2167
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
2168
          exception);
2169
        if (q == (Quantum *) NULL)
2170
          {
2171
            status=MagickFalse;
2172
            continue;
2173
          }
2174
        for (x=0; x < (ssize_t) image->columns; x++)
2175
        {
2176
          ssize_t
2177
            j;
2178
2179
          for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2180
          {
2181
            PixelChannel
2182
              channel;
2183
2184
            PixelTrait
2185
              traits;
2186
2187
            channel=GetPixelChannelChannel(image,j);
2188
            traits=GetPixelChannelTraits(image,channel);
2189
            if ((traits & UpdatePixelTrait) == 0)
2190
              continue;
2191
            q[j]=depth_map[ScaleQuantumToMap(q[j])];
2192
          }
2193
          q+=(ptrdiff_t) GetPixelChannels(image);
2194
        }
2195
        if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2196
          {
2197
            status=MagickFalse;
2198
            continue;
2199
          }
2200
      }
2201
      image_view=DestroyCacheView(image_view);
2202
      depth_map=(Quantum *) RelinquishMagickMemory(depth_map);
2203
      if (status != MagickFalse)
2204
        image->depth=depth;
2205
      return(status);
2206
    }
2207
#endif
2208
  /*
2209
    Scale pixels to desired depth.
2210
  */
2211
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2212
  #pragma omp parallel for schedule(static) shared(status) \
2213
    magick_number_threads(image,image,image->rows,2)
2214
#endif
2215
166k
  for (y=0; y < (ssize_t) image->rows; y++)
2216
164k
  {
2217
164k
    ssize_t
2218
164k
      x;
2219
2220
164k
    Quantum
2221
164k
      *magick_restrict q;
2222
2223
164k
    if (status == MagickFalse)
2224
0
      continue;
2225
164k
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2226
164k
    if (q == (Quantum *) NULL)
2227
0
      {
2228
0
        status=MagickFalse;
2229
0
        continue;
2230
0
      }
2231
274M
    for (x=0; x < (ssize_t) image->columns; x++)
2232
274M
    {
2233
274M
      ssize_t
2234
274M
        i;
2235
2236
550M
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2237
276M
      {
2238
276M
        PixelChannel
2239
276M
          channel;
2240
2241
276M
        PixelTrait
2242
276M
          traits;
2243
2244
276M
        channel=GetPixelChannelChannel(image,i);
2245
276M
        traits=GetPixelChannelTraits(image,channel);
2246
276M
        if ((traits & UpdatePixelTrait) == 0)
2247
122k
          continue;
2248
276M
        q[i]=ScaleAnyToQuantum(ScaleQuantumToAny(ClampPixel((MagickRealType)
2249
276M
          q[i]),range),range);
2250
276M
      }
2251
274M
      q+=(ptrdiff_t) GetPixelChannels(image);
2252
274M
    }
2253
164k
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2254
0
      {
2255
0
        status=MagickFalse;
2256
0
        continue;
2257
0
      }
2258
164k
  }
2259
2.10k
  image_view=DestroyCacheView(image_view);
2260
2.10k
  if (status != MagickFalse)
2261
2.10k
    image->depth=depth;
2262
2.10k
  return(status);
2263
2.10k
}
2264

2265
/*
2266
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2267
%                                                                             %
2268
%                                                                             %
2269
%                                                                             %
2270
%   S e t I m a g e T y p e                                                   %
2271
%                                                                             %
2272
%                                                                             %
2273
%                                                                             %
2274
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2275
%
2276
%  SetImageType() sets the type of image.  Choose from these types:
2277
%
2278
%        Bilevel        Grayscale       GrayscaleMatte
2279
%        Palette        PaletteMatte    TrueColor
2280
%        TrueColorMatte ColorSeparation ColorSeparationMatte
2281
%        OptimizeType
2282
%
2283
%  The format of the SetImageType method is:
2284
%
2285
%      MagickBooleanType SetImageType(Image *image,const ImageType type,
2286
%        ExceptionInfo *exception)
2287
%
2288
%  A description of each parameter follows:
2289
%
2290
%    o image: the image.
2291
%
2292
%    o type: Image type.
2293
%
2294
%    o exception: return any errors or warnings in this structure.
2295
%
2296
*/
2297
MagickExport MagickBooleanType SetImageType(Image *image,const ImageType type,
2298
  ExceptionInfo *exception)
2299
8.54k
{
2300
8.54k
  const char
2301
8.54k
    *artifact;
2302
2303
8.54k
  ImageInfo
2304
8.54k
    *image_info;
2305
2306
8.54k
  MagickBooleanType
2307
8.54k
    status;
2308
2309
8.54k
  QuantizeInfo
2310
8.54k
    *quantize_info;
2311
2312
8.54k
  assert(image != (Image *) NULL);
2313
8.54k
  if (IsEventLogging() != MagickFalse)
2314
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2315
8.54k
  assert(image->signature == MagickCoreSignature);
2316
8.54k
  status=MagickTrue;
2317
8.54k
  image_info=AcquireImageInfo();
2318
8.54k
  image_info->dither=image->dither;
2319
8.54k
  artifact=GetImageArtifact(image,"dither");
2320
8.54k
  if (artifact != (const char *) NULL)
2321
0
    (void) SetImageOption(image_info,"dither",artifact);
2322
8.54k
  switch (type)
2323
8.54k
  {
2324
2.28k
    case BilevelType:
2325
2.28k
    {
2326
2.28k
      if (IsGrayImageType(image->type) == MagickFalse)
2327
1.21k
        status=TransformImageColorspace(image,GRAYColorspace,exception);
2328
2.28k
      (void) NormalizeImage(image,exception);
2329
2.28k
      (void) BilevelImage(image,(double) QuantumRange/2.0,exception);
2330
2.28k
      quantize_info=AcquireQuantizeInfo(image_info);
2331
2.28k
      quantize_info->number_colors=2;
2332
2.28k
      quantize_info->colorspace=GRAYColorspace;
2333
2.28k
      status=QuantizeImage(quantize_info,image,exception);
2334
2.28k
      quantize_info=DestroyQuantizeInfo(quantize_info);
2335
2.28k
      image->alpha_trait=UndefinedPixelTrait;
2336
2.28k
      break;
2337
0
    }
2338
3.32k
    case GrayscaleType:
2339
3.32k
    {
2340
3.32k
      if (IsGrayImageType(image->type) == MagickFalse)
2341
3.32k
        status=TransformImageColorspace(image,GRAYColorspace,exception);
2342
3.32k
      image->alpha_trait=UndefinedPixelTrait;
2343
3.32k
      break;
2344
0
    }
2345
602
    case GrayscaleAlphaType:
2346
602
    {
2347
602
      if (IsGrayImageType(image->type) == MagickFalse)
2348
602
        status=TransformImageColorspace(image,GRAYColorspace,exception);
2349
602
      if ((image->alpha_trait & BlendPixelTrait) == 0)
2350
0
        (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2351
602
      break;
2352
0
    }
2353
555
    case PaletteType:
2354
555
    {
2355
555
      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2356
0
        status=TransformImageColorspace(image,sRGBColorspace,exception);
2357
555
      if ((image->storage_class == DirectClass) || (image->colors > 256))
2358
421
        {
2359
421
          quantize_info=AcquireQuantizeInfo(image_info);
2360
421
          quantize_info->number_colors=256;
2361
421
          status=QuantizeImage(quantize_info,image,exception);
2362
421
          quantize_info=DestroyQuantizeInfo(quantize_info);
2363
421
        }
2364
555
      image->alpha_trait=UndefinedPixelTrait;
2365
555
      break;
2366
0
    }
2367
742
    case PaletteBilevelAlphaType:
2368
742
    {
2369
742
      ChannelType
2370
742
        channel_mask;
2371
2372
742
      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2373
0
        status=TransformImageColorspace(image,sRGBColorspace,exception);
2374
742
      if ((image->alpha_trait & BlendPixelTrait) == 0)
2375
0
        (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2376
742
      channel_mask=SetImageChannelMask(image,AlphaChannel);
2377
742
      (void) BilevelImage(image,(double) QuantumRange/2.0,exception);
2378
742
      (void) SetImageChannelMask(image,channel_mask);
2379
742
      quantize_info=AcquireQuantizeInfo(image_info);
2380
742
      status=QuantizeImage(quantize_info,image,exception);
2381
742
      quantize_info=DestroyQuantizeInfo(quantize_info);
2382
742
      break;
2383
0
    }
2384
56
    case PaletteAlphaType:
2385
56
    {
2386
56
      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2387
0
        status=TransformImageColorspace(image,sRGBColorspace,exception);
2388
56
      if ((image->alpha_trait & BlendPixelTrait) == 0)
2389
0
        (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2390
56
      quantize_info=AcquireQuantizeInfo(image_info);
2391
56
      quantize_info->colorspace=TransparentColorspace;
2392
56
      status=QuantizeImage(quantize_info,image,exception);
2393
56
      quantize_info=DestroyQuantizeInfo(quantize_info);
2394
56
      break;
2395
0
    }
2396
788
    case TrueColorType:
2397
788
    {
2398
788
      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2399
0
        status=TransformImageColorspace(image,sRGBColorspace,exception);
2400
788
      if (image->storage_class != DirectClass)
2401
565
        status=SetImageStorageClass(image,DirectClass,exception);
2402
788
      image->alpha_trait=UndefinedPixelTrait;
2403
788
      break;
2404
0
    }
2405
197
    case TrueColorAlphaType:
2406
197
    {
2407
197
      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
2408
0
        status=TransformImageColorspace(image,sRGBColorspace,exception);
2409
197
      if (image->storage_class != DirectClass)
2410
53
        status=SetImageStorageClass(image,DirectClass,exception);
2411
197
      if ((image->alpha_trait & BlendPixelTrait) == 0)
2412
0
        (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2413
197
      break;
2414
0
    }
2415
0
    case ColorSeparationType:
2416
0
    {
2417
0
      if (image->colorspace != CMYKColorspace)
2418
0
        status=TransformImageColorspace(image,CMYKColorspace,exception);
2419
0
      if (image->storage_class != DirectClass)
2420
0
        status=SetImageStorageClass(image,DirectClass,exception);
2421
0
      image->alpha_trait=UndefinedPixelTrait;
2422
0
      break;
2423
0
    }
2424
0
    case ColorSeparationAlphaType:
2425
0
    {
2426
0
      if (image->colorspace != CMYKColorspace)
2427
0
        status=TransformImageColorspace(image,CMYKColorspace,exception);
2428
0
      if (image->storage_class != DirectClass)
2429
0
        status=SetImageStorageClass(image,DirectClass,exception);
2430
0
      if ((image->alpha_trait & BlendPixelTrait) == 0)
2431
0
        status=SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2432
0
      break;
2433
0
    }
2434
0
    case OptimizeType:
2435
0
    case UndefinedType:
2436
0
      break;
2437
8.54k
  }
2438
8.54k
  image_info=DestroyImageInfo(image_info);
2439
8.54k
  if (status == MagickFalse)
2440
0
    return(status);
2441
8.54k
  image->type=type;
2442
8.54k
  return(MagickTrue);
2443
8.54k
}