blob: 388438805e06a5bf0c4474d8505cea8ab57b27ca [file] [log] [blame]
cristyd04e7bf2012-03-03 19:19:12 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% CCCC H H AAA N N N N EEEEE L %
7% C H H A A NN N NN N E L %
8% C HHHHH AAAAA N N N N N N RRR L %
9% C H H A A N NN N NN E L %
10% CCCC H H A A N N N N EEEEE LLLLL %
11% %
12% %
13% MagickCore Image Channel Methods %
14% %
15% Software Design %
16% John Cristy %
17% December 2003 %
18% %
19% %
cristy45ef08f2012-12-07 13:13:34 +000020% Copyright 1999-2013 ImageMagick Studio LLC, a non-profit organization %
cristyd04e7bf2012-03-03 19:19:12 +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 "MagickCore/studio.h"
cristy8c8c9162013-05-27 10:14:21 +000044#include "MagickCore/cache-private.h"
cristyc1119af2012-04-16 15:28:23 +000045#include "MagickCore/colorspace-private.h"
cristy8c8c9162013-05-27 10:14:21 +000046#include "MagickCore/composite-private.h"
47#include "MagickCore/enhance.h"
cristya15140f2012-03-04 01:21:15 +000048#include "MagickCore/image.h"
49#include "MagickCore/list.h"
50#include "MagickCore/log.h"
cristyab272ac2012-03-04 22:08:07 +000051#include "MagickCore/monitor.h"
52#include "MagickCore/monitor-private.h"
cristya15140f2012-03-04 01:21:15 +000053#include "MagickCore/option.h"
cristyab272ac2012-03-04 22:08:07 +000054#include "MagickCore/pixel-accessor.h"
cristy8c8c9162013-05-27 10:14:21 +000055#include "MagickCore/pixel-private.h"
cristyac245f82012-05-05 17:13:57 +000056#include "MagickCore/resource_.h"
cristy8748f562012-03-08 13:58:34 +000057#include "MagickCore/string-private.h"
cristy16881e62012-05-06 14:41:29 +000058#include "MagickCore/thread-private.h"
cristya15140f2012-03-04 01:21:15 +000059#include "MagickCore/token.h"
cristyd04e7bf2012-03-03 19:19:12 +000060#include "MagickCore/utility.h"
61#include "MagickCore/version.h"
62
63/*
64%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
65% %
66% %
67% %
cristy5f257b22012-03-07 00:27:29 +000068% C h a n n e l F x I m a g e %
cristyd04e7bf2012-03-03 19:19:12 +000069% %
70% %
71% %
72%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
73%
cristy5f257b22012-03-07 00:27:29 +000074% ChannelFxImage() applies a channel expression to the specified image. The
75% expression consists of one or more channels, either mnemonic or numeric (e.g.
76% red, 1), separated by actions as follows:
cristyab272ac2012-03-04 22:08:07 +000077%
78% <=> exchange two channels (e.g. red<=>blue)
cristy7ea920d2012-03-16 01:08:01 +000079% => copy one channel to another channel (e.g. red=>green)
80% = assign a constant value to a channel (e.g. red=50%)
81% , write new image channels in the specified order (e.g. red, green)
cristy3c213612012-03-16 01:14:48 +000082% | add a new output image for the next set of channel operations
83% ; move to the next input image for the source of channel data
cristyab272ac2012-03-04 22:08:07 +000084%
cristy7ea920d2012-03-16 01:08:01 +000085% For example, to create 3 grayscale images from the red, green, and blue
86% channels of an image, use:
cristyab272ac2012-03-04 22:08:07 +000087%
cristy3c213612012-03-16 01:14:48 +000088% -channel-fx "red; green; blue"
cristy7ea920d2012-03-16 01:08:01 +000089%
90% A channel without an operation symbol implies separate (i.e, semicolon).
cristyd04e7bf2012-03-03 19:19:12 +000091%
cristy5f257b22012-03-07 00:27:29 +000092% The format of the ChannelFxImage method is:
cristyd04e7bf2012-03-03 19:19:12 +000093%
cristy5f257b22012-03-07 00:27:29 +000094% Image *ChannelFxImage(const Image *image,const char *expression,
95% ExceptionInfo *exception)
cristyd04e7bf2012-03-03 19:19:12 +000096%
97% A description of each parameter follows:
98%
cristyab272ac2012-03-04 22:08:07 +000099% o image: the image.
cristyd04e7bf2012-03-03 19:19:12 +0000100%
101% o expression: A channel expression.
102%
103% o exception: return any errors or warnings in this structure.
104%
105*/
cristya15140f2012-03-04 01:21:15 +0000106
107typedef enum
108{
109 ExtractChannelOp,
cristy8748f562012-03-08 13:58:34 +0000110 AssignChannelOp,
cristya15140f2012-03-04 01:21:15 +0000111 ExchangeChannelOp,
112 TransferChannelOp
cristy5f257b22012-03-07 00:27:29 +0000113} ChannelFx;
cristya15140f2012-03-04 01:21:15 +0000114
cristyab272ac2012-03-04 22:08:07 +0000115static inline size_t MagickMin(const size_t x,const size_t y)
cristya15140f2012-03-04 01:21:15 +0000116{
cristyab272ac2012-03-04 22:08:07 +0000117 if (x < y)
118 return(x);
119 return(y);
cristya15140f2012-03-04 01:21:15 +0000120}
121
cristyab272ac2012-03-04 22:08:07 +0000122static MagickBooleanType ChannelImage(Image *destination_image,
cristy8748f562012-03-08 13:58:34 +0000123 const PixelChannel destination_channel,const ChannelFx channel_op,
cristy3bb378a2012-03-08 14:38:52 +0000124 const Image *source_image,const PixelChannel source_channel,
cristy8748f562012-03-08 13:58:34 +0000125 const Quantum pixel,ExceptionInfo *exception)
cristyab272ac2012-03-04 22:08:07 +0000126{
127 CacheView
128 *source_view,
129 *destination_view;
130
131 MagickBooleanType
132 status;
133
134 size_t
cristyac245f82012-05-05 17:13:57 +0000135 height,
136 width;
cristyab272ac2012-03-04 22:08:07 +0000137
138 ssize_t
139 y;
140
141 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000142 source_view=AcquireVirtualCacheView(source_image,exception);
143 destination_view=AcquireAuthenticCacheView(destination_image,exception);
cristyab272ac2012-03-04 22:08:07 +0000144 height=MagickMin(source_image->rows,destination_image->rows);
cristyac245f82012-05-05 17:13:57 +0000145 width=MagickMin(source_image->columns,destination_image->columns);
cristyab272ac2012-03-04 22:08:07 +0000146#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +0000147 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000148 magick_threads(source_image,source_image,height,1)
cristyab272ac2012-03-04 22:08:07 +0000149#endif
150 for (y=0; y < (ssize_t) height; y++)
151 {
cristy4ee60402012-03-11 02:01:20 +0000152 PixelTrait
153 destination_traits,
154 source_traits;
155
cristyab272ac2012-03-04 22:08:07 +0000156 register const Quantum
157 *restrict p;
158
159 register Quantum
160 *restrict q;
161
162 register ssize_t
163 x;
164
cristyab272ac2012-03-04 22:08:07 +0000165 if (status == MagickFalse)
166 continue;
167 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
168 exception);
cristy4ee60402012-03-11 02:01:20 +0000169 q=GetCacheViewAuthenticPixels(destination_view,0,y,
cristyab272ac2012-03-04 22:08:07 +0000170 destination_image->columns,1,exception);
171 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
172 {
173 status=MagickFalse;
174 continue;
175 }
cristycf1296e2012-08-26 23:40:49 +0000176 destination_traits=GetPixelChannelTraits(destination_image,
cristy4ee60402012-03-11 02:01:20 +0000177 destination_channel);
cristycf1296e2012-08-26 23:40:49 +0000178 source_traits=GetPixelChannelTraits(source_image,source_channel);
cristy4ee60402012-03-11 02:01:20 +0000179 if ((destination_traits == UndefinedPixelTrait) ||
180 (source_traits == UndefinedPixelTrait))
181 continue;
cristyab272ac2012-03-04 22:08:07 +0000182 for (x=0; x < (ssize_t) width; x++)
183 {
cristy8748f562012-03-08 13:58:34 +0000184 if (channel_op == AssignChannelOp)
185 SetPixelChannel(destination_image,destination_channel,pixel,q);
186 else
cristy4ee60402012-03-11 02:01:20 +0000187 SetPixelChannel(destination_image,destination_channel,
188 GetPixelChannel(source_image,source_channel,p),q);
189 p+=GetPixelChannels(source_image);
cristyab272ac2012-03-04 22:08:07 +0000190 q+=GetPixelChannels(destination_image);
191 }
192 if (SyncCacheViewAuthenticPixels(destination_view,exception) == MagickFalse)
193 status=MagickFalse;
194 }
195 destination_view=DestroyCacheView(destination_view);
196 source_view=DestroyCacheView(source_view);
197 return(status);
198}
199
cristy5f257b22012-03-07 00:27:29 +0000200MagickExport Image *ChannelFxImage(const Image *image,const char *expression,
201 ExceptionInfo *exception)
cristyd04e7bf2012-03-03 19:19:12 +0000202{
cristy5f257b22012-03-07 00:27:29 +0000203#define ChannelFxImageTag "ChannelFx/Image"
cristyab272ac2012-03-04 22:08:07 +0000204
cristy5f257b22012-03-07 00:27:29 +0000205 ChannelFx
cristya15140f2012-03-04 01:21:15 +0000206 channel_op;
207
cristyfe88ede2012-03-22 00:34:48 +0000208 ChannelType
209 channel_mask;
210
cristy4ee60402012-03-11 02:01:20 +0000211 char
212 token[MaxTextExtent];
213
cristya15140f2012-03-04 01:21:15 +0000214 const char
215 *p;
216
cristyab272ac2012-03-04 22:08:07 +0000217 const Image
218 *source_image;
219
cristy8748f562012-03-08 13:58:34 +0000220 double
221 pixel;
222
cristya15140f2012-03-04 01:21:15 +0000223 Image
cristyab272ac2012-03-04 22:08:07 +0000224 *destination_image;
cristya15140f2012-03-04 01:21:15 +0000225
cristy4ee60402012-03-11 02:01:20 +0000226 MagickBooleanType
227 status;
228
cristya15140f2012-03-04 01:21:15 +0000229 PixelChannel
cristyab272ac2012-03-04 22:08:07 +0000230 source_channel,
231 destination_channel;
cristya15140f2012-03-04 01:21:15 +0000232
cristyab272ac2012-03-04 22:08:07 +0000233 ssize_t
234 channels;
235
236 assert(image != (Image *) NULL);
237 assert(image->signature == MagickSignature);
238 if (image->debug != MagickFalse)
239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
240 assert(exception != (ExceptionInfo *) NULL);
241 assert(exception->signature == MagickSignature);
242 source_image=image;
243 destination_image=CloneImage(source_image,0,0,MagickTrue,exception);
244 if (destination_image == (Image *) NULL)
245 return((Image *) NULL);
cristyab272ac2012-03-04 22:08:07 +0000246 if (expression == (const char *) NULL)
247 return(destination_image);
248 destination_channel=RedPixelChannel;
cristyfe88ede2012-03-22 00:34:48 +0000249 channel_mask=UndefinedChannel;
cristy8748f562012-03-08 13:58:34 +0000250 pixel=0.0;
cristya15140f2012-03-04 01:21:15 +0000251 p=(char *) expression;
252 GetMagickToken(p,&p,token);
cristy4ee60402012-03-11 02:01:20 +0000253 channel_op=ExtractChannelOp;
cristy09ef5be2012-03-13 13:01:27 +0000254 for (channels=0; *token != '\0'; )
cristya15140f2012-03-04 01:21:15 +0000255 {
cristya15140f2012-03-04 01:21:15 +0000256 ssize_t
257 i;
258
259 /*
260 Interpret channel expression.
261 */
cristy68bc2572013-05-16 01:09:13 +0000262 switch (*token)
263 {
cristy68bc2572013-05-16 01:09:13 +0000264 case ',':
cristya15140f2012-03-04 01:21:15 +0000265 {
cristya15140f2012-03-04 01:21:15 +0000266 GetMagickToken(p,&p,token);
cristy68bc2572013-05-16 01:09:13 +0000267 break;
cristya15140f2012-03-04 01:21:15 +0000268 }
cristy68bc2572013-05-16 01:09:13 +0000269 case '|':
cristya15140f2012-03-04 01:21:15 +0000270 {
cristyab272ac2012-03-04 22:08:07 +0000271 if (GetNextImageInList(source_image) != (Image *) NULL)
272 source_image=GetNextImageInList(source_image);
cristya15140f2012-03-04 01:21:15 +0000273 else
cristyab272ac2012-03-04 22:08:07 +0000274 source_image=GetFirstImageInList(source_image);
cristya15140f2012-03-04 01:21:15 +0000275 GetMagickToken(p,&p,token);
cristy68bc2572013-05-16 01:09:13 +0000276 break;
cristya15140f2012-03-04 01:21:15 +0000277 }
cristy68bc2572013-05-16 01:09:13 +0000278 case ';':
cristya15140f2012-03-04 01:21:15 +0000279 {
cristyab272ac2012-03-04 22:08:07 +0000280 Image
281 *canvas;
282
cristycf1296e2012-08-26 23:40:49 +0000283 SetPixelChannelMask(destination_image,channel_mask);
cristy68bc2572013-05-16 01:09:13 +0000284 if ((channel_op == ExtractChannelOp) && (channels == 1))
cristyfe88ede2012-03-22 00:34:48 +0000285 (void) SetImageColorspace(destination_image,GRAYColorspace,exception);
cristy4ee60402012-03-11 02:01:20 +0000286 status=SetImageStorageClass(destination_image,DirectClass,exception);
287 if (status == MagickFalse)
288 {
cristyfc68ef52012-03-11 23:33:15 +0000289 destination_image=DestroyImageList(destination_image);
290 return(destination_image);
cristy4ee60402012-03-11 02:01:20 +0000291 }
cristyab272ac2012-03-04 22:08:07 +0000292 canvas=CloneImage(source_image,0,0,MagickTrue,exception);
293 if (canvas == (Image *) NULL)
294 {
cristyfc68ef52012-03-11 23:33:15 +0000295 destination_image=DestroyImageList(destination_image);
296 return(destination_image);
cristyab272ac2012-03-04 22:08:07 +0000297 }
298 AppendImageToList(&destination_image,canvas);
299 destination_image=GetLastImageInList(destination_image);
cristya15140f2012-03-04 01:21:15 +0000300 GetMagickToken(p,&p,token);
cristyab272ac2012-03-04 22:08:07 +0000301 channels=0;
302 destination_channel=RedPixelChannel;
cristyfe88ede2012-03-22 00:34:48 +0000303 channel_mask=UndefinedChannel;
cristy68bc2572013-05-16 01:09:13 +0000304 break;
cristya15140f2012-03-04 01:21:15 +0000305 }
cristy68bc2572013-05-16 01:09:13 +0000306 default:
307 break;
308 }
cristya15140f2012-03-04 01:21:15 +0000309 i=ParsePixelChannelOption(token);
310 if (i < 0)
311 {
312 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
cristyefe601c2013-01-05 17:51:12 +0000313 "UnrecognizedChannelType","`%s'",token);
cristyab272ac2012-03-04 22:08:07 +0000314 destination_image=DestroyImageList(destination_image);
cristyfc68ef52012-03-11 23:33:15 +0000315 return(destination_image);
cristya15140f2012-03-04 01:21:15 +0000316 }
cristyab272ac2012-03-04 22:08:07 +0000317 source_channel=(PixelChannel) i;
cristya15140f2012-03-04 01:21:15 +0000318 channel_op=ExtractChannelOp;
319 GetMagickToken(p,&p,token);
320 if (*token == '<')
321 {
322 channel_op=ExchangeChannelOp;
323 GetMagickToken(p,&p,token);
324 }
325 if (*token == '=')
cristy8748f562012-03-08 13:58:34 +0000326 {
327 if (channel_op != ExchangeChannelOp)
328 channel_op=AssignChannelOp;
329 GetMagickToken(p,&p,token);
330 }
cristya15140f2012-03-04 01:21:15 +0000331 if (*token == '>')
332 {
333 if (channel_op != ExchangeChannelOp)
334 channel_op=TransferChannelOp;
335 GetMagickToken(p,&p,token);
336 }
cristy8748f562012-03-08 13:58:34 +0000337 switch (channel_op)
338 {
339 case AssignChannelOp:
340 {
cristy41db1742012-03-09 00:57:52 +0000341 pixel=StringToDoubleInterval(token,(double) QuantumRange+1.0);
cristy3bb378a2012-03-08 14:38:52 +0000342 GetMagickToken(p,&p,token);
cristy8748f562012-03-08 13:58:34 +0000343 break;
344 }
345 case ExchangeChannelOp:
346 case TransferChannelOp:
cristya15140f2012-03-04 01:21:15 +0000347 {
348 i=ParsePixelChannelOption(token);
349 if (i < 0)
350 {
351 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
cristyefe601c2013-01-05 17:51:12 +0000352 "UnrecognizedChannelType","`%s'",token);
cristyab272ac2012-03-04 22:08:07 +0000353 destination_image=DestroyImageList(destination_image);
cristyfc68ef52012-03-11 23:33:15 +0000354 return(destination_image);
cristya15140f2012-03-04 01:21:15 +0000355 }
cristyab272ac2012-03-04 22:08:07 +0000356 destination_channel=(PixelChannel) i;
cristy04817142013-05-20 17:14:03 +0000357 switch (destination_channel)
358 {
cristyed27bb72013-05-20 17:24:07 +0000359 case RedPixelChannel:
360 case GreenPixelChannel:
361 case BluePixelChannel:
362 case BlackPixelChannel:
363 case IndexPixelChannel:
364 break;
cristy04817142013-05-20 17:14:03 +0000365 case AlphaPixelChannel:
366 {
367 destination_image->alpha_trait=BlendPixelTrait;
368 break;
369 }
370 case ReadMaskPixelChannel:
371 {
372 destination_image->read_mask=MagickTrue;
373 break;
374 }
375 case WriteMaskPixelChannel:
376 {
377 destination_image->write_mask=MagickTrue;
378 break;
379 }
cristyed27bb72013-05-20 17:24:07 +0000380 case MetaPixelChannel:
cristy04817142013-05-20 17:14:03 +0000381 default:
cristyed27bb72013-05-20 17:24:07 +0000382 {
383 (void) SetPixelMetaChannels(destination_image,(size_t) (i-
384 GetPixelChannels(destination_image)+1),exception);
cristy04817142013-05-20 17:14:03 +0000385 break;
cristyed27bb72013-05-20 17:24:07 +0000386 }
cristy04817142013-05-20 17:14:03 +0000387 }
cristyaa2c16c2012-03-25 22:21:35 +0000388 channel_mask=(ChannelType) (channel_mask | ParseChannelOption(token));
cristy68bc2572013-05-16 01:09:13 +0000389 if (((channels >= 1) || (destination_channel >= 1)) &&
390 (IsGrayColorspace(destination_image->colorspace) != MagickFalse))
391 (void) SetImageColorspace(destination_image,sRGBColorspace,exception);
cristy3bb378a2012-03-08 14:38:52 +0000392 GetMagickToken(p,&p,token);
cristy8748f562012-03-08 13:58:34 +0000393 break;
cristya15140f2012-03-04 01:21:15 +0000394 }
cristyfe88ede2012-03-22 00:34:48 +0000395 default:
cristy3c213612012-03-16 01:14:48 +0000396 break;
cristy7ea920d2012-03-16 01:08:01 +0000397 }
cristy3bb378a2012-03-08 14:38:52 +0000398 status=ChannelImage(destination_image,destination_channel,channel_op,
399 source_image,source_channel,ClampToQuantum(pixel),exception);
cristya15140f2012-03-04 01:21:15 +0000400 if (status == MagickFalse)
401 {
cristyab272ac2012-03-04 22:08:07 +0000402 destination_image=DestroyImageList(destination_image);
cristya15140f2012-03-04 01:21:15 +0000403 break;
404 }
cristy4ee60402012-03-11 02:01:20 +0000405 channels++;
cristy8748f562012-03-08 13:58:34 +0000406 if (channel_op == ExchangeChannelOp)
407 {
cristy4ee60402012-03-11 02:01:20 +0000408 status=ChannelImage(destination_image,source_channel,channel_op,
409 source_image,destination_channel,ClampToQuantum(pixel),exception);
cristy8748f562012-03-08 13:58:34 +0000410 if (status == MagickFalse)
411 {
412 destination_image=DestroyImageList(destination_image);
413 break;
414 }
cristy4ee60402012-03-11 02:01:20 +0000415 channels++;
cristy8748f562012-03-08 13:58:34 +0000416 }
cristyfe88ede2012-03-22 00:34:48 +0000417 switch (channel_op)
418 {
419 case ExtractChannelOp:
420 {
cristy9dbae2c2013-05-27 20:12:53 +0000421 channel_mask=(ChannelType) (channel_mask | (1 << destination_channel));
cristyaa2c16c2012-03-25 22:21:35 +0000422 destination_channel=(PixelChannel) (destination_channel+1);
cristyfe88ede2012-03-22 00:34:48 +0000423 break;
424 }
425 default:
426 break;
427 }
cristy5f257b22012-03-07 00:27:29 +0000428 status=SetImageProgress(source_image,ChannelFxImageTag,p-expression,
cristyab272ac2012-03-04 22:08:07 +0000429 strlen(expression));
430 if (status == MagickFalse)
431 break;
cristya15140f2012-03-04 01:21:15 +0000432 }
cristycf1296e2012-08-26 23:40:49 +0000433 SetPixelChannelMask(destination_image,channel_mask);
cristy68bc2572013-05-16 01:09:13 +0000434 if ((channel_op == ExtractChannelOp) && (channels == 1))
cristyfe88ede2012-03-22 00:34:48 +0000435 (void) SetImageColorspace(destination_image,GRAYColorspace,exception);
cristy4ee60402012-03-11 02:01:20 +0000436 status=SetImageStorageClass(destination_image,DirectClass,exception);
437 if (status == MagickFalse)
438 {
439 destination_image=GetLastImageInList(destination_image);
440 return((Image *) NULL);
441 }
cristy09ef5be2012-03-13 13:01:27 +0000442 return(GetFirstImageInList(destination_image));
cristyd04e7bf2012-03-03 19:19:12 +0000443}
cristy0c643722012-03-05 19:18:42 +0000444
445/*
446%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
447% %
448% %
449% %
450% C o m b i n e I m a g e s %
451% %
452% %
453% %
454%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
455%
456% CombineImages() combines one or more images into a single image. The
457% grayscale value of the pixels of each image in the sequence is assigned in
458% order to the specified channels of the combined image. The typical
459% ordering would be image 1 => Red, 2 => Green, 3 => Blue, etc.
460%
461% The format of the CombineImages method is:
462%
cristy46f354c2012-07-04 13:31:29 +0000463% Image *CombineImages(const Image *images,const ColorspaceType colorspace,
464% ExceptionInfo *exception)
cristy0c643722012-03-05 19:18:42 +0000465%
466% A description of each parameter follows:
467%
cristy46f354c2012-07-04 13:31:29 +0000468% o images: the image sequence.
469%
470% o colorspace: the image colorspace.
cristy0c643722012-03-05 19:18:42 +0000471%
472% o exception: return any errors or warnings in this structure.
473%
474*/
cristy46f354c2012-07-04 13:31:29 +0000475MagickExport Image *CombineImages(const Image *image,
476 const ColorspaceType colorspace,ExceptionInfo *exception)
cristy0c643722012-03-05 19:18:42 +0000477{
478#define CombineImageTag "Combine/Image"
479
480 CacheView
481 *combine_view;
482
483 Image
484 *combine_image;
485
486 MagickBooleanType
487 status;
488
489 MagickOffsetType
490 progress;
491
492 ssize_t
493 y;
494
495 /*
496 Ensure the image are the same size.
497 */
498 assert(image != (const Image *) NULL);
499 assert(image->signature == MagickSignature);
500 if (image->debug != MagickFalse)
501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
502 assert(exception != (ExceptionInfo *) NULL);
503 assert(exception->signature == MagickSignature);
504 combine_image=CloneImage(image,0,0,MagickTrue,exception);
505 if (combine_image == (Image *) NULL)
506 return((Image *) NULL);
507 if (SetImageStorageClass(combine_image,DirectClass,exception) == MagickFalse)
508 {
509 combine_image=DestroyImage(combine_image);
510 return((Image *) NULL);
511 }
cristy74f3f942013-05-01 11:11:28 +0000512 (void) SetImageColorspace(combine_image,colorspace,exception);
cristy0c643722012-03-05 19:18:42 +0000513 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy8a46d822012-08-28 23:32:39 +0000514 combine_image->alpha_trait=BlendPixelTrait;
cristy0c643722012-03-05 19:18:42 +0000515 /*
516 Combine images.
517 */
518 status=MagickTrue;
519 progress=0;
cristy46ff2672012-12-14 15:32:26 +0000520 combine_view=AcquireAuthenticCacheView(combine_image,exception);
cristy0c643722012-03-05 19:18:42 +0000521 for (y=0; y < (ssize_t) combine_image->rows; y++)
522 {
523 CacheView
524 *image_view;
525
526 const Image
527 *next;
528
529 Quantum
530 *pixels;
531
532 register const Quantum
533 *restrict p;
534
535 register Quantum
536 *restrict q;
537
538 register ssize_t
539 i;
540
541 if (status == MagickFalse)
542 continue;
543 pixels=GetCacheViewAuthenticPixels(combine_view,0,y,combine_image->columns,
544 1,exception);
545 if (pixels == (Quantum *) NULL)
546 {
547 status=MagickFalse;
548 continue;
549 }
550 next=image;
cristyc1119af2012-04-16 15:28:23 +0000551 for (i=0; i < (ssize_t) GetPixelChannels(combine_image); i++)
cristy0c643722012-03-05 19:18:42 +0000552 {
cristy0c643722012-03-05 19:18:42 +0000553 register ssize_t
554 x;
555
cristy5a23c552013-02-13 14:34:28 +0000556 PixelChannel channel=GetPixelChannelChannel(combine_image,i);
557 PixelTrait traits=GetPixelChannelTraits(combine_image,channel);
cristyc1119af2012-04-16 15:28:23 +0000558 if (traits == UndefinedPixelTrait)
cristy0c643722012-03-05 19:18:42 +0000559 continue;
cristy5a23c552013-02-13 14:34:28 +0000560 if (next == (Image *) NULL)
561 continue;
cristy46ff2672012-12-14 15:32:26 +0000562 image_view=AcquireVirtualCacheView(next,exception);
cristy0c643722012-03-05 19:18:42 +0000563 p=GetCacheViewVirtualPixels(image_view,0,y,next->columns,1,exception);
564 if (p == (const Quantum *) NULL)
565 continue;
566 q=pixels;
567 for (x=0; x < (ssize_t) combine_image->columns; x++)
568 {
cristyf3381ae2012-07-13 16:30:09 +0000569 if (x < (ssize_t) next->columns)
cristy0c643722012-03-05 19:18:42 +0000570 {
cristyf3381ae2012-07-13 16:30:09 +0000571 q[i]=GetPixelGray(next,p);
572 p+=GetPixelChannels(next);
cristy0c643722012-03-05 19:18:42 +0000573 }
574 q+=GetPixelChannels(combine_image);
575 }
576 image_view=DestroyCacheView(image_view);
577 next=GetNextImageInList(next);
cristy0c643722012-03-05 19:18:42 +0000578 }
cristyf3381ae2012-07-13 16:30:09 +0000579 if (SyncCacheViewAuthenticPixels(combine_view,exception) == MagickFalse)
580 status=MagickFalse;
581 if (image->progress_monitor != (MagickProgressMonitor) NULL)
582 {
583 MagickBooleanType
584 proceed;
585
586 proceed=SetImageProgress(image,CombineImageTag,progress++,
587 combine_image->rows);
588 if (proceed == MagickFalse)
589 status=MagickFalse;
590 }
cristy0c643722012-03-05 19:18:42 +0000591 }
592 combine_view=DestroyCacheView(combine_view);
593 if (status == MagickFalse)
594 combine_image=DestroyImage(combine_image);
595 return(combine_image);
596}
597
598/*
599%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
600% %
601% %
602% %
cristy8c8c9162013-05-27 10:14:21 +0000603% G e t I m a g e A l p h a C h a n n e l %
604% %
605% %
606% %
607%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
608%
609% GetImageAlphaChannel() returns MagickFalse if the image alpha channel is
610% not activated. That is, the image is RGB rather than RGBA or CMYK rather
611% than CMYKA.
612%
613% The format of the GetImageAlphaChannel method is:
614%
615% MagickBooleanType GetImageAlphaChannel(const Image *image)
616%
617% A description of each parameter follows:
618%
619% o image: the image.
620%
621*/
622MagickExport MagickBooleanType GetImageAlphaChannel(const Image *image)
623{
624 assert(image != (const Image *) NULL);
625 if (image->debug != MagickFalse)
626 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
627 assert(image->signature == MagickSignature);
628 return(image->alpha_trait == BlendPixelTrait ? MagickTrue : MagickFalse);
629}
630
631/*
632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
633% %
634% %
635% %
cristy0c643722012-03-05 19:18:42 +0000636% S e p a r a t e I m a g e %
637% %
638% %
639% %
640%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
641%
642% SeparateImage() separates a channel from the image and returns it as a
643% grayscale image.
644%
645% The format of the SeparateImage method is:
646%
647% Image *SeparateImage(const Image *image,const ChannelType channel,
648% ExceptionInfo *exception)
649%
650% A description of each parameter follows:
651%
652% o image: the image.
653%
654% o channel: the image channel.
655%
656% o exception: return any errors or warnings in this structure.
657%
658*/
659MagickExport Image *SeparateImage(const Image *image,
660 const ChannelType channel_type,ExceptionInfo *exception)
661{
662#define GetChannelBit(mask,bit) (((size_t) (mask) >> (size_t) (bit)) & 0x01)
663#define SeparateImageTag "Separate/Image"
664
665 CacheView
666 *image_view,
667 *separate_view;
668
669 Image
670 *separate_image;
671
672 MagickBooleanType
673 status;
674
675 MagickOffsetType
676 progress;
677
678 ssize_t
679 y;
680
681 /*
cristy8fdeeb32013-03-31 21:15:31 +0000682 Initialize separate image attributes.
cristy0c643722012-03-05 19:18:42 +0000683 */
684 assert(image != (Image *) NULL);
685 assert(image->signature == MagickSignature);
686 if (image->debug != MagickFalse)
687 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
688 assert(exception != (ExceptionInfo *) NULL);
689 assert(exception->signature == MagickSignature);
690 separate_image=CloneImage(image,image->columns,image->rows,MagickTrue,
691 exception);
692 if (separate_image == (Image *) NULL)
693 return((Image *) NULL);
694 if (SetImageStorageClass(separate_image,DirectClass,exception) == MagickFalse)
695 {
696 separate_image=DestroyImage(separate_image);
697 return((Image *) NULL);
698 }
cristyaa7a2252012-08-15 12:19:00 +0000699 (void) SetImageColorspace(separate_image,GRAYColorspace,exception);
cristy9b8205c2013-04-16 11:16:19 +0000700 separate_image->alpha_trait=UndefinedPixelTrait;
cristy0c643722012-03-05 19:18:42 +0000701 /*
702 Separate image.
703 */
704 status=MagickTrue;
705 progress=0;
cristy46ff2672012-12-14 15:32:26 +0000706 image_view=AcquireVirtualCacheView(image,exception);
707 separate_view=AcquireAuthenticCacheView(separate_image,exception);
cristy0c643722012-03-05 19:18:42 +0000708#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +0000709 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000710 magick_threads(image,image,image->rows,1)
cristy0c643722012-03-05 19:18:42 +0000711#endif
712 for (y=0; y < (ssize_t) image->rows; y++)
713 {
714 register const Quantum
715 *restrict p;
716
717 register Quantum
718 *restrict q;
719
720 register ssize_t
721 x;
722
723 if (status == MagickFalse)
724 continue;
725 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
726 q=QueueCacheViewAuthenticPixels(separate_view,0,y,separate_image->columns,1,
727 exception);
728 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
729 {
730 status=MagickFalse;
731 continue;
732 }
733 for (x=0; x < (ssize_t) image->columns; x++)
734 {
735 register ssize_t
736 i;
737
cristy883fde12013-04-08 00:50:13 +0000738 if (GetPixelReadMask(image,p) == 0)
cristy0c643722012-03-05 19:18:42 +0000739 {
cristyc3a58022013-10-09 23:22:42 +0000740 SetPixelBackgoundColor(separate_image,q);
cristy0c643722012-03-05 19:18:42 +0000741 p+=GetPixelChannels(image);
742 q+=GetPixelChannels(separate_image);
743 continue;
744 }
745 SetPixelChannel(separate_image,GrayPixelChannel,0,q);
746 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
747 {
cristy5a23c552013-02-13 14:34:28 +0000748 PixelChannel channel=GetPixelChannelChannel(image,i);
749 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy0c643722012-03-05 19:18:42 +0000750 if ((traits == UndefinedPixelTrait) ||
751 (GetChannelBit(channel_type,channel) == 0))
752 continue;
cristy9b8205c2013-04-16 11:16:19 +0000753 SetPixelChannel(separate_image,GrayPixelChannel,p[i],q);
cristy0c643722012-03-05 19:18:42 +0000754 }
755 p+=GetPixelChannels(image);
756 q+=GetPixelChannels(separate_image);
757 }
758 if (SyncCacheViewAuthenticPixels(separate_view,exception) == MagickFalse)
759 status=MagickFalse;
760 if (image->progress_monitor != (MagickProgressMonitor) NULL)
761 {
762 MagickBooleanType
763 proceed;
764
765#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000766 #pragma omp critical (MagickCore_SeparateImage)
cristy0c643722012-03-05 19:18:42 +0000767#endif
768 proceed=SetImageProgress(image,SeparateImageTag,progress++,image->rows);
769 if (proceed == MagickFalse)
770 status=MagickFalse;
771 }
772 }
773 separate_view=DestroyCacheView(separate_view);
774 image_view=DestroyCacheView(image_view);
cristy1c2f48d2012-12-14 01:20:55 +0000775 if (status == MagickFalse)
776 separate_image=DestroyImage(separate_image);
cristy0c643722012-03-05 19:18:42 +0000777 return(separate_image);
778}
779
780/*
781%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
782% %
783% %
784% %
785% S e p a r a t e I m a g e s %
786% %
787% %
788% %
789%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
790%
791% SeparateImages() returns a separate grayscale image for each channel
792% specified.
793%
794% The format of the SeparateImages method is:
795%
cristydfdb19e2012-03-21 22:22:24 +0000796% Image *SeparateImages(const Image *image,ExceptionInfo *exception)
cristy0c643722012-03-05 19:18:42 +0000797%
798% A description of each parameter follows:
799%
800% o image: the image.
801%
802% o exception: return any errors or warnings in this structure.
803%
804*/
cristydfdb19e2012-03-21 22:22:24 +0000805MagickExport Image *SeparateImages(const Image *image,ExceptionInfo *exception)
cristy0c643722012-03-05 19:18:42 +0000806{
807 Image
808 *images,
809 *separate_image;
810
811 register ssize_t
812 i;
813
814 assert(image != (Image *) NULL);
815 assert(image->signature == MagickSignature);
816 if (image->debug != MagickFalse)
817 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
818 images=NewImageList();
819 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
820 {
cristy5a23c552013-02-13 14:34:28 +0000821 PixelChannel channel=GetPixelChannelChannel(image,i);
822 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy0c643722012-03-05 19:18:42 +0000823 if ((traits == UndefinedPixelTrait) ||
824 ((traits & UpdatePixelTrait) == 0))
825 continue;
826 separate_image=SeparateImage(image,(ChannelType) (1 << channel),exception);
827 if (separate_image != (Image *) NULL)
828 AppendImageToList(&images,separate_image);
829 }
cristye5ef2ce2012-04-29 21:14:32 +0000830 if (images == (Image *) NULL)
831 images=SeparateImage(image,UndefinedChannel,exception);
cristy0c643722012-03-05 19:18:42 +0000832 return(images);
833}
cristy8c8c9162013-05-27 10:14:21 +0000834
835/*
836%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
837% %
838% %
839% %
840% S e t I m a g e A l p h a C h a n n e l %
841% %
842% %
843% %
844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
845%
846% SetImageAlphaChannel() activates, deactivates, resets, or sets the alpha
847% channel.
848%
849% The format of the SetImageAlphaChannel method is:
850%
851% MagickBooleanType SetImageAlphaChannel(Image *image,
852% const AlphaChannelOption alpha_type,ExceptionInfo *exception)
853%
854% A description of each parameter follows:
855%
856% o image: the image.
857%
858% o alpha_type: The alpha channel type: ActivateAlphaChannel,
859% CopyAlphaChannel, DeactivateAlphaChannel, ExtractAlphaChannel,
860% OpaqueAlphaChannel, SetAlphaChannel, ShapeAlphaChannel, and
861% TransparentAlphaChannel.
862%
863% o exception: return any errors or warnings in this structure.
864%
865*/
866
867static inline void FlattenPixelInfo(const Image *image,const PixelInfo *p,
868 const double alpha,const Quantum *q,const double beta,
869 Quantum *composite)
870{
871 double
872 Da,
873 gamma,
874 Sa;
875
876 register ssize_t
877 i;
878
879 /*
880 Compose pixel p over pixel q with the given alpha.
881 */
882 Sa=QuantumScale*alpha;
883 Da=QuantumScale*beta,
884 gamma=Sa*(-Da)+Sa+Da;
885 gamma=PerceptibleReciprocal(gamma);
886 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
887 {
888 PixelChannel channel=GetPixelChannelChannel(image,i);
889 PixelTrait traits=GetPixelChannelTraits(image,channel);
890 if (traits == UndefinedPixelTrait)
891 continue;
892 switch (channel)
893 {
894 case RedPixelChannel:
895 {
896 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
897 (double) p->red,alpha));
898 break;
899 }
900 case GreenPixelChannel:
901 {
902 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
903 (double) p->green,alpha));
904 break;
905 }
906 case BluePixelChannel:
907 {
908 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
909 (double) p->blue,alpha));
910 break;
911 }
912 case BlackPixelChannel:
913 {
914 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
915 (double) p->black,alpha));
916 break;
917 }
918 case AlphaPixelChannel:
919 {
920 composite[i]=ClampToQuantum(QuantumRange*(Sa*(-Da)+Sa+Da));
921 break;
922 }
923 default:
924 break;
925 }
926 }
927}
928
929MagickExport MagickBooleanType SetImageAlphaChannel(Image *image,
930 const AlphaChannelOption alpha_type,ExceptionInfo *exception)
931{
932 MagickBooleanType
933 status;
934
935 assert(image != (Image *) NULL);
936 if (image->debug != MagickFalse)
937 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
938 assert(image->signature == MagickSignature);
939 status=MagickTrue;
940 switch (alpha_type)
941 {
942 case ActivateAlphaChannel:
943 {
944 image->alpha_trait=BlendPixelTrait;
945 break;
946 }
947 case BackgroundAlphaChannel:
948 {
949 CacheView
950 *image_view;
951
952 ssize_t
953 y;
954
955 /*
956 Set transparent pixels to background color.
957 */
958 if (image->alpha_trait != BlendPixelTrait)
959 break;
960 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
961 break;
962 image_view=AcquireAuthenticCacheView(image,exception);
963#if defined(MAGICKCORE_OPENMP_SUPPORT)
964 #pragma omp parallel for schedule(static,4) shared(status) \
965 magick_threads(image,image,image->rows,1)
966#endif
967 for (y=0; y < (ssize_t) image->rows; y++)
968 {
969 register Quantum
970 *restrict q;
971
972 register ssize_t
973 x;
974
975 if (status == MagickFalse)
976 continue;
977 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
978 exception);
979 if (q == (Quantum *) NULL)
980 {
981 status=MagickFalse;
982 continue;
983 }
984 for (x=0; x < (ssize_t) image->columns; x++)
985 {
986 if (GetPixelAlpha(image,q) == TransparentAlpha)
987 {
988 SetPixelInfoPixel(image,&image->background_color,q);
989 SetPixelChannel(image,AlphaPixelChannel,TransparentAlpha,q);
990 }
991 q+=GetPixelChannels(image);
992 }
993 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
994 status=MagickFalse;
995 }
996 image_view=DestroyCacheView(image_view);
997 return(status);
998 }
999 case CopyAlphaChannel:
1000 case ShapeAlphaChannel:
1001 {
1002 /*
1003 Copy pixel intensity to the alpha channel.
1004 */
1005 status=CompositeImage(image,image,IntensityCompositeOp,MagickTrue,0,0,
1006 exception);
1007 if (alpha_type == ShapeAlphaChannel)
1008 (void) LevelImageColors(image,&image->background_color,
1009 &image->background_color,MagickTrue,exception);
1010 break;
1011 }
1012 case DeactivateAlphaChannel:
1013 {
1014 image->alpha_trait=CopyPixelTrait;
1015 break;
1016 }
1017 case ExtractAlphaChannel:
1018 {
1019 status=CompositeImage(image,image,AlphaCompositeOp,MagickTrue,0,0,
1020 exception);
1021 image->alpha_trait=CopyPixelTrait;
1022 break;
1023 }
1024 case OpaqueAlphaChannel:
1025 {
1026 status=SetImageAlpha(image,OpaqueAlpha,exception);
1027 break;
1028 }
1029 case RemoveAlphaChannel:
1030 {
1031 CacheView
1032 *image_view;
1033
1034 ssize_t
1035 y;
1036
1037 /*
1038 Remove transparency.
1039 */
1040 if (image->alpha_trait != BlendPixelTrait)
1041 break;
1042 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1043 break;
1044 image_view=AcquireAuthenticCacheView(image,exception);
1045#if defined(MAGICKCORE_OPENMP_SUPPORT)
1046 #pragma omp parallel for schedule(static,4) shared(status) \
1047 magick_threads(image,image,image->rows,1)
1048#endif
1049 for (y=0; y < (ssize_t) image->rows; y++)
1050 {
1051 register Quantum
1052 *restrict q;
1053
1054 register ssize_t
1055 x;
1056
1057 if (status == MagickFalse)
1058 continue;
1059 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
1060 exception);
1061 if (q == (Quantum *) NULL)
1062 {
1063 status=MagickFalse;
1064 continue;
1065 }
1066 for (x=0; x < (ssize_t) image->columns; x++)
1067 {
1068 FlattenPixelInfo(image,&image->background_color,
1069 image->background_color.alpha,q,(double)
1070 GetPixelAlpha(image,q),q);
1071 q+=GetPixelChannels(image);
1072 }
1073 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1074 status=MagickFalse;
1075 }
1076 image_view=DestroyCacheView(image_view);
1077 image->alpha_trait=image->background_color.alpha_trait;
1078 return(status);
1079 }
1080 case SetAlphaChannel:
1081 {
1082 if (image->alpha_trait != BlendPixelTrait)
1083 status=SetImageAlpha(image,OpaqueAlpha,exception);
1084 break;
1085 }
1086 case TransparentAlphaChannel:
1087 {
1088 status=SetImageAlpha(image,TransparentAlpha,exception);
1089 break;
1090 }
1091 case UndefinedAlphaChannel:
1092 break;
1093 }
1094 if (status == MagickFalse)
1095 return(status);
1096 return(SyncImagePixelCache(image,exception));
1097}