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