blob: b5c7b05e0218c495efbeb5bb5891641d9cb4f892 [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% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 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*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/property.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colormap.h"
50#include "MagickCore/colorspace.h"
51#include "MagickCore/configure.h"
52#include "MagickCore/constitute.h"
53#include "MagickCore/decorate.h"
54#include "MagickCore/draw.h"
55#include "MagickCore/enhance.h"
56#include "MagickCore/exception.h"
57#include "MagickCore/exception-private.h"
58#include "MagickCore/effect.h"
59#include "MagickCore/fx.h"
60#include "MagickCore/gem.h"
61#include "MagickCore/geometry.h"
62#include "MagickCore/image-private.h"
63#include "MagickCore/list.h"
64#include "MagickCore/log.h"
65#include "MagickCore/memory_.h"
66#include "MagickCore/monitor.h"
67#include "MagickCore/monitor-private.h"
68#include "MagickCore/montage.h"
69#include "MagickCore/option.h"
70#include "MagickCore/pixel-accessor.h"
71#include "MagickCore/quantize.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/random_.h"
74#include "MagickCore/random-private.h"
75#include "MagickCore/resize.h"
76#include "MagickCore/resource_.h"
77#include "MagickCore/segment.h"
78#include "MagickCore/shear.h"
79#include "MagickCore/signature-private.h"
80#include "MagickCore/string_.h"
81#include "MagickCore/string-private.h"
82#include "MagickCore/thread-private.h"
83#include "MagickCore/threshold.h"
84#include "MagickCore/transform.h"
85#include "MagickCore/xml-tree.h"
cristy3ed852e2009-09-05 21:47:34 +000086
87/*
88 Define declarations.
89*/
90#define ThresholdsFilename "thresholds.xml"
91
92/*
93 Typedef declarations.
94*/
95struct _ThresholdMap
96{
97 char
98 *map_id,
99 *description;
100
cristybb503372010-05-27 20:51:26 +0000101 size_t
cristy3ed852e2009-09-05 21:47:34 +0000102 width,
103 height;
104
cristybb503372010-05-27 20:51:26 +0000105 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000106 divisor,
107 *levels;
108};
109
110/*
111%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
112% %
113% %
114% %
115% A d a p t i v e T h r e s h o l d I m a g e %
116% %
117% %
118% %
119%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
120%
121% AdaptiveThresholdImage() selects an individual threshold for each pixel
122% based on the range of intensity values in its local neighborhood. This
123% allows for thresholding of an image whose global intensity histogram
124% doesn't contain distinctive peaks.
125%
126% The format of the AdaptiveThresholdImage method is:
127%
128% Image *AdaptiveThresholdImage(const Image *image,
cristybb503372010-05-27 20:51:26 +0000129% const size_t width,const size_t height,
130% const ssize_t offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000131%
132% A description of each parameter follows:
133%
134% o image: the image.
135%
136% o width: the width of the local neighborhood.
137%
138% o height: the height of the local neighborhood.
139%
140% o offset: the mean offset.
141%
142% o exception: return any errors or warnings in this structure.
143%
144*/
145MagickExport Image *AdaptiveThresholdImage(const Image *image,
cristybb503372010-05-27 20:51:26 +0000146 const size_t width,const size_t height,const ssize_t offset,
cristy3ed852e2009-09-05 21:47:34 +0000147 ExceptionInfo *exception)
148{
149#define ThresholdImageTag "Threshold/Image"
150
cristyc4c8d132010-01-07 01:58:38 +0000151 CacheView
152 *image_view,
153 *threshold_view;
154
cristy3ed852e2009-09-05 21:47:34 +0000155 Image
156 *threshold_image;
157
cristy3ed852e2009-09-05 21:47:34 +0000158 MagickBooleanType
159 status;
160
cristy5f959472010-05-27 22:19:46 +0000161 MagickOffsetType
162 progress;
163
cristy4c08aed2011-07-01 19:47:50 +0000164 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000165 zero;
166
167 MagickRealType
168 number_pixels;
169
cristy5f959472010-05-27 22:19:46 +0000170 ssize_t
171 y;
172
cristy3ed852e2009-09-05 21:47:34 +0000173 assert(image != (const Image *) NULL);
174 assert(image->signature == MagickSignature);
175 if (image->debug != MagickFalse)
176 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
177 assert(exception != (ExceptionInfo *) NULL);
178 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +0000179 threshold_image=CloneImage(image,0,0,MagickTrue,exception);
180 if (threshold_image == (Image *) NULL)
181 return((Image *) NULL);
182 if (SetImageStorageClass(threshold_image,DirectClass) == MagickFalse)
183 {
184 InheritException(exception,&threshold_image->exception);
185 threshold_image=DestroyImage(threshold_image);
186 return((Image *) NULL);
187 }
188 /*
189 Local adaptive threshold.
190 */
191 status=MagickTrue;
192 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000193 GetPixelInfo(image,&zero);
cristy3ed852e2009-09-05 21:47:34 +0000194 number_pixels=(MagickRealType) width*height;
195 image_view=AcquireCacheView(image);
196 threshold_view=AcquireCacheView(threshold_image);
cristyb5d5f722009-11-04 03:03:49 +0000197#if defined(MAGICKCORE_OPENMP_SUPPORT)
198 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000199#endif
cristybb503372010-05-27 20:51:26 +0000200 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000201 {
202 MagickBooleanType
203 sync;
204
cristy4c08aed2011-07-01 19:47:50 +0000205 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000206 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000207
cristybb503372010-05-27 20:51:26 +0000208 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000209 x;
210
cristy4c08aed2011-07-01 19:47:50 +0000211 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000212 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000213
214 if (status == MagickFalse)
215 continue;
cristyd99b0962010-05-29 23:14:26 +0000216 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
217 height/2L,image->columns+width,height,exception);
cristy3ed852e2009-09-05 21:47:34 +0000218 q=GetCacheViewAuthenticPixels(threshold_view,0,y,threshold_image->columns,1,
219 exception);
cristy4c08aed2011-07-01 19:47:50 +0000220 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000221 {
222 status=MagickFalse;
223 continue;
224 }
cristybb503372010-05-27 20:51:26 +0000225 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000226 {
cristy4c08aed2011-07-01 19:47:50 +0000227 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000228 mean,
229 pixel;
230
cristy4c08aed2011-07-01 19:47:50 +0000231 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +0000232 *r;
233
cristybb503372010-05-27 20:51:26 +0000234 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000235 u;
236
cristy9d314ff2011-03-09 01:30:28 +0000237 ssize_t
238 v;
239
cristy3ed852e2009-09-05 21:47:34 +0000240 pixel=zero;
241 mean=zero;
242 r=p;
cristybb503372010-05-27 20:51:26 +0000243 for (v=0; v < (ssize_t) height; v++)
cristy3ed852e2009-09-05 21:47:34 +0000244 {
cristybb503372010-05-27 20:51:26 +0000245 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +0000246 {
cristy4c08aed2011-07-01 19:47:50 +0000247 pixel.red+=GetPixelAlpha(image,r+u*GetPixelChannels(image));
248 pixel.green+=GetPixelGreen(image,r+u*GetPixelChannels(image));
249 pixel.blue+=GetPixelBlue(image,r+u*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +0000250 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +0000251 pixel.black+=GetPixelBlack(image,r+u*GetPixelChannels(image));
252 pixel.alpha+=GetPixelAlpha(image,r+u*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +0000253 }
cristy4c08aed2011-07-01 19:47:50 +0000254 r+=(image->columns+width)*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000255 }
256 mean.red=(MagickRealType) (pixel.red/number_pixels+offset);
257 mean.green=(MagickRealType) (pixel.green/number_pixels+offset);
258 mean.blue=(MagickRealType) (pixel.blue/number_pixels+offset);
cristy4c08aed2011-07-01 19:47:50 +0000259 mean.black=(MagickRealType) (pixel.black/number_pixels+offset);
260 mean.alpha=(MagickRealType) (pixel.alpha/number_pixels+offset);
261 SetPixelRed(threshold_image,(Quantum) (((MagickRealType)
262 GetPixelRed(threshold_image,q) <= mean.red) ? 0 : QuantumRange),q);
263 SetPixelGreen(threshold_image,(Quantum) (((MagickRealType)
264 GetPixelGreen(threshold_image,q) <= mean.green) ? 0 : QuantumRange),q);
265 SetPixelBlue(threshold_image,(Quantum) (((MagickRealType)
266 GetPixelBlue(threshold_image,q) <= mean.blue) ? 0 : QuantumRange),q);
cristy3ed852e2009-09-05 21:47:34 +0000267 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +0000268 SetPixelBlack(threshold_image,(Quantum) (((MagickRealType)
269 GetPixelBlack(threshold_image,q) <= mean.black) ? 0 : QuantumRange),
270 q);
271 SetPixelAlpha(threshold_image,(Quantum) (((MagickRealType)
272 GetPixelAlpha(threshold_image,q) <= mean.alpha) ? 0 : QuantumRange),q);
273 p+=GetPixelChannels(image);
274 q+=GetPixelChannels(threshold_image);
cristy3ed852e2009-09-05 21:47:34 +0000275 }
276 sync=SyncCacheViewAuthenticPixels(threshold_view,exception);
277 if (sync == MagickFalse)
278 status=MagickFalse;
279 if (image->progress_monitor != (MagickProgressMonitor) NULL)
280 {
281 MagickBooleanType
282 proceed;
283
cristyb5d5f722009-11-04 03:03:49 +0000284#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000285 #pragma omp critical (MagickCore_AdaptiveThresholdImage)
286#endif
287 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
288 image->rows);
289 if (proceed == MagickFalse)
290 status=MagickFalse;
291 }
292 }
293 threshold_view=DestroyCacheView(threshold_view);
294 image_view=DestroyCacheView(image_view);
295 if (status == MagickFalse)
296 threshold_image=DestroyImage(threshold_image);
297 return(threshold_image);
298}
299
300/*
301%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
302% %
303% %
304% %
305% B i l e v e l I m a g e %
306% %
307% %
308% %
309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
310%
311% BilevelImage() changes the value of individual pixels based on the
312% intensity of each pixel channel. The result is a high-contrast image.
313%
314% More precisely each channel value of the image is 'thresholded' so that if
315% it is equal to or less than the given value it is set to zero, while any
316% value greater than that give is set to it maximum or QuantumRange.
317%
318% This function is what is used to implement the "-threshold" operator for
319% the command line API.
320%
321% If the default channel setting is given the image is thresholded using just
322% the gray 'intensity' of the image, rather than the individual channels.
323%
324% The format of the BilevelImageChannel method is:
325%
326% MagickBooleanType BilevelImage(Image *image,const double threshold)
327% MagickBooleanType BilevelImageChannel(Image *image,
328% const ChannelType channel,const double threshold)
329%
330% A description of each parameter follows:
331%
332% o image: the image.
333%
334% o channel: the channel type.
335%
336% o threshold: define the threshold values.
337%
cristyf89cb1d2011-07-07 01:24:37 +0000338% Aside: You can get the same results as operator using LevelImages()
cristy3ed852e2009-09-05 21:47:34 +0000339% with the 'threshold' value for both the black_point and the white_point.
340%
341*/
342
343MagickExport MagickBooleanType BilevelImage(Image *image,const double threshold)
344{
345 MagickBooleanType
346 status;
347
348 status=BilevelImageChannel(image,DefaultChannels,threshold);
349 return(status);
350}
351
352MagickExport MagickBooleanType BilevelImageChannel(Image *image,
353 const ChannelType channel,const double threshold)
354{
355#define ThresholdImageTag "Threshold/Image"
356
cristyc4c8d132010-01-07 01:58:38 +0000357 CacheView
358 *image_view;
359
cristy3ed852e2009-09-05 21:47:34 +0000360 ExceptionInfo
361 *exception;
362
cristy3ed852e2009-09-05 21:47:34 +0000363 MagickBooleanType
364 status;
365
cristy5f959472010-05-27 22:19:46 +0000366 MagickOffsetType
367 progress;
368
369 ssize_t
370 y;
371
cristy3ed852e2009-09-05 21:47:34 +0000372 assert(image != (Image *) NULL);
373 assert(image->signature == MagickSignature);
374 if (image->debug != MagickFalse)
375 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
376 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
377 return(MagickFalse);
378 /*
379 Bilevel threshold image.
380 */
381 status=MagickTrue;
382 progress=0;
383 exception=(&image->exception);
384 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000385#if defined(MAGICKCORE_OPENMP_SUPPORT)
386 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000387#endif
cristybb503372010-05-27 20:51:26 +0000388 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000389 {
cristybb503372010-05-27 20:51:26 +0000390 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000391 x;
392
cristy4c08aed2011-07-01 19:47:50 +0000393 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000394 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000395
396 if (status == MagickFalse)
397 continue;
398 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000399 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000400 {
401 status=MagickFalse;
402 continue;
403 }
cristy3ed852e2009-09-05 21:47:34 +0000404 if (channel == DefaultChannels)
405 {
cristybb503372010-05-27 20:51:26 +0000406 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000407 {
cristy4c08aed2011-07-01 19:47:50 +0000408 SetPixelRed(image,(Quantum) ((MagickRealType)
409 GetPixelIntensity(image,q) <= threshold ? 0 : QuantumRange),q);
410 SetPixelGreen(image,GetPixelRed(image,q),q);
411 SetPixelBlue(image,GetPixelRed(image,q),q);
412 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000413 }
414 }
415 else
cristybb503372010-05-27 20:51:26 +0000416 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000417 {
cristy2b9582a2011-07-04 17:38:56 +0000418 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000419 SetPixelRed(image,(Quantum) ((MagickRealType)
420 GetPixelRed(image,q) <= threshold ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +0000421 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000422 SetPixelGreen(image,(Quantum) ((MagickRealType)
423 GetPixelGreen(image,q) <= threshold ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +0000424 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000425 SetPixelBlue(image,(Quantum) ((MagickRealType)
426 GetPixelBlue(image,q) <= threshold ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +0000427 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000428 (image->colorspace == CMYKColorspace))
429 SetPixelBlack(image,(Quantum) ((MagickRealType)
430 GetPixelBlack(image,q) <= threshold ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +0000431 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000432 {
433 if (image->matte == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000434 SetPixelAlpha(image,(Quantum) ((MagickRealType)
435 GetPixelAlpha(image,q) <= threshold ? 0 : QuantumRange),q);
cristy3ed852e2009-09-05 21:47:34 +0000436 else
cristy4c08aed2011-07-01 19:47:50 +0000437 SetPixelAlpha(image,(Quantum) ((MagickRealType)
438 GetPixelAlpha(image,q) >= threshold ? OpaqueAlpha :
439 TransparentAlpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000440 }
cristy4c08aed2011-07-01 19:47:50 +0000441 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000442 }
443 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
444 status=MagickFalse;
445 if (image->progress_monitor != (MagickProgressMonitor) NULL)
446 {
447 MagickBooleanType
448 proceed;
449
cristyb5d5f722009-11-04 03:03:49 +0000450#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000451 #pragma omp critical (MagickCore_BilevelImageChannel)
452#endif
453 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
454 image->rows);
455 if (proceed == MagickFalse)
456 status=MagickFalse;
457 }
458 }
459 image_view=DestroyCacheView(image_view);
460 return(status);
461}
462
463/*
464%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
465% %
466% %
467% %
468% B l a c k T h r e s h o l d I m a g e %
469% %
470% %
471% %
472%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
473%
474% BlackThresholdImage() is like ThresholdImage() but forces all pixels below
cristy4e101302009-09-17 12:49:12 +0000475% the threshold into black while leaving all pixels at or above the threshold
cristy3ed852e2009-09-05 21:47:34 +0000476% unchanged.
477%
478% The format of the BlackThresholdImage method is:
479%
480% MagickBooleanType BlackThresholdImage(Image *image,const char *threshold)
481% MagickBooleanType BlackThresholdImageChannel(Image *image,
482% const ChannelType channel,const char *threshold,
483% ExceptionInfo *exception)
484%
485% A description of each parameter follows:
486%
487% o image: the image.
488%
489% o channel: the channel or channels to be thresholded.
490%
491% o threshold: Define the threshold value.
492%
493% o exception: return any errors or warnings in this structure.
494%
495*/
496MagickExport MagickBooleanType BlackThresholdImage(Image *image,
497 const char *threshold)
498{
499 MagickBooleanType
500 status;
501
502 status=BlackThresholdImageChannel(image,DefaultChannels,threshold,
503 &image->exception);
504 return(status);
505}
506
507MagickExport MagickBooleanType BlackThresholdImageChannel(Image *image,
508 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
509{
510#define ThresholdImageTag "Threshold/Image"
511
cristyc4c8d132010-01-07 01:58:38 +0000512 CacheView
513 *image_view;
514
cristy3ed852e2009-09-05 21:47:34 +0000515 GeometryInfo
516 geometry_info;
517
cristy3ed852e2009-09-05 21:47:34 +0000518 MagickBooleanType
519 status;
520
cristy5f959472010-05-27 22:19:46 +0000521 MagickOffsetType
522 progress;
523
cristy4c08aed2011-07-01 19:47:50 +0000524 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000525 threshold;
526
527 MagickStatusType
528 flags;
529
cristy5f959472010-05-27 22:19:46 +0000530 ssize_t
531 y;
532
cristy3ed852e2009-09-05 21:47:34 +0000533 assert(image != (Image *) NULL);
534 assert(image->signature == MagickSignature);
535 if (image->debug != MagickFalse)
536 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
537 if (thresholds == (const char *) NULL)
538 return(MagickTrue);
539 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
540 return(MagickFalse);
cristy4c08aed2011-07-01 19:47:50 +0000541 GetPixelInfo(image,&threshold);
cristy3ed852e2009-09-05 21:47:34 +0000542 flags=ParseGeometry(thresholds,&geometry_info);
543 threshold.red=geometry_info.rho;
544 threshold.green=geometry_info.sigma;
545 if ((flags & SigmaValue) == 0)
546 threshold.green=threshold.red;
547 threshold.blue=geometry_info.xi;
548 if ((flags & XiValue) == 0)
549 threshold.blue=threshold.red;
cristy4c08aed2011-07-01 19:47:50 +0000550 threshold.alpha=geometry_info.psi;
cristy3ed852e2009-09-05 21:47:34 +0000551 if ((flags & PsiValue) == 0)
cristy4c08aed2011-07-01 19:47:50 +0000552 threshold.alpha=threshold.red;
553 threshold.black=geometry_info.chi;
cristy3ed852e2009-09-05 21:47:34 +0000554 if ((flags & ChiValue) == 0)
cristy4c08aed2011-07-01 19:47:50 +0000555 threshold.black=threshold.red;
cristy3ed852e2009-09-05 21:47:34 +0000556 if ((flags & PercentValue) != 0)
557 {
558 threshold.red*=(QuantumRange/100.0);
559 threshold.green*=(QuantumRange/100.0);
560 threshold.blue*=(QuantumRange/100.0);
cristy4c08aed2011-07-01 19:47:50 +0000561 threshold.alpha*=(QuantumRange/100.0);
562 threshold.black*=(QuantumRange/100.0);
cristy3ed852e2009-09-05 21:47:34 +0000563 }
564 /*
565 Black threshold image.
566 */
567 status=MagickTrue;
568 progress=0;
569 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000570#if defined(MAGICKCORE_OPENMP_SUPPORT)
571 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000572#endif
cristybb503372010-05-27 20:51:26 +0000573 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000574 {
cristybb503372010-05-27 20:51:26 +0000575 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000576 x;
577
cristy4c08aed2011-07-01 19:47:50 +0000578 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000579 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000580
581 if (status == MagickFalse)
582 continue;
583 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000584 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000585 {
586 status=MagickFalse;
587 continue;
588 }
cristybb503372010-05-27 20:51:26 +0000589 for (x=0; x < (ssize_t) image->columns; x++)
cristyb0ea1af2009-11-28 20:44:46 +0000590 {
591 if (channel != DefaultChannels)
592 {
cristy4c08aed2011-07-01 19:47:50 +0000593 if (GetPixelIntensity(image,q) < GetPixelInfoIntensity(&threshold))
cristyb0ea1af2009-11-28 20:44:46 +0000594 {
cristy4c08aed2011-07-01 19:47:50 +0000595 SetPixelRed(image,0,q);
596 SetPixelGreen(image,0,q);
597 SetPixelBlue(image,0,q);
cristyb0ea1af2009-11-28 20:44:46 +0000598 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +0000599 SetPixelBlack(image,0,q);
cristyb0ea1af2009-11-28 20:44:46 +0000600 }
601 }
602 else
603 {
cristy2b9582a2011-07-04 17:38:56 +0000604 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000605 ((MagickRealType) GetPixelRed(image,q) < threshold.red))
606 SetPixelRed(image,0,q);
cristy2b9582a2011-07-04 17:38:56 +0000607 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000608 ((MagickRealType) GetPixelGreen(image,q) < threshold.green))
609 SetPixelGreen(image,0,q);
cristy2b9582a2011-07-04 17:38:56 +0000610 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000611 ((MagickRealType) GetPixelBlue(image,q) < threshold.blue))
612 SetPixelBlue(image,0,q);
cristy2b9582a2011-07-04 17:38:56 +0000613 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristyb0ea1af2009-11-28 20:44:46 +0000614 (image->colorspace == CMYKColorspace) &&
cristy4c08aed2011-07-01 19:47:50 +0000615 ((MagickRealType) GetPixelBlack(image,q) < threshold.black))
616 SetPixelBlack(image,0,q);
cristy2b9582a2011-07-04 17:38:56 +0000617 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000618 ((MagickRealType) GetPixelAlpha(image,q) < threshold.alpha))
619 SetPixelAlpha(image,0,q);
cristyb0ea1af2009-11-28 20:44:46 +0000620 }
cristy4c08aed2011-07-01 19:47:50 +0000621 q+=GetPixelChannels(image);
cristyb0ea1af2009-11-28 20:44:46 +0000622 }
cristy3ed852e2009-09-05 21:47:34 +0000623 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
624 status=MagickFalse;
625 if (image->progress_monitor != (MagickProgressMonitor) NULL)
626 {
627 MagickBooleanType
628 proceed;
629
cristyb5d5f722009-11-04 03:03:49 +0000630#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000631 #pragma omp critical (MagickCore_BlackThresholdImageChannel)
632#endif
633 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
634 image->rows);
635 if (proceed == MagickFalse)
636 status=MagickFalse;
637 }
638 }
639 image_view=DestroyCacheView(image_view);
640 return(status);
641}
642
643/*
644%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
645% %
646% %
647% %
cristy1eb45dd2009-09-25 16:38:06 +0000648% C l a m p I m a g e %
649% %
650% %
651% %
652%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
653%
cristyecb0c6d2009-09-25 16:50:09 +0000654% ClampImage() restricts the color range from 0 to the quantum depth.
cristy1eb45dd2009-09-25 16:38:06 +0000655%
656% The format of the ClampImageChannel method is:
657%
658% MagickBooleanType ClampImage(Image *image)
659% MagickBooleanType ClampImageChannel(Image *image,
660% const ChannelType channel)
661%
662% A description of each parameter follows:
663%
664% o image: the image.
665%
666% o channel: the channel type.
667%
668*/
669
cristy75ffdb72010-01-07 17:40:12 +0000670static inline Quantum ClampToUnsignedQuantum(const Quantum quantum)
cristy1eb45dd2009-09-25 16:38:06 +0000671{
672#if defined(MAGICKCORE_HDRI_SUPPORT)
673 if (quantum <= 0)
674 return(0);
675 if (quantum >= QuantumRange)
676 return(QuantumRange);
677 return(quantum);
678#else
679 return(quantum);
680#endif
681}
682
683MagickExport MagickBooleanType ClampImage(Image *image)
684{
685 MagickBooleanType
686 status;
687
688 status=ClampImageChannel(image,DefaultChannels);
689 return(status);
690}
691
692MagickExport MagickBooleanType ClampImageChannel(Image *image,
693 const ChannelType channel)
694{
695#define ClampImageTag "Clamp/Image"
696
cristyc4c8d132010-01-07 01:58:38 +0000697 CacheView
698 *image_view;
699
cristy1eb45dd2009-09-25 16:38:06 +0000700 ExceptionInfo
701 *exception;
702
cristy1eb45dd2009-09-25 16:38:06 +0000703 MagickBooleanType
704 status;
705
cristy5f959472010-05-27 22:19:46 +0000706 MagickOffsetType
707 progress;
708
709 ssize_t
710 y;
711
cristy1eb45dd2009-09-25 16:38:06 +0000712 assert(image != (Image *) NULL);
713 assert(image->signature == MagickSignature);
714 if (image->debug != MagickFalse)
715 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
716 if (image->storage_class == PseudoClass)
717 {
cristybb503372010-05-27 20:51:26 +0000718 register ssize_t
cristy1eb45dd2009-09-25 16:38:06 +0000719 i;
720
721 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000722 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000723
724 q=image->colormap;
cristybb503372010-05-27 20:51:26 +0000725 for (i=0; i < (ssize_t) image->colors; i++)
cristy1eb45dd2009-09-25 16:38:06 +0000726 {
cristy4c08aed2011-07-01 19:47:50 +0000727 q->red=ClampToUnsignedQuantum(q->red);
728 q->green=ClampToUnsignedQuantum(q->green);
729 q->blue=ClampToUnsignedQuantum(q->blue);
730 q->alpha=ClampToUnsignedQuantum(q->alpha);
cristy1eb45dd2009-09-25 16:38:06 +0000731 q++;
732 }
733 return(SyncImage(image));
734 }
735 /*
cristy611721d2009-09-25 16:42:17 +0000736 Clamp image.
cristy1eb45dd2009-09-25 16:38:06 +0000737 */
738 status=MagickTrue;
739 progress=0;
740 exception=(&image->exception);
741 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000742#if defined(MAGICKCORE_OPENMP_SUPPORT)
743 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy1eb45dd2009-09-25 16:38:06 +0000744#endif
cristybb503372010-05-27 20:51:26 +0000745 for (y=0; y < (ssize_t) image->rows; y++)
cristy1eb45dd2009-09-25 16:38:06 +0000746 {
cristybb503372010-05-27 20:51:26 +0000747 register ssize_t
cristy1eb45dd2009-09-25 16:38:06 +0000748 x;
749
cristy4c08aed2011-07-01 19:47:50 +0000750 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000751 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000752
753 if (status == MagickFalse)
754 continue;
755 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000756 if (q == (const Quantum *) NULL)
cristy1eb45dd2009-09-25 16:38:06 +0000757 {
758 status=MagickFalse;
759 continue;
760 }
cristybb503372010-05-27 20:51:26 +0000761 for (x=0; x < (ssize_t) image->columns; x++)
cristy1eb45dd2009-09-25 16:38:06 +0000762 {
cristy2b9582a2011-07-04 17:38:56 +0000763 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000764 SetPixelRed(image,ClampToUnsignedQuantum(GetPixelRed(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +0000765 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000766 SetPixelGreen(image,ClampToUnsignedQuantum(GetPixelGreen(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +0000767 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000768 SetPixelBlue(image,ClampToUnsignedQuantum(GetPixelBlue(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +0000769 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy1eb45dd2009-09-25 16:38:06 +0000770 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000771 SetPixelBlack(image,ClampToUnsignedQuantum(GetPixelBlack(image,q)),q);
cristy2b9582a2011-07-04 17:38:56 +0000772 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000773 SetPixelAlpha(image,ClampToUnsignedQuantum(GetPixelAlpha(image,q)),q);
774 q+=GetPixelChannels(image);
cristy1eb45dd2009-09-25 16:38:06 +0000775 }
776 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
777 status=MagickFalse;
778 if (image->progress_monitor != (MagickProgressMonitor) NULL)
779 {
780 MagickBooleanType
781 proceed;
782
cristyb5d5f722009-11-04 03:03:49 +0000783#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1eb45dd2009-09-25 16:38:06 +0000784 #pragma omp critical (MagickCore_ClampImageChannel)
785#endif
786 proceed=SetImageProgress(image,ClampImageTag,progress++,
787 image->rows);
788 if (proceed == MagickFalse)
789 status=MagickFalse;
790 }
791 }
792 image_view=DestroyCacheView(image_view);
793 return(status);
794}
795
796/*
797%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
798% %
799% %
800% %
cristy3ed852e2009-09-05 21:47:34 +0000801% D e s t r o y T h r e s h o l d M a p %
802% %
803% %
804% %
805%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
806%
807% DestroyThresholdMap() de-allocate the given ThresholdMap
808%
809% The format of the ListThresholdMaps method is:
810%
811% ThresholdMap *DestroyThresholdMap(Threshold *map)
812%
813% A description of each parameter follows.
814%
815% o map: Pointer to the Threshold map to destroy
816%
817*/
818MagickExport ThresholdMap *DestroyThresholdMap(ThresholdMap *map)
819{
820 assert(map != (ThresholdMap *) NULL);
821 if (map->map_id != (char *) NULL)
822 map->map_id=DestroyString(map->map_id);
823 if (map->description != (char *) NULL)
824 map->description=DestroyString(map->description);
cristybb503372010-05-27 20:51:26 +0000825 if (map->levels != (ssize_t *) NULL)
826 map->levels=(ssize_t *) RelinquishMagickMemory(map->levels);
cristy3ed852e2009-09-05 21:47:34 +0000827 map=(ThresholdMap *) RelinquishMagickMemory(map);
828 return(map);
829}
830
831/*
832%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
833% %
834% %
835% %
836+ G e t T h r e s h o l d M a p F i l e %
837% %
838% %
839% %
840%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
841%
842% GetThresholdMapFile() look for a given threshold map name or alias in the
843% given XML file data, and return the allocated the map when found.
844%
845% The format of the ListThresholdMaps method is:
846%
847% ThresholdMap *GetThresholdMap(const char *xml,const char *filename,
848% const char *map_id,ExceptionInfo *exception)
849%
850% A description of each parameter follows.
851%
852% o xml: The threshold map list in XML format.
853%
854% o filename: The threshold map XML filename.
855%
856% o map_id: ID of the map to look for in XML list.
857%
858% o exception: return any errors or warnings in this structure.
859%
860*/
861MagickExport ThresholdMap *GetThresholdMapFile(const char *xml,
862 const char *filename,const char *map_id,ExceptionInfo *exception)
863{
864 const char
865 *attr,
866 *content;
867
868 double
869 value;
870
cristy092b7f72010-11-05 15:54:37 +0000871 ThresholdMap
cristy3ed852e2009-09-05 21:47:34 +0000872 *map;
873
874 XMLTreeInfo
875 *description,
876 *levels,
877 *threshold,
878 *thresholds;
879
880 map = (ThresholdMap *)NULL;
881 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
882 "Loading threshold map file \"%s\" ...",filename);
883 thresholds=NewXMLTree(xml,exception);
884 if ( thresholds == (XMLTreeInfo *)NULL )
885 return(map);
886
887 for( threshold = GetXMLTreeChild(thresholds,"threshold");
888 threshold != (XMLTreeInfo *)NULL;
889 threshold = GetNextXMLTreeTag(threshold) ) {
890 attr = GetXMLTreeAttribute(threshold, "map");
891 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
892 break;
893 attr = GetXMLTreeAttribute(threshold, "alias");
894 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
895 break;
896 }
897 if ( threshold == (XMLTreeInfo *)NULL ) {
898 return(map);
899 }
900 description = GetXMLTreeChild(threshold,"description");
901 if ( description == (XMLTreeInfo *)NULL ) {
902 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
903 "XmlMissingElement", "<description>, map \"%s\"", map_id);
904 thresholds = DestroyXMLTree(thresholds);
905 return(map);
906 }
907 levels = GetXMLTreeChild(threshold,"levels");
908 if ( levels == (XMLTreeInfo *)NULL ) {
909 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
910 "XmlMissingElement", "<levels>, map \"%s\"", map_id);
911 thresholds = DestroyXMLTree(thresholds);
912 return(map);
913 }
914
915 /* The map has been found -- Allocate a Threshold Map to return */
cristy73bd4a52010-10-05 11:24:23 +0000916 map = (ThresholdMap *)AcquireMagickMemory(sizeof(ThresholdMap));
cristy3ed852e2009-09-05 21:47:34 +0000917 if ( map == (ThresholdMap *)NULL )
918 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
919 map->map_id = (char *)NULL;
920 map->description = (char *)NULL;
cristybb503372010-05-27 20:51:26 +0000921 map->levels = (ssize_t *) NULL;
cristy3ed852e2009-09-05 21:47:34 +0000922
923 /* Assign Basic Attributes */
924 attr = GetXMLTreeAttribute(threshold, "map");
925 if ( attr != (char *)NULL )
926 map->map_id = ConstantString(attr);
927
928 content = GetXMLTreeContent(description);
929 if ( content != (char *)NULL )
930 map->description = ConstantString(content);
931
932 attr = GetXMLTreeAttribute(levels, "width");
933 if ( attr == (char *)NULL ) {
934 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
935 "XmlMissingAttribute", "<levels width>, map \"%s\"", map_id);
936 thresholds = DestroyXMLTree(thresholds);
937 map = DestroyThresholdMap(map);
938 return(map);
939 }
cristye27293e2009-12-18 02:53:20 +0000940 map->width = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000941 if ( map->width == 0 ) {
942 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
943 "XmlInvalidAttribute", "<levels width>, map \"%s\"", map_id);
944 thresholds = DestroyXMLTree(thresholds);
945 map = DestroyThresholdMap(map);
946 return(map);
947 }
948
949 attr = GetXMLTreeAttribute(levels, "height");
950 if ( attr == (char *)NULL ) {
951 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
952 "XmlMissingAttribute", "<levels height>, map \"%s\"", map_id);
953 thresholds = DestroyXMLTree(thresholds);
954 map = DestroyThresholdMap(map);
955 return(map);
956 }
cristye27293e2009-12-18 02:53:20 +0000957 map->height = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000958 if ( map->height == 0 ) {
959 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
960 "XmlInvalidAttribute", "<levels height>, map \"%s\"", map_id);
961 thresholds = DestroyXMLTree(thresholds);
962 map = DestroyThresholdMap(map);
963 return(map);
964 }
965
966 attr = GetXMLTreeAttribute(levels, "divisor");
967 if ( attr == (char *)NULL ) {
968 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
969 "XmlMissingAttribute", "<levels divisor>, map \"%s\"", map_id);
970 thresholds = DestroyXMLTree(thresholds);
971 map = DestroyThresholdMap(map);
972 return(map);
973 }
cristy55a91cd2010-12-01 00:57:40 +0000974 map->divisor = (ssize_t) StringToLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000975 if ( map->divisor < 2 ) {
976 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
977 "XmlInvalidAttribute", "<levels divisor>, map \"%s\"", map_id);
978 thresholds = DestroyXMLTree(thresholds);
979 map = DestroyThresholdMap(map);
980 return(map);
981 }
982
983 /* Allocate theshold levels array */
984 content = GetXMLTreeContent(levels);
985 if ( content == (char *)NULL ) {
986 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
987 "XmlMissingContent", "<levels>, map \"%s\"", map_id);
988 thresholds = DestroyXMLTree(thresholds);
989 map = DestroyThresholdMap(map);
990 return(map);
991 }
cristybb503372010-05-27 20:51:26 +0000992 map->levels=(ssize_t *) AcquireQuantumMemory((size_t) map->width,map->height*
cristy3ed852e2009-09-05 21:47:34 +0000993 sizeof(*map->levels));
cristybb503372010-05-27 20:51:26 +0000994 if ( map->levels == (ssize_t *)NULL )
cristy3ed852e2009-09-05 21:47:34 +0000995 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
996 { /* parse levels into integer array */
cristycee97112010-05-28 00:44:52 +0000997 ssize_t i;
cristy3ed852e2009-09-05 21:47:34 +0000998 char *p;
cristybb503372010-05-27 20:51:26 +0000999 for( i=0; i< (ssize_t) (map->width*map->height); i++) {
cristycee97112010-05-28 00:44:52 +00001000 map->levels[i] = (ssize_t)strtol(content, &p, 10);
cristy3ed852e2009-09-05 21:47:34 +00001001 if ( p == content ) {
1002 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1003 "XmlInvalidContent", "<level> too few values, map \"%s\"", map_id);
1004 thresholds = DestroyXMLTree(thresholds);
1005 map = DestroyThresholdMap(map);
1006 return(map);
1007 }
1008 if ( map->levels[i] < 0 || map->levels[i] > map->divisor ) {
1009 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
cristye8c25f92010-06-03 00:53:06 +00001010 "XmlInvalidContent", "<level> %.20g out of range, map \"%s\"",
1011 (double) map->levels[i],map_id);
cristy3ed852e2009-09-05 21:47:34 +00001012 thresholds = DestroyXMLTree(thresholds);
1013 map = DestroyThresholdMap(map);
1014 return(map);
1015 }
1016 content = p;
1017 }
1018 value=(double) strtol(content,&p,10);
cristyda16f162011-02-19 23:52:17 +00001019 (void) value;
cristy3ed852e2009-09-05 21:47:34 +00001020 if (p != content)
1021 {
1022 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1023 "XmlInvalidContent", "<level> too many values, map \"%s\"", map_id);
1024 thresholds=DestroyXMLTree(thresholds);
1025 map=DestroyThresholdMap(map);
1026 return(map);
1027 }
1028 }
1029
1030 thresholds = DestroyXMLTree(thresholds);
1031 return(map);
1032}
1033
1034/*
1035%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1036% %
1037% %
1038% %
1039% G e t T h r e s h o l d M a p %
1040% %
1041% %
1042% %
1043%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1044%
1045% GetThresholdMap() load and search one or more threshold map files for the
1046% a map matching the given name or aliase.
1047%
1048% The format of the GetThresholdMap method is:
1049%
1050% ThresholdMap *GetThresholdMap(const char *map_id,
1051% ExceptionInfo *exception)
1052%
1053% A description of each parameter follows.
1054%
1055% o map_id: ID of the map to look for.
1056%
1057% o exception: return any errors or warnings in this structure.
1058%
1059*/
1060MagickExport ThresholdMap *GetThresholdMap(const char *map_id,
1061 ExceptionInfo *exception)
1062{
1063 const StringInfo
1064 *option;
1065
1066 LinkedListInfo
1067 *options;
1068
1069 ThresholdMap
1070 *map;
1071
1072 map=(ThresholdMap *)NULL;
1073 options=GetConfigureOptions(ThresholdsFilename,exception);
1074 while (( option=(const StringInfo *) GetNextValueInLinkedList(options) )
1075 != (const StringInfo *) NULL && map == (ThresholdMap *)NULL )
1076 map=GetThresholdMapFile((const char *) GetStringInfoDatum(option),
1077 GetStringInfoPath(option),map_id,exception);
1078 options=DestroyConfigureOptions(options);
1079 return(map);
1080}
1081
1082/*
1083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1084% %
1085% %
1086% %
1087+ L i s t T h r e s h o l d M a p F i l e %
1088% %
1089% %
1090% %
1091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1092%
1093% ListThresholdMapFile() lists the threshold maps and their descriptions
1094% in the given XML file data.
1095%
1096% The format of the ListThresholdMaps method is:
1097%
1098% MagickBooleanType ListThresholdMaps(FILE *file,const char*xml,
1099% const char *filename,ExceptionInfo *exception)
1100%
1101% A description of each parameter follows.
1102%
1103% o file: An pointer to the output FILE.
1104%
1105% o xml: The threshold map list in XML format.
1106%
1107% o filename: The threshold map XML filename.
1108%
1109% o exception: return any errors or warnings in this structure.
1110%
1111*/
1112MagickBooleanType ListThresholdMapFile(FILE *file,const char *xml,
1113 const char *filename,ExceptionInfo *exception)
1114{
1115 XMLTreeInfo *thresholds,*threshold,*description;
1116 const char *map,*alias,*content;
1117
1118 assert( xml != (char *)NULL );
1119 assert( file != (FILE *)NULL );
1120
1121 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
1122 "Loading threshold map file \"%s\" ...",filename);
1123 thresholds=NewXMLTree(xml,exception);
1124 if ( thresholds == (XMLTreeInfo *)NULL )
1125 return(MagickFalse);
1126
cristy1e604812011-05-19 18:07:50 +00001127 (void) FormatLocaleFile(file,"%-16s %-12s %s\n","Map","Alias","Description");
1128 (void) FormatLocaleFile(file,
1129 "----------------------------------------------------\n");
cristy3ed852e2009-09-05 21:47:34 +00001130
1131 for( threshold = GetXMLTreeChild(thresholds,"threshold");
1132 threshold != (XMLTreeInfo *)NULL;
1133 threshold = GetNextXMLTreeTag(threshold) )
1134 {
1135 map = GetXMLTreeAttribute(threshold, "map");
1136 if (map == (char *) NULL) {
1137 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1138 "XmlMissingAttribute", "<map>");
1139 thresholds=DestroyXMLTree(thresholds);
1140 return(MagickFalse);
1141 }
1142 alias = GetXMLTreeAttribute(threshold, "alias");
1143 /* alias is optional, no if test needed */
1144 description=GetXMLTreeChild(threshold,"description");
1145 if ( description == (XMLTreeInfo *)NULL ) {
1146 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1147 "XmlMissingElement", "<description>, map \"%s\"", map);
1148 thresholds=DestroyXMLTree(thresholds);
1149 return(MagickFalse);
1150 }
1151 content=GetXMLTreeContent(description);
1152 if ( content == (char *)NULL ) {
1153 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1154 "XmlMissingContent", "<description>, map \"%s\"", map);
1155 thresholds=DestroyXMLTree(thresholds);
1156 return(MagickFalse);
1157 }
cristy1e604812011-05-19 18:07:50 +00001158 (void) FormatLocaleFile(file,"%-16s %-12s %s\n",map,alias ? alias : "",
1159 content);
cristy3ed852e2009-09-05 21:47:34 +00001160 }
1161 thresholds=DestroyXMLTree(thresholds);
1162 return(MagickTrue);
1163}
1164
1165/*
1166%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1167% %
1168% %
1169% %
1170% L i s t T h r e s h o l d M a p s %
1171% %
1172% %
1173% %
1174%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1175%
1176% ListThresholdMaps() lists the threshold maps and their descriptions
1177% as defined by "threshold.xml" to a file.
1178%
1179% The format of the ListThresholdMaps method is:
1180%
1181% MagickBooleanType ListThresholdMaps(FILE *file,ExceptionInfo *exception)
1182%
1183% A description of each parameter follows.
1184%
1185% o file: An pointer to the output FILE.
1186%
1187% o exception: return any errors or warnings in this structure.
1188%
1189*/
1190MagickExport MagickBooleanType ListThresholdMaps(FILE *file,
1191 ExceptionInfo *exception)
1192{
1193 const StringInfo
1194 *option;
1195
1196 LinkedListInfo
1197 *options;
1198
1199 MagickStatusType
1200 status;
1201
1202 status=MagickFalse;
1203 if ( file == (FILE *)NULL )
1204 file = stdout;
1205 options=GetConfigureOptions(ThresholdsFilename,exception);
1206
cristy1e604812011-05-19 18:07:50 +00001207 (void) FormatLocaleFile(file,
1208 "\n Threshold Maps for Ordered Dither Operations\n");
cristy3ed852e2009-09-05 21:47:34 +00001209 while ( ( option=(const StringInfo *) GetNextValueInLinkedList(options) )
1210 != (const StringInfo *) NULL)
1211 {
cristyb51dff52011-05-19 16:55:47 +00001212 (void) FormatLocaleFile(file,"\nPATH: %s\n\n",GetStringInfoPath(option));
cristy3ed852e2009-09-05 21:47:34 +00001213 status|=ListThresholdMapFile(file,(const char *) GetStringInfoDatum(option),
1214 GetStringInfoPath(option),exception);
1215 }
1216 options=DestroyConfigureOptions(options);
1217 return(status != 0 ? MagickTrue : MagickFalse);
1218}
1219
1220/*
1221%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1222% %
1223% %
1224% %
cristy3ed852e2009-09-05 21:47:34 +00001225% O r d e r e d P o s t e r i z e I m a g e %
1226% %
1227% %
1228% %
1229%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1230%
1231% OrderedPosterizeImage() will perform a ordered dither based on a number
1232% of pre-defined dithering threshold maps, but over multiple intensity
1233% levels, which can be different for different channels, according to the
1234% input argument.
1235%
1236% The format of the OrderedPosterizeImage method is:
1237%
1238% MagickBooleanType OrderedPosterizeImage(Image *image,
1239% const char *threshold_map,ExceptionInfo *exception)
1240% MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1241% const ChannelType channel,const char *threshold_map,
1242% ExceptionInfo *exception)
1243%
1244% A description of each parameter follows:
1245%
1246% o image: the image.
1247%
1248% o channel: the channel or channels to be thresholded.
1249%
1250% o threshold_map: A string containing the name of the threshold dither
1251% map to use, followed by zero or more numbers representing the number
1252% of color levels tho dither between.
1253%
cristyf998fb32011-04-27 23:00:47 +00001254% Any level number less than 2 will be equivalent to 2, and means only
cristy3ed852e2009-09-05 21:47:34 +00001255% binary dithering will be applied to each color channel.
1256%
1257% No numbers also means a 2 level (bitmap) dither will be applied to all
1258% channels, while a single number is the number of levels applied to each
1259% channel in sequence. More numbers will be applied in turn to each of
1260% the color channels.
1261%
1262% For example: "o3x3,6" will generate a 6 level posterization of the
1263% image with a ordered 3x3 diffused pixel dither being applied between
1264% each level. While checker,8,8,4 will produce a 332 colormaped image
1265% with only a single checkerboard hash pattern (50% grey) between each
1266% color level, to basically double the number of color levels with
1267% a bare minimim of dithering.
1268%
1269% o exception: return any errors or warnings in this structure.
1270%
1271*/
1272MagickExport MagickBooleanType OrderedPosterizeImage(Image *image,
1273 const char *threshold_map,ExceptionInfo *exception)
1274{
1275 MagickBooleanType
1276 status;
1277
1278 status=OrderedPosterizeImageChannel(image,DefaultChannels,threshold_map,
1279 exception);
1280 return(status);
1281}
1282
1283MagickExport MagickBooleanType OrderedPosterizeImageChannel(Image *image,
1284 const ChannelType channel,const char *threshold_map,ExceptionInfo *exception)
1285{
1286#define DitherImageTag "Dither/Image"
1287
cristyc4c8d132010-01-07 01:58:38 +00001288 CacheView
1289 *image_view;
1290
cristy3ed852e2009-09-05 21:47:34 +00001291 LongPixelPacket
1292 levels;
1293
1294 MagickBooleanType
1295 status;
1296
cristy5f959472010-05-27 22:19:46 +00001297 MagickOffsetType
1298 progress;
1299
1300 ssize_t
1301 y;
1302
cristy3ed852e2009-09-05 21:47:34 +00001303 ThresholdMap
1304 *map;
1305
cristy3ed852e2009-09-05 21:47:34 +00001306 assert(image != (Image *) NULL);
1307 assert(image->signature == MagickSignature);
1308 if (image->debug != MagickFalse)
1309 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1310 assert(exception != (ExceptionInfo *) NULL);
1311 assert(exception->signature == MagickSignature);
1312 if (threshold_map == (const char *) NULL)
1313 return(MagickTrue);
1314 {
1315 char
1316 token[MaxTextExtent];
1317
1318 register const char
1319 *p;
1320
1321 p=(char *)threshold_map;
1322 while (((isspace((int) ((unsigned char) *p)) != 0) || (*p == ',')) &&
1323 (*p != '\0'))
1324 p++;
1325 threshold_map=p;
1326 while (((isspace((int) ((unsigned char) *p)) == 0) && (*p != ',')) &&
1327 (*p != '\0')) {
cristy37e0b382011-06-07 13:31:21 +00001328 if ((p-threshold_map) >= (MaxTextExtent-1))
cristy3ed852e2009-09-05 21:47:34 +00001329 break;
1330 token[p-threshold_map] = *p;
1331 p++;
1332 }
1333 token[p-threshold_map] = '\0';
1334 map = GetThresholdMap(token, exception);
1335 if ( map == (ThresholdMap *)NULL ) {
1336 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1337 "InvalidArgument","%s : '%s'","ordered-dither",threshold_map);
1338 return(MagickFalse);
1339 }
1340 }
glennrp1e7f7bc2011-03-02 19:25:28 +00001341 /* Set channel levels from extra comma separated arguments
cristy3ed852e2009-09-05 21:47:34 +00001342 Default to 2, the single value given, or individual channel values
1343 */
1344#if 1
glennrp1e7f7bc2011-03-02 19:25:28 +00001345 { /* parse directly as a comma separated list of integers */
cristy3ed852e2009-09-05 21:47:34 +00001346 char *p;
1347
1348 p = strchr((char *) threshold_map,',');
1349 if ( p != (char *)NULL && isdigit((int) ((unsigned char) *(++p))) )
cristy4c08aed2011-07-01 19:47:50 +00001350 levels.black = (unsigned int) strtoul(p, &p, 10);
cristy3ed852e2009-09-05 21:47:34 +00001351 else
cristy4c08aed2011-07-01 19:47:50 +00001352 levels.black = 2;
cristy3ed852e2009-09-05 21:47:34 +00001353
cristy4c08aed2011-07-01 19:47:50 +00001354 levels.red = ((channel & RedChannel ) != 0) ? levels.black : 0;
1355 levels.green = ((channel & GreenChannel) != 0) ? levels.black : 0;
1356 levels.blue = ((channel & BlueChannel) != 0) ? levels.black : 0;
1357 levels.black = ((channel & BlackChannel) != 0 &&
1358 (image->colorspace == CMYKColorspace)) ? levels.black : 0;
cristy2b9582a2011-07-04 17:38:56 +00001359 levels.alpha = ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) ? levels.black : 0;
cristy3ed852e2009-09-05 21:47:34 +00001360
1361 /* if more than a single number, each channel has a separate value */
1362 if ( p != (char *) NULL && *p == ',' ) {
1363 p=strchr((char *) threshold_map,',');
1364 p++;
cristy2b9582a2011-07-04 17:38:56 +00001365 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristyecd0ab52010-05-30 14:59:20 +00001366 levels.red = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy2b9582a2011-07-04 17:38:56 +00001367 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristyecd0ab52010-05-30 14:59:20 +00001368 levels.green = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy2b9582a2011-07-04 17:38:56 +00001369 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristyecd0ab52010-05-30 14:59:20 +00001370 levels.blue = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy2b9582a2011-07-04 17:38:56 +00001371 if ((GetPixelBlackTraits(image) & ActivePixelTrait) != 0 &&
cristy4c08aed2011-07-01 19:47:50 +00001372 (image->colorspace == CMYKColorspace))
1373 levels.black=(unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy2b9582a2011-07-04 17:38:56 +00001374 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001375 levels.alpha = (unsigned int) strtoul(p, &p, 10), (void)(*p == ',' && p++);
cristy3ed852e2009-09-05 21:47:34 +00001376 }
1377 }
1378#else
1379 /* Parse level values as a geometry */
1380 /* This difficult!
1381 * How to map GeometryInfo structure elements into
1382 * LongPixelPacket structure elements, but according to channel?
1383 * Note the channels list may skip elements!!!!
1384 * EG -channel BA -ordered-dither map,2,3
cristy4c08aed2011-07-01 19:47:50 +00001385 * will need to map g.rho -> l.blue, and g.sigma -> l.alpha
cristy3ed852e2009-09-05 21:47:34 +00001386 * A simpler way is needed, probably converting geometry to a temporary
cristybb503372010-05-27 20:51:26 +00001387 * array, then using channel to advance the index into ssize_t pixel packet.
cristy3ed852e2009-09-05 21:47:34 +00001388 */
1389#endif
1390
1391#if 0
cristye8c25f92010-06-03 00:53:06 +00001392printf("DEBUG levels r=%u g=%u b=%u a=%u i=%u\n",
cristy4c08aed2011-07-01 19:47:50 +00001393 levels.red, levels.green, levels.blue, levels.alpha, levels.index);
cristy3ed852e2009-09-05 21:47:34 +00001394#endif
1395
1396 { /* Do the posterized ordered dithering of the image */
cristycee97112010-05-28 00:44:52 +00001397 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001398 d;
1399
1400 /* d = number of psuedo-level divisions added between color levels */
1401 d = map->divisor-1;
1402
1403 /* reduce levels to levels - 1 */
1404 levels.red = levels.red ? levels.red-1 : 0;
1405 levels.green = levels.green ? levels.green-1 : 0;
1406 levels.blue = levels.blue ? levels.blue-1 : 0;
cristy4c08aed2011-07-01 19:47:50 +00001407 levels.black = levels.black ? levels.black-1 : 0;
1408 levels.alpha = levels.alpha ? levels.alpha-1 : 0;
cristy3ed852e2009-09-05 21:47:34 +00001409
1410 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1411 {
1412 InheritException(exception,&image->exception);
1413 return(MagickFalse);
1414 }
1415 status=MagickTrue;
1416 progress=0;
1417 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001418#if defined(MAGICKCORE_OPENMP_SUPPORT)
1419 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001420#endif
cristybb503372010-05-27 20:51:26 +00001421 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001422 {
cristybb503372010-05-27 20:51:26 +00001423 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001424 x;
1425
cristy4c08aed2011-07-01 19:47:50 +00001426 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001427 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001428
1429 if (status == MagickFalse)
1430 continue;
1431 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001432 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001433 {
1434 status=MagickFalse;
1435 continue;
1436 }
cristybb503372010-05-27 20:51:26 +00001437 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001438 {
cristycee97112010-05-28 00:44:52 +00001439 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001440 threshold,
1441 t,
1442 l;
1443
1444 /*
1445 Figure out the dither threshold for this pixel
1446 This must be a integer from 1 to map->divisor-1
1447 */
1448 threshold = map->levels[(x%map->width) +map->width*(y%map->height)];
1449
1450 /* Dither each channel in the image as appropriate
1451 Notes on the integer Math...
1452 total number of divisions = (levels-1)*(divisor-1)+1)
1453 t1 = this colors psuedo_level =
1454 q->red * total_divisions / (QuantumRange+1)
1455 l = posterization level 0..levels
1456 t = dither threshold level 0..divisor-1 NB: 0 only on last
1457 Each color_level is of size QuantumRange / (levels-1)
1458 NB: All input levels and divisor are already had 1 subtracted
1459 Opacity is inverted so 'off' represents transparent.
1460 */
cristy4c08aed2011-07-01 19:47:50 +00001461 if (levels.red != 0) {
1462 t = (ssize_t) (QuantumScale*GetPixelRed(image,q)*(levels.red*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001463 l = t/d; t = t-l*d;
cristy4c08aed2011-07-01 19:47:50 +00001464 SetPixelRed(image,RoundToQuantum((MagickRealType)
1465 ((l+(t >= threshold))*(MagickRealType) QuantumRange/levels.red)),q);
cristy3ed852e2009-09-05 21:47:34 +00001466 }
cristy4c08aed2011-07-01 19:47:50 +00001467 if (levels.green != 0) {
1468 t = (ssize_t) (QuantumScale*GetPixelGreen(image,q)*
cristyfba5a8b2011-05-03 17:12:12 +00001469 (levels.green*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001470 l = t/d; t = t-l*d;
cristy4c08aed2011-07-01 19:47:50 +00001471 SetPixelGreen(image,RoundToQuantum((MagickRealType)
1472 ((l+(t >= threshold))*(MagickRealType) QuantumRange/levels.green)),q);
cristy3ed852e2009-09-05 21:47:34 +00001473 }
cristy4c08aed2011-07-01 19:47:50 +00001474 if (levels.blue != 0) {
1475 t = (ssize_t) (QuantumScale*GetPixelBlue(image,q)*
cristyfba5a8b2011-05-03 17:12:12 +00001476 (levels.blue*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001477 l = t/d; t = t-l*d;
cristy4c08aed2011-07-01 19:47:50 +00001478 SetPixelBlue(image,RoundToQuantum((MagickRealType)
1479 ((l+(t >= threshold))*(MagickRealType) QuantumRange/levels.blue)),q);
cristy3ed852e2009-09-05 21:47:34 +00001480 }
cristy4c08aed2011-07-01 19:47:50 +00001481 if (levels.alpha != 0) {
1482 t = (ssize_t) ((1.0-QuantumScale*GetPixelAlpha(image,q))*
1483 (levels.alpha*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001484 l = t/d; t = t-l*d;
cristy4c08aed2011-07-01 19:47:50 +00001485 SetPixelAlpha(image,RoundToQuantum((MagickRealType)
cristyfba5a8b2011-05-03 17:12:12 +00001486 ((1.0-l-(t >= threshold))*(MagickRealType) QuantumRange/
cristy4c08aed2011-07-01 19:47:50 +00001487 levels.alpha)),q);
cristy3ed852e2009-09-05 21:47:34 +00001488 }
cristy4c08aed2011-07-01 19:47:50 +00001489 if (levels.black != 0) {
1490 t = (ssize_t) (QuantumScale*GetPixelBlack(image,q)*
1491 (levels.black*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001492 l = t/d; t = t-l*d;
cristy4c08aed2011-07-01 19:47:50 +00001493 SetPixelBlack(image,RoundToQuantum((MagickRealType)
1494 ((l+(t>=threshold))*(MagickRealType) QuantumRange/levels.black)),q);
cristy3ed852e2009-09-05 21:47:34 +00001495 }
cristy4c08aed2011-07-01 19:47:50 +00001496 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001497 }
1498 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1499 status=MagickFalse;
1500 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1501 {
1502 MagickBooleanType
1503 proceed;
1504
cristyb5d5f722009-11-04 03:03:49 +00001505#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001506 #pragma omp critical (MagickCore_OrderedPosterizeImageChannel)
1507#endif
1508 proceed=SetImageProgress(image,DitherImageTag,progress++,image->rows);
1509 if (proceed == MagickFalse)
1510 status=MagickFalse;
1511 }
1512 }
1513 image_view=DestroyCacheView(image_view);
1514 }
1515 map=DestroyThresholdMap(map);
1516 return(MagickTrue);
1517}
1518
1519/*
1520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1521% %
1522% %
1523% %
1524% R a n d o m T h r e s h o l d I m a g e %
1525% %
1526% %
1527% %
1528%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1529%
1530% RandomThresholdImage() changes the value of individual pixels based on the
1531% intensity of each pixel compared to a random threshold. The result is a
1532% low-contrast, two color image.
1533%
1534% The format of the RandomThresholdImage method is:
1535%
1536% MagickBooleanType RandomThresholdImageChannel(Image *image,
1537% const char *thresholds,ExceptionInfo *exception)
1538% MagickBooleanType RandomThresholdImageChannel(Image *image,
1539% const ChannelType channel,const char *thresholds,
1540% ExceptionInfo *exception)
1541%
1542% A description of each parameter follows:
1543%
1544% o image: the image.
1545%
1546% o channel: the channel or channels to be thresholded.
1547%
1548% o thresholds: a geometry string containing low,high thresholds. If the
1549% string contains 2x2, 3x3, or 4x4, an ordered dither of order 2, 3, or 4
1550% is performed instead.
1551%
1552% o exception: return any errors or warnings in this structure.
1553%
1554*/
1555
1556MagickExport MagickBooleanType RandomThresholdImage(Image *image,
1557 const char *thresholds,ExceptionInfo *exception)
1558{
1559 MagickBooleanType
1560 status;
1561
1562 status=RandomThresholdImageChannel(image,DefaultChannels,thresholds,
1563 exception);
1564 return(status);
1565}
1566
1567MagickExport MagickBooleanType RandomThresholdImageChannel(Image *image,
1568 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1569{
1570#define ThresholdImageTag "Threshold/Image"
1571
cristyfa112112010-01-04 17:48:07 +00001572 CacheView
1573 *image_view;
1574
cristy3ed852e2009-09-05 21:47:34 +00001575 GeometryInfo
1576 geometry_info;
1577
1578 MagickStatusType
1579 flags;
1580
cristy3ed852e2009-09-05 21:47:34 +00001581 MagickBooleanType
1582 status;
1583
cristy5f959472010-05-27 22:19:46 +00001584 MagickOffsetType
1585 progress;
1586
cristy4c08aed2011-07-01 19:47:50 +00001587 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001588 threshold;
1589
1590 MagickRealType
1591 min_threshold,
1592 max_threshold;
1593
1594 RandomInfo
cristyfa112112010-01-04 17:48:07 +00001595 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00001596
cristy5f959472010-05-27 22:19:46 +00001597 ssize_t
1598 y;
1599
cristy3ed852e2009-09-05 21:47:34 +00001600 assert(image != (Image *) NULL);
1601 assert(image->signature == MagickSignature);
1602 if (image->debug != MagickFalse)
1603 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1604 assert(exception != (ExceptionInfo *) NULL);
1605 assert(exception->signature == MagickSignature);
1606 if (thresholds == (const char *) NULL)
1607 return(MagickTrue);
cristy4c08aed2011-07-01 19:47:50 +00001608 GetPixelInfo(image,&threshold);
cristy3ed852e2009-09-05 21:47:34 +00001609 min_threshold=0.0;
1610 max_threshold=(MagickRealType) QuantumRange;
1611 flags=ParseGeometry(thresholds,&geometry_info);
1612 min_threshold=geometry_info.rho;
1613 max_threshold=geometry_info.sigma;
1614 if ((flags & SigmaValue) == 0)
1615 max_threshold=min_threshold;
1616 if (strchr(thresholds,'%') != (char *) NULL)
1617 {
1618 max_threshold*=(MagickRealType) (0.01*QuantumRange);
1619 min_threshold*=(MagickRealType) (0.01*QuantumRange);
1620 }
1621 else
1622 if (((max_threshold == min_threshold) || (max_threshold == 1)) &&
1623 (min_threshold <= 8))
1624 {
1625 /*
1626 Backward Compatibility -- ordered-dither -- IM v 6.2.9-6.
1627 */
1628 status=OrderedPosterizeImageChannel(image,channel,thresholds,exception);
1629 return(status);
1630 }
1631 /*
1632 Random threshold image.
1633 */
1634 status=MagickTrue;
1635 progress=0;
cristy9a9230e2011-04-26 14:56:14 +00001636 if (channel == CompositeChannels)
cristy3ed852e2009-09-05 21:47:34 +00001637 {
1638 if (AcquireImageColormap(image,2) == MagickFalse)
1639 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1640 image->filename);
1641 random_info=AcquireRandomInfoThreadSet();
1642 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001643#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy5c9e6f22010-09-17 17:31:01 +00001644 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001645#endif
cristybb503372010-05-27 20:51:26 +00001646 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001647 {
cristy5c9e6f22010-09-17 17:31:01 +00001648 const int
1649 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00001650
cristy3ed852e2009-09-05 21:47:34 +00001651 MagickBooleanType
1652 sync;
1653
cristybb503372010-05-27 20:51:26 +00001654 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001655 x;
1656
cristy4c08aed2011-07-01 19:47:50 +00001657 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001658 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001659
1660 if (status == MagickFalse)
1661 continue;
1662 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
1663 exception);
cristy4c08aed2011-07-01 19:47:50 +00001664 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001665 {
1666 status=MagickFalse;
1667 continue;
1668 }
cristybb503372010-05-27 20:51:26 +00001669 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001670 {
cristy3ed852e2009-09-05 21:47:34 +00001671 MagickRealType
1672 intensity;
1673
cristy4c08aed2011-07-01 19:47:50 +00001674 Quantum
1675 index;
1676
1677 intensity=(MagickRealType) GetPixelIntensity(image,q);
cristy3ed852e2009-09-05 21:47:34 +00001678 if (intensity < min_threshold)
cristy4c08aed2011-07-01 19:47:50 +00001679 threshold.black=min_threshold;
cristy3ed852e2009-09-05 21:47:34 +00001680 else
cristy4c08aed2011-07-01 19:47:50 +00001681 if (intensity > max_threshold)
1682 threshold.black=max_threshold;
1683 else
1684 threshold.black=(MagickRealType)(QuantumRange*
1685 GetPseudoRandomValue(random_info[id]));
1686 index=(Quantum) (intensity <= threshold.black ? 0 : 1);
1687 SetPixelIndex(image,index,q);
1688 SetPixelPacket(image,image->colormap+(ssize_t) index,q);
1689 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001690 }
1691 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1692 if (sync == MagickFalse)
1693 status=MagickFalse;
1694 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1695 {
1696 MagickBooleanType
1697 proceed;
1698
cristyb5d5f722009-11-04 03:03:49 +00001699#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001700 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1701#endif
1702 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1703 image->rows);
1704 if (proceed == MagickFalse)
1705 status=MagickFalse;
1706 }
1707 }
1708 image_view=DestroyCacheView(image_view);
1709 random_info=DestroyRandomInfoThreadSet(random_info);
1710 return(status);
1711 }
1712 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1713 {
1714 InheritException(exception,&image->exception);
1715 return(MagickFalse);
1716 }
1717 random_info=AcquireRandomInfoThreadSet();
1718 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001719#if defined(MAGICKCORE_OPENMP_SUPPORT)
1720 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001721#endif
cristybb503372010-05-27 20:51:26 +00001722 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001723 {
cristy5c9e6f22010-09-17 17:31:01 +00001724 const int
1725 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00001726
cristy4c08aed2011-07-01 19:47:50 +00001727 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001728 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001729
cristy5c9e6f22010-09-17 17:31:01 +00001730 register ssize_t
1731 x;
1732
cristy3ed852e2009-09-05 21:47:34 +00001733 if (status == MagickFalse)
1734 continue;
1735 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001736 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001737 {
1738 status=MagickFalse;
1739 continue;
1740 }
cristybb503372010-05-27 20:51:26 +00001741 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001742 {
cristy2b9582a2011-07-04 17:38:56 +00001743 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001744 {
cristy4c08aed2011-07-01 19:47:50 +00001745 if ((MagickRealType) GetPixelRed(image,q) < min_threshold)
cristy3ed852e2009-09-05 21:47:34 +00001746 threshold.red=min_threshold;
1747 else
cristy4c08aed2011-07-01 19:47:50 +00001748 if ((MagickRealType) GetPixelRed(image,q) > max_threshold)
cristy3ed852e2009-09-05 21:47:34 +00001749 threshold.red=max_threshold;
1750 else
1751 threshold.red=(MagickRealType) (QuantumRange*
1752 GetPseudoRandomValue(random_info[id]));
1753 }
cristy2b9582a2011-07-04 17:38:56 +00001754 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001755 {
cristy4c08aed2011-07-01 19:47:50 +00001756 if ((MagickRealType) GetPixelGreen(image,q) < min_threshold)
cristy3ed852e2009-09-05 21:47:34 +00001757 threshold.green=min_threshold;
1758 else
cristy4c08aed2011-07-01 19:47:50 +00001759 if ((MagickRealType) GetPixelGreen(image,q) > max_threshold)
cristy3ed852e2009-09-05 21:47:34 +00001760 threshold.green=max_threshold;
1761 else
1762 threshold.green=(MagickRealType) (QuantumRange*
1763 GetPseudoRandomValue(random_info[id]));
1764 }
cristy2b9582a2011-07-04 17:38:56 +00001765 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001766 {
cristy4c08aed2011-07-01 19:47:50 +00001767 if ((MagickRealType) GetPixelBlue(image,q) < min_threshold)
cristy3ed852e2009-09-05 21:47:34 +00001768 threshold.blue=min_threshold;
1769 else
cristy4c08aed2011-07-01 19:47:50 +00001770 if ((MagickRealType) GetPixelBlue(image,q) > max_threshold)
cristy3ed852e2009-09-05 21:47:34 +00001771 threshold.blue=max_threshold;
1772 else
1773 threshold.blue=(MagickRealType) (QuantumRange*
1774 GetPseudoRandomValue(random_info[id]));
1775 }
cristy2b9582a2011-07-04 17:38:56 +00001776 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001777 (image->colorspace == CMYKColorspace))
1778 {
cristy4c08aed2011-07-01 19:47:50 +00001779 if ((MagickRealType) GetPixelBlack(image,q) < min_threshold)
1780 threshold.black=min_threshold;
cristy3ed852e2009-09-05 21:47:34 +00001781 else
cristy4c08aed2011-07-01 19:47:50 +00001782 if ((MagickRealType) GetPixelBlack(image,q) > max_threshold)
1783 threshold.black=max_threshold;
cristy3ed852e2009-09-05 21:47:34 +00001784 else
cristy4c08aed2011-07-01 19:47:50 +00001785 threshold.black=(MagickRealType) (QuantumRange*
1786 GetPseudoRandomValue(random_info[id]));
1787 }
cristy2b9582a2011-07-04 17:38:56 +00001788 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001789 {
1790 if ((MagickRealType) GetPixelAlpha(image,q) < min_threshold)
1791 threshold.alpha=min_threshold;
1792 else
1793 if ((MagickRealType) GetPixelAlpha(image,q) > max_threshold)
1794 threshold.alpha=max_threshold;
1795 else
1796 threshold.alpha=(MagickRealType) (QuantumRange*
cristy3ed852e2009-09-05 21:47:34 +00001797 GetPseudoRandomValue(random_info[id]));
1798 }
cristy2b9582a2011-07-04 17:38:56 +00001799 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001800 SetPixelRed(image,(Quantum) ((MagickRealType)
1801 GetPixelRed(image,q) <= threshold.red ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +00001802 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001803 SetPixelGreen(image,(Quantum) ((MagickRealType)
1804 GetPixelGreen(image,q) <= threshold.green ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +00001805 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001806 SetPixelBlue(image,(Quantum) ((MagickRealType)
1807 GetPixelBlue(image,q) <= threshold.blue ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +00001808 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00001809 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00001810 SetPixelBlack(image,(Quantum) ((MagickRealType)
1811 GetPixelBlack(image,q) <= threshold.black ? 0 : QuantumRange),q);
cristy2b9582a2011-07-04 17:38:56 +00001812 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001813 SetPixelAlpha(image,(Quantum) ((MagickRealType)
1814 GetPixelAlpha(image,q) <= threshold.alpha ? 0 : QuantumRange),q);
1815 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001816 }
1817 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1818 status=MagickFalse;
1819 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1820 {
1821 MagickBooleanType
1822 proceed;
1823
cristyb5d5f722009-11-04 03:03:49 +00001824#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001825 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1826#endif
1827 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1828 image->rows);
1829 if (proceed == MagickFalse)
1830 status=MagickFalse;
1831 }
1832 }
1833 image_view=DestroyCacheView(image_view);
1834 random_info=DestroyRandomInfoThreadSet(random_info);
1835 return(status);
1836}
1837
1838/*
1839%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1840% %
1841% %
1842% %
1843% W h i t e T h r e s h o l d I m a g e %
1844% %
1845% %
1846% %
1847%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1848%
1849% WhiteThresholdImage() is like ThresholdImage() but forces all pixels above
cristy4e101302009-09-17 12:49:12 +00001850% the threshold into white while leaving all pixels at or below the threshold
cristy3ed852e2009-09-05 21:47:34 +00001851% unchanged.
1852%
1853% The format of the WhiteThresholdImage method is:
1854%
1855% MagickBooleanType WhiteThresholdImage(Image *image,const char *threshold)
1856% MagickBooleanType WhiteThresholdImageChannel(Image *image,
1857% const ChannelType channel,const char *threshold,
1858% ExceptionInfo *exception)
1859%
1860% A description of each parameter follows:
1861%
1862% o image: the image.
1863%
1864% o channel: the channel or channels to be thresholded.
1865%
1866% o threshold: Define the threshold value.
1867%
1868% o exception: return any errors or warnings in this structure.
1869%
1870*/
1871MagickExport MagickBooleanType WhiteThresholdImage(Image *image,
1872 const char *threshold)
1873{
1874 MagickBooleanType
1875 status;
1876
1877 status=WhiteThresholdImageChannel(image,DefaultChannels,threshold,
1878 &image->exception);
1879 return(status);
1880}
1881
1882MagickExport MagickBooleanType WhiteThresholdImageChannel(Image *image,
1883 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1884{
1885#define ThresholdImageTag "Threshold/Image"
1886
cristy5f959472010-05-27 22:19:46 +00001887 CacheView
1888 *image_view;
1889
cristy3ed852e2009-09-05 21:47:34 +00001890 GeometryInfo
1891 geometry_info;
1892
cristy3ed852e2009-09-05 21:47:34 +00001893 MagickBooleanType
1894 status;
1895
cristy4c08aed2011-07-01 19:47:50 +00001896 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001897 threshold;
1898
cristy5f959472010-05-27 22:19:46 +00001899 MagickOffsetType
1900 progress;
1901
cristy3ed852e2009-09-05 21:47:34 +00001902 MagickStatusType
1903 flags;
1904
cristy5f959472010-05-27 22:19:46 +00001905 ssize_t
1906 y;
cristy3ed852e2009-09-05 21:47:34 +00001907
1908 assert(image != (Image *) NULL);
1909 assert(image->signature == MagickSignature);
1910 if (image->debug != MagickFalse)
1911 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1912 if (thresholds == (const char *) NULL)
1913 return(MagickTrue);
1914 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1915 return(MagickFalse);
1916 flags=ParseGeometry(thresholds,&geometry_info);
cristy4c08aed2011-07-01 19:47:50 +00001917 GetPixelInfo(image,&threshold);
cristy3ed852e2009-09-05 21:47:34 +00001918 threshold.red=geometry_info.rho;
1919 threshold.green=geometry_info.sigma;
1920 if ((flags & SigmaValue) == 0)
1921 threshold.green=threshold.red;
1922 threshold.blue=geometry_info.xi;
1923 if ((flags & XiValue) == 0)
1924 threshold.blue=threshold.red;
cristy4c08aed2011-07-01 19:47:50 +00001925 threshold.alpha=geometry_info.psi;
cristy3ed852e2009-09-05 21:47:34 +00001926 if ((flags & PsiValue) == 0)
cristy4c08aed2011-07-01 19:47:50 +00001927 threshold.alpha=threshold.red;
1928 threshold.black=geometry_info.chi;
cristy3ed852e2009-09-05 21:47:34 +00001929 if ((flags & ChiValue) == 0)
cristy4c08aed2011-07-01 19:47:50 +00001930 threshold.black=threshold.red;
cristy3ed852e2009-09-05 21:47:34 +00001931 if ((flags & PercentValue) != 0)
1932 {
1933 threshold.red*=(QuantumRange/100.0);
1934 threshold.green*=(QuantumRange/100.0);
1935 threshold.blue*=(QuantumRange/100.0);
cristy4c08aed2011-07-01 19:47:50 +00001936 threshold.alpha*=(QuantumRange/100.0);
1937 threshold.black*=(QuantumRange/100.0);
cristy3ed852e2009-09-05 21:47:34 +00001938 }
1939 /*
1940 White threshold image.
1941 */
1942 status=MagickTrue;
1943 progress=0;
1944 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001945#if defined(MAGICKCORE_OPENMP_SUPPORT)
1946 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001947#endif
cristybb503372010-05-27 20:51:26 +00001948 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001949 {
cristybb503372010-05-27 20:51:26 +00001950 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001951 x;
1952
cristy4c08aed2011-07-01 19:47:50 +00001953 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001954 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001955
1956 if (status == MagickFalse)
1957 continue;
1958 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001959 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001960 {
1961 status=MagickFalse;
1962 continue;
1963 }
cristybb503372010-05-27 20:51:26 +00001964 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001965 {
cristyb0ea1af2009-11-28 20:44:46 +00001966 if (channel != DefaultChannels)
1967 {
cristy4c08aed2011-07-01 19:47:50 +00001968 if (GetPixelIntensity(image,q) > GetPixelInfoIntensity(&threshold))
cristyb0ea1af2009-11-28 20:44:46 +00001969 {
cristy4c08aed2011-07-01 19:47:50 +00001970 SetPixelRed(image,QuantumRange,q);
1971 SetPixelGreen(image,QuantumRange,q);
1972 SetPixelBlue(image,QuantumRange,q);
cristyb0ea1af2009-11-28 20:44:46 +00001973 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00001974 SetPixelBlack(image,QuantumRange,q);
cristyb0ea1af2009-11-28 20:44:46 +00001975 }
1976 }
1977 else
1978 {
cristy2b9582a2011-07-04 17:38:56 +00001979 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001980 ((MagickRealType) GetPixelRed(image,q) > threshold.red))
1981 SetPixelRed(image,QuantumRange,q);
cristy2b9582a2011-07-04 17:38:56 +00001982 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001983 ((MagickRealType) GetPixelGreen(image,q) > threshold.green))
1984 SetPixelGreen(image,QuantumRange,q);
cristy2b9582a2011-07-04 17:38:56 +00001985 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001986 ((MagickRealType) GetPixelBlue(image,q) > threshold.blue))
1987 SetPixelBlue(image,QuantumRange,q);
cristy2b9582a2011-07-04 17:38:56 +00001988 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristyb0ea1af2009-11-28 20:44:46 +00001989 (image->colorspace == CMYKColorspace) &&
cristy4c08aed2011-07-01 19:47:50 +00001990 ((MagickRealType) GetPixelBlack(image,q)) > threshold.black)
1991 SetPixelBlack(image,QuantumRange,q);
cristy2b9582a2011-07-04 17:38:56 +00001992 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001993 ((MagickRealType) GetPixelAlpha(image,q) > threshold.alpha))
1994 SetPixelAlpha(image,QuantumRange,q);
cristyb0ea1af2009-11-28 20:44:46 +00001995 }
cristy4c08aed2011-07-01 19:47:50 +00001996 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001997 }
1998 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1999 status=MagickFalse;
2000 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2001 {
2002 MagickBooleanType
2003 proceed;
2004
cristyb5d5f722009-11-04 03:03:49 +00002005#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002006 #pragma omp critical (MagickCore_WhiteThresholdImageChannel)
2007#endif
2008 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
2009 image->rows);
2010 if (proceed == MagickFalse)
2011 status=MagickFalse;
2012 }
2013 }
2014 image_view=DestroyCacheView(image_view);
2015 return(status);
2016}