/src/graphicsmagick/magick/enhance.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2003 - 2019 GraphicsMagick Group |
3 | | % Copyright (C) 2002 ImageMagick Studio |
4 | | % Copyright 1991-1999 E. I. du Pont de Nemours and Company |
5 | | % |
6 | | % This program is covered by multiple licenses, which are described in |
7 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
8 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
9 | | % |
10 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
11 | | % % |
12 | | % % |
13 | | % % |
14 | | % EEEEE N N H H AAA N N CCCC EEEEE % |
15 | | % E NN N H H A A NN N C E % |
16 | | % EEE N N N HHHHH AAAAA N N N C EEE % |
17 | | % E N NN H H A A N NN C E % |
18 | | % EEEEE N N H H A A N N CCCC EEEEE % |
19 | | % % |
20 | | % % |
21 | | % GraphicsMagick Image Enhancement Methods % |
22 | | % % |
23 | | % % |
24 | | % Software Design % |
25 | | % John Cristy % |
26 | | % July 1992 % |
27 | | % Re-Written % |
28 | | % Bob Friesenhahn % |
29 | | % May 2008 % |
30 | | % % |
31 | | % % |
32 | | % % |
33 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
34 | | % |
35 | | % |
36 | | % |
37 | | */ |
38 | | |
39 | | /* |
40 | | Include declarations. |
41 | | */ |
42 | | #include "magick/studio.h" |
43 | | #include "magick/attribute.h" |
44 | | #include "magick/color.h" |
45 | | #include "magick/enhance.h" |
46 | | #include "magick/gem.h" |
47 | | #include "magick/pixel_iterator.h" |
48 | | #include "magick/log.h" |
49 | | #include "magick/monitor.h" |
50 | | #include "magick/utility.h" |
51 | | |
52 | | static MagickPassFail |
53 | | BuildChannelHistogramsCB(void *mutable_data, /* User provided mutable data */ |
54 | | const void *immutable_data, /* User provided immutable data */ |
55 | | const Image * restrict const_image, /* Input image */ |
56 | | const PixelPacket * restrict pixels, /* Pixel row */ |
57 | | const IndexPacket * restrict indexes, /* Pixel indexes */ |
58 | | const long npixels, /* Number of pixels in row */ |
59 | | ExceptionInfo *exception /* Exception report */ |
60 | | ) |
61 | 0 | { |
62 | | /* |
63 | | Built an image histogram in the supplied table. |
64 | | |
65 | | Should be executed by just one thread in order to avoid contention |
66 | | for the histogram table. |
67 | | */ |
68 | 0 | DoublePixelPacket |
69 | 0 | *histogram = (DoublePixelPacket *) mutable_data; |
70 | |
|
71 | 0 | register long |
72 | 0 | i; |
73 | |
|
74 | 0 | ARG_NOT_USED(immutable_data); |
75 | 0 | ARG_NOT_USED(indexes); |
76 | 0 | ARG_NOT_USED(exception); |
77 | |
|
78 | 0 | for (i=0; i < npixels; i++) |
79 | 0 | { |
80 | 0 | histogram[ScaleQuantumToMap(pixels[i].red)].red++; |
81 | 0 | histogram[ScaleQuantumToMap(pixels[i].green)].green++; |
82 | 0 | histogram[ScaleQuantumToMap(pixels[i].blue)].blue++; |
83 | 0 | if (const_image->matte) |
84 | 0 | histogram[ScaleQuantumToMap(pixels[i].opacity)].opacity++; |
85 | 0 | } |
86 | |
|
87 | 0 | return MagickPass; |
88 | 0 | } |
89 | | |
90 | | static DoublePixelPacket * |
91 | | BuildChannelHistograms(const Image *image, ExceptionInfo *exception) |
92 | 0 | { |
93 | 0 | MagickPassFail |
94 | 0 | status = MagickPass; |
95 | |
|
96 | 0 | DoublePixelPacket |
97 | 0 | *histogram; |
98 | |
|
99 | 0 | histogram=MagickAllocateArray(DoublePixelPacket *,(MaxMap+1), |
100 | 0 | sizeof(DoublePixelPacket)); |
101 | 0 | if (histogram == (DoublePixelPacket *) NULL) |
102 | 0 | { |
103 | 0 | ThrowException(exception,ResourceLimitError,MemoryAllocationFailed, |
104 | 0 | image->filename); |
105 | 0 | return (DoublePixelPacket *) NULL; |
106 | 0 | } |
107 | | |
108 | 0 | (void) memset(histogram,0,(MaxMap+1)*sizeof(DoublePixelPacket)); |
109 | |
|
110 | 0 | { |
111 | 0 | PixelIteratorOptions |
112 | 0 | iterator_options; |
113 | |
|
114 | 0 | InitializePixelIteratorOptions(&iterator_options,exception); |
115 | 0 | iterator_options.max_threads=1; |
116 | 0 | status=PixelIterateMonoRead(BuildChannelHistogramsCB, |
117 | 0 | &iterator_options, |
118 | 0 | "[%s] Building histogram...", |
119 | 0 | histogram,NULL, |
120 | 0 | 0,0,image->columns,image->rows, |
121 | 0 | image,exception); |
122 | 0 | } |
123 | |
|
124 | 0 | if (status == MagickFail) |
125 | 0 | MagickFreeMemory(histogram); |
126 | |
|
127 | 0 | return histogram; |
128 | 0 | } |
129 | | |
130 | | /* |
131 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
132 | | % % |
133 | | % % |
134 | | % C o n t r a s t I m a g e % |
135 | | % % |
136 | | % % |
137 | | % % |
138 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
139 | | % |
140 | | % ContrastImage() enhances the intensity differences between the lighter and |
141 | | % darker elements of the image. Set sharpen to a value other than 0 to |
142 | | % increase the image contrast otherwise the contrast is reduced. |
143 | | % |
144 | | % The format of the ContrastImage method is: |
145 | | % |
146 | | % unsigned int ContrastImage(Image *image,const unsigned int sharpen) |
147 | | % |
148 | | % A description of each parameter follows: |
149 | | % |
150 | | % o image: The image. |
151 | | % |
152 | | % o sharpen: Increase or decrease image contrast. |
153 | | % |
154 | | % |
155 | | */ |
156 | | static MagickPassFail |
157 | | ContrastImagePixels(void *mutable_data, /* User provided mutable data */ |
158 | | const void *immutable_data, /* User provided immutable data */ |
159 | | Image * restrict image, /* Modify image */ |
160 | | PixelPacket * restrict pixels, /* Pixel row */ |
161 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
162 | | const long npixels, /* Number of pixels in row */ |
163 | | ExceptionInfo *exception) /* Exception report */ |
164 | 0 | { |
165 | | /* |
166 | | Modulate pixels contrast. |
167 | | */ |
168 | 0 | const double |
169 | 0 | sign = *((const double *) immutable_data); |
170 | |
|
171 | 0 | static const double |
172 | 0 | alpha=0.50000000000099997787827987849595956504344940185546875; /* 0.5+MagickEpsilon */ |
173 | |
|
174 | 0 | register long |
175 | 0 | i; |
176 | |
|
177 | 0 | ARG_NOT_USED(mutable_data); |
178 | 0 | ARG_NOT_USED(image); |
179 | 0 | ARG_NOT_USED(indexes); |
180 | 0 | ARG_NOT_USED(exception); |
181 | |
|
182 | 0 | for (i=0; i < npixels; i++) |
183 | 0 | { |
184 | 0 | double |
185 | 0 | brightness, |
186 | 0 | hue, |
187 | 0 | saturation; |
188 | |
|
189 | 0 | TransformHSL(pixels[i].red,pixels[i].green,pixels[i].blue, |
190 | 0 | &hue,&saturation,&brightness); |
191 | 0 | brightness+= |
192 | 0 | alpha*sign*(alpha*(sin(MagickPI*(brightness-alpha))+1.0)-brightness); |
193 | 0 | if (brightness > 1.0) |
194 | 0 | brightness=1.0; |
195 | 0 | if (brightness < 0.0) |
196 | 0 | brightness=0.0; |
197 | 0 | HSLTransform(hue,saturation,brightness,&pixels[i].red, |
198 | 0 | &pixels[i].green,&pixels[i].blue); |
199 | 0 | } |
200 | |
|
201 | 0 | return MagickPass; |
202 | 0 | } |
203 | 0 | #define DullContrastImageText "[%s] Dulling contrast..." |
204 | 0 | #define SharpenContrastImageText "[%s] Sharpening contrast..." |
205 | | MagickExport MagickPassFail ContrastImage(Image *image,const unsigned int sharpen) |
206 | 0 | { |
207 | 0 | double |
208 | 0 | sign; |
209 | |
|
210 | 0 | const char |
211 | 0 | *progress_message; |
212 | |
|
213 | 0 | unsigned int |
214 | 0 | is_grayscale; |
215 | |
|
216 | 0 | MagickPassFail |
217 | 0 | status=MagickPass; |
218 | |
|
219 | 0 | assert(image != (Image *) NULL); |
220 | 0 | assert(image->signature == MagickSignature); |
221 | 0 | is_grayscale=image->is_grayscale; |
222 | 0 | sign=sharpen ? 1.0 : -1.0; |
223 | 0 | progress_message=sharpen ? SharpenContrastImageText : DullContrastImageText; |
224 | 0 | if (image->storage_class == PseudoClass) |
225 | 0 | { |
226 | 0 | (void) ContrastImagePixels(NULL,&sign,image,image->colormap, |
227 | 0 | (IndexPacket *) NULL,image->colors, |
228 | 0 | &image->exception); |
229 | 0 | status=SyncImage(image); |
230 | 0 | } |
231 | 0 | else |
232 | 0 | { |
233 | 0 | status=PixelIterateMonoModify(ContrastImagePixels,NULL, |
234 | 0 | progress_message, |
235 | 0 | NULL,&sign,0,0,image->columns,image->rows, |
236 | 0 | image,&image->exception); |
237 | 0 | } |
238 | 0 | image->is_grayscale=is_grayscale; |
239 | 0 | return(status); |
240 | 0 | } |
241 | | |
242 | | /* |
243 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
244 | | % % |
245 | | % % |
246 | | % E q u a l i z e I m a g e % |
247 | | % % |
248 | | % % |
249 | | % % |
250 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
251 | | % |
252 | | % EqualizeImage() applies a histogram equalization to the image. |
253 | | % |
254 | | % The format of the EqualizeImage method is: |
255 | | % |
256 | | % unsigned int EqualizeImage(Image *image) |
257 | | % |
258 | | % A description of each parameter follows: |
259 | | % |
260 | | % o image: The image. |
261 | | % |
262 | | */ |
263 | | typedef struct _ApplyLevelsOptions_t |
264 | | { |
265 | | PixelPacket |
266 | | *map; |
267 | | |
268 | | MagickBool |
269 | | level_red, |
270 | | level_green, |
271 | | level_blue, |
272 | | level_opacity; |
273 | | } ApplyLevels_t; |
274 | | |
275 | | static MagickPassFail |
276 | | ApplyLevels(void *mutable_data, /* User provided mutable data */ |
277 | | const void *immutable_data, /* User provided immutable data */ |
278 | | Image * restrict image, /* Modify image */ |
279 | | PixelPacket * restrict pixels, /* Pixel row */ |
280 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
281 | | const long npixels, /* Number of pixels in row */ |
282 | | ExceptionInfo *exception) /* Exception report */ |
283 | 0 | { |
284 | | /* |
285 | | Apply a levels transformation based on a supplied look-up table. |
286 | | */ |
287 | 0 | const ApplyLevels_t |
288 | 0 | *options = (const ApplyLevels_t *) immutable_data; |
289 | |
|
290 | 0 | register long |
291 | 0 | i; |
292 | |
|
293 | 0 | PixelPacket |
294 | 0 | *map=options->map; |
295 | |
|
296 | 0 | MagickBool |
297 | 0 | level_red=options->level_red, |
298 | 0 | level_green=options->level_green, |
299 | 0 | level_blue=options->level_blue, |
300 | 0 | level_opacity=options->level_opacity; |
301 | |
|
302 | 0 | ARG_NOT_USED(mutable_data); |
303 | 0 | ARG_NOT_USED(image); |
304 | 0 | ARG_NOT_USED(indexes); |
305 | 0 | ARG_NOT_USED(exception); |
306 | |
|
307 | 0 | for (i=0; i < npixels; i++) |
308 | 0 | { |
309 | 0 | if (level_red) |
310 | 0 | pixels[i].red=map[ScaleQuantumToMap(pixels[i].red)].red; |
311 | 0 | if (level_green) |
312 | 0 | pixels[i].green=map[ScaleQuantumToMap(pixels[i].green)].green; |
313 | 0 | if (level_blue) |
314 | 0 | pixels[i].blue=map[ScaleQuantumToMap(pixels[i].blue)].blue; |
315 | 0 | if (level_opacity) |
316 | 0 | pixels[i].opacity=map[ScaleQuantumToMap(pixels[i].opacity)].opacity; |
317 | 0 | } |
318 | 0 | return MagickPass; |
319 | 0 | } |
320 | | |
321 | | #define EqualizeImageText "Equalize... " |
322 | | MagickExport MagickPassFail EqualizeImage(Image *image) |
323 | 0 | { |
324 | 0 | DoublePixelPacket |
325 | 0 | high, |
326 | 0 | *histogram, |
327 | 0 | intensity, |
328 | 0 | low, |
329 | 0 | *map; |
330 | |
|
331 | 0 | ApplyLevels_t |
332 | 0 | levels; |
333 | |
|
334 | 0 | register long |
335 | 0 | i; |
336 | |
|
337 | 0 | unsigned int |
338 | 0 | is_grayscale; |
339 | |
|
340 | 0 | MagickPassFail |
341 | 0 | status=MagickPass; |
342 | | |
343 | | /* |
344 | | Allocate and initialize arrays. |
345 | | */ |
346 | 0 | assert(image != (Image *) NULL); |
347 | 0 | assert(image->signature == MagickSignature); |
348 | 0 | is_grayscale=image->is_grayscale; |
349 | 0 | map=MagickAllocateMemory(DoublePixelPacket *,(MaxMap+1)*sizeof(DoublePixelPacket)); |
350 | 0 | levels.map=MagickAllocateMemory(PixelPacket *,(MaxMap+1)*sizeof(PixelPacket)); |
351 | 0 | if ((map == (DoublePixelPacket *) NULL) || |
352 | 0 | (levels.map == (PixelPacket *) NULL)) |
353 | 0 | { |
354 | 0 | MagickFreeMemory(map); |
355 | 0 | MagickFreeMemory(levels.map); |
356 | 0 | ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed, |
357 | 0 | MagickMsg(OptionError,UnableToEqualizeImage)); |
358 | 0 | } |
359 | | /* |
360 | | Build histogram. |
361 | | */ |
362 | 0 | histogram=BuildChannelHistograms(image,&image->exception); |
363 | 0 | if (histogram == (DoublePixelPacket *) NULL) |
364 | 0 | { |
365 | 0 | MagickFreeMemory(map); |
366 | 0 | MagickFreeMemory(levels.map); |
367 | 0 | return MagickFail; |
368 | 0 | } |
369 | | /* |
370 | | Integrate the histogram to get the equalization map. |
371 | | */ |
372 | 0 | (void) memset(&intensity,0,sizeof(DoublePixelPacket)); |
373 | 0 | for (i=0; i <= (long) MaxMap; i++) |
374 | 0 | { |
375 | 0 | intensity.red+=histogram[i].red; |
376 | 0 | intensity.green+=histogram[i].green; |
377 | 0 | intensity.blue+=histogram[i].blue; |
378 | 0 | if (image->matte) |
379 | 0 | intensity.opacity+=histogram[i].opacity; |
380 | 0 | map[i]=intensity; |
381 | 0 | } |
382 | 0 | low=map[0]; |
383 | 0 | high=map[MaxMap]; |
384 | 0 | (void) memset(levels.map,0,(MaxMap+1)*sizeof(PixelPacket)); |
385 | 0 | levels.level_red = (low.red != high.red); |
386 | 0 | levels.level_green = (low.green != high.green); |
387 | 0 | levels.level_blue = (low.blue != high.blue); |
388 | 0 | levels.level_opacity= (image->matte && (low.opacity != high.opacity)); |
389 | 0 | for (i=0; i <= (long) MaxMap; i++) |
390 | 0 | { |
391 | 0 | if (levels.level_red) |
392 | 0 | levels.map[i].red=ScaleMapToQuantum( |
393 | 0 | (MaxMap*(map[i].red-low.red))/(high.red-low.red)); |
394 | 0 | if (levels.level_green) |
395 | 0 | levels.map[i].green=ScaleMapToQuantum( |
396 | 0 | (MaxMap*(map[i].green-low.green))/(high.green-low.green)); |
397 | 0 | if (levels.level_blue) |
398 | 0 | levels.map[i].blue=ScaleMapToQuantum( |
399 | 0 | (MaxMap*(map[i].blue-low.blue))/(high.blue-low.blue)); |
400 | 0 | if (levels.level_opacity) |
401 | 0 | levels.map[i].opacity=ScaleMapToQuantum( |
402 | 0 | (MaxMap*(map[i].opacity-low.opacity))/(high.opacity-low.opacity)); |
403 | 0 | } |
404 | 0 | MagickFreeMemory(histogram); |
405 | 0 | MagickFreeMemory(map); |
406 | | /* |
407 | | Stretch the histogram based on the map. |
408 | | */ |
409 | 0 | if (image->storage_class == PseudoClass) |
410 | 0 | { |
411 | 0 | (void) ApplyLevels(NULL,&levels,image,image->colormap, |
412 | 0 | (IndexPacket *) NULL,image->colors, |
413 | 0 | &image->exception); |
414 | 0 | status=SyncImage(image); |
415 | 0 | } |
416 | 0 | else |
417 | 0 | { |
418 | 0 | status=PixelIterateMonoModify(ApplyLevels, |
419 | 0 | NULL, |
420 | 0 | "[%s] Applying histogram equalization...", |
421 | 0 | NULL,&levels, |
422 | 0 | 0,0,image->columns,image->rows, |
423 | 0 | image, |
424 | 0 | &image->exception); |
425 | 0 | } |
426 | 0 | MagickFreeMemory(levels.map); |
427 | 0 | image->is_grayscale=is_grayscale; |
428 | 0 | return(status); |
429 | 0 | } |
430 | | |
431 | | /* |
432 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
433 | | % % |
434 | | % % |
435 | | % G a m m a I m a g e % |
436 | | % % |
437 | | % % |
438 | | % % |
439 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
440 | | % |
441 | | % Use GammaImage() to gamma-correct an image. The same image viewed on |
442 | | % different devices will have perceptual differences in the way the |
443 | | % image's intensities are represented on the screen. Specify individual |
444 | | % gamma levels for the red, green, and blue channels (e.g. "1.0,2.2,0.45"), |
445 | | % or adjust all three with a single gamma parameter. Values typically range |
446 | | % from 0.45 to 2.6. |
447 | | % |
448 | | % You can also reduce the influence of a particular channel with a gamma |
449 | | % value of 0. |
450 | | % |
451 | | % The format of the GammaImage method is: |
452 | | % |
453 | | % MagickPassFail GammaImage(Image *image,const char *level) |
454 | | % |
455 | | % A description of each parameter follows: |
456 | | % |
457 | | % o image: The image. |
458 | | % |
459 | | % o level: Define the level of gamma correction. |
460 | | % |
461 | | % |
462 | | */ |
463 | | #if MaxMap == MaxRGB |
464 | | typedef struct _ApplyLevelsDiscrete_t |
465 | | { |
466 | | Quantum |
467 | | * restrict color, /* red, green, & blue */ |
468 | | * restrict red, /* red */ |
469 | | * restrict green, /* green */ |
470 | | * restrict blue, /* blue */ |
471 | | * restrict opacity; /* Opacity */ |
472 | | } ApplyLevelsDiscrete_t; |
473 | | |
474 | | static MagickPassFail |
475 | | ApplyLevelsDiscrete(void *mutable_data, /* User provided mutable data */ |
476 | | const void *immutable_data, /* User provided immutable data */ |
477 | | Image * restrict image, /* Modify image */ |
478 | | PixelPacket * restrict pixels, /* Pixel row */ |
479 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
480 | | const long npixels, /* Number of pixels in row */ |
481 | | ExceptionInfo *exception) /* Exception report */ |
482 | 0 | { |
483 | | /* |
484 | | Apply a levels transformation based on a supplied look-up table. |
485 | | */ |
486 | 0 | const ApplyLevelsDiscrete_t |
487 | 0 | levels = *(const ApplyLevelsDiscrete_t *) immutable_data; |
488 | |
|
489 | 0 | register long |
490 | 0 | i; |
491 | |
|
492 | 0 | ARG_NOT_USED(mutable_data); |
493 | 0 | ARG_NOT_USED(image); |
494 | 0 | ARG_NOT_USED(indexes); |
495 | 0 | ARG_NOT_USED(exception); |
496 | |
|
497 | 0 | for (i=0; i < npixels; i++) |
498 | 0 | { |
499 | 0 | if (levels.color) |
500 | 0 | { |
501 | 0 | pixels[i].red=levels.color[ScaleQuantumToMap(pixels[i].red)]; |
502 | 0 | pixels[i].green=levels.color[ScaleQuantumToMap(pixels[i].green)]; |
503 | 0 | pixels[i].blue=levels.color[ScaleQuantumToMap(pixels[i].blue)]; |
504 | 0 | } |
505 | 0 | else |
506 | 0 | { |
507 | 0 | if (levels.red) |
508 | 0 | pixels[i].red=levels.red[ScaleQuantumToMap(pixels[i].red)]; |
509 | 0 | if (levels.green) |
510 | 0 | pixels[i].green=levels.green[ScaleQuantumToMap(pixels[i].green)]; |
511 | 0 | if (levels.blue) |
512 | 0 | pixels[i].blue=levels.blue[ScaleQuantumToMap(pixels[i].blue)]; |
513 | 0 | } |
514 | 0 | if (levels.opacity) |
515 | 0 | pixels[i].opacity=levels.opacity[ScaleQuantumToMap(pixels[i].opacity)]; |
516 | 0 | } |
517 | |
|
518 | 0 | return MagickPass; |
519 | 0 | } |
520 | | #endif /* if MaxMap == MaxRGB */ |
521 | | |
522 | | /* |
523 | | Gamma correct value in range of 0.0 to 1.0 |
524 | | */ |
525 | | static inline double |
526 | | GammaCorrect(const double value, const double gamma) |
527 | 0 | { |
528 | 0 | return pow(value,1.0/gamma); |
529 | 0 | } |
530 | | |
531 | | #if MaxMap != MaxRGB |
532 | | typedef DoublePixelPacket GammaCorrectPixelsOptions_t; |
533 | | |
534 | | static MagickPassFail |
535 | | GammaCorrectPixels(void *mutable_data, /* User provided mutable data */ |
536 | | const void *immutable_data, /* User provided immutable data */ |
537 | | Image * restrict image, /* Modify image */ |
538 | | PixelPacket * restrict pixels, /* Pixel row */ |
539 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
540 | | const long npixels, /* Number of pixels in row */ |
541 | | ExceptionInfo *exception) /* Exception report */ |
542 | | { |
543 | | /* |
544 | | Apply a gamma transformation based on slow accurate math. |
545 | | */ |
546 | | const GammaCorrectPixelsOptions_t |
547 | | options = *(const GammaCorrectPixelsOptions_t *) immutable_data; |
548 | | |
549 | | register long |
550 | | i; |
551 | | |
552 | | MagickBool |
553 | | red_flag, |
554 | | green_flag, |
555 | | blue_flag; |
556 | | |
557 | | ARG_NOT_USED(mutable_data); |
558 | | ARG_NOT_USED(image); |
559 | | ARG_NOT_USED(indexes); |
560 | | ARG_NOT_USED(exception); |
561 | | |
562 | | red_flag=(options.red != 1.0); |
563 | | green_flag=(options.green != 1.0); |
564 | | blue_flag=(options.blue != 1.0); |
565 | | |
566 | | for (i=0; i < npixels; i++) |
567 | | { |
568 | | double |
569 | | value; |
570 | | |
571 | | if (red_flag) |
572 | | { |
573 | | value=MaxRGBDouble*GammaCorrect(pixels[i].red/MaxRGBDouble,options.red); |
574 | | pixels[i].red=RoundDoubleToQuantum(value); |
575 | | } |
576 | | if (green_flag) |
577 | | { |
578 | | value=MaxRGBDouble*GammaCorrect(pixels[i].green/MaxRGBDouble,options.green); |
579 | | pixels[i].green=RoundDoubleToQuantum(value); |
580 | | } |
581 | | if (blue_flag) |
582 | | { |
583 | | value=MaxRGBDouble*GammaCorrect(pixels[i].blue/MaxRGBDouble,options.blue); |
584 | | pixels[i].blue=RoundDoubleToQuantum(value); |
585 | | } |
586 | | } |
587 | | |
588 | | return MagickPass; |
589 | | } |
590 | | #endif /* MaxMap != MaxRGB */ |
591 | | |
592 | | MagickExport MagickPassFail GammaImage(Image *image,const char *level) |
593 | 0 | { |
594 | 0 | double |
595 | 0 | #if MaxMap == MaxRGB |
596 | 0 | gamma_color=0.0, |
597 | 0 | #endif /* if MaxMap == MaxRGB */ |
598 | 0 | gamma_red=1.0, |
599 | 0 | gamma_green=1.0, |
600 | 0 | gamma_blue=1.0; |
601 | |
|
602 | 0 | long |
603 | 0 | count; |
604 | |
|
605 | 0 | unsigned int |
606 | 0 | is_grayscale; |
607 | |
|
608 | 0 | MagickBool |
609 | 0 | level_color=MagickFalse, |
610 | 0 | level_red=MagickFalse, |
611 | 0 | level_green=MagickFalse, |
612 | 0 | level_blue=MagickFalse, |
613 | 0 | unity_gamma; |
614 | |
|
615 | 0 | MagickPassFail |
616 | 0 | status=MagickPass; |
617 | |
|
618 | 0 | assert(image != (Image *) NULL); |
619 | 0 | assert(image->signature == MagickSignature); |
620 | 0 | if (level == (char *) NULL) |
621 | 0 | return(MagickFail); |
622 | 0 | count=sscanf(level,"%lf%*[,/]%lf%*[,/]%lf",&gamma_red,&gamma_green, |
623 | 0 | &gamma_blue); |
624 | 0 | if (count == 1) |
625 | 0 | { |
626 | 0 | gamma_green=gamma_red; |
627 | 0 | gamma_blue=gamma_red; |
628 | 0 | } |
629 | |
|
630 | 0 | unity_gamma=((gamma_red == gamma_green) && (gamma_green == gamma_blue)); |
631 | |
|
632 | 0 | if ((unity_gamma) && (gamma_red != 1.0)) |
633 | 0 | { |
634 | 0 | #if MaxMap == MaxRGB |
635 | 0 | gamma_color=gamma_red; |
636 | 0 | #endif /* if MaxMap == MaxRGB */ |
637 | 0 | level_color = MagickTrue; |
638 | 0 | } |
639 | 0 | else |
640 | 0 | { |
641 | 0 | level_red = ((gamma_red != 0.0) && (gamma_red != 1.0)); |
642 | 0 | level_green = ((gamma_green != 0.0) && (gamma_green != 1.0)); |
643 | 0 | level_blue = ((gamma_blue != 0.0) && (gamma_blue != 1.0)); |
644 | 0 | } |
645 | |
|
646 | 0 | is_grayscale=((image->is_grayscale) && (unity_gamma)); |
647 | |
|
648 | 0 | if (!level_color && !level_red && !level_green && !level_blue) |
649 | 0 | return(MagickPass); |
650 | | |
651 | 0 | #if MaxMap == MaxRGB |
652 | 0 | { |
653 | 0 | ApplyLevelsDiscrete_t |
654 | 0 | levels; |
655 | |
|
656 | 0 | register long |
657 | 0 | i; |
658 | | |
659 | | /* |
660 | | Allocate and initialize gamma maps. |
661 | | */ |
662 | 0 | (void) memset(&levels,0,sizeof(levels)); |
663 | 0 | if (level_color) |
664 | 0 | levels.color=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum)); |
665 | 0 | if (level_red) |
666 | 0 | levels.red=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum)); |
667 | 0 | if (level_green) |
668 | 0 | levels.green=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum)); |
669 | 0 | if (level_blue) |
670 | 0 | levels.blue=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum)); |
671 | 0 | if ((level_color && !levels.color) || |
672 | 0 | (level_red && !levels.red) || |
673 | 0 | (level_green && !levels.green) || |
674 | 0 | (level_blue && !levels.blue)) |
675 | 0 | { |
676 | 0 | MagickFreeMemory(levels.color); |
677 | 0 | MagickFreeMemory(levels.red); |
678 | 0 | MagickFreeMemory(levels.green); |
679 | 0 | MagickFreeMemory(levels.blue); |
680 | 0 | ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed, |
681 | 0 | UnableToGammaCorrectImage); |
682 | 0 | } |
683 | | #if (MaxMap > 256) && defined(HAVE_OPENMP) |
684 | | # pragma omp parallel for |
685 | | #endif |
686 | 0 | for (i=0; i <= (long) MaxMap; i++) |
687 | 0 | { |
688 | 0 | if (levels.color) |
689 | 0 | levels.color[i]= |
690 | 0 | ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_color)); |
691 | 0 | if (levels.red) |
692 | 0 | levels.red[i]= |
693 | 0 | ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_red)); |
694 | 0 | if (levels.green) |
695 | 0 | levels.green[i]= |
696 | 0 | ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_green)); |
697 | 0 | if (levels.blue) |
698 | 0 | levels.blue[i]= |
699 | 0 | ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_blue)); |
700 | 0 | } |
701 | | /* |
702 | | Apply gamma. |
703 | | */ |
704 | 0 | if (image->storage_class == PseudoClass) |
705 | 0 | { |
706 | 0 | (void) ApplyLevelsDiscrete(NULL,&levels,image,image->colormap, |
707 | 0 | (IndexPacket *) NULL,image->colors, |
708 | 0 | &image->exception); |
709 | 0 | status=SyncImage(image); |
710 | 0 | } |
711 | 0 | else |
712 | 0 | { |
713 | 0 | status=PixelIterateMonoModify(ApplyLevelsDiscrete, |
714 | 0 | NULL, |
715 | 0 | "[%s] Applying gamma correction...", |
716 | 0 | NULL,&levels, |
717 | 0 | 0,0,image->columns,image->rows, |
718 | 0 | image, |
719 | 0 | &image->exception); |
720 | 0 | } |
721 | 0 | MagickFreeMemory(levels.color); |
722 | 0 | MagickFreeMemory(levels.red); |
723 | 0 | MagickFreeMemory(levels.green); |
724 | 0 | MagickFreeMemory(levels.blue); |
725 | 0 | } |
726 | | #else /* if MaxMap == MaxRGB */ |
727 | | { |
728 | | GammaCorrectPixelsOptions_t |
729 | | levels; |
730 | | |
731 | | levels.red=gamma_red; |
732 | | levels.green=gamma_green; |
733 | | levels.blue=gamma_blue; |
734 | | levels.opacity=OpaqueOpacity; |
735 | | |
736 | | /* |
737 | | Apply gamma. |
738 | | */ |
739 | | if (image->storage_class == PseudoClass) |
740 | | { |
741 | | (void) GammaCorrectPixels(NULL,&levels,image,image->colormap, |
742 | | (IndexPacket *) NULL,image->colors, |
743 | | &image->exception); |
744 | | status=SyncImage(image); |
745 | | } |
746 | | else |
747 | | { |
748 | | status=PixelIterateMonoModify(GammaCorrectPixels, |
749 | | NULL, |
750 | | "[%s] Applying gamma correction...", |
751 | | NULL,&levels, |
752 | | 0,0,image->columns,image->rows, |
753 | | image, |
754 | | &image->exception); |
755 | | } |
756 | | } |
757 | | |
758 | | #endif/* if MaxMap != MaxRGB */ |
759 | | |
760 | 0 | if (image->gamma != 0.0) |
761 | 0 | image->gamma*=(gamma_red+gamma_green+gamma_blue)/3.0; |
762 | 0 | image->is_grayscale=is_grayscale; |
763 | 0 | return(status); |
764 | 0 | } |
765 | | |
766 | | /* |
767 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
768 | | % % |
769 | | % % |
770 | | % L e v e l I m a g e % |
771 | | % % |
772 | | % % |
773 | | % % |
774 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
775 | | % |
776 | | % LevelImage() adjusts the levels of an image by scaling the colors falling |
777 | | % between specified white and black points to the full available quantum |
778 | | % range. The parameters provided represent the black, mid (gamma), and white |
779 | | % points. The black point specifies the darkest color in the image. Colors |
780 | | % darker than the black point are set to zero. Mid point specifies a gamma |
781 | | % correction to apply to the image. White point specifies the lightest color |
782 | | % in the image. Colors brighter than the white point are set to the maximum |
783 | | % quantum value. |
784 | | % |
785 | | % The format of the LevelImage method is: |
786 | | % |
787 | | % unsigned int LevelImage(Image *image,const char *level) |
788 | | % |
789 | | % A description of each parameter follows: |
790 | | % |
791 | | % o image: The image. |
792 | | % |
793 | | % o levels: Specify the levels as a string of the form "black/mid/white" |
794 | | % (e.g. "10,1.0,65000) where black and white have the range of 0-MaxRGB, |
795 | | % and mid has the range 0-10. |
796 | | % |
797 | | % |
798 | | */ |
799 | | #define LevelImageText "Level... " |
800 | | MagickExport MagickPassFail LevelImage(Image *image,const char *levels) |
801 | 0 | { |
802 | 0 | double |
803 | 0 | black_point, |
804 | 0 | mid_point, |
805 | 0 | white_point; |
806 | |
|
807 | 0 | MagickPassFail |
808 | 0 | status=MagickPass; |
809 | |
|
810 | 0 | assert(image != (Image *) NULL); |
811 | 0 | assert(image->signature == MagickSignature); |
812 | 0 | assert(levels != (char *) NULL); |
813 | |
|
814 | 0 | { |
815 | | /* |
816 | | Parse levels argument. |
817 | | */ |
818 | 0 | char |
819 | 0 | buffer[MaxTextExtent]; |
820 | |
|
821 | 0 | MagickBool |
822 | 0 | percent = MagickFalse; |
823 | |
|
824 | 0 | int |
825 | 0 | count; |
826 | |
|
827 | 0 | register long |
828 | 0 | i; |
829 | |
|
830 | 0 | const char |
831 | 0 | *lp; |
832 | |
|
833 | 0 | char |
834 | 0 | *cp; |
835 | |
|
836 | 0 | black_point=0.0; |
837 | 0 | mid_point=1.0; |
838 | 0 | white_point=MaxRGB; |
839 | |
|
840 | 0 | cp=buffer; |
841 | 0 | lp=levels; |
842 | 0 | for (i=sizeof(buffer)-1 ; (*lp != 0) && (i != 0) ; lp++) |
843 | 0 | { |
844 | 0 | if (*lp == '%') |
845 | 0 | { |
846 | 0 | percent = MagickTrue; |
847 | 0 | } |
848 | 0 | else |
849 | 0 | { |
850 | 0 | *cp++=*lp; |
851 | 0 | i--; |
852 | 0 | } |
853 | 0 | } |
854 | 0 | *cp=0; |
855 | |
|
856 | 0 | count=sscanf(buffer,"%lf%*[,/]%lf%*[,/]%lf",&black_point,&mid_point, |
857 | 0 | &white_point); |
858 | 0 | if (percent) |
859 | 0 | { |
860 | 0 | if (count > 0) |
861 | 0 | black_point*=MaxRGB/100.0; |
862 | 0 | if (count > 2) |
863 | 0 | white_point*=MaxRGB/100.0; |
864 | 0 | } |
865 | 0 | black_point=ConstrainToQuantum(black_point); |
866 | 0 | white_point=ConstrainToQuantum(white_point); |
867 | 0 | if (count == 1) |
868 | 0 | white_point=MaxRGB-black_point; |
869 | 0 | } |
870 | |
|
871 | 0 | status=LevelImageChannel(image,AllChannels,black_point,mid_point,white_point); |
872 | |
|
873 | 0 | return status; |
874 | 0 | } |
875 | | |
876 | | /* |
877 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
878 | | % % |
879 | | % % |
880 | | % L e v e l I m a g e C h a n n e l % |
881 | | % % |
882 | | % % |
883 | | % % |
884 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
885 | | % |
886 | | % LevelImageChannel() adjusts the levels of one or more channels by |
887 | | % scaling the colors falling between specified white and black points to |
888 | | % the full available quantum range. The parameters provided represent the |
889 | | % black, mid (gamma), and white points. The black point specifies the |
890 | | % darkest color in the image. Colors darker than the black point are set to |
891 | | % zero. Mid point specifies a gamma correction to apply to the image. |
892 | | % White point specifies the lightest color in the image. Colors brighter |
893 | | % than the white point are set to the maximum quantum value. |
894 | | % |
895 | | % The format of the LevelImage method is: |
896 | | % |
897 | | % MagickPassFail LevelImageChannel(Image *image, |
898 | | % const ChannelType channel, |
899 | | % const double black_point, |
900 | | % const double mid_point, |
901 | | % const double white_point) |
902 | | % |
903 | | % A description of each parameter follows: |
904 | | % |
905 | | % o image: The image. |
906 | | % |
907 | | % o channel: Identify which channel to level: Red, Cyan, Green, Magenta, |
908 | | % Blue, Yellow, Opacity, or All. |
909 | | % |
910 | | % o black_point, mid_point, white_point: Specify the levels where the black |
911 | | % and white points have the range of 0-MaxRGB, and mid has the range 0-10. |
912 | | % |
913 | | % |
914 | | */ |
915 | | MagickExport MagickPassFail LevelImageChannel(Image *image, |
916 | | const ChannelType channel,const double black_point,const double mid_point, |
917 | | const double white_point) |
918 | 0 | { |
919 | 0 | double |
920 | 0 | black, |
921 | 0 | value, |
922 | 0 | white; |
923 | |
|
924 | 0 | ApplyLevels_t |
925 | 0 | levels; |
926 | |
|
927 | 0 | unsigned int |
928 | 0 | is_grayscale = MagickFalse; |
929 | |
|
930 | 0 | register long |
931 | 0 | i; |
932 | |
|
933 | 0 | MagickPassFail |
934 | 0 | status=MagickPass; |
935 | | |
936 | | /* |
937 | | Allocate and initialize levels map. |
938 | | */ |
939 | 0 | assert(image != (Image *) NULL); |
940 | 0 | assert(image->signature == MagickSignature); |
941 | 0 | levels.map=MagickAllocateArray(PixelPacket *,(MaxMap+1),sizeof(PixelPacket)); |
942 | 0 | if (levels.map == (PixelPacket *) NULL) |
943 | 0 | ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed, |
944 | 0 | UnableToLevelImage); |
945 | | /* |
946 | | Determine which channels to operate on. |
947 | | */ |
948 | 0 | levels.level_red=MagickFalse; |
949 | 0 | levels.level_green=MagickFalse; |
950 | 0 | levels.level_blue=MagickFalse; |
951 | 0 | levels.level_opacity=MagickFalse; |
952 | 0 | switch (channel) |
953 | 0 | { |
954 | 0 | case RedChannel: |
955 | 0 | case CyanChannel: |
956 | 0 | levels.level_red=MagickTrue; |
957 | 0 | break; |
958 | 0 | case GreenChannel: |
959 | 0 | case MagentaChannel: |
960 | 0 | levels.level_green=MagickTrue; |
961 | 0 | break; |
962 | 0 | case BlueChannel: |
963 | 0 | case YellowChannel: |
964 | 0 | levels.level_blue=MagickTrue; |
965 | 0 | break; |
966 | 0 | case OpacityChannel: |
967 | 0 | case BlackChannel: |
968 | 0 | levels.level_opacity=MagickTrue; |
969 | 0 | break; |
970 | 0 | case AllChannels: |
971 | 0 | levels.level_red=MagickTrue; |
972 | 0 | levels.level_green=MagickTrue; |
973 | 0 | levels.level_blue=MagickTrue; |
974 | 0 | is_grayscale=image->is_grayscale; |
975 | 0 | break; |
976 | 0 | default: |
977 | 0 | break; |
978 | 0 | } |
979 | | /* |
980 | | Build leveling map. |
981 | | */ |
982 | 0 | black=ScaleQuantumToMap(black_point); |
983 | 0 | white=ScaleQuantumToMap(white_point); |
984 | 0 | for (i=0; i <= (long) MaxMap; i++) |
985 | 0 | { |
986 | 0 | if (i < black) |
987 | 0 | { |
988 | 0 | levels.map[i].red=levels.map[i].green=levels.map[i].blue=levels.map[i].opacity=0; |
989 | 0 | continue; |
990 | 0 | } |
991 | 0 | if (i > white) |
992 | 0 | { |
993 | 0 | levels.map[i].red=levels.map[i].green=levels.map[i].blue=levels.map[i].opacity=MaxRGB; |
994 | 0 | continue; |
995 | 0 | } |
996 | 0 | value=MaxRGB*(pow(((double) i-black)/(white-black),1.0/mid_point)); |
997 | 0 | levels.map[i].red=levels.map[i].green=levels.map[i].blue=levels.map[i].opacity= |
998 | 0 | RoundDoubleToQuantum(value); |
999 | 0 | } |
1000 | | /* |
1001 | | Apply levels |
1002 | | */ |
1003 | 0 | if (image->storage_class == PseudoClass) |
1004 | 0 | { |
1005 | 0 | (void) ApplyLevels(NULL,&levels,image,image->colormap, |
1006 | 0 | (IndexPacket *) NULL,image->colors, |
1007 | 0 | &image->exception); |
1008 | 0 | status=SyncImage(image); |
1009 | 0 | } |
1010 | 0 | else |
1011 | 0 | { |
1012 | 0 | status=PixelIterateMonoModify(ApplyLevels, |
1013 | 0 | NULL, |
1014 | 0 | "[%s] Leveling channels...", |
1015 | 0 | NULL,&levels, |
1016 | 0 | 0,0,image->columns,image->rows, |
1017 | 0 | image, |
1018 | 0 | &image->exception); |
1019 | 0 | } |
1020 | 0 | MagickFreeMemory(levels.map); |
1021 | |
|
1022 | 0 | image->is_grayscale=is_grayscale; |
1023 | |
|
1024 | 0 | return(status); |
1025 | 0 | } |
1026 | | |
1027 | | /* |
1028 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1029 | | % % |
1030 | | % % |
1031 | | % M o d u l a t e I m a g e % |
1032 | | % % |
1033 | | % % |
1034 | | % % |
1035 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1036 | | % |
1037 | | % ModulateImage() lets you control the brightness, saturation, and hue |
1038 | | % of an image. Modulate represents the brightness, saturation, and hue |
1039 | | % as one parameter (e.g. 90,150,100). |
1040 | | % |
1041 | | % The format of the ModulateImage method is: |
1042 | | % |
1043 | | % unsigned int ModulateImage(Image *image,const char *modulate) |
1044 | | % |
1045 | | % A description of each parameter follows: |
1046 | | % |
1047 | | % o image: The image. |
1048 | | % |
1049 | | % o modulate: Define the percent change in brightness, saturation, and |
1050 | | % hue. |
1051 | | % |
1052 | | % |
1053 | | */ |
1054 | | typedef struct _ModulateImageParameters_t |
1055 | | { |
1056 | | double |
1057 | | percent_brightness, |
1058 | | percent_hue, |
1059 | | percent_saturation; |
1060 | | } ModulateImageParameters_t; |
1061 | | |
1062 | | static MagickPassFail |
1063 | | ModulateImagePixels(void *mutable_data, /* User provided mutable data */ |
1064 | | const void *immutable_data, /* User provided immutable data */ |
1065 | | Image * restrict image, /* Modify image */ |
1066 | | PixelPacket * restrict pixels, /* Pixel row */ |
1067 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
1068 | | const long npixels, /* Number of pixels in row */ |
1069 | | ExceptionInfo *exception) /* Exception report */ |
1070 | 0 | { |
1071 | | /* |
1072 | | Modulate image pixels according to options. |
1073 | | */ |
1074 | 0 | const ModulateImageParameters_t |
1075 | 0 | param = *(const ModulateImageParameters_t *) immutable_data; |
1076 | |
|
1077 | 0 | register long |
1078 | 0 | i; |
1079 | |
|
1080 | 0 | ARG_NOT_USED(mutable_data); |
1081 | 0 | ARG_NOT_USED(image); |
1082 | 0 | ARG_NOT_USED(indexes); |
1083 | 0 | ARG_NOT_USED(exception); |
1084 | |
|
1085 | 0 | for (i=0; i < npixels; i++) |
1086 | 0 | { |
1087 | 0 | double |
1088 | 0 | brightness, |
1089 | 0 | hue, |
1090 | 0 | saturation; |
1091 | |
|
1092 | 0 | TransformHSL(pixels[i].red,pixels[i].green,pixels[i].blue, |
1093 | 0 | &hue,&saturation,&brightness); |
1094 | 0 | brightness*=(0.01+MagickEpsilon)*param.percent_brightness; |
1095 | 0 | if (brightness > 1.0) |
1096 | 0 | brightness=1.0; |
1097 | 0 | saturation*=(0.01+MagickEpsilon)*param.percent_saturation; |
1098 | 0 | if (saturation > 1.0) |
1099 | 0 | saturation=1.0; |
1100 | |
|
1101 | 0 | hue += (param.percent_hue/200.0 - 0.5); |
1102 | 0 | while (hue < 0.0) |
1103 | 0 | hue += 1.0; |
1104 | 0 | while (hue > 1.0) |
1105 | 0 | hue -= 1.0; |
1106 | 0 | HSLTransform(hue,saturation,brightness, |
1107 | 0 | &pixels[i].red,&pixels[i].green,&pixels[i].blue); |
1108 | 0 | } |
1109 | |
|
1110 | 0 | return MagickPass; |
1111 | 0 | } |
1112 | | |
1113 | | MagickExport MagickPassFail ModulateImage(Image *image,const char *modulate) |
1114 | 0 | { |
1115 | 0 | char |
1116 | 0 | progress_message[MaxTextExtent]; |
1117 | |
|
1118 | 0 | ModulateImageParameters_t |
1119 | 0 | param; |
1120 | |
|
1121 | 0 | unsigned int |
1122 | 0 | is_grayscale; |
1123 | |
|
1124 | 0 | MagickPassFail |
1125 | 0 | status=MagickPass; |
1126 | |
|
1127 | 0 | assert(image != (Image *) NULL); |
1128 | 0 | assert(image->signature == MagickSignature); |
1129 | 0 | if (modulate == (char *) NULL) |
1130 | 0 | return(MagickFail); |
1131 | 0 | is_grayscale=image->is_grayscale; |
1132 | 0 | param.percent_brightness=100.0; |
1133 | 0 | param.percent_saturation=100.0; |
1134 | 0 | param.percent_hue=100.0; |
1135 | 0 | (void) sscanf(modulate,"%lf%*[,/]%lf%*[,/]%lf",¶m.percent_brightness, |
1136 | 0 | ¶m.percent_saturation,¶m.percent_hue); |
1137 | | /* |
1138 | | Ensure that adjustment values are positive so they don't need to |
1139 | | be checked in Modulate. |
1140 | | */ |
1141 | 0 | param.percent_brightness=AbsoluteValue(param.percent_brightness); |
1142 | 0 | param.percent_saturation=AbsoluteValue(param.percent_saturation); |
1143 | 0 | param.percent_hue=AbsoluteValue(param.percent_hue); |
1144 | |
|
1145 | 0 | FormatString(progress_message,"[%%s] Modulate %g/%g/%g...", |
1146 | 0 | param.percent_brightness,param.percent_saturation, |
1147 | 0 | param.percent_hue); |
1148 | 0 | TransformColorspace(image,RGBColorspace); |
1149 | 0 | if (image->storage_class == PseudoClass) |
1150 | 0 | { |
1151 | 0 | (void) ModulateImagePixels(NULL,¶m,image,image->colormap, |
1152 | 0 | (IndexPacket *) NULL,image->colors, |
1153 | 0 | &image->exception); |
1154 | 0 | status=MagickMonitorFormatted(image->colors,(size_t) image->colors+1,&image->exception, |
1155 | 0 | progress_message,image->filename); |
1156 | 0 | status&=SyncImage(image); |
1157 | 0 | } |
1158 | 0 | else |
1159 | 0 | { |
1160 | 0 | status=PixelIterateMonoModify(ModulateImagePixels,NULL,progress_message, |
1161 | 0 | NULL,¶m,0,0,image->columns,image->rows, |
1162 | 0 | image,&image->exception); |
1163 | 0 | } |
1164 | |
|
1165 | 0 | image->is_grayscale=is_grayscale; |
1166 | 0 | return(status); |
1167 | 0 | } |
1168 | | |
1169 | | /* |
1170 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1171 | | % % |
1172 | | % % |
1173 | | % N e g a t e I m a g e % |
1174 | | % % |
1175 | | % % |
1176 | | % % |
1177 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1178 | | % |
1179 | | % Method NegateImage negates the colors in the reference image. The |
1180 | | % Grayscale option means that only grayscale values within the image are |
1181 | | % negated. |
1182 | | % |
1183 | | % The format of the NegateImage method is: |
1184 | | % |
1185 | | % unsigned int NegateImage(Image *image,const unsigned int grayscale) |
1186 | | % |
1187 | | % A description of each parameter follows: |
1188 | | % |
1189 | | % o image: The image. |
1190 | | % |
1191 | | % |
1192 | | */ |
1193 | | static MagickPassFail |
1194 | | NegateImagePixels(void *mutable_data, /* User provided mutable data */ |
1195 | | const void *immutable_data, /* User provided immutable data */ |
1196 | | Image * restrict image, /* Modify image */ |
1197 | | PixelPacket * restrict pixels, /* Pixel row */ |
1198 | | IndexPacket * restrict indexes, /* Pixel row indexes */ |
1199 | | const long npixels, /* Number of pixels in row */ |
1200 | | ExceptionInfo *exception) /* Exception report */ |
1201 | 1.22M | { |
1202 | | /* |
1203 | | Negate the pixels. |
1204 | | */ |
1205 | 1.22M | const unsigned int |
1206 | 1.22M | grayscale = *((const unsigned int *) immutable_data); |
1207 | | |
1208 | 1.22M | register long |
1209 | 1.22M | i; |
1210 | | |
1211 | 1.22M | ARG_NOT_USED(mutable_data); |
1212 | 1.22M | ARG_NOT_USED(image); |
1213 | 1.22M | ARG_NOT_USED(indexes); |
1214 | 1.22M | ARG_NOT_USED(exception); |
1215 | | |
1216 | 1.22M | if (grayscale) |
1217 | 0 | { |
1218 | | /* Process only the non-gray pixels */ |
1219 | 0 | for (i=0; i < npixels; i++) |
1220 | 0 | { |
1221 | 0 | if (!IsGray(pixels[i])) |
1222 | 0 | continue; |
1223 | 0 | pixels[i].red=(~pixels[i].red); |
1224 | 0 | pixels[i].green=(~pixels[i].green); |
1225 | 0 | pixels[i].blue=(~pixels[i].blue); |
1226 | 0 | if (image->colorspace == CMYKColorspace) |
1227 | 0 | pixels[i].opacity=(~pixels[i].opacity); |
1228 | 0 | } |
1229 | 0 | } |
1230 | 1.22M | else |
1231 | 1.22M | { |
1232 | | /* Process all pixels */ |
1233 | 619M | for (i=0; i < npixels; i++) |
1234 | 617M | { |
1235 | 617M | pixels[i].red=(~pixels[i].red); |
1236 | 617M | pixels[i].green=(~pixels[i].green); |
1237 | 617M | pixels[i].blue=(~pixels[i].blue); |
1238 | 617M | if (image->colorspace == CMYKColorspace) |
1239 | 0 | pixels[i].opacity=(~pixels[i].opacity); |
1240 | 617M | } |
1241 | 1.22M | } |
1242 | | |
1243 | 1.22M | return MagickPass; |
1244 | 1.22M | } |
1245 | | |
1246 | 6.93k | #define NegateImageText "[%s] Negate..." |
1247 | | MagickExport MagickPassFail NegateImage(Image *image,const unsigned int grayscale) |
1248 | 9.94k | { |
1249 | 9.94k | unsigned int |
1250 | 9.94k | non_gray = grayscale; |
1251 | | |
1252 | 9.94k | unsigned int |
1253 | 9.94k | is_grayscale; |
1254 | | |
1255 | 9.94k | MagickPassFail |
1256 | 9.94k | status=MagickPass; |
1257 | | |
1258 | 9.94k | assert(image != (Image *) NULL); |
1259 | 9.94k | assert(image->signature == MagickSignature); |
1260 | 9.94k | is_grayscale=image->is_grayscale; |
1261 | 9.94k | if (*ImageGetClipMaskInlined(image)) |
1262 | 398 | image->storage_class=DirectClass; |
1263 | | |
1264 | 9.94k | if (image->storage_class == PseudoClass) |
1265 | 3.01k | { |
1266 | 3.01k | (void) NegateImagePixels(NULL,&non_gray,image,image->colormap, |
1267 | 3.01k | (IndexPacket *) NULL,image->colors, |
1268 | 3.01k | &image->exception); |
1269 | 3.01k | status=SyncImage(image); |
1270 | 3.01k | } |
1271 | 6.93k | else |
1272 | 6.93k | { |
1273 | 6.93k | status=PixelIterateMonoModify(NegateImagePixels,NULL,NegateImageText, |
1274 | 6.93k | NULL,&non_gray,0,0,image->columns,image->rows, |
1275 | 6.93k | image,&image->exception); |
1276 | 6.93k | } |
1277 | | |
1278 | 9.94k | image->is_grayscale=is_grayscale; |
1279 | 9.94k | return(status); |
1280 | 9.94k | } |
1281 | | |
1282 | | /* |
1283 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1284 | | % % |
1285 | | % % |
1286 | | % N o r m a l i z e I m a g e % |
1287 | | % % |
1288 | | % % |
1289 | | % % |
1290 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1291 | | % |
1292 | | % The NormalizeImage() method enhances the contrast of a color image by |
1293 | | % adjusting the pixels color to span the entire range of colors available. |
1294 | | % |
1295 | | % The format of the NormalizeImage method is: |
1296 | | % |
1297 | | % unsigned int NormalizeImage(Image *image) |
1298 | | % |
1299 | | % A description of each parameter follows: |
1300 | | % |
1301 | | % o image: The image. |
1302 | | % |
1303 | | % |
1304 | | */ |
1305 | | #define MaxRange(color) ScaleQuantumToMap(color) |
1306 | | /* |
1307 | | Find histogram bounds based on a minimum threshold value. |
1308 | | */ |
1309 | | #define FindHistogramBoundsAlg(Q,threshold,low,high,histogram) \ |
1310 | 0 | { \ |
1311 | 0 | double \ |
1312 | 0 | intensity; \ |
1313 | 0 | \ |
1314 | 0 | intensity=0.0; \ |
1315 | 0 | for (low.Q=0.0; low.Q < MaxMapDouble; low.Q += 1.0) \ |
1316 | 0 | { \ |
1317 | 0 | intensity+=histogram[(long) low.Q].Q; \ |
1318 | 0 | if (intensity > threshold) \ |
1319 | 0 | break; \ |
1320 | 0 | } \ |
1321 | 0 | intensity=0.0; \ |
1322 | 0 | for (high.Q=MaxMapDouble; high.Q >= 1.0; high.Q -= 1.0) \ |
1323 | 0 | { \ |
1324 | 0 | intensity+=histogram[(long) high.Q].Q; \ |
1325 | 0 | if (intensity > threshold) \ |
1326 | 0 | break; \ |
1327 | 0 | } \ |
1328 | 0 | } |
1329 | | /* |
1330 | | Find histogram bounds, but with additional fallback in case |
1331 | | contrast is not reasonable. |
1332 | | */ |
1333 | | #define FindHistogramBounds(Q,threshold,low,high,histogram) \ |
1334 | 0 | { \ |
1335 | 0 | FindHistogramBoundsAlg(Q,threshold,low,high,histogram); \ |
1336 | 0 | if (low.red == high.red) \ |
1337 | 0 | FindHistogramBoundsAlg(Q,0.0,low,high,histogram); \ |
1338 | 0 | } |
1339 | | /* |
1340 | | Compute levels map entry for a quantum. |
1341 | | */ |
1342 | 0 | #define ComputeHistogramMapQuantum(Q,levels,low,high) \ |
1343 | 0 | { \ |
1344 | 0 | if (i < (long) low.Q) \ |
1345 | 0 | levels.map[i].Q=0; \ |
1346 | 0 | else \ |
1347 | 0 | if (i > (long) high.Q) \ |
1348 | 0 | levels.map[i].Q=MaxRGB; \ |
1349 | 0 | else \ |
1350 | 0 | if (low.Q != high.Q) \ |
1351 | 0 | levels.map[i].Q= \ |
1352 | 0 | ScaleMapToQuantum((MaxMapDouble*(i-low.Q))/(high.Q-low.Q)); \ |
1353 | 0 | } |
1354 | | MagickExport MagickPassFail NormalizeImage(Image *image) |
1355 | 0 | { |
1356 | 0 | DoublePixelPacket |
1357 | 0 | high, |
1358 | 0 | *histogram, |
1359 | 0 | low; |
1360 | |
|
1361 | 0 | ApplyLevels_t |
1362 | 0 | levels; |
1363 | |
|
1364 | 0 | register long |
1365 | 0 | i; |
1366 | |
|
1367 | 0 | unsigned int |
1368 | 0 | is_grayscale; |
1369 | |
|
1370 | 0 | double |
1371 | 0 | threshold, |
1372 | 0 | threshold_percent; |
1373 | |
|
1374 | 0 | MagickPassFail |
1375 | 0 | status=MagickPass; |
1376 | | |
1377 | | /* |
1378 | | Allocate histogram and normalize map. |
1379 | | */ |
1380 | 0 | assert(image != (Image *) NULL); |
1381 | 0 | assert(image->signature == MagickSignature); |
1382 | 0 | is_grayscale=image->is_grayscale; |
1383 | 0 | levels.map=MagickAllocateMemory(PixelPacket *,(MaxMap+1)*sizeof(PixelPacket)); |
1384 | 0 | if (levels.map == (PixelPacket *) NULL) |
1385 | 0 | ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed, |
1386 | 0 | UnableToNormalizeImage); |
1387 | | /* |
1388 | | Form histogram. |
1389 | | */ |
1390 | 0 | histogram=BuildChannelHistograms(image,&image->exception); |
1391 | 0 | if (histogram == (DoublePixelPacket *) NULL) |
1392 | 0 | { |
1393 | 0 | MagickFreeMemory(levels.map); |
1394 | 0 | return MagickFail; |
1395 | 0 | } |
1396 | | /* |
1397 | | Find the histogram boundaries by locating the 0.1 percent levels. |
1398 | | */ |
1399 | 0 | threshold_percent=0.1; |
1400 | 0 | MagickAttributeToDouble(image,"histogram-threshold",threshold_percent); |
1401 | 0 | threshold=(long) ((double) image->columns*image->rows*0.01*threshold_percent); |
1402 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
1403 | 0 | "Histogram Threshold = %g%% (%g)", threshold_percent, threshold); |
1404 | 0 | FindHistogramBounds(red,threshold,low,high,histogram); |
1405 | 0 | FindHistogramBounds(green,threshold,low,high,histogram); |
1406 | 0 | FindHistogramBounds(blue,threshold,low,high,histogram); |
1407 | 0 | high.opacity=0.0; |
1408 | 0 | low.opacity=0.0; |
1409 | 0 | if (image->matte) |
1410 | 0 | FindHistogramBounds(opacity,threshold,low,high,histogram); |
1411 | |
|
1412 | 0 | MagickFreeMemory(histogram); |
1413 | | |
1414 | | /* |
1415 | | Stretch the histogram to create the normalized image mapping. |
1416 | | */ |
1417 | 0 | (void) memset(levels.map,0,(MaxMap+1)*sizeof(PixelPacket)); |
1418 | |
|
1419 | 0 | for (i=0; i <= (long) MaxMap; i++) |
1420 | 0 | { |
1421 | 0 | ComputeHistogramMapQuantum(red,levels,low,high); |
1422 | 0 | ComputeHistogramMapQuantum(green,levels,low,high); |
1423 | 0 | ComputeHistogramMapQuantum(blue,levels,low,high); |
1424 | 0 | levels.map[i].opacity=OpaqueOpacity; |
1425 | 0 | if (image->matte) |
1426 | 0 | ComputeHistogramMapQuantum(opacity,levels,low,high); |
1427 | 0 | } |
1428 | | |
1429 | | /* |
1430 | | Normalize the image. |
1431 | | */ |
1432 | 0 | levels.level_red = (low.red != high.red); |
1433 | 0 | levels.level_green = (low.green != high.green); |
1434 | 0 | levels.level_blue = (low.blue != high.blue); |
1435 | 0 | levels.level_opacity= (image->matte && (low.opacity != high.opacity)); |
1436 | 0 | if (image->storage_class == PseudoClass) |
1437 | 0 | { |
1438 | 0 | (void) ApplyLevels(NULL,&levels,image,image->colormap, |
1439 | 0 | (IndexPacket *) NULL,image->colors, |
1440 | 0 | &image->exception); |
1441 | 0 | status=SyncImage(image); |
1442 | 0 | } |
1443 | 0 | else |
1444 | 0 | { |
1445 | 0 | status=PixelIterateMonoModify(ApplyLevels, |
1446 | 0 | NULL, |
1447 | 0 | "[%s] Applying histogram normalization...", |
1448 | 0 | NULL,&levels, |
1449 | 0 | 0,0,image->columns,image->rows, |
1450 | 0 | image, |
1451 | 0 | &image->exception); |
1452 | 0 | } |
1453 | 0 | MagickFreeMemory(levels.map); |
1454 | 0 | image->is_grayscale=is_grayscale; |
1455 | 0 | return(status); |
1456 | 0 | } |