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