blob: 629286a49e7a9dc32bad02a6d250272c50d3b318 [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"
cristyce70c172010-01-07 17:15:30 +00002547#define LevelValue(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristy3ed852e2009-09-05 21:47:34 +00002548 pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
2549
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
cristy3ed852e2009-09-05 21:47:34 +00002566 /*
2567 Allocate and initialize levels map.
2568 */
2569 assert(image != (Image *) NULL);
2570 assert(image->signature == MagickSignature);
2571 if (image->debug != MagickFalse)
2572 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2573 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002574#if defined(MAGICKCORE_OPENMP_SUPPORT)
2575 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002576#endif
2577 for (i=0; i < (long) image->colors; i++)
2578 {
2579 /*
2580 Level colormap.
2581 */
2582 if ((channel & RedChannel) != 0)
2583 image->colormap[i].red=LevelValue(image->colormap[i].red);
2584 if ((channel & GreenChannel) != 0)
2585 image->colormap[i].green=LevelValue(image->colormap[i].green);
2586 if ((channel & BlueChannel) != 0)
2587 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2588 if ((channel & OpacityChannel) != 0)
2589 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2590 }
2591 /*
2592 Level image.
2593 */
2594 status=MagickTrue;
2595 progress=0;
2596 exception=(&image->exception);
2597 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002598#if defined(MAGICKCORE_OPENMP_SUPPORT)
2599 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002600#endif
2601 for (y=0; y < (long) image->rows; y++)
2602 {
2603 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002604 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002605
2606 register long
2607 x;
2608
2609 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002610 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002611
2612 if (status == MagickFalse)
2613 continue;
2614 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2615 if (q == (PixelPacket *) NULL)
2616 {
2617 status=MagickFalse;
2618 continue;
2619 }
2620 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2621 for (x=0; x < (long) image->columns; x++)
2622 {
2623 if ((channel & RedChannel) != 0)
2624 q->red=LevelValue(q->red);
2625 if ((channel & GreenChannel) != 0)
2626 q->green=LevelValue(q->green);
2627 if ((channel & BlueChannel) != 0)
2628 q->blue=LevelValue(q->blue);
2629 if (((channel & OpacityChannel) != 0) &&
2630 (image->matte == MagickTrue))
2631 q->opacity=LevelValue(q->opacity);
2632 if (((channel & IndexChannel) != 0) &&
2633 (image->colorspace == CMYKColorspace))
2634 indexes[x]=LevelValue(indexes[x]);
2635 q++;
2636 }
2637 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2638 status=MagickFalse;
2639 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2640 {
2641 MagickBooleanType
2642 proceed;
2643
cristyb5d5f722009-11-04 03:03:49 +00002644#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002645 #pragma omp critical (MagickCore_LevelImageChannel)
2646#endif
2647 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2648 if (proceed == MagickFalse)
2649 status=MagickFalse;
2650 }
2651 }
2652 image_view=DestroyCacheView(image_view);
2653 return(status);
2654}
2655
2656/*
2657%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2658% %
2659% %
2660% %
2661% L e v e l i z e I m a g e C h a n n e l %
2662% %
2663% %
2664% %
2665%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2666%
2667% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2668% the specific channels specified. It compresses the full range of color
2669% values, so that they lie between the given black and white points. Gamma is
2670% applied before the values are mapped.
2671%
2672% LevelizeImageChannel() can be called with by using a +level command line
2673% API option, or using a '!' on a -level or LevelImage() geometry string.
2674%
2675% It can be used for example de-contrast a greyscale image to the exact
2676% levels specified. Or by using specific levels for each channel of an image
2677% you can convert a gray-scale image to any linear color gradient, according
2678% to those levels.
2679%
2680% The format of the LevelizeImageChannel method is:
2681%
2682% MagickBooleanType LevelizeImageChannel(Image *image,
2683% const ChannelType channel,const char *levels)
2684%
2685% A description of each parameter follows:
2686%
2687% o image: the image.
2688%
2689% o channel: the channel.
2690%
2691% o black_point: The level to map zero (black) to.
2692%
2693% o white_point: The level to map QuantiumRange (white) to.
2694%
2695% o gamma: adjust gamma by this factor before mapping values.
2696%
2697*/
2698MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2699 const ChannelType channel,const double black_point,const double white_point,
2700 const double gamma)
2701{
2702#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002703#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002704 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2705 black_point))
2706
cristyc4c8d132010-01-07 01:58:38 +00002707 CacheView
2708 *image_view;
2709
cristy3ed852e2009-09-05 21:47:34 +00002710 ExceptionInfo
2711 *exception;
2712
2713 long
2714 progress,
2715 y;
2716
2717 MagickBooleanType
2718 status;
2719
2720 register long
2721 i;
2722
cristy3ed852e2009-09-05 21:47:34 +00002723 /*
2724 Allocate and initialize levels map.
2725 */
2726 assert(image != (Image *) NULL);
2727 assert(image->signature == MagickSignature);
2728 if (image->debug != MagickFalse)
2729 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2730 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002731#if defined(MAGICKCORE_OPENMP_SUPPORT)
2732 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002733#endif
2734 for (i=0; i < (long) image->colors; i++)
2735 {
2736 /*
2737 Level colormap.
2738 */
2739 if ((channel & RedChannel) != 0)
2740 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2741 if ((channel & GreenChannel) != 0)
2742 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2743 if ((channel & BlueChannel) != 0)
2744 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2745 if ((channel & OpacityChannel) != 0)
2746 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2747 }
2748 /*
2749 Level image.
2750 */
2751 status=MagickTrue;
2752 progress=0;
2753 exception=(&image->exception);
2754 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002755#if defined(MAGICKCORE_OPENMP_SUPPORT)
2756 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002757#endif
2758 for (y=0; y < (long) image->rows; y++)
2759 {
2760 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002761 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002762
2763 register long
2764 x;
2765
2766 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002767 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002768
2769 if (status == MagickFalse)
2770 continue;
2771 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2772 if (q == (PixelPacket *) NULL)
2773 {
2774 status=MagickFalse;
2775 continue;
2776 }
2777 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2778 for (x=0; x < (long) image->columns; x++)
2779 {
2780 if ((channel & RedChannel) != 0)
2781 q->red=LevelizeValue(q->red);
2782 if ((channel & GreenChannel) != 0)
2783 q->green=LevelizeValue(q->green);
2784 if ((channel & BlueChannel) != 0)
2785 q->blue=LevelizeValue(q->blue);
2786 if (((channel & OpacityChannel) != 0) &&
2787 (image->matte == MagickTrue))
2788 q->opacity=LevelizeValue(q->opacity);
2789 if (((channel & IndexChannel) != 0) &&
2790 (image->colorspace == CMYKColorspace))
2791 indexes[x]=LevelizeValue(indexes[x]);
2792 q++;
2793 }
2794 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2795 status=MagickFalse;
2796 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2797 {
2798 MagickBooleanType
2799 proceed;
2800
cristyb5d5f722009-11-04 03:03:49 +00002801#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002802 #pragma omp critical (MagickCore_LevelizeImageChannel)
2803#endif
2804 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2805 if (proceed == MagickFalse)
2806 status=MagickFalse;
2807 }
2808 }
2809 return(status);
2810}
2811
2812/*
2813%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2814% %
2815% %
2816% %
2817% L e v e l I m a g e C o l o r s %
2818% %
2819% %
2820% %
2821%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2822%
cristyee0f8d72009-09-19 00:58:29 +00002823% LevelImageColor() maps the given color to "black" and "white" values,
2824% linearly spreading out the colors, and level values on a channel by channel
2825% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002826% different level ranges for each of the color channels seperatally.
2827%
2828% If the boolean 'invert' is set true the image values will modifyed in the
2829% reverse direction. That is any existing "black" and "white" colors in the
2830% image will become the color values given, with all other values compressed
2831% appropriatally. This effectivally maps a greyscale gradient into the given
2832% color gradient.
2833%
cristy308b4e62009-09-21 14:40:44 +00002834% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002835%
cristy308b4e62009-09-21 14:40:44 +00002836% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002837% const MagickPixelPacket *black_color,
2838% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002839% MagickBooleanType LevelColorsImageChannel(Image *image,
2840% const ChannelType channel,const MagickPixelPacket *black_color,
2841% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002842%
2843% A description of each parameter follows:
2844%
2845% o image: the image.
2846%
2847% o channel: the channel.
2848%
2849% o black_color: The color to map black to/from
2850%
2851% o white_point: The color to map white to/from
2852%
2853% o invert: if true map the colors (levelize), rather than from (level)
2854%
2855*/
cristy308b4e62009-09-21 14:40:44 +00002856
2857MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002858 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2859 const MagickBooleanType invert)
2860{
cristy308b4e62009-09-21 14:40:44 +00002861 MagickBooleanType
2862 status;
cristy3ed852e2009-09-05 21:47:34 +00002863
cristy308b4e62009-09-21 14:40:44 +00002864 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2865 invert);
2866 return(status);
2867}
2868
2869MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2870 const ChannelType channel,const MagickPixelPacket *black_color,
2871 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2872{
cristy3ed852e2009-09-05 21:47:34 +00002873 MagickStatusType
2874 status;
2875
2876 /*
2877 Allocate and initialize levels map.
2878 */
2879 assert(image != (Image *) NULL);
2880 assert(image->signature == MagickSignature);
2881 if (image->debug != MagickFalse)
2882 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2883 status=MagickFalse;
2884 if (invert == MagickFalse)
2885 {
2886 if ((channel & RedChannel) != 0)
2887 status|=LevelImageChannel(image,RedChannel,
2888 black_color->red,white_color->red,(double) 1.0);
2889 if ((channel & GreenChannel) != 0)
2890 status|=LevelImageChannel(image,GreenChannel,
2891 black_color->green,white_color->green,(double) 1.0);
2892 if ((channel & BlueChannel) != 0)
2893 status|=LevelImageChannel(image,BlueChannel,
2894 black_color->blue,white_color->blue,(double) 1.0);
2895 if (((channel & OpacityChannel) != 0) &&
2896 (image->matte == MagickTrue))
2897 status|=LevelImageChannel(image,OpacityChannel,
2898 black_color->opacity,white_color->opacity,(double) 1.0);
2899 if (((channel & IndexChannel) != 0) &&
2900 (image->colorspace == CMYKColorspace))
2901 status|=LevelImageChannel(image,IndexChannel,
2902 black_color->index,white_color->index,(double) 1.0);
2903 }
2904 else
2905 {
2906 if ((channel & RedChannel) != 0)
2907 status|=LevelizeImageChannel(image,RedChannel,
2908 black_color->red,white_color->red,(double) 1.0);
2909 if ((channel & GreenChannel) != 0)
2910 status|=LevelizeImageChannel(image,GreenChannel,
2911 black_color->green,white_color->green,(double) 1.0);
2912 if ((channel & BlueChannel) != 0)
2913 status|=LevelizeImageChannel(image,BlueChannel,
2914 black_color->blue,white_color->blue,(double) 1.0);
2915 if (((channel & OpacityChannel) != 0) &&
2916 (image->matte == MagickTrue))
2917 status|=LevelizeImageChannel(image,OpacityChannel,
2918 black_color->opacity,white_color->opacity,(double) 1.0);
2919 if (((channel & IndexChannel) != 0) &&
2920 (image->colorspace == CMYKColorspace))
2921 status|=LevelizeImageChannel(image,IndexChannel,
2922 black_color->index,white_color->index,(double) 1.0);
2923 }
2924 return(status == 0 ? MagickFalse : MagickTrue);
2925}
2926
2927/*
2928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2929% %
2930% %
2931% %
2932% L i n e a r S t r e t c h I m a g e %
2933% %
2934% %
2935% %
2936%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2937%
2938% The LinearStretchImage() discards any pixels below the black point and
2939% above the white point and levels the remaining pixels.
2940%
2941% The format of the LinearStretchImage method is:
2942%
2943% MagickBooleanType LinearStretchImage(Image *image,
2944% const double black_point,const double white_point)
2945%
2946% A description of each parameter follows:
2947%
2948% o image: the image.
2949%
2950% o black_point: the black point.
2951%
2952% o white_point: the white point.
2953%
2954*/
2955MagickExport MagickBooleanType LinearStretchImage(Image *image,
2956 const double black_point,const double white_point)
2957{
2958#define LinearStretchImageTag "LinearStretch/Image"
2959
2960 ExceptionInfo
2961 *exception;
2962
2963 long
2964 black,
2965 white,
2966 y;
2967
2968 MagickBooleanType
2969 status;
2970
2971 MagickRealType
2972 *histogram,
2973 intensity;
2974
2975 MagickSizeType
2976 number_pixels;
2977
2978 /*
2979 Allocate histogram and linear map.
2980 */
2981 assert(image != (Image *) NULL);
2982 assert(image->signature == MagickSignature);
2983 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2984 sizeof(*histogram));
2985 if (histogram == (MagickRealType *) NULL)
2986 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2987 image->filename);
2988 /*
2989 Form histogram.
2990 */
2991 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2992 exception=(&image->exception);
2993 for (y=0; y < (long) image->rows; y++)
2994 {
2995 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002996 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002997
2998 register long
2999 x;
3000
3001 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3002 if (p == (const PixelPacket *) NULL)
3003 break;
3004 for (x=(long) image->columns-1; x >= 0; x--)
3005 {
3006 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3007 p++;
3008 }
3009 }
3010 /*
3011 Find the histogram boundaries by locating the black and white point levels.
3012 */
3013 number_pixels=(MagickSizeType) image->columns*image->rows;
3014 intensity=0.0;
3015 for (black=0; black < (long) MaxMap; black++)
3016 {
3017 intensity+=histogram[black];
3018 if (intensity >= black_point)
3019 break;
3020 }
3021 intensity=0.0;
3022 for (white=(long) MaxMap; white != 0; white--)
3023 {
3024 intensity+=histogram[white];
3025 if (intensity >= white_point)
3026 break;
3027 }
3028 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3029 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3030 1.0);
3031 return(status);
3032}
3033
3034/*
3035%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3036% %
3037% %
3038% %
3039% M o d u l a t e I m a g e %
3040% %
3041% %
3042% %
3043%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3044%
3045% ModulateImage() lets you control the brightness, saturation, and hue
3046% of an image. Modulate represents the brightness, saturation, and hue
3047% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3048% modulation is lightness, saturation, and hue. And if the colorspace is
3049% HWB, use blackness, whiteness, and hue.
3050%
3051% The format of the ModulateImage method is:
3052%
3053% MagickBooleanType ModulateImage(Image *image,const char *modulate)
3054%
3055% A description of each parameter follows:
3056%
3057% o image: the image.
3058%
3059% o modulate: Define the percent change in brightness, saturation, and
3060% hue.
3061%
3062*/
3063
3064static void ModulateHSB(const double percent_hue,
3065 const double percent_saturation,const double percent_brightness,
3066 Quantum *red,Quantum *green,Quantum *blue)
3067{
3068 double
3069 brightness,
3070 hue,
3071 saturation;
3072
3073 /*
3074 Increase or decrease color brightness, saturation, or hue.
3075 */
3076 assert(red != (Quantum *) NULL);
3077 assert(green != (Quantum *) NULL);
3078 assert(blue != (Quantum *) NULL);
3079 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3080 hue+=0.5*(0.01*percent_hue-1.0);
3081 while (hue < 0.0)
3082 hue+=1.0;
3083 while (hue > 1.0)
3084 hue-=1.0;
3085 saturation*=0.01*percent_saturation;
3086 brightness*=0.01*percent_brightness;
3087 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3088}
3089
3090static void ModulateHSL(const double percent_hue,
3091 const double percent_saturation,const double percent_lightness,
3092 Quantum *red,Quantum *green,Quantum *blue)
3093{
3094 double
3095 hue,
3096 lightness,
3097 saturation;
3098
3099 /*
3100 Increase or decrease color lightness, saturation, or hue.
3101 */
3102 assert(red != (Quantum *) NULL);
3103 assert(green != (Quantum *) NULL);
3104 assert(blue != (Quantum *) NULL);
3105 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3106 hue+=0.5*(0.01*percent_hue-1.0);
3107 while (hue < 0.0)
3108 hue+=1.0;
3109 while (hue > 1.0)
3110 hue-=1.0;
3111 saturation*=0.01*percent_saturation;
3112 lightness*=0.01*percent_lightness;
3113 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3114}
3115
3116static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3117{
3118 double
3119 blackness,
3120 hue,
3121 whiteness;
3122
3123 /*
3124 Increase or decrease color blackness, whiteness, or hue.
3125 */
3126 assert(red != (Quantum *) NULL);
3127 assert(green != (Quantum *) NULL);
3128 assert(blue != (Quantum *) NULL);
3129 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3130 hue+=0.5*(0.01*percent_hue-1.0);
3131 while (hue < 0.0)
3132 hue+=1.0;
3133 while (hue > 1.0)
3134 hue-=1.0;
3135 blackness*=0.01*percent_blackness;
3136 whiteness*=0.01*percent_whiteness;
3137 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3138}
3139
3140MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3141{
3142#define ModulateImageTag "Modulate/Image"
3143
cristyc4c8d132010-01-07 01:58:38 +00003144 CacheView
3145 *image_view;
3146
cristy3ed852e2009-09-05 21:47:34 +00003147 ColorspaceType
3148 colorspace;
3149
3150 const char
3151 *artifact;
3152
3153 double
3154 percent_brightness,
3155 percent_hue,
3156 percent_saturation;
3157
3158 ExceptionInfo
3159 *exception;
3160
3161 GeometryInfo
3162 geometry_info;
3163
3164 long
3165 progress,
3166 y;
3167
3168 MagickBooleanType
3169 status;
3170
3171 MagickStatusType
3172 flags;
3173
3174 register long
3175 i;
3176
cristy3ed852e2009-09-05 21:47:34 +00003177 /*
cristy2b726bd2010-01-11 01:05:39 +00003178 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003179 */
3180 assert(image != (Image *) NULL);
3181 assert(image->signature == MagickSignature);
3182 if (image->debug != MagickFalse)
3183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3184 if (modulate == (char *) NULL)
3185 return(MagickFalse);
3186 flags=ParseGeometry(modulate,&geometry_info);
3187 percent_brightness=geometry_info.rho;
3188 percent_saturation=geometry_info.sigma;
3189 if ((flags & SigmaValue) == 0)
3190 percent_saturation=100.0;
3191 percent_hue=geometry_info.xi;
3192 if ((flags & XiValue) == 0)
3193 percent_hue=100.0;
3194 colorspace=UndefinedColorspace;
3195 artifact=GetImageArtifact(image,"modulate:colorspace");
3196 if (artifact != (const char *) NULL)
3197 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3198 MagickFalse,artifact);
3199 if (image->storage_class == PseudoClass)
3200 {
3201 /*
3202 Modulate colormap.
3203 */
cristyb5d5f722009-11-04 03:03:49 +00003204#if defined(MAGICKCORE_OPENMP_SUPPORT)
3205 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003206#endif
3207 for (i=0; i < (long) image->colors; i++)
3208 switch (colorspace)
3209 {
3210 case HSBColorspace:
3211 {
3212 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3213 &image->colormap[i].red,&image->colormap[i].green,
3214 &image->colormap[i].blue);
3215 break;
3216 }
3217 case HSLColorspace:
3218 default:
3219 {
3220 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3221 &image->colormap[i].red,&image->colormap[i].green,
3222 &image->colormap[i].blue);
3223 break;
3224 }
3225 case HWBColorspace:
3226 {
3227 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3228 &image->colormap[i].red,&image->colormap[i].green,
3229 &image->colormap[i].blue);
3230 break;
3231 }
3232 }
3233 }
3234 /*
3235 Modulate image.
3236 */
3237 status=MagickTrue;
3238 progress=0;
3239 exception=(&image->exception);
3240 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003241#if defined(MAGICKCORE_OPENMP_SUPPORT)
3242 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003243#endif
3244 for (y=0; y < (long) image->rows; y++)
3245 {
3246 register long
3247 x;
3248
3249 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003250 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003251
3252 if (status == MagickFalse)
3253 continue;
3254 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3255 if (q == (PixelPacket *) NULL)
3256 {
3257 status=MagickFalse;
3258 continue;
3259 }
3260 for (x=0; x < (long) image->columns; x++)
3261 {
3262 switch (colorspace)
3263 {
3264 case HSBColorspace:
3265 {
3266 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3267 &q->red,&q->green,&q->blue);
3268 break;
3269 }
3270 case HSLColorspace:
3271 default:
3272 {
3273 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3274 &q->red,&q->green,&q->blue);
3275 break;
3276 }
3277 case HWBColorspace:
3278 {
3279 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3280 &q->red,&q->green,&q->blue);
3281 break;
3282 }
3283 }
3284 q++;
3285 }
3286 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3287 status=MagickFalse;
3288 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3289 {
3290 MagickBooleanType
3291 proceed;
3292
cristyb5d5f722009-11-04 03:03:49 +00003293#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003294 #pragma omp critical (MagickCore_ModulateImage)
3295#endif
3296 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3297 if (proceed == MagickFalse)
3298 status=MagickFalse;
3299 }
3300 }
3301 image_view=DestroyCacheView(image_view);
3302 return(status);
3303}
3304
3305/*
3306%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3307% %
3308% %
3309% %
3310% N e g a t e I m a g e %
3311% %
3312% %
3313% %
3314%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3315%
3316% NegateImage() negates the colors in the reference image. The grayscale
3317% option means that only grayscale values within the image are negated.
3318%
3319% The format of the NegateImageChannel method is:
3320%
3321% MagickBooleanType NegateImage(Image *image,
3322% const MagickBooleanType grayscale)
3323% MagickBooleanType NegateImageChannel(Image *image,
3324% const ChannelType channel,const MagickBooleanType grayscale)
3325%
3326% A description of each parameter follows:
3327%
3328% o image: the image.
3329%
3330% o channel: the channel.
3331%
3332% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3333%
3334*/
3335
3336MagickExport MagickBooleanType NegateImage(Image *image,
3337 const MagickBooleanType grayscale)
3338{
3339 MagickBooleanType
3340 status;
3341
3342 status=NegateImageChannel(image,DefaultChannels,grayscale);
3343 return(status);
3344}
3345
3346MagickExport MagickBooleanType NegateImageChannel(Image *image,
3347 const ChannelType channel,const MagickBooleanType grayscale)
3348{
3349#define NegateImageTag "Negate/Image"
3350
cristyc4c8d132010-01-07 01:58:38 +00003351 CacheView
3352 *image_view;
3353
cristy3ed852e2009-09-05 21:47:34 +00003354 ExceptionInfo
3355 *exception;
3356
3357 long
3358 progress,
3359 y;
3360
3361 MagickBooleanType
3362 status;
3363
3364 register long
3365 i;
3366
cristy3ed852e2009-09-05 21:47:34 +00003367 assert(image != (Image *) NULL);
3368 assert(image->signature == MagickSignature);
3369 if (image->debug != MagickFalse)
3370 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3371 if (image->storage_class == PseudoClass)
3372 {
3373 /*
3374 Negate colormap.
3375 */
cristyb5d5f722009-11-04 03:03:49 +00003376#if defined(MAGICKCORE_OPENMP_SUPPORT)
3377 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003378#endif
3379 for (i=0; i < (long) image->colors; i++)
3380 {
3381 if (grayscale != MagickFalse)
3382 if ((image->colormap[i].red != image->colormap[i].green) ||
3383 (image->colormap[i].green != image->colormap[i].blue))
3384 continue;
3385 if ((channel & RedChannel) != 0)
3386 image->colormap[i].red=(Quantum) QuantumRange-
3387 image->colormap[i].red;
3388 if ((channel & GreenChannel) != 0)
3389 image->colormap[i].green=(Quantum) QuantumRange-
3390 image->colormap[i].green;
3391 if ((channel & BlueChannel) != 0)
3392 image->colormap[i].blue=(Quantum) QuantumRange-
3393 image->colormap[i].blue;
3394 }
3395 }
3396 /*
3397 Negate image.
3398 */
3399 status=MagickTrue;
3400 progress=0;
3401 exception=(&image->exception);
3402 image_view=AcquireCacheView(image);
3403 if (grayscale != MagickFalse)
3404 {
cristyb5d5f722009-11-04 03:03:49 +00003405#if defined(MAGICKCORE_OPENMP_SUPPORT)
3406 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003407#endif
3408 for (y=0; y < (long) image->rows; y++)
3409 {
3410 MagickBooleanType
3411 sync;
3412
3413 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003414 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003415
3416 register long
3417 x;
3418
3419 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003420 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003421
3422 if (status == MagickFalse)
3423 continue;
3424 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3425 exception);
3426 if (q == (PixelPacket *) NULL)
3427 {
3428 status=MagickFalse;
3429 continue;
3430 }
3431 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3432 for (x=0; x < (long) image->columns; x++)
3433 {
3434 if ((q->red != q->green) || (q->green != q->blue))
3435 {
3436 q++;
3437 continue;
3438 }
3439 if ((channel & RedChannel) != 0)
3440 q->red=(Quantum) QuantumRange-q->red;
3441 if ((channel & GreenChannel) != 0)
3442 q->green=(Quantum) QuantumRange-q->green;
3443 if ((channel & BlueChannel) != 0)
3444 q->blue=(Quantum) QuantumRange-q->blue;
3445 if ((channel & OpacityChannel) != 0)
3446 q->opacity=(Quantum) QuantumRange-q->opacity;
3447 if (((channel & IndexChannel) != 0) &&
3448 (image->colorspace == CMYKColorspace))
3449 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3450 q++;
3451 }
3452 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3453 if (sync == MagickFalse)
3454 status=MagickFalse;
3455 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3456 {
3457 MagickBooleanType
3458 proceed;
3459
cristyb5d5f722009-11-04 03:03:49 +00003460#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003461 #pragma omp critical (MagickCore_NegateImageChannel)
3462#endif
3463 proceed=SetImageProgress(image,NegateImageTag,progress++,
3464 image->rows);
3465 if (proceed == MagickFalse)
3466 status=MagickFalse;
3467 }
3468 }
3469 image_view=DestroyCacheView(image_view);
3470 return(MagickTrue);
3471 }
3472 /*
3473 Negate image.
3474 */
cristyb5d5f722009-11-04 03:03:49 +00003475#if defined(MAGICKCORE_OPENMP_SUPPORT)
3476 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003477#endif
3478 for (y=0; y < (long) image->rows; y++)
3479 {
3480 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003481 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003482
3483 register long
3484 x;
3485
3486 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003487 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003488
3489 if (status == MagickFalse)
3490 continue;
3491 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3492 if (q == (PixelPacket *) NULL)
3493 {
3494 status=MagickFalse;
3495 continue;
3496 }
3497 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3498 for (x=0; x < (long) image->columns; x++)
3499 {
3500 if ((channel & RedChannel) != 0)
3501 q->red=(Quantum) QuantumRange-q->red;
3502 if ((channel & GreenChannel) != 0)
3503 q->green=(Quantum) QuantumRange-q->green;
3504 if ((channel & BlueChannel) != 0)
3505 q->blue=(Quantum) QuantumRange-q->blue;
3506 if ((channel & OpacityChannel) != 0)
3507 q->opacity=(Quantum) QuantumRange-q->opacity;
3508 if (((channel & IndexChannel) != 0) &&
3509 (image->colorspace == CMYKColorspace))
3510 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3511 q++;
3512 }
3513 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3514 status=MagickFalse;
3515 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3516 {
3517 MagickBooleanType
3518 proceed;
3519
cristyb5d5f722009-11-04 03:03:49 +00003520#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003521 #pragma omp critical (MagickCore_NegateImageChannel)
3522#endif
3523 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3524 if (proceed == MagickFalse)
3525 status=MagickFalse;
3526 }
3527 }
3528 image_view=DestroyCacheView(image_view);
3529 return(status);
3530}
3531
3532/*
3533%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3534% %
3535% %
3536% %
3537% N o r m a l i z e I m a g e %
3538% %
3539% %
3540% %
3541%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3542%
3543% The NormalizeImage() method enhances the contrast of a color image by
3544% mapping the darkest 2 percent of all pixel to black and the brightest
3545% 1 percent to white.
3546%
3547% The format of the NormalizeImage method is:
3548%
3549% MagickBooleanType NormalizeImage(Image *image)
3550% MagickBooleanType NormalizeImageChannel(Image *image,
3551% const ChannelType channel)
3552%
3553% A description of each parameter follows:
3554%
3555% o image: the image.
3556%
3557% o channel: the channel.
3558%
3559*/
3560
3561MagickExport MagickBooleanType NormalizeImage(Image *image)
3562{
3563 MagickBooleanType
3564 status;
3565
3566 status=NormalizeImageChannel(image,DefaultChannels);
3567 return(status);
3568}
3569
3570MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3571 const ChannelType channel)
3572{
3573 double
3574 black_point,
3575 white_point;
3576
3577 black_point=(double) image->columns*image->rows*0.02;
3578 white_point=(double) image->columns*image->rows*0.99;
3579 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3580}
3581
3582/*
3583%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3584% %
3585% %
3586% %
3587% S i g m o i d a l C o n t r a s t I m a g e %
3588% %
3589% %
3590% %
3591%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3592%
3593% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3594% sigmoidal contrast algorithm. Increase the contrast of the image using a
3595% sigmoidal transfer function without saturating highlights or shadows.
3596% Contrast indicates how much to increase the contrast (0 is none; 3 is
3597% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3598% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3599% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3600% is reduced.
3601%
3602% The format of the SigmoidalContrastImage method is:
3603%
3604% MagickBooleanType SigmoidalContrastImage(Image *image,
3605% const MagickBooleanType sharpen,const char *levels)
3606% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3607% const ChannelType channel,const MagickBooleanType sharpen,
3608% const double contrast,const double midpoint)
3609%
3610% A description of each parameter follows:
3611%
3612% o image: the image.
3613%
3614% o channel: the channel.
3615%
3616% o sharpen: Increase or decrease image contrast.
3617%
3618% o contrast: control the "shoulder" of the contast curve.
3619%
3620% o midpoint: control the "toe" of the contast curve.
3621%
3622*/
3623
3624MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3625 const MagickBooleanType sharpen,const char *levels)
3626{
3627 GeometryInfo
3628 geometry_info;
3629
3630 MagickBooleanType
3631 status;
3632
3633 MagickStatusType
3634 flags;
3635
3636 flags=ParseGeometry(levels,&geometry_info);
3637 if ((flags & SigmaValue) == 0)
3638 geometry_info.sigma=1.0*QuantumRange/2.0;
3639 if ((flags & PercentValue) != 0)
3640 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3641 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3642 geometry_info.rho,geometry_info.sigma);
3643 return(status);
3644}
3645
3646MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3647 const ChannelType channel,const MagickBooleanType sharpen,
3648 const double contrast,const double midpoint)
3649{
3650#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3651
cristyc4c8d132010-01-07 01:58:38 +00003652 CacheView
3653 *image_view;
3654
cristy3ed852e2009-09-05 21:47:34 +00003655 ExceptionInfo
3656 *exception;
3657
3658 long
3659 progress,
3660 y;
3661
3662 MagickBooleanType
3663 status;
3664
3665 MagickRealType
3666 *sigmoidal_map;
3667
3668 register long
3669 i;
3670
cristy3ed852e2009-09-05 21:47:34 +00003671 /*
3672 Allocate and initialize sigmoidal maps.
3673 */
3674 assert(image != (Image *) NULL);
3675 assert(image->signature == MagickSignature);
3676 if (image->debug != MagickFalse)
3677 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3678 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3679 sizeof(*sigmoidal_map));
3680 if (sigmoidal_map == (MagickRealType *) NULL)
3681 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3682 image->filename);
3683 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003684#if defined(MAGICKCORE_OPENMP_SUPPORT)
3685 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003686#endif
3687 for (i=0; i <= (long) MaxMap; i++)
3688 {
3689 if (sharpen != MagickFalse)
3690 {
3691 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3692 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3693 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3694 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3695 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3696 (double) QuantumRange)))))+0.5));
3697 continue;
3698 }
3699 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3700 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3701 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3702 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3703 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3704 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3705 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3706 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3707 (double) QuantumRange*contrast))))))/contrast)));
3708 }
3709 if (image->storage_class == PseudoClass)
3710 {
3711 /*
3712 Sigmoidal-contrast enhance colormap.
3713 */
cristyb5d5f722009-11-04 03:03:49 +00003714#if defined(MAGICKCORE_OPENMP_SUPPORT)
3715 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003716#endif
3717 for (i=0; i < (long) image->colors; i++)
3718 {
3719 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003720 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003721 ScaleQuantumToMap(image->colormap[i].red)]);
3722 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003723 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003724 ScaleQuantumToMap(image->colormap[i].green)]);
3725 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003726 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003727 ScaleQuantumToMap(image->colormap[i].blue)]);
3728 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003729 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003730 ScaleQuantumToMap(image->colormap[i].opacity)]);
3731 }
3732 }
3733 /*
3734 Sigmoidal-contrast enhance image.
3735 */
3736 status=MagickTrue;
3737 progress=0;
3738 exception=(&image->exception);
3739 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003740#if defined(MAGICKCORE_OPENMP_SUPPORT)
3741 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003742#endif
3743 for (y=0; y < (long) image->rows; y++)
3744 {
3745 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003746 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003747
3748 register long
3749 x;
3750
3751 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003752 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003753
3754 if (status == MagickFalse)
3755 continue;
3756 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3757 if (q == (PixelPacket *) NULL)
3758 {
3759 status=MagickFalse;
3760 continue;
3761 }
3762 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3763 for (x=0; x < (long) image->columns; x++)
3764 {
3765 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003766 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003767 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003768 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003769 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003770 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003771 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003772 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003773 if (((channel & IndexChannel) != 0) &&
3774 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003775 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003776 ScaleQuantumToMap(indexes[x])]);
3777 q++;
3778 }
3779 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3780 status=MagickFalse;
3781 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3782 {
3783 MagickBooleanType
3784 proceed;
3785
cristyb5d5f722009-11-04 03:03:49 +00003786#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003787 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3788#endif
3789 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3790 image->rows);
3791 if (proceed == MagickFalse)
3792 status=MagickFalse;
3793 }
3794 }
3795 image_view=DestroyCacheView(image_view);
3796 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3797 return(status);
3798}