blob: 0ca521e100f8be6c55f97ad8cc26e0d353fd983a [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 GammaImageChannel(image, channel, gamma);
129 return LevelImageChannel(image, channel,
130 0.0, (double)QuantumRange, gamma);
131 }
132
133 /*
134 auto-gamma each channel separateally
135 */
136 status = MagickTrue;
137 if ((channel & RedChannel) != 0)
138 {
cristy2b726bd2010-01-11 01:05:39 +0000139 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
140 &image->exception);
141 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000142 //status = status && GammaImageChannel(image, RedChannel, gamma);
143 status = status && LevelImageChannel(image, RedChannel,
144 0.0, (double)QuantumRange, gamma);
145 }
146 if ((channel & GreenChannel) != 0)
147 {
cristy2b726bd2010-01-11 01:05:39 +0000148 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
149 &image->exception);
150 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000151 //status = status && GammaImageChannel(image, GreenChannel, gamma);
152 status = status && LevelImageChannel(image, GreenChannel,
153 0.0, (double)QuantumRange, gamma);
154 }
155 if ((channel & BlueChannel) != 0)
156 {
cristy2b726bd2010-01-11 01:05:39 +0000157 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
158 &image->exception);
159 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000160 //status = status && GammaImageChannel(image, BlueChannel, gamma);
161 status = status && LevelImageChannel(image, BlueChannel,
162 0.0, (double)QuantumRange, gamma);
163 }
164 if (((channel & OpacityChannel) != 0) &&
165 (image->matte == MagickTrue))
166 {
cristy2b726bd2010-01-11 01:05:39 +0000167 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
168 &image->exception);
169 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000170 //status = status && GammaImageChannel(image, OpacityChannel, gamma);
171 status = status && LevelImageChannel(image, OpacityChannel,
172 0.0, (double)QuantumRange, gamma);
173 }
174 if (((channel & IndexChannel) != 0) &&
175 (image->colorspace == CMYKColorspace))
176 {
cristy2b726bd2010-01-11 01:05:39 +0000177 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
178 &image->exception);
179 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000180 //status = status && GammaImageChannel(image, IndexChannel, gamma);
181 status = status && LevelImageChannel(image, IndexChannel,
182 0.0, (double)QuantumRange, gamma);
183 }
184 return(status != 0 ? MagickTrue : MagickFalse);
185}
186
187/*
188%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
189% %
190% %
191% %
192% A u t o L e v e l I m a g e %
193% %
194% %
195% %
196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
197%
198% AutoLevelImage() adjusts the levels of a particular image channel by
199% scaling the minimum and maximum values to the full quantum range.
200%
201% The format of the LevelImage method is:
202%
203% MagickBooleanType AutoLevelImage(Image *image)
204% MagickBooleanType AutoLevelImageChannel(Image *image,
205% const ChannelType channel)
206%
207% A description of each parameter follows:
208%
209% o image: The image to auto-level
210%
211% o channel: The channels to auto-level. If the special 'SyncChannels'
212% flag is set the min/max/mean value of all given channels is used for
213% all given channels, to all channels in the same way.
214%
215*/
216
217MagickExport MagickBooleanType AutoLevelImage(Image *image)
218{
219 return(AutoLevelImageChannel(image,DefaultChannels));
220}
221
222MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
223 const ChannelType channel)
224{
225 /*
226 This is simply a convenience function around a Min/Max Histogram Stretch
227 */
228 return MinMaxStretchImage(image, channel, 0.0, 0.0);
229}
230
231/*
232%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
233% %
234% %
235% %
cristya28d6b82010-01-11 20:03:47 +0000236% B r i g h t n e s s C o n t r a s t I m a g e %
237% %
238% %
239% %
240%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
241%
242% Use BrightnessContrastImage() to change the brightness and/or contrast of
243% an image. It converts the brightness and contrast parameters into slope
244% and intercept and calls a polynomical function to apply to the image.
245%
246% The format of the BrightnessContrastImage method is:
247%
248% MagickBooleanType BrightnessContrastImage(Image *image,
249% const double brightness,const double contrast)
250% MagickBooleanType BrightnessContrastImageChannel(Image *image,
251% const ChannelType channel,const double brightness,
252% const double contrast)
253%
254% A description of each parameter follows:
255%
256% o image: the image.
257%
258% o channel: the channel.
259%
260% o brightness: the brightness percent (-100 .. 100).
261%
262% o contrast: the contrast percent (-100 .. 100).
263%
264*/
265
266MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
267 const double brightness,const double contrast)
268{
269 MagickBooleanType
270 status;
271
272 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
273 contrast);
274 return(status);
275}
276
277MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
278 const ChannelType channel,const double brightness,const double contrast)
279{
280#define BrightnessContastImageTag "BrightnessContast/Image"
281
282 double
283 alpha,
284 intercept,
285 coefficients[2],
286 slope;
287
288 MagickBooleanType
289 status;
290
291 /*
292 Compute slope and intercept.
293 */
294 assert(image != (Image *) NULL);
295 assert(image->signature == MagickSignature);
296 if (image->debug != MagickFalse)
297 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
298 alpha=contrast;
299 if ((100-contrast) <= 0.1)
cristyae2532e2010-01-12 16:40:09 +0000300 alpha=99.999999;
301 slope=MagickPI*(((alpha*alpha)/20000.0)+(3.0*alpha/200.0))/4.0;
302 slope=sin(slope)/cos(slope)+1.0;
cristya28d6b82010-01-11 20:03:47 +0000303 if (slope < 0.0)
304 slope=0.0;
305 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
306 coefficients[0]=slope;
307 coefficients[1]=intercept;
308 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
309 &image->exception);
310 return(status);
311}
312
313/*
314%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
315% %
316% %
317% %
cristy3ed852e2009-09-05 21:47:34 +0000318% C o l o r D e c i s i o n L i s t I m a g e %
319% %
320% %
321% %
322%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
323%
324% ColorDecisionListImage() accepts a lightweight Color Correction Collection
325% (CCC) file which solely contains one or more color corrections and applies
326% the correction to the image. Here is a sample CCC file:
327%
328% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
329% <ColorCorrection id="cc03345">
330% <SOPNode>
331% <Slope> 0.9 1.2 0.5 </Slope>
332% <Offset> 0.4 -0.5 0.6 </Offset>
333% <Power> 1.0 0.8 1.5 </Power>
334% </SOPNode>
335% <SATNode>
336% <Saturation> 0.85 </Saturation>
337% </SATNode>
338% </ColorCorrection>
339% </ColorCorrectionCollection>
340%
341% which includes the slop, offset, and power for each of the RGB channels
342% as well as the saturation.
343%
344% The format of the ColorDecisionListImage method is:
345%
346% MagickBooleanType ColorDecisionListImage(Image *image,
347% const char *color_correction_collection)
348%
349% A description of each parameter follows:
350%
351% o image: the image.
352%
353% o color_correction_collection: the color correction collection in XML.
354%
355*/
356MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
357 const char *color_correction_collection)
358{
359#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
360
361 typedef struct _Correction
362 {
363 double
364 slope,
365 offset,
366 power;
367 } Correction;
368
369 typedef struct _ColorCorrection
370 {
371 Correction
372 red,
373 green,
374 blue;
375
376 double
377 saturation;
378 } ColorCorrection;
379
cristyc4c8d132010-01-07 01:58:38 +0000380 CacheView
381 *image_view;
382
cristy3ed852e2009-09-05 21:47:34 +0000383 char
384 token[MaxTextExtent];
385
386 ColorCorrection
387 color_correction;
388
389 const char
390 *content,
391 *p;
392
393 ExceptionInfo
394 *exception;
395
396 long
397 progress,
398 y;
399
400 MagickBooleanType
401 status;
402
403 PixelPacket
404 *cdl_map;
405
406 register long
407 i;
408
409 XMLTreeInfo
410 *cc,
411 *ccc,
412 *sat,
413 *sop;
414
cristy3ed852e2009-09-05 21:47:34 +0000415 /*
416 Allocate and initialize cdl maps.
417 */
418 assert(image != (Image *) NULL);
419 assert(image->signature == MagickSignature);
420 if (image->debug != MagickFalse)
421 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
422 if (color_correction_collection == (const char *) NULL)
423 return(MagickFalse);
424 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
425 if (ccc == (XMLTreeInfo *) NULL)
426 return(MagickFalse);
427 cc=GetXMLTreeChild(ccc,"ColorCorrection");
428 if (cc == (XMLTreeInfo *) NULL)
429 {
430 ccc=DestroyXMLTree(ccc);
431 return(MagickFalse);
432 }
433 color_correction.red.slope=1.0;
434 color_correction.red.offset=0.0;
435 color_correction.red.power=1.0;
436 color_correction.green.slope=1.0;
437 color_correction.green.offset=0.0;
438 color_correction.green.power=1.0;
439 color_correction.blue.slope=1.0;
440 color_correction.blue.offset=0.0;
441 color_correction.blue.power=1.0;
442 color_correction.saturation=0.0;
443 sop=GetXMLTreeChild(cc,"SOPNode");
444 if (sop != (XMLTreeInfo *) NULL)
445 {
446 XMLTreeInfo
447 *offset,
448 *power,
449 *slope;
450
451 slope=GetXMLTreeChild(sop,"Slope");
452 if (slope != (XMLTreeInfo *) NULL)
453 {
454 content=GetXMLTreeContent(slope);
455 p=(const char *) content;
456 for (i=0; (*p != '\0') && (i < 3); i++)
457 {
458 GetMagickToken(p,&p,token);
459 if (*token == ',')
460 GetMagickToken(p,&p,token);
461 switch (i)
462 {
cristyf2f27272009-12-17 14:48:46 +0000463 case 0: color_correction.red.slope=StringToDouble(token); break;
464 case 1: color_correction.green.slope=StringToDouble(token); break;
465 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000466 }
467 }
468 }
469 offset=GetXMLTreeChild(sop,"Offset");
470 if (offset != (XMLTreeInfo *) NULL)
471 {
472 content=GetXMLTreeContent(offset);
473 p=(const char *) content;
474 for (i=0; (*p != '\0') && (i < 3); i++)
475 {
476 GetMagickToken(p,&p,token);
477 if (*token == ',')
478 GetMagickToken(p,&p,token);
479 switch (i)
480 {
cristyf2f27272009-12-17 14:48:46 +0000481 case 0: color_correction.red.offset=StringToDouble(token); break;
482 case 1: color_correction.green.offset=StringToDouble(token); break;
483 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000484 }
485 }
486 }
487 power=GetXMLTreeChild(sop,"Power");
488 if (power != (XMLTreeInfo *) NULL)
489 {
490 content=GetXMLTreeContent(power);
491 p=(const char *) content;
492 for (i=0; (*p != '\0') && (i < 3); i++)
493 {
494 GetMagickToken(p,&p,token);
495 if (*token == ',')
496 GetMagickToken(p,&p,token);
497 switch (i)
498 {
cristyf2f27272009-12-17 14:48:46 +0000499 case 0: color_correction.red.power=StringToDouble(token); break;
500 case 1: color_correction.green.power=StringToDouble(token); break;
501 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000502 }
503 }
504 }
505 }
506 sat=GetXMLTreeChild(cc,"SATNode");
507 if (sat != (XMLTreeInfo *) NULL)
508 {
509 XMLTreeInfo
510 *saturation;
511
512 saturation=GetXMLTreeChild(sat,"Saturation");
513 if (saturation != (XMLTreeInfo *) NULL)
514 {
515 content=GetXMLTreeContent(saturation);
516 p=(const char *) content;
517 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000518 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000519 }
520 }
521 ccc=DestroyXMLTree(ccc);
522 if (image->debug != MagickFalse)
523 {
524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
525 " Color Correction Collection:");
526 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000527 " color_correction.red.slope: %.15g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000528 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000529 " color_correction.red.offset: %.15g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000531 " color_correction.red.power: %.15g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000532 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000533 " color_correction.green.slope: %.15g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000534 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000535 " color_correction.green.offset: %.15g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000537 " color_correction.green.power: %.15g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000539 " color_correction.blue.slope: %.15g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000540 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000541 " color_correction.blue.offset: %.15g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000542 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000543 " color_correction.blue.power: %.15g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000544 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000545 " color_correction.saturation: %.15g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000546 }
547 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
548 if (cdl_map == (PixelPacket *) NULL)
549 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
550 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000551#if defined(MAGICKCORE_OPENMP_SUPPORT)
552 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000553#endif
554 for (i=0; i <= (long) MaxMap; i++)
555 {
cristyce70c172010-01-07 17:15:30 +0000556 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000557 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
558 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000559 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000560 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
561 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000562 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000563 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
564 color_correction.blue.offset,color_correction.blue.power)))));
565 }
566 if (image->storage_class == PseudoClass)
567 {
568 /*
569 Apply transfer function to colormap.
570 */
cristyb5d5f722009-11-04 03:03:49 +0000571#if defined(MAGICKCORE_OPENMP_SUPPORT)
572 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000573#endif
574 for (i=0; i < (long) image->colors; i++)
575 {
576 double
577 luma;
578
579 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
580 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000581 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000582 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000583 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000584 color_correction.saturation*cdl_map[ScaleQuantumToMap(
585 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000586 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000587 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
588 }
589 }
590 /*
591 Apply transfer function to image.
592 */
593 status=MagickTrue;
594 progress=0;
595 exception=(&image->exception);
596 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000597#if defined(MAGICKCORE_OPENMP_SUPPORT)
598 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000599#endif
600 for (y=0; y < (long) image->rows; y++)
601 {
602 double
603 luma;
604
605 register long
606 x;
607
608 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000609 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000610
611 if (status == MagickFalse)
612 continue;
613 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
614 if (q == (PixelPacket *) NULL)
615 {
616 status=MagickFalse;
617 continue;
618 }
619 for (x=0; x < (long) image->columns; x++)
620 {
621 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000622 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000623 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000624 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000625 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000626 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000627 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
628 q++;
629 }
630 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
631 status=MagickFalse;
632 if (image->progress_monitor != (MagickProgressMonitor) NULL)
633 {
634 MagickBooleanType
635 proceed;
636
cristyb5d5f722009-11-04 03:03:49 +0000637#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000638 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
639#endif
640 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
641 progress++,image->rows);
642 if (proceed == MagickFalse)
643 status=MagickFalse;
644 }
645 }
646 image_view=DestroyCacheView(image_view);
647 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
648 return(status);
649}
650
651/*
652%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
653% %
654% %
655% %
656% C l u t I m a g e %
657% %
658% %
659% %
660%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
661%
662% ClutImage() replaces each color value in the given image, by using it as an
663% index to lookup a replacement color value in a Color Look UP Table in the
664% form of an image. The values are extracted along a diagonal of the CLUT
665% image so either a horizontal or vertial gradient image can be used.
666%
667% Typically this is used to either re-color a gray-scale image according to a
668% color gradient in the CLUT image, or to perform a freeform histogram
669% (level) adjustment according to the (typically gray-scale) gradient in the
670% CLUT image.
671%
672% When the 'channel' mask includes the matte/alpha transparency channel but
673% one image has no such channel it is assumed that that image is a simple
674% gray-scale image that will effect the alpha channel values, either for
675% gray-scale coloring (with transparent or semi-transparent colors), or
676% a histogram adjustment of existing alpha channel values. If both images
677% have matte channels, direct and normal indexing is applied, which is rarely
678% used.
679%
680% The format of the ClutImage method is:
681%
682% MagickBooleanType ClutImage(Image *image,Image *clut_image)
683% MagickBooleanType ClutImageChannel(Image *image,
684% const ChannelType channel,Image *clut_image)
685%
686% A description of each parameter follows:
687%
688% o image: the image, which is replaced by indexed CLUT values
689%
690% o clut_image: the color lookup table image for replacement color values.
691%
692% o channel: the channel.
693%
694*/
695
696MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
697{
698 return(ClutImageChannel(image,DefaultChannels,clut_image));
699}
700
701MagickExport MagickBooleanType ClutImageChannel(Image *image,
702 const ChannelType channel,const Image *clut_image)
703{
704#define ClutImageTag "Clut/Image"
705
cristyfa112112010-01-04 17:48:07 +0000706 CacheView
707 *image_view;
708
cristy3ed852e2009-09-05 21:47:34 +0000709 ExceptionInfo
710 *exception;
711
712 long
713 adjust,
714 progress,
715 y;
716
717 MagickBooleanType
718 status;
719
720 MagickPixelPacket
721 zero;
722
723 ResampleFilter
cristyfa112112010-01-04 17:48:07 +0000724 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +0000725
726 assert(image != (Image *) NULL);
727 assert(image->signature == MagickSignature);
728 if (image->debug != MagickFalse)
729 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
730 assert(clut_image != (Image *) NULL);
731 assert(clut_image->signature == MagickSignature);
732 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
733 return(MagickFalse);
734 /*
735 Clut image.
736 */
737 status=MagickTrue;
738 progress=0;
739 GetMagickPixelPacket(clut_image,&zero);
740 adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
741 exception=(&image->exception);
742 resample_filter=AcquireResampleFilterThreadSet(clut_image,MagickTrue,
743 exception);
744 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000745#if defined(MAGICKCORE_OPENMP_SUPPORT)
746 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000747#endif
748 for (y=0; y < (long) image->rows; y++)
749 {
750 MagickPixelPacket
751 pixel;
752
753 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000754 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000755
756 register long
757 id,
758 x;
759
760 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000761 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000762
763 if (status == MagickFalse)
764 continue;
765 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
766 if (q == (PixelPacket *) NULL)
767 {
768 status=MagickFalse;
769 continue;
770 }
771 indexes=GetCacheViewAuthenticIndexQueue(image_view);
772 pixel=zero;
773 id=GetOpenMPThreadId();
774 for (x=0; x < (long) image->columns; x++)
775 {
776 /*
777 PROGRAMMERS WARNING:
778
779 Apply OpacityChannel BEFORE the color channels. Do not re-order.
780
781 The handling special case 2 (coloring gray-scale), requires access to
782 the unmodified colors of the original image to determine the index
783 value. As such alpha/matte channel handling must be performed BEFORE,
784 any of the color channels are modified.
785
786 */
787 if ((channel & OpacityChannel) != 0)
788 {
789 if (clut_image->matte == MagickFalse)
790 {
791 /*
792 A gray-scale LUT replacement for an image alpha channel.
793 */
794 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
cristy46f08202010-01-10 04:04:21 +0000795 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
796 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
cristy3ed852e2009-09-05 21:47:34 +0000797 adjust),&pixel);
798 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
799 &pixel));
800 }
801 else
802 if (image->matte == MagickFalse)
803 {
804 /*
805 A greyscale image being colored by a LUT with transparency.
806 */
807 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
808 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
809 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000810 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000811 }
812 else
813 {
814 /*
815 Direct alpha channel lookup.
816 */
817 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
818 q->opacity*(clut_image->columns-adjust),QuantumScale*
819 q->opacity* (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000820 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000821 }
822 }
823 if ((channel & RedChannel) != 0)
824 {
825 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
826 (clut_image->columns-adjust),QuantumScale*q->red*
827 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000828 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000829 }
830 if ((channel & GreenChannel) != 0)
831 {
832 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
833 (clut_image->columns-adjust),QuantumScale*q->green*
834 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000835 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000836 }
837 if ((channel & BlueChannel) != 0)
838 {
839 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
840 (clut_image->columns-adjust),QuantumScale*q->blue*
841 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000842 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000843 }
844 if (((channel & IndexChannel) != 0) &&
845 (image->colorspace == CMYKColorspace))
846 {
847 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
848 (clut_image->columns-adjust),QuantumScale*indexes[x]*
849 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000850 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000851 }
852 q++;
853 }
854 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
855 status=MagickFalse;
856 if (image->progress_monitor != (MagickProgressMonitor) NULL)
857 {
858 MagickBooleanType
859 proceed;
860
cristyb5d5f722009-11-04 03:03:49 +0000861#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000862 #pragma omp critical (MagickCore_ClutImageChannel)
863#endif
864 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
865 if (proceed == MagickFalse)
866 status=MagickFalse;
867 }
868 }
869 image_view=DestroyCacheView(image_view);
870 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
871 /*
872 Enable alpha channel if CLUT image could enable it.
873 */
874 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
875 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
876 return(status);
877}
878
879/*
880%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
881% %
882% %
883% %
884% C o n t r a s t I m a g e %
885% %
886% %
887% %
888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
889%
890% ContrastImage() enhances the intensity differences between the lighter and
891% darker elements of the image. Set sharpen to a MagickTrue to increase the
892% image contrast otherwise the contrast is reduced.
893%
894% The format of the ContrastImage method is:
895%
896% MagickBooleanType ContrastImage(Image *image,
897% const MagickBooleanType sharpen)
898%
899% A description of each parameter follows:
900%
901% o image: the image.
902%
903% o sharpen: Increase or decrease image contrast.
904%
905*/
906
907static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
908{
909 double
910 brightness,
911 hue,
912 saturation;
913
914 /*
915 Enhance contrast: dark color become darker, light color become lighter.
916 */
917 assert(red != (Quantum *) NULL);
918 assert(green != (Quantum *) NULL);
919 assert(blue != (Quantum *) NULL);
920 hue=0.0;
921 saturation=0.0;
922 brightness=0.0;
923 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
924 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
925 if (brightness > 1.0)
926 brightness=1.0;
927 else
928 if (brightness < 0.0)
929 brightness=0.0;
930 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
931}
932
933MagickExport MagickBooleanType ContrastImage(Image *image,
934 const MagickBooleanType sharpen)
935{
936#define ContrastImageTag "Contrast/Image"
937
cristyc4c8d132010-01-07 01:58:38 +0000938 CacheView
939 *image_view;
940
cristy3ed852e2009-09-05 21:47:34 +0000941 ExceptionInfo
942 *exception;
943
944 int
945 sign;
946
947 long
948 progress,
949 y;
950
951 MagickBooleanType
952 status;
953
954 register long
955 i;
956
cristy3ed852e2009-09-05 21:47:34 +0000957 assert(image != (Image *) NULL);
958 assert(image->signature == MagickSignature);
959 if (image->debug != MagickFalse)
960 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
961 sign=sharpen != MagickFalse ? 1 : -1;
962 if (image->storage_class == PseudoClass)
963 {
964 /*
965 Contrast enhance colormap.
966 */
967 for (i=0; i < (long) image->colors; i++)
968 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
969 &image->colormap[i].blue);
970 }
971 /*
972 Contrast enhance image.
973 */
974 status=MagickTrue;
975 progress=0;
976 exception=(&image->exception);
977 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000978#if defined(MAGICKCORE_OPENMP_SUPPORT)
979 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000980#endif
981 for (y=0; y < (long) image->rows; y++)
982 {
983 register long
984 x;
985
986 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000987 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000988
989 if (status == MagickFalse)
990 continue;
991 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
992 if (q == (PixelPacket *) NULL)
993 {
994 status=MagickFalse;
995 continue;
996 }
997 for (x=0; x < (long) image->columns; x++)
998 {
999 Contrast(sign,&q->red,&q->green,&q->blue);
1000 q++;
1001 }
1002 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1003 status=MagickFalse;
1004 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1005 {
1006 MagickBooleanType
1007 proceed;
1008
cristyb5d5f722009-11-04 03:03:49 +00001009#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001010 #pragma omp critical (MagickCore_ContrastImage)
1011#endif
1012 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1013 if (proceed == MagickFalse)
1014 status=MagickFalse;
1015 }
1016 }
1017 image_view=DestroyCacheView(image_view);
1018 return(status);
1019}
1020
1021/*
1022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1023% %
1024% %
1025% %
1026% C o n t r a s t S t r e t c h I m a g e %
1027% %
1028% %
1029% %
1030%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1031%
1032% The ContrastStretchImage() is a simple image enhancement technique that
1033% attempts to improve the contrast in an image by `stretching' the range of
1034% intensity values it contains to span a desired range of values. It differs
1035% from the more sophisticated histogram equalization in that it can only
1036% apply % a linear scaling function to the image pixel values. As a result
1037% the `enhancement' is less harsh.
1038%
1039% The format of the ContrastStretchImage method is:
1040%
1041% MagickBooleanType ContrastStretchImage(Image *image,
1042% const char *levels)
1043% MagickBooleanType ContrastStretchImageChannel(Image *image,
1044% const unsigned long channel,const double black_point,
1045% const double white_point)
1046%
1047% A description of each parameter follows:
1048%
1049% o image: the image.
1050%
1051% o channel: the channel.
1052%
1053% o black_point: the black point.
1054%
1055% o white_point: the white point.
1056%
1057% o levels: Specify the levels where the black and white points have the
1058% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1059%
1060*/
1061
1062MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1063 const char *levels)
1064{
1065 double
1066 black_point,
1067 white_point;
1068
1069 GeometryInfo
1070 geometry_info;
1071
1072 MagickBooleanType
1073 status;
1074
1075 MagickStatusType
1076 flags;
1077
1078 /*
1079 Parse levels.
1080 */
1081 if (levels == (char *) NULL)
1082 return(MagickFalse);
1083 flags=ParseGeometry(levels,&geometry_info);
1084 black_point=geometry_info.rho;
1085 white_point=(double) image->columns*image->rows;
1086 if ((flags & SigmaValue) != 0)
1087 white_point=geometry_info.sigma;
1088 if ((flags & PercentValue) != 0)
1089 {
1090 black_point*=(double) QuantumRange/100.0;
1091 white_point*=(double) QuantumRange/100.0;
1092 }
1093 if ((flags & SigmaValue) == 0)
1094 white_point=(double) image->columns*image->rows-black_point;
1095 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1096 white_point);
1097 return(status);
1098}
1099
1100MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1101 const ChannelType channel,const double black_point,const double white_point)
1102{
1103#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1104#define ContrastStretchImageTag "ContrastStretch/Image"
1105
cristyc4c8d132010-01-07 01:58:38 +00001106 CacheView
1107 *image_view;
1108
cristy3ed852e2009-09-05 21:47:34 +00001109 double
1110 intensity;
1111
1112 ExceptionInfo
1113 *exception;
1114
1115 long
1116 progress,
1117 y;
1118
1119 MagickBooleanType
1120 status;
1121
1122 MagickPixelPacket
1123 black,
1124 *histogram,
1125 *stretch_map,
1126 white;
1127
1128 register long
1129 i;
1130
cristy3ed852e2009-09-05 21:47:34 +00001131 /*
1132 Allocate histogram and stretch map.
1133 */
1134 assert(image != (Image *) NULL);
1135 assert(image->signature == MagickSignature);
1136 if (image->debug != MagickFalse)
1137 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1138 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1139 sizeof(*histogram));
1140 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1141 sizeof(*stretch_map));
1142 if ((histogram == (MagickPixelPacket *) NULL) ||
1143 (stretch_map == (MagickPixelPacket *) NULL))
1144 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1145 image->filename);
1146 /*
1147 Form histogram.
1148 */
1149 status=MagickTrue;
1150 exception=(&image->exception);
1151 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1152 image_view=AcquireCacheView(image);
1153 for (y=0; y < (long) image->rows; y++)
1154 {
1155 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001156 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001157
1158 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001159 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001160
1161 register long
1162 x;
1163
1164 if (status == MagickFalse)
1165 continue;
1166 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1167 if (p == (const PixelPacket *) NULL)
1168 {
1169 status=MagickFalse;
1170 continue;
1171 }
1172 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1173 if (channel == DefaultChannels)
1174 for (x=0; x < (long) image->columns; x++)
1175 {
1176 Quantum
1177 intensity;
1178
1179 intensity=PixelIntensityToQuantum(p);
1180 histogram[ScaleQuantumToMap(intensity)].red++;
1181 histogram[ScaleQuantumToMap(intensity)].green++;
1182 histogram[ScaleQuantumToMap(intensity)].blue++;
1183 histogram[ScaleQuantumToMap(intensity)].index++;
1184 p++;
1185 }
1186 else
1187 for (x=0; x < (long) image->columns; x++)
1188 {
1189 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001190 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001191 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001192 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001193 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001194 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001195 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001196 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001197 if (((channel & IndexChannel) != 0) &&
1198 (image->colorspace == CMYKColorspace))
1199 histogram[ScaleQuantumToMap(indexes[x])].index++;
1200 p++;
1201 }
1202 }
1203 /*
1204 Find the histogram boundaries by locating the black/white levels.
1205 */
1206 black.red=0.0;
1207 white.red=MaxRange(QuantumRange);
1208 if ((channel & RedChannel) != 0)
1209 {
1210 intensity=0.0;
1211 for (i=0; i <= (long) MaxMap; i++)
1212 {
1213 intensity+=histogram[i].red;
1214 if (intensity > black_point)
1215 break;
1216 }
1217 black.red=(MagickRealType) i;
1218 intensity=0.0;
1219 for (i=(long) MaxMap; i != 0; i--)
1220 {
1221 intensity+=histogram[i].red;
1222 if (intensity > ((double) image->columns*image->rows-white_point))
1223 break;
1224 }
1225 white.red=(MagickRealType) i;
1226 }
1227 black.green=0.0;
1228 white.green=MaxRange(QuantumRange);
1229 if ((channel & GreenChannel) != 0)
1230 {
1231 intensity=0.0;
1232 for (i=0; i <= (long) MaxMap; i++)
1233 {
1234 intensity+=histogram[i].green;
1235 if (intensity > black_point)
1236 break;
1237 }
1238 black.green=(MagickRealType) i;
1239 intensity=0.0;
1240 for (i=(long) MaxMap; i != 0; i--)
1241 {
1242 intensity+=histogram[i].green;
1243 if (intensity > ((double) image->columns*image->rows-white_point))
1244 break;
1245 }
1246 white.green=(MagickRealType) i;
1247 }
1248 black.blue=0.0;
1249 white.blue=MaxRange(QuantumRange);
1250 if ((channel & BlueChannel) != 0)
1251 {
1252 intensity=0.0;
1253 for (i=0; i <= (long) MaxMap; i++)
1254 {
1255 intensity+=histogram[i].blue;
1256 if (intensity > black_point)
1257 break;
1258 }
1259 black.blue=(MagickRealType) i;
1260 intensity=0.0;
1261 for (i=(long) MaxMap; i != 0; i--)
1262 {
1263 intensity+=histogram[i].blue;
1264 if (intensity > ((double) image->columns*image->rows-white_point))
1265 break;
1266 }
1267 white.blue=(MagickRealType) i;
1268 }
1269 black.opacity=0.0;
1270 white.opacity=MaxRange(QuantumRange);
1271 if ((channel & OpacityChannel) != 0)
1272 {
1273 intensity=0.0;
1274 for (i=0; i <= (long) MaxMap; i++)
1275 {
1276 intensity+=histogram[i].opacity;
1277 if (intensity > black_point)
1278 break;
1279 }
1280 black.opacity=(MagickRealType) i;
1281 intensity=0.0;
1282 for (i=(long) MaxMap; i != 0; i--)
1283 {
1284 intensity+=histogram[i].opacity;
1285 if (intensity > ((double) image->columns*image->rows-white_point))
1286 break;
1287 }
1288 white.opacity=(MagickRealType) i;
1289 }
1290 black.index=0.0;
1291 white.index=MaxRange(QuantumRange);
1292 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1293 {
1294 intensity=0.0;
1295 for (i=0; i <= (long) MaxMap; i++)
1296 {
1297 intensity+=histogram[i].index;
1298 if (intensity > black_point)
1299 break;
1300 }
1301 black.index=(MagickRealType) i;
1302 intensity=0.0;
1303 for (i=(long) MaxMap; i != 0; i--)
1304 {
1305 intensity+=histogram[i].index;
1306 if (intensity > ((double) image->columns*image->rows-white_point))
1307 break;
1308 }
1309 white.index=(MagickRealType) i;
1310 }
1311 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1312 /*
1313 Stretch the histogram to create the stretched image mapping.
1314 */
1315 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001316#if defined(MAGICKCORE_OPENMP_SUPPORT)
1317 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001318#endif
1319 for (i=0; i <= (long) MaxMap; i++)
1320 {
1321 if ((channel & RedChannel) != 0)
1322 {
1323 if (i < (long) black.red)
1324 stretch_map[i].red=0.0;
1325 else
1326 if (i > (long) white.red)
1327 stretch_map[i].red=(MagickRealType) QuantumRange;
1328 else
1329 if (black.red != white.red)
1330 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1331 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1332 }
1333 if ((channel & GreenChannel) != 0)
1334 {
1335 if (i < (long) black.green)
1336 stretch_map[i].green=0.0;
1337 else
1338 if (i > (long) white.green)
1339 stretch_map[i].green=(MagickRealType) QuantumRange;
1340 else
1341 if (black.green != white.green)
1342 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1343 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1344 black.green)));
1345 }
1346 if ((channel & BlueChannel) != 0)
1347 {
1348 if (i < (long) black.blue)
1349 stretch_map[i].blue=0.0;
1350 else
1351 if (i > (long) white.blue)
1352 stretch_map[i].blue=(MagickRealType) QuantumRange;
1353 else
1354 if (black.blue != white.blue)
1355 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1356 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1357 black.blue)));
1358 }
1359 if ((channel & OpacityChannel) != 0)
1360 {
1361 if (i < (long) black.opacity)
1362 stretch_map[i].opacity=0.0;
1363 else
1364 if (i > (long) white.opacity)
1365 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1366 else
1367 if (black.opacity != white.opacity)
1368 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1369 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1370 black.opacity)));
1371 }
1372 if (((channel & IndexChannel) != 0) &&
1373 (image->colorspace == CMYKColorspace))
1374 {
1375 if (i < (long) black.index)
1376 stretch_map[i].index=0.0;
1377 else
1378 if (i > (long) white.index)
1379 stretch_map[i].index=(MagickRealType) QuantumRange;
1380 else
1381 if (black.index != white.index)
1382 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1383 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1384 black.index)));
1385 }
1386 }
1387 /*
1388 Stretch the image.
1389 */
1390 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1391 (image->colorspace == CMYKColorspace)))
1392 image->storage_class=DirectClass;
1393 if (image->storage_class == PseudoClass)
1394 {
1395 /*
1396 Stretch colormap.
1397 */
cristyb5d5f722009-11-04 03:03:49 +00001398#if defined(MAGICKCORE_OPENMP_SUPPORT)
1399 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001400#endif
1401 for (i=0; i < (long) image->colors; i++)
1402 {
1403 if ((channel & RedChannel) != 0)
1404 {
1405 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001406 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001407 ScaleQuantumToMap(image->colormap[i].red)].red);
1408 }
1409 if ((channel & GreenChannel) != 0)
1410 {
1411 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001412 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001413 ScaleQuantumToMap(image->colormap[i].green)].green);
1414 }
1415 if ((channel & BlueChannel) != 0)
1416 {
1417 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001418 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001419 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1420 }
1421 if ((channel & OpacityChannel) != 0)
1422 {
1423 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001424 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001425 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1426 }
1427 }
1428 }
1429 /*
1430 Stretch image.
1431 */
1432 status=MagickTrue;
1433 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001434#if defined(MAGICKCORE_OPENMP_SUPPORT)
1435 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001436#endif
1437 for (y=0; y < (long) image->rows; y++)
1438 {
1439 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001440 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001441
1442 register long
1443 x;
1444
1445 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001446 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001447
1448 if (status == MagickFalse)
1449 continue;
1450 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1451 if (q == (PixelPacket *) NULL)
1452 {
1453 status=MagickFalse;
1454 continue;
1455 }
1456 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1457 for (x=0; x < (long) image->columns; x++)
1458 {
1459 if ((channel & RedChannel) != 0)
1460 {
1461 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001462 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001463 }
1464 if ((channel & GreenChannel) != 0)
1465 {
1466 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001467 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001468 q->green)].green);
1469 }
1470 if ((channel & BlueChannel) != 0)
1471 {
1472 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001473 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001474 q->blue)].blue);
1475 }
1476 if ((channel & OpacityChannel) != 0)
1477 {
1478 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001479 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001480 q->opacity)].opacity);
1481 }
1482 if (((channel & IndexChannel) != 0) &&
1483 (image->colorspace == CMYKColorspace))
1484 {
1485 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001486 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001487 ScaleQuantumToMap(indexes[x])].index);
1488 }
1489 q++;
1490 }
1491 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1492 status=MagickFalse;
1493 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1494 {
1495 MagickBooleanType
1496 proceed;
1497
cristyb5d5f722009-11-04 03:03:49 +00001498#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001499 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1500#endif
1501 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1502 image->rows);
1503 if (proceed == MagickFalse)
1504 status=MagickFalse;
1505 }
1506 }
1507 image_view=DestroyCacheView(image_view);
1508 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1509 return(status);
1510}
1511
1512/*
1513%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1514% %
1515% %
1516% %
1517% E n h a n c e I m a g e %
1518% %
1519% %
1520% %
1521%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1522%
1523% EnhanceImage() applies a digital filter that improves the quality of a
1524% noisy image.
1525%
1526% The format of the EnhanceImage method is:
1527%
1528% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1529%
1530% A description of each parameter follows:
1531%
1532% o image: the image.
1533%
1534% o exception: return any errors or warnings in this structure.
1535%
1536*/
1537MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1538{
1539#define Enhance(weight) \
1540 mean=((MagickRealType) r->red+pixel.red)/2; \
1541 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1542 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1543 mean)*distance*distance; \
1544 mean=((MagickRealType) r->green+pixel.green)/2; \
1545 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1546 distance_squared+=4.0*distance*distance; \
1547 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1548 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1549 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1550 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1551 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1552 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1553 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1554 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1555 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1556 QuantumRange/25.0f)) \
1557 { \
1558 aggregate.red+=(weight)*r->red; \
1559 aggregate.green+=(weight)*r->green; \
1560 aggregate.blue+=(weight)*r->blue; \
1561 aggregate.opacity+=(weight)*r->opacity; \
1562 total_weight+=(weight); \
1563 } \
1564 r++;
1565#define EnhanceImageTag "Enhance/Image"
1566
cristyc4c8d132010-01-07 01:58:38 +00001567 CacheView
1568 *enhance_view,
1569 *image_view;
1570
cristy3ed852e2009-09-05 21:47:34 +00001571 Image
1572 *enhance_image;
1573
1574 long
1575 progress,
1576 y;
1577
1578 MagickBooleanType
1579 status;
1580
1581 MagickPixelPacket
1582 zero;
1583
cristy3ed852e2009-09-05 21:47:34 +00001584 /*
1585 Initialize enhanced image attributes.
1586 */
1587 assert(image != (const Image *) NULL);
1588 assert(image->signature == MagickSignature);
1589 if (image->debug != MagickFalse)
1590 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1591 assert(exception != (ExceptionInfo *) NULL);
1592 assert(exception->signature == MagickSignature);
1593 if ((image->columns < 5) || (image->rows < 5))
1594 return((Image *) NULL);
1595 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1596 exception);
1597 if (enhance_image == (Image *) NULL)
1598 return((Image *) NULL);
1599 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1600 {
1601 InheritException(exception,&enhance_image->exception);
1602 enhance_image=DestroyImage(enhance_image);
1603 return((Image *) NULL);
1604 }
1605 /*
1606 Enhance image.
1607 */
1608 status=MagickTrue;
1609 progress=0;
1610 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1611 image_view=AcquireCacheView(image);
1612 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001613#if defined(MAGICKCORE_OPENMP_SUPPORT)
1614 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001615#endif
1616 for (y=0; y < (long) image->rows; y++)
1617 {
1618 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001619 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001620
1621 register long
1622 x;
1623
1624 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001625 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001626
1627 /*
1628 Read another scan line.
1629 */
1630 if (status == MagickFalse)
1631 continue;
1632 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1633 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1634 exception);
1635 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1636 {
1637 status=MagickFalse;
1638 continue;
1639 }
1640 for (x=0; x < (long) image->columns; x++)
1641 {
1642 MagickPixelPacket
1643 aggregate;
1644
1645 MagickRealType
1646 distance,
1647 distance_squared,
1648 mean,
1649 total_weight;
1650
1651 PixelPacket
1652 pixel;
1653
1654 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001655 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001656
1657 /*
1658 Compute weighted average of target pixel color components.
1659 */
1660 aggregate=zero;
1661 total_weight=0.0;
1662 r=p+2*(image->columns+4)+2;
1663 pixel=(*r);
1664 r=p;
1665 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1666 r=p+(image->columns+4);
1667 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1668 r=p+2*(image->columns+4);
1669 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1670 r=p+3*(image->columns+4);
1671 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1672 r=p+4*(image->columns+4);
1673 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1674 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1675 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1676 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1677 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1678 total_weight);
1679 p++;
1680 q++;
1681 }
1682 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1683 status=MagickFalse;
1684 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1685 {
1686 MagickBooleanType
1687 proceed;
1688
cristyb5d5f722009-11-04 03:03:49 +00001689#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001690 #pragma omp critical (MagickCore_EnhanceImage)
1691#endif
1692 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1693 if (proceed == MagickFalse)
1694 status=MagickFalse;
1695 }
1696 }
1697 enhance_view=DestroyCacheView(enhance_view);
1698 image_view=DestroyCacheView(image_view);
1699 return(enhance_image);
1700}
1701
1702/*
1703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1704% %
1705% %
1706% %
1707% E q u a l i z e I m a g e %
1708% %
1709% %
1710% %
1711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1712%
1713% EqualizeImage() applies a histogram equalization to the image.
1714%
1715% The format of the EqualizeImage method is:
1716%
1717% MagickBooleanType EqualizeImage(Image *image)
1718% MagickBooleanType EqualizeImageChannel(Image *image,
1719% const ChannelType channel)
1720%
1721% A description of each parameter follows:
1722%
1723% o image: the image.
1724%
1725% o channel: the channel.
1726%
1727*/
1728
1729MagickExport MagickBooleanType EqualizeImage(Image *image)
1730{
1731 return(EqualizeImageChannel(image,DefaultChannels));
1732}
1733
1734MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1735 const ChannelType channel)
1736{
1737#define EqualizeImageTag "Equalize/Image"
1738
cristyc4c8d132010-01-07 01:58:38 +00001739 CacheView
1740 *image_view;
1741
cristy3ed852e2009-09-05 21:47:34 +00001742 ExceptionInfo
1743 *exception;
1744
1745 long
1746 progress,
1747 y;
1748
1749 MagickBooleanType
1750 status;
1751
1752 MagickPixelPacket
1753 black,
1754 *equalize_map,
1755 *histogram,
1756 intensity,
1757 *map,
1758 white;
1759
1760 register long
1761 i;
1762
cristy3ed852e2009-09-05 21:47:34 +00001763 /*
1764 Allocate and initialize histogram arrays.
1765 */
1766 assert(image != (Image *) NULL);
1767 assert(image->signature == MagickSignature);
1768 if (image->debug != MagickFalse)
1769 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1770 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1771 sizeof(*equalize_map));
1772 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1773 sizeof(*histogram));
1774 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1775 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1776 (histogram == (MagickPixelPacket *) NULL) ||
1777 (map == (MagickPixelPacket *) NULL))
1778 {
1779 if (map != (MagickPixelPacket *) NULL)
1780 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1781 if (histogram != (MagickPixelPacket *) NULL)
1782 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1783 if (equalize_map != (MagickPixelPacket *) NULL)
1784 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1785 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1786 image->filename);
1787 }
1788 /*
1789 Form histogram.
1790 */
1791 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1792 exception=(&image->exception);
1793 for (y=0; y < (long) image->rows; y++)
1794 {
1795 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001796 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001797
1798 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001799 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001800
1801 register long
1802 x;
1803
1804 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1805 if (p == (const PixelPacket *) NULL)
1806 break;
1807 indexes=GetVirtualIndexQueue(image);
1808 for (x=0; x < (long) image->columns; x++)
1809 {
1810 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001811 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001812 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001813 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001814 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001815 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001816 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001817 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001818 if (((channel & IndexChannel) != 0) &&
1819 (image->colorspace == CMYKColorspace))
1820 histogram[ScaleQuantumToMap(indexes[x])].index++;
1821 p++;
1822 }
1823 }
1824 /*
1825 Integrate the histogram to get the equalization map.
1826 */
1827 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1828 for (i=0; i <= (long) MaxMap; i++)
1829 {
1830 if ((channel & RedChannel) != 0)
1831 intensity.red+=histogram[i].red;
1832 if ((channel & GreenChannel) != 0)
1833 intensity.green+=histogram[i].green;
1834 if ((channel & BlueChannel) != 0)
1835 intensity.blue+=histogram[i].blue;
1836 if ((channel & OpacityChannel) != 0)
1837 intensity.opacity+=histogram[i].opacity;
1838 if (((channel & IndexChannel) != 0) &&
1839 (image->colorspace == CMYKColorspace))
1840 intensity.index+=histogram[i].index;
1841 map[i]=intensity;
1842 }
1843 black=map[0];
1844 white=map[(int) MaxMap];
1845 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001846#if defined(MAGICKCORE_OPENMP_SUPPORT)
1847 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001848#endif
1849 for (i=0; i <= (long) MaxMap; i++)
1850 {
1851 if (((channel & RedChannel) != 0) && (white.red != black.red))
1852 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1853 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1854 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1855 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1856 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1857 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1858 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1859 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1860 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1861 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1862 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1863 (white.opacity-black.opacity)));
1864 if ((((channel & IndexChannel) != 0) &&
1865 (image->colorspace == CMYKColorspace)) &&
1866 (white.index != black.index))
1867 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1868 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1869 }
1870 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1871 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1872 if (image->storage_class == PseudoClass)
1873 {
1874 /*
1875 Equalize colormap.
1876 */
cristyb5d5f722009-11-04 03:03:49 +00001877#if defined(MAGICKCORE_OPENMP_SUPPORT)
1878 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001879#endif
1880 for (i=0; i < (long) image->colors; i++)
1881 {
1882 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001883 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001884 ScaleQuantumToMap(image->colormap[i].red)].red);
1885 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001886 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001887 ScaleQuantumToMap(image->colormap[i].green)].green);
1888 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001889 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001890 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1891 if (((channel & OpacityChannel) != 0) &&
1892 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001893 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001894 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1895 }
1896 }
1897 /*
1898 Equalize image.
1899 */
1900 status=MagickTrue;
1901 progress=0;
1902 exception=(&image->exception);
1903 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001904#if defined(MAGICKCORE_OPENMP_SUPPORT)
1905 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001906#endif
1907 for (y=0; y < (long) image->rows; y++)
1908 {
1909 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001910 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001911
1912 register long
1913 x;
1914
1915 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001916 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001917
1918 if (status == MagickFalse)
1919 continue;
1920 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1921 if (q == (PixelPacket *) NULL)
1922 {
1923 status=MagickFalse;
1924 continue;
1925 }
1926 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1927 for (x=0; x < (long) image->columns; x++)
1928 {
1929 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001930 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001931 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001932 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001933 q->green)].green);
1934 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001935 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001936 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001937 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001938 q->opacity)].opacity);
1939 if ((((channel & IndexChannel) != 0) &&
1940 (image->colorspace == CMYKColorspace)) &&
1941 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001942 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001943 indexes[x])].index);
1944 q++;
1945 }
1946 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1947 status=MagickFalse;
1948 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1949 {
1950 MagickBooleanType
1951 proceed;
1952
cristyb5d5f722009-11-04 03:03:49 +00001953#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001954 #pragma omp critical (MagickCore_EqualizeImageChannel)
1955#endif
1956 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1957 if (proceed == MagickFalse)
1958 status=MagickFalse;
1959 }
1960 }
1961 image_view=DestroyCacheView(image_view);
1962 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1963 return(status);
1964}
1965
1966/*
1967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1968% %
1969% %
1970% %
1971% G a m m a I m a g e %
1972% %
1973% %
1974% %
1975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1976%
1977% GammaImage() gamma-corrects a particular image channel. The same
1978% image viewed on different devices will have perceptual differences in the
1979% way the image's intensities are represented on the screen. Specify
1980% individual gamma levels for the red, green, and blue channels, or adjust
1981% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1982%
1983% You can also reduce the influence of a particular channel with a gamma
1984% value of 0.
1985%
1986% The format of the GammaImage method is:
1987%
1988% MagickBooleanType GammaImage(Image *image,const double gamma)
1989% MagickBooleanType GammaImageChannel(Image *image,
1990% const ChannelType channel,const double gamma)
1991%
1992% A description of each parameter follows:
1993%
1994% o image: the image.
1995%
1996% o channel: the channel.
1997%
1998% o gamma: the image gamma.
1999%
2000*/
2001MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2002{
2003 GeometryInfo
2004 geometry_info;
2005
2006 MagickPixelPacket
2007 gamma;
2008
2009 MagickStatusType
2010 flags,
2011 status;
2012
2013 assert(image != (Image *) NULL);
2014 assert(image->signature == MagickSignature);
2015 if (image->debug != MagickFalse)
2016 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2017 if (level == (char *) NULL)
2018 return(MagickFalse);
2019 flags=ParseGeometry(level,&geometry_info);
2020 gamma.red=geometry_info.rho;
2021 gamma.green=geometry_info.sigma;
2022 if ((flags & SigmaValue) == 0)
2023 gamma.green=gamma.red;
2024 gamma.blue=geometry_info.xi;
2025 if ((flags & XiValue) == 0)
2026 gamma.blue=gamma.red;
2027 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2028 return(MagickTrue);
2029 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2030 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2031 GreenChannel | BlueChannel),(double) gamma.red);
2032 else
2033 {
2034 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2035 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2036 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2037 }
2038 return(status != 0 ? MagickTrue : MagickFalse);
2039}
2040
2041MagickExport MagickBooleanType GammaImageChannel(Image *image,
2042 const ChannelType channel,const double gamma)
2043{
2044#define GammaCorrectImageTag "GammaCorrect/Image"
2045
cristyc4c8d132010-01-07 01:58:38 +00002046 CacheView
2047 *image_view;
2048
cristy3ed852e2009-09-05 21:47:34 +00002049 ExceptionInfo
2050 *exception;
2051
2052 long
2053 progress,
2054 y;
2055
2056 MagickBooleanType
2057 status;
2058
2059 Quantum
2060 *gamma_map;
2061
2062 register long
2063 i;
2064
cristy3ed852e2009-09-05 21:47:34 +00002065 /*
2066 Allocate and initialize gamma maps.
2067 */
2068 assert(image != (Image *) NULL);
2069 assert(image->signature == MagickSignature);
2070 if (image->debug != MagickFalse)
2071 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2072 if (gamma == 1.0)
2073 return(MagickTrue);
2074 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2075 if (gamma_map == (Quantum *) NULL)
2076 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2077 image->filename);
2078 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2079 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002080#if defined(MAGICKCORE_OPENMP_SUPPORT)
2081 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002082#endif
2083 for (i=0; i <= (long) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002084 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002085 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2086 if (image->storage_class == PseudoClass)
2087 {
2088 /*
2089 Gamma-correct colormap.
2090 */
cristyb5d5f722009-11-04 03:03:49 +00002091#if defined(MAGICKCORE_OPENMP_SUPPORT)
2092 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002093#endif
2094 for (i=0; i < (long) image->colors; i++)
2095 {
2096 if ((channel & RedChannel) != 0)
2097 image->colormap[i].red=gamma_map[
2098 ScaleQuantumToMap(image->colormap[i].red)];
2099 if ((channel & GreenChannel) != 0)
2100 image->colormap[i].green=gamma_map[
2101 ScaleQuantumToMap(image->colormap[i].green)];
2102 if ((channel & BlueChannel) != 0)
2103 image->colormap[i].blue=gamma_map[
2104 ScaleQuantumToMap(image->colormap[i].blue)];
2105 if ((channel & OpacityChannel) != 0)
2106 {
2107 if (image->matte == MagickFalse)
2108 image->colormap[i].opacity=gamma_map[
2109 ScaleQuantumToMap(image->colormap[i].opacity)];
2110 else
2111 image->colormap[i].opacity=(Quantum) QuantumRange-
2112 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2113 image->colormap[i].opacity))];
2114 }
2115 }
2116 }
2117 /*
2118 Gamma-correct image.
2119 */
2120 status=MagickTrue;
2121 progress=0;
2122 exception=(&image->exception);
2123 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002124#if defined(MAGICKCORE_OPENMP_SUPPORT)
2125 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002126#endif
2127 for (y=0; y < (long) image->rows; y++)
2128 {
2129 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002130 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002131
2132 register long
2133 x;
2134
2135 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002136 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002137
2138 if (status == MagickFalse)
2139 continue;
2140 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2141 if (q == (PixelPacket *) NULL)
2142 {
2143 status=MagickFalse;
2144 continue;
2145 }
2146 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2147 for (x=0; x < (long) image->columns; x++)
2148 {
cristy6cbd7f52009-10-17 16:06:51 +00002149 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002150 {
cristy6cbd7f52009-10-17 16:06:51 +00002151 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2152 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2153 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2154 }
2155 else
2156 {
2157 if ((channel & RedChannel) != 0)
2158 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2159 if ((channel & GreenChannel) != 0)
2160 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2161 if ((channel & BlueChannel) != 0)
2162 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2163 if ((channel & OpacityChannel) != 0)
2164 {
2165 if (image->matte == MagickFalse)
2166 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2167 else
2168 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002169 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002170 }
cristy3ed852e2009-09-05 21:47:34 +00002171 }
2172 q++;
2173 }
2174 if (((channel & IndexChannel) != 0) &&
2175 (image->colorspace == CMYKColorspace))
2176 for (x=0; x < (long) image->columns; x++)
2177 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2178 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2179 status=MagickFalse;
2180 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2181 {
2182 MagickBooleanType
2183 proceed;
2184
cristyb5d5f722009-11-04 03:03:49 +00002185#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002186 #pragma omp critical (MagickCore_GammaImageChannel)
2187#endif
2188 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2189 image->rows);
2190 if (proceed == MagickFalse)
2191 status=MagickFalse;
2192 }
2193 }
2194 image_view=DestroyCacheView(image_view);
2195 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2196 if (image->gamma != 0.0)
2197 image->gamma*=gamma;
2198 return(status);
2199}
2200
2201/*
2202%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2203% %
2204% %
2205% %
2206% H a l d C l u t I m a g e %
2207% %
2208% %
2209% %
2210%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2211%
2212% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2213% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2214% Create it with the HALD coder. You can apply any color transformation to
2215% the Hald image and then use this method to apply the transform to the
2216% image.
2217%
2218% The format of the HaldClutImage method is:
2219%
2220% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2221% MagickBooleanType HaldClutImageChannel(Image *image,
2222% const ChannelType channel,Image *hald_image)
2223%
2224% A description of each parameter follows:
2225%
2226% o image: the image, which is replaced by indexed CLUT values
2227%
2228% o hald_image: the color lookup table image for replacement color values.
2229%
2230% o channel: the channel.
2231%
2232*/
2233
2234static inline size_t MagickMin(const size_t x,const size_t y)
2235{
2236 if (x < y)
2237 return(x);
2238 return(y);
2239}
2240
2241MagickExport MagickBooleanType HaldClutImage(Image *image,
2242 const Image *hald_image)
2243{
2244 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2245}
2246
2247MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2248 const ChannelType channel,const Image *hald_image)
2249{
2250#define HaldClutImageTag "Clut/Image"
2251
2252 typedef struct _HaldInfo
2253 {
2254 MagickRealType
2255 x,
2256 y,
2257 z;
2258 } HaldInfo;
2259
cristyfa112112010-01-04 17:48:07 +00002260 CacheView
2261 *image_view;
2262
cristy3ed852e2009-09-05 21:47:34 +00002263 double
2264 width;
2265
2266 ExceptionInfo
2267 *exception;
2268
2269 long
2270 progress,
2271 y;
2272
2273 MagickBooleanType
2274 status;
2275
2276 MagickPixelPacket
2277 zero;
2278
2279 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002280 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002281
2282 size_t
2283 cube_size,
2284 length,
2285 level;
2286
cristy3ed852e2009-09-05 21:47:34 +00002287 assert(image != (Image *) NULL);
2288 assert(image->signature == MagickSignature);
2289 if (image->debug != MagickFalse)
2290 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2291 assert(hald_image != (Image *) NULL);
2292 assert(hald_image->signature == MagickSignature);
2293 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2294 return(MagickFalse);
2295 if (image->matte == MagickFalse)
2296 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2297 /*
2298 Hald clut image.
2299 */
2300 status=MagickTrue;
2301 progress=0;
2302 length=MagickMin(hald_image->columns,hald_image->rows);
2303 for (level=2; (level*level*level) < length; level++) ;
2304 level*=level;
2305 cube_size=level*level;
2306 width=(double) hald_image->columns;
2307 GetMagickPixelPacket(hald_image,&zero);
2308 exception=(&image->exception);
2309 resample_filter=AcquireResampleFilterThreadSet(hald_image,MagickTrue,
2310 exception);
2311 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002312#if defined(MAGICKCORE_OPENMP_SUPPORT)
2313 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002314#endif
2315 for (y=0; y < (long) image->rows; y++)
2316 {
2317 double
2318 offset;
2319
2320 HaldInfo
2321 point;
2322
2323 MagickPixelPacket
2324 pixel,
2325 pixel1,
2326 pixel2,
2327 pixel3,
2328 pixel4;
2329
2330 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002331 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002332
2333 register long
2334 id,
2335 x;
2336
2337 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002338 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002339
2340 if (status == MagickFalse)
2341 continue;
2342 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2343 if (q == (PixelPacket *) NULL)
2344 {
2345 status=MagickFalse;
2346 continue;
2347 }
2348 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2349 pixel=zero;
2350 pixel1=zero;
2351 pixel2=zero;
2352 pixel3=zero;
2353 pixel4=zero;
2354 id=GetOpenMPThreadId();
2355 for (x=0; x < (long) image->columns; x++)
2356 {
2357 point.x=QuantumScale*(level-1.0)*q->red;
2358 point.y=QuantumScale*(level-1.0)*q->green;
2359 point.z=QuantumScale*(level-1.0)*q->blue;
2360 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2361 point.x-=floor(point.x);
2362 point.y-=floor(point.y);
2363 point.z-=floor(point.z);
2364 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2365 floor(offset/width),&pixel1);
2366 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2367 floor((offset+level)/width),&pixel2);
2368 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2369 pixel2.opacity,point.y,&pixel3);
2370 offset+=cube_size;
2371 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2372 floor(offset/width),&pixel1);
2373 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2374 floor((offset+level)/width),&pixel2);
2375 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2376 pixel2.opacity,point.y,&pixel4);
2377 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2378 pixel4.opacity,point.z,&pixel);
2379 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002380 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002381 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002382 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002383 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002384 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002385 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002386 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002387 if (((channel & IndexChannel) != 0) &&
2388 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002389 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002390 q++;
2391 }
2392 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2393 status=MagickFalse;
2394 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2395 {
2396 MagickBooleanType
2397 proceed;
2398
cristyb5d5f722009-11-04 03:03:49 +00002399#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002400 #pragma omp critical (MagickCore_HaldClutImageChannel)
2401#endif
2402 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2403 if (proceed == MagickFalse)
2404 status=MagickFalse;
2405 }
2406 }
2407 image_view=DestroyCacheView(image_view);
2408 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2409 return(status);
2410}
2411
2412/*
2413%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2414% %
2415% %
2416% %
2417% L e v e l I m a g e %
2418% %
2419% %
2420% %
2421%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2422%
2423% LevelImage() adjusts the levels of a particular image channel by
2424% scaling the colors falling between specified white and black points to
2425% the full available quantum range.
2426%
2427% The parameters provided represent the black, and white points. The black
2428% point specifies the darkest color in the image. Colors darker than the
2429% black point are set to zero. White point specifies the lightest color in
2430% the image. Colors brighter than the white point are set to the maximum
2431% quantum value.
2432%
2433% If a '!' flag is given, map black and white colors to the given levels
2434% rather than mapping those levels to black and white. See
2435% LevelizeImageChannel() and LevelizeImageChannel(), below.
2436%
2437% Gamma specifies a gamma correction to apply to the image.
2438%
2439% The format of the LevelImage method is:
2440%
2441% MagickBooleanType LevelImage(Image *image,const char *levels)
2442%
2443% A description of each parameter follows:
2444%
2445% o image: the image.
2446%
2447% o levels: Specify the levels where the black and white points have the
2448% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2449% A '!' flag inverts the re-mapping.
2450%
2451*/
2452
2453MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2454{
2455 double
2456 black_point,
2457 gamma,
2458 white_point;
2459
2460 GeometryInfo
2461 geometry_info;
2462
2463 MagickBooleanType
2464 status;
2465
2466 MagickStatusType
2467 flags;
2468
2469 /*
2470 Parse levels.
2471 */
2472 if (levels == (char *) NULL)
2473 return(MagickFalse);
2474 flags=ParseGeometry(levels,&geometry_info);
2475 black_point=geometry_info.rho;
2476 white_point=(double) QuantumRange;
2477 if ((flags & SigmaValue) != 0)
2478 white_point=geometry_info.sigma;
2479 gamma=1.0;
2480 if ((flags & XiValue) != 0)
2481 gamma=geometry_info.xi;
2482 if ((flags & PercentValue) != 0)
2483 {
2484 black_point*=(double) image->columns*image->rows/100.0;
2485 white_point*=(double) image->columns*image->rows/100.0;
2486 }
2487 if ((flags & SigmaValue) == 0)
2488 white_point=(double) QuantumRange-black_point;
2489 if ((flags & AspectValue ) == 0)
2490 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2491 gamma);
2492 else
cristy308b4e62009-09-21 14:40:44 +00002493 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002494 return(status);
2495}
2496
2497/*
2498%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2499% %
2500% %
2501% %
cristy308b4e62009-09-21 14:40:44 +00002502% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002503% %
2504% %
2505% %
2506%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2507%
cristy308b4e62009-09-21 14:40:44 +00002508% LevelizeImage() applies the normal level operation to the image, spreading
2509% out the values between the black and white points over the entire range of
2510% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002511%
2512% It is typically used to improve image contrast, or to provide a controlled
2513% linear threshold for the image. If the black and white points are set to
2514% the minimum and maximum values found in the image, the image can be
2515% normalized. or by swapping black and white values, negate the image.
2516%
cristy308b4e62009-09-21 14:40:44 +00002517% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002518%
cristy308b4e62009-09-21 14:40:44 +00002519% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2520% const double white_point,const double gamma)
2521% MagickBooleanType LevelizeImageChannel(Image *image,
2522% const ChannelType channel,const double black_point,
2523% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002524%
2525% A description of each parameter follows:
2526%
2527% o image: the image.
2528%
2529% o channel: the channel.
2530%
2531% o black_point: The level which is to be mapped to zero (black)
2532%
2533% o white_point: The level which is to be mapped to QuantiumRange (white)
2534%
2535% o gamma: adjust gamma by this factor before mapping values.
2536% use 1.0 for purely linear stretching of image color values
2537%
2538*/
cristy308b4e62009-09-21 14:40:44 +00002539
2540MagickExport MagickBooleanType LevelizeImage(Image *image,
2541 const double black_point,const double white_point,const double gamma)
2542{
2543 MagickBooleanType
2544 status;
2545
2546 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2547 gamma);
2548 return(status);
2549}
2550
cristy3ed852e2009-09-05 21:47:34 +00002551MagickExport MagickBooleanType LevelImageChannel(Image *image,
2552 const ChannelType channel,const double black_point,const double white_point,
2553 const double gamma)
2554{
2555#define LevelImageTag "Level/Image"
cristyce70c172010-01-07 17:15:30 +00002556#define LevelValue(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristy3ed852e2009-09-05 21:47:34 +00002557 pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
2558
cristyc4c8d132010-01-07 01:58:38 +00002559 CacheView
2560 *image_view;
2561
cristy3ed852e2009-09-05 21:47:34 +00002562 ExceptionInfo
2563 *exception;
2564
2565 long
2566 progress,
2567 y;
2568
2569 MagickBooleanType
2570 status;
2571
2572 register long
2573 i;
2574
cristy3ed852e2009-09-05 21:47:34 +00002575 /*
2576 Allocate and initialize levels map.
2577 */
2578 assert(image != (Image *) NULL);
2579 assert(image->signature == MagickSignature);
2580 if (image->debug != MagickFalse)
2581 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2582 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002583#if defined(MAGICKCORE_OPENMP_SUPPORT)
2584 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002585#endif
2586 for (i=0; i < (long) image->colors; i++)
2587 {
2588 /*
2589 Level colormap.
2590 */
2591 if ((channel & RedChannel) != 0)
2592 image->colormap[i].red=LevelValue(image->colormap[i].red);
2593 if ((channel & GreenChannel) != 0)
2594 image->colormap[i].green=LevelValue(image->colormap[i].green);
2595 if ((channel & BlueChannel) != 0)
2596 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2597 if ((channel & OpacityChannel) != 0)
2598 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2599 }
2600 /*
2601 Level image.
2602 */
2603 status=MagickTrue;
2604 progress=0;
2605 exception=(&image->exception);
2606 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002607#if defined(MAGICKCORE_OPENMP_SUPPORT)
2608 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002609#endif
2610 for (y=0; y < (long) image->rows; y++)
2611 {
2612 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002613 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002614
2615 register long
2616 x;
2617
2618 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002619 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002620
2621 if (status == MagickFalse)
2622 continue;
2623 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2624 if (q == (PixelPacket *) NULL)
2625 {
2626 status=MagickFalse;
2627 continue;
2628 }
2629 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2630 for (x=0; x < (long) image->columns; x++)
2631 {
2632 if ((channel & RedChannel) != 0)
2633 q->red=LevelValue(q->red);
2634 if ((channel & GreenChannel) != 0)
2635 q->green=LevelValue(q->green);
2636 if ((channel & BlueChannel) != 0)
2637 q->blue=LevelValue(q->blue);
2638 if (((channel & OpacityChannel) != 0) &&
2639 (image->matte == MagickTrue))
2640 q->opacity=LevelValue(q->opacity);
2641 if (((channel & IndexChannel) != 0) &&
2642 (image->colorspace == CMYKColorspace))
2643 indexes[x]=LevelValue(indexes[x]);
2644 q++;
2645 }
2646 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2647 status=MagickFalse;
2648 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2649 {
2650 MagickBooleanType
2651 proceed;
2652
cristyb5d5f722009-11-04 03:03:49 +00002653#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002654 #pragma omp critical (MagickCore_LevelImageChannel)
2655#endif
2656 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2657 if (proceed == MagickFalse)
2658 status=MagickFalse;
2659 }
2660 }
2661 image_view=DestroyCacheView(image_view);
2662 return(status);
2663}
2664
2665/*
2666%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2667% %
2668% %
2669% %
2670% L e v e l i z e I m a g e C h a n n e l %
2671% %
2672% %
2673% %
2674%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2675%
2676% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2677% the specific channels specified. It compresses the full range of color
2678% values, so that they lie between the given black and white points. Gamma is
2679% applied before the values are mapped.
2680%
2681% LevelizeImageChannel() can be called with by using a +level command line
2682% API option, or using a '!' on a -level or LevelImage() geometry string.
2683%
2684% It can be used for example de-contrast a greyscale image to the exact
2685% levels specified. Or by using specific levels for each channel of an image
2686% you can convert a gray-scale image to any linear color gradient, according
2687% to those levels.
2688%
2689% The format of the LevelizeImageChannel method is:
2690%
2691% MagickBooleanType LevelizeImageChannel(Image *image,
2692% const ChannelType channel,const char *levels)
2693%
2694% A description of each parameter follows:
2695%
2696% o image: the image.
2697%
2698% o channel: the channel.
2699%
2700% o black_point: The level to map zero (black) to.
2701%
2702% o white_point: The level to map QuantiumRange (white) to.
2703%
2704% o gamma: adjust gamma by this factor before mapping values.
2705%
2706*/
2707MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2708 const ChannelType channel,const double black_point,const double white_point,
2709 const double gamma)
2710{
2711#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002712#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002713 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2714 black_point))
2715
cristyc4c8d132010-01-07 01:58:38 +00002716 CacheView
2717 *image_view;
2718
cristy3ed852e2009-09-05 21:47:34 +00002719 ExceptionInfo
2720 *exception;
2721
2722 long
2723 progress,
2724 y;
2725
2726 MagickBooleanType
2727 status;
2728
2729 register long
2730 i;
2731
cristy3ed852e2009-09-05 21:47:34 +00002732 /*
2733 Allocate and initialize levels map.
2734 */
2735 assert(image != (Image *) NULL);
2736 assert(image->signature == MagickSignature);
2737 if (image->debug != MagickFalse)
2738 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2739 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002740#if defined(MAGICKCORE_OPENMP_SUPPORT)
2741 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002742#endif
2743 for (i=0; i < (long) image->colors; i++)
2744 {
2745 /*
2746 Level colormap.
2747 */
2748 if ((channel & RedChannel) != 0)
2749 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2750 if ((channel & GreenChannel) != 0)
2751 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2752 if ((channel & BlueChannel) != 0)
2753 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2754 if ((channel & OpacityChannel) != 0)
2755 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2756 }
2757 /*
2758 Level image.
2759 */
2760 status=MagickTrue;
2761 progress=0;
2762 exception=(&image->exception);
2763 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002764#if defined(MAGICKCORE_OPENMP_SUPPORT)
2765 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002766#endif
2767 for (y=0; y < (long) image->rows; y++)
2768 {
2769 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002770 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002771
2772 register long
2773 x;
2774
2775 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002776 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002777
2778 if (status == MagickFalse)
2779 continue;
2780 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2781 if (q == (PixelPacket *) NULL)
2782 {
2783 status=MagickFalse;
2784 continue;
2785 }
2786 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2787 for (x=0; x < (long) image->columns; x++)
2788 {
2789 if ((channel & RedChannel) != 0)
2790 q->red=LevelizeValue(q->red);
2791 if ((channel & GreenChannel) != 0)
2792 q->green=LevelizeValue(q->green);
2793 if ((channel & BlueChannel) != 0)
2794 q->blue=LevelizeValue(q->blue);
2795 if (((channel & OpacityChannel) != 0) &&
2796 (image->matte == MagickTrue))
2797 q->opacity=LevelizeValue(q->opacity);
2798 if (((channel & IndexChannel) != 0) &&
2799 (image->colorspace == CMYKColorspace))
2800 indexes[x]=LevelizeValue(indexes[x]);
2801 q++;
2802 }
2803 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2804 status=MagickFalse;
2805 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2806 {
2807 MagickBooleanType
2808 proceed;
2809
cristyb5d5f722009-11-04 03:03:49 +00002810#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002811 #pragma omp critical (MagickCore_LevelizeImageChannel)
2812#endif
2813 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2814 if (proceed == MagickFalse)
2815 status=MagickFalse;
2816 }
2817 }
2818 return(status);
2819}
2820
2821/*
2822%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2823% %
2824% %
2825% %
2826% L e v e l I m a g e C o l o r s %
2827% %
2828% %
2829% %
2830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2831%
cristyee0f8d72009-09-19 00:58:29 +00002832% LevelImageColor() maps the given color to "black" and "white" values,
2833% linearly spreading out the colors, and level values on a channel by channel
2834% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002835% different level ranges for each of the color channels seperatally.
2836%
2837% If the boolean 'invert' is set true the image values will modifyed in the
2838% reverse direction. That is any existing "black" and "white" colors in the
2839% image will become the color values given, with all other values compressed
2840% appropriatally. This effectivally maps a greyscale gradient into the given
2841% color gradient.
2842%
cristy308b4e62009-09-21 14:40:44 +00002843% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002844%
cristy308b4e62009-09-21 14:40:44 +00002845% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002846% const MagickPixelPacket *black_color,
2847% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002848% MagickBooleanType LevelColorsImageChannel(Image *image,
2849% const ChannelType channel,const MagickPixelPacket *black_color,
2850% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002851%
2852% A description of each parameter follows:
2853%
2854% o image: the image.
2855%
2856% o channel: the channel.
2857%
2858% o black_color: The color to map black to/from
2859%
2860% o white_point: The color to map white to/from
2861%
2862% o invert: if true map the colors (levelize), rather than from (level)
2863%
2864*/
cristy308b4e62009-09-21 14:40:44 +00002865
2866MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002867 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2868 const MagickBooleanType invert)
2869{
cristy308b4e62009-09-21 14:40:44 +00002870 MagickBooleanType
2871 status;
cristy3ed852e2009-09-05 21:47:34 +00002872
cristy308b4e62009-09-21 14:40:44 +00002873 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2874 invert);
2875 return(status);
2876}
2877
2878MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2879 const ChannelType channel,const MagickPixelPacket *black_color,
2880 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2881{
cristy3ed852e2009-09-05 21:47:34 +00002882 MagickStatusType
2883 status;
2884
2885 /*
2886 Allocate and initialize levels map.
2887 */
2888 assert(image != (Image *) NULL);
2889 assert(image->signature == MagickSignature);
2890 if (image->debug != MagickFalse)
2891 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2892 status=MagickFalse;
2893 if (invert == MagickFalse)
2894 {
2895 if ((channel & RedChannel) != 0)
2896 status|=LevelImageChannel(image,RedChannel,
2897 black_color->red,white_color->red,(double) 1.0);
2898 if ((channel & GreenChannel) != 0)
2899 status|=LevelImageChannel(image,GreenChannel,
2900 black_color->green,white_color->green,(double) 1.0);
2901 if ((channel & BlueChannel) != 0)
2902 status|=LevelImageChannel(image,BlueChannel,
2903 black_color->blue,white_color->blue,(double) 1.0);
2904 if (((channel & OpacityChannel) != 0) &&
2905 (image->matte == MagickTrue))
2906 status|=LevelImageChannel(image,OpacityChannel,
2907 black_color->opacity,white_color->opacity,(double) 1.0);
2908 if (((channel & IndexChannel) != 0) &&
2909 (image->colorspace == CMYKColorspace))
2910 status|=LevelImageChannel(image,IndexChannel,
2911 black_color->index,white_color->index,(double) 1.0);
2912 }
2913 else
2914 {
2915 if ((channel & RedChannel) != 0)
2916 status|=LevelizeImageChannel(image,RedChannel,
2917 black_color->red,white_color->red,(double) 1.0);
2918 if ((channel & GreenChannel) != 0)
2919 status|=LevelizeImageChannel(image,GreenChannel,
2920 black_color->green,white_color->green,(double) 1.0);
2921 if ((channel & BlueChannel) != 0)
2922 status|=LevelizeImageChannel(image,BlueChannel,
2923 black_color->blue,white_color->blue,(double) 1.0);
2924 if (((channel & OpacityChannel) != 0) &&
2925 (image->matte == MagickTrue))
2926 status|=LevelizeImageChannel(image,OpacityChannel,
2927 black_color->opacity,white_color->opacity,(double) 1.0);
2928 if (((channel & IndexChannel) != 0) &&
2929 (image->colorspace == CMYKColorspace))
2930 status|=LevelizeImageChannel(image,IndexChannel,
2931 black_color->index,white_color->index,(double) 1.0);
2932 }
2933 return(status == 0 ? MagickFalse : MagickTrue);
2934}
2935
2936/*
2937%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2938% %
2939% %
2940% %
2941% L i n e a r S t r e t c h I m a g e %
2942% %
2943% %
2944% %
2945%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2946%
2947% The LinearStretchImage() discards any pixels below the black point and
2948% above the white point and levels the remaining pixels.
2949%
2950% The format of the LinearStretchImage method is:
2951%
2952% MagickBooleanType LinearStretchImage(Image *image,
2953% const double black_point,const double white_point)
2954%
2955% A description of each parameter follows:
2956%
2957% o image: the image.
2958%
2959% o black_point: the black point.
2960%
2961% o white_point: the white point.
2962%
2963*/
2964MagickExport MagickBooleanType LinearStretchImage(Image *image,
2965 const double black_point,const double white_point)
2966{
2967#define LinearStretchImageTag "LinearStretch/Image"
2968
2969 ExceptionInfo
2970 *exception;
2971
2972 long
2973 black,
2974 white,
2975 y;
2976
2977 MagickBooleanType
2978 status;
2979
2980 MagickRealType
2981 *histogram,
2982 intensity;
2983
2984 MagickSizeType
2985 number_pixels;
2986
2987 /*
2988 Allocate histogram and linear map.
2989 */
2990 assert(image != (Image *) NULL);
2991 assert(image->signature == MagickSignature);
2992 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2993 sizeof(*histogram));
2994 if (histogram == (MagickRealType *) NULL)
2995 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2996 image->filename);
2997 /*
2998 Form histogram.
2999 */
3000 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3001 exception=(&image->exception);
3002 for (y=0; y < (long) image->rows; y++)
3003 {
3004 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003005 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003006
3007 register long
3008 x;
3009
3010 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3011 if (p == (const PixelPacket *) NULL)
3012 break;
3013 for (x=(long) image->columns-1; x >= 0; x--)
3014 {
3015 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3016 p++;
3017 }
3018 }
3019 /*
3020 Find the histogram boundaries by locating the black and white point levels.
3021 */
3022 number_pixels=(MagickSizeType) image->columns*image->rows;
3023 intensity=0.0;
3024 for (black=0; black < (long) MaxMap; black++)
3025 {
3026 intensity+=histogram[black];
3027 if (intensity >= black_point)
3028 break;
3029 }
3030 intensity=0.0;
3031 for (white=(long) MaxMap; white != 0; white--)
3032 {
3033 intensity+=histogram[white];
3034 if (intensity >= white_point)
3035 break;
3036 }
3037 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3038 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3039 1.0);
3040 return(status);
3041}
3042
3043/*
3044%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3045% %
3046% %
3047% %
3048% M o d u l a t e I m a g e %
3049% %
3050% %
3051% %
3052%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3053%
3054% ModulateImage() lets you control the brightness, saturation, and hue
3055% of an image. Modulate represents the brightness, saturation, and hue
3056% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3057% modulation is lightness, saturation, and hue. And if the colorspace is
3058% HWB, use blackness, whiteness, and hue.
3059%
3060% The format of the ModulateImage method is:
3061%
3062% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3063%
3064% A description of each parameter follows:
3065%
3066% o image: the image.
3067%
3068% o modulate: Define the percent change in brightness, saturation, and
3069% hue.
3070%
3071*/
3072
3073static void ModulateHSB(const double percent_hue,
3074 const double percent_saturation,const double percent_brightness,
3075 Quantum *red,Quantum *green,Quantum *blue)
3076{
3077 double
3078 brightness,
3079 hue,
3080 saturation;
3081
3082 /*
3083 Increase or decrease color brightness, saturation, or hue.
3084 */
3085 assert(red != (Quantum *) NULL);
3086 assert(green != (Quantum *) NULL);
3087 assert(blue != (Quantum *) NULL);
3088 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3089 hue+=0.5*(0.01*percent_hue-1.0);
3090 while (hue < 0.0)
3091 hue+=1.0;
3092 while (hue > 1.0)
3093 hue-=1.0;
3094 saturation*=0.01*percent_saturation;
3095 brightness*=0.01*percent_brightness;
3096 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3097}
3098
3099static void ModulateHSL(const double percent_hue,
3100 const double percent_saturation,const double percent_lightness,
3101 Quantum *red,Quantum *green,Quantum *blue)
3102{
3103 double
3104 hue,
3105 lightness,
3106 saturation;
3107
3108 /*
3109 Increase or decrease color lightness, saturation, or hue.
3110 */
3111 assert(red != (Quantum *) NULL);
3112 assert(green != (Quantum *) NULL);
3113 assert(blue != (Quantum *) NULL);
3114 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3115 hue+=0.5*(0.01*percent_hue-1.0);
3116 while (hue < 0.0)
3117 hue+=1.0;
3118 while (hue > 1.0)
3119 hue-=1.0;
3120 saturation*=0.01*percent_saturation;
3121 lightness*=0.01*percent_lightness;
3122 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3123}
3124
3125static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3126{
3127 double
3128 blackness,
3129 hue,
3130 whiteness;
3131
3132 /*
3133 Increase or decrease color blackness, whiteness, or hue.
3134 */
3135 assert(red != (Quantum *) NULL);
3136 assert(green != (Quantum *) NULL);
3137 assert(blue != (Quantum *) NULL);
3138 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3139 hue+=0.5*(0.01*percent_hue-1.0);
3140 while (hue < 0.0)
3141 hue+=1.0;
3142 while (hue > 1.0)
3143 hue-=1.0;
3144 blackness*=0.01*percent_blackness;
3145 whiteness*=0.01*percent_whiteness;
3146 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3147}
3148
3149MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3150{
3151#define ModulateImageTag "Modulate/Image"
3152
cristyc4c8d132010-01-07 01:58:38 +00003153 CacheView
3154 *image_view;
3155
cristy3ed852e2009-09-05 21:47:34 +00003156 ColorspaceType
3157 colorspace;
3158
3159 const char
3160 *artifact;
3161
3162 double
3163 percent_brightness,
3164 percent_hue,
3165 percent_saturation;
3166
3167 ExceptionInfo
3168 *exception;
3169
3170 GeometryInfo
3171 geometry_info;
3172
3173 long
3174 progress,
3175 y;
3176
3177 MagickBooleanType
3178 status;
3179
3180 MagickStatusType
3181 flags;
3182
3183 register long
3184 i;
3185
cristy3ed852e2009-09-05 21:47:34 +00003186 /*
cristy2b726bd2010-01-11 01:05:39 +00003187 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003188 */
3189 assert(image != (Image *) NULL);
3190 assert(image->signature == MagickSignature);
3191 if (image->debug != MagickFalse)
3192 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3193 if (modulate == (char *) NULL)
3194 return(MagickFalse);
3195 flags=ParseGeometry(modulate,&geometry_info);
3196 percent_brightness=geometry_info.rho;
3197 percent_saturation=geometry_info.sigma;
3198 if ((flags & SigmaValue) == 0)
3199 percent_saturation=100.0;
3200 percent_hue=geometry_info.xi;
3201 if ((flags & XiValue) == 0)
3202 percent_hue=100.0;
3203 colorspace=UndefinedColorspace;
3204 artifact=GetImageArtifact(image,"modulate:colorspace");
3205 if (artifact != (const char *) NULL)
3206 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3207 MagickFalse,artifact);
3208 if (image->storage_class == PseudoClass)
3209 {
3210 /*
3211 Modulate colormap.
3212 */
cristyb5d5f722009-11-04 03:03:49 +00003213#if defined(MAGICKCORE_OPENMP_SUPPORT)
3214 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003215#endif
3216 for (i=0; i < (long) image->colors; i++)
3217 switch (colorspace)
3218 {
3219 case HSBColorspace:
3220 {
3221 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3222 &image->colormap[i].red,&image->colormap[i].green,
3223 &image->colormap[i].blue);
3224 break;
3225 }
3226 case HSLColorspace:
3227 default:
3228 {
3229 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3230 &image->colormap[i].red,&image->colormap[i].green,
3231 &image->colormap[i].blue);
3232 break;
3233 }
3234 case HWBColorspace:
3235 {
3236 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3237 &image->colormap[i].red,&image->colormap[i].green,
3238 &image->colormap[i].blue);
3239 break;
3240 }
3241 }
3242 }
3243 /*
3244 Modulate image.
3245 */
3246 status=MagickTrue;
3247 progress=0;
3248 exception=(&image->exception);
3249 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003250#if defined(MAGICKCORE_OPENMP_SUPPORT)
3251 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003252#endif
3253 for (y=0; y < (long) image->rows; y++)
3254 {
3255 register long
3256 x;
3257
3258 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003259 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003260
3261 if (status == MagickFalse)
3262 continue;
3263 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3264 if (q == (PixelPacket *) NULL)
3265 {
3266 status=MagickFalse;
3267 continue;
3268 }
3269 for (x=0; x < (long) image->columns; x++)
3270 {
3271 switch (colorspace)
3272 {
3273 case HSBColorspace:
3274 {
3275 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3276 &q->red,&q->green,&q->blue);
3277 break;
3278 }
3279 case HSLColorspace:
3280 default:
3281 {
3282 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3283 &q->red,&q->green,&q->blue);
3284 break;
3285 }
3286 case HWBColorspace:
3287 {
3288 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3289 &q->red,&q->green,&q->blue);
3290 break;
3291 }
3292 }
3293 q++;
3294 }
3295 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3296 status=MagickFalse;
3297 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3298 {
3299 MagickBooleanType
3300 proceed;
3301
cristyb5d5f722009-11-04 03:03:49 +00003302#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003303 #pragma omp critical (MagickCore_ModulateImage)
3304#endif
3305 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3306 if (proceed == MagickFalse)
3307 status=MagickFalse;
3308 }
3309 }
3310 image_view=DestroyCacheView(image_view);
3311 return(status);
3312}
3313
3314/*
3315%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3316% %
3317% %
3318% %
3319% N e g a t e I m a g e %
3320% %
3321% %
3322% %
3323%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3324%
3325% NegateImage() negates the colors in the reference image. The grayscale
3326% option means that only grayscale values within the image are negated.
3327%
3328% The format of the NegateImageChannel method is:
3329%
3330% MagickBooleanType NegateImage(Image *image,
3331% const MagickBooleanType grayscale)
3332% MagickBooleanType NegateImageChannel(Image *image,
3333% const ChannelType channel,const MagickBooleanType grayscale)
3334%
3335% A description of each parameter follows:
3336%
3337% o image: the image.
3338%
3339% o channel: the channel.
3340%
3341% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3342%
3343*/
3344
3345MagickExport MagickBooleanType NegateImage(Image *image,
3346 const MagickBooleanType grayscale)
3347{
3348 MagickBooleanType
3349 status;
3350
3351 status=NegateImageChannel(image,DefaultChannels,grayscale);
3352 return(status);
3353}
3354
3355MagickExport MagickBooleanType NegateImageChannel(Image *image,
3356 const ChannelType channel,const MagickBooleanType grayscale)
3357{
3358#define NegateImageTag "Negate/Image"
3359
cristyc4c8d132010-01-07 01:58:38 +00003360 CacheView
3361 *image_view;
3362
cristy3ed852e2009-09-05 21:47:34 +00003363 ExceptionInfo
3364 *exception;
3365
3366 long
3367 progress,
3368 y;
3369
3370 MagickBooleanType
3371 status;
3372
3373 register long
3374 i;
3375
cristy3ed852e2009-09-05 21:47:34 +00003376 assert(image != (Image *) NULL);
3377 assert(image->signature == MagickSignature);
3378 if (image->debug != MagickFalse)
3379 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3380 if (image->storage_class == PseudoClass)
3381 {
3382 /*
3383 Negate colormap.
3384 */
cristyb5d5f722009-11-04 03:03:49 +00003385#if defined(MAGICKCORE_OPENMP_SUPPORT)
3386 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003387#endif
3388 for (i=0; i < (long) image->colors; i++)
3389 {
3390 if (grayscale != MagickFalse)
3391 if ((image->colormap[i].red != image->colormap[i].green) ||
3392 (image->colormap[i].green != image->colormap[i].blue))
3393 continue;
3394 if ((channel & RedChannel) != 0)
3395 image->colormap[i].red=(Quantum) QuantumRange-
3396 image->colormap[i].red;
3397 if ((channel & GreenChannel) != 0)
3398 image->colormap[i].green=(Quantum) QuantumRange-
3399 image->colormap[i].green;
3400 if ((channel & BlueChannel) != 0)
3401 image->colormap[i].blue=(Quantum) QuantumRange-
3402 image->colormap[i].blue;
3403 }
3404 }
3405 /*
3406 Negate image.
3407 */
3408 status=MagickTrue;
3409 progress=0;
3410 exception=(&image->exception);
3411 image_view=AcquireCacheView(image);
3412 if (grayscale != MagickFalse)
3413 {
cristyb5d5f722009-11-04 03:03:49 +00003414#if defined(MAGICKCORE_OPENMP_SUPPORT)
3415 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003416#endif
3417 for (y=0; y < (long) image->rows; y++)
3418 {
3419 MagickBooleanType
3420 sync;
3421
3422 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003423 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003424
3425 register long
3426 x;
3427
3428 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003429 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003430
3431 if (status == MagickFalse)
3432 continue;
3433 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3434 exception);
3435 if (q == (PixelPacket *) NULL)
3436 {
3437 status=MagickFalse;
3438 continue;
3439 }
3440 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3441 for (x=0; x < (long) image->columns; x++)
3442 {
3443 if ((q->red != q->green) || (q->green != q->blue))
3444 {
3445 q++;
3446 continue;
3447 }
3448 if ((channel & RedChannel) != 0)
3449 q->red=(Quantum) QuantumRange-q->red;
3450 if ((channel & GreenChannel) != 0)
3451 q->green=(Quantum) QuantumRange-q->green;
3452 if ((channel & BlueChannel) != 0)
3453 q->blue=(Quantum) QuantumRange-q->blue;
3454 if ((channel & OpacityChannel) != 0)
3455 q->opacity=(Quantum) QuantumRange-q->opacity;
3456 if (((channel & IndexChannel) != 0) &&
3457 (image->colorspace == CMYKColorspace))
3458 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3459 q++;
3460 }
3461 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3462 if (sync == MagickFalse)
3463 status=MagickFalse;
3464 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3465 {
3466 MagickBooleanType
3467 proceed;
3468
cristyb5d5f722009-11-04 03:03:49 +00003469#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003470 #pragma omp critical (MagickCore_NegateImageChannel)
3471#endif
3472 proceed=SetImageProgress(image,NegateImageTag,progress++,
3473 image->rows);
3474 if (proceed == MagickFalse)
3475 status=MagickFalse;
3476 }
3477 }
3478 image_view=DestroyCacheView(image_view);
3479 return(MagickTrue);
3480 }
3481 /*
3482 Negate image.
3483 */
cristyb5d5f722009-11-04 03:03:49 +00003484#if defined(MAGICKCORE_OPENMP_SUPPORT)
3485 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003486#endif
3487 for (y=0; y < (long) image->rows; y++)
3488 {
3489 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003490 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003491
3492 register long
3493 x;
3494
3495 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003496 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003497
3498 if (status == MagickFalse)
3499 continue;
3500 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3501 if (q == (PixelPacket *) NULL)
3502 {
3503 status=MagickFalse;
3504 continue;
3505 }
3506 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3507 for (x=0; x < (long) image->columns; x++)
3508 {
3509 if ((channel & RedChannel) != 0)
3510 q->red=(Quantum) QuantumRange-q->red;
3511 if ((channel & GreenChannel) != 0)
3512 q->green=(Quantum) QuantumRange-q->green;
3513 if ((channel & BlueChannel) != 0)
3514 q->blue=(Quantum) QuantumRange-q->blue;
3515 if ((channel & OpacityChannel) != 0)
3516 q->opacity=(Quantum) QuantumRange-q->opacity;
3517 if (((channel & IndexChannel) != 0) &&
3518 (image->colorspace == CMYKColorspace))
3519 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3520 q++;
3521 }
3522 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3523 status=MagickFalse;
3524 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3525 {
3526 MagickBooleanType
3527 proceed;
3528
cristyb5d5f722009-11-04 03:03:49 +00003529#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003530 #pragma omp critical (MagickCore_NegateImageChannel)
3531#endif
3532 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3533 if (proceed == MagickFalse)
3534 status=MagickFalse;
3535 }
3536 }
3537 image_view=DestroyCacheView(image_view);
3538 return(status);
3539}
3540
3541/*
3542%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3543% %
3544% %
3545% %
3546% N o r m a l i z e I m a g e %
3547% %
3548% %
3549% %
3550%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3551%
3552% The NormalizeImage() method enhances the contrast of a color image by
3553% mapping the darkest 2 percent of all pixel to black and the brightest
3554% 1 percent to white.
3555%
3556% The format of the NormalizeImage method is:
3557%
3558% MagickBooleanType NormalizeImage(Image *image)
3559% MagickBooleanType NormalizeImageChannel(Image *image,
3560% const ChannelType channel)
3561%
3562% A description of each parameter follows:
3563%
3564% o image: the image.
3565%
3566% o channel: the channel.
3567%
3568*/
3569
3570MagickExport MagickBooleanType NormalizeImage(Image *image)
3571{
3572 MagickBooleanType
3573 status;
3574
3575 status=NormalizeImageChannel(image,DefaultChannels);
3576 return(status);
3577}
3578
3579MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3580 const ChannelType channel)
3581{
3582 double
3583 black_point,
3584 white_point;
3585
3586 black_point=(double) image->columns*image->rows*0.02;
3587 white_point=(double) image->columns*image->rows*0.99;
3588 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3589}
3590
3591/*
3592%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3593% %
3594% %
3595% %
3596% S i g m o i d a l C o n t r a s t I m a g e %
3597% %
3598% %
3599% %
3600%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3601%
3602% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3603% sigmoidal contrast algorithm. Increase the contrast of the image using a
3604% sigmoidal transfer function without saturating highlights or shadows.
3605% Contrast indicates how much to increase the contrast (0 is none; 3 is
3606% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3607% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3608% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3609% is reduced.
3610%
3611% The format of the SigmoidalContrastImage method is:
3612%
3613% MagickBooleanType SigmoidalContrastImage(Image *image,
3614% const MagickBooleanType sharpen,const char *levels)
3615% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3616% const ChannelType channel,const MagickBooleanType sharpen,
3617% const double contrast,const double midpoint)
3618%
3619% A description of each parameter follows:
3620%
3621% o image: the image.
3622%
3623% o channel: the channel.
3624%
3625% o sharpen: Increase or decrease image contrast.
3626%
3627% o contrast: control the "shoulder" of the contast curve.
3628%
3629% o midpoint: control the "toe" of the contast curve.
3630%
3631*/
3632
3633MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3634 const MagickBooleanType sharpen,const char *levels)
3635{
3636 GeometryInfo
3637 geometry_info;
3638
3639 MagickBooleanType
3640 status;
3641
3642 MagickStatusType
3643 flags;
3644
3645 flags=ParseGeometry(levels,&geometry_info);
3646 if ((flags & SigmaValue) == 0)
3647 geometry_info.sigma=1.0*QuantumRange/2.0;
3648 if ((flags & PercentValue) != 0)
3649 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3650 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3651 geometry_info.rho,geometry_info.sigma);
3652 return(status);
3653}
3654
3655MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3656 const ChannelType channel,const MagickBooleanType sharpen,
3657 const double contrast,const double midpoint)
3658{
3659#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3660
cristyc4c8d132010-01-07 01:58:38 +00003661 CacheView
3662 *image_view;
3663
cristy3ed852e2009-09-05 21:47:34 +00003664 ExceptionInfo
3665 *exception;
3666
3667 long
3668 progress,
3669 y;
3670
3671 MagickBooleanType
3672 status;
3673
3674 MagickRealType
3675 *sigmoidal_map;
3676
3677 register long
3678 i;
3679
cristy3ed852e2009-09-05 21:47:34 +00003680 /*
3681 Allocate and initialize sigmoidal maps.
3682 */
3683 assert(image != (Image *) NULL);
3684 assert(image->signature == MagickSignature);
3685 if (image->debug != MagickFalse)
3686 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3687 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3688 sizeof(*sigmoidal_map));
3689 if (sigmoidal_map == (MagickRealType *) NULL)
3690 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3691 image->filename);
3692 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003693#if defined(MAGICKCORE_OPENMP_SUPPORT)
3694 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003695#endif
3696 for (i=0; i <= (long) MaxMap; i++)
3697 {
3698 if (sharpen != MagickFalse)
3699 {
3700 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3701 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3702 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3703 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3704 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3705 (double) QuantumRange)))))+0.5));
3706 continue;
3707 }
3708 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3709 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3710 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3711 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3712 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3713 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3714 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3715 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3716 (double) QuantumRange*contrast))))))/contrast)));
3717 }
3718 if (image->storage_class == PseudoClass)
3719 {
3720 /*
3721 Sigmoidal-contrast enhance colormap.
3722 */
cristyb5d5f722009-11-04 03:03:49 +00003723#if defined(MAGICKCORE_OPENMP_SUPPORT)
3724 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003725#endif
3726 for (i=0; i < (long) image->colors; i++)
3727 {
3728 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003729 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003730 ScaleQuantumToMap(image->colormap[i].red)]);
3731 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003732 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003733 ScaleQuantumToMap(image->colormap[i].green)]);
3734 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003735 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003736 ScaleQuantumToMap(image->colormap[i].blue)]);
3737 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003738 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003739 ScaleQuantumToMap(image->colormap[i].opacity)]);
3740 }
3741 }
3742 /*
3743 Sigmoidal-contrast enhance image.
3744 */
3745 status=MagickTrue;
3746 progress=0;
3747 exception=(&image->exception);
3748 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003749#if defined(MAGICKCORE_OPENMP_SUPPORT)
3750 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003751#endif
3752 for (y=0; y < (long) image->rows; y++)
3753 {
3754 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003755 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003756
3757 register long
3758 x;
3759
3760 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003761 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003762
3763 if (status == MagickFalse)
3764 continue;
3765 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3766 if (q == (PixelPacket *) NULL)
3767 {
3768 status=MagickFalse;
3769 continue;
3770 }
3771 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3772 for (x=0; x < (long) image->columns; x++)
3773 {
3774 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003775 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003776 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003777 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003778 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003779 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003780 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003781 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003782 if (((channel & IndexChannel) != 0) &&
3783 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003784 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003785 ScaleQuantumToMap(indexes[x])]);
3786 q++;
3787 }
3788 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3789 status=MagickFalse;
3790 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3791 {
3792 MagickBooleanType
3793 proceed;
3794
cristyb5d5f722009-11-04 03:03:49 +00003795#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003796 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3797#endif
3798 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3799 image->rows);
3800 if (proceed == MagickFalse)
3801 status=MagickFalse;
3802 }
3803 }
3804 image_view=DestroyCacheView(image_view);
3805 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3806 return(status);
3807}