blob: 1c783041109d515e9f5eda09ab282efb421d9861 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
20% Copyright 1999-2009 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/artifact.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/composite-private.h"
51#include "magick/enhance.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
54#include "magick/gem.h"
55#include "magick/geometry.h"
56#include "magick/histogram.h"
57#include "magick/image.h"
58#include "magick/image-private.h"
59#include "magick/memory_.h"
60#include "magick/monitor.h"
61#include "magick/monitor-private.h"
62#include "magick/option.h"
63#include "magick/quantum.h"
64#include "magick/quantum-private.h"
65#include "magick/resample.h"
66#include "magick/resample-private.h"
67#include "magick/statistic.h"
68#include "magick/string_.h"
69#include "magick/thread-private.h"
70#include "magick/token.h"
71#include "magick/xml-tree.h"
72
73/*
74%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
75% %
76% %
77% %
78% A u t o G a m m a I m a g e %
79% %
80% %
81% %
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83%
84% AutoGammaImage() extract the 'mean' from the image and adjust the image
85% to try make set its gamma appropriatally.
86%
cristy308b4e62009-09-21 14:40:44 +000087% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000088%
89% MagickBooleanType AutoGammaImage(Image *image)
90% MagickBooleanType AutoGammaImageChannel(Image *image,
91% const ChannelType channel)
92%
93% A description of each parameter follows:
94%
95% o image: The image to auto-level
96%
97% o channel: The channels to auto-level. If the special 'SyncChannels'
98% flag is set all given channels is adjusted in the same way using the
99% mean average of those channels.
100%
101*/
102
103MagickExport MagickBooleanType AutoGammaImage(Image *image)
104{
105 return(AutoGammaImageChannel(image,DefaultChannels));
106}
107
108MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
109 const ChannelType channel)
110{
111 MagickStatusType
112 status;
113
114 double
anthony4efe5972009-09-11 06:46:40 +0000115 mean,junk,gamma,logmean;
116
117 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000118
119 if ((channel & SyncChannels) != 0 )
120 {
121 /*
122 Apply gamma correction equally accross all given channels
123 */
124 GetImageChannelMean(image, channel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000125 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000126 //return GammaImageChannel(image, channel, gamma);
127 return LevelImageChannel(image, channel,
128 0.0, (double)QuantumRange, gamma);
129 }
130
131 /*
132 auto-gamma each channel separateally
133 */
134 status = MagickTrue;
135 if ((channel & RedChannel) != 0)
136 {
137 GetImageChannelMean(image, RedChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000138 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000139 //status = status && GammaImageChannel(image, RedChannel, gamma);
140 status = status && LevelImageChannel(image, RedChannel,
141 0.0, (double)QuantumRange, gamma);
142 }
143 if ((channel & GreenChannel) != 0)
144 {
145 GetImageChannelMean(image, GreenChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000146 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000147 //status = status && GammaImageChannel(image, GreenChannel, gamma);
148 status = status && LevelImageChannel(image, GreenChannel,
149 0.0, (double)QuantumRange, gamma);
150 }
151 if ((channel & BlueChannel) != 0)
152 {
153 GetImageChannelMean(image, BlueChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000154 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000155 //status = status && GammaImageChannel(image, BlueChannel, gamma);
156 status = status && LevelImageChannel(image, BlueChannel,
157 0.0, (double)QuantumRange, gamma);
158 }
159 if (((channel & OpacityChannel) != 0) &&
160 (image->matte == MagickTrue))
161 {
162 GetImageChannelMean(image, OpacityChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000163 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000164 //status = status && GammaImageChannel(image, OpacityChannel, gamma);
165 status = status && LevelImageChannel(image, OpacityChannel,
166 0.0, (double)QuantumRange, gamma);
167 }
168 if (((channel & IndexChannel) != 0) &&
169 (image->colorspace == CMYKColorspace))
170 {
171 GetImageChannelMean(image, IndexChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000172 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000173 //status = status && GammaImageChannel(image, IndexChannel, gamma);
174 status = status && LevelImageChannel(image, IndexChannel,
175 0.0, (double)QuantumRange, gamma);
176 }
177 return(status != 0 ? MagickTrue : MagickFalse);
178}
179
180/*
181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
182% %
183% %
184% %
185% A u t o L e v e l I m a g e %
186% %
187% %
188% %
189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
190%
191% AutoLevelImage() adjusts the levels of a particular image channel by
192% scaling the minimum and maximum values to the full quantum range.
193%
194% The format of the LevelImage method is:
195%
196% MagickBooleanType AutoLevelImage(Image *image)
197% MagickBooleanType AutoLevelImageChannel(Image *image,
198% const ChannelType channel)
199%
200% A description of each parameter follows:
201%
202% o image: The image to auto-level
203%
204% o channel: The channels to auto-level. If the special 'SyncChannels'
205% flag is set the min/max/mean value of all given channels is used for
206% all given channels, to all channels in the same way.
207%
208*/
209
210MagickExport MagickBooleanType AutoLevelImage(Image *image)
211{
212 return(AutoLevelImageChannel(image,DefaultChannels));
213}
214
215MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
216 const ChannelType channel)
217{
218 /*
219 This is simply a convenience function around a Min/Max Histogram Stretch
220 */
221 return MinMaxStretchImage(image, channel, 0.0, 0.0);
222}
223
224/*
225%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
226% %
227% %
228% %
229% C o l o r D e c i s i o n L i s t I m a g e %
230% %
231% %
232% %
233%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
234%
235% ColorDecisionListImage() accepts a lightweight Color Correction Collection
236% (CCC) file which solely contains one or more color corrections and applies
237% the correction to the image. Here is a sample CCC file:
238%
239% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
240% <ColorCorrection id="cc03345">
241% <SOPNode>
242% <Slope> 0.9 1.2 0.5 </Slope>
243% <Offset> 0.4 -0.5 0.6 </Offset>
244% <Power> 1.0 0.8 1.5 </Power>
245% </SOPNode>
246% <SATNode>
247% <Saturation> 0.85 </Saturation>
248% </SATNode>
249% </ColorCorrection>
250% </ColorCorrectionCollection>
251%
252% which includes the slop, offset, and power for each of the RGB channels
253% as well as the saturation.
254%
255% The format of the ColorDecisionListImage method is:
256%
257% MagickBooleanType ColorDecisionListImage(Image *image,
258% const char *color_correction_collection)
259%
260% A description of each parameter follows:
261%
262% o image: the image.
263%
264% o color_correction_collection: the color correction collection in XML.
265%
266*/
267MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
268 const char *color_correction_collection)
269{
270#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
271
272 typedef struct _Correction
273 {
274 double
275 slope,
276 offset,
277 power;
278 } Correction;
279
280 typedef struct _ColorCorrection
281 {
282 Correction
283 red,
284 green,
285 blue;
286
287 double
288 saturation;
289 } ColorCorrection;
290
291 char
292 token[MaxTextExtent];
293
294 ColorCorrection
295 color_correction;
296
297 const char
298 *content,
299 *p;
300
301 ExceptionInfo
302 *exception;
303
304 long
305 progress,
306 y;
307
308 MagickBooleanType
309 status;
310
311 PixelPacket
312 *cdl_map;
313
314 register long
315 i;
316
317 XMLTreeInfo
318 *cc,
319 *ccc,
320 *sat,
321 *sop;
322
323 CacheView
324 *image_view;
325
326 /*
327 Allocate and initialize cdl maps.
328 */
329 assert(image != (Image *) NULL);
330 assert(image->signature == MagickSignature);
331 if (image->debug != MagickFalse)
332 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
333 if (color_correction_collection == (const char *) NULL)
334 return(MagickFalse);
335 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
336 if (ccc == (XMLTreeInfo *) NULL)
337 return(MagickFalse);
338 cc=GetXMLTreeChild(ccc,"ColorCorrection");
339 if (cc == (XMLTreeInfo *) NULL)
340 {
341 ccc=DestroyXMLTree(ccc);
342 return(MagickFalse);
343 }
344 color_correction.red.slope=1.0;
345 color_correction.red.offset=0.0;
346 color_correction.red.power=1.0;
347 color_correction.green.slope=1.0;
348 color_correction.green.offset=0.0;
349 color_correction.green.power=1.0;
350 color_correction.blue.slope=1.0;
351 color_correction.blue.offset=0.0;
352 color_correction.blue.power=1.0;
353 color_correction.saturation=0.0;
354 sop=GetXMLTreeChild(cc,"SOPNode");
355 if (sop != (XMLTreeInfo *) NULL)
356 {
357 XMLTreeInfo
358 *offset,
359 *power,
360 *slope;
361
362 slope=GetXMLTreeChild(sop,"Slope");
363 if (slope != (XMLTreeInfo *) NULL)
364 {
365 content=GetXMLTreeContent(slope);
366 p=(const char *) content;
367 for (i=0; (*p != '\0') && (i < 3); i++)
368 {
369 GetMagickToken(p,&p,token);
370 if (*token == ',')
371 GetMagickToken(p,&p,token);
372 switch (i)
373 {
374 case 0: color_correction.red.slope=atof(token); break;
375 case 1: color_correction.green.slope=atof(token); break;
376 case 2: color_correction.blue.slope=atof(token); break;
377 }
378 }
379 }
380 offset=GetXMLTreeChild(sop,"Offset");
381 if (offset != (XMLTreeInfo *) NULL)
382 {
383 content=GetXMLTreeContent(offset);
384 p=(const char *) content;
385 for (i=0; (*p != '\0') && (i < 3); i++)
386 {
387 GetMagickToken(p,&p,token);
388 if (*token == ',')
389 GetMagickToken(p,&p,token);
390 switch (i)
391 {
392 case 0: color_correction.red.offset=atof(token); break;
393 case 1: color_correction.green.offset=atof(token); break;
394 case 2: color_correction.blue.offset=atof(token); break;
395 }
396 }
397 }
398 power=GetXMLTreeChild(sop,"Power");
399 if (power != (XMLTreeInfo *) NULL)
400 {
401 content=GetXMLTreeContent(power);
402 p=(const char *) content;
403 for (i=0; (*p != '\0') && (i < 3); i++)
404 {
405 GetMagickToken(p,&p,token);
406 if (*token == ',')
407 GetMagickToken(p,&p,token);
408 switch (i)
409 {
410 case 0: color_correction.red.power=atof(token); break;
411 case 1: color_correction.green.power=atof(token); break;
412 case 2: color_correction.blue.power=atof(token); break;
413 }
414 }
415 }
416 }
417 sat=GetXMLTreeChild(cc,"SATNode");
418 if (sat != (XMLTreeInfo *) NULL)
419 {
420 XMLTreeInfo
421 *saturation;
422
423 saturation=GetXMLTreeChild(sat,"Saturation");
424 if (saturation != (XMLTreeInfo *) NULL)
425 {
426 content=GetXMLTreeContent(saturation);
427 p=(const char *) content;
428 GetMagickToken(p,&p,token);
429 color_correction.saturation=atof(token);
430 }
431 }
432 ccc=DestroyXMLTree(ccc);
433 if (image->debug != MagickFalse)
434 {
435 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
436 " Color Correction Collection:");
437 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
438 " color_correction.red.slope: %g",color_correction.red.slope);
439 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
440 " color_correction.red.offset: %g",color_correction.red.offset);
441 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
442 " color_correction.red.power: %g",color_correction.red.power);
443 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
444 " color_correction.green.slope: %g",color_correction.green.slope);
445 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
446 " color_correction.green.offset: %g",color_correction.green.offset);
447 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
448 " color_correction.green.power: %g",color_correction.green.power);
449 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
450 " color_correction.blue.slope: %g",color_correction.blue.slope);
451 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
452 " color_correction.blue.offset: %g",color_correction.blue.offset);
453 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
454 " color_correction.blue.power: %g",color_correction.blue.power);
455 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
456 " color_correction.saturation: %g",color_correction.saturation);
457 }
458 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
459 if (cdl_map == (PixelPacket *) NULL)
460 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
461 image->filename);
cristy629a6c72009-09-13 23:28:22 +0000462#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000463 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +0000464#endif
465 for (i=0; i <= (long) MaxMap; i++)
466 {
467 cdl_map[i].red=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
468 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
469 color_correction.red.offset,color_correction.red.power)))));
470 cdl_map[i].green=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
471 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
472 color_correction.green.offset,color_correction.green.power)))));
473 cdl_map[i].blue=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
474 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
475 color_correction.blue.offset,color_correction.blue.power)))));
476 }
477 if (image->storage_class == PseudoClass)
478 {
479 /*
480 Apply transfer function to colormap.
481 */
cristy629a6c72009-09-13 23:28:22 +0000482#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000483 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000484#endif
485 for (i=0; i < (long) image->colors; i++)
486 {
487 double
488 luma;
489
490 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
491 0.0722*image->colormap[i].blue;
492 image->colormap[i].red=RoundToQuantum(luma+color_correction.saturation*
493 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
494 image->colormap[i].green=RoundToQuantum(luma+
495 color_correction.saturation*cdl_map[ScaleQuantumToMap(
496 image->colormap[i].green)].green-luma);
497 image->colormap[i].blue=RoundToQuantum(luma+color_correction.saturation*
498 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
499 }
500 }
501 /*
502 Apply transfer function to image.
503 */
504 status=MagickTrue;
505 progress=0;
506 exception=(&image->exception);
507 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +0000508#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000509 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000510#endif
511 for (y=0; y < (long) image->rows; y++)
512 {
513 double
514 luma;
515
516 register long
517 x;
518
519 register PixelPacket
520 *__restrict q;
521
522 if (status == MagickFalse)
523 continue;
524 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
525 if (q == (PixelPacket *) NULL)
526 {
527 status=MagickFalse;
528 continue;
529 }
530 for (x=0; x < (long) image->columns; x++)
531 {
532 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
533 q->red=RoundToQuantum(luma+color_correction.saturation*
534 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
535 q->green=RoundToQuantum(luma+color_correction.saturation*
536 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
537 q->blue=RoundToQuantum(luma+color_correction.saturation*
538 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
539 q++;
540 }
541 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
542 status=MagickFalse;
543 if (image->progress_monitor != (MagickProgressMonitor) NULL)
544 {
545 MagickBooleanType
546 proceed;
547
cristy629a6c72009-09-13 23:28:22 +0000548#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +0000549 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
550#endif
551 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
552 progress++,image->rows);
553 if (proceed == MagickFalse)
554 status=MagickFalse;
555 }
556 }
557 image_view=DestroyCacheView(image_view);
558 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
559 return(status);
560}
561
562/*
563%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
564% %
565% %
566% %
567% C l u t I m a g e %
568% %
569% %
570% %
571%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
572%
573% ClutImage() replaces each color value in the given image, by using it as an
574% index to lookup a replacement color value in a Color Look UP Table in the
575% form of an image. The values are extracted along a diagonal of the CLUT
576% image so either a horizontal or vertial gradient image can be used.
577%
578% Typically this is used to either re-color a gray-scale image according to a
579% color gradient in the CLUT image, or to perform a freeform histogram
580% (level) adjustment according to the (typically gray-scale) gradient in the
581% CLUT image.
582%
583% When the 'channel' mask includes the matte/alpha transparency channel but
584% one image has no such channel it is assumed that that image is a simple
585% gray-scale image that will effect the alpha channel values, either for
586% gray-scale coloring (with transparent or semi-transparent colors), or
587% a histogram adjustment of existing alpha channel values. If both images
588% have matte channels, direct and normal indexing is applied, which is rarely
589% used.
590%
591% The format of the ClutImage method is:
592%
593% MagickBooleanType ClutImage(Image *image,Image *clut_image)
594% MagickBooleanType ClutImageChannel(Image *image,
595% const ChannelType channel,Image *clut_image)
596%
597% A description of each parameter follows:
598%
599% o image: the image, which is replaced by indexed CLUT values
600%
601% o clut_image: the color lookup table image for replacement color values.
602%
603% o channel: the channel.
604%
605*/
606
607MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
608{
609 return(ClutImageChannel(image,DefaultChannels,clut_image));
610}
611
612MagickExport MagickBooleanType ClutImageChannel(Image *image,
613 const ChannelType channel,const Image *clut_image)
614{
615#define ClutImageTag "Clut/Image"
616
617 ExceptionInfo
618 *exception;
619
620 long
621 adjust,
622 progress,
623 y;
624
625 MagickBooleanType
626 status;
627
628 MagickPixelPacket
629 zero;
630
631 ResampleFilter
632 **resample_filter;
633
634 CacheView
635 *image_view;
636
637 assert(image != (Image *) NULL);
638 assert(image->signature == MagickSignature);
639 if (image->debug != MagickFalse)
640 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
641 assert(clut_image != (Image *) NULL);
642 assert(clut_image->signature == MagickSignature);
643 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
644 return(MagickFalse);
645 /*
646 Clut image.
647 */
648 status=MagickTrue;
649 progress=0;
650 GetMagickPixelPacket(clut_image,&zero);
651 adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
652 exception=(&image->exception);
653 resample_filter=AcquireResampleFilterThreadSet(clut_image,MagickTrue,
654 exception);
655 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +0000656#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000657 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000658#endif
659 for (y=0; y < (long) image->rows; y++)
660 {
661 MagickPixelPacket
662 pixel;
663
664 register IndexPacket
665 *__restrict indexes;
666
667 register long
668 id,
669 x;
670
671 register PixelPacket
672 *__restrict q;
673
674 if (status == MagickFalse)
675 continue;
676 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
677 if (q == (PixelPacket *) NULL)
678 {
679 status=MagickFalse;
680 continue;
681 }
682 indexes=GetCacheViewAuthenticIndexQueue(image_view);
683 pixel=zero;
684 id=GetOpenMPThreadId();
685 for (x=0; x < (long) image->columns; x++)
686 {
687 /*
688 PROGRAMMERS WARNING:
689
690 Apply OpacityChannel BEFORE the color channels. Do not re-order.
691
692 The handling special case 2 (coloring gray-scale), requires access to
693 the unmodified colors of the original image to determine the index
694 value. As such alpha/matte channel handling must be performed BEFORE,
695 any of the color channels are modified.
696
697 */
698 if ((channel & OpacityChannel) != 0)
699 {
700 if (clut_image->matte == MagickFalse)
701 {
702 /*
703 A gray-scale LUT replacement for an image alpha channel.
704 */
705 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
706 (QuantumRange-q->opacity)*(clut_image->columns+adjust),
707 QuantumScale*(QuantumRange-q->opacity)*(clut_image->rows+
708 adjust),&pixel);
709 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
710 &pixel));
711 }
712 else
713 if (image->matte == MagickFalse)
714 {
715 /*
716 A greyscale image being colored by a LUT with transparency.
717 */
718 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
719 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
720 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
721 q->opacity=RoundToQuantum(pixel.opacity);
722 }
723 else
724 {
725 /*
726 Direct alpha channel lookup.
727 */
728 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
729 q->opacity*(clut_image->columns-adjust),QuantumScale*
730 q->opacity* (clut_image->rows-adjust),&pixel);
731 q->opacity=RoundToQuantum(pixel.opacity);
732 }
733 }
734 if ((channel & RedChannel) != 0)
735 {
736 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
737 (clut_image->columns-adjust),QuantumScale*q->red*
738 (clut_image->rows-adjust),&pixel);
739 q->red=RoundToQuantum(pixel.red);
740 }
741 if ((channel & GreenChannel) != 0)
742 {
743 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
744 (clut_image->columns-adjust),QuantumScale*q->green*
745 (clut_image->rows-adjust),&pixel);
746 q->green=RoundToQuantum(pixel.green);
747 }
748 if ((channel & BlueChannel) != 0)
749 {
750 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
751 (clut_image->columns-adjust),QuantumScale*q->blue*
752 (clut_image->rows-adjust),&pixel);
753 q->blue=RoundToQuantum(pixel.blue);
754 }
755 if (((channel & IndexChannel) != 0) &&
756 (image->colorspace == CMYKColorspace))
757 {
758 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
759 (clut_image->columns-adjust),QuantumScale*indexes[x]*
760 (clut_image->rows-adjust),&pixel);
761 indexes[x]=RoundToQuantum(pixel.index);
762 }
763 q++;
764 }
765 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
766 status=MagickFalse;
767 if (image->progress_monitor != (MagickProgressMonitor) NULL)
768 {
769 MagickBooleanType
770 proceed;
771
cristy629a6c72009-09-13 23:28:22 +0000772#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +0000773 #pragma omp critical (MagickCore_ClutImageChannel)
774#endif
775 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
776 if (proceed == MagickFalse)
777 status=MagickFalse;
778 }
779 }
780 image_view=DestroyCacheView(image_view);
781 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
782 /*
783 Enable alpha channel if CLUT image could enable it.
784 */
785 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
786 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
787 return(status);
788}
789
790/*
791%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
792% %
793% %
794% %
795% C o n t r a s t I m a g e %
796% %
797% %
798% %
799%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
800%
801% ContrastImage() enhances the intensity differences between the lighter and
802% darker elements of the image. Set sharpen to a MagickTrue to increase the
803% image contrast otherwise the contrast is reduced.
804%
805% The format of the ContrastImage method is:
806%
807% MagickBooleanType ContrastImage(Image *image,
808% const MagickBooleanType sharpen)
809%
810% A description of each parameter follows:
811%
812% o image: the image.
813%
814% o sharpen: Increase or decrease image contrast.
815%
816*/
817
818static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
819{
820 double
821 brightness,
822 hue,
823 saturation;
824
825 /*
826 Enhance contrast: dark color become darker, light color become lighter.
827 */
828 assert(red != (Quantum *) NULL);
829 assert(green != (Quantum *) NULL);
830 assert(blue != (Quantum *) NULL);
831 hue=0.0;
832 saturation=0.0;
833 brightness=0.0;
834 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
835 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
836 if (brightness > 1.0)
837 brightness=1.0;
838 else
839 if (brightness < 0.0)
840 brightness=0.0;
841 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
842}
843
844MagickExport MagickBooleanType ContrastImage(Image *image,
845 const MagickBooleanType sharpen)
846{
847#define ContrastImageTag "Contrast/Image"
848
849 ExceptionInfo
850 *exception;
851
852 int
853 sign;
854
855 long
856 progress,
857 y;
858
859 MagickBooleanType
860 status;
861
862 register long
863 i;
864
865 CacheView
866 *image_view;
867
868 assert(image != (Image *) NULL);
869 assert(image->signature == MagickSignature);
870 if (image->debug != MagickFalse)
871 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
872 sign=sharpen != MagickFalse ? 1 : -1;
873 if (image->storage_class == PseudoClass)
874 {
875 /*
876 Contrast enhance colormap.
877 */
878 for (i=0; i < (long) image->colors; i++)
879 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
880 &image->colormap[i].blue);
881 }
882 /*
883 Contrast enhance image.
884 */
885 status=MagickTrue;
886 progress=0;
887 exception=(&image->exception);
888 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +0000889#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +0000890 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000891#endif
892 for (y=0; y < (long) image->rows; y++)
893 {
894 register long
895 x;
896
897 register PixelPacket
898 *__restrict q;
899
900 if (status == MagickFalse)
901 continue;
902 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
903 if (q == (PixelPacket *) NULL)
904 {
905 status=MagickFalse;
906 continue;
907 }
908 for (x=0; x < (long) image->columns; x++)
909 {
910 Contrast(sign,&q->red,&q->green,&q->blue);
911 q++;
912 }
913 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
914 status=MagickFalse;
915 if (image->progress_monitor != (MagickProgressMonitor) NULL)
916 {
917 MagickBooleanType
918 proceed;
919
cristy629a6c72009-09-13 23:28:22 +0000920#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +0000921 #pragma omp critical (MagickCore_ContrastImage)
922#endif
923 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
924 if (proceed == MagickFalse)
925 status=MagickFalse;
926 }
927 }
928 image_view=DestroyCacheView(image_view);
929 return(status);
930}
931
932/*
933%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
934% %
935% %
936% %
937% C o n t r a s t S t r e t c h I m a g e %
938% %
939% %
940% %
941%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
942%
943% The ContrastStretchImage() is a simple image enhancement technique that
944% attempts to improve the contrast in an image by `stretching' the range of
945% intensity values it contains to span a desired range of values. It differs
946% from the more sophisticated histogram equalization in that it can only
947% apply % a linear scaling function to the image pixel values. As a result
948% the `enhancement' is less harsh.
949%
950% The format of the ContrastStretchImage method is:
951%
952% MagickBooleanType ContrastStretchImage(Image *image,
953% const char *levels)
954% MagickBooleanType ContrastStretchImageChannel(Image *image,
955% const unsigned long channel,const double black_point,
956% const double white_point)
957%
958% A description of each parameter follows:
959%
960% o image: the image.
961%
962% o channel: the channel.
963%
964% o black_point: the black point.
965%
966% o white_point: the white point.
967%
968% o levels: Specify the levels where the black and white points have the
969% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
970%
971*/
972
973MagickExport MagickBooleanType ContrastStretchImage(Image *image,
974 const char *levels)
975{
976 double
977 black_point,
978 white_point;
979
980 GeometryInfo
981 geometry_info;
982
983 MagickBooleanType
984 status;
985
986 MagickStatusType
987 flags;
988
989 /*
990 Parse levels.
991 */
992 if (levels == (char *) NULL)
993 return(MagickFalse);
994 flags=ParseGeometry(levels,&geometry_info);
995 black_point=geometry_info.rho;
996 white_point=(double) image->columns*image->rows;
997 if ((flags & SigmaValue) != 0)
998 white_point=geometry_info.sigma;
999 if ((flags & PercentValue) != 0)
1000 {
1001 black_point*=(double) QuantumRange/100.0;
1002 white_point*=(double) QuantumRange/100.0;
1003 }
1004 if ((flags & SigmaValue) == 0)
1005 white_point=(double) image->columns*image->rows-black_point;
1006 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1007 white_point);
1008 return(status);
1009}
1010
1011MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1012 const ChannelType channel,const double black_point,const double white_point)
1013{
1014#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1015#define ContrastStretchImageTag "ContrastStretch/Image"
1016
1017 double
1018 intensity;
1019
1020 ExceptionInfo
1021 *exception;
1022
1023 long
1024 progress,
1025 y;
1026
1027 MagickBooleanType
1028 status;
1029
1030 MagickPixelPacket
1031 black,
1032 *histogram,
1033 *stretch_map,
1034 white;
1035
1036 register long
1037 i;
1038
1039 CacheView
1040 *image_view;
1041
1042 /*
1043 Allocate histogram and stretch map.
1044 */
1045 assert(image != (Image *) NULL);
1046 assert(image->signature == MagickSignature);
1047 if (image->debug != MagickFalse)
1048 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1049 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1050 sizeof(*histogram));
1051 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1052 sizeof(*stretch_map));
1053 if ((histogram == (MagickPixelPacket *) NULL) ||
1054 (stretch_map == (MagickPixelPacket *) NULL))
1055 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1056 image->filename);
1057 /*
1058 Form histogram.
1059 */
1060 status=MagickTrue;
1061 exception=(&image->exception);
1062 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1063 image_view=AcquireCacheView(image);
1064 for (y=0; y < (long) image->rows; y++)
1065 {
1066 register const PixelPacket
1067 *__restrict p;
1068
1069 register IndexPacket
1070 *__restrict indexes;
1071
1072 register long
1073 x;
1074
1075 if (status == MagickFalse)
1076 continue;
1077 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1078 if (p == (const PixelPacket *) NULL)
1079 {
1080 status=MagickFalse;
1081 continue;
1082 }
1083 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1084 if (channel == DefaultChannels)
1085 for (x=0; x < (long) image->columns; x++)
1086 {
1087 Quantum
1088 intensity;
1089
1090 intensity=PixelIntensityToQuantum(p);
1091 histogram[ScaleQuantumToMap(intensity)].red++;
1092 histogram[ScaleQuantumToMap(intensity)].green++;
1093 histogram[ScaleQuantumToMap(intensity)].blue++;
1094 histogram[ScaleQuantumToMap(intensity)].index++;
1095 p++;
1096 }
1097 else
1098 for (x=0; x < (long) image->columns; x++)
1099 {
1100 if ((channel & RedChannel) != 0)
1101 histogram[ScaleQuantumToMap(p->red)].red++;
1102 if ((channel & GreenChannel) != 0)
1103 histogram[ScaleQuantumToMap(p->green)].green++;
1104 if ((channel & BlueChannel) != 0)
1105 histogram[ScaleQuantumToMap(p->blue)].blue++;
1106 if ((channel & OpacityChannel) != 0)
1107 histogram[ScaleQuantumToMap(p->opacity)].opacity++;
1108 if (((channel & IndexChannel) != 0) &&
1109 (image->colorspace == CMYKColorspace))
1110 histogram[ScaleQuantumToMap(indexes[x])].index++;
1111 p++;
1112 }
1113 }
1114 /*
1115 Find the histogram boundaries by locating the black/white levels.
1116 */
1117 black.red=0.0;
1118 white.red=MaxRange(QuantumRange);
1119 if ((channel & RedChannel) != 0)
1120 {
1121 intensity=0.0;
1122 for (i=0; i <= (long) MaxMap; i++)
1123 {
1124 intensity+=histogram[i].red;
1125 if (intensity > black_point)
1126 break;
1127 }
1128 black.red=(MagickRealType) i;
1129 intensity=0.0;
1130 for (i=(long) MaxMap; i != 0; i--)
1131 {
1132 intensity+=histogram[i].red;
1133 if (intensity > ((double) image->columns*image->rows-white_point))
1134 break;
1135 }
1136 white.red=(MagickRealType) i;
1137 }
1138 black.green=0.0;
1139 white.green=MaxRange(QuantumRange);
1140 if ((channel & GreenChannel) != 0)
1141 {
1142 intensity=0.0;
1143 for (i=0; i <= (long) MaxMap; i++)
1144 {
1145 intensity+=histogram[i].green;
1146 if (intensity > black_point)
1147 break;
1148 }
1149 black.green=(MagickRealType) i;
1150 intensity=0.0;
1151 for (i=(long) MaxMap; i != 0; i--)
1152 {
1153 intensity+=histogram[i].green;
1154 if (intensity > ((double) image->columns*image->rows-white_point))
1155 break;
1156 }
1157 white.green=(MagickRealType) i;
1158 }
1159 black.blue=0.0;
1160 white.blue=MaxRange(QuantumRange);
1161 if ((channel & BlueChannel) != 0)
1162 {
1163 intensity=0.0;
1164 for (i=0; i <= (long) MaxMap; i++)
1165 {
1166 intensity+=histogram[i].blue;
1167 if (intensity > black_point)
1168 break;
1169 }
1170 black.blue=(MagickRealType) i;
1171 intensity=0.0;
1172 for (i=(long) MaxMap; i != 0; i--)
1173 {
1174 intensity+=histogram[i].blue;
1175 if (intensity > ((double) image->columns*image->rows-white_point))
1176 break;
1177 }
1178 white.blue=(MagickRealType) i;
1179 }
1180 black.opacity=0.0;
1181 white.opacity=MaxRange(QuantumRange);
1182 if ((channel & OpacityChannel) != 0)
1183 {
1184 intensity=0.0;
1185 for (i=0; i <= (long) MaxMap; i++)
1186 {
1187 intensity+=histogram[i].opacity;
1188 if (intensity > black_point)
1189 break;
1190 }
1191 black.opacity=(MagickRealType) i;
1192 intensity=0.0;
1193 for (i=(long) MaxMap; i != 0; i--)
1194 {
1195 intensity+=histogram[i].opacity;
1196 if (intensity > ((double) image->columns*image->rows-white_point))
1197 break;
1198 }
1199 white.opacity=(MagickRealType) i;
1200 }
1201 black.index=0.0;
1202 white.index=MaxRange(QuantumRange);
1203 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1204 {
1205 intensity=0.0;
1206 for (i=0; i <= (long) MaxMap; i++)
1207 {
1208 intensity+=histogram[i].index;
1209 if (intensity > black_point)
1210 break;
1211 }
1212 black.index=(MagickRealType) i;
1213 intensity=0.0;
1214 for (i=(long) MaxMap; i != 0; i--)
1215 {
1216 intensity+=histogram[i].index;
1217 if (intensity > ((double) image->columns*image->rows-white_point))
1218 break;
1219 }
1220 white.index=(MagickRealType) i;
1221 }
1222 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1223 /*
1224 Stretch the histogram to create the stretched image mapping.
1225 */
1226 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristy629a6c72009-09-13 23:28:22 +00001227#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001228 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001229#endif
1230 for (i=0; i <= (long) MaxMap; i++)
1231 {
1232 if ((channel & RedChannel) != 0)
1233 {
1234 if (i < (long) black.red)
1235 stretch_map[i].red=0.0;
1236 else
1237 if (i > (long) white.red)
1238 stretch_map[i].red=(MagickRealType) QuantumRange;
1239 else
1240 if (black.red != white.red)
1241 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1242 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1243 }
1244 if ((channel & GreenChannel) != 0)
1245 {
1246 if (i < (long) black.green)
1247 stretch_map[i].green=0.0;
1248 else
1249 if (i > (long) white.green)
1250 stretch_map[i].green=(MagickRealType) QuantumRange;
1251 else
1252 if (black.green != white.green)
1253 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1254 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1255 black.green)));
1256 }
1257 if ((channel & BlueChannel) != 0)
1258 {
1259 if (i < (long) black.blue)
1260 stretch_map[i].blue=0.0;
1261 else
1262 if (i > (long) white.blue)
1263 stretch_map[i].blue=(MagickRealType) QuantumRange;
1264 else
1265 if (black.blue != white.blue)
1266 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1267 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1268 black.blue)));
1269 }
1270 if ((channel & OpacityChannel) != 0)
1271 {
1272 if (i < (long) black.opacity)
1273 stretch_map[i].opacity=0.0;
1274 else
1275 if (i > (long) white.opacity)
1276 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1277 else
1278 if (black.opacity != white.opacity)
1279 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1280 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1281 black.opacity)));
1282 }
1283 if (((channel & IndexChannel) != 0) &&
1284 (image->colorspace == CMYKColorspace))
1285 {
1286 if (i < (long) black.index)
1287 stretch_map[i].index=0.0;
1288 else
1289 if (i > (long) white.index)
1290 stretch_map[i].index=(MagickRealType) QuantumRange;
1291 else
1292 if (black.index != white.index)
1293 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1294 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1295 black.index)));
1296 }
1297 }
1298 /*
1299 Stretch the image.
1300 */
1301 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1302 (image->colorspace == CMYKColorspace)))
1303 image->storage_class=DirectClass;
1304 if (image->storage_class == PseudoClass)
1305 {
1306 /*
1307 Stretch colormap.
1308 */
cristy629a6c72009-09-13 23:28:22 +00001309#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001310 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001311#endif
1312 for (i=0; i < (long) image->colors; i++)
1313 {
1314 if ((channel & RedChannel) != 0)
1315 {
1316 if (black.red != white.red)
1317 image->colormap[i].red=RoundToQuantum(stretch_map[
1318 ScaleQuantumToMap(image->colormap[i].red)].red);
1319 }
1320 if ((channel & GreenChannel) != 0)
1321 {
1322 if (black.green != white.green)
1323 image->colormap[i].green=RoundToQuantum(stretch_map[
1324 ScaleQuantumToMap(image->colormap[i].green)].green);
1325 }
1326 if ((channel & BlueChannel) != 0)
1327 {
1328 if (black.blue != white.blue)
1329 image->colormap[i].blue=RoundToQuantum(stretch_map[
1330 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1331 }
1332 if ((channel & OpacityChannel) != 0)
1333 {
1334 if (black.opacity != white.opacity)
1335 image->colormap[i].opacity=RoundToQuantum(stretch_map[
1336 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1337 }
1338 }
1339 }
1340 /*
1341 Stretch image.
1342 */
1343 status=MagickTrue;
1344 progress=0;
cristy629a6c72009-09-13 23:28:22 +00001345#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001346 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001347#endif
1348 for (y=0; y < (long) image->rows; y++)
1349 {
1350 register IndexPacket
1351 *__restrict indexes;
1352
1353 register long
1354 x;
1355
1356 register PixelPacket
1357 *__restrict q;
1358
1359 if (status == MagickFalse)
1360 continue;
1361 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1362 if (q == (PixelPacket *) NULL)
1363 {
1364 status=MagickFalse;
1365 continue;
1366 }
1367 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1368 for (x=0; x < (long) image->columns; x++)
1369 {
1370 if ((channel & RedChannel) != 0)
1371 {
1372 if (black.red != white.red)
1373 q->red=RoundToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
1374 }
1375 if ((channel & GreenChannel) != 0)
1376 {
1377 if (black.green != white.green)
1378 q->green=RoundToQuantum(stretch_map[ScaleQuantumToMap(
1379 q->green)].green);
1380 }
1381 if ((channel & BlueChannel) != 0)
1382 {
1383 if (black.blue != white.blue)
1384 q->blue=RoundToQuantum(stretch_map[ScaleQuantumToMap(
1385 q->blue)].blue);
1386 }
1387 if ((channel & OpacityChannel) != 0)
1388 {
1389 if (black.opacity != white.opacity)
1390 q->opacity=RoundToQuantum(stretch_map[ScaleQuantumToMap(
1391 q->opacity)].opacity);
1392 }
1393 if (((channel & IndexChannel) != 0) &&
1394 (image->colorspace == CMYKColorspace))
1395 {
1396 if (black.index != white.index)
1397 indexes[x]=(IndexPacket) RoundToQuantum(stretch_map[
1398 ScaleQuantumToMap(indexes[x])].index);
1399 }
1400 q++;
1401 }
1402 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1403 status=MagickFalse;
1404 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1405 {
1406 MagickBooleanType
1407 proceed;
1408
cristy629a6c72009-09-13 23:28:22 +00001409#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00001410 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1411#endif
1412 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1413 image->rows);
1414 if (proceed == MagickFalse)
1415 status=MagickFalse;
1416 }
1417 }
1418 image_view=DestroyCacheView(image_view);
1419 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1420 return(status);
1421}
1422
1423/*
1424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1425% %
1426% %
1427% %
1428% E n h a n c e I m a g e %
1429% %
1430% %
1431% %
1432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433%
1434% EnhanceImage() applies a digital filter that improves the quality of a
1435% noisy image.
1436%
1437% The format of the EnhanceImage method is:
1438%
1439% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1440%
1441% A description of each parameter follows:
1442%
1443% o image: the image.
1444%
1445% o exception: return any errors or warnings in this structure.
1446%
1447*/
1448MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1449{
1450#define Enhance(weight) \
1451 mean=((MagickRealType) r->red+pixel.red)/2; \
1452 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1453 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1454 mean)*distance*distance; \
1455 mean=((MagickRealType) r->green+pixel.green)/2; \
1456 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1457 distance_squared+=4.0*distance*distance; \
1458 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1459 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1460 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1461 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1462 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1463 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1464 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1465 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1466 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1467 QuantumRange/25.0f)) \
1468 { \
1469 aggregate.red+=(weight)*r->red; \
1470 aggregate.green+=(weight)*r->green; \
1471 aggregate.blue+=(weight)*r->blue; \
1472 aggregate.opacity+=(weight)*r->opacity; \
1473 total_weight+=(weight); \
1474 } \
1475 r++;
1476#define EnhanceImageTag "Enhance/Image"
1477
1478 Image
1479 *enhance_image;
1480
1481 long
1482 progress,
1483 y;
1484
1485 MagickBooleanType
1486 status;
1487
1488 MagickPixelPacket
1489 zero;
1490
1491 CacheView
1492 *enhance_view,
1493 *image_view;
1494
1495 /*
1496 Initialize enhanced image attributes.
1497 */
1498 assert(image != (const Image *) NULL);
1499 assert(image->signature == MagickSignature);
1500 if (image->debug != MagickFalse)
1501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1502 assert(exception != (ExceptionInfo *) NULL);
1503 assert(exception->signature == MagickSignature);
1504 if ((image->columns < 5) || (image->rows < 5))
1505 return((Image *) NULL);
1506 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1507 exception);
1508 if (enhance_image == (Image *) NULL)
1509 return((Image *) NULL);
1510 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1511 {
1512 InheritException(exception,&enhance_image->exception);
1513 enhance_image=DestroyImage(enhance_image);
1514 return((Image *) NULL);
1515 }
1516 /*
1517 Enhance image.
1518 */
1519 status=MagickTrue;
1520 progress=0;
1521 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1522 image_view=AcquireCacheView(image);
1523 enhance_view=AcquireCacheView(enhance_image);
cristy629a6c72009-09-13 23:28:22 +00001524#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001525 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001526#endif
1527 for (y=0; y < (long) image->rows; y++)
1528 {
1529 register const PixelPacket
1530 *__restrict p;
1531
1532 register long
1533 x;
1534
1535 register PixelPacket
1536 *__restrict q;
1537
1538 /*
1539 Read another scan line.
1540 */
1541 if (status == MagickFalse)
1542 continue;
1543 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1544 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1545 exception);
1546 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1547 {
1548 status=MagickFalse;
1549 continue;
1550 }
1551 for (x=0; x < (long) image->columns; x++)
1552 {
1553 MagickPixelPacket
1554 aggregate;
1555
1556 MagickRealType
1557 distance,
1558 distance_squared,
1559 mean,
1560 total_weight;
1561
1562 PixelPacket
1563 pixel;
1564
1565 register const PixelPacket
1566 *__restrict r;
1567
1568 /*
1569 Compute weighted average of target pixel color components.
1570 */
1571 aggregate=zero;
1572 total_weight=0.0;
1573 r=p+2*(image->columns+4)+2;
1574 pixel=(*r);
1575 r=p;
1576 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1577 r=p+(image->columns+4);
1578 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1579 r=p+2*(image->columns+4);
1580 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1581 r=p+3*(image->columns+4);
1582 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1583 r=p+4*(image->columns+4);
1584 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1585 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1586 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1587 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1588 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1589 total_weight);
1590 p++;
1591 q++;
1592 }
1593 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1594 status=MagickFalse;
1595 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1596 {
1597 MagickBooleanType
1598 proceed;
1599
cristy629a6c72009-09-13 23:28:22 +00001600#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00001601 #pragma omp critical (MagickCore_EnhanceImage)
1602#endif
1603 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1604 if (proceed == MagickFalse)
1605 status=MagickFalse;
1606 }
1607 }
1608 enhance_view=DestroyCacheView(enhance_view);
1609 image_view=DestroyCacheView(image_view);
1610 return(enhance_image);
1611}
1612
1613/*
1614%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1615% %
1616% %
1617% %
1618% E q u a l i z e I m a g e %
1619% %
1620% %
1621% %
1622%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1623%
1624% EqualizeImage() applies a histogram equalization to the image.
1625%
1626% The format of the EqualizeImage method is:
1627%
1628% MagickBooleanType EqualizeImage(Image *image)
1629% MagickBooleanType EqualizeImageChannel(Image *image,
1630% const ChannelType channel)
1631%
1632% A description of each parameter follows:
1633%
1634% o image: the image.
1635%
1636% o channel: the channel.
1637%
1638*/
1639
1640MagickExport MagickBooleanType EqualizeImage(Image *image)
1641{
1642 return(EqualizeImageChannel(image,DefaultChannels));
1643}
1644
1645MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1646 const ChannelType channel)
1647{
1648#define EqualizeImageTag "Equalize/Image"
1649
1650 ExceptionInfo
1651 *exception;
1652
1653 long
1654 progress,
1655 y;
1656
1657 MagickBooleanType
1658 status;
1659
1660 MagickPixelPacket
1661 black,
1662 *equalize_map,
1663 *histogram,
1664 intensity,
1665 *map,
1666 white;
1667
1668 register long
1669 i;
1670
1671 CacheView
1672 *image_view;
1673
1674 /*
1675 Allocate and initialize histogram arrays.
1676 */
1677 assert(image != (Image *) NULL);
1678 assert(image->signature == MagickSignature);
1679 if (image->debug != MagickFalse)
1680 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1681 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1682 sizeof(*equalize_map));
1683 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1684 sizeof(*histogram));
1685 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1686 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1687 (histogram == (MagickPixelPacket *) NULL) ||
1688 (map == (MagickPixelPacket *) NULL))
1689 {
1690 if (map != (MagickPixelPacket *) NULL)
1691 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1692 if (histogram != (MagickPixelPacket *) NULL)
1693 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1694 if (equalize_map != (MagickPixelPacket *) NULL)
1695 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1696 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1697 image->filename);
1698 }
1699 /*
1700 Form histogram.
1701 */
1702 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1703 exception=(&image->exception);
1704 for (y=0; y < (long) image->rows; y++)
1705 {
1706 register const IndexPacket
1707 *__restrict indexes;
1708
1709 register const PixelPacket
1710 *__restrict p;
1711
1712 register long
1713 x;
1714
1715 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1716 if (p == (const PixelPacket *) NULL)
1717 break;
1718 indexes=GetVirtualIndexQueue(image);
1719 for (x=0; x < (long) image->columns; x++)
1720 {
1721 if ((channel & RedChannel) != 0)
1722 histogram[ScaleQuantumToMap(p->red)].red++;
1723 if ((channel & GreenChannel) != 0)
1724 histogram[ScaleQuantumToMap(p->green)].green++;
1725 if ((channel & BlueChannel) != 0)
1726 histogram[ScaleQuantumToMap(p->blue)].blue++;
1727 if ((channel & OpacityChannel) != 0)
1728 histogram[ScaleQuantumToMap(p->opacity)].opacity++;
1729 if (((channel & IndexChannel) != 0) &&
1730 (image->colorspace == CMYKColorspace))
1731 histogram[ScaleQuantumToMap(indexes[x])].index++;
1732 p++;
1733 }
1734 }
1735 /*
1736 Integrate the histogram to get the equalization map.
1737 */
1738 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1739 for (i=0; i <= (long) MaxMap; i++)
1740 {
1741 if ((channel & RedChannel) != 0)
1742 intensity.red+=histogram[i].red;
1743 if ((channel & GreenChannel) != 0)
1744 intensity.green+=histogram[i].green;
1745 if ((channel & BlueChannel) != 0)
1746 intensity.blue+=histogram[i].blue;
1747 if ((channel & OpacityChannel) != 0)
1748 intensity.opacity+=histogram[i].opacity;
1749 if (((channel & IndexChannel) != 0) &&
1750 (image->colorspace == CMYKColorspace))
1751 intensity.index+=histogram[i].index;
1752 map[i]=intensity;
1753 }
1754 black=map[0];
1755 white=map[(int) MaxMap];
1756 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristy629a6c72009-09-13 23:28:22 +00001757#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001758 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001759#endif
1760 for (i=0; i <= (long) MaxMap; i++)
1761 {
1762 if (((channel & RedChannel) != 0) && (white.red != black.red))
1763 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1764 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1765 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1766 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1767 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1768 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1769 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1770 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1771 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1772 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1773 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1774 (white.opacity-black.opacity)));
1775 if ((((channel & IndexChannel) != 0) &&
1776 (image->colorspace == CMYKColorspace)) &&
1777 (white.index != black.index))
1778 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1779 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1780 }
1781 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1782 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1783 if (image->storage_class == PseudoClass)
1784 {
1785 /*
1786 Equalize colormap.
1787 */
cristy629a6c72009-09-13 23:28:22 +00001788#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001789 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001790#endif
1791 for (i=0; i < (long) image->colors; i++)
1792 {
1793 if (((channel & RedChannel) != 0) && (white.red != black.red))
1794 image->colormap[i].red=RoundToQuantum(equalize_map[
1795 ScaleQuantumToMap(image->colormap[i].red)].red);
1796 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1797 image->colormap[i].green=RoundToQuantum(equalize_map[
1798 ScaleQuantumToMap(image->colormap[i].green)].green);
1799 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1800 image->colormap[i].blue=RoundToQuantum(equalize_map[
1801 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1802 if (((channel & OpacityChannel) != 0) &&
1803 (white.opacity != black.opacity))
1804 image->colormap[i].opacity=RoundToQuantum(equalize_map[
1805 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1806 }
1807 }
1808 /*
1809 Equalize image.
1810 */
1811 status=MagickTrue;
1812 progress=0;
1813 exception=(&image->exception);
1814 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00001815#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001816 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001817#endif
1818 for (y=0; y < (long) image->rows; y++)
1819 {
1820 register IndexPacket
1821 *__restrict indexes;
1822
1823 register long
1824 x;
1825
1826 register PixelPacket
1827 *__restrict q;
1828
1829 if (status == MagickFalse)
1830 continue;
1831 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1832 if (q == (PixelPacket *) NULL)
1833 {
1834 status=MagickFalse;
1835 continue;
1836 }
1837 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1838 for (x=0; x < (long) image->columns; x++)
1839 {
1840 if (((channel & RedChannel) != 0) && (white.red != black.red))
1841 q->red=RoundToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
1842 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1843 q->green=RoundToQuantum(equalize_map[ScaleQuantumToMap(
1844 q->green)].green);
1845 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1846 q->blue=RoundToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
1847 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1848 q->opacity=RoundToQuantum(equalize_map[ScaleQuantumToMap(
1849 q->opacity)].opacity);
1850 if ((((channel & IndexChannel) != 0) &&
1851 (image->colorspace == CMYKColorspace)) &&
1852 (white.index != black.index))
1853 indexes[x]=RoundToQuantum(equalize_map[ScaleQuantumToMap(
1854 indexes[x])].index);
1855 q++;
1856 }
1857 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1858 status=MagickFalse;
1859 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1860 {
1861 MagickBooleanType
1862 proceed;
1863
cristy629a6c72009-09-13 23:28:22 +00001864#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00001865 #pragma omp critical (MagickCore_EqualizeImageChannel)
1866#endif
1867 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1868 if (proceed == MagickFalse)
1869 status=MagickFalse;
1870 }
1871 }
1872 image_view=DestroyCacheView(image_view);
1873 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1874 return(status);
1875}
1876
1877/*
1878%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1879% %
1880% %
1881% %
1882% G a m m a I m a g e %
1883% %
1884% %
1885% %
1886%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1887%
1888% GammaImage() gamma-corrects a particular image channel. The same
1889% image viewed on different devices will have perceptual differences in the
1890% way the image's intensities are represented on the screen. Specify
1891% individual gamma levels for the red, green, and blue channels, or adjust
1892% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1893%
1894% You can also reduce the influence of a particular channel with a gamma
1895% value of 0.
1896%
1897% The format of the GammaImage method is:
1898%
1899% MagickBooleanType GammaImage(Image *image,const double gamma)
1900% MagickBooleanType GammaImageChannel(Image *image,
1901% const ChannelType channel,const double gamma)
1902%
1903% A description of each parameter follows:
1904%
1905% o image: the image.
1906%
1907% o channel: the channel.
1908%
1909% o gamma: the image gamma.
1910%
1911*/
1912MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1913{
1914 GeometryInfo
1915 geometry_info;
1916
1917 MagickPixelPacket
1918 gamma;
1919
1920 MagickStatusType
1921 flags,
1922 status;
1923
1924 assert(image != (Image *) NULL);
1925 assert(image->signature == MagickSignature);
1926 if (image->debug != MagickFalse)
1927 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1928 if (level == (char *) NULL)
1929 return(MagickFalse);
1930 flags=ParseGeometry(level,&geometry_info);
1931 gamma.red=geometry_info.rho;
1932 gamma.green=geometry_info.sigma;
1933 if ((flags & SigmaValue) == 0)
1934 gamma.green=gamma.red;
1935 gamma.blue=geometry_info.xi;
1936 if ((flags & XiValue) == 0)
1937 gamma.blue=gamma.red;
1938 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1939 return(MagickTrue);
1940 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1941 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1942 GreenChannel | BlueChannel),(double) gamma.red);
1943 else
1944 {
1945 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
1946 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
1947 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
1948 }
1949 return(status != 0 ? MagickTrue : MagickFalse);
1950}
1951
1952MagickExport MagickBooleanType GammaImageChannel(Image *image,
1953 const ChannelType channel,const double gamma)
1954{
1955#define GammaCorrectImageTag "GammaCorrect/Image"
1956
1957 ExceptionInfo
1958 *exception;
1959
1960 long
1961 progress,
1962 y;
1963
1964 MagickBooleanType
1965 status;
1966
1967 Quantum
1968 *gamma_map;
1969
1970 register long
1971 i;
1972
1973 CacheView
1974 *image_view;
1975
1976 /*
1977 Allocate and initialize gamma maps.
1978 */
1979 assert(image != (Image *) NULL);
1980 assert(image->signature == MagickSignature);
1981 if (image->debug != MagickFalse)
1982 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1983 if (gamma == 1.0)
1984 return(MagickTrue);
1985 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1986 if (gamma_map == (Quantum *) NULL)
1987 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1988 image->filename);
1989 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1990 if (gamma != 0.0)
cristy629a6c72009-09-13 23:28:22 +00001991#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00001992 #pragma omp parallel for
cristy3ed852e2009-09-05 21:47:34 +00001993#endif
1994 for (i=0; i <= (long) MaxMap; i++)
1995 gamma_map[i]=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
1996 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1997 if (image->storage_class == PseudoClass)
1998 {
1999 /*
2000 Gamma-correct colormap.
2001 */
cristy629a6c72009-09-13 23:28:22 +00002002#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002003 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002004#endif
2005 for (i=0; i < (long) image->colors; i++)
2006 {
2007 if ((channel & RedChannel) != 0)
2008 image->colormap[i].red=gamma_map[
2009 ScaleQuantumToMap(image->colormap[i].red)];
2010 if ((channel & GreenChannel) != 0)
2011 image->colormap[i].green=gamma_map[
2012 ScaleQuantumToMap(image->colormap[i].green)];
2013 if ((channel & BlueChannel) != 0)
2014 image->colormap[i].blue=gamma_map[
2015 ScaleQuantumToMap(image->colormap[i].blue)];
2016 if ((channel & OpacityChannel) != 0)
2017 {
2018 if (image->matte == MagickFalse)
2019 image->colormap[i].opacity=gamma_map[
2020 ScaleQuantumToMap(image->colormap[i].opacity)];
2021 else
2022 image->colormap[i].opacity=(Quantum) QuantumRange-
2023 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2024 image->colormap[i].opacity))];
2025 }
2026 }
2027 }
2028 /*
2029 Gamma-correct image.
2030 */
2031 status=MagickTrue;
2032 progress=0;
2033 exception=(&image->exception);
2034 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002035#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002036 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002037#endif
2038 for (y=0; y < (long) image->rows; y++)
2039 {
2040 register IndexPacket
2041 *__restrict indexes;
2042
2043 register long
2044 x;
2045
2046 register PixelPacket
2047 *__restrict q;
2048
2049 if (status == MagickFalse)
2050 continue;
2051 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2052 if (q == (PixelPacket *) NULL)
2053 {
2054 status=MagickFalse;
2055 continue;
2056 }
2057 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2058 for (x=0; x < (long) image->columns; x++)
2059 {
2060 if ((channel & RedChannel) != 0)
2061 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2062 if ((channel & GreenChannel) != 0)
2063 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2064 if ((channel & BlueChannel) != 0)
2065 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2066 if ((channel & OpacityChannel) != 0)
2067 {
2068 if (image->matte == MagickFalse)
2069 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2070 else
2071 q->opacity=(Quantum) QuantumRange-gamma_map[
2072 ScaleQuantumToMap((Quantum) (QuantumRange-q->opacity))];
2073 }
2074 q++;
2075 }
2076 if (((channel & IndexChannel) != 0) &&
2077 (image->colorspace == CMYKColorspace))
2078 for (x=0; x < (long) image->columns; x++)
2079 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2080 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2081 status=MagickFalse;
2082 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2083 {
2084 MagickBooleanType
2085 proceed;
2086
cristy629a6c72009-09-13 23:28:22 +00002087#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002088 #pragma omp critical (MagickCore_GammaImageChannel)
2089#endif
2090 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2091 image->rows);
2092 if (proceed == MagickFalse)
2093 status=MagickFalse;
2094 }
2095 }
2096 image_view=DestroyCacheView(image_view);
2097 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2098 if (image->gamma != 0.0)
2099 image->gamma*=gamma;
2100 return(status);
2101}
2102
2103/*
2104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2105% %
2106% %
2107% %
2108% H a l d C l u t I m a g e %
2109% %
2110% %
2111% %
2112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2113%
2114% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2115% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2116% Create it with the HALD coder. You can apply any color transformation to
2117% the Hald image and then use this method to apply the transform to the
2118% image.
2119%
2120% The format of the HaldClutImage method is:
2121%
2122% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2123% MagickBooleanType HaldClutImageChannel(Image *image,
2124% const ChannelType channel,Image *hald_image)
2125%
2126% A description of each parameter follows:
2127%
2128% o image: the image, which is replaced by indexed CLUT values
2129%
2130% o hald_image: the color lookup table image for replacement color values.
2131%
2132% o channel: the channel.
2133%
2134*/
2135
2136static inline size_t MagickMin(const size_t x,const size_t y)
2137{
2138 if (x < y)
2139 return(x);
2140 return(y);
2141}
2142
2143MagickExport MagickBooleanType HaldClutImage(Image *image,
2144 const Image *hald_image)
2145{
2146 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2147}
2148
2149MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2150 const ChannelType channel,const Image *hald_image)
2151{
2152#define HaldClutImageTag "Clut/Image"
2153
2154 typedef struct _HaldInfo
2155 {
2156 MagickRealType
2157 x,
2158 y,
2159 z;
2160 } HaldInfo;
2161
2162 double
2163 width;
2164
2165 ExceptionInfo
2166 *exception;
2167
2168 long
2169 progress,
2170 y;
2171
2172 MagickBooleanType
2173 status;
2174
2175 MagickPixelPacket
2176 zero;
2177
2178 ResampleFilter
2179 **resample_filter;
2180
2181 size_t
2182 cube_size,
2183 length,
2184 level;
2185
2186 CacheView
2187 *image_view;
2188
2189 assert(image != (Image *) NULL);
2190 assert(image->signature == MagickSignature);
2191 if (image->debug != MagickFalse)
2192 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2193 assert(hald_image != (Image *) NULL);
2194 assert(hald_image->signature == MagickSignature);
2195 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2196 return(MagickFalse);
2197 if (image->matte == MagickFalse)
2198 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2199 /*
2200 Hald clut image.
2201 */
2202 status=MagickTrue;
2203 progress=0;
2204 length=MagickMin(hald_image->columns,hald_image->rows);
2205 for (level=2; (level*level*level) < length; level++) ;
2206 level*=level;
2207 cube_size=level*level;
2208 width=(double) hald_image->columns;
2209 GetMagickPixelPacket(hald_image,&zero);
2210 exception=(&image->exception);
2211 resample_filter=AcquireResampleFilterThreadSet(hald_image,MagickTrue,
2212 exception);
2213 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002214#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002215 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002216#endif
2217 for (y=0; y < (long) image->rows; y++)
2218 {
2219 double
2220 offset;
2221
2222 HaldInfo
2223 point;
2224
2225 MagickPixelPacket
2226 pixel,
2227 pixel1,
2228 pixel2,
2229 pixel3,
2230 pixel4;
2231
2232 register IndexPacket
2233 *__restrict indexes;
2234
2235 register long
2236 id,
2237 x;
2238
2239 register PixelPacket
2240 *__restrict q;
2241
2242 if (status == MagickFalse)
2243 continue;
2244 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2245 if (q == (PixelPacket *) NULL)
2246 {
2247 status=MagickFalse;
2248 continue;
2249 }
2250 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2251 pixel=zero;
2252 pixel1=zero;
2253 pixel2=zero;
2254 pixel3=zero;
2255 pixel4=zero;
2256 id=GetOpenMPThreadId();
2257 for (x=0; x < (long) image->columns; x++)
2258 {
2259 point.x=QuantumScale*(level-1.0)*q->red;
2260 point.y=QuantumScale*(level-1.0)*q->green;
2261 point.z=QuantumScale*(level-1.0)*q->blue;
2262 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2263 point.x-=floor(point.x);
2264 point.y-=floor(point.y);
2265 point.z-=floor(point.z);
2266 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2267 floor(offset/width),&pixel1);
2268 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2269 floor((offset+level)/width),&pixel2);
2270 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2271 pixel2.opacity,point.y,&pixel3);
2272 offset+=cube_size;
2273 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2274 floor(offset/width),&pixel1);
2275 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2276 floor((offset+level)/width),&pixel2);
2277 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2278 pixel2.opacity,point.y,&pixel4);
2279 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2280 pixel4.opacity,point.z,&pixel);
2281 if ((channel & RedChannel) != 0)
2282 q->red=RoundToQuantum(pixel.red);
2283 if ((channel & GreenChannel) != 0)
2284 q->green=RoundToQuantum(pixel.green);
2285 if ((channel & BlueChannel) != 0)
2286 q->blue=RoundToQuantum(pixel.blue);
2287 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2288 q->opacity=RoundToQuantum(pixel.opacity);
2289 if (((channel & IndexChannel) != 0) &&
2290 (image->colorspace == CMYKColorspace))
2291 indexes[x]=RoundToQuantum(pixel.index);
2292 q++;
2293 }
2294 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2295 status=MagickFalse;
2296 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2297 {
2298 MagickBooleanType
2299 proceed;
2300
cristy629a6c72009-09-13 23:28:22 +00002301#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002302 #pragma omp critical (MagickCore_HaldClutImageChannel)
2303#endif
2304 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2305 if (proceed == MagickFalse)
2306 status=MagickFalse;
2307 }
2308 }
2309 image_view=DestroyCacheView(image_view);
2310 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2311 return(status);
2312}
2313
2314/*
2315%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2316% %
2317% %
2318% %
2319% L e v e l I m a g e %
2320% %
2321% %
2322% %
2323%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2324%
2325% LevelImage() adjusts the levels of a particular image channel by
2326% scaling the colors falling between specified white and black points to
2327% the full available quantum range.
2328%
2329% The parameters provided represent the black, and white points. The black
2330% point specifies the darkest color in the image. Colors darker than the
2331% black point are set to zero. White point specifies the lightest color in
2332% the image. Colors brighter than the white point are set to the maximum
2333% quantum value.
2334%
2335% If a '!' flag is given, map black and white colors to the given levels
2336% rather than mapping those levels to black and white. See
2337% LevelizeImageChannel() and LevelizeImageChannel(), below.
2338%
2339% Gamma specifies a gamma correction to apply to the image.
2340%
2341% The format of the LevelImage method is:
2342%
2343% MagickBooleanType LevelImage(Image *image,const char *levels)
2344%
2345% A description of each parameter follows:
2346%
2347% o image: the image.
2348%
2349% o levels: Specify the levels where the black and white points have the
2350% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2351% A '!' flag inverts the re-mapping.
2352%
2353*/
2354
2355MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2356{
2357 double
2358 black_point,
2359 gamma,
2360 white_point;
2361
2362 GeometryInfo
2363 geometry_info;
2364
2365 MagickBooleanType
2366 status;
2367
2368 MagickStatusType
2369 flags;
2370
2371 /*
2372 Parse levels.
2373 */
2374 if (levels == (char *) NULL)
2375 return(MagickFalse);
2376 flags=ParseGeometry(levels,&geometry_info);
2377 black_point=geometry_info.rho;
2378 white_point=(double) QuantumRange;
2379 if ((flags & SigmaValue) != 0)
2380 white_point=geometry_info.sigma;
2381 gamma=1.0;
2382 if ((flags & XiValue) != 0)
2383 gamma=geometry_info.xi;
2384 if ((flags & PercentValue) != 0)
2385 {
2386 black_point*=(double) image->columns*image->rows/100.0;
2387 white_point*=(double) image->columns*image->rows/100.0;
2388 }
2389 if ((flags & SigmaValue) == 0)
2390 white_point=(double) QuantumRange-black_point;
2391 if ((flags & AspectValue ) == 0)
2392 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2393 gamma);
2394 else
cristy308b4e62009-09-21 14:40:44 +00002395 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002396 return(status);
2397}
2398
2399/*
2400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2401% %
2402% %
2403% %
cristy308b4e62009-09-21 14:40:44 +00002404% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002405% %
2406% %
2407% %
2408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2409%
cristy308b4e62009-09-21 14:40:44 +00002410% LevelizeImage() applies the normal level operation to the image, spreading
2411% out the values between the black and white points over the entire range of
2412% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002413%
2414% It is typically used to improve image contrast, or to provide a controlled
2415% linear threshold for the image. If the black and white points are set to
2416% the minimum and maximum values found in the image, the image can be
2417% normalized. or by swapping black and white values, negate the image.
2418%
cristy308b4e62009-09-21 14:40:44 +00002419% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002420%
cristy308b4e62009-09-21 14:40:44 +00002421% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2422% const double white_point,const double gamma)
2423% MagickBooleanType LevelizeImageChannel(Image *image,
2424% const ChannelType channel,const double black_point,
2425% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002426%
2427% A description of each parameter follows:
2428%
2429% o image: the image.
2430%
2431% o channel: the channel.
2432%
2433% o black_point: The level which is to be mapped to zero (black)
2434%
2435% o white_point: The level which is to be mapped to QuantiumRange (white)
2436%
2437% o gamma: adjust gamma by this factor before mapping values.
2438% use 1.0 for purely linear stretching of image color values
2439%
2440*/
cristy308b4e62009-09-21 14:40:44 +00002441
2442MagickExport MagickBooleanType LevelizeImage(Image *image,
2443 const double black_point,const double white_point,const double gamma)
2444{
2445 MagickBooleanType
2446 status;
2447
2448 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2449 gamma);
2450 return(status);
2451}
2452
cristy3ed852e2009-09-05 21:47:34 +00002453MagickExport MagickBooleanType LevelImageChannel(Image *image,
2454 const ChannelType channel,const double black_point,const double white_point,
2455 const double gamma)
2456{
2457#define LevelImageTag "Level/Image"
2458#define LevelValue(x) (RoundToQuantum((MagickRealType) QuantumRange* \
2459 pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
2460
2461 ExceptionInfo
2462 *exception;
2463
2464 long
2465 progress,
2466 y;
2467
2468 MagickBooleanType
2469 status;
2470
2471 register long
2472 i;
2473
2474 CacheView
2475 *image_view;
2476
2477 /*
2478 Allocate and initialize levels map.
2479 */
2480 assert(image != (Image *) NULL);
2481 assert(image->signature == MagickSignature);
2482 if (image->debug != MagickFalse)
2483 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2484 if (image->storage_class == PseudoClass)
cristy629a6c72009-09-13 23:28:22 +00002485#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002486 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002487#endif
2488 for (i=0; i < (long) image->colors; i++)
2489 {
2490 /*
2491 Level colormap.
2492 */
2493 if ((channel & RedChannel) != 0)
2494 image->colormap[i].red=LevelValue(image->colormap[i].red);
2495 if ((channel & GreenChannel) != 0)
2496 image->colormap[i].green=LevelValue(image->colormap[i].green);
2497 if ((channel & BlueChannel) != 0)
2498 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2499 if ((channel & OpacityChannel) != 0)
2500 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2501 }
2502 /*
2503 Level image.
2504 */
2505 status=MagickTrue;
2506 progress=0;
2507 exception=(&image->exception);
2508 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002509#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002510 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002511#endif
2512 for (y=0; y < (long) image->rows; y++)
2513 {
2514 register IndexPacket
2515 *__restrict indexes;
2516
2517 register long
2518 x;
2519
2520 register PixelPacket
2521 *__restrict q;
2522
2523 if (status == MagickFalse)
2524 continue;
2525 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2526 if (q == (PixelPacket *) NULL)
2527 {
2528 status=MagickFalse;
2529 continue;
2530 }
2531 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2532 for (x=0; x < (long) image->columns; x++)
2533 {
2534 if ((channel & RedChannel) != 0)
2535 q->red=LevelValue(q->red);
2536 if ((channel & GreenChannel) != 0)
2537 q->green=LevelValue(q->green);
2538 if ((channel & BlueChannel) != 0)
2539 q->blue=LevelValue(q->blue);
2540 if (((channel & OpacityChannel) != 0) &&
2541 (image->matte == MagickTrue))
2542 q->opacity=LevelValue(q->opacity);
2543 if (((channel & IndexChannel) != 0) &&
2544 (image->colorspace == CMYKColorspace))
2545 indexes[x]=LevelValue(indexes[x]);
2546 q++;
2547 }
2548 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2549 status=MagickFalse;
2550 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2551 {
2552 MagickBooleanType
2553 proceed;
2554
cristy629a6c72009-09-13 23:28:22 +00002555#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002556 #pragma omp critical (MagickCore_LevelImageChannel)
2557#endif
2558 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2559 if (proceed == MagickFalse)
2560 status=MagickFalse;
2561 }
2562 }
2563 image_view=DestroyCacheView(image_view);
2564 return(status);
2565}
2566
2567/*
2568%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2569% %
2570% %
2571% %
2572% L e v e l i z e I m a g e C h a n n e l %
2573% %
2574% %
2575% %
2576%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2577%
2578% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2579% the specific channels specified. It compresses the full range of color
2580% values, so that they lie between the given black and white points. Gamma is
2581% applied before the values are mapped.
2582%
2583% LevelizeImageChannel() can be called with by using a +level command line
2584% API option, or using a '!' on a -level or LevelImage() geometry string.
2585%
2586% It can be used for example de-contrast a greyscale image to the exact
2587% levels specified. Or by using specific levels for each channel of an image
2588% you can convert a gray-scale image to any linear color gradient, according
2589% to those levels.
2590%
2591% The format of the LevelizeImageChannel method is:
2592%
2593% MagickBooleanType LevelizeImageChannel(Image *image,
2594% const ChannelType channel,const char *levels)
2595%
2596% A description of each parameter follows:
2597%
2598% o image: the image.
2599%
2600% o channel: the channel.
2601%
2602% o black_point: The level to map zero (black) to.
2603%
2604% o white_point: The level to map QuantiumRange (white) to.
2605%
2606% o gamma: adjust gamma by this factor before mapping values.
2607%
2608*/
2609MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2610 const ChannelType channel,const double black_point,const double white_point,
2611 const double gamma)
2612{
2613#define LevelizeImageTag "Levelize/Image"
2614#define LevelizeValue(x) (RoundToQuantum(((MagickRealType) \
2615 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2616 black_point))
2617
2618 ExceptionInfo
2619 *exception;
2620
2621 long
2622 progress,
2623 y;
2624
2625 MagickBooleanType
2626 status;
2627
2628 register long
2629 i;
2630
2631 CacheView
2632 *image_view;
2633
2634 /*
2635 Allocate and initialize levels map.
2636 */
2637 assert(image != (Image *) NULL);
2638 assert(image->signature == MagickSignature);
2639 if (image->debug != MagickFalse)
2640 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2641 if (image->storage_class == PseudoClass)
cristy629a6c72009-09-13 23:28:22 +00002642#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002643 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002644#endif
2645 for (i=0; i < (long) image->colors; i++)
2646 {
2647 /*
2648 Level colormap.
2649 */
2650 if ((channel & RedChannel) != 0)
2651 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2652 if ((channel & GreenChannel) != 0)
2653 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2654 if ((channel & BlueChannel) != 0)
2655 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2656 if ((channel & OpacityChannel) != 0)
2657 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2658 }
2659 /*
2660 Level image.
2661 */
2662 status=MagickTrue;
2663 progress=0;
2664 exception=(&image->exception);
2665 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00002666#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00002667 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002668#endif
2669 for (y=0; y < (long) image->rows; y++)
2670 {
2671 register IndexPacket
2672 *__restrict indexes;
2673
2674 register long
2675 x;
2676
2677 register PixelPacket
2678 *__restrict q;
2679
2680 if (status == MagickFalse)
2681 continue;
2682 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2683 if (q == (PixelPacket *) NULL)
2684 {
2685 status=MagickFalse;
2686 continue;
2687 }
2688 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2689 for (x=0; x < (long) image->columns; x++)
2690 {
2691 if ((channel & RedChannel) != 0)
2692 q->red=LevelizeValue(q->red);
2693 if ((channel & GreenChannel) != 0)
2694 q->green=LevelizeValue(q->green);
2695 if ((channel & BlueChannel) != 0)
2696 q->blue=LevelizeValue(q->blue);
2697 if (((channel & OpacityChannel) != 0) &&
2698 (image->matte == MagickTrue))
2699 q->opacity=LevelizeValue(q->opacity);
2700 if (((channel & IndexChannel) != 0) &&
2701 (image->colorspace == CMYKColorspace))
2702 indexes[x]=LevelizeValue(indexes[x]);
2703 q++;
2704 }
2705 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2706 status=MagickFalse;
2707 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2708 {
2709 MagickBooleanType
2710 proceed;
2711
cristy629a6c72009-09-13 23:28:22 +00002712#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00002713 #pragma omp critical (MagickCore_LevelizeImageChannel)
2714#endif
2715 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2716 if (proceed == MagickFalse)
2717 status=MagickFalse;
2718 }
2719 }
2720 return(status);
2721}
2722
2723/*
2724%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2725% %
2726% %
2727% %
2728% L e v e l I m a g e C o l o r s %
2729% %
2730% %
2731% %
2732%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2733%
cristyee0f8d72009-09-19 00:58:29 +00002734% LevelImageColor() maps the given color to "black" and "white" values,
2735% linearly spreading out the colors, and level values on a channel by channel
2736% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002737% different level ranges for each of the color channels seperatally.
2738%
2739% If the boolean 'invert' is set true the image values will modifyed in the
2740% reverse direction. That is any existing "black" and "white" colors in the
2741% image will become the color values given, with all other values compressed
2742% appropriatally. This effectivally maps a greyscale gradient into the given
2743% color gradient.
2744%
cristy308b4e62009-09-21 14:40:44 +00002745% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002746%
cristy308b4e62009-09-21 14:40:44 +00002747% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002748% const MagickPixelPacket *black_color,
2749% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002750% MagickBooleanType LevelColorsImageChannel(Image *image,
2751% const ChannelType channel,const MagickPixelPacket *black_color,
2752% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002753%
2754% A description of each parameter follows:
2755%
2756% o image: the image.
2757%
2758% o channel: the channel.
2759%
2760% o black_color: The color to map black to/from
2761%
2762% o white_point: The color to map white to/from
2763%
2764% o invert: if true map the colors (levelize), rather than from (level)
2765%
2766*/
cristy308b4e62009-09-21 14:40:44 +00002767
2768MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002769 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2770 const MagickBooleanType invert)
2771{
cristy308b4e62009-09-21 14:40:44 +00002772 MagickBooleanType
2773 status;
cristy3ed852e2009-09-05 21:47:34 +00002774
cristy308b4e62009-09-21 14:40:44 +00002775 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2776 invert);
2777 return(status);
2778}
2779
2780MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2781 const ChannelType channel,const MagickPixelPacket *black_color,
2782 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2783{
cristy3ed852e2009-09-05 21:47:34 +00002784 MagickStatusType
2785 status;
2786
2787 /*
2788 Allocate and initialize levels map.
2789 */
2790 assert(image != (Image *) NULL);
2791 assert(image->signature == MagickSignature);
2792 if (image->debug != MagickFalse)
2793 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2794 status=MagickFalse;
2795 if (invert == MagickFalse)
2796 {
2797 if ((channel & RedChannel) != 0)
2798 status|=LevelImageChannel(image,RedChannel,
2799 black_color->red,white_color->red,(double) 1.0);
2800 if ((channel & GreenChannel) != 0)
2801 status|=LevelImageChannel(image,GreenChannel,
2802 black_color->green,white_color->green,(double) 1.0);
2803 if ((channel & BlueChannel) != 0)
2804 status|=LevelImageChannel(image,BlueChannel,
2805 black_color->blue,white_color->blue,(double) 1.0);
2806 if (((channel & OpacityChannel) != 0) &&
2807 (image->matte == MagickTrue))
2808 status|=LevelImageChannel(image,OpacityChannel,
2809 black_color->opacity,white_color->opacity,(double) 1.0);
2810 if (((channel & IndexChannel) != 0) &&
2811 (image->colorspace == CMYKColorspace))
2812 status|=LevelImageChannel(image,IndexChannel,
2813 black_color->index,white_color->index,(double) 1.0);
2814 }
2815 else
2816 {
2817 if ((channel & RedChannel) != 0)
2818 status|=LevelizeImageChannel(image,RedChannel,
2819 black_color->red,white_color->red,(double) 1.0);
2820 if ((channel & GreenChannel) != 0)
2821 status|=LevelizeImageChannel(image,GreenChannel,
2822 black_color->green,white_color->green,(double) 1.0);
2823 if ((channel & BlueChannel) != 0)
2824 status|=LevelizeImageChannel(image,BlueChannel,
2825 black_color->blue,white_color->blue,(double) 1.0);
2826 if (((channel & OpacityChannel) != 0) &&
2827 (image->matte == MagickTrue))
2828 status|=LevelizeImageChannel(image,OpacityChannel,
2829 black_color->opacity,white_color->opacity,(double) 1.0);
2830 if (((channel & IndexChannel) != 0) &&
2831 (image->colorspace == CMYKColorspace))
2832 status|=LevelizeImageChannel(image,IndexChannel,
2833 black_color->index,white_color->index,(double) 1.0);
2834 }
2835 return(status == 0 ? MagickFalse : MagickTrue);
2836}
2837
2838/*
2839%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2840% %
2841% %
2842% %
2843% L i n e a r S t r e t c h I m a g e %
2844% %
2845% %
2846% %
2847%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2848%
2849% The LinearStretchImage() discards any pixels below the black point and
2850% above the white point and levels the remaining pixels.
2851%
2852% The format of the LinearStretchImage method is:
2853%
2854% MagickBooleanType LinearStretchImage(Image *image,
2855% const double black_point,const double white_point)
2856%
2857% A description of each parameter follows:
2858%
2859% o image: the image.
2860%
2861% o black_point: the black point.
2862%
2863% o white_point: the white point.
2864%
2865*/
2866MagickExport MagickBooleanType LinearStretchImage(Image *image,
2867 const double black_point,const double white_point)
2868{
2869#define LinearStretchImageTag "LinearStretch/Image"
2870
2871 ExceptionInfo
2872 *exception;
2873
2874 long
2875 black,
2876 white,
2877 y;
2878
2879 MagickBooleanType
2880 status;
2881
2882 MagickRealType
2883 *histogram,
2884 intensity;
2885
2886 MagickSizeType
2887 number_pixels;
2888
2889 /*
2890 Allocate histogram and linear map.
2891 */
2892 assert(image != (Image *) NULL);
2893 assert(image->signature == MagickSignature);
2894 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2895 sizeof(*histogram));
2896 if (histogram == (MagickRealType *) NULL)
2897 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2898 image->filename);
2899 /*
2900 Form histogram.
2901 */
2902 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2903 exception=(&image->exception);
2904 for (y=0; y < (long) image->rows; y++)
2905 {
2906 register const PixelPacket
2907 *__restrict p;
2908
2909 register long
2910 x;
2911
2912 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2913 if (p == (const PixelPacket *) NULL)
2914 break;
2915 for (x=(long) image->columns-1; x >= 0; x--)
2916 {
2917 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2918 p++;
2919 }
2920 }
2921 /*
2922 Find the histogram boundaries by locating the black and white point levels.
2923 */
2924 number_pixels=(MagickSizeType) image->columns*image->rows;
2925 intensity=0.0;
2926 for (black=0; black < (long) MaxMap; black++)
2927 {
2928 intensity+=histogram[black];
2929 if (intensity >= black_point)
2930 break;
2931 }
2932 intensity=0.0;
2933 for (white=(long) MaxMap; white != 0; white--)
2934 {
2935 intensity+=histogram[white];
2936 if (intensity >= white_point)
2937 break;
2938 }
2939 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
2940 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
2941 1.0);
2942 return(status);
2943}
2944
2945/*
2946%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2947% %
2948% %
2949% %
2950% M o d u l a t e I m a g e %
2951% %
2952% %
2953% %
2954%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2955%
2956% ModulateImage() lets you control the brightness, saturation, and hue
2957% of an image. Modulate represents the brightness, saturation, and hue
2958% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2959% modulation is lightness, saturation, and hue. And if the colorspace is
2960% HWB, use blackness, whiteness, and hue.
2961%
2962% The format of the ModulateImage method is:
2963%
2964% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2965%
2966% A description of each parameter follows:
2967%
2968% o image: the image.
2969%
2970% o modulate: Define the percent change in brightness, saturation, and
2971% hue.
2972%
2973*/
2974
2975static void ModulateHSB(const double percent_hue,
2976 const double percent_saturation,const double percent_brightness,
2977 Quantum *red,Quantum *green,Quantum *blue)
2978{
2979 double
2980 brightness,
2981 hue,
2982 saturation;
2983
2984 /*
2985 Increase or decrease color brightness, saturation, or hue.
2986 */
2987 assert(red != (Quantum *) NULL);
2988 assert(green != (Quantum *) NULL);
2989 assert(blue != (Quantum *) NULL);
2990 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2991 hue+=0.5*(0.01*percent_hue-1.0);
2992 while (hue < 0.0)
2993 hue+=1.0;
2994 while (hue > 1.0)
2995 hue-=1.0;
2996 saturation*=0.01*percent_saturation;
2997 brightness*=0.01*percent_brightness;
2998 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2999}
3000
3001static void ModulateHSL(const double percent_hue,
3002 const double percent_saturation,const double percent_lightness,
3003 Quantum *red,Quantum *green,Quantum *blue)
3004{
3005 double
3006 hue,
3007 lightness,
3008 saturation;
3009
3010 /*
3011 Increase or decrease color lightness, saturation, or hue.
3012 */
3013 assert(red != (Quantum *) NULL);
3014 assert(green != (Quantum *) NULL);
3015 assert(blue != (Quantum *) NULL);
3016 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3017 hue+=0.5*(0.01*percent_hue-1.0);
3018 while (hue < 0.0)
3019 hue+=1.0;
3020 while (hue > 1.0)
3021 hue-=1.0;
3022 saturation*=0.01*percent_saturation;
3023 lightness*=0.01*percent_lightness;
3024 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3025}
3026
3027static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3028{
3029 double
3030 blackness,
3031 hue,
3032 whiteness;
3033
3034 /*
3035 Increase or decrease color blackness, whiteness, or hue.
3036 */
3037 assert(red != (Quantum *) NULL);
3038 assert(green != (Quantum *) NULL);
3039 assert(blue != (Quantum *) NULL);
3040 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3041 hue+=0.5*(0.01*percent_hue-1.0);
3042 while (hue < 0.0)
3043 hue+=1.0;
3044 while (hue > 1.0)
3045 hue-=1.0;
3046 blackness*=0.01*percent_blackness;
3047 whiteness*=0.01*percent_whiteness;
3048 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3049}
3050
3051MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3052{
3053#define ModulateImageTag "Modulate/Image"
3054
3055 ColorspaceType
3056 colorspace;
3057
3058 const char
3059 *artifact;
3060
3061 double
3062 percent_brightness,
3063 percent_hue,
3064 percent_saturation;
3065
3066 ExceptionInfo
3067 *exception;
3068
3069 GeometryInfo
3070 geometry_info;
3071
3072 long
3073 progress,
3074 y;
3075
3076 MagickBooleanType
3077 status;
3078
3079 MagickStatusType
3080 flags;
3081
3082 register long
3083 i;
3084
3085 CacheView
3086 *image_view;
3087
3088 /*
3089 Initialize gamma table.
3090 */
3091 assert(image != (Image *) NULL);
3092 assert(image->signature == MagickSignature);
3093 if (image->debug != MagickFalse)
3094 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3095 if (modulate == (char *) NULL)
3096 return(MagickFalse);
3097 flags=ParseGeometry(modulate,&geometry_info);
3098 percent_brightness=geometry_info.rho;
3099 percent_saturation=geometry_info.sigma;
3100 if ((flags & SigmaValue) == 0)
3101 percent_saturation=100.0;
3102 percent_hue=geometry_info.xi;
3103 if ((flags & XiValue) == 0)
3104 percent_hue=100.0;
3105 colorspace=UndefinedColorspace;
3106 artifact=GetImageArtifact(image,"modulate:colorspace");
3107 if (artifact != (const char *) NULL)
3108 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3109 MagickFalse,artifact);
3110 if (image->storage_class == PseudoClass)
3111 {
3112 /*
3113 Modulate colormap.
3114 */
cristy629a6c72009-09-13 23:28:22 +00003115#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003116 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003117#endif
3118 for (i=0; i < (long) image->colors; i++)
3119 switch (colorspace)
3120 {
3121 case HSBColorspace:
3122 {
3123 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3124 &image->colormap[i].red,&image->colormap[i].green,
3125 &image->colormap[i].blue);
3126 break;
3127 }
3128 case HSLColorspace:
3129 default:
3130 {
3131 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3132 &image->colormap[i].red,&image->colormap[i].green,
3133 &image->colormap[i].blue);
3134 break;
3135 }
3136 case HWBColorspace:
3137 {
3138 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3139 &image->colormap[i].red,&image->colormap[i].green,
3140 &image->colormap[i].blue);
3141 break;
3142 }
3143 }
3144 }
3145 /*
3146 Modulate image.
3147 */
3148 status=MagickTrue;
3149 progress=0;
3150 exception=(&image->exception);
3151 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00003152#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003153 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003154#endif
3155 for (y=0; y < (long) image->rows; y++)
3156 {
3157 register long
3158 x;
3159
3160 register PixelPacket
3161 *__restrict q;
3162
3163 if (status == MagickFalse)
3164 continue;
3165 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3166 if (q == (PixelPacket *) NULL)
3167 {
3168 status=MagickFalse;
3169 continue;
3170 }
3171 for (x=0; x < (long) image->columns; x++)
3172 {
3173 switch (colorspace)
3174 {
3175 case HSBColorspace:
3176 {
3177 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3178 &q->red,&q->green,&q->blue);
3179 break;
3180 }
3181 case HSLColorspace:
3182 default:
3183 {
3184 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3185 &q->red,&q->green,&q->blue);
3186 break;
3187 }
3188 case HWBColorspace:
3189 {
3190 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3191 &q->red,&q->green,&q->blue);
3192 break;
3193 }
3194 }
3195 q++;
3196 }
3197 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3198 status=MagickFalse;
3199 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3200 {
3201 MagickBooleanType
3202 proceed;
3203
cristy629a6c72009-09-13 23:28:22 +00003204#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003205 #pragma omp critical (MagickCore_ModulateImage)
3206#endif
3207 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3208 if (proceed == MagickFalse)
3209 status=MagickFalse;
3210 }
3211 }
3212 image_view=DestroyCacheView(image_view);
3213 return(status);
3214}
3215
3216/*
3217%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3218% %
3219% %
3220% %
3221% N e g a t e I m a g e %
3222% %
3223% %
3224% %
3225%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3226%
3227% NegateImage() negates the colors in the reference image. The grayscale
3228% option means that only grayscale values within the image are negated.
3229%
3230% The format of the NegateImageChannel method is:
3231%
3232% MagickBooleanType NegateImage(Image *image,
3233% const MagickBooleanType grayscale)
3234% MagickBooleanType NegateImageChannel(Image *image,
3235% const ChannelType channel,const MagickBooleanType grayscale)
3236%
3237% A description of each parameter follows:
3238%
3239% o image: the image.
3240%
3241% o channel: the channel.
3242%
3243% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3244%
3245*/
3246
3247MagickExport MagickBooleanType NegateImage(Image *image,
3248 const MagickBooleanType grayscale)
3249{
3250 MagickBooleanType
3251 status;
3252
3253 status=NegateImageChannel(image,DefaultChannels,grayscale);
3254 return(status);
3255}
3256
3257MagickExport MagickBooleanType NegateImageChannel(Image *image,
3258 const ChannelType channel,const MagickBooleanType grayscale)
3259{
3260#define NegateImageTag "Negate/Image"
3261
3262 ExceptionInfo
3263 *exception;
3264
3265 long
3266 progress,
3267 y;
3268
3269 MagickBooleanType
3270 status;
3271
3272 register long
3273 i;
3274
3275 CacheView
3276 *image_view;
3277
3278 assert(image != (Image *) NULL);
3279 assert(image->signature == MagickSignature);
3280 if (image->debug != MagickFalse)
3281 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3282 if (image->storage_class == PseudoClass)
3283 {
3284 /*
3285 Negate colormap.
3286 */
cristy629a6c72009-09-13 23:28:22 +00003287#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003288 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003289#endif
3290 for (i=0; i < (long) image->colors; i++)
3291 {
3292 if (grayscale != MagickFalse)
3293 if ((image->colormap[i].red != image->colormap[i].green) ||
3294 (image->colormap[i].green != image->colormap[i].blue))
3295 continue;
3296 if ((channel & RedChannel) != 0)
3297 image->colormap[i].red=(Quantum) QuantumRange-
3298 image->colormap[i].red;
3299 if ((channel & GreenChannel) != 0)
3300 image->colormap[i].green=(Quantum) QuantumRange-
3301 image->colormap[i].green;
3302 if ((channel & BlueChannel) != 0)
3303 image->colormap[i].blue=(Quantum) QuantumRange-
3304 image->colormap[i].blue;
3305 }
3306 }
3307 /*
3308 Negate image.
3309 */
3310 status=MagickTrue;
3311 progress=0;
3312 exception=(&image->exception);
3313 image_view=AcquireCacheView(image);
3314 if (grayscale != MagickFalse)
3315 {
cristy629a6c72009-09-13 23:28:22 +00003316#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003317 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003318#endif
3319 for (y=0; y < (long) image->rows; y++)
3320 {
3321 MagickBooleanType
3322 sync;
3323
3324 register IndexPacket
3325 *__restrict indexes;
3326
3327 register long
3328 x;
3329
3330 register PixelPacket
3331 *__restrict q;
3332
3333 if (status == MagickFalse)
3334 continue;
3335 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3336 exception);
3337 if (q == (PixelPacket *) NULL)
3338 {
3339 status=MagickFalse;
3340 continue;
3341 }
3342 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3343 for (x=0; x < (long) image->columns; x++)
3344 {
3345 if ((q->red != q->green) || (q->green != q->blue))
3346 {
3347 q++;
3348 continue;
3349 }
3350 if ((channel & RedChannel) != 0)
3351 q->red=(Quantum) QuantumRange-q->red;
3352 if ((channel & GreenChannel) != 0)
3353 q->green=(Quantum) QuantumRange-q->green;
3354 if ((channel & BlueChannel) != 0)
3355 q->blue=(Quantum) QuantumRange-q->blue;
3356 if ((channel & OpacityChannel) != 0)
3357 q->opacity=(Quantum) QuantumRange-q->opacity;
3358 if (((channel & IndexChannel) != 0) &&
3359 (image->colorspace == CMYKColorspace))
3360 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3361 q++;
3362 }
3363 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3364 if (sync == MagickFalse)
3365 status=MagickFalse;
3366 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3367 {
3368 MagickBooleanType
3369 proceed;
3370
cristy629a6c72009-09-13 23:28:22 +00003371#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003372 #pragma omp critical (MagickCore_NegateImageChannel)
3373#endif
3374 proceed=SetImageProgress(image,NegateImageTag,progress++,
3375 image->rows);
3376 if (proceed == MagickFalse)
3377 status=MagickFalse;
3378 }
3379 }
3380 image_view=DestroyCacheView(image_view);
3381 return(MagickTrue);
3382 }
3383 /*
3384 Negate image.
3385 */
cristy629a6c72009-09-13 23:28:22 +00003386#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003387 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003388#endif
3389 for (y=0; y < (long) image->rows; y++)
3390 {
3391 register IndexPacket
3392 *__restrict indexes;
3393
3394 register long
3395 x;
3396
3397 register PixelPacket
3398 *__restrict q;
3399
3400 if (status == MagickFalse)
3401 continue;
3402 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3403 if (q == (PixelPacket *) NULL)
3404 {
3405 status=MagickFalse;
3406 continue;
3407 }
3408 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3409 for (x=0; x < (long) image->columns; x++)
3410 {
3411 if ((channel & RedChannel) != 0)
3412 q->red=(Quantum) QuantumRange-q->red;
3413 if ((channel & GreenChannel) != 0)
3414 q->green=(Quantum) QuantumRange-q->green;
3415 if ((channel & BlueChannel) != 0)
3416 q->blue=(Quantum) QuantumRange-q->blue;
3417 if ((channel & OpacityChannel) != 0)
3418 q->opacity=(Quantum) QuantumRange-q->opacity;
3419 if (((channel & IndexChannel) != 0) &&
3420 (image->colorspace == CMYKColorspace))
3421 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3422 q++;
3423 }
3424 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3425 status=MagickFalse;
3426 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3427 {
3428 MagickBooleanType
3429 proceed;
3430
cristy629a6c72009-09-13 23:28:22 +00003431#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003432 #pragma omp critical (MagickCore_NegateImageChannel)
3433#endif
3434 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3435 if (proceed == MagickFalse)
3436 status=MagickFalse;
3437 }
3438 }
3439 image_view=DestroyCacheView(image_view);
3440 return(status);
3441}
3442
3443/*
3444%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3445% %
3446% %
3447% %
3448% N o r m a l i z e I m a g e %
3449% %
3450% %
3451% %
3452%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3453%
3454% The NormalizeImage() method enhances the contrast of a color image by
3455% mapping the darkest 2 percent of all pixel to black and the brightest
3456% 1 percent to white.
3457%
3458% The format of the NormalizeImage method is:
3459%
3460% MagickBooleanType NormalizeImage(Image *image)
3461% MagickBooleanType NormalizeImageChannel(Image *image,
3462% const ChannelType channel)
3463%
3464% A description of each parameter follows:
3465%
3466% o image: the image.
3467%
3468% o channel: the channel.
3469%
3470*/
3471
3472MagickExport MagickBooleanType NormalizeImage(Image *image)
3473{
3474 MagickBooleanType
3475 status;
3476
3477 status=NormalizeImageChannel(image,DefaultChannels);
3478 return(status);
3479}
3480
3481MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3482 const ChannelType channel)
3483{
3484 double
3485 black_point,
3486 white_point;
3487
3488 black_point=(double) image->columns*image->rows*0.02;
3489 white_point=(double) image->columns*image->rows*0.99;
3490 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3491}
3492
3493/*
3494%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3495% %
3496% %
3497% %
3498% S i g m o i d a l C o n t r a s t I m a g e %
3499% %
3500% %
3501% %
3502%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3503%
3504% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3505% sigmoidal contrast algorithm. Increase the contrast of the image using a
3506% sigmoidal transfer function without saturating highlights or shadows.
3507% Contrast indicates how much to increase the contrast (0 is none; 3 is
3508% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3509% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3510% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3511% is reduced.
3512%
3513% The format of the SigmoidalContrastImage method is:
3514%
3515% MagickBooleanType SigmoidalContrastImage(Image *image,
3516% const MagickBooleanType sharpen,const char *levels)
3517% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3518% const ChannelType channel,const MagickBooleanType sharpen,
3519% const double contrast,const double midpoint)
3520%
3521% A description of each parameter follows:
3522%
3523% o image: the image.
3524%
3525% o channel: the channel.
3526%
3527% o sharpen: Increase or decrease image contrast.
3528%
3529% o contrast: control the "shoulder" of the contast curve.
3530%
3531% o midpoint: control the "toe" of the contast curve.
3532%
3533*/
3534
3535MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3536 const MagickBooleanType sharpen,const char *levels)
3537{
3538 GeometryInfo
3539 geometry_info;
3540
3541 MagickBooleanType
3542 status;
3543
3544 MagickStatusType
3545 flags;
3546
3547 flags=ParseGeometry(levels,&geometry_info);
3548 if ((flags & SigmaValue) == 0)
3549 geometry_info.sigma=1.0*QuantumRange/2.0;
3550 if ((flags & PercentValue) != 0)
3551 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3552 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3553 geometry_info.rho,geometry_info.sigma);
3554 return(status);
3555}
3556
3557MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3558 const ChannelType channel,const MagickBooleanType sharpen,
3559 const double contrast,const double midpoint)
3560{
3561#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3562
3563 ExceptionInfo
3564 *exception;
3565
3566 long
3567 progress,
3568 y;
3569
3570 MagickBooleanType
3571 status;
3572
3573 MagickRealType
3574 *sigmoidal_map;
3575
3576 register long
3577 i;
3578
3579 CacheView
3580 *image_view;
3581
3582 /*
3583 Allocate and initialize sigmoidal maps.
3584 */
3585 assert(image != (Image *) NULL);
3586 assert(image->signature == MagickSignature);
3587 if (image->debug != MagickFalse)
3588 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3589 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3590 sizeof(*sigmoidal_map));
3591 if (sigmoidal_map == (MagickRealType *) NULL)
3592 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3593 image->filename);
3594 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristy629a6c72009-09-13 23:28:22 +00003595#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003596 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003597#endif
3598 for (i=0; i <= (long) MaxMap; i++)
3599 {
3600 if (sharpen != MagickFalse)
3601 {
3602 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3603 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3604 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3605 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3606 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3607 (double) QuantumRange)))))+0.5));
3608 continue;
3609 }
3610 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3611 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3612 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3613 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3614 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3615 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3616 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3617 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3618 (double) QuantumRange*contrast))))))/contrast)));
3619 }
3620 if (image->storage_class == PseudoClass)
3621 {
3622 /*
3623 Sigmoidal-contrast enhance colormap.
3624 */
cristy629a6c72009-09-13 23:28:22 +00003625#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003626 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003627#endif
3628 for (i=0; i < (long) image->colors; i++)
3629 {
3630 if ((channel & RedChannel) != 0)
3631 image->colormap[i].red=RoundToQuantum(sigmoidal_map[
3632 ScaleQuantumToMap(image->colormap[i].red)]);
3633 if ((channel & GreenChannel) != 0)
3634 image->colormap[i].green=RoundToQuantum(sigmoidal_map[
3635 ScaleQuantumToMap(image->colormap[i].green)]);
3636 if ((channel & BlueChannel) != 0)
3637 image->colormap[i].blue=RoundToQuantum(sigmoidal_map[
3638 ScaleQuantumToMap(image->colormap[i].blue)]);
3639 if ((channel & OpacityChannel) != 0)
3640 image->colormap[i].opacity=RoundToQuantum(sigmoidal_map[
3641 ScaleQuantumToMap(image->colormap[i].opacity)]);
3642 }
3643 }
3644 /*
3645 Sigmoidal-contrast enhance image.
3646 */
3647 status=MagickTrue;
3648 progress=0;
3649 exception=(&image->exception);
3650 image_view=AcquireCacheView(image);
cristy629a6c72009-09-13 23:28:22 +00003651#if defined(_OPENMP) && (_OPENMP >= 200203)
cristye0f584d2009-10-11 00:59:14 +00003652 #pragma omp parallel for shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003653#endif
3654 for (y=0; y < (long) image->rows; y++)
3655 {
3656 register IndexPacket
3657 *__restrict indexes;
3658
3659 register long
3660 x;
3661
3662 register PixelPacket
3663 *__restrict q;
3664
3665 if (status == MagickFalse)
3666 continue;
3667 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3668 if (q == (PixelPacket *) NULL)
3669 {
3670 status=MagickFalse;
3671 continue;
3672 }
3673 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3674 for (x=0; x < (long) image->columns; x++)
3675 {
3676 if ((channel & RedChannel) != 0)
3677 q->red=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
3678 if ((channel & GreenChannel) != 0)
3679 q->green=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
3680 if ((channel & BlueChannel) != 0)
3681 q->blue=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
3682 if ((channel & OpacityChannel) != 0)
3683 q->opacity=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
3684 if (((channel & IndexChannel) != 0) &&
3685 (image->colorspace == CMYKColorspace))
3686 indexes[x]=(IndexPacket) RoundToQuantum(sigmoidal_map[
3687 ScaleQuantumToMap(indexes[x])]);
3688 q++;
3689 }
3690 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3691 status=MagickFalse;
3692 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3693 {
3694 MagickBooleanType
3695 proceed;
3696
cristy629a6c72009-09-13 23:28:22 +00003697#if defined(_OPENMP) && (_OPENMP >= 200203)
cristy3ed852e2009-09-05 21:47:34 +00003698 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3699#endif
3700 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3701 image->rows);
3702 if (proceed == MagickFalse)
3703 status=MagickFalse;
3704 }
3705 }
3706 image_view=DestroyCacheView(image_view);
3707 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3708 return(status);
3709}