blob: 9b9959439fc2bd9b9dd6f86c9ac2bcb135c311d5 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/composite-private.h"
51#include "magick/enhance.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
cristya28d6b82010-01-11 20:03:47 +000054#include "magick/fx.h"
cristy3ed852e2009-09-05 21:47:34 +000055#include "magick/gem.h"
56#include "magick/geometry.h"
57#include "magick/histogram.h"
58#include "magick/image.h"
59#include "magick/image-private.h"
60#include "magick/memory_.h"
61#include "magick/monitor.h"
62#include "magick/monitor-private.h"
63#include "magick/option.h"
64#include "magick/quantum.h"
65#include "magick/quantum-private.h"
66#include "magick/resample.h"
67#include "magick/resample-private.h"
68#include "magick/statistic.h"
69#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000070#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000071#include "magick/thread-private.h"
72#include "magick/token.h"
73#include "magick/xml-tree.h"
74
75/*
76%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77% %
78% %
79% %
80% A u t o G a m m a I m a g e %
81% %
82% %
83% %
84%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85%
86% AutoGammaImage() extract the 'mean' from the image and adjust the image
87% to try make set its gamma appropriatally.
88%
cristy308b4e62009-09-21 14:40:44 +000089% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000090%
91% MagickBooleanType AutoGammaImage(Image *image)
92% MagickBooleanType AutoGammaImageChannel(Image *image,
93% const ChannelType channel)
94%
95% A description of each parameter follows:
96%
97% o image: The image to auto-level
98%
99% o channel: The channels to auto-level. If the special 'SyncChannels'
100% flag is set all given channels is adjusted in the same way using the
101% mean average of those channels.
102%
103*/
104
105MagickExport MagickBooleanType AutoGammaImage(Image *image)
106{
107 return(AutoGammaImageChannel(image,DefaultChannels));
108}
109
110MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
111 const ChannelType channel)
112{
113 MagickStatusType
114 status;
115
116 double
cristy2b726bd2010-01-11 01:05:39 +0000117 mean,sans,gamma,logmean;
anthony4efe5972009-09-11 06:46:40 +0000118
119 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000120
121 if ((channel & SyncChannels) != 0 )
122 {
123 /*
124 Apply gamma correction equally accross all given channels
125 */
cristy2b726bd2010-01-11 01:05:39 +0000126 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
127 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000128 return LevelImageChannel(image, channel,
129 0.0, (double)QuantumRange, gamma);
130 }
131
132 /*
133 auto-gamma each channel separateally
134 */
135 status = MagickTrue;
136 if ((channel & RedChannel) != 0)
137 {
cristy2b726bd2010-01-11 01:05:39 +0000138 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
139 &image->exception);
140 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000141 status = status && LevelImageChannel(image, RedChannel,
142 0.0, (double)QuantumRange, gamma);
143 }
144 if ((channel & GreenChannel) != 0)
145 {
cristy2b726bd2010-01-11 01:05:39 +0000146 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
147 &image->exception);
148 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000149 status = status && LevelImageChannel(image, GreenChannel,
150 0.0, (double)QuantumRange, gamma);
151 }
152 if ((channel & BlueChannel) != 0)
153 {
cristy2b726bd2010-01-11 01:05:39 +0000154 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
155 &image->exception);
156 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000157 status = status && LevelImageChannel(image, BlueChannel,
158 0.0, (double)QuantumRange, gamma);
159 }
160 if (((channel & OpacityChannel) != 0) &&
161 (image->matte == MagickTrue))
162 {
cristy2b726bd2010-01-11 01:05:39 +0000163 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
164 &image->exception);
165 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000166 status = status && LevelImageChannel(image, OpacityChannel,
167 0.0, (double)QuantumRange, gamma);
168 }
169 if (((channel & IndexChannel) != 0) &&
170 (image->colorspace == CMYKColorspace))
171 {
cristy2b726bd2010-01-11 01:05:39 +0000172 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
173 &image->exception);
174 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000175 status = status && LevelImageChannel(image, IndexChannel,
176 0.0, (double)QuantumRange, gamma);
177 }
178 return(status != 0 ? MagickTrue : MagickFalse);
179}
180
181/*
182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
183% %
184% %
185% %
186% A u t o L e v e l I m a g e %
187% %
188% %
189% %
190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191%
192% AutoLevelImage() adjusts the levels of a particular image channel by
193% scaling the minimum and maximum values to the full quantum range.
194%
195% The format of the LevelImage method is:
196%
197% MagickBooleanType AutoLevelImage(Image *image)
198% MagickBooleanType AutoLevelImageChannel(Image *image,
199% const ChannelType channel)
200%
201% A description of each parameter follows:
202%
203% o image: The image to auto-level
204%
205% o channel: The channels to auto-level. If the special 'SyncChannels'
206% flag is set the min/max/mean value of all given channels is used for
207% all given channels, to all channels in the same way.
208%
209*/
210
211MagickExport MagickBooleanType AutoLevelImage(Image *image)
212{
213 return(AutoLevelImageChannel(image,DefaultChannels));
214}
215
216MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
217 const ChannelType channel)
218{
219 /*
220 This is simply a convenience function around a Min/Max Histogram Stretch
221 */
222 return MinMaxStretchImage(image, channel, 0.0, 0.0);
223}
224
225/*
226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
227% %
228% %
229% %
cristya28d6b82010-01-11 20:03:47 +0000230% B r i g h t n e s s C o n t r a s t I m a g e %
231% %
232% %
233% %
234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
235%
236% Use BrightnessContrastImage() to change the brightness and/or contrast of
237% an image. It converts the brightness and contrast parameters into slope
238% and intercept and calls a polynomical function to apply to the image.
239%
240% The format of the BrightnessContrastImage method is:
241%
242% MagickBooleanType BrightnessContrastImage(Image *image,
243% const double brightness,const double contrast)
244% MagickBooleanType BrightnessContrastImageChannel(Image *image,
245% const ChannelType channel,const double brightness,
246% const double contrast)
247%
248% A description of each parameter follows:
249%
250% o image: the image.
251%
252% o channel: the channel.
253%
254% o brightness: the brightness percent (-100 .. 100).
255%
256% o contrast: the contrast percent (-100 .. 100).
257%
258*/
259
260MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
261 const double brightness,const double contrast)
262{
263 MagickBooleanType
264 status;
265
266 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
267 contrast);
268 return(status);
269}
270
271MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
272 const ChannelType channel,const double brightness,const double contrast)
273{
274#define BrightnessContastImageTag "BrightnessContast/Image"
275
276 double
277 alpha,
278 intercept,
279 coefficients[2],
280 slope;
281
282 MagickBooleanType
283 status;
284
285 /*
286 Compute slope and intercept.
287 */
288 assert(image != (Image *) NULL);
289 assert(image->signature == MagickSignature);
290 if (image->debug != MagickFalse)
291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
292 alpha=contrast;
293 if ((100-contrast) <= 0.1)
cristyae2532e2010-01-12 16:40:09 +0000294 alpha=99.999999;
295 slope=MagickPI*(((alpha*alpha)/20000.0)+(3.0*alpha/200.0))/4.0;
296 slope=sin(slope)/cos(slope)+1.0;
cristya28d6b82010-01-11 20:03:47 +0000297 if (slope < 0.0)
298 slope=0.0;
299 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
300 coefficients[0]=slope;
301 coefficients[1]=intercept;
302 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
303 &image->exception);
304 return(status);
305}
306
307/*
308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
309% %
310% %
311% %
cristy3ed852e2009-09-05 21:47:34 +0000312% C o l o r D e c i s i o n L i s t I m a g e %
313% %
314% %
315% %
316%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
317%
318% ColorDecisionListImage() accepts a lightweight Color Correction Collection
319% (CCC) file which solely contains one or more color corrections and applies
320% the correction to the image. Here is a sample CCC file:
321%
322% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
323% <ColorCorrection id="cc03345">
324% <SOPNode>
325% <Slope> 0.9 1.2 0.5 </Slope>
326% <Offset> 0.4 -0.5 0.6 </Offset>
327% <Power> 1.0 0.8 1.5 </Power>
328% </SOPNode>
329% <SATNode>
330% <Saturation> 0.85 </Saturation>
331% </SATNode>
332% </ColorCorrection>
333% </ColorCorrectionCollection>
334%
335% which includes the slop, offset, and power for each of the RGB channels
336% as well as the saturation.
337%
338% The format of the ColorDecisionListImage method is:
339%
340% MagickBooleanType ColorDecisionListImage(Image *image,
341% const char *color_correction_collection)
342%
343% A description of each parameter follows:
344%
345% o image: the image.
346%
347% o color_correction_collection: the color correction collection in XML.
348%
349*/
350MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
351 const char *color_correction_collection)
352{
353#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
354
355 typedef struct _Correction
356 {
357 double
358 slope,
359 offset,
360 power;
361 } Correction;
362
363 typedef struct _ColorCorrection
364 {
365 Correction
366 red,
367 green,
368 blue;
369
370 double
371 saturation;
372 } ColorCorrection;
373
cristyc4c8d132010-01-07 01:58:38 +0000374 CacheView
375 *image_view;
376
cristy3ed852e2009-09-05 21:47:34 +0000377 char
378 token[MaxTextExtent];
379
380 ColorCorrection
381 color_correction;
382
383 const char
384 *content,
385 *p;
386
387 ExceptionInfo
388 *exception;
389
390 long
391 progress,
392 y;
393
394 MagickBooleanType
395 status;
396
397 PixelPacket
398 *cdl_map;
399
400 register long
401 i;
402
403 XMLTreeInfo
404 *cc,
405 *ccc,
406 *sat,
407 *sop;
408
cristy3ed852e2009-09-05 21:47:34 +0000409 /*
410 Allocate and initialize cdl maps.
411 */
412 assert(image != (Image *) NULL);
413 assert(image->signature == MagickSignature);
414 if (image->debug != MagickFalse)
415 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
416 if (color_correction_collection == (const char *) NULL)
417 return(MagickFalse);
418 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
419 if (ccc == (XMLTreeInfo *) NULL)
420 return(MagickFalse);
421 cc=GetXMLTreeChild(ccc,"ColorCorrection");
422 if (cc == (XMLTreeInfo *) NULL)
423 {
424 ccc=DestroyXMLTree(ccc);
425 return(MagickFalse);
426 }
427 color_correction.red.slope=1.0;
428 color_correction.red.offset=0.0;
429 color_correction.red.power=1.0;
430 color_correction.green.slope=1.0;
431 color_correction.green.offset=0.0;
432 color_correction.green.power=1.0;
433 color_correction.blue.slope=1.0;
434 color_correction.blue.offset=0.0;
435 color_correction.blue.power=1.0;
436 color_correction.saturation=0.0;
437 sop=GetXMLTreeChild(cc,"SOPNode");
438 if (sop != (XMLTreeInfo *) NULL)
439 {
440 XMLTreeInfo
441 *offset,
442 *power,
443 *slope;
444
445 slope=GetXMLTreeChild(sop,"Slope");
446 if (slope != (XMLTreeInfo *) NULL)
447 {
448 content=GetXMLTreeContent(slope);
449 p=(const char *) content;
450 for (i=0; (*p != '\0') && (i < 3); i++)
451 {
452 GetMagickToken(p,&p,token);
453 if (*token == ',')
454 GetMagickToken(p,&p,token);
455 switch (i)
456 {
cristyf2f27272009-12-17 14:48:46 +0000457 case 0: color_correction.red.slope=StringToDouble(token); break;
458 case 1: color_correction.green.slope=StringToDouble(token); break;
459 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000460 }
461 }
462 }
463 offset=GetXMLTreeChild(sop,"Offset");
464 if (offset != (XMLTreeInfo *) NULL)
465 {
466 content=GetXMLTreeContent(offset);
467 p=(const char *) content;
468 for (i=0; (*p != '\0') && (i < 3); i++)
469 {
470 GetMagickToken(p,&p,token);
471 if (*token == ',')
472 GetMagickToken(p,&p,token);
473 switch (i)
474 {
cristyf2f27272009-12-17 14:48:46 +0000475 case 0: color_correction.red.offset=StringToDouble(token); break;
476 case 1: color_correction.green.offset=StringToDouble(token); break;
477 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000478 }
479 }
480 }
481 power=GetXMLTreeChild(sop,"Power");
482 if (power != (XMLTreeInfo *) NULL)
483 {
484 content=GetXMLTreeContent(power);
485 p=(const char *) content;
486 for (i=0; (*p != '\0') && (i < 3); i++)
487 {
488 GetMagickToken(p,&p,token);
489 if (*token == ',')
490 GetMagickToken(p,&p,token);
491 switch (i)
492 {
cristyf2f27272009-12-17 14:48:46 +0000493 case 0: color_correction.red.power=StringToDouble(token); break;
494 case 1: color_correction.green.power=StringToDouble(token); break;
495 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000496 }
497 }
498 }
499 }
500 sat=GetXMLTreeChild(cc,"SATNode");
501 if (sat != (XMLTreeInfo *) NULL)
502 {
503 XMLTreeInfo
504 *saturation;
505
506 saturation=GetXMLTreeChild(sat,"Saturation");
507 if (saturation != (XMLTreeInfo *) NULL)
508 {
509 content=GetXMLTreeContent(saturation);
510 p=(const char *) content;
511 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000512 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000513 }
514 }
515 ccc=DestroyXMLTree(ccc);
516 if (image->debug != MagickFalse)
517 {
518 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
519 " Color Correction Collection:");
520 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000521 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000522 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000523 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000525 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000526 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000527 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000528 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000529 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000531 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000532 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000533 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000534 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000535 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000537 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000539 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000540 }
541 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
542 if (cdl_map == (PixelPacket *) NULL)
543 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
544 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000545#if defined(MAGICKCORE_OPENMP_SUPPORT)
546 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000547#endif
548 for (i=0; i <= (long) MaxMap; i++)
549 {
cristyce70c172010-01-07 17:15:30 +0000550 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000551 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
552 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000553 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000554 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
555 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000556 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000557 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
558 color_correction.blue.offset,color_correction.blue.power)))));
559 }
560 if (image->storage_class == PseudoClass)
561 {
562 /*
563 Apply transfer function to colormap.
564 */
cristyb5d5f722009-11-04 03:03:49 +0000565#if defined(MAGICKCORE_OPENMP_SUPPORT)
566 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000567#endif
568 for (i=0; i < (long) image->colors; i++)
569 {
570 double
571 luma;
572
573 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
574 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000575 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000576 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000577 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000578 color_correction.saturation*cdl_map[ScaleQuantumToMap(
579 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000580 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000581 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
582 }
583 }
584 /*
585 Apply transfer function to image.
586 */
587 status=MagickTrue;
588 progress=0;
589 exception=(&image->exception);
590 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
594 for (y=0; y < (long) image->rows; y++)
595 {
596 double
597 luma;
598
599 register long
600 x;
601
602 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000603 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000604
605 if (status == MagickFalse)
606 continue;
607 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
608 if (q == (PixelPacket *) NULL)
609 {
610 status=MagickFalse;
611 continue;
612 }
613 for (x=0; x < (long) image->columns; x++)
614 {
615 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000616 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000617 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000618 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000619 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000620 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000621 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
622 q++;
623 }
624 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
625 status=MagickFalse;
626 if (image->progress_monitor != (MagickProgressMonitor) NULL)
627 {
628 MagickBooleanType
629 proceed;
630
cristyb5d5f722009-11-04 03:03:49 +0000631#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000632 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
633#endif
634 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
635 progress++,image->rows);
636 if (proceed == MagickFalse)
637 status=MagickFalse;
638 }
639 }
640 image_view=DestroyCacheView(image_view);
641 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
642 return(status);
643}
644
645/*
646%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
647% %
648% %
649% %
650% C l u t I m a g e %
651% %
652% %
653% %
654%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
655%
656% ClutImage() replaces each color value in the given image, by using it as an
657% index to lookup a replacement color value in a Color Look UP Table in the
658% form of an image. The values are extracted along a diagonal of the CLUT
659% image so either a horizontal or vertial gradient image can be used.
660%
661% Typically this is used to either re-color a gray-scale image according to a
662% color gradient in the CLUT image, or to perform a freeform histogram
663% (level) adjustment according to the (typically gray-scale) gradient in the
664% CLUT image.
665%
666% When the 'channel' mask includes the matte/alpha transparency channel but
667% one image has no such channel it is assumed that that image is a simple
668% gray-scale image that will effect the alpha channel values, either for
669% gray-scale coloring (with transparent or semi-transparent colors), or
670% a histogram adjustment of existing alpha channel values. If both images
671% have matte channels, direct and normal indexing is applied, which is rarely
672% used.
673%
674% The format of the ClutImage method is:
675%
676% MagickBooleanType ClutImage(Image *image,Image *clut_image)
677% MagickBooleanType ClutImageChannel(Image *image,
678% const ChannelType channel,Image *clut_image)
679%
680% A description of each parameter follows:
681%
682% o image: the image, which is replaced by indexed CLUT values
683%
684% o clut_image: the color lookup table image for replacement color values.
685%
686% o channel: the channel.
687%
688*/
689
690MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
691{
692 return(ClutImageChannel(image,DefaultChannels,clut_image));
693}
694
695MagickExport MagickBooleanType ClutImageChannel(Image *image,
696 const ChannelType channel,const Image *clut_image)
697{
698#define ClutImageTag "Clut/Image"
699
cristyfa112112010-01-04 17:48:07 +0000700 CacheView
701 *image_view;
702
cristy3ed852e2009-09-05 21:47:34 +0000703 ExceptionInfo
704 *exception;
705
706 long
707 adjust,
708 progress,
709 y;
710
711 MagickBooleanType
712 status;
713
714 MagickPixelPacket
715 zero;
716
717 ResampleFilter
cristyfa112112010-01-04 17:48:07 +0000718 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +0000719
720 assert(image != (Image *) NULL);
721 assert(image->signature == MagickSignature);
722 if (image->debug != MagickFalse)
723 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
724 assert(clut_image != (Image *) NULL);
725 assert(clut_image->signature == MagickSignature);
726 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
727 return(MagickFalse);
728 /*
729 Clut image.
730 */
731 status=MagickTrue;
732 progress=0;
733 GetMagickPixelPacket(clut_image,&zero);
734 adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
735 exception=(&image->exception);
736 resample_filter=AcquireResampleFilterThreadSet(clut_image,MagickTrue,
737 exception);
738 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000739#if defined(MAGICKCORE_OPENMP_SUPPORT)
740 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000741#endif
742 for (y=0; y < (long) image->rows; y++)
743 {
744 MagickPixelPacket
745 pixel;
746
747 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000748 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000749
750 register long
751 id,
752 x;
753
754 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000755 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000756
757 if (status == MagickFalse)
758 continue;
759 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
760 if (q == (PixelPacket *) NULL)
761 {
762 status=MagickFalse;
763 continue;
764 }
765 indexes=GetCacheViewAuthenticIndexQueue(image_view);
766 pixel=zero;
767 id=GetOpenMPThreadId();
768 for (x=0; x < (long) image->columns; x++)
769 {
770 /*
771 PROGRAMMERS WARNING:
772
773 Apply OpacityChannel BEFORE the color channels. Do not re-order.
774
775 The handling special case 2 (coloring gray-scale), requires access to
776 the unmodified colors of the original image to determine the index
777 value. As such alpha/matte channel handling must be performed BEFORE,
778 any of the color channels are modified.
779
780 */
781 if ((channel & OpacityChannel) != 0)
782 {
783 if (clut_image->matte == MagickFalse)
784 {
785 /*
786 A gray-scale LUT replacement for an image alpha channel.
787 */
788 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
cristy46f08202010-01-10 04:04:21 +0000789 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
790 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
cristy3ed852e2009-09-05 21:47:34 +0000791 adjust),&pixel);
792 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
793 &pixel));
794 }
795 else
796 if (image->matte == MagickFalse)
797 {
798 /*
799 A greyscale image being colored by a LUT with transparency.
800 */
801 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
802 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
803 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000804 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000805 }
806 else
807 {
808 /*
809 Direct alpha channel lookup.
810 */
811 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
812 q->opacity*(clut_image->columns-adjust),QuantumScale*
813 q->opacity* (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000814 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000815 }
816 }
817 if ((channel & RedChannel) != 0)
818 {
819 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
820 (clut_image->columns-adjust),QuantumScale*q->red*
821 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000822 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000823 }
824 if ((channel & GreenChannel) != 0)
825 {
826 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
827 (clut_image->columns-adjust),QuantumScale*q->green*
828 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000829 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000830 }
831 if ((channel & BlueChannel) != 0)
832 {
833 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
834 (clut_image->columns-adjust),QuantumScale*q->blue*
835 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000836 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000837 }
838 if (((channel & IndexChannel) != 0) &&
839 (image->colorspace == CMYKColorspace))
840 {
841 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
842 (clut_image->columns-adjust),QuantumScale*indexes[x]*
843 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000844 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000845 }
846 q++;
847 }
848 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
849 status=MagickFalse;
850 if (image->progress_monitor != (MagickProgressMonitor) NULL)
851 {
852 MagickBooleanType
853 proceed;
854
cristyb5d5f722009-11-04 03:03:49 +0000855#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000856 #pragma omp critical (MagickCore_ClutImageChannel)
857#endif
858 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
859 if (proceed == MagickFalse)
860 status=MagickFalse;
861 }
862 }
863 image_view=DestroyCacheView(image_view);
864 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
865 /*
866 Enable alpha channel if CLUT image could enable it.
867 */
868 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
869 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
870 return(status);
871}
872
873/*
874%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
875% %
876% %
877% %
878% C o n t r a s t I m a g e %
879% %
880% %
881% %
882%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
883%
884% ContrastImage() enhances the intensity differences between the lighter and
885% darker elements of the image. Set sharpen to a MagickTrue to increase the
886% image contrast otherwise the contrast is reduced.
887%
888% The format of the ContrastImage method is:
889%
890% MagickBooleanType ContrastImage(Image *image,
891% const MagickBooleanType sharpen)
892%
893% A description of each parameter follows:
894%
895% o image: the image.
896%
897% o sharpen: Increase or decrease image contrast.
898%
899*/
900
901static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
902{
903 double
904 brightness,
905 hue,
906 saturation;
907
908 /*
909 Enhance contrast: dark color become darker, light color become lighter.
910 */
911 assert(red != (Quantum *) NULL);
912 assert(green != (Quantum *) NULL);
913 assert(blue != (Quantum *) NULL);
914 hue=0.0;
915 saturation=0.0;
916 brightness=0.0;
917 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
918 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
919 if (brightness > 1.0)
920 brightness=1.0;
921 else
922 if (brightness < 0.0)
923 brightness=0.0;
924 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
925}
926
927MagickExport MagickBooleanType ContrastImage(Image *image,
928 const MagickBooleanType sharpen)
929{
930#define ContrastImageTag "Contrast/Image"
931
cristyc4c8d132010-01-07 01:58:38 +0000932 CacheView
933 *image_view;
934
cristy3ed852e2009-09-05 21:47:34 +0000935 ExceptionInfo
936 *exception;
937
938 int
939 sign;
940
941 long
942 progress,
943 y;
944
945 MagickBooleanType
946 status;
947
948 register long
949 i;
950
cristy3ed852e2009-09-05 21:47:34 +0000951 assert(image != (Image *) NULL);
952 assert(image->signature == MagickSignature);
953 if (image->debug != MagickFalse)
954 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
955 sign=sharpen != MagickFalse ? 1 : -1;
956 if (image->storage_class == PseudoClass)
957 {
958 /*
959 Contrast enhance colormap.
960 */
961 for (i=0; i < (long) image->colors; i++)
962 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
963 &image->colormap[i].blue);
964 }
965 /*
966 Contrast enhance image.
967 */
968 status=MagickTrue;
969 progress=0;
970 exception=(&image->exception);
971 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000972#if defined(MAGICKCORE_OPENMP_SUPPORT)
973 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000974#endif
975 for (y=0; y < (long) image->rows; y++)
976 {
977 register long
978 x;
979
980 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000981 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000982
983 if (status == MagickFalse)
984 continue;
985 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
986 if (q == (PixelPacket *) NULL)
987 {
988 status=MagickFalse;
989 continue;
990 }
991 for (x=0; x < (long) image->columns; x++)
992 {
993 Contrast(sign,&q->red,&q->green,&q->blue);
994 q++;
995 }
996 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
997 status=MagickFalse;
998 if (image->progress_monitor != (MagickProgressMonitor) NULL)
999 {
1000 MagickBooleanType
1001 proceed;
1002
cristyb5d5f722009-11-04 03:03:49 +00001003#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001004 #pragma omp critical (MagickCore_ContrastImage)
1005#endif
1006 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1007 if (proceed == MagickFalse)
1008 status=MagickFalse;
1009 }
1010 }
1011 image_view=DestroyCacheView(image_view);
1012 return(status);
1013}
1014
1015/*
1016%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1017% %
1018% %
1019% %
1020% C o n t r a s t S t r e t c h I m a g e %
1021% %
1022% %
1023% %
1024%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1025%
1026% The ContrastStretchImage() is a simple image enhancement technique that
1027% attempts to improve the contrast in an image by `stretching' the range of
1028% intensity values it contains to span a desired range of values. It differs
1029% from the more sophisticated histogram equalization in that it can only
1030% apply % a linear scaling function to the image pixel values. As a result
1031% the `enhancement' is less harsh.
1032%
1033% The format of the ContrastStretchImage method is:
1034%
1035% MagickBooleanType ContrastStretchImage(Image *image,
1036% const char *levels)
1037% MagickBooleanType ContrastStretchImageChannel(Image *image,
1038% const unsigned long channel,const double black_point,
1039% const double white_point)
1040%
1041% A description of each parameter follows:
1042%
1043% o image: the image.
1044%
1045% o channel: the channel.
1046%
1047% o black_point: the black point.
1048%
1049% o white_point: the white point.
1050%
1051% o levels: Specify the levels where the black and white points have the
1052% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1053%
1054*/
1055
1056MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1057 const char *levels)
1058{
1059 double
1060 black_point,
1061 white_point;
1062
1063 GeometryInfo
1064 geometry_info;
1065
1066 MagickBooleanType
1067 status;
1068
1069 MagickStatusType
1070 flags;
1071
1072 /*
1073 Parse levels.
1074 */
1075 if (levels == (char *) NULL)
1076 return(MagickFalse);
1077 flags=ParseGeometry(levels,&geometry_info);
1078 black_point=geometry_info.rho;
1079 white_point=(double) image->columns*image->rows;
1080 if ((flags & SigmaValue) != 0)
1081 white_point=geometry_info.sigma;
1082 if ((flags & PercentValue) != 0)
1083 {
1084 black_point*=(double) QuantumRange/100.0;
1085 white_point*=(double) QuantumRange/100.0;
1086 }
1087 if ((flags & SigmaValue) == 0)
1088 white_point=(double) image->columns*image->rows-black_point;
1089 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1090 white_point);
1091 return(status);
1092}
1093
1094MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1095 const ChannelType channel,const double black_point,const double white_point)
1096{
1097#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1098#define ContrastStretchImageTag "ContrastStretch/Image"
1099
cristyc4c8d132010-01-07 01:58:38 +00001100 CacheView
1101 *image_view;
1102
cristy3ed852e2009-09-05 21:47:34 +00001103 double
1104 intensity;
1105
1106 ExceptionInfo
1107 *exception;
1108
1109 long
1110 progress,
1111 y;
1112
1113 MagickBooleanType
1114 status;
1115
1116 MagickPixelPacket
1117 black,
1118 *histogram,
1119 *stretch_map,
1120 white;
1121
1122 register long
1123 i;
1124
cristy3ed852e2009-09-05 21:47:34 +00001125 /*
1126 Allocate histogram and stretch map.
1127 */
1128 assert(image != (Image *) NULL);
1129 assert(image->signature == MagickSignature);
1130 if (image->debug != MagickFalse)
1131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1132 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1133 sizeof(*histogram));
1134 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1135 sizeof(*stretch_map));
1136 if ((histogram == (MagickPixelPacket *) NULL) ||
1137 (stretch_map == (MagickPixelPacket *) NULL))
1138 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1139 image->filename);
1140 /*
1141 Form histogram.
1142 */
1143 status=MagickTrue;
1144 exception=(&image->exception);
1145 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1146 image_view=AcquireCacheView(image);
1147 for (y=0; y < (long) image->rows; y++)
1148 {
1149 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001150 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001151
1152 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001153 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001154
1155 register long
1156 x;
1157
1158 if (status == MagickFalse)
1159 continue;
1160 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1161 if (p == (const PixelPacket *) NULL)
1162 {
1163 status=MagickFalse;
1164 continue;
1165 }
1166 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1167 if (channel == DefaultChannels)
1168 for (x=0; x < (long) image->columns; x++)
1169 {
1170 Quantum
1171 intensity;
1172
1173 intensity=PixelIntensityToQuantum(p);
1174 histogram[ScaleQuantumToMap(intensity)].red++;
1175 histogram[ScaleQuantumToMap(intensity)].green++;
1176 histogram[ScaleQuantumToMap(intensity)].blue++;
1177 histogram[ScaleQuantumToMap(intensity)].index++;
1178 p++;
1179 }
1180 else
1181 for (x=0; x < (long) image->columns; x++)
1182 {
1183 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001184 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001185 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001186 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001187 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001188 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001189 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001190 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001191 if (((channel & IndexChannel) != 0) &&
1192 (image->colorspace == CMYKColorspace))
1193 histogram[ScaleQuantumToMap(indexes[x])].index++;
1194 p++;
1195 }
1196 }
1197 /*
1198 Find the histogram boundaries by locating the black/white levels.
1199 */
1200 black.red=0.0;
1201 white.red=MaxRange(QuantumRange);
1202 if ((channel & RedChannel) != 0)
1203 {
1204 intensity=0.0;
1205 for (i=0; i <= (long) MaxMap; i++)
1206 {
1207 intensity+=histogram[i].red;
1208 if (intensity > black_point)
1209 break;
1210 }
1211 black.red=(MagickRealType) i;
1212 intensity=0.0;
1213 for (i=(long) MaxMap; i != 0; i--)
1214 {
1215 intensity+=histogram[i].red;
1216 if (intensity > ((double) image->columns*image->rows-white_point))
1217 break;
1218 }
1219 white.red=(MagickRealType) i;
1220 }
1221 black.green=0.0;
1222 white.green=MaxRange(QuantumRange);
1223 if ((channel & GreenChannel) != 0)
1224 {
1225 intensity=0.0;
1226 for (i=0; i <= (long) MaxMap; i++)
1227 {
1228 intensity+=histogram[i].green;
1229 if (intensity > black_point)
1230 break;
1231 }
1232 black.green=(MagickRealType) i;
1233 intensity=0.0;
1234 for (i=(long) MaxMap; i != 0; i--)
1235 {
1236 intensity+=histogram[i].green;
1237 if (intensity > ((double) image->columns*image->rows-white_point))
1238 break;
1239 }
1240 white.green=(MagickRealType) i;
1241 }
1242 black.blue=0.0;
1243 white.blue=MaxRange(QuantumRange);
1244 if ((channel & BlueChannel) != 0)
1245 {
1246 intensity=0.0;
1247 for (i=0; i <= (long) MaxMap; i++)
1248 {
1249 intensity+=histogram[i].blue;
1250 if (intensity > black_point)
1251 break;
1252 }
1253 black.blue=(MagickRealType) i;
1254 intensity=0.0;
1255 for (i=(long) MaxMap; i != 0; i--)
1256 {
1257 intensity+=histogram[i].blue;
1258 if (intensity > ((double) image->columns*image->rows-white_point))
1259 break;
1260 }
1261 white.blue=(MagickRealType) i;
1262 }
1263 black.opacity=0.0;
1264 white.opacity=MaxRange(QuantumRange);
1265 if ((channel & OpacityChannel) != 0)
1266 {
1267 intensity=0.0;
1268 for (i=0; i <= (long) MaxMap; i++)
1269 {
1270 intensity+=histogram[i].opacity;
1271 if (intensity > black_point)
1272 break;
1273 }
1274 black.opacity=(MagickRealType) i;
1275 intensity=0.0;
1276 for (i=(long) MaxMap; i != 0; i--)
1277 {
1278 intensity+=histogram[i].opacity;
1279 if (intensity > ((double) image->columns*image->rows-white_point))
1280 break;
1281 }
1282 white.opacity=(MagickRealType) i;
1283 }
1284 black.index=0.0;
1285 white.index=MaxRange(QuantumRange);
1286 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1287 {
1288 intensity=0.0;
1289 for (i=0; i <= (long) MaxMap; i++)
1290 {
1291 intensity+=histogram[i].index;
1292 if (intensity > black_point)
1293 break;
1294 }
1295 black.index=(MagickRealType) i;
1296 intensity=0.0;
1297 for (i=(long) MaxMap; i != 0; i--)
1298 {
1299 intensity+=histogram[i].index;
1300 if (intensity > ((double) image->columns*image->rows-white_point))
1301 break;
1302 }
1303 white.index=(MagickRealType) i;
1304 }
1305 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1306 /*
1307 Stretch the histogram to create the stretched image mapping.
1308 */
1309 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001310#if defined(MAGICKCORE_OPENMP_SUPPORT)
1311 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001312#endif
1313 for (i=0; i <= (long) MaxMap; i++)
1314 {
1315 if ((channel & RedChannel) != 0)
1316 {
1317 if (i < (long) black.red)
1318 stretch_map[i].red=0.0;
1319 else
1320 if (i > (long) white.red)
1321 stretch_map[i].red=(MagickRealType) QuantumRange;
1322 else
1323 if (black.red != white.red)
1324 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1325 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1326 }
1327 if ((channel & GreenChannel) != 0)
1328 {
1329 if (i < (long) black.green)
1330 stretch_map[i].green=0.0;
1331 else
1332 if (i > (long) white.green)
1333 stretch_map[i].green=(MagickRealType) QuantumRange;
1334 else
1335 if (black.green != white.green)
1336 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1337 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1338 black.green)));
1339 }
1340 if ((channel & BlueChannel) != 0)
1341 {
1342 if (i < (long) black.blue)
1343 stretch_map[i].blue=0.0;
1344 else
1345 if (i > (long) white.blue)
1346 stretch_map[i].blue=(MagickRealType) QuantumRange;
1347 else
1348 if (black.blue != white.blue)
1349 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1350 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1351 black.blue)));
1352 }
1353 if ((channel & OpacityChannel) != 0)
1354 {
1355 if (i < (long) black.opacity)
1356 stretch_map[i].opacity=0.0;
1357 else
1358 if (i > (long) white.opacity)
1359 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1360 else
1361 if (black.opacity != white.opacity)
1362 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1363 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1364 black.opacity)));
1365 }
1366 if (((channel & IndexChannel) != 0) &&
1367 (image->colorspace == CMYKColorspace))
1368 {
1369 if (i < (long) black.index)
1370 stretch_map[i].index=0.0;
1371 else
1372 if (i > (long) white.index)
1373 stretch_map[i].index=(MagickRealType) QuantumRange;
1374 else
1375 if (black.index != white.index)
1376 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1377 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1378 black.index)));
1379 }
1380 }
1381 /*
1382 Stretch the image.
1383 */
1384 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1385 (image->colorspace == CMYKColorspace)))
1386 image->storage_class=DirectClass;
1387 if (image->storage_class == PseudoClass)
1388 {
1389 /*
1390 Stretch colormap.
1391 */
cristyb5d5f722009-11-04 03:03:49 +00001392#if defined(MAGICKCORE_OPENMP_SUPPORT)
1393 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001394#endif
1395 for (i=0; i < (long) image->colors; i++)
1396 {
1397 if ((channel & RedChannel) != 0)
1398 {
1399 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001400 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001401 ScaleQuantumToMap(image->colormap[i].red)].red);
1402 }
1403 if ((channel & GreenChannel) != 0)
1404 {
1405 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001406 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001407 ScaleQuantumToMap(image->colormap[i].green)].green);
1408 }
1409 if ((channel & BlueChannel) != 0)
1410 {
1411 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001412 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001413 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1414 }
1415 if ((channel & OpacityChannel) != 0)
1416 {
1417 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001418 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001419 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1420 }
1421 }
1422 }
1423 /*
1424 Stretch image.
1425 */
1426 status=MagickTrue;
1427 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001428#if defined(MAGICKCORE_OPENMP_SUPPORT)
1429 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001430#endif
1431 for (y=0; y < (long) image->rows; y++)
1432 {
1433 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001434 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001435
1436 register long
1437 x;
1438
1439 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001440 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001441
1442 if (status == MagickFalse)
1443 continue;
1444 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1445 if (q == (PixelPacket *) NULL)
1446 {
1447 status=MagickFalse;
1448 continue;
1449 }
1450 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1451 for (x=0; x < (long) image->columns; x++)
1452 {
1453 if ((channel & RedChannel) != 0)
1454 {
1455 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001456 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001457 }
1458 if ((channel & GreenChannel) != 0)
1459 {
1460 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001461 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001462 q->green)].green);
1463 }
1464 if ((channel & BlueChannel) != 0)
1465 {
1466 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001467 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001468 q->blue)].blue);
1469 }
1470 if ((channel & OpacityChannel) != 0)
1471 {
1472 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001473 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001474 q->opacity)].opacity);
1475 }
1476 if (((channel & IndexChannel) != 0) &&
1477 (image->colorspace == CMYKColorspace))
1478 {
1479 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001480 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001481 ScaleQuantumToMap(indexes[x])].index);
1482 }
1483 q++;
1484 }
1485 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1486 status=MagickFalse;
1487 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1488 {
1489 MagickBooleanType
1490 proceed;
1491
cristyb5d5f722009-11-04 03:03:49 +00001492#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001493 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1494#endif
1495 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1496 image->rows);
1497 if (proceed == MagickFalse)
1498 status=MagickFalse;
1499 }
1500 }
1501 image_view=DestroyCacheView(image_view);
1502 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1503 return(status);
1504}
1505
1506/*
1507%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1508% %
1509% %
1510% %
1511% E n h a n c e I m a g e %
1512% %
1513% %
1514% %
1515%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1516%
1517% EnhanceImage() applies a digital filter that improves the quality of a
1518% noisy image.
1519%
1520% The format of the EnhanceImage method is:
1521%
1522% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1523%
1524% A description of each parameter follows:
1525%
1526% o image: the image.
1527%
1528% o exception: return any errors or warnings in this structure.
1529%
1530*/
1531MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1532{
1533#define Enhance(weight) \
1534 mean=((MagickRealType) r->red+pixel.red)/2; \
1535 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1536 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1537 mean)*distance*distance; \
1538 mean=((MagickRealType) r->green+pixel.green)/2; \
1539 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1540 distance_squared+=4.0*distance*distance; \
1541 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1542 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1543 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1544 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1545 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1546 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1547 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1548 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1549 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1550 QuantumRange/25.0f)) \
1551 { \
1552 aggregate.red+=(weight)*r->red; \
1553 aggregate.green+=(weight)*r->green; \
1554 aggregate.blue+=(weight)*r->blue; \
1555 aggregate.opacity+=(weight)*r->opacity; \
1556 total_weight+=(weight); \
1557 } \
1558 r++;
1559#define EnhanceImageTag "Enhance/Image"
1560
cristyc4c8d132010-01-07 01:58:38 +00001561 CacheView
1562 *enhance_view,
1563 *image_view;
1564
cristy3ed852e2009-09-05 21:47:34 +00001565 Image
1566 *enhance_image;
1567
1568 long
1569 progress,
1570 y;
1571
1572 MagickBooleanType
1573 status;
1574
1575 MagickPixelPacket
1576 zero;
1577
cristy3ed852e2009-09-05 21:47:34 +00001578 /*
1579 Initialize enhanced image attributes.
1580 */
1581 assert(image != (const Image *) NULL);
1582 assert(image->signature == MagickSignature);
1583 if (image->debug != MagickFalse)
1584 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1585 assert(exception != (ExceptionInfo *) NULL);
1586 assert(exception->signature == MagickSignature);
1587 if ((image->columns < 5) || (image->rows < 5))
1588 return((Image *) NULL);
1589 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1590 exception);
1591 if (enhance_image == (Image *) NULL)
1592 return((Image *) NULL);
1593 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1594 {
1595 InheritException(exception,&enhance_image->exception);
1596 enhance_image=DestroyImage(enhance_image);
1597 return((Image *) NULL);
1598 }
1599 /*
1600 Enhance image.
1601 */
1602 status=MagickTrue;
1603 progress=0;
1604 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1605 image_view=AcquireCacheView(image);
1606 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001607#if defined(MAGICKCORE_OPENMP_SUPPORT)
1608 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001609#endif
1610 for (y=0; y < (long) image->rows; y++)
1611 {
1612 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001613 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001614
1615 register long
1616 x;
1617
1618 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001619 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001620
1621 /*
1622 Read another scan line.
1623 */
1624 if (status == MagickFalse)
1625 continue;
1626 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1627 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1628 exception);
1629 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1630 {
1631 status=MagickFalse;
1632 continue;
1633 }
1634 for (x=0; x < (long) image->columns; x++)
1635 {
1636 MagickPixelPacket
1637 aggregate;
1638
1639 MagickRealType
1640 distance,
1641 distance_squared,
1642 mean,
1643 total_weight;
1644
1645 PixelPacket
1646 pixel;
1647
1648 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001649 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001650
1651 /*
1652 Compute weighted average of target pixel color components.
1653 */
1654 aggregate=zero;
1655 total_weight=0.0;
1656 r=p+2*(image->columns+4)+2;
1657 pixel=(*r);
1658 r=p;
1659 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1660 r=p+(image->columns+4);
1661 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1662 r=p+2*(image->columns+4);
1663 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1664 r=p+3*(image->columns+4);
1665 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1666 r=p+4*(image->columns+4);
1667 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1668 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1669 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1670 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1671 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1672 total_weight);
1673 p++;
1674 q++;
1675 }
1676 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1677 status=MagickFalse;
1678 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1679 {
1680 MagickBooleanType
1681 proceed;
1682
cristyb5d5f722009-11-04 03:03:49 +00001683#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001684 #pragma omp critical (MagickCore_EnhanceImage)
1685#endif
1686 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1687 if (proceed == MagickFalse)
1688 status=MagickFalse;
1689 }
1690 }
1691 enhance_view=DestroyCacheView(enhance_view);
1692 image_view=DestroyCacheView(image_view);
1693 return(enhance_image);
1694}
1695
1696/*
1697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1698% %
1699% %
1700% %
1701% E q u a l i z e I m a g e %
1702% %
1703% %
1704% %
1705%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1706%
1707% EqualizeImage() applies a histogram equalization to the image.
1708%
1709% The format of the EqualizeImage method is:
1710%
1711% MagickBooleanType EqualizeImage(Image *image)
1712% MagickBooleanType EqualizeImageChannel(Image *image,
1713% const ChannelType channel)
1714%
1715% A description of each parameter follows:
1716%
1717% o image: the image.
1718%
1719% o channel: the channel.
1720%
1721*/
1722
1723MagickExport MagickBooleanType EqualizeImage(Image *image)
1724{
1725 return(EqualizeImageChannel(image,DefaultChannels));
1726}
1727
1728MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1729 const ChannelType channel)
1730{
1731#define EqualizeImageTag "Equalize/Image"
1732
cristyc4c8d132010-01-07 01:58:38 +00001733 CacheView
1734 *image_view;
1735
cristy3ed852e2009-09-05 21:47:34 +00001736 ExceptionInfo
1737 *exception;
1738
1739 long
1740 progress,
1741 y;
1742
1743 MagickBooleanType
1744 status;
1745
1746 MagickPixelPacket
1747 black,
1748 *equalize_map,
1749 *histogram,
1750 intensity,
1751 *map,
1752 white;
1753
1754 register long
1755 i;
1756
cristy3ed852e2009-09-05 21:47:34 +00001757 /*
1758 Allocate and initialize histogram arrays.
1759 */
1760 assert(image != (Image *) NULL);
1761 assert(image->signature == MagickSignature);
1762 if (image->debug != MagickFalse)
1763 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1764 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1765 sizeof(*equalize_map));
1766 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1767 sizeof(*histogram));
1768 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1769 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1770 (histogram == (MagickPixelPacket *) NULL) ||
1771 (map == (MagickPixelPacket *) NULL))
1772 {
1773 if (map != (MagickPixelPacket *) NULL)
1774 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1775 if (histogram != (MagickPixelPacket *) NULL)
1776 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1777 if (equalize_map != (MagickPixelPacket *) NULL)
1778 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1779 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1780 image->filename);
1781 }
1782 /*
1783 Form histogram.
1784 */
1785 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1786 exception=(&image->exception);
1787 for (y=0; y < (long) image->rows; y++)
1788 {
1789 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001790 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001791
1792 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001793 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001794
1795 register long
1796 x;
1797
1798 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1799 if (p == (const PixelPacket *) NULL)
1800 break;
1801 indexes=GetVirtualIndexQueue(image);
1802 for (x=0; x < (long) image->columns; x++)
1803 {
1804 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001805 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001806 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001807 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001808 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001809 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001810 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001811 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001812 if (((channel & IndexChannel) != 0) &&
1813 (image->colorspace == CMYKColorspace))
1814 histogram[ScaleQuantumToMap(indexes[x])].index++;
1815 p++;
1816 }
1817 }
1818 /*
1819 Integrate the histogram to get the equalization map.
1820 */
1821 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1822 for (i=0; i <= (long) MaxMap; i++)
1823 {
1824 if ((channel & RedChannel) != 0)
1825 intensity.red+=histogram[i].red;
1826 if ((channel & GreenChannel) != 0)
1827 intensity.green+=histogram[i].green;
1828 if ((channel & BlueChannel) != 0)
1829 intensity.blue+=histogram[i].blue;
1830 if ((channel & OpacityChannel) != 0)
1831 intensity.opacity+=histogram[i].opacity;
1832 if (((channel & IndexChannel) != 0) &&
1833 (image->colorspace == CMYKColorspace))
1834 intensity.index+=histogram[i].index;
1835 map[i]=intensity;
1836 }
1837 black=map[0];
1838 white=map[(int) MaxMap];
1839 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001840#if defined(MAGICKCORE_OPENMP_SUPPORT)
1841 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001842#endif
1843 for (i=0; i <= (long) MaxMap; i++)
1844 {
1845 if (((channel & RedChannel) != 0) && (white.red != black.red))
1846 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1847 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1848 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1849 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1850 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1851 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1852 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1853 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1854 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1855 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1856 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1857 (white.opacity-black.opacity)));
1858 if ((((channel & IndexChannel) != 0) &&
1859 (image->colorspace == CMYKColorspace)) &&
1860 (white.index != black.index))
1861 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1862 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1863 }
1864 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1865 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1866 if (image->storage_class == PseudoClass)
1867 {
1868 /*
1869 Equalize colormap.
1870 */
cristyb5d5f722009-11-04 03:03:49 +00001871#if defined(MAGICKCORE_OPENMP_SUPPORT)
1872 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001873#endif
1874 for (i=0; i < (long) image->colors; i++)
1875 {
1876 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001877 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001878 ScaleQuantumToMap(image->colormap[i].red)].red);
1879 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001880 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001881 ScaleQuantumToMap(image->colormap[i].green)].green);
1882 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001883 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001884 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1885 if (((channel & OpacityChannel) != 0) &&
1886 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001887 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001888 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1889 }
1890 }
1891 /*
1892 Equalize image.
1893 */
1894 status=MagickTrue;
1895 progress=0;
1896 exception=(&image->exception);
1897 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001898#if defined(MAGICKCORE_OPENMP_SUPPORT)
1899 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001900#endif
1901 for (y=0; y < (long) image->rows; y++)
1902 {
1903 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001904 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001905
1906 register long
1907 x;
1908
1909 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001910 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001911
1912 if (status == MagickFalse)
1913 continue;
1914 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1915 if (q == (PixelPacket *) NULL)
1916 {
1917 status=MagickFalse;
1918 continue;
1919 }
1920 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1921 for (x=0; x < (long) image->columns; x++)
1922 {
1923 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001924 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001925 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001926 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001927 q->green)].green);
1928 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001929 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001930 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001931 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001932 q->opacity)].opacity);
1933 if ((((channel & IndexChannel) != 0) &&
1934 (image->colorspace == CMYKColorspace)) &&
1935 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001936 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001937 indexes[x])].index);
1938 q++;
1939 }
1940 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1941 status=MagickFalse;
1942 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1943 {
1944 MagickBooleanType
1945 proceed;
1946
cristyb5d5f722009-11-04 03:03:49 +00001947#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001948 #pragma omp critical (MagickCore_EqualizeImageChannel)
1949#endif
1950 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1951 if (proceed == MagickFalse)
1952 status=MagickFalse;
1953 }
1954 }
1955 image_view=DestroyCacheView(image_view);
1956 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1957 return(status);
1958}
1959
1960/*
1961%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1962% %
1963% %
1964% %
1965% G a m m a I m a g e %
1966% %
1967% %
1968% %
1969%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1970%
1971% GammaImage() gamma-corrects a particular image channel. The same
1972% image viewed on different devices will have perceptual differences in the
1973% way the image's intensities are represented on the screen. Specify
1974% individual gamma levels for the red, green, and blue channels, or adjust
1975% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1976%
1977% You can also reduce the influence of a particular channel with a gamma
1978% value of 0.
1979%
1980% The format of the GammaImage method is:
1981%
1982% MagickBooleanType GammaImage(Image *image,const double gamma)
1983% MagickBooleanType GammaImageChannel(Image *image,
1984% const ChannelType channel,const double gamma)
1985%
1986% A description of each parameter follows:
1987%
1988% o image: the image.
1989%
1990% o channel: the channel.
1991%
1992% o gamma: the image gamma.
1993%
1994*/
1995MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1996{
1997 GeometryInfo
1998 geometry_info;
1999
2000 MagickPixelPacket
2001 gamma;
2002
2003 MagickStatusType
2004 flags,
2005 status;
2006
2007 assert(image != (Image *) NULL);
2008 assert(image->signature == MagickSignature);
2009 if (image->debug != MagickFalse)
2010 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2011 if (level == (char *) NULL)
2012 return(MagickFalse);
2013 flags=ParseGeometry(level,&geometry_info);
2014 gamma.red=geometry_info.rho;
2015 gamma.green=geometry_info.sigma;
2016 if ((flags & SigmaValue) == 0)
2017 gamma.green=gamma.red;
2018 gamma.blue=geometry_info.xi;
2019 if ((flags & XiValue) == 0)
2020 gamma.blue=gamma.red;
2021 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2022 return(MagickTrue);
2023 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2024 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2025 GreenChannel | BlueChannel),(double) gamma.red);
2026 else
2027 {
2028 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2029 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2030 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2031 }
2032 return(status != 0 ? MagickTrue : MagickFalse);
2033}
2034
2035MagickExport MagickBooleanType GammaImageChannel(Image *image,
2036 const ChannelType channel,const double gamma)
2037{
2038#define GammaCorrectImageTag "GammaCorrect/Image"
2039
cristyc4c8d132010-01-07 01:58:38 +00002040 CacheView
2041 *image_view;
2042
cristy3ed852e2009-09-05 21:47:34 +00002043 ExceptionInfo
2044 *exception;
2045
2046 long
2047 progress,
2048 y;
2049
2050 MagickBooleanType
2051 status;
2052
2053 Quantum
2054 *gamma_map;
2055
2056 register long
2057 i;
2058
cristy3ed852e2009-09-05 21:47:34 +00002059 /*
2060 Allocate and initialize gamma maps.
2061 */
2062 assert(image != (Image *) NULL);
2063 assert(image->signature == MagickSignature);
2064 if (image->debug != MagickFalse)
2065 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2066 if (gamma == 1.0)
2067 return(MagickTrue);
2068 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2069 if (gamma_map == (Quantum *) NULL)
2070 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2071 image->filename);
2072 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2073 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002074#if defined(MAGICKCORE_OPENMP_SUPPORT)
2075 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002076#endif
2077 for (i=0; i <= (long) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002078 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002079 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2080 if (image->storage_class == PseudoClass)
2081 {
2082 /*
2083 Gamma-correct colormap.
2084 */
cristyb5d5f722009-11-04 03:03:49 +00002085#if defined(MAGICKCORE_OPENMP_SUPPORT)
2086 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002087#endif
2088 for (i=0; i < (long) image->colors; i++)
2089 {
2090 if ((channel & RedChannel) != 0)
2091 image->colormap[i].red=gamma_map[
2092 ScaleQuantumToMap(image->colormap[i].red)];
2093 if ((channel & GreenChannel) != 0)
2094 image->colormap[i].green=gamma_map[
2095 ScaleQuantumToMap(image->colormap[i].green)];
2096 if ((channel & BlueChannel) != 0)
2097 image->colormap[i].blue=gamma_map[
2098 ScaleQuantumToMap(image->colormap[i].blue)];
2099 if ((channel & OpacityChannel) != 0)
2100 {
2101 if (image->matte == MagickFalse)
2102 image->colormap[i].opacity=gamma_map[
2103 ScaleQuantumToMap(image->colormap[i].opacity)];
2104 else
2105 image->colormap[i].opacity=(Quantum) QuantumRange-
2106 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2107 image->colormap[i].opacity))];
2108 }
2109 }
2110 }
2111 /*
2112 Gamma-correct image.
2113 */
2114 status=MagickTrue;
2115 progress=0;
2116 exception=(&image->exception);
2117 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002118#if defined(MAGICKCORE_OPENMP_SUPPORT)
2119 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002120#endif
2121 for (y=0; y < (long) image->rows; y++)
2122 {
2123 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002124 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002125
2126 register long
2127 x;
2128
2129 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002130 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002131
2132 if (status == MagickFalse)
2133 continue;
2134 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2135 if (q == (PixelPacket *) NULL)
2136 {
2137 status=MagickFalse;
2138 continue;
2139 }
2140 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2141 for (x=0; x < (long) image->columns; x++)
2142 {
cristy6cbd7f52009-10-17 16:06:51 +00002143 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002144 {
cristy6cbd7f52009-10-17 16:06:51 +00002145 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2146 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2147 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2148 }
2149 else
2150 {
2151 if ((channel & RedChannel) != 0)
2152 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2153 if ((channel & GreenChannel) != 0)
2154 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2155 if ((channel & BlueChannel) != 0)
2156 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2157 if ((channel & OpacityChannel) != 0)
2158 {
2159 if (image->matte == MagickFalse)
2160 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2161 else
2162 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002163 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002164 }
cristy3ed852e2009-09-05 21:47:34 +00002165 }
2166 q++;
2167 }
2168 if (((channel & IndexChannel) != 0) &&
2169 (image->colorspace == CMYKColorspace))
2170 for (x=0; x < (long) image->columns; x++)
2171 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2172 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2173 status=MagickFalse;
2174 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2175 {
2176 MagickBooleanType
2177 proceed;
2178
cristyb5d5f722009-11-04 03:03:49 +00002179#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002180 #pragma omp critical (MagickCore_GammaImageChannel)
2181#endif
2182 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2183 image->rows);
2184 if (proceed == MagickFalse)
2185 status=MagickFalse;
2186 }
2187 }
2188 image_view=DestroyCacheView(image_view);
2189 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2190 if (image->gamma != 0.0)
2191 image->gamma*=gamma;
2192 return(status);
2193}
2194
2195/*
2196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2197% %
2198% %
2199% %
2200% H a l d C l u t I m a g e %
2201% %
2202% %
2203% %
2204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2205%
2206% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2207% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2208% Create it with the HALD coder. You can apply any color transformation to
2209% the Hald image and then use this method to apply the transform to the
2210% image.
2211%
2212% The format of the HaldClutImage method is:
2213%
2214% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2215% MagickBooleanType HaldClutImageChannel(Image *image,
2216% const ChannelType channel,Image *hald_image)
2217%
2218% A description of each parameter follows:
2219%
2220% o image: the image, which is replaced by indexed CLUT values
2221%
2222% o hald_image: the color lookup table image for replacement color values.
2223%
2224% o channel: the channel.
2225%
2226*/
2227
2228static inline size_t MagickMin(const size_t x,const size_t y)
2229{
2230 if (x < y)
2231 return(x);
2232 return(y);
2233}
2234
2235MagickExport MagickBooleanType HaldClutImage(Image *image,
2236 const Image *hald_image)
2237{
2238 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2239}
2240
2241MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2242 const ChannelType channel,const Image *hald_image)
2243{
2244#define HaldClutImageTag "Clut/Image"
2245
2246 typedef struct _HaldInfo
2247 {
2248 MagickRealType
2249 x,
2250 y,
2251 z;
2252 } HaldInfo;
2253
cristyfa112112010-01-04 17:48:07 +00002254 CacheView
2255 *image_view;
2256
cristy3ed852e2009-09-05 21:47:34 +00002257 double
2258 width;
2259
2260 ExceptionInfo
2261 *exception;
2262
2263 long
2264 progress,
2265 y;
2266
2267 MagickBooleanType
2268 status;
2269
2270 MagickPixelPacket
2271 zero;
2272
2273 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002274 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002275
2276 size_t
2277 cube_size,
2278 length,
2279 level;
2280
cristy3ed852e2009-09-05 21:47:34 +00002281 assert(image != (Image *) NULL);
2282 assert(image->signature == MagickSignature);
2283 if (image->debug != MagickFalse)
2284 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2285 assert(hald_image != (Image *) NULL);
2286 assert(hald_image->signature == MagickSignature);
2287 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2288 return(MagickFalse);
2289 if (image->matte == MagickFalse)
2290 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2291 /*
2292 Hald clut image.
2293 */
2294 status=MagickTrue;
2295 progress=0;
2296 length=MagickMin(hald_image->columns,hald_image->rows);
2297 for (level=2; (level*level*level) < length; level++) ;
2298 level*=level;
2299 cube_size=level*level;
2300 width=(double) hald_image->columns;
2301 GetMagickPixelPacket(hald_image,&zero);
2302 exception=(&image->exception);
2303 resample_filter=AcquireResampleFilterThreadSet(hald_image,MagickTrue,
2304 exception);
2305 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002306#if defined(MAGICKCORE_OPENMP_SUPPORT)
2307 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002308#endif
2309 for (y=0; y < (long) image->rows; y++)
2310 {
2311 double
2312 offset;
2313
2314 HaldInfo
2315 point;
2316
2317 MagickPixelPacket
2318 pixel,
2319 pixel1,
2320 pixel2,
2321 pixel3,
2322 pixel4;
2323
2324 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002325 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002326
2327 register long
2328 id,
2329 x;
2330
2331 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002332 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002333
2334 if (status == MagickFalse)
2335 continue;
2336 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2337 if (q == (PixelPacket *) NULL)
2338 {
2339 status=MagickFalse;
2340 continue;
2341 }
2342 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2343 pixel=zero;
2344 pixel1=zero;
2345 pixel2=zero;
2346 pixel3=zero;
2347 pixel4=zero;
2348 id=GetOpenMPThreadId();
2349 for (x=0; x < (long) image->columns; x++)
2350 {
2351 point.x=QuantumScale*(level-1.0)*q->red;
2352 point.y=QuantumScale*(level-1.0)*q->green;
2353 point.z=QuantumScale*(level-1.0)*q->blue;
2354 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2355 point.x-=floor(point.x);
2356 point.y-=floor(point.y);
2357 point.z-=floor(point.z);
2358 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2359 floor(offset/width),&pixel1);
2360 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2361 floor((offset+level)/width),&pixel2);
2362 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2363 pixel2.opacity,point.y,&pixel3);
2364 offset+=cube_size;
2365 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2366 floor(offset/width),&pixel1);
2367 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2368 floor((offset+level)/width),&pixel2);
2369 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2370 pixel2.opacity,point.y,&pixel4);
2371 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2372 pixel4.opacity,point.z,&pixel);
2373 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002374 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002375 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002376 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002377 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002378 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002379 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002380 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002381 if (((channel & IndexChannel) != 0) &&
2382 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002383 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002384 q++;
2385 }
2386 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2387 status=MagickFalse;
2388 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2389 {
2390 MagickBooleanType
2391 proceed;
2392
cristyb5d5f722009-11-04 03:03:49 +00002393#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002394 #pragma omp critical (MagickCore_HaldClutImageChannel)
2395#endif
2396 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2397 if (proceed == MagickFalse)
2398 status=MagickFalse;
2399 }
2400 }
2401 image_view=DestroyCacheView(image_view);
2402 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2403 return(status);
2404}
2405
2406/*
2407%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2408% %
2409% %
2410% %
2411% L e v e l I m a g e %
2412% %
2413% %
2414% %
2415%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2416%
2417% LevelImage() adjusts the levels of a particular image channel by
2418% scaling the colors falling between specified white and black points to
2419% the full available quantum range.
2420%
2421% The parameters provided represent the black, and white points. The black
2422% point specifies the darkest color in the image. Colors darker than the
2423% black point are set to zero. White point specifies the lightest color in
2424% the image. Colors brighter than the white point are set to the maximum
2425% quantum value.
2426%
2427% If a '!' flag is given, map black and white colors to the given levels
2428% rather than mapping those levels to black and white. See
2429% LevelizeImageChannel() and LevelizeImageChannel(), below.
2430%
2431% Gamma specifies a gamma correction to apply to the image.
2432%
2433% The format of the LevelImage method is:
2434%
2435% MagickBooleanType LevelImage(Image *image,const char *levels)
2436%
2437% A description of each parameter follows:
2438%
2439% o image: the image.
2440%
2441% o levels: Specify the levels where the black and white points have the
2442% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2443% A '!' flag inverts the re-mapping.
2444%
2445*/
2446
2447MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2448{
2449 double
2450 black_point,
2451 gamma,
2452 white_point;
2453
2454 GeometryInfo
2455 geometry_info;
2456
2457 MagickBooleanType
2458 status;
2459
2460 MagickStatusType
2461 flags;
2462
2463 /*
2464 Parse levels.
2465 */
2466 if (levels == (char *) NULL)
2467 return(MagickFalse);
2468 flags=ParseGeometry(levels,&geometry_info);
2469 black_point=geometry_info.rho;
2470 white_point=(double) QuantumRange;
2471 if ((flags & SigmaValue) != 0)
2472 white_point=geometry_info.sigma;
2473 gamma=1.0;
2474 if ((flags & XiValue) != 0)
2475 gamma=geometry_info.xi;
2476 if ((flags & PercentValue) != 0)
2477 {
2478 black_point*=(double) image->columns*image->rows/100.0;
2479 white_point*=(double) image->columns*image->rows/100.0;
2480 }
2481 if ((flags & SigmaValue) == 0)
2482 white_point=(double) QuantumRange-black_point;
2483 if ((flags & AspectValue ) == 0)
2484 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2485 gamma);
2486 else
cristy308b4e62009-09-21 14:40:44 +00002487 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002488 return(status);
2489}
2490
2491/*
2492%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2493% %
2494% %
2495% %
cristy308b4e62009-09-21 14:40:44 +00002496% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002497% %
2498% %
2499% %
2500%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2501%
cristy308b4e62009-09-21 14:40:44 +00002502% LevelizeImage() applies the normal level operation to the image, spreading
2503% out the values between the black and white points over the entire range of
2504% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002505%
2506% It is typically used to improve image contrast, or to provide a controlled
2507% linear threshold for the image. If the black and white points are set to
2508% the minimum and maximum values found in the image, the image can be
2509% normalized. or by swapping black and white values, negate the image.
2510%
cristy308b4e62009-09-21 14:40:44 +00002511% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002512%
cristy308b4e62009-09-21 14:40:44 +00002513% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2514% const double white_point,const double gamma)
2515% MagickBooleanType LevelizeImageChannel(Image *image,
2516% const ChannelType channel,const double black_point,
2517% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002518%
2519% A description of each parameter follows:
2520%
2521% o image: the image.
2522%
2523% o channel: the channel.
2524%
2525% o black_point: The level which is to be mapped to zero (black)
2526%
2527% o white_point: The level which is to be mapped to QuantiumRange (white)
2528%
2529% o gamma: adjust gamma by this factor before mapping values.
2530% use 1.0 for purely linear stretching of image color values
2531%
2532*/
cristy308b4e62009-09-21 14:40:44 +00002533
2534MagickExport MagickBooleanType LevelizeImage(Image *image,
2535 const double black_point,const double white_point,const double gamma)
2536{
2537 MagickBooleanType
2538 status;
2539
2540 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2541 gamma);
2542 return(status);
2543}
2544
cristy3ed852e2009-09-05 21:47:34 +00002545MagickExport MagickBooleanType LevelImageChannel(Image *image,
2546 const ChannelType channel,const double black_point,const double white_point,
2547 const double gamma)
2548{
2549#define LevelImageTag "Level/Image"
cristyce70c172010-01-07 17:15:30 +00002550#define LevelValue(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristy3ed852e2009-09-05 21:47:34 +00002551 pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
2552
cristyc4c8d132010-01-07 01:58:38 +00002553 CacheView
2554 *image_view;
2555
cristy3ed852e2009-09-05 21:47:34 +00002556 ExceptionInfo
2557 *exception;
2558
2559 long
2560 progress,
2561 y;
2562
2563 MagickBooleanType
2564 status;
2565
2566 register long
2567 i;
2568
cristy3ed852e2009-09-05 21:47:34 +00002569 /*
2570 Allocate and initialize levels map.
2571 */
2572 assert(image != (Image *) NULL);
2573 assert(image->signature == MagickSignature);
2574 if (image->debug != MagickFalse)
2575 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2576 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002577#if defined(MAGICKCORE_OPENMP_SUPPORT)
2578 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002579#endif
2580 for (i=0; i < (long) image->colors; i++)
2581 {
2582 /*
2583 Level colormap.
2584 */
2585 if ((channel & RedChannel) != 0)
2586 image->colormap[i].red=LevelValue(image->colormap[i].red);
2587 if ((channel & GreenChannel) != 0)
2588 image->colormap[i].green=LevelValue(image->colormap[i].green);
2589 if ((channel & BlueChannel) != 0)
2590 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2591 if ((channel & OpacityChannel) != 0)
2592 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2593 }
2594 /*
2595 Level image.
2596 */
2597 status=MagickTrue;
2598 progress=0;
2599 exception=(&image->exception);
2600 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002601#if defined(MAGICKCORE_OPENMP_SUPPORT)
2602 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002603#endif
2604 for (y=0; y < (long) image->rows; y++)
2605 {
2606 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002607 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002608
2609 register long
2610 x;
2611
2612 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002613 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002614
2615 if (status == MagickFalse)
2616 continue;
2617 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2618 if (q == (PixelPacket *) NULL)
2619 {
2620 status=MagickFalse;
2621 continue;
2622 }
2623 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2624 for (x=0; x < (long) image->columns; x++)
2625 {
2626 if ((channel & RedChannel) != 0)
2627 q->red=LevelValue(q->red);
2628 if ((channel & GreenChannel) != 0)
2629 q->green=LevelValue(q->green);
2630 if ((channel & BlueChannel) != 0)
2631 q->blue=LevelValue(q->blue);
2632 if (((channel & OpacityChannel) != 0) &&
2633 (image->matte == MagickTrue))
2634 q->opacity=LevelValue(q->opacity);
2635 if (((channel & IndexChannel) != 0) &&
2636 (image->colorspace == CMYKColorspace))
2637 indexes[x]=LevelValue(indexes[x]);
2638 q++;
2639 }
2640 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2641 status=MagickFalse;
2642 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2643 {
2644 MagickBooleanType
2645 proceed;
2646
cristyb5d5f722009-11-04 03:03:49 +00002647#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002648 #pragma omp critical (MagickCore_LevelImageChannel)
2649#endif
2650 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2651 if (proceed == MagickFalse)
2652 status=MagickFalse;
2653 }
2654 }
2655 image_view=DestroyCacheView(image_view);
2656 return(status);
2657}
2658
2659/*
2660%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2661% %
2662% %
2663% %
2664% L e v e l i z e I m a g e C h a n n e l %
2665% %
2666% %
2667% %
2668%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2669%
2670% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2671% the specific channels specified. It compresses the full range of color
2672% values, so that they lie between the given black and white points. Gamma is
2673% applied before the values are mapped.
2674%
2675% LevelizeImageChannel() can be called with by using a +level command line
2676% API option, or using a '!' on a -level or LevelImage() geometry string.
2677%
2678% It can be used for example de-contrast a greyscale image to the exact
2679% levels specified. Or by using specific levels for each channel of an image
2680% you can convert a gray-scale image to any linear color gradient, according
2681% to those levels.
2682%
2683% The format of the LevelizeImageChannel method is:
2684%
2685% MagickBooleanType LevelizeImageChannel(Image *image,
2686% const ChannelType channel,const char *levels)
2687%
2688% A description of each parameter follows:
2689%
2690% o image: the image.
2691%
2692% o channel: the channel.
2693%
2694% o black_point: The level to map zero (black) to.
2695%
2696% o white_point: The level to map QuantiumRange (white) to.
2697%
2698% o gamma: adjust gamma by this factor before mapping values.
2699%
2700*/
2701MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2702 const ChannelType channel,const double black_point,const double white_point,
2703 const double gamma)
2704{
2705#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002706#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002707 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2708 black_point))
2709
cristyc4c8d132010-01-07 01:58:38 +00002710 CacheView
2711 *image_view;
2712
cristy3ed852e2009-09-05 21:47:34 +00002713 ExceptionInfo
2714 *exception;
2715
2716 long
2717 progress,
2718 y;
2719
2720 MagickBooleanType
2721 status;
2722
2723 register long
2724 i;
2725
cristy3ed852e2009-09-05 21:47:34 +00002726 /*
2727 Allocate and initialize levels map.
2728 */
2729 assert(image != (Image *) NULL);
2730 assert(image->signature == MagickSignature);
2731 if (image->debug != MagickFalse)
2732 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2733 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002734#if defined(MAGICKCORE_OPENMP_SUPPORT)
2735 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002736#endif
2737 for (i=0; i < (long) image->colors; i++)
2738 {
2739 /*
2740 Level colormap.
2741 */
2742 if ((channel & RedChannel) != 0)
2743 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2744 if ((channel & GreenChannel) != 0)
2745 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2746 if ((channel & BlueChannel) != 0)
2747 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2748 if ((channel & OpacityChannel) != 0)
2749 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2750 }
2751 /*
2752 Level image.
2753 */
2754 status=MagickTrue;
2755 progress=0;
2756 exception=(&image->exception);
2757 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002758#if defined(MAGICKCORE_OPENMP_SUPPORT)
2759 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002760#endif
2761 for (y=0; y < (long) image->rows; y++)
2762 {
2763 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002764 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002765
2766 register long
2767 x;
2768
2769 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002770 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002771
2772 if (status == MagickFalse)
2773 continue;
2774 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2775 if (q == (PixelPacket *) NULL)
2776 {
2777 status=MagickFalse;
2778 continue;
2779 }
2780 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2781 for (x=0; x < (long) image->columns; x++)
2782 {
2783 if ((channel & RedChannel) != 0)
2784 q->red=LevelizeValue(q->red);
2785 if ((channel & GreenChannel) != 0)
2786 q->green=LevelizeValue(q->green);
2787 if ((channel & BlueChannel) != 0)
2788 q->blue=LevelizeValue(q->blue);
2789 if (((channel & OpacityChannel) != 0) &&
2790 (image->matte == MagickTrue))
2791 q->opacity=LevelizeValue(q->opacity);
2792 if (((channel & IndexChannel) != 0) &&
2793 (image->colorspace == CMYKColorspace))
2794 indexes[x]=LevelizeValue(indexes[x]);
2795 q++;
2796 }
2797 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2798 status=MagickFalse;
2799 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2800 {
2801 MagickBooleanType
2802 proceed;
2803
cristyb5d5f722009-11-04 03:03:49 +00002804#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002805 #pragma omp critical (MagickCore_LevelizeImageChannel)
2806#endif
2807 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2808 if (proceed == MagickFalse)
2809 status=MagickFalse;
2810 }
2811 }
2812 return(status);
2813}
2814
2815/*
2816%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2817% %
2818% %
2819% %
2820% L e v e l I m a g e C o l o r s %
2821% %
2822% %
2823% %
2824%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2825%
cristyee0f8d72009-09-19 00:58:29 +00002826% LevelImageColor() maps the given color to "black" and "white" values,
2827% linearly spreading out the colors, and level values on a channel by channel
2828% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002829% different level ranges for each of the color channels seperatally.
2830%
2831% If the boolean 'invert' is set true the image values will modifyed in the
2832% reverse direction. That is any existing "black" and "white" colors in the
2833% image will become the color values given, with all other values compressed
2834% appropriatally. This effectivally maps a greyscale gradient into the given
2835% color gradient.
2836%
cristy308b4e62009-09-21 14:40:44 +00002837% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002838%
cristy308b4e62009-09-21 14:40:44 +00002839% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002840% const MagickPixelPacket *black_color,
2841% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002842% MagickBooleanType LevelColorsImageChannel(Image *image,
2843% const ChannelType channel,const MagickPixelPacket *black_color,
2844% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002845%
2846% A description of each parameter follows:
2847%
2848% o image: the image.
2849%
2850% o channel: the channel.
2851%
2852% o black_color: The color to map black to/from
2853%
2854% o white_point: The color to map white to/from
2855%
2856% o invert: if true map the colors (levelize), rather than from (level)
2857%
2858*/
cristy308b4e62009-09-21 14:40:44 +00002859
2860MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002861 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2862 const MagickBooleanType invert)
2863{
cristy308b4e62009-09-21 14:40:44 +00002864 MagickBooleanType
2865 status;
cristy3ed852e2009-09-05 21:47:34 +00002866
cristy308b4e62009-09-21 14:40:44 +00002867 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2868 invert);
2869 return(status);
2870}
2871
2872MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2873 const ChannelType channel,const MagickPixelPacket *black_color,
2874 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2875{
cristy3ed852e2009-09-05 21:47:34 +00002876 MagickStatusType
2877 status;
2878
2879 /*
2880 Allocate and initialize levels map.
2881 */
2882 assert(image != (Image *) NULL);
2883 assert(image->signature == MagickSignature);
2884 if (image->debug != MagickFalse)
2885 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2886 status=MagickFalse;
2887 if (invert == MagickFalse)
2888 {
2889 if ((channel & RedChannel) != 0)
2890 status|=LevelImageChannel(image,RedChannel,
2891 black_color->red,white_color->red,(double) 1.0);
2892 if ((channel & GreenChannel) != 0)
2893 status|=LevelImageChannel(image,GreenChannel,
2894 black_color->green,white_color->green,(double) 1.0);
2895 if ((channel & BlueChannel) != 0)
2896 status|=LevelImageChannel(image,BlueChannel,
2897 black_color->blue,white_color->blue,(double) 1.0);
2898 if (((channel & OpacityChannel) != 0) &&
2899 (image->matte == MagickTrue))
2900 status|=LevelImageChannel(image,OpacityChannel,
2901 black_color->opacity,white_color->opacity,(double) 1.0);
2902 if (((channel & IndexChannel) != 0) &&
2903 (image->colorspace == CMYKColorspace))
2904 status|=LevelImageChannel(image,IndexChannel,
2905 black_color->index,white_color->index,(double) 1.0);
2906 }
2907 else
2908 {
2909 if ((channel & RedChannel) != 0)
2910 status|=LevelizeImageChannel(image,RedChannel,
2911 black_color->red,white_color->red,(double) 1.0);
2912 if ((channel & GreenChannel) != 0)
2913 status|=LevelizeImageChannel(image,GreenChannel,
2914 black_color->green,white_color->green,(double) 1.0);
2915 if ((channel & BlueChannel) != 0)
2916 status|=LevelizeImageChannel(image,BlueChannel,
2917 black_color->blue,white_color->blue,(double) 1.0);
2918 if (((channel & OpacityChannel) != 0) &&
2919 (image->matte == MagickTrue))
2920 status|=LevelizeImageChannel(image,OpacityChannel,
2921 black_color->opacity,white_color->opacity,(double) 1.0);
2922 if (((channel & IndexChannel) != 0) &&
2923 (image->colorspace == CMYKColorspace))
2924 status|=LevelizeImageChannel(image,IndexChannel,
2925 black_color->index,white_color->index,(double) 1.0);
2926 }
2927 return(status == 0 ? MagickFalse : MagickTrue);
2928}
2929
2930/*
2931%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2932% %
2933% %
2934% %
2935% L i n e a r S t r e t c h I m a g e %
2936% %
2937% %
2938% %
2939%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2940%
2941% The LinearStretchImage() discards any pixels below the black point and
2942% above the white point and levels the remaining pixels.
2943%
2944% The format of the LinearStretchImage method is:
2945%
2946% MagickBooleanType LinearStretchImage(Image *image,
2947% const double black_point,const double white_point)
2948%
2949% A description of each parameter follows:
2950%
2951% o image: the image.
2952%
2953% o black_point: the black point.
2954%
2955% o white_point: the white point.
2956%
2957*/
2958MagickExport MagickBooleanType LinearStretchImage(Image *image,
2959 const double black_point,const double white_point)
2960{
2961#define LinearStretchImageTag "LinearStretch/Image"
2962
2963 ExceptionInfo
2964 *exception;
2965
2966 long
2967 black,
2968 white,
2969 y;
2970
2971 MagickBooleanType
2972 status;
2973
2974 MagickRealType
2975 *histogram,
2976 intensity;
2977
2978 MagickSizeType
2979 number_pixels;
2980
2981 /*
2982 Allocate histogram and linear map.
2983 */
2984 assert(image != (Image *) NULL);
2985 assert(image->signature == MagickSignature);
2986 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2987 sizeof(*histogram));
2988 if (histogram == (MagickRealType *) NULL)
2989 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2990 image->filename);
2991 /*
2992 Form histogram.
2993 */
2994 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2995 exception=(&image->exception);
2996 for (y=0; y < (long) image->rows; y++)
2997 {
2998 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002999 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003000
3001 register long
3002 x;
3003
3004 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3005 if (p == (const PixelPacket *) NULL)
3006 break;
3007 for (x=(long) image->columns-1; x >= 0; x--)
3008 {
3009 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3010 p++;
3011 }
3012 }
3013 /*
3014 Find the histogram boundaries by locating the black and white point levels.
3015 */
3016 number_pixels=(MagickSizeType) image->columns*image->rows;
3017 intensity=0.0;
3018 for (black=0; black < (long) MaxMap; black++)
3019 {
3020 intensity+=histogram[black];
3021 if (intensity >= black_point)
3022 break;
3023 }
3024 intensity=0.0;
3025 for (white=(long) MaxMap; white != 0; white--)
3026 {
3027 intensity+=histogram[white];
3028 if (intensity >= white_point)
3029 break;
3030 }
3031 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3032 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3033 1.0);
3034 return(status);
3035}
3036
3037/*
3038%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3039% %
3040% %
3041% %
3042% M o d u l a t e I m a g e %
3043% %
3044% %
3045% %
3046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3047%
3048% ModulateImage() lets you control the brightness, saturation, and hue
3049% of an image. Modulate represents the brightness, saturation, and hue
3050% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3051% modulation is lightness, saturation, and hue. And if the colorspace is
3052% HWB, use blackness, whiteness, and hue.
3053%
3054% The format of the ModulateImage method is:
3055%
3056% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3057%
3058% A description of each parameter follows:
3059%
3060% o image: the image.
3061%
3062% o modulate: Define the percent change in brightness, saturation, and
3063% hue.
3064%
3065*/
3066
3067static void ModulateHSB(const double percent_hue,
3068 const double percent_saturation,const double percent_brightness,
3069 Quantum *red,Quantum *green,Quantum *blue)
3070{
3071 double
3072 brightness,
3073 hue,
3074 saturation;
3075
3076 /*
3077 Increase or decrease color brightness, saturation, or hue.
3078 */
3079 assert(red != (Quantum *) NULL);
3080 assert(green != (Quantum *) NULL);
3081 assert(blue != (Quantum *) NULL);
3082 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3083 hue+=0.5*(0.01*percent_hue-1.0);
3084 while (hue < 0.0)
3085 hue+=1.0;
3086 while (hue > 1.0)
3087 hue-=1.0;
3088 saturation*=0.01*percent_saturation;
3089 brightness*=0.01*percent_brightness;
3090 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3091}
3092
3093static void ModulateHSL(const double percent_hue,
3094 const double percent_saturation,const double percent_lightness,
3095 Quantum *red,Quantum *green,Quantum *blue)
3096{
3097 double
3098 hue,
3099 lightness,
3100 saturation;
3101
3102 /*
3103 Increase or decrease color lightness, saturation, or hue.
3104 */
3105 assert(red != (Quantum *) NULL);
3106 assert(green != (Quantum *) NULL);
3107 assert(blue != (Quantum *) NULL);
3108 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3109 hue+=0.5*(0.01*percent_hue-1.0);
3110 while (hue < 0.0)
3111 hue+=1.0;
3112 while (hue > 1.0)
3113 hue-=1.0;
3114 saturation*=0.01*percent_saturation;
3115 lightness*=0.01*percent_lightness;
3116 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3117}
3118
3119static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3120{
3121 double
3122 blackness,
3123 hue,
3124 whiteness;
3125
3126 /*
3127 Increase or decrease color blackness, whiteness, or hue.
3128 */
3129 assert(red != (Quantum *) NULL);
3130 assert(green != (Quantum *) NULL);
3131 assert(blue != (Quantum *) NULL);
3132 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3133 hue+=0.5*(0.01*percent_hue-1.0);
3134 while (hue < 0.0)
3135 hue+=1.0;
3136 while (hue > 1.0)
3137 hue-=1.0;
3138 blackness*=0.01*percent_blackness;
3139 whiteness*=0.01*percent_whiteness;
3140 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3141}
3142
3143MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3144{
3145#define ModulateImageTag "Modulate/Image"
3146
cristyc4c8d132010-01-07 01:58:38 +00003147 CacheView
3148 *image_view;
3149
cristy3ed852e2009-09-05 21:47:34 +00003150 ColorspaceType
3151 colorspace;
3152
3153 const char
3154 *artifact;
3155
3156 double
3157 percent_brightness,
3158 percent_hue,
3159 percent_saturation;
3160
3161 ExceptionInfo
3162 *exception;
3163
3164 GeometryInfo
3165 geometry_info;
3166
3167 long
3168 progress,
3169 y;
3170
3171 MagickBooleanType
3172 status;
3173
3174 MagickStatusType
3175 flags;
3176
3177 register long
3178 i;
3179
cristy3ed852e2009-09-05 21:47:34 +00003180 /*
cristy2b726bd2010-01-11 01:05:39 +00003181 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003182 */
3183 assert(image != (Image *) NULL);
3184 assert(image->signature == MagickSignature);
3185 if (image->debug != MagickFalse)
3186 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3187 if (modulate == (char *) NULL)
3188 return(MagickFalse);
3189 flags=ParseGeometry(modulate,&geometry_info);
3190 percent_brightness=geometry_info.rho;
3191 percent_saturation=geometry_info.sigma;
3192 if ((flags & SigmaValue) == 0)
3193 percent_saturation=100.0;
3194 percent_hue=geometry_info.xi;
3195 if ((flags & XiValue) == 0)
3196 percent_hue=100.0;
3197 colorspace=UndefinedColorspace;
3198 artifact=GetImageArtifact(image,"modulate:colorspace");
3199 if (artifact != (const char *) NULL)
3200 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3201 MagickFalse,artifact);
3202 if (image->storage_class == PseudoClass)
3203 {
3204 /*
3205 Modulate colormap.
3206 */
cristyb5d5f722009-11-04 03:03:49 +00003207#if defined(MAGICKCORE_OPENMP_SUPPORT)
3208 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003209#endif
3210 for (i=0; i < (long) image->colors; i++)
3211 switch (colorspace)
3212 {
3213 case HSBColorspace:
3214 {
3215 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3216 &image->colormap[i].red,&image->colormap[i].green,
3217 &image->colormap[i].blue);
3218 break;
3219 }
3220 case HSLColorspace:
3221 default:
3222 {
3223 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3224 &image->colormap[i].red,&image->colormap[i].green,
3225 &image->colormap[i].blue);
3226 break;
3227 }
3228 case HWBColorspace:
3229 {
3230 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3231 &image->colormap[i].red,&image->colormap[i].green,
3232 &image->colormap[i].blue);
3233 break;
3234 }
3235 }
3236 }
3237 /*
3238 Modulate image.
3239 */
3240 status=MagickTrue;
3241 progress=0;
3242 exception=(&image->exception);
3243 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003244#if defined(MAGICKCORE_OPENMP_SUPPORT)
3245 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003246#endif
3247 for (y=0; y < (long) image->rows; y++)
3248 {
3249 register long
3250 x;
3251
3252 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003253 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003254
3255 if (status == MagickFalse)
3256 continue;
3257 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3258 if (q == (PixelPacket *) NULL)
3259 {
3260 status=MagickFalse;
3261 continue;
3262 }
3263 for (x=0; x < (long) image->columns; x++)
3264 {
3265 switch (colorspace)
3266 {
3267 case HSBColorspace:
3268 {
3269 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3270 &q->red,&q->green,&q->blue);
3271 break;
3272 }
3273 case HSLColorspace:
3274 default:
3275 {
3276 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3277 &q->red,&q->green,&q->blue);
3278 break;
3279 }
3280 case HWBColorspace:
3281 {
3282 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3283 &q->red,&q->green,&q->blue);
3284 break;
3285 }
3286 }
3287 q++;
3288 }
3289 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3290 status=MagickFalse;
3291 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3292 {
3293 MagickBooleanType
3294 proceed;
3295
cristyb5d5f722009-11-04 03:03:49 +00003296#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003297 #pragma omp critical (MagickCore_ModulateImage)
3298#endif
3299 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3300 if (proceed == MagickFalse)
3301 status=MagickFalse;
3302 }
3303 }
3304 image_view=DestroyCacheView(image_view);
3305 return(status);
3306}
3307
3308/*
3309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3310% %
3311% %
3312% %
3313% N e g a t e I m a g e %
3314% %
3315% %
3316% %
3317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3318%
3319% NegateImage() negates the colors in the reference image. The grayscale
3320% option means that only grayscale values within the image are negated.
3321%
3322% The format of the NegateImageChannel method is:
3323%
3324% MagickBooleanType NegateImage(Image *image,
3325% const MagickBooleanType grayscale)
3326% MagickBooleanType NegateImageChannel(Image *image,
3327% const ChannelType channel,const MagickBooleanType grayscale)
3328%
3329% A description of each parameter follows:
3330%
3331% o image: the image.
3332%
3333% o channel: the channel.
3334%
3335% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3336%
3337*/
3338
3339MagickExport MagickBooleanType NegateImage(Image *image,
3340 const MagickBooleanType grayscale)
3341{
3342 MagickBooleanType
3343 status;
3344
3345 status=NegateImageChannel(image,DefaultChannels,grayscale);
3346 return(status);
3347}
3348
3349MagickExport MagickBooleanType NegateImageChannel(Image *image,
3350 const ChannelType channel,const MagickBooleanType grayscale)
3351{
3352#define NegateImageTag "Negate/Image"
3353
cristyc4c8d132010-01-07 01:58:38 +00003354 CacheView
3355 *image_view;
3356
cristy3ed852e2009-09-05 21:47:34 +00003357 ExceptionInfo
3358 *exception;
3359
3360 long
3361 progress,
3362 y;
3363
3364 MagickBooleanType
3365 status;
3366
3367 register long
3368 i;
3369
cristy3ed852e2009-09-05 21:47:34 +00003370 assert(image != (Image *) NULL);
3371 assert(image->signature == MagickSignature);
3372 if (image->debug != MagickFalse)
3373 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3374 if (image->storage_class == PseudoClass)
3375 {
3376 /*
3377 Negate colormap.
3378 */
cristyb5d5f722009-11-04 03:03:49 +00003379#if defined(MAGICKCORE_OPENMP_SUPPORT)
3380 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003381#endif
3382 for (i=0; i < (long) image->colors; i++)
3383 {
3384 if (grayscale != MagickFalse)
3385 if ((image->colormap[i].red != image->colormap[i].green) ||
3386 (image->colormap[i].green != image->colormap[i].blue))
3387 continue;
3388 if ((channel & RedChannel) != 0)
3389 image->colormap[i].red=(Quantum) QuantumRange-
3390 image->colormap[i].red;
3391 if ((channel & GreenChannel) != 0)
3392 image->colormap[i].green=(Quantum) QuantumRange-
3393 image->colormap[i].green;
3394 if ((channel & BlueChannel) != 0)
3395 image->colormap[i].blue=(Quantum) QuantumRange-
3396 image->colormap[i].blue;
3397 }
3398 }
3399 /*
3400 Negate image.
3401 */
3402 status=MagickTrue;
3403 progress=0;
3404 exception=(&image->exception);
3405 image_view=AcquireCacheView(image);
3406 if (grayscale != MagickFalse)
3407 {
cristyb5d5f722009-11-04 03:03:49 +00003408#if defined(MAGICKCORE_OPENMP_SUPPORT)
3409 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003410#endif
3411 for (y=0; y < (long) image->rows; y++)
3412 {
3413 MagickBooleanType
3414 sync;
3415
3416 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003417 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003418
3419 register long
3420 x;
3421
3422 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003423 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003424
3425 if (status == MagickFalse)
3426 continue;
3427 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3428 exception);
3429 if (q == (PixelPacket *) NULL)
3430 {
3431 status=MagickFalse;
3432 continue;
3433 }
3434 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3435 for (x=0; x < (long) image->columns; x++)
3436 {
3437 if ((q->red != q->green) || (q->green != q->blue))
3438 {
3439 q++;
3440 continue;
3441 }
3442 if ((channel & RedChannel) != 0)
3443 q->red=(Quantum) QuantumRange-q->red;
3444 if ((channel & GreenChannel) != 0)
3445 q->green=(Quantum) QuantumRange-q->green;
3446 if ((channel & BlueChannel) != 0)
3447 q->blue=(Quantum) QuantumRange-q->blue;
3448 if ((channel & OpacityChannel) != 0)
3449 q->opacity=(Quantum) QuantumRange-q->opacity;
3450 if (((channel & IndexChannel) != 0) &&
3451 (image->colorspace == CMYKColorspace))
3452 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3453 q++;
3454 }
3455 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3456 if (sync == MagickFalse)
3457 status=MagickFalse;
3458 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3459 {
3460 MagickBooleanType
3461 proceed;
3462
cristyb5d5f722009-11-04 03:03:49 +00003463#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003464 #pragma omp critical (MagickCore_NegateImageChannel)
3465#endif
3466 proceed=SetImageProgress(image,NegateImageTag,progress++,
3467 image->rows);
3468 if (proceed == MagickFalse)
3469 status=MagickFalse;
3470 }
3471 }
3472 image_view=DestroyCacheView(image_view);
3473 return(MagickTrue);
3474 }
3475 /*
3476 Negate image.
3477 */
cristyb5d5f722009-11-04 03:03:49 +00003478#if defined(MAGICKCORE_OPENMP_SUPPORT)
3479 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003480#endif
3481 for (y=0; y < (long) image->rows; y++)
3482 {
3483 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003484 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003485
3486 register long
3487 x;
3488
3489 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003490 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003491
3492 if (status == MagickFalse)
3493 continue;
3494 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3495 if (q == (PixelPacket *) NULL)
3496 {
3497 status=MagickFalse;
3498 continue;
3499 }
3500 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3501 for (x=0; x < (long) image->columns; x++)
3502 {
3503 if ((channel & RedChannel) != 0)
3504 q->red=(Quantum) QuantumRange-q->red;
3505 if ((channel & GreenChannel) != 0)
3506 q->green=(Quantum) QuantumRange-q->green;
3507 if ((channel & BlueChannel) != 0)
3508 q->blue=(Quantum) QuantumRange-q->blue;
3509 if ((channel & OpacityChannel) != 0)
3510 q->opacity=(Quantum) QuantumRange-q->opacity;
3511 if (((channel & IndexChannel) != 0) &&
3512 (image->colorspace == CMYKColorspace))
3513 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3514 q++;
3515 }
3516 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3517 status=MagickFalse;
3518 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3519 {
3520 MagickBooleanType
3521 proceed;
3522
cristyb5d5f722009-11-04 03:03:49 +00003523#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003524 #pragma omp critical (MagickCore_NegateImageChannel)
3525#endif
3526 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3527 if (proceed == MagickFalse)
3528 status=MagickFalse;
3529 }
3530 }
3531 image_view=DestroyCacheView(image_view);
3532 return(status);
3533}
3534
3535/*
3536%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3537% %
3538% %
3539% %
3540% N o r m a l i z e I m a g e %
3541% %
3542% %
3543% %
3544%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3545%
3546% The NormalizeImage() method enhances the contrast of a color image by
3547% mapping the darkest 2 percent of all pixel to black and the brightest
3548% 1 percent to white.
3549%
3550% The format of the NormalizeImage method is:
3551%
3552% MagickBooleanType NormalizeImage(Image *image)
3553% MagickBooleanType NormalizeImageChannel(Image *image,
3554% const ChannelType channel)
3555%
3556% A description of each parameter follows:
3557%
3558% o image: the image.
3559%
3560% o channel: the channel.
3561%
3562*/
3563
3564MagickExport MagickBooleanType NormalizeImage(Image *image)
3565{
3566 MagickBooleanType
3567 status;
3568
3569 status=NormalizeImageChannel(image,DefaultChannels);
3570 return(status);
3571}
3572
3573MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3574 const ChannelType channel)
3575{
3576 double
3577 black_point,
3578 white_point;
3579
3580 black_point=(double) image->columns*image->rows*0.02;
3581 white_point=(double) image->columns*image->rows*0.99;
3582 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3583}
3584
3585/*
3586%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3587% %
3588% %
3589% %
3590% S i g m o i d a l C o n t r a s t I m a g e %
3591% %
3592% %
3593% %
3594%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3595%
3596% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3597% sigmoidal contrast algorithm. Increase the contrast of the image using a
3598% sigmoidal transfer function without saturating highlights or shadows.
3599% Contrast indicates how much to increase the contrast (0 is none; 3 is
3600% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3601% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3602% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3603% is reduced.
3604%
3605% The format of the SigmoidalContrastImage method is:
3606%
3607% MagickBooleanType SigmoidalContrastImage(Image *image,
3608% const MagickBooleanType sharpen,const char *levels)
3609% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3610% const ChannelType channel,const MagickBooleanType sharpen,
3611% const double contrast,const double midpoint)
3612%
3613% A description of each parameter follows:
3614%
3615% o image: the image.
3616%
3617% o channel: the channel.
3618%
3619% o sharpen: Increase or decrease image contrast.
3620%
3621% o contrast: control the "shoulder" of the contast curve.
3622%
3623% o midpoint: control the "toe" of the contast curve.
3624%
3625*/
3626
3627MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3628 const MagickBooleanType sharpen,const char *levels)
3629{
3630 GeometryInfo
3631 geometry_info;
3632
3633 MagickBooleanType
3634 status;
3635
3636 MagickStatusType
3637 flags;
3638
3639 flags=ParseGeometry(levels,&geometry_info);
3640 if ((flags & SigmaValue) == 0)
3641 geometry_info.sigma=1.0*QuantumRange/2.0;
3642 if ((flags & PercentValue) != 0)
3643 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3644 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3645 geometry_info.rho,geometry_info.sigma);
3646 return(status);
3647}
3648
3649MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3650 const ChannelType channel,const MagickBooleanType sharpen,
3651 const double contrast,const double midpoint)
3652{
3653#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3654
cristyc4c8d132010-01-07 01:58:38 +00003655 CacheView
3656 *image_view;
3657
cristy3ed852e2009-09-05 21:47:34 +00003658 ExceptionInfo
3659 *exception;
3660
3661 long
3662 progress,
3663 y;
3664
3665 MagickBooleanType
3666 status;
3667
3668 MagickRealType
3669 *sigmoidal_map;
3670
3671 register long
3672 i;
3673
cristy3ed852e2009-09-05 21:47:34 +00003674 /*
3675 Allocate and initialize sigmoidal maps.
3676 */
3677 assert(image != (Image *) NULL);
3678 assert(image->signature == MagickSignature);
3679 if (image->debug != MagickFalse)
3680 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3681 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3682 sizeof(*sigmoidal_map));
3683 if (sigmoidal_map == (MagickRealType *) NULL)
3684 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3685 image->filename);
3686 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003687#if defined(MAGICKCORE_OPENMP_SUPPORT)
3688 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003689#endif
3690 for (i=0; i <= (long) MaxMap; i++)
3691 {
3692 if (sharpen != MagickFalse)
3693 {
3694 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3695 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3696 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3697 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3698 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3699 (double) QuantumRange)))))+0.5));
3700 continue;
3701 }
3702 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3703 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3704 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3705 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3706 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3707 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3708 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3709 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3710 (double) QuantumRange*contrast))))))/contrast)));
3711 }
3712 if (image->storage_class == PseudoClass)
3713 {
3714 /*
3715 Sigmoidal-contrast enhance colormap.
3716 */
cristyb5d5f722009-11-04 03:03:49 +00003717#if defined(MAGICKCORE_OPENMP_SUPPORT)
3718 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003719#endif
3720 for (i=0; i < (long) image->colors; i++)
3721 {
3722 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003723 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003724 ScaleQuantumToMap(image->colormap[i].red)]);
3725 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003726 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003727 ScaleQuantumToMap(image->colormap[i].green)]);
3728 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003729 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003730 ScaleQuantumToMap(image->colormap[i].blue)]);
3731 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003732 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003733 ScaleQuantumToMap(image->colormap[i].opacity)]);
3734 }
3735 }
3736 /*
3737 Sigmoidal-contrast enhance image.
3738 */
3739 status=MagickTrue;
3740 progress=0;
3741 exception=(&image->exception);
3742 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003743#if defined(MAGICKCORE_OPENMP_SUPPORT)
3744 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003745#endif
3746 for (y=0; y < (long) image->rows; y++)
3747 {
3748 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003749 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003750
3751 register long
3752 x;
3753
3754 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003755 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003756
3757 if (status == MagickFalse)
3758 continue;
3759 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3760 if (q == (PixelPacket *) NULL)
3761 {
3762 status=MagickFalse;
3763 continue;
3764 }
3765 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3766 for (x=0; x < (long) image->columns; x++)
3767 {
3768 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003769 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003770 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003771 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003772 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003773 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003774 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003775 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003776 if (((channel & IndexChannel) != 0) &&
3777 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003778 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003779 ScaleQuantumToMap(indexes[x])]);
3780 q++;
3781 }
3782 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3783 status=MagickFalse;
3784 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3785 {
3786 MagickBooleanType
3787 proceed;
3788
cristyb5d5f722009-11-04 03:03:49 +00003789#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003790 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3791#endif
3792 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3793 image->rows);
3794 if (proceed == MagickFalse)
3795 status=MagickFalse;
3796 }
3797 }
3798 image_view=DestroyCacheView(image_view);
3799 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3800 return(status);
3801}