blob: f1c7bbd0e23ce2db9d960ee0bae2bf8df987299f [file] [log] [blame]
cristy3e2860c2010-01-24 01:36:30 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% FFFFF EEEEE AAA TTTTT U U RRRR EEEEE %
7% F E A A T U U R R E %
8% FFF EEE AAAAA T U U RRRR EEE %
9% F E A A T U U R R E %
10% F EEEEE A A T UUU R R EEEEE %
11% %
12% %
13% MagickCore Image Feature Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
20% Copyright 1999-2010 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% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/property.h"
45#include "magick/animate.h"
46#include "magick/blob.h"
47#include "magick/blob-private.h"
48#include "magick/cache.h"
49#include "magick/cache-private.h"
50#include "magick/cache-view.h"
51#include "magick/client.h"
52#include "magick/color.h"
53#include "magick/color-private.h"
54#include "magick/colorspace.h"
55#include "magick/colorspace-private.h"
56#include "magick/composite.h"
57#include "magick/composite-private.h"
58#include "magick/compress.h"
59#include "magick/constitute.h"
60#include "magick/deprecate.h"
61#include "magick/display.h"
62#include "magick/draw.h"
63#include "magick/enhance.h"
64#include "magick/exception.h"
65#include "magick/exception-private.h"
66#include "magick/feature.h"
67#include "magick/gem.h"
68#include "magick/geometry.h"
69#include "magick/list.h"
70#include "magick/image-private.h"
71#include "magick/magic.h"
72#include "magick/magick.h"
73#include "magick/memory_.h"
74#include "magick/module.h"
75#include "magick/monitor.h"
76#include "magick/monitor-private.h"
77#include "magick/option.h"
78#include "magick/paint.h"
79#include "magick/pixel-private.h"
80#include "magick/profile.h"
81#include "magick/quantize.h"
82#include "magick/random_.h"
83#include "magick/segment.h"
84#include "magick/semaphore.h"
85#include "magick/signature-private.h"
86#include "magick/string_.h"
87#include "magick/thread-private.h"
88#include "magick/timer.h"
89#include "magick/utility.h"
90#include "magick/version.h"
91
92/*
93%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94% %
95% %
96% %
97% G e t I m a g e C h a n n e l F e a t u r e s %
98% %
99% %
100% %
101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102%
cristy7e9726d2010-01-26 02:08:40 +0000103% GetImageChannelFeatures() returns features for each channel in the image at
104% at 0, 45, 90, and 135 degrees for the specified distance. The features
105% include the angular second momentum, contrast, correlation, sum of squares:
106% variance, inverse difference moment, sum average, sum varience, sum entropy,
107% entropy, difference variance, difference entropy, information measures of
108% correlation 1, information measures of correlation 2, and maximum
109% correlation coefficient. You can access the red channel contrast, for
110% example, like this:
cristy3e2860c2010-01-24 01:36:30 +0000111%
cristy7e9726d2010-01-26 02:08:40 +0000112% channel_features=GetImageChannelFeatures(image,1,excepton);
cristy3e2860c2010-01-24 01:36:30 +0000113% contrast=channel_features[RedChannel].contrast;
114%
115% Use MagickRelinquishMemory() to free the features buffer.
116%
117% The format of the GetImageChannelFeatures method is:
118%
119% ChannelFeatures *GetImageChannelFeatures(const Image *image,
120% ExceptionInfo *exception)
121%
122% A description of each parameter follows:
123%
124% o image: the image.
125%
cristy7e9726d2010-01-26 02:08:40 +0000126% o distance: the distance.
127%
cristy3e2860c2010-01-24 01:36:30 +0000128% o exception: return any errors or warnings in this structure.
129%
130*/
131MagickExport ChannelFeatures *GetImageChannelFeatures(const Image *image,
cristy7e9726d2010-01-26 02:08:40 +0000132 const unsigned long distance,ExceptionInfo *exception)
cristy3e2860c2010-01-24 01:36:30 +0000133{
cristyf2bf2c72010-01-25 19:54:15 +0000134 typedef struct _SpatialDependenceMatrix
135 {
136 LongPixelPacket
cristy7e9726d2010-01-26 02:08:40 +0000137 tones[4];
cristyf2bf2c72010-01-25 19:54:15 +0000138 } SpatialDependenceMatrix;
139
cristy2070fa52010-01-24 03:17:57 +0000140 CacheView
141 *image_view;
142
cristy3e2860c2010-01-24 01:36:30 +0000143 ChannelFeatures
144 *channel_features;
145
cristy2070fa52010-01-24 03:17:57 +0000146 LongPixelPacket
cristyf2bf2c72010-01-25 19:54:15 +0000147 tone,
148 *tones;
cristy2070fa52010-01-24 03:17:57 +0000149
cristy3e2860c2010-01-24 01:36:30 +0000150 long
151 y;
152
cristy2070fa52010-01-24 03:17:57 +0000153 MagickBooleanType
154 status;
155
156 register long
157 i;
158
cristy3e2860c2010-01-24 01:36:30 +0000159 size_t
160 length;
161
cristyf2bf2c72010-01-25 19:54:15 +0000162 SpatialDependenceMatrix
163 **pixels;
164
165 unsigned long
166 number_tones;
167
cristy3e2860c2010-01-24 01:36:30 +0000168 assert(image != (Image *) NULL);
169 assert(image->signature == MagickSignature);
170 if (image->debug != MagickFalse)
171 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy7e9726d2010-01-26 02:08:40 +0000172 if ((image->columns < (distance+1)) || (image->rows < (distance+1)))
173 return((ChannelFeatures *) NULL);
cristy3e2860c2010-01-24 01:36:30 +0000174 length=AllChannels+1UL;
175 channel_features=(ChannelFeatures *) AcquireQuantumMemory(length,
176 sizeof(*channel_features));
177 if (channel_features == (ChannelFeatures *) NULL)
178 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
179 (void) ResetMagickMemory(channel_features,0,length*
180 sizeof(*channel_features));
cristy2070fa52010-01-24 03:17:57 +0000181 /*
cristyf2bf2c72010-01-25 19:54:15 +0000182 Form tones.
cristy2070fa52010-01-24 03:17:57 +0000183 */
cristyf2bf2c72010-01-25 19:54:15 +0000184 tones=(LongPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*tones));
185 if (tones == (LongPixelPacket *) NULL)
cristy2070fa52010-01-24 03:17:57 +0000186 {
187 (void) ThrowMagickException(exception,GetMagickModule(),
188 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
189 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
190 channel_features);
191 return(channel_features);
192 }
193 for (i=0; i <= (long) MaxMap; i++)
194 {
cristyf2bf2c72010-01-25 19:54:15 +0000195 tones[i].red=(~0UL);
196 tones[i].green=(~0UL);
197 tones[i].blue=(~0UL);
198 tones[i].opacity=(~0UL);
199 tones[i].index=(~0UL);
cristy2070fa52010-01-24 03:17:57 +0000200 }
201 status=MagickTrue;
202 image_view=AcquireCacheView(image);
203#if defined(MAGICKCORE_OPENMP_SUPPORT)
204 #pragma omp parallel for schedule(dynamic,4) shared(status)
205#endif
cristy3e2860c2010-01-24 01:36:30 +0000206 for (y=0; y < (long) image->rows; y++)
207 {
208 register const IndexPacket
209 *restrict indexes;
210
211 register const PixelPacket
212 *restrict p;
213
214 register long
215 x;
216
cristy2070fa52010-01-24 03:17:57 +0000217 if (status == MagickFalse)
218 continue;
219 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy3e2860c2010-01-24 01:36:30 +0000220 if (p == (const PixelPacket *) NULL)
cristy2070fa52010-01-24 03:17:57 +0000221 {
222 status=MagickFalse;
223 continue;
224 }
225 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3e2860c2010-01-24 01:36:30 +0000226 for (x=0; x < (long) image->columns; x++)
227 {
cristyf2bf2c72010-01-25 19:54:15 +0000228 tones[ScaleQuantumToMap(p->red)].red=ScaleQuantumToMap(p->red);
229 tones[ScaleQuantumToMap(p->green)].green=ScaleQuantumToMap(p->green);
230 tones[ScaleQuantumToMap(p->blue)].blue=ScaleQuantumToMap(p->blue);
cristy2070fa52010-01-24 03:17:57 +0000231 if (image->matte != MagickFalse)
cristyf2bf2c72010-01-25 19:54:15 +0000232 tones[ScaleQuantumToMap(p->opacity)].opacity=
cristy2070fa52010-01-24 03:17:57 +0000233 ScaleQuantumToMap(p->opacity);
234 if (image->colorspace == CMYKColorspace)
cristyf2bf2c72010-01-25 19:54:15 +0000235 tones[ScaleQuantumToMap(indexes[x])].index=
cristy2070fa52010-01-24 03:17:57 +0000236 ScaleQuantumToMap(indexes[x]);
cristy3e2860c2010-01-24 01:36:30 +0000237 p++;
238 }
239 }
cristy30c510a2010-01-24 03:43:00 +0000240 image_view=DestroyCacheView(image_view);
241 if (status == MagickFalse)
242 {
cristyf2bf2c72010-01-25 19:54:15 +0000243 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
cristy30c510a2010-01-24 03:43:00 +0000244 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
245 channel_features);
246 return(channel_features);
247 }
cristyf2bf2c72010-01-25 19:54:15 +0000248 (void) ResetMagickMemory(&tone,0,sizeof(tone));
cristy2070fa52010-01-24 03:17:57 +0000249 for (i=0; i <= (long) MaxMap; i++)
250 {
cristyf2bf2c72010-01-25 19:54:15 +0000251 if (tones[i].red != ~0UL)
252 tones[tone.red++].red=tones[i].red;
253 if (tones[i].green != ~0UL)
254 tones[tone.green++].green=tones[i].green;
255 if (tones[i].blue != ~0UL)
256 tones[tone.blue++].blue=tones[i].blue;
cristy2070fa52010-01-24 03:17:57 +0000257 if (image->matte != MagickFalse)
cristyf2bf2c72010-01-25 19:54:15 +0000258 if (tones[i].opacity != ~0UL)
259 tones[tone.opacity++].opacity=tones[i].opacity;
cristy2070fa52010-01-24 03:17:57 +0000260 if (image->colorspace == CMYKColorspace)
cristyf2bf2c72010-01-25 19:54:15 +0000261 if (tones[i].index != ~0UL)
262 tones[tone.index++].index=tones[i].index;
cristy2070fa52010-01-24 03:17:57 +0000263 }
cristyf2bf2c72010-01-25 19:54:15 +0000264 /*
265 Allocate spatial dependence matrix.
266 */
267 number_tones=tone.red;
268 if (tone.green > number_tones)
269 number_tones=tone.green;
270 if (tone.blue > number_tones)
271 number_tones=tone.blue;
272 if (image->matte != MagickFalse)
273 if (tone.opacity > number_tones)
274 number_tones=tone.opacity;
275 if (image->colorspace == CMYKColorspace)
276 if (tone.index > number_tones)
277 number_tones=tone.index;
278 pixels=(SpatialDependenceMatrix **) AcquireQuantumMemory(number_tones,
cristy7e9726d2010-01-26 02:08:40 +0000279 sizeof(*pixels));
cristyf2bf2c72010-01-25 19:54:15 +0000280 if (pixels == (SpatialDependenceMatrix **) NULL)
281 {
282 (void) ThrowMagickException(exception,GetMagickModule(),
283 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
284 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
285 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
286 channel_features);
287 return(channel_features);
288 }
cristy7e9726d2010-01-26 02:08:40 +0000289 for (i=0; i < (long) number_tones; i++)
cristyf2bf2c72010-01-25 19:54:15 +0000290 {
291 pixels[i]=(SpatialDependenceMatrix *) AcquireQuantumMemory(number_tones,
cristy7e9726d2010-01-26 02:08:40 +0000292 sizeof(**pixels));
cristyf2bf2c72010-01-25 19:54:15 +0000293 if (pixels[i] == (SpatialDependenceMatrix *) NULL)
294 break;
295 (void) ResetMagickMemory(pixels[i],0,number_tones*sizeof(*pixels));
296 }
cristy7e9726d2010-01-26 02:08:40 +0000297 if (i < (long) number_tones)
cristyf2bf2c72010-01-25 19:54:15 +0000298 {
299 (void) ThrowMagickException(exception,GetMagickModule(),
300 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
301 for (i--; i >= 0; i--)
302 pixels[i]=(SpatialDependenceMatrix *) RelinquishMagickMemory(pixels[i]);
303 pixels=(SpatialDependenceMatrix **) RelinquishMagickMemory(pixels);
304 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
305 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
306 channel_features);
307 return(channel_features);
308 }
309 /*
310 Initialize spatial dependence matrix.
311 */
312 status=MagickTrue;
313 image_view=AcquireCacheView(image);
314#if defined(MAGICKCORE_OPENMP_SUPPORT)
315 #pragma omp parallel for schedule(dynamic,4) shared(status)
316#endif
317 for (y=0; y < (long) image->rows; y++)
318 {
319 long
320 u,
321 v;
322
323 register const IndexPacket
324 *restrict indexes;
325
326 register const PixelPacket
327 *restrict p;
328
329 register long
330 x;
331
cristy7e9726d2010-01-26 02:08:40 +0000332 ssize_t
333 offset;
334
cristyf2bf2c72010-01-25 19:54:15 +0000335 if (status == MagickFalse)
336 continue;
cristy7e9726d2010-01-26 02:08:40 +0000337 p=GetCacheViewVirtualPixels(image_view,-(long) distance,y,image->columns+
338 2*distance,distance+1,exception);
cristyf2bf2c72010-01-25 19:54:15 +0000339 if (p == (const PixelPacket *) NULL)
340 {
341 status=MagickFalse;
342 continue;
343 }
344 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy7e9726d2010-01-26 02:08:40 +0000345 p+=distance;
346 indexes+=distance;
cristyf2bf2c72010-01-25 19:54:15 +0000347 for (x=0; x < (long) image->columns; x++)
348 {
349 for (i=0; i < 4; i++)
350 {
cristy7e9726d2010-01-26 02:08:40 +0000351 switch (i)
352 {
353 case 0:
354 {
355 /*
356 0 degrees.
357 */
358 offset=(ssize_t) distance;
359 break;
360 }
361 case 1:
362 {
363 /*
364 45 degrees.
365 */
366 offset=(ssize_t) (image->columns+2*distance)-distance;
367 break;
368 }
369 case 2:
370 {
371 /*
372 90 degrees.
373 */
374 offset=(ssize_t) (image->columns+2*distance);
375 break;
376 }
377 case 3:
378 {
379 /*
380 135 degrees.
381 */
382 offset=(ssize_t) (image->columns+2*distance)+distance;
383 break;
384 }
385 }
386 u=0;
387 v=0;
388 while (tones[u].red != ScaleQuantumToMap(p->red))
389 u++;
390 while (tones[v].red != ScaleQuantumToMap((p+offset)->red))
391 v++;
392 pixels[u][v].tones[i].red++;
393 pixels[v][u].tones[i].red++;
394 u=0;
395 v=0;
396 while (tones[u].green != ScaleQuantumToMap(p->green))
397 u++;
398 while (tones[v].green != ScaleQuantumToMap((p+offset)->green))
399 v++;
400 pixels[u][v].tones[i].green++;
401 pixels[v][u].tones[i].green++;
402 u=0;
403 v=0;
404 while (tones[u].blue != ScaleQuantumToMap(p->blue))
405 u++;
406 while (tones[v].blue != ScaleQuantumToMap((p+offset)->blue))
407 v++;
408 pixels[u][v].tones[i].blue++;
409 pixels[v][u].tones[i].blue++;
410 if (image->matte != MagickFalse)
411 {
412 u=0;
413 v=0;
414 while (tones[u].opacity != ScaleQuantumToMap(p->opacity))
415 u++;
416 while (tones[v].opacity != ScaleQuantumToMap((p+offset)->opacity))
417 v++;
418 pixels[u][v].tones[i].opacity++;
419 pixels[v][u].tones[i].opacity++;
420 }
421 if (image->colorspace == CMYKColorspace)
422 {
423 u=0;
424 v=0;
425 while (tones[u].index != ScaleQuantumToMap(indexes[x]))
426 u++;
427 while (tones[v].index != ScaleQuantumToMap(indexes[x+offset]))
428 v++;
429 pixels[u][v].tones[i].index++;
430 pixels[v][u].tones[i].index++;
431 }
cristyf2bf2c72010-01-25 19:54:15 +0000432 }
cristy7e9726d2010-01-26 02:08:40 +0000433 p++;
cristyf2bf2c72010-01-25 19:54:15 +0000434 }
435 }
436 image_view=DestroyCacheView(image_view);
437 if (status == MagickFalse)
438 {
439 (void) ThrowMagickException(exception,GetMagickModule(),
440 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
cristy7e9726d2010-01-26 02:08:40 +0000441 for (i=0; i < (long) number_tones; i++)
cristyf2bf2c72010-01-25 19:54:15 +0000442 pixels[i]=(SpatialDependenceMatrix *) RelinquishMagickMemory(pixels[i]);
443 pixels=(SpatialDependenceMatrix **) RelinquishMagickMemory(pixels);
444 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
445 channel_features=(ChannelFeatures *) RelinquishMagickMemory(
446 channel_features);
447 return(channel_features);
448 }
449 /*
cristy7e9726d2010-01-26 02:08:40 +0000450 Normalize spatial dependence matrix.
451 */
452#if defined(MAGICKCORE_OPENMP_SUPPORT)
453 #pragma omp parallel for schedule(dynamic,4) shared(status)
454#endif
455 for (y=0; y < (long) number_tones; y++)
456 {
457 register long
458 x;
459
460 unsigned long
461 normalize;
462
463 for (x=0; x < (long) number_tones; x++)
464 {
465 for (i=0; i < 4; i++)
466 {
467 switch (i)
468 {
469 case 0:
470 {
471 /*
472 0 degrees.
473 */
474 normalize=2*image->rows*(image->columns-distance);
475 break;
476 }
477 case 1:
478 {
479 /*
480 45 degrees.
481 */
482 normalize=2*(image->rows-distance)*(image->columns-distance);
483 break;
484 }
485 case 2:
486 {
487 /*
488 90 degrees.
489 */
490 normalize=2*(image->rows-distance)*image->columns;
491 break;
492 }
493 case 3:
494 {
495 /*
496 135 degrees.
497 */
498 normalize=2*(image->rows-distance)*(image->columns-distance);
499 break;
500 }
501 }
502 pixels[x][y].tones[i].red/=normalize;
503 }
504 }
505 }
506 /*
cristyf2bf2c72010-01-25 19:54:15 +0000507 Relinquish resources.
508 */
cristy7e9726d2010-01-26 02:08:40 +0000509 for (i=0; i < (long) number_tones; i++)
cristyf2bf2c72010-01-25 19:54:15 +0000510 pixels[i]=(SpatialDependenceMatrix *) RelinquishMagickMemory(pixels[i]);
511 pixels=(SpatialDependenceMatrix **) RelinquishMagickMemory(pixels);
512 tones=(LongPixelPacket *) RelinquishMagickMemory(tones);
cristy3e2860c2010-01-24 01:36:30 +0000513 return(channel_features);
514}