Coverage Report

Created: 2026-09-13 07:02

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