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