blob: 72c493f52835c356ef9d9e69afa2f619a406eb53 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% SSSSS TTTTT AAA TTTTT IIIII SSSSS TTTTT IIIII CCCC %
7% SS T A A T I SS T I C %
8% SSS T AAAAA T I SSS T I C %
9% SS T A A T I SS T I C %
10% SSSSS T A A T IIIII SSSSS T IIIII CCCC %
11% %
12% %
cristy3e2860c2010-01-24 01:36:30 +000013% MagickCore Image Statistical Methods %
cristy3ed852e2009-09-05 21:47:34 +000014% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% 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/gem.h"
67#include "magick/geometry.h"
68#include "magick/list.h"
69#include "magick/image-private.h"
70#include "magick/magic.h"
71#include "magick/magick.h"
72#include "magick/memory_.h"
73#include "magick/module.h"
74#include "magick/monitor.h"
cristyacd5a392009-09-18 00:04:22 +000075#include "magick/monitor-private.h"
cristy3ed852e2009-09-05 21:47:34 +000076#include "magick/option.h"
77#include "magick/paint.h"
78#include "magick/pixel-private.h"
79#include "magick/profile.h"
80#include "magick/quantize.h"
81#include "magick/random_.h"
cristy351842f2010-03-07 15:27:38 +000082#include "magick/random-private.h"
cristy3ed852e2009-09-05 21:47:34 +000083#include "magick/segment.h"
84#include "magick/semaphore.h"
85#include "magick/signature-private.h"
86#include "magick/statistic.h"
87#include "magick/string_.h"
88#include "magick/thread-private.h"
89#include "magick/timer.h"
90#include "magick/utility.h"
91#include "magick/version.h"
92
93/*
94%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95% %
96% %
97% %
cristyd18ae7c2010-03-07 17:39:52 +000098% E v a l u a t e I m a g e %
cristy316d5172009-09-17 19:31:25 +000099% %
100% %
101% %
102%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
103%
cristyd18ae7c2010-03-07 17:39:52 +0000104% EvaluateImage() applies a value to the image with an arithmetic, relational,
105% or logical operator to an image. Use these operations to lighten or darken
106% an image, to increase or decrease contrast in an image, or to produce the
107% "negative" of an image.
cristy316d5172009-09-17 19:31:25 +0000108%
cristyd18ae7c2010-03-07 17:39:52 +0000109% The format of the EvaluateImageChannel method is:
cristy316d5172009-09-17 19:31:25 +0000110%
cristyd18ae7c2010-03-07 17:39:52 +0000111% MagickBooleanType EvaluateImage(Image *image,
112% const MagickEvaluateOperator op,const double value,
113% ExceptionInfo *exception)
114% MagickBooleanType EvaluateImages(Image *images,
115% const MagickEvaluateOperator op,const double value,
116% ExceptionInfo *exception)
117% MagickBooleanType EvaluateImageChannel(Image *image,
118% const ChannelType channel,const MagickEvaluateOperator op,
119% const double value,ExceptionInfo *exception)
cristy316d5172009-09-17 19:31:25 +0000120%
121% A description of each parameter follows:
122%
cristyd18ae7c2010-03-07 17:39:52 +0000123% o image: the image.
124%
125% o channel: the channel.
126%
127% o op: A channel op.
128%
129% o value: A value value.
cristy316d5172009-09-17 19:31:25 +0000130%
131% o exception: return any errors or warnings in this structure.
132%
133*/
134
135static MagickPixelPacket **DestroyPixelThreadSet(MagickPixelPacket **pixels)
136{
137 register long
138 i;
139
140 assert(pixels != (MagickPixelPacket **) NULL);
141 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
142 if (pixels[i] != (MagickPixelPacket *) NULL)
143 pixels[i]=(MagickPixelPacket *) RelinquishMagickMemory(pixels[i]);
144 pixels=(MagickPixelPacket **) RelinquishAlignedMemory(pixels);
145 return(pixels);
146}
147
148static MagickPixelPacket **AcquirePixelThreadSet(const Image *image)
149{
150 register long
151 i,
152 j;
153
154 MagickPixelPacket
155 **pixels;
156
157 unsigned long
158 number_threads;
159
160 number_threads=GetOpenMPMaximumThreads();
161 pixels=(MagickPixelPacket **) AcquireAlignedMemory(number_threads,
162 sizeof(*pixels));
163 if (pixels == (MagickPixelPacket **) NULL)
164 return((MagickPixelPacket **) NULL);
165 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
166 for (i=0; i < (long) number_threads; i++)
167 {
168 pixels[i]=(MagickPixelPacket *) AcquireQuantumMemory(image->columns,
169 sizeof(**pixels));
170 if (pixels[i] == (MagickPixelPacket *) NULL)
171 return(DestroyPixelThreadSet(pixels));
172 for (j=0; j < (long) image->columns; j++)
173 GetMagickPixelPacket(image,&pixels[i][j]);
174 }
175 return(pixels);
176}
177
cristy351842f2010-03-07 15:27:38 +0000178static inline double MagickMax(const double x,const double y)
179{
180 if (x > y)
181 return(x);
182 return(y);
183}
184
185static inline double MagickMin(const double x,const double y)
186{
187 if (x < y)
188 return(x);
189 return(y);
190}
191
cristyd18ae7c2010-03-07 17:39:52 +0000192static MagickRealType ApplyEvaluateOperator(RandomInfo *random_info,
193 Quantum pixel,const MagickEvaluateOperator op,const MagickRealType value)
cristy351842f2010-03-07 15:27:38 +0000194{
195 MagickRealType
196 result;
197
198 result=0.0;
199 switch (op)
200 {
201 case UndefinedEvaluateOperator:
202 break;
203 case AddEvaluateOperator:
204 {
205 result=(MagickRealType) (pixel+value);
206 break;
207 }
208 case AddModulusEvaluateOperator:
209 {
210 /*
211 This returns a 'floored modulus' of the addition which is a
212 positive result. It differs from % or fmod() which returns a
213 'truncated modulus' result, where floor() is replaced by trunc()
214 and could return a negative result (which is clipped).
215 */
216 result=pixel+value;
217 result-=(QuantumRange+1)*floor(result/(QuantumRange+1));
218 break;
219 }
220 case AndEvaluateOperator:
221 {
222 result=(MagickRealType) ((unsigned long) pixel & (unsigned long)
223 (value+0.5));
224 break;
225 }
226 case CosineEvaluateOperator:
227 {
228 result=(MagickRealType) (QuantumRange*(0.5*cos((double) (2.0*MagickPI*
229 QuantumScale*pixel*value))+0.5));
230 break;
231 }
232 case DivideEvaluateOperator:
233 {
234 result=pixel/(value == 0.0 ? 1.0 : value);
235 break;
236 }
237 case GaussianNoiseEvaluateOperator:
238 {
239 result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
240 GaussianNoise,value);
241 break;
242 }
243 case ImpulseNoiseEvaluateOperator:
244 {
245 result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
246 ImpulseNoise,value);
247 break;
248 }
249 case LaplacianNoiseEvaluateOperator:
250 {
251 result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
252 LaplacianNoise,value);
253 break;
254 }
255 case LeftShiftEvaluateOperator:
256 {
257 result=(MagickRealType) ((unsigned long) pixel << (unsigned long)
258 (value+0.5));
259 break;
260 }
261 case LogEvaluateOperator:
262 {
263 result=(MagickRealType) (QuantumRange*log((double) (QuantumScale*value*
264 pixel+1.0))/log((double) (value+1.0)));
265 break;
266 }
267 case MaxEvaluateOperator:
268 {
269 result=(MagickRealType) MagickMax((double) pixel,value);
270 break;
271 }
272 case MeanEvaluateOperator:
273 {
274 result=(MagickRealType) ((pixel+value)/2.0);
275 break;
276 }
277 case MinEvaluateOperator:
278 {
279 result=(MagickRealType) MagickMin((double) pixel,value);
280 break;
281 }
282 case MultiplicativeNoiseEvaluateOperator:
283 {
284 result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
285 MultiplicativeGaussianNoise,value);
286 break;
287 }
288 case MultiplyEvaluateOperator:
289 {
290 result=(MagickRealType) (value*pixel);
291 break;
292 }
293 case OrEvaluateOperator:
294 {
295 result=(MagickRealType) ((unsigned long) pixel | (unsigned long)
296 (value+0.5));
297 break;
298 }
299 case PoissonNoiseEvaluateOperator:
300 {
301 result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
302 PoissonNoise,value);
303 break;
304 }
305 case PowEvaluateOperator:
306 {
307 result=(MagickRealType) (QuantumRange*pow((double) (QuantumScale*pixel),
308 (double) value));
309 break;
310 }
311 case RightShiftEvaluateOperator:
312 {
313 result=(MagickRealType) ((unsigned long) pixel >> (unsigned long)
314 (value+0.5));
315 break;
316 }
317 case SetEvaluateOperator:
318 {
319 result=value;
320 break;
321 }
322 case SineEvaluateOperator:
323 {
324 result=(MagickRealType) (QuantumRange*(0.5*sin((double) (2.0*MagickPI*
325 QuantumScale*pixel*value))+0.5));
326 break;
327 }
328 case SubtractEvaluateOperator:
329 {
330 result=(MagickRealType) (pixel-value);
331 break;
332 }
333 case ThresholdEvaluateOperator:
334 {
335 result=(MagickRealType) (((MagickRealType) pixel <= value) ? 0 :
336 QuantumRange);
337 break;
338 }
339 case ThresholdBlackEvaluateOperator:
340 {
341 result=(MagickRealType) (((MagickRealType) pixel <= value) ? 0 : pixel);
342 break;
343 }
344 case ThresholdWhiteEvaluateOperator:
345 {
346 result=(MagickRealType) (((MagickRealType) pixel > value) ? QuantumRange :
347 pixel);
348 break;
349 }
350 case UniformNoiseEvaluateOperator:
351 {
352 result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
353 UniformNoise,value);
354 break;
355 }
356 case XorEvaluateOperator:
357 {
358 result=(MagickRealType) ((unsigned long) pixel ^ (unsigned long)
359 (value+0.5));
360 break;
361 }
362 }
cristyd18ae7c2010-03-07 17:39:52 +0000363 return(result);
cristy351842f2010-03-07 15:27:38 +0000364}
365
366MagickExport MagickBooleanType EvaluateImage(Image *image,
367 const MagickEvaluateOperator op,const double value,ExceptionInfo *exception)
368{
369 MagickBooleanType
370 status;
371
372 status=EvaluateImageChannel(image,AllChannels,op,value,exception);
373 return(status);
374}
375
cristyd18ae7c2010-03-07 17:39:52 +0000376MagickExport Image *EvaluateImages(const Image *images,
377 const MagickEvaluateOperator op,ExceptionInfo *exception)
378{
379#define EvaluateImageTag "Evaluate/Image"
380
381 CacheView
382 *evaluate_view;
383
384 const Image
385 *next;
386
387 Image
388 *evaluate_image;
389
390 long
391 progress,
392 y;
393
394 MagickBooleanType
395 status;
396
397 MagickPixelPacket
398 **restrict evaluate_pixels,
399 zero;
400
401 RandomInfo
402 **restrict random_info;
403
404 unsigned long
405 number_images;
406
407 /*
408 Ensure the image are the same size.
409 */
410 assert(images != (Image *) NULL);
411 assert(images->signature == MagickSignature);
412 if (images->debug != MagickFalse)
413 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
414 assert(exception != (ExceptionInfo *) NULL);
415 assert(exception->signature == MagickSignature);
416 for (next=images; next != (Image *) NULL; next=GetNextImageInList(next))
417 if ((next->columns != images->columns) || (next->rows != images->rows))
418 {
419 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
420 "ImageWidthsOrHeightsDiffer","`%s'",images->filename);
421 return((Image *) NULL);
422 }
423 /*
424 Initialize evaluate next attributes.
425 */
426 evaluate_image=CloneImage(images,images->columns,images->rows,MagickTrue,
427 exception);
428 if (evaluate_image == (Image *) NULL)
429 return((Image *) NULL);
430 if (SetImageStorageClass(evaluate_image,DirectClass) == MagickFalse)
431 {
432 InheritException(exception,&evaluate_image->exception);
433 evaluate_image=DestroyImage(evaluate_image);
434 return((Image *) NULL);
435 }
436 evaluate_pixels=AcquirePixelThreadSet(images);
437 if (evaluate_pixels == (MagickPixelPacket **) NULL)
438 {
439 evaluate_image=DestroyImage(evaluate_image);
440 (void) ThrowMagickException(exception,GetMagickModule(),
441 ResourceLimitError,"MemoryAllocationFailed","`%s'",images->filename);
442 return((Image *) NULL);
443 }
444 /*
445 Evaluate image pixels.
446 */
447 status=MagickTrue;
448 progress=0;
449 GetMagickPixelPacket(images,&zero);
450 random_info=AcquireRandomInfoThreadSet();
451 number_images=GetImageListLength(images);
452 evaluate_view=AcquireCacheView(evaluate_image);
453#if defined(MAGICKCORE_OPENMP_SUPPORT)
454 #pragma omp parallel for schedule(dynamic) shared(progress,status)
455#endif
456 for (y=0; y < (long) evaluate_image->rows; y++)
457 {
458 CacheView
459 *image_view;
460
461 const Image
462 *next;
463
464 MagickPixelPacket
465 pixel;
466
467 register IndexPacket
468 *restrict evaluate_indexes;
469
470 register long
471 i,
472 id,
473 x;
474
475 register MagickPixelPacket
476 *evaluate_pixel;
477
478 register PixelPacket
479 *restrict q;
480
481 if (status == MagickFalse)
482 continue;
483 q=QueueCacheViewAuthenticPixels(evaluate_view,0,y,evaluate_image->columns,1,
484 exception);
485 if (q == (PixelPacket *) NULL)
486 {
487 status=MagickFalse;
488 continue;
489 }
490 evaluate_indexes=GetCacheViewAuthenticIndexQueue(evaluate_view);
491 pixel=zero;
492 id=GetOpenMPThreadId();
493 evaluate_pixel=evaluate_pixels[id];
494 for (x=0; x < (long) evaluate_image->columns; x++)
495 evaluate_pixel[x]=zero;
496 next=images;
497 for (i=0; i < (long) number_images; i++)
498 {
499 register const IndexPacket
500 *indexes;
501
502 register const PixelPacket
503 *p;
504
505 image_view=AcquireCacheView(next);
506 p=GetCacheViewVirtualPixels(image_view,0,y,next->columns,1,exception);
507 if (p == (const PixelPacket *) NULL)
508 {
509 image_view=DestroyCacheView(image_view);
510 break;
511 }
512 indexes=GetCacheViewVirtualIndexQueue(image_view);
513 for (x=0; x < (long) next->columns; x++)
514 {
515 evaluate_pixel[x].red=ApplyEvaluateOperator(random_info[id],p->red,
cristy4a747d12010-03-08 20:00:33 +0000516 i == 0 ? AddEvaluateOperator : op,evaluate_pixel[x].red);
cristyd18ae7c2010-03-07 17:39:52 +0000517 evaluate_pixel[x].green=ApplyEvaluateOperator(random_info[id],p->green,
cristy4a747d12010-03-08 20:00:33 +0000518 i == 0 ? AddEvaluateOperator : op,evaluate_pixel[x].green);
cristyd18ae7c2010-03-07 17:39:52 +0000519 evaluate_pixel[x].blue=ApplyEvaluateOperator(random_info[id],p->blue,
cristy4a747d12010-03-08 20:00:33 +0000520 i == 0 ? AddEvaluateOperator : op,evaluate_pixel[x].blue);
cristyd18ae7c2010-03-07 17:39:52 +0000521 evaluate_pixel[x].opacity=ApplyEvaluateOperator(random_info[id],
cristy4a747d12010-03-08 20:00:33 +0000522 p->opacity,i == 0 ? AddEvaluateOperator : op,
523 evaluate_pixel[x].opacity);
cristyd18ae7c2010-03-07 17:39:52 +0000524 if (evaluate_image->colorspace == CMYKColorspace)
525 evaluate_pixel[x].index=ApplyEvaluateOperator(random_info[id],
cristy4a747d12010-03-08 20:00:33 +0000526 indexes[x],i == 0 ? AddEvaluateOperator : op,
527 evaluate_pixel[x].index);
cristyd18ae7c2010-03-07 17:39:52 +0000528 p++;
529 }
530 image_view=DestroyCacheView(image_view);
531 next=GetNextImageInList(next);
532 }
533 for (x=0; x < (long) evaluate_image->columns; x++)
534 {
535 q->red=ClampToQuantum(evaluate_pixel[x].red);
536 q->green=ClampToQuantum(evaluate_pixel[x].green);
537 q->blue=ClampToQuantum(evaluate_pixel[x].blue);
cristyc02484c2010-03-08 00:14:58 +0000538 if (evaluate_image->matte == MagickFalse)
539 q->opacity=ClampToQuantum(evaluate_pixel[x].opacity);
540 else
541 q->opacity=ClampToQuantum(QuantumRange-evaluate_pixel[x].opacity);
cristyd18ae7c2010-03-07 17:39:52 +0000542 if (evaluate_image->colorspace == CMYKColorspace)
543 evaluate_indexes[x]=ClampToQuantum(evaluate_pixel[x].index);
544 q++;
545 }
546 if (SyncCacheViewAuthenticPixels(evaluate_view,exception) == MagickFalse)
547 status=MagickFalse;
548 if (images->progress_monitor != (MagickProgressMonitor) NULL)
549 {
550 MagickBooleanType
551 proceed;
552
553#if defined(MAGICKCORE_OPENMP_SUPPORT)
554 #pragma omp critical (MagickCore_EvaluateImages)
555#endif
556 proceed=SetImageProgress(images,EvaluateImageTag,progress++,
557 evaluate_image->rows);
558 if (proceed == MagickFalse)
559 status=MagickFalse;
560 }
561 }
562 evaluate_view=DestroyCacheView(evaluate_view);
563 evaluate_pixels=DestroyPixelThreadSet(evaluate_pixels);
564 random_info=DestroyRandomInfoThreadSet(random_info);
565 if (status == MagickFalse)
566 evaluate_image=DestroyImage(evaluate_image);
567 return(evaluate_image);
568}
569
cristy351842f2010-03-07 15:27:38 +0000570MagickExport MagickBooleanType EvaluateImageChannel(Image *image,
571 const ChannelType channel,const MagickEvaluateOperator op,const double value,
572 ExceptionInfo *exception)
573{
cristy351842f2010-03-07 15:27:38 +0000574 CacheView
575 *image_view;
576
577 long
578 progress,
579 y;
580
581 MagickBooleanType
582 status;
583
584 RandomInfo
585 **restrict random_info;
586
587 assert(image != (Image *) NULL);
588 assert(image->signature == MagickSignature);
589 if (image->debug != MagickFalse)
590 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
591 assert(exception != (ExceptionInfo *) NULL);
592 assert(exception->signature == MagickSignature);
593 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
594 {
595 InheritException(exception,&image->exception);
596 return(MagickFalse);
597 }
598 status=MagickTrue;
599 progress=0;
600 random_info=AcquireRandomInfoThreadSet();
601 image_view=AcquireCacheView(image);
602#if defined(MAGICKCORE_OPENMP_SUPPORT)
603 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
604#endif
605 for (y=0; y < (long) image->rows; y++)
606 {
607 register IndexPacket
608 *restrict indexes;
609
610 register long
611 id,
612 x;
613
614 register PixelPacket
615 *restrict q;
616
617 if (status == MagickFalse)
618 continue;
619 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
620 if (q == (PixelPacket *) NULL)
621 {
622 status=MagickFalse;
623 continue;
624 }
625 indexes=GetCacheViewAuthenticIndexQueue(image_view);
626 id=GetOpenMPThreadId();
627 for (x=0; x < (long) image->columns; x++)
628 {
629 if ((channel & RedChannel) != 0)
cristyd18ae7c2010-03-07 17:39:52 +0000630 q->red=ClampToQuantum(ApplyEvaluateOperator(random_info[id],q->red,op,
631 value));
cristy351842f2010-03-07 15:27:38 +0000632 if ((channel & GreenChannel) != 0)
cristyd18ae7c2010-03-07 17:39:52 +0000633 q->green=ClampToQuantum(ApplyEvaluateOperator(random_info[id],q->green,
634 op,value));
cristy351842f2010-03-07 15:27:38 +0000635 if ((channel & BlueChannel) != 0)
cristyd18ae7c2010-03-07 17:39:52 +0000636 q->blue=ClampToQuantum(ApplyEvaluateOperator(random_info[id],q->blue,op,
637 value));
cristy351842f2010-03-07 15:27:38 +0000638 if ((channel & OpacityChannel) != 0)
639 {
640 if (image->matte == MagickFalse)
cristyd18ae7c2010-03-07 17:39:52 +0000641 q->opacity=ClampToQuantum(ApplyEvaluateOperator(random_info[id],
642 q->opacity,op,value));
cristy351842f2010-03-07 15:27:38 +0000643 else
cristyd18ae7c2010-03-07 17:39:52 +0000644 q->opacity=ClampToQuantum(QuantumRange-ApplyEvaluateOperator(
645 random_info[id],(Quantum) GetAlphaPixelComponent(q),op,value));
cristy351842f2010-03-07 15:27:38 +0000646 }
647 if (((channel & IndexChannel) != 0) && (indexes != (IndexPacket *) NULL))
cristyd18ae7c2010-03-07 17:39:52 +0000648 indexes[x]=(IndexPacket) ClampToQuantum(ApplyEvaluateOperator(
649 random_info[id],indexes[x],op,value));
cristy351842f2010-03-07 15:27:38 +0000650 q++;
651 }
652 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
653 status=MagickFalse;
654 if (image->progress_monitor != (MagickProgressMonitor) NULL)
655 {
656 MagickBooleanType
657 proceed;
658
659#if defined(MAGICKCORE_OPENMP_SUPPORT)
660 #pragma omp critical (MagickCore_EvaluateImageChannel)
661#endif
662 proceed=SetImageProgress(image,EvaluateImageTag,progress++,image->rows);
663 if (proceed == MagickFalse)
664 status=MagickFalse;
665 }
666 }
667 image_view=DestroyCacheView(image_view);
668 random_info=DestroyRandomInfoThreadSet(random_info);
669 return(status);
670}
671
672/*
673%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
674% %
675% %
676% %
677% F u n c t i o n I m a g e %
678% %
679% %
680% %
681%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
682%
683% FunctionImage() applies a value to the image with an arithmetic, relational,
684% or logical operator to an image. Use these operations to lighten or darken
685% an image, to increase or decrease contrast in an image, or to produce the
686% "negative" of an image.
687%
688% The format of the FunctionImageChannel method is:
689%
690% MagickBooleanType FunctionImage(Image *image,
691% const MagickFunction function,const long number_parameters,
692% const double *parameters,ExceptionInfo *exception)
693% MagickBooleanType FunctionImageChannel(Image *image,
694% const ChannelType channel,const MagickFunction function,
695% const long number_parameters,const double *argument,
696% ExceptionInfo *exception)
697%
698% A description of each parameter follows:
699%
700% o image: the image.
701%
702% o channel: the channel.
703%
704% o function: A channel function.
705%
706% o parameters: one or more parameters.
707%
708% o exception: return any errors or warnings in this structure.
709%
710*/
711
712static Quantum ApplyFunction(Quantum pixel,const MagickFunction function,
713 const unsigned long number_parameters,const double *parameters,
714 ExceptionInfo *exception)
715{
716 MagickRealType
717 result;
718
719 register long
720 i;
721
722 (void) exception;
723 result=0.0;
724 switch (function)
725 {
726 case PolynomialFunction:
727 {
728 /*
729 * Polynomial
730 * Parameters: polynomial constants, highest to lowest order
731 * For example: c0*x^3 + c1*x^2 + c2*x + c3
732 */
733 result=0.0;
734 for (i=0; i < (long) number_parameters; i++)
735 result = result*QuantumScale*pixel + parameters[i];
736 result *= QuantumRange;
737 break;
738 }
739 case SinusoidFunction:
740 {
741 /* Sinusoid Function
742 * Parameters: Freq, Phase, Ampl, bias
743 */
744 double freq,phase,ampl,bias;
745 freq = ( number_parameters >= 1 ) ? parameters[0] : 1.0;
746 phase = ( number_parameters >= 2 ) ? parameters[1] : 0.0;
747 ampl = ( number_parameters >= 3 ) ? parameters[2] : 0.5;
748 bias = ( number_parameters >= 4 ) ? parameters[3] : 0.5;
749 result=(MagickRealType) (QuantumRange*(ampl*sin((double) (2.0*MagickPI*
750 (freq*QuantumScale*pixel + phase/360.0) )) + bias ) );
751 break;
752 }
753 case ArcsinFunction:
754 {
755 /* Arcsin Function (peged at range limits for invalid results)
756 * Parameters: Width, Center, Range, Bias
757 */
758 double width,range,center,bias;
759 width = ( number_parameters >= 1 ) ? parameters[0] : 1.0;
760 center = ( number_parameters >= 2 ) ? parameters[1] : 0.5;
761 range = ( number_parameters >= 3 ) ? parameters[2] : 1.0;
762 bias = ( number_parameters >= 4 ) ? parameters[3] : 0.5;
763 result = 2.0/width*(QuantumScale*pixel - center);
764 if ( result <= -1.0 )
765 result = bias - range/2.0;
766 else if ( result >= 1.0 )
767 result = bias + range/2.0;
768 else
769 result=range/MagickPI*asin((double)result) + bias;
770 result *= QuantumRange;
771 break;
772 }
773 case ArctanFunction:
774 {
775 /* Arctan Function
776 * Parameters: Slope, Center, Range, Bias
777 */
778 double slope,range,center,bias;
779 slope = ( number_parameters >= 1 ) ? parameters[0] : 1.0;
780 center = ( number_parameters >= 2 ) ? parameters[1] : 0.5;
781 range = ( number_parameters >= 3 ) ? parameters[2] : 1.0;
782 bias = ( number_parameters >= 4 ) ? parameters[3] : 0.5;
783 result = MagickPI*slope*(QuantumScale*pixel - center);
784 result=(MagickRealType) (QuantumRange*(range/MagickPI*atan((double)
785 result) + bias ) );
786 break;
787 }
788 case UndefinedFunction:
789 break;
790 }
791 return(ClampToQuantum(result));
792}
793
794MagickExport MagickBooleanType FunctionImage(Image *image,
795 const MagickFunction function,const unsigned long number_parameters,
796 const double *parameters,ExceptionInfo *exception)
797{
798 MagickBooleanType
799 status;
800
801 status=FunctionImageChannel(image,AllChannels,function,number_parameters,
802 parameters,exception);
803 return(status);
804}
805
806MagickExport MagickBooleanType FunctionImageChannel(Image *image,
807 const ChannelType channel,const MagickFunction function,
808 const unsigned long number_parameters,const double *parameters,
809 ExceptionInfo *exception)
810{
811#define FunctionImageTag "Function/Image "
812
813 CacheView
814 *image_view;
815
816 long
817 progress,
818 y;
819
820 MagickBooleanType
821 status;
822
823 assert(image != (Image *) NULL);
824 assert(image->signature == MagickSignature);
825 if (image->debug != MagickFalse)
826 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
827 assert(exception != (ExceptionInfo *) NULL);
828 assert(exception->signature == MagickSignature);
829 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
830 {
831 InheritException(exception,&image->exception);
832 return(MagickFalse);
833 }
834 status=MagickTrue;
835 progress=0;
836 image_view=AcquireCacheView(image);
837#if defined(MAGICKCORE_OPENMP_SUPPORT)
838 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
839#endif
840 for (y=0; y < (long) image->rows; y++)
841 {
842 register IndexPacket
843 *restrict indexes;
844
845 register long
846 x;
847
848 register PixelPacket
849 *restrict q;
850
851 if (status == MagickFalse)
852 continue;
853 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
854 if (q == (PixelPacket *) NULL)
855 {
856 status=MagickFalse;
857 continue;
858 }
859 indexes=GetCacheViewAuthenticIndexQueue(image_view);
860 for (x=0; x < (long) image->columns; x++)
861 {
862 if ((channel & RedChannel) != 0)
863 q->red=ApplyFunction(q->red,function,number_parameters,parameters,
864 exception);
865 if ((channel & GreenChannel) != 0)
866 q->green=ApplyFunction(q->green,function,number_parameters,parameters,
867 exception);
868 if ((channel & BlueChannel) != 0)
869 q->blue=ApplyFunction(q->blue,function,number_parameters,parameters,
870 exception);
871 if ((channel & OpacityChannel) != 0)
872 {
873 if (image->matte == MagickFalse)
874 q->opacity=ApplyFunction(q->opacity,function,number_parameters,
875 parameters,exception);
876 else
877 q->opacity=(Quantum) QuantumRange-ApplyFunction((Quantum)
878 GetAlphaPixelComponent(q),function,number_parameters,parameters,
879 exception);
880 }
881 if (((channel & IndexChannel) != 0) && (indexes != (IndexPacket *) NULL))
882 indexes[x]=(IndexPacket) ApplyFunction(indexes[x],function,
883 number_parameters,parameters,exception);
884 q++;
885 }
886 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
887 status=MagickFalse;
888 if (image->progress_monitor != (MagickProgressMonitor) NULL)
889 {
890 MagickBooleanType
891 proceed;
892
893#if defined(MAGICKCORE_OPENMP_SUPPORT)
894 #pragma omp critical (MagickCore_FunctionImageChannel)
895#endif
896 proceed=SetImageProgress(image,FunctionImageTag,progress++,image->rows);
897 if (proceed == MagickFalse)
898 status=MagickFalse;
899 }
900 }
901 image_view=DestroyCacheView(image_view);
902 return(status);
903}
904
905/*
906%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
907% %
908% %
909% %
cristy3ed852e2009-09-05 21:47:34 +0000910+ G e t I m a g e C h a n n e l E x t r e m a %
911% %
912% %
913% %
914%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
915%
916% GetImageChannelExtrema() returns the extrema of one or more image channels.
917%
918% The format of the GetImageChannelExtrema method is:
919%
920% MagickBooleanType GetImageChannelExtrema(const Image *image,
921% const ChannelType channel,unsigned long *minima,unsigned long *maxima,
922% ExceptionInfo *exception)
923%
924% A description of each parameter follows:
925%
926% o image: the image.
927%
928% o channel: the channel.
929%
930% o minima: the minimum value in the channel.
931%
932% o maxima: the maximum value in the channel.
933%
934% o exception: return any errors or warnings in this structure.
935%
936*/
937
938MagickExport MagickBooleanType GetImageExtrema(const Image *image,
939 unsigned long *minima,unsigned long *maxima,ExceptionInfo *exception)
940{
941 return(GetImageChannelExtrema(image,AllChannels,minima,maxima,exception));
942}
943
944MagickExport MagickBooleanType GetImageChannelExtrema(const Image *image,
945 const ChannelType channel,unsigned long *minima,unsigned long *maxima,
946 ExceptionInfo *exception)
947{
948 double
949 max,
950 min;
951
952 MagickBooleanType
953 status;
954
955 assert(image != (Image *) NULL);
956 assert(image->signature == MagickSignature);
957 if (image->debug != MagickFalse)
958 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
959 status=GetImageChannelRange(image,channel,&min,&max,exception);
960 *minima=(unsigned long) (min+0.5);
961 *maxima=(unsigned long) (max+0.5);
962 return(status);
963}
964
965/*
966%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
967% %
968% %
969% %
970% G e t I m a g e C h a n n e l M e a n %
971% %
972% %
973% %
974%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
975%
976% GetImageChannelMean() returns the mean and standard deviation of one or more
977% image channels.
978%
979% The format of the GetImageChannelMean method is:
980%
981% MagickBooleanType GetImageChannelMean(const Image *image,
982% const ChannelType channel,double *mean,double *standard_deviation,
983% ExceptionInfo *exception)
984%
985% A description of each parameter follows:
986%
987% o image: the image.
988%
989% o channel: the channel.
990%
991% o mean: the average value in the channel.
992%
993% o standard_deviation: the standard deviation of the channel.
994%
995% o exception: return any errors or warnings in this structure.
996%
997*/
998
999MagickExport MagickBooleanType GetImageMean(const Image *image,double *mean,
1000 double *standard_deviation,ExceptionInfo *exception)
1001{
1002 MagickBooleanType
1003 status;
1004
1005 status=GetImageChannelMean(image,AllChannels,mean,standard_deviation,
1006 exception);
1007 return(status);
1008}
1009
1010MagickExport MagickBooleanType GetImageChannelMean(const Image *image,
1011 const ChannelType channel,double *mean,double *standard_deviation,
1012 ExceptionInfo *exception)
1013{
1014 double
1015 area;
1016
1017 long
1018 y;
1019
1020 assert(image != (Image *) NULL);
1021 assert(image->signature == MagickSignature);
1022 if (image->debug != MagickFalse)
1023 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1024 *mean=0.0;
1025 *standard_deviation=0.0;
1026 area=0.0;
1027 for (y=0; y < (long) image->rows; y++)
1028 {
1029 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001030 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001031
1032 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001033 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001034
1035 register long
1036 x;
1037
1038 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1039 if (p == (const PixelPacket *) NULL)
1040 break;
1041 indexes=GetVirtualIndexQueue(image);
1042 for (x=0; x < (long) image->columns; x++)
1043 {
1044 if ((channel & RedChannel) != 0)
1045 {
cristyce70c172010-01-07 17:15:30 +00001046 *mean+=GetRedPixelComponent(p);
1047 *standard_deviation+=(double) p->red*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001048 area++;
1049 }
1050 if ((channel & GreenChannel) != 0)
1051 {
cristyce70c172010-01-07 17:15:30 +00001052 *mean+=GetGreenPixelComponent(p);
1053 *standard_deviation+=(double) p->green*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001054 area++;
1055 }
1056 if ((channel & BlueChannel) != 0)
1057 {
cristyce70c172010-01-07 17:15:30 +00001058 *mean+=GetBluePixelComponent(p);
1059 *standard_deviation+=(double) p->blue*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001060 area++;
1061 }
1062 if ((channel & OpacityChannel) != 0)
1063 {
cristyce70c172010-01-07 17:15:30 +00001064 *mean+=GetOpacityPixelComponent(p);
1065 *standard_deviation+=(double) p->opacity*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001066 area++;
1067 }
1068 if (((channel & IndexChannel) != 0) &&
1069 (image->colorspace == CMYKColorspace))
1070 {
1071 *mean+=indexes[x];
1072 *standard_deviation+=(double) indexes[x]*indexes[x];
1073 area++;
1074 }
1075 p++;
1076 }
1077 }
1078 if (y < (long) image->rows)
1079 return(MagickFalse);
1080 if (area != 0)
1081 {
1082 *mean/=area;
1083 *standard_deviation/=area;
1084 }
1085 *standard_deviation=sqrt(*standard_deviation-(*mean*(*mean)));
1086 return(y == (long) image->rows ? MagickTrue : MagickFalse);
1087}
1088
1089/*
1090%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1091% %
1092% %
1093% %
1094% G e t I m a g e C h a n n e l K u r t o s i s %
1095% %
1096% %
1097% %
1098%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1099%
1100% GetImageChannelKurtosis() returns the kurtosis and skewness of one or more
1101% image channels.
1102%
1103% The format of the GetImageChannelKurtosis method is:
1104%
1105% MagickBooleanType GetImageChannelKurtosis(const Image *image,
1106% const ChannelType channel,double *kurtosis,double *skewness,
1107% ExceptionInfo *exception)
1108%
1109% A description of each parameter follows:
1110%
1111% o image: the image.
1112%
1113% o channel: the channel.
1114%
1115% o kurtosis: the kurtosis of the channel.
1116%
1117% o skewness: the skewness of the channel.
1118%
1119% o exception: return any errors or warnings in this structure.
1120%
1121*/
1122
1123MagickExport MagickBooleanType GetImageKurtosis(const Image *image,
1124 double *kurtosis,double *skewness,ExceptionInfo *exception)
1125{
1126 MagickBooleanType
1127 status;
1128
1129 status=GetImageChannelKurtosis(image,AllChannels,kurtosis,skewness,
1130 exception);
1131 return(status);
1132}
1133
1134MagickExport MagickBooleanType GetImageChannelKurtosis(const Image *image,
1135 const ChannelType channel,double *kurtosis,double *skewness,
1136 ExceptionInfo *exception)
1137{
1138 double
1139 area,
1140 mean,
1141 standard_deviation,
1142 sum_squares,
1143 sum_cubes,
1144 sum_fourth_power;
1145
1146 long
1147 y;
1148
1149 assert(image != (Image *) NULL);
1150 assert(image->signature == MagickSignature);
1151 if (image->debug != MagickFalse)
1152 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1153 *kurtosis=0.0;
1154 *skewness=0.0;
1155 area=0.0;
1156 mean=0.0;
1157 standard_deviation=0.0;
1158 sum_squares=0.0;
1159 sum_cubes=0.0;
1160 sum_fourth_power=0.0;
1161 for (y=0; y < (long) image->rows; y++)
1162 {
1163 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001164 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001165
1166 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001167 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001168
1169 register long
1170 x;
1171
1172 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1173 if (p == (const PixelPacket *) NULL)
1174 break;
1175 indexes=GetVirtualIndexQueue(image);
1176 for (x=0; x < (long) image->columns; x++)
1177 {
1178 if ((channel & RedChannel) != 0)
1179 {
cristyce70c172010-01-07 17:15:30 +00001180 mean+=GetRedPixelComponent(p);
1181 sum_squares+=(double) p->red*GetRedPixelComponent(p);
1182 sum_cubes+=(double) p->red*p->red*GetRedPixelComponent(p);
cristy46f08202010-01-10 04:04:21 +00001183 sum_fourth_power+=(double) p->red*p->red*p->red*
1184 GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001185 area++;
1186 }
1187 if ((channel & GreenChannel) != 0)
1188 {
cristyce70c172010-01-07 17:15:30 +00001189 mean+=GetGreenPixelComponent(p);
1190 sum_squares+=(double) p->green*GetGreenPixelComponent(p);
1191 sum_cubes+=(double) p->green*p->green*GetGreenPixelComponent(p);
cristy46f08202010-01-10 04:04:21 +00001192 sum_fourth_power+=(double) p->green*p->green*p->green*
1193 GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001194 area++;
1195 }
1196 if ((channel & BlueChannel) != 0)
1197 {
cristyce70c172010-01-07 17:15:30 +00001198 mean+=GetBluePixelComponent(p);
1199 sum_squares+=(double) p->blue*GetBluePixelComponent(p);
1200 sum_cubes+=(double) p->blue*p->blue*GetBluePixelComponent(p);
cristy46f08202010-01-10 04:04:21 +00001201 sum_fourth_power+=(double) p->blue*p->blue*p->blue*
1202 GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001203 area++;
1204 }
1205 if ((channel & OpacityChannel) != 0)
1206 {
cristyce70c172010-01-07 17:15:30 +00001207 mean+=GetOpacityPixelComponent(p);
1208 sum_squares+=(double) p->opacity*GetOpacityPixelComponent(p);
1209 sum_cubes+=(double) p->opacity*p->opacity*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001210 sum_fourth_power+=(double) p->opacity*p->opacity*p->opacity*
cristyce70c172010-01-07 17:15:30 +00001211 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001212 area++;
1213 }
1214 if (((channel & IndexChannel) != 0) &&
1215 (image->colorspace == CMYKColorspace))
1216 {
1217 mean+=indexes[x];
1218 sum_squares+=(double) indexes[x]*indexes[x];
1219 sum_cubes+=(double) indexes[x]*indexes[x]*indexes[x];
1220 sum_fourth_power+=(double) indexes[x]*indexes[x]*indexes[x]*
1221 indexes[x];
1222 area++;
1223 }
1224 p++;
1225 }
1226 }
1227 if (y < (long) image->rows)
1228 return(MagickFalse);
1229 if (area != 0.0)
1230 {
1231 mean/=area;
1232 sum_squares/=area;
1233 sum_cubes/=area;
1234 sum_fourth_power/=area;
1235 }
1236 standard_deviation=sqrt(sum_squares-(mean*mean));
1237 if (standard_deviation != 0.0)
1238 {
1239 *kurtosis=sum_fourth_power-4.0*mean*sum_cubes+6.0*mean*mean*sum_squares-
1240 3.0*mean*mean*mean*mean;
1241 *kurtosis/=standard_deviation*standard_deviation*standard_deviation*
1242 standard_deviation;
1243 *kurtosis-=3.0;
1244 *skewness=sum_cubes-3.0*mean*sum_squares+2.0*mean*mean*mean;
1245 *skewness/=standard_deviation*standard_deviation*standard_deviation;
1246 }
1247 return(y == (long) image->rows ? MagickTrue : MagickFalse);
1248}
cristy46f08202010-01-10 04:04:21 +00001249
cristy3ed852e2009-09-05 21:47:34 +00001250/*
1251%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1252% %
1253% %
1254% %
1255% G e t I m a g e C h a n n e l R a n g e %
1256% %
1257% %
1258% %
1259%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1260%
1261% GetImageChannelRange() returns the range of one or more image channels.
1262%
1263% The format of the GetImageChannelRange method is:
1264%
1265% MagickBooleanType GetImageChannelRange(const Image *image,
1266% const ChannelType channel,double *minima,double *maxima,
1267% ExceptionInfo *exception)
1268%
1269% A description of each parameter follows:
1270%
1271% o image: the image.
1272%
1273% o channel: the channel.
1274%
1275% o minima: the minimum value in the channel.
1276%
1277% o maxima: the maximum value in the channel.
1278%
1279% o exception: return any errors or warnings in this structure.
1280%
1281*/
1282
1283MagickExport MagickBooleanType GetImageRange(const Image *image,
1284 double *minima,double *maxima,ExceptionInfo *exception)
1285{
1286 return(GetImageChannelRange(image,AllChannels,minima,maxima,exception));
1287}
1288
1289MagickExport MagickBooleanType GetImageChannelRange(const Image *image,
1290 const ChannelType channel,double *minima,double *maxima,
1291 ExceptionInfo *exception)
1292{
1293 long
1294 y;
1295
1296 MagickPixelPacket
1297 pixel;
1298
1299 assert(image != (Image *) NULL);
1300 assert(image->signature == MagickSignature);
1301 if (image->debug != MagickFalse)
1302 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1303 *maxima=(-1.0E-37);
1304 *minima=1.0E+37;
1305 GetMagickPixelPacket(image,&pixel);
1306 for (y=0; y < (long) image->rows; y++)
1307 {
1308 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001309 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001310
1311 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001312 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001313
1314 register long
1315 x;
1316
1317 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1318 if (p == (const PixelPacket *) NULL)
1319 break;
1320 indexes=GetVirtualIndexQueue(image);
1321 for (x=0; x < (long) image->columns; x++)
1322 {
1323 SetMagickPixelPacket(image,p,indexes+x,&pixel);
1324 if ((channel & RedChannel) != 0)
1325 {
1326 if (pixel.red < *minima)
1327 *minima=(double) pixel.red;
1328 if (pixel.red > *maxima)
1329 *maxima=(double) pixel.red;
1330 }
1331 if ((channel & GreenChannel) != 0)
1332 {
1333 if (pixel.green < *minima)
1334 *minima=(double) pixel.green;
1335 if (pixel.green > *maxima)
1336 *maxima=(double) pixel.green;
1337 }
1338 if ((channel & BlueChannel) != 0)
1339 {
1340 if (pixel.blue < *minima)
1341 *minima=(double) pixel.blue;
1342 if (pixel.blue > *maxima)
1343 *maxima=(double) pixel.blue;
1344 }
1345 if ((channel & OpacityChannel) != 0)
1346 {
1347 if (pixel.opacity < *minima)
1348 *minima=(double) pixel.opacity;
1349 if (pixel.opacity > *maxima)
1350 *maxima=(double) pixel.opacity;
1351 }
1352 if (((channel & IndexChannel) != 0) &&
1353 (image->colorspace == CMYKColorspace))
1354 {
1355 if ((double) indexes[x] < *minima)
1356 *minima=(double) indexes[x];
1357 if ((double) indexes[x] > *maxima)
1358 *maxima=(double) indexes[x];
1359 }
1360 p++;
1361 }
1362 }
1363 return(y == (long) image->rows ? MagickTrue : MagickFalse);
1364}
1365
1366/*
1367%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1368% %
1369% %
1370% %
1371% G e t I m a g e C h a n n e l S t a t i s t i c s %
1372% %
1373% %
1374% %
1375%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1376%
1377% GetImageChannelStatistics() returns statistics for each channel in the
1378% image. The statistics include the channel depth, its minima, maxima, mean,
1379% standard deviation, kurtosis and skewness. You can access the red channel
1380% mean, for example, like this:
1381%
1382% channel_statistics=GetImageChannelStatistics(image,excepton);
1383% red_mean=channel_statistics[RedChannel].mean;
1384%
1385% Use MagickRelinquishMemory() to free the statistics buffer.
1386%
1387% The format of the GetImageChannelStatistics method is:
1388%
1389% ChannelStatistics *GetImageChannelStatistics(const Image *image,
1390% ExceptionInfo *exception)
1391%
1392% A description of each parameter follows:
1393%
1394% o image: the image.
1395%
1396% o exception: return any errors or warnings in this structure.
1397%
1398*/
cristy3ed852e2009-09-05 21:47:34 +00001399MagickExport ChannelStatistics *GetImageChannelStatistics(const Image *image,
1400 ExceptionInfo *exception)
1401{
1402 ChannelStatistics
1403 *channel_statistics;
1404
1405 double
1406 area,
1407 sum_squares,
1408 sum_cubes;
1409
1410 long
1411 y;
1412
1413 MagickStatusType
1414 status;
1415
1416 QuantumAny
1417 range;
1418
1419 register long
1420 i;
1421
1422 size_t
1423 length;
1424
1425 unsigned long
1426 channels,
1427 depth;
1428
1429 assert(image != (Image *) NULL);
1430 assert(image->signature == MagickSignature);
1431 if (image->debug != MagickFalse)
1432 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1433 length=AllChannels+1UL;
1434 channel_statistics=(ChannelStatistics *) AcquireQuantumMemory(length,
1435 sizeof(*channel_statistics));
1436 if (channel_statistics == (ChannelStatistics *) NULL)
1437 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1438 (void) ResetMagickMemory(channel_statistics,0,length*
1439 sizeof(*channel_statistics));
1440 for (i=0; i <= AllChannels; i++)
1441 {
1442 channel_statistics[i].depth=1;
1443 channel_statistics[i].maxima=(-1.0E-37);
1444 channel_statistics[i].minima=1.0E+37;
1445 channel_statistics[i].mean=0.0;
1446 channel_statistics[i].standard_deviation=0.0;
1447 channel_statistics[i].kurtosis=0.0;
1448 channel_statistics[i].skewness=0.0;
1449 }
1450 for (y=0; y < (long) image->rows; y++)
1451 {
1452 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001453 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001454
1455 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001456 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001457
1458 register long
1459 x;
1460
1461 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1462 if (p == (const PixelPacket *) NULL)
1463 break;
1464 indexes=GetVirtualIndexQueue(image);
1465 for (x=0; x < (long) image->columns; )
1466 {
1467 if (channel_statistics[RedChannel].depth != MAGICKCORE_QUANTUM_DEPTH)
1468 {
1469 depth=channel_statistics[RedChannel].depth;
1470 range=GetQuantumRange(depth);
1471 status=p->red != ScaleAnyToQuantum(ScaleQuantumToAny(p->red,range),
1472 range) ? MagickTrue : MagickFalse;
1473 if (status != MagickFalse)
1474 {
1475 channel_statistics[RedChannel].depth++;
1476 continue;
1477 }
1478 }
1479 if (channel_statistics[GreenChannel].depth != MAGICKCORE_QUANTUM_DEPTH)
1480 {
1481 depth=channel_statistics[GreenChannel].depth;
1482 range=GetQuantumRange(depth);
1483 status=p->green != ScaleAnyToQuantum(ScaleQuantumToAny(p->green,
1484 range),range) ? MagickTrue : MagickFalse;
1485 if (status != MagickFalse)
1486 {
1487 channel_statistics[GreenChannel].depth++;
1488 continue;
1489 }
1490 }
1491 if (channel_statistics[BlueChannel].depth != MAGICKCORE_QUANTUM_DEPTH)
1492 {
1493 depth=channel_statistics[BlueChannel].depth;
1494 range=GetQuantumRange(depth);
1495 status=p->blue != ScaleAnyToQuantum(ScaleQuantumToAny(p->blue,
1496 range),range) ? MagickTrue : MagickFalse;
1497 if (status != MagickFalse)
1498 {
1499 channel_statistics[BlueChannel].depth++;
1500 continue;
1501 }
1502 }
1503 if (image->matte != MagickFalse)
1504 {
1505 if (channel_statistics[OpacityChannel].depth != MAGICKCORE_QUANTUM_DEPTH)
1506 {
1507 depth=channel_statistics[OpacityChannel].depth;
1508 range=GetQuantumRange(depth);
1509 status=p->opacity != ScaleAnyToQuantum(ScaleQuantumToAny(
1510 p->opacity,range),range) ? MagickTrue : MagickFalse;
1511 if (status != MagickFalse)
1512 {
1513 channel_statistics[OpacityChannel].depth++;
1514 continue;
1515 }
1516 }
1517 }
1518 if (image->colorspace == CMYKColorspace)
1519 {
1520 if (channel_statistics[BlackChannel].depth != MAGICKCORE_QUANTUM_DEPTH)
1521 {
1522 depth=channel_statistics[BlackChannel].depth;
1523 range=GetQuantumRange(depth);
1524 status=indexes[x] != ScaleAnyToQuantum(ScaleQuantumToAny(
1525 indexes[x],range),range) ? MagickTrue : MagickFalse;
1526 if (status != MagickFalse)
1527 {
1528 channel_statistics[BlackChannel].depth++;
1529 continue;
1530 }
1531 }
1532 }
1533 if ((double) p->red < channel_statistics[RedChannel].minima)
cristyce70c172010-01-07 17:15:30 +00001534 channel_statistics[RedChannel].minima=(double) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001535 if ((double) p->red > channel_statistics[RedChannel].maxima)
cristyce70c172010-01-07 17:15:30 +00001536 channel_statistics[RedChannel].maxima=(double) GetRedPixelComponent(p);
1537 channel_statistics[RedChannel].mean+=GetRedPixelComponent(p);
cristy46f08202010-01-10 04:04:21 +00001538 channel_statistics[RedChannel].standard_deviation+=(double) p->red*
1539 GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001540 channel_statistics[RedChannel].kurtosis+=(double) p->red*p->red*
cristyce70c172010-01-07 17:15:30 +00001541 p->red*GetRedPixelComponent(p);
cristy46f08202010-01-10 04:04:21 +00001542 channel_statistics[RedChannel].skewness+=(double) p->red*p->red*
1543 GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001544 if ((double) p->green < channel_statistics[GreenChannel].minima)
cristy46f08202010-01-10 04:04:21 +00001545 channel_statistics[GreenChannel].minima=(double)
1546 GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001547 if ((double) p->green > channel_statistics[GreenChannel].maxima)
cristy46f08202010-01-10 04:04:21 +00001548 channel_statistics[GreenChannel].maxima=(double)
1549 GetGreenPixelComponent(p);
cristyce70c172010-01-07 17:15:30 +00001550 channel_statistics[GreenChannel].mean+=GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001551 channel_statistics[GreenChannel].standard_deviation+=(double) p->green*
cristyce70c172010-01-07 17:15:30 +00001552 GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001553 channel_statistics[GreenChannel].kurtosis+=(double) p->green*p->green*
cristyce70c172010-01-07 17:15:30 +00001554 p->green*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001555 channel_statistics[GreenChannel].skewness+=(double) p->green*p->green*
cristyce70c172010-01-07 17:15:30 +00001556 GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001557 if ((double) p->blue < channel_statistics[BlueChannel].minima)
cristy46f08202010-01-10 04:04:21 +00001558 channel_statistics[BlueChannel].minima=(double)
1559 GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001560 if ((double) p->blue > channel_statistics[BlueChannel].maxima)
cristy46f08202010-01-10 04:04:21 +00001561 channel_statistics[BlueChannel].maxima=(double)
1562 GetBluePixelComponent(p);
cristyce70c172010-01-07 17:15:30 +00001563 channel_statistics[BlueChannel].mean+=GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001564 channel_statistics[BlueChannel].standard_deviation+=(double) p->blue*
cristyce70c172010-01-07 17:15:30 +00001565 GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001566 channel_statistics[BlueChannel].kurtosis+=(double) p->blue*p->blue*
cristyce70c172010-01-07 17:15:30 +00001567 p->blue*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001568 channel_statistics[BlueChannel].skewness+=(double) p->blue*p->blue*
cristyce70c172010-01-07 17:15:30 +00001569 GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001570 if (image->matte != MagickFalse)
1571 {
1572 if ((double) p->opacity < channel_statistics[OpacityChannel].minima)
cristy46f08202010-01-10 04:04:21 +00001573 channel_statistics[OpacityChannel].minima=(double)
1574 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001575 if ((double) p->opacity > channel_statistics[OpacityChannel].maxima)
cristy46f08202010-01-10 04:04:21 +00001576 channel_statistics[OpacityChannel].maxima=(double)
1577 GetOpacityPixelComponent(p);
cristyce70c172010-01-07 17:15:30 +00001578 channel_statistics[OpacityChannel].mean+=GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001579 channel_statistics[OpacityChannel].standard_deviation+=(double)
cristyce70c172010-01-07 17:15:30 +00001580 p->opacity*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001581 channel_statistics[OpacityChannel].kurtosis+=(double) p->opacity*
cristyce70c172010-01-07 17:15:30 +00001582 p->opacity*p->opacity*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001583 channel_statistics[OpacityChannel].skewness+=(double) p->opacity*
cristyce70c172010-01-07 17:15:30 +00001584 p->opacity*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00001585 }
1586 if (image->colorspace == CMYKColorspace)
1587 {
1588 if ((double) indexes[x] < channel_statistics[BlackChannel].minima)
1589 channel_statistics[BlackChannel].minima=(double) indexes[x];
1590 if ((double) indexes[x] > channel_statistics[BlackChannel].maxima)
1591 channel_statistics[BlackChannel].maxima=(double) indexes[x];
1592 channel_statistics[BlackChannel].mean+=indexes[x];
1593 channel_statistics[BlackChannel].standard_deviation+=(double)
1594 indexes[x]*indexes[x];
1595 channel_statistics[BlackChannel].kurtosis+=(double) indexes[x]*
1596 indexes[x]*indexes[x]*indexes[x];
1597 channel_statistics[BlackChannel].skewness+=(double) indexes[x]*
1598 indexes[x]*indexes[x];
1599 }
1600 x++;
1601 p++;
1602 }
1603 }
1604 area=(double) image->columns*image->rows;
1605 for (i=0; i < AllChannels; i++)
1606 {
1607 channel_statistics[i].mean/=area;
1608 channel_statistics[i].standard_deviation/=area;
1609 channel_statistics[i].kurtosis/=area;
1610 channel_statistics[i].skewness/=area;
1611 }
1612 for (i=0; i < AllChannels; i++)
1613 {
1614 channel_statistics[AllChannels].depth=(unsigned long) MagickMax((double)
1615 channel_statistics[AllChannels].depth,(double)
1616 channel_statistics[i].depth);
1617 channel_statistics[AllChannels].minima=MagickMin(
1618 channel_statistics[AllChannels].minima,channel_statistics[i].minima);
1619 channel_statistics[AllChannels].maxima=MagickMax(
1620 channel_statistics[AllChannels].maxima,channel_statistics[i].maxima);
1621 channel_statistics[AllChannels].mean+=channel_statistics[i].mean;
1622 channel_statistics[AllChannels].standard_deviation+=
1623 channel_statistics[i].standard_deviation;
1624 channel_statistics[AllChannels].kurtosis+=channel_statistics[i].kurtosis;
1625 channel_statistics[AllChannels].skewness+=channel_statistics[i].skewness;
1626 }
1627 channels=4;
1628 if (image->colorspace == CMYKColorspace)
1629 channels++;
1630 channel_statistics[AllChannels].mean/=channels;
1631 channel_statistics[AllChannels].standard_deviation/=channels;
1632 channel_statistics[AllChannels].kurtosis/=channels;
1633 channel_statistics[AllChannels].skewness/=channels;
1634 for (i=0; i <= AllChannels; i++)
1635 {
1636 sum_squares=0.0;
1637 sum_squares=channel_statistics[i].standard_deviation;
1638 sum_cubes=0.0;
1639 sum_cubes=channel_statistics[i].skewness;
1640 channel_statistics[i].standard_deviation=sqrt(
1641 channel_statistics[i].standard_deviation-
1642 (channel_statistics[i].mean*channel_statistics[i].mean));
1643 if (channel_statistics[i].standard_deviation == 0.0)
1644 {
1645 channel_statistics[i].kurtosis=0.0;
1646 channel_statistics[i].skewness=0.0;
1647 }
1648 else
1649 {
1650 channel_statistics[i].skewness=(channel_statistics[i].skewness-
1651 3.0*channel_statistics[i].mean*sum_squares+
1652 2.0*channel_statistics[i].mean*channel_statistics[i].mean*
1653 channel_statistics[i].mean)/
1654 (channel_statistics[i].standard_deviation*
1655 channel_statistics[i].standard_deviation*
1656 channel_statistics[i].standard_deviation);
1657 channel_statistics[i].kurtosis=(channel_statistics[i].kurtosis-
1658 4.0*channel_statistics[i].mean*sum_cubes+
1659 6.0*channel_statistics[i].mean*channel_statistics[i].mean*sum_squares-
1660 3.0*channel_statistics[i].mean*channel_statistics[i].mean*
1661 1.0*channel_statistics[i].mean*channel_statistics[i].mean)/
1662 (channel_statistics[i].standard_deviation*
1663 channel_statistics[i].standard_deviation*
1664 channel_statistics[i].standard_deviation*
1665 channel_statistics[i].standard_deviation)-3.0;
1666 }
1667 }
1668 return(channel_statistics);
1669}