Coverage Report

Created: 2026-06-07 07:20

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
802
0
  const char
803
0
    *artifact,
804
0
    *metrics[CCMaxMetrics];
805
806
0
  double
807
0
    max_threshold,
808
0
    min_threshold;
809
810
0
  Image
811
0
    *component_image;
812
813
0
  MagickBooleanType
814
0
    status;
815
816
0
  MagickOffsetType
817
0
    progress;
818
819
0
  MatrixInfo
820
0
    *equivalences;
821
822
0
  size_t
823
0
    size;
824
825
0
  ssize_t
826
0
    background_id,
827
0
    connect4[2][2] = { { -1,  0 }, {  0, -1 } },
828
0
    connect8[4][2] = { { -1, -1 }, { -1,  0 }, { -1,  1 }, {  0, -1 } },
829
0
    dx,
830
0
    dy,
831
0
    first,
832
0
    i,
833
0
    last,
834
0
    n,
835
0
    step,
836
0
    y;
837
838
  /*
839
    Initialize connected components image attributes.
840
  */
841
0
  assert(image != (Image *) NULL);
842
0
  assert(image->signature == MagickCoreSignature);
843
0
  assert(exception != (ExceptionInfo *) NULL);
844
0
  assert(exception->signature == MagickCoreSignature);
845
0
  if (IsEventLogging() != MagickFalse)
846
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
847
0
  if (objects != (CCObjectInfo **) NULL)
848
0
    *objects=(CCObjectInfo *) NULL;
849
0
  component_image=CloneImage(image,0,0,MagickTrue,exception);
850
0
  if (component_image == (Image *) NULL)
851
0
    return((Image *) NULL);
852
0
  component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
853
0
  if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
854
0
    {
855
0
      component_image=DestroyImage(component_image);
856
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
857
0
    }
858
  /*
859
    Initialize connected components equivalences.
860
  */
861
0
  size=image->columns*image->rows;
862
0
  if (image->columns != (size/image->rows))
863
0
    {
864
0
      component_image=DestroyImage(component_image);
865
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
866
0
    }
867
0
  equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
868
0
  if (equivalences == (MatrixInfo *) NULL)
869
0
    {
870
0
      component_image=DestroyImage(component_image);
871
0
      return((Image *) NULL);
872
0
    }
873
0
  for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
874
0
    (void) SetMatrixElement(equivalences,n,0,&n);
875
0
  object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
876
0
  if (object == (CCObjectInfo *) NULL)
877
0
    {
878
0
      equivalences=DestroyMatrixInfo(equivalences);
879
0
      component_image=DestroyImage(component_image);
880
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
881
0
    }
882
0
  (void) memset(object,0,MaxColormapSize*sizeof(*object));
883
0
  for (i=0; i < (ssize_t) MaxColormapSize; i++)
884
0
  {
885
0
    object[i].id=i;
886
0
    object[i].bounding_box.x=(ssize_t) image->columns;
887
0
    object[i].bounding_box.y=(ssize_t) image->rows;
888
0
    GetPixelInfo(image,&object[i].color);
889
0
  }
890
  /*
891
    Find connected components.
892
  */
893
0
  status=MagickTrue;
894
0
  progress=0;
895
0
  image_view=AcquireVirtualCacheView(image,exception);
896
0
  for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
897
0
  {
898
0
    if (status == MagickFalse)
899
0
      continue;
900
0
    dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
901
0
    dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
902
0
    for (y=0; y < (ssize_t) image->rows; y++)
903
0
    {
904
0
      const Quantum
905
0
        *magick_restrict p;
906
907
0
      ssize_t
908
0
        x;
909
910
0
      if (status == MagickFalse)
911
0
        continue;
912
0
      p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
913
0
      if (p == (const Quantum *) NULL)
914
0
        {
915
0
          status=MagickFalse;
916
0
          continue;
917
0
        }
918
0
      p+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
919
0
      for (x=0; x < (ssize_t) image->columns; x++)
920
0
      {
921
0
        PixelInfo
922
0
          pixel,
923
0
          target;
924
925
0
        ssize_t
926
0
          neighbor_offset,
927
0
          obj,
928
0
          offset,
929
0
          ox,
930
0
          oy,
931
0
          root;
932
933
        /*
934
          Is neighbor an authentic pixel and a different color than the pixel?
935
        */
936
0
        GetPixelInfoPixel(image,p,&pixel);
937
0
        if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
938
0
            ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
939
0
          {
940
0
            p+=(ptrdiff_t) GetPixelChannels(image);
941
0
            continue;
942
0
          }
943
0
        neighbor_offset=dy*((ssize_t) GetPixelChannels(image)*(ssize_t)
944
0
          image->columns)+dx*(ssize_t) GetPixelChannels(image);
945
0
        GetPixelInfoPixel(image,p+neighbor_offset,&target);
946
0
        if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
947
0
          {
948
0
            p+=(ptrdiff_t) GetPixelChannels(image);
949
0
            continue;
950
0
          }
951
        /*
952
          Resolve this equivalence.
953
        */
954
0
        offset=y*(ssize_t) image->columns+x;
955
0
        neighbor_offset=dy*(ssize_t) image->columns+dx;
956
0
        ox=offset;
957
0
        status=GetMatrixElement(equivalences,ox,0,&obj);
958
0
        while (obj != ox)
959
0
        {
960
0
          ox=obj;
961
0
          status=GetMatrixElement(equivalences,ox,0,&obj);
962
0
        }
963
0
        oy=offset+neighbor_offset;
964
0
        status=GetMatrixElement(equivalences,oy,0,&obj);
965
0
        while (obj != oy)
966
0
        {
967
0
          oy=obj;
968
0
          status=GetMatrixElement(equivalences,oy,0,&obj);
969
0
        }
970
0
        if (ox < oy)
971
0
          {
972
0
            status=SetMatrixElement(equivalences,oy,0,&ox);
973
0
            root=ox;
974
0
          }
975
0
        else
976
0
          {
977
0
            status=SetMatrixElement(equivalences,ox,0,&oy);
978
0
            root=oy;
979
0
          }
980
0
        ox=offset;
981
0
        status=GetMatrixElement(equivalences,ox,0,&obj);
982
0
        while (obj != root)
983
0
        {
984
0
          status=GetMatrixElement(equivalences,ox,0,&obj);
985
0
          status=SetMatrixElement(equivalences,ox,0,&root);
986
0
        }
987
0
        oy=offset+neighbor_offset;
988
0
        status=GetMatrixElement(equivalences,oy,0,&obj);
989
0
        while (obj != root)
990
0
        {
991
0
          status=GetMatrixElement(equivalences,oy,0,&obj);
992
0
          status=SetMatrixElement(equivalences,oy,0,&root);
993
0
        }
994
0
        status=SetMatrixElement(equivalences,y*(ssize_t) image->columns+x,0,
995
0
          &root);
996
0
        p+=(ptrdiff_t) GetPixelChannels(image);
997
0
      }
998
0
    }
999
0
  }
1000
  /*
1001
    Label connected components.
1002
  */
1003
0
  n=0;
1004
0
  component_view=AcquireAuthenticCacheView(component_image,exception);
1005
0
  for (y=0; y < (ssize_t) component_image->rows; y++)
1006
0
  {
1007
0
    const Quantum
1008
0
      *magick_restrict p;
1009
1010
0
    Quantum
1011
0
      *magick_restrict q;
1012
1013
0
    ssize_t
1014
0
      x;
1015
1016
0
    if (status == MagickFalse)
1017
0
      continue;
1018
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1019
0
    q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
1020
0
      1,exception);
1021
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1022
0
      {
1023
0
        status=MagickFalse;
1024
0
        continue;
1025
0
      }
1026
0
    for (x=0; x < (ssize_t) component_image->columns; x++)
1027
0
    {
1028
0
      ssize_t
1029
0
        id,
1030
0
        offset;
1031
1032
0
      offset=y*(ssize_t) image->columns+x;
1033
0
      status=GetMatrixElement(equivalences,offset,0,&id);
1034
0
      if (id != offset)
1035
0
        status=GetMatrixElement(equivalences,id,0,&id);
1036
0
      else
1037
0
        {
1038
0
          id=n++;
1039
0
          if (id >= (ssize_t) MaxColormapSize)
1040
0
            break;
1041
0
        }
1042
0
      status=SetMatrixElement(equivalences,offset,0,&id);
1043
0
      if (x < object[id].bounding_box.x)
1044
0
        object[id].bounding_box.x=x;
1045
0
      if (x >= (ssize_t) object[id].bounding_box.width)
1046
0
        object[id].bounding_box.width=(size_t) x;
1047
0
      if (y < object[id].bounding_box.y)
1048
0
        object[id].bounding_box.y=y;
1049
0
      if (y >= (ssize_t) object[id].bounding_box.height)
1050
0
        object[id].bounding_box.height=(size_t) y;
1051
0
      object[id].color.red+=QuantumScale*(double) GetPixelRed(image,p);
1052
0
      object[id].color.green+=QuantumScale*(double) GetPixelGreen(image,p);
1053
0
      object[id].color.blue+=QuantumScale*(double) GetPixelBlue(image,p);
1054
0
      if (image->alpha_trait != UndefinedPixelTrait)
1055
0
        object[id].color.alpha+=QuantumScale*(double) GetPixelAlpha(image,p);
1056
0
      if (image->colorspace == CMYKColorspace)
1057
0
        object[id].color.black+=QuantumScale*(double) GetPixelBlack(image,p);
1058
0
      object[id].centroid.x+=x;
1059
0
      object[id].centroid.y+=y;
1060
0
      object[id].area++;
1061
0
      SetPixelIndex(component_image,(Quantum) id,q);
1062
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1063
0
      q+=(ptrdiff_t) GetPixelChannels(component_image);
1064
0
    }
1065
0
    if (n > (ssize_t) MaxColormapSize)
1066
0
      break;
1067
0
    if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
1068
0
      status=MagickFalse;
1069
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1070
0
      {
1071
0
        MagickBooleanType
1072
0
          proceed;
1073
1074
0
        progress++;
1075
0
        proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
1076
0
          image->rows);
1077
0
        if (proceed == MagickFalse)
1078
0
          status=MagickFalse;
1079
0
      }
1080
0
  }
1081
0
  component_view=DestroyCacheView(component_view);
1082
0
  image_view=DestroyCacheView(image_view);
1083
0
  equivalences=DestroyMatrixInfo(equivalences);
1084
0
  if (n > (ssize_t) MaxColormapSize)
1085
0
    {
1086
0
      object=(CCObjectInfo *) RelinquishMagickMemory(object);
1087
0
      component_image=DestroyImage(component_image);
1088
0
      ThrowImageException(ResourceLimitError,"TooManyObjects");
1089
0
    }
1090
0
  background_id=0;
1091
0
  min_threshold=0.0;
1092
0
  max_threshold=0.0;
1093
0
  component_image->colors=(size_t) n;
1094
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
1095
0
  {
1096
0
    object[i].bounding_box.width=(size_t) ((ssize_t)
1097
0
      object[i].bounding_box.width-(object[i].bounding_box.x-1));
1098
0
    object[i].bounding_box.height=(size_t) ((ssize_t)
1099
0
      object[i].bounding_box.height-(object[i].bounding_box.y-1));
1100
0
    object[i].color.red/=(QuantumScale*object[i].area);
1101
0
    object[i].color.green/=(QuantumScale*object[i].area);
1102
0
    object[i].color.blue/=(QuantumScale*object[i].area);
1103
0
    if (image->alpha_trait != UndefinedPixelTrait)
1104
0
      object[i].color.alpha/=(QuantumScale*object[i].area);
1105
0
    if (image->colorspace == CMYKColorspace)
1106
0
      object[i].color.black/=(QuantumScale*object[i].area);
1107
0
    object[i].centroid.x/=object[i].area;
1108
0
    object[i].centroid.y/=object[i].area;
1109
0
    max_threshold+=object[i].area;
1110
0
    if (object[i].area > object[background_id].area)
1111
0
      background_id=i;
1112
0
  }
1113
0
  max_threshold+=MagickEpsilon;
1114
0
  n=(-1);
1115
0
  artifact=GetImageArtifact(image,"connected-components:background-id");
1116
0
  if (artifact != (const char *) NULL)
1117
0
    background_id=(ssize_t) StringToLong(artifact);
1118
0
  artifact=GetImageArtifact(image,"connected-components:area-threshold");
1119
0
  if (artifact != (const char *) NULL)
1120
0
    {
1121
      /*
1122
        Merge any object not within the min and max area threshold.
1123
      */
1124
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1125
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1126
0
        if (((object[i].area < min_threshold) ||
1127
0
             (object[i].area >= max_threshold)) && (i != background_id))
1128
0
          object[i].merge=MagickTrue;
1129
0
    }
1130
0
  artifact=GetImageArtifact(image,"connected-components:keep-colors");
1131
0
  if (artifact != (const char *) NULL)
1132
0
    {
1133
0
      const char
1134
0
        *p;
1135
1136
      /*
1137
        Keep selected objects based on color, merge others.
1138
      */
1139
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1140
0
        object[i].merge=MagickTrue;
1141
0
      for (p=artifact;  ; )
1142
0
      {
1143
0
        char
1144
0
          color[MagickPathExtent];
1145
1146
0
        PixelInfo
1147
0
          pixel;
1148
1149
0
        const char
1150
0
          *q;
1151
1152
0
        for (q=p; *q != '\0'; q++)
1153
0
          if (*q == ';')
1154
0
            break;
1155
0
        (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
1156
0
          MagickPathExtent));
1157
0
        (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
1158
0
        for (i=0; i < (ssize_t) component_image->colors; i++)
1159
0
          if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
1160
0
            object[i].merge=MagickFalse;
1161
0
        if (*q == '\0')
1162
0
          break;
1163
0
        p=q+1;
1164
0
      }
1165
0
    }
1166
0
  artifact=GetImageArtifact(image,"connected-components:keep-ids");
1167
0
  if (artifact == (const char *) NULL)
1168
0
    artifact=GetImageArtifact(image,"connected-components:keep");
1169
0
  if (artifact != (const char *) NULL)
1170
0
    {
1171
      /*
1172
        Keep selected objects based on id, merge others.
1173
      */
1174
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1175
0
        object[i].merge=MagickTrue;
1176
0
      for (c=(char *) artifact; *c != '\0'; )
1177
0
      {
1178
0
        while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
1179
0
          c++;
1180
0
        first=(ssize_t) strtol(c,&c,10);
1181
0
        if (first < 0)
1182
0
          first+=(ssize_t) component_image->colors;
1183
0
        last=first;
1184
0
        while (isspace((int) ((unsigned char) *c)) != 0)
1185
0
          c++;
1186
0
        if (*c == '-')
1187
0
          {
1188
0
            last=(ssize_t) strtol(c+1,&c,10);
1189
0
            if (last < 0)
1190
0
              last+=(ssize_t) component_image->colors;
1191
0
          }
1192
0
        step=(ssize_t) (first > last ? -1 : 1);
1193
0
        for ( ; first != (last+step); first+=step)
1194
0
          if ((first >= 0) &&
1195
0
              (first < (ssize_t) component_image->colors))
1196
0
            object[first].merge=MagickFalse;
1197
0
      }
1198
0
    }
1199
0
  artifact=GetImageArtifact(image,"connected-components:keep-top");
1200
0
  if (artifact != (const char *) NULL)
1201
0
    {
1202
0
      CCObjectInfo
1203
0
        *top_objects;
1204
1205
0
      ssize_t
1206
0
        top_ids;
1207
1208
      /*
1209
        Keep top objects.
1210
      */
1211
0
      top_ids=(ssize_t) StringToLong(artifact);
1212
0
      if (top_ids < 0)
1213
0
        top_ids=0;
1214
0
      if (top_ids >= (ssize_t) component_image->colors)
1215
0
        top_ids=(ssize_t) component_image->colors-1;
1216
0
      top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
1217
0
        sizeof(*top_objects));
1218
0
      if (top_objects == (CCObjectInfo *) NULL)
1219
0
        {
1220
0
          object=(CCObjectInfo *) RelinquishMagickMemory(object);
1221
0
          component_image=DestroyImage(component_image);
1222
0
          ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1223
0
        }
1224
0
      (void) memcpy(top_objects,object,component_image->colors*sizeof(*object));
1225
0
      qsort((void *) top_objects,component_image->colors,sizeof(*top_objects),
1226
0
        CCObjectInfoCompare);
1227
0
      for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
1228
0
      {
1229
0
        ssize_t id = (ssize_t) top_objects[i].id;
1230
0
        if ((id >= 0) && (id < (ssize_t) component_image->colors))
1231
0
          object[id].merge=MagickTrue;
1232
0
      }
1233
0
      top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
1234
0
    }
1235
0
  artifact=GetImageArtifact(image,"connected-components:remove-colors");
1236
0
  if (artifact != (const char *) NULL)
1237
0
    {
1238
0
      const char
1239
0
        *p;
1240
1241
      /*
1242
        Remove selected objects based on color, keep others.
1243
      */
1244
0
      for (p=artifact;  ; )
1245
0
      {
1246
0
        char
1247
0
          color[MagickPathExtent];
1248
1249
0
        PixelInfo
1250
0
          pixel;
1251
1252
0
        const char
1253
0
          *q;
1254
1255
0
        for (q=p; *q != '\0'; q++)
1256
0
          if (*q == ';')
1257
0
            break;
1258
0
        (void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
1259
0
          MagickPathExtent));
1260
0
        (void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
1261
0
        for (i=0; i < (ssize_t) component_image->colors; i++)
1262
0
          if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
1263
0
            object[i].merge=MagickTrue;
1264
0
        if (*q == '\0')
1265
0
          break;
1266
0
        p=q+1;
1267
0
      }
1268
0
    }
1269
0
  artifact=GetImageArtifact(image,"connected-components:remove-ids");
1270
0
  if (artifact == (const char *) NULL)
1271
0
    artifact=GetImageArtifact(image,"connected-components:remove");
1272
0
  if (artifact != (const char *) NULL)
1273
0
    for (c=(char *) artifact; *c != '\0'; )
1274
0
    {
1275
      /*
1276
        Remove selected objects based on id, keep others.
1277
      */
1278
0
      while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
1279
0
        c++;
1280
0
      first=(ssize_t) strtol(c,&c,10);
1281
0
      if (first < 0)
1282
0
        first+=(ssize_t) component_image->colors;
1283
0
      last=first;
1284
0
      while (isspace((int) ((unsigned char) *c)) != 0)
1285
0
        c++;
1286
0
      if (*c == '-')
1287
0
        {
1288
0
          last=(ssize_t) strtol(c+1,&c,10);
1289
0
          if (last < 0)
1290
0
            last+=(ssize_t) component_image->colors;
1291
0
        }
1292
0
      step=(ssize_t) (first > last ? -1 : 1);
1293
0
      for ( ; first != (last+step); first+=step)
1294
0
        if ((first >= 0) &&
1295
0
            (first < (ssize_t) component_image->colors))
1296
0
          object[first].merge=MagickTrue;
1297
0
    }
1298
0
  artifact=GetImageArtifact(image,"connected-components:perimeter-threshold");
1299
0
  if (artifact != (const char *) NULL)
1300
0
    {
1301
      /*
1302
        Merge any object not within the min and max perimeter threshold.
1303
      */
1304
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1305
0
      metrics[++n]="perimeter";
1306
0
      PerimeterThreshold(component_image,object,n,exception);
1307
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1308
0
        if (((object[i].metric[n] < min_threshold) ||
1309
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1310
0
          object[i].merge=MagickTrue;
1311
0
    }
1312
0
  artifact=GetImageArtifact(image,"connected-components:circularity-threshold");
1313
0
  if (artifact != (const char *) NULL)
1314
0
    {
1315
      /*
1316
        Merge any object not within the min and max circularity threshold.
1317
      */
1318
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1319
0
      metrics[++n]="circularity";
1320
0
      CircularityThreshold(component_image,object,n,exception);
1321
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1322
0
        if (((object[i].metric[n] < min_threshold) ||
1323
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1324
0
          object[i].merge=MagickTrue;
1325
0
    }
1326
0
  artifact=GetImageArtifact(image,"connected-components:diameter-threshold");
1327
0
  if (artifact != (const char *) NULL)
1328
0
    {
1329
      /*
1330
        Merge any object not within the min and max diameter threshold.
1331
      */
1332
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1333
0
      metrics[++n]="diameter";
1334
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1335
0
      {
1336
0
        object[i].metric[n]=ceil(sqrt(4.0*object[i].area/MagickPI)-0.5);
1337
0
        if (((object[i].metric[n] < min_threshold) ||
1338
0
            (object[i].metric[n] >= max_threshold)) && (i != background_id))
1339
0
          object[i].merge=MagickTrue;
1340
0
      }
1341
0
    }
1342
0
  artifact=GetImageArtifact(image,"connected-components:major-axis-threshold");
1343
0
  if (artifact != (const char *) NULL)
1344
0
    {
1345
      /*
1346
        Merge any object not within the min and max ellipse major threshold.
1347
      */
1348
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1349
0
      metrics[++n]="major-axis";
1350
0
      MajorAxisThreshold(component_image,object,n,exception);
1351
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1352
0
        if (((object[i].metric[n] < min_threshold) ||
1353
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1354
0
          object[i].merge=MagickTrue;
1355
0
    }
1356
0
  artifact=GetImageArtifact(image,"connected-components:minor-axis-threshold");
1357
0
  if (artifact != (const char *) NULL)
1358
0
    {
1359
      /*
1360
        Merge any object not within the min and max ellipse minor threshold.
1361
      */
1362
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1363
0
      metrics[++n]="minor-axis";
1364
0
      MinorAxisThreshold(component_image,object,n,exception);
1365
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1366
0
        if (((object[i].metric[n] < min_threshold) ||
1367
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1368
0
          object[i].merge=MagickTrue;
1369
0
    }
1370
0
  artifact=GetImageArtifact(image,"connected-components:eccentricity-threshold");
1371
0
  if (artifact != (const char *) NULL)
1372
0
    {
1373
      /*
1374
        Merge any object not within the min and max eccentricity threshold.
1375
      */
1376
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1377
0
      metrics[++n]="eccentricity";
1378
0
      EccentricityThreshold(component_image,object,n,exception);
1379
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1380
0
        if (((object[i].metric[n] < min_threshold) ||
1381
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1382
0
          object[i].merge=MagickTrue;
1383
0
    }
1384
0
  artifact=GetImageArtifact(image,"connected-components:angle-threshold");
1385
0
  if (artifact != (const char *) NULL)
1386
0
    {
1387
      /*
1388
        Merge any object not within the min and max ellipse angle threshold.
1389
      */
1390
0
      (void) MagickSscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
1391
0
      metrics[++n]="angle";
1392
0
      AngleThreshold(component_image,object,n,exception);
1393
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1394
0
        if (((object[i].metric[n] < min_threshold) ||
1395
0
             (object[i].metric[n] >= max_threshold)) && (i != background_id))
1396
0
          object[i].merge=MagickTrue;
1397
0
    }
1398
  /*
1399
    Merge any object not within the min and max area threshold.
1400
  */
1401
0
  component_view=AcquireAuthenticCacheView(component_image,exception);
1402
0
  object_view=AcquireVirtualCacheView(component_image,exception);
1403
0
  (void) SetCacheViewVirtualPixelMethod(object_view,TileVirtualPixelMethod);
1404
0
  for (i=0; i < (ssize_t) component_image->colors; i++)
1405
0
  {
1406
0
    RectangleInfo
1407
0
      bounding_box;
1408
1409
0
    size_t
1410
0
      id;
1411
1412
0
    ssize_t
1413
0
      j;
1414
1415
0
    if (status == MagickFalse)
1416
0
      continue;
1417
0
    if ((object[i].merge == MagickFalse) || (i == background_id))
1418
0
      continue;  /* keep object */
1419
    /*
1420
      Merge this object.
1421
    */
1422
0
    for (j=0; j < (ssize_t) component_image->colors; j++)
1423
0
      object[j].census=0;
1424
0
    bounding_box=object[i].bounding_box;
1425
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
1426
0
    {
1427
0
      const Quantum
1428
0
        *magick_restrict p;
1429
1430
0
      ssize_t
1431
0
        x;
1432
1433
0
      if (status == MagickFalse)
1434
0
        continue;
1435
0
      p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
1436
0
        bounding_box.y+y,bounding_box.width,1,exception);
1437
0
      if (p == (const Quantum *) NULL)
1438
0
        {
1439
0
          status=MagickFalse;
1440
0
          continue;
1441
0
        }
1442
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
1443
0
      {
1444
0
        ssize_t
1445
0
          k;
1446
1447
0
        if (status == MagickFalse)
1448
0
          continue;
1449
0
        j=(ssize_t) GetPixelIndex(component_image,p);
1450
0
        if (j == i)
1451
0
          for (k=0; k < (ssize_t) (connectivity > 4 ? 4 : 2); k++)
1452
0
          {
1453
0
            const Quantum
1454
0
              *q;
1455
1456
            /*
1457
              Compute area of adjacent objects.
1458
            */
1459
0
            if (status == MagickFalse)
1460
0
              continue;
1461
0
            dx=connectivity > 4 ? connect8[k][1] : connect4[k][1];
1462
0
            dy=connectivity > 4 ? connect8[k][0] : connect4[k][0];
1463
0
            q=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
1464
0
              bounding_box.y+y+dy,1,1,exception);
1465
0
            if (q == (const Quantum *) NULL)
1466
0
              {
1467
0
                status=MagickFalse;
1468
0
                break;
1469
0
              }
1470
0
            j=(ssize_t) GetPixelIndex(component_image,q);
1471
0
            if (j != i)
1472
0
              object[j].census++;
1473
0
          }
1474
0
        p+=(ptrdiff_t) GetPixelChannels(component_image);
1475
0
      }
1476
0
    }
1477
    /*
1478
      Merge with object of greatest adjacent area.
1479
    */
1480
0
    id=0;
1481
0
    for (j=1; j < (ssize_t) component_image->colors; j++)
1482
0
      if (object[j].census > object[id].census)
1483
0
        id=(size_t) j;
1484
0
    object[i].area=0.0;
1485
0
    for (y=0; y < (ssize_t) bounding_box.height; y++)
1486
0
    {
1487
0
      Quantum
1488
0
        *magick_restrict q;
1489
1490
0
      ssize_t
1491
0
        x;
1492
1493
0
      if (status == MagickFalse)
1494
0
        continue;
1495
0
      q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
1496
0
        bounding_box.y+y,bounding_box.width,1,exception);
1497
0
      if (q == (Quantum *) NULL)
1498
0
        {
1499
0
          status=MagickFalse;
1500
0
          continue;
1501
0
        }
1502
0
      for (x=0; x < (ssize_t) bounding_box.width; x++)
1503
0
      {
1504
0
        if ((ssize_t) GetPixelIndex(component_image,q) == i)
1505
0
          SetPixelIndex(component_image,(Quantum) id,q);
1506
0
        q+=(ptrdiff_t) GetPixelChannels(component_image);
1507
0
      }
1508
0
      if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
1509
0
        status=MagickFalse;
1510
0
    }
1511
0
  }
1512
0
  object_view=DestroyCacheView(object_view);
1513
0
  component_view=DestroyCacheView(component_view);
1514
0
  artifact=GetImageArtifact(image,"connected-components:mean-color");
1515
0
  if (IsStringTrue(artifact) != MagickFalse)
1516
0
    {
1517
      /*
1518
        Replace object with mean color.
1519
      */
1520
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1521
0
        component_image->colormap[i]=object[i].color;
1522
0
    }
1523
0
  (void) SyncImage(component_image,exception);
1524
0
  artifact=GetImageArtifact(image,"connected-components:verbose");
1525
0
  if ((IsStringTrue(artifact) != MagickFalse) ||
1526
0
      (objects != (CCObjectInfo **) NULL))
1527
0
    {
1528
0
      ssize_t
1529
0
        key,
1530
0
        order;
1531
1532
      /*
1533
        Report statistics on each unique object.
1534
      */
1535
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1536
0
      {
1537
0
        object[i].bounding_box.width=0;
1538
0
        object[i].bounding_box.height=0;
1539
0
        object[i].bounding_box.x=(ssize_t) component_image->columns;
1540
0
        object[i].bounding_box.y=(ssize_t) component_image->rows;
1541
0
        object[i].centroid.x=0;
1542
0
        object[i].centroid.y=0;
1543
0
        object[i].census=object[i].area == 0.0 ? 0.0 : 1.0;
1544
0
        object[i].area=0;
1545
0
      }
1546
0
      component_view=AcquireVirtualCacheView(component_image,exception);
1547
0
      for (y=0; y < (ssize_t) component_image->rows; y++)
1548
0
      {
1549
0
        const Quantum
1550
0
          *magick_restrict p;
1551
1552
0
        ssize_t
1553
0
          x;
1554
1555
0
        if (status == MagickFalse)
1556
0
          continue;
1557
0
        p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
1558
0
          1,exception);
1559
0
        if (p == (const Quantum *) NULL)
1560
0
          {
1561
0
            status=MagickFalse;
1562
0
            continue;
1563
0
          }
1564
0
        for (x=0; x < (ssize_t) component_image->columns; x++)
1565
0
        {
1566
0
          size_t
1567
0
            id;
1568
1569
0
          id=(size_t) GetPixelIndex(component_image,p);
1570
0
          if (x < object[id].bounding_box.x)
1571
0
            object[id].bounding_box.x=x;
1572
0
          if (x > (ssize_t) object[id].bounding_box.width)
1573
0
            object[id].bounding_box.width=(size_t) x;
1574
0
          if (y < object[id].bounding_box.y)
1575
0
            object[id].bounding_box.y=y;
1576
0
          if (y > (ssize_t) object[id].bounding_box.height)
1577
0
            object[id].bounding_box.height=(size_t) y;
1578
0
          object[id].centroid.x+=x;
1579
0
          object[id].centroid.y+=y;
1580
0
          object[id].area++;
1581
0
          p+=(ptrdiff_t) GetPixelChannels(component_image);
1582
0
        }
1583
0
      }
1584
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1585
0
      {
1586
0
        object[i].bounding_box.width=(size_t) ((ssize_t)
1587
0
          object[i].bounding_box.width-(object[i].bounding_box.x-1));
1588
0
        object[i].bounding_box.height=(size_t) ((ssize_t)
1589
0
          object[i].bounding_box.height-(object[i].bounding_box.y-1));
1590
0
        object[i].centroid.x=object[i].centroid.x/object[i].area;
1591
0
        object[i].centroid.y=object[i].centroid.y/object[i].area;
1592
0
      }
1593
0
      component_view=DestroyCacheView(component_view);
1594
0
      order=1;
1595
0
      artifact=GetImageArtifact(image,"connected-components:sort-order");
1596
0
      if (artifact != (const char *) NULL)
1597
0
        if (LocaleCompare(artifact,"decreasing") == 0)
1598
0
          order=(-1);
1599
0
      key=0;
1600
0
      artifact=GetImageArtifact(image,"connected-components:sort");
1601
0
      if (artifact != (const char *) NULL)
1602
0
        {
1603
0
          if (LocaleCompare(artifact,"area") == 0)
1604
0
            key=1;
1605
0
          if (LocaleCompare(artifact,"width") == 0)
1606
0
            key=2;
1607
0
          if (LocaleCompare(artifact,"height") == 0)
1608
0
            key=3;
1609
0
          if (LocaleCompare(artifact,"x") == 0)
1610
0
            key=4;
1611
0
          if (LocaleCompare(artifact,"y") == 0)
1612
0
            key=5;
1613
0
        }
1614
0
      for (i=0; i < (ssize_t) component_image->colors; i++)
1615
0
         object[i].key=order*key;
1616
0
      qsort((void *) object,component_image->colors,sizeof(*object),
1617
0
        CCObjectInfoCompare);
1618
0
      if (objects == (CCObjectInfo **) NULL)
1619
0
        {
1620
0
          ssize_t
1621
0
            j;
1622
1623
0
          artifact=GetImageArtifact(image,
1624
0
            "connected-components:exclude-header");
1625
0
          if (IsStringTrue(artifact) == MagickFalse)
1626
0
            {
1627
0
              (void) fprintf(stdout,"Objects (");
1628
0
              artifact=GetImageArtifact(image,
1629
0
                "connected-components:exclude-ids");
1630
0
              if (IsStringTrue(artifact) == MagickFalse)
1631
0
                (void) fprintf(stdout,"id: ");
1632
0
              (void) fprintf(stdout,"bounding-box centroid area mean-color");
1633
0
              for (j=0; j <= n; j++)
1634
0
                (void) fprintf(stdout," %s",metrics[j]);
1635
0
              (void) fprintf(stdout,"):\n");
1636
0
            }
1637
0
          for (i=0; i < (ssize_t) component_image->colors; i++)
1638
0
            if (object[i].census > 0.0)
1639
0
              {
1640
0
                char
1641
0
                  mean_color[MagickPathExtent];
1642
1643
0
                GetColorTuple(&object[i].color,MagickFalse,mean_color);
1644
0
                (void) fprintf(stdout,"  ");
1645
0
                artifact=GetImageArtifact(image,
1646
0
                  "connected-components:exclude-ids");
1647
0
                if (IsStringTrue(artifact) == MagickFalse)
1648
0
                  (void) fprintf(stdout,"%.20g: ",(double) object[i].id);
1649
0
                (void) fprintf(stdout,
1650
0
                  "%.20gx%.20g%+.20g%+.20g %.1f,%.1f %.*g %s",(double)
1651
0
                  object[i].bounding_box.width,(double)
1652
0
                  object[i].bounding_box.height,(double)
1653
0
                  object[i].bounding_box.x,(double) object[i].bounding_box.y,
1654
0
                  object[i].centroid.x,object[i].centroid.y,
1655
0
                  GetMagickPrecision(),(double) object[i].area,mean_color);
1656
0
                for (j=0; j <= n; j++)
1657
0
                  (void) fprintf(stdout," %.*g",GetMagickPrecision(),
1658
0
                    object[i].metric[j]);
1659
0
                (void) fprintf(stdout,"\n");
1660
0
              }
1661
0
        }
1662
0
    }
1663
0
  if (objects == (CCObjectInfo **) NULL)
1664
0
    object=(CCObjectInfo *) RelinquishMagickMemory(object);
1665
0
  else
1666
0
    *objects=object;
1667
0
  return(component_image);
1668
0
}
1669

1670
/*
1671
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1672
%                                                                             %
1673
%                                                                             %
1674
%                                                                             %
1675
%     I n t e g r a l I m a g e                                               %
1676
%                                                                             %
1677
%                                                                             %
1678
%                                                                             %
1679
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1680
%
1681
%  IntegralImage() returns the sum of values (pixel values) in the image.
1682
%
1683
%  The format of the IntegralImage method is:
1684
%
1685
%      Image *IntegralImage(const Image *image,ExceptionInfo *exception)
1686
%
1687
%  A description of each parameter follows:
1688
%
1689
%    o image: the image.
1690
%
1691
%    o exception: return any errors or warnings in this structure.
1692
%
1693
*/
1694
MagickExport Image *IntegralImage(const Image *image,ExceptionInfo *exception)
1695
0
{
1696
0
#define IntegralImageTag  "Integral/Image"
1697
1698
0
  CacheView
1699
0
    *image_view,
1700
0
    *integral_view;
1701
1702
0
  Image
1703
0
    *integral_image;
1704
1705
0
  MagickBooleanType
1706
0
    status;
1707
1708
0
  MagickOffsetType
1709
0
    progress;
1710
1711
0
  ssize_t
1712
0
    y;
1713
1714
  /*
1715
    Initialize integral image.
1716
  */
1717
0
  assert(image != (const Image *) NULL);
1718
0
  assert(image->signature == MagickCoreSignature);
1719
0
  assert(exception != (ExceptionInfo *) NULL);
1720
0
  assert(exception->signature == MagickCoreSignature);
1721
0
  if (IsEventLogging() != MagickFalse)
1722
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1723
0
  integral_image=CloneImage(image,0,0,MagickTrue,exception);
1724
0
  if (integral_image == (Image *) NULL)
1725
0
    return((Image *) NULL);
1726
0
  if (SetImageStorageClass(integral_image,DirectClass,exception) == MagickFalse)
1727
0
    {
1728
0
      integral_image=DestroyImage(integral_image);
1729
0
      return((Image *) NULL);
1730
0
    }
1731
  /*
1732
    Calculate the sum of values (pixel values) in the image.
1733
  */
1734
0
  status=MagickTrue;
1735
0
  progress=0;
1736
0
  image_view=AcquireVirtualCacheView(integral_image,exception);
1737
0
  integral_view=AcquireAuthenticCacheView(integral_image,exception);
1738
0
  for (y=0; y < (ssize_t) integral_image->rows; y++)
1739
0
  {
1740
0
    const Quantum
1741
0
      *magick_restrict p;
1742
1743
0
    MagickBooleanType
1744
0
      sync;
1745
1746
0
    Quantum
1747
0
      *magick_restrict q;
1748
1749
0
    ssize_t
1750
0
      x;
1751
1752
0
    if (status == MagickFalse)
1753
0
      continue;
1754
0
    p=GetCacheViewVirtualPixels(integral_view,0,y-1,integral_image->columns,1,
1755
0
      exception);
1756
0
    q=GetCacheViewAuthenticPixels(integral_view,0,y,integral_image->columns,1,
1757
0
      exception);
1758
0
    if ((p == (const Quantum *) NULL) || (p == (Quantum *) NULL))
1759
0
      {
1760
0
        status=MagickFalse;
1761
0
        continue;
1762
0
      }
1763
0
    for (x=0; x < (ssize_t) integral_image->columns; x++)
1764
0
    {
1765
0
      ssize_t
1766
0
        i;
1767
1768
0
      for (i=0; i < (ssize_t) GetPixelChannels(integral_image); i++)
1769
0
      {
1770
0
        double
1771
0
          sum;
1772
1773
0
        PixelTrait traits = GetPixelChannelTraits(integral_image,
1774
0
          (PixelChannel) i);
1775
0
        if (traits == UndefinedPixelTrait)
1776
0
          continue;
1777
0
        if ((traits & CopyPixelTrait) != 0)
1778
0
          continue;
1779
0
        sum=(double) q[i];
1780
0
        if (x > 0)
1781
0
          sum+=(double) (q-GetPixelChannels(integral_image))[i];
1782
0
        if (y > 0)
1783
0
          sum+=(double) p[i];
1784
0
        if ((x > 0) && (y > 0))
1785
0
          sum-=(double) (p-GetPixelChannels(integral_image))[i];
1786
0
        q[i]=ClampToQuantum(sum);
1787
0
      }
1788
0
      p+=(ptrdiff_t) GetPixelChannels(integral_image);
1789
0
      q+=(ptrdiff_t) GetPixelChannels(integral_image);
1790
0
    }
1791
0
    sync=SyncCacheViewAuthenticPixels(integral_view,exception);
1792
0
    if (sync == MagickFalse)
1793
0
      status=MagickFalse;
1794
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1795
0
      {
1796
0
        MagickBooleanType
1797
0
          proceed;
1798
1799
0
        progress++;
1800
0
        proceed=SetImageProgress(integral_image,IntegralImageTag,progress,
1801
0
          integral_image->rows);
1802
0
        if (proceed == MagickFalse)
1803
0
          status=MagickFalse;
1804
0
      }
1805
0
  }
1806
0
  integral_view=DestroyCacheView(integral_view);
1807
0
  image_view=DestroyCacheView(image_view);
1808
0
  if (status == MagickFalse)
1809
0
    integral_image=DestroyImage(integral_image);
1810
0
  return(integral_image);
1811
0
}