Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/imagemagick/MagickCore/vision.c
Line
Count
Source
1
/*
2
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3
%                                                                             %
4
%                                                                             %
5
%                                                                             %
6
%                   V   V  IIIII  SSSSS  IIIII   OOO   N   N                  %
7
%                   V   V    I    SS       I    O   O  NN  N                  %
8
%                   V   V    I     SSS     I    O   O  N N N                  %
9
%                    V V     I       SS    I    O   O  N  NN                  %
10
%                     V    IIIII  SSSSS  IIIII   OOO   N   N                  %
11
%                                                                             %
12
%                                                                             %
13
%                      MagickCore Computer Vision Methods                     %
14
%                                                                             %
15
%                              Software Design                                %
16
%                                   Cristy                                    %
17
%                               September 2014                                %
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
#include "MagickCore/studio.h"
40
#include "MagickCore/artifact.h"
41
#include "MagickCore/blob.h"
42
#include "MagickCore/cache-view.h"
43
#include "MagickCore/color.h"
44
#include "MagickCore/color-private.h"
45
#include "MagickCore/colormap.h"
46
#include "MagickCore/colorspace.h"
47
#include "MagickCore/constitute.h"
48
#include "MagickCore/decorate.h"
49
#include "MagickCore/distort.h"
50
#include "MagickCore/draw.h"
51
#include "MagickCore/enhance.h"
52
#include "MagickCore/exception.h"
53
#include "MagickCore/exception-private.h"
54
#include "MagickCore/effect.h"
55
#include "MagickCore/gem.h"
56
#include "MagickCore/geometry.h"
57
#include "MagickCore/image-private.h"
58
#include "MagickCore/list.h"
59
#include "MagickCore/log.h"
60
#include "MagickCore/matrix.h"
61
#include "MagickCore/memory_.h"
62
#include "MagickCore/memory-private.h"
63
#include "MagickCore/monitor.h"
64
#include "MagickCore/monitor-private.h"
65
#include "MagickCore/montage.h"
66
#include "MagickCore/morphology.h"
67
#include "MagickCore/morphology-private.h"
68
#include "MagickCore/opencl-private.h"
69
#include "MagickCore/paint.h"
70
#include "MagickCore/pixel-accessor.h"
71
#include "MagickCore/property.h"
72
#include "MagickCore/quantum.h"
73
#include "MagickCore/resource_.h"
74
#include "MagickCore/signature-private.h"
75
#include "MagickCore/string_.h"
76
#include "MagickCore/string-private.h"
77
#include "MagickCore/thread-private.h"
78
#include "MagickCore/token.h"
79
#include "MagickCore/vision.h"
80

81
/*
82
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83
%                                                                             %
84
%                                                                             %
85
%                                                                             %
86
%     C o n n e c t e d C o m p o n e n t s I m a g e                         %
87
%                                                                             %
88
%                                                                             %
89
%                                                                             %
90
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91
%
92
%  ConnectedComponentsImage() returns the connected-components of the image
93
%  uniquely labeled.  The returned connected components image colors member
94
%  defines the number of unique objects.  Choose from 4 or 8-way connectivity.
95
%
96
%  You are responsible for freeing the connected components objects resources
97
%  with this statement;
98
%
99
%    objects = (CCObjectInfo *) RelinquishMagickMemory(objects);
100
%
101
%  The format of the ConnectedComponentsImage method is:
102
%
103
%      Image *ConnectedComponentsImage(const Image *image,
104
%        const size_t connectivity,CCObjectInfo **objects,
105
%        ExceptionInfo *exception)
106
%
107
%  A description of each parameter follows:
108
%
109
%    o image: the image.
110
%
111
%    o connectivity: how many neighbors to visit, choose from 4 or 8.
112
%
113
%    o objects: return the attributes of each unique object.
114
%
115
%    o exception: return any errors or warnings in this structure.
116
%
117
*/
118
119
static int CCObjectInfoCompare(const void *x,const void *y)
120
0
{
121
0
  CCObjectInfo
122
0
    *p,
123
0
    *q;
124
125
0
  p=(CCObjectInfo *) x;
126
0
  q=(CCObjectInfo *) y;
127
0
  if (p->key == -5)
128
0
    return((int) (q->bounding_box.y-(ssize_t) p->bounding_box.y));
129
0
  if (p->key == -4)
130
0
    return((int) (q->bounding_box.x-(ssize_t) p->bounding_box.x));
131
0
  if (p->key == -3)
132
0
    return((int) (q->bounding_box.height-p->bounding_box.height));
133
0
  if (p->key == -2)
134
0
    return((int) (q->bounding_box.width-p->bounding_box.width));
135
0
  if (p->key == -1)
136
0
    return((int) (q->area-(ssize_t) p->area));
137
0
  if (p->key == 1)
138
0
    return((int) (p->area-(ssize_t) q->area));
139
0
  if (p->key == 2)
140
0
    return((int) (p->bounding_box.width-q->bounding_box.width));
141
0
  if (p->key == 3)
142
0
    return((int) (p->bounding_box.height-q->bounding_box.height));
143
0
  if (p->key == 4)
144
0
    return((int) (p->bounding_box.x-(ssize_t) q->bounding_box.x));
145
0
  if (p->key == 5)
146
0
    return((int) (p->bounding_box.y-(ssize_t) q->bounding_box.y));
147
0
  return((int) (q->area-(ssize_t) p->area));
148
0
}
149
150
static void PerimeterThreshold(const Image *component_image,
151
  CCObjectInfo *object,const ssize_t metric_index,ExceptionInfo *exception)
152
0
{
153
0
  MagickBooleanType
154
0
    status;
155
156
0
  ssize_t
157
0
    i;
158
159
0
  status=MagickTrue;
160
#if defined(MAGICKCORE_OPENMP_SUPPORT)
161
  #pragma omp parallel for schedule(dynamic) shared(status) \
162
    magick_number_threads(component_image,component_image,component_image->colors,1)
163
#endif
164
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
165
0
  {
166
0
    CacheView
167
0
      *component_view;
168
169
0
    RectangleInfo
170
0
      bounding_box;
171
172
0
    size_t
173
0
      pattern[4] = { 1, 0, 0, 0 };
174
175
0
    ssize_t
176
0
      y;
177
178
    /*
179
      Compute perimeter of each object.
180
    */
181
0
    if (status == MagickFalse)
182
0
      continue;
183
0
    component_view=AcquireAuthenticCacheView(component_image,exception);
184
0
    bounding_box=object[i].bounding_box;
185
0
    for (y=(-1); y < (ssize_t) bounding_box.height; y++)
186
0
    {
187
0
      const Quantum
188
0
        *magick_restrict p;
189
190
0
      ssize_t
191
0
        x;
192
193
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
194
0
        bounding_box.y+y,bounding_box.width+2,2,exception);
195
0
      if (p == (const Quantum *) NULL)
196
0
        {
197
0
          status=MagickFalse;
198
0
          break;
199
0
        }
200
0
      for (x=(-1); x < (ssize_t) bounding_box.width; x++)
201
0
      {
202
0
        Quantum
203
0
          pixels[4];
204
205
0
        size_t
206
0
          foreground;
207
208
0
        ssize_t
209
0
          v;
210
211
        /*
212
          An Algorithm for Calculating Objects’ Shape Features in Binary
213
          Images, Lifeng He, Yuyan Chao.
214
        */
215
0
        foreground=0;
216
0
        for (v=0; v < 2; v++)
217
0
        {
218
0
          ssize_t
219
0
            u;
220
221
0
          for (u=0; u < 2; u++)
222
0
          {
223
0
            ssize_t
224
0
              offset;
225
226
0
            offset=v*((ssize_t) bounding_box.width+2)*
227
0
              (ssize_t) GetPixelChannels(component_image)+u*
228
0
              (ssize_t) GetPixelChannels(component_image);
229
0
            pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
230
0
            if ((ssize_t) pixels[2*v+u] == i)
231
0
              foreground++;
232
0
          }
233
0
        }
234
0
        if (foreground == 1)
235
0
          pattern[1]++;
236
0
        else
237
0
          if (foreground == 2)
238
0
            {
239
0
              if ((((ssize_t) pixels[0] == i) && ((ssize_t) pixels[3] == i)) ||
240
0
                  (((ssize_t) pixels[1] == i) && ((ssize_t) pixels[2] == i)))
241
0
                pattern[0]++;  /* diagonal */
242
0
              else
243
0
                pattern[2]++;
244
0
            }
245
0
          else
246
0
            if (foreground == 3)
247
0
              pattern[3]++;
248
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
249
0
      }
250
0
    }
251
0
    component_view=DestroyCacheView(component_view);
252
0
    object[i].metric[metric_index]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
253
0
      MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
254
0
  }
255
0
}
256
257
static void CircularityThreshold(const Image *component_image,
258
  CCObjectInfo *object,const ssize_t metric_index,ExceptionInfo *exception)
259
0
{
260
0
  MagickBooleanType
261
0
    status;
262
263
0
  ssize_t
264
0
    i;
265
266
0
  status=MagickTrue;
267
#if defined(MAGICKCORE_OPENMP_SUPPORT)
268
  #pragma omp parallel for schedule(dynamic) shared(status) \
269
    magick_number_threads(component_image,component_image,component_image->colors,1)
270
#endif
271
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
272
0
  {
273
0
    CacheView
274
0
      *component_view;
275
276
0
    RectangleInfo
277
0
      bounding_box;
278
279
0
    size_t
280
0
      pattern[4] = { 1, 0, 0, 0 };
281
282
0
    ssize_t
283
0
      y;
284
285
    /*
286
      Compute perimeter of each object.
287
    */
288
0
    if (status == MagickFalse)
289
0
      continue;
290
0
    component_view=AcquireAuthenticCacheView(component_image,exception);
291
0
    bounding_box=object[i].bounding_box;
292
0
    for (y=(-1); y < (ssize_t) bounding_box.height; y++)
293
0
    {
294
0
      const Quantum
295
0
        *magick_restrict p;
296
297
0
      ssize_t
298
0
        x;
299
300
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
301
0
        bounding_box.y+y,bounding_box.width+2,2,exception);
302
0
      if (p == (const Quantum *) NULL)
303
0
        {
304
0
          status=MagickFalse;
305
0
          break;
306
0
        }
307
0
      for (x=(-1); x < (ssize_t) bounding_box.width; x++)
308
0
      {
309
0
        Quantum
310
0
          pixels[4];
311
312
0
        ssize_t
313
0
          v;
314
315
0
        size_t
316
0
          foreground;
317
318
        /*
319
          An Algorithm for Calculating Objects’ Shape Features in Binary
320
          Images, Lifeng He, Yuyan Chao.
321
        */
322
0
        foreground=0;
323
0
        for (v=0; v < 2; v++)
324
0
        {
325
0
          ssize_t
326
0
            u;
327
328
0
          for (u=0; u < 2; u++)
329
0
          {
330
0
            ssize_t
331
0
              offset;
332
333
0
            offset=v*((ssize_t) bounding_box.width+2)*
334
0
              (ssize_t) GetPixelChannels(component_image)+u*
335
0
              (ssize_t) GetPixelChannels(component_image);
336
0
            pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
337
0
            if ((ssize_t) pixels[2*v+u] == i)
338
0
              foreground++;
339
0
          }
340
0
        }
341
0
        if (foreground == 1)
342
0
          pattern[1]++;
343
0
        else
344
0
          if (foreground == 2)
345
0
            {
346
0
              if ((((ssize_t) pixels[0] == i) && ((ssize_t) pixels[3] == i)) ||
347
0
                  (((ssize_t) pixels[1] == i) && ((ssize_t) pixels[2] == i)))
348
0
                pattern[0]++;  /* diagonal */
349
0
              else
350
0
                pattern[2]++;
351
0
            }
352
0
          else
353
0
            if (foreground == 3)
354
0
              pattern[3]++;
355
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
356
0
      }
357
0
    }
358
0
    component_view=DestroyCacheView(component_view);
359
0
    object[i].metric[metric_index]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
360
0
      MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
361
0
    object[i].metric[metric_index]=4.0*MagickPI*object[i].area/
362
0
      (object[i].metric[metric_index]*object[i].metric[metric_index]);
363
0
  }
364
0
}
365
366
static void MajorAxisThreshold(const Image *component_image,
367
  CCObjectInfo *object,const ssize_t metric_index,ExceptionInfo *exception)
368
0
{
369
0
  MagickBooleanType
370
0
    status;
371
372
0
  ssize_t
373
0
    i;
374
375
0
  status=MagickTrue;
376
#if defined(MAGICKCORE_OPENMP_SUPPORT)
377
  #pragma omp parallel for schedule(dynamic) shared(status) \
378
    magick_number_threads(component_image,component_image,component_image->colors,1)
379
#endif
380
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
381
0
  {
382
0
    CacheView
383
0
      *component_view;
384
385
0
    double
386
0
      M00 = 0.0,
387
0
      M01 = 0.0,
388
0
      M02 = 0.0,
389
0
      M10 = 0.0,
390
0
      M11 = 0.0,
391
0
      M20 = 0.0;
392
393
0
    PointInfo
394
0
      centroid = { 0.0, 0.0 };
395
396
0
    RectangleInfo
397
0
      bounding_box;
398
399
0
    const Quantum
400
0
      *magick_restrict p;
401
402
0
    ssize_t
403
0
      x;
404
405
0
    ssize_t
406
0
      y;
407
408
    /*
409
      Compute ellipse major axis of each object.
410
    */
411
0
    if (status == MagickFalse)
412
0
      continue;
413
0
    component_view=AcquireAuthenticCacheView(component_image,exception);
414
0
    bounding_box=object[i].bounding_box;
415
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
416
0
    {
417
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
418
0
        bounding_box.y+y,bounding_box.width,1,exception);
419
0
      if (p == (const Quantum *) NULL)
420
0
        {
421
0
          status=MagickFalse;
422
0
          break;
423
0
        }
424
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
425
0
      {
426
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
427
0
          {
428
0
            M00++;
429
0
            M10+=x;
430
0
            M01+=y;
431
0
          }
432
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
433
0
      }
434
0
    }
435
0
    centroid.x=M10*MagickSafeReciprocal(M00);
436
0
    centroid.y=M01*MagickSafeReciprocal(M00);
437
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
438
0
    {
439
0
      if (status == MagickFalse)
440
0
        continue;
441
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
442
0
        bounding_box.y+y,bounding_box.width,1,exception);
443
0
      if (p == (const Quantum *) NULL)
444
0
        {
445
0
          status=MagickFalse;
446
0
          break;
447
0
        }
448
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
449
0
      {
450
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
451
0
          {
452
0
            M11+=(x-centroid.x)*(y-centroid.y);
453
0
            M20+=(x-centroid.x)*(x-centroid.x);
454
0
            M02+=(y-centroid.y)*(y-centroid.y);
455
0
          }
456
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
457
0
      }
458
0
    }
459
0
    component_view=DestroyCacheView(component_view);
460
0
    object[i].metric[metric_index]=sqrt((2.0*MagickSafeReciprocal(M00))*
461
0
      ((M20+M02)+sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
462
0
  }
463
0
}
464
465
static void MinorAxisThreshold(const Image *component_image,
466
  CCObjectInfo *object,const ssize_t metric_index,ExceptionInfo *exception)
467
0
{
468
0
  MagickBooleanType
469
0
    status;
470
471
0
  ssize_t
472
0
    i;
473
474
0
  status=MagickTrue;
475
#if defined(MAGICKCORE_OPENMP_SUPPORT)
476
  #pragma omp parallel for schedule(dynamic) shared(status) \
477
    magick_number_threads(component_image,component_image,component_image->colors,1)
478
#endif
479
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
480
0
  {
481
0
    CacheView
482
0
      *component_view;
483
484
0
    double
485
0
      M00 = 0.0,
486
0
      M01 = 0.0,
487
0
      M02 = 0.0,
488
0
      M10 = 0.0,
489
0
      M11 = 0.0,
490
0
      M20 = 0.0;
491
492
0
    PointInfo
493
0
      centroid = { 0.0, 0.0 };
494
495
0
    RectangleInfo
496
0
      bounding_box;
497
498
0
    const Quantum
499
0
      *magick_restrict p;
500
501
0
    ssize_t
502
0
      x;
503
504
0
    ssize_t
505
0
      y;
506
507
    /*
508
      Compute ellipse major axis of each object.
509
    */
510
0
    if (status == MagickFalse)
511
0
      continue;
512
0
    component_view=AcquireAuthenticCacheView(component_image,exception);
513
0
    bounding_box=object[i].bounding_box;
514
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
515
0
    {
516
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
517
0
        bounding_box.y+y,bounding_box.width,1,exception);
518
0
      if (p == (const Quantum *) NULL)
519
0
        {
520
0
          status=MagickFalse;
521
0
          break;
522
0
        }
523
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
524
0
      {
525
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
526
0
          {
527
0
            M00++;
528
0
            M10+=x;
529
0
            M01+=y;
530
0
          }
531
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
532
0
      }
533
0
    }
534
0
    centroid.x=M10*MagickSafeReciprocal(M00);
535
0
    centroid.y=M01*MagickSafeReciprocal(M00);
536
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
537
0
    {
538
0
      if (status == MagickFalse)
539
0
        continue;
540
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
541
0
        bounding_box.y+y,bounding_box.width,1,exception);
542
0
      if (p == (const Quantum *) NULL)
543
0
        {
544
0
          status=MagickFalse;
545
0
          break;
546
0
        }
547
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
548
0
      {
549
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
550
0
          {
551
0
            M11+=(x-centroid.x)*(y-centroid.y);
552
0
            M20+=(x-centroid.x)*(x-centroid.x);
553
0
            M02+=(y-centroid.y)*(y-centroid.y);
554
0
          }
555
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
556
0
      }
557
0
    }
558
0
    component_view=DestroyCacheView(component_view);
559
0
    object[i].metric[metric_index]=sqrt((2.0*MagickSafeReciprocal(M00))*
560
0
      ((M20+M02)-sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
561
0
  }
562
0
}
563
564
static void EccentricityThreshold(const Image *component_image,
565
  CCObjectInfo *object,const ssize_t metric_index,ExceptionInfo *exception)
566
0
{
567
0
  MagickBooleanType
568
0
    status;
569
570
0
  ssize_t
571
0
    i;
572
573
0
  status=MagickTrue;
574
#if defined(MAGICKCORE_OPENMP_SUPPORT)
575
  #pragma omp parallel for schedule(dynamic) shared(status) \
576
    magick_number_threads(component_image,component_image,component_image->colors,1)
577
#endif
578
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
579
0
  {
580
0
    CacheView
581
0
      *component_view;
582
583
0
    double
584
0
      M00 = 0.0,
585
0
      M01 = 0.0,
586
0
      M02 = 0.0,
587
0
      M10 = 0.0,
588
0
      M11 = 0.0,
589
0
      M20 = 0.0;
590
591
0
    PointInfo
592
0
      centroid = { 0.0, 0.0 },
593
0
      ellipse_axis = { 0.0, 0.0 };
594
595
0
    RectangleInfo
596
0
      bounding_box;
597
598
0
    const Quantum
599
0
      *magick_restrict p;
600
601
0
    ssize_t
602
0
      x;
603
604
0
    ssize_t
605
0
      y;
606
607
    /*
608
      Compute eccentricity of each object.
609
    */
610
0
    if (status == MagickFalse)
611
0
      continue;
612
0
    component_view=AcquireAuthenticCacheView(component_image,exception);
613
0
    bounding_box=object[i].bounding_box;
614
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
615
0
    {
616
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
617
0
        bounding_box.y+y,bounding_box.width,1,exception);
618
0
      if (p == (const Quantum *) NULL)
619
0
        {
620
0
          status=MagickFalse;
621
0
          break;
622
0
        }
623
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
624
0
      {
625
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
626
0
          {
627
0
            M00++;
628
0
            M10+=x;
629
0
            M01+=y;
630
0
          }
631
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
632
0
      }
633
0
    }
634
0
    centroid.x=M10*MagickSafeReciprocal(M00);
635
0
    centroid.y=M01*MagickSafeReciprocal(M00);
636
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
637
0
    {
638
0
      if (status == MagickFalse)
639
0
        continue;
640
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
641
0
        bounding_box.y+y,bounding_box.width,1,exception);
642
0
      if (p == (const Quantum *) NULL)
643
0
        {
644
0
          status=MagickFalse;
645
0
          break;
646
0
        }
647
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
648
0
      {
649
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
650
0
          {
651
0
            M11+=(x-centroid.x)*(y-centroid.y);
652
0
            M20+=(x-centroid.x)*(x-centroid.x);
653
0
            M02+=(y-centroid.y)*(y-centroid.y);
654
0
          }
655
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
656
0
      }
657
0
    }
658
0
    component_view=DestroyCacheView(component_view);
659
0
    ellipse_axis.x=sqrt((2.0*MagickSafeReciprocal(M00))*((M20+M02)+
660
0
      sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
661
0
    ellipse_axis.y=sqrt((2.0*MagickSafeReciprocal(M00))*((M20+M02)-
662
0
      sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
663
0
    object[i].metric[metric_index]=sqrt(1.0-(ellipse_axis.y*ellipse_axis.y*
664
0
      MagickSafeReciprocal(ellipse_axis.x*ellipse_axis.x)));
665
0
  }
666
0
}
667
668
static void AngleThreshold(const Image *component_image,
669
  CCObjectInfo *object,const ssize_t metric_index,ExceptionInfo *exception)
670
0
{
671
0
  MagickBooleanType
672
0
    status;
673
674
0
  ssize_t
675
0
    i;
676
677
0
  status=MagickTrue;
678
#if defined(MAGICKCORE_OPENMP_SUPPORT)
679
  #pragma omp parallel for schedule(dynamic) shared(status) \
680
    magick_number_threads(component_image,component_image,component_image->colors,1)
681
#endif
682
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
683
0
  {
684
0
    CacheView
685
0
      *component_view;
686
687
0
    double
688
0
      M00 = 0.0,
689
0
      M01 = 0.0,
690
0
      M02 = 0.0,
691
0
      M10 = 0.0,
692
0
      M11 = 0.0,
693
0
      M20 = 0.0;
694
695
0
    PointInfo
696
0
      centroid = { 0.0, 0.0 };
697
698
0
    RectangleInfo
699
0
      bounding_box;
700
701
0
    const Quantum
702
0
      *magick_restrict p;
703
704
0
    ssize_t
705
0
      x;
706
707
0
    ssize_t
708
0
      y;
709
710
    /*
711
      Compute ellipse angle of each object.
712
    */
713
0
    if (status == MagickFalse)
714
0
      continue;
715
0
    component_view=AcquireAuthenticCacheView(component_image,exception);
716
0
    bounding_box=object[i].bounding_box;
717
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
718
0
    {
719
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
720
0
        bounding_box.y+y,bounding_box.width,1,exception);
721
0
      if (p == (const Quantum *) NULL)
722
0
        {
723
0
          status=MagickFalse;
724
0
          break;
725
0
        }
726
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
727
0
      {
728
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
729
0
          {
730
0
            M00++;
731
0
            M10+=x;
732
0
            M01+=y;
733
0
          }
734
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
735
0
      }
736
0
    }
737
0
    centroid.x=M10*MagickSafeReciprocal(M00);
738
0
    centroid.y=M01*MagickSafeReciprocal(M00);
739
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
740
0
    {
741
0
      if (status == MagickFalse)
742
0
        continue;
743
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
744
0
        bounding_box.y+y,bounding_box.width,1,exception);
745
0
      if (p == (const Quantum *) NULL)
746
0
        {
747
0
          status=MagickFalse;
748
0
          break;
749
0
        }
750
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
751
0
      {
752
0
        if ((ssize_t) GetPixelIndex(component_image,p) == i)
753
0
          {
754
0
            M11+=(x-centroid.x)*(y-centroid.y);
755
0
            M20+=(x-centroid.x)*(x-centroid.x);
756
0
            M02+=(y-centroid.y)*(y-centroid.y);
757
0
          }
758
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
759
0
      }
760
0
    }
761
0
    component_view=DestroyCacheView(component_view);
762
0
    object[i].metric[metric_index]=RadiansToDegrees(1.0/2.0*atan(2.0*M11*
763
0
      MagickSafeReciprocal(M20-M02)));
764
0
    if (fabs(M11) < 0.0)
765
0
        {
766
0
          if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
767
0
            object[i].metric[metric_index]+=90.0;
768
0
        }
769
0
      else
770
0
        if (M11 < 0.0)
771
0
          {
772
0
            if (fabs(M20-M02) >= 0.0)
773
0
              {
774
0
                if ((M20-M02) < 0.0)
775
0
                  object[i].metric[metric_index]+=90.0;
776
0
                else
777
0
                  object[i].metric[metric_index]+=180.0;
778
0
              }
779
0
          }
780
0
        else
781
0
          if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
782
0
            object[i].metric[metric_index]+=90.0;
783
0
  }
784
0
}
785
786
MagickExport Image *ConnectedComponentsImage(const Image *image,
787
  const size_t connectivity,CCObjectInfo **objects,ExceptionInfo *exception)
788
0
{
789
0
#define ConnectedComponentsImageTag  "ConnectedComponents/Image"
790
791
0
  CacheView
792
0
    *component_view,
793
0
    *image_view,
794
0
    *object_view;
795
796
0
  CCObjectInfo
797
0
    *object;
798
799
0
  char
800
0
    *c,
801
0
    *d;
802
803
0
  const char
804
0
    *artifact,
805
0
    *metrics[CCMaxMetrics];
806
807
0
  double
808
0
    max_threshold,
809
0
    min_threshold;
810
811
0
  Image
812
0
    *component_image;
813
814
0
  MagickBooleanType
815
0
    status;
816
817
0
  MagickOffsetType
818
0
    progress;
819
820
0
  MatrixInfo
821
0
    *equivalences;
822
823
0
  size_t
824
0
    *neighbors,
825
0
    size;
826
827
0
  ssize_t
828
0
    background_id,
829
0
    connect4[2][2] = { { -1,  0 }, {  0, -1 } },
830
0
    connect8[4][2] = { { -1, -1 }, { -1,  0 }, { -1,  1 }, {  0, -1 } },
831
0
    dx,
832
0
    dy,
833
0
    first,
834
0
    i,
835
0
    last,
836
0
    n,
837
0
    step,
838
0
    y;
839
840
  /*
841
    Initialize connected components image attributes.
842
  */
843
0
  assert(image != (Image *) NULL);
844
0
  assert(image->signature == MagickCoreSignature);
845
0
  assert(exception != (ExceptionInfo *) NULL);
846
0
  assert(exception->signature == MagickCoreSignature);
847
0
  if (IsEventLogging() != MagickFalse)
848
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
849
0
  if (objects != (CCObjectInfo **) NULL)
850
0
    *objects=(CCObjectInfo *) NULL;
851
0
  component_image=CloneImage(image,0,0,MagickTrue,exception);
852
0
  if (component_image == (Image *) NULL)
853
0
    return((Image *) NULL);
854
0
  component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
855
0
  if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
856
0
    {
857
0
      component_image=DestroyImage(component_image);
858
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
859
0
    }
860
  /*
861
    Initialize connected components equivalences.
862
  */
863
0
  size=image->columns*image->rows;
864
0
  if (image->columns != (size/image->rows))
865
0
    {
866
0
      component_image=DestroyImage(component_image);
867
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
868
0
    }
869
0
  equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
870
0
  if (equivalences == (MatrixInfo *) NULL)
871
0
    {
872
0
      component_image=DestroyImage(component_image);
873
0
      return((Image *) NULL);
874
0
    }
875
0
  for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
876
0
    (void) SetMatrixElement(equivalences,n,0,&n);
877
0
  object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
878
0
  if (object == (CCObjectInfo *) NULL)
879
0
    {
880
0
      equivalences=DestroyMatrixInfo(equivalences);
881
0
      component_image=DestroyImage(component_image);
882
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
883
0
    }
884
0
  (void) memset(object,0,MaxColormapSize*sizeof(*object));
885
0
  for (i=0; i < (ssize_t) MaxColormapSize; i++)
886
0
  {
887
0
    object[i].id=i;
888
0
    object[i].bounding_box.x=(ssize_t) image->columns;
889
0
    object[i].bounding_box.y=(ssize_t) image->rows;
890
0
    GetPixelInfo(image,&object[i].color);
891
0
  }
892
  /*
893
    Find connected components.
894
  */
895
0
  status=MagickTrue;
896
0
  progress=0;
897
0
  image_view=AcquireVirtualCacheView(image,exception);
898
0
  for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
899
0
  {
900
0
    if (status == MagickFalse)
901
0
      continue;
902
0
    dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
903
0
    dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
904
0
    for (y=0; y < (ssize_t) image->rows; y++)
905
0
    {
906
0
      const Quantum
907
0
        *magick_restrict p;
908
909
0
      ssize_t
910
0
        x;
911
912
0
      if (status == MagickFalse)
913
0
        continue;
914
0
      p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
915
0
      if (p == (const Quantum *) NULL)
916
0
        {
917
0
          status=MagickFalse;
918
0
          continue;
919
0
        }
920
0
      p+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
921
0
      for (x=0; x < (ssize_t) image->columns; x++)
922
0
      {
923
0
        PixelInfo
924
0
          pixel,
925
0
          target;
926
927
0
        ssize_t
928
0
          neighbor_offset,
929
0
          obj,
930
0
          offset,
931
0
          ox,
932
0
          oy,
933
0
          root;
934
935
        /*
936
          Is neighbor an authentic pixel and a different color than the pixel?
937
        */
938
0
        GetPixelInfoPixel(image,p,&pixel);
939
0
        if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
940
0
            ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
941
0
          {
942
0
            p+=(ptrdiff_t) GetPixelChannels(image);
943
0
            continue;
944
0
          }
945
0
        neighbor_offset=dy*((ssize_t) GetPixelChannels(image)*(ssize_t)
946
0
          image->columns)+dx*(ssize_t) GetPixelChannels(image);
947
0
        GetPixelInfoPixel(image,p+neighbor_offset,&target);
948
0
        if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
949
0
          {
950
0
            p+=(ptrdiff_t) GetPixelChannels(image);
951
0
            continue;
952
0
          }
953
        /*
954
          Resolve this equivalence.
955
        */
956
0
        offset=y*(ssize_t) image->columns+x;
957
0
        neighbor_offset=dy*(ssize_t) image->columns+dx;
958
0
        ox=offset;
959
0
        status=GetMatrixElement(equivalences,ox,0,&obj);
960
0
        while (obj != ox)
961
0
        {
962
0
          ox=obj;
963
0
          status=GetMatrixElement(equivalences,ox,0,&obj);
964
0
        }
965
0
        oy=offset+neighbor_offset;
966
0
        status=GetMatrixElement(equivalences,oy,0,&obj);
967
0
        while (obj != oy)
968
0
        {
969
0
          oy=obj;
970
0
          status=GetMatrixElement(equivalences,oy,0,&obj);
971
0
        }
972
0
        if (ox < oy)
973
0
          {
974
0
            status=SetMatrixElement(equivalences,oy,0,&ox);
975
0
            root=ox;
976
0
          }
977
0
        else
978
0
          {
979
0
            status=SetMatrixElement(equivalences,ox,0,&oy);
980
0
            root=oy;
981
0
          }
982
0
        ox=offset;
983
0
        status=GetMatrixElement(equivalences,ox,0,&obj);
984
0
        while (obj != root)
985
0
        {
986
0
          status=GetMatrixElement(equivalences,ox,0,&obj);
987
0
          status=SetMatrixElement(equivalences,ox,0,&root);
988
0
        }
989
0
        oy=offset+neighbor_offset;
990
0
        status=GetMatrixElement(equivalences,oy,0,&obj);
991
0
        while (obj != root)
992
0
        {
993
0
          status=GetMatrixElement(equivalences,oy,0,&obj);
994
0
          status=SetMatrixElement(equivalences,oy,0,&root);
995
0
        }
996
0
        status=SetMatrixElement(equivalences,y*(ssize_t) image->columns+x,0,
997
0
          &root);
998
0
        p+=(ptrdiff_t) GetPixelChannels(image);
999
0
      }
1000
0
    }
1001
0
  }
1002
  /*
1003
    Label connected components.
1004
  */
1005
0
  n=0;
1006
0
  component_view=AcquireAuthenticCacheView(component_image,exception);
1007
0
  for (y=0; y < (ssize_t) component_image->rows; y++)
1008
0
  {
1009
0
    const Quantum
1010
0
      *magick_restrict p;
1011
1012
0
    Quantum
1013
0
      *magick_restrict q;
1014
1015
0
    ssize_t
1016
0
      x;
1017
1018
0
    if (status == MagickFalse)
1019
0
      continue;
1020
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1021
0
    q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
1022
0
      1,exception);
1023
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1024
0
      {
1025
0
        status=MagickFalse;
1026
0
        continue;
1027
0
      }
1028
0
    for (x=0; x < (ssize_t) component_image->columns; x++)
1029
0
    {
1030
0
      ssize_t
1031
0
        id,
1032
0
        offset;
1033
1034
0
      offset=y*(ssize_t) image->columns+x;
1035
0
      status=GetMatrixElement(equivalences,offset,0,&id);
1036
0
      if (id != offset)
1037
0
        status=GetMatrixElement(equivalences,id,0,&id);
1038
0
      else
1039
0
        {
1040
0
          id=n++;
1041
0
          if (id >= (ssize_t) MaxColormapSize)
1042
0
            break;
1043
0
        }
1044
0
      status=SetMatrixElement(equivalences,offset,0,&id);
1045
0
      if (x < object[id].bounding_box.x)
1046
0
        object[id].bounding_box.x=x;
1047
0
      if (x >= (ssize_t) object[id].bounding_box.width)
1048
0
        object[id].bounding_box.width=(size_t) x;
1049
0
      if (y < object[id].bounding_box.y)
1050
0
        object[id].bounding_box.y=y;
1051
0
      if (y >= (ssize_t) object[id].bounding_box.height)
1052
0
        object[id].bounding_box.height=(size_t) y;
1053
0
      object[id].color.red+=QuantumScale*(double) GetPixelRed(image,p);
1054
0
      object[id].color.green+=QuantumScale*(double) GetPixelGreen(image,p);
1055
0
      object[id].color.blue+=QuantumScale*(double) GetPixelBlue(image,p);
1056
0
      if (image->alpha_trait != UndefinedPixelTrait)
1057
0
        object[id].color.alpha+=QuantumScale*(double) GetPixelAlpha(image,p);
1058
0
      if (image->colorspace == CMYKColorspace)
1059
0
        object[id].color.black+=QuantumScale*(double) GetPixelBlack(image,p);
1060
0
      object[id].centroid.x+=x;
1061
0
      object[id].centroid.y+=y;
1062
0
      object[id].area++;
1063
0
      SetPixelIndex(component_image,(Quantum) id,q);
1064
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1065
0
      q+=(ptrdiff_t) GetPixelChannels(component_image);
1066
0
    }
1067
0
    if (n > (ssize_t) MaxColormapSize)
1068
0
      break;
1069
0
    if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
1070
0
      status=MagickFalse;
1071
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1072
0
      {
1073
0
        MagickBooleanType
1074
0
          proceed;
1075
1076
0
        progress++;
1077
0
        proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
1078
0
          image->rows);
1079
0
        if (proceed == MagickFalse)
1080
0
          status=MagickFalse;
1081
0
      }
1082
0
  }
1083
0
  component_view=DestroyCacheView(component_view);
1084
0
  image_view=DestroyCacheView(image_view);
1085
0
  equivalences=DestroyMatrixInfo(equivalences);
1086
0
  if (n > (ssize_t) MaxColormapSize)
1087
0
    {
1088
0
      object=(CCObjectInfo *) RelinquishMagickMemory(object);
1089
0
      component_image=DestroyImage(component_image);
1090
0
      ThrowImageException(ResourceLimitError,"TooManyObjects");
1091
0
    }
1092
0
  background_id=0;
1093
0
  min_threshold=0.0;
1094
0
  max_threshold=0.0;
1095
0
  component_image->colors=(size_t) n;
1096
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
1097
0
  {
1098
0
    object[i].bounding_box.width=(size_t) ((ssize_t)
1099
0
      object[i].bounding_box.width-(object[i].bounding_box.x-1));
1100
0
    object[i].bounding_box.height=(size_t) ((ssize_t)
1101
0
      object[i].bounding_box.height-(object[i].bounding_box.y-1));
1102
0
    object[i].color.red/=(QuantumScale*object[i].area);
1103
0
    object[i].color.green/=(QuantumScale*object[i].area);
1104
0
    object[i].color.blue/=(QuantumScale*object[i].area);
1105
0
    if (image->alpha_trait != UndefinedPixelTrait)
1106
0
      object[i].color.alpha/=(QuantumScale*object[i].area);
1107
0
    if (image->colorspace == CMYKColorspace)
1108
0
      object[i].color.black/=(QuantumScale*object[i].area);
1109
0
    object[i].centroid.x/=object[i].area;
1110
0
    object[i].centroid.y/=object[i].area;
1111
0
    max_threshold+=object[i].area;
1112
0
    if (object[i].area > object[background_id].area)
1113
0
      background_id=i;
1114
0
  }
1115
0
  max_threshold+=MagickEpsilon;
1116
0
  n=(-1);
1117
0
  artifact=GetImageArtifact(image,"connected-components:background-id");
1118
0
  if (artifact != (const char *) NULL)
1119
0
    background_id=(ssize_t) StringToLong(artifact);
1120
0
  artifact=GetImageArtifact(image,"connected-components:area-threshold");
1121
0
  if (artifact != (const char *) NULL)
1122
0
    {
1123
      /*
1124
        Merge any object not within the min and max area threshold.
1125
      */
1126
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1127
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1128
0
        if (((object[i].area < min_threshold) ||
1129
0
             (object[i].area >= max_threshold)) && (i != background_id))
1130
0
          object[i].merge=MagickTrue;
1131
0
    }
1132
0
  artifact=GetImageArtifact(image,"connected-components:keep-colors");
1133
0
  if (artifact != (const char *) NULL)
1134
0
    {
1135
0
      const char
1136
0
        *p;
1137
1138
      /*
1139
        Keep selected objects based on color, merge others.
1140
      */
1141
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1142
0
        object[i].merge=MagickTrue;
1143
0
      for (p=artifact;  ; )
1144
0
      {
1145
0
        char
1146
0
          color[MagickPathExtent];
1147
1148
0
        PixelInfo
1149
0
          pixel;
1150
1151
0
        const char
1152
0
          *q;
1153
1154
0
        for (q=p; *q != '\0'; q++)
1155
0
          if (*q == ';')
1156
0
            break;
1157
0
        (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
1158
0
          MagickPathExtent));
1159
0
        (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
1160
0
        for (i=0; i < (ssize_t) component_image->colors; i++)
1161
0
          if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
1162
0
            object[i].merge=MagickFalse;
1163
0
        if (*q == '\0')
1164
0
          break;
1165
0
        p=q+1;
1166
0
      }
1167
0
    }
1168
0
  artifact=GetImageArtifact(image,"connected-components:keep-ids");
1169
0
  if (artifact == (const char *) NULL)
1170
0
    artifact=GetImageArtifact(image,"connected-components:keep");
1171
0
  if (artifact != (const char *) NULL)
1172
0
    {
1173
      /*
1174
        Keep selected objects based on id, merge others.
1175
      */
1176
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1177
0
        object[i].merge=MagickTrue;
1178
0
      for (c=(char *) artifact; *c != '\0'; )
1179
0
      {
1180
0
        while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
1181
0
          c++;
1182
0
        d=c;
1183
0
        first=(ssize_t) strtol(c,&d,10);
1184
0
        if (d == c)
1185
0
          break;
1186
0
        c=d;
1187
0
        if (first < 0)
1188
0
          first+=(ssize_t) component_image->colors;
1189
0
        last=first;
1190
0
        while (isspace((int) ((unsigned char) *c)) != 0)
1191
0
          c++;
1192
0
        if (*c == '-')
1193
0
          {
1194
0
            last=(ssize_t) strtol(c+1,&c,10);
1195
0
            if (last < 0)
1196
0
              last+=(ssize_t) component_image->colors;
1197
0
          }
1198
0
        step=(ssize_t) (first > last ? -1 : 1);
1199
0
        for ( ; first != (last+step); first+=step)
1200
0
          if ((first >= 0) &&
1201
0
              (first < (ssize_t) component_image->colors))
1202
0
            object[first].merge=MagickFalse;
1203
0
      }
1204
0
    }
1205
0
  artifact=GetImageArtifact(image,"connected-components:keep-top");
1206
0
  if (artifact != (const char *) NULL)
1207
0
    {
1208
0
      CCObjectInfo
1209
0
        *top_objects;
1210
1211
0
      ssize_t
1212
0
        top_ids;
1213
1214
      /*
1215
        Keep top objects.
1216
      */
1217
0
      top_ids=(ssize_t) StringToLong(artifact);
1218
0
      if (top_ids < 0)
1219
0
        top_ids=0;
1220
0
      if (top_ids >= (ssize_t) component_image->colors)
1221
0
        top_ids=(ssize_t) component_image->colors-1;
1222
0
      top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
1223
0
        sizeof(*top_objects));
1224
0
      if (top_objects == (CCObjectInfo *) NULL)
1225
0
        {
1226
0
          object=(CCObjectInfo *) RelinquishMagickMemory(object);
1227
0
          component_image=DestroyImage(component_image);
1228
0
          ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1229
0
        }
1230
0
      (void) memcpy(top_objects,object,component_image->colors*sizeof(*object));
1231
0
      qsort((void *) top_objects,component_image->colors,sizeof(*top_objects),
1232
0
        CCObjectInfoCompare);
1233
0
      for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
1234
0
      {
1235
0
        ssize_t id = (ssize_t) top_objects[i].id;
1236
0
        if ((id >= 0) && (id < (ssize_t) component_image->colors))
1237
0
          object[id].merge=MagickTrue;
1238
0
      }
1239
0
      top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
1240
0
    }
1241
0
  artifact=GetImageArtifact(image,"connected-components:remove-colors");
1242
0
  if (artifact != (const char *) NULL)
1243
0
    {
1244
0
      const char
1245
0
        *p;
1246
1247
      /*
1248
        Remove selected objects based on color, keep others.
1249
      */
1250
0
      for (p=artifact;  ; )
1251
0
      {
1252
0
        char
1253
0
          color[MagickPathExtent];
1254
1255
0
        PixelInfo
1256
0
          pixel;
1257
1258
0
        const char
1259
0
          *q;
1260
1261
0
        for (q=p; *q != '\0'; q++)
1262
0
          if (*q == ';')
1263
0
            break;
1264
0
        (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
1265
0
          MagickPathExtent));
1266
0
        (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
1267
0
        for (i=0; i < (ssize_t) component_image->colors; i++)
1268
0
          if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
1269
0
            object[i].merge=MagickTrue;
1270
0
        if (*q == '\0')
1271
0
          break;
1272
0
        p=q+1;
1273
0
      }
1274
0
    }
1275
0
  artifact=GetImageArtifact(image,"connected-components:remove-ids");
1276
0
  if (artifact == (const char *) NULL)
1277
0
    artifact=GetImageArtifact(image,"connected-components:remove");
1278
0
  if (artifact != (const char *) NULL)
1279
0
    for (c=(char *) artifact; *c != '\0'; )
1280
0
    {
1281
      /*
1282
        Remove selected objects based on id, keep others.
1283
      */
1284
0
      while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
1285
0
        c++;
1286
0
      d=c;
1287
0
      first=(ssize_t) strtol(c,&d,10);
1288
0
      if (d == c)
1289
0
        break;
1290
0
      c=d;
1291
0
      if (first < 0)
1292
0
        first+=(ssize_t) component_image->colors;
1293
0
      last=first;
1294
0
      while (isspace((int) ((unsigned char) *c)) != 0)
1295
0
        c++;
1296
0
      if (*c == '-')
1297
0
        {
1298
0
          last=(ssize_t) strtol(c+1,&c,10);
1299
0
          if (last < 0)
1300
0
            last+=(ssize_t) component_image->colors;
1301
0
        }
1302
0
      step=(ssize_t) (first > last ? -1 : 1);
1303
0
      for ( ; first != (last+step); first+=step)
1304
0
        if ((first >= 0) &&
1305
0
            (first < (ssize_t) component_image->colors))
1306
0
          object[first].merge=MagickTrue;
1307
0
    }
1308
0
  artifact=GetImageArtifact(image,"connected-components:perimeter-threshold");
1309
0
  if (artifact != (const char *) NULL)
1310
0
    {
1311
      /*
1312
        Merge any object not within the min and max perimeter threshold.
1313
      */
1314
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1315
0
      metrics[++n]="perimeter";
1316
0
      PerimeterThreshold(component_image,object,n,exception);
1317
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1318
0
        if (((object[i].metric[n] < min_threshold) ||
1319
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1320
0
          object[i].merge=MagickTrue;
1321
0
    }
1322
0
  artifact=GetImageArtifact(image,"connected-components:circularity-threshold");
1323
0
  if (artifact != (const char *) NULL)
1324
0
    {
1325
      /*
1326
        Merge any object not within the min and max circularity threshold.
1327
      */
1328
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1329
0
      metrics[++n]="circularity";
1330
0
      CircularityThreshold(component_image,object,n,exception);
1331
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1332
0
        if (((object[i].metric[n] < min_threshold) ||
1333
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1334
0
          object[i].merge=MagickTrue;
1335
0
    }
1336
0
  artifact=GetImageArtifact(image,"connected-components:diameter-threshold");
1337
0
  if (artifact != (const char *) NULL)
1338
0
    {
1339
      /*
1340
        Merge any object not within the min and max diameter threshold.
1341
      */
1342
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1343
0
      metrics[++n]="diameter";
1344
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1345
0
      {
1346
0
        object[i].metric[n]=ceil(sqrt(4.0*object[i].area/MagickPI)-0.5);
1347
0
        if (((object[i].metric[n] < min_threshold) ||
1348
0
            (object[i].metric[n] >= max_threshold)) && (i != background_id))
1349
0
          object[i].merge=MagickTrue;
1350
0
      }
1351
0
    }
1352
0
  artifact=GetImageArtifact(image,"connected-components:major-axis-threshold");
1353
0
  if (artifact != (const char *) NULL)
1354
0
    {
1355
      /*
1356
        Merge any object not within the min and max ellipse major threshold.
1357
      */
1358
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1359
0
      metrics[++n]="major-axis";
1360
0
      MajorAxisThreshold(component_image,object,n,exception);
1361
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1362
0
        if (((object[i].metric[n] < min_threshold) ||
1363
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1364
0
          object[i].merge=MagickTrue;
1365
0
    }
1366
0
  artifact=GetImageArtifact(image,"connected-components:minor-axis-threshold");
1367
0
  if (artifact != (const char *) NULL)
1368
0
    {
1369
      /*
1370
        Merge any object not within the min and max ellipse minor threshold.
1371
      */
1372
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1373
0
      metrics[++n]="minor-axis";
1374
0
      MinorAxisThreshold(component_image,object,n,exception);
1375
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1376
0
        if (((object[i].metric[n] < min_threshold) ||
1377
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1378
0
          object[i].merge=MagickTrue;
1379
0
    }
1380
0
  artifact=GetImageArtifact(image,"connected-components:eccentricity-threshold");
1381
0
  if (artifact != (const char *) NULL)
1382
0
    {
1383
      /*
1384
        Merge any object not within the min and max eccentricity threshold.
1385
      */
1386
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1387
0
      metrics[++n]="eccentricity";
1388
0
      EccentricityThreshold(component_image,object,n,exception);
1389
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1390
0
        if (((object[i].metric[n] < min_threshold) ||
1391
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1392
0
          object[i].merge=MagickTrue;
1393
0
    }
1394
0
  artifact=GetImageArtifact(image,"connected-components:angle-threshold");
1395
0
  if (artifact != (const char *) NULL)
1396
0
    {
1397
      /*
1398
        Merge any object not within the min and max ellipse angle threshold.
1399
      */
1400
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1401
0
      metrics[++n]="angle";
1402
0
      AngleThreshold(component_image,object,n,exception);
1403
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1404
0
        if (((object[i].metric[n] < min_threshold) ||
1405
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1406
0
          object[i].merge=MagickTrue;
1407
0
    }
1408
  /*
1409
    Merge any object not within the min and max area threshold.
1410
  */
1411
0
  neighbors=(size_t *) AcquireQuantumMemory(component_image->colors+1,
1412
0
    sizeof(*neighbors));
1413
0
  if (neighbors == (size_t *) NULL)
1414
0
    {
1415
0
      object=(CCObjectInfo *) RelinquishMagickMemory(object);
1416
0
      component_image=DestroyImage(component_image);
1417
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1418
0
    }
1419
0
  component_view=AcquireAuthenticCacheView(component_image,exception);
1420
0
  object_view=AcquireVirtualCacheView(component_image,exception);
1421
0
  (void) SetCacheViewVirtualPixelMethod(object_view,TileVirtualPixelMethod);
1422
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
1423
0
  {
1424
0
    RectangleInfo
1425
0
      bounding_box;
1426
1427
0
    size_t
1428
0
      count,
1429
0
      id;
1430
1431
0
    ssize_t
1432
0
      j;
1433
1434
0
    if (status == MagickFalse)
1435
0
      continue;
1436
0
    if ((object[i].merge == MagickFalse) || (i == background_id))
1437
0
      continue;  /* keep object */
1438
    /*
1439
      Merge this object.
1440
    */
1441
0
    count=0;
1442
0
    bounding_box=object[i].bounding_box;
1443
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
1444
0
    {
1445
0
      const Quantum
1446
0
        *magick_restrict p;
1447
1448
0
      ssize_t
1449
0
        x;
1450
1451
0
      if (status == MagickFalse)
1452
0
        continue;
1453
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1454
0
        bounding_box.y+y,bounding_box.width,1,exception);
1455
0
      if (p == (const Quantum *) NULL)
1456
0
        {
1457
0
          status=MagickFalse;
1458
0
          continue;
1459
0
        }
1460
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
1461
0
      {
1462
0
        ssize_t
1463
0
          k;
1464
1465
0
        if (status == MagickFalse)
1466
0
          continue;
1467
0
        j=(ssize_t) GetPixelIndex(component_image,p);
1468
0
        if (j == i)
1469
0
          for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
1470
0
          {
1471
0
            const Quantum
1472
0
              *q;
1473
1474
            /*
1475
              Compute area of adjacent objects.
1476
            */
1477
0
            if (status == MagickFalse)
1478
0
              continue;
1479
0
            dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
1480
0
            dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
1481
0
            q=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
1482
0
              bounding_box.y+y+dy,1,1,exception);
1483
0
            if (q == (const Quantum *) NULL)
1484
0
              {
1485
0
                status=MagickFalse;
1486
0
                break;
1487
0
              }
1488
0
            j=(ssize_t) GetPixelIndex(component_image,q);
1489
0
            if (j != i)
1490
0
              {
1491
0
                if (object[j].census == 0.0)
1492
0
                  neighbors[count++]=(size_t) j;
1493
0
                object[j].census++;
1494
0
              }
1495
0
          }
1496
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
1497
0
      }
1498
0
    }
1499
    /*
1500
      Merge with object of greatest adjacent area.
1501
    */
1502
0
    id=0;
1503
0
    for (j=0; j < (ssize_t) count; j++)
1504
0
      if ((object[neighbors[j]].census > object[id].census) ||
1505
0
          ((object[neighbors[j]].census == object[id].census) &&
1506
0
           (neighbors[j] < id)))
1507
0
        id=neighbors[j];
1508
0
    for (j=0; j < (ssize_t) count; j++)
1509
0
      object[neighbors[j]].census=0.0;
1510
0
    object[i].area=0.0;
1511
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
1512
0
    {
1513
0
      Quantum
1514
0
        *magick_restrict q;
1515
1516
0
      ssize_t
1517
0
        x;
1518
1519
0
      if (status == MagickFalse)
1520
0
        continue;
1521
0
      q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
1522
0
        bounding_box.y+y,bounding_box.width,1,exception);
1523
0
      if (q == (Quantum *) NULL)
1524
0
        {
1525
0
          status=MagickFalse;
1526
0
          continue;
1527
0
        }
1528
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
1529
0
      {
1530
0
        if ((ssize_t) GetPixelIndex(component_image,q) == i)
1531
0
          SetPixelIndex(component_image,(Quantum) id,q);
1532
0
        q+=(ptrdiff_t) GetPixelChannels(component_image);
1533
0
      }
1534
0
      if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
1535
0
        status=MagickFalse;
1536
0
    }
1537
0
  }
1538
0
  object_view=DestroyCacheView(object_view);
1539
0
  component_view=DestroyCacheView(component_view);
1540
0
  neighbors=(size_t *) RelinquishMagickMemory(neighbors);
1541
0
  artifact=GetImageArtifact(image,"connected-components:mean-color");
1542
0
  if (IsStringTrue(artifact) != MagickFalse)
1543
0
    {
1544
      /*
1545
        Replace object with mean color.
1546
      */
1547
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1548
0
        component_image->colormap[i]=object[i].color;
1549
0
    }
1550
0
  (void) SyncImage(component_image,exception);
1551
0
  artifact=GetImageArtifact(image,"connected-components:verbose");
1552
0
  if ((IsStringTrue(artifact) != MagickFalse) ||
1553
0
      (objects != (CCObjectInfo **) NULL))
1554
0
    {
1555
0
      ssize_t
1556
0
        key,
1557
0
        order;
1558
1559
      /*
1560
        Report statistics on each unique object.
1561
      */
1562
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1563
0
      {
1564
0
        object[i].bounding_box.width=0;
1565
0
        object[i].bounding_box.height=0;
1566
0
        object[i].bounding_box.x=(ssize_t) component_image->columns;
1567
0
        object[i].bounding_box.y=(ssize_t) component_image->rows;
1568
0
        object[i].centroid.x=0;
1569
0
        object[i].centroid.y=0;
1570
0
        object[i].census=object[i].area == 0.0 ? 0.0 : 1.0;
1571
0
        object[i].area=0;
1572
0
      }
1573
0
      component_view=AcquireVirtualCacheView(component_image,exception);
1574
0
      for (y=0; y < (ssize_t) component_image->rows; y++)
1575
0
      {
1576
0
        const Quantum
1577
0
          *magick_restrict p;
1578
1579
0
        ssize_t
1580
0
          x;
1581
1582
0
        if (status == MagickFalse)
1583
0
          continue;
1584
0
        p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
1585
0
          1,exception);
1586
0
        if (p == (const Quantum *) NULL)
1587
0
          {
1588
0
            status=MagickFalse;
1589
0
            continue;
1590
0
          }
1591
0
        for (x=0; x < (ssize_t) component_image->columns; x++)
1592
0
        {
1593
0
          size_t
1594
0
            id;
1595
1596
0
          id=(size_t) GetPixelIndex(component_image,p);
1597
0
          if (x < object[id].bounding_box.x)
1598
0
            object[id].bounding_box.x=x;
1599
0
          if (x > (ssize_t) object[id].bounding_box.width)
1600
0
            object[id].bounding_box.width=(size_t) x;
1601
0
          if (y < object[id].bounding_box.y)
1602
0
            object[id].bounding_box.y=y;
1603
0
          if (y > (ssize_t) object[id].bounding_box.height)
1604
0
            object[id].bounding_box.height=(size_t) y;
1605
0
          object[id].centroid.x+=x;
1606
0
          object[id].centroid.y+=y;
1607
0
          object[id].area++;
1608
0
          p+=(ptrdiff_t) GetPixelChannels(component_image);
1609
0
        }
1610
0
      }
1611
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1612
0
      {
1613
0
        object[i].bounding_box.width=(size_t) ((ssize_t)
1614
0
          object[i].bounding_box.width-(object[i].bounding_box.x-1));
1615
0
        object[i].bounding_box.height=(size_t) ((ssize_t)
1616
0
          object[i].bounding_box.height-(object[i].bounding_box.y-1));
1617
0
        object[i].centroid.x=object[i].centroid.x/object[i].area;
1618
0
        object[i].centroid.y=object[i].centroid.y/object[i].area;
1619
0
      }
1620
0
      component_view=DestroyCacheView(component_view);
1621
0
      order=1;
1622
0
      artifact=GetImageArtifact(image,"connected-components:sort-order");
1623
0
      if (artifact != (const char *) NULL)
1624
0
        if (LocaleCompare(artifact,"decreasing") == 0)
1625
0
          order=(-1);
1626
0
      key=0;
1627
0
      artifact=GetImageArtifact(image,"connected-components:sort");
1628
0
      if (artifact != (const char *) NULL)
1629
0
        {
1630
0
          if (LocaleCompare(artifact,"area") == 0)
1631
0
            key=1;
1632
0
          if (LocaleCompare(artifact,"width") == 0)
1633
0
            key=2;
1634
0
          if (LocaleCompare(artifact,"height") == 0)
1635
0
            key=3;
1636
0
          if (LocaleCompare(artifact,"x") == 0)
1637
0
            key=4;
1638
0
          if (LocaleCompare(artifact,"y") == 0)
1639
0
            key=5;
1640
0
        }
1641
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1642
0
         object[i].key=order*key;
1643
0
      qsort((void *) object,component_image->colors,sizeof(*object),
1644
0
        CCObjectInfoCompare);
1645
0
      if (objects == (CCObjectInfo **) NULL)
1646
0
        {
1647
0
          ssize_t
1648
0
            j;
1649
1650
0
          artifact=GetImageArtifact(image,
1651
0
            "connected-components:exclude-header");
1652
0
          if (IsStringTrue(artifact) == MagickFalse)
1653
0
            {
1654
0
              (void) fprintf(stdout,"Objects (");
1655
0
              artifact=GetImageArtifact(image,
1656
0
                "connected-components:exclude-ids");
1657
0
              if (IsStringTrue(artifact) == MagickFalse)
1658
0
                (void) fprintf(stdout,"id: ");
1659
0
              (void) fprintf(stdout,"bounding-box centroid area mean-color");
1660
0
              for (j=0; j <= n; j++)
1661
0
                (void) fprintf(stdout," %s",metrics[j]);
1662
0
              (void) fprintf(stdout,"):\n");
1663
0
            }
1664
0
          for (i=0; i < (ssize_t) component_image->colors; i++)
1665
0
            if (object[i].census > 0.0)
1666
0
              {
1667
0
                char
1668
0
                  mean_color[MagickPathExtent];
1669
1670
0
                GetColorTuple(&object[i].color,MagickFalse,mean_color);
1671
0
                (void) fprintf(stdout,"  ");
1672
0
                artifact=GetImageArtifact(image,
1673
0
                  "connected-components:exclude-ids");
1674
0
                if (IsStringTrue(artifact) == MagickFalse)
1675
0
                  (void) fprintf(stdout,"%.17g: ",(double) object[i].id);
1676
0
                (void) fprintf(stdout,
1677
0
                  "%.17gx%.17g%+.20g%+.20g %.1f,%.1f %.*g %s",(double)
1678
0
                  object[i].bounding_box.width,(double)
1679
0
                  object[i].bounding_box.height,(double)
1680
0
                  object[i].bounding_box.x,(double) object[i].bounding_box.y,
1681
0
                  object[i].centroid.x,object[i].centroid.y,
1682
0
                  GetMagickPrecision(),(double) object[i].area,mean_color);
1683
0
                for (j=0; j <= n; j++)
1684
0
                  (void) fprintf(stdout," %.*g",GetMagickPrecision(),
1685
0
                    object[i].metric[j]);
1686
0
                (void) fprintf(stdout,"\n");
1687
0
              }
1688
0
        }
1689
0
    }
1690
0
  if (objects == (CCObjectInfo **) NULL)
1691
0
    object=(CCObjectInfo *) RelinquishMagickMemory(object);
1692
0
  else
1693
0
    *objects=object;
1694
0
  return(component_image);
1695
0
}
1696

1697
/*
1698
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1699
%                                                                             %
1700
%                                                                             %
1701
%                                                                             %
1702
%     I n t e g r a l I m a g e                                               %
1703
%                                                                             %
1704
%                                                                             %
1705
%                                                                             %
1706
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1707
%
1708
%  IntegralImage() returns the sum of values (pixel values) in the image.
1709
%
1710
%  The format of the IntegralImage method is:
1711
%
1712
%      Image *IntegralImage(const Image *image,ExceptionInfo *exception)
1713
%
1714
%  A description of each parameter follows:
1715
%
1716
%    o image: the image.
1717
%
1718
%    o exception: return any errors or warnings in this structure.
1719
%
1720
*/
1721
MagickExport Image *IntegralImage(const Image *image,ExceptionInfo *exception)
1722
0
{
1723
0
#define IntegralImageTag  "Integral/Image"
1724
1725
0
  CacheView
1726
0
    *image_view,
1727
0
    *integral_view;
1728
1729
0
  Image
1730
0
    *integral_image;
1731
1732
0
  MagickBooleanType
1733
0
    status;
1734
1735
0
  MagickOffsetType
1736
0
    progress;
1737
1738
0
  ssize_t
1739
0
    y;
1740
1741
  /*
1742
    Initialize integral image.
1743
  */
1744
0
  assert(image != (const Image *) NULL);
1745
0
  assert(image->signature == MagickCoreSignature);
1746
0
  assert(exception != (ExceptionInfo *) NULL);
1747
0
  assert(exception->signature == MagickCoreSignature);
1748
0
  if (IsEventLogging() != MagickFalse)
1749
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1750
0
  integral_image=CloneImage(image,0,0,MagickTrue,exception);
1751
0
  if (integral_image == (Image *) NULL)
1752
0
    return((Image *) NULL);
1753
0
  if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
1754
0
    {
1755
0
      integral_image=DestroyImage(integral_image);
1756
0
      return((Image *) NULL);
1757
0
    }
1758
  /*
1759
    Calculate the sum of values (pixel values) in the image.
1760
  */
1761
0
  status=MagickTrue;
1762
0
  progress=0;
1763
0
  image_view=AcquireVirtualCacheView(integral_image,exception);
1764
0
  integral_view=AcquireAuthenticCacheView(integral_image,exception);
1765
0
  for (y=0; y < (ssize_t) integral_image->rows; y++)
1766
0
  {
1767
0
    const Quantum
1768
0
      *magick_restrict p;
1769
1770
0
    MagickBooleanType
1771
0
      sync;
1772
1773
0
    Quantum
1774
0
      *magick_restrict q;
1775
1776
0
    ssize_t
1777
0
      x;
1778
1779
0
    if (status == MagickFalse)
1780
0
      continue;
1781
0
    p=GetCacheViewVirtualPixels(integral_view,0,y-1,integral_image->columns,1,
1782
0
      exception);
1783
0
    q=GetCacheViewAuthenticPixels(integral_view,0,y,integral_image->columns,1,
1784
0
      exception);
1785
0
    if ((p == (const Quantum *) NULL) || (p == (Quantum *) NULL))
1786
0
      {
1787
0
        status=MagickFalse;
1788
0
        continue;
1789
0
      }
1790
0
    for (x=0; x < (ssize_t) integral_image->columns; x++)
1791
0
    {
1792
0
      ssize_t
1793
0
        i;
1794
1795
0
      for (i=0; i < (ssize_t) GetPixelChannels(integral_image); i++)
1796
0
      {
1797
0
        double
1798
0
          sum;
1799
1800
0
        PixelTrait traits = GetPixelChannelTraits(integral_image,
1801
0
          (PixelChannel) i);
1802
0
        if (traits == UndefinedPixelTrait)
1803
0
          continue;
1804
0
        if ((traits & CopyPixelTrait) != 0)
1805
0
          continue;
1806
0
        sum=(double) q[i];
1807
0
        if (x > 0)
1808
0
          sum+=(double) (q-GetPixelChannels(integral_image))[i];
1809
0
        if (y > 0)
1810
0
          sum+=(double) p[i];
1811
0
        if ((x > 0) && (y > 0))
1812
0
          sum-=(double) (p-GetPixelChannels(integral_image))[i];
1813
0
        q[i]=ClampToQuantum(sum);
1814
0
      }
1815
0
      p+=(ptrdiff_t) GetPixelChannels(integral_image);
1816
0
      q+=(ptrdiff_t) GetPixelChannels(integral_image);
1817
0
    }
1818
0
    sync=SyncCacheViewAuthenticPixels(integral_view,exception);
1819
0
    if (sync == MagickFalse)
1820
0
      status=MagickFalse;
1821
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1822
0
      {
1823
0
        MagickBooleanType
1824
0
          proceed;
1825
1826
0
        progress++;
1827
0
        proceed=SetImageProgress(integral_image,IntegralImageTag,progress,
1828
0
          integral_image->rows);
1829
0
        if (proceed == MagickFalse)
1830
0
          status=MagickFalse;
1831
0
      }
1832
0
  }
1833
0
  integral_view=DestroyCacheView(integral_view);
1834
0
  image_view=DestroyCacheView(image_view);
1835
0
  if (status == MagickFalse)
1836
0
    integral_image=DestroyImage(integral_image);
1837
0
  return(integral_image);
1838
0
}