blob: 603084e1582b00e9ebc4c7158a194c13f9eb7a73 [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"
54#include "magick/gem.h"
55#include "magick/geometry.h"
56#include "magick/histogram.h"
57#include "magick/image.h"
58#include "magick/image-private.h"
59#include "magick/memory_.h"
60#include "magick/monitor.h"
61#include "magick/monitor-private.h"
62#include "magick/option.h"
63#include "magick/quantum.h"
64#include "magick/quantum-private.h"
65#include "magick/resample.h"
66#include "magick/resample-private.h"
67#include "magick/statistic.h"
68#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000069#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000070#include "magick/thread-private.h"
71#include "magick/token.h"
72#include "magick/xml-tree.h"
73
74/*
75%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76% %
77% %
78% %
79% A u t o G a m m a I m a g e %
80% %
81% %
82% %
83%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84%
85% AutoGammaImage() extract the 'mean' from the image and adjust the image
86% to try make set its gamma appropriatally.
87%
cristy308b4e62009-09-21 14:40:44 +000088% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000089%
90% MagickBooleanType AutoGammaImage(Image *image)
91% MagickBooleanType AutoGammaImageChannel(Image *image,
92% const ChannelType channel)
93%
94% A description of each parameter follows:
95%
96% o image: The image to auto-level
97%
98% o channel: The channels to auto-level. If the special 'SyncChannels'
99% flag is set all given channels is adjusted in the same way using the
100% mean average of those channels.
101%
102*/
103
104MagickExport MagickBooleanType AutoGammaImage(Image *image)
105{
106 return(AutoGammaImageChannel(image,DefaultChannels));
107}
108
109MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
110 const ChannelType channel)
111{
112 MagickStatusType
113 status;
114
115 double
anthony4efe5972009-09-11 06:46:40 +0000116 mean,junk,gamma,logmean;
117
118 logmean=log(0.5);
cristy3ed852e2009-09-05 21:47:34 +0000119
120 if ((channel & SyncChannels) != 0 )
121 {
122 /*
123 Apply gamma correction equally accross all given channels
124 */
125 GetImageChannelMean(image, channel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000126 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000127 //return GammaImageChannel(image, channel, gamma);
128 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 {
138 GetImageChannelMean(image, RedChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000139 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000140 //status = status && GammaImageChannel(image, RedChannel, gamma);
141 status = status && LevelImageChannel(image, RedChannel,
142 0.0, (double)QuantumRange, gamma);
143 }
144 if ((channel & GreenChannel) != 0)
145 {
146 GetImageChannelMean(image, GreenChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000147 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000148 //status = status && GammaImageChannel(image, GreenChannel, gamma);
149 status = status && LevelImageChannel(image, GreenChannel,
150 0.0, (double)QuantumRange, gamma);
151 }
152 if ((channel & BlueChannel) != 0)
153 {
154 GetImageChannelMean(image, BlueChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000155 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000156 //status = status && GammaImageChannel(image, BlueChannel, gamma);
157 status = status && LevelImageChannel(image, BlueChannel,
158 0.0, (double)QuantumRange, gamma);
159 }
160 if (((channel & OpacityChannel) != 0) &&
161 (image->matte == MagickTrue))
162 {
163 GetImageChannelMean(image, OpacityChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000164 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000165 //status = status && GammaImageChannel(image, OpacityChannel, gamma);
166 status = status && LevelImageChannel(image, OpacityChannel,
167 0.0, (double)QuantumRange, gamma);
168 }
169 if (((channel & IndexChannel) != 0) &&
170 (image->colorspace == CMYKColorspace))
171 {
172 GetImageChannelMean(image, IndexChannel, &mean, &junk, &image->exception);
anthony4efe5972009-09-11 06:46:40 +0000173 gamma = log(mean*QuantumScale)/logmean;
cristy3ed852e2009-09-05 21:47:34 +0000174 //status = status && GammaImageChannel(image, IndexChannel, gamma);
175 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% %
230% C o l o r D e c i s i o n L i s t I m a g e %
231% %
232% %
233% %
234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
235%
236% ColorDecisionListImage() accepts a lightweight Color Correction Collection
237% (CCC) file which solely contains one or more color corrections and applies
238% the correction to the image. Here is a sample CCC file:
239%
240% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
241% <ColorCorrection id="cc03345">
242% <SOPNode>
243% <Slope> 0.9 1.2 0.5 </Slope>
244% <Offset> 0.4 -0.5 0.6 </Offset>
245% <Power> 1.0 0.8 1.5 </Power>
246% </SOPNode>
247% <SATNode>
248% <Saturation> 0.85 </Saturation>
249% </SATNode>
250% </ColorCorrection>
251% </ColorCorrectionCollection>
252%
253% which includes the slop, offset, and power for each of the RGB channels
254% as well as the saturation.
255%
256% The format of the ColorDecisionListImage method is:
257%
258% MagickBooleanType ColorDecisionListImage(Image *image,
259% const char *color_correction_collection)
260%
261% A description of each parameter follows:
262%
263% o image: the image.
264%
265% o color_correction_collection: the color correction collection in XML.
266%
267*/
268MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
269 const char *color_correction_collection)
270{
271#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
272
273 typedef struct _Correction
274 {
275 double
276 slope,
277 offset,
278 power;
279 } Correction;
280
281 typedef struct _ColorCorrection
282 {
283 Correction
284 red,
285 green,
286 blue;
287
288 double
289 saturation;
290 } ColorCorrection;
291
cristyc4c8d132010-01-07 01:58:38 +0000292 CacheView
293 *image_view;
294
cristy3ed852e2009-09-05 21:47:34 +0000295 char
296 token[MaxTextExtent];
297
298 ColorCorrection
299 color_correction;
300
301 const char
302 *content,
303 *p;
304
305 ExceptionInfo
306 *exception;
307
308 long
309 progress,
310 y;
311
312 MagickBooleanType
313 status;
314
315 PixelPacket
316 *cdl_map;
317
318 register long
319 i;
320
321 XMLTreeInfo
322 *cc,
323 *ccc,
324 *sat,
325 *sop;
326
cristy3ed852e2009-09-05 21:47:34 +0000327 /*
328 Allocate and initialize cdl maps.
329 */
330 assert(image != (Image *) NULL);
331 assert(image->signature == MagickSignature);
332 if (image->debug != MagickFalse)
333 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
334 if (color_correction_collection == (const char *) NULL)
335 return(MagickFalse);
336 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
337 if (ccc == (XMLTreeInfo *) NULL)
338 return(MagickFalse);
339 cc=GetXMLTreeChild(ccc,"ColorCorrection");
340 if (cc == (XMLTreeInfo *) NULL)
341 {
342 ccc=DestroyXMLTree(ccc);
343 return(MagickFalse);
344 }
345 color_correction.red.slope=1.0;
346 color_correction.red.offset=0.0;
347 color_correction.red.power=1.0;
348 color_correction.green.slope=1.0;
349 color_correction.green.offset=0.0;
350 color_correction.green.power=1.0;
351 color_correction.blue.slope=1.0;
352 color_correction.blue.offset=0.0;
353 color_correction.blue.power=1.0;
354 color_correction.saturation=0.0;
355 sop=GetXMLTreeChild(cc,"SOPNode");
356 if (sop != (XMLTreeInfo *) NULL)
357 {
358 XMLTreeInfo
359 *offset,
360 *power,
361 *slope;
362
363 slope=GetXMLTreeChild(sop,"Slope");
364 if (slope != (XMLTreeInfo *) NULL)
365 {
366 content=GetXMLTreeContent(slope);
367 p=(const char *) content;
368 for (i=0; (*p != '\0') && (i < 3); i++)
369 {
370 GetMagickToken(p,&p,token);
371 if (*token == ',')
372 GetMagickToken(p,&p,token);
373 switch (i)
374 {
cristyf2f27272009-12-17 14:48:46 +0000375 case 0: color_correction.red.slope=StringToDouble(token); break;
376 case 1: color_correction.green.slope=StringToDouble(token); break;
377 case 2: color_correction.blue.slope=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000378 }
379 }
380 }
381 offset=GetXMLTreeChild(sop,"Offset");
382 if (offset != (XMLTreeInfo *) NULL)
383 {
384 content=GetXMLTreeContent(offset);
385 p=(const char *) content;
386 for (i=0; (*p != '\0') && (i < 3); i++)
387 {
388 GetMagickToken(p,&p,token);
389 if (*token == ',')
390 GetMagickToken(p,&p,token);
391 switch (i)
392 {
cristyf2f27272009-12-17 14:48:46 +0000393 case 0: color_correction.red.offset=StringToDouble(token); break;
394 case 1: color_correction.green.offset=StringToDouble(token); break;
395 case 2: color_correction.blue.offset=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000396 }
397 }
398 }
399 power=GetXMLTreeChild(sop,"Power");
400 if (power != (XMLTreeInfo *) NULL)
401 {
402 content=GetXMLTreeContent(power);
403 p=(const char *) content;
404 for (i=0; (*p != '\0') && (i < 3); i++)
405 {
406 GetMagickToken(p,&p,token);
407 if (*token == ',')
408 GetMagickToken(p,&p,token);
409 switch (i)
410 {
cristyf2f27272009-12-17 14:48:46 +0000411 case 0: color_correction.red.power=StringToDouble(token); break;
412 case 1: color_correction.green.power=StringToDouble(token); break;
413 case 2: color_correction.blue.power=StringToDouble(token); break;
cristy3ed852e2009-09-05 21:47:34 +0000414 }
415 }
416 }
417 }
418 sat=GetXMLTreeChild(cc,"SATNode");
419 if (sat != (XMLTreeInfo *) NULL)
420 {
421 XMLTreeInfo
422 *saturation;
423
424 saturation=GetXMLTreeChild(sat,"Saturation");
425 if (saturation != (XMLTreeInfo *) NULL)
426 {
427 content=GetXMLTreeContent(saturation);
428 p=(const char *) content;
429 GetMagickToken(p,&p,token);
cristyf2f27272009-12-17 14:48:46 +0000430 color_correction.saturation=StringToDouble(token);
cristy3ed852e2009-09-05 21:47:34 +0000431 }
432 }
433 ccc=DestroyXMLTree(ccc);
434 if (image->debug != MagickFalse)
435 {
436 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
437 " Color Correction Collection:");
438 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000439 " color_correction.red.slope: %.15g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000440 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000441 " color_correction.red.offset: %.15g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000442 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000443 " color_correction.red.power: %.15g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000444 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000445 " color_correction.green.slope: %.15g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000446 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000447 " color_correction.green.offset: %.15g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000448 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000449 " color_correction.green.power: %.15g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000450 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000451 " color_correction.blue.slope: %.15g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000452 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000453 " color_correction.blue.offset: %.15g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000454 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000455 " color_correction.blue.power: %.15g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000456 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy8cd5b312010-01-07 01:10:24 +0000457 " color_correction.saturation: %.15g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000458 }
459 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
460 if (cdl_map == (PixelPacket *) NULL)
461 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
462 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000463#if defined(MAGICKCORE_OPENMP_SUPPORT)
464 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +0000465#endif
466 for (i=0; i <= (long) MaxMap; i++)
467 {
cristyce70c172010-01-07 17:15:30 +0000468 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000469 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
470 color_correction.red.offset,color_correction.red.power)))));
cristyce70c172010-01-07 17:15:30 +0000471 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000472 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
473 color_correction.green.offset,color_correction.green.power)))));
cristyce70c172010-01-07 17:15:30 +0000474 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +0000475 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
476 color_correction.blue.offset,color_correction.blue.power)))));
477 }
478 if (image->storage_class == PseudoClass)
479 {
480 /*
481 Apply transfer function to colormap.
482 */
cristyb5d5f722009-11-04 03:03:49 +0000483#if defined(MAGICKCORE_OPENMP_SUPPORT)
484 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000485#endif
486 for (i=0; i < (long) image->colors; i++)
487 {
488 double
489 luma;
490
491 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
492 0.0722*image->colormap[i].blue;
cristyce70c172010-01-07 17:15:30 +0000493 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000494 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
cristyce70c172010-01-07 17:15:30 +0000495 image->colormap[i].green=ClampToQuantum(luma+
cristy3ed852e2009-09-05 21:47:34 +0000496 color_correction.saturation*cdl_map[ScaleQuantumToMap(
497 image->colormap[i].green)].green-luma);
cristyce70c172010-01-07 17:15:30 +0000498 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000499 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
500 }
501 }
502 /*
503 Apply transfer function to image.
504 */
505 status=MagickTrue;
506 progress=0;
507 exception=(&image->exception);
508 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000509#if defined(MAGICKCORE_OPENMP_SUPPORT)
510 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000511#endif
512 for (y=0; y < (long) image->rows; y++)
513 {
514 double
515 luma;
516
517 register long
518 x;
519
520 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000521 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000522
523 if (status == MagickFalse)
524 continue;
525 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
526 if (q == (PixelPacket *) NULL)
527 {
528 status=MagickFalse;
529 continue;
530 }
531 for (x=0; x < (long) image->columns; x++)
532 {
533 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
cristyce70c172010-01-07 17:15:30 +0000534 q->red=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000535 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
cristyce70c172010-01-07 17:15:30 +0000536 q->green=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000537 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
cristyce70c172010-01-07 17:15:30 +0000538 q->blue=ClampToQuantum(luma+color_correction.saturation*
cristy3ed852e2009-09-05 21:47:34 +0000539 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
540 q++;
541 }
542 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
543 status=MagickFalse;
544 if (image->progress_monitor != (MagickProgressMonitor) NULL)
545 {
546 MagickBooleanType
547 proceed;
548
cristyb5d5f722009-11-04 03:03:49 +0000549#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000550 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
551#endif
552 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
553 progress++,image->rows);
554 if (proceed == MagickFalse)
555 status=MagickFalse;
556 }
557 }
558 image_view=DestroyCacheView(image_view);
559 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
560 return(status);
561}
562
563/*
564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
565% %
566% %
567% %
568% C l u t I m a g e %
569% %
570% %
571% %
572%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
573%
574% ClutImage() replaces each color value in the given image, by using it as an
575% index to lookup a replacement color value in a Color Look UP Table in the
576% form of an image. The values are extracted along a diagonal of the CLUT
577% image so either a horizontal or vertial gradient image can be used.
578%
579% Typically this is used to either re-color a gray-scale image according to a
580% color gradient in the CLUT image, or to perform a freeform histogram
581% (level) adjustment according to the (typically gray-scale) gradient in the
582% CLUT image.
583%
584% When the 'channel' mask includes the matte/alpha transparency channel but
585% one image has no such channel it is assumed that that image is a simple
586% gray-scale image that will effect the alpha channel values, either for
587% gray-scale coloring (with transparent or semi-transparent colors), or
588% a histogram adjustment of existing alpha channel values. If both images
589% have matte channels, direct and normal indexing is applied, which is rarely
590% used.
591%
592% The format of the ClutImage method is:
593%
594% MagickBooleanType ClutImage(Image *image,Image *clut_image)
595% MagickBooleanType ClutImageChannel(Image *image,
596% const ChannelType channel,Image *clut_image)
597%
598% A description of each parameter follows:
599%
600% o image: the image, which is replaced by indexed CLUT values
601%
602% o clut_image: the color lookup table image for replacement color values.
603%
604% o channel: the channel.
605%
606*/
607
608MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
609{
610 return(ClutImageChannel(image,DefaultChannels,clut_image));
611}
612
613MagickExport MagickBooleanType ClutImageChannel(Image *image,
614 const ChannelType channel,const Image *clut_image)
615{
616#define ClutImageTag "Clut/Image"
617
cristyfa112112010-01-04 17:48:07 +0000618 CacheView
619 *image_view;
620
cristy3ed852e2009-09-05 21:47:34 +0000621 ExceptionInfo
622 *exception;
623
624 long
625 adjust,
626 progress,
627 y;
628
629 MagickBooleanType
630 status;
631
632 MagickPixelPacket
633 zero;
634
635 ResampleFilter
cristyfa112112010-01-04 17:48:07 +0000636 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +0000637
638 assert(image != (Image *) NULL);
639 assert(image->signature == MagickSignature);
640 if (image->debug != MagickFalse)
641 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
642 assert(clut_image != (Image *) NULL);
643 assert(clut_image->signature == MagickSignature);
644 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
645 return(MagickFalse);
646 /*
647 Clut image.
648 */
649 status=MagickTrue;
650 progress=0;
651 GetMagickPixelPacket(clut_image,&zero);
652 adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
653 exception=(&image->exception);
654 resample_filter=AcquireResampleFilterThreadSet(clut_image,MagickTrue,
655 exception);
656 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000657#if defined(MAGICKCORE_OPENMP_SUPPORT)
658 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000659#endif
660 for (y=0; y < (long) image->rows; y++)
661 {
662 MagickPixelPacket
663 pixel;
664
665 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000666 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000667
668 register long
669 id,
670 x;
671
672 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000673 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000674
675 if (status == MagickFalse)
676 continue;
677 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
678 if (q == (PixelPacket *) NULL)
679 {
680 status=MagickFalse;
681 continue;
682 }
683 indexes=GetCacheViewAuthenticIndexQueue(image_view);
684 pixel=zero;
685 id=GetOpenMPThreadId();
686 for (x=0; x < (long) image->columns; x++)
687 {
688 /*
689 PROGRAMMERS WARNING:
690
691 Apply OpacityChannel BEFORE the color channels. Do not re-order.
692
693 The handling special case 2 (coloring gray-scale), requires access to
694 the unmodified colors of the original image to determine the index
695 value. As such alpha/matte channel handling must be performed BEFORE,
696 any of the color channels are modified.
697
698 */
699 if ((channel & OpacityChannel) != 0)
700 {
701 if (clut_image->matte == MagickFalse)
702 {
703 /*
704 A gray-scale LUT replacement for an image alpha channel.
705 */
706 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
cristy46f08202010-01-10 04:04:21 +0000707 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
708 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
cristy3ed852e2009-09-05 21:47:34 +0000709 adjust),&pixel);
710 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
711 &pixel));
712 }
713 else
714 if (image->matte == MagickFalse)
715 {
716 /*
717 A greyscale image being colored by a LUT with transparency.
718 */
719 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
720 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
721 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000722 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000723 }
724 else
725 {
726 /*
727 Direct alpha channel lookup.
728 */
729 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
730 q->opacity*(clut_image->columns-adjust),QuantumScale*
731 q->opacity* (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000732 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000733 }
734 }
735 if ((channel & RedChannel) != 0)
736 {
737 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
738 (clut_image->columns-adjust),QuantumScale*q->red*
739 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000740 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000741 }
742 if ((channel & GreenChannel) != 0)
743 {
744 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
745 (clut_image->columns-adjust),QuantumScale*q->green*
746 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000747 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000748 }
749 if ((channel & BlueChannel) != 0)
750 {
751 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
752 (clut_image->columns-adjust),QuantumScale*q->blue*
753 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000754 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000755 }
756 if (((channel & IndexChannel) != 0) &&
757 (image->colorspace == CMYKColorspace))
758 {
759 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
760 (clut_image->columns-adjust),QuantumScale*indexes[x]*
761 (clut_image->rows-adjust),&pixel);
cristyce70c172010-01-07 17:15:30 +0000762 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000763 }
764 q++;
765 }
766 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
767 status=MagickFalse;
768 if (image->progress_monitor != (MagickProgressMonitor) NULL)
769 {
770 MagickBooleanType
771 proceed;
772
cristyb5d5f722009-11-04 03:03:49 +0000773#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000774 #pragma omp critical (MagickCore_ClutImageChannel)
775#endif
776 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
777 if (proceed == MagickFalse)
778 status=MagickFalse;
779 }
780 }
781 image_view=DestroyCacheView(image_view);
782 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
783 /*
784 Enable alpha channel if CLUT image could enable it.
785 */
786 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
787 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
788 return(status);
789}
790
791/*
792%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
793% %
794% %
795% %
796% C o n t r a s t I m a g e %
797% %
798% %
799% %
800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
801%
802% ContrastImage() enhances the intensity differences between the lighter and
803% darker elements of the image. Set sharpen to a MagickTrue to increase the
804% image contrast otherwise the contrast is reduced.
805%
806% The format of the ContrastImage method is:
807%
808% MagickBooleanType ContrastImage(Image *image,
809% const MagickBooleanType sharpen)
810%
811% A description of each parameter follows:
812%
813% o image: the image.
814%
815% o sharpen: Increase or decrease image contrast.
816%
817*/
818
819static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
820{
821 double
822 brightness,
823 hue,
824 saturation;
825
826 /*
827 Enhance contrast: dark color become darker, light color become lighter.
828 */
829 assert(red != (Quantum *) NULL);
830 assert(green != (Quantum *) NULL);
831 assert(blue != (Quantum *) NULL);
832 hue=0.0;
833 saturation=0.0;
834 brightness=0.0;
835 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
836 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
837 if (brightness > 1.0)
838 brightness=1.0;
839 else
840 if (brightness < 0.0)
841 brightness=0.0;
842 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
843}
844
845MagickExport MagickBooleanType ContrastImage(Image *image,
846 const MagickBooleanType sharpen)
847{
848#define ContrastImageTag "Contrast/Image"
849
cristyc4c8d132010-01-07 01:58:38 +0000850 CacheView
851 *image_view;
852
cristy3ed852e2009-09-05 21:47:34 +0000853 ExceptionInfo
854 *exception;
855
856 int
857 sign;
858
859 long
860 progress,
861 y;
862
863 MagickBooleanType
864 status;
865
866 register long
867 i;
868
cristy3ed852e2009-09-05 21:47:34 +0000869 assert(image != (Image *) NULL);
870 assert(image->signature == MagickSignature);
871 if (image->debug != MagickFalse)
872 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
873 sign=sharpen != MagickFalse ? 1 : -1;
874 if (image->storage_class == PseudoClass)
875 {
876 /*
877 Contrast enhance colormap.
878 */
879 for (i=0; i < (long) image->colors; i++)
880 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
881 &image->colormap[i].blue);
882 }
883 /*
884 Contrast enhance image.
885 */
886 status=MagickTrue;
887 progress=0;
888 exception=(&image->exception);
889 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000890#if defined(MAGICKCORE_OPENMP_SUPPORT)
891 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000892#endif
893 for (y=0; y < (long) image->rows; y++)
894 {
895 register long
896 x;
897
898 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000899 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000900
901 if (status == MagickFalse)
902 continue;
903 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
904 if (q == (PixelPacket *) NULL)
905 {
906 status=MagickFalse;
907 continue;
908 }
909 for (x=0; x < (long) image->columns; x++)
910 {
911 Contrast(sign,&q->red,&q->green,&q->blue);
912 q++;
913 }
914 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
915 status=MagickFalse;
916 if (image->progress_monitor != (MagickProgressMonitor) NULL)
917 {
918 MagickBooleanType
919 proceed;
920
cristyb5d5f722009-11-04 03:03:49 +0000921#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000922 #pragma omp critical (MagickCore_ContrastImage)
923#endif
924 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
925 if (proceed == MagickFalse)
926 status=MagickFalse;
927 }
928 }
929 image_view=DestroyCacheView(image_view);
930 return(status);
931}
932
933/*
934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
935% %
936% %
937% %
938% C o n t r a s t S t r e t c h I m a g e %
939% %
940% %
941% %
942%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
943%
944% The ContrastStretchImage() is a simple image enhancement technique that
945% attempts to improve the contrast in an image by `stretching' the range of
946% intensity values it contains to span a desired range of values. It differs
947% from the more sophisticated histogram equalization in that it can only
948% apply % a linear scaling function to the image pixel values. As a result
949% the `enhancement' is less harsh.
950%
951% The format of the ContrastStretchImage method is:
952%
953% MagickBooleanType ContrastStretchImage(Image *image,
954% const char *levels)
955% MagickBooleanType ContrastStretchImageChannel(Image *image,
956% const unsigned long channel,const double black_point,
957% const double white_point)
958%
959% A description of each parameter follows:
960%
961% o image: the image.
962%
963% o channel: the channel.
964%
965% o black_point: the black point.
966%
967% o white_point: the white point.
968%
969% o levels: Specify the levels where the black and white points have the
970% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
971%
972*/
973
974MagickExport MagickBooleanType ContrastStretchImage(Image *image,
975 const char *levels)
976{
977 double
978 black_point,
979 white_point;
980
981 GeometryInfo
982 geometry_info;
983
984 MagickBooleanType
985 status;
986
987 MagickStatusType
988 flags;
989
990 /*
991 Parse levels.
992 */
993 if (levels == (char *) NULL)
994 return(MagickFalse);
995 flags=ParseGeometry(levels,&geometry_info);
996 black_point=geometry_info.rho;
997 white_point=(double) image->columns*image->rows;
998 if ((flags & SigmaValue) != 0)
999 white_point=geometry_info.sigma;
1000 if ((flags & PercentValue) != 0)
1001 {
1002 black_point*=(double) QuantumRange/100.0;
1003 white_point*=(double) QuantumRange/100.0;
1004 }
1005 if ((flags & SigmaValue) == 0)
1006 white_point=(double) image->columns*image->rows-black_point;
1007 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1008 white_point);
1009 return(status);
1010}
1011
1012MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1013 const ChannelType channel,const double black_point,const double white_point)
1014{
1015#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1016#define ContrastStretchImageTag "ContrastStretch/Image"
1017
cristyc4c8d132010-01-07 01:58:38 +00001018 CacheView
1019 *image_view;
1020
cristy3ed852e2009-09-05 21:47:34 +00001021 double
1022 intensity;
1023
1024 ExceptionInfo
1025 *exception;
1026
1027 long
1028 progress,
1029 y;
1030
1031 MagickBooleanType
1032 status;
1033
1034 MagickPixelPacket
1035 black,
1036 *histogram,
1037 *stretch_map,
1038 white;
1039
1040 register long
1041 i;
1042
cristy3ed852e2009-09-05 21:47:34 +00001043 /*
1044 Allocate histogram and stretch map.
1045 */
1046 assert(image != (Image *) NULL);
1047 assert(image->signature == MagickSignature);
1048 if (image->debug != MagickFalse)
1049 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1050 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1051 sizeof(*histogram));
1052 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1053 sizeof(*stretch_map));
1054 if ((histogram == (MagickPixelPacket *) NULL) ||
1055 (stretch_map == (MagickPixelPacket *) NULL))
1056 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1057 image->filename);
1058 /*
1059 Form histogram.
1060 */
1061 status=MagickTrue;
1062 exception=(&image->exception);
1063 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1064 image_view=AcquireCacheView(image);
1065 for (y=0; y < (long) image->rows; y++)
1066 {
1067 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001068 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001069
1070 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001071 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001072
1073 register long
1074 x;
1075
1076 if (status == MagickFalse)
1077 continue;
1078 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1079 if (p == (const PixelPacket *) NULL)
1080 {
1081 status=MagickFalse;
1082 continue;
1083 }
1084 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1085 if (channel == DefaultChannels)
1086 for (x=0; x < (long) image->columns; x++)
1087 {
1088 Quantum
1089 intensity;
1090
1091 intensity=PixelIntensityToQuantum(p);
1092 histogram[ScaleQuantumToMap(intensity)].red++;
1093 histogram[ScaleQuantumToMap(intensity)].green++;
1094 histogram[ScaleQuantumToMap(intensity)].blue++;
1095 histogram[ScaleQuantumToMap(intensity)].index++;
1096 p++;
1097 }
1098 else
1099 for (x=0; x < (long) image->columns; x++)
1100 {
1101 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001102 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001103 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001104 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001105 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001106 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001107 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001108 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001109 if (((channel & IndexChannel) != 0) &&
1110 (image->colorspace == CMYKColorspace))
1111 histogram[ScaleQuantumToMap(indexes[x])].index++;
1112 p++;
1113 }
1114 }
1115 /*
1116 Find the histogram boundaries by locating the black/white levels.
1117 */
1118 black.red=0.0;
1119 white.red=MaxRange(QuantumRange);
1120 if ((channel & RedChannel) != 0)
1121 {
1122 intensity=0.0;
1123 for (i=0; i <= (long) MaxMap; i++)
1124 {
1125 intensity+=histogram[i].red;
1126 if (intensity > black_point)
1127 break;
1128 }
1129 black.red=(MagickRealType) i;
1130 intensity=0.0;
1131 for (i=(long) MaxMap; i != 0; i--)
1132 {
1133 intensity+=histogram[i].red;
1134 if (intensity > ((double) image->columns*image->rows-white_point))
1135 break;
1136 }
1137 white.red=(MagickRealType) i;
1138 }
1139 black.green=0.0;
1140 white.green=MaxRange(QuantumRange);
1141 if ((channel & GreenChannel) != 0)
1142 {
1143 intensity=0.0;
1144 for (i=0; i <= (long) MaxMap; i++)
1145 {
1146 intensity+=histogram[i].green;
1147 if (intensity > black_point)
1148 break;
1149 }
1150 black.green=(MagickRealType) i;
1151 intensity=0.0;
1152 for (i=(long) MaxMap; i != 0; i--)
1153 {
1154 intensity+=histogram[i].green;
1155 if (intensity > ((double) image->columns*image->rows-white_point))
1156 break;
1157 }
1158 white.green=(MagickRealType) i;
1159 }
1160 black.blue=0.0;
1161 white.blue=MaxRange(QuantumRange);
1162 if ((channel & BlueChannel) != 0)
1163 {
1164 intensity=0.0;
1165 for (i=0; i <= (long) MaxMap; i++)
1166 {
1167 intensity+=histogram[i].blue;
1168 if (intensity > black_point)
1169 break;
1170 }
1171 black.blue=(MagickRealType) i;
1172 intensity=0.0;
1173 for (i=(long) MaxMap; i != 0; i--)
1174 {
1175 intensity+=histogram[i].blue;
1176 if (intensity > ((double) image->columns*image->rows-white_point))
1177 break;
1178 }
1179 white.blue=(MagickRealType) i;
1180 }
1181 black.opacity=0.0;
1182 white.opacity=MaxRange(QuantumRange);
1183 if ((channel & OpacityChannel) != 0)
1184 {
1185 intensity=0.0;
1186 for (i=0; i <= (long) MaxMap; i++)
1187 {
1188 intensity+=histogram[i].opacity;
1189 if (intensity > black_point)
1190 break;
1191 }
1192 black.opacity=(MagickRealType) i;
1193 intensity=0.0;
1194 for (i=(long) MaxMap; i != 0; i--)
1195 {
1196 intensity+=histogram[i].opacity;
1197 if (intensity > ((double) image->columns*image->rows-white_point))
1198 break;
1199 }
1200 white.opacity=(MagickRealType) i;
1201 }
1202 black.index=0.0;
1203 white.index=MaxRange(QuantumRange);
1204 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1205 {
1206 intensity=0.0;
1207 for (i=0; i <= (long) MaxMap; i++)
1208 {
1209 intensity+=histogram[i].index;
1210 if (intensity > black_point)
1211 break;
1212 }
1213 black.index=(MagickRealType) i;
1214 intensity=0.0;
1215 for (i=(long) MaxMap; i != 0; i--)
1216 {
1217 intensity+=histogram[i].index;
1218 if (intensity > ((double) image->columns*image->rows-white_point))
1219 break;
1220 }
1221 white.index=(MagickRealType) i;
1222 }
1223 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1224 /*
1225 Stretch the histogram to create the stretched image mapping.
1226 */
1227 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
cristyb5d5f722009-11-04 03:03:49 +00001228#if defined(MAGICKCORE_OPENMP_SUPPORT)
1229 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001230#endif
1231 for (i=0; i <= (long) MaxMap; i++)
1232 {
1233 if ((channel & RedChannel) != 0)
1234 {
1235 if (i < (long) black.red)
1236 stretch_map[i].red=0.0;
1237 else
1238 if (i > (long) white.red)
1239 stretch_map[i].red=(MagickRealType) QuantumRange;
1240 else
1241 if (black.red != white.red)
1242 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1243 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1244 }
1245 if ((channel & GreenChannel) != 0)
1246 {
1247 if (i < (long) black.green)
1248 stretch_map[i].green=0.0;
1249 else
1250 if (i > (long) white.green)
1251 stretch_map[i].green=(MagickRealType) QuantumRange;
1252 else
1253 if (black.green != white.green)
1254 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1255 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1256 black.green)));
1257 }
1258 if ((channel & BlueChannel) != 0)
1259 {
1260 if (i < (long) black.blue)
1261 stretch_map[i].blue=0.0;
1262 else
1263 if (i > (long) white.blue)
1264 stretch_map[i].blue=(MagickRealType) QuantumRange;
1265 else
1266 if (black.blue != white.blue)
1267 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1268 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1269 black.blue)));
1270 }
1271 if ((channel & OpacityChannel) != 0)
1272 {
1273 if (i < (long) black.opacity)
1274 stretch_map[i].opacity=0.0;
1275 else
1276 if (i > (long) white.opacity)
1277 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1278 else
1279 if (black.opacity != white.opacity)
1280 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1281 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1282 black.opacity)));
1283 }
1284 if (((channel & IndexChannel) != 0) &&
1285 (image->colorspace == CMYKColorspace))
1286 {
1287 if (i < (long) black.index)
1288 stretch_map[i].index=0.0;
1289 else
1290 if (i > (long) white.index)
1291 stretch_map[i].index=(MagickRealType) QuantumRange;
1292 else
1293 if (black.index != white.index)
1294 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1295 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1296 black.index)));
1297 }
1298 }
1299 /*
1300 Stretch the image.
1301 */
1302 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1303 (image->colorspace == CMYKColorspace)))
1304 image->storage_class=DirectClass;
1305 if (image->storage_class == PseudoClass)
1306 {
1307 /*
1308 Stretch colormap.
1309 */
cristyb5d5f722009-11-04 03:03:49 +00001310#if defined(MAGICKCORE_OPENMP_SUPPORT)
1311 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001312#endif
1313 for (i=0; i < (long) image->colors; i++)
1314 {
1315 if ((channel & RedChannel) != 0)
1316 {
1317 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001318 image->colormap[i].red=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001319 ScaleQuantumToMap(image->colormap[i].red)].red);
1320 }
1321 if ((channel & GreenChannel) != 0)
1322 {
1323 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001324 image->colormap[i].green=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001325 ScaleQuantumToMap(image->colormap[i].green)].green);
1326 }
1327 if ((channel & BlueChannel) != 0)
1328 {
1329 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001330 image->colormap[i].blue=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001331 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1332 }
1333 if ((channel & OpacityChannel) != 0)
1334 {
1335 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001336 image->colormap[i].opacity=ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001337 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1338 }
1339 }
1340 }
1341 /*
1342 Stretch image.
1343 */
1344 status=MagickTrue;
1345 progress=0;
cristyb5d5f722009-11-04 03:03:49 +00001346#if defined(MAGICKCORE_OPENMP_SUPPORT)
1347 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001348#endif
1349 for (y=0; y < (long) image->rows; y++)
1350 {
1351 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001352 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001353
1354 register long
1355 x;
1356
1357 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001358 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001359
1360 if (status == MagickFalse)
1361 continue;
1362 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1363 if (q == (PixelPacket *) NULL)
1364 {
1365 status=MagickFalse;
1366 continue;
1367 }
1368 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1369 for (x=0; x < (long) image->columns; x++)
1370 {
1371 if ((channel & RedChannel) != 0)
1372 {
1373 if (black.red != white.red)
cristyce70c172010-01-07 17:15:30 +00001374 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001375 }
1376 if ((channel & GreenChannel) != 0)
1377 {
1378 if (black.green != white.green)
cristyce70c172010-01-07 17:15:30 +00001379 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001380 q->green)].green);
1381 }
1382 if ((channel & BlueChannel) != 0)
1383 {
1384 if (black.blue != white.blue)
cristyce70c172010-01-07 17:15:30 +00001385 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001386 q->blue)].blue);
1387 }
1388 if ((channel & OpacityChannel) != 0)
1389 {
1390 if (black.opacity != white.opacity)
cristyce70c172010-01-07 17:15:30 +00001391 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001392 q->opacity)].opacity);
1393 }
1394 if (((channel & IndexChannel) != 0) &&
1395 (image->colorspace == CMYKColorspace))
1396 {
1397 if (black.index != white.index)
cristyce70c172010-01-07 17:15:30 +00001398 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
cristy3ed852e2009-09-05 21:47:34 +00001399 ScaleQuantumToMap(indexes[x])].index);
1400 }
1401 q++;
1402 }
1403 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1404 status=MagickFalse;
1405 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1406 {
1407 MagickBooleanType
1408 proceed;
1409
cristyb5d5f722009-11-04 03:03:49 +00001410#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001411 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1412#endif
1413 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1414 image->rows);
1415 if (proceed == MagickFalse)
1416 status=MagickFalse;
1417 }
1418 }
1419 image_view=DestroyCacheView(image_view);
1420 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1421 return(status);
1422}
1423
1424/*
1425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1426% %
1427% %
1428% %
1429% E n h a n c e I m a g e %
1430% %
1431% %
1432% %
1433%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1434%
1435% EnhanceImage() applies a digital filter that improves the quality of a
1436% noisy image.
1437%
1438% The format of the EnhanceImage method is:
1439%
1440% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1441%
1442% A description of each parameter follows:
1443%
1444% o image: the image.
1445%
1446% o exception: return any errors or warnings in this structure.
1447%
1448*/
1449MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1450{
1451#define Enhance(weight) \
1452 mean=((MagickRealType) r->red+pixel.red)/2; \
1453 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1454 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1455 mean)*distance*distance; \
1456 mean=((MagickRealType) r->green+pixel.green)/2; \
1457 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1458 distance_squared+=4.0*distance*distance; \
1459 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1460 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1461 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1462 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1463 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1464 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1465 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1466 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1467 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1468 QuantumRange/25.0f)) \
1469 { \
1470 aggregate.red+=(weight)*r->red; \
1471 aggregate.green+=(weight)*r->green; \
1472 aggregate.blue+=(weight)*r->blue; \
1473 aggregate.opacity+=(weight)*r->opacity; \
1474 total_weight+=(weight); \
1475 } \
1476 r++;
1477#define EnhanceImageTag "Enhance/Image"
1478
cristyc4c8d132010-01-07 01:58:38 +00001479 CacheView
1480 *enhance_view,
1481 *image_view;
1482
cristy3ed852e2009-09-05 21:47:34 +00001483 Image
1484 *enhance_image;
1485
1486 long
1487 progress,
1488 y;
1489
1490 MagickBooleanType
1491 status;
1492
1493 MagickPixelPacket
1494 zero;
1495
cristy3ed852e2009-09-05 21:47:34 +00001496 /*
1497 Initialize enhanced image attributes.
1498 */
1499 assert(image != (const Image *) NULL);
1500 assert(image->signature == MagickSignature);
1501 if (image->debug != MagickFalse)
1502 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1503 assert(exception != (ExceptionInfo *) NULL);
1504 assert(exception->signature == MagickSignature);
1505 if ((image->columns < 5) || (image->rows < 5))
1506 return((Image *) NULL);
1507 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1508 exception);
1509 if (enhance_image == (Image *) NULL)
1510 return((Image *) NULL);
1511 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1512 {
1513 InheritException(exception,&enhance_image->exception);
1514 enhance_image=DestroyImage(enhance_image);
1515 return((Image *) NULL);
1516 }
1517 /*
1518 Enhance image.
1519 */
1520 status=MagickTrue;
1521 progress=0;
1522 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1523 image_view=AcquireCacheView(image);
1524 enhance_view=AcquireCacheView(enhance_image);
cristyb5d5f722009-11-04 03:03:49 +00001525#if defined(MAGICKCORE_OPENMP_SUPPORT)
1526 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001527#endif
1528 for (y=0; y < (long) image->rows; y++)
1529 {
1530 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001531 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001532
1533 register long
1534 x;
1535
1536 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001537 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001538
1539 /*
1540 Read another scan line.
1541 */
1542 if (status == MagickFalse)
1543 continue;
1544 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1545 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1546 exception);
1547 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1548 {
1549 status=MagickFalse;
1550 continue;
1551 }
1552 for (x=0; x < (long) image->columns; x++)
1553 {
1554 MagickPixelPacket
1555 aggregate;
1556
1557 MagickRealType
1558 distance,
1559 distance_squared,
1560 mean,
1561 total_weight;
1562
1563 PixelPacket
1564 pixel;
1565
1566 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001567 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00001568
1569 /*
1570 Compute weighted average of target pixel color components.
1571 */
1572 aggregate=zero;
1573 total_weight=0.0;
1574 r=p+2*(image->columns+4)+2;
1575 pixel=(*r);
1576 r=p;
1577 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1578 r=p+(image->columns+4);
1579 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1580 r=p+2*(image->columns+4);
1581 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1582 r=p+3*(image->columns+4);
1583 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1584 r=p+4*(image->columns+4);
1585 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1586 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1587 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1588 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1589 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1590 total_weight);
1591 p++;
1592 q++;
1593 }
1594 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1595 status=MagickFalse;
1596 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1597 {
1598 MagickBooleanType
1599 proceed;
1600
cristyb5d5f722009-11-04 03:03:49 +00001601#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001602 #pragma omp critical (MagickCore_EnhanceImage)
1603#endif
1604 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1605 if (proceed == MagickFalse)
1606 status=MagickFalse;
1607 }
1608 }
1609 enhance_view=DestroyCacheView(enhance_view);
1610 image_view=DestroyCacheView(image_view);
1611 return(enhance_image);
1612}
1613
1614/*
1615%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1616% %
1617% %
1618% %
1619% E q u a l i z e I m a g e %
1620% %
1621% %
1622% %
1623%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1624%
1625% EqualizeImage() applies a histogram equalization to the image.
1626%
1627% The format of the EqualizeImage method is:
1628%
1629% MagickBooleanType EqualizeImage(Image *image)
1630% MagickBooleanType EqualizeImageChannel(Image *image,
1631% const ChannelType channel)
1632%
1633% A description of each parameter follows:
1634%
1635% o image: the image.
1636%
1637% o channel: the channel.
1638%
1639*/
1640
1641MagickExport MagickBooleanType EqualizeImage(Image *image)
1642{
1643 return(EqualizeImageChannel(image,DefaultChannels));
1644}
1645
1646MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1647 const ChannelType channel)
1648{
1649#define EqualizeImageTag "Equalize/Image"
1650
cristyc4c8d132010-01-07 01:58:38 +00001651 CacheView
1652 *image_view;
1653
cristy3ed852e2009-09-05 21:47:34 +00001654 ExceptionInfo
1655 *exception;
1656
1657 long
1658 progress,
1659 y;
1660
1661 MagickBooleanType
1662 status;
1663
1664 MagickPixelPacket
1665 black,
1666 *equalize_map,
1667 *histogram,
1668 intensity,
1669 *map,
1670 white;
1671
1672 register long
1673 i;
1674
cristy3ed852e2009-09-05 21:47:34 +00001675 /*
1676 Allocate and initialize histogram arrays.
1677 */
1678 assert(image != (Image *) NULL);
1679 assert(image->signature == MagickSignature);
1680 if (image->debug != MagickFalse)
1681 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1682 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1683 sizeof(*equalize_map));
1684 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1685 sizeof(*histogram));
1686 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1687 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1688 (histogram == (MagickPixelPacket *) NULL) ||
1689 (map == (MagickPixelPacket *) NULL))
1690 {
1691 if (map != (MagickPixelPacket *) NULL)
1692 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1693 if (histogram != (MagickPixelPacket *) NULL)
1694 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1695 if (equalize_map != (MagickPixelPacket *) NULL)
1696 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1697 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1698 image->filename);
1699 }
1700 /*
1701 Form histogram.
1702 */
1703 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1704 exception=(&image->exception);
1705 for (y=0; y < (long) image->rows; y++)
1706 {
1707 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001708 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001709
1710 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001711 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001712
1713 register long
1714 x;
1715
1716 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1717 if (p == (const PixelPacket *) NULL)
1718 break;
1719 indexes=GetVirtualIndexQueue(image);
1720 for (x=0; x < (long) image->columns; x++)
1721 {
1722 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001723 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
cristy3ed852e2009-09-05 21:47:34 +00001724 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001725 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
cristy3ed852e2009-09-05 21:47:34 +00001726 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001727 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
cristy3ed852e2009-09-05 21:47:34 +00001728 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001729 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
cristy3ed852e2009-09-05 21:47:34 +00001730 if (((channel & IndexChannel) != 0) &&
1731 (image->colorspace == CMYKColorspace))
1732 histogram[ScaleQuantumToMap(indexes[x])].index++;
1733 p++;
1734 }
1735 }
1736 /*
1737 Integrate the histogram to get the equalization map.
1738 */
1739 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1740 for (i=0; i <= (long) MaxMap; i++)
1741 {
1742 if ((channel & RedChannel) != 0)
1743 intensity.red+=histogram[i].red;
1744 if ((channel & GreenChannel) != 0)
1745 intensity.green+=histogram[i].green;
1746 if ((channel & BlueChannel) != 0)
1747 intensity.blue+=histogram[i].blue;
1748 if ((channel & OpacityChannel) != 0)
1749 intensity.opacity+=histogram[i].opacity;
1750 if (((channel & IndexChannel) != 0) &&
1751 (image->colorspace == CMYKColorspace))
1752 intensity.index+=histogram[i].index;
1753 map[i]=intensity;
1754 }
1755 black=map[0];
1756 white=map[(int) MaxMap];
1757 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
cristyb5d5f722009-11-04 03:03:49 +00001758#if defined(MAGICKCORE_OPENMP_SUPPORT)
1759 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001760#endif
1761 for (i=0; i <= (long) MaxMap; i++)
1762 {
1763 if (((channel & RedChannel) != 0) && (white.red != black.red))
1764 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1765 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1766 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1767 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1768 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1769 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1770 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1771 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1772 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1773 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1774 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1775 (white.opacity-black.opacity)));
1776 if ((((channel & IndexChannel) != 0) &&
1777 (image->colorspace == CMYKColorspace)) &&
1778 (white.index != black.index))
1779 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1780 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1781 }
1782 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1783 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1784 if (image->storage_class == PseudoClass)
1785 {
1786 /*
1787 Equalize colormap.
1788 */
cristyb5d5f722009-11-04 03:03:49 +00001789#if defined(MAGICKCORE_OPENMP_SUPPORT)
1790 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001791#endif
1792 for (i=0; i < (long) image->colors; i++)
1793 {
1794 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001795 image->colormap[i].red=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001796 ScaleQuantumToMap(image->colormap[i].red)].red);
1797 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001798 image->colormap[i].green=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001799 ScaleQuantumToMap(image->colormap[i].green)].green);
1800 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001801 image->colormap[i].blue=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001802 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1803 if (((channel & OpacityChannel) != 0) &&
1804 (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001805 image->colormap[i].opacity=ClampToQuantum(equalize_map[
cristy3ed852e2009-09-05 21:47:34 +00001806 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1807 }
1808 }
1809 /*
1810 Equalize image.
1811 */
1812 status=MagickTrue;
1813 progress=0;
1814 exception=(&image->exception);
1815 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001816#if defined(MAGICKCORE_OPENMP_SUPPORT)
1817 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001818#endif
1819 for (y=0; y < (long) image->rows; y++)
1820 {
1821 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001822 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001823
1824 register long
1825 x;
1826
1827 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001828 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001829
1830 if (status == MagickFalse)
1831 continue;
1832 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1833 if (q == (PixelPacket *) NULL)
1834 {
1835 status=MagickFalse;
1836 continue;
1837 }
1838 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1839 for (x=0; x < (long) image->columns; x++)
1840 {
1841 if (((channel & RedChannel) != 0) && (white.red != black.red))
cristyce70c172010-01-07 17:15:30 +00001842 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
cristy3ed852e2009-09-05 21:47:34 +00001843 if (((channel & GreenChannel) != 0) && (white.green != black.green))
cristyce70c172010-01-07 17:15:30 +00001844 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001845 q->green)].green);
1846 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
cristyce70c172010-01-07 17:15:30 +00001847 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
cristy3ed852e2009-09-05 21:47:34 +00001848 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
cristyce70c172010-01-07 17:15:30 +00001849 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001850 q->opacity)].opacity);
1851 if ((((channel & IndexChannel) != 0) &&
1852 (image->colorspace == CMYKColorspace)) &&
1853 (white.index != black.index))
cristyce70c172010-01-07 17:15:30 +00001854 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
cristy3ed852e2009-09-05 21:47:34 +00001855 indexes[x])].index);
1856 q++;
1857 }
1858 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1859 status=MagickFalse;
1860 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1861 {
1862 MagickBooleanType
1863 proceed;
1864
cristyb5d5f722009-11-04 03:03:49 +00001865#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001866 #pragma omp critical (MagickCore_EqualizeImageChannel)
1867#endif
1868 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1869 if (proceed == MagickFalse)
1870 status=MagickFalse;
1871 }
1872 }
1873 image_view=DestroyCacheView(image_view);
1874 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1875 return(status);
1876}
1877
1878/*
1879%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1880% %
1881% %
1882% %
1883% G a m m a I m a g e %
1884% %
1885% %
1886% %
1887%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1888%
1889% GammaImage() gamma-corrects a particular image channel. The same
1890% image viewed on different devices will have perceptual differences in the
1891% way the image's intensities are represented on the screen. Specify
1892% individual gamma levels for the red, green, and blue channels, or adjust
1893% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1894%
1895% You can also reduce the influence of a particular channel with a gamma
1896% value of 0.
1897%
1898% The format of the GammaImage method is:
1899%
1900% MagickBooleanType GammaImage(Image *image,const double gamma)
1901% MagickBooleanType GammaImageChannel(Image *image,
1902% const ChannelType channel,const double gamma)
1903%
1904% A description of each parameter follows:
1905%
1906% o image: the image.
1907%
1908% o channel: the channel.
1909%
1910% o gamma: the image gamma.
1911%
1912*/
1913MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1914{
1915 GeometryInfo
1916 geometry_info;
1917
1918 MagickPixelPacket
1919 gamma;
1920
1921 MagickStatusType
1922 flags,
1923 status;
1924
1925 assert(image != (Image *) NULL);
1926 assert(image->signature == MagickSignature);
1927 if (image->debug != MagickFalse)
1928 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1929 if (level == (char *) NULL)
1930 return(MagickFalse);
1931 flags=ParseGeometry(level,&geometry_info);
1932 gamma.red=geometry_info.rho;
1933 gamma.green=geometry_info.sigma;
1934 if ((flags & SigmaValue) == 0)
1935 gamma.green=gamma.red;
1936 gamma.blue=geometry_info.xi;
1937 if ((flags & XiValue) == 0)
1938 gamma.blue=gamma.red;
1939 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1940 return(MagickTrue);
1941 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1942 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1943 GreenChannel | BlueChannel),(double) gamma.red);
1944 else
1945 {
1946 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
1947 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
1948 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
1949 }
1950 return(status != 0 ? MagickTrue : MagickFalse);
1951}
1952
1953MagickExport MagickBooleanType GammaImageChannel(Image *image,
1954 const ChannelType channel,const double gamma)
1955{
1956#define GammaCorrectImageTag "GammaCorrect/Image"
1957
cristyc4c8d132010-01-07 01:58:38 +00001958 CacheView
1959 *image_view;
1960
cristy3ed852e2009-09-05 21:47:34 +00001961 ExceptionInfo
1962 *exception;
1963
1964 long
1965 progress,
1966 y;
1967
1968 MagickBooleanType
1969 status;
1970
1971 Quantum
1972 *gamma_map;
1973
1974 register long
1975 i;
1976
cristy3ed852e2009-09-05 21:47:34 +00001977 /*
1978 Allocate and initialize gamma maps.
1979 */
1980 assert(image != (Image *) NULL);
1981 assert(image->signature == MagickSignature);
1982 if (image->debug != MagickFalse)
1983 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1984 if (gamma == 1.0)
1985 return(MagickTrue);
1986 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1987 if (gamma_map == (Quantum *) NULL)
1988 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1989 image->filename);
1990 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1991 if (gamma != 0.0)
cristyb5d5f722009-11-04 03:03:49 +00001992#if defined(MAGICKCORE_OPENMP_SUPPORT)
1993 #pragma omp parallel for schedule(dynamic,4)
cristy3ed852e2009-09-05 21:47:34 +00001994#endif
1995 for (i=0; i <= (long) MaxMap; i++)
cristyce70c172010-01-07 17:15:30 +00001996 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
cristy3ed852e2009-09-05 21:47:34 +00001997 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1998 if (image->storage_class == PseudoClass)
1999 {
2000 /*
2001 Gamma-correct colormap.
2002 */
cristyb5d5f722009-11-04 03:03:49 +00002003#if defined(MAGICKCORE_OPENMP_SUPPORT)
2004 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002005#endif
2006 for (i=0; i < (long) image->colors; i++)
2007 {
2008 if ((channel & RedChannel) != 0)
2009 image->colormap[i].red=gamma_map[
2010 ScaleQuantumToMap(image->colormap[i].red)];
2011 if ((channel & GreenChannel) != 0)
2012 image->colormap[i].green=gamma_map[
2013 ScaleQuantumToMap(image->colormap[i].green)];
2014 if ((channel & BlueChannel) != 0)
2015 image->colormap[i].blue=gamma_map[
2016 ScaleQuantumToMap(image->colormap[i].blue)];
2017 if ((channel & OpacityChannel) != 0)
2018 {
2019 if (image->matte == MagickFalse)
2020 image->colormap[i].opacity=gamma_map[
2021 ScaleQuantumToMap(image->colormap[i].opacity)];
2022 else
2023 image->colormap[i].opacity=(Quantum) QuantumRange-
2024 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2025 image->colormap[i].opacity))];
2026 }
2027 }
2028 }
2029 /*
2030 Gamma-correct image.
2031 */
2032 status=MagickTrue;
2033 progress=0;
2034 exception=(&image->exception);
2035 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002036#if defined(MAGICKCORE_OPENMP_SUPPORT)
2037 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002038#endif
2039 for (y=0; y < (long) image->rows; y++)
2040 {
2041 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002042 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002043
2044 register long
2045 x;
2046
2047 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002048 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002049
2050 if (status == MagickFalse)
2051 continue;
2052 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2053 if (q == (PixelPacket *) NULL)
2054 {
2055 status=MagickFalse;
2056 continue;
2057 }
2058 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2059 for (x=0; x < (long) image->columns; x++)
2060 {
cristy6cbd7f52009-10-17 16:06:51 +00002061 if (channel == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +00002062 {
cristy6cbd7f52009-10-17 16:06:51 +00002063 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2064 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2065 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2066 }
2067 else
2068 {
2069 if ((channel & RedChannel) != 0)
2070 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2071 if ((channel & GreenChannel) != 0)
2072 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2073 if ((channel & BlueChannel) != 0)
2074 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2075 if ((channel & OpacityChannel) != 0)
2076 {
2077 if (image->matte == MagickFalse)
2078 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2079 else
2080 q->opacity=(Quantum) QuantumRange-gamma_map[
cristy46f08202010-01-10 04:04:21 +00002081 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
cristy6cbd7f52009-10-17 16:06:51 +00002082 }
cristy3ed852e2009-09-05 21:47:34 +00002083 }
2084 q++;
2085 }
2086 if (((channel & IndexChannel) != 0) &&
2087 (image->colorspace == CMYKColorspace))
2088 for (x=0; x < (long) image->columns; x++)
2089 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2090 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2091 status=MagickFalse;
2092 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2093 {
2094 MagickBooleanType
2095 proceed;
2096
cristyb5d5f722009-11-04 03:03:49 +00002097#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002098 #pragma omp critical (MagickCore_GammaImageChannel)
2099#endif
2100 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2101 image->rows);
2102 if (proceed == MagickFalse)
2103 status=MagickFalse;
2104 }
2105 }
2106 image_view=DestroyCacheView(image_view);
2107 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2108 if (image->gamma != 0.0)
2109 image->gamma*=gamma;
2110 return(status);
2111}
2112
2113/*
2114%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2115% %
2116% %
2117% %
2118% H a l d C l u t I m a g e %
2119% %
2120% %
2121% %
2122%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2123%
2124% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2125% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2126% Create it with the HALD coder. You can apply any color transformation to
2127% the Hald image and then use this method to apply the transform to the
2128% image.
2129%
2130% The format of the HaldClutImage method is:
2131%
2132% MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2133% MagickBooleanType HaldClutImageChannel(Image *image,
2134% const ChannelType channel,Image *hald_image)
2135%
2136% A description of each parameter follows:
2137%
2138% o image: the image, which is replaced by indexed CLUT values
2139%
2140% o hald_image: the color lookup table image for replacement color values.
2141%
2142% o channel: the channel.
2143%
2144*/
2145
2146static inline size_t MagickMin(const size_t x,const size_t y)
2147{
2148 if (x < y)
2149 return(x);
2150 return(y);
2151}
2152
2153MagickExport MagickBooleanType HaldClutImage(Image *image,
2154 const Image *hald_image)
2155{
2156 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2157}
2158
2159MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2160 const ChannelType channel,const Image *hald_image)
2161{
2162#define HaldClutImageTag "Clut/Image"
2163
2164 typedef struct _HaldInfo
2165 {
2166 MagickRealType
2167 x,
2168 y,
2169 z;
2170 } HaldInfo;
2171
cristyfa112112010-01-04 17:48:07 +00002172 CacheView
2173 *image_view;
2174
cristy3ed852e2009-09-05 21:47:34 +00002175 double
2176 width;
2177
2178 ExceptionInfo
2179 *exception;
2180
2181 long
2182 progress,
2183 y;
2184
2185 MagickBooleanType
2186 status;
2187
2188 MagickPixelPacket
2189 zero;
2190
2191 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00002192 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00002193
2194 size_t
2195 cube_size,
2196 length,
2197 level;
2198
cristy3ed852e2009-09-05 21:47:34 +00002199 assert(image != (Image *) NULL);
2200 assert(image->signature == MagickSignature);
2201 if (image->debug != MagickFalse)
2202 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2203 assert(hald_image != (Image *) NULL);
2204 assert(hald_image->signature == MagickSignature);
2205 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2206 return(MagickFalse);
2207 if (image->matte == MagickFalse)
2208 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2209 /*
2210 Hald clut image.
2211 */
2212 status=MagickTrue;
2213 progress=0;
2214 length=MagickMin(hald_image->columns,hald_image->rows);
2215 for (level=2; (level*level*level) < length; level++) ;
2216 level*=level;
2217 cube_size=level*level;
2218 width=(double) hald_image->columns;
2219 GetMagickPixelPacket(hald_image,&zero);
2220 exception=(&image->exception);
2221 resample_filter=AcquireResampleFilterThreadSet(hald_image,MagickTrue,
2222 exception);
2223 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002224#if defined(MAGICKCORE_OPENMP_SUPPORT)
2225 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002226#endif
2227 for (y=0; y < (long) image->rows; y++)
2228 {
2229 double
2230 offset;
2231
2232 HaldInfo
2233 point;
2234
2235 MagickPixelPacket
2236 pixel,
2237 pixel1,
2238 pixel2,
2239 pixel3,
2240 pixel4;
2241
2242 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002243 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002244
2245 register long
2246 id,
2247 x;
2248
2249 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002250 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002251
2252 if (status == MagickFalse)
2253 continue;
2254 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2255 if (q == (PixelPacket *) NULL)
2256 {
2257 status=MagickFalse;
2258 continue;
2259 }
2260 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2261 pixel=zero;
2262 pixel1=zero;
2263 pixel2=zero;
2264 pixel3=zero;
2265 pixel4=zero;
2266 id=GetOpenMPThreadId();
2267 for (x=0; x < (long) image->columns; x++)
2268 {
2269 point.x=QuantumScale*(level-1.0)*q->red;
2270 point.y=QuantumScale*(level-1.0)*q->green;
2271 point.z=QuantumScale*(level-1.0)*q->blue;
2272 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2273 point.x-=floor(point.x);
2274 point.y-=floor(point.y);
2275 point.z-=floor(point.z);
2276 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2277 floor(offset/width),&pixel1);
2278 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2279 floor((offset+level)/width),&pixel2);
2280 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2281 pixel2.opacity,point.y,&pixel3);
2282 offset+=cube_size;
2283 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2284 floor(offset/width),&pixel1);
2285 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2286 floor((offset+level)/width),&pixel2);
2287 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2288 pixel2.opacity,point.y,&pixel4);
2289 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2290 pixel4.opacity,point.z,&pixel);
2291 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002292 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002293 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002294 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002295 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002296 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002297 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +00002298 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002299 if (((channel & IndexChannel) != 0) &&
2300 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00002301 indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002302 q++;
2303 }
2304 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2305 status=MagickFalse;
2306 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2307 {
2308 MagickBooleanType
2309 proceed;
2310
cristyb5d5f722009-11-04 03:03:49 +00002311#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002312 #pragma omp critical (MagickCore_HaldClutImageChannel)
2313#endif
2314 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2315 if (proceed == MagickFalse)
2316 status=MagickFalse;
2317 }
2318 }
2319 image_view=DestroyCacheView(image_view);
2320 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2321 return(status);
2322}
2323
2324/*
2325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2326% %
2327% %
2328% %
2329% L e v e l I m a g e %
2330% %
2331% %
2332% %
2333%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2334%
2335% LevelImage() adjusts the levels of a particular image channel by
2336% scaling the colors falling between specified white and black points to
2337% the full available quantum range.
2338%
2339% The parameters provided represent the black, and white points. The black
2340% point specifies the darkest color in the image. Colors darker than the
2341% black point are set to zero. White point specifies the lightest color in
2342% the image. Colors brighter than the white point are set to the maximum
2343% quantum value.
2344%
2345% If a '!' flag is given, map black and white colors to the given levels
2346% rather than mapping those levels to black and white. See
2347% LevelizeImageChannel() and LevelizeImageChannel(), below.
2348%
2349% Gamma specifies a gamma correction to apply to the image.
2350%
2351% The format of the LevelImage method is:
2352%
2353% MagickBooleanType LevelImage(Image *image,const char *levels)
2354%
2355% A description of each parameter follows:
2356%
2357% o image: the image.
2358%
2359% o levels: Specify the levels where the black and white points have the
2360% range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2361% A '!' flag inverts the re-mapping.
2362%
2363*/
2364
2365MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2366{
2367 double
2368 black_point,
2369 gamma,
2370 white_point;
2371
2372 GeometryInfo
2373 geometry_info;
2374
2375 MagickBooleanType
2376 status;
2377
2378 MagickStatusType
2379 flags;
2380
2381 /*
2382 Parse levels.
2383 */
2384 if (levels == (char *) NULL)
2385 return(MagickFalse);
2386 flags=ParseGeometry(levels,&geometry_info);
2387 black_point=geometry_info.rho;
2388 white_point=(double) QuantumRange;
2389 if ((flags & SigmaValue) != 0)
2390 white_point=geometry_info.sigma;
2391 gamma=1.0;
2392 if ((flags & XiValue) != 0)
2393 gamma=geometry_info.xi;
2394 if ((flags & PercentValue) != 0)
2395 {
2396 black_point*=(double) image->columns*image->rows/100.0;
2397 white_point*=(double) image->columns*image->rows/100.0;
2398 }
2399 if ((flags & SigmaValue) == 0)
2400 white_point=(double) QuantumRange-black_point;
2401 if ((flags & AspectValue ) == 0)
2402 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2403 gamma);
2404 else
cristy308b4e62009-09-21 14:40:44 +00002405 status=LevelizeImage(image,black_point,white_point,gamma);
cristy3ed852e2009-09-05 21:47:34 +00002406 return(status);
2407}
2408
2409/*
2410%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2411% %
2412% %
2413% %
cristy308b4e62009-09-21 14:40:44 +00002414% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002415% %
2416% %
2417% %
2418%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2419%
cristy308b4e62009-09-21 14:40:44 +00002420% LevelizeImage() applies the normal level operation to the image, spreading
2421% out the values between the black and white points over the entire range of
2422% values. Gamma correction is also applied after the values has been mapped.
cristy3ed852e2009-09-05 21:47:34 +00002423%
2424% It is typically used to improve image contrast, or to provide a controlled
2425% linear threshold for the image. If the black and white points are set to
2426% the minimum and maximum values found in the image, the image can be
2427% normalized. or by swapping black and white values, negate the image.
2428%
cristy308b4e62009-09-21 14:40:44 +00002429% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002430%
cristy308b4e62009-09-21 14:40:44 +00002431% MagickBooleanType LevelizeImage(Image *image,const double black_point,
2432% const double white_point,const double gamma)
2433% MagickBooleanType LevelizeImageChannel(Image *image,
2434% const ChannelType channel,const double black_point,
2435% const double white_point,const double gamma)
cristy3ed852e2009-09-05 21:47:34 +00002436%
2437% A description of each parameter follows:
2438%
2439% o image: the image.
2440%
2441% o channel: the channel.
2442%
2443% o black_point: The level which is to be mapped to zero (black)
2444%
2445% o white_point: The level which is to be mapped to QuantiumRange (white)
2446%
2447% o gamma: adjust gamma by this factor before mapping values.
2448% use 1.0 for purely linear stretching of image color values
2449%
2450*/
cristy308b4e62009-09-21 14:40:44 +00002451
2452MagickExport MagickBooleanType LevelizeImage(Image *image,
2453 const double black_point,const double white_point,const double gamma)
2454{
2455 MagickBooleanType
2456 status;
2457
2458 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2459 gamma);
2460 return(status);
2461}
2462
cristy3ed852e2009-09-05 21:47:34 +00002463MagickExport MagickBooleanType LevelImageChannel(Image *image,
2464 const ChannelType channel,const double black_point,const double white_point,
2465 const double gamma)
2466{
2467#define LevelImageTag "Level/Image"
cristyce70c172010-01-07 17:15:30 +00002468#define LevelValue(x) (ClampToQuantum((MagickRealType) QuantumRange* \
cristy3ed852e2009-09-05 21:47:34 +00002469 pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
2470
cristyc4c8d132010-01-07 01:58:38 +00002471 CacheView
2472 *image_view;
2473
cristy3ed852e2009-09-05 21:47:34 +00002474 ExceptionInfo
2475 *exception;
2476
2477 long
2478 progress,
2479 y;
2480
2481 MagickBooleanType
2482 status;
2483
2484 register long
2485 i;
2486
cristy3ed852e2009-09-05 21:47:34 +00002487 /*
2488 Allocate and initialize levels map.
2489 */
2490 assert(image != (Image *) NULL);
2491 assert(image->signature == MagickSignature);
2492 if (image->debug != MagickFalse)
2493 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2494 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002495#if defined(MAGICKCORE_OPENMP_SUPPORT)
2496 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002497#endif
2498 for (i=0; i < (long) image->colors; i++)
2499 {
2500 /*
2501 Level colormap.
2502 */
2503 if ((channel & RedChannel) != 0)
2504 image->colormap[i].red=LevelValue(image->colormap[i].red);
2505 if ((channel & GreenChannel) != 0)
2506 image->colormap[i].green=LevelValue(image->colormap[i].green);
2507 if ((channel & BlueChannel) != 0)
2508 image->colormap[i].blue=LevelValue(image->colormap[i].blue);
2509 if ((channel & OpacityChannel) != 0)
2510 image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
2511 }
2512 /*
2513 Level image.
2514 */
2515 status=MagickTrue;
2516 progress=0;
2517 exception=(&image->exception);
2518 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002519#if defined(MAGICKCORE_OPENMP_SUPPORT)
2520 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002521#endif
2522 for (y=0; y < (long) image->rows; y++)
2523 {
2524 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002525 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002526
2527 register long
2528 x;
2529
2530 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002531 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002532
2533 if (status == MagickFalse)
2534 continue;
2535 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2536 if (q == (PixelPacket *) NULL)
2537 {
2538 status=MagickFalse;
2539 continue;
2540 }
2541 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2542 for (x=0; x < (long) image->columns; x++)
2543 {
2544 if ((channel & RedChannel) != 0)
2545 q->red=LevelValue(q->red);
2546 if ((channel & GreenChannel) != 0)
2547 q->green=LevelValue(q->green);
2548 if ((channel & BlueChannel) != 0)
2549 q->blue=LevelValue(q->blue);
2550 if (((channel & OpacityChannel) != 0) &&
2551 (image->matte == MagickTrue))
2552 q->opacity=LevelValue(q->opacity);
2553 if (((channel & IndexChannel) != 0) &&
2554 (image->colorspace == CMYKColorspace))
2555 indexes[x]=LevelValue(indexes[x]);
2556 q++;
2557 }
2558 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2559 status=MagickFalse;
2560 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2561 {
2562 MagickBooleanType
2563 proceed;
2564
cristyb5d5f722009-11-04 03:03:49 +00002565#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002566 #pragma omp critical (MagickCore_LevelImageChannel)
2567#endif
2568 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2569 if (proceed == MagickFalse)
2570 status=MagickFalse;
2571 }
2572 }
2573 image_view=DestroyCacheView(image_view);
2574 return(status);
2575}
2576
2577/*
2578%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2579% %
2580% %
2581% %
2582% L e v e l i z e I m a g e C h a n n e l %
2583% %
2584% %
2585% %
2586%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2587%
2588% LevelizeImageChannel() applies the reversed LevelImage() operation to just
2589% the specific channels specified. It compresses the full range of color
2590% values, so that they lie between the given black and white points. Gamma is
2591% applied before the values are mapped.
2592%
2593% LevelizeImageChannel() can be called with by using a +level command line
2594% API option, or using a '!' on a -level or LevelImage() geometry string.
2595%
2596% It can be used for example de-contrast a greyscale image to the exact
2597% levels specified. Or by using specific levels for each channel of an image
2598% you can convert a gray-scale image to any linear color gradient, according
2599% to those levels.
2600%
2601% The format of the LevelizeImageChannel method is:
2602%
2603% MagickBooleanType LevelizeImageChannel(Image *image,
2604% const ChannelType channel,const char *levels)
2605%
2606% A description of each parameter follows:
2607%
2608% o image: the image.
2609%
2610% o channel: the channel.
2611%
2612% o black_point: The level to map zero (black) to.
2613%
2614% o white_point: The level to map QuantiumRange (white) to.
2615%
2616% o gamma: adjust gamma by this factor before mapping values.
2617%
2618*/
2619MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2620 const ChannelType channel,const double black_point,const double white_point,
2621 const double gamma)
2622{
2623#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002624#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy3ed852e2009-09-05 21:47:34 +00002625 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2626 black_point))
2627
cristyc4c8d132010-01-07 01:58:38 +00002628 CacheView
2629 *image_view;
2630
cristy3ed852e2009-09-05 21:47:34 +00002631 ExceptionInfo
2632 *exception;
2633
2634 long
2635 progress,
2636 y;
2637
2638 MagickBooleanType
2639 status;
2640
2641 register long
2642 i;
2643
cristy3ed852e2009-09-05 21:47:34 +00002644 /*
2645 Allocate and initialize levels map.
2646 */
2647 assert(image != (Image *) NULL);
2648 assert(image->signature == MagickSignature);
2649 if (image->debug != MagickFalse)
2650 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2651 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002652#if defined(MAGICKCORE_OPENMP_SUPPORT)
2653 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002654#endif
2655 for (i=0; i < (long) image->colors; i++)
2656 {
2657 /*
2658 Level colormap.
2659 */
2660 if ((channel & RedChannel) != 0)
2661 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2662 if ((channel & GreenChannel) != 0)
2663 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2664 if ((channel & BlueChannel) != 0)
2665 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2666 if ((channel & OpacityChannel) != 0)
2667 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2668 }
2669 /*
2670 Level image.
2671 */
2672 status=MagickTrue;
2673 progress=0;
2674 exception=(&image->exception);
2675 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002676#if defined(MAGICKCORE_OPENMP_SUPPORT)
2677 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002678#endif
2679 for (y=0; y < (long) image->rows; y++)
2680 {
2681 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002682 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002683
2684 register long
2685 x;
2686
2687 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002688 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002689
2690 if (status == MagickFalse)
2691 continue;
2692 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2693 if (q == (PixelPacket *) NULL)
2694 {
2695 status=MagickFalse;
2696 continue;
2697 }
2698 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2699 for (x=0; x < (long) image->columns; x++)
2700 {
2701 if ((channel & RedChannel) != 0)
2702 q->red=LevelizeValue(q->red);
2703 if ((channel & GreenChannel) != 0)
2704 q->green=LevelizeValue(q->green);
2705 if ((channel & BlueChannel) != 0)
2706 q->blue=LevelizeValue(q->blue);
2707 if (((channel & OpacityChannel) != 0) &&
2708 (image->matte == MagickTrue))
2709 q->opacity=LevelizeValue(q->opacity);
2710 if (((channel & IndexChannel) != 0) &&
2711 (image->colorspace == CMYKColorspace))
2712 indexes[x]=LevelizeValue(indexes[x]);
2713 q++;
2714 }
2715 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2716 status=MagickFalse;
2717 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2718 {
2719 MagickBooleanType
2720 proceed;
2721
cristyb5d5f722009-11-04 03:03:49 +00002722#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002723 #pragma omp critical (MagickCore_LevelizeImageChannel)
2724#endif
2725 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2726 if (proceed == MagickFalse)
2727 status=MagickFalse;
2728 }
2729 }
2730 return(status);
2731}
2732
2733/*
2734%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2735% %
2736% %
2737% %
2738% L e v e l I m a g e C o l o r s %
2739% %
2740% %
2741% %
2742%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2743%
cristyee0f8d72009-09-19 00:58:29 +00002744% LevelImageColor() maps the given color to "black" and "white" values,
2745% linearly spreading out the colors, and level values on a channel by channel
2746% bases, as per LevelImage(). The given colors allows you to specify
cristy3ed852e2009-09-05 21:47:34 +00002747% different level ranges for each of the color channels seperatally.
2748%
2749% If the boolean 'invert' is set true the image values will modifyed in the
2750% reverse direction. That is any existing "black" and "white" colors in the
2751% image will become the color values given, with all other values compressed
2752% appropriatally. This effectivally maps a greyscale gradient into the given
2753% color gradient.
2754%
cristy308b4e62009-09-21 14:40:44 +00002755% The format of the LevelColorsImageChannel method is:
cristy3ed852e2009-09-05 21:47:34 +00002756%
cristy308b4e62009-09-21 14:40:44 +00002757% MagickBooleanType LevelColorsImage(Image *image,
cristyee0f8d72009-09-19 00:58:29 +00002758% const MagickPixelPacket *black_color,
2759% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy308b4e62009-09-21 14:40:44 +00002760% MagickBooleanType LevelColorsImageChannel(Image *image,
2761% const ChannelType channel,const MagickPixelPacket *black_color,
2762% const MagickPixelPacket *white_color,const MagickBooleanType invert)
cristy3ed852e2009-09-05 21:47:34 +00002763%
2764% A description of each parameter follows:
2765%
2766% o image: the image.
2767%
2768% o channel: the channel.
2769%
2770% o black_color: The color to map black to/from
2771%
2772% o white_point: The color to map white to/from
2773%
2774% o invert: if true map the colors (levelize), rather than from (level)
2775%
2776*/
cristy308b4e62009-09-21 14:40:44 +00002777
2778MagickExport MagickBooleanType LevelColorsImage(Image *image,
cristy3ed852e2009-09-05 21:47:34 +00002779 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2780 const MagickBooleanType invert)
2781{
cristy308b4e62009-09-21 14:40:44 +00002782 MagickBooleanType
2783 status;
cristy3ed852e2009-09-05 21:47:34 +00002784
cristy308b4e62009-09-21 14:40:44 +00002785 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2786 invert);
2787 return(status);
2788}
2789
2790MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2791 const ChannelType channel,const MagickPixelPacket *black_color,
2792 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2793{
cristy3ed852e2009-09-05 21:47:34 +00002794 MagickStatusType
2795 status;
2796
2797 /*
2798 Allocate and initialize levels map.
2799 */
2800 assert(image != (Image *) NULL);
2801 assert(image->signature == MagickSignature);
2802 if (image->debug != MagickFalse)
2803 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2804 status=MagickFalse;
2805 if (invert == MagickFalse)
2806 {
2807 if ((channel & RedChannel) != 0)
2808 status|=LevelImageChannel(image,RedChannel,
2809 black_color->red,white_color->red,(double) 1.0);
2810 if ((channel & GreenChannel) != 0)
2811 status|=LevelImageChannel(image,GreenChannel,
2812 black_color->green,white_color->green,(double) 1.0);
2813 if ((channel & BlueChannel) != 0)
2814 status|=LevelImageChannel(image,BlueChannel,
2815 black_color->blue,white_color->blue,(double) 1.0);
2816 if (((channel & OpacityChannel) != 0) &&
2817 (image->matte == MagickTrue))
2818 status|=LevelImageChannel(image,OpacityChannel,
2819 black_color->opacity,white_color->opacity,(double) 1.0);
2820 if (((channel & IndexChannel) != 0) &&
2821 (image->colorspace == CMYKColorspace))
2822 status|=LevelImageChannel(image,IndexChannel,
2823 black_color->index,white_color->index,(double) 1.0);
2824 }
2825 else
2826 {
2827 if ((channel & RedChannel) != 0)
2828 status|=LevelizeImageChannel(image,RedChannel,
2829 black_color->red,white_color->red,(double) 1.0);
2830 if ((channel & GreenChannel) != 0)
2831 status|=LevelizeImageChannel(image,GreenChannel,
2832 black_color->green,white_color->green,(double) 1.0);
2833 if ((channel & BlueChannel) != 0)
2834 status|=LevelizeImageChannel(image,BlueChannel,
2835 black_color->blue,white_color->blue,(double) 1.0);
2836 if (((channel & OpacityChannel) != 0) &&
2837 (image->matte == MagickTrue))
2838 status|=LevelizeImageChannel(image,OpacityChannel,
2839 black_color->opacity,white_color->opacity,(double) 1.0);
2840 if (((channel & IndexChannel) != 0) &&
2841 (image->colorspace == CMYKColorspace))
2842 status|=LevelizeImageChannel(image,IndexChannel,
2843 black_color->index,white_color->index,(double) 1.0);
2844 }
2845 return(status == 0 ? MagickFalse : MagickTrue);
2846}
2847
2848/*
2849%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2850% %
2851% %
2852% %
2853% L i n e a r S t r e t c h I m a g e %
2854% %
2855% %
2856% %
2857%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2858%
2859% The LinearStretchImage() discards any pixels below the black point and
2860% above the white point and levels the remaining pixels.
2861%
2862% The format of the LinearStretchImage method is:
2863%
2864% MagickBooleanType LinearStretchImage(Image *image,
2865% const double black_point,const double white_point)
2866%
2867% A description of each parameter follows:
2868%
2869% o image: the image.
2870%
2871% o black_point: the black point.
2872%
2873% o white_point: the white point.
2874%
2875*/
2876MagickExport MagickBooleanType LinearStretchImage(Image *image,
2877 const double black_point,const double white_point)
2878{
2879#define LinearStretchImageTag "LinearStretch/Image"
2880
2881 ExceptionInfo
2882 *exception;
2883
2884 long
2885 black,
2886 white,
2887 y;
2888
2889 MagickBooleanType
2890 status;
2891
2892 MagickRealType
2893 *histogram,
2894 intensity;
2895
2896 MagickSizeType
2897 number_pixels;
2898
2899 /*
2900 Allocate histogram and linear map.
2901 */
2902 assert(image != (Image *) NULL);
2903 assert(image->signature == MagickSignature);
2904 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2905 sizeof(*histogram));
2906 if (histogram == (MagickRealType *) NULL)
2907 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2908 image->filename);
2909 /*
2910 Form histogram.
2911 */
2912 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2913 exception=(&image->exception);
2914 for (y=0; y < (long) image->rows; y++)
2915 {
2916 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002917 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002918
2919 register long
2920 x;
2921
2922 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2923 if (p == (const PixelPacket *) NULL)
2924 break;
2925 for (x=(long) image->columns-1; x >= 0; x--)
2926 {
2927 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2928 p++;
2929 }
2930 }
2931 /*
2932 Find the histogram boundaries by locating the black and white point levels.
2933 */
2934 number_pixels=(MagickSizeType) image->columns*image->rows;
2935 intensity=0.0;
2936 for (black=0; black < (long) MaxMap; black++)
2937 {
2938 intensity+=histogram[black];
2939 if (intensity >= black_point)
2940 break;
2941 }
2942 intensity=0.0;
2943 for (white=(long) MaxMap; white != 0; white--)
2944 {
2945 intensity+=histogram[white];
2946 if (intensity >= white_point)
2947 break;
2948 }
2949 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
2950 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
2951 1.0);
2952 return(status);
2953}
2954
2955/*
2956%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2957% %
2958% %
2959% %
2960% M o d u l a t e I m a g e %
2961% %
2962% %
2963% %
2964%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2965%
2966% ModulateImage() lets you control the brightness, saturation, and hue
2967% of an image. Modulate represents the brightness, saturation, and hue
2968% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2969% modulation is lightness, saturation, and hue. And if the colorspace is
2970% HWB, use blackness, whiteness, and hue.
2971%
2972% The format of the ModulateImage method is:
2973%
2974% MagickBooleanType ModulateImage(Image *image,const char *modulate)
2975%
2976% A description of each parameter follows:
2977%
2978% o image: the image.
2979%
2980% o modulate: Define the percent change in brightness, saturation, and
2981% hue.
2982%
2983*/
2984
2985static void ModulateHSB(const double percent_hue,
2986 const double percent_saturation,const double percent_brightness,
2987 Quantum *red,Quantum *green,Quantum *blue)
2988{
2989 double
2990 brightness,
2991 hue,
2992 saturation;
2993
2994 /*
2995 Increase or decrease color brightness, saturation, or hue.
2996 */
2997 assert(red != (Quantum *) NULL);
2998 assert(green != (Quantum *) NULL);
2999 assert(blue != (Quantum *) NULL);
3000 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3001 hue+=0.5*(0.01*percent_hue-1.0);
3002 while (hue < 0.0)
3003 hue+=1.0;
3004 while (hue > 1.0)
3005 hue-=1.0;
3006 saturation*=0.01*percent_saturation;
3007 brightness*=0.01*percent_brightness;
3008 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3009}
3010
3011static void ModulateHSL(const double percent_hue,
3012 const double percent_saturation,const double percent_lightness,
3013 Quantum *red,Quantum *green,Quantum *blue)
3014{
3015 double
3016 hue,
3017 lightness,
3018 saturation;
3019
3020 /*
3021 Increase or decrease color lightness, saturation, or hue.
3022 */
3023 assert(red != (Quantum *) NULL);
3024 assert(green != (Quantum *) NULL);
3025 assert(blue != (Quantum *) NULL);
3026 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3027 hue+=0.5*(0.01*percent_hue-1.0);
3028 while (hue < 0.0)
3029 hue+=1.0;
3030 while (hue > 1.0)
3031 hue-=1.0;
3032 saturation*=0.01*percent_saturation;
3033 lightness*=0.01*percent_lightness;
3034 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3035}
3036
3037static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3038{
3039 double
3040 blackness,
3041 hue,
3042 whiteness;
3043
3044 /*
3045 Increase or decrease color blackness, whiteness, or hue.
3046 */
3047 assert(red != (Quantum *) NULL);
3048 assert(green != (Quantum *) NULL);
3049 assert(blue != (Quantum *) NULL);
3050 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3051 hue+=0.5*(0.01*percent_hue-1.0);
3052 while (hue < 0.0)
3053 hue+=1.0;
3054 while (hue > 1.0)
3055 hue-=1.0;
3056 blackness*=0.01*percent_blackness;
3057 whiteness*=0.01*percent_whiteness;
3058 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3059}
3060
3061MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3062{
3063#define ModulateImageTag "Modulate/Image"
3064
cristyc4c8d132010-01-07 01:58:38 +00003065 CacheView
3066 *image_view;
3067
cristy3ed852e2009-09-05 21:47:34 +00003068 ColorspaceType
3069 colorspace;
3070
3071 const char
3072 *artifact;
3073
3074 double
3075 percent_brightness,
3076 percent_hue,
3077 percent_saturation;
3078
3079 ExceptionInfo
3080 *exception;
3081
3082 GeometryInfo
3083 geometry_info;
3084
3085 long
3086 progress,
3087 y;
3088
3089 MagickBooleanType
3090 status;
3091
3092 MagickStatusType
3093 flags;
3094
3095 register long
3096 i;
3097
cristy3ed852e2009-09-05 21:47:34 +00003098 /*
3099 Initialize gamma table.
3100 */
3101 assert(image != (Image *) NULL);
3102 assert(image->signature == MagickSignature);
3103 if (image->debug != MagickFalse)
3104 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3105 if (modulate == (char *) NULL)
3106 return(MagickFalse);
3107 flags=ParseGeometry(modulate,&geometry_info);
3108 percent_brightness=geometry_info.rho;
3109 percent_saturation=geometry_info.sigma;
3110 if ((flags & SigmaValue) == 0)
3111 percent_saturation=100.0;
3112 percent_hue=geometry_info.xi;
3113 if ((flags & XiValue) == 0)
3114 percent_hue=100.0;
3115 colorspace=UndefinedColorspace;
3116 artifact=GetImageArtifact(image,"modulate:colorspace");
3117 if (artifact != (const char *) NULL)
3118 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3119 MagickFalse,artifact);
3120 if (image->storage_class == PseudoClass)
3121 {
3122 /*
3123 Modulate colormap.
3124 */
cristyb5d5f722009-11-04 03:03:49 +00003125#if defined(MAGICKCORE_OPENMP_SUPPORT)
3126 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003127#endif
3128 for (i=0; i < (long) image->colors; i++)
3129 switch (colorspace)
3130 {
3131 case HSBColorspace:
3132 {
3133 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3134 &image->colormap[i].red,&image->colormap[i].green,
3135 &image->colormap[i].blue);
3136 break;
3137 }
3138 case HSLColorspace:
3139 default:
3140 {
3141 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3142 &image->colormap[i].red,&image->colormap[i].green,
3143 &image->colormap[i].blue);
3144 break;
3145 }
3146 case HWBColorspace:
3147 {
3148 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3149 &image->colormap[i].red,&image->colormap[i].green,
3150 &image->colormap[i].blue);
3151 break;
3152 }
3153 }
3154 }
3155 /*
3156 Modulate image.
3157 */
3158 status=MagickTrue;
3159 progress=0;
3160 exception=(&image->exception);
3161 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003162#if defined(MAGICKCORE_OPENMP_SUPPORT)
3163 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003164#endif
3165 for (y=0; y < (long) image->rows; y++)
3166 {
3167 register long
3168 x;
3169
3170 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003171 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003172
3173 if (status == MagickFalse)
3174 continue;
3175 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3176 if (q == (PixelPacket *) NULL)
3177 {
3178 status=MagickFalse;
3179 continue;
3180 }
3181 for (x=0; x < (long) image->columns; x++)
3182 {
3183 switch (colorspace)
3184 {
3185 case HSBColorspace:
3186 {
3187 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3188 &q->red,&q->green,&q->blue);
3189 break;
3190 }
3191 case HSLColorspace:
3192 default:
3193 {
3194 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3195 &q->red,&q->green,&q->blue);
3196 break;
3197 }
3198 case HWBColorspace:
3199 {
3200 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3201 &q->red,&q->green,&q->blue);
3202 break;
3203 }
3204 }
3205 q++;
3206 }
3207 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3208 status=MagickFalse;
3209 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3210 {
3211 MagickBooleanType
3212 proceed;
3213
cristyb5d5f722009-11-04 03:03:49 +00003214#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003215 #pragma omp critical (MagickCore_ModulateImage)
3216#endif
3217 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3218 if (proceed == MagickFalse)
3219 status=MagickFalse;
3220 }
3221 }
3222 image_view=DestroyCacheView(image_view);
3223 return(status);
3224}
3225
3226/*
3227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3228% %
3229% %
3230% %
3231% N e g a t e I m a g e %
3232% %
3233% %
3234% %
3235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3236%
3237% NegateImage() negates the colors in the reference image. The grayscale
3238% option means that only grayscale values within the image are negated.
3239%
3240% The format of the NegateImageChannel method is:
3241%
3242% MagickBooleanType NegateImage(Image *image,
3243% const MagickBooleanType grayscale)
3244% MagickBooleanType NegateImageChannel(Image *image,
3245% const ChannelType channel,const MagickBooleanType grayscale)
3246%
3247% A description of each parameter follows:
3248%
3249% o image: the image.
3250%
3251% o channel: the channel.
3252%
3253% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3254%
3255*/
3256
3257MagickExport MagickBooleanType NegateImage(Image *image,
3258 const MagickBooleanType grayscale)
3259{
3260 MagickBooleanType
3261 status;
3262
3263 status=NegateImageChannel(image,DefaultChannels,grayscale);
3264 return(status);
3265}
3266
3267MagickExport MagickBooleanType NegateImageChannel(Image *image,
3268 const ChannelType channel,const MagickBooleanType grayscale)
3269{
3270#define NegateImageTag "Negate/Image"
3271
cristyc4c8d132010-01-07 01:58:38 +00003272 CacheView
3273 *image_view;
3274
cristy3ed852e2009-09-05 21:47:34 +00003275 ExceptionInfo
3276 *exception;
3277
3278 long
3279 progress,
3280 y;
3281
3282 MagickBooleanType
3283 status;
3284
3285 register long
3286 i;
3287
cristy3ed852e2009-09-05 21:47:34 +00003288 assert(image != (Image *) NULL);
3289 assert(image->signature == MagickSignature);
3290 if (image->debug != MagickFalse)
3291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3292 if (image->storage_class == PseudoClass)
3293 {
3294 /*
3295 Negate colormap.
3296 */
cristyb5d5f722009-11-04 03:03:49 +00003297#if defined(MAGICKCORE_OPENMP_SUPPORT)
3298 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003299#endif
3300 for (i=0; i < (long) image->colors; i++)
3301 {
3302 if (grayscale != MagickFalse)
3303 if ((image->colormap[i].red != image->colormap[i].green) ||
3304 (image->colormap[i].green != image->colormap[i].blue))
3305 continue;
3306 if ((channel & RedChannel) != 0)
3307 image->colormap[i].red=(Quantum) QuantumRange-
3308 image->colormap[i].red;
3309 if ((channel & GreenChannel) != 0)
3310 image->colormap[i].green=(Quantum) QuantumRange-
3311 image->colormap[i].green;
3312 if ((channel & BlueChannel) != 0)
3313 image->colormap[i].blue=(Quantum) QuantumRange-
3314 image->colormap[i].blue;
3315 }
3316 }
3317 /*
3318 Negate image.
3319 */
3320 status=MagickTrue;
3321 progress=0;
3322 exception=(&image->exception);
3323 image_view=AcquireCacheView(image);
3324 if (grayscale != MagickFalse)
3325 {
cristyb5d5f722009-11-04 03:03:49 +00003326#if defined(MAGICKCORE_OPENMP_SUPPORT)
3327 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003328#endif
3329 for (y=0; y < (long) image->rows; y++)
3330 {
3331 MagickBooleanType
3332 sync;
3333
3334 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003335 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003336
3337 register long
3338 x;
3339
3340 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003341 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003342
3343 if (status == MagickFalse)
3344 continue;
3345 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3346 exception);
3347 if (q == (PixelPacket *) NULL)
3348 {
3349 status=MagickFalse;
3350 continue;
3351 }
3352 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3353 for (x=0; x < (long) image->columns; x++)
3354 {
3355 if ((q->red != q->green) || (q->green != q->blue))
3356 {
3357 q++;
3358 continue;
3359 }
3360 if ((channel & RedChannel) != 0)
3361 q->red=(Quantum) QuantumRange-q->red;
3362 if ((channel & GreenChannel) != 0)
3363 q->green=(Quantum) QuantumRange-q->green;
3364 if ((channel & BlueChannel) != 0)
3365 q->blue=(Quantum) QuantumRange-q->blue;
3366 if ((channel & OpacityChannel) != 0)
3367 q->opacity=(Quantum) QuantumRange-q->opacity;
3368 if (((channel & IndexChannel) != 0) &&
3369 (image->colorspace == CMYKColorspace))
3370 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3371 q++;
3372 }
3373 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3374 if (sync == MagickFalse)
3375 status=MagickFalse;
3376 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3377 {
3378 MagickBooleanType
3379 proceed;
3380
cristyb5d5f722009-11-04 03:03:49 +00003381#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003382 #pragma omp critical (MagickCore_NegateImageChannel)
3383#endif
3384 proceed=SetImageProgress(image,NegateImageTag,progress++,
3385 image->rows);
3386 if (proceed == MagickFalse)
3387 status=MagickFalse;
3388 }
3389 }
3390 image_view=DestroyCacheView(image_view);
3391 return(MagickTrue);
3392 }
3393 /*
3394 Negate image.
3395 */
cristyb5d5f722009-11-04 03:03:49 +00003396#if defined(MAGICKCORE_OPENMP_SUPPORT)
3397 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003398#endif
3399 for (y=0; y < (long) image->rows; y++)
3400 {
3401 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003402 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003403
3404 register long
3405 x;
3406
3407 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003408 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003409
3410 if (status == MagickFalse)
3411 continue;
3412 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3413 if (q == (PixelPacket *) NULL)
3414 {
3415 status=MagickFalse;
3416 continue;
3417 }
3418 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3419 for (x=0; x < (long) image->columns; x++)
3420 {
3421 if ((channel & RedChannel) != 0)
3422 q->red=(Quantum) QuantumRange-q->red;
3423 if ((channel & GreenChannel) != 0)
3424 q->green=(Quantum) QuantumRange-q->green;
3425 if ((channel & BlueChannel) != 0)
3426 q->blue=(Quantum) QuantumRange-q->blue;
3427 if ((channel & OpacityChannel) != 0)
3428 q->opacity=(Quantum) QuantumRange-q->opacity;
3429 if (((channel & IndexChannel) != 0) &&
3430 (image->colorspace == CMYKColorspace))
3431 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3432 q++;
3433 }
3434 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3435 status=MagickFalse;
3436 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3437 {
3438 MagickBooleanType
3439 proceed;
3440
cristyb5d5f722009-11-04 03:03:49 +00003441#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003442 #pragma omp critical (MagickCore_NegateImageChannel)
3443#endif
3444 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3445 if (proceed == MagickFalse)
3446 status=MagickFalse;
3447 }
3448 }
3449 image_view=DestroyCacheView(image_view);
3450 return(status);
3451}
3452
3453/*
3454%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3455% %
3456% %
3457% %
3458% N o r m a l i z e I m a g e %
3459% %
3460% %
3461% %
3462%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3463%
3464% The NormalizeImage() method enhances the contrast of a color image by
3465% mapping the darkest 2 percent of all pixel to black and the brightest
3466% 1 percent to white.
3467%
3468% The format of the NormalizeImage method is:
3469%
3470% MagickBooleanType NormalizeImage(Image *image)
3471% MagickBooleanType NormalizeImageChannel(Image *image,
3472% const ChannelType channel)
3473%
3474% A description of each parameter follows:
3475%
3476% o image: the image.
3477%
3478% o channel: the channel.
3479%
3480*/
3481
3482MagickExport MagickBooleanType NormalizeImage(Image *image)
3483{
3484 MagickBooleanType
3485 status;
3486
3487 status=NormalizeImageChannel(image,DefaultChannels);
3488 return(status);
3489}
3490
3491MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3492 const ChannelType channel)
3493{
3494 double
3495 black_point,
3496 white_point;
3497
3498 black_point=(double) image->columns*image->rows*0.02;
3499 white_point=(double) image->columns*image->rows*0.99;
3500 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3501}
3502
3503/*
3504%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3505% %
3506% %
3507% %
3508% S i g m o i d a l C o n t r a s t I m a g e %
3509% %
3510% %
3511% %
3512%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3513%
3514% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3515% sigmoidal contrast algorithm. Increase the contrast of the image using a
3516% sigmoidal transfer function without saturating highlights or shadows.
3517% Contrast indicates how much to increase the contrast (0 is none; 3 is
3518% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3519% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3520% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3521% is reduced.
3522%
3523% The format of the SigmoidalContrastImage method is:
3524%
3525% MagickBooleanType SigmoidalContrastImage(Image *image,
3526% const MagickBooleanType sharpen,const char *levels)
3527% MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3528% const ChannelType channel,const MagickBooleanType sharpen,
3529% const double contrast,const double midpoint)
3530%
3531% A description of each parameter follows:
3532%
3533% o image: the image.
3534%
3535% o channel: the channel.
3536%
3537% o sharpen: Increase or decrease image contrast.
3538%
3539% o contrast: control the "shoulder" of the contast curve.
3540%
3541% o midpoint: control the "toe" of the contast curve.
3542%
3543*/
3544
3545MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3546 const MagickBooleanType sharpen,const char *levels)
3547{
3548 GeometryInfo
3549 geometry_info;
3550
3551 MagickBooleanType
3552 status;
3553
3554 MagickStatusType
3555 flags;
3556
3557 flags=ParseGeometry(levels,&geometry_info);
3558 if ((flags & SigmaValue) == 0)
3559 geometry_info.sigma=1.0*QuantumRange/2.0;
3560 if ((flags & PercentValue) != 0)
3561 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3562 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3563 geometry_info.rho,geometry_info.sigma);
3564 return(status);
3565}
3566
3567MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3568 const ChannelType channel,const MagickBooleanType sharpen,
3569 const double contrast,const double midpoint)
3570{
3571#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3572
cristyc4c8d132010-01-07 01:58:38 +00003573 CacheView
3574 *image_view;
3575
cristy3ed852e2009-09-05 21:47:34 +00003576 ExceptionInfo
3577 *exception;
3578
3579 long
3580 progress,
3581 y;
3582
3583 MagickBooleanType
3584 status;
3585
3586 MagickRealType
3587 *sigmoidal_map;
3588
3589 register long
3590 i;
3591
cristy3ed852e2009-09-05 21:47:34 +00003592 /*
3593 Allocate and initialize sigmoidal maps.
3594 */
3595 assert(image != (Image *) NULL);
3596 assert(image->signature == MagickSignature);
3597 if (image->debug != MagickFalse)
3598 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3599 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3600 sizeof(*sigmoidal_map));
3601 if (sigmoidal_map == (MagickRealType *) NULL)
3602 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3603 image->filename);
3604 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003605#if defined(MAGICKCORE_OPENMP_SUPPORT)
3606 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003607#endif
3608 for (i=0; i <= (long) MaxMap; i++)
3609 {
3610 if (sharpen != MagickFalse)
3611 {
3612 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3613 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3614 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3615 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3616 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3617 (double) QuantumRange)))))+0.5));
3618 continue;
3619 }
3620 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3621 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3622 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3623 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3624 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3625 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3626 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3627 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3628 (double) QuantumRange*contrast))))))/contrast)));
3629 }
3630 if (image->storage_class == PseudoClass)
3631 {
3632 /*
3633 Sigmoidal-contrast enhance colormap.
3634 */
cristyb5d5f722009-11-04 03:03:49 +00003635#if defined(MAGICKCORE_OPENMP_SUPPORT)
3636 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003637#endif
3638 for (i=0; i < (long) image->colors; i++)
3639 {
3640 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003641 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003642 ScaleQuantumToMap(image->colormap[i].red)]);
3643 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003644 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003645 ScaleQuantumToMap(image->colormap[i].green)]);
3646 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003647 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003648 ScaleQuantumToMap(image->colormap[i].blue)]);
3649 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003650 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003651 ScaleQuantumToMap(image->colormap[i].opacity)]);
3652 }
3653 }
3654 /*
3655 Sigmoidal-contrast enhance image.
3656 */
3657 status=MagickTrue;
3658 progress=0;
3659 exception=(&image->exception);
3660 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00003661#if defined(MAGICKCORE_OPENMP_SUPPORT)
3662 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003663#endif
3664 for (y=0; y < (long) image->rows; y++)
3665 {
3666 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003667 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003668
3669 register long
3670 x;
3671
3672 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003673 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003674
3675 if (status == MagickFalse)
3676 continue;
3677 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3678 if (q == (PixelPacket *) NULL)
3679 {
3680 status=MagickFalse;
3681 continue;
3682 }
3683 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3684 for (x=0; x < (long) image->columns; x++)
3685 {
3686 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003687 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
cristy3ed852e2009-09-05 21:47:34 +00003688 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003689 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
cristy3ed852e2009-09-05 21:47:34 +00003690 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003691 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
cristy3ed852e2009-09-05 21:47:34 +00003692 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003693 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
cristy3ed852e2009-09-05 21:47:34 +00003694 if (((channel & IndexChannel) != 0) &&
3695 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003696 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
cristy3ed852e2009-09-05 21:47:34 +00003697 ScaleQuantumToMap(indexes[x])]);
3698 q++;
3699 }
3700 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3701 status=MagickFalse;
3702 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3703 {
3704 MagickBooleanType
3705 proceed;
3706
cristyb5d5f722009-11-04 03:03:49 +00003707#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003708 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3709#endif
3710 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3711 image->rows);
3712 if (proceed == MagickFalse)
3713 status=MagickFalse;
3714 }
3715 }
3716 image_view=DestroyCacheView(image_view);
3717 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3718 return(status);
3719}