packet_irq_handler(struct pcilynx *lynx)
{
	struct client *client;
	u32 tcode_mask, tcode, timestamp;
	size_t length;
	struct timespec64 ts64;

	/* FIXME: Also report rcv_speed. */

	length = __le32_to_cpu(lynx->rcv_pcl->pcl_status) & 0x00001fff;
	tcode  = __le32_to_cpu(lynx->rcv_buffer[1]) >> 4 & 0xf;

	ktime_get_real_ts64(&ts64);
	timestamp = ts64.tv_nsec / NSEC_PER_USEC;
	lynx->rcv_buffer[0] = (__force __le32)timestamp;

	if (length == PHY_PACKET_SIZE)
		tcode_mask = 1 << TCODE_PHY_PACKET;
	else
		tcode_mask = 1 << tcode;

	spin_lock(&lynx->client_list_lock);

	list_for_each_entry(client, &lynx->client_list, link)
		if (client->tcode_mask & tcode_mask)
			packet_buffer_put(&client->buffer,
					  lynx->rcv_buffer, length + 4);

	spin_unlock(&lynx->client_list_lock);
}

#define TCODE_PHY_PACKET		0x10

struct pcl {
	__le32 next;
	__le32 async_error_next;
	u32 user_data;
	__le32 pcl_status;
	__le32 remaining_transfer_count;
	__le32 next_data_buffer;
	struct {
		__le32 control;
		__le32 pointer;
	} buffer[13];
}

struct packet {
	unsigned int length;
	char data[];
}

struct packet_buffer {
	char *data;
	size_t capacity;
	long total_packet_count, lost_packet_count;
	atomic_t size;
	struct packet *head, *tail;
	wait_queue_head_t wait;
}

struct pcilynx {
	struct pci_dev *pci_device;
	__iomem char *registers;

	struct pcl *rcv_start_pcl, *rcv_pcl;
	__le32 *rcv_buffer;

	dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;

	spinlock_t client_list_lock;
	struct list_head client_list;

	struct miscdevice misc;
	struct list_head link;
	struct kref kref;
}

struct client {
	struct pcilynx *lynx;
	u32 tcode_mask;
	struct packet_buffer buffer;
	struct list_head link;
}

static const struct file_operations nosy_ops = {
	.owner =		THIS_MODULE,
	.read =			nosy_read,
	.unlocked_ioctl =	nosy_ioctl,
	.poll =			nosy_poll,
	.open =			nosy_open,
	.release =		nosy_release,
};

#define PHY_PACKET_SIZE 12 /* 1 payload, 1 inverse, 1 ack = 3 quadlets */

static struct pci_device_id pci_table[] = {
	{
		.vendor =    PCI_VENDOR_ID_TI,
		.device =    PCI_DEVICE_ID_TI_PCILYNX,
		.subvendor = PCI_ANY_ID,
		.subdevice = PCI_ANY_ID,
	},
	{ }	/* Terminating entry */
};

static struct pci_driver lynx_pci_driver = {
	.name =		driver_name,
	.id_table =	pci_table,
	.probe =	add_card,
	.remove =	remove_card,
};


remove_card(struct pci_dev *dev)
{
	struct pcilynx *lynx = pci_get_drvdata(dev);
	struct client *client;

	mutex_lock(&card_mutex);
	list_del_init(&lynx->link);
	misc_deregister(&lynx->misc);
	mutex_unlock(&card_mutex);

	reg_write(lynx, PCI_INT_ENABLE, 0);
	free_irq(lynx->pci_device->irq, lynx);

	spin_lock_irq(&lynx->client_list_lock);
	list_for_each_entry(client, &lynx->client_list, link)
		wake_up_interruptible(&client->buffer.wait);
	spin_unlock_irq(&lynx->client_list_lock);

	pci_free_consistent(lynx->pci_device, sizeof(struct pcl),
			    lynx->rcv_start_pcl, lynx->rcv_start_pcl_bus);
	pci_free_consistent(lynx->pci_device, sizeof(struct pcl),
			    lynx->rcv_pcl, lynx->rcv_pcl_bus);
	pci_free_consistent(lynx->pci_device, PAGE_SIZE,
			    lynx->rcv_buffer, lynx->rcv_buffer_bus);

	iounmap(lynx->registers);
	pci_disable_device(dev);
	lynx_put(lynx);
}

firewire: nosy: Fix a use-after-free bug in nosy_ioctl()

For each device, the nosy driver allocates a pcilynx structure.
A use-after-free might happen in the following scenario:

 1. Open nosy device for the first time and call ioctl with command
    NOSY_IOC_START, then a new client A will be malloced and added to
    doubly linked list.
 2. Open nosy device for the second time and call ioctl with command
    NOSY_IOC_START, then a new client B will be malloced and added to
    doubly linked list.
 3. Call ioctl with command NOSY_IOC_START for client A, then client A
    will be readded to the doubly linked list. Now the doubly linked
    list is messed up.
 4. Close the first nosy device and nosy_release will be called. In
    nosy_release, client A will be unlinked and freed.
 5. Close the second nosy device, and client A will be referenced,
    resulting in UAF.

The root cause of this bug is that the element in the doubly linked list
is reentered into the list.

Fix this bug by adding a check before inserting a client.  If a client
is already in the linked list, don't insert it.

The following KASAN report reveals it:

   BUG: KASAN: use-after-free in nosy_release+0x1ea/0x210
   Write of size 8 at addr ffff888102ad7360 by task poc
   CPU: 3 PID: 337 Comm: poc Not tainted 5.12.0-rc5+ #6
   Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.12.0-59-gc9ba5276e321-prebuilt.qemu.org 04/01/2014
   Call Trace:
     nosy_release+0x1ea/0x210
     __fput+0x1e2/0x840
     task_work_run+0xe8/0x180
     exit_to_user_mode_prepare+0x114/0x120
     syscall_exit_to_user_mode+0x1d/0x40
     entry_SYSCALL_64_after_hwframe+0x44/0xae

   Allocated by task 337:
     nosy_open+0x154/0x4d0
     misc_open+0x2ec/0x410
     chrdev_open+0x20d/0x5a0
     do_dentry_open+0x40f/0xe80
     path_openat+0x1cf9/0x37b0
     do_filp_open+0x16d/0x390
     do_sys_openat2+0x11d/0x360
     __x64_sys_open+0xfd/0x1a0
     do_syscall_64+0x33/0x40
     entry_SYSCALL_64_after_hwframe+0x44/0xae

   Freed by task 337:
     kfree+0x8f/0x210
     nosy_release+0x158/0x210
     __fput+0x1e2/0x840
     task_work_run+0xe8/0x180
     exit_to_user_mode_prepare+0x114/0x120
     syscall_exit_to_user_mode+0x1d/0x40
     entry_SYSCALL_64_after_hwframe+0x44/0xae

   The buggy address belongs to the object at ffff888102ad7300 which belongs to the cache kmalloc-128 of size 128
   The buggy address is located 96 bytes inside of 128-byte region [ffff888102ad7300, ffff888102ad7380)

[ Modified to use 'list_empty()' inside proper lock  - Linus ]

Link: https://lore.kernel.org/lkml/1617433116-5930-1-git-send-email-zheyuma97@gmail.com/
Reported-and-tested-by: 马哲宇 (Zheyu Ma) <zheyuma97@gmail.com>
Signed-off-by: Zheyu Ma <zheyuma97@gmail.com>
Cc: Greg Kroah-Hartman <greg@kroah.com>
Cc: Stefan Richter <stefanr@s5r6.in-berlin.de>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

struct pcl {
	__le32 next;
	__le32 async_error_next;
	u32 user_data;
	__le32 pcl_status;
	__le32 remaining_transfer_count;
	__le32 next_data_buffer;
	struct {
		__le32 control;
		__le32 pointer;
	} buffer[13];
}

struct packet {
	unsigned int length;
	char data[];
}

struct packet_buffer {
	char *data;
	size_t capacity;
	long total_packet_count, lost_packet_count;
	atomic_t size;
	struct packet *head, *tail;
	wait_queue_head_t wait;
}

struct pcilynx {
	struct pci_dev *pci_device;
	__iomem char *registers;

	struct pcl *rcv_start_pcl, *rcv_pcl;
	__le32 *rcv_buffer;

	dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;

	spinlock_t client_list_lock;
	struct list_head client_list;

	struct miscdevice misc;
	struct list_head link;
	struct kref kref;
}

struct client {
	struct pcilynx *lynx;
	u32 tcode_mask;
	struct packet_buffer buffer;
	struct list_head link;
}

static DEFINE_MUTEX(card_mutex);

static const struct file_operations nosy_ops = {
	.owner =		THIS_MODULE,
	.read =			nosy_read,
	.unlocked_ioctl =	nosy_ioctl,
	.poll =			nosy_poll,
	.open =			nosy_open,
	.release =		nosy_release,
};

static struct pci_device_id pci_table[] = {
	{
		.vendor =    PCI_VENDOR_ID_TI,
		.device =    PCI_DEVICE_ID_TI_PCILYNX,
		.subvendor = PCI_ANY_ID,
		.subdevice = PCI_ANY_ID,
	},
	{ }	/* Terminating entry */
};

static struct pci_driver lynx_pci_driver = {
	.name =		driver_name,
	.id_table =	pci_table,
	.probe =	add_card,
	.remove =	remove_card,
};


static int IntensityCompare(const void *x,const void *y)
{
  const MagickPixelPacket
    *color_1,
    *color_2;

  int
    intensity;

  color_1=(const MagickPixelPacket *) x;
  color_2=(const MagickPixelPacket *) y;
  intensity=(int) MagickPixelIntensity(color_2)-(int)
    MagickPixelIntensity(color_1);
  return(intensity);
}


MagickExport MagickBooleanType EvaluateImage(Image *image,
  const MagickEvaluateOperator op,const double value,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  status=EvaluateImageChannel(image,CompositeChannels,op,value,exception);
  return(status);
}


MagickExport MagickBooleanType GetImageKurtosis(const Image *image,
  double *kurtosis,double *skewness,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  status=GetImageChannelKurtosis(image,CompositeChannels,kurtosis,skewness,
    exception);
  return(status);
}


MagickExport MagickBooleanType GetImageChannelKurtosis(const Image *image,
  const ChannelType channel,double *kurtosis,double *skewness,
  ExceptionInfo *exception)
{
  double
    area,
    mean,
    standard_deviation,
    sum_squares,
    sum_cubes,
    sum_fourth_power;

  ssize_t
    y;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  *kurtosis=0.0;
  *skewness=0.0;
  area=0.0;
  mean=0.0;
  standard_deviation=0.0;
  sum_squares=0.0;
  sum_cubes=0.0;
  sum_fourth_power=0.0;
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const IndexPacket
      *magick_restrict indexes;

    const PixelPacket
      *magick_restrict p;

    ssize_t
      x;

    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=GetVirtualIndexQueue(image);
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if ((channel & RedChannel) != 0)
        {
          mean+=GetPixelRed(p);
          sum_squares+=(double) GetPixelRed(p)*GetPixelRed(p);
          sum_cubes+=(double) GetPixelRed(p)*GetPixelRed(p)*GetPixelRed(p);
          sum_fourth_power+=(double) GetPixelRed(p)*GetPixelRed(p)*
            GetPixelRed(p)*GetPixelRed(p);
          area++;
        }
      if ((channel & GreenChannel) != 0)
        {
          mean+=GetPixelGreen(p);
          sum_squares+=(double) GetPixelGreen(p)*GetPixelGreen(p);
          sum_cubes+=(double) GetPixelGreen(p)*GetPixelGreen(p)*
            GetPixelGreen(p);
          sum_fourth_power+=(double) GetPixelGreen(p)*GetPixelGreen(p)*
            GetPixelGreen(p)*GetPixelGreen(p);
          area++;
        }
      if ((channel & BlueChannel) != 0)
        {
          mean+=GetPixelBlue(p);
          sum_squares+=(double) GetPixelBlue(p)*GetPixelBlue(p);
          sum_cubes+=(double) GetPixelBlue(p)*GetPixelBlue(p)*GetPixelBlue(p);
          sum_fourth_power+=(double) GetPixelBlue(p)*GetPixelBlue(p)*
            GetPixelBlue(p)*GetPixelBlue(p);
          area++;
        }
      if ((channel & OpacityChannel) != 0)
        {
          mean+=GetPixelAlpha(p);
          sum_squares+=(double) GetPixelOpacity(p)*GetPixelAlpha(p);
          sum_cubes+=(double) GetPixelOpacity(p)*GetPixelAlpha(p)*
            GetPixelAlpha(p);
          sum_fourth_power+=(double) GetPixelAlpha(p)*GetPixelAlpha(p)*
            GetPixelAlpha(p)*GetPixelAlpha(p);
          area++;
        }
      if (((channel & IndexChannel) != 0) &&
          (image->colorspace == CMYKColorspace))
        {
          double
            index;

          index=(double) GetPixelIndex(indexes+x);
          mean+=index;
          sum_squares+=index*index;
          sum_cubes+=index*index*index;
          sum_fourth_power+=index*index*index*index;
          area++;
        }
      p++;
    }
  }
  if (y < (ssize_t) image->rows)
    return(MagickFalse);
  if (area != 0.0)
    {
      mean/=area;
      sum_squares/=area;
      sum_cubes/=area;
      sum_fourth_power/=area;
    }
  standard_deviation=sqrt(sum_squares-(mean*mean));
  if (standard_deviation != 0.0)
    {
      *kurtosis=sum_fourth_power-4.0*mean*sum_cubes+6.0*mean*mean*sum_squares-
        3.0*mean*mean*mean*mean;
      *kurtosis/=standard_deviation*standard_deviation*standard_deviation*
        standard_deviation;
      *kurtosis-=3.0;
      *skewness=sum_cubes-3.0*mean*sum_squares+2.0*mean*mean*mean;
      *skewness/=standard_deviation*standard_deviation*standard_deviation;
    }
  return(y == (ssize_t) image->rows ? MagickTrue : MagickFalse);
}

typedef struct _ListNode
{
  size_t
    next[9],
    count,
    signature;
} ListNode;

typedef struct _SkipList
{
  ssize_t
    level;

  ListNode
    *nodes;
} SkipList;

typedef struct _PixelList
{
  size_t
    length,
    seed,
    signature;

  SkipList
    lists[ListChannels];
} PixelList;


static inline void InsertPixelList(const Image *image,const PixelPacket *pixel,
  const IndexPacket *indexes,PixelList *pixel_list)
{
  size_t
    signature;

  unsigned short
    index;

  index=ScaleQuantumToShort(GetPixelRed(pixel));
  signature=pixel_list->lists[0].nodes[index].signature;
  if (signature == pixel_list->signature)
    pixel_list->lists[0].nodes[index].count++;
  else
    AddNodePixelList(pixel_list,0,index);
  index=ScaleQuantumToShort(GetPixelGreen(pixel));
  signature=pixel_list->lists[1].nodes[index].signature;
  if (signature == pixel_list->signature)
    pixel_list->lists[1].nodes[index].count++;
  else
    AddNodePixelList(pixel_list,1,index);
  index=ScaleQuantumToShort(GetPixelBlue(pixel));
  signature=pixel_list->lists[2].nodes[index].signature;
  if (signature == pixel_list->signature)
    pixel_list->lists[2].nodes[index].count++;
  else
    AddNodePixelList(pixel_list,2,index);
  index=ScaleQuantumToShort(GetPixelOpacity(pixel));
  signature=pixel_list->lists[3].nodes[index].signature;
  if (signature == pixel_list->signature)
    pixel_list->lists[3].nodes[index].count++;
  else
    AddNodePixelList(pixel_list,3,index);
  if (image->colorspace == CMYKColorspace)
    index=ScaleQuantumToShort(GetPixelIndex(indexes));
  signature=pixel_list->lists[4].nodes[index].signature;
  if (signature == pixel_list->signature)
    pixel_list->lists[4].nodes[index].count++;
  else
    AddNodePixelList(pixel_list,4,index);
}


MagickExport MagickBooleanType EvaluateImageChannel(Image *image,
  const ChannelType channel,const MagickEvaluateOperator op,const double value,
  ExceptionInfo *exception)
{
  CacheView
    *image_view;

  MagickBooleanType
    status;

  MagickOffsetType
    progress;

  RandomInfo
    **magick_restrict random_info;

  ssize_t
    y;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  unsigned long
    key;
#endif

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    {
      InheritException(exception,&image->exception);
      return(MagickFalse);
    }
  status=MagickTrue;
  progress=0;
  random_info=AcquireRandomInfoThreadSet();
  image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  key=GetRandomSecretKey(random_info[0]);
  #pragma omp parallel for schedule(static) shared(progress,status) \
    magick_number_threads(image,image,image->rows,key == ~0UL)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const int
      id = GetOpenMPThreadId();

    IndexPacket
      *magick_restrict indexes;

    PixelPacket
      *magick_restrict q;

    ssize_t
      x;

    if (status == MagickFalse)
      continue;
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (q == (PixelPacket *) NULL)
      {
        status=MagickFalse;
        continue;
      }
    indexes=GetCacheViewAuthenticIndexQueue(image_view);
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      MagickRealType
        result;

      if ((channel & RedChannel) != 0)
        {
          result=ApplyEvaluateOperator(random_info[id],GetPixelRed(q),op,value);
          if (op == MeanEvaluateOperator)
            result/=2.0;
          SetPixelRed(q,ClampToQuantum(result));
        }
      if ((channel & GreenChannel) != 0)
        {
          result=ApplyEvaluateOperator(random_info[id],GetPixelGreen(q),op,
            value);
          if (op == MeanEvaluateOperator)
            result/=2.0;
          SetPixelGreen(q,ClampToQuantum(result));
        }
      if ((channel & BlueChannel) != 0)
        {
          result=ApplyEvaluateOperator(random_info[id],GetPixelBlue(q),op,
            value);
          if (op == MeanEvaluateOperator)
            result/=2.0;
          SetPixelBlue(q,ClampToQuantum(result));
        }
      if ((channel & OpacityChannel) != 0)
        {
          if (image->matte == MagickFalse)
            {
              result=ApplyEvaluateOperator(random_info[id],GetPixelOpacity(q),
                op,value);
              if (op == MeanEvaluateOperator)
                result/=2.0;
              SetPixelOpacity(q,ClampToQuantum(result));
            }
          else
            {
              result=ApplyEvaluateOperator(random_info[id],GetPixelAlpha(q),
                op,value);
              if (op == MeanEvaluateOperator)
                result/=2.0;
              SetPixelAlpha(q,ClampToQuantum(result));
            }
        }
      if (((channel & IndexChannel) != 0) && (indexes != (IndexPacket *) NULL))
        {
          result=ApplyEvaluateOperator(random_info[id],GetPixelIndex(indexes+x),
            op,value);
          if (op == MeanEvaluateOperator)
            result/=2.0;
          SetPixelIndex(indexes+x,ClampToQuantum(result));
        }
      q++;
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
      {
        MagickBooleanType
          proceed;

        proceed=SetImageProgress(image,EvaluateImageTag,progress++,image->rows);
        if (proceed == MagickFalse)
          status=MagickFalse;
      }
  }
  image_view=DestroyCacheView(image_view);
  random_info=DestroyRandomInfoThreadSet(random_info);
  return(status);
}


MagickExport MagickBooleanType FunctionImage(Image *image,
  const MagickFunction function,const size_t number_parameters,
  const double *parameters,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  status=FunctionImageChannel(image,CompositeChannels,function,
    number_parameters,parameters,exception);
  return(status);
}


MagickExport MagickBooleanType GetImageMean(const Image *image,double *mean,
  double *standard_deviation,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  status=GetImageChannelMean(image,CompositeChannels,mean,standard_deviation,
    exception);
  return(status);
}


MagickExport Image *PolynomialImage(const Image *images,
  const size_t number_terms,const double *terms,ExceptionInfo *exception)
{
  Image
    *polynomial_image;

  polynomial_image=PolynomialImageChannel(images,DefaultChannels,number_terms,
    terms,exception);
  return(polynomial_image);
}


static PixelList *DestroyPixelList(PixelList *pixel_list)
{
  ssize_t
    i;

  if (pixel_list == (PixelList *) NULL)
    return((PixelList *) NULL);
  for (i=0; i < ListChannels; i++)
    if (pixel_list->lists[i].nodes != (ListNode *) NULL)
      pixel_list->lists[i].nodes=(ListNode *) RelinquishAlignedMemory(
        pixel_list->lists[i].nodes);
  pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
  return(pixel_list);
}

#define ListChannels  5

typedef struct _ListNode
{
  size_t
    next[9],
    count,
    signature;
} ListNode;

typedef struct _SkipList
{
  ssize_t
    level;

  ListNode
    *nodes;
} SkipList;

typedef struct _PixelList
{
  size_t
    length,
    seed,
    signature;

  SkipList
    lists[ListChannels];
} PixelList;


static void GetMaximumPixelList(PixelList *pixel_list,MagickPixelPacket *pixel)
{
  SkipList
    *list;

  ssize_t
    channel;

  size_t
    color,
    maximum;

  ssize_t
    count;

  unsigned short
    channels[ListChannels];

  /*
    Find the maximum value for each of the color.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    color=65536L;
    count=0;
    maximum=list->nodes[color].next[0];
    do
    {
      color=list->nodes[color].next[0];
      if (color > maximum)
        maximum=color;
      count+=list->nodes[color].count;
    } while (count < (ssize_t) pixel_list->length);
    channels[channel]=(unsigned short) maximum;
  }
  pixel->red=(MagickRealType) ScaleShortToQuantum(channels[0]);
  pixel->green=(MagickRealType) ScaleShortToQuantum(channels[1]);
  pixel->blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
  pixel->opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
  pixel->index=(MagickRealType) ScaleShortToQuantum(channels[4]);
}


static void GetStandardDeviationPixelList(PixelList *pixel_list,
  MagickPixelPacket *pixel)
{
  MagickRealType
    sum,
    sum_squared;

  SkipList
    *list;

  ssize_t
    channel;

  size_t
    color;

  ssize_t
    count;

  unsigned short
    channels[ListChannels];

  /*
    Find the standard-deviation value for each of the color.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    color=65536L;
    count=0;
    sum=0.0;
    sum_squared=0.0;
    do
    {
      ssize_t
        i;

      color=list->nodes[color].next[0];
      sum+=(MagickRealType) list->nodes[color].count*color;
      for (i=0; i < (ssize_t) list->nodes[color].count; i++)
        sum_squared+=((MagickRealType) color)*((MagickRealType) color);
      count+=list->nodes[color].count;
    } while (count < (ssize_t) pixel_list->length);
    sum/=pixel_list->length;
    sum_squared/=pixel_list->length;
    channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
  }
  pixel->red=(MagickRealType) ScaleShortToQuantum(channels[0]);
  pixel->green=(MagickRealType) ScaleShortToQuantum(channels[1]);
  pixel->blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
  pixel->opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
  pixel->index=(MagickRealType) ScaleShortToQuantum(channels[4]);
}


static void GetNonpeakPixelList(PixelList *pixel_list,MagickPixelPacket *pixel)
{
  SkipList
    *list;

  ssize_t
    channel;

  size_t
    color,
    next,
    previous;

  ssize_t
    count;

  unsigned short
    channels[5];

  /*
    Finds the non peak value for each of the colors.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    color=65536L;
    next=list->nodes[color].next[0];
    count=0;
    do
    {
      previous=color;
      color=next;
      next=list->nodes[color].next[0];
      count+=list->nodes[color].count;
    } while (count <= (ssize_t) (pixel_list->length >> 1));
    if ((previous == 65536UL) && (next != 65536UL))
      color=next;
    else
      if ((previous != 65536UL) && (next == 65536UL))
        color=previous;
    channels[channel]=(unsigned short) color;
  }
  pixel->red=(MagickRealType) ScaleShortToQuantum(channels[0]);
  pixel->green=(MagickRealType) ScaleShortToQuantum(channels[1]);
  pixel->blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
  pixel->opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
  pixel->index=(MagickRealType) ScaleShortToQuantum(channels[4]);
}


MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
  const size_t width,const size_t height,ExceptionInfo *exception)
{
  Image
    *statistic_image;

  statistic_image=StatisticImageChannel(image,DefaultChannels,type,width,
    height,exception);
  return(statistic_image);
}


MagickExport MagickBooleanType GetImageChannelEntropy(const Image *image,
  const ChannelType channel,double *entropy,ExceptionInfo *exception)
{
  ChannelStatistics
    *channel_statistics;

  size_t
    channels;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  channel_statistics=GetImageChannelStatistics(image,exception);
  if (channel_statistics == (ChannelStatistics *) NULL)
    return(MagickFalse);
  channels=0;
  channel_statistics[CompositeChannels].entropy=0.0;
  if ((channel & RedChannel) != 0)
    {
      channel_statistics[CompositeChannels].entropy+=
        channel_statistics[RedChannel].entropy;
      channels++;
    }
  if ((channel & GreenChannel) != 0)
    {
      channel_statistics[CompositeChannels].entropy+=
        channel_statistics[GreenChannel].entropy;
      channels++;
    }
  if ((channel & BlueChannel) != 0)
    {
      channel_statistics[CompositeChannels].entropy+=
        channel_statistics[BlueChannel].entropy;
      channels++;
    }
  if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
    {
      channel_statistics[CompositeChannels].entropy+=
        channel_statistics[OpacityChannel].entropy;
      channels++;
    }
  if (((channel & IndexChannel) != 0) &&
      (image->colorspace == CMYKColorspace))
    {
      channel_statistics[CompositeChannels].entropy+=
        channel_statistics[BlackChannel].entropy;
      channels++;
    }
  channel_statistics[CompositeChannels].entropy/=channels;
  *entropy=channel_statistics[CompositeChannels].entropy;
  channel_statistics=(ChannelStatistics *) RelinquishMagickMemory(
    channel_statistics);
  return(MagickTrue);
}


MagickExport MagickBooleanType GetImageEntropy(const Image *image,
  double *entropy,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  status=GetImageChannelEntropy(image,CompositeChannels,entropy,exception);
  return(status);
}


static MagickPixelPacket **DestroyPixelThreadSet(const Image *images,
  MagickPixelPacket **pixels)
{
  ssize_t
    i;

  size_t
    rows;

  assert(pixels != (MagickPixelPacket **) NULL);
  rows=MagickMax(GetImageListLength(images),
    (size_t) GetMagickResourceLimit(ThreadResource));
  for (i=0; i < (ssize_t) rows; i++)
    if (pixels[i] != (MagickPixelPacket *) NULL)
      pixels[i]=(MagickPixelPacket *) RelinquishMagickMemory(pixels[i]);
  pixels=(MagickPixelPacket **) RelinquishMagickMemory(pixels);
  return(pixels);
}


MagickExport MagickBooleanType FunctionImageChannel(Image *image,
  const ChannelType channel,const MagickFunction function,
  const size_t number_parameters,const double *parameters,
  ExceptionInfo *exception)
{
#define FunctionImageTag  "Function/Image "

  CacheView
    *image_view;

  MagickBooleanType
    status;

  MagickOffsetType
    progress;

  ssize_t
    y;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    {
      InheritException(exception,&image->exception);
      return(MagickFalse);
    }
#if defined(MAGICKCORE_OPENCL_SUPPORT)
  status=AccelerateFunctionImage(image,channel,function,number_parameters,
    parameters,exception);
  if (status != MagickFalse)
    return(status);
#endif
  status=MagickTrue;
  progress=0;
  image_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static) shared(progress,status) \
    magick_number_threads(image,image,image->rows,1)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    IndexPacket
      *magick_restrict indexes;

    ssize_t
      x;

    PixelPacket
      *magick_restrict q;

    if (status == MagickFalse)
      continue;
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
    if (q == (PixelPacket *) NULL)
      {
        status=MagickFalse;
        continue;
      }
    indexes=GetCacheViewAuthenticIndexQueue(image_view);
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      if ((channel & RedChannel) != 0)
        SetPixelRed(q,ApplyFunction(GetPixelRed(q),function,
          number_parameters,parameters,exception));
      if ((channel & GreenChannel) != 0)
        SetPixelGreen(q,ApplyFunction(GetPixelGreen(q),function,
          number_parameters,parameters,exception));
      if ((channel & BlueChannel) != 0)
        SetPixelBlue(q,ApplyFunction(GetPixelBlue(q),function,
          number_parameters,parameters,exception));
      if ((channel & OpacityChannel) != 0)
        {
          if (image->matte == MagickFalse)
            SetPixelOpacity(q,ApplyFunction(GetPixelOpacity(q),function,
              number_parameters,parameters,exception));
          else
            SetPixelAlpha(q,ApplyFunction((Quantum) GetPixelAlpha(q),function,
              number_parameters,parameters,exception));
        }
      if (((channel & IndexChannel) != 0) && (indexes != (IndexPacket *) NULL))
        SetPixelIndex(indexes+x,ApplyFunction(GetPixelIndex(indexes+x),function,
          number_parameters,parameters,exception));
      q++;
    }
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
      status=MagickFalse;
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
      {
        MagickBooleanType
          proceed;

        proceed=SetImageProgress(image,FunctionImageTag,progress++,image->rows);
        if (proceed == MagickFalse)
          status=MagickFalse;
      }
  }
  image_view=DestroyCacheView(image_view);
  return(status);
}


static inline double EvaluateMax(const double x,const double y)
{
  if (x > y)
    return(x);
  return(y);
}


MagickExport MagickBooleanType GetImageChannelExtrema(const Image *image,
  const ChannelType channel,size_t *minima,size_t *maxima,
  ExceptionInfo *exception)
{
  double
    max,
    min;

  MagickBooleanType
    status;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  status=GetImageChannelRange(image,channel,&min,&max,exception);
  *minima=(size_t) ceil(min-0.5);
  *maxima=(size_t) floor(max+0.5);
  return(status);
}


static PixelList **AcquirePixelListThreadSet(const size_t width,
  const size_t height)
{
  PixelList
    **pixel_list;

  ssize_t
    i;

  size_t
    number_threads;

  number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
  pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
    sizeof(*pixel_list));
  if (pixel_list == (PixelList **) NULL)
    return((PixelList **) NULL);
  (void) memset(pixel_list,0,number_threads*sizeof(*pixel_list));
  for (i=0; i < (ssize_t) number_threads; i++)
  {
    pixel_list[i]=AcquirePixelList(width,height);
    if (pixel_list[i] == (PixelList *) NULL)
      return(DestroyPixelListThreadSet(pixel_list));
  }
  return(pixel_list);
}


MagickExport Image *PolynomialImageChannel(const Image *images,
  const ChannelType channel,const size_t number_terms,const double *terms,
  ExceptionInfo *exception)
{
#define PolynomialImageTag  "Polynomial/Image"

  CacheView
    *polynomial_view;

  Image
    *image;

  MagickBooleanType
    status;

  MagickOffsetType
    progress;

  MagickPixelPacket
    **magick_restrict polynomial_pixels,
    zero;

  ssize_t
    y;

  assert(images != (Image *) NULL);
  assert(images->signature == MagickCoreSignature);
  if (images->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  image=AcquireImageCanvas(images,exception);
  if (image == (Image *) NULL)
    return((Image *) NULL);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    {
      InheritException(exception,&image->exception);
      image=DestroyImage(image);
      return((Image *) NULL);
    }
  polynomial_pixels=AcquirePixelThreadSet(images);
  if (polynomial_pixels == (MagickPixelPacket **) NULL)
    {
      image=DestroyImage(image);
      (void) ThrowMagickException(exception,GetMagickModule(),
        ResourceLimitError,"MemoryAllocationFailed","`%s'",images->filename);
      return((Image *) NULL);
    }
  /*
    Polynomial image pixels.
  */
  status=MagickTrue;
  progress=0;
  GetMagickPixelPacket(images,&zero);
  polynomial_view=AcquireAuthenticCacheView(image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
  #pragma omp parallel for schedule(static) shared(progress,status) \
    magick_number_threads(image,image,image->rows,1)
#endif
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    CacheView
      *image_view;

    const Image
      *next;

    const int
      id = GetOpenMPThreadId();

    IndexPacket
      *magick_restrict polynomial_indexes;

    MagickPixelPacket
      *polynomial_pixel;

    PixelPacket
      *magick_restrict q;

    ssize_t
      i,
      x;

    size_t
      number_images;

    if (status == MagickFalse)
      continue;
    q=QueueCacheViewAuthenticPixels(polynomial_view,0,y,image->columns,1,
      exception);
    if (q == (PixelPacket *) NULL)
      {
        status=MagickFalse;
        continue;
      }
    polynomial_indexes=GetCacheViewAuthenticIndexQueue(polynomial_view);
    polynomial_pixel=polynomial_pixels[id];
    for (x=0; x < (ssize_t) image->columns; x++)
      polynomial_pixel[x]=zero;
    next=images;
    number_images=GetImageListLength(images);
    for (i=0; i < (ssize_t) number_images; i++)
    {
      const IndexPacket
        *indexes;

      const PixelPacket
        *p;

      if (i >= (ssize_t) number_terms)
        break;
      image_view=AcquireVirtualCacheView(next,exception);
      p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
      if (p == (const PixelPacket *) NULL)
        {
          image_view=DestroyCacheView(image_view);
          break;
        }
      indexes=GetCacheViewVirtualIndexQueue(image_view);
      for (x=0; x < (ssize_t) image->columns; x++)
      {
        double
          coefficient,
          degree;

        coefficient=terms[i << 1];
        degree=terms[(i << 1)+1];
        if ((channel & RedChannel) != 0)
          polynomial_pixel[x].red+=coefficient*pow(QuantumScale*p->red,degree);
        if ((channel & GreenChannel) != 0)
          polynomial_pixel[x].green+=coefficient*pow(QuantumScale*p->green,
            degree);
        if ((channel & BlueChannel) != 0)
          polynomial_pixel[x].blue+=coefficient*pow(QuantumScale*p->blue,
            degree);
        if ((channel & OpacityChannel) != 0)
          polynomial_pixel[x].opacity+=coefficient*pow(QuantumScale*
            (QuantumRange-p->opacity),degree);
        if (((channel & IndexChannel) != 0) &&
            (image->colorspace == CMYKColorspace))
          polynomial_pixel[x].index+=coefficient*pow(QuantumScale*indexes[x],
            degree);
        p++;
      }
      image_view=DestroyCacheView(image_view);
      next=GetNextImageInList(next);
    }
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      SetPixelRed(q,ClampToQuantum(QuantumRange*polynomial_pixel[x].red));
      SetPixelGreen(q,ClampToQuantum(QuantumRange*polynomial_pixel[x].green));
      SetPixelBlue(q,ClampToQuantum(QuantumRange*polynomial_pixel[x].blue));
      if (image->matte == MagickFalse)
        SetPixelOpacity(q,ClampToQuantum(QuantumRange-QuantumRange*
          polynomial_pixel[x].opacity));
      else
        SetPixelAlpha(q,ClampToQuantum(QuantumRange-QuantumRange*
          polynomial_pixel[x].opacity));
      if (image->colorspace == CMYKColorspace)
        SetPixelIndex(polynomial_indexes+x,ClampToQuantum(QuantumRange*
          polynomial_pixel[x].index));
      q++;
    }
    if (SyncCacheViewAuthenticPixels(polynomial_view,exception) == MagickFalse)
      status=MagickFalse;
    if (images->progress_monitor != (MagickProgressMonitor) NULL)
      {
        MagickBooleanType
          proceed;

        proceed=SetImageProgress(images,PolynomialImageTag,progress++,
          image->rows);
        if (proceed == MagickFalse)
          status=MagickFalse;
      }
  }
  polynomial_view=DestroyCacheView(polynomial_view);
  polynomial_pixels=DestroyPixelThreadSet(images,polynomial_pixels);
  if (status == MagickFalse)
    image=DestroyImage(image);
  return(image);
}


static MagickPixelPacket **AcquirePixelThreadSet(const Image *images)
{
  const Image
    *next;

  MagickPixelPacket
    **pixels;

  ssize_t
    i,
    j;

  size_t
    columns,
    rows;

  rows=MagickMax(GetImageListLength(images),
    (size_t) GetMagickResourceLimit(ThreadResource));
  pixels=(MagickPixelPacket **) AcquireQuantumMemory(rows,sizeof(*pixels));
  if (pixels == (MagickPixelPacket **) NULL)
    return((MagickPixelPacket **) NULL);
  (void) memset(pixels,0,rows*sizeof(*pixels));
  columns=GetImageListLength(images);
  for (next=images; next != (Image *) NULL; next=next->next)
    columns=MagickMax(next->columns,columns);
  for (i=0; i < (ssize_t) rows; i++)
  {
    pixels[i]=(MagickPixelPacket *) AcquireQuantumMemory(columns,
      sizeof(**pixels));
    if (pixels[i] == (MagickPixelPacket *) NULL)
      return(DestroyPixelThreadSet(images,pixels));
    for (j=0; j < (ssize_t) columns; j++)
      GetMagickPixelPacket(images,&pixels[i][j]);
  }
  return(pixels);
}


static Quantum ApplyFunction(Quantum pixel,const MagickFunction function,
  const size_t number_parameters,const double *parameters,
  ExceptionInfo *exception)
{
  MagickRealType
    result;

  ssize_t
    i;

  (void) exception;
  result=0.0;
  switch (function)
  {
    case PolynomialFunction:
    {
      /*
       * Polynomial
       * Parameters:   polynomial constants,  highest to lowest order
       *   For example:      c0*x^3 + c1*x^2 + c2*x  + c3
       */
      result=0.0;
      for (i=0; i < (ssize_t) number_parameters; i++)
        result=result*QuantumScale*pixel + parameters[i];
      result*=QuantumRange;
      break;
    }
    case SinusoidFunction:
    {
      /* Sinusoid Function
       * Parameters:   Freq, Phase, Ampl, bias
       */
      double  freq,phase,ampl,bias;
      freq  = ( number_parameters >= 1 ) ? parameters[0] : 1.0;
      phase = ( number_parameters >= 2 ) ? parameters[1] : 0.0;
      ampl  = ( number_parameters >= 3 ) ? parameters[2] : 0.5;
      bias  = ( number_parameters >= 4 ) ? parameters[3] : 0.5;
      result=(MagickRealType) (QuantumRange*(ampl*sin((double) (2.0*MagickPI*
        (freq*QuantumScale*pixel + phase/360.0) )) + bias ) );
      break;
    }
    case ArcsinFunction:
    {
      double
        bias,
        center,
        range,
        width;

      /* Arcsin Function  (peged at range limits for invalid results)
       * Parameters:   Width, Center, Range, Bias
       */
      width=(number_parameters >= 1) ? parameters[0] : 1.0;
      center=(number_parameters >= 2) ? parameters[1] : 0.5;
      range=(number_parameters >= 3) ? parameters[2] : 1.0;
      bias=(number_parameters >= 4) ? parameters[3] : 0.5;
      result=2.0*PerceptibleReciprocal(width)*(QuantumScale*pixel-center);
      if (result <= -1.0)
        result=bias-range/2.0;
      else
        if (result >= 1.0)
          result=bias+range/2.0;
        else
          result=(MagickRealType) (range/MagickPI*asin((double) result)+bias);
      result*=QuantumRange;
      break;
    }
    case ArctanFunction:
    {
      /* Arctan Function
       * Parameters:   Slope, Center, Range, Bias
       */
      double  slope,range,center,bias;
      slope  = ( number_parameters >= 1 ) ? parameters[0] : 1.0;
      center = ( number_parameters >= 2 ) ? parameters[1] : 0.5;
      range  = ( number_parameters >= 3 ) ? parameters[2] : 1.0;
      bias   = ( number_parameters >= 4 ) ? parameters[3] : 0.5;
      result=(MagickRealType) (MagickPI*slope*(QuantumScale*pixel-center));
      result=(MagickRealType) (QuantumRange*(range/MagickPI*atan((double)
                  result) + bias ) );
      break;
    }
    case UndefinedFunction:
      break;
  }
  return(ClampToQuantum(result));
}


static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
{
  ssize_t
    i;

  assert(pixel_list != (PixelList **) NULL);
  for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
    if (pixel_list[i] != (PixelList *) NULL)
      pixel_list[i]=DestroyPixelList(pixel_list[i]);
  pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
  return(pixel_list);
}

typedef struct _SkipList
{
  ssize_t
    level;

  ListNode
    *nodes;
} SkipList;

typedef struct _PixelList
{
  size_t
    length,
    seed,
    signature;

  SkipList
    lists[ListChannels];
} PixelList;


static void ResetPixelList(PixelList *pixel_list)
{
  int
    level;

  ListNode
    *root;

  SkipList
    *list;

  ssize_t
    channel;

  /*
    Reset the skip-list.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    root=list->nodes+65536UL;
    list->level=0;
    for (level=0; level < 9; level++)
      root->next[level]=65536UL;
  }
  pixel_list->seed=pixel_list->signature++;
}


static void GetModePixelList(PixelList *pixel_list,MagickPixelPacket *pixel)
{
  SkipList
    *list;

  ssize_t
    channel;

  size_t
    color,
    max_count,
    mode;

  ssize_t
    count;

  unsigned short
    channels[5];

  /*
    Make each pixel the 'predominant color' of the specified neighborhood.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    color=65536L;
    mode=color;
    max_count=list->nodes[mode].count;
    count=0;
    do
    {
      color=list->nodes[color].next[0];
      if (list->nodes[color].count > max_count)
        {
          mode=color;
          max_count=list->nodes[mode].count;
        }
      count+=list->nodes[color].count;
    } while (count < (ssize_t) pixel_list->length);
    channels[channel]=(unsigned short) mode;
  }
  pixel->red=(MagickRealType) ScaleShortToQuantum(channels[0]);
  pixel->green=(MagickRealType) ScaleShortToQuantum(channels[1]);
  pixel->blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
  pixel->opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
  pixel->index=(MagickRealType) ScaleShortToQuantum(channels[4]);
}


MagickExport Image *EvaluateImages(const Image *images,
  const MagickEvaluateOperator op,ExceptionInfo *exception)
{
#define EvaluateImageTag  "Evaluate/Image"

  CacheView
    *evaluate_view;

  Image
    *image;

  MagickBooleanType
    status;

  MagickOffsetType
    progress;

  MagickPixelPacket
    **magick_restrict evaluate_pixels,
    zero;

  RandomInfo
    **magick_restrict random_info;

  size_t
    number_images;

  ssize_t
    y;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
  unsigned long
    key;
#endif

  assert(images != (Image *) NULL);
  assert(images->signature == MagickCoreSignature);
  if (images->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  image=AcquireImageCanvas(images,exception);
  if (image == (Image *) NULL)
    return((Image *) NULL);
  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
    {
      InheritException(exception,&image->exception);
      image=DestroyImage(image);
      return((Image *) NULL);
    }
  evaluate_pixels=AcquirePixelThreadSet(images);
  if (evaluate_pixels == (MagickPixelPacket **) NULL)
    {
      image=DestroyImage(image);
      (void) ThrowMagickException(exception,GetMagickModule(),
        ResourceLimitError,"MemoryAllocationFailed","`%s'",images->filename);
      return((Image *) NULL);
    }
  /*
    Evaluate image pixels.
  */
  status=MagickTrue;
  progress=0;
  number_images=GetImageListLength(images);
  GetMagickPixelPacket(images,&zero);
  random_info=AcquireRandomInfoThreadSet();
  evaluate_view=AcquireAuthenticCacheView(image,exception);
  if (op == MedianEvaluateOperator)
    {
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      key=GetRandomSecretKey(random_info[0]);
      #pragma omp parallel for schedule(static) shared(progress,status) \
        magick_number_threads(image,images,image->rows,key == ~0UL)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        CacheView
          *image_view;

        const Image
          *next;

        const int
          id = GetOpenMPThreadId();

        IndexPacket
          *magick_restrict evaluate_indexes;

        MagickPixelPacket
          *evaluate_pixel;

        PixelPacket
          *magick_restrict q;

        ssize_t
          x;

        if (status == MagickFalse)
          continue;
        q=QueueCacheViewAuthenticPixels(evaluate_view,0,y,image->columns,1,
          exception);
        if (q == (PixelPacket *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        evaluate_indexes=GetCacheViewAuthenticIndexQueue(evaluate_view);
        evaluate_pixel=evaluate_pixels[id];
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          ssize_t
            i;

          for (i=0; i < (ssize_t) number_images; i++)
            evaluate_pixel[i]=zero;
          next=images;
          for (i=0; i < (ssize_t) number_images; i++)
          {
            const IndexPacket
              *indexes;

            const PixelPacket
              *p;

            image_view=AcquireVirtualCacheView(next,exception);
            p=GetCacheViewVirtualPixels(image_view,x,y,1,1,exception);
            if (p == (const PixelPacket *) NULL)
              {
                image_view=DestroyCacheView(image_view);
                break;
              }
            indexes=GetCacheViewVirtualIndexQueue(image_view);
            evaluate_pixel[i].red=ApplyEvaluateOperator(random_info[id],
              GetPixelRed(p),op,evaluate_pixel[i].red);
            evaluate_pixel[i].green=ApplyEvaluateOperator(random_info[id],
              GetPixelGreen(p),op,evaluate_pixel[i].green);
            evaluate_pixel[i].blue=ApplyEvaluateOperator(random_info[id],
              GetPixelBlue(p),op,evaluate_pixel[i].blue);
            evaluate_pixel[i].opacity=ApplyEvaluateOperator(random_info[id],
              GetPixelAlpha(p),op,evaluate_pixel[i].opacity);
            if (image->colorspace == CMYKColorspace)
              evaluate_pixel[i].index=ApplyEvaluateOperator(random_info[id],
                *indexes,op,evaluate_pixel[i].index);
            image_view=DestroyCacheView(image_view);
            next=GetNextImageInList(next);
          }
          qsort((void *) evaluate_pixel,number_images,sizeof(*evaluate_pixel),
            IntensityCompare);
          SetPixelRed(q,ClampToQuantum(evaluate_pixel[i/2].red));
          SetPixelGreen(q,ClampToQuantum(evaluate_pixel[i/2].green));
          SetPixelBlue(q,ClampToQuantum(evaluate_pixel[i/2].blue));
          SetPixelAlpha(q,ClampToQuantum(evaluate_pixel[i/2].opacity));
          if (image->colorspace == CMYKColorspace)
            SetPixelIndex(evaluate_indexes+i,ClampToQuantum(
              evaluate_pixel[i/2].index));
          q++;
        }
        if (SyncCacheViewAuthenticPixels(evaluate_view,exception) == MagickFalse)
          status=MagickFalse;
        if (images->progress_monitor != (MagickProgressMonitor) NULL)
          {
            MagickBooleanType
              proceed;

#if defined(MAGICKCORE_OPENMP_SUPPORT)
            #pragma omp atomic
#endif
            progress++;
            proceed=SetImageProgress(images,EvaluateImageTag,progress,
              image->rows);
            if (proceed == MagickFalse)
              status=MagickFalse;
          }
      }
    }
  else
    {
#if defined(MAGICKCORE_OPENMP_SUPPORT)
      key=GetRandomSecretKey(random_info[0]);
      #pragma omp parallel for schedule(static) shared(progress,status) \
        magick_number_threads(image,images,image->rows,key == ~0UL)
#endif
      for (y=0; y < (ssize_t) image->rows; y++)
      {
        CacheView
          *image_view;

        const Image
          *next;

        const int
          id = GetOpenMPThreadId();

        IndexPacket
          *magick_restrict evaluate_indexes;

        ssize_t
          i,
          x;

        MagickPixelPacket
          *evaluate_pixel;

        PixelPacket
          *magick_restrict q;

        if (status == MagickFalse)
          continue;
        q=QueueCacheViewAuthenticPixels(evaluate_view,0,y,image->columns,1,
          exception);
        if (q == (PixelPacket *) NULL)
          {
            status=MagickFalse;
            continue;
          }
        evaluate_indexes=GetCacheViewAuthenticIndexQueue(evaluate_view);
        evaluate_pixel=evaluate_pixels[id];
        for (x=0; x < (ssize_t) image->columns; x++)
          evaluate_pixel[x]=zero;
        next=images;
        for (i=0; i < (ssize_t) number_images; i++)
        {
          const IndexPacket
            *indexes;

          const PixelPacket
            *p;

          image_view=AcquireVirtualCacheView(next,exception);
          p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,
            exception);
          if (p == (const PixelPacket *) NULL)
            {
              image_view=DestroyCacheView(image_view);
              break;
            }
          indexes=GetCacheViewVirtualIndexQueue(image_view);
          for (x=0; x < (ssize_t) image->columns; x++)
          {
            evaluate_pixel[x].red=ApplyEvaluateOperator(random_info[id],
              GetPixelRed(p),i == 0 ? AddEvaluateOperator : op,
              evaluate_pixel[x].red);
            evaluate_pixel[x].green=ApplyEvaluateOperator(random_info[id],
              GetPixelGreen(p),i == 0 ? AddEvaluateOperator : op,
              evaluate_pixel[x].green);
            evaluate_pixel[x].blue=ApplyEvaluateOperator(random_info[id],
              GetPixelBlue(p),i == 0 ? AddEvaluateOperator : op,
              evaluate_pixel[x].blue);
            evaluate_pixel[x].opacity=ApplyEvaluateOperator(random_info[id],
              GetPixelAlpha(p),i == 0 ? AddEvaluateOperator : op,
              evaluate_pixel[x].opacity);
            if (image->colorspace == CMYKColorspace)
              evaluate_pixel[x].index=ApplyEvaluateOperator(random_info[id],
                GetPixelIndex(indexes+x),i == 0 ? AddEvaluateOperator : op,
                evaluate_pixel[x].index);
            p++;
          }
          image_view=DestroyCacheView(image_view);
          next=GetNextImageInList(next);
        }
        if (op == MeanEvaluateOperator)
          for (x=0; x < (ssize_t) image->columns; x++)
          {
            evaluate_pixel[x].red/=number_images;
            evaluate_pixel[x].green/=number_images;
            evaluate_pixel[x].blue/=number_images;
            evaluate_pixel[x].opacity/=number_images;
            evaluate_pixel[x].index/=number_images;
          }
        if (op == RootMeanSquareEvaluateOperator)
          for (x=0; x < (ssize_t) image->columns; x++)
          {
            evaluate_pixel[x].red=sqrt((double) evaluate_pixel[x].red/
              number_images);
            evaluate_pixel[x].green=sqrt((double) evaluate_pixel[x].green/
              number_images);
            evaluate_pixel[x].blue=sqrt((double) evaluate_pixel[x].blue/
              number_images);
            evaluate_pixel[x].opacity=sqrt((double) evaluate_pixel[x].opacity/
              number_images);
            evaluate_pixel[x].index=sqrt((double) evaluate_pixel[x].index/
              number_images);
          }
        if (op == MultiplyEvaluateOperator)
          for (x=0; x < (ssize_t) image->columns; x++)
          {
            ssize_t
              j;

            for (j=0; j < (ssize_t) (number_images-1); j++)
            {
              evaluate_pixel[x].red*=(MagickRealType) QuantumScale;
              evaluate_pixel[x].green*=(MagickRealType) QuantumScale;
              evaluate_pixel[x].blue*=(MagickRealType) QuantumScale;
              evaluate_pixel[x].opacity*=(MagickRealType) QuantumScale;
              evaluate_pixel[x].index*=(MagickRealType) QuantumScale;
            }
          }
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          SetPixelRed(q,ClampToQuantum(evaluate_pixel[x].red));
          SetPixelGreen(q,ClampToQuantum(evaluate_pixel[x].green));
          SetPixelBlue(q,ClampToQuantum(evaluate_pixel[x].blue));
          SetPixelAlpha(q,ClampToQuantum(evaluate_pixel[x].opacity));
          if (image->colorspace == CMYKColorspace)
            SetPixelIndex(evaluate_indexes+x,ClampToQuantum(
              evaluate_pixel[x].index));
          q++;
        }
        if (SyncCacheViewAuthenticPixels(evaluate_view,exception) == MagickFalse)
          status=MagickFalse;
        if (images->progress_monitor != (MagickProgressMonitor) NULL)
          {
            MagickBooleanType
              proceed;

            proceed=SetImageProgress(images,EvaluateImageTag,progress++,
              image->rows);
            if (proceed == MagickFalse)
              status=MagickFalse;
          }
      }
    }
  evaluate_view=DestroyCacheView(evaluate_view);
  evaluate_pixels=DestroyPixelThreadSet(images,evaluate_pixels);
  random_info=DestroyRandomInfoThreadSet(random_info);
  if (status == MagickFalse)
    image=DestroyImage(image);
  return(image);
}


static inline double MagickLog10(const double x)
{
#define Log10Epsilon  (1.0e-11)

 if (fabs(x) < Log10Epsilon)
   return(log10(Log10Epsilon));
 return(log10(fabs(x)));
}


MagickExport MagickBooleanType GetImageChannelRange(const Image *image,
  const ChannelType channel,double *minima,double *maxima,
  ExceptionInfo *exception)
{
  MagickPixelPacket
    pixel;

  ssize_t
    y;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  *maxima=(-MagickMaximumValue);
  *minima=MagickMaximumValue;
  GetMagickPixelPacket(image,&pixel);
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const IndexPacket
      *magick_restrict indexes;

    const PixelPacket
      *magick_restrict p;

    ssize_t
      x;

    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=GetVirtualIndexQueue(image);
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      SetMagickPixelPacket(image,p,indexes+x,&pixel);
      if ((channel & RedChannel) != 0)
        {
          if (pixel.red < *minima)
            *minima=(double) pixel.red;
          if (pixel.red > *maxima)
            *maxima=(double) pixel.red;
        }
      if ((channel & GreenChannel) != 0)
        {
          if (pixel.green < *minima)
            *minima=(double) pixel.green;
          if (pixel.green > *maxima)
            *maxima=(double) pixel.green;
        }
      if ((channel & BlueChannel) != 0)
        {
          if (pixel.blue < *minima)
            *minima=(double) pixel.blue;
          if (pixel.blue > *maxima)
            *maxima=(double) pixel.blue;
        }
      if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
        {
          if ((QuantumRange-pixel.opacity) < *minima)
            *minima=(double) (QuantumRange-pixel.opacity);
          if ((QuantumRange-pixel.opacity) > *maxima)
            *maxima=(double) (QuantumRange-pixel.opacity);
        }
      if (((channel & IndexChannel) != 0) &&
          (image->colorspace == CMYKColorspace))
        {
          if ((double) pixel.index < *minima)
            *minima=(double) pixel.index;
          if ((double) pixel.index > *maxima)
            *maxima=(double) pixel.index;
        }
      p++;
    }
  }
  return(y == (ssize_t) image->rows ? MagickTrue : MagickFalse);
}


static MagickRealType ApplyEvaluateOperator(RandomInfo *random_info,
  const Quantum pixel,const MagickEvaluateOperator op,
  const MagickRealType value)
{
  MagickRealType
    result;

  ssize_t
    i;

  result=0.0;
  switch (op)
  {
    case UndefinedEvaluateOperator:
      break;
    case AbsEvaluateOperator:
    {
      result=(MagickRealType) fabs((double) (pixel+value));
      break;
    }
    case AddEvaluateOperator:
    {
      result=(MagickRealType) (pixel+value);
      break;
    }
    case AddModulusEvaluateOperator:
    {
      /*
        This returns a 'floored modulus' of the addition which is a
        positive result.  It differs from  % or fmod() which returns a
        'truncated modulus' result, where floor() is replaced by trunc()
        and could return a negative result (which is clipped).
      */
      result=pixel+value;
      result-=(QuantumRange+1.0)*floor((double) result/(QuantumRange+1.0));
      break;
    }
    case AndEvaluateOperator:
    {
      result=(MagickRealType) ((ssize_t) pixel & (ssize_t) (value+0.5));
      break;
    }
    case CosineEvaluateOperator:
    {
      result=(MagickRealType) (QuantumRange*(0.5*cos((double) (2.0*MagickPI*
        QuantumScale*pixel*value))+0.5));
      break;
    }
    case DivideEvaluateOperator:
    {
      result=pixel/(value == 0.0 ? 1.0 : value);
      break;
    }
    case ExponentialEvaluateOperator:
    {
      result=(MagickRealType) (QuantumRange*exp((double) (value*QuantumScale*
        pixel)));
      break;
    }
    case GaussianNoiseEvaluateOperator:
    {
      result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
        GaussianNoise,value);
      break;
    }
    case ImpulseNoiseEvaluateOperator:
    {
      result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
        ImpulseNoise,value);
      break;
    }
    case InverseLogEvaluateOperator:
    {
      result=(QuantumRange*pow((value+1.0),QuantumScale*pixel)-1.0)*
        PerceptibleReciprocal(value);
      break;
    }
    case LaplacianNoiseEvaluateOperator:
    {
      result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
        LaplacianNoise,value);
      break;
    }
    case LeftShiftEvaluateOperator:
    {
      result=(double) pixel;
      for (i=0; i < (ssize_t) value; i++)
        result*=2.0;
      break;
    }
    case LogEvaluateOperator:
    {
      if ((QuantumScale*pixel) >= MagickEpsilon)
        result=(MagickRealType) (QuantumRange*log((double) (QuantumScale*value*
          pixel+1.0))/log((double) (value+1.0)));
      break;
    }
    case MaxEvaluateOperator:
    {
      result=(MagickRealType) EvaluateMax((double) pixel,value);
      break;
    }
    case MeanEvaluateOperator:
    {
      result=(MagickRealType) (pixel+value);
      break;
    }
    case MedianEvaluateOperator:
    {
      result=(MagickRealType) (pixel+value);
      break;
    }
    case MinEvaluateOperator:
    {
      result=(MagickRealType) MagickMin((double) pixel,value);
      break;
    }
    case MultiplicativeNoiseEvaluateOperator:
    {
      result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
        MultiplicativeGaussianNoise,value);
      break;
    }
    case MultiplyEvaluateOperator:
    {
      result=(MagickRealType) (value*pixel);
      break;
    }
    case OrEvaluateOperator:
    {
      result=(MagickRealType) ((ssize_t) pixel | (ssize_t) (value+0.5));
      break;
    }
    case PoissonNoiseEvaluateOperator:
    {
      result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
        PoissonNoise,value);
      break;
    }
    case PowEvaluateOperator:
    {
      if (pixel < 0)
        result=(MagickRealType) -(QuantumRange*pow((double) -(QuantumScale*
          pixel),(double) value));
      else
        result=(MagickRealType) (QuantumRange*pow((double) (QuantumScale*pixel),
          (double) value));
      break;
    }
    case RightShiftEvaluateOperator:
    {
      result=(MagickRealType) pixel;
      for (i=0; i < (ssize_t) value; i++)
        result/=2.0;
      break;
    }
    case RootMeanSquareEvaluateOperator:
    {
      result=((MagickRealType) pixel*pixel+value);
      break;
    }
    case SetEvaluateOperator:
    {
      result=value;
      break;
    }
    case SineEvaluateOperator:
    {
      result=(MagickRealType) (QuantumRange*(0.5*sin((double) (2.0*MagickPI*
        QuantumScale*pixel*value))+0.5));
      break;
    }
    case SubtractEvaluateOperator:
    {
      result=(MagickRealType) (pixel-value);
      break;
    }
    case SumEvaluateOperator:
    {
      result=(MagickRealType) (pixel+value);
      break;
    }
    case ThresholdEvaluateOperator:
    {
      result=(MagickRealType) (((MagickRealType) pixel <= value) ? 0 :
        QuantumRange);
      break;
    }
    case ThresholdBlackEvaluateOperator:
    {
      result=(MagickRealType) (((MagickRealType) pixel <= value) ? 0 : pixel);
      break;
    }
    case ThresholdWhiteEvaluateOperator:
    {
      result=(MagickRealType) (((MagickRealType) pixel > value) ? QuantumRange :
        pixel);
      break;
    }
    case UniformNoiseEvaluateOperator:
    {
      result=(MagickRealType) GenerateDifferentialNoise(random_info,pixel,
        UniformNoise,value);
      break;
    }
    case XorEvaluateOperator:
    {
      result=(MagickRealType) ((ssize_t) pixel ^ (ssize_t) (value+0.5));
      break;
    }
  }
  return(result);
}

typedef struct _SkipList
{
  ssize_t
    level;

  ListNode
    *nodes;
} SkipList;


MagickExport MagickBooleanType GetImageExtrema(const Image *image,
  size_t *minima,size_t *maxima,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  status=GetImageChannelExtrema(image,CompositeChannels,minima,maxima,
    exception);
  return(status);
}


static void GetMeanPixelList(PixelList *pixel_list,MagickPixelPacket *pixel)
{
  MagickRealType
    sum;

  SkipList
    *list;

  ssize_t
    channel;

  size_t
    color;

  ssize_t
    count;

  unsigned short
    channels[ListChannels];

  /*
    Find the mean value for each of the color.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    color=65536L;
    count=0;
    sum=0.0;
    do
    {
      color=list->nodes[color].next[0];
      sum+=(MagickRealType) list->nodes[color].count*color;
      count+=list->nodes[color].count;
    } while (count < (ssize_t) pixel_list->length);
    sum/=pixel_list->length;
    channels[channel]=(unsigned short) sum;
  }
  pixel->red=(MagickRealType) ScaleShortToQuantum(channels[0]);
  pixel->green=(MagickRealType) ScaleShortToQuantum(channels[1]);
  pixel->blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
  pixel->opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
  pixel->index=(MagickRealType) ScaleShortToQuantum(channels[4]);
}


static void GetMinimumPixelList(PixelList *pixel_list,MagickPixelPacket *pixel)
{
  SkipList
    *list;

  ssize_t
    channel;

  size_t
    color,
    minimum;

  ssize_t
    count;

  unsigned short
    channels[ListChannels];

  /*
    Find the minimum value for each of the color.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    count=0;
    color=65536UL;
    minimum=list->nodes[color].next[0];
    do
    {
      color=list->nodes[color].next[0];
      if (color < minimum)
        minimum=color;
      count+=list->nodes[color].count;
    } while (count < (ssize_t) pixel_list->length);
    channels[channel]=(unsigned short) minimum;
  }
  pixel->red=(MagickRealType) ScaleShortToQuantum(channels[0]);
  pixel->green=(MagickRealType) ScaleShortToQuantum(channels[1]);
  pixel->blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
  pixel->opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
  pixel->index=(MagickRealType) ScaleShortToQuantum(channels[4]);
}


MagickExport ChannelPerceptualHash *GetImageChannelPerceptualHash(
  const Image *image,ExceptionInfo *exception)
{
  ChannelMoments
    *moments;

  ChannelPerceptualHash
    *perceptual_hash;

  Image
    *hash_image;

  MagickBooleanType
    status;

  ssize_t
    i;

  ssize_t
    channel;

  /*
    Blur then transform to sRGB colorspace.
  */
  hash_image=BlurImage(image,0.0,1.0,exception);
  if (hash_image == (Image *) NULL)
    return((ChannelPerceptualHash *) NULL);
  hash_image->depth=8;
  status=TransformImageColorspace(hash_image,sRGBColorspace);
  if (status == MagickFalse)
    return((ChannelPerceptualHash *) NULL);
  moments=GetImageChannelMoments(hash_image,exception);
  hash_image=DestroyImage(hash_image);
  if (moments == (ChannelMoments *) NULL)
    return((ChannelPerceptualHash *) NULL);
  perceptual_hash=(ChannelPerceptualHash *) AcquireQuantumMemory(
    CompositeChannels+1UL,sizeof(*perceptual_hash));
  if (perceptual_hash == (ChannelPerceptualHash *) NULL)
    return((ChannelPerceptualHash *) NULL);
  for (channel=0; channel <= CompositeChannels; channel++)
    for (i=0; i < MaximumNumberOfImageMoments; i++)
      perceptual_hash[channel].P[i]=(-MagickLog10(moments[channel].I[i]));
  moments=(ChannelMoments *) RelinquishMagickMemory(moments);
  /*
    Blur then transform to HCLp colorspace.
  */
  hash_image=BlurImage(image,0.0,1.0,exception);
  if (hash_image == (Image *) NULL)
    {
      perceptual_hash=(ChannelPerceptualHash *) RelinquishMagickMemory(
        perceptual_hash);
      return((ChannelPerceptualHash *) NULL);
    }
  hash_image->depth=8;
  status=TransformImageColorspace(hash_image,HCLpColorspace);
  if (status == MagickFalse)
    {
      perceptual_hash=(ChannelPerceptualHash *) RelinquishMagickMemory(
        perceptual_hash);
      return((ChannelPerceptualHash *) NULL);
    }
  moments=GetImageChannelMoments(hash_image,exception);
  hash_image=DestroyImage(hash_image);
  if (moments == (ChannelMoments *) NULL)
    {
      perceptual_hash=(ChannelPerceptualHash *) RelinquishMagickMemory(
        perceptual_hash);
      return((ChannelPerceptualHash *) NULL);
    }
  for (channel=0; channel <= CompositeChannels; channel++)
    for (i=0; i < MaximumNumberOfImageMoments; i++)
      perceptual_hash[channel].Q[i]=(-MagickLog10(moments[channel].I[i]));
  moments=(ChannelMoments *) RelinquishMagickMemory(moments);
  return(perceptual_hash);
}


static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
  const size_t color)
{
  SkipList
    *list;

  ssize_t
    level;

  size_t
    search,
    update[9];

  /*
    Initialize the node.
  */
  list=pixel_list->lists+channel;
  list->nodes[color].signature=pixel_list->signature;
  list->nodes[color].count=1;
  /*
    Determine where it belongs in the list.
  */
  search=65536UL;
  for (level=list->level; level >= 0; level--)
  {
    while (list->nodes[search].next[level] < color)
      search=list->nodes[search].next[level];
    update[level]=search;
  }
  /*
    Generate a pseudo-random level for this node.
  */
  for (level=0; ; level++)
  {
    pixel_list->seed=(pixel_list->seed*42893621L)+1L;
    if ((pixel_list->seed & 0x300) != 0x300)
      break;
  }
  if (level > 8)
    level=8;
  if (level > (list->level+2))
    level=list->level+2;
  /*
    If we're raising the list's level, link back to the root node.
  */
  while (level > list->level)
  {
    list->level++;
    update[list->level]=65536UL;
  }
  /*
    Link the node into the skip-list.
  */
  do
  {
    list->nodes[color].next[level]=list->nodes[update[level]].next[level];
    list->nodes[update[level]].next[level]=color;
  } while (level-- > 0);
}


static void GetMedianPixelList(PixelList *pixel_list,MagickPixelPacket *pixel)
{
  SkipList
    *list;

  ssize_t
    channel;

  size_t
    color;

  ssize_t
    count;

  unsigned short
    channels[ListChannels];

  /*
    Find the median value for each of the color.
  */
  for (channel=0; channel < 5; channel++)
  {
    list=pixel_list->lists+channel;
    color=65536L;
    count=0;
    do
    {
      color=list->nodes[color].next[0];
      count+=list->nodes[color].count;
    } while (count <= (ssize_t) (pixel_list->length >> 1));
    channels[channel]=(unsigned short) color;
  }
  GetMagickPixelPacket((const Image *) NULL,pixel);
  pixel->red=(MagickRealType) ScaleShortToQuantum(channels[0]);
  pixel->green=(MagickRealType) ScaleShortToQuantum(channels[1]);
  pixel->blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
  pixel->opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
  pixel->index=(MagickRealType) ScaleShortToQuantum(channels[4]);
}


MagickExport ChannelMoments *GetImageChannelMoments(const Image *image,
  ExceptionInfo *exception)
{
#define MaxNumberImageMoments  8

  ChannelMoments
    *channel_moments;

  double
    M00[CompositeChannels+1],
    M01[CompositeChannels+1],
    M02[CompositeChannels+1],
    M03[CompositeChannels+1],
    M10[CompositeChannels+1],
    M11[CompositeChannels+1],
    M12[CompositeChannels+1],
    M20[CompositeChannels+1],
    M21[CompositeChannels+1],
    M22[CompositeChannels+1],
    M30[CompositeChannels+1];

  MagickPixelPacket
    pixel;

  PointInfo
    centroid[CompositeChannels+1];

  ssize_t
    channel,
    channels,
    y;

  size_t
    length;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  length=CompositeChannels+1UL;
  channel_moments=(ChannelMoments *) AcquireQuantumMemory(length,
    sizeof(*channel_moments));
  if (channel_moments == (ChannelMoments *) NULL)
    return(channel_moments);
  (void) memset(channel_moments,0,length*sizeof(*channel_moments));
  (void) memset(centroid,0,sizeof(centroid));
  (void) memset(M00,0,sizeof(M00));
  (void) memset(M01,0,sizeof(M01));
  (void) memset(M02,0,sizeof(M02));
  (void) memset(M03,0,sizeof(M03));
  (void) memset(M10,0,sizeof(M10));
  (void) memset(M11,0,sizeof(M11));
  (void) memset(M12,0,sizeof(M12));
  (void) memset(M20,0,sizeof(M20));
  (void) memset(M21,0,sizeof(M21));
  (void) memset(M22,0,sizeof(M22));
  (void) memset(M30,0,sizeof(M30));
  GetMagickPixelPacket(image,&pixel);
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const IndexPacket
      *magick_restrict indexes;

    const PixelPacket
      *magick_restrict p;

    ssize_t
      x;

    /*
      Compute center of mass (centroid).
    */
    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=GetVirtualIndexQueue(image);
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      SetMagickPixelPacket(image,p,indexes+x,&pixel);
      M00[RedChannel]+=QuantumScale*pixel.red;
      M10[RedChannel]+=x*QuantumScale*pixel.red;
      M01[RedChannel]+=y*QuantumScale*pixel.red;
      M00[GreenChannel]+=QuantumScale*pixel.green;
      M10[GreenChannel]+=x*QuantumScale*pixel.green;
      M01[GreenChannel]+=y*QuantumScale*pixel.green;
      M00[BlueChannel]+=QuantumScale*pixel.blue;
      M10[BlueChannel]+=x*QuantumScale*pixel.blue;
      M01[BlueChannel]+=y*QuantumScale*pixel.blue;
      if (image->matte != MagickFalse)
        {
          M00[OpacityChannel]+=QuantumScale*pixel.opacity;
          M10[OpacityChannel]+=x*QuantumScale*pixel.opacity;
          M01[OpacityChannel]+=y*QuantumScale*pixel.opacity;
        }
      if (image->colorspace == CMYKColorspace)
        {
          M00[IndexChannel]+=QuantumScale*pixel.index;
          M10[IndexChannel]+=x*QuantumScale*pixel.index;
          M01[IndexChannel]+=y*QuantumScale*pixel.index;
        }
      p++;
    }
  }
  for (channel=0; channel <= CompositeChannels; channel++)
  {
    /*
      Compute center of mass (centroid).
    */
    if (M00[channel] < MagickEpsilon)
      {
        M00[channel]+=MagickEpsilon;
        centroid[channel].x=(double) image->columns/2.0;
        centroid[channel].y=(double) image->rows/2.0;
        continue;
      }
    M00[channel]+=MagickEpsilon;
    centroid[channel].x=M10[channel]/M00[channel];
    centroid[channel].y=M01[channel]/M00[channel];
  }
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const IndexPacket
      *magick_restrict indexes;

    const PixelPacket
      *magick_restrict p;

    ssize_t
      x;

    /*
      Compute the image moments.
    */
    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=GetVirtualIndexQueue(image);
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      SetMagickPixelPacket(image,p,indexes+x,&pixel);
      M11[RedChannel]+=(x-centroid[RedChannel].x)*(y-
        centroid[RedChannel].y)*QuantumScale*pixel.red;
      M20[RedChannel]+=(x-centroid[RedChannel].x)*(x-
        centroid[RedChannel].x)*QuantumScale*pixel.red;
      M02[RedChannel]+=(y-centroid[RedChannel].y)*(y-
        centroid[RedChannel].y)*QuantumScale*pixel.red;
      M21[RedChannel]+=(x-centroid[RedChannel].x)*(x-
        centroid[RedChannel].x)*(y-centroid[RedChannel].y)*QuantumScale*
        pixel.red;
      M12[RedChannel]+=(x-centroid[RedChannel].x)*(y-
        centroid[RedChannel].y)*(y-centroid[RedChannel].y)*QuantumScale*
        pixel.red;
      M22[RedChannel]+=(x-centroid[RedChannel].x)*(x-
        centroid[RedChannel].x)*(y-centroid[RedChannel].y)*(y-
        centroid[RedChannel].y)*QuantumScale*pixel.red;
      M30[RedChannel]+=(x-centroid[RedChannel].x)*(x-
        centroid[RedChannel].x)*(x-centroid[RedChannel].x)*QuantumScale*
        pixel.red;
      M03[RedChannel]+=(y-centroid[RedChannel].y)*(y-
        centroid[RedChannel].y)*(y-centroid[RedChannel].y)*QuantumScale*
        pixel.red;
      M11[GreenChannel]+=(x-centroid[GreenChannel].x)*(y-
        centroid[GreenChannel].y)*QuantumScale*pixel.green;
      M20[GreenChannel]+=(x-centroid[GreenChannel].x)*(x-
        centroid[GreenChannel].x)*QuantumScale*pixel.green;
      M02[GreenChannel]+=(y-centroid[GreenChannel].y)*(y-
        centroid[GreenChannel].y)*QuantumScale*pixel.green;
      M21[GreenChannel]+=(x-centroid[GreenChannel].x)*(x-
        centroid[GreenChannel].x)*(y-centroid[GreenChannel].y)*QuantumScale*
        pixel.green;
      M12[GreenChannel]+=(x-centroid[GreenChannel].x)*(y-
        centroid[GreenChannel].y)*(y-centroid[GreenChannel].y)*QuantumScale*
        pixel.green;
      M22[GreenChannel]+=(x-centroid[GreenChannel].x)*(x-
        centroid[GreenChannel].x)*(y-centroid[GreenChannel].y)*(y-
        centroid[GreenChannel].y)*QuantumScale*pixel.green;
      M30[GreenChannel]+=(x-centroid[GreenChannel].x)*(x-
        centroid[GreenChannel].x)*(x-centroid[GreenChannel].x)*QuantumScale*
        pixel.green;
      M03[GreenChannel]+=(y-centroid[GreenChannel].y)*(y-
        centroid[GreenChannel].y)*(y-centroid[GreenChannel].y)*QuantumScale*
        pixel.green;
      M11[BlueChannel]+=(x-centroid[BlueChannel].x)*(y-
        centroid[BlueChannel].y)*QuantumScale*pixel.blue;
      M20[BlueChannel]+=(x-centroid[BlueChannel].x)*(x-
        centroid[BlueChannel].x)*QuantumScale*pixel.blue;
      M02[BlueChannel]+=(y-centroid[BlueChannel].y)*(y-
        centroid[BlueChannel].y)*QuantumScale*pixel.blue;
      M21[BlueChannel]+=(x-centroid[BlueChannel].x)*(x-
        centroid[BlueChannel].x)*(y-centroid[BlueChannel].y)*QuantumScale*
        pixel.blue;
      M12[BlueChannel]+=(x-centroid[BlueChannel].x)*(y-
        centroid[BlueChannel].y)*(y-centroid[BlueChannel].y)*QuantumScale*
        pixel.blue;
      M22[BlueChannel]+=(x-centroid[BlueChannel].x)*(x-
        centroid[BlueChannel].x)*(y-centroid[BlueChannel].y)*(y-
        centroid[BlueChannel].y)*QuantumScale*pixel.blue;
      M30[BlueChannel]+=(x-centroid[BlueChannel].x)*(x-
        centroid[BlueChannel].x)*(x-centroid[BlueChannel].x)*QuantumScale*
        pixel.blue;
      M03[BlueChannel]+=(y-centroid[BlueChannel].y)*(y-
        centroid[BlueChannel].y)*(y-centroid[BlueChannel].y)*QuantumScale*
        pixel.blue;
      if (image->matte != MagickFalse)
        {
          M11[OpacityChannel]+=(x-centroid[OpacityChannel].x)*(y-
            centroid[OpacityChannel].y)*QuantumScale*pixel.opacity;
          M20[OpacityChannel]+=(x-centroid[OpacityChannel].x)*(x-
            centroid[OpacityChannel].x)*QuantumScale*pixel.opacity;
          M02[OpacityChannel]+=(y-centroid[OpacityChannel].y)*(y-
            centroid[OpacityChannel].y)*QuantumScale*pixel.opacity;
          M21[OpacityChannel]+=(x-centroid[OpacityChannel].x)*(x-
            centroid[OpacityChannel].x)*(y-centroid[OpacityChannel].y)*
            QuantumScale*pixel.opacity;
          M12[OpacityChannel]+=(x-centroid[OpacityChannel].x)*(y-
            centroid[OpacityChannel].y)*(y-centroid[OpacityChannel].y)*
            QuantumScale*pixel.opacity;
          M22[OpacityChannel]+=(x-centroid[OpacityChannel].x)*(x-
            centroid[OpacityChannel].x)*(y-centroid[OpacityChannel].y)*(y-
            centroid[OpacityChannel].y)*QuantumScale*pixel.opacity;
          M30[OpacityChannel]+=(x-centroid[OpacityChannel].x)*(x-
            centroid[OpacityChannel].x)*(x-centroid[OpacityChannel].x)*
            QuantumScale*pixel.opacity;
          M03[OpacityChannel]+=(y-centroid[OpacityChannel].y)*(y-
            centroid[OpacityChannel].y)*(y-centroid[OpacityChannel].y)*
            QuantumScale*pixel.opacity;
        }
      if (image->colorspace == CMYKColorspace)
        {
          M11[IndexChannel]+=(x-centroid[IndexChannel].x)*(y-
            centroid[IndexChannel].y)*QuantumScale*pixel.index;
          M20[IndexChannel]+=(x-centroid[IndexChannel].x)*(x-
            centroid[IndexChannel].x)*QuantumScale*pixel.index;
          M02[IndexChannel]+=(y-centroid[IndexChannel].y)*(y-
            centroid[IndexChannel].y)*QuantumScale*pixel.index;
          M21[IndexChannel]+=(x-centroid[IndexChannel].x)*(x-
            centroid[IndexChannel].x)*(y-centroid[IndexChannel].y)*
            QuantumScale*pixel.index;
          M12[IndexChannel]+=(x-centroid[IndexChannel].x)*(y-
            centroid[IndexChannel].y)*(y-centroid[IndexChannel].y)*
            QuantumScale*pixel.index;
          M22[IndexChannel]+=(x-centroid[IndexChannel].x)*(x-
            centroid[IndexChannel].x)*(y-centroid[IndexChannel].y)*(y-
            centroid[IndexChannel].y)*QuantumScale*pixel.index;
          M30[IndexChannel]+=(x-centroid[IndexChannel].x)*(x-
            centroid[IndexChannel].x)*(x-centroid[IndexChannel].x)*
            QuantumScale*pixel.index;
          M03[IndexChannel]+=(y-centroid[IndexChannel].y)*(y-
            centroid[IndexChannel].y)*(y-centroid[IndexChannel].y)*
            QuantumScale*pixel.index;
        }
      p++;
    }
  }
  channels=3;
  M00[CompositeChannels]+=(M00[RedChannel]+M00[GreenChannel]+M00[BlueChannel]);
  M01[CompositeChannels]+=(M01[RedChannel]+M01[GreenChannel]+M01[BlueChannel]);
  M02[CompositeChannels]+=(M02[RedChannel]+M02[GreenChannel]+M02[BlueChannel]);
  M03[CompositeChannels]+=(M03[RedChannel]+M03[GreenChannel]+M03[BlueChannel]);
  M10[CompositeChannels]+=(M10[RedChannel]+M10[GreenChannel]+M10[BlueChannel]);
  M11[CompositeChannels]+=(M11[RedChannel]+M11[GreenChannel]+M11[BlueChannel]);
  M12[CompositeChannels]+=(M12[RedChannel]+M12[GreenChannel]+M12[BlueChannel]);
  M20[CompositeChannels]+=(M20[RedChannel]+M20[GreenChannel]+M20[BlueChannel]);
  M21[CompositeChannels]+=(M21[RedChannel]+M21[GreenChannel]+M21[BlueChannel]);
  M22[CompositeChannels]+=(M22[RedChannel]+M22[GreenChannel]+M22[BlueChannel]);
  M30[CompositeChannels]+=(M30[RedChannel]+M30[GreenChannel]+M30[BlueChannel]);
  if (image->matte != MagickFalse)
    {
      channels+=1;
      M00[CompositeChannels]+=M00[OpacityChannel];
      M01[CompositeChannels]+=M01[OpacityChannel];
      M02[CompositeChannels]+=M02[OpacityChannel];
      M03[CompositeChannels]+=M03[OpacityChannel];
      M10[CompositeChannels]+=M10[OpacityChannel];
      M11[CompositeChannels]+=M11[OpacityChannel];
      M12[CompositeChannels]+=M12[OpacityChannel];
      M20[CompositeChannels]+=M20[OpacityChannel];
      M21[CompositeChannels]+=M21[OpacityChannel];
      M22[CompositeChannels]+=M22[OpacityChannel];
      M30[CompositeChannels]+=M30[OpacityChannel];
    }
  if (image->colorspace == CMYKColorspace)
    {
      channels+=1;
      M00[CompositeChannels]+=M00[IndexChannel];
      M01[CompositeChannels]+=M01[IndexChannel];
      M02[CompositeChannels]+=M02[IndexChannel];
      M03[CompositeChannels]+=M03[IndexChannel];
      M10[CompositeChannels]+=M10[IndexChannel];
      M11[CompositeChannels]+=M11[IndexChannel];
      M12[CompositeChannels]+=M12[IndexChannel];
      M20[CompositeChannels]+=M20[IndexChannel];
      M21[CompositeChannels]+=M21[IndexChannel];
      M22[CompositeChannels]+=M22[IndexChannel];
      M30[CompositeChannels]+=M30[IndexChannel];
    }
  M00[CompositeChannels]/=(double) channels;
  M01[CompositeChannels]/=(double) channels;
  M02[CompositeChannels]/=(double) channels;
  M03[CompositeChannels]/=(double) channels;
  M10[CompositeChannels]/=(double) channels;
  M11[CompositeChannels]/=(double) channels;
  M12[CompositeChannels]/=(double) channels;
  M20[CompositeChannels]/=(double) channels;
  M21[CompositeChannels]/=(double) channels;
  M22[CompositeChannels]/=(double) channels;
  M30[CompositeChannels]/=(double) channels;
  for (channel=0; channel <= CompositeChannels; channel++)
  {
    /*
      Compute elliptical angle, major and minor axes, eccentricity, & intensity.
    */
    channel_moments[channel].centroid=centroid[channel];
    channel_moments[channel].ellipse_axis.x=sqrt((2.0*
      PerceptibleReciprocal(M00[channel]))*((M20[channel]+M02[channel])+
      sqrt(4.0*M11[channel]*M11[channel]+(M20[channel]-M02[channel])*
      (M20[channel]-M02[channel]))));
    channel_moments[channel].ellipse_axis.y=sqrt((2.0*
      PerceptibleReciprocal(M00[channel]))*((M20[channel]+M02[channel])-
      sqrt(4.0*M11[channel]*M11[channel]+(M20[channel]-M02[channel])*
      (M20[channel]-M02[channel]))));
    channel_moments[channel].ellipse_angle=RadiansToDegrees(1.0/2.0*atan(2.0*
      M11[channel]*PerceptibleReciprocal(M20[channel]-M02[channel])));
    if (fabs(M11[channel]) < 0.0)
      {
        if ((fabs(M20[channel]-M02[channel]) >= 0.0) &&
            ((M20[channel]-M02[channel]) < 0.0))
          channel_moments[channel].ellipse_angle+=90.0;
      }
    else
      if (M11[channel] < 0.0)
        {
          if (fabs(M20[channel]-M02[channel]) >= 0.0)
            {
              if ((M20[channel]-M02[channel]) < 0.0)
                channel_moments[channel].ellipse_angle+=90.0;
              else
                channel_moments[channel].ellipse_angle+=180.0;
            }
        }
      else
        if ((fabs(M20[channel]-M02[channel]) >= 0.0) &&
            ((M20[channel]-M02[channel]) < 0.0))
          channel_moments[channel].ellipse_angle+=90.0;
    channel_moments[channel].ellipse_eccentricity=sqrt(1.0-(
      channel_moments[channel].ellipse_axis.y*
      channel_moments[channel].ellipse_axis.y*PerceptibleReciprocal(
      channel_moments[channel].ellipse_axis.x*
      channel_moments[channel].ellipse_axis.x)));
    channel_moments[channel].ellipse_intensity=M00[channel]/
      (MagickPI*channel_moments[channel].ellipse_axis.x*
      channel_moments[channel].ellipse_axis.y+MagickEpsilon);
  }
  for (channel=0; channel <= CompositeChannels; channel++)
  {
    /*
      Normalize image moments.
    */
    M10[channel]=0.0;
    M01[channel]=0.0;
    M11[channel]/=pow(M00[channel],1.0+(1.0+1.0)/2.0);
    M20[channel]/=pow(M00[channel],1.0+(2.0+0.0)/2.0);
    M02[channel]/=pow(M00[channel],1.0+(0.0+2.0)/2.0);
    M21[channel]/=pow(M00[channel],1.0+(2.0+1.0)/2.0);
    M12[channel]/=pow(M00[channel],1.0+(1.0+2.0)/2.0);
    M22[channel]/=pow(M00[channel],1.0+(2.0+2.0)/2.0);
    M30[channel]/=pow(M00[channel],1.0+(3.0+0.0)/2.0);
    M03[channel]/=pow(M00[channel],1.0+(0.0+3.0)/2.0);
    M00[channel]=1.0;
  }
  for (channel=0; channel <= CompositeChannels; channel++)
  {
    /*
      Compute Hu invariant moments.
    */
    channel_moments[channel].I[0]=M20[channel]+M02[channel];
    channel_moments[channel].I[1]=(M20[channel]-M02[channel])*
      (M20[channel]-M02[channel])+4.0*M11[channel]*M11[channel];
    channel_moments[channel].I[2]=(M30[channel]-3.0*M12[channel])*
      (M30[channel]-3.0*M12[channel])+(3.0*M21[channel]-M03[channel])*
      (3.0*M21[channel]-M03[channel]);
    channel_moments[channel].I[3]=(M30[channel]+M12[channel])*
      (M30[channel]+M12[channel])+(M21[channel]+M03[channel])*
      (M21[channel]+M03[channel]);
    channel_moments[channel].I[4]=(M30[channel]-3.0*M12[channel])*
      (M30[channel]+M12[channel])*((M30[channel]+M12[channel])*
      (M30[channel]+M12[channel])-3.0*(M21[channel]+M03[channel])*
      (M21[channel]+M03[channel]))+(3.0*M21[channel]-M03[channel])*
      (M21[channel]+M03[channel])*(3.0*(M30[channel]+M12[channel])*
      (M30[channel]+M12[channel])-(M21[channel]+M03[channel])*
      (M21[channel]+M03[channel]));
    channel_moments[channel].I[5]=(M20[channel]-M02[channel])*
      ((M30[channel]+M12[channel])*(M30[channel]+M12[channel])-
      (M21[channel]+M03[channel])*(M21[channel]+M03[channel]))+
      4.0*M11[channel]*(M30[channel]+M12[channel])*(M21[channel]+M03[channel]);
    channel_moments[channel].I[6]=(3.0*M21[channel]-M03[channel])*
      (M30[channel]+M12[channel])*((M30[channel]+M12[channel])*
      (M30[channel]+M12[channel])-3.0*(M21[channel]+M03[channel])*
      (M21[channel]+M03[channel]))-(M30[channel]-3*M12[channel])*
      (M21[channel]+M03[channel])*(3.0*(M30[channel]+M12[channel])*
      (M30[channel]+M12[channel])-(M21[channel]+M03[channel])*
      (M21[channel]+M03[channel]));
    channel_moments[channel].I[7]=M11[channel]*((M30[channel]+M12[channel])*
      (M30[channel]+M12[channel])-(M03[channel]+M21[channel])*
      (M03[channel]+M21[channel]))-(M20[channel]-M02[channel])*
      (M30[channel]+M12[channel])*(M03[channel]+M21[channel]);
  }
  if (y < (ssize_t) image->rows)
    channel_moments=(ChannelMoments *) RelinquishMagickMemory(channel_moments);
  return(channel_moments);
}