blob: 30557864b740ab2e36e93e121e0a0977ebb0356c [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*/
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"
cristy09d81172010-10-21 16:15:05 +000082#include "magick/thread-private.h"
cristy3ed852e2009-09-05 21:47:34 +000083#include "magick/threshold.h"
cristy09d81172010-10-21 16:15:05 +000084#include "magick/transform.h"
cristy3ed852e2009-09-05 21:47:34 +000085#include "magick/xml-tree.h"
86
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
cristy3ed852e2009-09-05 21:47:34 +0000164 MagickPixelPacket
165 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;
193 GetMagickPixelPacket(image,&zero);
194 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
205 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000206 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000207
208 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000209 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000210
211 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000212 *restrict threshold_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000213
cristybb503372010-05-27 20:51:26 +0000214 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000215 x;
216
217 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000218 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000219
220 if (status == MagickFalse)
221 continue;
cristyd99b0962010-05-29 23:14:26 +0000222 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
223 height/2L,image->columns+width,height,exception);
cristy3ed852e2009-09-05 21:47:34 +0000224 q=GetCacheViewAuthenticPixels(threshold_view,0,y,threshold_image->columns,1,
225 exception);
226 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
227 {
228 status=MagickFalse;
229 continue;
230 }
231 indexes=GetCacheViewVirtualIndexQueue(image_view);
232 threshold_indexes=GetCacheViewAuthenticIndexQueue(threshold_view);
cristybb503372010-05-27 20:51:26 +0000233 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000234 {
cristy3ed852e2009-09-05 21:47:34 +0000235 MagickPixelPacket
236 mean,
237 pixel;
238
239 register const PixelPacket
240 *r;
241
cristybb503372010-05-27 20:51:26 +0000242 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000243 u;
244
cristy9d314ff2011-03-09 01:30:28 +0000245 ssize_t
246 v;
247
cristy3ed852e2009-09-05 21:47:34 +0000248 pixel=zero;
249 mean=zero;
250 r=p;
cristybb503372010-05-27 20:51:26 +0000251 for (v=0; v < (ssize_t) height; v++)
cristy3ed852e2009-09-05 21:47:34 +0000252 {
cristybb503372010-05-27 20:51:26 +0000253 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +0000254 {
255 pixel.red+=r[u].red;
256 pixel.green+=r[u].green;
257 pixel.blue+=r[u].blue;
258 pixel.opacity+=r[u].opacity;
259 if (image->colorspace == CMYKColorspace)
260 pixel.index=(MagickRealType) indexes[x+(r-p)+u];
261 }
262 r+=image->columns+width;
263 }
264 mean.red=(MagickRealType) (pixel.red/number_pixels+offset);
265 mean.green=(MagickRealType) (pixel.green/number_pixels+offset);
266 mean.blue=(MagickRealType) (pixel.blue/number_pixels+offset);
267 mean.opacity=(MagickRealType) (pixel.opacity/number_pixels+offset);
268 if (image->colorspace == CMYKColorspace)
269 mean.index=(MagickRealType) (pixel.index/number_pixels+offset);
270 q->red=(Quantum) (((MagickRealType) q->red <= mean.red) ?
271 0 : QuantumRange);
272 q->green=(Quantum) (((MagickRealType) q->green <= mean.green) ?
273 0 : QuantumRange);
274 q->blue=(Quantum) (((MagickRealType) q->blue <= mean.blue) ?
275 0 : QuantumRange);
276 q->opacity=(Quantum) (((MagickRealType) q->opacity <= mean.opacity) ?
277 0 : QuantumRange);
278 if (image->colorspace == CMYKColorspace)
279 threshold_indexes[x]=(IndexPacket) (((MagickRealType)
280 threshold_indexes[x] <= mean.index) ? 0 : QuantumRange);
281 p++;
282 q++;
283 }
284 sync=SyncCacheViewAuthenticPixels(threshold_view,exception);
285 if (sync == MagickFalse)
286 status=MagickFalse;
287 if (image->progress_monitor != (MagickProgressMonitor) NULL)
288 {
289 MagickBooleanType
290 proceed;
291
cristyb5d5f722009-11-04 03:03:49 +0000292#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000293 #pragma omp critical (MagickCore_AdaptiveThresholdImage)
294#endif
295 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
296 image->rows);
297 if (proceed == MagickFalse)
298 status=MagickFalse;
299 }
300 }
301 threshold_view=DestroyCacheView(threshold_view);
302 image_view=DestroyCacheView(image_view);
303 if (status == MagickFalse)
304 threshold_image=DestroyImage(threshold_image);
305 return(threshold_image);
306}
307
308/*
309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
310% %
311% %
312% %
313% B i l e v e l I m a g e %
314% %
315% %
316% %
317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
318%
319% BilevelImage() changes the value of individual pixels based on the
320% intensity of each pixel channel. The result is a high-contrast image.
321%
322% More precisely each channel value of the image is 'thresholded' so that if
323% it is equal to or less than the given value it is set to zero, while any
324% value greater than that give is set to it maximum or QuantumRange.
325%
326% This function is what is used to implement the "-threshold" operator for
327% the command line API.
328%
329% If the default channel setting is given the image is thresholded using just
330% the gray 'intensity' of the image, rather than the individual channels.
331%
332% The format of the BilevelImageChannel method is:
333%
334% MagickBooleanType BilevelImage(Image *image,const double threshold)
335% MagickBooleanType BilevelImageChannel(Image *image,
336% const ChannelType channel,const double threshold)
337%
338% A description of each parameter follows:
339%
340% o image: the image.
341%
342% o channel: the channel type.
343%
344% o threshold: define the threshold values.
345%
346% Aside: You can get the same results as operator using LevelImageChannels()
347% with the 'threshold' value for both the black_point and the white_point.
348%
349*/
350
351MagickExport MagickBooleanType BilevelImage(Image *image,const double threshold)
352{
353 MagickBooleanType
354 status;
355
356 status=BilevelImageChannel(image,DefaultChannels,threshold);
357 return(status);
358}
359
360MagickExport MagickBooleanType BilevelImageChannel(Image *image,
361 const ChannelType channel,const double threshold)
362{
363#define ThresholdImageTag "Threshold/Image"
364
cristyc4c8d132010-01-07 01:58:38 +0000365 CacheView
366 *image_view;
367
cristy3ed852e2009-09-05 21:47:34 +0000368 ExceptionInfo
369 *exception;
370
cristy3ed852e2009-09-05 21:47:34 +0000371 MagickBooleanType
372 status;
373
cristy5f959472010-05-27 22:19:46 +0000374 MagickOffsetType
375 progress;
376
377 ssize_t
378 y;
379
cristy3ed852e2009-09-05 21:47:34 +0000380 assert(image != (Image *) NULL);
381 assert(image->signature == MagickSignature);
382 if (image->debug != MagickFalse)
383 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
384 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
385 return(MagickFalse);
386 /*
387 Bilevel threshold image.
388 */
389 status=MagickTrue;
390 progress=0;
391 exception=(&image->exception);
392 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000393#if defined(MAGICKCORE_OPENMP_SUPPORT)
394 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000395#endif
cristybb503372010-05-27 20:51:26 +0000396 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000397 {
398 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000399 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000400
cristybb503372010-05-27 20:51:26 +0000401 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000402 x;
403
404 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000405 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000406
407 if (status == MagickFalse)
408 continue;
409 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
410 if (q == (PixelPacket *) NULL)
411 {
412 status=MagickFalse;
413 continue;
414 }
415 indexes=GetCacheViewAuthenticIndexQueue(image_view);
416 if (channel == DefaultChannels)
417 {
cristybb503372010-05-27 20:51:26 +0000418 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000419 {
420 q->red=(Quantum) ((MagickRealType) PixelIntensityToQuantum(q) <=
421 threshold ? 0 : QuantumRange);
422 q->green=q->red;
423 q->blue=q->red;
424 q++;
425 }
426 }
427 else
cristybb503372010-05-27 20:51:26 +0000428 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000429 {
430 if ((channel & RedChannel) != 0)
431 q->red=(Quantum) ((MagickRealType) q->red <= threshold ? 0 :
432 QuantumRange);
433 if ((channel & GreenChannel) != 0)
434 q->green=(Quantum) ((MagickRealType) q->green <= threshold ? 0 :
435 QuantumRange);
436 if ((channel & BlueChannel) != 0)
437 q->blue=(Quantum) ((MagickRealType) q->blue <= threshold ? 0 :
438 QuantumRange);
439 if ((channel & OpacityChannel) != 0)
440 {
441 if (image->matte == MagickFalse)
442 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold ?
443 0 : QuantumRange);
444 else
445 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold ?
446 OpaqueOpacity : TransparentOpacity);
447 }
448 if (((channel & IndexChannel) != 0) &&
449 (image->colorspace == CMYKColorspace))
450 indexes[x]=(IndexPacket) ((MagickRealType) indexes[x] <= threshold ?
451 0 : QuantumRange);
452 q++;
453 }
454 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
455 status=MagickFalse;
456 if (image->progress_monitor != (MagickProgressMonitor) NULL)
457 {
458 MagickBooleanType
459 proceed;
460
cristyb5d5f722009-11-04 03:03:49 +0000461#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000462 #pragma omp critical (MagickCore_BilevelImageChannel)
463#endif
464 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
465 image->rows);
466 if (proceed == MagickFalse)
467 status=MagickFalse;
468 }
469 }
470 image_view=DestroyCacheView(image_view);
471 return(status);
472}
473
474/*
475%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
476% %
477% %
478% %
479% B l a c k T h r e s h o l d I m a g e %
480% %
481% %
482% %
483%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
484%
485% BlackThresholdImage() is like ThresholdImage() but forces all pixels below
cristy4e101302009-09-17 12:49:12 +0000486% the threshold into black while leaving all pixels at or above the threshold
cristy3ed852e2009-09-05 21:47:34 +0000487% unchanged.
488%
489% The format of the BlackThresholdImage method is:
490%
491% MagickBooleanType BlackThresholdImage(Image *image,const char *threshold)
492% MagickBooleanType BlackThresholdImageChannel(Image *image,
493% const ChannelType channel,const char *threshold,
494% ExceptionInfo *exception)
495%
496% A description of each parameter follows:
497%
498% o image: the image.
499%
500% o channel: the channel or channels to be thresholded.
501%
502% o threshold: Define the threshold value.
503%
504% o exception: return any errors or warnings in this structure.
505%
506*/
507MagickExport MagickBooleanType BlackThresholdImage(Image *image,
508 const char *threshold)
509{
510 MagickBooleanType
511 status;
512
513 status=BlackThresholdImageChannel(image,DefaultChannels,threshold,
514 &image->exception);
515 return(status);
516}
517
518MagickExport MagickBooleanType BlackThresholdImageChannel(Image *image,
519 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
520{
521#define ThresholdImageTag "Threshold/Image"
522
cristyc4c8d132010-01-07 01:58:38 +0000523 CacheView
524 *image_view;
525
cristy3ed852e2009-09-05 21:47:34 +0000526 GeometryInfo
527 geometry_info;
528
cristy3ed852e2009-09-05 21:47:34 +0000529 MagickBooleanType
530 status;
531
cristy5f959472010-05-27 22:19:46 +0000532 MagickOffsetType
533 progress;
534
cristy3ed852e2009-09-05 21:47:34 +0000535 MagickPixelPacket
536 threshold;
537
538 MagickStatusType
539 flags;
540
cristy5f959472010-05-27 22:19:46 +0000541 ssize_t
542 y;
543
cristy3ed852e2009-09-05 21:47:34 +0000544 assert(image != (Image *) NULL);
545 assert(image->signature == MagickSignature);
546 if (image->debug != MagickFalse)
547 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
548 if (thresholds == (const char *) NULL)
549 return(MagickTrue);
550 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
551 return(MagickFalse);
552 GetMagickPixelPacket(image,&threshold);
553 flags=ParseGeometry(thresholds,&geometry_info);
554 threshold.red=geometry_info.rho;
555 threshold.green=geometry_info.sigma;
556 if ((flags & SigmaValue) == 0)
557 threshold.green=threshold.red;
558 threshold.blue=geometry_info.xi;
559 if ((flags & XiValue) == 0)
560 threshold.blue=threshold.red;
561 threshold.opacity=geometry_info.psi;
562 if ((flags & PsiValue) == 0)
563 threshold.opacity=threshold.red;
564 threshold.index=geometry_info.chi;
565 if ((flags & ChiValue) == 0)
566 threshold.index=threshold.red;
567 if ((flags & PercentValue) != 0)
568 {
569 threshold.red*=(QuantumRange/100.0);
570 threshold.green*=(QuantumRange/100.0);
571 threshold.blue*=(QuantumRange/100.0);
572 threshold.opacity*=(QuantumRange/100.0);
573 threshold.index*=(QuantumRange/100.0);
574 }
575 /*
576 Black threshold image.
577 */
578 status=MagickTrue;
579 progress=0;
580 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000581#if defined(MAGICKCORE_OPENMP_SUPPORT)
582 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000583#endif
cristybb503372010-05-27 20:51:26 +0000584 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000585 {
586 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000587 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000588
cristybb503372010-05-27 20:51:26 +0000589 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000590 x;
591
592 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000593 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000594
595 if (status == MagickFalse)
596 continue;
597 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
598 if (q == (PixelPacket *) NULL)
599 {
600 status=MagickFalse;
601 continue;
602 }
603 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +0000604 for (x=0; x < (ssize_t) image->columns; x++)
cristyb0ea1af2009-11-28 20:44:46 +0000605 {
606 if (channel != DefaultChannels)
607 {
608 if (PixelIntensity(q) < MagickPixelIntensity(&threshold))
609 {
610 q->red=(Quantum) 0;
611 q->green=(Quantum) 0;
612 q->blue=(Quantum) 0;
613 if (image->colorspace == CMYKColorspace)
614 indexes[x]=(Quantum) 0;
615 }
616 }
617 else
618 {
619 if (((channel & RedChannel) != 0) &&
620 ((MagickRealType) q->red < threshold.red))
621 q->red=(Quantum) 0;
622 if (((channel & GreenChannel) != 0) &&
623 ((MagickRealType) q->green < threshold.green))
624 q->green=(Quantum) 0;
625 if (((channel & BlueChannel) != 0) &&
626 ((MagickRealType) q->blue < threshold.blue))
627 q->blue=(Quantum) 0;
628 if (((channel & OpacityChannel) != 0) &&
629 ((MagickRealType) q->opacity < threshold.opacity))
630 q->opacity=(Quantum) 0;
631 if (((channel & IndexChannel) != 0) &&
632 (image->colorspace == CMYKColorspace) &&
633 ((MagickRealType) indexes[x] < threshold.index))
634 indexes[x]=(Quantum) 0;
635 }
636 q++;
637 }
cristy3ed852e2009-09-05 21:47:34 +0000638 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
639 status=MagickFalse;
640 if (image->progress_monitor != (MagickProgressMonitor) NULL)
641 {
642 MagickBooleanType
643 proceed;
644
cristyb5d5f722009-11-04 03:03:49 +0000645#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000646 #pragma omp critical (MagickCore_BlackThresholdImageChannel)
647#endif
648 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
649 image->rows);
650 if (proceed == MagickFalse)
651 status=MagickFalse;
652 }
653 }
654 image_view=DestroyCacheView(image_view);
655 return(status);
656}
657
658/*
659%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
660% %
661% %
662% %
cristy1eb45dd2009-09-25 16:38:06 +0000663% C l a m p I m a g e %
664% %
665% %
666% %
667%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
668%
cristyecb0c6d2009-09-25 16:50:09 +0000669% ClampImage() restricts the color range from 0 to the quantum depth.
cristy1eb45dd2009-09-25 16:38:06 +0000670%
671% The format of the ClampImageChannel method is:
672%
673% MagickBooleanType ClampImage(Image *image)
674% MagickBooleanType ClampImageChannel(Image *image,
675% const ChannelType channel)
676%
677% A description of each parameter follows:
678%
679% o image: the image.
680%
681% o channel: the channel type.
682%
683*/
684
cristy75ffdb72010-01-07 17:40:12 +0000685static inline Quantum ClampToUnsignedQuantum(const Quantum quantum)
cristy1eb45dd2009-09-25 16:38:06 +0000686{
687#if defined(MAGICKCORE_HDRI_SUPPORT)
688 if (quantum <= 0)
689 return(0);
690 if (quantum >= QuantumRange)
691 return(QuantumRange);
692 return(quantum);
693#else
694 return(quantum);
695#endif
696}
697
698MagickExport MagickBooleanType ClampImage(Image *image)
699{
700 MagickBooleanType
701 status;
702
703 status=ClampImageChannel(image,DefaultChannels);
704 return(status);
705}
706
707MagickExport MagickBooleanType ClampImageChannel(Image *image,
708 const ChannelType channel)
709{
710#define ClampImageTag "Clamp/Image"
711
cristyc4c8d132010-01-07 01:58:38 +0000712 CacheView
713 *image_view;
714
cristy1eb45dd2009-09-25 16:38:06 +0000715 ExceptionInfo
716 *exception;
717
cristy1eb45dd2009-09-25 16:38:06 +0000718 MagickBooleanType
719 status;
720
cristy5f959472010-05-27 22:19:46 +0000721 MagickOffsetType
722 progress;
723
724 ssize_t
725 y;
726
cristy1eb45dd2009-09-25 16:38:06 +0000727 assert(image != (Image *) NULL);
728 assert(image->signature == MagickSignature);
729 if (image->debug != MagickFalse)
730 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
731 if (image->storage_class == PseudoClass)
732 {
cristybb503372010-05-27 20:51:26 +0000733 register ssize_t
cristy1eb45dd2009-09-25 16:38:06 +0000734 i;
735
736 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000737 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000738
739 q=image->colormap;
cristybb503372010-05-27 20:51:26 +0000740 for (i=0; i < (ssize_t) image->colors; i++)
cristy1eb45dd2009-09-25 16:38:06 +0000741 {
cristy75ffdb72010-01-07 17:40:12 +0000742 q->red=ClampToUnsignedQuantum(q->red);
743 q->green=ClampToUnsignedQuantum(q->green);
744 q->blue=ClampToUnsignedQuantum(q->blue);
745 q->opacity=ClampToUnsignedQuantum(q->opacity);
cristy1eb45dd2009-09-25 16:38:06 +0000746 q++;
747 }
748 return(SyncImage(image));
749 }
750 /*
cristy611721d2009-09-25 16:42:17 +0000751 Clamp image.
cristy1eb45dd2009-09-25 16:38:06 +0000752 */
753 status=MagickTrue;
754 progress=0;
755 exception=(&image->exception);
756 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +0000757#if defined(MAGICKCORE_OPENMP_SUPPORT)
758 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy1eb45dd2009-09-25 16:38:06 +0000759#endif
cristybb503372010-05-27 20:51:26 +0000760 for (y=0; y < (ssize_t) image->rows; y++)
cristy1eb45dd2009-09-25 16:38:06 +0000761 {
762 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000763 *restrict indexes;
cristy1eb45dd2009-09-25 16:38:06 +0000764
cristybb503372010-05-27 20:51:26 +0000765 register ssize_t
cristy1eb45dd2009-09-25 16:38:06 +0000766 x;
767
768 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000769 *restrict q;
cristy1eb45dd2009-09-25 16:38:06 +0000770
771 if (status == MagickFalse)
772 continue;
773 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
774 if (q == (PixelPacket *) NULL)
775 {
776 status=MagickFalse;
777 continue;
778 }
779 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +0000780 for (x=0; x < (ssize_t) image->columns; x++)
cristy1eb45dd2009-09-25 16:38:06 +0000781 {
782 if ((channel & RedChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000783 q->red=ClampToUnsignedQuantum(q->red);
cristy1eb45dd2009-09-25 16:38:06 +0000784 if ((channel & GreenChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000785 q->green=ClampToUnsignedQuantum(q->green);
cristy1eb45dd2009-09-25 16:38:06 +0000786 if ((channel & BlueChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000787 q->blue=ClampToUnsignedQuantum(q->blue);
cristy1eb45dd2009-09-25 16:38:06 +0000788 if ((channel & OpacityChannel) != 0)
cristy75ffdb72010-01-07 17:40:12 +0000789 q->opacity=ClampToUnsignedQuantum(q->opacity);
cristy1eb45dd2009-09-25 16:38:06 +0000790 if (((channel & IndexChannel) != 0) &&
791 (image->colorspace == CMYKColorspace))
cristy75ffdb72010-01-07 17:40:12 +0000792 indexes[x]=(IndexPacket) ClampToUnsignedQuantum(indexes[x]);
cristy1eb45dd2009-09-25 16:38:06 +0000793 q++;
794 }
795 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
796 status=MagickFalse;
797 if (image->progress_monitor != (MagickProgressMonitor) NULL)
798 {
799 MagickBooleanType
800 proceed;
801
cristyb5d5f722009-11-04 03:03:49 +0000802#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy1eb45dd2009-09-25 16:38:06 +0000803 #pragma omp critical (MagickCore_ClampImageChannel)
804#endif
805 proceed=SetImageProgress(image,ClampImageTag,progress++,
806 image->rows);
807 if (proceed == MagickFalse)
808 status=MagickFalse;
809 }
810 }
811 image_view=DestroyCacheView(image_view);
812 return(status);
813}
814
815/*
816%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
817% %
818% %
819% %
cristy3ed852e2009-09-05 21:47:34 +0000820% D e s t r o y T h r e s h o l d M a p %
821% %
822% %
823% %
824%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
825%
826% DestroyThresholdMap() de-allocate the given ThresholdMap
827%
828% The format of the ListThresholdMaps method is:
829%
830% ThresholdMap *DestroyThresholdMap(Threshold *map)
831%
832% A description of each parameter follows.
833%
834% o map: Pointer to the Threshold map to destroy
835%
836*/
837MagickExport ThresholdMap *DestroyThresholdMap(ThresholdMap *map)
838{
839 assert(map != (ThresholdMap *) NULL);
840 if (map->map_id != (char *) NULL)
841 map->map_id=DestroyString(map->map_id);
842 if (map->description != (char *) NULL)
843 map->description=DestroyString(map->description);
cristybb503372010-05-27 20:51:26 +0000844 if (map->levels != (ssize_t *) NULL)
845 map->levels=(ssize_t *) RelinquishMagickMemory(map->levels);
cristy3ed852e2009-09-05 21:47:34 +0000846 map=(ThresholdMap *) RelinquishMagickMemory(map);
847 return(map);
848}
849
850/*
851%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
852% %
853% %
854% %
855+ G e t T h r e s h o l d M a p F i l e %
856% %
857% %
858% %
859%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
860%
861% GetThresholdMapFile() look for a given threshold map name or alias in the
862% given XML file data, and return the allocated the map when found.
863%
864% The format of the ListThresholdMaps method is:
865%
866% ThresholdMap *GetThresholdMap(const char *xml,const char *filename,
867% const char *map_id,ExceptionInfo *exception)
868%
869% A description of each parameter follows.
870%
871% o xml: The threshold map list in XML format.
872%
873% o filename: The threshold map XML filename.
874%
875% o map_id: ID of the map to look for in XML list.
876%
877% o exception: return any errors or warnings in this structure.
878%
879*/
880MagickExport ThresholdMap *GetThresholdMapFile(const char *xml,
881 const char *filename,const char *map_id,ExceptionInfo *exception)
882{
883 const char
884 *attr,
885 *content;
886
887 double
888 value;
889
cristy092b7f72010-11-05 15:54:37 +0000890 ThresholdMap
cristy3ed852e2009-09-05 21:47:34 +0000891 *map;
892
893 XMLTreeInfo
894 *description,
895 *levels,
896 *threshold,
897 *thresholds;
898
899 map = (ThresholdMap *)NULL;
900 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
901 "Loading threshold map file \"%s\" ...",filename);
902 thresholds=NewXMLTree(xml,exception);
903 if ( thresholds == (XMLTreeInfo *)NULL )
904 return(map);
905
906 for( threshold = GetXMLTreeChild(thresholds,"threshold");
907 threshold != (XMLTreeInfo *)NULL;
908 threshold = GetNextXMLTreeTag(threshold) ) {
909 attr = GetXMLTreeAttribute(threshold, "map");
910 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
911 break;
912 attr = GetXMLTreeAttribute(threshold, "alias");
913 if ( (attr != (char *)NULL) && (LocaleCompare(map_id,attr) == 0) )
914 break;
915 }
916 if ( threshold == (XMLTreeInfo *)NULL ) {
917 return(map);
918 }
919 description = GetXMLTreeChild(threshold,"description");
920 if ( description == (XMLTreeInfo *)NULL ) {
921 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
922 "XmlMissingElement", "<description>, map \"%s\"", map_id);
923 thresholds = DestroyXMLTree(thresholds);
924 return(map);
925 }
926 levels = GetXMLTreeChild(threshold,"levels");
927 if ( levels == (XMLTreeInfo *)NULL ) {
928 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
929 "XmlMissingElement", "<levels>, map \"%s\"", map_id);
930 thresholds = DestroyXMLTree(thresholds);
931 return(map);
932 }
933
934 /* The map has been found -- Allocate a Threshold Map to return */
cristy73bd4a52010-10-05 11:24:23 +0000935 map = (ThresholdMap *)AcquireMagickMemory(sizeof(ThresholdMap));
cristy3ed852e2009-09-05 21:47:34 +0000936 if ( map == (ThresholdMap *)NULL )
937 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
938 map->map_id = (char *)NULL;
939 map->description = (char *)NULL;
cristybb503372010-05-27 20:51:26 +0000940 map->levels = (ssize_t *) NULL;
cristy3ed852e2009-09-05 21:47:34 +0000941
942 /* Assign Basic Attributes */
943 attr = GetXMLTreeAttribute(threshold, "map");
944 if ( attr != (char *)NULL )
945 map->map_id = ConstantString(attr);
946
947 content = GetXMLTreeContent(description);
948 if ( content != (char *)NULL )
949 map->description = ConstantString(content);
950
951 attr = GetXMLTreeAttribute(levels, "width");
952 if ( attr == (char *)NULL ) {
953 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
954 "XmlMissingAttribute", "<levels width>, map \"%s\"", map_id);
955 thresholds = DestroyXMLTree(thresholds);
956 map = DestroyThresholdMap(map);
957 return(map);
958 }
cristye27293e2009-12-18 02:53:20 +0000959 map->width = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000960 if ( map->width == 0 ) {
961 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
962 "XmlInvalidAttribute", "<levels width>, map \"%s\"", map_id);
963 thresholds = DestroyXMLTree(thresholds);
964 map = DestroyThresholdMap(map);
965 return(map);
966 }
967
968 attr = GetXMLTreeAttribute(levels, "height");
969 if ( attr == (char *)NULL ) {
970 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
971 "XmlMissingAttribute", "<levels height>, map \"%s\"", map_id);
972 thresholds = DestroyXMLTree(thresholds);
973 map = DestroyThresholdMap(map);
974 return(map);
975 }
cristye27293e2009-12-18 02:53:20 +0000976 map->height = StringToUnsignedLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000977 if ( map->height == 0 ) {
978 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
979 "XmlInvalidAttribute", "<levels height>, map \"%s\"", map_id);
980 thresholds = DestroyXMLTree(thresholds);
981 map = DestroyThresholdMap(map);
982 return(map);
983 }
984
985 attr = GetXMLTreeAttribute(levels, "divisor");
986 if ( attr == (char *)NULL ) {
987 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
988 "XmlMissingAttribute", "<levels divisor>, map \"%s\"", map_id);
989 thresholds = DestroyXMLTree(thresholds);
990 map = DestroyThresholdMap(map);
991 return(map);
992 }
cristy55a91cd2010-12-01 00:57:40 +0000993 map->divisor = (ssize_t) StringToLong(attr);
cristy3ed852e2009-09-05 21:47:34 +0000994 if ( map->divisor < 2 ) {
995 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
996 "XmlInvalidAttribute", "<levels divisor>, map \"%s\"", map_id);
997 thresholds = DestroyXMLTree(thresholds);
998 map = DestroyThresholdMap(map);
999 return(map);
1000 }
1001
1002 /* Allocate theshold levels array */
1003 content = GetXMLTreeContent(levels);
1004 if ( content == (char *)NULL ) {
1005 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1006 "XmlMissingContent", "<levels>, map \"%s\"", map_id);
1007 thresholds = DestroyXMLTree(thresholds);
1008 map = DestroyThresholdMap(map);
1009 return(map);
1010 }
cristybb503372010-05-27 20:51:26 +00001011 map->levels=(ssize_t *) AcquireQuantumMemory((size_t) map->width,map->height*
cristy3ed852e2009-09-05 21:47:34 +00001012 sizeof(*map->levels));
cristybb503372010-05-27 20:51:26 +00001013 if ( map->levels == (ssize_t *)NULL )
cristy3ed852e2009-09-05 21:47:34 +00001014 ThrowFatalException(ResourceLimitFatalError,"UnableToAcquireThresholdMap");
1015 { /* parse levels into integer array */
cristycee97112010-05-28 00:44:52 +00001016 ssize_t i;
cristy3ed852e2009-09-05 21:47:34 +00001017 char *p;
cristybb503372010-05-27 20:51:26 +00001018 for( i=0; i< (ssize_t) (map->width*map->height); i++) {
cristycee97112010-05-28 00:44:52 +00001019 map->levels[i] = (ssize_t)strtol(content, &p, 10);
cristy3ed852e2009-09-05 21:47:34 +00001020 if ( p == content ) {
1021 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1022 "XmlInvalidContent", "<level> too few values, map \"%s\"", map_id);
1023 thresholds = DestroyXMLTree(thresholds);
1024 map = DestroyThresholdMap(map);
1025 return(map);
1026 }
1027 if ( map->levels[i] < 0 || map->levels[i] > map->divisor ) {
1028 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
cristye8c25f92010-06-03 00:53:06 +00001029 "XmlInvalidContent", "<level> %.20g out of range, map \"%s\"",
1030 (double) map->levels[i],map_id);
cristy3ed852e2009-09-05 21:47:34 +00001031 thresholds = DestroyXMLTree(thresholds);
1032 map = DestroyThresholdMap(map);
1033 return(map);
1034 }
1035 content = p;
1036 }
1037 value=(double) strtol(content,&p,10);
cristyda16f162011-02-19 23:52:17 +00001038 (void) value;
cristy3ed852e2009-09-05 21:47:34 +00001039 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 }
glennrp1e7f7bc2011-03-02 19:25:28 +00001414 /* Set channel levels from extra comma separated arguments
cristy3ed852e2009-09-05 21:47:34 +00001415 Default to 2, the single value given, or individual channel values
1416 */
1417#if 1
glennrp1e7f7bc2011-03-02 19:25:28 +00001418 { /* parse directly as a comma separated list of integers */
cristy3ed852e2009-09-05 21:47:34 +00001419 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) {
anthonybffd2672010-12-06 06:10:48 +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;
cristy55a91cd2010-12-01 00:57:40 +00001540 q->red=RoundToQuantum((MagickRealType) ((l+(t >= threshold))*
1541 (MagickRealType) QuantumRange/levels.red));
cristy3ed852e2009-09-05 21:47:34 +00001542 }
1543 if (levels.green) {
anthonybffd2672010-12-06 06:10:48 +00001544 t = (ssize_t) (QuantumScale*q->green*(levels.green*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001545 l = t/d; t = t-l*d;
cristy55a91cd2010-12-01 00:57:40 +00001546 q->green=RoundToQuantum((MagickRealType) ((l+(t >= threshold))*
1547 (MagickRealType) QuantumRange/levels.green));
cristy3ed852e2009-09-05 21:47:34 +00001548 }
1549 if (levels.blue) {
anthonybffd2672010-12-06 06:10:48 +00001550 t = (ssize_t) (QuantumScale*q->blue*(levels.blue*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001551 l = t/d; t = t-l*d;
cristy55a91cd2010-12-01 00:57:40 +00001552 q->blue=RoundToQuantum((MagickRealType) ((l+(t >= threshold))*
1553 (MagickRealType) QuantumRange/levels.blue));
cristy3ed852e2009-09-05 21:47:34 +00001554 }
1555 if (levels.opacity) {
anthonybffd2672010-12-06 06:10:48 +00001556 t = (ssize_t) ((1.0-QuantumScale*q->opacity)*(levels.opacity*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001557 l = t/d; t = t-l*d;
cristy55a91cd2010-12-01 00:57:40 +00001558 q->opacity=RoundToQuantum((MagickRealType) ((1.0-l-(t >= threshold))*
1559 (MagickRealType) QuantumRange/levels.opacity));
cristy3ed852e2009-09-05 21:47:34 +00001560 }
1561 if (levels.index) {
anthonybffd2672010-12-06 06:10:48 +00001562 t = (ssize_t) (QuantumScale*indexes[x]*(levels.index*d+1));
cristy3ed852e2009-09-05 21:47:34 +00001563 l = t/d; t = t-l*d;
cristy55a91cd2010-12-01 00:57:40 +00001564 indexes[x]=(IndexPacket) RoundToQuantum((MagickRealType) ((l+
1565 (t>=threshold))*(MagickRealType) QuantumRange/levels.index));
cristy3ed852e2009-09-05 21:47:34 +00001566 }
1567 q++;
1568 }
1569 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1570 status=MagickFalse;
1571 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1572 {
1573 MagickBooleanType
1574 proceed;
1575
cristyb5d5f722009-11-04 03:03:49 +00001576#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001577 #pragma omp critical (MagickCore_OrderedPosterizeImageChannel)
1578#endif
1579 proceed=SetImageProgress(image,DitherImageTag,progress++,image->rows);
1580 if (proceed == MagickFalse)
1581 status=MagickFalse;
1582 }
1583 }
1584 image_view=DestroyCacheView(image_view);
1585 }
1586 map=DestroyThresholdMap(map);
1587 return(MagickTrue);
1588}
1589
1590/*
1591%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1592% %
1593% %
1594% %
1595% R a n d o m T h r e s h o l d I m a g e %
1596% %
1597% %
1598% %
1599%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1600%
1601% RandomThresholdImage() changes the value of individual pixels based on the
1602% intensity of each pixel compared to a random threshold. The result is a
1603% low-contrast, two color image.
1604%
1605% The format of the RandomThresholdImage method is:
1606%
1607% MagickBooleanType RandomThresholdImageChannel(Image *image,
1608% const char *thresholds,ExceptionInfo *exception)
1609% MagickBooleanType RandomThresholdImageChannel(Image *image,
1610% const ChannelType channel,const char *thresholds,
1611% ExceptionInfo *exception)
1612%
1613% A description of each parameter follows:
1614%
1615% o image: the image.
1616%
1617% o channel: the channel or channels to be thresholded.
1618%
1619% o thresholds: a geometry string containing low,high thresholds. If the
1620% string contains 2x2, 3x3, or 4x4, an ordered dither of order 2, 3, or 4
1621% is performed instead.
1622%
1623% o exception: return any errors or warnings in this structure.
1624%
1625*/
1626
1627MagickExport MagickBooleanType RandomThresholdImage(Image *image,
1628 const char *thresholds,ExceptionInfo *exception)
1629{
1630 MagickBooleanType
1631 status;
1632
1633 status=RandomThresholdImageChannel(image,DefaultChannels,thresholds,
1634 exception);
1635 return(status);
1636}
1637
1638MagickExport MagickBooleanType RandomThresholdImageChannel(Image *image,
1639 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1640{
1641#define ThresholdImageTag "Threshold/Image"
1642
cristyfa112112010-01-04 17:48:07 +00001643 CacheView
1644 *image_view;
1645
cristy3ed852e2009-09-05 21:47:34 +00001646 GeometryInfo
1647 geometry_info;
1648
1649 MagickStatusType
1650 flags;
1651
cristy3ed852e2009-09-05 21:47:34 +00001652 MagickBooleanType
1653 status;
1654
cristy5f959472010-05-27 22:19:46 +00001655 MagickOffsetType
1656 progress;
1657
cristy3ed852e2009-09-05 21:47:34 +00001658 MagickPixelPacket
1659 threshold;
1660
1661 MagickRealType
1662 min_threshold,
1663 max_threshold;
1664
1665 RandomInfo
cristyfa112112010-01-04 17:48:07 +00001666 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00001667
cristy5f959472010-05-27 22:19:46 +00001668 ssize_t
1669 y;
1670
cristy3ed852e2009-09-05 21:47:34 +00001671 assert(image != (Image *) NULL);
1672 assert(image->signature == MagickSignature);
1673 if (image->debug != MagickFalse)
1674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1675 assert(exception != (ExceptionInfo *) NULL);
1676 assert(exception->signature == MagickSignature);
1677 if (thresholds == (const char *) NULL)
1678 return(MagickTrue);
1679 GetMagickPixelPacket(image,&threshold);
1680 min_threshold=0.0;
1681 max_threshold=(MagickRealType) QuantumRange;
1682 flags=ParseGeometry(thresholds,&geometry_info);
1683 min_threshold=geometry_info.rho;
1684 max_threshold=geometry_info.sigma;
1685 if ((flags & SigmaValue) == 0)
1686 max_threshold=min_threshold;
1687 if (strchr(thresholds,'%') != (char *) NULL)
1688 {
1689 max_threshold*=(MagickRealType) (0.01*QuantumRange);
1690 min_threshold*=(MagickRealType) (0.01*QuantumRange);
1691 }
1692 else
1693 if (((max_threshold == min_threshold) || (max_threshold == 1)) &&
1694 (min_threshold <= 8))
1695 {
1696 /*
1697 Backward Compatibility -- ordered-dither -- IM v 6.2.9-6.
1698 */
1699 status=OrderedPosterizeImageChannel(image,channel,thresholds,exception);
1700 return(status);
1701 }
1702 /*
1703 Random threshold image.
1704 */
1705 status=MagickTrue;
1706 progress=0;
1707 if (channel == AllChannels)
1708 {
1709 if (AcquireImageColormap(image,2) == MagickFalse)
1710 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1711 image->filename);
1712 random_info=AcquireRandomInfoThreadSet();
1713 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001714#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy5c9e6f22010-09-17 17:31:01 +00001715 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001716#endif
cristybb503372010-05-27 20:51:26 +00001717 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001718 {
cristy5c9e6f22010-09-17 17:31:01 +00001719 const int
1720 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00001721
cristy3ed852e2009-09-05 21:47:34 +00001722 MagickBooleanType
1723 sync;
1724
1725 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001726 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001727
cristybb503372010-05-27 20:51:26 +00001728 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001729 x;
1730
1731 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001732 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001733
1734 if (status == MagickFalse)
1735 continue;
1736 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
1737 exception);
1738 if (q == (PixelPacket *) NULL)
1739 {
1740 status=MagickFalse;
1741 continue;
1742 }
1743 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001744 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001745 {
1746 IndexPacket
1747 index;
1748
1749 MagickRealType
1750 intensity;
1751
1752 intensity=(MagickRealType) PixelIntensityToQuantum(q);
1753 if (intensity < min_threshold)
1754 threshold.index=min_threshold;
1755 else if (intensity > max_threshold)
1756 threshold.index=max_threshold;
1757 else
1758 threshold.index=(MagickRealType)(QuantumRange*
1759 GetPseudoRandomValue(random_info[id]));
1760 index=(IndexPacket) (intensity <= threshold.index ? 0 : 1);
1761 indexes[x]=index;
cristybb503372010-05-27 20:51:26 +00001762 *q++=image->colormap[(ssize_t) index];
cristy3ed852e2009-09-05 21:47:34 +00001763 }
1764 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1765 if (sync == MagickFalse)
1766 status=MagickFalse;
1767 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1768 {
1769 MagickBooleanType
1770 proceed;
1771
cristyb5d5f722009-11-04 03:03:49 +00001772#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001773 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1774#endif
1775 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1776 image->rows);
1777 if (proceed == MagickFalse)
1778 status=MagickFalse;
1779 }
1780 }
1781 image_view=DestroyCacheView(image_view);
1782 random_info=DestroyRandomInfoThreadSet(random_info);
1783 return(status);
1784 }
1785 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1786 {
1787 InheritException(exception,&image->exception);
1788 return(MagickFalse);
1789 }
1790 random_info=AcquireRandomInfoThreadSet();
1791 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00001792#if defined(MAGICKCORE_OPENMP_SUPPORT)
1793 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001794#endif
cristybb503372010-05-27 20:51:26 +00001795 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001796 {
cristy5c9e6f22010-09-17 17:31:01 +00001797 const int
1798 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00001799
cristy3ed852e2009-09-05 21:47:34 +00001800 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001801 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001802
cristy3ed852e2009-09-05 21:47:34 +00001803 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001804 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001805
cristy5c9e6f22010-09-17 17:31:01 +00001806 register ssize_t
1807 x;
1808
cristy3ed852e2009-09-05 21:47:34 +00001809 if (status == MagickFalse)
1810 continue;
1811 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1812 if (q == (PixelPacket *) NULL)
1813 {
1814 status=MagickFalse;
1815 continue;
1816 }
1817 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00001818 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001819 {
1820 if ((channel & RedChannel) != 0)
1821 {
1822 if ((MagickRealType) q->red < min_threshold)
1823 threshold.red=min_threshold;
1824 else
1825 if ((MagickRealType) q->red > max_threshold)
1826 threshold.red=max_threshold;
1827 else
1828 threshold.red=(MagickRealType) (QuantumRange*
1829 GetPseudoRandomValue(random_info[id]));
1830 }
1831 if ((channel & GreenChannel) != 0)
1832 {
1833 if ((MagickRealType) q->green < min_threshold)
1834 threshold.green=min_threshold;
1835 else
1836 if ((MagickRealType) q->green > max_threshold)
1837 threshold.green=max_threshold;
1838 else
1839 threshold.green=(MagickRealType) (QuantumRange*
1840 GetPseudoRandomValue(random_info[id]));
1841 }
1842 if ((channel & BlueChannel) != 0)
1843 {
1844 if ((MagickRealType) q->blue < min_threshold)
1845 threshold.blue=min_threshold;
1846 else
1847 if ((MagickRealType) q->blue > max_threshold)
1848 threshold.blue=max_threshold;
1849 else
1850 threshold.blue=(MagickRealType) (QuantumRange*
1851 GetPseudoRandomValue(random_info[id]));
1852 }
1853 if ((channel & OpacityChannel) != 0)
1854 {
1855 if ((MagickRealType) q->opacity < min_threshold)
1856 threshold.opacity=min_threshold;
1857 else
1858 if ((MagickRealType) q->opacity > max_threshold)
1859 threshold.opacity=max_threshold;
1860 else
1861 threshold.opacity=(MagickRealType) (QuantumRange*
1862 GetPseudoRandomValue(random_info[id]));
1863 }
1864 if (((channel & IndexChannel) != 0) &&
1865 (image->colorspace == CMYKColorspace))
1866 {
1867 if ((MagickRealType) indexes[x] < min_threshold)
1868 threshold.index=min_threshold;
1869 else
1870 if ((MagickRealType) indexes[x] > max_threshold)
1871 threshold.index=max_threshold;
1872 else
1873 threshold.index=(MagickRealType) (QuantumRange*
1874 GetPseudoRandomValue(random_info[id]));
1875 }
1876 if ((channel & RedChannel) != 0)
1877 q->red=(Quantum) ((MagickRealType) q->red <= threshold.red ? 0 :
1878 QuantumRange);
1879 if ((channel & GreenChannel) != 0)
1880 q->green=(Quantum) ((MagickRealType) q->green <= threshold.green ? 0 :
1881 QuantumRange);
1882 if ((channel & BlueChannel) != 0)
1883 q->blue=(Quantum) ((MagickRealType) q->blue <= threshold.blue ? 0 :
1884 QuantumRange);
1885 if ((channel & OpacityChannel) != 0)
1886 q->opacity=(Quantum) ((MagickRealType) q->opacity <= threshold.opacity ?
1887 0 : QuantumRange);
1888 if (((channel & IndexChannel) != 0) &&
1889 (image->colorspace == CMYKColorspace))
1890 indexes[x]=(IndexPacket) ((MagickRealType) indexes[x] <=
1891 threshold.index ? 0 : QuantumRange);
1892 q++;
1893 }
1894 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1895 status=MagickFalse;
1896 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1897 {
1898 MagickBooleanType
1899 proceed;
1900
cristyb5d5f722009-11-04 03:03:49 +00001901#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001902 #pragma omp critical (MagickCore_RandomThresholdImageChannel)
1903#endif
1904 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
1905 image->rows);
1906 if (proceed == MagickFalse)
1907 status=MagickFalse;
1908 }
1909 }
1910 image_view=DestroyCacheView(image_view);
1911 random_info=DestroyRandomInfoThreadSet(random_info);
1912 return(status);
1913}
1914
1915/*
1916%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1917% %
1918% %
1919% %
1920% W h i t e T h r e s h o l d I m a g e %
1921% %
1922% %
1923% %
1924%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1925%
1926% WhiteThresholdImage() is like ThresholdImage() but forces all pixels above
cristy4e101302009-09-17 12:49:12 +00001927% the threshold into white while leaving all pixels at or below the threshold
cristy3ed852e2009-09-05 21:47:34 +00001928% unchanged.
1929%
1930% The format of the WhiteThresholdImage method is:
1931%
1932% MagickBooleanType WhiteThresholdImage(Image *image,const char *threshold)
1933% MagickBooleanType WhiteThresholdImageChannel(Image *image,
1934% const ChannelType channel,const char *threshold,
1935% ExceptionInfo *exception)
1936%
1937% A description of each parameter follows:
1938%
1939% o image: the image.
1940%
1941% o channel: the channel or channels to be thresholded.
1942%
1943% o threshold: Define the threshold value.
1944%
1945% o exception: return any errors or warnings in this structure.
1946%
1947*/
1948MagickExport MagickBooleanType WhiteThresholdImage(Image *image,
1949 const char *threshold)
1950{
1951 MagickBooleanType
1952 status;
1953
1954 status=WhiteThresholdImageChannel(image,DefaultChannels,threshold,
1955 &image->exception);
1956 return(status);
1957}
1958
1959MagickExport MagickBooleanType WhiteThresholdImageChannel(Image *image,
1960 const ChannelType channel,const char *thresholds,ExceptionInfo *exception)
1961{
1962#define ThresholdImageTag "Threshold/Image"
1963
cristy5f959472010-05-27 22:19:46 +00001964 CacheView
1965 *image_view;
1966
cristy3ed852e2009-09-05 21:47:34 +00001967 GeometryInfo
1968 geometry_info;
1969
cristy3ed852e2009-09-05 21:47:34 +00001970 MagickBooleanType
1971 status;
1972
1973 MagickPixelPacket
1974 threshold;
1975
cristy5f959472010-05-27 22:19:46 +00001976 MagickOffsetType
1977 progress;
1978
cristy3ed852e2009-09-05 21:47:34 +00001979 MagickStatusType
1980 flags;
1981
cristy5f959472010-05-27 22:19:46 +00001982 ssize_t
1983 y;
cristy3ed852e2009-09-05 21:47:34 +00001984
1985 assert(image != (Image *) NULL);
1986 assert(image->signature == MagickSignature);
1987 if (image->debug != MagickFalse)
1988 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1989 if (thresholds == (const char *) NULL)
1990 return(MagickTrue);
1991 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1992 return(MagickFalse);
1993 flags=ParseGeometry(thresholds,&geometry_info);
1994 GetMagickPixelPacket(image,&threshold);
1995 threshold.red=geometry_info.rho;
1996 threshold.green=geometry_info.sigma;
1997 if ((flags & SigmaValue) == 0)
1998 threshold.green=threshold.red;
1999 threshold.blue=geometry_info.xi;
2000 if ((flags & XiValue) == 0)
2001 threshold.blue=threshold.red;
2002 threshold.opacity=geometry_info.psi;
2003 if ((flags & PsiValue) == 0)
2004 threshold.opacity=threshold.red;
2005 threshold.index=geometry_info.chi;
2006 if ((flags & ChiValue) == 0)
2007 threshold.index=threshold.red;
2008 if ((flags & PercentValue) != 0)
2009 {
2010 threshold.red*=(QuantumRange/100.0);
2011 threshold.green*=(QuantumRange/100.0);
2012 threshold.blue*=(QuantumRange/100.0);
2013 threshold.opacity*=(QuantumRange/100.0);
2014 threshold.index*=(QuantumRange/100.0);
2015 }
2016 /*
2017 White threshold image.
2018 */
2019 status=MagickTrue;
2020 progress=0;
2021 image_view=AcquireCacheView(image);
cristyb5d5f722009-11-04 03:03:49 +00002022#if defined(MAGICKCORE_OPENMP_SUPPORT)
2023 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002024#endif
cristybb503372010-05-27 20:51:26 +00002025 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002026 {
2027 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002028 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002029
cristybb503372010-05-27 20:51:26 +00002030 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002031 x;
2032
2033 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002034 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002035
2036 if (status == MagickFalse)
2037 continue;
2038 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2039 if (q == (PixelPacket *) NULL)
2040 {
2041 status=MagickFalse;
2042 continue;
2043 }
2044 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002045 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002046 {
cristyb0ea1af2009-11-28 20:44:46 +00002047 if (channel != DefaultChannels)
2048 {
2049 if (PixelIntensity(q) > MagickPixelIntensity(&threshold))
2050 {
2051 q->red=(Quantum) QuantumRange;
2052 q->green=(Quantum) QuantumRange;
2053 q->blue=(Quantum) QuantumRange;
2054 if (image->colorspace == CMYKColorspace)
2055 indexes[x]=(Quantum) QuantumRange;
2056 }
2057 }
2058 else
2059 {
2060 if (((channel & RedChannel) != 0) &&
2061 ((MagickRealType) q->red > threshold.red))
2062 q->red=(Quantum) QuantumRange;
2063 if (((channel & GreenChannel) != 0) &&
2064 ((MagickRealType) q->green > threshold.green))
2065 q->green=(Quantum) QuantumRange;
2066 if (((channel & BlueChannel) != 0) &&
2067 ((MagickRealType) q->blue > threshold.blue))
2068 q->blue=(Quantum) QuantumRange;
2069 if (((channel & OpacityChannel) != 0) &&
2070 ((MagickRealType) q->opacity > threshold.opacity))
2071 q->opacity=(Quantum) QuantumRange;
2072 if (((channel & IndexChannel) != 0) &&
2073 (image->colorspace == CMYKColorspace) &&
2074 ((MagickRealType) indexes[x] > threshold.index))
2075 indexes[x]=(Quantum) QuantumRange;
2076 }
cristy3ed852e2009-09-05 21:47:34 +00002077 q++;
2078 }
2079 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2080 status=MagickFalse;
2081 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2082 {
2083 MagickBooleanType
2084 proceed;
2085
cristyb5d5f722009-11-04 03:03:49 +00002086#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002087 #pragma omp critical (MagickCore_WhiteThresholdImageChannel)
2088#endif
2089 proceed=SetImageProgress(image,ThresholdImageTag,progress++,
2090 image->rows);
2091 if (proceed == MagickFalse)
2092 status=MagickFalse;
2093 }
2094 }
2095 image_view=DestroyCacheView(image_view);
2096 return(status);
2097}