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