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