/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                        PPPP    AAA   L      M   M                           %
%                        P   P  A   A  L      MM MM                           %
%                        PPPP   AAAAA  L      M M M                           %
%                        P      A   A  L      M   M                           %
%                        P      A   A  LLLLL  M   M                           %
%                                                                             %
%                                                                             %
%                    Read/Write ImageMagick Image Format.                     %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                           Christopher R. Hawks                              %
%                    based on pnmtopalm by Bill Janssen                       % 
%                       and ppmtoTbmp by Ian Goldberg                         % 
%                                 Dec 2001                                    % 
%                                                                             %
%                                                                             %
%  Copyright (C) 2001 ImageMagick Studio, a non-profit organization dedicated %
%  to making software imaging solutions freely available.                     %
%                                                                             %
%  Permission is hereby granted, free of charge, to any person obtaining a    %
%  copy of this software and associated documentation files ("ImageMagick"),  %
%  to deal in ImageMagick without restriction, including without limitation   %
%  the rights to use, copy, modify, merge, publish, distribute, sublicense,   %
%  and/or sell copies of ImageMagick, and to permit persons to whom the       %
%  ImageMagick is furnished to do so, subject to the following conditions:    %
%                                                                             %
%  The above copyright notice and this permission notice shall be included in %
%  all copies or substantial portions of ImageMagick.                         %
%                                                                             %
%  The software is provided "as is", without warranty of any kind, express or %
%  implied, including but not limited to the warranties of merchantability,   %
%  fitness for a particular purpose and noninfringement.  In no event shall   %
%  ImageMagick Studio be liable for any claim, damages or other liability,    %
%  whether in an action of contract, tort or otherwise, arising from, out of  %
%  or in connection with ImageMagick or the use or other dealings in          %
%  ImageMagick.                                                               %
%                                                                             %
%  Except as contained in this notice, the name of the ImageMagick Studio     %
%  shall not be used in advertising or otherwise to promote the sale, use or  %
%  other dealings in ImageMagick without prior written authorization from the %
%  ImageMagick Studio.                                                        %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

// 4 space tabs

/*
  Include declarations.
*/
#include "magick.h"
#include "defines.h"

/*
  Forward declarations.
*/
static unsigned int
  WritePALMImage(const ImageInfo *,Image *);

#define MIN(a,b) (a<b?a:b)
#define PALM_IS_COMPRESSED_FLAG		0x8000
#define PALM_HAS_COLORMAP_FLAG		0x4000
#define PALM_HAS_TRANSPARENCY_FLAG	0x2000
#define PALM_IS_INDIRECT			0x1000
#define PALM_IS_FOR_SCREEN			0x0800
#define PALM_IS_DIRECT_COLOR		0x0400
#define PALM_HAS_FOUR_BYTE_FIELD	0x0200

#define PALM_COMPRESSION_SCANLINE	0x00
#define PALM_COMPRESSION_RLE		0x01
#define PALM_COMPRESSION_NONE		0xFF



/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d P A L M I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ReadPALMImage reads an image of raw bites in LSB order and returns
%  it.  It allocates the memory necessary for the new Image structure and
%  returns a pointer to the new image.
%
%  The format of the ReadPALMImage method is:
%
%      Image *ReadPALMImage(const ImageInfo *image_info,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image:  Method ReadPALMImage returns a pointer to the image after
%      reading.  A null image is returned if there is a memory shortage or
%      if the image cannot be read.
%
%    o image_info: Specifies a pointer to an ImageInfo structure.
%
%    o exception: return any errors or warnings in this structure.
%
%
*/
static Image *ReadPALMImage(const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  Image
    *image;

  IndexPacket
    index;

  int
    y;

  register IndexPacket
    *indexes;

  register int
    x;

  register PixelPacket
    *q;

  unsigned char
    *lastrow,
    *one_row,
    *ptr;

  unsigned int
    status,
    i,
    bytes_per_row,
    flags,
    bpp,
    version,
    nextDepthOffset,
    transparentIndex,
    compressionType,
    pad,
    size,
    byte,
    count,
    mask,
    bit;

  /*
    Open image file.
  */
  image=AllocateImage(image_info);
  status=OpenBlob(image_info,image,ReadBinaryType);
  if (status == False)
    ThrowReaderException(FileOpenWarning,"Unable to open file",image);

  image->columns = ReadBlobMSBShort(image);
  image->rows = ReadBlobMSBShort(image);
  bytes_per_row = ReadBlobMSBShort(image);
  flags = ReadBlobMSBShort(image);
  bpp = ReadBlobByte(image);
  version = ReadBlobByte(image);
  nextDepthOffset = ReadBlobMSBShort(image);
  transparentIndex = ReadBlobByte(image);
  compressionType = ReadBlobByte(image);
  pad = ReadBlobMSBShort(image);
  if(flags & PALM_IS_COMPRESSED_FLAG)		/* compressed size */
    {
    if(flags & PALM_HAS_FOUR_BYTE_FIELD)	/* big size */
      size = ReadBlobMSBLong(image);
    else
      size = ReadBlobMSBShort(image);
    }

  image->storage_class=PseudoClass;
  image->depth = 8;

  one_row = (unsigned char *) AcquireMemory(bytes_per_row);
  if (one_row == (unsigned char *) NULL)
    ThrowReaderException(ResourceLimitWarning,"Memory allocation failed",image);
  if (compressionType == PALM_COMPRESSION_SCANLINE)
    lastrow = (unsigned char *) AcquireMemory(bytes_per_row);

  /*
    Initialize image colormap.
  */
  if (!AllocateImageColormap(image,1 << bpp))
    ThrowReaderException(ResourceLimitWarning,"Memory allocation failed",image);

  mask = (1 << bpp) - 1;

  for(y = 0; y < image->rows; y++)
    {
    if (compressionType == PALM_COMPRESSION_RLE)
      {
      image->compression = RunlengthEncodedCompression;
      for (i = 0; i < bytes_per_row; )
        {
        count = ReadBlobByte(image);
        byte = ReadBlobByte(image);
        memset(one_row + i, byte, count);
        i += count;
        }
      }
    else
    if (compressionType == PALM_COMPRESSION_SCANLINE)
      {
      image->compression = FaxCompression;
      for (i = 0; i < bytes_per_row; i += 8)
        {
        count = ReadBlobByte(image);
        byte = MIN(bytes_per_row - i, 8);
        for (bit = 0; bit < byte; bit++)
          {
          if (y == 0 || count & (1 << (7 - bit)))
            one_row[i + bit] = ReadBlobByte(image);
          else
            one_row[i + bit] = lastrow[i + bit];
          }
        }
      memcpy(lastrow, one_row, bytes_per_row);
      }
    else
      {
      image->compression = NoCompression;
      (void) ReadBlob(image, bytes_per_row, one_row);
      }

    ptr = one_row;
    q = SetImagePixels(image,0,y,image->columns,1);
    if (q == (PixelPacket *) NULL)
      break;
    indexes=GetIndexes(image);
    bit = 8 - bpp;
    for(x = 0; x < image->columns; x++)
      {
      index = mask - (((*ptr) & (mask << bit)) >> bit);
      indexes[x] = index;
      *q++=image->colormap[index];
      if (!bit)
        {
        ++ptr;
        bit = 8 - bpp;
        }
      else
        {
        bit -= bpp;
        }
      }
    }
  LiberateMemory((void **) &one_row);
  CloseBlob(image);
  return(image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r P A L M I m a g e                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RegisterPALMImage adds attributes for the PALM image format to
%  the list of supported formats.  The attributes include the image format
%  tag, a method to read and/or write the format, whether the format
%  supports the saving of more than one frame to the same file or blob,
%  whether the format supports native in-memory I/O, and a brief
%  description of the format.
%
%  The format of the RegisterPALMImage method is:
%
%      RegisterPALMImage(void)
%
*/
ModuleExport void RegisterPALMImage(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("PALM");
  entry->decoder=ReadPALMImage;
  entry->encoder=WritePALMImage;
  entry->adjoin=False;
  entry->description=
    AllocateString("Internal picture format for the Palm organizer.");
  entry->module=AllocateString("PALM");
  RegisterMagickInfo(entry);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r P A L M I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method UnregisterPALMImage removes format registrations made by the
%  PALM module from the list of supported formats.
%
%  The format of the UnregisterPALMImage method is:
%
%      UnregisterPALMImage(void)
%
*/
ModuleExport void UnregisterPALMImage(void)
{
  UnregisterMagickInfo("PALM");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e P A L M I m a g e                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method WritePALMImage writes an image of raw bits in LSB order to a file.
%
%  The format of the WritePALMImage method is:
%
%      unsigned int WritePALMImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows.
%
%    o status: Method WritePALMImage return True if the image is written.
%      False is returned is there is a memory shortage or if the image file
%      fails to write.
%
%    o image_info: Specifies a pointer to an ImageInfo structure.
%
%    o image:  A pointer to a Image structure.
%
%
*/
static unsigned int WritePALMImage(const ImageInfo *image_info,Image *image)
  {
  int
    y;

  register IndexPacket
    *indexes;

  register int
    x;

  register PixelPacket
    *p;

  unsigned char
    version,
    bit,
    byte,
    color,
    *lastrow,
    *one_row,
    *ptr;

  unsigned int
    status,
    count,
    bpp,
    bytes_per_row;

  unsigned short
    flags = 0;

  /*
    Open output image file.
  */
  status=OpenBlob(image_info,image,WriteBinaryType);
  if (status == False)
    ThrowWriterException(FileOpenWarning,"Unable to open file",image);
  TransformRGBImage(image,RGBColorspace);

  for (bpp = 1;  (1 << bpp) < image->colors;  bpp *= 2)
  	  ;

  if(image->colors < 17)	/* Use gray scale */
    {
    /*
      Convert image to a grayscale image.
    */
    if (!IsGrayImage(image))
      {
      QuantizeInfo quantize_info;

      GetQuantizeInfo(&quantize_info);
      quantize_info.number_colors = image->colors;
      quantize_info.dither = image_info->dither;
      quantize_info.colorspace = GRAYColorspace;
      (void) QuantizeImage(&quantize_info,image);
      }
    (void) SortColormapByIntensity(image);

    /* Write Tbmp header. */
    (void) WriteBlobMSBShort(image, image->columns);	/* width */
    (void) WriteBlobMSBShort(image, image->rows );	/* height */
    /* bytes per row - always a word boundary */
    bytes_per_row = ((image->columns + (16 / bpp -1)) / (16 / bpp)) * 2;
    (void) WriteBlobMSBShort(image, bytes_per_row);
    if(image->compression == RunlengthEncodedCompression
        || image->compression == FaxCompression)
      flags |= PALM_IS_COMPRESSED_FLAG;
    if(bytes_per_row * image->rows > 48000 && flags & PALM_IS_COMPRESSED_FLAG)
      flags |= PALM_HAS_FOUR_BYTE_FIELD;
    (void) WriteBlobMSBShort(image, flags);
    (void) WriteBlobByte(image,bpp);
    if(bpp > 1)
      version = 1;
    if(image->compression == RunlengthEncodedCompression
        || image->compression == FaxCompression)
      version = 2;
    (void) WriteBlobByte(image,version);
    (void) WriteBlobMSBShort(image, 0);	/* offset */
    (void) WriteBlobByte(image,0);	/* trans index */
    if(image->compression == RunlengthEncodedCompression)
      (void) WriteBlobByte(image,PALM_COMPRESSION_RLE);
    else
    if(image->compression == FaxCompression)
      (void) WriteBlobByte(image,PALM_COMPRESSION_SCANLINE);
    else
      (void) WriteBlobByte(image,PALM_COMPRESSION_NONE);
    (void) WriteBlobMSBShort(image, 0);	/* reserved */
    if(flags & PALM_IS_COMPRESSED_FLAG)
      {
      if(flags & PALM_HAS_FOUR_BYTE_FIELD)
        (void) WriteBlobMSBLong(image, 0);	/* Store compressed size here */
      else
        (void) WriteBlobMSBShort(image, 0);	/* Store compressed size here */
      }
    one_row = (unsigned char *) AcquireMemory(bytes_per_row);
    if (one_row == (unsigned char *) NULL)
      ThrowWriterException(ResourceLimitWarning,
                                 "Memory allocation failed", image);
    if (image->compression == FaxCompression)
      lastrow = (unsigned char *) AcquireMemory(bytes_per_row);
    if (lastrow == (unsigned char *) NULL)
      ThrowWriterException(ResourceLimitWarning,
                                 "Memory allocation failed", image);

    for (y=0; y < (int) image->rows; y++)
      {
      ptr = one_row;
      memset(ptr, 0, bytes_per_row);
      p=GetImagePixels(image,0,y,image->columns,1);
      if (p == (PixelPacket *) NULL)
        break;
      indexes=GetIndexes(image);
      byte = 0x00;
      bit = 8 - bpp;
      for (x=0; x < (int) image->columns; x++)
        {
        /* Make sure we use the entire colorspace for bpp */
        color = indexes[x] * ((1 << bpp) - 1) / (image->colors - 1);
        byte |= color << bit;
        if (bit == 0)
          {
          *ptr++ = byte;
          byte = 0x00;
          bit = 8 - bpp;
          }
        else
          {
          bit -= bpp;
          }
        }
      if ((image->columns % (8 / bpp)) != 0) /* Handle case of partial byte */
        *ptr++ = byte;

      if(image->compression == RunlengthEncodedCompression)
        {
        x = 0;
        while(x < bytes_per_row)
          {
          byte = one_row[x];
          count = 1;
          while(one_row[++x] == byte && count < 255 && x < bytes_per_row)
            count++;
          (void) WriteBlobByte(image, count);
          (void) WriteBlobByte(image, byte);
          }
        }
      else
      if(image->compression == FaxCompression)	/* Scanline really */
        {
        char tmpbuf[8];
        char *tptr;

        for (x = 0;  x < bytes_per_row;  x += 8)
          {
          tptr = tmpbuf;
          for(bit = 0, byte = 0; bit < MIN(8, bytes_per_row - x); bit++)
            {
            if((y == 0) || (lastrow[x + bit] != one_row[x + bit]))
              {
              byte |= (1 << (7 - bit));
              *tptr++ = one_row[x + bit];
	          }
	        }
          (void) WriteBlobByte(image, byte);
          (void) WriteBlob(image, tptr - tmpbuf, tmpbuf);
	      }
	    memcpy (lastrow, one_row, bytes_per_row);
        }
      else
        {
        (void) WriteBlob(image, bytes_per_row, one_row);
        }
      }
    if(flags & PALM_IS_COMPRESSED_FLAG)
      {
      if(flags & PALM_HAS_FOUR_BYTE_FIELD)
      count = SizeBlob(image);	/* compressed size */
      SeekBlob(image, 16, SEEK_SET);
      if(flags & PALM_HAS_FOUR_BYTE_FIELD)
        (void) WriteBlobMSBLong(image, count - 16);
      else
        (void) WriteBlobMSBShort(image, count - 16);
      }
    CloseBlob(image);
    LiberateMemory((void **) &one_row);
    if (image->compression == FaxCompression)
      LiberateMemory((void **) &lastrow);
    return(True);
    }
  }
