]> granicus.if.org Git - imagemagick/blobdiff - MagickCore/channel.c
(no commit message)
[imagemagick] / MagickCore / channel.c
index 4fd75d6c31c5ca378ec1d21dd1fff3623ca7d02e..bd96b55cf461fbd5a74ea7fd7d14c15d32fe3be0 100644 (file)
   Include declarations.
 */
 #include "MagickCore/studio.h"
+#include "MagickCore/colorspace-private.h"
 #include "MagickCore/image.h"
 #include "MagickCore/list.h"
 #include "MagickCore/log.h"
+#include "MagickCore/monitor.h"
+#include "MagickCore/monitor-private.h"
 #include "MagickCore/option.h"
+#include "MagickCore/pixel-accessor.h"
+#include "MagickCore/resource_.h"
+#include "MagickCore/string-private.h"
+#include "MagickCore/thread-private.h"
 #include "MagickCore/token.h"
 #include "MagickCore/utility.h"
 #include "MagickCore/version.h"
 %                                                                             %
 %                                                                             %
 %                                                                             %
-%     C h a n n e l O p e r a t i o n I m a g e                               %
+%     C h a n n e l F x I m a g e                                             %
 %                                                                             %
 %                                                                             %
 %                                                                             %
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %
-%  ChannelOperationImage() applies a channel expression to the specified image.
+%  ChannelFxImage() applies a channel expression to the specified image.  The
+%  expression consists of one or more channels, either mnemonic or numeric (e.g.
+%  red, 1), separated by actions as follows:
 %
-%  The format of the ChannelOperationImage method is:
+%    <=>     exchange two channels (e.g. red<=>blue)
+%    =>      copy one channel to another channel (e.g. red=>green)
+%    =       assign a constant value to a channel (e.g. red=50%)
+%    ,       write new image channels in the specified order (e.g. red, green)
+%    |       add a new output image for the next set of channel operations
+%    ;       move to the next input image for the source of channel data
 %
-%      Image *ChannelOperationImage(const Image *images,
-%        const char *expression,ExceptionInfo *exception)
+%  For example, to create 3 grayscale images from the red, green, and blue
+%  channels of an image, use:
+%
+%    -channel-fx "red; green; blue"
+%
+%  A channel without an operation symbol implies separate (i.e, semicolon).
+%
+%  The format of the ChannelFxImage method is:
+%
+%      Image *ChannelFxImage(const Image *image,const char *expression,
+%        ExceptionInfo *exception)
 %
 %  A description of each parameter follows:
 %
-%    o images: the images.
+%    o image: the image.
 %
 %    o expression: A channel expression.
 %
 typedef enum
 {
   ExtractChannelOp,
+  AssignChannelOp,
   ExchangeChannelOp,
   TransferChannelOp
-} ChannelOperation;
+} ChannelFx;
 
-static MagickBooleanType ChannelImage(Image *channel_image,const Image *image,
-  const ChannelOperation channel_op,const PixelChannel p_channel,
-  const PixelChannel q_channel,ExceptionInfo *exception)
+static inline size_t MagickMin(const size_t x,const size_t y)
 {
-  return(MagickTrue);
+  if (x < y)
+    return(x);
+  return(y);
 }
 
-MagickExport Image *ChannelOperationImage(const Image *images,
-  const char *expression,ExceptionInfo *exception)
+static MagickBooleanType ChannelImage(Image *destination_image,
+  const PixelChannel destination_channel,const ChannelFx channel_op,
+  const Image *source_image,const PixelChannel source_channel,
+  const Quantum pixel,ExceptionInfo *exception)
 {
-  char
-    token[MaxTextExtent];
+  CacheView
+    *source_view,
+    *destination_view;
+
+  MagickBooleanType
+    status;
+
+  size_t
+    height,
+    width;
+
+  ssize_t
+    y;
+
+  status=MagickTrue;
+  source_view=AcquireVirtualCacheView(source_image,exception);
+  destination_view=AcquireAuthenticCacheView(destination_image,exception);
+  height=MagickMin(source_image->rows,destination_image->rows);
+  width=MagickMin(source_image->columns,destination_image->columns);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+  #pragma omp parallel for schedule(static) shared(status) \
+    dynamic_number_threads(source_image,width,height,1)
+#endif
+  for (y=0; y < (ssize_t) height; y++)
+  {
+    PixelTrait
+      destination_traits,
+      source_traits;
+
+    register const Quantum
+      *restrict p;
 
-  ChannelOperation
+    register Quantum
+      *restrict q;
+
+    register ssize_t
+      x;
+
+    if (status == MagickFalse)
+      continue;
+    p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
+      exception);
+    q=GetCacheViewAuthenticPixels(destination_view,0,y,
+      destination_image->columns,1,exception);
+    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
+      {
+        status=MagickFalse;
+        continue;
+      }
+    destination_traits=GetPixelChannelMapTraits(destination_image,
+      destination_channel);
+    source_traits=GetPixelChannelMapTraits(source_image,source_channel);
+    if ((destination_traits == UndefinedPixelTrait) ||
+        (source_traits == UndefinedPixelTrait))
+      continue;
+    for (x=0; x < (ssize_t) width; x++)
+    {
+      if (channel_op == AssignChannelOp)
+        SetPixelChannel(destination_image,destination_channel,pixel,q);
+      else
+        SetPixelChannel(destination_image,destination_channel,
+          GetPixelChannel(source_image,source_channel,p),q);
+      p+=GetPixelChannels(source_image);
+      q+=GetPixelChannels(destination_image);
+    }
+    if (SyncCacheViewAuthenticPixels(destination_view,exception) == MagickFalse)
+      status=MagickFalse;
+  }
+  destination_view=DestroyCacheView(destination_view);
+  source_view=DestroyCacheView(source_view);
+  return(status);
+}
+
+MagickExport Image *ChannelFxImage(const Image *image,const char *expression,
+  ExceptionInfo *exception)
+{
+#define ChannelFxImageTag  "ChannelFx/Image"
+
+  ChannelFx
     channel_op;
 
+  ChannelType
+    channel_mask;
+
+  char
+    token[MaxTextExtent];
+
   const char
     *p;
 
+  const Image
+    *source_image;
+
+  double
+    pixel;
+
   Image
-    *channel_images;
+    *destination_image;
+
+  MagickBooleanType
+    status;
 
   PixelChannel
-    p_channel,
-    q_channel;
-
-  assert(images != (Image *) NULL);
-  assert(images->signature == MagickSignature);
-  if (images->debug != MagickFalse)
-    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
-  channel_images=CloneImage(images,images->columns,images->columns,MagickTrue,
-     exception);
+    source_channel,
+    destination_channel;
+
+  ssize_t
+    channels;
+
+  assert(image != (Image *) NULL);
+  assert(image->signature == MagickSignature);
+  if (image->debug != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+  assert(exception != (ExceptionInfo *) NULL);
+  assert(exception->signature == MagickSignature);
+  source_image=image;
+  destination_image=CloneImage(source_image,0,0,MagickTrue,exception);
+  if (destination_image == (Image *) NULL)
+    return((Image *) NULL);
+  if (IsGrayColorspace(image->colorspace) != MagickFalse)
+    (void) TransformImageColorspace((Image *) image,RGBColorspace,exception);
+  if (expression == (const char *) NULL)
+    return(destination_image);
+  destination_channel=RedPixelChannel;
+  channel_mask=UndefinedChannel;
+  pixel=0.0;
   p=(char *) expression;
   GetMagickToken(p,&p,token);
-  for (q_channel=RedPixelChannel; *p != '\0'; )
+  channel_op=ExtractChannelOp;
+  for (channels=0; *token != '\0'; )
   {
-    MagickBooleanType
-      status;
-
     ssize_t
       i;
 
@@ -131,33 +259,55 @@ MagickExport Image *ChannelOperationImage(const Image *images,
     */
     if (*token == ',')
       {
-        q_channel=(PixelChannel) ((ssize_t) q_channel+1);
+        destination_channel=(PixelChannel) ((ssize_t) destination_channel+1);
         GetMagickToken(p,&p,token);
       }
     if (*token == '|')
       {
-        if (GetNextImageInList(images) != (Image *) NULL)
-          images=GetNextImageInList(images);
+        if (GetNextImageInList(source_image) != (Image *) NULL)
+          source_image=GetNextImageInList(source_image);
         else
-          images=GetFirstImageInList(images);
+          source_image=GetFirstImageInList(source_image);
         GetMagickToken(p,&p,token);
       }
     if (*token == ';')
       {
-        AppendImageToList(&channel_images,CloneImage(images,
-          channel_images->columns,channel_images->rows,MagickTrue,exception));
-        channel_images=GetLastImageInList(channel_images);
+        Image
+          *canvas;
+
+        SetPixelChannelMapMask(destination_image,channel_mask);
+        if ((channel_op == ExtractChannelOp) && (destination_channel == 1))
+          (void) SetImageColorspace(destination_image,GRAYColorspace,exception);
+        status=SetImageStorageClass(destination_image,DirectClass,exception);
+        if (status == MagickFalse)
+          {
+            destination_image=DestroyImageList(destination_image);
+            return(destination_image);
+          }
+        canvas=CloneImage(source_image,0,0,MagickTrue,exception);
+        if (canvas == (Image *) NULL)
+          {
+            destination_image=DestroyImageList(destination_image);
+            return(destination_image);
+          }
+        if (IsGrayColorspace(canvas->colorspace) != MagickFalse)
+          (void) TransformImageColorspace(canvas,RGBColorspace,exception);
+        AppendImageToList(&destination_image,canvas);
+        destination_image=GetLastImageInList(destination_image);
         GetMagickToken(p,&p,token);
+        channels=0;
+        destination_channel=RedPixelChannel;
+        channel_mask=UndefinedChannel;
       }
     i=ParsePixelChannelOption(token);
     if (i < 0)
       {
         (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-          "UnableToParseExpression","`%s'",p);
-        channel_images=DestroyImageList(channel_images);
-        break;
+          "UnrecognizedChannelType","'%s'",token);
+        destination_image=DestroyImageList(destination_image);
+        return(destination_image);
       }
-    p_channel=(PixelChannel) i;
+    source_channel=(PixelChannel) i;
     channel_op=ExtractChannelOp;
     GetMagickToken(p,&p,token);
     if (*token == '<')
@@ -166,32 +316,488 @@ MagickExport Image *ChannelOperationImage(const Image *images,
         GetMagickToken(p,&p,token);
       }
     if (*token == '=')
-      GetMagickToken(p,&p,token);
+      {
+        if (channel_op != ExchangeChannelOp)
+          channel_op=AssignChannelOp;
+        GetMagickToken(p,&p,token);
+      }
     if (*token == '>')
       {
         if (channel_op != ExchangeChannelOp)
           channel_op=TransferChannelOp;
         GetMagickToken(p,&p,token);
       }
-    if (channel_op != ExtractChannelOp)
+    switch (channel_op)
+    {
+      case AssignChannelOp:
+      {
+        pixel=StringToDoubleInterval(token,(double) QuantumRange+1.0);
+        GetMagickToken(p,&p,token);
+        break;
+      }
+      case ExchangeChannelOp:
+      case TransferChannelOp:
       {
         i=ParsePixelChannelOption(token);
         if (i < 0)
           {
             (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-              "UnableToParseExpression","`%s'",p);
-            channel_images=DestroyImageList(channel_images);
-            break;
+              "UnrecognizedChannelType","'%s'",token);
+            destination_image=DestroyImageList(destination_image);
+            return(destination_image);
           }
-        q_channel=(PixelChannel) i;
+        destination_channel=(PixelChannel) i;
+        channel_mask=(ChannelType) (channel_mask | ParseChannelOption(token));
+        if (LocaleCompare(token,"gray") == 0)
+          (void) SetImageColorspace(destination_image,GRAYColorspace,exception);
+        if ((LocaleCompare(token,"black") == 0) ||
+            (LocaleCompare(token,"c") == 0) ||
+            (LocaleCompare(token,"cyan") == 0) ||
+            (LocaleCompare(token,"k") == 0) ||
+            (LocaleCompare(token,"m") == 0) ||
+            (LocaleCompare(token,"magenta") == 0) ||
+            (LocaleCompare(token,"y") == 0) ||
+            (LocaleCompare(token,"yellow") == 0))
+          (void) SetImageColorspace(destination_image,CMYKColorspace,exception);
+        if ((LocaleCompare(token,"Cb") == 0) ||
+            (LocaleCompare(token,"Cr") == 0))
+          (void) SetImageColorspace(destination_image,YCbCrColorspace,
+            exception);
+        if (LocaleCompare(token,"alpha") == 0)
+          (void) SetImageAlpha(destination_image,OpaqueAlpha,exception);
+        if (i >= (ssize_t) GetPixelChannels(destination_image))
+          (void) SetPixelMetaChannels(destination_image,(size_t) (i-
+            GetPixelChannels(destination_image)+1),exception);
+        GetMagickToken(p,&p,token);
+        break;
       }
-    status=ChannelImage(channel_images,images,channel_op,p_channel,q_channel,
-      exception);
+      default:
+        break;
+    }
+    status=ChannelImage(destination_image,destination_channel,channel_op,
+      source_image,source_channel,ClampToQuantum(pixel),exception);
     if (status == MagickFalse)
       {
-        channel_images=DestroyImageList(channel_images);
+        destination_image=DestroyImageList(destination_image);
+        break;
+      }
+    channels++;
+    if (channel_op == ExchangeChannelOp)
+      {
+        status=ChannelImage(destination_image,source_channel,channel_op,
+          source_image,destination_channel,ClampToQuantum(pixel),exception);
+        if (status == MagickFalse)
+          {
+            destination_image=DestroyImageList(destination_image);
+            break;
+          }
+        channels++;
+      }
+    switch (channel_op)
+    {
+      case ExtractChannelOp:
+      {
+        channel_mask=(ChannelType) (channel_mask | (1 << destination_channel));
+        destination_channel=(PixelChannel) (destination_channel+1);
         break;
       }
+      default:
+        break;
+    }
+    status=SetImageProgress(source_image,ChannelFxImageTag,p-expression,
+      strlen(expression));
+    if (status == MagickFalse)
+      break;
+  }
+  SetPixelChannelMapMask(destination_image,channel_mask);
+  if ((channel_op == ExtractChannelOp) && (destination_channel == 1))
+    (void) SetImageColorspace(destination_image,GRAYColorspace,exception);
+  status=SetImageStorageClass(destination_image,DirectClass,exception);
+  if (status == MagickFalse)
+    {
+      destination_image=GetLastImageInList(destination_image);
+      return((Image *) NULL);
+    }
+  return(GetFirstImageInList(destination_image));
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%     C o m b i n e I m a g e s                                               %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  CombineImages() combines one or more images into a single image.  The
+%  grayscale value of the pixels of each image in the sequence is assigned in
+%  order to the specified channels of the combined image.   The typical
+%  ordering would be image 1 => Red, 2 => Green, 3 => Blue, etc.
+%
+%  The format of the CombineImages method is:
+%
+%      Image *CombineImages(const Image *images,const ColorspaceType colorspace,
+%        ExceptionInfo *exception)
+%
+%  A description of each parameter follows:
+%
+%    o images: the image sequence.
+%
+%    o colorspace: the image colorspace.
+%
+%    o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport Image *CombineImages(const Image *image,
+  const ColorspaceType colorspace,ExceptionInfo *exception)
+{
+#define CombineImageTag  "Combine/Image"
+
+  CacheView
+    *combine_view;
+
+  Image
+    *combine_image;
+
+  MagickBooleanType
+    status;
+
+  MagickOffsetType
+    progress;
+
+  ssize_t
+    y;
+
+  /*
+    Ensure the image are the same size.
+  */
+  assert(image != (const Image *) NULL);
+  assert(image->signature == MagickSignature);
+  if (image->debug != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+  assert(exception != (ExceptionInfo *) NULL);
+  assert(exception->signature == MagickSignature);
+  combine_image=CloneImage(image,0,0,MagickTrue,exception);
+  if (combine_image == (Image *) NULL)
+    return((Image *) NULL);
+  if (SetImageStorageClass(combine_image,DirectClass,exception) == MagickFalse)
+    {
+      combine_image=DestroyImage(combine_image);
+      return((Image *) NULL);
+    }
+  if (IsGrayColorspace(image->colorspace) != MagickFalse)
+    (void) SetImageColorspace(combine_image,RGBColorspace,exception);
+  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+    combine_image->matte=MagickTrue;
+  /*
+    Combine images.
+  */
+  status=MagickTrue;
+  progress=0;
+  combine_view=AcquireAuthenticCacheView(combine_image,exception);
+  for (y=0; y < (ssize_t) combine_image->rows; y++)
+  {
+    CacheView
+      *image_view;
+
+    const Image
+      *next;
+
+    Quantum
+      *pixels;
+
+    register const Quantum
+      *restrict p;
+
+    register Quantum
+      *restrict q;
+
+    register ssize_t
+      i;
+
+    if (status == MagickFalse)
+      continue;
+    pixels=GetCacheViewAuthenticPixels(combine_view,0,y,combine_image->columns,
+      1,exception);
+    if (pixels == (Quantum *) NULL)
+      {
+        status=MagickFalse;
+        continue;
+      }
+    next=image;
+    for (i=0; i < (ssize_t) GetPixelChannels(combine_image); i++)
+    {
+      PixelChannel
+        channel;
+
+      PixelTrait
+        traits;
+
+      register ssize_t
+        x;
+
+      if (next == (Image *) NULL)
+        continue;
+      channel=GetPixelChannelMapChannel(combine_image,i);
+      traits=GetPixelChannelMapTraits(combine_image,channel);
+      if (traits == UndefinedPixelTrait)
+        continue;
+      image_view=AcquireVirtualCacheView(next,exception);
+      p=GetCacheViewVirtualPixels(image_view,0,y,next->columns,1,exception);
+      if (p == (const Quantum *) NULL)
+        continue;
+      q=pixels;
+      for (x=0; x < (ssize_t) combine_image->columns; x++)
+      {
+        if (x < (ssize_t) next->columns)
+          {
+            q[i]=GetPixelGray(next,p);
+            p+=GetPixelChannels(next);
+          }
+        q+=GetPixelChannels(combine_image);
+      }
+      image_view=DestroyCacheView(image_view);
+      next=GetNextImageInList(next);
+    }
+    if (SyncCacheViewAuthenticPixels(combine_view,exception) == MagickFalse)
+      status=MagickFalse;
+    if (image->progress_monitor != (MagickProgressMonitor) NULL)
+      {
+        MagickBooleanType
+          proceed;
+
+        proceed=SetImageProgress(image,CombineImageTag,progress++,
+          combine_image->rows);
+        if (proceed == MagickFalse)
+          status=MagickFalse;
+      }
+  }
+  combine_view=DestroyCacheView(combine_view);
+  if (status == MagickFalse)
+    combine_image=DestroyImage(combine_image);
+  (void) TransformImageColorspace(combine_image,colorspace,exception);
+  return(combine_image);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%     S e p a r a t e I m a g e                                               %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  SeparateImage() separates a channel from the image and returns it as a
+%  grayscale image.
+%
+%  The format of the SeparateImage method is:
+%
+%      Image *SeparateImage(const Image *image,const ChannelType channel,
+%        ExceptionInfo *exception)
+%
+%  A description of each parameter follows:
+%
+%    o image: the image.
+%
+%    o channel: the image channel.
+%
+%    o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport Image *SeparateImage(const Image *image,
+  const ChannelType channel_type,ExceptionInfo *exception)
+{
+#define GetChannelBit(mask,bit)  (((size_t) (mask) >> (size_t) (bit)) & 0x01)
+#define SeparateImageTag  "Separate/Image"
+
+  CacheView
+    *image_view,
+    *separate_view;
+
+  Image
+    *separate_image;
+
+  MagickBooleanType
+    status;
+
+  MagickOffsetType
+    progress;
+
+  ssize_t
+    y;
+
+  /*
+    Initialize spread image attributes.
+  */
+  assert(image != (Image *) NULL);
+  assert(image->signature == MagickSignature);
+  if (image->debug != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+  assert(exception != (ExceptionInfo *) NULL);
+  assert(exception->signature == MagickSignature);
+  separate_image=CloneImage(image,image->columns,image->rows,MagickTrue,
+    exception);
+  if (separate_image == (Image *) NULL)
+    return((Image *) NULL);
+  if (SetImageStorageClass(separate_image,DirectClass,exception) == MagickFalse)
+    {
+      separate_image=DestroyImage(separate_image);
+      return((Image *) NULL);
+    }
+  separate_image->matte=MagickFalse;
+  (void) SetImageColorspace(separate_image,GRAYColorspace,exception);
+  /*
+    Separate image.
+  */
+  status=MagickTrue;
+  progress=0;
+  image_view=AcquireVirtualCacheView(image,exception);
+  separate_view=AcquireAuthenticCacheView(separate_image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+  #pragma omp parallel for schedule(static) shared(progress,status) \
+    dynamic_number_threads(image,image->columns,image->rows,1)
+#endif
+  for (y=0; y < (ssize_t) image->rows; y++)
+  {
+    register const Quantum
+      *restrict p;
+
+    register Quantum
+      *restrict q;
+
+    register ssize_t
+      x;
+
+    if (status == MagickFalse)
+      continue;
+    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
+    q=QueueCacheViewAuthenticPixels(separate_view,0,y,separate_image->columns,1,
+      exception);
+    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
+      {
+        status=MagickFalse;
+        continue;
+      }
+    for (x=0; x < (ssize_t) image->columns; x++)
+    {
+      register ssize_t
+        i;
+
+      if (GetPixelMask(image,p) != 0)
+        {
+          p+=GetPixelChannels(image);
+          q+=GetPixelChannels(separate_image);
+          continue;
+        }
+      SetPixelChannel(separate_image,GrayPixelChannel,0,q);
+      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+      {
+        double
+          pixel;
+
+        PixelChannel
+          channel;
+
+        PixelTrait
+          traits;
+
+        channel=GetPixelChannelMapChannel(image,i);
+        traits=GetPixelChannelMapTraits(image,channel);
+        if ((traits == UndefinedPixelTrait) ||
+            (GetChannelBit(channel_type,channel) == 0))
+          continue;
+        pixel=p[i];
+        if (IssRGBColorspace(image->colorspace) != MagickFalse)
+          pixel=InversesRGBCompandor(pixel);
+        SetPixelChannel(separate_image,GrayPixelChannel,ClampToQuantum(pixel),
+          q);
+      }
+      p+=GetPixelChannels(image);
+      q+=GetPixelChannels(separate_image);
+    }
+    if (SyncCacheViewAuthenticPixels(separate_view,exception) == MagickFalse)
+      status=MagickFalse;
+    if (image->progress_monitor != (MagickProgressMonitor) NULL)
+      {
+        MagickBooleanType
+          proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+        #pragma omp critical (MagickCore_SeparateImage)
+#endif
+        proceed=SetImageProgress(image,SeparateImageTag,progress++,image->rows);
+        if (proceed == MagickFalse)
+          status=MagickFalse;
+      }
+  }
+  separate_view=DestroyCacheView(separate_view);
+  image_view=DestroyCacheView(image_view);
+  return(separate_image);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%     S e p a r a t e I m a g e s                                             %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  SeparateImages() returns a separate grayscale image for each channel
+%  specified.
+%
+%  The format of the SeparateImages method is:
+%
+%      Image *SeparateImages(const Image *image,ExceptionInfo *exception)
+%
+%  A description of each parameter follows:
+%
+%    o image: the image.
+%
+%    o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport Image *SeparateImages(const Image *image,ExceptionInfo *exception)
+{
+  Image
+    *images,
+    *separate_image;
+
+  register ssize_t
+    i;
+
+  assert(image != (Image *) NULL);
+  assert(image->signature == MagickSignature);
+  if (image->debug != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+  images=NewImageList();
+  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+  {
+    PixelChannel
+      channel;
+
+    PixelTrait
+      traits;
+
+    channel=GetPixelChannelMapChannel(image,i);
+    traits=GetPixelChannelMapTraits(image,channel);
+    if ((traits == UndefinedPixelTrait) ||
+        ((traits & UpdatePixelTrait) == 0))
+      continue;
+    separate_image=SeparateImage(image,(ChannelType) (1 << channel),exception);
+    if (separate_image != (Image *) NULL)
+      AppendImageToList(&images,separate_image);
   }
-  return(channel_images);
+  if (images == (Image *) NULL)
+    images=SeparateImage(image,UndefinedChannel,exception);
+  return(images);
 }