blob: 20d8ca5f625f23eb90b0e94c5ec9b2071a5a11e5 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/composite-private.h"
51#include "magick/enhance.h"
52#include "magick/exception.h"
53#include "magick/exception-private.h"
cristya28d6b82010-01-11 20:03:47 +000054#include "magick/fx.h"
cristy3ed852e2009-09-05 21:47:34 +000055#include "magick/gem.h"
56#include "magick/geometry.h"
57#include "magick/histogram.h"
58#include "magick/image.h"
59#include "magick/image-private.h"
60#include "magick/memory_.h"
61#include "magick/monitor.h"
62#include "magick/monitor-private.h"
63#include "magick/option.h"
64#include "magick/quantum.h"
65#include "magick/quantum-private.h"
66#include "magick/resample.h"
67#include "magick/resample-private.h"
68#include "magick/statistic.h"
69#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000070#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000071#include "magick/thread-private.h"
72#include "magick/token.h"
73#include "magick/xml-tree.h"
74
75/*
76%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77% %
78% %
79% %
80% A u t o G a m m a I m a g e %
81% %
82% %
83% %
84%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85%
86% AutoGammaImage() extract the 'mean' from the image and adjust the image
87% to try make set its gamma appropriatally.
88%
cristy308b4e62009-09-21 14:40:44 +000089% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000090%
91% MagickBooleanType AutoGammaImage(Image *image)
92% MagickBooleanType AutoGammaImageChannel(Image *image,
93% const ChannelType channel)
94%
95% A description of each parameter follows:
96%
97% o image: The image to auto-level
98%
99% o channel: The channels to auto-level. If the special 'SyncChannels'
100% flag is set all given channels is adjusted in the same way using the
101% mean average of those channels.
102%
103*/
104
105MagickExport MagickBooleanType AutoGammaImage(Image *image)
106{
107 return(AutoGammaImageChannel(image,DefaultChannels));
108}
109
110MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
111 const ChannelType channel)
112{
113 MagickStatusType
114 status;
115
116 double
cristy2b726bd2010-01-11 01:05:39 +0000117 mean,sans,gamma,logmean;
anthony4efe5972009-09-11 06:46:40 +0000118
119 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000120
121 if ((channel & SyncChannels) != 0 )
122 {
123 /*
124 Apply gamma correction equally accross all given channels
125 */
cristy2b726bd2010-01-11 01:05:39 +0000126 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
127 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000128 return LevelImageChannel(image, channel,
129 0.0, (double)QuantumRange, gamma);
130 }
131
132 /*
133 auto-gamma each channel separateally
134 */
135 status = MagickTrue;
136 if ((channel & RedChannel) != 0)
137 {
cristy2b726bd2010-01-11 01:05:39 +0000138 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
139 &image->exception);
140 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000141 status = status && LevelImageChannel(image, RedChannel,
142 0.0, (double)QuantumRange, gamma);
143 }
144 if ((channel & GreenChannel) != 0)
145 {
cristy2b726bd2010-01-11 01:05:39 +0000146 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
147 &image->exception);
148 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000149 status = status && LevelImageChannel(image, GreenChannel,
150 0.0, (double)QuantumRange, gamma);
151 }
152 if ((channel & BlueChannel) != 0)
153 {
cristy2b726bd2010-01-11 01:05:39 +0000154 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
155 &image->exception);
156 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000157 status = status && LevelImageChannel(image, BlueChannel,
158 0.0, (double)QuantumRange, gamma);
159 }
160 if (((channel & OpacityChannel) != 0) &&
161 (image->matte == MagickTrue))
162 {
cristy2b726bd2010-01-11 01:05:39 +0000163 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
164 &image->exception);
165 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000166 status = status && LevelImageChannel(image, OpacityChannel,
167 0.0, (double)QuantumRange, gamma);
168 }
169 if (((channel & IndexChannel) != 0) &&
170 (image->colorspace == CMYKColorspace))
171 {
cristy2b726bd2010-01-11 01:05:39 +0000172 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
173 &image->exception);
174 gamma=log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000175 status = status && LevelImageChannel(image, IndexChannel,
176 0.0, (double)QuantumRange, gamma);
177 }
178 return(status != 0 ? MagickTrue : MagickFalse);
179}
180
181/*
182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
183% %
184% %
185% %
186% A u t o L e v e l I m a g e %
187% %
188% %
189% %
190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191%
192% AutoLevelImage() adjusts the levels of a particular image channel by
193% scaling the minimum and maximum values to the full quantum range.
194%
195% The format of the LevelImage method is:
196%
197% MagickBooleanType AutoLevelImage(Image *image)
198% MagickBooleanType AutoLevelImageChannel(Image *image,
199% const ChannelType channel)
200%
201% A description of each parameter follows:
202%
203% o image: The image to auto-level
204%
205% o channel: The channels to auto-level. If the special 'SyncChannels'
206% flag is set the min/max/mean value of all given channels is used for
207% all given channels, to all channels in the same way.
208%
209*/
210
211MagickExport MagickBooleanType AutoLevelImage(Image *image)
212{
213 return(AutoLevelImageChannel(image,DefaultChannels));
214}
215
216MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
217 const ChannelType channel)
218{
219 /*
220 This is simply a convenience function around a Min/Max Histogram Stretch
221 */
222 return MinMaxStretchImage(image, channel, 0.0, 0.0);
223}
224
225/*
226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
227% %
228% %
229% %
cristya28d6b82010-01-11 20:03:47 +0000230% B r i g h t n e s s C o n t r a s t I m a g e %
231% %
232% %
233% %
234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
235%
236% Use BrightnessContrastImage() to change the brightness and/or contrast of
237% an image. It converts the brightness and contrast parameters into slope
238% and intercept and calls a polynomical function to apply to the image.
239%
240% The format of the BrightnessContrastImage method is:
241%
242% MagickBooleanType BrightnessContrastImage(Image *image,
243% const double brightness,const double contrast)
244% MagickBooleanType BrightnessContrastImageChannel(Image *image,
245% const ChannelType channel,const double brightness,
246% const double contrast)
247%
248% A description of each parameter follows:
249%
250% o image: the image.
251%
252% o channel: the channel.
253%
254% o brightness: the brightness percent (-100 .. 100).
255%
256% o contrast: the contrast percent (-100 .. 100).
257%
258*/
259
260MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
261 const double brightness,const double contrast)
262{
263 MagickBooleanType
264 status;
265
266 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
267 contrast);
268 return(status);
269}
270
271MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
272 const ChannelType channel,const double brightness,const double contrast)
273{
274#define BrightnessContastImageTag "BrightnessContast/Image"
275
276 double
277 alpha,
278 intercept,
279 coefficients[2],
280 slope;
281
282 MagickBooleanType
283 status;
284
285 /*
286 Compute slope and intercept.
287 */
288 assert(image != (Image *) NULL);
289 assert(image->signature == MagickSignature);
290 if (image->debug != MagickFalse)
291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
292 alpha=contrast;
cristyca6da3e2010-03-26 01:27:47 +0000293 slope=tan(MagickPI*(alpha/100.0+1.0)/4.0);
cristya28d6b82010-01-11 20:03:47 +0000294 if (slope < 0.0)
295 slope=0.0;
296 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
297 coefficients[0]=slope;
298 coefficients[1]=intercept;
299 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
300 &image->exception);
301 return(status);
302}
303
304/*
305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
306% %
307% %
308% %
cristy3ed852e2009-09-05 21:47:34 +0000309% C o l o r D e c i s i o n L i s t I m a g e %
310% %
311% %
312% %
313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
314%
315% ColorDecisionListImage() accepts a lightweight Color Correction Collection
316% (CCC) file which solely contains one or more color corrections and applies
317% the correction to the image. Here is a sample CCC file:
318%
319% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
320% <ColorCorrection id="cc03345">
321% <SOPNode>
322% <Slope> 0.9 1.2 0.5 </Slope>
323% <Offset> 0.4 -0.5 0.6 </Offset>
324% <Power> 1.0 0.8 1.5 </Power>
325% </SOPNode>
326% <SATNode>
327% <Saturation> 0.85 </Saturation>
328% </SATNode>
329% </ColorCorrection>
330% </ColorCorrectionCollection>
331%
332% which includes the slop, offset, and power for each of the RGB channels
333% as well as the saturation.
334%
335% The format of the ColorDecisionListImage method is:
336%
337% MagickBooleanType ColorDecisionListImage(Image *image,
338% const char *color_correction_collection)
339%
340% A description of each parameter follows:
341%
342% o image: the image.
343%
344% o color_correction_collection: the color correction collection in XML.
345%
346*/
347MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
348 const char *color_correction_collection)
349{
350#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
351
352 typedef struct _Correction
353 {
354 double
355 slope,
356 offset,
357 power;
358 } Correction;
359
360 typedef struct _ColorCorrection
361 {
362 Correction
363 red,
364 green,
365 blue;
366
367 double
368 saturation;
369 } ColorCorrection;
370
cristyc4c8d132010-01-07 01:58:38 +0000371 CacheView
372 *image_view;
373
cristy3ed852e2009-09-05 21:47:34 +0000374 char
375 token[MaxTextExtent];
376
377 ColorCorrection
378 color_correction;
379
380 const char
381 *content,
382 *p;
383
384 ExceptionInfo
385 *exception;
386
387 long
388 progress,
389 y;
390
391 MagickBooleanType
392 status;
393
394 PixelPacket
395 *cdl_map;
396
397 register long
398 i;
399
400 XMLTreeInfo
401 *cc,
402 *ccc,
403 *sat,
404 *sop;
405
cristy3ed852e2009-09-05 21:47:34 +0000406 /*
407 Allocate and initialize cdl maps.
408 */
409 assert(image != (Image *) NULL);
410 assert(image->signature == MagickSignature);
411 if (image->debug != MagickFalse)
412 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
413 if (color_correction_collection == (const char *) NULL)
414 return(MagickFalse);
415 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
416 if (ccc == (XMLTreeInfo *) NULL)
417 return(MagickFalse);
418 cc=GetXMLTreeChild(ccc,"ColorCorrection");
419 if (cc == (XMLTreeInfo *) NULL)
420 {
421 ccc=DestroyXMLTree(ccc);
422 return(MagickFalse);
423 }
424 color_correction.red.slope=1.0;
425 color_correction.red.offset=0.0;
426 color_correction.red.power=1.0;
427 color_correction.green.slope=1.0;
428 color_correction.green.offset=0.0;
429 color_correction.green.power=1.0;
430 color_correction.blue.slope=1.0;
431 color_correction.blue.offset=0.0;
432 color_correction.blue.power=1.0;
433 color_correction.saturation=0.0;
434 sop=GetXMLTreeChild(cc,"SOPNode");
435 if (sop != (XMLTreeInfo *) NULL)
436 {
437 XMLTreeInfo
438 *offset,
439 *power,
440 *slope;
441
442 slope=GetXMLTreeChild(sop,"Slope");
443 if (slope != (XMLTreeInfo *) NULL)
444 {
445 content=GetXMLTreeContent(slope);
446 p=(const char *) content;
447 for (i=0; (*p != '\0') && (i < 3); i++)
448 {
449 GetMagickToken(p,&p,token);
450 if (*token == ',')
451 GetMagickToken(p,&p,token);
452 switch (i)
453 {
cristyf2f27272009-12-17 14:48:46 +0000454 case 0: color_correction.red.slope=StringToDouble(token); break;
455 case 1: color_correction.green.slope=StringToDouble(token); break;
456 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000457 }
458 }
459 }
460 offset=GetXMLTreeChild(sop,"Offset");
461 if (offset != (XMLTreeInfo *) NULL)
462 {
463 content=GetXMLTreeContent(offset);
464 p=(const char *) content;
465 for (i=0; (*p != '\0') && (i < 3); i++)
466 {
467 GetMagickToken(p,&p,token);
468 if (*token == ',')
469 GetMagickToken(p,&p,token);
470 switch (i)
471 {
cristyf2f27272009-12-17 14:48:46 +0000472 case 0: color_correction.red.offset=StringToDouble(token); break;
473 case 1: color_correction.green.offset=StringToDouble(token); break;
474 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000475 }
476 }
477 }
478 power=GetXMLTreeChild(sop,"Power");
479 if (power != (XMLTreeInfo *) NULL)
480 {
481 content=GetXMLTreeContent(power);
482 p=(const char *) content;
483 for (i=0; (*p != '\0') && (i < 3); i++)
484 {
485 GetMagickToken(p,&p,token);
486 if (*token == ',')
487 GetMagickToken(p,&p,token);
488 switch (i)
489 {
cristyf2f27272009-12-17 14:48:46 +0000490 case 0: color_correction.red.power=StringToDouble(token); break;
491 case 1: color_correction.green.power=StringToDouble(token); break;
492 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000493 }
494 }
495 }
496 }
497 sat=GetXMLTreeChild(cc,"SATNode");
498 if (sat != (XMLTreeInfo *) NULL)
499 {
500 XMLTreeInfo
501 *saturation;
502
503 saturation=GetXMLTreeChild(sat,"Saturation");
504 if (saturation != (XMLTreeInfo *) NULL)
505 {
506 content=GetXMLTreeContent(saturation);
507 p=(const char *) content;
508 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000509 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000510 }
511 }
512 ccc=DestroyXMLTree(ccc);
513 if (image->debug != MagickFalse)
514 {
515 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
516 " Color Correction Collection:");
517 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000518 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000519 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000520 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000521 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000522 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000523 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000524 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000525 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000526 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000527 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000528 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000529 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000530 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000531 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000532 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000533 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000534 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000535 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000536 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000537 }
538 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
539 if (cdl_map == (PixelPacket *) NULL)
540 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
541 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000542#if defined(MAGICKCORE_OPENMP_SUPPORT)
543 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000544#endif
545 for (i=0; i <= (long) MaxMap; i++)
546 {
cristyce70c172010-01-07 17:15:30 +0000547 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000548 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
549 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000550 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000551 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
552 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000553 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000554 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
555 color_correction.blue.offset,color_correction.blue.power)))));
556 }
557 if (image->storage_class == PseudoClass)
558 {
559 /*
560 Apply transfer function to colormap.
561 */
cristyb5d5f722009-11-04 03:03:49 +0000562#if defined(MAGICKCORE_OPENMP_SUPPORT)
563 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000564#endif
565 for (i=0; i < (long) image->colors; i++)
566 {
567 double
568 luma;
569
570 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
571 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000572 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000573 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000574 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000575 color_correction.saturation*cdl_map[ScaleQuantumToMap(
576 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000577 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000578 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
579 }
580 }
581 /*
582 Apply transfer function to image.
583 */
584 status=MagickTrue;
585 progress=0;
586 exception=(&image->exception);
587 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000588#if defined(MAGICKCORE_OPENMP_SUPPORT)
589 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000590#endif
591 for (y=0; y < (long) image->rows; y++)
592 {
593 double
594 luma;
595
596 register long
597 x;
598
599 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000600 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000601
602 if (status == MagickFalse)
603 continue;
604 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
605 if (q == (PixelPacket *) NULL)
606 {
607 status=MagickFalse;
608 continue;
609 }
610 for (x=0; x < (long) image->columns; x++)
611 {
612 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000613 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000614 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000615 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000616 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000617 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000618 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
619 q++;
620 }
621 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
622 status=MagickFalse;
623 if (image->progress_monitor != (MagickProgressMonitor) NULL)
624 {
625 MagickBooleanType
626 proceed;
627
cristyb5d5f722009-11-04 03:03:49 +0000628#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000629 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
630#endif
631 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
632 progress++,image->rows);
633 if (proceed == MagickFalse)
634 status=MagickFalse;
635 }
636 }
637 image_view=DestroyCacheView(image_view);
638 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
639 return(status);
640}
641
642/*
643%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
644% %
645% %
646% %
647% C l u t I m a g e %
648% %
649% %
650% %
651%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
652%
653% ClutImage() replaces each color value in the given image, by using it as an
654% index to lookup a replacement color value in a Color Look UP Table in the
655% form of an image. The values are extracted along a diagonal of the CLUT
656% image so either a horizontal or vertial gradient image can be used.
657%
658% Typically this is used to either re-color a gray-scale image according to a
659% color gradient in the CLUT image, or to perform a freeform histogram
660% (level) adjustment according to the (typically gray-scale) gradient in the
661% CLUT image.
662%
663% When the 'channel' mask includes the matte/alpha transparency channel but
664% one image has no such channel it is assumed that that image is a simple
665% gray-scale image that will effect the alpha channel values, either for
666% gray-scale coloring (with transparent or semi-transparent colors), or
667% a histogram adjustment of existing alpha channel values. If both images
668% have matte channels, direct and normal indexing is applied, which is rarely
669% used.
670%
671% The format of the ClutImage method is:
672%
673% MagickBooleanType ClutImage(Image *image,Image *clut_image)
674% MagickBooleanType ClutImageChannel(Image *image,
675% const ChannelType channel,Image *clut_image)
676%
677% A description of each parameter follows:
678%
679% o image: the image, which is replaced by indexed CLUT values
680%
681% o clut_image: the color lookup table image for replacement color values.
682%
683% o channel: the channel.
684%
685*/
686
687MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
688{
689 return(ClutImageChannel(image,DefaultChannels,clut_image));
690}
691
692MagickExport MagickBooleanType ClutImageChannel(Image *image,
693 const ChannelType channel,const Image *clut_image)
694{
695#define ClutImageTag "Clut/Image"
696
cristyfa112112010-01-04 17:48:07 +0000697 CacheView
698 *image_view;
699
cristy3ed852e2009-09-05 21:47:34 +0000700 ExceptionInfo
701 *exception;
702
703 long
704 adjust,
705 progress,
706 y;
707
708 MagickBooleanType
709 status;
710
711 MagickPixelPacket
712 zero;
713
714 ResampleFilter
cristyfa112112010-01-04 17:48:07 +0000715 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +0000716
717 assert(image != (Image *) NULL);
718 assert(image->signature == MagickSignature);
719 if (image->debug != MagickFalse)
720 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
721 assert(clut_image != (Image *) NULL);
722 assert(clut_image->signature == MagickSignature);
723 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
724 return(MagickFalse);
725 /*
726 Clut image.
727 */
728 status=MagickTrue;
729 progress=0;
730 GetMagickPixelPacket(clut_image,&zero);
731 adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
732 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +0000733 resample_filter=AcquireResampleFilterThreadSet(clut_image,
734 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000735 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000736#if defined(MAGICKCORE_OPENMP_SUPPORT)
737 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000738#endif
739 for (y=0; y < (long) image->rows; y++)
740 {
741 MagickPixelPacket
742 pixel;
743
744 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000745 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000746
747 register long
748 id,
749 x;
750
751 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000752 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000753
754 if (status == MagickFalse)
755 continue;
756 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
757 if (q == (PixelPacket *) NULL)
758 {
759 status=MagickFalse;
760 continue;
761 }
762 indexes=GetCacheViewAuthenticIndexQueue(image_view);
763 pixel=zero;
764 id=GetOpenMPThreadId();
765 for (x=0; x < (long) image->columns; x++)
766 {
767 /*
768 PROGRAMMERS WARNING:
769
770 Apply OpacityChannel BEFORE the color channels. Do not re-order.
771
772 The handling special case 2 (coloring gray-scale), requires access to
773 the unmodified colors of the original image to determine the index
774 value. As such alpha/matte channel handling must be performed BEFORE,
775 any of the color channels are modified.
776
777 */
778 if ((channel & OpacityChannel) != 0)
779 {
780 if (clut_image->matte == MagickFalse)
781 {
782 /*
783 A gray-scale LUT replacement for an image alpha channel.
784 */
785 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
cristy46f08202010-01-10 04:04:21 +0000786 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
787 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
cristy3ed852e2009-09-05 21:47:34 +0000788 adjust),&pixel);
789 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
790 &pixel));
791 }
792 else
793 if (image->matte == MagickFalse)
794 {
795 /*
796 A greyscale image being colored by a LUT with transparency.
797 */
798 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
799 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
800 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000801 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000802 }
803 else
804 {
805 /*
806 Direct alpha channel lookup.
807 */
808 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
809 q->opacity*(clut_image->columns-adjust),QuantumScale*
810 q->opacity* (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000811 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000812 }
813 }
814 if ((channel & RedChannel) != 0)
815 {
816 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
817 (clut_image->columns-adjust),QuantumScale*q->red*
818 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000819 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000820 }
821 if ((channel & GreenChannel) != 0)
822 {
823 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
824 (clut_image->columns-adjust),QuantumScale*q->green*
825 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000826 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000827 }
828 if ((channel & BlueChannel) != 0)
829 {
830 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
831 (clut_image->columns-adjust),QuantumScale*q->blue*
832 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000833 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000834 }
835 if (((channel & IndexChannel) != 0) &&
836 (image->colorspace == CMYKColorspace))
837 {
838 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
839 (clut_image->columns-adjust),QuantumScale*indexes[x]*
840 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000841 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000842 }
843 q++;
844 }
845 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
846 status=MagickFalse;
847 if (image->progress_monitor != (MagickProgressMonitor) NULL)
848 {
849 MagickBooleanType
850 proceed;
851
cristyb5d5f722009-11-04 03:03:49 +0000852#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000853 #pragma omp critical (MagickCore_ClutImageChannel)
854#endif
855 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
856 if (proceed == MagickFalse)
857 status=MagickFalse;
858 }
859 }
860 image_view=DestroyCacheView(image_view);
861 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
862 /*
863 Enable alpha channel if CLUT image could enable it.
864 */
865 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
866 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
867 return(status);
868}
869
870/*
871%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
872% %
873% %
874% %
875% C o n t r a s t I m a g e %
876% %
877% %
878% %
879%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
880%
881% ContrastImage() enhances the intensity differences between the lighter and
882% darker elements of the image. Set sharpen to a MagickTrue to increase the
883% image contrast otherwise the contrast is reduced.
884%
885% The format of the ContrastImage method is:
886%
887% MagickBooleanType ContrastImage(Image *image,
888% const MagickBooleanType sharpen)
889%
890% A description of each parameter follows:
891%
892% o image: the image.
893%
894% o sharpen: Increase or decrease image contrast.
895%
896*/
897
898static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
899{
900 double
901 brightness,
902 hue,
903 saturation;
904
905 /*
906 Enhance contrast: dark color become darker, light color become lighter.
907 */
908 assert(red != (Quantum *) NULL);
909 assert(green != (Quantum *) NULL);
910 assert(blue != (Quantum *) NULL);
911 hue=0.0;
912 saturation=0.0;
913 brightness=0.0;
914 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
915 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
916 if (brightness > 1.0)
917 brightness=1.0;
918 else
919 if (brightness < 0.0)
920 brightness=0.0;
921 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
922}
923
924MagickExport MagickBooleanType ContrastImage(Image *image,
925 const MagickBooleanType sharpen)
926{
927#define ContrastImageTag "Contrast/Image"
928
cristyc4c8d132010-01-07 01:58:38 +0000929 CacheView
930 *image_view;
931
cristy3ed852e2009-09-05 21:47:34 +0000932 ExceptionInfo
933 *exception;
934
935 int
936 sign;
937
938 long
939 progress,
940 y;
941
942 MagickBooleanType
943 status;
944
945 register long
946 i;
947
cristy3ed852e2009-09-05 21:47:34 +0000948 assert(image != (Image *) NULL);
949 assert(image->signature == MagickSignature);
950 if (image->debug != MagickFalse)
951 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
952 sign=sharpen != MagickFalse ? 1 : -1;
953 if (image->storage_class == PseudoClass)
954 {
955 /*
956 Contrast enhance colormap.
957 */
958 for (i=0; i < (long) image->colors; i++)
959 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
960 &image->colormap[i].blue);
961 }
962 /*
963 Contrast enhance image.
964 */
965 status=MagickTrue;
966 progress=0;
967 exception=(&image->exception);
968 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000969#if defined(MAGICKCORE_OPENMP_SUPPORT)
970 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000971#endif
972 for (y=0; y < (long) image->rows; y++)
973 {
974 register long
975 x;
976
977 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000978 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000979
980 if (status == MagickFalse)
981 continue;
982 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
983 if (q == (PixelPacket *) NULL)
984 {
985 status=MagickFalse;
986 continue;
987 }
988 for (x=0; x < (long) image->columns; x++)
989 {
990 Contrast(sign,&q->red,&q->green,&q->blue);
991 q++;
992 }
993 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
994 status=MagickFalse;
995 if (image->progress_monitor != (MagickProgressMonitor) NULL)
996 {
997 MagickBooleanType
998 proceed;
999
cristyb5d5f722009-11-04 03:03:49 +00001000#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001001 #pragma omp critical (MagickCore_ContrastImage)
1002#endif
1003 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1004 if (proceed == MagickFalse)
1005 status=MagickFalse;
1006 }
1007 }
1008 image_view=DestroyCacheView(image_view);
1009 return(status);
1010}
1011
1012/*
1013%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1014% %
1015% %
1016% %
1017% C o n t r a s t S t r e t c h I m a g e %
1018% %
1019% %
1020% %
1021%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1022%
1023% The ContrastStretchImage() is a simple image enhancement technique that
1024% attempts to improve the contrast in an image by `stretching' the range of
1025% intensity values it contains to span a desired range of values. It differs
1026% from the more sophisticated histogram equalization in that it can only
1027% apply % a linear scaling function to the image pixel values. As a result
1028% the `enhancement' is less harsh.
1029%
1030% The format of the ContrastStretchImage method is:
1031%
1032% MagickBooleanType ContrastStretchImage(Image *image,
1033% const char *levels)
1034% MagickBooleanType ContrastStretchImageChannel(Image *image,
1035% const unsigned long channel,const double black_point,
1036% const double white_point)
1037%
1038% A description of each parameter follows:
1039%
1040% o image: the image.
1041%
1042% o channel: the channel.
1043%
1044% o black_point: the black point.
1045%
1046% o white_point: the white point.
1047%
1048% o levels: Specify the levels where the black and white points have the
1049% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1050%
1051*/
1052
1053MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1054 const char *levels)
1055{
1056 double
1057 black_point,
1058 white_point;
1059
1060 GeometryInfo
1061 geometry_info;
1062
1063 MagickBooleanType
1064 status;
1065
1066 MagickStatusType
1067 flags;
1068
1069 /*
1070 Parse levels.
1071 */
1072 if (levels == (char *) NULL)
1073 return(MagickFalse);
1074 flags=ParseGeometry(levels,&geometry_info);
1075 black_point=geometry_info.rho;
1076 white_point=(double) image->columns*image->rows;
1077 if ((flags & SigmaValue) != 0)
1078 white_point=geometry_info.sigma;
1079 if ((flags & PercentValue) != 0)
1080 {
1081 black_point*=(double) QuantumRange/100.0;
1082 white_point*=(double) QuantumRange/100.0;
1083 }
1084 if ((flags & SigmaValue) == 0)
1085 white_point=(double) image->columns*image->rows-black_point;
1086 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1087 white_point);
1088 return(status);
1089}
1090
1091MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1092 const ChannelType channel,const double black_point,const double white_point)
1093{
1094#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1095#define ContrastStretchImageTag "ContrastStretch/Image"
1096
cristyc4c8d132010-01-07 01:58:38 +00001097 CacheView
1098 *image_view;
1099
cristy3ed852e2009-09-05 21:47:34 +00001100 double
1101 intensity;
1102
1103 ExceptionInfo
1104 *exception;
1105
1106 long
1107 progress,
1108 y;
1109
1110 MagickBooleanType
1111 status;
1112
1113 MagickPixelPacket
1114 black,
1115 *histogram,
1116 *stretch_map,
1117 white;
1118
1119 register long
1120 i;
1121
cristy3ed852e2009-09-05 21:47:34 +00001122 /*
1123 Allocate histogram and stretch map.
1124 */
1125 assert(image != (Image *) NULL);
1126 assert(image->signature == MagickSignature);
1127 if (image->debug != MagickFalse)
1128 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1129 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1130 sizeof(*histogram));
1131 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1132 sizeof(*stretch_map));
1133 if ((histogram == (MagickPixelPacket *) NULL) ||
1134 (stretch_map == (MagickPixelPacket *) NULL))
1135 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1136 image->filename);
1137 /*
1138 Form histogram.
1139 */
1140 status=MagickTrue;
1141 exception=(&image->exception);
1142 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1143 image_view=AcquireCacheView(image);
1144 for (y=0; y < (long) image->rows; y++)
1145 {
1146 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001147 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001148
1149 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001150 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001151
1152 register long
1153 x;
1154
1155 if (status == MagickFalse)
1156 continue;
1157 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1158 if (p == (const PixelPacket *) NULL)
1159 {
1160 status=MagickFalse;
1161 continue;
1162 }
1163 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1164 if (channel == DefaultChannels)
1165 for (x=0; x < (long) image->columns; x++)
1166 {
1167 Quantum
1168 intensity;
1169
1170 intensity=PixelIntensityToQuantum(p);
1171 histogram[ScaleQuantumToMap(intensity)].red++;
1172 histogram[ScaleQuantumToMap(intensity)].green++;
1173 histogram[ScaleQuantumToMap(intensity)].blue++;
1174 histogram[ScaleQuantumToMap(intensity)].index++;
1175 p++;
1176 }
1177 else
1178 for (x=0; x < (long) image->columns; x++)
1179 {
1180 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001181 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001182 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001183 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001184 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001185 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001186 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001187 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001188 if (((channel & IndexChannel) != 0) &&
1189 (image->colorspace == CMYKColorspace))
1190 histogram[ScaleQuantumToMap(indexes[x])].index++;
1191 p++;
1192 }
1193 }
1194 /*
1195 Find the histogram boundaries by locating the black/white levels.
1196 */
1197 black.red=0.0;
1198 white.red=MaxRange(QuantumRange);
1199 if ((channel & RedChannel) != 0)
1200 {
1201 intensity=0.0;
1202 for (i=0; i <= (long) MaxMap; i++)
1203 {
1204 intensity+=histogram[i].red;
1205 if (intensity > black_point)
1206 break;
1207 }
1208 black.red=(MagickRealType) i;
1209 intensity=0.0;
1210 for (i=(long) MaxMap; i != 0; i--)
1211 {
1212 intensity+=histogram[i].red;
1213 if (intensity > ((double) image->columns*image->rows-white_point))
1214 break;
1215 }
1216 white.red=(MagickRealType) i;
1217 }
1218 black.green=0.0;
1219 white.green=MaxRange(QuantumRange);
1220 if ((channel & GreenChannel) != 0)
1221 {
1222 intensity=0.0;
1223 for (i=0; i <= (long) MaxMap; i++)
1224 {
1225 intensity+=histogram[i].green;
1226 if (intensity > black_point)
1227 break;
1228 }
1229 black.green=(MagickRealType) i;
1230 intensity=0.0;
1231 for (i=(long) MaxMap; i != 0; i--)
1232 {
1233 intensity+=histogram[i].green;
1234 if (intensity > ((double) image->columns*image->rows-white_point))
1235 break;
1236 }
1237 white.green=(MagickRealType) i;
1238 }
1239 black.blue=0.0;
1240 white.blue=MaxRange(QuantumRange);
1241 if ((channel & BlueChannel) != 0)
1242 {
1243 intensity=0.0;
1244 for (i=0; i <= (long) MaxMap; i++)
1245 {
1246 intensity+=histogram[i].blue;
1247 if (intensity > black_point)
1248 break;
1249 }
1250 black.blue=(MagickRealType) i;
1251 intensity=0.0;
1252 for (i=(long) MaxMap; i != 0; i--)
1253 {
1254 intensity+=histogram[i].blue;
1255 if (intensity > ((double) image->columns*image->rows-white_point))
1256 break;
1257 }
1258 white.blue=(MagickRealType) i;
1259 }
1260 black.opacity=0.0;
1261 white.opacity=MaxRange(QuantumRange);
1262 if ((channel & OpacityChannel) != 0)
1263 {
1264 intensity=0.0;
1265 for (i=0; i <= (long) MaxMap; i++)
1266 {
1267 intensity+=histogram[i].opacity;
1268 if (intensity > black_point)
1269 break;
1270 }
1271 black.opacity=(MagickRealType) i;
1272 intensity=0.0;
1273 for (i=(long) MaxMap; i != 0; i--)
1274 {
1275 intensity+=histogram[i].opacity;
1276 if (intensity > ((double) image->columns*image->rows-white_point))
1277 break;
1278 }
1279 white.opacity=(MagickRealType) i;
1280 }
1281 black.index=0.0;
1282 white.index=MaxRange(QuantumRange);
1283 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1284 {
1285 intensity=0.0;
1286 for (i=0; i <= (long) MaxMap; i++)
1287 {
1288 intensity+=histogram[i].index;
1289 if (intensity > black_point)
1290 break;
1291 }
1292 black.index=(MagickRealType) i;
1293 intensity=0.0;
1294 for (i=(long) MaxMap; i != 0; i--)
1295 {
1296 intensity+=histogram[i].index;
1297 if (intensity > ((double) image->columns*image->rows-white_point))
1298 break;
1299 }
1300 white.index=(MagickRealType) i;
1301 }
1302 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1303 /*
1304 Stretch the histogram to create the stretched image mapping.
1305 */
1306 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001307#if defined(MAGICKCORE_OPENMP_SUPPORT)
1308 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001309#endif
1310 for (i=0; i <= (long) MaxMap; i++)
1311 {
1312 if ((channel & RedChannel) != 0)
1313 {
1314 if (i < (long) black.red)
1315 stretch_map[i].red=0.0;
1316 else
1317 if (i > (long) white.red)
1318 stretch_map[i].red=(MagickRealType) QuantumRange;
1319 else
1320 if (black.red != white.red)
1321 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1322 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1323 }
1324 if ((channel & GreenChannel) != 0)
1325 {
1326 if (i < (long) black.green)
1327 stretch_map[i].green=0.0;
1328 else
1329 if (i > (long) white.green)
1330 stretch_map[i].green=(MagickRealType) QuantumRange;
1331 else
1332 if (black.green != white.green)
1333 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1334 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1335 black.green)));
1336 }
1337 if ((channel & BlueChannel) != 0)
1338 {
1339 if (i < (long) black.blue)
1340 stretch_map[i].blue=0.0;
1341 else
1342 if (i > (long) white.blue)
1343 stretch_map[i].blue=(MagickRealType) QuantumRange;
1344 else
1345 if (black.blue != white.blue)
1346 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1347 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1348 black.blue)));
1349 }
1350 if ((channel & OpacityChannel) != 0)
1351 {
1352 if (i < (long) black.opacity)
1353 stretch_map[i].opacity=0.0;
1354 else
1355 if (i > (long) white.opacity)
1356 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1357 else
1358 if (black.opacity != white.opacity)
1359 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1360 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1361 black.opacity)));
1362 }
1363 if (((channel & IndexChannel) != 0) &&
1364 (image->colorspace == CMYKColorspace))
1365 {
1366 if (i < (long) black.index)
1367 stretch_map[i].index=0.0;
1368 else
1369 if (i > (long) white.index)
1370 stretch_map[i].index=(MagickRealType) QuantumRange;
1371 else
1372 if (black.index != white.index)
1373 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1374 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1375 black.index)));
1376 }
1377 }
1378 /*
1379 Stretch the image.
1380 */
1381 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1382 (image->colorspace == CMYKColorspace)))
1383 image->storage_class=DirectClass;
1384 if (image->storage_class == PseudoClass)
1385 {
1386 /*
1387 Stretch colormap.
1388 */
cristyb5d5f722009-11-04 03:03:49 +00001389#if defined(MAGICKCORE_OPENMP_SUPPORT)
1390 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001391#endif
1392 for (i=0; i < (long) image->colors; i++)
1393 {
1394 if ((channel & RedChannel) != 0)
1395 {
1396 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001397 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001398 ScaleQuantumToMap(image->colormap[i].red)].red);
1399 }
1400 if ((channel & GreenChannel) != 0)
1401 {
1402 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001403 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001404 ScaleQuantumToMap(image->colormap[i].green)].green);
1405 }
1406 if ((channel & BlueChannel) != 0)
1407 {
1408 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001409 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001410 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1411 }
1412 if ((channel & OpacityChannel) != 0)
1413 {
1414 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001415 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001416 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1417 }
1418 }
1419 }
1420 /*
1421 Stretch image.
1422 */
1423 status=MagickTrue;
1424 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001425#if defined(MAGICKCORE_OPENMP_SUPPORT)
1426 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001427#endif
1428 for (y=0; y < (long) image->rows; y++)
1429 {
1430 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001431 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001432
1433 register long
1434 x;
1435
1436 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001437 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001438
1439 if (status == MagickFalse)
1440 continue;
1441 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1442 if (q == (PixelPacket *) NULL)
1443 {
1444 status=MagickFalse;
1445 continue;
1446 }
1447 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1448 for (x=0; x < (long) image->columns; x++)
1449 {
1450 if ((channel & RedChannel) != 0)
1451 {
1452 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001453 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001454 }
1455 if ((channel & GreenChannel) != 0)
1456 {
1457 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001458 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001459 q->green)].green);
1460 }
1461 if ((channel & BlueChannel) != 0)
1462 {
1463 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001464 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001465 q->blue)].blue);
1466 }
1467 if ((channel & OpacityChannel) != 0)
1468 {
1469 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001470 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001471 q->opacity)].opacity);
1472 }
1473 if (((channel & IndexChannel) != 0) &&
1474 (image->colorspace == CMYKColorspace))
1475 {
1476 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001477 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001478 ScaleQuantumToMap(indexes[x])].index);
1479 }
1480 q++;
1481 }
1482 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1483 status=MagickFalse;
1484 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1485 {
1486 MagickBooleanType
1487 proceed;
1488
cristyb5d5f722009-11-04 03:03:49 +00001489#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001490 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1491#endif
1492 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1493 image->rows);
1494 if (proceed == MagickFalse)
1495 status=MagickFalse;
1496 }
1497 }
1498 image_view=DestroyCacheView(image_view);
1499 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1500 return(status);
1501}
1502
1503/*
1504%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1505% %
1506% %
1507% %
1508% E n h a n c e I m a g e %
1509% %
1510% %
1511% %
1512%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1513%
1514% EnhanceImage() applies a digital filter that improves the quality of a
1515% noisy image.
1516%
1517% The format of the EnhanceImage method is:
1518%
1519% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1520%
1521% A description of each parameter follows:
1522%
1523% o image: the image.
1524%
1525% o exception: return any errors or warnings in this structure.
1526%
1527*/
1528MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1529{
1530#define Enhance(weight) \
1531 mean=((MagickRealType) r->red+pixel.red)/2; \
1532 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1533 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1534 mean)*distance*distance; \
1535 mean=((MagickRealType) r->green+pixel.green)/2; \
1536 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1537 distance_squared+=4.0*distance*distance; \
1538 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1539 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1540 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1541 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1542 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1543 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1544 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1545 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1546 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1547 QuantumRange/25.0f)) \
1548 { \
1549 aggregate.red+=(weight)*r->red; \
1550 aggregate.green+=(weight)*r->green; \
1551 aggregate.blue+=(weight)*r->blue; \
1552 aggregate.opacity+=(weight)*r->opacity; \
1553 total_weight+=(weight); \
1554 } \
1555 r++;
1556#define EnhanceImageTag "Enhance/Image"
1557
cristyc4c8d132010-01-07 01:58:38 +00001558 CacheView
1559 *enhance_view,
1560 *image_view;
1561
cristy3ed852e2009-09-05 21:47:34 +00001562 Image
1563 *enhance_image;
1564
1565 long
1566 progress,
1567 y;
1568
1569 MagickBooleanType
1570 status;
1571
1572 MagickPixelPacket
1573 zero;
1574
cristy3ed852e2009-09-05 21:47:34 +00001575 /*
1576 Initialize enhanced image attributes.
1577 */
1578 assert(image != (const Image *) NULL);
1579 assert(image->signature == MagickSignature);
1580 if (image->debug != MagickFalse)
1581 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1582 assert(exception != (ExceptionInfo *) NULL);
1583 assert(exception->signature == MagickSignature);
1584 if ((image->columns < 5) || (image->rows < 5))
1585 return((Image *) NULL);
1586 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1587 exception);
1588 if (enhance_image == (Image *) NULL)
1589 return((Image *) NULL);
1590 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1591 {
1592 InheritException(exception,&enhance_image->exception);
1593 enhance_image=DestroyImage(enhance_image);
1594 return((Image *) NULL);
1595 }
1596 /*
1597 Enhance image.
1598 */
1599 status=MagickTrue;
1600 progress=0;
1601 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1602 image_view=AcquireCacheView(image);
1603 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001604#if defined(MAGICKCORE_OPENMP_SUPPORT)
1605 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001606#endif
1607 for (y=0; y < (long) image->rows; y++)
1608 {
1609 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001610 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001611
1612 register long
1613 x;
1614
1615 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001616 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001617
1618 /*
1619 Read another scan line.
1620 */
1621 if (status == MagickFalse)
1622 continue;
1623 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1624 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1625 exception);
1626 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1627 {
1628 status=MagickFalse;
1629 continue;
1630 }
1631 for (x=0; x < (long) image->columns; x++)
1632 {
1633 MagickPixelPacket
1634 aggregate;
1635
1636 MagickRealType
1637 distance,
1638 distance_squared,
1639 mean,
1640 total_weight;
1641
1642 PixelPacket
1643 pixel;
1644
1645 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001646 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001647
1648 /*
1649 Compute weighted average of target pixel color components.
1650 */
1651 aggregate=zero;
1652 total_weight=0.0;
1653 r=p+2*(image->columns+4)+2;
1654 pixel=(*r);
1655 r=p;
1656 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1657 r=p+(image->columns+4);
1658 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1659 r=p+2*(image->columns+4);
1660 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1661 r=p+3*(image->columns+4);
1662 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1663 r=p+4*(image->columns+4);
1664 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1665 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1666 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1667 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1668 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1669 total_weight);
1670 p++;
1671 q++;
1672 }
1673 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1674 status=MagickFalse;
1675 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1676 {
1677 MagickBooleanType
1678 proceed;
1679
cristyb5d5f722009-11-04 03:03:49 +00001680#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001681 #pragma omp critical (MagickCore_EnhanceImage)
1682#endif
1683 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1684 if (proceed == MagickFalse)
1685 status=MagickFalse;
1686 }
1687 }
1688 enhance_view=DestroyCacheView(enhance_view);
1689 image_view=DestroyCacheView(image_view);
1690 return(enhance_image);
1691}
1692
1693/*
1694%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1695% %
1696% %
1697% %
1698% E q u a l i z e I m a g e %
1699% %
1700% %
1701% %
1702%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1703%
1704% EqualizeImage() applies a histogram equalization to the image.
1705%
1706% The format of the EqualizeImage method is:
1707%
1708% MagickBooleanType EqualizeImage(Image *image)
1709% MagickBooleanType EqualizeImageChannel(Image *image,
1710% const ChannelType channel)
1711%
1712% A description of each parameter follows:
1713%
1714% o image: the image.
1715%
1716% o channel: the channel.
1717%
1718*/
1719
1720MagickExport MagickBooleanType EqualizeImage(Image *image)
1721{
1722 return(EqualizeImageChannel(image,DefaultChannels));
1723}
1724
1725MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1726 const ChannelType channel)
1727{
1728#define EqualizeImageTag "Equalize/Image"
1729
cristyc4c8d132010-01-07 01:58:38 +00001730 CacheView
1731 *image_view;
1732
cristy3ed852e2009-09-05 21:47:34 +00001733 ExceptionInfo
1734 *exception;
1735
1736 long
1737 progress,
1738 y;
1739
1740 MagickBooleanType
1741 status;
1742
1743 MagickPixelPacket
1744 black,
1745 *equalize_map,
1746 *histogram,
1747 intensity,
1748 *map,
1749 white;
1750
1751 register long
1752 i;
1753
cristy3ed852e2009-09-05 21:47:34 +00001754 /*
1755 Allocate and initialize histogram arrays.
1756 */
1757 assert(image != (Image *) NULL);
1758 assert(image->signature == MagickSignature);
1759 if (image->debug != MagickFalse)
1760 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1761 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1762 sizeof(*equalize_map));
1763 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1764 sizeof(*histogram));
1765 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1766 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1767 (histogram == (MagickPixelPacket *) NULL) ||
1768 (map == (MagickPixelPacket *) NULL))
1769 {
1770 if (map != (MagickPixelPacket *) NULL)
1771 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1772 if (histogram != (MagickPixelPacket *) NULL)
1773 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1774 if (equalize_map != (MagickPixelPacket *) NULL)
1775 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1776 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1777 image->filename);
1778 }
1779 /*
1780 Form histogram.
1781 */
1782 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1783 exception=(&image->exception);
1784 for (y=0; y < (long) image->rows; y++)
1785 {
1786 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001787 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001788
1789 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001790 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001791
1792 register long
1793 x;
1794
1795 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1796 if (p == (const PixelPacket *) NULL)
1797 break;
1798 indexes=GetVirtualIndexQueue(image);
1799 for (x=0; x < (long) image->columns; x++)
1800 {
1801 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001802 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001803 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001804 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001805 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001806 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001807 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001808 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001809 if (((channel & IndexChannel) != 0) &&
1810 (image->colorspace == CMYKColorspace))
1811 histogram[ScaleQuantumToMap(indexes[x])].index++;
1812 p++;
1813 }
1814 }
1815 /*
1816 Integrate the histogram to get the equalization map.
1817 */
1818 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1819 for (i=0; i <= (long) MaxMap; i++)
1820 {
1821 if ((channel & RedChannel) != 0)
1822 intensity.red+=histogram[i].red;
1823 if ((channel & GreenChannel) != 0)
1824 intensity.green+=histogram[i].green;
1825 if ((channel & BlueChannel) != 0)
1826 intensity.blue+=histogram[i].blue;
1827 if ((channel & OpacityChannel) != 0)
1828 intensity.opacity+=histogram[i].opacity;
1829 if (((channel & IndexChannel) != 0) &&
1830 (image->colorspace == CMYKColorspace))
1831 intensity.index+=histogram[i].index;
1832 map[i]=intensity;
1833 }
1834 black=map[0];
1835 white=map[(int) MaxMap];
1836 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001837#if defined(MAGICKCORE_OPENMP_SUPPORT)
1838 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001839#endif
1840 for (i=0; i <= (long) MaxMap; i++)
1841 {
1842 if (((channel & RedChannel) != 0) && (white.red != black.red))
1843 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1844 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1845 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1846 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1847 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1848 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1849 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1850 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1851 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1852 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1853 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1854 (white.opacity-black.opacity)));
1855 if ((((channel & IndexChannel) != 0) &&
1856 (image->colorspace == CMYKColorspace)) &&
1857 (white.index != black.index))
1858 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1859 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1860 }
1861 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1862 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1863 if (image->storage_class == PseudoClass)
1864 {
1865 /*
1866 Equalize colormap.
1867 */
cristyb5d5f722009-11-04 03:03:49 +00001868#if defined(MAGICKCORE_OPENMP_SUPPORT)
1869 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001870#endif
1871 for (i=0; i < (long) image->colors; i++)
1872 {
1873 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001874 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001875 ScaleQuantumToMap(image->colormap[i].red)].red);
1876 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001877 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001878 ScaleQuantumToMap(image->colormap[i].green)].green);
1879 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001880 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001881 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1882 if (((channel & OpacityChannel) != 0) &&
1883 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001884 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001885 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1886 }
1887 }
1888 /*
1889 Equalize image.
1890 */
1891 status=MagickTrue;
1892 progress=0;
1893 exception=(&image->exception);
1894 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001895#if defined(MAGICKCORE_OPENMP_SUPPORT)
1896 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001897#endif
1898 for (y=0; y < (long) image->rows; y++)
1899 {
1900 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001901 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001902
1903 register long
1904 x;
1905
1906 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001907 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001908
1909 if (status == MagickFalse)
1910 continue;
1911 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1912 if (q == (PixelPacket *) NULL)
1913 {
1914 status=MagickFalse;
1915 continue;
1916 }
1917 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1918 for (x=0; x < (long) image->columns; x++)
1919 {
1920 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001921 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001922 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001923 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001924 q->green)].green);
1925 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001926 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001927 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001928 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001929 q->opacity)].opacity);
1930 if ((((channel & IndexChannel) != 0) &&
1931 (image->colorspace == CMYKColorspace)) &&
1932 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001933 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001934 indexes[x])].index);
1935 q++;
1936 }
1937 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1938 status=MagickFalse;
1939 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1940 {
1941 MagickBooleanType
1942 proceed;
1943
cristyb5d5f722009-11-04 03:03:49 +00001944#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001945 #pragma omp critical (MagickCore_EqualizeImageChannel)
1946#endif
1947 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1948 if (proceed == MagickFalse)
1949 status=MagickFalse;
1950 }
1951 }
1952 image_view=DestroyCacheView(image_view);
1953 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1954 return(status);
1955}
1956
1957/*
1958%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1959% %
1960% %
1961% %
1962% G a m m a I m a g e %
1963% %
1964% %
1965% %
1966%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1967%
1968% GammaImage() gamma-corrects a particular image channel. The same
1969% image viewed on different devices will have perceptual differences in the
1970% way the image's intensities are represented on the screen. Specify
1971% individual gamma levels for the red, green, and blue channels, or adjust
1972% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1973%
1974% You can also reduce the influence of a particular channel with a gamma
1975% value of 0.
1976%
1977% The format of the GammaImage method is:
1978%
1979% MagickBooleanType GammaImage(Image *image,const double gamma)
1980% MagickBooleanType GammaImageChannel(Image *image,
1981% const ChannelType channel,const double gamma)
1982%
1983% A description of each parameter follows:
1984%
1985% o image: the image.
1986%
1987% o channel: the channel.
1988%
1989% o gamma: the image gamma.
1990%
1991*/
1992MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1993{
1994 GeometryInfo
1995 geometry_info;
1996
1997 MagickPixelPacket
1998 gamma;
1999
2000 MagickStatusType
2001 flags,
2002 status;
2003
2004 assert(image != (Image *) NULL);
2005 assert(image->signature == MagickSignature);
2006 if (image->debug != MagickFalse)
2007 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2008 if (level == (char *) NULL)
2009 return(MagickFalse);
2010 flags=ParseGeometry(level,&geometry_info);
2011 gamma.red=geometry_info.rho;
2012 gamma.green=geometry_info.sigma;
2013 if ((flags & SigmaValue) == 0)
2014 gamma.green=gamma.red;
2015 gamma.blue=geometry_info.xi;
2016 if ((flags & XiValue) == 0)
2017 gamma.blue=gamma.red;
2018 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2019 return(MagickTrue);
2020 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2021 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2022 GreenChannel | BlueChannel),(double) gamma.red);
2023 else
2024 {
2025 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2026 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2027 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2028 }
2029 return(status != 0 ? MagickTrue : MagickFalse);
2030}
2031
2032MagickExport MagickBooleanType GammaImageChannel(Image *image,
2033 const ChannelType channel,const double gamma)
2034{
2035#define GammaCorrectImageTag "GammaCorrect/Image"
2036
cristyc4c8d132010-01-07 01:58:38 +00002037 CacheView
2038 *image_view;
2039
cristy3ed852e2009-09-05 21:47:34 +00002040 ExceptionInfo
2041 *exception;
2042
2043 long
2044 progress,
2045 y;
2046
2047 MagickBooleanType
2048 status;
2049
2050 Quantum
2051 *gamma_map;
2052
2053 register long
2054 i;
2055
cristy3ed852e2009-09-05 21:47:34 +00002056 /*
2057 Allocate and initialize gamma maps.
2058 */
2059 assert(image != (Image *) NULL);
2060 assert(image->signature == MagickSignature);
2061 if (image->debug != MagickFalse)
2062 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2063 if (gamma == 1.0)
2064 return(MagickTrue);
2065 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2066 if (gamma_map == (Quantum *) NULL)
2067 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2068 image->filename);
2069 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2070 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00002071#if defined(MAGICKCORE_OPENMP_SUPPORT)
2072 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00002073#endif
2074 for (i=0; i <= (long) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00002075 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00002076 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2077 if (image->storage_class == PseudoClass)
2078 {
2079 /*
2080 Gamma-correct colormap.
2081 */
cristyb5d5f722009-11-04 03:03:49 +00002082#if defined(MAGICKCORE_OPENMP_SUPPORT)
2083 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002084#endif
2085 for (i=0; i < (long) image->colors; i++)
2086 {
2087 if ((channel & RedChannel) != 0)
2088 image->colormap[i].red=gamma_map[
2089 ScaleQuantumToMap(image->colormap[i].red)];
2090 if ((channel & GreenChannel) != 0)
2091 image->colormap[i].green=gamma_map[
2092 ScaleQuantumToMap(image->colormap[i].green)];
2093 if ((channel & BlueChannel) != 0)
2094 image->colormap[i].blue=gamma_map[
2095 ScaleQuantumToMap(image->colormap[i].blue)];
2096 if ((channel & OpacityChannel) != 0)
2097 {
2098 if (image->matte == MagickFalse)
2099 image->colormap[i].opacity=gamma_map[
2100 ScaleQuantumToMap(image->colormap[i].opacity)];
2101 else
2102 image->colormap[i].opacity=(Quantum) QuantumRange-
2103 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2104 image->colormap[i].opacity))];
2105 }
2106 }
2107 }
2108 /*
2109 Gamma-correct image.
2110 */
2111 status=MagickTrue;
2112 progress=0;
2113 exception=(&image->exception);
2114 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002115#if defined(MAGICKCORE_OPENMP_SUPPORT)
2116 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002117#endif
2118 for (y=0; y < (long) image->rows; y++)
2119 {
2120 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002121 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002122
2123 register long
2124 x;
2125
2126 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002127 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002128
2129 if (status == MagickFalse)
2130 continue;
2131 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2132 if (q == (PixelPacket *) NULL)
2133 {
2134 status=MagickFalse;
2135 continue;
2136 }
2137 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2138 for (x=0; x < (long) image->columns; x++)
2139 {
cristy6cbd7f52009-10-17 16:06:51 +00002140 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002141 {
cristy6cbd7f52009-10-17 16:06:51 +00002142 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2143 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2144 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2145 }
2146 else
2147 {
2148 if ((channel & RedChannel) != 0)
2149 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2150 if ((channel & GreenChannel) != 0)
2151 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2152 if ((channel & BlueChannel) != 0)
2153 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2154 if ((channel & OpacityChannel) != 0)
2155 {
2156 if (image->matte == MagickFalse)
2157 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2158 else
2159 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002160 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002161 }
cristy3ed852e2009-09-05 21:47:34 +00002162 }
2163 q++;
2164 }
2165 if (((channel & IndexChannel) != 0) &&
2166 (image->colorspace == CMYKColorspace))
2167 for (x=0; x < (long) image->columns; x++)
2168 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2169 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2170 status=MagickFalse;
2171 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2172 {
2173 MagickBooleanType
2174 proceed;
2175
cristyb5d5f722009-11-04 03:03:49 +00002176#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002177 #pragma omp critical (MagickCore_GammaImageChannel)
2178#endif
2179 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2180 image->rows);
2181 if (proceed == MagickFalse)
2182 status=MagickFalse;
2183 }
2184 }
2185 image_view=DestroyCacheView(image_view);
2186 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2187 if (image->gamma != 0.0)
2188 image->gamma*=gamma;
2189 return(status);
2190}
2191
2192/*
2193%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2194% %
2195% %
2196% %
2197% H a l d C l u t I m a g e %
2198% %
2199% %
2200% %
2201%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2202%
2203% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2204% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2205% Create it with the HALD coder. You can apply any color transformation to
2206% the Hald image and then use this method to apply the transform to the
2207% image.
2208%
2209% The format of the HaldClutImage method is:
2210%
2211% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2212% MagickBooleanType HaldClutImageChannel(Image *image,
2213% const ChannelType channel,Image *hald_image)
2214%
2215% A description of each parameter follows:
2216%
2217% o image: the image, which is replaced by indexed CLUT values
2218%
2219% o hald_image: the color lookup table image for replacement color values.
2220%
2221% o channel: the channel.
2222%
2223*/
2224
2225static inline size_t MagickMin(const size_t x,const size_t y)
2226{
2227 if (x < y)
2228 return(x);
2229 return(y);
2230}
2231
2232MagickExport MagickBooleanType HaldClutImage(Image *image,
2233 const Image *hald_image)
2234{
2235 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2236}
2237
2238MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2239 const ChannelType channel,const Image *hald_image)
2240{
2241#define HaldClutImageTag "Clut/Image"
2242
2243 typedef struct _HaldInfo
2244 {
2245 MagickRealType
2246 x,
2247 y,
2248 z;
2249 } HaldInfo;
2250
cristyfa112112010-01-04 17:48:07 +00002251 CacheView
2252 *image_view;
2253
cristy3ed852e2009-09-05 21:47:34 +00002254 double
2255 width;
2256
2257 ExceptionInfo
2258 *exception;
2259
2260 long
2261 progress,
2262 y;
2263
2264 MagickBooleanType
2265 status;
2266
2267 MagickPixelPacket
2268 zero;
2269
2270 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002271 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002272
2273 size_t
2274 cube_size,
2275 length,
2276 level;
2277
cristy3ed852e2009-09-05 21:47:34 +00002278 assert(image != (Image *) NULL);
2279 assert(image->signature == MagickSignature);
2280 if (image->debug != MagickFalse)
2281 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2282 assert(hald_image != (Image *) NULL);
2283 assert(hald_image->signature == MagickSignature);
2284 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2285 return(MagickFalse);
2286 if (image->matte == MagickFalse)
2287 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2288 /*
2289 Hald clut image.
2290 */
2291 status=MagickTrue;
2292 progress=0;
2293 length=MagickMin(hald_image->columns,hald_image->rows);
2294 for (level=2; (level*level*level) < length; level++) ;
2295 level*=level;
2296 cube_size=level*level;
2297 width=(double) hald_image->columns;
2298 GetMagickPixelPacket(hald_image,&zero);
2299 exception=(&image->exception);
cristyb2a11ae2010-02-22 00:53:36 +00002300 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2301 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002302 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002303#if defined(MAGICKCORE_OPENMP_SUPPORT)
2304 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002305#endif
2306 for (y=0; y < (long) image->rows; y++)
2307 {
2308 double
2309 offset;
2310
2311 HaldInfo
2312 point;
2313
2314 MagickPixelPacket
2315 pixel,
2316 pixel1,
2317 pixel2,
2318 pixel3,
2319 pixel4;
2320
2321 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002322 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002323
2324 register long
2325 id,
2326 x;
2327
2328 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002329 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002330
2331 if (status == MagickFalse)
2332 continue;
2333 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2334 if (q == (PixelPacket *) NULL)
2335 {
2336 status=MagickFalse;
2337 continue;
2338 }
2339 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2340 pixel=zero;
2341 pixel1=zero;
2342 pixel2=zero;
2343 pixel3=zero;
2344 pixel4=zero;
2345 id=GetOpenMPThreadId();
2346 for (x=0; x < (long) image->columns; x++)
2347 {
2348 point.x=QuantumScale*(level-1.0)*q->red;
2349 point.y=QuantumScale*(level-1.0)*q->green;
2350 point.z=QuantumScale*(level-1.0)*q->blue;
2351 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2352 point.x-=floor(point.x);
2353 point.y-=floor(point.y);
2354 point.z-=floor(point.z);
2355 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2356 floor(offset/width),&pixel1);
2357 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2358 floor((offset+level)/width),&pixel2);
2359 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2360 pixel2.opacity,point.y,&pixel3);
2361 offset+=cube_size;
2362 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2363 floor(offset/width),&pixel1);
2364 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2365 floor((offset+level)/width),&pixel2);
2366 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2367 pixel2.opacity,point.y,&pixel4);
2368 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2369 pixel4.opacity,point.z,&pixel);
2370 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002371 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002372 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002373 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002374 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002375 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002376 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002377 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002378 if (((channel & IndexChannel) != 0) &&
2379 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002380 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002381 q++;
2382 }
2383 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2384 status=MagickFalse;
2385 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2386 {
2387 MagickBooleanType
2388 proceed;
2389
cristyb5d5f722009-11-04 03:03:49 +00002390#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002391 #pragma omp critical (MagickCore_HaldClutImageChannel)
2392#endif
2393 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2394 if (proceed == MagickFalse)
2395 status=MagickFalse;
2396 }
2397 }
2398 image_view=DestroyCacheView(image_view);
2399 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2400 return(status);
2401}
2402
2403/*
2404%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2405% %
2406% %
2407% %
2408% L e v e l I m a g e %
2409% %
2410% %
2411% %
2412%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2413%
2414% LevelImage() adjusts the levels of a particular image channel by
2415% scaling the colors falling between specified white and black points to
2416% the full available quantum range.
2417%
2418% The parameters provided represent the black, and white points. The black
2419% point specifies the darkest color in the image. Colors darker than the
2420% black point are set to zero. White point specifies the lightest color in
2421% the image. Colors brighter than the white point are set to the maximum
2422% quantum value.
2423%
2424% If a '!' flag is given, map black and white colors to the given levels
2425% rather than mapping those levels to black and white. See
2426% LevelizeImageChannel() and LevelizeImageChannel(), below.
2427%
2428% Gamma specifies a gamma correction to apply to the image.
2429%
2430% The format of the LevelImage method is:
2431%
2432% MagickBooleanType LevelImage(Image *image,const char *levels)
2433%
2434% A description of each parameter follows:
2435%
2436% o image: the image.
2437%
2438% o levels: Specify the levels where the black and white points have the
2439% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2440% A '!' flag inverts the re-mapping.
2441%
2442*/
2443
2444MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2445{
2446 double
2447 black_point,
2448 gamma,
2449 white_point;
2450
2451 GeometryInfo
2452 geometry_info;
2453
2454 MagickBooleanType
2455 status;
2456
2457 MagickStatusType
2458 flags;
2459
2460 /*
2461 Parse levels.
2462 */
2463 if (levels == (char *) NULL)
2464 return(MagickFalse);
2465 flags=ParseGeometry(levels,&geometry_info);
2466 black_point=geometry_info.rho;
2467 white_point=(double) QuantumRange;
2468 if ((flags & SigmaValue) != 0)
2469 white_point=geometry_info.sigma;
2470 gamma=1.0;
2471 if ((flags & XiValue) != 0)
2472 gamma=geometry_info.xi;
2473 if ((flags & PercentValue) != 0)
2474 {
2475 black_point*=(double) image->columns*image->rows/100.0;
2476 white_point*=(double) image->columns*image->rows/100.0;
2477 }
2478 if ((flags & SigmaValue) == 0)
2479 white_point=(double) QuantumRange-black_point;
2480 if ((flags & AspectValue ) == 0)
2481 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2482 gamma);
2483 else
cristy308b4e62009-09-21 14:40:44 +00002484 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002485 return(status);
2486}
2487
2488/*
2489%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2490% %
2491% %
2492% %
cristy308b4e62009-09-21 14:40:44 +00002493% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002494% %
2495% %
2496% %
2497%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2498%
cristy308b4e62009-09-21 14:40:44 +00002499% LevelizeImage() applies the normal level operation to the image, spreading
2500% out the values between the black and white points over the entire range of
2501% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002502%
2503% It is typically used to improve image contrast, or to provide a controlled
2504% linear threshold for the image. If the black and white points are set to
2505% the minimum and maximum values found in the image, the image can be
2506% normalized. or by swapping black and white values, negate the image.
2507%
cristy308b4e62009-09-21 14:40:44 +00002508% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002509%
cristy308b4e62009-09-21 14:40:44 +00002510% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2511% const double white_point,const double gamma)
2512% MagickBooleanType LevelizeImageChannel(Image *image,
2513% const ChannelType channel,const double black_point,
2514% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002515%
2516% A description of each parameter follows:
2517%
2518% o image: the image.
2519%
2520% o channel: the channel.
2521%
2522% o black_point: The level which is to be mapped to zero (black)
2523%
2524% o white_point: The level which is to be mapped to QuantiumRange (white)
2525%
2526% o gamma: adjust gamma by this factor before mapping values.
2527% use 1.0 for purely linear stretching of image color values
2528%
2529*/
cristy308b4e62009-09-21 14:40:44 +00002530
2531MagickExport MagickBooleanType LevelizeImage(Image *image,
2532 const double black_point,const double white_point,const double gamma)
2533{
2534 MagickBooleanType
2535 status;
2536
2537 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2538 gamma);
2539 return(status);
2540}
2541
cristy3ed852e2009-09-05 21:47:34 +00002542MagickExport MagickBooleanType LevelImageChannel(Image *image,
2543 const ChannelType channel,const double black_point,const double white_point,
2544 const double gamma)
2545{
2546#define LevelImageTag "Level/Image"
cristy3bb09042010-04-16 18:22:18 +00002547#define LevelValue(x) (KlampToQuantum((MagickRealType) QuantumRange* \
anthony7fe39fc2010-04-06 03:19:20 +00002548 pow(scale*((double)(x)-black_point),1.0/gamma)))
cristy3ed852e2009-09-05 21:47:34 +00002549
cristyc4c8d132010-01-07 01:58:38 +00002550 CacheView
2551 *image_view;
2552
cristy3ed852e2009-09-05 21:47:34 +00002553 ExceptionInfo
2554 *exception;
2555
2556 long
2557 progress,
2558 y;
2559
2560 MagickBooleanType
2561 status;
2562
2563 register long
2564 i;
2565
anthony7fe39fc2010-04-06 03:19:20 +00002566 register double
2567 scale;
2568
cristy3ed852e2009-09-05 21:47:34 +00002569 /*
2570 Allocate and initialize levels map.
2571 */
2572 assert(image != (Image *) NULL);
2573 assert(image->signature == MagickSignature);
2574 if (image->debug != MagickFalse)
2575 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
anthony7fe39fc2010-04-06 03:19:20 +00002576 scale = (white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristy3ed852e2009-09-05 21:47:34 +00002577 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002578#if defined(MAGICKCORE_OPENMP_SUPPORT)
2579 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002580#endif
2581 for (i=0; i < (long) image->colors; i++)
2582 {
2583 /*
2584 Level colormap.
2585 */
2586 if ((channel & RedChannel) != 0)
2587 image->colormap[i].red=LevelValue(image->colormap[i].red);
2588 if ((channel & GreenChannel) != 0)
2589 image->colormap[i].green=LevelValue(image->colormap[i].green);
2590 if ((channel & BlueChannel) != 0)
2591 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2592 if ((channel & OpacityChannel) != 0)
2593 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2594 }
2595 /*
2596 Level image.
2597 */
2598 status=MagickTrue;
2599 progress=0;
2600 exception=(&image->exception);
2601 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002602#if defined(MAGICKCORE_OPENMP_SUPPORT)
2603 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002604#endif
2605 for (y=0; y < (long) image->rows; y++)
2606 {
2607 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002608 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002609
2610 register long
2611 x;
2612
2613 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002614 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002615
2616 if (status == MagickFalse)
2617 continue;
2618 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2619 if (q == (PixelPacket *) NULL)
2620 {
2621 status=MagickFalse;
2622 continue;
2623 }
2624 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2625 for (x=0; x < (long) image->columns; x++)
2626 {
2627 if ((channel & RedChannel) != 0)
2628 q->red=LevelValue(q->red);
2629 if ((channel & GreenChannel) != 0)
2630 q->green=LevelValue(q->green);
2631 if ((channel & BlueChannel) != 0)
2632 q->blue=LevelValue(q->blue);
2633 if (((channel & OpacityChannel) != 0) &&
2634 (image->matte == MagickTrue))
anthony7fe39fc2010-04-06 03:19:20 +00002635 q->opacity=QuantumRange-LevelValue(QuantumRange-q->opacity);
cristy3ed852e2009-09-05 21:47:34 +00002636 if (((channel & IndexChannel) != 0) &&
2637 (image->colorspace == CMYKColorspace))
2638 indexes[x]=LevelValue(indexes[x]);
2639 q++;
2640 }
2641 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2642 status=MagickFalse;
2643 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2644 {
2645 MagickBooleanType
2646 proceed;
2647
cristyb5d5f722009-11-04 03:03:49 +00002648#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002649 #pragma omp critical (MagickCore_LevelImageChannel)
2650#endif
2651 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2652 if (proceed == MagickFalse)
2653 status=MagickFalse;
2654 }
2655 }
2656 image_view=DestroyCacheView(image_view);
2657 return(status);
2658}
2659
2660/*
2661%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2662% %
2663% %
2664% %
2665% L e v e l i z e I m a g e C h a n n e l %
2666% %
2667% %
2668% %
2669%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2670%
2671% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2672% the specific channels specified. It compresses the full range of color
2673% values, so that they lie between the given black and white points. Gamma is
2674% applied before the values are mapped.
2675%
2676% LevelizeImageChannel() can be called with by using a +level command line
2677% API option, or using a '!' on a -level or LevelImage() geometry string.
2678%
2679% It can be used for example de-contrast a greyscale image to the exact
2680% levels specified. Or by using specific levels for each channel of an image
2681% you can convert a gray-scale image to any linear color gradient, according
2682% to those levels.
2683%
2684% The format of the LevelizeImageChannel method is:
2685%
2686% MagickBooleanType LevelizeImageChannel(Image *image,
2687% const ChannelType channel,const char *levels)
2688%
2689% A description of each parameter follows:
2690%
2691% o image: the image.
2692%
2693% o channel: the channel.
2694%
2695% o black_point: The level to map zero (black) to.
2696%
2697% o white_point: The level to map QuantiumRange (white) to.
2698%
2699% o gamma: adjust gamma by this factor before mapping values.
2700%
2701*/
2702MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2703 const ChannelType channel,const double black_point,const double white_point,
2704 const double gamma)
2705{
2706#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002707#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002708 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2709 black_point))
2710
cristyc4c8d132010-01-07 01:58:38 +00002711 CacheView
2712 *image_view;
2713
cristy3ed852e2009-09-05 21:47:34 +00002714 ExceptionInfo
2715 *exception;
2716
2717 long
2718 progress,
2719 y;
2720
2721 MagickBooleanType
2722 status;
2723
2724 register long
2725 i;
2726
cristy3ed852e2009-09-05 21:47:34 +00002727 /*
2728 Allocate and initialize levels map.
2729 */
2730 assert(image != (Image *) NULL);
2731 assert(image->signature == MagickSignature);
2732 if (image->debug != MagickFalse)
2733 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2734 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002735#if defined(MAGICKCORE_OPENMP_SUPPORT)
2736 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002737#endif
2738 for (i=0; i < (long) image->colors; i++)
2739 {
2740 /*
2741 Level colormap.
2742 */
2743 if ((channel & RedChannel) != 0)
2744 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2745 if ((channel & GreenChannel) != 0)
2746 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2747 if ((channel & BlueChannel) != 0)
2748 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2749 if ((channel & OpacityChannel) != 0)
2750 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2751 }
2752 /*
2753 Level image.
2754 */
2755 status=MagickTrue;
2756 progress=0;
2757 exception=(&image->exception);
2758 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002759#if defined(MAGICKCORE_OPENMP_SUPPORT)
2760 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002761#endif
2762 for (y=0; y < (long) image->rows; y++)
2763 {
2764 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002765 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002766
2767 register long
2768 x;
2769
2770 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002771 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002772
2773 if (status == MagickFalse)
2774 continue;
2775 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2776 if (q == (PixelPacket *) NULL)
2777 {
2778 status=MagickFalse;
2779 continue;
2780 }
2781 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2782 for (x=0; x < (long) image->columns; x++)
2783 {
2784 if ((channel & RedChannel) != 0)
2785 q->red=LevelizeValue(q->red);
2786 if ((channel & GreenChannel) != 0)
2787 q->green=LevelizeValue(q->green);
2788 if ((channel & BlueChannel) != 0)
2789 q->blue=LevelizeValue(q->blue);
2790 if (((channel & OpacityChannel) != 0) &&
2791 (image->matte == MagickTrue))
2792 q->opacity=LevelizeValue(q->opacity);
2793 if (((channel & IndexChannel) != 0) &&
2794 (image->colorspace == CMYKColorspace))
2795 indexes[x]=LevelizeValue(indexes[x]);
2796 q++;
2797 }
2798 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2799 status=MagickFalse;
2800 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2801 {
2802 MagickBooleanType
2803 proceed;
2804
cristyb5d5f722009-11-04 03:03:49 +00002805#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002806 #pragma omp critical (MagickCore_LevelizeImageChannel)
2807#endif
2808 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2809 if (proceed == MagickFalse)
2810 status=MagickFalse;
2811 }
2812 }
2813 return(status);
2814}
2815
2816/*
2817%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2818% %
2819% %
2820% %
2821% L e v e l I m a g e C o l o r s %
2822% %
2823% %
2824% %
2825%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2826%
cristyee0f8d72009-09-19 00:58:29 +00002827% LevelImageColor() maps the given color to "black" and "white" values,
2828% linearly spreading out the colors, and level values on a channel by channel
2829% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002830% different level ranges for each of the color channels seperatally.
2831%
2832% If the boolean 'invert' is set true the image values will modifyed in the
2833% reverse direction. That is any existing "black" and "white" colors in the
2834% image will become the color values given, with all other values compressed
2835% appropriatally. This effectivally maps a greyscale gradient into the given
2836% color gradient.
2837%
cristy308b4e62009-09-21 14:40:44 +00002838% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002839%
cristy308b4e62009-09-21 14:40:44 +00002840% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002841% const MagickPixelPacket *black_color,
2842% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002843% MagickBooleanType LevelColorsImageChannel(Image *image,
2844% const ChannelType channel,const MagickPixelPacket *black_color,
2845% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002846%
2847% A description of each parameter follows:
2848%
2849% o image: the image.
2850%
2851% o channel: the channel.
2852%
2853% o black_color: The color to map black to/from
2854%
2855% o white_point: The color to map white to/from
2856%
2857% o invert: if true map the colors (levelize), rather than from (level)
2858%
2859*/
cristy308b4e62009-09-21 14:40:44 +00002860
2861MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002862 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2863 const MagickBooleanType invert)
2864{
cristy308b4e62009-09-21 14:40:44 +00002865 MagickBooleanType
2866 status;
cristy3ed852e2009-09-05 21:47:34 +00002867
cristy308b4e62009-09-21 14:40:44 +00002868 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2869 invert);
2870 return(status);
2871}
2872
2873MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2874 const ChannelType channel,const MagickPixelPacket *black_color,
2875 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2876{
cristy3ed852e2009-09-05 21:47:34 +00002877 MagickStatusType
2878 status;
2879
2880 /*
2881 Allocate and initialize levels map.
2882 */
2883 assert(image != (Image *) NULL);
2884 assert(image->signature == MagickSignature);
2885 if (image->debug != MagickFalse)
2886 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2887 status=MagickFalse;
2888 if (invert == MagickFalse)
2889 {
2890 if ((channel & RedChannel) != 0)
2891 status|=LevelImageChannel(image,RedChannel,
2892 black_color->red,white_color->red,(double) 1.0);
2893 if ((channel & GreenChannel) != 0)
2894 status|=LevelImageChannel(image,GreenChannel,
2895 black_color->green,white_color->green,(double) 1.0);
2896 if ((channel & BlueChannel) != 0)
2897 status|=LevelImageChannel(image,BlueChannel,
2898 black_color->blue,white_color->blue,(double) 1.0);
2899 if (((channel & OpacityChannel) != 0) &&
2900 (image->matte == MagickTrue))
2901 status|=LevelImageChannel(image,OpacityChannel,
2902 black_color->opacity,white_color->opacity,(double) 1.0);
2903 if (((channel & IndexChannel) != 0) &&
2904 (image->colorspace == CMYKColorspace))
2905 status|=LevelImageChannel(image,IndexChannel,
2906 black_color->index,white_color->index,(double) 1.0);
2907 }
2908 else
2909 {
2910 if ((channel & RedChannel) != 0)
2911 status|=LevelizeImageChannel(image,RedChannel,
2912 black_color->red,white_color->red,(double) 1.0);
2913 if ((channel & GreenChannel) != 0)
2914 status|=LevelizeImageChannel(image,GreenChannel,
2915 black_color->green,white_color->green,(double) 1.0);
2916 if ((channel & BlueChannel) != 0)
2917 status|=LevelizeImageChannel(image,BlueChannel,
2918 black_color->blue,white_color->blue,(double) 1.0);
2919 if (((channel & OpacityChannel) != 0) &&
2920 (image->matte == MagickTrue))
2921 status|=LevelizeImageChannel(image,OpacityChannel,
2922 black_color->opacity,white_color->opacity,(double) 1.0);
2923 if (((channel & IndexChannel) != 0) &&
2924 (image->colorspace == CMYKColorspace))
2925 status|=LevelizeImageChannel(image,IndexChannel,
2926 black_color->index,white_color->index,(double) 1.0);
2927 }
2928 return(status == 0 ? MagickFalse : MagickTrue);
2929}
2930
2931/*
2932%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2933% %
2934% %
2935% %
2936% L i n e a r S t r e t c h I m a g e %
2937% %
2938% %
2939% %
2940%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2941%
2942% The LinearStretchImage() discards any pixels below the black point and
2943% above the white point and levels the remaining pixels.
2944%
2945% The format of the LinearStretchImage method is:
2946%
2947% MagickBooleanType LinearStretchImage(Image *image,
2948% const double black_point,const double white_point)
2949%
2950% A description of each parameter follows:
2951%
2952% o image: the image.
2953%
2954% o black_point: the black point.
2955%
2956% o white_point: the white point.
2957%
2958*/
2959MagickExport MagickBooleanType LinearStretchImage(Image *image,
2960 const double black_point,const double white_point)
2961{
2962#define LinearStretchImageTag "LinearStretch/Image"
2963
2964 ExceptionInfo
2965 *exception;
2966
2967 long
2968 black,
2969 white,
2970 y;
2971
2972 MagickBooleanType
2973 status;
2974
2975 MagickRealType
2976 *histogram,
2977 intensity;
2978
2979 MagickSizeType
2980 number_pixels;
2981
2982 /*
2983 Allocate histogram and linear map.
2984 */
2985 assert(image != (Image *) NULL);
2986 assert(image->signature == MagickSignature);
2987 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2988 sizeof(*histogram));
2989 if (histogram == (MagickRealType *) NULL)
2990 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2991 image->filename);
2992 /*
2993 Form histogram.
2994 */
2995 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2996 exception=(&image->exception);
2997 for (y=0; y < (long) image->rows; y++)
2998 {
2999 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003000 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003001
3002 register long
3003 x;
3004
3005 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3006 if (p == (const PixelPacket *) NULL)
3007 break;
3008 for (x=(long) image->columns-1; x >= 0; x--)
3009 {
3010 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3011 p++;
3012 }
3013 }
3014 /*
3015 Find the histogram boundaries by locating the black and white point levels.
3016 */
3017 number_pixels=(MagickSizeType) image->columns*image->rows;
3018 intensity=0.0;
3019 for (black=0; black < (long) MaxMap; black++)
3020 {
3021 intensity+=histogram[black];
3022 if (intensity >= black_point)
3023 break;
3024 }
3025 intensity=0.0;
3026 for (white=(long) MaxMap; white != 0; white--)
3027 {
3028 intensity+=histogram[white];
3029 if (intensity >= white_point)
3030 break;
3031 }
3032 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3033 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3034 1.0);
3035 return(status);
3036}
3037
3038/*
3039%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3040% %
3041% %
3042% %
3043% M o d u l a t e I m a g e %
3044% %
3045% %
3046% %
3047%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3048%
3049% ModulateImage() lets you control the brightness, saturation, and hue
3050% of an image. Modulate represents the brightness, saturation, and hue
3051% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3052% modulation is lightness, saturation, and hue. And if the colorspace is
3053% HWB, use blackness, whiteness, and hue.
3054%
3055% The format of the ModulateImage method is:
3056%
3057% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3058%
3059% A description of each parameter follows:
3060%
3061% o image: the image.
3062%
3063% o modulate: Define the percent change in brightness, saturation, and
3064% hue.
3065%
3066*/
3067
3068static void ModulateHSB(const double percent_hue,
3069 const double percent_saturation,const double percent_brightness,
3070 Quantum *red,Quantum *green,Quantum *blue)
3071{
3072 double
3073 brightness,
3074 hue,
3075 saturation;
3076
3077 /*
3078 Increase or decrease color brightness, saturation, or hue.
3079 */
3080 assert(red != (Quantum *) NULL);
3081 assert(green != (Quantum *) NULL);
3082 assert(blue != (Quantum *) NULL);
3083 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3084 hue+=0.5*(0.01*percent_hue-1.0);
3085 while (hue < 0.0)
3086 hue+=1.0;
3087 while (hue > 1.0)
3088 hue-=1.0;
3089 saturation*=0.01*percent_saturation;
3090 brightness*=0.01*percent_brightness;
3091 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3092}
3093
3094static void ModulateHSL(const double percent_hue,
3095 const double percent_saturation,const double percent_lightness,
3096 Quantum *red,Quantum *green,Quantum *blue)
3097{
3098 double
3099 hue,
3100 lightness,
3101 saturation;
3102
3103 /*
3104 Increase or decrease color lightness, saturation, or hue.
3105 */
3106 assert(red != (Quantum *) NULL);
3107 assert(green != (Quantum *) NULL);
3108 assert(blue != (Quantum *) NULL);
3109 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3110 hue+=0.5*(0.01*percent_hue-1.0);
3111 while (hue < 0.0)
3112 hue+=1.0;
3113 while (hue > 1.0)
3114 hue-=1.0;
3115 saturation*=0.01*percent_saturation;
3116 lightness*=0.01*percent_lightness;
3117 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3118}
3119
3120static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3121{
3122 double
3123 blackness,
3124 hue,
3125 whiteness;
3126
3127 /*
3128 Increase or decrease color blackness, whiteness, or hue.
3129 */
3130 assert(red != (Quantum *) NULL);
3131 assert(green != (Quantum *) NULL);
3132 assert(blue != (Quantum *) NULL);
3133 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3134 hue+=0.5*(0.01*percent_hue-1.0);
3135 while (hue < 0.0)
3136 hue+=1.0;
3137 while (hue > 1.0)
3138 hue-=1.0;
3139 blackness*=0.01*percent_blackness;
3140 whiteness*=0.01*percent_whiteness;
3141 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3142}
3143
3144MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3145{
3146#define ModulateImageTag "Modulate/Image"
3147
cristyc4c8d132010-01-07 01:58:38 +00003148 CacheView
3149 *image_view;
3150
cristy3ed852e2009-09-05 21:47:34 +00003151 ColorspaceType
3152 colorspace;
3153
3154 const char
3155 *artifact;
3156
3157 double
3158 percent_brightness,
3159 percent_hue,
3160 percent_saturation;
3161
3162 ExceptionInfo
3163 *exception;
3164
3165 GeometryInfo
3166 geometry_info;
3167
3168 long
3169 progress,
3170 y;
3171
3172 MagickBooleanType
3173 status;
3174
3175 MagickStatusType
3176 flags;
3177
3178 register long
3179 i;
3180
cristy3ed852e2009-09-05 21:47:34 +00003181 /*
cristy2b726bd2010-01-11 01:05:39 +00003182 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003183 */
3184 assert(image != (Image *) NULL);
3185 assert(image->signature == MagickSignature);
3186 if (image->debug != MagickFalse)
3187 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3188 if (modulate == (char *) NULL)
3189 return(MagickFalse);
3190 flags=ParseGeometry(modulate,&geometry_info);
3191 percent_brightness=geometry_info.rho;
3192 percent_saturation=geometry_info.sigma;
3193 if ((flags & SigmaValue) == 0)
3194 percent_saturation=100.0;
3195 percent_hue=geometry_info.xi;
3196 if ((flags & XiValue) == 0)
3197 percent_hue=100.0;
3198 colorspace=UndefinedColorspace;
3199 artifact=GetImageArtifact(image,"modulate:colorspace");
3200 if (artifact != (const char *) NULL)
3201 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3202 MagickFalse,artifact);
3203 if (image->storage_class == PseudoClass)
3204 {
3205 /*
3206 Modulate colormap.
3207 */
cristyb5d5f722009-11-04 03:03:49 +00003208#if defined(MAGICKCORE_OPENMP_SUPPORT)
3209 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003210#endif
3211 for (i=0; i < (long) image->colors; i++)
3212 switch (colorspace)
3213 {
3214 case HSBColorspace:
3215 {
3216 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3217 &image->colormap[i].red,&image->colormap[i].green,
3218 &image->colormap[i].blue);
3219 break;
3220 }
3221 case HSLColorspace:
3222 default:
3223 {
3224 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3225 &image->colormap[i].red,&image->colormap[i].green,
3226 &image->colormap[i].blue);
3227 break;
3228 }
3229 case HWBColorspace:
3230 {
3231 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3232 &image->colormap[i].red,&image->colormap[i].green,
3233 &image->colormap[i].blue);
3234 break;
3235 }
3236 }
3237 }
3238 /*
3239 Modulate image.
3240 */
3241 status=MagickTrue;
3242 progress=0;
3243 exception=(&image->exception);
3244 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003245#if defined(MAGICKCORE_OPENMP_SUPPORT)
3246 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003247#endif
3248 for (y=0; y < (long) image->rows; y++)
3249 {
3250 register long
3251 x;
3252
3253 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003254 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003255
3256 if (status == MagickFalse)
3257 continue;
3258 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3259 if (q == (PixelPacket *) NULL)
3260 {
3261 status=MagickFalse;
3262 continue;
3263 }
3264 for (x=0; x < (long) image->columns; x++)
3265 {
3266 switch (colorspace)
3267 {
3268 case HSBColorspace:
3269 {
3270 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3271 &q->red,&q->green,&q->blue);
3272 break;
3273 }
3274 case HSLColorspace:
3275 default:
3276 {
3277 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3278 &q->red,&q->green,&q->blue);
3279 break;
3280 }
3281 case HWBColorspace:
3282 {
3283 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3284 &q->red,&q->green,&q->blue);
3285 break;
3286 }
3287 }
3288 q++;
3289 }
3290 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3291 status=MagickFalse;
3292 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3293 {
3294 MagickBooleanType
3295 proceed;
3296
cristyb5d5f722009-11-04 03:03:49 +00003297#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003298 #pragma omp critical (MagickCore_ModulateImage)
3299#endif
3300 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3301 if (proceed == MagickFalse)
3302 status=MagickFalse;
3303 }
3304 }
3305 image_view=DestroyCacheView(image_view);
3306 return(status);
3307}
3308
3309/*
3310%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3311% %
3312% %
3313% %
3314% N e g a t e I m a g e %
3315% %
3316% %
3317% %
3318%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3319%
3320% NegateImage() negates the colors in the reference image. The grayscale
3321% option means that only grayscale values within the image are negated.
3322%
3323% The format of the NegateImageChannel method is:
3324%
3325% MagickBooleanType NegateImage(Image *image,
3326% const MagickBooleanType grayscale)
3327% MagickBooleanType NegateImageChannel(Image *image,
3328% const ChannelType channel,const MagickBooleanType grayscale)
3329%
3330% A description of each parameter follows:
3331%
3332% o image: the image.
3333%
3334% o channel: the channel.
3335%
3336% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3337%
3338*/
3339
3340MagickExport MagickBooleanType NegateImage(Image *image,
3341 const MagickBooleanType grayscale)
3342{
3343 MagickBooleanType
3344 status;
3345
3346 status=NegateImageChannel(image,DefaultChannels,grayscale);
3347 return(status);
3348}
3349
3350MagickExport MagickBooleanType NegateImageChannel(Image *image,
3351 const ChannelType channel,const MagickBooleanType grayscale)
3352{
3353#define NegateImageTag "Negate/Image"
3354
cristyc4c8d132010-01-07 01:58:38 +00003355 CacheView
3356 *image_view;
3357
cristy3ed852e2009-09-05 21:47:34 +00003358 ExceptionInfo
3359 *exception;
3360
3361 long
3362 progress,
3363 y;
3364
3365 MagickBooleanType
3366 status;
3367
3368 register long
3369 i;
3370
cristy3ed852e2009-09-05 21:47:34 +00003371 assert(image != (Image *) NULL);
3372 assert(image->signature == MagickSignature);
3373 if (image->debug != MagickFalse)
3374 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3375 if (image->storage_class == PseudoClass)
3376 {
3377 /*
3378 Negate colormap.
3379 */
cristyb5d5f722009-11-04 03:03:49 +00003380#if defined(MAGICKCORE_OPENMP_SUPPORT)
3381 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003382#endif
3383 for (i=0; i < (long) image->colors; i++)
3384 {
3385 if (grayscale != MagickFalse)
3386 if ((image->colormap[i].red != image->colormap[i].green) ||
3387 (image->colormap[i].green != image->colormap[i].blue))
3388 continue;
3389 if ((channel & RedChannel) != 0)
3390 image->colormap[i].red=(Quantum) QuantumRange-
3391 image->colormap[i].red;
3392 if ((channel & GreenChannel) != 0)
3393 image->colormap[i].green=(Quantum) QuantumRange-
3394 image->colormap[i].green;
3395 if ((channel & BlueChannel) != 0)
3396 image->colormap[i].blue=(Quantum) QuantumRange-
3397 image->colormap[i].blue;
3398 }
3399 }
3400 /*
3401 Negate image.
3402 */
3403 status=MagickTrue;
3404 progress=0;
3405 exception=(&image->exception);
3406 image_view=AcquireCacheView(image);
3407 if (grayscale != MagickFalse)
3408 {
cristyb5d5f722009-11-04 03:03:49 +00003409#if defined(MAGICKCORE_OPENMP_SUPPORT)
3410 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003411#endif
3412 for (y=0; y < (long) image->rows; y++)
3413 {
3414 MagickBooleanType
3415 sync;
3416
3417 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003418 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003419
3420 register long
3421 x;
3422
3423 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003424 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003425
3426 if (status == MagickFalse)
3427 continue;
3428 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3429 exception);
3430 if (q == (PixelPacket *) NULL)
3431 {
3432 status=MagickFalse;
3433 continue;
3434 }
3435 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3436 for (x=0; x < (long) image->columns; x++)
3437 {
3438 if ((q->red != q->green) || (q->green != q->blue))
3439 {
3440 q++;
3441 continue;
3442 }
3443 if ((channel & RedChannel) != 0)
3444 q->red=(Quantum) QuantumRange-q->red;
3445 if ((channel & GreenChannel) != 0)
3446 q->green=(Quantum) QuantumRange-q->green;
3447 if ((channel & BlueChannel) != 0)
3448 q->blue=(Quantum) QuantumRange-q->blue;
3449 if ((channel & OpacityChannel) != 0)
3450 q->opacity=(Quantum) QuantumRange-q->opacity;
3451 if (((channel & IndexChannel) != 0) &&
3452 (image->colorspace == CMYKColorspace))
3453 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3454 q++;
3455 }
3456 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3457 if (sync == MagickFalse)
3458 status=MagickFalse;
3459 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3460 {
3461 MagickBooleanType
3462 proceed;
3463
cristyb5d5f722009-11-04 03:03:49 +00003464#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003465 #pragma omp critical (MagickCore_NegateImageChannel)
3466#endif
3467 proceed=SetImageProgress(image,NegateImageTag,progress++,
3468 image->rows);
3469 if (proceed == MagickFalse)
3470 status=MagickFalse;
3471 }
3472 }
3473 image_view=DestroyCacheView(image_view);
3474 return(MagickTrue);
3475 }
3476 /*
3477 Negate image.
3478 */
cristyb5d5f722009-11-04 03:03:49 +00003479#if defined(MAGICKCORE_OPENMP_SUPPORT)
3480 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003481#endif
3482 for (y=0; y < (long) image->rows; y++)
3483 {
3484 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003485 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003486
3487 register long
3488 x;
3489
3490 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003491 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003492
3493 if (status == MagickFalse)
3494 continue;
3495 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3496 if (q == (PixelPacket *) NULL)
3497 {
3498 status=MagickFalse;
3499 continue;
3500 }
3501 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3502 for (x=0; x < (long) image->columns; x++)
3503 {
3504 if ((channel & RedChannel) != 0)
3505 q->red=(Quantum) QuantumRange-q->red;
3506 if ((channel & GreenChannel) != 0)
3507 q->green=(Quantum) QuantumRange-q->green;
3508 if ((channel & BlueChannel) != 0)
3509 q->blue=(Quantum) QuantumRange-q->blue;
3510 if ((channel & OpacityChannel) != 0)
3511 q->opacity=(Quantum) QuantumRange-q->opacity;
3512 if (((channel & IndexChannel) != 0) &&
3513 (image->colorspace == CMYKColorspace))
3514 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3515 q++;
3516 }
3517 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3518 status=MagickFalse;
3519 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3520 {
3521 MagickBooleanType
3522 proceed;
3523
cristyb5d5f722009-11-04 03:03:49 +00003524#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003525 #pragma omp critical (MagickCore_NegateImageChannel)
3526#endif
3527 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3528 if (proceed == MagickFalse)
3529 status=MagickFalse;
3530 }
3531 }
3532 image_view=DestroyCacheView(image_view);
3533 return(status);
3534}
3535
3536/*
3537%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3538% %
3539% %
3540% %
3541% N o r m a l i z e I m a g e %
3542% %
3543% %
3544% %
3545%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3546%
3547% The NormalizeImage() method enhances the contrast of a color image by
3548% mapping the darkest 2 percent of all pixel to black and the brightest
3549% 1 percent to white.
3550%
3551% The format of the NormalizeImage method is:
3552%
3553% MagickBooleanType NormalizeImage(Image *image)
3554% MagickBooleanType NormalizeImageChannel(Image *image,
3555% const ChannelType channel)
3556%
3557% A description of each parameter follows:
3558%
3559% o image: the image.
3560%
3561% o channel: the channel.
3562%
3563*/
3564
3565MagickExport MagickBooleanType NormalizeImage(Image *image)
3566{
3567 MagickBooleanType
3568 status;
3569
3570 status=NormalizeImageChannel(image,DefaultChannels);
3571 return(status);
3572}
3573
3574MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3575 const ChannelType channel)
3576{
3577 double
3578 black_point,
3579 white_point;
3580
3581 black_point=(double) image->columns*image->rows*0.02;
3582 white_point=(double) image->columns*image->rows*0.99;
3583 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3584}
3585
3586/*
3587%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3588% %
3589% %
3590% %
3591% S i g m o i d a l C o n t r a s t I m a g e %
3592% %
3593% %
3594% %
3595%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3596%
3597% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3598% sigmoidal contrast algorithm. Increase the contrast of the image using a
3599% sigmoidal transfer function without saturating highlights or shadows.
3600% Contrast indicates how much to increase the contrast (0 is none; 3 is
3601% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3602% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3603% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3604% is reduced.
3605%
3606% The format of the SigmoidalContrastImage method is:
3607%
3608% MagickBooleanType SigmoidalContrastImage(Image *image,
3609% const MagickBooleanType sharpen,const char *levels)
3610% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3611% const ChannelType channel,const MagickBooleanType sharpen,
3612% const double contrast,const double midpoint)
3613%
3614% A description of each parameter follows:
3615%
3616% o image: the image.
3617%
3618% o channel: the channel.
3619%
3620% o sharpen: Increase or decrease image contrast.
3621%
3622% o contrast: control the "shoulder" of the contast curve.
3623%
3624% o midpoint: control the "toe" of the contast curve.
3625%
3626*/
3627
3628MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3629 const MagickBooleanType sharpen,const char *levels)
3630{
3631 GeometryInfo
3632 geometry_info;
3633
3634 MagickBooleanType
3635 status;
3636
3637 MagickStatusType
3638 flags;
3639
3640 flags=ParseGeometry(levels,&geometry_info);
3641 if ((flags & SigmaValue) == 0)
3642 geometry_info.sigma=1.0*QuantumRange/2.0;
3643 if ((flags & PercentValue) != 0)
3644 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3645 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3646 geometry_info.rho,geometry_info.sigma);
3647 return(status);
3648}
3649
3650MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3651 const ChannelType channel,const MagickBooleanType sharpen,
3652 const double contrast,const double midpoint)
3653{
3654#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3655
cristyc4c8d132010-01-07 01:58:38 +00003656 CacheView
3657 *image_view;
3658
cristy3ed852e2009-09-05 21:47:34 +00003659 ExceptionInfo
3660 *exception;
3661
3662 long
3663 progress,
3664 y;
3665
3666 MagickBooleanType
3667 status;
3668
3669 MagickRealType
3670 *sigmoidal_map;
3671
3672 register long
3673 i;
3674
cristy3ed852e2009-09-05 21:47:34 +00003675 /*
3676 Allocate and initialize sigmoidal maps.
3677 */
3678 assert(image != (Image *) NULL);
3679 assert(image->signature == MagickSignature);
3680 if (image->debug != MagickFalse)
3681 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3682 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3683 sizeof(*sigmoidal_map));
3684 if (sigmoidal_map == (MagickRealType *) NULL)
3685 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3686 image->filename);
3687 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003688#if defined(MAGICKCORE_OPENMP_SUPPORT)
3689 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003690#endif
3691 for (i=0; i <= (long) MaxMap; i++)
3692 {
3693 if (sharpen != MagickFalse)
3694 {
3695 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3696 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3697 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3698 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3699 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3700 (double) QuantumRange)))))+0.5));
3701 continue;
3702 }
3703 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3704 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3705 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3706 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3707 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3708 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3709 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3710 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3711 (double) QuantumRange*contrast))))))/contrast)));
3712 }
3713 if (image->storage_class == PseudoClass)
3714 {
3715 /*
3716 Sigmoidal-contrast enhance colormap.
3717 */
cristyb5d5f722009-11-04 03:03:49 +00003718#if defined(MAGICKCORE_OPENMP_SUPPORT)
3719 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003720#endif
3721 for (i=0; i < (long) image->colors; i++)
3722 {
3723 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003724 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003725 ScaleQuantumToMap(image->colormap[i].red)]);
3726 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003727 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003728 ScaleQuantumToMap(image->colormap[i].green)]);
3729 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003730 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003731 ScaleQuantumToMap(image->colormap[i].blue)]);
3732 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003733 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003734 ScaleQuantumToMap(image->colormap[i].opacity)]);
3735 }
3736 }
3737 /*
3738 Sigmoidal-contrast enhance image.
3739 */
3740 status=MagickTrue;
3741 progress=0;
3742 exception=(&image->exception);
3743 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003744#if defined(MAGICKCORE_OPENMP_SUPPORT)
3745 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003746#endif
3747 for (y=0; y < (long) image->rows; y++)
3748 {
3749 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003750 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003751
3752 register long
3753 x;
3754
3755 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003756 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003757
3758 if (status == MagickFalse)
3759 continue;
3760 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3761 if (q == (PixelPacket *) NULL)
3762 {
3763 status=MagickFalse;
3764 continue;
3765 }
3766 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3767 for (x=0; x < (long) image->columns; x++)
3768 {
3769 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003770 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003771 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003772 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003773 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003774 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003775 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003776 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003777 if (((channel & IndexChannel) != 0) &&
3778 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003779 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003780 ScaleQuantumToMap(indexes[x])]);
3781 q++;
3782 }
3783 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3784 status=MagickFalse;
3785 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3786 {
3787 MagickBooleanType
3788 proceed;
3789
cristyb5d5f722009-11-04 03:03:49 +00003790#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003791 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3792#endif
3793 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3794 image->rows);
3795 if (proceed == MagickFalse)
3796 status=MagickFalse;
3797 }
3798 }
3799 image_view=DestroyCacheView(image_view);
3800 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3801 return(status);
3802}