blob: ab6c20860dcc2cb2f08954f5b4179464d8094145 [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 {
740 p+=GetPixelChannels(image);
741 q+=GetPixelChannels(separate_image);
742 continue;
743 }
744 SetPixelChannel(separate_image,GrayPixelChannel,0,q);
745 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
746 {
cristy5a23c552013-02-13 14:34:28 +0000747 PixelChannel channel=GetPixelChannelChannel(image,i);
748 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy0c643722012-03-05 19:18:42 +0000749 if ((traits == UndefinedPixelTrait) ||
750 (GetChannelBit(channel_type,channel) == 0))
751 continue;
cristy9b8205c2013-04-16 11:16:19 +0000752 SetPixelChannel(separate_image,GrayPixelChannel,p[i],q);
cristy0c643722012-03-05 19:18:42 +0000753 }
754 p+=GetPixelChannels(image);
755 q+=GetPixelChannels(separate_image);
756 }
757 if (SyncCacheViewAuthenticPixels(separate_view,exception) == MagickFalse)
758 status=MagickFalse;
759 if (image->progress_monitor != (MagickProgressMonitor) NULL)
760 {
761 MagickBooleanType
762 proceed;
763
764#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000765 #pragma omp critical (MagickCore_SeparateImage)
cristy0c643722012-03-05 19:18:42 +0000766#endif
767 proceed=SetImageProgress(image,SeparateImageTag,progress++,image->rows);
768 if (proceed == MagickFalse)
769 status=MagickFalse;
770 }
771 }
772 separate_view=DestroyCacheView(separate_view);
773 image_view=DestroyCacheView(image_view);
cristy1c2f48d2012-12-14 01:20:55 +0000774 if (status == MagickFalse)
775 separate_image=DestroyImage(separate_image);
cristy0c643722012-03-05 19:18:42 +0000776 return(separate_image);
777}
778
779/*
780%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
781% %
782% %
783% %
784% S e p a r a t e I m a g e s %
785% %
786% %
787% %
788%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
789%
790% SeparateImages() returns a separate grayscale image for each channel
791% specified.
792%
793% The format of the SeparateImages method is:
794%
cristydfdb19e2012-03-21 22:22:24 +0000795% Image *SeparateImages(const Image *image,ExceptionInfo *exception)
cristy0c643722012-03-05 19:18:42 +0000796%
797% A description of each parameter follows:
798%
799% o image: the image.
800%
801% o exception: return any errors or warnings in this structure.
802%
803*/
cristydfdb19e2012-03-21 22:22:24 +0000804MagickExport Image *SeparateImages(const Image *image,ExceptionInfo *exception)
cristy0c643722012-03-05 19:18:42 +0000805{
806 Image
807 *images,
808 *separate_image;
809
810 register ssize_t
811 i;
812
813 assert(image != (Image *) NULL);
814 assert(image->signature == MagickSignature);
815 if (image->debug != MagickFalse)
816 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
817 images=NewImageList();
818 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
819 {
cristy5a23c552013-02-13 14:34:28 +0000820 PixelChannel channel=GetPixelChannelChannel(image,i);
821 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy0c643722012-03-05 19:18:42 +0000822 if ((traits == UndefinedPixelTrait) ||
823 ((traits & UpdatePixelTrait) == 0))
824 continue;
825 separate_image=SeparateImage(image,(ChannelType) (1 << channel),exception);
826 if (separate_image != (Image *) NULL)
827 AppendImageToList(&images,separate_image);
828 }
cristye5ef2ce2012-04-29 21:14:32 +0000829 if (images == (Image *) NULL)
830 images=SeparateImage(image,UndefinedChannel,exception);
cristy0c643722012-03-05 19:18:42 +0000831 return(images);
832}
cristy8c8c9162013-05-27 10:14:21 +0000833
834/*
835%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
836% %
837% %
838% %
839% S e t I m a g e A l p h a C h a n n e l %
840% %
841% %
842% %
843%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
844%
845% SetImageAlphaChannel() activates, deactivates, resets, or sets the alpha
846% channel.
847%
848% The format of the SetImageAlphaChannel method is:
849%
850% MagickBooleanType SetImageAlphaChannel(Image *image,
851% const AlphaChannelOption alpha_type,ExceptionInfo *exception)
852%
853% A description of each parameter follows:
854%
855% o image: the image.
856%
857% o alpha_type: The alpha channel type: ActivateAlphaChannel,
858% CopyAlphaChannel, DeactivateAlphaChannel, ExtractAlphaChannel,
859% OpaqueAlphaChannel, SetAlphaChannel, ShapeAlphaChannel, and
860% TransparentAlphaChannel.
861%
862% o exception: return any errors or warnings in this structure.
863%
864*/
865
866static inline void FlattenPixelInfo(const Image *image,const PixelInfo *p,
867 const double alpha,const Quantum *q,const double beta,
868 Quantum *composite)
869{
870 double
871 Da,
872 gamma,
873 Sa;
874
875 register ssize_t
876 i;
877
878 /*
879 Compose pixel p over pixel q with the given alpha.
880 */
881 Sa=QuantumScale*alpha;
882 Da=QuantumScale*beta,
883 gamma=Sa*(-Da)+Sa+Da;
884 gamma=PerceptibleReciprocal(gamma);
885 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
886 {
887 PixelChannel channel=GetPixelChannelChannel(image,i);
888 PixelTrait traits=GetPixelChannelTraits(image,channel);
889 if (traits == UndefinedPixelTrait)
890 continue;
891 switch (channel)
892 {
893 case RedPixelChannel:
894 {
895 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
896 (double) p->red,alpha));
897 break;
898 }
899 case GreenPixelChannel:
900 {
901 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
902 (double) p->green,alpha));
903 break;
904 }
905 case BluePixelChannel:
906 {
907 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
908 (double) p->blue,alpha));
909 break;
910 }
911 case BlackPixelChannel:
912 {
913 composite[i]=ClampToQuantum(gamma*MagickOver_((double) q[i],beta,
914 (double) p->black,alpha));
915 break;
916 }
917 case AlphaPixelChannel:
918 {
919 composite[i]=ClampToQuantum(QuantumRange*(Sa*(-Da)+Sa+Da));
920 break;
921 }
922 default:
923 break;
924 }
925 }
926}
927
928MagickExport MagickBooleanType SetImageAlphaChannel(Image *image,
929 const AlphaChannelOption alpha_type,ExceptionInfo *exception)
930{
931 MagickBooleanType
932 status;
933
934 assert(image != (Image *) NULL);
935 if (image->debug != MagickFalse)
936 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
937 assert(image->signature == MagickSignature);
938 status=MagickTrue;
939 switch (alpha_type)
940 {
941 case ActivateAlphaChannel:
942 {
943 image->alpha_trait=BlendPixelTrait;
944 break;
945 }
946 case BackgroundAlphaChannel:
947 {
948 CacheView
949 *image_view;
950
951 ssize_t
952 y;
953
954 /*
955 Set transparent pixels to background color.
956 */
957 if (image->alpha_trait != BlendPixelTrait)
958 break;
959 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
960 break;
961 image_view=AcquireAuthenticCacheView(image,exception);
962#if defined(MAGICKCORE_OPENMP_SUPPORT)
963 #pragma omp parallel for schedule(static,4) shared(status) \
964 magick_threads(image,image,image->rows,1)
965#endif
966 for (y=0; y < (ssize_t) image->rows; y++)
967 {
968 register Quantum
969 *restrict q;
970
971 register ssize_t
972 x;
973
974 if (status == MagickFalse)
975 continue;
976 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
977 exception);
978 if (q == (Quantum *) NULL)
979 {
980 status=MagickFalse;
981 continue;
982 }
983 for (x=0; x < (ssize_t) image->columns; x++)
984 {
985 if (GetPixelAlpha(image,q) == TransparentAlpha)
986 {
987 SetPixelInfoPixel(image,&image->background_color,q);
988 SetPixelChannel(image,AlphaPixelChannel,TransparentAlpha,q);
989 }
990 q+=GetPixelChannels(image);
991 }
992 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
993 status=MagickFalse;
994 }
995 image_view=DestroyCacheView(image_view);
996 return(status);
997 }
998 case CopyAlphaChannel:
999 case ShapeAlphaChannel:
1000 {
1001 /*
1002 Copy pixel intensity to the alpha channel.
1003 */
1004 status=CompositeImage(image,image,IntensityCompositeOp,MagickTrue,0,0,
1005 exception);
1006 if (alpha_type == ShapeAlphaChannel)
1007 (void) LevelImageColors(image,&image->background_color,
1008 &image->background_color,MagickTrue,exception);
1009 break;
1010 }
1011 case DeactivateAlphaChannel:
1012 {
1013 image->alpha_trait=CopyPixelTrait;
1014 break;
1015 }
1016 case ExtractAlphaChannel:
1017 {
1018 status=CompositeImage(image,image,AlphaCompositeOp,MagickTrue,0,0,
1019 exception);
1020 image->alpha_trait=CopyPixelTrait;
1021 break;
1022 }
1023 case OpaqueAlphaChannel:
1024 {
1025 status=SetImageAlpha(image,OpaqueAlpha,exception);
1026 break;
1027 }
1028 case RemoveAlphaChannel:
1029 {
1030 CacheView
1031 *image_view;
1032
1033 ssize_t
1034 y;
1035
1036 /*
1037 Remove transparency.
1038 */
1039 if (image->alpha_trait != BlendPixelTrait)
1040 break;
1041 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1042 break;
1043 image_view=AcquireAuthenticCacheView(image,exception);
1044#if defined(MAGICKCORE_OPENMP_SUPPORT)
1045 #pragma omp parallel for schedule(static,4) shared(status) \
1046 magick_threads(image,image,image->rows,1)
1047#endif
1048 for (y=0; y < (ssize_t) image->rows; y++)
1049 {
1050 register Quantum
1051 *restrict q;
1052
1053 register ssize_t
1054 x;
1055
1056 if (status == MagickFalse)
1057 continue;
1058 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
1059 exception);
1060 if (q == (Quantum *) NULL)
1061 {
1062 status=MagickFalse;
1063 continue;
1064 }
1065 for (x=0; x < (ssize_t) image->columns; x++)
1066 {
1067 FlattenPixelInfo(image,&image->background_color,
1068 image->background_color.alpha,q,(double)
1069 GetPixelAlpha(image,q),q);
1070 q+=GetPixelChannels(image);
1071 }
1072 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1073 status=MagickFalse;
1074 }
1075 image_view=DestroyCacheView(image_view);
1076 image->alpha_trait=image->background_color.alpha_trait;
1077 return(status);
1078 }
1079 case SetAlphaChannel:
1080 {
1081 if (image->alpha_trait != BlendPixelTrait)
1082 status=SetImageAlpha(image,OpaqueAlpha,exception);
1083 break;
1084 }
1085 case TransparentAlphaChannel:
1086 {
1087 status=SetImageAlpha(image,TransparentAlpha,exception);
1088 break;
1089 }
1090 case UndefinedAlphaChannel:
1091 break;
1092 }
1093 if (status == MagickFalse)
1094 return(status);
1095 return(SyncImagePixelCache(image,exception));
1096}