MagickCore 6.9.13
Loading...
Searching...
No Matches
threshold.c
1/*
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% Cristy %
17% October 1996 %
18% %
19% %
20% Copyright 1999 ImageMagick Studio LLC, a non-profit organization %
21% 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% https://imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/artifact.h"
45#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colormap.h"
50#include "magick/colorspace.h"
51#include "magick/colorspace-private.h"
52#include "magick/configure.h"
53#include "magick/constitute.h"
54#include "magick/decorate.h"
55#include "magick/draw.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
59#include "magick/effect.h"
60#include "magick/fx.h"
61#include "magick/gem.h"
62#include "magick/geometry.h"
63#include "magick/image-private.h"
64#include "magick/list.h"
65#include "magick/log.h"
66#include "magick/memory_.h"
67#include "magick/monitor.h"
68#include "magick/monitor-private.h"
69#include "magick/montage.h"
70#include "magick/option.h"
71#include "magick/pixel-private.h"
72#include "magick/property.h"
73#include "magick/quantize.h"
74#include "magick/quantum.h"
75#include "magick/random_.h"
76#include "magick/random-private.h"
77#include "magick/resize.h"
78#include "magick/resource_.h"
79#include "magick/segment.h"
80#include "magick/shear.h"
81#include "magick/signature-private.h"
82#include "magick/string_.h"
83#include "magick/string-private.h"
84#include "magick/thread-private.h"
85#include "magick/threshold.h"
86#include "magick/transform.h"
87#include "magick/xml-tree.h"
88
89/*
90 Define declarations.
91*/
92#define ThresholdsFilename "thresholds.xml"
93
94/*
95 Typedef declarations.
96*/
98{
99 char
100 *map_id,
101 *description;
102
103 size_t
104 width,
105 height;
106
107 ssize_t
108 divisor,
109 *levels;
110};
111
112/*
113 Static declarations.
114*/
115#if MAGICKCORE_ZERO_CONFIGURATION_SUPPORT
116 #include "magick/threshold-map.h"
117#else
118static const char
119 *BuiltinMap =
120 "<?xml version=\"1.0\"?>"
121 "<thresholds>"
122 " <threshold map=\"threshold\" alias=\"1x1\">"
123 " <description>Threshold 1x1 (non-dither)</description>"
124 " <levels width=\"1\" height=\"1\" divisor=\"2\">"
125 " 1"
126 " </levels>"
127 " </threshold>"
128 " <threshold map=\"checks\" alias=\"2x1\">"
129 " <description>Checkerboard 2x1 (dither)</description>"
130 " <levels width=\"2\" height=\"2\" divisor=\"3\">"
131 " 1 2"
132 " 2 1"
133 " </levels>"
134 " </threshold>"
135 "</thresholds>";
136#endif
137
138/*
139%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
140% %
141% %
142% %
143% A d a p t i v e T h r e s h o l d I m a g e %
144% %
145% %
146% %
147%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
148%
149% AdaptiveThresholdImage() selects an individual threshold for each pixel
150% based on the range of intensity values in its local neighborhood. This
151% allows for thresholding of an image whose global intensity histogram
152% doesn't contain distinctive peaks.
153%
154% The format of the AdaptiveThresholdImage method is:
155%
156% Image *AdaptiveThresholdImage(const Image *image,
157% const size_t width,const size_t height,
158% const ssize_t offset,ExceptionInfo *exception)
159%
160% A description of each parameter follows:
161%
162% o image: the image.
163%
164% o width: the width of the local neighborhood.
165%
166% o height: the height of the local neighborhood.
167%
168% o offset: the mean offset.
169%
170% o exception: return any errors or warnings in this structure.
171%
172*/
173MagickExport Image *AdaptiveThresholdImage(const Image *image,
174 const size_t width,const size_t height,const ssize_t offset,
175 ExceptionInfo *exception)
176{
177#define ThresholdImageTag "Threshold/Image"
178
180 *image_view,
181 *threshold_view;
182
183 Image
184 *threshold_image;
185
186 MagickBooleanType
187 status;
188
189 MagickOffsetType
190 progress;
191
193 zero;
194
195 MagickRealType
196 number_pixels;
197
198 ssize_t
199 y;
200
201 assert(image != (const Image *) NULL);
202 assert(image->signature == MagickCoreSignature);
203 if (IsEventLogging() != MagickFalse)
204 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
205 assert(exception != (ExceptionInfo *) NULL);
206 assert(exception->signature == MagickCoreSignature);
207 threshold_image=CloneImage(image,0,0,MagickTrue,exception);
208 if (threshold_image == (Image *) NULL)
209 return((Image *) NULL);
210 if ((width == 0) || (height == 0))
211 return(threshold_image);
212 if (SetImageStorageClass(threshold_image,DirectClass) == MagickFalse)
213 {
214 InheritException(exception,&threshold_image->exception);
215 threshold_image=DestroyImage(threshold_image);
216 return((Image *) NULL);
217 }
218 /*
219 Local adaptive threshold.
220 */
221 status=MagickTrue;
222 progress=0;
223 GetMagickPixelPacket(image,&zero);
224 number_pixels=(MagickRealType) (width*height);
225 image_view=AcquireVirtualCacheView(image,exception);
226 threshold_view=AcquireAuthenticCacheView(threshold_image,exception);
227#if defined(MAGICKCORE_OPENMP_SUPPORT)
228 #pragma omp parallel for schedule(static) shared(progress,status) \
229 magick_number_threads(image,threshold_image,image->rows,1)
230#endif
231 for (y=0; y < (ssize_t) image->rows; y++)
232 {
233 MagickBooleanType
234 sync;
235
237 channel_bias,
238 channel_sum;
239
240 const IndexPacket
241 *magick_restrict indexes;
242
243 const PixelPacket
244 *magick_restrict p,
245 *magick_restrict r;
246
247 IndexPacket
248 *magick_restrict threshold_indexes;
249
251 *magick_restrict q;
252
253 ssize_t
254 x;
255
256 ssize_t
257 u,
258 v;
259
260 if (status == MagickFalse)
261 continue;
262 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
263 height/2L,image->columns+width,height,exception);
264 q=GetCacheViewAuthenticPixels(threshold_view,0,y,threshold_image->columns,1,
265 exception);
266 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
267 {
268 status=MagickFalse;
269 continue;
270 }
271 indexes=GetCacheViewVirtualIndexQueue(image_view);
272 threshold_indexes=GetCacheViewAuthenticIndexQueue(threshold_view);
273 channel_bias=zero;
274 channel_sum=zero;
275 r=p;
276 for (v=0; v < (ssize_t) height; v++)
277 {
278 for (u=0; u < (ssize_t) width; u++)
279 {
280 if (u == (ssize_t) (width-1))
281 {
282 channel_bias.red+=(MagickRealType) r[u].red;
283 channel_bias.green+=(MagickRealType) r[u].green;
284 channel_bias.blue+=(MagickRealType) r[u].blue;
285 channel_bias.opacity+=(MagickRealType) r[u].opacity;
286 if (image->colorspace == CMYKColorspace)
287 channel_bias.index=(MagickRealType)
288 GetPixelIndex(indexes+(r-p)+u);
289 }
290 channel_sum.red+=(MagickRealType) r[u].red;
291 channel_sum.green+=(MagickRealType) r[u].green;
292 channel_sum.blue+=(MagickRealType) r[u].blue;
293 channel_sum.opacity+=(MagickRealType) r[u].opacity;
294 if (image->colorspace == CMYKColorspace)
295 channel_sum.index=(MagickRealType) GetPixelIndex(indexes+(r-p)+u);
296 }
297 r+=(ptrdiff_t) image->columns+width;
298 }
299 for (x=0; x < (ssize_t) image->columns; x++)
300 {
302 mean;
303
304 mean=zero;
305 r=p;
306 channel_sum.red-=channel_bias.red;
307 channel_sum.green-=channel_bias.green;
308 channel_sum.blue-=channel_bias.blue;
309 channel_sum.opacity-=channel_bias.opacity;
310 channel_sum.index-=channel_bias.index;
311 channel_bias=zero;
312 for (v=0; v < (ssize_t) height; v++)
313 {
314 channel_bias.red+=(MagickRealType) r[0].red;
315 channel_bias.green+=(MagickRealType) r[0].green;
316 channel_bias.blue+=(MagickRealType) r[0].blue;
317 channel_bias.opacity+=(MagickRealType) r[0].opacity;
318 if (image->colorspace == CMYKColorspace)
319 channel_bias.index=(MagickRealType) GetPixelIndex(indexes+x+(r-p)+0);
320 channel_sum.red+=(MagickRealType) r[width-1].red;
321 channel_sum.green+=(MagickRealType) r[width-1].green;
322 channel_sum.blue+=(MagickRealType) r[width-1].blue;
323 channel_sum.opacity+=(MagickRealType) r[width-1].opacity;
324 if (image->colorspace == CMYKColorspace)
325 channel_sum.index=(MagickRealType) GetPixelIndex(indexes+x+(r-p)+
326 width-1);
327 r+=(ptrdiff_t) image->columns+width;
328 }
329 mean.red=(MagickRealType) (channel_sum.red/number_pixels+offset);
330 mean.green=(MagickRealType) (channel_sum.green/number_pixels+offset);
331 mean.blue=(MagickRealType) (channel_sum.blue/number_pixels+offset);
332 mean.opacity=(MagickRealType) (channel_sum.opacity/number_pixels+offset);
333 if (image->colorspace == CMYKColorspace)
334 mean.index=(MagickRealType) (channel_sum.index/number_pixels+offset);
335 SetPixelRed(q,((MagickRealType) GetPixelRed(q) <= mean.red) ?
336 0 : QuantumRange);
337 SetPixelGreen(q,((MagickRealType) GetPixelGreen(q) <= mean.green) ?
338 0 : QuantumRange);
339 SetPixelBlue(q,((MagickRealType) GetPixelBlue(q) <= mean.blue) ?
340 0 : QuantumRange);
341 SetPixelOpacity(q,((MagickRealType) GetPixelOpacity(q) <= mean.opacity) ?
342 0 : QuantumRange);
343 if (image->colorspace == CMYKColorspace)
344 SetPixelIndex(threshold_indexes+x,(((MagickRealType) GetPixelIndex(
345 threshold_indexes+x) <= mean.index) ? 0 : QuantumRange));
346 p++;
347 q++;
348 }
349 sync=SyncCacheViewAuthenticPixels(threshold_view,exception);
350 if (sync == MagickFalse)
351 status=MagickFalse;
352 if (image->progress_monitor != (MagickProgressMonitor) NULL)
353 {
354 MagickBooleanType
355 proceed;
356
357#if defined(MAGICKCORE_OPENMP_SUPPORT)
358 #pragma omp atomic
359#endif
360 progress++;
361 proceed=SetImageProgress(image,ThresholdImageTag,progress,image->rows);
362 if (proceed == MagickFalse)
363 status=MagickFalse;
364 }
365 }
366 threshold_view=DestroyCacheView(threshold_view);
367 image_view=DestroyCacheView(image_view);
368 if (status == MagickFalse)
369 threshold_image=DestroyImage(threshold_image);
370 return(threshold_image);
371}
372
373/*
374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
375% %
376% %
377% %
378% A u t o T h r e s h o l d I m a g e %
379% %
380% %
381% %
382%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
383%
384% AutoThresholdImage() automatically performs image thresholding
385% dependent on which method you specify.
386%
387% The format of the AutoThresholdImage method is:
388%
389% MagickBooleanType AutoThresholdImage(Image *image,
390% const AutoThresholdMethod method,ExceptionInfo *exception)
391%
392% A description of each parameter follows:
393%
394% o image: The image to auto-threshold.
395%
396% o method: choose from Kapur, OTSU, or Triangle.
397%
398% o exception: return any errors or warnings in this structure.
399%
400*/
401
402static double KapurThreshold(const Image *image,const double *histogram,
403 ExceptionInfo *exception)
404{
405#define MaxIntensity 255
406
407 double
408 *black_entropy,
409 *cumulative_histogram,
410 entropy,
411 epsilon,
412 maximum_entropy,
413 *white_entropy;
414
415 ssize_t
416 i,
417 j;
418
419 size_t
420 threshold;
421
422 /*
423 Compute optimal threshold from the entropy of the histogram.
424 */
425 cumulative_histogram=(double *) AcquireQuantumMemory(MaxIntensity+1UL,
426 sizeof(*cumulative_histogram));
427 black_entropy=(double *) AcquireQuantumMemory(MaxIntensity+1UL,
428 sizeof(*black_entropy));
429 white_entropy=(double *) AcquireQuantumMemory(MaxIntensity+1UL,
430 sizeof(*white_entropy));
431 if ((cumulative_histogram == (double *) NULL) ||
432 (black_entropy == (double *) NULL) || (white_entropy == (double *) NULL))
433 {
434 if (white_entropy != (double *) NULL)
435 white_entropy=(double *) RelinquishMagickMemory(white_entropy);
436 if (black_entropy != (double *) NULL)
437 black_entropy=(double *) RelinquishMagickMemory(black_entropy);
438 if (cumulative_histogram != (double *) NULL)
439 cumulative_histogram=(double *)
440 RelinquishMagickMemory(cumulative_histogram);
441 (void) ThrowMagickException(exception,GetMagickModule(),
442 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
443 return(-1.0);
444 }
445 /*
446 Entropy for black and white parts of the histogram.
447 */
448 cumulative_histogram[0]=histogram[0];
449 for (i=1; i <= MaxIntensity; i++)
450 cumulative_histogram[i]=cumulative_histogram[i-1]+histogram[i];
451 epsilon=MagickMinimumValue;
452 for (j=0; j <= MaxIntensity; j++)
453 {
454 /*
455 Black entropy.
456 */
457 black_entropy[j]=0.0;
458 if (cumulative_histogram[j] > epsilon)
459 {
460 entropy=0.0;
461 for (i=0; i <= j; i++)
462 if (histogram[i] > epsilon)
463 entropy-=histogram[i]/cumulative_histogram[j]*
464 log(histogram[i]/cumulative_histogram[j]);
465 black_entropy[j]=entropy;
466 }
467 /*
468 White entropy.
469 */
470 white_entropy[j]=0.0;
471 if ((1.0-cumulative_histogram[j]) > epsilon)
472 {
473 entropy=0.0;
474 for (i=j+1; i <= MaxIntensity; i++)
475 if (histogram[i] > epsilon)
476 entropy-=histogram[i]/(1.0-cumulative_histogram[j])*
477 log(histogram[i]/(1.0-cumulative_histogram[j]));
478 white_entropy[j]=entropy;
479 }
480 }
481 /*
482 Find histogram bin with maximum entropy.
483 */
484 maximum_entropy=black_entropy[0]+white_entropy[0];
485 threshold=0;
486 for (j=1; j <= MaxIntensity; j++)
487 if ((black_entropy[j]+white_entropy[j]) > maximum_entropy)
488 {
489 maximum_entropy=black_entropy[j]+white_entropy[j];
490 threshold=(size_t) j;
491 }
492 /*
493 Free resources.
494 */
495 white_entropy=(double *) RelinquishMagickMemory(white_entropy);
496 black_entropy=(double *) RelinquishMagickMemory(black_entropy);
497 cumulative_histogram=(double *) RelinquishMagickMemory(cumulative_histogram);
498 return(100.0*threshold/MaxIntensity);
499}
500
501static double OTSUThreshold(const Image *image,const double *histogram,
502 ExceptionInfo *exception)
503{
504 double
505 max_sigma,
506 *myu,
507 *omega,
508 *probability,
509 *sigma,
510 threshold;
511
512 ssize_t
513 i;
514
515 /*
516 Compute optimal threshold from maximization of inter-class variance.
517 */
518 myu=(double *) AcquireQuantumMemory(MaxIntensity+1UL,sizeof(*myu));
519 omega=(double *) AcquireQuantumMemory(MaxIntensity+1UL,sizeof(*omega));
520 probability=(double *) AcquireQuantumMemory(MaxIntensity+1UL,
521 sizeof(*probability));
522 sigma=(double *) AcquireQuantumMemory(MaxIntensity+1UL,sizeof(*sigma));
523 if ((myu == (double *) NULL) || (omega == (double *) NULL) ||
524 (probability == (double *) NULL) || (sigma == (double *) NULL))
525 {
526 if (sigma != (double *) NULL)
527 sigma=(double *) RelinquishMagickMemory(sigma);
528 if (probability != (double *) NULL)
529 probability=(double *) RelinquishMagickMemory(probability);
530 if (omega != (double *) NULL)
531 omega=(double *) RelinquishMagickMemory(omega);
532 if (myu != (double *) NULL)
533 myu=(double *) RelinquishMagickMemory(myu);
534 (void) ThrowMagickException(exception,GetMagickModule(),
535 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
536 return(-1.0);
537 }
538 /*
539 Calculate probability density.
540 */
541 for (i=0; i <= (ssize_t) MaxIntensity; i++)
542 probability[i]=histogram[i];
543 /*
544 Generate probability of graylevels and mean value for separation.
545 */
546 omega[0]=probability[0];
547 myu[0]=0.0;
548 for (i=1; i <= (ssize_t) MaxIntensity; i++)
549 {
550 omega[i]=omega[i-1]+probability[i];
551 myu[i]=myu[i-1]+i*probability[i];
552 }
553 /*
554 Sigma maximization: inter-class variance and compute optimal threshold.
555 */
556 threshold=0;
557 max_sigma=0.0;
558 for (i=0; i < (ssize_t) MaxIntensity; i++)
559 {
560 sigma[i]=0.0;
561 if ((omega[i] != 0.0) && (omega[i] != 1.0))
562 sigma[i]=pow(myu[MaxIntensity]*omega[i]-myu[i],2.0)/(omega[i]*(1.0-
563 omega[i]));
564 if (sigma[i] > max_sigma)
565 {
566 max_sigma=sigma[i];
567 threshold=(double) i;
568 }
569 }
570 /*
571 Free resources.
572 */
573 myu=(double *) RelinquishMagickMemory(myu);
574 omega=(double *) RelinquishMagickMemory(omega);
575 probability=(double *) RelinquishMagickMemory(probability);
576 sigma=(double *) RelinquishMagickMemory(sigma);
577 return(100.0*threshold/MaxIntensity);
578}
579
580static double TriangleThreshold(const double *histogram)
581{
582 double
583 a,
584 b,
585 c,
586 count,
587 distance,
588 inverse_ratio,
589 max_distance,
590 segment,
591 x1,
592 x2,
593 y1,
594 y2;
595
596 ssize_t
597 i;
598
599 ssize_t
600 end,
601 max,
602 start,
603 threshold;
604
605 /*
606 Compute optimal threshold with triangle algorithm.
607 */
608 start=0; /* find start bin, first bin not zero count */
609 for (i=0; i <= (ssize_t) MaxIntensity; i++)
610 if (histogram[i] > 0.0)
611 {
612 start=i;
613 break;
614 }
615 end=0; /* find end bin, last bin not zero count */
616 for (i=(ssize_t) MaxIntensity; i >= 0; i--)
617 if (histogram[i] > 0.0)
618 {
619 end=i;
620 break;
621 }
622 max=0; /* find max bin, bin with largest count */
623 count=0.0;
624 for (i=0; i <= (ssize_t) MaxIntensity; i++)
625 if (histogram[i] > count)
626 {
627 max=i;
628 count=histogram[i];
629 }
630 /*
631 Compute threshold at split point.
632 */
633 x1=(double) max;
634 y1=histogram[max];
635 x2=(double) end;
636 if ((max-start) >= (end-max))
637 x2=(double) start;
638 y2=0.0;
639 a=y1-y2;
640 b=x2-x1;
641 c=(-1.0)*(a*x1+b*y1);
642 inverse_ratio=1.0/sqrt(a*a+b*b+c*c);
643 threshold=0;
644 max_distance=0.0;
645 if (x2 == (double) start)
646 for (i=start; i < max; i++)
647 {
648 segment=inverse_ratio*(a*i+b*histogram[i]+c);
649 distance=sqrt(segment*segment);
650 if ((distance > max_distance) && (segment > 0.0))
651 {
652 threshold=i;
653 max_distance=distance;
654 }
655 }
656 else
657 for (i=end; i > max; i--)
658 {
659 segment=inverse_ratio*(a*i+b*histogram[i]+c);
660 distance=sqrt(segment*segment);
661 if ((distance > max_distance) && (segment < 0.0))
662 {
663 threshold=i;
664 max_distance=distance;
665 }
666 }
667 return(100.0*threshold/MaxIntensity);
668}
669
670MagickExport MagickBooleanType AutoThresholdImage(Image *image,
671 const AutoThresholdMethod method,ExceptionInfo *exception)
672{
674 *image_view;
675
676 char
677 property[MagickPathExtent];
678
679 const char
680 *artifact;
681
682 double
683 gamma,
684 *histogram,
685 sum,
686 threshold;
687
688 MagickBooleanType
689 status;
690
691 ssize_t
692 i;
693
694 ssize_t
695 y;
696
697 /*
698 Form histogram.
699 */
700 assert(image != (Image *) NULL);
701 assert(image->signature == MagickCoreSignature);
702 if (IsEventLogging() != MagickFalse)
703 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
704 histogram=(double *) AcquireQuantumMemory(MaxIntensity+1UL,
705 sizeof(*histogram));
706 if (histogram == (double *) NULL)
707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
708 image->filename);
709 status=MagickTrue;
710 (void) memset(histogram,0,(MaxIntensity+1UL)*sizeof(*histogram));
711 image_view=AcquireVirtualCacheView(image,exception);
712 for (y=0; y < (ssize_t) image->rows; y++)
713 {
714 const PixelPacket
715 *magick_restrict p;
716
717 ssize_t
718 x;
719
720 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
721 if (p == (const PixelPacket *) NULL)
722 break;
723 for (x=0; x < (ssize_t) image->columns; x++)
724 {
725 double intensity = GetPixelIntensity(image,p);
726 histogram[ScaleQuantumToChar(ClampToQuantum(intensity))]++;
727 p++;
728 }
729 }
730 image_view=DestroyCacheView(image_view);
731 /*
732 Normalize histogram.
733 */
734 sum=0.0;
735 for (i=0; i <= (ssize_t) MaxIntensity; i++)
736 sum+=histogram[i];
737 gamma=PerceptibleReciprocal(sum);
738 for (i=0; i <= (ssize_t) MaxIntensity; i++)
739 histogram[i]=gamma*histogram[i];
740 /*
741 Discover threshold from histogram.
742 */
743 switch (method)
744 {
745 case KapurThresholdMethod:
746 {
747 threshold=KapurThreshold(image,histogram,exception);
748 break;
749 }
750 case OTSUThresholdMethod:
751 default:
752 {
753 threshold=OTSUThreshold(image,histogram,exception);
754 break;
755 }
756 case TriangleThresholdMethod:
757 {
758 threshold=TriangleThreshold(histogram);
759 break;
760 }
761 }
762 histogram=(double *) RelinquishMagickMemory(histogram);
763 if (threshold < 0.0)
764 status=MagickFalse;
765 if (status == MagickFalse)
766 return(MagickFalse);
767 /*
768 Threshold image.
769 */
770 (void) FormatLocaleString(property,MagickPathExtent,"%g%%",threshold);
771 (void) SetImageProperty(image,"auto-threshold:threshold",property);
772 artifact=GetImageArtifact(image,"threshold:verbose");
773 if (IsStringTrue(artifact) != MagickFalse)
774 (void) FormatLocaleFile(stdout,"%.*g%%\n",GetMagickPrecision(),threshold);
775 return(BilevelImage(image,(MagickRealType) QuantumRange*threshold/100.0));
776}
777
778/*
779%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
780% %
781% %
782% %
783% B i l e v e l I m a g e %
784% %
785% %
786% %
787%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
788%
789% BilevelImage() changes the value of individual pixels based on the
790% intensity of each pixel channel. The result is a high-contrast image.
791%
792% More precisely each channel value of the image is 'thresholded' so that if
793% it is equal to or less than the given value it is set to zero, while any
794% value greater than that give is set to it maximum or QuantumRange.
795%
796% This function is what is used to implement the "-threshold" operator for
797% the command line API.
798%
799% If the default channel setting is given the image is thresholded using just
800% the gray 'intensity' of the image, rather than the individual channels.
801%
802% The format of the BilevelImageChannel method is:
803%
804% MagickBooleanType BilevelImage(Image *image,const double threshold)
805% MagickBooleanType BilevelImageChannel(Image *image,
806% const ChannelType channel,const double threshold)
807%
808% A description of each parameter follows:
809%
810% o image: the image.
811%
812% o channel: the channel type.
813%
814% o threshold: define the threshold values.
815%
816% Aside: You can get the same results as operator using LevelImageChannels()
817% with the 'threshold' value for both the black_point and the white_point.
818%
819*/
820
821MagickExport MagickBooleanType BilevelImage(Image *image,const double threshold)
822{
823 MagickBooleanType
824 status;
825
826 status=BilevelImageChannel(image,DefaultChannels,threshold);
827 return(status);
828}
829
830MagickExport MagickBooleanType BilevelImageChannel(Image *image,
831 const ChannelType channel,const double threshold)
832{
833#define ThresholdImageTag "Threshold/Image"
834
836 *image_view;
837
839 *exception;
840
841 MagickBooleanType
842 status;
843
844 MagickOffsetType
845 progress;
846
847 ssize_t
848 y;
849
850 assert(image != (Image *) NULL);
851 assert(image->signature == MagickCoreSignature);
852 if (IsEventLogging() != MagickFalse)
853 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
854 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
855 return(MagickFalse);
856 if (IsGrayColorspace(image->colorspace) == MagickFalse)
857 (void) SetImageColorspace(image,sRGBColorspace);
858 /*
859 Bilevel threshold image.
860 */
861 status=MagickTrue;
862 progress=0;
863 exception=(&image->exception);
864 image_view=AcquireAuthenticCacheView(image,exception);
865#if defined(MAGICKCORE_OPENMP_SUPPORT)
866 #pragma omp parallel for schedule(static) shared(progress,status) \
867 magick_number_threads(image,image,image->rows,2)
868#endif
869 for (y=0; y < (ssize_t) image->rows; y++)
870 {
871 IndexPacket
872 *magick_restrict indexes;
873
874 ssize_t
875 x;
876
878 *magick_restrict q;
879
880 if (status == MagickFalse)
881 continue;
882 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
883 if (q == (PixelPacket *) NULL)
884 {
885 status=MagickFalse;
886 continue;
887 }
888 indexes=GetCacheViewAuthenticIndexQueue(image_view);
889 if ((channel & SyncChannels) != 0)
890 {
891 for (x=0; x < (ssize_t) image->columns; x++)
892 {
893 SetPixelRed(q,GetPixelIntensity(image,q) <= threshold ? 0 :
894 QuantumRange);
895 SetPixelGreen(q,GetPixelRed(q));
896 SetPixelBlue(q,GetPixelRed(q));
897 q++;
898 }
899 }
900 else
901 for (x=0; x < (ssize_t) image->columns; x++)
902 {
903 if ((channel & RedChannel) != 0)
904 SetPixelRed(q,(MagickRealType) GetPixelRed(q) <= threshold ? 0 :
905 QuantumRange);
906 if ((channel & GreenChannel) != 0)
907 SetPixelGreen(q,(MagickRealType) GetPixelGreen(q) <= threshold ? 0 :
908 QuantumRange);
909 if ((channel & BlueChannel) != 0)
910 SetPixelBlue(q,(MagickRealType) GetPixelBlue(q) <= threshold ? 0 :
911 QuantumRange);
912 if ((channel & OpacityChannel) != 0)
913 {
914 if (image->matte == MagickFalse)
915 SetPixelOpacity(q,(MagickRealType) GetPixelOpacity(q) <=
916 threshold ? 0 : QuantumRange);
917 else
918 SetPixelAlpha(q,(MagickRealType) GetPixelAlpha(q) <= threshold ?
919 OpaqueOpacity : TransparentOpacity);
920 }
921 if (((channel & IndexChannel) != 0) &&
922 (image->colorspace == CMYKColorspace))
923 SetPixelIndex(indexes+x,(MagickRealType) GetPixelIndex(indexes+x) <=
924 threshold ? 0 : QuantumRange);
925 q++;
926 }
927 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
928 status=MagickFalse;
929 if (image->progress_monitor != (MagickProgressMonitor) NULL)
930 {
931 MagickBooleanType
932 proceed;
933
934#if defined(MAGICKCORE_OPENMP_SUPPORT)
935 #pragma omp atomic
936#endif
937 progress++;
938 proceed=SetImageProgress(image,ThresholdImageTag,progress,image->rows);
939 if (proceed == MagickFalse)
940 status=MagickFalse;
941 }
942 }
943 image_view=DestroyCacheView(image_view);
944 return(status);
945}
946
947/*
948%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
949% %
950% %
951% %
952% B l a c k T h r e s h o l d I m a g e %
953% %
954% %
955% %
956%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
957%
958% BlackThresholdImage() is like ThresholdImage() but forces all pixels below
959% the threshold into black while leaving all pixels at or above the threshold
960% unchanged.
961%
962% The format of the BlackThresholdImage method is:
963%
964% MagickBooleanType BlackThresholdImage(Image *image,const char *threshold)
965% MagickBooleanType BlackThresholdImageChannel(Image *image,
966% const ChannelType channel,const char *threshold,
967% ExceptionInfo *exception)
968%
969% A description of each parameter follows:
970%
971% o image: the image.
972%
973% o channel: the channel or channels to be thresholded.
974%
975% o threshold: Define the threshold value.
976%
977% o exception: return any errors or warnings in this structure.
978%
979*/
980MagickExport MagickBooleanType BlackThresholdImage(Image *image,
981 const char *threshold)
982{
983 MagickBooleanType
984 status;
985
986 status=BlackThresholdImageChannel(image,DefaultChannels,threshold,
987 &image->exception);
988 return(status);
989}
990
991MagickExport MagickBooleanType BlackThresholdImageChannel(Image *image,
992 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
993{
994#define ThresholdImageTag "Threshold/Image"
995
997 *image_view;
998
1000 geometry_info;
1001
1002 MagickBooleanType
1003 status;
1004
1005 MagickOffsetType
1006 progress;
1007
1009 threshold;
1010
1011 MagickStatusType
1012 flags;
1013
1014 ssize_t
1015 y;
1016
1017 assert(image != (Image *) NULL);
1018 assert(image->signature == MagickCoreSignature);
1019 if (IsEventLogging() != MagickFalse)
1020 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1021 if (thresholds == (const char *) NULL)
1022 return(MagickTrue);
1023 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1024 return(MagickFalse);
1025 GetMagickPixelPacket(image,&threshold);
1026 flags=ParseGeometry(thresholds,&geometry_info);
1027 threshold.red=geometry_info.rho;
1028 threshold.green=geometry_info.sigma;
1029 if ((flags & SigmaValue) == 0)
1030 threshold.green=threshold.red;
1031 threshold.blue=geometry_info.xi;
1032 if ((flags & XiValue) == 0)
1033 threshold.blue=threshold.red;
1034 threshold.opacity=geometry_info.psi;
1035 if ((flags & PsiValue) == 0)
1036 threshold.opacity=threshold.red;
1037 threshold.index=geometry_info.chi;
1038 if ((flags & ChiValue) == 0)
1039 threshold.index=threshold.red;
1040 if ((flags & PercentValue) != 0)
1041 {
1042 threshold.red*=(MagickRealType) QuantumRange/100.0;
1043 threshold.green*=(MagickRealType) QuantumRange/100.0;
1044 threshold.blue*=(MagickRealType) QuantumRange/100.0;
1045 threshold.opacity*=(MagickRealType) QuantumRange/100.0;
1046 threshold.index*=(MagickRealType) QuantumRange/100.0;
1047 }
1048 if ((IsMagickGray(&threshold) == MagickFalse) &&
1049 (IsGrayColorspace(image->colorspace) != MagickFalse))
1050 (void) SetImageColorspace(image,sRGBColorspace);
1051 /*
1052 Black threshold image.
1053 */
1054 status=MagickTrue;
1055 progress=0;
1056 image_view=AcquireAuthenticCacheView(image,exception);
1057#if defined(MAGICKCORE_OPENMP_SUPPORT)
1058 #pragma omp parallel for schedule(static) shared(progress,status) \
1059 magick_number_threads(image,image,image->rows,1)
1060#endif
1061 for (y=0; y < (ssize_t) image->rows; y++)
1062 {
1063 IndexPacket
1064 *magick_restrict indexes;
1065
1066 ssize_t
1067 x;
1068
1070 *magick_restrict q;
1071
1072 if (status == MagickFalse)
1073 continue;
1074 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1075 if (q == (PixelPacket *) NULL)
1076 {
1077 status=MagickFalse;
1078 continue;
1079 }
1080 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1081 for (x=0; x < (ssize_t) image->columns; x++)
1082 {
1083 if (((channel & RedChannel) != 0) &&
1084 ((MagickRealType) GetPixelRed(q) < threshold.red))
1085 SetPixelRed(q,0);
1086 if (((channel & GreenChannel) != 0) &&
1087 ((MagickRealType) GetPixelGreen(q) < threshold.green))
1088 SetPixelGreen(q,0);
1089 if (((channel & BlueChannel) != 0) &&
1090 ((MagickRealType) GetPixelBlue(q) < threshold.blue))
1091 SetPixelBlue(q,0);
1092 if (((channel & OpacityChannel) != 0) &&
1093 ((MagickRealType) GetPixelOpacity(q) < threshold.opacity))
1094 SetPixelOpacity(q,0);
1095 if (((channel & IndexChannel) != 0) &&
1096 (image->colorspace == CMYKColorspace) &&
1097 ((MagickRealType) GetPixelIndex(indexes+x) < threshold.index))
1098 SetPixelIndex(indexes+x,0);
1099 q++;
1100 }
1101 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1102 status=MagickFalse;
1103 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1104 {
1105 MagickBooleanType
1106 proceed;
1107
1108#if defined(MAGICKCORE_OPENMP_SUPPORT)
1109 #pragma omp atomic
1110#endif
1111 progress++;
1112 proceed=SetImageProgress(image,ThresholdImageTag,progress,image->rows);
1113 if (proceed == MagickFalse)
1114 status=MagickFalse;
1115 }
1116 }
1117 image_view=DestroyCacheView(image_view);
1118 return(status);
1119}
1120
1121/*
1122%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1123% %
1124% %
1125% %
1126% C l a m p I m a g e %
1127% %
1128% %
1129% %
1130%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1131%
1132% ClampImage() set each pixel whose value is below zero to zero and any the
1133% pixel whose value is above the quantum range to the quantum range (e.g.
1134% 65535) otherwise the pixel value remains unchanged.
1135%
1136% The format of the ClampImageChannel method is:
1137%
1138% MagickBooleanType ClampImage(Image *image)
1139% MagickBooleanType ClampImageChannel(Image *image,
1140% const ChannelType channel)
1141%
1142% A description of each parameter follows:
1143%
1144% o image: the image.
1145%
1146% o channel: the channel type.
1147%
1148*/
1149
1150MagickExport MagickBooleanType ClampImage(Image *image)
1151{
1152 MagickBooleanType
1153 status;
1154
1155 status=ClampImageChannel(image,DefaultChannels);
1156 return(status);
1157}
1158
1159MagickExport MagickBooleanType ClampImageChannel(Image *image,
1160 const ChannelType channel)
1161{
1162#define ClampImageTag "Clamp/Image"
1163
1164 CacheView
1165 *image_view;
1166
1168 *exception;
1169
1170 MagickBooleanType
1171 status;
1172
1173 MagickOffsetType
1174 progress;
1175
1176 ssize_t
1177 y;
1178
1179 assert(image != (Image *) NULL);
1180 assert(image->signature == MagickCoreSignature);
1181 if (IsEventLogging() != MagickFalse)
1182 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1183 if (image->storage_class == PseudoClass)
1184 {
1185 ssize_t
1186 i;
1187
1189 *magick_restrict q;
1190
1191 q=image->colormap;
1192 for (i=0; i < (ssize_t) image->colors; i++)
1193 {
1194 SetPixelRed(q,ClampPixel((MagickRealType) GetPixelRed(q)));
1195 SetPixelGreen(q,ClampPixel((MagickRealType) GetPixelGreen(q)));
1196 SetPixelBlue(q,ClampPixel((MagickRealType) GetPixelBlue(q)));
1197 SetPixelOpacity(q,ClampPixel((MagickRealType) GetPixelOpacity(q)));
1198 q++;
1199 }
1200 return(SyncImage(image));
1201 }
1202 /*
1203 Clamp image.
1204 */
1205 status=MagickTrue;
1206 progress=0;
1207 exception=(&image->exception);
1208 image_view=AcquireAuthenticCacheView(image,exception);
1209#if defined(MAGICKCORE_OPENMP_SUPPORT)
1210 #pragma omp parallel for schedule(static) shared(progress,status) \
1211 magick_number_threads(image,image,image->rows,2)
1212#endif
1213 for (y=0; y < (ssize_t) image->rows; y++)
1214 {
1215 IndexPacket
1216 *magick_restrict indexes;
1217
1218 ssize_t
1219 x;
1220
1222 *magick_restrict q;
1223
1224 if (status == MagickFalse)
1225 continue;
1226 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1227 if (q == (PixelPacket *) NULL)
1228 {
1229 status=MagickFalse;
1230 continue;
1231 }
1232 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1233 for (x=0; x < (ssize_t) image->columns; x++)
1234 {
1235 if ((channel & RedChannel) != 0)
1236 SetPixelRed(q,ClampPixel((MagickRealType) GetPixelRed(q)));
1237 if ((channel & GreenChannel) != 0)
1238 SetPixelGreen(q,ClampPixel((MagickRealType) GetPixelGreen(q)));
1239 if ((channel & BlueChannel) != 0)
1240 SetPixelBlue(q,ClampPixel((MagickRealType) GetPixelBlue(q)));
1241 if ((channel & OpacityChannel) != 0)
1242 SetPixelOpacity(q,ClampPixel((MagickRealType) GetPixelOpacity(q)));
1243 if (((channel & IndexChannel) != 0) &&
1244 (image->colorspace == CMYKColorspace))
1245 SetPixelIndex(indexes+x,ClampPixel((MagickRealType) GetPixelIndex(
1246 indexes+x)));
1247 q++;
1248 }
1249 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1250 status=MagickFalse;
1251 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1252 {
1253 MagickBooleanType
1254 proceed;
1255
1256#if defined(MAGICKCORE_OPENMP_SUPPORT)
1257 #pragma omp atomic
1258#endif
1259 progress++;
1260 proceed=SetImageProgress(image,ClampImageTag,progress,image->rows);
1261 if (proceed == MagickFalse)
1262 status=MagickFalse;
1263 }
1264 }
1265 image_view=DestroyCacheView(image_view);
1266 return(status);
1267}
1268
1269/*
1270%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1271% %
1272% %
1273% %
1274% D e s t r o y T h r e s h o l d M a p %
1275% %
1276% %
1277% %
1278%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1279%
1280% DestroyThresholdMap() de-allocate the given ThresholdMap
1281%
1282% The format of the ListThresholdMaps method is:
1283%
1284% ThresholdMap *DestroyThresholdMap(Threshold *map)
1285%
1286% A description of each parameter follows.
1287%
1288% o map: Pointer to the Threshold map to destroy
1289%
1290*/
1291MagickExport ThresholdMap *DestroyThresholdMap(ThresholdMap *map)
1292{
1293 assert(map != (ThresholdMap *) NULL);
1294 if (map->map_id != (char *) NULL)
1295 map->map_id=DestroyString(map->map_id);
1296 if (map->description != (char *) NULL)
1297 map->description=DestroyString(map->description);
1298 if (map->levels != (ssize_t *) NULL)
1299 map->levels=(ssize_t *) RelinquishMagickMemory(map->levels);
1300 map=(ThresholdMap *) RelinquishMagickMemory(map);
1301 return(map);
1302}
1303
1304/*
1305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1306% %
1307% %
1308% %
1309+ G e t T h r e s h o l d M a p F i l e %
1310% %
1311% %
1312% %
1313%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1314%
1315% GetThresholdMapFile() look for a given threshold map name or alias in the
1316% given XML file data, and return the allocated the map when found.
1317%
1318% The format of the GetThresholdMapFile method is:
1319%
1320% ThresholdMap *GetThresholdMap(const char *xml,const char *filename,
1321% const char *map_id,ExceptionInfo *exception)
1322%
1323% A description of each parameter follows.
1324%
1325% o xml: The threshold map list in XML format.
1326%
1327% o filename: The threshold map XML filename.
1328%
1329% o map_id: ID of the map to look for in XML list.
1330%
1331% o exception: return any errors or warnings in this structure.
1332%
1333*/
1334MagickExport ThresholdMap *GetThresholdMapFile(const char *xml,
1335 const char *filename,const char *map_id,ExceptionInfo *exception)
1336{
1337 const char
1338 *attribute,
1339 *content;
1340
1341 double
1342 value;
1343
1345 *map;
1346
1348 *description,
1349 *levels,
1350 *threshold,
1351 *thresholds;
1352
1353 map = (ThresholdMap *) NULL;
1354 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
1355 "Loading threshold map file \"%s\" ...",filename);
1356 thresholds=NewXMLTree(xml,exception);
1357 if ( thresholds == (XMLTreeInfo *) NULL )
1358 return(map);
1359 for (threshold = GetXMLTreeChild(thresholds,"threshold");
1360 threshold != (XMLTreeInfo *) NULL;
1361 threshold = GetNextXMLTreeTag(threshold) )
1362 {
1363 attribute=GetXMLTreeAttribute(threshold, "map");
1364 if ((attribute != (char *) NULL) && (LocaleCompare(map_id,attribute) == 0))
1365 break;
1366 attribute=GetXMLTreeAttribute(threshold, "alias");
1367 if ((attribute != (char *) NULL) && (LocaleCompare(map_id,attribute) == 0))
1368 break;
1369 }
1370 if (threshold == (XMLTreeInfo *) NULL)
1371 {
1372 thresholds=DestroyXMLTree(thresholds);
1373 return(map);
1374 }
1375 description=GetXMLTreeChild(threshold,"description");
1376 if (description == (XMLTreeInfo *) NULL)
1377 {
1378 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1379 "XmlMissingElement", "<description>, map \"%s\"", map_id);
1380 thresholds=DestroyXMLTree(thresholds);
1381 return(map);
1382 }
1383 levels=GetXMLTreeChild(threshold,"levels");
1384 if (levels == (XMLTreeInfo *) NULL)
1385 {
1386 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1387 "XmlMissingElement", "<levels>, map \"%s\"", map_id);
1388 thresholds=DestroyXMLTree(thresholds);
1389 return(map);
1390 }
1391 /*
1392 The map has been found -- allocate a Threshold Map to return
1393 */
1394 map=(ThresholdMap *) AcquireMagickMemory(sizeof(ThresholdMap));
1395 if (map == (ThresholdMap *) NULL)
1396 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
1397 map->map_id=(char *) NULL;
1398 map->description=(char *) NULL;
1399 map->levels=(ssize_t *) NULL;
1400 /*
1401 Assign basic attributeibutes.
1402 */
1403 attribute=GetXMLTreeAttribute(threshold,"map");
1404 if (attribute != (char *) NULL)
1405 map->map_id=ConstantString(attribute);
1406 content=GetXMLTreeContent(description);
1407 if (content != (char *) NULL)
1408 map->description=ConstantString(content);
1409 attribute=GetXMLTreeAttribute(levels,"width");
1410 if (attribute == (char *) NULL)
1411 {
1412 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1413 "XmlMissingAttribute", "<levels width>, map \"%s\"",map_id);
1414 thresholds=DestroyXMLTree(thresholds);
1415 map=DestroyThresholdMap(map);
1416 return(map);
1417 }
1418 map->width=StringToUnsignedLong(attribute);
1419 if (map->width == 0)
1420 {
1421 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1422 "XmlInvalidAttribute", "<levels width>, map \"%s\"", map_id);
1423 thresholds=DestroyXMLTree(thresholds);
1424 map=DestroyThresholdMap(map);
1425 return(map);
1426 }
1427 attribute=GetXMLTreeAttribute(levels,"height");
1428 if (attribute == (char *) NULL)
1429 {
1430 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1431 "XmlMissingAttribute", "<levels height>, map \"%s\"", map_id);
1432 thresholds=DestroyXMLTree(thresholds);
1433 map=DestroyThresholdMap(map);
1434 return(map);
1435 }
1436 map->height=StringToUnsignedLong(attribute);
1437 if (map->height == 0)
1438 {
1439 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1440 "XmlInvalidAttribute", "<levels height>, map \"%s\"", map_id);
1441 thresholds=DestroyXMLTree(thresholds);
1442 map=DestroyThresholdMap(map);
1443 return(map);
1444 }
1445 attribute=GetXMLTreeAttribute(levels, "divisor");
1446 if (attribute == (char *) NULL)
1447 {
1448 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1449 "XmlMissingAttribute", "<levels divisor>, map \"%s\"", map_id);
1450 thresholds=DestroyXMLTree(thresholds);
1451 map=DestroyThresholdMap(map);
1452 return(map);
1453 }
1454 map->divisor=(ssize_t) StringToLong(attribute);
1455 if (map->divisor < 2)
1456 {
1457 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1458 "XmlInvalidAttribute", "<levels divisor>, map \"%s\"", map_id);
1459 thresholds=DestroyXMLTree(thresholds);
1460 map=DestroyThresholdMap(map);
1461 return(map);
1462 }
1463 /*
1464 Allocate theshold levels array.
1465 */
1466 content=GetXMLTreeContent(levels);
1467 if (content == (char *) NULL)
1468 {
1469 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1470 "XmlMissingContent", "<levels>, map \"%s\"", map_id);
1471 thresholds=DestroyXMLTree(thresholds);
1472 map=DestroyThresholdMap(map);
1473 return(map);
1474 }
1475 map->levels=(ssize_t *) AcquireQuantumMemory((size_t) map->width,map->height*
1476 sizeof(*map->levels));
1477 if (map->levels == (ssize_t *) NULL)
1478 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
1479 {
1480 char
1481 *p;
1482
1483 ssize_t
1484 i;
1485
1486 /*
1487 Parse levels into integer array.
1488 */
1489 for (i=0; i< (ssize_t) (map->width*map->height); i++)
1490 {
1491 map->levels[i]=(ssize_t) strtol(content,&p,10);
1492 if (p == content)
1493 {
1494 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1495 "XmlInvalidContent", "<level> too few values, map \"%s\"", map_id);
1496 thresholds=DestroyXMLTree(thresholds);
1497 map=DestroyThresholdMap(map);
1498 return(map);
1499 }
1500 if ((map->levels[i] < 0) || (map->levels[i] > map->divisor))
1501 {
1502 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1503 "XmlInvalidContent", "<level> %.20g out of range, map \"%s\"",
1504 (double) map->levels[i],map_id);
1505 thresholds=DestroyXMLTree(thresholds);
1506 map=DestroyThresholdMap(map);
1507 return(map);
1508 }
1509 content=p;
1510 }
1511 value=(double) strtol(content,&p,10);
1512 (void) value;
1513 if (p != content)
1514 {
1515 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1516 "XmlInvalidContent", "<level> too many values, map \"%s\"", map_id);
1517 thresholds=DestroyXMLTree(thresholds);
1518 map=DestroyThresholdMap(map);
1519 return(map);
1520 }
1521 }
1522 thresholds=DestroyXMLTree(thresholds);
1523 return(map);
1524}
1525
1526/*
1527%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1528% %
1529% %
1530% %
1531% G e t T h r e s h o l d M a p %
1532% %
1533% %
1534% %
1535%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1536%
1537% GetThresholdMap() load and search one or more threshold map files for the
1538% a map matching the given name or aliase.
1539%
1540% The format of the GetThresholdMap method is:
1541%
1542% ThresholdMap *GetThresholdMap(const char *map_id,
1543% ExceptionInfo *exception)
1544%
1545% A description of each parameter follows.
1546%
1547% o map_id: ID of the map to look for.
1548%
1549% o exception: return any errors or warnings in this structure.
1550%
1551*/
1552MagickExport ThresholdMap *GetThresholdMap(const char *map_id,
1553 ExceptionInfo *exception)
1554{
1555 const StringInfo
1556 *option;
1557
1559 *options;
1560
1562 *map;
1563
1564 map=GetThresholdMapFile(BuiltinMap,"built-in",map_id,exception);
1565 if (map != (ThresholdMap *) NULL)
1566 return(map);
1567 options=GetConfigureOptions(ThresholdsFilename,exception);
1568 option=(const StringInfo *) GetNextValueInLinkedList(options);
1569 while (option != (const StringInfo *) NULL)
1570 {
1571 map=GetThresholdMapFile((const char *) GetStringInfoDatum(option),
1572 GetStringInfoPath(option),map_id,exception);
1573 if (map != (ThresholdMap *) NULL)
1574 break;
1575 option=(const StringInfo *) GetNextValueInLinkedList(options);
1576 }
1577 options=DestroyConfigureOptions(options);
1578 return(map);
1579}
1580
1581/*
1582%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1583% %
1584% %
1585% %
1586+ L i s t T h r e s h o l d M a p F i l e %
1587% %
1588% %
1589% %
1590%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1591%
1592% ListThresholdMapFile() lists the threshold maps and their descriptions
1593% in the given XML file data.
1594%
1595% The format of the ListThresholdMaps method is:
1596%
1597% MagickBooleanType ListThresholdMaps(FILE *file,const char*xml,
1598% const char *filename,ExceptionInfo *exception)
1599%
1600% A description of each parameter follows.
1601%
1602% o file: An pointer to the output FILE.
1603%
1604% o xml: The threshold map list in XML format.
1605%
1606% o filename: The threshold map XML filename.
1607%
1608% o exception: return any errors or warnings in this structure.
1609%
1610*/
1611MagickBooleanType ListThresholdMapFile(FILE *file,const char *xml,
1612 const char *filename,ExceptionInfo *exception)
1613{
1614 XMLTreeInfo *thresholds,*threshold,*description;
1615 const char *map,*alias,*content;
1616
1617 assert( xml != (char *) NULL );
1618 assert( file != (FILE *) NULL );
1619
1620 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
1621 "Loading threshold map file \"%s\" ...",filename);
1622 thresholds=NewXMLTree(xml,exception);
1623 if ( thresholds == (XMLTreeInfo *) NULL )
1624 return(MagickFalse);
1625
1626 (void) FormatLocaleFile(file,"%-16s %-12s %s\n","Map","Alias","Description");
1627 (void) FormatLocaleFile(file,
1628 "----------------------------------------------------\n");
1629
1630 for( threshold = GetXMLTreeChild(thresholds,"threshold");
1631 threshold != (XMLTreeInfo *) NULL;
1632 threshold = GetNextXMLTreeTag(threshold) )
1633 {
1634 map = GetXMLTreeAttribute(threshold, "map");
1635 if (map == (char *) NULL) {
1636 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1637 "XmlMissingAttribute", "<map>");
1638 thresholds=DestroyXMLTree(thresholds);
1639 return(MagickFalse);
1640 }
1641 alias = GetXMLTreeAttribute(threshold, "alias");
1642 /* alias is optional, no if test needed */
1643 description=GetXMLTreeChild(threshold,"description");
1644 if ( description == (XMLTreeInfo *) NULL ) {
1645 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1646 "XmlMissingElement", "<description>, map \"%s\"", map);
1647 thresholds=DestroyXMLTree(thresholds);
1648 return(MagickFalse);
1649 }
1650 content=GetXMLTreeContent(description);
1651 if ( content == (char *) NULL ) {
1652 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1653 "XmlMissingContent", "<description>, map \"%s\"", map);
1654 thresholds=DestroyXMLTree(thresholds);
1655 return(MagickFalse);
1656 }
1657 (void) FormatLocaleFile(file,"%-16s %-12s %s\n",map,alias ? alias : "",
1658 content);
1659 }
1660 thresholds=DestroyXMLTree(thresholds);
1661 return(MagickTrue);
1662}
1663
1664/*
1665%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1666% %
1667% %
1668% %
1669% L i s t T h r e s h o l d M a p s %
1670% %
1671% %
1672% %
1673%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1674%
1675% ListThresholdMaps() lists the threshold maps and their descriptions
1676% as defined by "threshold.xml" to a file.
1677%
1678% The format of the ListThresholdMaps method is:
1679%
1680% MagickBooleanType ListThresholdMaps(FILE *file,ExceptionInfo *exception)
1681%
1682% A description of each parameter follows.
1683%
1684% o file: An pointer to the output FILE.
1685%
1686% o exception: return any errors or warnings in this structure.
1687%
1688*/
1689MagickExport MagickBooleanType ListThresholdMaps(FILE *file,
1690 ExceptionInfo *exception)
1691{
1692 const StringInfo
1693 *option;
1694
1696 *options;
1697
1698 MagickStatusType
1699 status;
1700
1701 status=MagickTrue;
1702 if (file == (FILE *) NULL)
1703 file=stdout;
1704 options=GetConfigureOptions(ThresholdsFilename,exception);
1705 (void) FormatLocaleFile(file,
1706 "\n Threshold Maps for Ordered Dither Operations\n");
1707 option=(const StringInfo *) GetNextValueInLinkedList(options);
1708 while (option != (const StringInfo *) NULL)
1709 {
1710 (void) FormatLocaleFile(file,"\nPath: %s\n\n",GetStringInfoPath(option));
1711 status&=ListThresholdMapFile(file,(const char *) GetStringInfoDatum(option),
1712 GetStringInfoPath(option),exception);
1713 option=(const StringInfo *) GetNextValueInLinkedList(options);
1714 }
1715 options=DestroyConfigureOptions(options);
1716 return(status != 0 ? MagickTrue : MagickFalse);
1717}
1718
1719/*
1720%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1721% %
1722% %
1723% %
1724% O r d e r e d D i t h e r I m a g e %
1725% %
1726% %
1727% %
1728%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1729%
1730% OrderedDitherImage() uses the ordered dithering technique of reducing color
1731% images to monochrome using positional information to retain as much
1732% information as possible.
1733%
1734% WARNING: This function is deprecated, and is now just a call to
1735% the more more powerful OrderedPosterizeImage(); function.
1736%
1737% The format of the OrderedDitherImage method is:
1738%
1739% MagickBooleanType OrderedDitherImage(Image *image)
1740% MagickBooleanType OrderedDitherImageChannel(Image *image,
1741% const ChannelType channel,ExceptionInfo *exception)
1742%
1743% A description of each parameter follows:
1744%
1745% o image: the image.
1746%
1747% o channel: the channel or channels to be thresholded.
1748%
1749% o exception: return any errors or warnings in this structure.
1750%
1751*/
1752
1753MagickExport MagickBooleanType OrderedDitherImage(Image *image)
1754{
1755 MagickBooleanType
1756 status;
1757
1758 status=OrderedDitherImageChannel(image,DefaultChannels,&image->exception);
1759 return(status);
1760}
1761
1762MagickExport MagickBooleanType OrderedDitherImageChannel(Image *image,
1763 const ChannelType channel,ExceptionInfo *exception)
1764{
1765 MagickBooleanType
1766 status;
1767
1768 /*
1769 Call the augumented function OrderedPosterizeImage()
1770 */
1771 status=OrderedPosterizeImageChannel(image,channel,"o8x8",exception);
1772 return(status);
1773}
1774
1775/*
1776%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1777% %
1778% %
1779% %
1780% O r d e r e d P o s t e r i z e I m a g e %
1781% %
1782% %
1783% %
1784%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1785%
1786% OrderedPosterizeImage() will perform a ordered dither based on a number
1787% of pre-defined dithering threshold maps, but over multiple intensity
1788% levels, which can be different for different channels, according to the
1789% input argument.
1790%
1791% The format of the OrderedPosterizeImage method is:
1792%
1793% MagickBooleanType OrderedPosterizeImage(Image *image,
1794% const char *threshold_map,ExceptionInfo *exception)
1795% MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1796% const ChannelType channel,const char *threshold_map,
1797% ExceptionInfo *exception)
1798%
1799% A description of each parameter follows:
1800%
1801% o image: the image.
1802%
1803% o channel: the channel or channels to be thresholded.
1804%
1805% o threshold_map: A string containing the name of the threshold dither
1806% map to use, followed by zero or more numbers representing the number
1807% of color levels tho dither between.
1808%
1809% Any level number less than 2 will be equivalent to 2, and means only
1810% binary dithering will be applied to each color channel.
1811%
1812% No numbers also means a 2 level (bitmap) dither will be applied to all
1813% channels, while a single number is the number of levels applied to each
1814% channel in sequence. More numbers will be applied in turn to each of
1815% the color channels.
1816%
1817% For example: "o3x3,6" will generate a 6 level posterization of the
1818% image with a ordered 3x3 diffused pixel dither being applied between
1819% each level. While checker,8,8,4 will produce a 332 colormaped image
1820% with only a single checkerboard hash pattern (50% grey) between each
1821% color level, to basically double the number of color levels with
1822% a bare minimum of dithering.
1823%
1824% o exception: return any errors or warnings in this structure.
1825%
1826*/
1827MagickExport MagickBooleanType OrderedPosterizeImage(Image *image,
1828 const char *threshold_map,ExceptionInfo *exception)
1829{
1830 MagickBooleanType
1831 status;
1832
1833 status=OrderedPosterizeImageChannel(image,DefaultChannels,threshold_map,
1834 exception);
1835 return(status);
1836}
1837
1838MagickExport MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1839 const ChannelType channel,const char *threshold_map,ExceptionInfo *exception)
1840{
1841#define DitherImageTag "Dither/Image"
1842
1843 CacheView
1844 *image_view;
1845
1847 levels;
1848
1849 MagickBooleanType
1850 status;
1851
1852 MagickOffsetType
1853 progress;
1854
1855 ssize_t
1856 y;
1857
1859 *map;
1860
1861 assert(image != (Image *) NULL);
1862 assert(image->signature == MagickCoreSignature);
1863 assert(exception != (ExceptionInfo *) NULL);
1864 assert(exception->signature == MagickCoreSignature);
1865 if (IsEventLogging() != MagickFalse)
1866 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1867 if (threshold_map == (const char *) NULL)
1868 return(MagickTrue);
1869 {
1870 char
1871 token[MaxTextExtent];
1872
1873 const char
1874 *p;
1875
1876 p=(char *)threshold_map;
1877 while (((isspace((int) ((unsigned char) *p)) != 0) || (*p == ',')) &&
1878 (*p != '\0'))
1879 p++;
1880 threshold_map=p;
1881 while (((isspace((int) ((unsigned char) *p)) == 0) && (*p != ',')) &&
1882 (*p != '\0')) {
1883 if ((p-threshold_map) >= (MaxTextExtent-1))
1884 break;
1885 token[p-threshold_map] = *p;
1886 p++;
1887 }
1888 token[p-threshold_map] = '\0';
1889 map = GetThresholdMap(token, exception);
1890 if ( map == (ThresholdMap *) NULL ) {
1891 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1892 "InvalidArgument","%s : '%s'","ordered-dither",threshold_map);
1893 return(MagickFalse);
1894 }
1895 }
1896 /* Set channel levels from extra comma separated arguments
1897 Default to 2, the single value given, or individual channel values
1898 */
1899#if 1
1900 { /* parse directly as a comma separated list of integers */
1901 char *p;
1902
1903 p = strchr((char *) threshold_map,',');
1904 if ( p != (char *) NULL && isdigit((int) ((unsigned char) *(++p))) )
1905 levels.index = (unsigned int) strtoul(p, &p, 10);
1906 else
1907 levels.index = 2;
1908
1909 levels.red = ((channel & RedChannel ) != 0) ? levels.index : 0;
1910 levels.green = ((channel & GreenChannel) != 0) ? levels.index : 0;
1911 levels.blue = ((channel & BlueChannel) != 0) ? levels.index : 0;
1912 levels.opacity = ((channel & OpacityChannel) != 0) ? levels.index : 0;
1913 levels.index = ((channel & IndexChannel) != 0
1914 && (image->colorspace == CMYKColorspace)) ? levels.index : 0;
1915
1916 /* if more than a single number, each channel has a separate value */
1917 if ( p != (char *) NULL && *p == ',' ) {
1918 p=strchr((char *) threshold_map,',');
1919 p++;
1920 if ((channel & RedChannel) != 0)
1921 levels.red = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
1922 if ((channel & GreenChannel) != 0)
1923 levels.green = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
1924 if ((channel & BlueChannel) != 0)
1925 levels.blue = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
1926 if ((channel & IndexChannel) != 0 && image->colorspace == CMYKColorspace)
1927 levels.index=(unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
1928 if ((channel & OpacityChannel) != 0)
1929 levels.opacity = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
1930 }
1931 }
1932#else
1933 /* Parse level values as a geometry */
1934 /* This difficult!
1935 * How to map GeometryInfo structure elements into
1936 * LongPixelPacket structure elements, but according to channel?
1937 * Note the channels list may skip elements!!!!
1938 * EG -channel BA -ordered-dither map,2,3
1939 * will need to map g.rho -> l.blue, and g.sigma -> l.opacity
1940 * A simpler way is needed, probably converting geometry to a temporary
1941 * array, then using channel to advance the index into ssize_t pixel packet.
1942 */
1943#endif
1944
1945#if 0
1946printf("DEBUG levels r=%u g=%u b=%u a=%u i=%u\n",
1947 levels.red, levels.green, levels.blue, levels.opacity, levels.index);
1948#endif
1949
1950 { /* Do the posterized ordered dithering of the image */
1951 ssize_t
1952 d;
1953
1954 /* d = number of psuedo-level divisions added between color levels */
1955 d = map->divisor-1;
1956
1957 /* reduce levels to levels - 1 */
1958 levels.red = levels.red ? levels.red-1 : 0;
1959 levels.green = levels.green ? levels.green-1 : 0;
1960 levels.blue = levels.blue ? levels.blue-1 : 0;
1961 levels.opacity = levels.opacity ? levels.opacity-1 : 0;
1962 levels.index = levels.index ? levels.index-1 : 0;
1963
1964 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1965 {
1966 InheritException(exception,&image->exception);
1967 return(MagickFalse);
1968 }
1969 status=MagickTrue;
1970 progress=0;
1971 image_view=AcquireAuthenticCacheView(image,exception);
1972#if defined(MAGICKCORE_OPENMP_SUPPORT)
1973 #pragma omp parallel for schedule(static) shared(progress,status) \
1974 magick_number_threads(image,image,image->rows,1)
1975#endif
1976 for (y=0; y < (ssize_t) image->rows; y++)
1977 {
1978 IndexPacket
1979 *magick_restrict indexes;
1980
1981 ssize_t
1982 x;
1983
1985 *magick_restrict q;
1986
1987 if (status == MagickFalse)
1988 continue;
1989 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1990 if (q == (PixelPacket *) NULL)
1991 {
1992 status=MagickFalse;
1993 continue;
1994 }
1995 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1996 for (x=0; x < (ssize_t) image->columns; x++)
1997 {
1998 ssize_t
1999 threshold,
2000 t,
2001 l;
2002
2003 /*
2004 Figure out the dither threshold for this pixel
2005 This must be a integer from 1 to map->divisor-1
2006 */
2007 threshold = map->levels[(x%map->width) +map->width*(y%map->height)];
2008
2009 /* Dither each channel in the image as appropriate
2010 Notes on the integer Math...
2011 total number of divisions = (levels-1)*(divisor-1)+1)
2012 t1 = this colors psuedo_level =
2013 q->red * total_divisions / (QuantumRange+1)
2014 l = posterization level 0..levels
2015 t = dither threshold level 0..divisor-1 NB: 0 only on last
2016 Each color_level is of size QuantumRange / (levels-1)
2017 NB: All input levels and divisor are already had 1 subtracted
2018 Opacity is inverted so 'off' represents transparent.
2019 */
2020 if (levels.red) {
2021 t = (ssize_t) (QuantumScale*(MagickRealType) GetPixelRed(q)*
2022 (levels.red*d+1));
2023 l = t/d; t = t-l*d;
2024 SetPixelRed(q,ClampToQuantum((MagickRealType)
2025 ((l+(t >= threshold))*(MagickRealType) QuantumRange/levels.red)));
2026 }
2027 if (levels.green) {
2028 t = (ssize_t) (QuantumScale*(MagickRealType) GetPixelGreen(q)*
2029 (levels.green*d+1));
2030 l = t/d; t = t-l*d;
2031 SetPixelGreen(q,ClampToQuantum((MagickRealType)
2032 ((l+(t >= threshold))*(MagickRealType) QuantumRange/levels.green)));
2033 }
2034 if (levels.blue) {
2035 t = (ssize_t) (QuantumScale*(MagickRealType) GetPixelBlue(q)*
2036 (levels.blue*d+1));
2037 l = t/d; t = t-l*d;
2038 SetPixelBlue(q,ClampToQuantum((MagickRealType)
2039 ((l+(t >= threshold))*(MagickRealType) QuantumRange/levels.blue)));
2040 }
2041 if (levels.opacity) {
2042 t = (ssize_t) ((1.0-QuantumScale*(MagickRealType) GetPixelOpacity(q))*
2043 (levels.opacity*d+1));
2044 l = t/d; t = t-l*d;
2045 SetPixelOpacity(q,ClampToQuantum((MagickRealType)
2046 ((1.0-l-(t >= threshold))*(MagickRealType) QuantumRange/
2047 levels.opacity)));
2048 }
2049 if (levels.index) {
2050 t = (ssize_t) (QuantumScale*(MagickRealType) GetPixelIndex(indexes+x)*
2051 (levels.index*d+1));
2052 l = t/d; t = t-l*d;
2053 SetPixelIndex(indexes+x,ClampToQuantum((MagickRealType) ((l+
2054 (t>=threshold))*(MagickRealType) QuantumRange/levels.index)));
2055 }
2056 q++;
2057 }
2058 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2059 status=MagickFalse;
2060 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2061 {
2062 MagickBooleanType
2063 proceed;
2064
2065#if defined(MAGICKCORE_OPENMP_SUPPORT)
2066 #pragma omp atomic
2067#endif
2068 progress++;
2069 proceed=SetImageProgress(image,DitherImageTag,progress,image->rows);
2070 if (proceed == MagickFalse)
2071 status=MagickFalse;
2072 }
2073 }
2074 image_view=DestroyCacheView(image_view);
2075 }
2076 map=DestroyThresholdMap(map);
2077 return(MagickTrue);
2078}
2079
2080/*
2081%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2082% %
2083% %
2084% %
2085% P e r c e p t i b l e I m a g e %
2086% %
2087% %
2088% %
2089%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2090%
2091% PerceptibleImage() set each pixel whose value is less than |epsilon| to
2092% epsilon or -epsilon (whichever is closer) otherwise the pixel value remains
2093% unchanged.
2094%
2095% The format of the PerceptibleImageChannel method is:
2096%
2097% MagickBooleanType PerceptibleImage(Image *image,const double epsilon)
2098% MagickBooleanType PerceptibleImageChannel(Image *image,
2099% const ChannelType channel,const double epsilon)
2100%
2101% A description of each parameter follows:
2102%
2103% o image: the image.
2104%
2105% o channel: the channel type.
2106%
2107% o epsilon: the epsilon threshold (e.g. 1.0e-9).
2108%
2109*/
2110
2111static inline Quantum PerceptibleThreshold(const Quantum quantum,
2112 const double epsilon)
2113{
2114 double
2115 sign;
2116
2117 sign=(double) quantum < 0.0 ? -1.0 : 1.0;
2118 if ((sign*(double) quantum) >= epsilon)
2119 return(quantum);
2120 return((Quantum) (sign*epsilon));
2121}
2122
2123MagickExport MagickBooleanType PerceptibleImage(Image *image,
2124 const double epsilon)
2125{
2126 MagickBooleanType
2127 status;
2128
2129 status=PerceptibleImageChannel(image,DefaultChannels,epsilon);
2130 return(status);
2131}
2132
2133MagickExport MagickBooleanType PerceptibleImageChannel(Image *image,
2134 const ChannelType channel,const double epsilon)
2135{
2136#define PerceptibleImageTag "Perceptible/Image"
2137
2138 CacheView
2139 *image_view;
2140
2142 *exception;
2143
2144 MagickBooleanType
2145 status;
2146
2147 MagickOffsetType
2148 progress;
2149
2150 ssize_t
2151 y;
2152
2153 assert(image != (Image *) NULL);
2154 assert(image->signature == MagickCoreSignature);
2155 if (IsEventLogging() != MagickFalse)
2156 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2157 if (image->storage_class == PseudoClass)
2158 {
2159 ssize_t
2160 i;
2161
2163 *magick_restrict q;
2164
2165 q=image->colormap;
2166 for (i=0; i < (ssize_t) image->colors; i++)
2167 {
2168 SetPixelRed(q,PerceptibleThreshold(GetPixelRed(q),epsilon));
2169 SetPixelGreen(q,PerceptibleThreshold(GetPixelGreen(q),epsilon));
2170 SetPixelBlue(q,PerceptibleThreshold(GetPixelBlue(q),epsilon));
2171 SetPixelOpacity(q,PerceptibleThreshold(GetPixelOpacity(q),epsilon));
2172 q++;
2173 }
2174 return(SyncImage(image));
2175 }
2176 /*
2177 Perceptible image.
2178 */
2179 status=MagickTrue;
2180 progress=0;
2181 exception=(&image->exception);
2182 image_view=AcquireAuthenticCacheView(image,exception);
2183#if defined(MAGICKCORE_OPENMP_SUPPORT)
2184 #pragma omp parallel for schedule(static) shared(progress,status) \
2185 magick_number_threads(image,image,image->rows,1)
2186#endif
2187 for (y=0; y < (ssize_t) image->rows; y++)
2188 {
2189 IndexPacket
2190 *magick_restrict indexes;
2191
2192 ssize_t
2193 x;
2194
2196 *magick_restrict q;
2197
2198 if (status == MagickFalse)
2199 continue;
2200 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2201 if (q == (PixelPacket *) NULL)
2202 {
2203 status=MagickFalse;
2204 continue;
2205 }
2206 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2207 for (x=0; x < (ssize_t) image->columns; x++)
2208 {
2209 if ((channel & RedChannel) != 0)
2210 SetPixelRed(q,PerceptibleThreshold(GetPixelRed(q),epsilon));
2211 if ((channel & GreenChannel) != 0)
2212 SetPixelGreen(q,PerceptibleThreshold(GetPixelGreen(q),epsilon));
2213 if ((channel & BlueChannel) != 0)
2214 SetPixelBlue(q,PerceptibleThreshold(GetPixelBlue(q),epsilon));
2215 if ((channel & OpacityChannel) != 0)
2216 SetPixelOpacity(q,PerceptibleThreshold(GetPixelOpacity(q),epsilon));
2217 if (((channel & IndexChannel) != 0) &&
2218 (image->colorspace == CMYKColorspace))
2219 SetPixelIndex(indexes+x,PerceptibleThreshold(GetPixelIndex(indexes+x),
2220 epsilon));
2221 q++;
2222 }
2223 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2224 status=MagickFalse;
2225 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2226 {
2227 MagickBooleanType
2228 proceed;
2229
2230#if defined(MAGICKCORE_OPENMP_SUPPORT)
2231 #pragma omp atomic
2232#endif
2233 progress++;
2234 proceed=SetImageProgress(image,PerceptibleImageTag,progress,
2235 image->rows);
2236 if (proceed == MagickFalse)
2237 status=MagickFalse;
2238 }
2239 }
2240 image_view=DestroyCacheView(image_view);
2241 return(status);
2242}
2243
2244/*
2245%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2246% %
2247% %
2248% %
2249% R a n d o m T h r e s h o l d I m a g e %
2250% %
2251% %
2252% %
2253%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2254%
2255% RandomThresholdImage() changes the value of individual pixels based on the
2256% intensity of each pixel compared to a random threshold. The result is a
2257% low-contrast, two color image.
2258%
2259% The format of the RandomThresholdImage method is:
2260%
2261% MagickBooleanType RandomThresholdImageChannel(Image *image,
2262% const char *thresholds,ExceptionInfo *exception)
2263% MagickBooleanType RandomThresholdImageChannel(Image *image,
2264% const ChannelType channel,const char *thresholds,
2265% ExceptionInfo *exception)
2266%
2267% A description of each parameter follows:
2268%
2269% o image: the image.
2270%
2271% o channel: the channel or channels to be thresholded.
2272%
2273% o thresholds: a geometry string containing low,high thresholds. If the
2274% string contains 2x2, 3x3, or 4x4, an ordered dither of order 2, 3, or 4
2275% is performed instead.
2276%
2277% o exception: return any errors or warnings in this structure.
2278%
2279*/
2280
2281MagickExport MagickBooleanType RandomThresholdImage(Image *image,
2282 const char *thresholds,ExceptionInfo *exception)
2283{
2284 MagickBooleanType
2285 status;
2286
2287 status=RandomThresholdImageChannel(image,DefaultChannels,thresholds,
2288 exception);
2289 return(status);
2290}
2291
2292MagickExport MagickBooleanType RandomThresholdImageChannel(Image *image,
2293 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
2294{
2295#define ThresholdImageTag "Threshold/Image"
2296
2297 CacheView
2298 *image_view;
2299
2301 geometry_info;
2302
2303 MagickStatusType
2304 flags;
2305
2306 MagickBooleanType
2307 status;
2308
2309 MagickOffsetType
2310 progress;
2311
2313 threshold;
2314
2315 MagickRealType
2316 min_threshold,
2317 max_threshold;
2318
2320 **magick_restrict random_info;
2321
2322 ssize_t
2323 y;
2324
2325#if defined(MAGICKCORE_OPENMP_SUPPORT)
2326 unsigned long
2327 key;
2328#endif
2329
2330 assert(image != (Image *) NULL);
2331 assert(image->signature == MagickCoreSignature);
2332 assert(exception != (ExceptionInfo *) NULL);
2333 assert(exception->signature == MagickCoreSignature);
2334 if (IsEventLogging() != MagickFalse)
2335 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2336 if (thresholds == (const char *) NULL)
2337 return(MagickTrue);
2338 GetMagickPixelPacket(image,&threshold);
2339 min_threshold=0.0;
2340 max_threshold=(MagickRealType) QuantumRange;
2341 flags=ParseGeometry(thresholds,&geometry_info);
2342 min_threshold=geometry_info.rho;
2343 max_threshold=geometry_info.sigma;
2344 if ((flags & SigmaValue) == 0)
2345 max_threshold=min_threshold;
2346 if (strchr(thresholds,'%') != (char *) NULL)
2347 {
2348 max_threshold*=0.01*(MagickRealType) QuantumRange;
2349 min_threshold*=0.01*(MagickRealType) QuantumRange;
2350 }
2351 else
2352 if (((max_threshold == min_threshold) || (max_threshold == 1)) &&
2353 (min_threshold <= 8))
2354 {
2355 /*
2356 Backward Compatibility -- ordered-dither -- IM v 6.2.9-6.
2357 */
2358 status=OrderedPosterizeImageChannel(image,channel,thresholds,exception);
2359 return(status);
2360 }
2361 /*
2362 Random threshold image.
2363 */
2364 status=MagickTrue;
2365 progress=0;
2366 if (channel == CompositeChannels)
2367 {
2368 if (AcquireImageColormap(image,2) == MagickFalse)
2369 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2370 image->filename);
2371 random_info=AcquireRandomInfoTLS();
2372 image_view=AcquireAuthenticCacheView(image,exception);
2373#if defined(MAGICKCORE_OPENMP_SUPPORT)
2374 key=GetRandomSecretKey(random_info[0]);
2375 #pragma omp parallel for schedule(static) shared(progress,status) \
2376 magick_number_threads(image,image,image->rows,key == ~0UL)
2377#endif
2378 for (y=0; y < (ssize_t) image->rows; y++)
2379 {
2380 const int
2381 id = GetOpenMPThreadId();
2382
2383 MagickBooleanType
2384 sync;
2385
2386 IndexPacket
2387 *magick_restrict indexes;
2388
2389 ssize_t
2390 x;
2391
2393 *magick_restrict q;
2394
2395 if (status == MagickFalse)
2396 continue;
2397 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
2398 exception);
2399 if (q == (PixelPacket *) NULL)
2400 {
2401 status=MagickFalse;
2402 continue;
2403 }
2404 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2405 for (x=0; x < (ssize_t) image->columns; x++)
2406 {
2407 IndexPacket
2408 index;
2409
2410 MagickRealType
2411 intensity;
2412
2413 intensity=GetPixelIntensity(image,q);
2414 if (intensity < min_threshold)
2415 threshold.index=min_threshold;
2416 else if (intensity > max_threshold)
2417 threshold.index=max_threshold;
2418 else
2419 threshold.index=(MagickRealType) QuantumRange*
2420 (MagickRealType) GetPseudoRandomValue(random_info[id]);
2421 index=(IndexPacket) (intensity <= threshold.index ? 0 : 1);
2422 SetPixelIndex(indexes+x,index);
2423 SetPixelRGBO(q,image->colormap+(ssize_t) index);
2424 q++;
2425 }
2426 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2427 if (sync == MagickFalse)
2428 status=MagickFalse;
2429 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2430 {
2431 MagickBooleanType
2432 proceed;
2433
2434#if defined(MAGICKCORE_OPENMP_SUPPORT)
2435 #pragma omp atomic
2436#endif
2437 progress++;
2438 proceed=SetImageProgress(image,ThresholdImageTag,progress,
2439 image->rows);
2440 if (proceed == MagickFalse)
2441 status=MagickFalse;
2442 }
2443 }
2444 image_view=DestroyCacheView(image_view);
2445 random_info=DestroyRandomInfoTLS(random_info);
2446 return(status);
2447 }
2448 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2449 {
2450 InheritException(exception,&image->exception);
2451 return(MagickFalse);
2452 }
2453 random_info=AcquireRandomInfoTLS();
2454 image_view=AcquireAuthenticCacheView(image,exception);
2455#if defined(MAGICKCORE_OPENMP_SUPPORT)
2456 key=GetRandomSecretKey(random_info[0]);
2457 #pragma omp parallel for schedule(static) shared(progress,status) \
2458 magick_number_threads(image,image,image->rows,key == ~0UL)
2459#endif
2460 for (y=0; y < (ssize_t) image->rows; y++)
2461 {
2462 const int
2463 id = GetOpenMPThreadId();
2464
2465 IndexPacket
2466 *magick_restrict indexes;
2467
2469 *magick_restrict q;
2470
2471 ssize_t
2472 x;
2473
2474 if (status == MagickFalse)
2475 continue;
2476 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2477 if (q == (PixelPacket *) NULL)
2478 {
2479 status=MagickFalse;
2480 continue;
2481 }
2482 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2483 for (x=0; x < (ssize_t) image->columns; x++)
2484 {
2485 if ((channel & RedChannel) != 0)
2486 {
2487 if ((MagickRealType) GetPixelRed(q) < min_threshold)
2488 threshold.red=min_threshold;
2489 else
2490 if ((MagickRealType) GetPixelRed(q) > max_threshold)
2491 threshold.red=max_threshold;
2492 else
2493 threshold.red=(MagickRealType) ((MagickRealType) QuantumRange*
2494 GetPseudoRandomValue(random_info[id]));
2495 }
2496 if ((channel & GreenChannel) != 0)
2497 {
2498 if ((MagickRealType) GetPixelGreen(q) < min_threshold)
2499 threshold.green=min_threshold;
2500 else
2501 if ((MagickRealType) GetPixelGreen(q) > max_threshold)
2502 threshold.green=max_threshold;
2503 else
2504 threshold.green=(MagickRealType) ((MagickRealType) QuantumRange*
2505 GetPseudoRandomValue(random_info[id]));
2506 }
2507 if ((channel & BlueChannel) != 0)
2508 {
2509 if ((MagickRealType) GetPixelBlue(q) < min_threshold)
2510 threshold.blue=min_threshold;
2511 else
2512 if ((MagickRealType) GetPixelBlue(q) > max_threshold)
2513 threshold.blue=max_threshold;
2514 else
2515 threshold.blue=(MagickRealType) ((MagickRealType) QuantumRange*
2516 GetPseudoRandomValue(random_info[id]));
2517 }
2518 if ((channel & OpacityChannel) != 0)
2519 {
2520 if ((MagickRealType) GetPixelOpacity(q) < min_threshold)
2521 threshold.opacity=min_threshold;
2522 else
2523 if ((MagickRealType) GetPixelOpacity(q) > max_threshold)
2524 threshold.opacity=max_threshold;
2525 else
2526 threshold.opacity=(MagickRealType) ((MagickRealType) QuantumRange*
2527 GetPseudoRandomValue(random_info[id]));
2528 }
2529 if (((channel & IndexChannel) != 0) &&
2530 (image->colorspace == CMYKColorspace))
2531 {
2532 if ((MagickRealType) GetPixelIndex(indexes+x) < min_threshold)
2533 threshold.index=min_threshold;
2534 else
2535 if ((MagickRealType) GetPixelIndex(indexes+x) > max_threshold)
2536 threshold.index=max_threshold;
2537 else
2538 threshold.index=(MagickRealType) ((MagickRealType) QuantumRange*
2539 GetPseudoRandomValue(random_info[id]));
2540 }
2541 if ((channel & RedChannel) != 0)
2542 SetPixelRed(q,(MagickRealType) GetPixelRed(q) <= threshold.red ?
2543 0 : QuantumRange);
2544 if ((channel & GreenChannel) != 0)
2545 SetPixelGreen(q,(MagickRealType) GetPixelGreen(q) <= threshold.green ?
2546 0 : QuantumRange);
2547 if ((channel & BlueChannel) != 0)
2548 SetPixelBlue(q,(MagickRealType) GetPixelBlue(q) <= threshold.blue ?
2549 0 : QuantumRange);
2550 if ((channel & OpacityChannel) != 0)
2551 SetPixelOpacity(q,(MagickRealType) GetPixelOpacity(q) <=
2552 threshold.opacity ? 0 : QuantumRange);
2553 if (((channel & IndexChannel) != 0) &&
2554 (image->colorspace == CMYKColorspace))
2555 SetPixelIndex(indexes+x,(MagickRealType) GetPixelIndex(indexes+x) <=
2556 threshold.index ? 0 : QuantumRange);
2557 q++;
2558 }
2559 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2560 status=MagickFalse;
2561 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2562 {
2563 MagickBooleanType
2564 proceed;
2565
2566#if defined(MAGICKCORE_OPENMP_SUPPORT)
2567 #pragma omp atomic
2568#endif
2569 progress++;
2570 proceed=SetImageProgress(image,ThresholdImageTag,progress,image->rows);
2571 if (proceed == MagickFalse)
2572 status=MagickFalse;
2573 }
2574 }
2575 image_view=DestroyCacheView(image_view);
2576 random_info=DestroyRandomInfoTLS(random_info);
2577 return(status);
2578}
2579
2580/*
2581%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2582% %
2583% %
2584% %
2585% W h i t e T h r e s h o l d I m a g e %
2586% %
2587% %
2588% %
2589%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2590%
2591% WhiteThresholdImage() is like ThresholdImage() but forces all pixels above
2592% the threshold into white while leaving all pixels at or below the threshold
2593% unchanged.
2594%
2595% The format of the WhiteThresholdImage method is:
2596%
2597% MagickBooleanType WhiteThresholdImage(Image *image,const char *threshold)
2598% MagickBooleanType WhiteThresholdImageChannel(Image *image,
2599% const ChannelType channel,const char *threshold,
2600% ExceptionInfo *exception)
2601%
2602% A description of each parameter follows:
2603%
2604% o image: the image.
2605%
2606% o channel: the channel or channels to be thresholded.
2607%
2608% o threshold: Define the threshold value.
2609%
2610% o exception: return any errors or warnings in this structure.
2611%
2612*/
2613MagickExport MagickBooleanType WhiteThresholdImage(Image *image,
2614 const char *threshold)
2615{
2616 MagickBooleanType
2617 status;
2618
2619 status=WhiteThresholdImageChannel(image,DefaultChannels,threshold,
2620 &image->exception);
2621 return(status);
2622}
2623
2624MagickExport MagickBooleanType WhiteThresholdImageChannel(Image *image,
2625 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
2626{
2627#define ThresholdImageTag "Threshold/Image"
2628
2629 CacheView
2630 *image_view;
2631
2633 geometry_info;
2634
2635 MagickBooleanType
2636 status;
2637
2638 MagickOffsetType
2639 progress;
2640
2642 threshold;
2643
2644 MagickStatusType
2645 flags;
2646
2647 ssize_t
2648 y;
2649
2650 assert(image != (Image *) NULL);
2651 assert(image->signature == MagickCoreSignature);
2652 if (IsEventLogging() != MagickFalse)
2653 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2654 if (thresholds == (const char *) NULL)
2655 return(MagickTrue);
2656 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2657 return(MagickFalse);
2658 flags=ParseGeometry(thresholds,&geometry_info);
2659 GetMagickPixelPacket(image,&threshold);
2660 threshold.red=geometry_info.rho;
2661 threshold.green=geometry_info.sigma;
2662 if ((flags & SigmaValue) == 0)
2663 threshold.green=threshold.red;
2664 threshold.blue=geometry_info.xi;
2665 if ((flags & XiValue) == 0)
2666 threshold.blue=threshold.red;
2667 threshold.opacity=geometry_info.psi;
2668 if ((flags & PsiValue) == 0)
2669 threshold.opacity=threshold.red;
2670 threshold.index=geometry_info.chi;
2671 if ((flags & ChiValue) == 0)
2672 threshold.index=threshold.red;
2673 if ((flags & PercentValue) != 0)
2674 {
2675 threshold.red*=(MagickRealType) QuantumRange/100.0;
2676 threshold.green*=(MagickRealType) QuantumRange/100.0;
2677 threshold.blue*=(MagickRealType) QuantumRange/100.0;
2678 threshold.opacity*=(MagickRealType) QuantumRange/100.0;
2679 threshold.index*=(MagickRealType) QuantumRange/100.0;
2680 }
2681 if ((IsMagickGray(&threshold) == MagickFalse) &&
2682 (IsGrayColorspace(image->colorspace) != MagickFalse))
2683 (void) SetImageColorspace(image,sRGBColorspace);
2684 /*
2685 White threshold image.
2686 */
2687 status=MagickTrue;
2688 progress=0;
2689 image_view=AcquireAuthenticCacheView(image,exception);
2690#if defined(MAGICKCORE_OPENMP_SUPPORT)
2691 #pragma omp parallel for schedule(static) shared(progress,status) \
2692 magick_number_threads(image,image,image->rows,2)
2693#endif
2694 for (y=0; y < (ssize_t) image->rows; y++)
2695 {
2696 IndexPacket
2697 *magick_restrict indexes;
2698
2699 ssize_t
2700 x;
2701
2703 *magick_restrict q;
2704
2705 if (status == MagickFalse)
2706 continue;
2707 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2708 if (q == (PixelPacket *) NULL)
2709 {
2710 status=MagickFalse;
2711 continue;
2712 }
2713 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2714 for (x=0; x < (ssize_t) image->columns; x++)
2715 {
2716 if (((channel & RedChannel) != 0) &&
2717 ((MagickRealType) GetPixelRed(q) > threshold.red))
2718 SetPixelRed(q,QuantumRange);
2719 if (((channel & GreenChannel) != 0) &&
2720 ((MagickRealType) GetPixelGreen(q) > threshold.green))
2721 SetPixelGreen(q,QuantumRange);
2722 if (((channel & BlueChannel) != 0) &&
2723 ((MagickRealType) GetPixelBlue(q) > threshold.blue))
2724 SetPixelBlue(q,QuantumRange);
2725 if (((channel & OpacityChannel) != 0) &&
2726 ((MagickRealType) GetPixelOpacity(q) > threshold.opacity))
2727 SetPixelOpacity(q,QuantumRange);
2728 if (((channel & IndexChannel) != 0) &&
2729 (image->colorspace == CMYKColorspace) &&
2730 ((MagickRealType) GetPixelIndex(indexes+x)) > threshold.index)
2731 SetPixelIndex(indexes+x,QuantumRange);
2732 q++;
2733 }
2734 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2735 status=MagickFalse;
2736 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2737 {
2738 MagickBooleanType
2739 proceed;
2740
2741#if defined(MAGICKCORE_OPENMP_SUPPORT)
2742 #pragma omp atomic
2743#endif
2744 progress++;
2745 proceed=SetImageProgress(image,ThresholdImageTag,progress,image->rows);
2746 if (proceed == MagickFalse)
2747 status=MagickFalse;
2748 }
2749 }
2750 image_view=DestroyCacheView(image_view);
2751 return(status);
2752}