blob: 211e6a857de7f9c590f3aff462f6efa478d5af9d [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% TTTTT H H RRRR EEEEE SSSSS H H OOO L DDDD %
7% T H H R R E SS H H O O L D D %
8% T HHHHH RRRR EEE SSS HHHHH O O L D D %
9% T H H R R E SS H H O O L D D %
10% T H H R R EEEEE SSSSS H H OOO LLLLL DDDD %
11% %
12% %
13% MagickCore Image Threshold Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
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/property.h"
45#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
cristye7e40552010-04-24 21:34:22 +000049#include "magick/colormap.h"
cristy3ed852e2009-09-05 21:47:34 +000050#include "magick/colorspace.h"
51#include "magick/configure.h"
52#include "magick/constitute.h"
53#include "magick/decorate.h"
54#include "magick/draw.h"
55#include "magick/enhance.h"
56#include "magick/exception.h"
57#include "magick/exception-private.h"
58#include "magick/effect.h"
59#include "magick/fx.h"
60#include "magick/gem.h"
61#include "magick/geometry.h"
62#include "magick/image-private.h"
63#include "magick/list.h"
64#include "magick/log.h"
65#include "magick/memory_.h"
66#include "magick/monitor.h"
67#include "magick/monitor-private.h"
68#include "magick/montage.h"
cristyf2f27272009-12-17 14:48:46 +000069#include "magick/option.h"
cristy3ed852e2009-09-05 21:47:34 +000070#include "magick/pixel-private.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resize.h"
76#include "magick/resource_.h"
77#include "magick/segment.h"
78#include "magick/shear.h"
79#include "magick/signature-private.h"
80#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000081#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000082#include "magick/transform.h"
83#include "magick/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000084#include "magick/xml-tree.h"
85
86/*
87 Define declarations.
88*/
89#define ThresholdsFilename "thresholds.xml"
90
91/*
92 Typedef declarations.
93*/
94struct _ThresholdMap
95{
96 char
97 *map_id,
98 *description;
99
100 unsigned long
101 width,
102 height;
103
104 long
105 divisor,
106 *levels;
107};
108
109/*
110%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
111% %
112% %
113% %
114% A d a p t i v e T h r e s h o l d I m a g e %
115% %
116% %
117% %
118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
119%
120% AdaptiveThresholdImage() selects an individual threshold for each pixel
121% based on the range of intensity values in its local neighborhood. This
122% allows for thresholding of an image whose global intensity histogram
123% doesn't contain distinctive peaks.
124%
125% The format of the AdaptiveThresholdImage method is:
126%
127% Image *AdaptiveThresholdImage(const Image *image,
128% const unsigned long width,const unsigned long height,
129% const long offset,ExceptionInfo *exception)
130%
131% A description of each parameter follows:
132%
133% o image: the image.
134%
135% o width: the width of the local neighborhood.
136%
137% o height: the height of the local neighborhood.
138%
139% o offset: the mean offset.
140%
141% o exception: return any errors or warnings in this structure.
142%
143*/
144MagickExport Image *AdaptiveThresholdImage(const Image *image,
145 const unsigned long width,const unsigned long height,const long offset,
146 ExceptionInfo *exception)
147{
148#define ThresholdImageTag "Threshold/Image"
149
cristyc4c8d132010-01-07 01:58:38 +0000150 CacheView
151 *image_view,
152 *threshold_view;
153
cristy3ed852e2009-09-05 21:47:34 +0000154 Image
155 *threshold_image;
156
157 long
158 progress,
159 y;
160
161 MagickBooleanType
162 status;
163
164 MagickPixelPacket
165 zero;
166
167 MagickRealType
168 number_pixels;
169
cristy3ed852e2009-09-05 21:47:34 +0000170 assert(image != (const Image *) NULL);
171 assert(image->signature == MagickSignature);
172 if (image->debug != MagickFalse)
173 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
174 assert(exception != (ExceptionInfo *) NULL);
175 assert(exception->signature == MagickSignature);
176 if ((image->columns < width) || (image->rows < height))
177 ThrowImageException(OptionError,"ImageSmallerThanRadius");
178 threshold_image=CloneImage(image,0,0,MagickTrue,exception);
179 if (threshold_image == (Image *) NULL)
180 return((Image *) NULL);
181 if (SetImageStorageClass(threshold_image,DirectClass) == MagickFalse)
182 {
183 InheritException(exception,&threshold_image->exception);
184 threshold_image=DestroyImage(threshold_image);
185 return((Image *) NULL);
186 }
187 /*
188 Local adaptive threshold.
189 */
190 status=MagickTrue;
191 progress=0;
192 GetMagickPixelPacket(image,&zero);
193 number_pixels=(MagickRealType) width*height;
194 image_view=AcquireCacheView(image);
195 threshold_view=AcquireCacheView(threshold_image);
cristyb5d5f722009-11-04 03:03:49 +0000196#if defined(MAGICKCORE_OPENMP_SUPPORT)
197 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000198#endif
199 for (y=0; y < (long) image->rows; y++)
200 {
201 MagickBooleanType
202 sync;
203
204 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000205 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000206
207 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000208 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000209
210 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000211 *restrict threshold_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000212
213 register long
214 x;
215
216 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000217 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000218
219 if (status == MagickFalse)
220 continue;
221 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-height/2L,
222 image->columns+width,height,exception);
223 q=GetCacheViewAuthenticPixels(threshold_view,0,y,threshold_image->columns,1,
224 exception);
225 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
226 {
227 status=MagickFalse;
228 continue;
229 }
230 indexes=GetCacheViewVirtualIndexQueue(image_view);
231 threshold_indexes=GetCacheViewAuthenticIndexQueue(threshold_view);
232 for (x=0; x < (long) image->columns; x++)
233 {
234 long
235 v;
236
237 MagickPixelPacket
238 mean,
239 pixel;
240
241 register const PixelPacket
242 *r;
243
244 register long
245 u;
246
247 pixel=zero;
248 mean=zero;
249 r=p;
250 for (v=0; v < (long) height; v++)
251 {
252 for (u=0; u < (long) width; u++)
253 {
254 pixel.red+=r[u].red;
255 pixel.green+=r[u].green;
256 pixel.blue+=r[u].blue;
257 pixel.opacity+=r[u].opacity;
258 if (image->colorspace == CMYKColorspace)
259 pixel.index=(MagickRealType) indexes[x+(r-p)+u];
260 }
261 r+=image->columns+width;
262 }
263 mean.red=(MagickRealType) (pixel.red/number_pixels+offset);
264 mean.green=(MagickRealType) (pixel.green/number_pixels+offset);
265 mean.blue=(MagickRealType) (pixel.blue/number_pixels+offset);
266 mean.opacity=(MagickRealType) (pixel.opacity/number_pixels+offset);
267 if (image->colorspace == CMYKColorspace)
268 mean.index=(MagickRealType) (pixel.index/number_pixels+offset);
269 q->red=(Quantum) (((MagickRealType) q->red <= mean.red) ?
270 0 : QuantumRange);
271 q->green=(Quantum) (((MagickRealType) q->green <= mean.green) ?
272 0 : QuantumRange);
273 q->blue=(Quantum) (((MagickRealType) q->blue <= mean.blue) ?
274 0 : QuantumRange);
275 q->opacity=(Quantum) (((MagickRealType) q->opacity <= mean.opacity) ?
276 0 : QuantumRange);
277 if (image->colorspace == CMYKColorspace)
278 threshold_indexes[x]=(IndexPacket) (((MagickRealType)
279 threshold_indexes[x] <= mean.index) ? 0 : QuantumRange);
280 p++;
281 q++;
282 }
283 sync=SyncCacheViewAuthenticPixels(threshold_view,exception);
284 if (sync == MagickFalse)
285 status=MagickFalse;
286 if (image->progress_monitor != (MagickProgressMonitor) NULL)
287 {
288 MagickBooleanType
289 proceed;
290
cristyb5d5f722009-11-04 03:03:49 +0000291#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000292 #pragma omp critical (MagickCore_AdaptiveThresholdImage)
293#endif
294 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
295 image->rows);
296 if (proceed == MagickFalse)
297 status=MagickFalse;
298 }
299 }
300 threshold_view=DestroyCacheView(threshold_view);
301 image_view=DestroyCacheView(image_view);
302 if (status == MagickFalse)
303 threshold_image=DestroyImage(threshold_image);
304 return(threshold_image);
305}
306
307/*
308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
309% %
310% %
311% %
312% B i l e v e l I m a g e %
313% %
314% %
315% %
316%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
317%
318% BilevelImage() changes the value of individual pixels based on the
319% intensity of each pixel channel. The result is a high-contrast image.
320%
321% More precisely each channel value of the image is 'thresholded' so that if
322% it is equal to or less than the given value it is set to zero, while any
323% value greater than that give is set to it maximum or QuantumRange.
324%
325% This function is what is used to implement the "-threshold" operator for
326% the command line API.
327%
328% If the default channel setting is given the image is thresholded using just
329% the gray 'intensity' of the image, rather than the individual channels.
330%
331% The format of the BilevelImageChannel method is:
332%
333% MagickBooleanType BilevelImage(Image *image,const double threshold)
334% MagickBooleanType BilevelImageChannel(Image *image,
335% const ChannelType channel,const double threshold)
336%
337% A description of each parameter follows:
338%
339% o image: the image.
340%
341% o channel: the channel type.
342%
343% o threshold: define the threshold values.
344%
345% Aside: You can get the same results as operator using LevelImageChannels()
346% with the 'threshold' value for both the black_point and the white_point.
347%
348*/
349
350MagickExport MagickBooleanType BilevelImage(Image *image,const double threshold)
351{
352 MagickBooleanType
353 status;
354
355 status=BilevelImageChannel(image,DefaultChannels,threshold);
356 return(status);
357}
358
359MagickExport MagickBooleanType BilevelImageChannel(Image *image,
360 const ChannelType channel,const double threshold)
361{
362#define ThresholdImageTag "Threshold/Image"
363
cristyc4c8d132010-01-07 01:58:38 +0000364 CacheView
365 *image_view;
366
cristy3ed852e2009-09-05 21:47:34 +0000367 ExceptionInfo
368 *exception;
369
370 long
371 progress,
372 y;
373
374 MagickBooleanType
375 status;
376
cristy3ed852e2009-09-05 21:47:34 +0000377 assert(image != (Image *) NULL);
378 assert(image->signature == MagickSignature);
379 if (image->debug != MagickFalse)
380 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
381 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
382 return(MagickFalse);
383 /*
384 Bilevel threshold image.
385 */
386 status=MagickTrue;
387 progress=0;
388 exception=(&image->exception);
389 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000390#if defined(MAGICKCORE_OPENMP_SUPPORT)
391 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000392#endif
393 for (y=0; y < (long) image->rows; y++)
394 {
395 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000396 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000397
398 register long
399 x;
400
401 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000402 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000403
404 if (status == MagickFalse)
405 continue;
406 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
407 if (q == (PixelPacket *) NULL)
408 {
409 status=MagickFalse;
410 continue;
411 }
412 indexes=GetCacheViewAuthenticIndexQueue(image_view);
413 if (channel == DefaultChannels)
414 {
415 for (x=0; x < (long) image->columns; x++)
416 {
417 q->red=(Quantum) ((MagickRealType) PixelIntensityToQuantum(q) <=
418 threshold ? 0 : QuantumRange);
419 q->green=q->red;
420 q->blue=q->red;
421 q++;
422 }
423 }
424 else
425 for (x=0; x < (long) image->columns; x++)
426 {
427 if ((channel & RedChannel) != 0)
428 q->red=(Quantum) ((MagickRealType) q->red <= threshold ? 0 :
429 QuantumRange);
430 if ((channel & GreenChannel) != 0)
431 q->green=(Quantum) ((MagickRealType) q->green <= threshold ? 0 :
432 QuantumRange);
433 if ((channel & BlueChannel) != 0)
434 q->blue=(Quantum) ((MagickRealType) q->blue <= threshold ? 0 :
435 QuantumRange);
436 if ((channel & OpacityChannel) != 0)
437 {
438 if (image->matte == MagickFalse)
439 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold ?
440 0 : QuantumRange);
441 else
442 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold ?
443 OpaqueOpacity : TransparentOpacity);
444 }
445 if (((channel & IndexChannel) != 0) &&
446 (image->colorspace == CMYKColorspace))
447 indexes[x]=(IndexPacket) ((MagickRealType) indexes[x] <= threshold ?
448 0 : QuantumRange);
449 q++;
450 }
451 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
452 status=MagickFalse;
453 if (image->progress_monitor != (MagickProgressMonitor) NULL)
454 {
455 MagickBooleanType
456 proceed;
457
cristyb5d5f722009-11-04 03:03:49 +0000458#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000459 #pragma omp critical (MagickCore_BilevelImageChannel)
460#endif
461 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
462 image->rows);
463 if (proceed == MagickFalse)
464 status=MagickFalse;
465 }
466 }
467 image_view=DestroyCacheView(image_view);
468 return(status);
469}
470
471/*
472%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
473% %
474% %
475% %
476% B l a c k T h r e s h o l d I m a g e %
477% %
478% %
479% %
480%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
481%
482% BlackThresholdImage() is like ThresholdImage() but forces all pixels below
cristy4e101302009-09-17 12:49:12 +0000483% the threshold into black while leaving all pixels at or above the threshold
cristy3ed852e2009-09-05 21:47:34 +0000484% unchanged.
485%
486% The format of the BlackThresholdImage method is:
487%
488% MagickBooleanType BlackThresholdImage(Image *image,const char *threshold)
489% MagickBooleanType BlackThresholdImageChannel(Image *image,
490% const ChannelType channel,const char *threshold,
491% ExceptionInfo *exception)
492%
493% A description of each parameter follows:
494%
495% o image: the image.
496%
497% o channel: the channel or channels to be thresholded.
498%
499% o threshold: Define the threshold value.
500%
501% o exception: return any errors or warnings in this structure.
502%
503*/
504MagickExport MagickBooleanType BlackThresholdImage(Image *image,
505 const char *threshold)
506{
507 MagickBooleanType
508 status;
509
510 status=BlackThresholdImageChannel(image,DefaultChannels,threshold,
511 &image->exception);
512 return(status);
513}
514
515MagickExport MagickBooleanType BlackThresholdImageChannel(Image *image,
516 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
517{
518#define ThresholdImageTag "Threshold/Image"
519
cristyc4c8d132010-01-07 01:58:38 +0000520 CacheView
521 *image_view;
522
cristy3ed852e2009-09-05 21:47:34 +0000523 GeometryInfo
524 geometry_info;
525
526 long
527 progress,
528 y;
529
530 MagickBooleanType
531 status;
532
533 MagickPixelPacket
534 threshold;
535
536 MagickStatusType
537 flags;
538
cristy3ed852e2009-09-05 21:47:34 +0000539 assert(image != (Image *) NULL);
540 assert(image->signature == MagickSignature);
541 if (image->debug != MagickFalse)
542 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
543 if (thresholds == (const char *) NULL)
544 return(MagickTrue);
545 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
546 return(MagickFalse);
547 GetMagickPixelPacket(image,&threshold);
548 flags=ParseGeometry(thresholds,&geometry_info);
549 threshold.red=geometry_info.rho;
550 threshold.green=geometry_info.sigma;
551 if ((flags & SigmaValue) == 0)
552 threshold.green=threshold.red;
553 threshold.blue=geometry_info.xi;
554 if ((flags & XiValue) == 0)
555 threshold.blue=threshold.red;
556 threshold.opacity=geometry_info.psi;
557 if ((flags & PsiValue) == 0)
558 threshold.opacity=threshold.red;
559 threshold.index=geometry_info.chi;
560 if ((flags & ChiValue) == 0)
561 threshold.index=threshold.red;
562 if ((flags & PercentValue) != 0)
563 {
564 threshold.red*=(QuantumRange/100.0);
565 threshold.green*=(QuantumRange/100.0);
566 threshold.blue*=(QuantumRange/100.0);
567 threshold.opacity*=(QuantumRange/100.0);
568 threshold.index*=(QuantumRange/100.0);
569 }
570 /*
571 Black threshold image.
572 */
573 status=MagickTrue;
574 progress=0;
575 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000576#if defined(MAGICKCORE_OPENMP_SUPPORT)
577 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000578#endif
579 for (y=0; y < (long) image->rows; y++)
580 {
581 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000582 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000583
584 register long
585 x;
586
587 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000588 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000589
590 if (status == MagickFalse)
591 continue;
592 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
593 if (q == (PixelPacket *) NULL)
594 {
595 status=MagickFalse;
596 continue;
597 }
598 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyb0ea1af2009-11-28 20:44:46 +0000599 for (x=0; x < (long) image->columns; x++)
600 {
601 if (channel != DefaultChannels)
602 {
603 if (PixelIntensity(q) < MagickPixelIntensity(&threshold))
604 {
605 q->red=(Quantum) 0;
606 q->green=(Quantum) 0;
607 q->blue=(Quantum) 0;
608 if (image->colorspace == CMYKColorspace)
609 indexes[x]=(Quantum) 0;
610 }
611 }
612 else
613 {
614 if (((channel & RedChannel) != 0) &&
615 ((MagickRealType) q->red < threshold.red))
616 q->red=(Quantum) 0;
617 if (((channel & GreenChannel) != 0) &&
618 ((MagickRealType) q->green < threshold.green))
619 q->green=(Quantum) 0;
620 if (((channel & BlueChannel) != 0) &&
621 ((MagickRealType) q->blue < threshold.blue))
622 q->blue=(Quantum) 0;
623 if (((channel & OpacityChannel) != 0) &&
624 ((MagickRealType) q->opacity < threshold.opacity))
625 q->opacity=(Quantum) 0;
626 if (((channel & IndexChannel) != 0) &&
627 (image->colorspace == CMYKColorspace) &&
628 ((MagickRealType) indexes[x] < threshold.index))
629 indexes[x]=(Quantum) 0;
630 }
631 q++;
632 }
cristy3ed852e2009-09-05 21:47:34 +0000633 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
634 status=MagickFalse;
635 if (image->progress_monitor != (MagickProgressMonitor) NULL)
636 {
637 MagickBooleanType
638 proceed;
639
cristyb5d5f722009-11-04 03:03:49 +0000640#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000641 #pragma omp critical (MagickCore_BlackThresholdImageChannel)
642#endif
643 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
644 image->rows);
645 if (proceed == MagickFalse)
646 status=MagickFalse;
647 }
648 }
649 image_view=DestroyCacheView(image_view);
650 return(status);
651}
652
653/*
654%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
655% %
656% %
657% %
cristy1eb45dd2009-09-25 16:38:06 +0000658% C l a m p I m a g e %
659% %
660% %
661% %
662%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
663%
cristyecb0c6d2009-09-25 16:50:09 +0000664% ClampImage() restricts the color range from 0 to the quantum depth.
cristy1eb45dd2009-09-25 16:38:06 +0000665%
666% The format of the ClampImageChannel method is:
667%
668% MagickBooleanType ClampImage(Image *image)
669% MagickBooleanType ClampImageChannel(Image *image,
670% const ChannelType channel)
671%
672% A description of each parameter follows:
673%
674% o image: the image.
675%
676% o channel: the channel type.
677%
678*/
679
cristy75ffdb72010-01-07 17:40:12 +0000680static inline Quantum ClampToUnsignedQuantum(const Quantum quantum)
cristy1eb45dd2009-09-25 16:38:06 +0000681{
682#if defined(MAGICKCORE_HDRI_SUPPORT)
683 if (quantum <= 0)
684 return(0);
685 if (quantum >= QuantumRange)
686 return(QuantumRange);
687 return(quantum);
688#else
689 return(quantum);
690#endif
691}
692
693MagickExport MagickBooleanType ClampImage(Image *image)
694{
695 MagickBooleanType
696 status;
697
698 status=ClampImageChannel(image,DefaultChannels);
699 return(status);
700}
701
702MagickExport MagickBooleanType ClampImageChannel(Image *image,
703 const ChannelType channel)
704{
705#define ClampImageTag "Clamp/Image"
706
cristyc4c8d132010-01-07 01:58:38 +0000707 CacheView
708 *image_view;
709
cristy1eb45dd2009-09-25 16:38:06 +0000710 ExceptionInfo
711 *exception;
712
713 long
714 progress,
715 y;
716
717 MagickBooleanType
718 status;
719
cristy1eb45dd2009-09-25 16:38:06 +0000720 assert(image != (Image *) NULL);
721 assert(image->signature == MagickSignature);
722 if (image->debug != MagickFalse)
723 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
724 if (image->storage_class == PseudoClass)
725 {
726 register long
727 i;
728
729 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000730 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000731
732 q=image->colormap;
733 for (i=0; i < (long) image->colors; i++)
734 {
cristy75ffdb72010-01-07 17:40:12 +0000735 q->red=ClampToUnsignedQuantum(q->red);
736 q->green=ClampToUnsignedQuantum(q->green);
737 q->blue=ClampToUnsignedQuantum(q->blue);
738 q->opacity=ClampToUnsignedQuantum(q->opacity);
cristy1eb45dd2009-09-25 16:38:06 +0000739 q++;
740 }
741 return(SyncImage(image));
742 }
743 /*
cristy611721d2009-09-25 16:42:17 +0000744 Clamp image.
cristy1eb45dd2009-09-25 16:38:06 +0000745 */
746 status=MagickTrue;
747 progress=0;
748 exception=(&image->exception);
749 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000750#if defined(MAGICKCORE_OPENMP_SUPPORT)
751 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy1eb45dd2009-09-25 16:38:06 +0000752#endif
753 for (y=0; y < (long) image->rows; y++)
754 {
755 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000756 *restrict indexes;
cristy1eb45dd2009-09-25 16:38:06 +0000757
758 register long
759 x;
760
761 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000762 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000763
764 if (status == MagickFalse)
765 continue;
766 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
767 if (q == (PixelPacket *) NULL)
768 {
769 status=MagickFalse;
770 continue;
771 }
772 indexes=GetCacheViewAuthenticIndexQueue(image_view);
773 for (x=0; x < (long) image->columns; x++)
774 {
775 if ((channel & RedChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000776 q->red=ClampToUnsignedQuantum(q->red);
cristy1eb45dd2009-09-25 16:38:06 +0000777 if ((channel & GreenChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000778 q->green=ClampToUnsignedQuantum(q->green);
cristy1eb45dd2009-09-25 16:38:06 +0000779 if ((channel & BlueChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000780 q->blue=ClampToUnsignedQuantum(q->blue);
cristy1eb45dd2009-09-25 16:38:06 +0000781 if ((channel & OpacityChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000782 q->opacity=ClampToUnsignedQuantum(q->opacity);
cristy1eb45dd2009-09-25 16:38:06 +0000783 if (((channel & IndexChannel) != 0) &&
784 (image->colorspace == CMYKColorspace))
cristy75ffdb72010-01-07 17:40:12 +0000785 indexes[x]=(IndexPacket) ClampToUnsignedQuantum(indexes[x]);
cristy1eb45dd2009-09-25 16:38:06 +0000786 q++;
787 }
788 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
789 status=MagickFalse;
790 if (image->progress_monitor != (MagickProgressMonitor) NULL)
791 {
792 MagickBooleanType
793 proceed;
794
cristyb5d5f722009-11-04 03:03:49 +0000795#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1eb45dd2009-09-25 16:38:06 +0000796 #pragma omp critical (MagickCore_ClampImageChannel)
797#endif
798 proceed=SetImageProgress(image,ClampImageTag,progress++,
799 image->rows);
800 if (proceed == MagickFalse)
801 status=MagickFalse;
802 }
803 }
804 image_view=DestroyCacheView(image_view);
805 return(status);
806}
807
808/*
809%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
810% %
811% %
812% %
cristy3ed852e2009-09-05 21:47:34 +0000813% D e s t r o y T h r e s h o l d M a p %
814% %
815% %
816% %
817%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
818%
819% DestroyThresholdMap() de-allocate the given ThresholdMap
820%
821% The format of the ListThresholdMaps method is:
822%
823% ThresholdMap *DestroyThresholdMap(Threshold *map)
824%
825% A description of each parameter follows.
826%
827% o map: Pointer to the Threshold map to destroy
828%
829*/
830MagickExport ThresholdMap *DestroyThresholdMap(ThresholdMap *map)
831{
832 assert(map != (ThresholdMap *) NULL);
833 if (map->map_id != (char *) NULL)
834 map->map_id=DestroyString(map->map_id);
835 if (map->description != (char *) NULL)
836 map->description=DestroyString(map->description);
837 if (map->levels != (long *) NULL)
838 map->levels=(long *) RelinquishMagickMemory(map->levels);
839 map=(ThresholdMap *) RelinquishMagickMemory(map);
840 return(map);
841}
842
843/*
844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
845% %
846% %
847% %
848+ G e t T h r e s h o l d M a p F i l e %
849% %
850% %
851% %
852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
853%
854% GetThresholdMapFile() look for a given threshold map name or alias in the
855% given XML file data, and return the allocated the map when found.
856%
857% The format of the ListThresholdMaps method is:
858%
859% ThresholdMap *GetThresholdMap(const char *xml,const char *filename,
860% const char *map_id,ExceptionInfo *exception)
861%
862% A description of each parameter follows.
863%
864% o xml: The threshold map list in XML format.
865%
866% o filename: The threshold map XML filename.
867%
868% o map_id: ID of the map to look for in XML list.
869%
870% o exception: return any errors or warnings in this structure.
871%
872*/
873MagickExport ThresholdMap *GetThresholdMapFile(const char *xml,
874 const char *filename,const char *map_id,ExceptionInfo *exception)
875{
876 const char
877 *attr,
878 *content;
879
880 double
881 value;
882
883 ThresholdMap
884 *map;
885
886 XMLTreeInfo
887 *description,
888 *levels,
889 *threshold,
890 *thresholds;
891
892 map = (ThresholdMap *)NULL;
893 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
894 "Loading threshold map file \"%s\" ...",filename);
895 thresholds=NewXMLTree(xml,exception);
896 if ( thresholds == (XMLTreeInfo *)NULL )
897 return(map);
898
899 for( threshold = GetXMLTreeChild(thresholds,"threshold");
900 threshold != (XMLTreeInfo *)NULL;
901 threshold = GetNextXMLTreeTag(threshold) ) {
902 attr = GetXMLTreeAttribute(threshold, "map");
903 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
904 break;
905 attr = GetXMLTreeAttribute(threshold, "alias");
906 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
907 break;
908 }
909 if ( threshold == (XMLTreeInfo *)NULL ) {
910 return(map);
911 }
912 description = GetXMLTreeChild(threshold,"description");
913 if ( description == (XMLTreeInfo *)NULL ) {
914 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
915 "XmlMissingElement", "<description>, map \"%s\"", map_id);
916 thresholds = DestroyXMLTree(thresholds);
917 return(map);
918 }
919 levels = GetXMLTreeChild(threshold,"levels");
920 if ( levels == (XMLTreeInfo *)NULL ) {
921 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
922 "XmlMissingElement", "<levels>, map \"%s\"", map_id);
923 thresholds = DestroyXMLTree(thresholds);
924 return(map);
925 }
926
927 /* The map has been found -- Allocate a Threshold Map to return */
cristy90823212009-12-12 20:48:33 +0000928 map = (ThresholdMap *)AcquireAlignedMemory(1,sizeof(ThresholdMap));
cristy3ed852e2009-09-05 21:47:34 +0000929 if ( map == (ThresholdMap *)NULL )
930 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
931 map->map_id = (char *)NULL;
932 map->description = (char *)NULL;
933 map->levels = (long *) NULL;
934
935 /* Assign Basic Attributes */
936 attr = GetXMLTreeAttribute(threshold, "map");
937 if ( attr != (char *)NULL )
938 map->map_id = ConstantString(attr);
939
940 content = GetXMLTreeContent(description);
941 if ( content != (char *)NULL )
942 map->description = ConstantString(content);
943
944 attr = GetXMLTreeAttribute(levels, "width");
945 if ( attr == (char *)NULL ) {
946 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
947 "XmlMissingAttribute", "<levels width>, map \"%s\"", map_id);
948 thresholds = DestroyXMLTree(thresholds);
949 map = DestroyThresholdMap(map);
950 return(map);
951 }
cristye27293e2009-12-18 02:53:20 +0000952 map->width = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000953 if ( map->width == 0 ) {
954 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
955 "XmlInvalidAttribute", "<levels width>, map \"%s\"", map_id);
956 thresholds = DestroyXMLTree(thresholds);
957 map = DestroyThresholdMap(map);
958 return(map);
959 }
960
961 attr = GetXMLTreeAttribute(levels, "height");
962 if ( attr == (char *)NULL ) {
963 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
964 "XmlMissingAttribute", "<levels height>, map \"%s\"", map_id);
965 thresholds = DestroyXMLTree(thresholds);
966 map = DestroyThresholdMap(map);
967 return(map);
968 }
cristye27293e2009-12-18 02:53:20 +0000969 map->height = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000970 if ( map->height == 0 ) {
971 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
972 "XmlInvalidAttribute", "<levels height>, map \"%s\"", map_id);
973 thresholds = DestroyXMLTree(thresholds);
974 map = DestroyThresholdMap(map);
975 return(map);
976 }
977
978 attr = GetXMLTreeAttribute(levels, "divisor");
979 if ( attr == (char *)NULL ) {
980 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
981 "XmlMissingAttribute", "<levels divisor>, map \"%s\"", map_id);
982 thresholds = DestroyXMLTree(thresholds);
983 map = DestroyThresholdMap(map);
984 return(map);
985 }
cristyf2f27272009-12-17 14:48:46 +0000986 map->divisor = StringToLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000987 if ( map->divisor < 2 ) {
988 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
989 "XmlInvalidAttribute", "<levels divisor>, map \"%s\"", map_id);
990 thresholds = DestroyXMLTree(thresholds);
991 map = DestroyThresholdMap(map);
992 return(map);
993 }
994
995 /* Allocate theshold levels array */
996 content = GetXMLTreeContent(levels);
997 if ( content == (char *)NULL ) {
998 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
999 "XmlMissingContent", "<levels>, map \"%s\"", map_id);
1000 thresholds = DestroyXMLTree(thresholds);
1001 map = DestroyThresholdMap(map);
1002 return(map);
1003 }
1004 map->levels=(long *) AcquireQuantumMemory((size_t) map->width,map->height*
1005 sizeof(*map->levels));
1006 if ( map->levels == (long *)NULL )
1007 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
1008 { /* parse levels into integer array */
1009 int i;
1010 char *p;
1011 for( i=0; i< (long) (map->width*map->height); i++) {
1012 map->levels[i] = (int)strtol(content, &p, 10);
1013 if ( p == content ) {
1014 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1015 "XmlInvalidContent", "<level> too few values, map \"%s\"", map_id);
1016 thresholds = DestroyXMLTree(thresholds);
1017 map = DestroyThresholdMap(map);
1018 return(map);
1019 }
1020 if ( map->levels[i] < 0 || map->levels[i] > map->divisor ) {
1021 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1022 "XmlInvalidContent", "<level> %ld out of range, map \"%s\"",
1023 map->levels[i], map_id);
1024 thresholds = DestroyXMLTree(thresholds);
1025 map = DestroyThresholdMap(map);
1026 return(map);
1027 }
1028 content = p;
1029 }
1030 value=(double) strtol(content,&p,10);
1031 if (p != content)
1032 {
1033 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1034 "XmlInvalidContent", "<level> too many values, map \"%s\"", map_id);
1035 thresholds=DestroyXMLTree(thresholds);
1036 map=DestroyThresholdMap(map);
1037 return(map);
1038 }
1039 }
1040
1041 thresholds = DestroyXMLTree(thresholds);
1042 return(map);
1043}
1044
1045/*
1046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1047% %
1048% %
1049% %
1050% G e t T h r e s h o l d M a p %
1051% %
1052% %
1053% %
1054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1055%
1056% GetThresholdMap() load and search one or more threshold map files for the
1057% a map matching the given name or aliase.
1058%
1059% The format of the GetThresholdMap method is:
1060%
1061% ThresholdMap *GetThresholdMap(const char *map_id,
1062% ExceptionInfo *exception)
1063%
1064% A description of each parameter follows.
1065%
1066% o map_id: ID of the map to look for.
1067%
1068% o exception: return any errors or warnings in this structure.
1069%
1070*/
1071MagickExport ThresholdMap *GetThresholdMap(const char *map_id,
1072 ExceptionInfo *exception)
1073{
1074 const StringInfo
1075 *option;
1076
1077 LinkedListInfo
1078 *options;
1079
1080 ThresholdMap
1081 *map;
1082
1083 map=(ThresholdMap *)NULL;
1084 options=GetConfigureOptions(ThresholdsFilename,exception);
1085 while (( option=(const StringInfo *) GetNextValueInLinkedList(options) )
1086 != (const StringInfo *) NULL && map == (ThresholdMap *)NULL )
1087 map=GetThresholdMapFile((const char *) GetStringInfoDatum(option),
1088 GetStringInfoPath(option),map_id,exception);
1089 options=DestroyConfigureOptions(options);
1090 return(map);
1091}
1092
1093/*
1094%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1095% %
1096% %
1097% %
1098+ L i s t T h r e s h o l d M a p F i l e %
1099% %
1100% %
1101% %
1102%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1103%
1104% ListThresholdMapFile() lists the threshold maps and their descriptions
1105% in the given XML file data.
1106%
1107% The format of the ListThresholdMaps method is:
1108%
1109% MagickBooleanType ListThresholdMaps(FILE *file,const char*xml,
1110% const char *filename,ExceptionInfo *exception)
1111%
1112% A description of each parameter follows.
1113%
1114% o file: An pointer to the output FILE.
1115%
1116% o xml: The threshold map list in XML format.
1117%
1118% o filename: The threshold map XML filename.
1119%
1120% o exception: return any errors or warnings in this structure.
1121%
1122*/
1123MagickBooleanType ListThresholdMapFile(FILE *file,const char *xml,
1124 const char *filename,ExceptionInfo *exception)
1125{
1126 XMLTreeInfo *thresholds,*threshold,*description;
1127 const char *map,*alias,*content;
1128
1129 assert( xml != (char *)NULL );
1130 assert( file != (FILE *)NULL );
1131
1132 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
1133 "Loading threshold map file \"%s\" ...",filename);
1134 thresholds=NewXMLTree(xml,exception);
1135 if ( thresholds == (XMLTreeInfo *)NULL )
1136 return(MagickFalse);
1137
1138 (void) fprintf(file,"%-16s %-12s %s\n", "Map", "Alias", "Description");
1139 (void) fprintf(file,"----------------------------------------------------\n");
1140
1141 for( threshold = GetXMLTreeChild(thresholds,"threshold");
1142 threshold != (XMLTreeInfo *)NULL;
1143 threshold = GetNextXMLTreeTag(threshold) )
1144 {
1145 map = GetXMLTreeAttribute(threshold, "map");
1146 if (map == (char *) NULL) {
1147 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1148 "XmlMissingAttribute", "<map>");
1149 thresholds=DestroyXMLTree(thresholds);
1150 return(MagickFalse);
1151 }
1152 alias = GetXMLTreeAttribute(threshold, "alias");
1153 /* alias is optional, no if test needed */
1154 description=GetXMLTreeChild(threshold,"description");
1155 if ( description == (XMLTreeInfo *)NULL ) {
1156 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1157 "XmlMissingElement", "<description>, map \"%s\"", map);
1158 thresholds=DestroyXMLTree(thresholds);
1159 return(MagickFalse);
1160 }
1161 content=GetXMLTreeContent(description);
1162 if ( content == (char *)NULL ) {
1163 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1164 "XmlMissingContent", "<description>, map \"%s\"", map);
1165 thresholds=DestroyXMLTree(thresholds);
1166 return(MagickFalse);
1167 }
1168 (void) fprintf(file,"%-16s %-12s %s\n",map,alias ? alias : "", content);
1169 }
1170 thresholds=DestroyXMLTree(thresholds);
1171 return(MagickTrue);
1172}
1173
1174/*
1175%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1176% %
1177% %
1178% %
1179% L i s t T h r e s h o l d M a p s %
1180% %
1181% %
1182% %
1183%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1184%
1185% ListThresholdMaps() lists the threshold maps and their descriptions
1186% as defined by "threshold.xml" to a file.
1187%
1188% The format of the ListThresholdMaps method is:
1189%
1190% MagickBooleanType ListThresholdMaps(FILE *file,ExceptionInfo *exception)
1191%
1192% A description of each parameter follows.
1193%
1194% o file: An pointer to the output FILE.
1195%
1196% o exception: return any errors or warnings in this structure.
1197%
1198*/
1199MagickExport MagickBooleanType ListThresholdMaps(FILE *file,
1200 ExceptionInfo *exception)
1201{
1202 const StringInfo
1203 *option;
1204
1205 LinkedListInfo
1206 *options;
1207
1208 MagickStatusType
1209 status;
1210
1211 status=MagickFalse;
1212 if ( file == (FILE *)NULL )
1213 file = stdout;
1214 options=GetConfigureOptions(ThresholdsFilename,exception);
1215
1216 (void) fprintf(file, "\n Threshold Maps for Ordered Dither Operations\n");
1217
1218 while ( ( option=(const StringInfo *) GetNextValueInLinkedList(options) )
1219 != (const StringInfo *) NULL)
1220 {
1221 (void) fprintf(file,"\nPATH: %s\n\n",GetStringInfoPath(option));
1222 status|=ListThresholdMapFile(file,(const char *) GetStringInfoDatum(option),
1223 GetStringInfoPath(option),exception);
1224 }
1225 options=DestroyConfigureOptions(options);
1226 return(status != 0 ? MagickTrue : MagickFalse);
1227}
1228
1229/*
1230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1231% %
1232% %
1233% %
1234% O r d e r e d D i t h e r I m a g e %
1235% %
1236% %
1237% %
1238%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1239%
1240% OrderedDitherImage() uses the ordered dithering technique of reducing color
1241% images to monochrome using positional information to retain as much
1242% information as possible.
1243%
1244% WARNING: This function is deprecated, and is now just a call to
1245% the more more powerful OrderedPosterizeImage(); function.
1246%
1247% The format of the OrderedDitherImage method is:
1248%
1249% MagickBooleanType OrderedDitherImage(Image *image)
1250% MagickBooleanType OrderedDitherImageChannel(Image *image,
1251% const ChannelType channel,ExceptionInfo *exception)
1252%
1253% A description of each parameter follows:
1254%
1255% o image: the image.
1256%
1257% o channel: the channel or channels to be thresholded.
1258%
1259% o exception: return any errors or warnings in this structure.
1260%
1261*/
1262
1263MagickExport MagickBooleanType OrderedDitherImage(Image *image)
1264{
1265 MagickBooleanType
1266 status;
1267
1268 status=OrderedDitherImageChannel(image,DefaultChannels,&image->exception);
1269 return(status);
1270}
1271
1272MagickExport MagickBooleanType OrderedDitherImageChannel(Image *image,
1273 const ChannelType channel,ExceptionInfo *exception)
1274{
1275 MagickBooleanType
1276 status;
1277
1278 /*
1279 Call the augumented function OrderedPosterizeImage()
1280 */
1281 status=OrderedPosterizeImageChannel(image,channel,"o8x8",exception);
1282 return(status);
1283}
1284
1285/*
1286%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1287% %
1288% %
1289% %
1290% O r d e r e d P o s t e r i z e I m a g e %
1291% %
1292% %
1293% %
1294%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1295%
1296% OrderedPosterizeImage() will perform a ordered dither based on a number
1297% of pre-defined dithering threshold maps, but over multiple intensity
1298% levels, which can be different for different channels, according to the
1299% input argument.
1300%
1301% The format of the OrderedPosterizeImage method is:
1302%
1303% MagickBooleanType OrderedPosterizeImage(Image *image,
1304% const char *threshold_map,ExceptionInfo *exception)
1305% MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1306% const ChannelType channel,const char *threshold_map,
1307% ExceptionInfo *exception)
1308%
1309% A description of each parameter follows:
1310%
1311% o image: the image.
1312%
1313% o channel: the channel or channels to be thresholded.
1314%
1315% o threshold_map: A string containing the name of the threshold dither
1316% map to use, followed by zero or more numbers representing the number
1317% of color levels tho dither between.
1318%
1319% Any level number less than 2 will be equivelent to 2, and means only
1320% binary dithering will be applied to each color channel.
1321%
1322% No numbers also means a 2 level (bitmap) dither will be applied to all
1323% channels, while a single number is the number of levels applied to each
1324% channel in sequence. More numbers will be applied in turn to each of
1325% the color channels.
1326%
1327% For example: "o3x3,6" will generate a 6 level posterization of the
1328% image with a ordered 3x3 diffused pixel dither being applied between
1329% each level. While checker,8,8,4 will produce a 332 colormaped image
1330% with only a single checkerboard hash pattern (50% grey) between each
1331% color level, to basically double the number of color levels with
1332% a bare minimim of dithering.
1333%
1334% o exception: return any errors or warnings in this structure.
1335%
1336*/
1337MagickExport MagickBooleanType OrderedPosterizeImage(Image *image,
1338 const char *threshold_map,ExceptionInfo *exception)
1339{
1340 MagickBooleanType
1341 status;
1342
1343 status=OrderedPosterizeImageChannel(image,DefaultChannels,threshold_map,
1344 exception);
1345 return(status);
1346}
1347
1348MagickExport MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1349 const ChannelType channel,const char *threshold_map,ExceptionInfo *exception)
1350{
1351#define DitherImageTag "Dither/Image"
1352
cristyc4c8d132010-01-07 01:58:38 +00001353 CacheView
1354 *image_view;
1355
cristy3ed852e2009-09-05 21:47:34 +00001356 long
1357 progress,
1358 y;
1359
1360 LongPixelPacket
1361 levels;
1362
1363 MagickBooleanType
1364 status;
1365
1366 ThresholdMap
1367 *map;
1368
cristy3ed852e2009-09-05 21:47:34 +00001369 assert(image != (Image *) NULL);
1370 assert(image->signature == MagickSignature);
1371 if (image->debug != MagickFalse)
1372 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1373 assert(exception != (ExceptionInfo *) NULL);
1374 assert(exception->signature == MagickSignature);
1375 if (threshold_map == (const char *) NULL)
1376 return(MagickTrue);
1377 {
1378 char
1379 token[MaxTextExtent];
1380
1381 register const char
1382 *p;
1383
1384 p=(char *)threshold_map;
1385 while (((isspace((int) ((unsigned char) *p)) != 0) || (*p == ',')) &&
1386 (*p != '\0'))
1387 p++;
1388 threshold_map=p;
1389 while (((isspace((int) ((unsigned char) *p)) == 0) && (*p != ',')) &&
1390 (*p != '\0')) {
1391 if ((p-threshold_map) >= MaxTextExtent)
1392 break;
1393 token[p-threshold_map] = *p;
1394 p++;
1395 }
1396 token[p-threshold_map] = '\0';
1397 map = GetThresholdMap(token, exception);
1398 if ( map == (ThresholdMap *)NULL ) {
1399 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1400 "InvalidArgument","%s : '%s'","ordered-dither",threshold_map);
1401 return(MagickFalse);
1402 }
1403 }
1404 /* Set channel levels from extra comma seperated arguments
1405 Default to 2, the single value given, or individual channel values
1406 */
1407#if 1
1408 { /* parse directly as a comma seperated list of integers */
1409 char *p;
1410
1411 p = strchr((char *) threshold_map,',');
1412 if ( p != (char *)NULL && isdigit((int) ((unsigned char) *(++p))) )
1413 levels.index = (unsigned long) strtol(p, &p, 10);
1414 else
1415 levels.index = 2;
1416
1417 levels.red = ((channel & RedChannel ) != 0) ? levels.index : 0;
1418 levels.green = ((channel & GreenChannel) != 0) ? levels.index : 0;
1419 levels.blue = ((channel & BlueChannel) != 0) ? levels.index : 0;
1420 levels.opacity = ((channel & OpacityChannel) != 0) ? levels.index : 0;
1421 levels.index = ((channel & IndexChannel) != 0
1422 && (image->colorspace == CMYKColorspace)) ? levels.index : 0;
1423
1424 /* if more than a single number, each channel has a separate value */
1425 if ( p != (char *) NULL && *p == ',' ) {
1426 p=strchr((char *) threshold_map,',');
1427 p++;
1428 if ((channel & RedChannel) != 0)
1429 levels.red = (unsigned long) strtol(p, &p, 10), (void)(*p == ',' && p++);
1430 if ((channel & GreenChannel) != 0)
1431 levels.green = (unsigned long) strtol(p, &p, 10), (void)(*p == ',' && p++);
1432 if ((channel & BlueChannel) != 0)
1433 levels.blue = (unsigned long) strtol(p, &p, 10), (void)(*p == ',' && p++);
1434 if ((channel & IndexChannel) != 0 && image->colorspace == CMYKColorspace)
1435 levels.index=(unsigned long) strtol(p, &p, 10), (void)(*p == ',' && p++);
1436 if ((channel & OpacityChannel) != 0)
1437 levels.opacity = (unsigned long) strtol(p, &p, 10), (void)(*p == ',' && p++);
1438 }
1439 }
1440#else
1441 /* Parse level values as a geometry */
1442 /* This difficult!
1443 * How to map GeometryInfo structure elements into
1444 * LongPixelPacket structure elements, but according to channel?
1445 * Note the channels list may skip elements!!!!
1446 * EG -channel BA -ordered-dither map,2,3
1447 * will need to map g.rho -> l.blue, and g.sigma -> l.opacity
1448 * A simpler way is needed, probably converting geometry to a temporary
1449 * array, then using channel to advance the index into long pixel packet.
1450 */
1451#endif
1452
1453#if 0
1454printf("DEBUG levels r=%ld g=%ld b=%ld a=%ld i=%ld\n",
1455 levels.red, levels.green, levels.blue, levels.opacity, levels.index);
1456#endif
1457
1458 { /* Do the posterized ordered dithering of the image */
1459 int
1460 d;
1461
1462 /* d = number of psuedo-level divisions added between color levels */
1463 d = map->divisor-1;
1464
1465 /* reduce levels to levels - 1 */
1466 levels.red = levels.red ? levels.red-1 : 0;
1467 levels.green = levels.green ? levels.green-1 : 0;
1468 levels.blue = levels.blue ? levels.blue-1 : 0;
1469 levels.opacity = levels.opacity ? levels.opacity-1 : 0;
1470 levels.index = levels.index ? levels.index-1 : 0;
1471
1472 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1473 {
1474 InheritException(exception,&image->exception);
1475 return(MagickFalse);
1476 }
1477 status=MagickTrue;
1478 progress=0;
1479 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001480#if defined(MAGICKCORE_OPENMP_SUPPORT)
1481 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001482#endif
1483 for (y=0; y < (long) image->rows; y++)
1484 {
1485 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001486 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001487
1488 register long
1489 x;
1490
1491 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001492 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001493
1494 if (status == MagickFalse)
1495 continue;
1496 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1497 if (q == (PixelPacket *) NULL)
1498 {
1499 status=MagickFalse;
1500 continue;
1501 }
1502 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1503 for (x=0; x < (long) image->columns; x++)
1504 {
1505 register int
1506 threshold,
1507 t,
1508 l;
1509
1510 /*
1511 Figure out the dither threshold for this pixel
1512 This must be a integer from 1 to map->divisor-1
1513 */
1514 threshold = map->levels[(x%map->width) +map->width*(y%map->height)];
1515
1516 /* Dither each channel in the image as appropriate
1517 Notes on the integer Math...
1518 total number of divisions = (levels-1)*(divisor-1)+1)
1519 t1 = this colors psuedo_level =
1520 q->red * total_divisions / (QuantumRange+1)
1521 l = posterization level 0..levels
1522 t = dither threshold level 0..divisor-1 NB: 0 only on last
1523 Each color_level is of size QuantumRange / (levels-1)
1524 NB: All input levels and divisor are already had 1 subtracted
1525 Opacity is inverted so 'off' represents transparent.
1526 */
1527 if (levels.red) {
1528 t = (int) (QuantumScale*q->red*(levels.red*d+1));
1529 l = t/d; t = t-l*d;
1530 q->red=(Quantum) ((l+(t >= threshold))*QuantumRange/levels.red);
1531 }
1532 if (levels.green) {
1533 t = (int) (QuantumScale*q->green*(levels.green*d+1));
1534 l = t/d; t = t-l*d;
1535 q->green=(Quantum) ((l+(t >= threshold))*QuantumRange/levels.green);
1536 }
1537 if (levels.blue) {
1538 t = (int) (QuantumScale*q->blue*(levels.blue*d+1));
1539 l = t/d; t = t-l*d;
1540 q->blue=(Quantum) ((l+(t >= threshold))*QuantumRange/levels.blue);
1541 }
1542 if (levels.opacity) {
1543 t = (int) ((1.0-QuantumScale*q->opacity)*(levels.opacity*d+1));
1544 l = t/d; t = t-l*d;
1545 q->opacity=(Quantum) ((1.0-l-(t >= threshold))*QuantumRange/
1546 levels.opacity);
1547 }
1548 if (levels.index) {
1549 t = (int) (QuantumScale*indexes[x]*(levels.index*d+1));
1550 l = t/d; t = t-l*d;
1551 indexes[x]=(IndexPacket) ((l+(t>=threshold))*QuantumRange/
1552 levels.index);
1553 }
1554 q++;
1555 }
1556 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1557 status=MagickFalse;
1558 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1559 {
1560 MagickBooleanType
1561 proceed;
1562
cristyb5d5f722009-11-04 03:03:49 +00001563#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001564 #pragma omp critical (MagickCore_OrderedPosterizeImageChannel)
1565#endif
1566 proceed=SetImageProgress(image,DitherImageTag,progress++,image->rows);
1567 if (proceed == MagickFalse)
1568 status=MagickFalse;
1569 }
1570 }
1571 image_view=DestroyCacheView(image_view);
1572 }
1573 map=DestroyThresholdMap(map);
1574 return(MagickTrue);
1575}
1576
1577/*
1578%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1579% %
1580% %
1581% %
1582% R a n d o m T h r e s h o l d I m a g e %
1583% %
1584% %
1585% %
1586%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1587%
1588% RandomThresholdImage() changes the value of individual pixels based on the
1589% intensity of each pixel compared to a random threshold. The result is a
1590% low-contrast, two color image.
1591%
1592% The format of the RandomThresholdImage method is:
1593%
1594% MagickBooleanType RandomThresholdImageChannel(Image *image,
1595% const char *thresholds,ExceptionInfo *exception)
1596% MagickBooleanType RandomThresholdImageChannel(Image *image,
1597% const ChannelType channel,const char *thresholds,
1598% ExceptionInfo *exception)
1599%
1600% A description of each parameter follows:
1601%
1602% o image: the image.
1603%
1604% o channel: the channel or channels to be thresholded.
1605%
1606% o thresholds: a geometry string containing low,high thresholds. If the
1607% string contains 2x2, 3x3, or 4x4, an ordered dither of order 2, 3, or 4
1608% is performed instead.
1609%
1610% o exception: return any errors or warnings in this structure.
1611%
1612*/
1613
1614MagickExport MagickBooleanType RandomThresholdImage(Image *image,
1615 const char *thresholds,ExceptionInfo *exception)
1616{
1617 MagickBooleanType
1618 status;
1619
1620 status=RandomThresholdImageChannel(image,DefaultChannels,thresholds,
1621 exception);
1622 return(status);
1623}
1624
1625MagickExport MagickBooleanType RandomThresholdImageChannel(Image *image,
1626 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1627{
1628#define ThresholdImageTag "Threshold/Image"
1629
cristyfa112112010-01-04 17:48:07 +00001630 CacheView
1631 *image_view;
1632
cristy3ed852e2009-09-05 21:47:34 +00001633 GeometryInfo
1634 geometry_info;
1635
1636 MagickStatusType
1637 flags;
1638
1639 long
1640 progress,
1641 y;
1642
1643 MagickBooleanType
1644 status;
1645
1646 MagickPixelPacket
1647 threshold;
1648
1649 MagickRealType
1650 min_threshold,
1651 max_threshold;
1652
1653 RandomInfo
cristyfa112112010-01-04 17:48:07 +00001654 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00001655
1656 assert(image != (Image *) NULL);
1657 assert(image->signature == MagickSignature);
1658 if (image->debug != MagickFalse)
1659 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1660 assert(exception != (ExceptionInfo *) NULL);
1661 assert(exception->signature == MagickSignature);
1662 if (thresholds == (const char *) NULL)
1663 return(MagickTrue);
1664 GetMagickPixelPacket(image,&threshold);
1665 min_threshold=0.0;
1666 max_threshold=(MagickRealType) QuantumRange;
1667 flags=ParseGeometry(thresholds,&geometry_info);
1668 min_threshold=geometry_info.rho;
1669 max_threshold=geometry_info.sigma;
1670 if ((flags & SigmaValue) == 0)
1671 max_threshold=min_threshold;
1672 if (strchr(thresholds,'%') != (char *) NULL)
1673 {
1674 max_threshold*=(MagickRealType) (0.01*QuantumRange);
1675 min_threshold*=(MagickRealType) (0.01*QuantumRange);
1676 }
1677 else
1678 if (((max_threshold == min_threshold) || (max_threshold == 1)) &&
1679 (min_threshold <= 8))
1680 {
1681 /*
1682 Backward Compatibility -- ordered-dither -- IM v 6.2.9-6.
1683 */
1684 status=OrderedPosterizeImageChannel(image,channel,thresholds,exception);
1685 return(status);
1686 }
1687 /*
1688 Random threshold image.
1689 */
1690 status=MagickTrue;
1691 progress=0;
1692 if (channel == AllChannels)
1693 {
1694 if (AcquireImageColormap(image,2) == MagickFalse)
1695 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1696 image->filename);
1697 random_info=AcquireRandomInfoThreadSet();
1698 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001699#if defined(MAGICKCORE_OPENMP_SUPPORT)
1700 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001701#endif
1702 for (y=0; y < (long) image->rows; y++)
1703 {
1704 MagickBooleanType
1705 sync;
1706
1707 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001708 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001709
1710 register long
1711 id,
1712 x;
1713
1714 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001715 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001716
1717 if (status == MagickFalse)
1718 continue;
1719 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
1720 exception);
1721 if (q == (PixelPacket *) NULL)
1722 {
1723 status=MagickFalse;
1724 continue;
1725 }
1726 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1727 id=GetOpenMPThreadId();
1728 for (x=0; x < (long) image->columns; x++)
1729 {
1730 IndexPacket
1731 index;
1732
1733 MagickRealType
1734 intensity;
1735
1736 intensity=(MagickRealType) PixelIntensityToQuantum(q);
1737 if (intensity < min_threshold)
1738 threshold.index=min_threshold;
1739 else if (intensity > max_threshold)
1740 threshold.index=max_threshold;
1741 else
1742 threshold.index=(MagickRealType)(QuantumRange*
1743 GetPseudoRandomValue(random_info[id]));
1744 index=(IndexPacket) (intensity <= threshold.index ? 0 : 1);
1745 indexes[x]=index;
1746 *q++=image->colormap[(long) index];
1747 }
1748 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1749 if (sync == MagickFalse)
1750 status=MagickFalse;
1751 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1752 {
1753 MagickBooleanType
1754 proceed;
1755
cristyb5d5f722009-11-04 03:03:49 +00001756#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001757 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1758#endif
1759 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1760 image->rows);
1761 if (proceed == MagickFalse)
1762 status=MagickFalse;
1763 }
1764 }
1765 image_view=DestroyCacheView(image_view);
1766 random_info=DestroyRandomInfoThreadSet(random_info);
1767 return(status);
1768 }
1769 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1770 {
1771 InheritException(exception,&image->exception);
1772 return(MagickFalse);
1773 }
1774 random_info=AcquireRandomInfoThreadSet();
1775 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001776#if defined(MAGICKCORE_OPENMP_SUPPORT)
1777 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001778#endif
1779 for (y=0; y < (long) image->rows; y++)
1780 {
1781 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001782 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001783
1784 register long
1785 id,
1786 x;
1787
1788 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001789 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001790
1791 if (status == MagickFalse)
1792 continue;
1793 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1794 if (q == (PixelPacket *) NULL)
1795 {
1796 status=MagickFalse;
1797 continue;
1798 }
1799 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1800 id=GetOpenMPThreadId();
1801 for (x=0; x < (long) image->columns; x++)
1802 {
1803 if ((channel & RedChannel) != 0)
1804 {
1805 if ((MagickRealType) q->red < min_threshold)
1806 threshold.red=min_threshold;
1807 else
1808 if ((MagickRealType) q->red > max_threshold)
1809 threshold.red=max_threshold;
1810 else
1811 threshold.red=(MagickRealType) (QuantumRange*
1812 GetPseudoRandomValue(random_info[id]));
1813 }
1814 if ((channel & GreenChannel) != 0)
1815 {
1816 if ((MagickRealType) q->green < min_threshold)
1817 threshold.green=min_threshold;
1818 else
1819 if ((MagickRealType) q->green > max_threshold)
1820 threshold.green=max_threshold;
1821 else
1822 threshold.green=(MagickRealType) (QuantumRange*
1823 GetPseudoRandomValue(random_info[id]));
1824 }
1825 if ((channel & BlueChannel) != 0)
1826 {
1827 if ((MagickRealType) q->blue < min_threshold)
1828 threshold.blue=min_threshold;
1829 else
1830 if ((MagickRealType) q->blue > max_threshold)
1831 threshold.blue=max_threshold;
1832 else
1833 threshold.blue=(MagickRealType) (QuantumRange*
1834 GetPseudoRandomValue(random_info[id]));
1835 }
1836 if ((channel & OpacityChannel) != 0)
1837 {
1838 if ((MagickRealType) q->opacity < min_threshold)
1839 threshold.opacity=min_threshold;
1840 else
1841 if ((MagickRealType) q->opacity > max_threshold)
1842 threshold.opacity=max_threshold;
1843 else
1844 threshold.opacity=(MagickRealType) (QuantumRange*
1845 GetPseudoRandomValue(random_info[id]));
1846 }
1847 if (((channel & IndexChannel) != 0) &&
1848 (image->colorspace == CMYKColorspace))
1849 {
1850 if ((MagickRealType) indexes[x] < min_threshold)
1851 threshold.index=min_threshold;
1852 else
1853 if ((MagickRealType) indexes[x] > max_threshold)
1854 threshold.index=max_threshold;
1855 else
1856 threshold.index=(MagickRealType) (QuantumRange*
1857 GetPseudoRandomValue(random_info[id]));
1858 }
1859 if ((channel & RedChannel) != 0)
1860 q->red=(Quantum) ((MagickRealType) q->red <= threshold.red ? 0 :
1861 QuantumRange);
1862 if ((channel & GreenChannel) != 0)
1863 q->green=(Quantum) ((MagickRealType) q->green <= threshold.green ? 0 :
1864 QuantumRange);
1865 if ((channel & BlueChannel) != 0)
1866 q->blue=(Quantum) ((MagickRealType) q->blue <= threshold.blue ? 0 :
1867 QuantumRange);
1868 if ((channel & OpacityChannel) != 0)
1869 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold.opacity ?
1870 0 : QuantumRange);
1871 if (((channel & IndexChannel) != 0) &&
1872 (image->colorspace == CMYKColorspace))
1873 indexes[x]=(IndexPacket) ((MagickRealType) indexes[x] <=
1874 threshold.index ? 0 : QuantumRange);
1875 q++;
1876 }
1877 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1878 status=MagickFalse;
1879 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1880 {
1881 MagickBooleanType
1882 proceed;
1883
cristyb5d5f722009-11-04 03:03:49 +00001884#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001885 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1886#endif
1887 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1888 image->rows);
1889 if (proceed == MagickFalse)
1890 status=MagickFalse;
1891 }
1892 }
1893 image_view=DestroyCacheView(image_view);
1894 random_info=DestroyRandomInfoThreadSet(random_info);
1895 return(status);
1896}
1897
1898/*
1899%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1900% %
1901% %
1902% %
1903% W h i t e T h r e s h o l d I m a g e %
1904% %
1905% %
1906% %
1907%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1908%
1909% WhiteThresholdImage() is like ThresholdImage() but forces all pixels above
cristy4e101302009-09-17 12:49:12 +00001910% the threshold into white while leaving all pixels at or below the threshold
cristy3ed852e2009-09-05 21:47:34 +00001911% unchanged.
1912%
1913% The format of the WhiteThresholdImage method is:
1914%
1915% MagickBooleanType WhiteThresholdImage(Image *image,const char *threshold)
1916% MagickBooleanType WhiteThresholdImageChannel(Image *image,
1917% const ChannelType channel,const char *threshold,
1918% ExceptionInfo *exception)
1919%
1920% A description of each parameter follows:
1921%
1922% o image: the image.
1923%
1924% o channel: the channel or channels to be thresholded.
1925%
1926% o threshold: Define the threshold value.
1927%
1928% o exception: return any errors or warnings in this structure.
1929%
1930*/
1931MagickExport MagickBooleanType WhiteThresholdImage(Image *image,
1932 const char *threshold)
1933{
1934 MagickBooleanType
1935 status;
1936
1937 status=WhiteThresholdImageChannel(image,DefaultChannels,threshold,
1938 &image->exception);
1939 return(status);
1940}
1941
1942MagickExport MagickBooleanType WhiteThresholdImageChannel(Image *image,
1943 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1944{
1945#define ThresholdImageTag "Threshold/Image"
1946
1947 GeometryInfo
1948 geometry_info;
1949
1950 long
1951 progress,
1952 y;
1953
1954 MagickBooleanType
1955 status;
1956
1957 MagickPixelPacket
1958 threshold;
1959
1960 MagickStatusType
1961 flags;
1962
1963 CacheView
1964 *image_view;
1965
1966 assert(image != (Image *) NULL);
1967 assert(image->signature == MagickSignature);
1968 if (image->debug != MagickFalse)
1969 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1970 if (thresholds == (const char *) NULL)
1971 return(MagickTrue);
1972 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1973 return(MagickFalse);
1974 flags=ParseGeometry(thresholds,&geometry_info);
1975 GetMagickPixelPacket(image,&threshold);
1976 threshold.red=geometry_info.rho;
1977 threshold.green=geometry_info.sigma;
1978 if ((flags & SigmaValue) == 0)
1979 threshold.green=threshold.red;
1980 threshold.blue=geometry_info.xi;
1981 if ((flags & XiValue) == 0)
1982 threshold.blue=threshold.red;
1983 threshold.opacity=geometry_info.psi;
1984 if ((flags & PsiValue) == 0)
1985 threshold.opacity=threshold.red;
1986 threshold.index=geometry_info.chi;
1987 if ((flags & ChiValue) == 0)
1988 threshold.index=threshold.red;
1989 if ((flags & PercentValue) != 0)
1990 {
1991 threshold.red*=(QuantumRange/100.0);
1992 threshold.green*=(QuantumRange/100.0);
1993 threshold.blue*=(QuantumRange/100.0);
1994 threshold.opacity*=(QuantumRange/100.0);
1995 threshold.index*=(QuantumRange/100.0);
1996 }
1997 /*
1998 White threshold image.
1999 */
2000 status=MagickTrue;
2001 progress=0;
2002 image_view=AcquireCacheView(image);
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 (y=0; y < (long) image->rows; y++)
2007 {
2008 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002009 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002010
2011 register long
2012 x;
2013
2014 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002015 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002016
2017 if (status == MagickFalse)
2018 continue;
2019 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2020 if (q == (PixelPacket *) NULL)
2021 {
2022 status=MagickFalse;
2023 continue;
2024 }
2025 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2026 for (x=0; x < (long) image->columns; x++)
2027 {
cristyb0ea1af2009-11-28 20:44:46 +00002028 if (channel != DefaultChannels)
2029 {
2030 if (PixelIntensity(q) > MagickPixelIntensity(&threshold))
2031 {
2032 q->red=(Quantum) QuantumRange;
2033 q->green=(Quantum) QuantumRange;
2034 q->blue=(Quantum) QuantumRange;
2035 if (image->colorspace == CMYKColorspace)
2036 indexes[x]=(Quantum) QuantumRange;
2037 }
2038 }
2039 else
2040 {
2041 if (((channel & RedChannel) != 0) &&
2042 ((MagickRealType) q->red > threshold.red))
2043 q->red=(Quantum) QuantumRange;
2044 if (((channel & GreenChannel) != 0) &&
2045 ((MagickRealType) q->green > threshold.green))
2046 q->green=(Quantum) QuantumRange;
2047 if (((channel & BlueChannel) != 0) &&
2048 ((MagickRealType) q->blue > threshold.blue))
2049 q->blue=(Quantum) QuantumRange;
2050 if (((channel & OpacityChannel) != 0) &&
2051 ((MagickRealType) q->opacity > threshold.opacity))
2052 q->opacity=(Quantum) QuantumRange;
2053 if (((channel & IndexChannel) != 0) &&
2054 (image->colorspace == CMYKColorspace) &&
2055 ((MagickRealType) indexes[x] > threshold.index))
2056 indexes[x]=(Quantum) QuantumRange;
2057 }
cristy3ed852e2009-09-05 21:47:34 +00002058 q++;
2059 }
2060 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2061 status=MagickFalse;
2062 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2063 {
2064 MagickBooleanType
2065 proceed;
2066
cristyb5d5f722009-11-04 03:03:49 +00002067#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002068 #pragma omp critical (MagickCore_WhiteThresholdImageChannel)
2069#endif
2070 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
2071 image->rows);
2072 if (proceed == MagickFalse)
2073 status=MagickFalse;
2074 }
2075 }
2076 image_view=DestroyCacheView(image_view);
2077 return(status);
2078}