/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                  W   W  BBBB   IIIII  N   N  FFFFF   OOO                    %
%                  W   W  B   B    I    NN  N  F      O   O                   %
%                  W W W  BBBB     I    N N N  FFF    O   O                   %
%                  WW WW  B   B    I    N  NN  F      O   O                   %
%                  W   W  BBBB   IIIII  N   N  F       OOO                    %
%                                                                             %
%                                                                             %
%                   Read Amiga Workbench Icon Image Format                    %
%                                                                             %
%                              Software Design                                %
%                              Gareth Davidson                                %
%                                 March 2026                                  %
%                                                                             %
%                                                                             %
%  Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization         %
%  dedicated to making software imaging solutions freely available.           %
%                                                                             %
%  You may not use this file except in compliance with the License.  You may  %
%  obtain a copy of the License at                                            %
%                                                                             %
%    https://imagemagick.org/license/                                         %
%                                                                             %
%  Unless required by applicable law or agreed to in writing, software        %
%  distributed under the License is distributed on an "AS IS" BASIS,          %
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
%  See the License for the specific language governing permissions and        %
%  limitations under the License.                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#include "MagickCore/studio.h"
#include "MagickCore/blob.h"
#include "MagickCore/blob-private.h"
#include "MagickCore/cache.h"
#include "MagickCore/exception.h"
#include "MagickCore/exception-private.h"
#include "MagickCore/image.h"
#include "MagickCore/image-private.h"
#include "MagickCore/list.h"
#include "MagickCore/log.h"
#include "MagickCore/magick.h"
#include "MagickCore/memory_.h"
#include "MagickCore/monitor.h"
#include "MagickCore/monitor-private.h"
#include "MagickCore/pixel-accessor.h"
#include "MagickCore/quantum-private.h"
#include "MagickCore/static.h"
#include "MagickCore/string_.h"
#include "MagickCore/module.h"
#if defined(MAGICKCORE_ZLIB_DELEGATE)
#include <zlib.h>
#endif

/*
  Define declarations.
*/
#define AmigaIconMagic  0xE310
#define MaxAmigaIconDimension  1024
#define MaxAmigaIconFormScanSize  (64UL*1024UL*1024UL)
#define MaxAmigaIconImageChunks  8
#define MaxAmigaIconScenes  8
#define MaxAmigaIconToolTypes  4096
#define MaxNewIconBitCount \
  ((size_t) MaxAmigaIconDimension*MaxAmigaIconDimension*16)

/*
  Typedef declarations.
*/
typedef enum
{
  AmigaIconChunkContinue,  /* keep walking the chunk list */
  AmigaIconChunkStop,      /* malformed chunk: stop, keep images so far */
  AmigaIconChunkFatal      /* abort the read */
} AmigaIconChunkStatus;

static MagickBooleanType GetRemainingBlobSize(Image *image,size_t *remaining)
{
  MagickOffsetType
    offset;

  MagickSizeType
    blob_size;

  offset=TellBlob(image);
  blob_size=GetBlobSize(image);
  if ((offset < 0) || ((MagickSizeType) offset > blob_size) ||
      (blob_size-(MagickSizeType) offset > (MagickSizeType) MAGICK_SIZE_MAX))
    {
      *remaining=0;
      return(MagickFalse);
    }
  *remaining=(size_t) (blob_size-(MagickSizeType) offset);
  return(MagickTrue);
}

/*
  Amiga Workbench system palettes.
*/
static const unsigned char AmigaWB1x[4][3] =
{
  {85,170,255},{255,255,255},{0,0,0},{255,136,0}
};

static const unsigned char AmigaWB2x[8][3] =
{
  {149,149,149},{0,0,0},{255,255,255},{59,103,162},
  {123,123,123},{175,175,175},{170,144,124},{255,169,151}
};

static unsigned int BitsToUInt(const unsigned char *data,size_t total_bits,
  size_t *bit_pos,size_t count)
{
  size_t
    i;

  unsigned int
    val;

  val=0;
  for (i=0; i < count; i++)
  {
    val<<=1;
    if (*bit_pos < total_bits)
      {
        size_t
          bit_idx,
          byte_idx;

        byte_idx=(*bit_pos) >> 3;
        bit_idx=7-((*bit_pos) & 7);
        val|=(unsigned int) ((data[byte_idx] >> bit_idx) & 1);
      }
    (*bit_pos)++;
  }
  return(val);
}

/*
  Count expanded bits from a NewIcon encoded line (for buffer sizing).
*/
static size_t NewIconLineBitCount(const char *src,size_t src_len)
{
  size_t
    bit_count,
    ch_idx;

  bit_count=0;
  for (ch_idx=0; ch_idx < src_len; ch_idx++)
  {
    unsigned int
      byte;

    byte=(unsigned int) (unsigned char) src[ch_idx];
    if (byte < 32)
      continue;
    if (byte <= 208)
      bit_count+=7;
    else
      bit_count+=(size_t) (byte-208)*7;
    if (bit_count > MaxNewIconBitCount)
      return(bit_count);
  }
  return(bit_count);
}

/*
  Decode one NewIcon 7-bit encoded line into a bit buffer.

  Each character maps to 7 bits; characters above 208 are RLE zero runs.
  Returns the number of bits written.
*/
static size_t DecodeNewIconLine(const char *src,size_t src_len,
  unsigned char *bits,size_t bits_alloc,size_t bit_offset)
{
  size_t
    bit_count,
    ch_idx;

  bit_count=bit_offset;
  for (ch_idx=0; ch_idx < src_len; ch_idx++)
  {
    int
      bit_i;

    unsigned int
      byte,
      val;

    byte=(unsigned int) (unsigned char) src[ch_idx];
    if (byte < 32)
      continue;
    if (byte <= 159)
      val=byte-32;
    else if (byte <= 208)
      val=byte-81;
    else
      {
        bit_count+=(size_t) (byte-208)*7;
        continue;
      }
    for (bit_i=6; bit_i >= 0; bit_i--)
    {
      if ((bit_count < bits_alloc*8) && ((val >> bit_i) & 1))
        bits[bit_count >> 3]|=(unsigned char) (1 << (7-(bit_count & 7)));
      bit_count++;
    }
  }
  return(bit_count);
}

/*
  Decode GlowIcon/ColorIcon RLE data.

  The RLE operates on a bit stream where entries are `depth` bits wide,
  but control bytes are always 8 bits.

  Returns allocated buffer with decoded bytes (one byte per entry).
  Caller must free with RelinquishMagickMemory().
*/
static unsigned char *DecodeAmigaRLE(const unsigned char *data,
  size_t data_size,size_t depth,size_t expected_count,size_t *decoded_count,
  MagickBooleanType *complete)
{
  size_t
    bit_pos,
    count,
    i,
    n,
    total_bits;

  unsigned char
    *result;

  unsigned int
    control,
    val;

  result=(unsigned char *) AcquireQuantumMemory(expected_count,
    sizeof(*result));
  if (result == (unsigned char *) NULL)
    return((unsigned char *) NULL);
  *complete=MagickFalse;
  total_bits=data_size*8;
  bit_pos=0;
  count=0;
  while ((bit_pos+8 <= total_bits) && (count < expected_count))
  {
    control=BitsToUInt(data,total_bits,&bit_pos,8);
    if (control == 128)
      continue;
    if (control <= 127)
      {
        n=(size_t) control+1;
        for (i=0; i < n; i++)
        {
          if (count >= expected_count)
            break;
          if (bit_pos+depth > total_bits)
            {
              *decoded_count=count;
              return(result);
            }
          result[count++]=(unsigned char) BitsToUInt(data,total_bits,&bit_pos,
            depth);
        }
      }
    else
      {
        if (bit_pos+depth > total_bits)
          {
            *decoded_count=count;
            return(result);
          }
        val=BitsToUInt(data,total_bits,&bit_pos,depth);
        n=257-(size_t) control;
        for (i=0; i < n; i++)
        {
          if (count >= expected_count)
            break;
          result[count++]=(unsigned char) val;
        }
      }
  }
  *decoded_count=count;
  *complete=count == expected_count ? MagickTrue : MagickFalse;
  return(result);
}

static MagickBooleanType AppendAmigaImage(Image **image,size_t width,
  size_t height,size_t scene,const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  if (scene > 0)
    {
      AcquireNextImage(image_info,*image,exception);
      if (GetNextImageInList(*image) == (Image *) NULL)
        return(MagickFalse);
      *image=SyncNextImageInList(*image);
      if (*image == (Image *) NULL)
        return(MagickFalse);
    }
  if (SetImageExtent(*image,width,height,exception) == MagickFalse)
    {
      if (scene > 0)
        DeleteImageFromList(image);
      return(MagickFalse);
    }
  (*image)->depth=8;
  (*image)->alpha_trait=BlendPixelTrait;
  (*image)->scene=scene;
  return(MagickTrue);
}

/*
  Render a paletted image (NewIcon or GlowIcon) to DirectClass RGBA.
*/
static MagickBooleanType RenderPalettedAmigaImage(Image *image,size_t width,
  size_t height,const unsigned char *pixels,size_t pixel_count,
  const unsigned char *palette,size_t pal_entries,
  MagickBooleanType has_transparent,unsigned char transparent_idx,
  ExceptionInfo *exception)
{
  ssize_t
    y;

  for (y=0; y < (ssize_t) height; y++)
  {
    Quantum
      *q;

    ssize_t
      x;

    q=QueueAuthenticPixels(image,0,y,width,1,exception);
    if (q == (Quantum *) NULL)
      return(MagickFalse);
    for (x=0; x < (ssize_t) width; x++)
    {
      size_t
        pi;

      unsigned char
        idx;

      pi=(size_t) y*width+(size_t) x;
      idx=pi < pixel_count ? pixels[pi] : 0;
      if ((palette != (const unsigned char *) NULL) &&
          ((size_t) idx < pal_entries))
        {
          SetPixelRed(image,ScaleCharToQuantum(palette[idx*3]),q);
          SetPixelGreen(image,ScaleCharToQuantum(palette[idx*3+1]),q);
          SetPixelBlue(image,ScaleCharToQuantum(palette[idx*3+2]),q);
        }
      else
        {
          SetPixelRed(image,0,q);
          SetPixelGreen(image,0,q);
          SetPixelBlue(image,0,q);
        }
      if (has_transparent && (idx == transparent_idx))
        SetPixelAlpha(image,TransparentAlpha,q);
      else
        SetPixelAlpha(image,OpaqueAlpha,q);
      q+=(ptrdiff_t) GetPixelChannels(image);
    }
    if (SyncAuthenticPixels(image,exception) == MagickFalse)
      return(MagickFalse);
  }
  return(MagickTrue);
}

/*
  Free a bitplane array; NULL entries are ignored.
*/
static unsigned char **RelinquishPlanes(unsigned char **planes,size_t count)
{
  size_t
    i;

  for (i=0; i < count; i++)
    planes[i]=(unsigned char *) RelinquishMagickMemory(planes[i]);
  return((unsigned char **) RelinquishMagickMemory(planes));
}

/*
  Read and render a classic planar Amiga icon image from the blob.

  Reads the 20-byte Image header, bitplane data, selects the system palette,
  renders to a new scene, and increments *scene.  Returns MagickTrue on
  success.  On failure the blob position is indeterminate.
*/
static MagickBooleanType ReadAndRenderClassicImage(Image **image,
  size_t *scene,unsigned int user_data,const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  const unsigned char
    (*palette)[3];

  MagickBooleanType
    render_status;

  size_t
    bytes_per_row,
    num_colors,
    p_idx,
    plane_size,
    remaining,
    words_per_row;

  ssize_t
    y;

  unsigned char
    plane_pick,
    plane_on_off,
    **planes;

  unsigned short
    depth,
    height,
    width;

  /*
    checked ~300k icons and only 64 set left_edge or top_edge, and every one
    was corruption that pushed the image out of bounds. So we'll skip those.
  */
  (void) ReadBlobMSBShort(*image);  /* left_edge */
  (void) ReadBlobMSBShort(*image);  /* top_edge */
  width=ReadBlobMSBShort(*image);
  height=ReadBlobMSBShort(*image);
  depth=ReadBlobMSBShort(*image);
  (void) ReadBlobMSBLong(*image);   /* image_data ptr */
  plane_pick=(unsigned char) ReadBlobByte(*image);
  plane_on_off=(unsigned char) ReadBlobByte(*image);
  (void) ReadBlobMSBLong(*image);   /* next ptr */
  if (EOFBlob(*image) != MagickFalse)
    return(MagickFalse);
  if ((width == 0) || (height == 0) || (depth == 0) ||
      (width > MaxAmigaIconDimension) ||
      (height > MaxAmigaIconDimension) || (depth > 8))
    return(MagickFalse);
  words_per_row=((size_t) width+15)/16;
  bytes_per_row=words_per_row*2;
  if (HeapOverflowSanityCheckGetSize(bytes_per_row,(size_t) height,
      &plane_size) != MagickFalse)
    return(MagickFalse);
  if ((GetRemainingBlobSize(*image,&remaining) == MagickFalse) ||
      (HeapOverflowSanityCheck(plane_size,(size_t) depth) != MagickFalse) ||
      (plane_size*(size_t) depth > remaining))
    return(MagickFalse);
  planes=(unsigned char **) AcquireQuantumMemory((size_t) depth,
    sizeof(*planes));
  if (planes == (unsigned char **) NULL)
    return(MagickFalse);
  (void) memset(planes,0,(size_t) depth*sizeof(*planes));
  for (p_idx=0; p_idx < (size_t) depth; p_idx++)
  {
    planes[p_idx]=(unsigned char *) AcquireQuantumMemory(plane_size,
      sizeof(*planes[p_idx]));
    if ((planes[p_idx] == (unsigned char *) NULL) ||
        (ReadBlob(*image,plane_size,planes[p_idx]) != (ssize_t) plane_size))
      {
        planes=RelinquishPlanes(planes,p_idx);
        return(MagickFalse);
      }
  }
  if (user_data & 255)
    {
      palette=AmigaWB2x;
      num_colors=8;
    }
  else
    {
      palette=AmigaWB1x;
      num_colors=4;
    }
  if (AppendAmigaImage(image,(size_t) width,(size_t) height,
      *scene,image_info,exception) == MagickFalse)
    {
      planes=RelinquishPlanes(planes,(size_t) depth);
      return(MagickFalse);
    }
  render_status=MagickTrue;
  for (y=0; y < (ssize_t) height; y++)
  {
    Quantum
      *q;

    ssize_t
      x;

    q=QueueAuthenticPixels(*image,0,y,(size_t) width,1,exception);
    if (q == (Quantum *) NULL)
      {
        render_status=MagickFalse;
        break;
      }
    for (x=0; x < (ssize_t) width; x++)
    {
      size_t
        bit_idx,
        byte_idx,
        data_plane,
        plane_num;

      unsigned int
        color_index;

      byte_idx=(size_t) y*bytes_per_row+((size_t) x >> 3);
      bit_idx=7-((size_t) x & 7);
      color_index=0;
      data_plane=0;
      for (plane_num=0; plane_num < (size_t) depth; plane_num++)
      {
        if (plane_pick & (1 << plane_num))
          {
            if (data_plane < (size_t) depth)
              color_index|=(unsigned int)
                (((planes[data_plane][byte_idx] >> bit_idx) & 1) << plane_num);
            data_plane++;
          }
        else if (plane_on_off & (1 << plane_num))
          color_index|=(1U << plane_num);
      }
      if (color_index < num_colors)
        {
          SetPixelRed(*image,ScaleCharToQuantum(palette[color_index][0]),q);
          SetPixelGreen(*image,ScaleCharToQuantum(palette[color_index][1]),q);
          SetPixelBlue(*image,ScaleCharToQuantum(palette[color_index][2]),q);
        }
      else
        {
          SetPixelRed(*image,0,q);
          SetPixelGreen(*image,0,q);
          SetPixelBlue(*image,0,q);
        }
      if (color_index == 0)
        SetPixelAlpha(*image,TransparentAlpha,q);
      else
        SetPixelAlpha(*image,OpaqueAlpha,q);
      q+=(ptrdiff_t) GetPixelChannels(*image);
    }
    if (SyncAuthenticPixels(*image,exception) == MagickFalse)
      {
        render_status=MagickFalse;
        break;
      }
  }
  planes=RelinquishPlanes(planes,(size_t) depth);
  if (render_status == MagickFalse)
    {
      if (*scene > 0)
        DeleteImageFromList(image);
      return(MagickFalse);
    }
  (*scene)++;
  return(MagickTrue);
}

/*
  Decode and render a NewIcon image from collected IM1=/IM2= lines.

  Returns MagickTrue if an image was successfully decoded and rendered.
*/
static MagickBooleanType DecodeAndRenderNewIcon(Image **image,size_t *scene,
  char **im_lines,size_t im_count,const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  char
    *first_line;

  size_t
    bit_count,
    bit_pos,
    line_bits_alloc,
    line_bits_bytes,
    line_idx,
    ni_depth,
    ni_height,
    ni_num_colors,
    ni_transparent,
    ni_width,
    pal_idx,
    pix_count;

  unsigned char
    *line_bits,
    *ni_palette,
    *ni_pixels;

  if ((im_count == 0) || (im_lines == (char **) NULL))
    return(MagickFalse);
  first_line=im_lines[0];
  if (strlen(first_line) < 5)
    return(MagickFalse);
  ni_transparent=((unsigned char) first_line[0] == 66) ? 1 : 0;
  if (((unsigned char) first_line[1] < 33) ||
      ((unsigned char) first_line[2] < 33) ||
      ((unsigned char) first_line[3] < 33) ||
      ((unsigned char) first_line[4] < 33))
    return(MagickFalse);
  ni_width=(size_t) ((unsigned char) first_line[1]-33);
  ni_height=(size_t) ((unsigned char) first_line[2]-33);
  ni_num_colors=(size_t) (((unsigned char) first_line[3]-33) << 6)+
    (size_t) ((unsigned char) first_line[4]-33);
  if ((ni_width == 0) || (ni_height == 0) || (ni_num_colors == 0) ||
      (ni_width > MaxAmigaIconDimension) ||
      (ni_height > MaxAmigaIconDimension) ||
      (ni_num_colors > 256))
    return(MagickFalse);
  ni_depth=1;
  while (((size_t) 1 << ni_depth) < ni_num_colors)
    ni_depth++;
  /*
    Find max expanded bit count to size the reusable decode buffer.
  */
  line_bits_alloc=NewIconLineBitCount(first_line+5,strlen(first_line+5));
  for (line_idx=1; line_idx < im_count; line_idx++)
  {
    size_t
      ll;

    ll=NewIconLineBitCount(im_lines[line_idx],strlen(im_lines[line_idx]));
    if (ll > line_bits_alloc)
      line_bits_alloc=ll;
  }
  line_bits_alloc+=7;
  if (line_bits_alloc > MaxNewIconBitCount)
    return(MagickFalse);
  line_bits_bytes=(line_bits_alloc+7)/8+1;
  line_bits=(unsigned char *) AcquireQuantumMemory(line_bits_bytes,
    sizeof(*line_bits));
  if (line_bits == (unsigned char *) NULL)
    return(MagickFalse);
  /*
    First line: extract palette only, discard remaining bits.
  */
  (void) memset(line_bits,0,line_bits_bytes*sizeof(*line_bits));
  bit_count=DecodeNewIconLine(first_line+5,strlen(first_line+5),line_bits,
    line_bits_bytes,0);
  if (bit_count < ni_num_colors*24)
    {
      line_bits=(unsigned char *) RelinquishMagickMemory(line_bits);
      return(MagickFalse);
    }
  ni_palette=(unsigned char *) AcquireQuantumMemory(ni_num_colors*3+3,
    sizeof(*ni_palette));
  if (ni_palette == (unsigned char *) NULL)
    {
      line_bits=(unsigned char *) RelinquishMagickMemory(line_bits);
      return(MagickFalse);
    }
  (void) memset(ni_palette,0,(ni_num_colors*3+3)*sizeof(*ni_palette));
  bit_pos=0;
  for (pal_idx=0; pal_idx < ni_num_colors; pal_idx++)
  {
    ni_palette[pal_idx*3]=(unsigned char) BitsToUInt(line_bits,bit_count,
      &bit_pos,8);
    ni_palette[pal_idx*3+1]=(unsigned char) BitsToUInt(line_bits,bit_count,
      &bit_pos,8);
    ni_palette[pal_idx*3+2]=(unsigned char) BitsToUInt(line_bits,bit_count,
      &bit_pos,8);
  }
  /*
    Subsequent lines: extract pixels per-line, discard leftover bits.
  */
  ni_pixels=(unsigned char *) AcquireQuantumMemory(ni_width*ni_height+1,
    sizeof(*ni_pixels));
  if (ni_pixels == (unsigned char *) NULL)
    {
      ni_palette=(unsigned char *) RelinquishMagickMemory(ni_palette);
      line_bits=(unsigned char *) RelinquishMagickMemory(line_bits);
      return(MagickFalse);
    }
  (void) memset(ni_pixels,0,(ni_width*ni_height+1)*sizeof(*ni_pixels));
  pix_count=0;
  for (line_idx=1; line_idx < im_count &&
       pix_count < ni_width*ni_height; line_idx++)
  {
    size_t
      lbp;

    (void) memset(line_bits,0,line_bits_bytes*sizeof(*line_bits));
    bit_count=DecodeNewIconLine(im_lines[line_idx],strlen(im_lines[line_idx]),
      line_bits,line_bits_bytes,0);
    lbp=0;
    while ((lbp+ni_depth <= bit_count) && (pix_count < ni_width*ni_height))
      ni_pixels[pix_count++]=(unsigned char) BitsToUInt(line_bits,bit_count,
        &lbp,ni_depth);
  }
  line_bits=(unsigned char *) RelinquishMagickMemory(line_bits);
  if (pix_count < ni_width*ni_height)
    {
      ni_pixels=(unsigned char *) RelinquishMagickMemory(ni_pixels);
      ni_palette=(unsigned char *) RelinquishMagickMemory(ni_palette);
      return(MagickFalse);
    }
  /*
    Render NewIcon to image.
  */
  if (AppendAmigaImage(image,ni_width,ni_height,*scene,image_info,
      exception) == MagickFalse)
    {
      ni_pixels=(unsigned char *) RelinquishMagickMemory(ni_pixels);
      ni_palette=(unsigned char *) RelinquishMagickMemory(ni_palette);
      return(MagickFalse);
    }
  if (RenderPalettedAmigaImage(*image,ni_width,ni_height,ni_pixels,pix_count,
      ni_palette,ni_num_colors,ni_transparent ? MagickTrue : MagickFalse,0,
      exception) == MagickFalse)
    {
      ni_pixels=(unsigned char *) RelinquishMagickMemory(ni_pixels);
      ni_palette=(unsigned char *) RelinquishMagickMemory(ni_palette);
      if (*scene > 0)
        DeleteImageFromList(image);
      return(MagickFalse);
    }
  ni_pixels=(unsigned char *) RelinquishMagickMemory(ni_pixels);
  ni_palette=(unsigned char *) RelinquishMagickMemory(ni_palette);
  (*scene)++;
  return(MagickTrue);
}

/*
  Read the ToolTypes string table and decode NewIcon (IM1=/IM2=) images
  found in it.  Returns AmigaIconChunkContinue on success,
  AmigaIconChunkStop when the table is truncated (keep the images decoded
  so far) and AmigaIconChunkFatal on hard failures (exception is set).
*/
static AmigaIconChunkStatus ReadToolTypesNewIcons(Image **image,size_t *scene,
  const ImageInfo *image_info,ExceptionInfo *exception)
{
  char
    **im1_lines,
    **im2_lines,
    **tooltypes;

  MagickBooleanType
    newicon_failure,
    tooltypes_corrupt,
    tooltypes_resource_failure;

  size_t
    im1_alloc,
    im1_count,
    im2_alloc,
    im2_count,
    num_entries,
    remaining,
    tt_idx;

  unsigned int
    count_field;

  if (EOFBlob(*image) != MagickFalse)
    return(AmigaIconChunkStop);
  count_field=ReadBlobMSBLong(*image);
  if (GetRemainingBlobSize(*image,&remaining) == MagickFalse)
    return(AmigaIconChunkStop);
  if (count_field < 8)
    num_entries=0;
  else
    num_entries=(size_t) (count_field/4-1);
  if ((num_entries > remaining/4) || (num_entries > MaxAmigaIconToolTypes))
    return(AmigaIconChunkStop);
  tooltypes=(char **) NULL;
  if (num_entries > 0)
    {
      tooltypes=(char **) AcquireQuantumMemory(num_entries,
        sizeof(*tooltypes));
      if (tooltypes == (char **) NULL)
        {
          (void) ThrowMagickException(exception,GetMagickModule(),
            ResourceLimitError,"MemoryAllocationFailed","`%s'",
            (*image)->filename);
          return(AmigaIconChunkFatal);
        }
      (void) memset(tooltypes,0,num_entries*sizeof(*tooltypes));
    }
  /*
    Read all tooltype strings, collect IM1=/IM2= lines.
  */
  newicon_failure=MagickFalse;
  tooltypes_corrupt=MagickFalse;
  tooltypes_resource_failure=MagickFalse;
  im1_lines=(char **) NULL;
  im2_lines=(char **) NULL;
  im1_count=0;
  im1_alloc=0;
  im2_count=0;
  im2_alloc=0;
  for (tt_idx=0; tt_idx < num_entries; tt_idx++)
  {
    unsigned int
      text_len;

    if (EOFBlob(*image) != MagickFalse)
      break;
    text_len=ReadBlobMSBLong(*image);
    if ((GetRemainingBlobSize(*image,&remaining) == MagickFalse) ||
        (text_len == 0) || ((size_t) text_len > remaining) ||
        (text_len == UINT_MAX))
      {
        tooltypes_corrupt=MagickTrue;
        break;
      }
    tooltypes[tt_idx]=(char *) AcquireQuantumMemory((size_t) text_len+1,
      sizeof(char));
    if (tooltypes[tt_idx] == (char *) NULL)
      {
        tooltypes_resource_failure=MagickTrue;
        break;
      }
    if (ReadBlob(*image,text_len,(unsigned char *) tooltypes[tt_idx]) !=
        (ssize_t) text_len)
      {
        tooltypes[tt_idx]=(char *) RelinquishMagickMemory(tooltypes[tt_idx]);
        tooltypes[tt_idx]=(char *) NULL;
        tooltypes_corrupt=MagickTrue;
        break;
      }
    tooltypes[tt_idx][text_len]='\0';
    if (strncmp(tooltypes[tt_idx],"IM1=",4) == 0)
      {
        if (im1_count >= im1_alloc)
          {
            char
              **new_lines;

            im1_alloc=im1_alloc == 0 ? 16 : im1_alloc*2;
            new_lines=(char **) ResizeQuantumMemory(im1_lines,im1_alloc,
              sizeof(*im1_lines));
            if (new_lines == (char **) NULL)
              {
                tooltypes_resource_failure=MagickTrue;
                break;
              }
            im1_lines=new_lines;
          }
        im1_lines[im1_count++]=tooltypes[tt_idx]+4;
      }
    else if (strncmp(tooltypes[tt_idx],"IM2=",4) == 0)
      {
        if (im2_count >= im2_alloc)
          {
            char
              **new_lines;

            im2_alloc=im2_alloc == 0 ? 16 : im2_alloc*2;
            new_lines=(char **) ResizeQuantumMemory(im2_lines,im2_alloc,
              sizeof(*im2_lines));
            if (new_lines == (char **) NULL)
              {
                tooltypes_resource_failure=MagickTrue;
                break;
              }
            im2_lines=new_lines;
          }
        im2_lines[im2_count++]=tooltypes[tt_idx]+4;
      }
  }
  /*
    Decode NewIcon from IM1= and IM2= lines.
  */
  if ((tooltypes_corrupt == MagickFalse) &&
      (tooltypes_resource_failure == MagickFalse))
    {
      if ((DecodeAndRenderNewIcon(image,scene,im1_lines,im1_count,
          image_info,exception) == MagickFalse) &&
          (exception->severity >= ErrorException))
        newicon_failure=MagickTrue;
      if ((newicon_failure == MagickFalse) &&
          (DecodeAndRenderNewIcon(image,scene,im2_lines,im2_count,
          image_info,exception) == MagickFalse) &&
          (exception->severity >= ErrorException))
        newicon_failure=MagickTrue;
    }
  if (im1_lines != (char **) NULL)
    im1_lines=(char **) RelinquishMagickMemory(im1_lines);
  if (im2_lines != (char **) NULL)
    im2_lines=(char **) RelinquishMagickMemory(im2_lines);
  if (tooltypes != (char **) NULL)
    {
      for (tt_idx=0; tt_idx < num_entries; tt_idx++)
        if (tooltypes[tt_idx] != (char *) NULL)
          tooltypes[tt_idx]=(char *)
            RelinquishMagickMemory(tooltypes[tt_idx]);
      tooltypes=(char **) RelinquishMagickMemory(tooltypes);
    }
  if (tooltypes_resource_failure != MagickFalse)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),
        ResourceLimitError,"MemoryAllocationFailed","`%s'",
        (*image)->filename);
      return(AmigaIconChunkFatal);
    }
  if (tooltypes_corrupt != MagickFalse)
    return(AmigaIconChunkStop);
  if (newicon_failure != MagickFalse)
    return(AmigaIconChunkFatal);
  return(AmigaIconChunkContinue);
}

/*
  Decode and render one GlowIcon IMAG chunk.  A palette-less IMAG reuses
  the previous chunk's palette via last_pal_data and last_pal_count; a
  rendered chunk's own palette replaces the saved one.
*/
static AmigaIconChunkStatus DecodeAndRenderGlowIcon(Image **image,
  size_t *scene,const unsigned char *chunk,size_t chunk_size,
  size_t icon_width,size_t icon_height,unsigned char **last_pal_data,
  size_t *last_pal_count,const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  const unsigned char
    *render_pal;

  MagickBooleanType
    rle_complete,
    status;

  size_t
    image_size,
    num_pal_colors,
    pal_count,
    palette_size,
    pixel_count,
    render_pal_entries;

  unsigned char
    has_palette,
    has_transparent,
    imag_depth,
    imag_flags,
    image_format,
    num_colors_m1,
    *pal_data,
    palette_format,
    *pixel_data,
    transparent_color;

  if ((chunk_size < 10) || (icon_width == 0) || (icon_height == 0) ||
      (icon_width > MaxAmigaIconDimension) ||
      (icon_height > MaxAmigaIconDimension))
    return(AmigaIconChunkContinue);
  transparent_color=chunk[0];
  num_colors_m1=chunk[1];
  imag_flags=chunk[2];
  image_format=chunk[3];
  palette_format=chunk[4];
  imag_depth=chunk[5];
  image_size=(((size_t) chunk[6] << 8) | (size_t) chunk[7])+1;
  palette_size=(((size_t) chunk[8] << 8) | (size_t) chunk[9])+1;
  has_transparent=(imag_flags & 1) ? 1 : 0;
  has_palette=(imag_flags & 2) ? 1 : 0;
  num_pal_colors=(size_t) num_colors_m1+1;
  if ((imag_depth == 0) || (imag_depth > 8) || (image_size > chunk_size-10))
    return(AmigaIconChunkStop);
  /*
    Decode image pixels.
  */
  if (image_format == 1)
    {
      rle_complete=MagickFalse;
      pixel_data=DecodeAmigaRLE(chunk+10,image_size,(size_t) imag_depth,
        icon_width*icon_height,&pixel_count,&rle_complete);
      if ((pixel_data != (unsigned char *) NULL) &&
          (rle_complete == MagickFalse))
        {
          pixel_data=(unsigned char *) RelinquishMagickMemory(pixel_data);
          return(AmigaIconChunkStop);
        }
    }
  else
    {
      pixel_count=image_size;
      if (pixel_count > icon_width*icon_height)
        pixel_count=icon_width*icon_height;
      pixel_data=(unsigned char *) AcquireQuantumMemory(pixel_count+1,
        sizeof(*pixel_data));
      if (pixel_data != (unsigned char *) NULL)
        (void) memcpy(pixel_data,chunk+10,pixel_count);
    }
  if (pixel_data == (unsigned char *) NULL)
    return(AmigaIconChunkStop);
  /*
    Decode palette.
  */
  pal_data=(unsigned char *) NULL;
  pal_count=0;
  if (has_palette && (palette_size <= chunk_size-10-image_size))
    {
      if (palette_format == 1)
        {
          rle_complete=MagickFalse;
          pal_data=DecodeAmigaRLE(chunk+10+image_size,palette_size,8,
            num_pal_colors*3,&pal_count,&rle_complete);
          if ((pal_data != (unsigned char *) NULL) &&
              (rle_complete == MagickFalse))
            {
              pal_data=(unsigned char *) RelinquishMagickMemory(pal_data);
              pixel_data=(unsigned char *)
                RelinquishMagickMemory(pixel_data);
              return(AmigaIconChunkStop);
            }
        }
      else
        {
          pal_count=palette_size;
          if (pal_count > num_pal_colors*3)
            pal_count=num_pal_colors*3;
          pal_data=(unsigned char *) AcquireQuantumMemory(pal_count+1,
            sizeof(*pal_data));
          if (pal_data != (unsigned char *) NULL)
            (void) memcpy(pal_data,chunk+10+image_size,pal_count);
        }
    }
  if (pal_data != (unsigned char *) NULL)
    {
      render_pal=pal_data;
      render_pal_entries=pal_count/3;
    }
  else
    {
      render_pal=*last_pal_data;
      render_pal_entries=*last_pal_count/3;
    }
  status=AppendAmigaImage(image,icon_width,icon_height,*scene,image_info,
    exception);
  if (status != MagickFalse)
    status=RenderPalettedAmigaImage(*image,icon_width,icon_height,pixel_data,
      pixel_count,render_pal,render_pal_entries,has_transparent ? MagickTrue :
      MagickFalse,transparent_color,exception);
  pixel_data=(unsigned char *) RelinquishMagickMemory(pixel_data);
  if (status == MagickFalse)
    {
      if (pal_data != (unsigned char *) NULL)
        pal_data=(unsigned char *) RelinquishMagickMemory(pal_data);
      return(AmigaIconChunkFatal);
    }
  if (pal_data != (unsigned char *) NULL)
    {
      if (*last_pal_data != (unsigned char *) NULL)
        *last_pal_data=(unsigned char *)
          RelinquishMagickMemory(*last_pal_data);
      *last_pal_data=pal_data;
      *last_pal_count=pal_count;
    }
  (*scene)++;
  return(AmigaIconChunkContinue);
}

#if defined(MAGICKCORE_ZLIB_DELEGATE)
/*
  Decode and render one zlib-compressed ARGB chunk (OS4-style icons).
*/
static AmigaIconChunkStatus DecodeAndRenderARGBIcon(Image **image,
  size_t *scene,const unsigned char *chunk,size_t chunk_size,
  size_t icon_width,size_t icon_height,const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  ssize_t
    y;

  uLongf
    raw_size;

  unsigned char
    *argb_raw;

  if ((chunk_size <= 10) || (icon_width == 0) || (icon_height == 0) ||
      (icon_width > MaxAmigaIconDimension) ||
      (icon_height > MaxAmigaIconDimension))
    return(AmigaIconChunkContinue);
  raw_size=(uLongf) (icon_width*icon_height*4);
  argb_raw=(unsigned char *) AcquireQuantumMemory((size_t) raw_size+1,
    sizeof(*argb_raw));
  if (argb_raw == (unsigned char *) NULL)
    return(AmigaIconChunkContinue);
  if (uncompress(argb_raw,&raw_size,chunk+10,(uLong) (chunk_size-10)) != Z_OK)
    {
      argb_raw=(unsigned char *) RelinquishMagickMemory(argb_raw);
      return(AmigaIconChunkContinue);
    }
  if (AppendAmigaImage(image,icon_width,icon_height,*scene,image_info,
      exception) == MagickFalse)
    {
      argb_raw=(unsigned char *) RelinquishMagickMemory(argb_raw);
      return(AmigaIconChunkFatal);
    }
  for (y=0; y < (ssize_t) icon_height; y++)
  {
    Quantum
      *q;

    ssize_t
      x;

    q=QueueAuthenticPixels(*image,0,y,icon_width,1,exception);
    if (q == (Quantum *) NULL)
      break;
    for (x=0; x < (ssize_t) icon_width; x++)
    {
      size_t
        pi;

      pi=((size_t) y*icon_width+(size_t) x)*4;
      if (pi+3 < (size_t) raw_size)
        {
          SetPixelRed(*image,ScaleCharToQuantum(argb_raw[pi+1]),q);
          SetPixelGreen(*image,ScaleCharToQuantum(argb_raw[pi+2]),q);
          SetPixelBlue(*image,ScaleCharToQuantum(argb_raw[pi+3]),q);
          SetPixelAlpha(*image,ScaleCharToQuantum(argb_raw[pi]),q);
        }
      else
        {
          SetPixelRed(*image,0,q);
          SetPixelGreen(*image,0,q);
          SetPixelBlue(*image,0,q);
          SetPixelAlpha(*image,TransparentAlpha,q);
        }
      q+=(ptrdiff_t) GetPixelChannels(*image);
    }
    if (SyncAuthenticPixels(*image,exception) == MagickFalse)
      break;
  }
  argb_raw=(unsigned char *) RelinquishMagickMemory(argb_raw);
  if (y < (ssize_t) icon_height)
    return(AmigaIconChunkFatal);
  (*scene)++;
  return(AmigaIconChunkContinue);
}
#endif

/*
  Walk the IFF chunks of a FORM ICON (FACE, IMAG, ARGB) and render each
  image found.  Returns MagickFalse only on a fatal error; a malformed
  chunk stops the walk but keeps any images already decoded.
*/
static MagickBooleanType ParseFormIconChunks(Image **image,size_t *scene,
  const unsigned char *chunks,size_t chunks_size,const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  AmigaIconChunkStatus
    chunk_status;

  size_t
    chunk_pos,
    image_chunks,
    icon_height,
    icon_width,
    last_pal_count;

  unsigned char
    *last_pal_data;

  chunk_pos=0;
  image_chunks=0;
  icon_width=0;
  icon_height=0;
  last_pal_data=(unsigned char *) NULL;
  last_pal_count=0;
  chunk_status=AmigaIconChunkContinue;
  while (chunk_pos+8 <= chunks_size)
  {
    const unsigned char
      *chunk_id;

    size_t
      chunk_data,
      chunk_size;

    chunk_id=chunks+chunk_pos;
    chunk_size=((size_t) chunks[chunk_pos+4] << 24) |
      ((size_t) chunks[chunk_pos+5] << 16) |
      ((size_t) chunks[chunk_pos+6] << 8) |
      (size_t) chunks[chunk_pos+7];
    chunk_data=chunk_pos+8;
    if ((chunk_size == 0) || (chunk_size > chunks_size-chunk_data))
      break;
    chunk_pos=chunk_data+chunk_size;
    if (chunk_size & 1)
      chunk_pos++;  /* IFF padding */
    if (memcmp(chunk_id,"FACE",4) == 0)
      {
        if (chunk_size >= 4)
          {
            icon_width=(size_t) chunks[chunk_data]+1;
            icon_height=(size_t) chunks[chunk_data+1]+1;
          }
      }
    else if (memcmp(chunk_id,"IMAG",4) == 0)
      {
        if ((*scene >= MaxAmigaIconScenes) ||
            (image_chunks >= MaxAmigaIconImageChunks))
          chunk_status=AmigaIconChunkStop;
        else
          {
            image_chunks++;
            chunk_status=DecodeAndRenderGlowIcon(image,scene,
              chunks+chunk_data,chunk_size,icon_width,icon_height,
              &last_pal_data,&last_pal_count,image_info,exception);
          }
      }
#if defined(MAGICKCORE_ZLIB_DELEGATE)
    else if (memcmp(chunk_id,"ARGB",4) == 0)
      {
        if ((*scene >= MaxAmigaIconScenes) ||
            (image_chunks >= MaxAmigaIconImageChunks))
          chunk_status=AmigaIconChunkStop;
        else
          {
            image_chunks++;
            chunk_status=DecodeAndRenderARGBIcon(image,scene,
              chunks+chunk_data,chunk_size,icon_width,icon_height,image_info,
              exception);
          }
      }
#endif
    if (chunk_status != AmigaIconChunkContinue)
      break;
  }
  if (last_pal_data != (unsigned char *) NULL)
    last_pal_data=(unsigned char *) RelinquishMagickMemory(last_pal_data);
  return(chunk_status == AmigaIconChunkFatal ? MagickFalse : MagickTrue);
}

/*
  Finish a read whose trailing sections (text fields, tooltypes or
  DrawerData2) are truncated.  Real-world icons are often cut short after
  the image data, so keep the images already decoded and warn.
*/
static Image *FinishTruncatedWBINFO(Image *image,const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageWarning,
    "InsufficientImageDataInFile","`%s'",image_info->filename);
  if (CloseBlob(image) == MagickFalse)
    return(DestroyImageList(image));
  return(GetFirstImageInList(image));
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s W B I N F O                                                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  IsWBINFO() returns MagickTrue if the image format type, identified by
%  the magick string, is an Amiga Workbench icon file.
%
%  The format of the IsWBINFO method is:
%
%      MagickBooleanType IsWBINFO(const unsigned char *magick,
%        const size_t length)
%
*/
static MagickBooleanType IsWBINFO(const unsigned char *magick,
  const size_t length)
{
  if (length < 2)
    return(MagickFalse);
  if ((magick[0] == 0xE3) && (magick[1] == 0x10))
    return(MagickTrue);
  return(MagickFalse);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d W B I N F O I m a g e                                             %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReadWBINFOImage() reads an Amiga Workbench .info icon file and returns
%  it as one or more images.  Each icon generation (Classic, NewIcon,
%  GlowIcon, ARGB) becomes a separate image in the returned list.
%
%  The format of the ReadWBINFOImage method is:
%
%      Image *ReadWBINFOImage(const ImageInfo *image_info,
%        ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image_info: the image info.
%
%    o exception: return any errors or warnings in this structure.
%
*/
static Image *ReadWBINFOImage(const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  Image
    *image;

  MagickBooleanType
    status;

  size_t
    scene;

  unsigned int
    gadget_render,
    has_default_tool,
    has_drawer_data,
    has_tool_window,
    has_tooltypes,
    select_render,
    user_data;

  unsigned short
    magic;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"%s",
    image_info->filename);
  image=AcquireImage(image_info,exception);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFalse)
    {
      image=DestroyImageList(image);
      return((Image *) NULL);
    }
  /*
    Read 78-byte DiskObject header (big-endian).
  */
  magic=ReadBlobMSBShort(image);
  if (magic != AmigaIconMagic)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  /*
    Skip version(2) and the embedded Gadget up to gadget_render: next(4),
    left_edge(2), top_edge(2), width(2), height(2), flags(2), activation(2),
    gadget_type(2).
  */
  if (SeekBlob(image,20,SEEK_CUR) < 0)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  gadget_render=ReadBlobMSBLong(image);
  select_render=ReadBlobMSBLong(image);
  /* gadget_text(4), mutual_exclude(4), special_info(4), gadget_id(2) */
  if (SeekBlob(image,14,SEEK_CUR) < 0)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  user_data=ReadBlobMSBLong(image);
  /* End of embedded Gadget, now at offset 48: icon_type(1), padding(1) */
  if (SeekBlob(image,2,SEEK_CUR) < 0)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  has_default_tool=ReadBlobMSBLong(image);
  has_tooltypes=ReadBlobMSBLong(image);
  /* current_x(4), current_y(4) */
  if (SeekBlob(image,8,SEEK_CUR) < 0)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  has_drawer_data=ReadBlobMSBLong(image);
  has_tool_window=ReadBlobMSBLong(image);
  /* stack_size(4), now at offset 78 */
  if (SeekBlob(image,4,SEEK_CUR) < 0)
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  if (EOFBlob(image) != MagickFalse)
    ThrowReaderException(CorruptImageError,"UnexpectedEndOfFile");
  scene=0;
  /*
    Skip DrawerData (56 bytes) if present.
  */
  if (has_drawer_data)
    {
      if (SeekBlob(image,56,SEEK_CUR) < 0)
        ThrowReaderException(CorruptImageError,"ImproperImageHeader");
    }
  /*
    Read classic planar image(s).
  */
  if (gadget_render)
    {
      if (ReadAndRenderClassicImage(&image,&scene,user_data,image_info,
          exception) == MagickFalse)
        ThrowReaderException(CorruptImageError,"ImproperImageHeader");
    }
  if (select_render)
    {
      if (ReadAndRenderClassicImage(&image,&scene,user_data,image_info,
          exception) == MagickFalse)
        {
          if (scene == 0)
            ThrowReaderException(CorruptImageError,"ImproperImageHeader");
          return(FinishTruncatedWBINFO(image,image_info,exception));
        }
    }
  /*
    Skip DefaultTool if present.
  */
  if (has_default_tool)
    {
      unsigned int
        text_len;

      text_len=ReadBlobMSBLong(image);
      if (SeekBlob(image,(MagickOffsetType) text_len,SEEK_CUR) < 0)
        ThrowReaderException(CorruptImageError,"ImproperImageHeader");
    }
  /*
    Read ToolTypes and decode NewIcons if present.
  */
  if (has_tooltypes)
    {
      AmigaIconChunkStatus
        tooltypes_status;

      tooltypes_status=ReadToolTypesNewIcons(&image,&scene,image_info,
        exception);
      if (tooltypes_status == AmigaIconChunkFatal)
        return(DestroyImageList(image));
      if (tooltypes_status == AmigaIconChunkStop)
        {
          if (scene == 0)
            ThrowReaderException(CorruptImageError,
              "InsufficientImageDataInFile");
          return(FinishTruncatedWBINFO(image,image_info,exception));
        }
    }
  /*
    Skip ToolWindow if present.
  */
  if (has_tool_window)
    {
      unsigned int
        text_len;

      text_len=ReadBlobMSBLong(image);
      if (SeekBlob(image,(MagickOffsetType) text_len,SEEK_CUR) < 0)
        ThrowReaderException(CorruptImageError,"ImproperImageHeader");
    }
  /*
    Skip DrawerData2 (6 bytes) if present.
  */
  if (has_drawer_data && (user_data & 255))
    {
      if (SeekBlob(image,6,SEEK_CUR) < 0)
        ThrowReaderException(CorruptImageError,"ImproperImageHeader");
    }
  /*
    Scan for FORM ICON (GlowIcons and ARGB images).
    Read a bounded tail buffer and search for "FORM" + "ICON".
  */
  {
    size_t
      remaining;

    if ((GetRemainingBlobSize(image,&remaining) != MagickFalse) &&
        (remaining > 16) && (remaining <= MaxAmigaIconFormScanSize))
      {
        unsigned char
          *rest_data;

        rest_data=(unsigned char *) AcquireQuantumMemory(remaining,
          sizeof(*rest_data));
        if (rest_data != (unsigned char *) NULL)
          {
            ssize_t
              bytes_read;

            bytes_read=ReadBlob(image,remaining,rest_data);
            if (bytes_read > 12)
              {
                size_t
                  search_pos;

                for (search_pos=0; search_pos+12 <= (size_t) bytes_read;
                     search_pos++)
                {
                  size_t
                    form_size;

                  if ((memcmp(rest_data+search_pos,"FORM",4) != 0) ||
                      (memcmp(rest_data+search_pos+8,"ICON",4) != 0))
                    continue;
                  form_size=((size_t) rest_data[search_pos+4] << 24) |
                    ((size_t) rest_data[search_pos+5] << 16) |
                    ((size_t) rest_data[search_pos+6] << 8) |
                    (size_t) rest_data[search_pos+7];
                  if (form_size > (size_t) bytes_read-search_pos-8)
                    continue;  /* truncated FORM, skip */
                  if ((form_size >= 4) &&
                      (ParseFormIconChunks(&image,&scene,
                       rest_data+search_pos+12,form_size-4,image_info,
                       exception) == MagickFalse))
                    {
                      rest_data=(unsigned char *)
                        RelinquishMagickMemory(rest_data);
                      return(DestroyImageList(image));
                    }
                  break;  /* only process first FORM ICON */
                }
              }
            rest_data=(unsigned char *) RelinquishMagickMemory(rest_data);
          }
      }
  }

  if (scene == 0)
    ThrowReaderException(ImageError,"NoImagesWereFound");
  if (CloseBlob(image) == MagickFalse)
    return(DestroyImageList(image));
  return(GetFirstImageInList(image));
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r W B I N F O I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  RegisterWBINFOImage() adds attributes for the Amiga Workbench Icon format to
%  the list of supported formats.
%
%  The format of the RegisterWBINFOImage method is:
%
%      size_t RegisterWBINFOImage(void)
%
*/
ModuleExport size_t RegisterWBINFOImage(void)
{
  MagickInfo
    *entry;

  entry=AcquireMagickInfo("WBINFO","WBINFO","Amiga Workbench Icon");
  entry->decoder=(DecodeImageHandler *) ReadWBINFOImage;
  entry->magick=(IsImageFormatHandler *) IsWBINFO;
  entry->flags^=CoderAdjoinFlag;
  entry->flags|=CoderDecoderSeekableStreamFlag;
  entry->mime_type=ConstantString("image/x-amiga-icon");
  (void) RegisterMagickInfo(entry);
  return(MagickImageCoderSignature);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r W B I N F O I m a g e                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  UnregisterWBINFOImage() removes format registrations made by the
%  WBINFO module from the list of supported formats.
%
%  The format of the UnregisterWBINFOImage method is:
%
%      UnregisterWBINFOImage(void)
%
*/
ModuleExport void UnregisterWBINFOImage(void)
{
  (void) UnregisterMagickInfo("WBINFO");
}
