blob: f73f6b868fc47d9681a479665f9bd8588b3dd806 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% TTTTT H H RRRR EEEEE SSSSS H H OOO L DDDD %
7% T H H R R E SS H H O O L D D %
8% T HHHHH RRRR EEE SSS HHHHH O O L D D %
9% T H H R R E SS H H O O L D D %
10% T H H R R EEEEE SSSSS H H OOO LLLLL DDDD %
11% %
12% %
13% MagickCore Image Threshold Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/property.h"
45#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
cristye7e40552010-04-24 21:34:22 +000049#include "magick/colormap.h"
cristy3ed852e2009-09-05 21:47:34 +000050#include "magick/colorspace.h"
51#include "magick/configure.h"
52#include "magick/constitute.h"
53#include "magick/decorate.h"
54#include "magick/draw.h"
55#include "magick/enhance.h"
56#include "magick/exception.h"
57#include "magick/exception-private.h"
58#include "magick/effect.h"
59#include "magick/fx.h"
60#include "magick/gem.h"
61#include "magick/geometry.h"
62#include "magick/image-private.h"
63#include "magick/list.h"
64#include "magick/log.h"
65#include "magick/memory_.h"
66#include "magick/monitor.h"
67#include "magick/monitor-private.h"
68#include "magick/montage.h"
cristyf2f27272009-12-17 14:48:46 +000069#include "magick/option.h"
cristy3ed852e2009-09-05 21:47:34 +000070#include "magick/pixel-private.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resize.h"
76#include "magick/resource_.h"
77#include "magick/segment.h"
78#include "magick/shear.h"
79#include "magick/signature-private.h"
80#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000081#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000082#include "magick/transform.h"
83#include "magick/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000084#include "magick/xml-tree.h"
85
86/*
87 Define declarations.
88*/
89#define ThresholdsFilename "thresholds.xml"
90
91/*
92 Typedef declarations.
93*/
94struct _ThresholdMap
95{
96 char
97 *map_id,
98 *description;
99
cristybb503372010-05-27 20:51:26 +0000100 size_t
cristy3ed852e2009-09-05 21:47:34 +0000101 width,
102 height;
103
cristybb503372010-05-27 20:51:26 +0000104 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000105 divisor,
106 *levels;
107};
108
109/*
110%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
111% %
112% %
113% %
114% A d a p t i v e T h r e s h o l d I m a g e %
115% %
116% %
117% %
118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
119%
120% AdaptiveThresholdImage() selects an individual threshold for each pixel
121% based on the range of intensity values in its local neighborhood. This
122% allows for thresholding of an image whose global intensity histogram
123% doesn't contain distinctive peaks.
124%
125% The format of the AdaptiveThresholdImage method is:
126%
127% Image *AdaptiveThresholdImage(const Image *image,
cristybb503372010-05-27 20:51:26 +0000128% const size_t width,const size_t height,
129% const ssize_t offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000130%
131% A description of each parameter follows:
132%
133% o image: the image.
134%
135% o width: the width of the local neighborhood.
136%
137% o height: the height of the local neighborhood.
138%
139% o offset: the mean offset.
140%
141% o exception: return any errors or warnings in this structure.
142%
143*/
144MagickExport Image *AdaptiveThresholdImage(const Image *image,
cristybb503372010-05-27 20:51:26 +0000145 const size_t width,const size_t height,const ssize_t offset,
cristy3ed852e2009-09-05 21:47:34 +0000146 ExceptionInfo *exception)
147{
148#define ThresholdImageTag "Threshold/Image"
149
cristyc4c8d132010-01-07 01:58:38 +0000150 CacheView
151 *image_view,
152 *threshold_view;
153
cristy3ed852e2009-09-05 21:47:34 +0000154 Image
155 *threshold_image;
156
cristy3ed852e2009-09-05 21:47:34 +0000157 MagickBooleanType
158 status;
159
cristy5f959472010-05-27 22:19:46 +0000160 MagickOffsetType
161 progress;
162
cristy3ed852e2009-09-05 21:47:34 +0000163 MagickPixelPacket
164 zero;
165
166 MagickRealType
167 number_pixels;
168
cristy5f959472010-05-27 22:19:46 +0000169 ssize_t
170 y;
171
cristy3ed852e2009-09-05 21:47:34 +0000172 assert(image != (const Image *) NULL);
173 assert(image->signature == MagickSignature);
174 if (image->debug != MagickFalse)
175 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
176 assert(exception != (ExceptionInfo *) NULL);
177 assert(exception->signature == MagickSignature);
178 if ((image->columns < width) || (image->rows < height))
179 ThrowImageException(OptionError,"ImageSmallerThanRadius");
180 threshold_image=CloneImage(image,0,0,MagickTrue,exception);
181 if (threshold_image == (Image *) NULL)
182 return((Image *) NULL);
183 if (SetImageStorageClass(threshold_image,DirectClass) == MagickFalse)
184 {
185 InheritException(exception,&threshold_image->exception);
186 threshold_image=DestroyImage(threshold_image);
187 return((Image *) NULL);
188 }
189 /*
190 Local adaptive threshold.
191 */
192 status=MagickTrue;
193 progress=0;
194 GetMagickPixelPacket(image,&zero);
195 number_pixels=(MagickRealType) width*height;
196 image_view=AcquireCacheView(image);
197 threshold_view=AcquireCacheView(threshold_image);
cristyb5d5f722009-11-04 03:03:49 +0000198#if defined(MAGICKCORE_OPENMP_SUPPORT)
199 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000200#endif
cristybb503372010-05-27 20:51:26 +0000201 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000202 {
203 MagickBooleanType
204 sync;
205
206 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000207 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000208
209 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000210 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000211
212 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000213 *restrict threshold_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000214
cristybb503372010-05-27 20:51:26 +0000215 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000216 x;
217
218 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000219 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000220
221 if (status == MagickFalse)
222 continue;
cristyd99b0962010-05-29 23:14:26 +0000223 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
224 height/2L,image->columns+width,height,exception);
cristy3ed852e2009-09-05 21:47:34 +0000225 q=GetCacheViewAuthenticPixels(threshold_view,0,y,threshold_image->columns,1,
226 exception);
227 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
228 {
229 status=MagickFalse;
230 continue;
231 }
232 indexes=GetCacheViewVirtualIndexQueue(image_view);
233 threshold_indexes=GetCacheViewAuthenticIndexQueue(threshold_view);
cristybb503372010-05-27 20:51:26 +0000234 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000235 {
cristybb503372010-05-27 20:51:26 +0000236 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000237 v;
238
239 MagickPixelPacket
240 mean,
241 pixel;
242
243 register const PixelPacket
244 *r;
245
cristybb503372010-05-27 20:51:26 +0000246 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000247 u;
248
249 pixel=zero;
250 mean=zero;
251 r=p;
cristybb503372010-05-27 20:51:26 +0000252 for (v=0; v < (ssize_t) height; v++)
cristy3ed852e2009-09-05 21:47:34 +0000253 {
cristybb503372010-05-27 20:51:26 +0000254 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +0000255 {
256 pixel.red+=r[u].red;
257 pixel.green+=r[u].green;
258 pixel.blue+=r[u].blue;
259 pixel.opacity+=r[u].opacity;
260 if (image->colorspace == CMYKColorspace)
261 pixel.index=(MagickRealType) indexes[x+(r-p)+u];
262 }
263 r+=image->columns+width;
264 }
265 mean.red=(MagickRealType) (pixel.red/number_pixels+offset);
266 mean.green=(MagickRealType) (pixel.green/number_pixels+offset);
267 mean.blue=(MagickRealType) (pixel.blue/number_pixels+offset);
268 mean.opacity=(MagickRealType) (pixel.opacity/number_pixels+offset);
269 if (image->colorspace == CMYKColorspace)
270 mean.index=(MagickRealType) (pixel.index/number_pixels+offset);
271 q->red=(Quantum) (((MagickRealType) q->red <= mean.red) ?
272 0 : QuantumRange);
273 q->green=(Quantum) (((MagickRealType) q->green <= mean.green) ?
274 0 : QuantumRange);
275 q->blue=(Quantum) (((MagickRealType) q->blue <= mean.blue) ?
276 0 : QuantumRange);
277 q->opacity=(Quantum) (((MagickRealType) q->opacity <= mean.opacity) ?
278 0 : QuantumRange);
279 if (image->colorspace == CMYKColorspace)
280 threshold_indexes[x]=(IndexPacket) (((MagickRealType)
281 threshold_indexes[x] <= mean.index) ? 0 : QuantumRange);
282 p++;
283 q++;
284 }
285 sync=SyncCacheViewAuthenticPixels(threshold_view,exception);
286 if (sync == MagickFalse)
287 status=MagickFalse;
288 if (image->progress_monitor != (MagickProgressMonitor) NULL)
289 {
290 MagickBooleanType
291 proceed;
292
cristyb5d5f722009-11-04 03:03:49 +0000293#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000294 #pragma omp critical (MagickCore_AdaptiveThresholdImage)
295#endif
296 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
297 image->rows);
298 if (proceed == MagickFalse)
299 status=MagickFalse;
300 }
301 }
302 threshold_view=DestroyCacheView(threshold_view);
303 image_view=DestroyCacheView(image_view);
304 if (status == MagickFalse)
305 threshold_image=DestroyImage(threshold_image);
306 return(threshold_image);
307}
308
309/*
310%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
311% %
312% %
313% %
314% B i l e v e l I m a g e %
315% %
316% %
317% %
318%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
319%
320% BilevelImage() changes the value of individual pixels based on the
321% intensity of each pixel channel. The result is a high-contrast image.
322%
323% More precisely each channel value of the image is 'thresholded' so that if
324% it is equal to or less than the given value it is set to zero, while any
325% value greater than that give is set to it maximum or QuantumRange.
326%
327% This function is what is used to implement the "-threshold" operator for
328% the command line API.
329%
330% If the default channel setting is given the image is thresholded using just
331% the gray 'intensity' of the image, rather than the individual channels.
332%
333% The format of the BilevelImageChannel method is:
334%
335% MagickBooleanType BilevelImage(Image *image,const double threshold)
336% MagickBooleanType BilevelImageChannel(Image *image,
337% const ChannelType channel,const double threshold)
338%
339% A description of each parameter follows:
340%
341% o image: the image.
342%
343% o channel: the channel type.
344%
345% o threshold: define the threshold values.
346%
347% Aside: You can get the same results as operator using LevelImageChannels()
348% with the 'threshold' value for both the black_point and the white_point.
349%
350*/
351
352MagickExport MagickBooleanType BilevelImage(Image *image,const double threshold)
353{
354 MagickBooleanType
355 status;
356
357 status=BilevelImageChannel(image,DefaultChannels,threshold);
358 return(status);
359}
360
361MagickExport MagickBooleanType BilevelImageChannel(Image *image,
362 const ChannelType channel,const double threshold)
363{
364#define ThresholdImageTag "Threshold/Image"
365
cristyc4c8d132010-01-07 01:58:38 +0000366 CacheView
367 *image_view;
368
cristy3ed852e2009-09-05 21:47:34 +0000369 ExceptionInfo
370 *exception;
371
cristy3ed852e2009-09-05 21:47:34 +0000372 MagickBooleanType
373 status;
374
cristy5f959472010-05-27 22:19:46 +0000375 MagickOffsetType
376 progress;
377
378 ssize_t
379 y;
380
cristy3ed852e2009-09-05 21:47:34 +0000381 assert(image != (Image *) NULL);
382 assert(image->signature == MagickSignature);
383 if (image->debug != MagickFalse)
384 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
385 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
386 return(MagickFalse);
387 /*
388 Bilevel threshold image.
389 */
390 status=MagickTrue;
391 progress=0;
392 exception=(&image->exception);
393 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000394#if defined(MAGICKCORE_OPENMP_SUPPORT)
395 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000396#endif
cristybb503372010-05-27 20:51:26 +0000397 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000398 {
399 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000400 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000401
cristybb503372010-05-27 20:51:26 +0000402 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000403 x;
404
405 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000406 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000407
408 if (status == MagickFalse)
409 continue;
410 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
411 if (q == (PixelPacket *) NULL)
412 {
413 status=MagickFalse;
414 continue;
415 }
416 indexes=GetCacheViewAuthenticIndexQueue(image_view);
417 if (channel == DefaultChannels)
418 {
cristybb503372010-05-27 20:51:26 +0000419 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000420 {
421 q->red=(Quantum) ((MagickRealType) PixelIntensityToQuantum(q) <=
422 threshold ? 0 : QuantumRange);
423 q->green=q->red;
424 q->blue=q->red;
425 q++;
426 }
427 }
428 else
cristybb503372010-05-27 20:51:26 +0000429 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000430 {
431 if ((channel & RedChannel) != 0)
432 q->red=(Quantum) ((MagickRealType) q->red <= threshold ? 0 :
433 QuantumRange);
434 if ((channel & GreenChannel) != 0)
435 q->green=(Quantum) ((MagickRealType) q->green <= threshold ? 0 :
436 QuantumRange);
437 if ((channel & BlueChannel) != 0)
438 q->blue=(Quantum) ((MagickRealType) q->blue <= threshold ? 0 :
439 QuantumRange);
440 if ((channel & OpacityChannel) != 0)
441 {
442 if (image->matte == MagickFalse)
443 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold ?
444 0 : QuantumRange);
445 else
446 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold ?
447 OpaqueOpacity : TransparentOpacity);
448 }
449 if (((channel & IndexChannel) != 0) &&
450 (image->colorspace == CMYKColorspace))
451 indexes[x]=(IndexPacket) ((MagickRealType) indexes[x] <= threshold ?
452 0 : QuantumRange);
453 q++;
454 }
455 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
456 status=MagickFalse;
457 if (image->progress_monitor != (MagickProgressMonitor) NULL)
458 {
459 MagickBooleanType
460 proceed;
461
cristyb5d5f722009-11-04 03:03:49 +0000462#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000463 #pragma omp critical (MagickCore_BilevelImageChannel)
464#endif
465 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
466 image->rows);
467 if (proceed == MagickFalse)
468 status=MagickFalse;
469 }
470 }
471 image_view=DestroyCacheView(image_view);
472 return(status);
473}
474
475/*
476%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
477% %
478% %
479% %
480% B l a c k T h r e s h o l d I m a g e %
481% %
482% %
483% %
484%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
485%
486% BlackThresholdImage() is like ThresholdImage() but forces all pixels below
cristy4e101302009-09-17 12:49:12 +0000487% the threshold into black while leaving all pixels at or above the threshold
cristy3ed852e2009-09-05 21:47:34 +0000488% unchanged.
489%
490% The format of the BlackThresholdImage method is:
491%
492% MagickBooleanType BlackThresholdImage(Image *image,const char *threshold)
493% MagickBooleanType BlackThresholdImageChannel(Image *image,
494% const ChannelType channel,const char *threshold,
495% ExceptionInfo *exception)
496%
497% A description of each parameter follows:
498%
499% o image: the image.
500%
501% o channel: the channel or channels to be thresholded.
502%
503% o threshold: Define the threshold value.
504%
505% o exception: return any errors or warnings in this structure.
506%
507*/
508MagickExport MagickBooleanType BlackThresholdImage(Image *image,
509 const char *threshold)
510{
511 MagickBooleanType
512 status;
513
514 status=BlackThresholdImageChannel(image,DefaultChannels,threshold,
515 &image->exception);
516 return(status);
517}
518
519MagickExport MagickBooleanType BlackThresholdImageChannel(Image *image,
520 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
521{
522#define ThresholdImageTag "Threshold/Image"
523
cristyc4c8d132010-01-07 01:58:38 +0000524 CacheView
525 *image_view;
526
cristy3ed852e2009-09-05 21:47:34 +0000527 GeometryInfo
528 geometry_info;
529
cristy3ed852e2009-09-05 21:47:34 +0000530 MagickBooleanType
531 status;
532
cristy5f959472010-05-27 22:19:46 +0000533 MagickOffsetType
534 progress;
535
cristy3ed852e2009-09-05 21:47:34 +0000536 MagickPixelPacket
537 threshold;
538
539 MagickStatusType
540 flags;
541
cristy5f959472010-05-27 22:19:46 +0000542 ssize_t
543 y;
544
cristy3ed852e2009-09-05 21:47:34 +0000545 assert(image != (Image *) NULL);
546 assert(image->signature == MagickSignature);
547 if (image->debug != MagickFalse)
548 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
549 if (thresholds == (const char *) NULL)
550 return(MagickTrue);
551 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
552 return(MagickFalse);
553 GetMagickPixelPacket(image,&threshold);
554 flags=ParseGeometry(thresholds,&geometry_info);
555 threshold.red=geometry_info.rho;
556 threshold.green=geometry_info.sigma;
557 if ((flags & SigmaValue) == 0)
558 threshold.green=threshold.red;
559 threshold.blue=geometry_info.xi;
560 if ((flags & XiValue) == 0)
561 threshold.blue=threshold.red;
562 threshold.opacity=geometry_info.psi;
563 if ((flags & PsiValue) == 0)
564 threshold.opacity=threshold.red;
565 threshold.index=geometry_info.chi;
566 if ((flags & ChiValue) == 0)
567 threshold.index=threshold.red;
568 if ((flags & PercentValue) != 0)
569 {
570 threshold.red*=(QuantumRange/100.0);
571 threshold.green*=(QuantumRange/100.0);
572 threshold.blue*=(QuantumRange/100.0);
573 threshold.opacity*=(QuantumRange/100.0);
574 threshold.index*=(QuantumRange/100.0);
575 }
576 /*
577 Black threshold image.
578 */
579 status=MagickTrue;
580 progress=0;
581 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000582#if defined(MAGICKCORE_OPENMP_SUPPORT)
583 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000584#endif
cristybb503372010-05-27 20:51:26 +0000585 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000586 {
587 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000588 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000589
cristybb503372010-05-27 20:51:26 +0000590 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000591 x;
592
593 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000594 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 if (status == MagickFalse)
597 continue;
598 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
599 if (q == (PixelPacket *) NULL)
600 {
601 status=MagickFalse;
602 continue;
603 }
604 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +0000605 for (x=0; x < (ssize_t) image->columns; x++)
cristyb0ea1af2009-11-28 20:44:46 +0000606 {
607 if (channel != DefaultChannels)
608 {
609 if (PixelIntensity(q) < MagickPixelIntensity(&threshold))
610 {
611 q->red=(Quantum) 0;
612 q->green=(Quantum) 0;
613 q->blue=(Quantum) 0;
614 if (image->colorspace == CMYKColorspace)
615 indexes[x]=(Quantum) 0;
616 }
617 }
618 else
619 {
620 if (((channel & RedChannel) != 0) &&
621 ((MagickRealType) q->red < threshold.red))
622 q->red=(Quantum) 0;
623 if (((channel & GreenChannel) != 0) &&
624 ((MagickRealType) q->green < threshold.green))
625 q->green=(Quantum) 0;
626 if (((channel & BlueChannel) != 0) &&
627 ((MagickRealType) q->blue < threshold.blue))
628 q->blue=(Quantum) 0;
629 if (((channel & OpacityChannel) != 0) &&
630 ((MagickRealType) q->opacity < threshold.opacity))
631 q->opacity=(Quantum) 0;
632 if (((channel & IndexChannel) != 0) &&
633 (image->colorspace == CMYKColorspace) &&
634 ((MagickRealType) indexes[x] < threshold.index))
635 indexes[x]=(Quantum) 0;
636 }
637 q++;
638 }
cristy3ed852e2009-09-05 21:47:34 +0000639 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
640 status=MagickFalse;
641 if (image->progress_monitor != (MagickProgressMonitor) NULL)
642 {
643 MagickBooleanType
644 proceed;
645
cristyb5d5f722009-11-04 03:03:49 +0000646#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000647 #pragma omp critical (MagickCore_BlackThresholdImageChannel)
648#endif
649 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
650 image->rows);
651 if (proceed == MagickFalse)
652 status=MagickFalse;
653 }
654 }
655 image_view=DestroyCacheView(image_view);
656 return(status);
657}
658
659/*
660%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
661% %
662% %
663% %
cristy1eb45dd2009-09-25 16:38:06 +0000664% C l a m p I m a g e %
665% %
666% %
667% %
668%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
669%
cristyecb0c6d2009-09-25 16:50:09 +0000670% ClampImage() restricts the color range from 0 to the quantum depth.
cristy1eb45dd2009-09-25 16:38:06 +0000671%
672% The format of the ClampImageChannel method is:
673%
674% MagickBooleanType ClampImage(Image *image)
675% MagickBooleanType ClampImageChannel(Image *image,
676% const ChannelType channel)
677%
678% A description of each parameter follows:
679%
680% o image: the image.
681%
682% o channel: the channel type.
683%
684*/
685
cristy75ffdb72010-01-07 17:40:12 +0000686static inline Quantum ClampToUnsignedQuantum(const Quantum quantum)
cristy1eb45dd2009-09-25 16:38:06 +0000687{
688#if defined(MAGICKCORE_HDRI_SUPPORT)
689 if (quantum <= 0)
690 return(0);
691 if (quantum >= QuantumRange)
692 return(QuantumRange);
693 return(quantum);
694#else
695 return(quantum);
696#endif
697}
698
699MagickExport MagickBooleanType ClampImage(Image *image)
700{
701 MagickBooleanType
702 status;
703
704 status=ClampImageChannel(image,DefaultChannels);
705 return(status);
706}
707
708MagickExport MagickBooleanType ClampImageChannel(Image *image,
709 const ChannelType channel)
710{
711#define ClampImageTag "Clamp/Image"
712
cristyc4c8d132010-01-07 01:58:38 +0000713 CacheView
714 *image_view;
715
cristy1eb45dd2009-09-25 16:38:06 +0000716 ExceptionInfo
717 *exception;
718
cristy1eb45dd2009-09-25 16:38:06 +0000719 MagickBooleanType
720 status;
721
cristy5f959472010-05-27 22:19:46 +0000722 MagickOffsetType
723 progress;
724
725 ssize_t
726 y;
727
cristy1eb45dd2009-09-25 16:38:06 +0000728 assert(image != (Image *) NULL);
729 assert(image->signature == MagickSignature);
730 if (image->debug != MagickFalse)
731 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
732 if (image->storage_class == PseudoClass)
733 {
cristybb503372010-05-27 20:51:26 +0000734 register ssize_t
cristy1eb45dd2009-09-25 16:38:06 +0000735 i;
736
737 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000738 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000739
740 q=image->colormap;
cristybb503372010-05-27 20:51:26 +0000741 for (i=0; i < (ssize_t) image->colors; i++)
cristy1eb45dd2009-09-25 16:38:06 +0000742 {
cristy75ffdb72010-01-07 17:40:12 +0000743 q->red=ClampToUnsignedQuantum(q->red);
744 q->green=ClampToUnsignedQuantum(q->green);
745 q->blue=ClampToUnsignedQuantum(q->blue);
746 q->opacity=ClampToUnsignedQuantum(q->opacity);
cristy1eb45dd2009-09-25 16:38:06 +0000747 q++;
748 }
749 return(SyncImage(image));
750 }
751 /*
cristy611721d2009-09-25 16:42:17 +0000752 Clamp image.
cristy1eb45dd2009-09-25 16:38:06 +0000753 */
754 status=MagickTrue;
755 progress=0;
756 exception=(&image->exception);
757 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000758#if defined(MAGICKCORE_OPENMP_SUPPORT)
759 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy1eb45dd2009-09-25 16:38:06 +0000760#endif
cristybb503372010-05-27 20:51:26 +0000761 for (y=0; y < (ssize_t) image->rows; y++)
cristy1eb45dd2009-09-25 16:38:06 +0000762 {
763 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000764 *restrict indexes;
cristy1eb45dd2009-09-25 16:38:06 +0000765
cristybb503372010-05-27 20:51:26 +0000766 register ssize_t
cristy1eb45dd2009-09-25 16:38:06 +0000767 x;
768
769 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000770 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000771
772 if (status == MagickFalse)
773 continue;
774 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
775 if (q == (PixelPacket *) NULL)
776 {
777 status=MagickFalse;
778 continue;
779 }
780 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +0000781 for (x=0; x < (ssize_t) image->columns; x++)
cristy1eb45dd2009-09-25 16:38:06 +0000782 {
783 if ((channel & RedChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000784 q->red=ClampToUnsignedQuantum(q->red);
cristy1eb45dd2009-09-25 16:38:06 +0000785 if ((channel & GreenChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000786 q->green=ClampToUnsignedQuantum(q->green);
cristy1eb45dd2009-09-25 16:38:06 +0000787 if ((channel & BlueChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000788 q->blue=ClampToUnsignedQuantum(q->blue);
cristy1eb45dd2009-09-25 16:38:06 +0000789 if ((channel & OpacityChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000790 q->opacity=ClampToUnsignedQuantum(q->opacity);
cristy1eb45dd2009-09-25 16:38:06 +0000791 if (((channel & IndexChannel) != 0) &&
792 (image->colorspace == CMYKColorspace))
cristy75ffdb72010-01-07 17:40:12 +0000793 indexes[x]=(IndexPacket) ClampToUnsignedQuantum(indexes[x]);
cristy1eb45dd2009-09-25 16:38:06 +0000794 q++;
795 }
796 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
797 status=MagickFalse;
798 if (image->progress_monitor != (MagickProgressMonitor) NULL)
799 {
800 MagickBooleanType
801 proceed;
802
cristyb5d5f722009-11-04 03:03:49 +0000803#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1eb45dd2009-09-25 16:38:06 +0000804 #pragma omp critical (MagickCore_ClampImageChannel)
805#endif
806 proceed=SetImageProgress(image,ClampImageTag,progress++,
807 image->rows);
808 if (proceed == MagickFalse)
809 status=MagickFalse;
810 }
811 }
812 image_view=DestroyCacheView(image_view);
813 return(status);
814}
815
816/*
817%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
818% %
819% %
820% %
cristy3ed852e2009-09-05 21:47:34 +0000821% D e s t r o y T h r e s h o l d M a p %
822% %
823% %
824% %
825%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
826%
827% DestroyThresholdMap() de-allocate the given ThresholdMap
828%
829% The format of the ListThresholdMaps method is:
830%
831% ThresholdMap *DestroyThresholdMap(Threshold *map)
832%
833% A description of each parameter follows.
834%
835% o map: Pointer to the Threshold map to destroy
836%
837*/
838MagickExport ThresholdMap *DestroyThresholdMap(ThresholdMap *map)
839{
840 assert(map != (ThresholdMap *) NULL);
841 if (map->map_id != (char *) NULL)
842 map->map_id=DestroyString(map->map_id);
843 if (map->description != (char *) NULL)
844 map->description=DestroyString(map->description);
cristybb503372010-05-27 20:51:26 +0000845 if (map->levels != (ssize_t *) NULL)
846 map->levels=(ssize_t *) RelinquishMagickMemory(map->levels);
cristy3ed852e2009-09-05 21:47:34 +0000847 map=(ThresholdMap *) RelinquishMagickMemory(map);
848 return(map);
849}
850
851/*
852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
853% %
854% %
855% %
856+ G e t T h r e s h o l d M a p F i l e %
857% %
858% %
859% %
860%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
861%
862% GetThresholdMapFile() look for a given threshold map name or alias in the
863% given XML file data, and return the allocated the map when found.
864%
865% The format of the ListThresholdMaps method is:
866%
867% ThresholdMap *GetThresholdMap(const char *xml,const char *filename,
868% const char *map_id,ExceptionInfo *exception)
869%
870% A description of each parameter follows.
871%
872% o xml: The threshold map list in XML format.
873%
874% o filename: The threshold map XML filename.
875%
876% o map_id: ID of the map to look for in XML list.
877%
878% o exception: return any errors or warnings in this structure.
879%
880*/
881MagickExport ThresholdMap *GetThresholdMapFile(const char *xml,
882 const char *filename,const char *map_id,ExceptionInfo *exception)
883{
884 const char
885 *attr,
886 *content;
887
888 double
889 value;
890
891 ThresholdMap
892 *map;
893
894 XMLTreeInfo
895 *description,
896 *levels,
897 *threshold,
898 *thresholds;
899
900 map = (ThresholdMap *)NULL;
901 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
902 "Loading threshold map file \"%s\" ...",filename);
903 thresholds=NewXMLTree(xml,exception);
904 if ( thresholds == (XMLTreeInfo *)NULL )
905 return(map);
906
907 for( threshold = GetXMLTreeChild(thresholds,"threshold");
908 threshold != (XMLTreeInfo *)NULL;
909 threshold = GetNextXMLTreeTag(threshold) ) {
910 attr = GetXMLTreeAttribute(threshold, "map");
911 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
912 break;
913 attr = GetXMLTreeAttribute(threshold, "alias");
914 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
915 break;
916 }
917 if ( threshold == (XMLTreeInfo *)NULL ) {
918 return(map);
919 }
920 description = GetXMLTreeChild(threshold,"description");
921 if ( description == (XMLTreeInfo *)NULL ) {
922 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
923 "XmlMissingElement", "<description>, map \"%s\"", map_id);
924 thresholds = DestroyXMLTree(thresholds);
925 return(map);
926 }
927 levels = GetXMLTreeChild(threshold,"levels");
928 if ( levels == (XMLTreeInfo *)NULL ) {
929 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
930 "XmlMissingElement", "<levels>, map \"%s\"", map_id);
931 thresholds = DestroyXMLTree(thresholds);
932 return(map);
933 }
934
935 /* The map has been found -- Allocate a Threshold Map to return */
cristy73bd4a52010-10-05 11:24:23 +0000936 map = (ThresholdMap *)AcquireMagickMemory(sizeof(ThresholdMap));
cristy3ed852e2009-09-05 21:47:34 +0000937 if ( map == (ThresholdMap *)NULL )
938 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
939 map->map_id = (char *)NULL;
940 map->description = (char *)NULL;
cristybb503372010-05-27 20:51:26 +0000941 map->levels = (ssize_t *) NULL;
cristy3ed852e2009-09-05 21:47:34 +0000942
943 /* Assign Basic Attributes */
944 attr = GetXMLTreeAttribute(threshold, "map");
945 if ( attr != (char *)NULL )
946 map->map_id = ConstantString(attr);
947
948 content = GetXMLTreeContent(description);
949 if ( content != (char *)NULL )
950 map->description = ConstantString(content);
951
952 attr = GetXMLTreeAttribute(levels, "width");
953 if ( attr == (char *)NULL ) {
954 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
955 "XmlMissingAttribute", "<levels width>, map \"%s\"", map_id);
956 thresholds = DestroyXMLTree(thresholds);
957 map = DestroyThresholdMap(map);
958 return(map);
959 }
cristye27293e2009-12-18 02:53:20 +0000960 map->width = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000961 if ( map->width == 0 ) {
962 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
963 "XmlInvalidAttribute", "<levels width>, map \"%s\"", map_id);
964 thresholds = DestroyXMLTree(thresholds);
965 map = DestroyThresholdMap(map);
966 return(map);
967 }
968
969 attr = GetXMLTreeAttribute(levels, "height");
970 if ( attr == (char *)NULL ) {
971 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
972 "XmlMissingAttribute", "<levels height>, map \"%s\"", map_id);
973 thresholds = DestroyXMLTree(thresholds);
974 map = DestroyThresholdMap(map);
975 return(map);
976 }
cristye27293e2009-12-18 02:53:20 +0000977 map->height = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000978 if ( map->height == 0 ) {
979 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
980 "XmlInvalidAttribute", "<levels height>, map \"%s\"", map_id);
981 thresholds = DestroyXMLTree(thresholds);
982 map = DestroyThresholdMap(map);
983 return(map);
984 }
985
986 attr = GetXMLTreeAttribute(levels, "divisor");
987 if ( attr == (char *)NULL ) {
988 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
989 "XmlMissingAttribute", "<levels divisor>, map \"%s\"", map_id);
990 thresholds = DestroyXMLTree(thresholds);
991 map = DestroyThresholdMap(map);
992 return(map);
993 }
cristyf2f27272009-12-17 14:48:46 +0000994 map->divisor = StringToLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000995 if ( map->divisor < 2 ) {
996 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
997 "XmlInvalidAttribute", "<levels divisor>, map \"%s\"", map_id);
998 thresholds = DestroyXMLTree(thresholds);
999 map = DestroyThresholdMap(map);
1000 return(map);
1001 }
1002
1003 /* Allocate theshold levels array */
1004 content = GetXMLTreeContent(levels);
1005 if ( content == (char *)NULL ) {
1006 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1007 "XmlMissingContent", "<levels>, map \"%s\"", map_id);
1008 thresholds = DestroyXMLTree(thresholds);
1009 map = DestroyThresholdMap(map);
1010 return(map);
1011 }
cristybb503372010-05-27 20:51:26 +00001012 map->levels=(ssize_t *) AcquireQuantumMemory((size_t) map->width,map->height*
cristy3ed852e2009-09-05 21:47:34 +00001013 sizeof(*map->levels));
cristybb503372010-05-27 20:51:26 +00001014 if ( map->levels == (ssize_t *)NULL )
cristy3ed852e2009-09-05 21:47:34 +00001015 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
1016 { /* parse levels into integer array */
cristycee97112010-05-28 00:44:52 +00001017 ssize_t i;
cristy3ed852e2009-09-05 21:47:34 +00001018 char *p;
cristybb503372010-05-27 20:51:26 +00001019 for( i=0; i< (ssize_t) (map->width*map->height); i++) {
cristycee97112010-05-28 00:44:52 +00001020 map->levels[i] = (ssize_t)strtol(content, &p, 10);
cristy3ed852e2009-09-05 21:47:34 +00001021 if ( p == content ) {
1022 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1023 "XmlInvalidContent", "<level> too few values, map \"%s\"", map_id);
1024 thresholds = DestroyXMLTree(thresholds);
1025 map = DestroyThresholdMap(map);
1026 return(map);
1027 }
1028 if ( map->levels[i] < 0 || map->levels[i] > map->divisor ) {
1029 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
cristye8c25f92010-06-03 00:53:06 +00001030 "XmlInvalidContent", "<level> %.20g out of range, map \"%s\"",
1031 (double) map->levels[i],map_id);
cristy3ed852e2009-09-05 21:47:34 +00001032 thresholds = DestroyXMLTree(thresholds);
1033 map = DestroyThresholdMap(map);
1034 return(map);
1035 }
1036 content = p;
1037 }
1038 value=(double) strtol(content,&p,10);
1039 if (p != content)
1040 {
1041 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1042 "XmlInvalidContent", "<level> too many values, map \"%s\"", map_id);
1043 thresholds=DestroyXMLTree(thresholds);
1044 map=DestroyThresholdMap(map);
1045 return(map);
1046 }
1047 }
1048
1049 thresholds = DestroyXMLTree(thresholds);
1050 return(map);
1051}
1052
1053/*
1054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1055% %
1056% %
1057% %
1058% G e t T h r e s h o l d M a p %
1059% %
1060% %
1061% %
1062%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1063%
1064% GetThresholdMap() load and search one or more threshold map files for the
1065% a map matching the given name or aliase.
1066%
1067% The format of the GetThresholdMap method is:
1068%
1069% ThresholdMap *GetThresholdMap(const char *map_id,
1070% ExceptionInfo *exception)
1071%
1072% A description of each parameter follows.
1073%
1074% o map_id: ID of the map to look for.
1075%
1076% o exception: return any errors or warnings in this structure.
1077%
1078*/
1079MagickExport ThresholdMap *GetThresholdMap(const char *map_id,
1080 ExceptionInfo *exception)
1081{
1082 const StringInfo
1083 *option;
1084
1085 LinkedListInfo
1086 *options;
1087
1088 ThresholdMap
1089 *map;
1090
1091 map=(ThresholdMap *)NULL;
1092 options=GetConfigureOptions(ThresholdsFilename,exception);
1093 while (( option=(const StringInfo *) GetNextValueInLinkedList(options) )
1094 != (const StringInfo *) NULL && map == (ThresholdMap *)NULL )
1095 map=GetThresholdMapFile((const char *) GetStringInfoDatum(option),
1096 GetStringInfoPath(option),map_id,exception);
1097 options=DestroyConfigureOptions(options);
1098 return(map);
1099}
1100
1101/*
1102%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1103% %
1104% %
1105% %
1106+ L i s t T h r e s h o l d M a p F i l e %
1107% %
1108% %
1109% %
1110%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1111%
1112% ListThresholdMapFile() lists the threshold maps and their descriptions
1113% in the given XML file data.
1114%
1115% The format of the ListThresholdMaps method is:
1116%
1117% MagickBooleanType ListThresholdMaps(FILE *file,const char*xml,
1118% const char *filename,ExceptionInfo *exception)
1119%
1120% A description of each parameter follows.
1121%
1122% o file: An pointer to the output FILE.
1123%
1124% o xml: The threshold map list in XML format.
1125%
1126% o filename: The threshold map XML filename.
1127%
1128% o exception: return any errors or warnings in this structure.
1129%
1130*/
1131MagickBooleanType ListThresholdMapFile(FILE *file,const char *xml,
1132 const char *filename,ExceptionInfo *exception)
1133{
1134 XMLTreeInfo *thresholds,*threshold,*description;
1135 const char *map,*alias,*content;
1136
1137 assert( xml != (char *)NULL );
1138 assert( file != (FILE *)NULL );
1139
1140 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
1141 "Loading threshold map file \"%s\" ...",filename);
1142 thresholds=NewXMLTree(xml,exception);
1143 if ( thresholds == (XMLTreeInfo *)NULL )
1144 return(MagickFalse);
1145
1146 (void) fprintf(file,"%-16s %-12s %s\n", "Map", "Alias", "Description");
1147 (void) fprintf(file,"----------------------------------------------------\n");
1148
1149 for( threshold = GetXMLTreeChild(thresholds,"threshold");
1150 threshold != (XMLTreeInfo *)NULL;
1151 threshold = GetNextXMLTreeTag(threshold) )
1152 {
1153 map = GetXMLTreeAttribute(threshold, "map");
1154 if (map == (char *) NULL) {
1155 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1156 "XmlMissingAttribute", "<map>");
1157 thresholds=DestroyXMLTree(thresholds);
1158 return(MagickFalse);
1159 }
1160 alias = GetXMLTreeAttribute(threshold, "alias");
1161 /* alias is optional, no if test needed */
1162 description=GetXMLTreeChild(threshold,"description");
1163 if ( description == (XMLTreeInfo *)NULL ) {
1164 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1165 "XmlMissingElement", "<description>, map \"%s\"", map);
1166 thresholds=DestroyXMLTree(thresholds);
1167 return(MagickFalse);
1168 }
1169 content=GetXMLTreeContent(description);
1170 if ( content == (char *)NULL ) {
1171 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1172 "XmlMissingContent", "<description>, map \"%s\"", map);
1173 thresholds=DestroyXMLTree(thresholds);
1174 return(MagickFalse);
1175 }
1176 (void) fprintf(file,"%-16s %-12s %s\n",map,alias ? alias : "", content);
1177 }
1178 thresholds=DestroyXMLTree(thresholds);
1179 return(MagickTrue);
1180}
1181
1182/*
1183%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1184% %
1185% %
1186% %
1187% L i s t T h r e s h o l d M a p s %
1188% %
1189% %
1190% %
1191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1192%
1193% ListThresholdMaps() lists the threshold maps and their descriptions
1194% as defined by "threshold.xml" to a file.
1195%
1196% The format of the ListThresholdMaps method is:
1197%
1198% MagickBooleanType ListThresholdMaps(FILE *file,ExceptionInfo *exception)
1199%
1200% A description of each parameter follows.
1201%
1202% o file: An pointer to the output FILE.
1203%
1204% o exception: return any errors or warnings in this structure.
1205%
1206*/
1207MagickExport MagickBooleanType ListThresholdMaps(FILE *file,
1208 ExceptionInfo *exception)
1209{
1210 const StringInfo
1211 *option;
1212
1213 LinkedListInfo
1214 *options;
1215
1216 MagickStatusType
1217 status;
1218
1219 status=MagickFalse;
1220 if ( file == (FILE *)NULL )
1221 file = stdout;
1222 options=GetConfigureOptions(ThresholdsFilename,exception);
1223
1224 (void) fprintf(file, "\n Threshold Maps for Ordered Dither Operations\n");
1225
1226 while ( ( option=(const StringInfo *) GetNextValueInLinkedList(options) )
1227 != (const StringInfo *) NULL)
1228 {
1229 (void) fprintf(file,"\nPATH: %s\n\n",GetStringInfoPath(option));
1230 status|=ListThresholdMapFile(file,(const char *) GetStringInfoDatum(option),
1231 GetStringInfoPath(option),exception);
1232 }
1233 options=DestroyConfigureOptions(options);
1234 return(status != 0 ? MagickTrue : MagickFalse);
1235}
1236
1237/*
1238%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1239% %
1240% %
1241% %
1242% O r d e r e d D i t h e r I m a g e %
1243% %
1244% %
1245% %
1246%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1247%
1248% OrderedDitherImage() uses the ordered dithering technique of reducing color
1249% images to monochrome using positional information to retain as much
1250% information as possible.
1251%
1252% WARNING: This function is deprecated, and is now just a call to
1253% the more more powerful OrderedPosterizeImage(); function.
1254%
1255% The format of the OrderedDitherImage method is:
1256%
1257% MagickBooleanType OrderedDitherImage(Image *image)
1258% MagickBooleanType OrderedDitherImageChannel(Image *image,
1259% const ChannelType channel,ExceptionInfo *exception)
1260%
1261% A description of each parameter follows:
1262%
1263% o image: the image.
1264%
1265% o channel: the channel or channels to be thresholded.
1266%
1267% o exception: return any errors or warnings in this structure.
1268%
1269*/
1270
1271MagickExport MagickBooleanType OrderedDitherImage(Image *image)
1272{
1273 MagickBooleanType
1274 status;
1275
1276 status=OrderedDitherImageChannel(image,DefaultChannels,&image->exception);
1277 return(status);
1278}
1279
1280MagickExport MagickBooleanType OrderedDitherImageChannel(Image *image,
1281 const ChannelType channel,ExceptionInfo *exception)
1282{
1283 MagickBooleanType
1284 status;
1285
1286 /*
1287 Call the augumented function OrderedPosterizeImage()
1288 */
1289 status=OrderedPosterizeImageChannel(image,channel,"o8x8",exception);
1290 return(status);
1291}
1292
1293/*
1294%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1295% %
1296% %
1297% %
1298% O r d e r e d P o s t e r i z e I m a g e %
1299% %
1300% %
1301% %
1302%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1303%
1304% OrderedPosterizeImage() will perform a ordered dither based on a number
1305% of pre-defined dithering threshold maps, but over multiple intensity
1306% levels, which can be different for different channels, according to the
1307% input argument.
1308%
1309% The format of the OrderedPosterizeImage method is:
1310%
1311% MagickBooleanType OrderedPosterizeImage(Image *image,
1312% const char *threshold_map,ExceptionInfo *exception)
1313% MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1314% const ChannelType channel,const char *threshold_map,
1315% ExceptionInfo *exception)
1316%
1317% A description of each parameter follows:
1318%
1319% o image: the image.
1320%
1321% o channel: the channel or channels to be thresholded.
1322%
1323% o threshold_map: A string containing the name of the threshold dither
1324% map to use, followed by zero or more numbers representing the number
1325% of color levels tho dither between.
1326%
1327% Any level number less than 2 will be equivelent to 2, and means only
1328% binary dithering will be applied to each color channel.
1329%
1330% No numbers also means a 2 level (bitmap) dither will be applied to all
1331% channels, while a single number is the number of levels applied to each
1332% channel in sequence. More numbers will be applied in turn to each of
1333% the color channels.
1334%
1335% For example: "o3x3,6" will generate a 6 level posterization of the
1336% image with a ordered 3x3 diffused pixel dither being applied between
1337% each level. While checker,8,8,4 will produce a 332 colormaped image
1338% with only a single checkerboard hash pattern (50% grey) between each
1339% color level, to basically double the number of color levels with
1340% a bare minimim of dithering.
1341%
1342% o exception: return any errors or warnings in this structure.
1343%
1344*/
1345MagickExport MagickBooleanType OrderedPosterizeImage(Image *image,
1346 const char *threshold_map,ExceptionInfo *exception)
1347{
1348 MagickBooleanType
1349 status;
1350
1351 status=OrderedPosterizeImageChannel(image,DefaultChannels,threshold_map,
1352 exception);
1353 return(status);
1354}
1355
1356MagickExport MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1357 const ChannelType channel,const char *threshold_map,ExceptionInfo *exception)
1358{
1359#define DitherImageTag "Dither/Image"
1360
cristyc4c8d132010-01-07 01:58:38 +00001361 CacheView
1362 *image_view;
1363
cristy3ed852e2009-09-05 21:47:34 +00001364 LongPixelPacket
1365 levels;
1366
1367 MagickBooleanType
1368 status;
1369
cristy5f959472010-05-27 22:19:46 +00001370 MagickOffsetType
1371 progress;
1372
1373 ssize_t
1374 y;
1375
cristy3ed852e2009-09-05 21:47:34 +00001376 ThresholdMap
1377 *map;
1378
cristy3ed852e2009-09-05 21:47:34 +00001379 assert(image != (Image *) NULL);
1380 assert(image->signature == MagickSignature);
1381 if (image->debug != MagickFalse)
1382 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1383 assert(exception != (ExceptionInfo *) NULL);
1384 assert(exception->signature == MagickSignature);
1385 if (threshold_map == (const char *) NULL)
1386 return(MagickTrue);
1387 {
1388 char
1389 token[MaxTextExtent];
1390
1391 register const char
1392 *p;
1393
1394 p=(char *)threshold_map;
1395 while (((isspace((int) ((unsigned char) *p)) != 0) || (*p == ',')) &&
1396 (*p != '\0'))
1397 p++;
1398 threshold_map=p;
1399 while (((isspace((int) ((unsigned char) *p)) == 0) && (*p != ',')) &&
1400 (*p != '\0')) {
1401 if ((p-threshold_map) >= MaxTextExtent)
1402 break;
1403 token[p-threshold_map] = *p;
1404 p++;
1405 }
1406 token[p-threshold_map] = '\0';
1407 map = GetThresholdMap(token, exception);
1408 if ( map == (ThresholdMap *)NULL ) {
1409 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1410 "InvalidArgument","%s : '%s'","ordered-dither",threshold_map);
1411 return(MagickFalse);
1412 }
1413 }
1414 /* Set channel levels from extra comma seperated arguments
1415 Default to 2, the single value given, or individual channel values
1416 */
1417#if 1
1418 { /* parse directly as a comma seperated list of integers */
1419 char *p;
1420
1421 p = strchr((char *) threshold_map,',');
1422 if ( p != (char *)NULL && isdigit((int) ((unsigned char) *(++p))) )
cristyecd0ab52010-05-30 14:59:20 +00001423 levels.index = (unsigned int) strtoul(p, &p, 10);
cristy3ed852e2009-09-05 21:47:34 +00001424 else
1425 levels.index = 2;
1426
1427 levels.red = ((channel & RedChannel ) != 0) ? levels.index : 0;
1428 levels.green = ((channel & GreenChannel) != 0) ? levels.index : 0;
1429 levels.blue = ((channel & BlueChannel) != 0) ? levels.index : 0;
1430 levels.opacity = ((channel & OpacityChannel) != 0) ? levels.index : 0;
1431 levels.index = ((channel & IndexChannel) != 0
1432 && (image->colorspace == CMYKColorspace)) ? levels.index : 0;
1433
1434 /* if more than a single number, each channel has a separate value */
1435 if ( p != (char *) NULL && *p == ',' ) {
1436 p=strchr((char *) threshold_map,',');
1437 p++;
1438 if ((channel & RedChannel) != 0)
cristyecd0ab52010-05-30 14:59:20 +00001439 levels.red = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy3ed852e2009-09-05 21:47:34 +00001440 if ((channel & GreenChannel) != 0)
cristyecd0ab52010-05-30 14:59:20 +00001441 levels.green = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy3ed852e2009-09-05 21:47:34 +00001442 if ((channel & BlueChannel) != 0)
cristyecd0ab52010-05-30 14:59:20 +00001443 levels.blue = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy3ed852e2009-09-05 21:47:34 +00001444 if ((channel & IndexChannel) != 0 && image->colorspace == CMYKColorspace)
cristyecd0ab52010-05-30 14:59:20 +00001445 levels.index=(unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy3ed852e2009-09-05 21:47:34 +00001446 if ((channel & OpacityChannel) != 0)
cristyecd0ab52010-05-30 14:59:20 +00001447 levels.opacity = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy3ed852e2009-09-05 21:47:34 +00001448 }
1449 }
1450#else
1451 /* Parse level values as a geometry */
1452 /* This difficult!
1453 * How to map GeometryInfo structure elements into
1454 * LongPixelPacket structure elements, but according to channel?
1455 * Note the channels list may skip elements!!!!
1456 * EG -channel BA -ordered-dither map,2,3
1457 * will need to map g.rho -> l.blue, and g.sigma -> l.opacity
1458 * A simpler way is needed, probably converting geometry to a temporary
cristybb503372010-05-27 20:51:26 +00001459 * array, then using channel to advance the index into ssize_t pixel packet.
cristy3ed852e2009-09-05 21:47:34 +00001460 */
1461#endif
1462
1463#if 0
cristye8c25f92010-06-03 00:53:06 +00001464printf("DEBUG levels r=%u g=%u b=%u a=%u i=%u\n",
cristy3ed852e2009-09-05 21:47:34 +00001465 levels.red, levels.green, levels.blue, levels.opacity, levels.index);
1466#endif
1467
1468 { /* Do the posterized ordered dithering of the image */
cristycee97112010-05-28 00:44:52 +00001469 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001470 d;
1471
1472 /* d = number of psuedo-level divisions added between color levels */
1473 d = map->divisor-1;
1474
1475 /* reduce levels to levels - 1 */
1476 levels.red = levels.red ? levels.red-1 : 0;
1477 levels.green = levels.green ? levels.green-1 : 0;
1478 levels.blue = levels.blue ? levels.blue-1 : 0;
1479 levels.opacity = levels.opacity ? levels.opacity-1 : 0;
1480 levels.index = levels.index ? levels.index-1 : 0;
1481
1482 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1483 {
1484 InheritException(exception,&image->exception);
1485 return(MagickFalse);
1486 }
1487 status=MagickTrue;
1488 progress=0;
1489 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001490#if defined(MAGICKCORE_OPENMP_SUPPORT)
1491 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001492#endif
cristybb503372010-05-27 20:51:26 +00001493 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001494 {
1495 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001496 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001497
cristybb503372010-05-27 20:51:26 +00001498 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001499 x;
1500
1501 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001502 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001503
1504 if (status == MagickFalse)
1505 continue;
1506 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1507 if (q == (PixelPacket *) NULL)
1508 {
1509 status=MagickFalse;
1510 continue;
1511 }
1512 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001513 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001514 {
cristycee97112010-05-28 00:44:52 +00001515 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001516 threshold,
1517 t,
1518 l;
1519
1520 /*
1521 Figure out the dither threshold for this pixel
1522 This must be a integer from 1 to map->divisor-1
1523 */
1524 threshold = map->levels[(x%map->width) +map->width*(y%map->height)];
1525
1526 /* Dither each channel in the image as appropriate
1527 Notes on the integer Math...
1528 total number of divisions = (levels-1)*(divisor-1)+1)
1529 t1 = this colors psuedo_level =
1530 q->red * total_divisions / (QuantumRange+1)
1531 l = posterization level 0..levels
1532 t = dither threshold level 0..divisor-1 NB: 0 only on last
1533 Each color_level is of size QuantumRange / (levels-1)
1534 NB: All input levels and divisor are already had 1 subtracted
1535 Opacity is inverted so 'off' represents transparent.
1536 */
1537 if (levels.red) {
cristycee97112010-05-28 00:44:52 +00001538 t = (ssize_t) (QuantumScale*q->red*(levels.red*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001539 l = t/d; t = t-l*d;
1540 q->red=(Quantum) ((l+(t >= threshold))*QuantumRange/levels.red);
1541 }
1542 if (levels.green) {
cristycee97112010-05-28 00:44:52 +00001543 t = (ssize_t) (QuantumScale*q->green*(levels.green*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001544 l = t/d; t = t-l*d;
1545 q->green=(Quantum) ((l+(t >= threshold))*QuantumRange/levels.green);
1546 }
1547 if (levels.blue) {
cristycee97112010-05-28 00:44:52 +00001548 t = (ssize_t) (QuantumScale*q->blue*(levels.blue*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001549 l = t/d; t = t-l*d;
1550 q->blue=(Quantum) ((l+(t >= threshold))*QuantumRange/levels.blue);
1551 }
1552 if (levels.opacity) {
cristycee97112010-05-28 00:44:52 +00001553 t = (ssize_t) ((1.0-QuantumScale*q->opacity)*(levels.opacity*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001554 l = t/d; t = t-l*d;
1555 q->opacity=(Quantum) ((1.0-l-(t >= threshold))*QuantumRange/
1556 levels.opacity);
1557 }
1558 if (levels.index) {
cristycee97112010-05-28 00:44:52 +00001559 t = (ssize_t) (QuantumScale*indexes[x]*(levels.index*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001560 l = t/d; t = t-l*d;
1561 indexes[x]=(IndexPacket) ((l+(t>=threshold))*QuantumRange/
1562 levels.index);
1563 }
1564 q++;
1565 }
1566 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1567 status=MagickFalse;
1568 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1569 {
1570 MagickBooleanType
1571 proceed;
1572
cristyb5d5f722009-11-04 03:03:49 +00001573#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001574 #pragma omp critical (MagickCore_OrderedPosterizeImageChannel)
1575#endif
1576 proceed=SetImageProgress(image,DitherImageTag,progress++,image->rows);
1577 if (proceed == MagickFalse)
1578 status=MagickFalse;
1579 }
1580 }
1581 image_view=DestroyCacheView(image_view);
1582 }
1583 map=DestroyThresholdMap(map);
1584 return(MagickTrue);
1585}
1586
1587/*
1588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1589% %
1590% %
1591% %
1592% R a n d o m T h r e s h o l d I m a g e %
1593% %
1594% %
1595% %
1596%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1597%
1598% RandomThresholdImage() changes the value of individual pixels based on the
1599% intensity of each pixel compared to a random threshold. The result is a
1600% low-contrast, two color image.
1601%
1602% The format of the RandomThresholdImage method is:
1603%
1604% MagickBooleanType RandomThresholdImageChannel(Image *image,
1605% const char *thresholds,ExceptionInfo *exception)
1606% MagickBooleanType RandomThresholdImageChannel(Image *image,
1607% const ChannelType channel,const char *thresholds,
1608% ExceptionInfo *exception)
1609%
1610% A description of each parameter follows:
1611%
1612% o image: the image.
1613%
1614% o channel: the channel or channels to be thresholded.
1615%
1616% o thresholds: a geometry string containing low,high thresholds. If the
1617% string contains 2x2, 3x3, or 4x4, an ordered dither of order 2, 3, or 4
1618% is performed instead.
1619%
1620% o exception: return any errors or warnings in this structure.
1621%
1622*/
1623
1624MagickExport MagickBooleanType RandomThresholdImage(Image *image,
1625 const char *thresholds,ExceptionInfo *exception)
1626{
1627 MagickBooleanType
1628 status;
1629
1630 status=RandomThresholdImageChannel(image,DefaultChannels,thresholds,
1631 exception);
1632 return(status);
1633}
1634
1635MagickExport MagickBooleanType RandomThresholdImageChannel(Image *image,
1636 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1637{
1638#define ThresholdImageTag "Threshold/Image"
1639
cristyfa112112010-01-04 17:48:07 +00001640 CacheView
1641 *image_view;
1642
cristy3ed852e2009-09-05 21:47:34 +00001643 GeometryInfo
1644 geometry_info;
1645
1646 MagickStatusType
1647 flags;
1648
cristy3ed852e2009-09-05 21:47:34 +00001649 MagickBooleanType
1650 status;
1651
cristy5f959472010-05-27 22:19:46 +00001652 MagickOffsetType
1653 progress;
1654
cristy3ed852e2009-09-05 21:47:34 +00001655 MagickPixelPacket
1656 threshold;
1657
1658 MagickRealType
1659 min_threshold,
1660 max_threshold;
1661
1662 RandomInfo
cristyfa112112010-01-04 17:48:07 +00001663 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00001664
cristy5f959472010-05-27 22:19:46 +00001665 ssize_t
1666 y;
1667
cristy3ed852e2009-09-05 21:47:34 +00001668 assert(image != (Image *) NULL);
1669 assert(image->signature == MagickSignature);
1670 if (image->debug != MagickFalse)
1671 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1672 assert(exception != (ExceptionInfo *) NULL);
1673 assert(exception->signature == MagickSignature);
1674 if (thresholds == (const char *) NULL)
1675 return(MagickTrue);
1676 GetMagickPixelPacket(image,&threshold);
1677 min_threshold=0.0;
1678 max_threshold=(MagickRealType) QuantumRange;
1679 flags=ParseGeometry(thresholds,&geometry_info);
1680 min_threshold=geometry_info.rho;
1681 max_threshold=geometry_info.sigma;
1682 if ((flags & SigmaValue) == 0)
1683 max_threshold=min_threshold;
1684 if (strchr(thresholds,'%') != (char *) NULL)
1685 {
1686 max_threshold*=(MagickRealType) (0.01*QuantumRange);
1687 min_threshold*=(MagickRealType) (0.01*QuantumRange);
1688 }
1689 else
1690 if (((max_threshold == min_threshold) || (max_threshold == 1)) &&
1691 (min_threshold <= 8))
1692 {
1693 /*
1694 Backward Compatibility -- ordered-dither -- IM v 6.2.9-6.
1695 */
1696 status=OrderedPosterizeImageChannel(image,channel,thresholds,exception);
1697 return(status);
1698 }
1699 /*
1700 Random threshold image.
1701 */
1702 status=MagickTrue;
1703 progress=0;
1704 if (channel == AllChannels)
1705 {
1706 if (AcquireImageColormap(image,2) == MagickFalse)
1707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1708 image->filename);
1709 random_info=AcquireRandomInfoThreadSet();
1710 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001711#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy5c9e6f22010-09-17 17:31:01 +00001712 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001713#endif
cristybb503372010-05-27 20:51:26 +00001714 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001715 {
cristy5c9e6f22010-09-17 17:31:01 +00001716 const int
1717 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00001718
cristy3ed852e2009-09-05 21:47:34 +00001719 MagickBooleanType
1720 sync;
1721
1722 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001723 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001724
cristybb503372010-05-27 20:51:26 +00001725 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001726 x;
1727
1728 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001729 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001730
1731 if (status == MagickFalse)
1732 continue;
1733 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
1734 exception);
1735 if (q == (PixelPacket *) NULL)
1736 {
1737 status=MagickFalse;
1738 continue;
1739 }
1740 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001741 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001742 {
1743 IndexPacket
1744 index;
1745
1746 MagickRealType
1747 intensity;
1748
1749 intensity=(MagickRealType) PixelIntensityToQuantum(q);
1750 if (intensity < min_threshold)
1751 threshold.index=min_threshold;
1752 else if (intensity > max_threshold)
1753 threshold.index=max_threshold;
1754 else
1755 threshold.index=(MagickRealType)(QuantumRange*
1756 GetPseudoRandomValue(random_info[id]));
1757 index=(IndexPacket) (intensity <= threshold.index ? 0 : 1);
1758 indexes[x]=index;
cristybb503372010-05-27 20:51:26 +00001759 *q++=image->colormap[(ssize_t) index];
cristy3ed852e2009-09-05 21:47:34 +00001760 }
1761 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1762 if (sync == MagickFalse)
1763 status=MagickFalse;
1764 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1765 {
1766 MagickBooleanType
1767 proceed;
1768
cristyb5d5f722009-11-04 03:03:49 +00001769#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001770 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1771#endif
1772 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1773 image->rows);
1774 if (proceed == MagickFalse)
1775 status=MagickFalse;
1776 }
1777 }
1778 image_view=DestroyCacheView(image_view);
1779 random_info=DestroyRandomInfoThreadSet(random_info);
1780 return(status);
1781 }
1782 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1783 {
1784 InheritException(exception,&image->exception);
1785 return(MagickFalse);
1786 }
1787 random_info=AcquireRandomInfoThreadSet();
1788 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001789#if defined(MAGICKCORE_OPENMP_SUPPORT)
1790 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001791#endif
cristybb503372010-05-27 20:51:26 +00001792 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001793 {
cristy5c9e6f22010-09-17 17:31:01 +00001794 const int
1795 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00001796
cristy3ed852e2009-09-05 21:47:34 +00001797 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001798 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001799
cristy3ed852e2009-09-05 21:47:34 +00001800 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001801 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001802
cristy5c9e6f22010-09-17 17:31:01 +00001803 register ssize_t
1804 x;
1805
cristy3ed852e2009-09-05 21:47:34 +00001806 if (status == MagickFalse)
1807 continue;
1808 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1809 if (q == (PixelPacket *) NULL)
1810 {
1811 status=MagickFalse;
1812 continue;
1813 }
1814 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001815 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001816 {
1817 if ((channel & RedChannel) != 0)
1818 {
1819 if ((MagickRealType) q->red < min_threshold)
1820 threshold.red=min_threshold;
1821 else
1822 if ((MagickRealType) q->red > max_threshold)
1823 threshold.red=max_threshold;
1824 else
1825 threshold.red=(MagickRealType) (QuantumRange*
1826 GetPseudoRandomValue(random_info[id]));
1827 }
1828 if ((channel & GreenChannel) != 0)
1829 {
1830 if ((MagickRealType) q->green < min_threshold)
1831 threshold.green=min_threshold;
1832 else
1833 if ((MagickRealType) q->green > max_threshold)
1834 threshold.green=max_threshold;
1835 else
1836 threshold.green=(MagickRealType) (QuantumRange*
1837 GetPseudoRandomValue(random_info[id]));
1838 }
1839 if ((channel & BlueChannel) != 0)
1840 {
1841 if ((MagickRealType) q->blue < min_threshold)
1842 threshold.blue=min_threshold;
1843 else
1844 if ((MagickRealType) q->blue > max_threshold)
1845 threshold.blue=max_threshold;
1846 else
1847 threshold.blue=(MagickRealType) (QuantumRange*
1848 GetPseudoRandomValue(random_info[id]));
1849 }
1850 if ((channel & OpacityChannel) != 0)
1851 {
1852 if ((MagickRealType) q->opacity < min_threshold)
1853 threshold.opacity=min_threshold;
1854 else
1855 if ((MagickRealType) q->opacity > max_threshold)
1856 threshold.opacity=max_threshold;
1857 else
1858 threshold.opacity=(MagickRealType) (QuantumRange*
1859 GetPseudoRandomValue(random_info[id]));
1860 }
1861 if (((channel & IndexChannel) != 0) &&
1862 (image->colorspace == CMYKColorspace))
1863 {
1864 if ((MagickRealType) indexes[x] < min_threshold)
1865 threshold.index=min_threshold;
1866 else
1867 if ((MagickRealType) indexes[x] > max_threshold)
1868 threshold.index=max_threshold;
1869 else
1870 threshold.index=(MagickRealType) (QuantumRange*
1871 GetPseudoRandomValue(random_info[id]));
1872 }
1873 if ((channel & RedChannel) != 0)
1874 q->red=(Quantum) ((MagickRealType) q->red <= threshold.red ? 0 :
1875 QuantumRange);
1876 if ((channel & GreenChannel) != 0)
1877 q->green=(Quantum) ((MagickRealType) q->green <= threshold.green ? 0 :
1878 QuantumRange);
1879 if ((channel & BlueChannel) != 0)
1880 q->blue=(Quantum) ((MagickRealType) q->blue <= threshold.blue ? 0 :
1881 QuantumRange);
1882 if ((channel & OpacityChannel) != 0)
1883 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold.opacity ?
1884 0 : QuantumRange);
1885 if (((channel & IndexChannel) != 0) &&
1886 (image->colorspace == CMYKColorspace))
1887 indexes[x]=(IndexPacket) ((MagickRealType) indexes[x] <=
1888 threshold.index ? 0 : QuantumRange);
1889 q++;
1890 }
1891 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1892 status=MagickFalse;
1893 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1894 {
1895 MagickBooleanType
1896 proceed;
1897
cristyb5d5f722009-11-04 03:03:49 +00001898#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001899 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1900#endif
1901 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1902 image->rows);
1903 if (proceed == MagickFalse)
1904 status=MagickFalse;
1905 }
1906 }
1907 image_view=DestroyCacheView(image_view);
1908 random_info=DestroyRandomInfoThreadSet(random_info);
1909 return(status);
1910}
1911
1912/*
1913%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1914% %
1915% %
1916% %
1917% W h i t e T h r e s h o l d I m a g e %
1918% %
1919% %
1920% %
1921%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1922%
1923% WhiteThresholdImage() is like ThresholdImage() but forces all pixels above
cristy4e101302009-09-17 12:49:12 +00001924% the threshold into white while leaving all pixels at or below the threshold
cristy3ed852e2009-09-05 21:47:34 +00001925% unchanged.
1926%
1927% The format of the WhiteThresholdImage method is:
1928%
1929% MagickBooleanType WhiteThresholdImage(Image *image,const char *threshold)
1930% MagickBooleanType WhiteThresholdImageChannel(Image *image,
1931% const ChannelType channel,const char *threshold,
1932% ExceptionInfo *exception)
1933%
1934% A description of each parameter follows:
1935%
1936% o image: the image.
1937%
1938% o channel: the channel or channels to be thresholded.
1939%
1940% o threshold: Define the threshold value.
1941%
1942% o exception: return any errors or warnings in this structure.
1943%
1944*/
1945MagickExport MagickBooleanType WhiteThresholdImage(Image *image,
1946 const char *threshold)
1947{
1948 MagickBooleanType
1949 status;
1950
1951 status=WhiteThresholdImageChannel(image,DefaultChannels,threshold,
1952 &image->exception);
1953 return(status);
1954}
1955
1956MagickExport MagickBooleanType WhiteThresholdImageChannel(Image *image,
1957 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1958{
1959#define ThresholdImageTag "Threshold/Image"
1960
cristy5f959472010-05-27 22:19:46 +00001961 CacheView
1962 *image_view;
1963
cristy3ed852e2009-09-05 21:47:34 +00001964 GeometryInfo
1965 geometry_info;
1966
cristy3ed852e2009-09-05 21:47:34 +00001967 MagickBooleanType
1968 status;
1969
1970 MagickPixelPacket
1971 threshold;
1972
cristy5f959472010-05-27 22:19:46 +00001973 MagickOffsetType
1974 progress;
1975
cristy3ed852e2009-09-05 21:47:34 +00001976 MagickStatusType
1977 flags;
1978
cristy5f959472010-05-27 22:19:46 +00001979 ssize_t
1980 y;
cristy3ed852e2009-09-05 21:47:34 +00001981
1982 assert(image != (Image *) NULL);
1983 assert(image->signature == MagickSignature);
1984 if (image->debug != MagickFalse)
1985 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1986 if (thresholds == (const char *) NULL)
1987 return(MagickTrue);
1988 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1989 return(MagickFalse);
1990 flags=ParseGeometry(thresholds,&geometry_info);
1991 GetMagickPixelPacket(image,&threshold);
1992 threshold.red=geometry_info.rho;
1993 threshold.green=geometry_info.sigma;
1994 if ((flags & SigmaValue) == 0)
1995 threshold.green=threshold.red;
1996 threshold.blue=geometry_info.xi;
1997 if ((flags & XiValue) == 0)
1998 threshold.blue=threshold.red;
1999 threshold.opacity=geometry_info.psi;
2000 if ((flags & PsiValue) == 0)
2001 threshold.opacity=threshold.red;
2002 threshold.index=geometry_info.chi;
2003 if ((flags & ChiValue) == 0)
2004 threshold.index=threshold.red;
2005 if ((flags & PercentValue) != 0)
2006 {
2007 threshold.red*=(QuantumRange/100.0);
2008 threshold.green*=(QuantumRange/100.0);
2009 threshold.blue*=(QuantumRange/100.0);
2010 threshold.opacity*=(QuantumRange/100.0);
2011 threshold.index*=(QuantumRange/100.0);
2012 }
2013 /*
2014 White threshold image.
2015 */
2016 status=MagickTrue;
2017 progress=0;
2018 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002019#if defined(MAGICKCORE_OPENMP_SUPPORT)
2020 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002021#endif
cristybb503372010-05-27 20:51:26 +00002022 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002023 {
2024 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002025 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002026
cristybb503372010-05-27 20:51:26 +00002027 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002028 x;
2029
2030 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002031 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002032
2033 if (status == MagickFalse)
2034 continue;
2035 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2036 if (q == (PixelPacket *) NULL)
2037 {
2038 status=MagickFalse;
2039 continue;
2040 }
2041 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002042 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002043 {
cristyb0ea1af2009-11-28 20:44:46 +00002044 if (channel != DefaultChannels)
2045 {
2046 if (PixelIntensity(q) > MagickPixelIntensity(&threshold))
2047 {
2048 q->red=(Quantum) QuantumRange;
2049 q->green=(Quantum) QuantumRange;
2050 q->blue=(Quantum) QuantumRange;
2051 if (image->colorspace == CMYKColorspace)
2052 indexes[x]=(Quantum) QuantumRange;
2053 }
2054 }
2055 else
2056 {
2057 if (((channel & RedChannel) != 0) &&
2058 ((MagickRealType) q->red > threshold.red))
2059 q->red=(Quantum) QuantumRange;
2060 if (((channel & GreenChannel) != 0) &&
2061 ((MagickRealType) q->green > threshold.green))
2062 q->green=(Quantum) QuantumRange;
2063 if (((channel & BlueChannel) != 0) &&
2064 ((MagickRealType) q->blue > threshold.blue))
2065 q->blue=(Quantum) QuantumRange;
2066 if (((channel & OpacityChannel) != 0) &&
2067 ((MagickRealType) q->opacity > threshold.opacity))
2068 q->opacity=(Quantum) QuantumRange;
2069 if (((channel & IndexChannel) != 0) &&
2070 (image->colorspace == CMYKColorspace) &&
2071 ((MagickRealType) indexes[x] > threshold.index))
2072 indexes[x]=(Quantum) QuantumRange;
2073 }
cristy3ed852e2009-09-05 21:47:34 +00002074 q++;
2075 }
2076 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2077 status=MagickFalse;
2078 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2079 {
2080 MagickBooleanType
2081 proceed;
2082
cristyb5d5f722009-11-04 03:03:49 +00002083#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002084 #pragma omp critical (MagickCore_WhiteThresholdImageChannel)
2085#endif
2086 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
2087 image->rows);
2088 if (proceed == MagickFalse)
2089 status=MagickFalse;
2090 }
2091 }
2092 image_view=DestroyCacheView(image_view);
2093 return(status);
2094}