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