/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 | } |