/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                        V   V  IIIII  PPPP   SSSSS                           %
%                        V   V    I    P   P  SS                              %
%                        V   V    I    PPPP    SSS                            %
%                         V V     I    P         SS                           %
%                          V    IIIII  P      SSSSS                           %
%                                                                             %
%                                                                             %
%                        Read/Write VIPS Image Format                         %
%                                                                             %
%                              Software Design                                %
%                                Dirk Lemstra                                 %
%                                 April 2014                                  %
%                                                                             %
%                                                                             %
%  Copyright 1999-2018 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://www.imagemagick.org/script/license.php                           %
%                                                                             %
%  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/attribute.h"
#include "MagickCore/blob.h"
#include "MagickCore/blob-private.h"
#include "MagickCore/cache.h"
#include "MagickCore/colorspace.h"
#include "MagickCore/colorspace-private.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/magick.h"
#include "MagickCore/memory_.h"
#include "MagickCore/monitor.h"
#include "MagickCore/monitor-private.h"
#include "MagickCore/pixel-accessor.h"
#include "MagickCore/property.h"
#include "MagickCore/quantum-private.h"
#include "MagickCore/static.h"
#include "MagickCore/string_.h"
#include "MagickCore/module.h"

/*
  Define declaractions.
*/
#define VIPS_MAGIC_LSB 0x08f2a6b6U
#define VIPS_MAGIC_MSB 0xb6a6f208U

typedef enum
{
  VIPSBandFormatNOTSET    = -1,
  VIPSBandFormatUCHAR     = 0,  /* Unsigned 8-bit int */
  VIPSBandFormatCHAR      = 1,  /* Signed 8-bit int */
  VIPSBandFormatUSHORT    = 2,  /* Unsigned 16-bit int */
  VIPSBandFormatSHORT     = 3,  /* Signed 16-bit int */
  VIPSBandFormatUINT      = 4,  /* Unsigned 32-bit int */
  VIPSBandFormatINT       = 5,  /* Signed 32-bit int */
  VIPSBandFormatFLOAT     = 6,  /* 32-bit IEEE float */
  VIPSBandFormatCOMPLEX   = 7,  /* Complex (2 floats) */
  VIPSBandFormatDOUBLE    = 8,  /* 64-bit IEEE double */
  VIPSBandFormatDPCOMPLEX = 9   /* Complex (2 doubles) */
} VIPSBandFormat;

typedef enum
{
  VIPSCodingNONE  = 0,  /* VIPS computation format */
  VIPSCodingLABQ  = 2,  /* LABQ storage format */
  VIPSCodingRAD   = 6   /* Radiance storage format */
} VIPSCoding;

typedef enum
{
  VIPSTypeMULTIBAND = 0,   /* Some multiband image */
  VIPSTypeB_W       = 1,   /* Some single band image */
  VIPSTypeHISTOGRAM = 10,  /* Histogram or LUT */
  VIPSTypeFOURIER   = 24,  /* Image in Fourier space */
  VIPSTypeXYZ       = 12,  /* CIE XYZ colour space */
  VIPSTypeLAB       = 13,  /* CIE LAB colour space */
  VIPSTypeCMYK      = 15,  /* im_icc_export() */
  VIPSTypeLABQ      = 16,  /* 32-bit CIE LAB */
  VIPSTypeRGB       = 17,  /* Some RGB */
  VIPSTypeUCS       = 18,  /* UCS(1:1) colour space */
  VIPSTypeLCH       = 19,  /* CIE LCh colour space */
  VIPSTypeLABS      = 21,  /* 48-bit CIE LAB */
  VIPSTypesRGB      = 22,  /* sRGB colour space */
  VIPSTypeYXY       = 23,  /* CIE Yxy colour space */
  VIPSTypeRGB16     = 25,  /* 16-bit RGB */
  VIPSTypeGREY16    = 26   /* 16-bit monochrome */
} VIPSType;

/*
  Forward declarations.
*/
static MagickBooleanType
  WriteVIPSImage(const ImageInfo *,Image *,ExceptionInfo *);

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s V I P S                                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  IsVIPS() returns MagickTrue if the image format type, identified by the
%  magick string, is VIPS.
%
%  The format of the IsVIPS method is:
%
%      MagickBooleanType IsVIPS(const unsigned char *magick,const size_t length)
%
%  A description of each parameter follows:
%
%    o magick: compare image format pattern against these bytes.
%
%    o length: Specifies the length of the magick string.
%
*/
static MagickBooleanType IsVIPS(const unsigned char *magick,const size_t length)
{
  if (length < 4)
    return(MagickFalse);

  if (memcmp(magick,"\010\362\246\266",4) == 0)
    return(MagickTrue);

  if (memcmp(magick,"\266\246\362\010",4) == 0)
    return(MagickTrue);

  return(MagickFalse);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d V I P S I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReadVIPSImage() reads a VIPS image file 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 ReadVIPSImage method is:
%
%      Image *ReadVIPSmage(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 inline MagickBooleanType IsSupportedCombination(
  const VIPSBandFormat format,const VIPSType type)
{
  switch(type)
  {
    case VIPSTypeB_W:
    case VIPSTypeCMYK:
    case VIPSTypeRGB:
    case VIPSTypesRGB:
      return(MagickTrue);
    case VIPSTypeGREY16:
    case VIPSTypeRGB16:
      switch(format)
      {
        case VIPSBandFormatUSHORT:
        case VIPSBandFormatSHORT:
        case VIPSBandFormatUINT:
        case VIPSBandFormatINT:
        case VIPSBandFormatFLOAT:
        case VIPSBandFormatDOUBLE:
          return(MagickTrue);
        default:
          return(MagickFalse);
      }
    default:
      return(MagickFalse);
  }
}

static inline Quantum ReadVIPSPixelNONE(Image *image,
  const VIPSBandFormat format,const VIPSType type)
{
  switch(type)
  {
    case VIPSTypeB_W:
    case VIPSTypeRGB:
      {
        unsigned char
          c;

        switch(format)
        {
          case VIPSBandFormatUCHAR:
          case VIPSBandFormatCHAR:
            c=(unsigned char) ReadBlobByte(image);
            break;
          case VIPSBandFormatUSHORT:
          case VIPSBandFormatSHORT:
            c=(unsigned char) ReadBlobShort(image);
            break;
          case VIPSBandFormatUINT:
          case VIPSBandFormatINT:
            c=(unsigned char) ReadBlobLong(image);
            break;
          case VIPSBandFormatFLOAT:
            c=(unsigned char) ReadBlobFloat(image);
            break;
          case VIPSBandFormatDOUBLE:
            c=(unsigned char) ReadBlobDouble(image);
            break;
          default:
            c=0;
            break;
        }
        return(ScaleCharToQuantum(c));
      }
    case VIPSTypeGREY16:
    case VIPSTypeRGB16:
      {
        unsigned short
          s;

        switch(format)
        {
          case VIPSBandFormatUSHORT:
          case VIPSBandFormatSHORT:
            s=(unsigned short) ReadBlobShort(image);
            break;
          case VIPSBandFormatUINT:
          case VIPSBandFormatINT:
            s=(unsigned short) ReadBlobLong(image);
            break;
          case VIPSBandFormatFLOAT:
            s=(unsigned short) ReadBlobFloat(image);
            break;
          case VIPSBandFormatDOUBLE:
            s=(unsigned short) ReadBlobDouble(image);
            break;
          default:
            s=0;
            break;
        }
        return(ScaleShortToQuantum(s));
      }
    case VIPSTypeCMYK:
    case VIPSTypesRGB:
      switch(format)
      {
        case VIPSBandFormatUCHAR:
        case VIPSBandFormatCHAR:
          return(ScaleCharToQuantum((unsigned char) ReadBlobByte(image)));
        case VIPSBandFormatUSHORT:
        case VIPSBandFormatSHORT:
          return(ScaleShortToQuantum(ReadBlobShort(image)));
        case VIPSBandFormatUINT:
        case VIPSBandFormatINT:
          return(ScaleLongToQuantum(ReadBlobLong(image)));
        case VIPSBandFormatFLOAT:
          return((Quantum) ((float) QuantumRange*(ReadBlobFloat(image)/1.0)));
        case VIPSBandFormatDOUBLE:
          return((Quantum) ((double) QuantumRange*(ReadBlobDouble(
            image)/1.0)));
        default:
          return((Quantum) 0);
      }
    default:
      return((Quantum) 0);
  }
}

static MagickBooleanType ReadVIPSPixelsNONE(Image *image,
  const VIPSBandFormat format,const VIPSType type,const unsigned int channels,
  ExceptionInfo *exception)
{
  Quantum
    pixel;

  register Quantum
    *q;

  register ssize_t
    x;

  ssize_t
    y;

  for (y = 0; y < (ssize_t) image->rows; y++)
  {
    q=GetAuthenticPixels(image,0,y,image->columns,1,exception);
    if (q == (Quantum *) NULL)
      return MagickFalse;
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      pixel=ReadVIPSPixelNONE(image,format,type);
      SetPixelRed(image,pixel,q);
      if (channels < 3)
        {
          SetPixelGreen(image,pixel,q);
          SetPixelBlue(image,pixel,q);
          if (channels == 2)
            SetPixelAlpha(image,ReadVIPSPixelNONE(image,format,type),q);
        }
      else
        {
          SetPixelGreen(image,ReadVIPSPixelNONE(image,format,type),q);
          SetPixelBlue(image,ReadVIPSPixelNONE(image,format,type),q);
          if (channels == 4)
            {
              if (image->colorspace == CMYKColorspace)
                SetPixelIndex(image,ReadVIPSPixelNONE(image,format,type),q);
              else
                SetPixelAlpha(image,ReadVIPSPixelNONE(image,format,type),q);
            }
          else if (channels == 5)
            {
              SetPixelIndex(image,ReadVIPSPixelNONE(image,format,type),q);
              SetPixelAlpha(image,ReadVIPSPixelNONE(image,format,type),q);
            }
        }
      q+=GetPixelChannels(image);
    }
    if (SyncAuthenticPixels(image,exception) == MagickFalse)
      return MagickFalse;
  }
  return(MagickTrue);
}

static Image *ReadVIPSImage(const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  char
    buffer[MagickPathExtent],
    *metadata;

  Image
    *image;

  MagickBooleanType
    status;

  ssize_t
    n;

  unsigned int
    channels,
    marker;

  VIPSBandFormat
    format;

  VIPSCoding
    coding;

  VIPSType
    type;

  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  if (image_info->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
      image_info->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);

  image=AcquireImage(image_info,exception);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFalse)
    {
      image=DestroyImageList(image);
      return((Image *) NULL);
    }
  marker=ReadBlobLSBLong(image);
  if (marker == VIPS_MAGIC_LSB)
    image->endian=LSBEndian;
  else if (marker == VIPS_MAGIC_MSB)
    image->endian=MSBEndian;
  else
    ThrowReaderException(CorruptImageError,"ImproperImageHeader");
  image->columns=(size_t) ReadBlobLong(image);
  image->rows=(size_t) ReadBlobLong(image);
  status=SetImageExtent(image,image->columns,image->rows,exception);
  if (status == MagickFalse)
    return(DestroyImageList(image));
  channels=ReadBlobLong(image);
  (void) ReadBlobLong(image); /* Legacy */
  format=(VIPSBandFormat) ReadBlobLong(image);
  switch(format)
  {
    case VIPSBandFormatUCHAR:
    case VIPSBandFormatCHAR:
      image->depth=8;
      break;
    case VIPSBandFormatUSHORT:
    case VIPSBandFormatSHORT:
      image->depth=16;
      break;
    case VIPSBandFormatUINT:
    case VIPSBandFormatINT:
    case VIPSBandFormatFLOAT:
      image->depth=32;
      break;
    case VIPSBandFormatDOUBLE:
      image->depth=64;
      break;
    default:
    case VIPSBandFormatCOMPLEX:
    case VIPSBandFormatDPCOMPLEX:
    case VIPSBandFormatNOTSET:
      ThrowReaderException(CoderError,"Unsupported band format");
  }
  coding=(VIPSCoding) ReadBlobLong(image);
  type=(VIPSType) ReadBlobLong(image);
  switch(type)
  {
    case VIPSTypeCMYK:
      SetImageColorspace(image,CMYKColorspace,exception);
      if (channels == 5)
        image->alpha_trait=BlendPixelTrait;
      break;
    case VIPSTypeB_W:
    case VIPSTypeGREY16:
      SetImageColorspace(image,GRAYColorspace,exception);
      if (channels == 2)
        image->alpha_trait=BlendPixelTrait;
      break;
    case VIPSTypeRGB:
    case VIPSTypeRGB16:
      SetImageColorspace(image,RGBColorspace,exception);
      if (channels == 4)
        image->alpha_trait=BlendPixelTrait;
      break;
    case VIPSTypesRGB:
      SetImageColorspace(image,sRGBColorspace,exception);
      if (channels == 4)
        image->alpha_trait=BlendPixelTrait;
      break;
    default:
    case VIPSTypeFOURIER:
    case VIPSTypeHISTOGRAM:
    case VIPSTypeLAB:
    case VIPSTypeLABS:
    case VIPSTypeLABQ:
    case VIPSTypeLCH:
    case VIPSTypeMULTIBAND:
    case VIPSTypeUCS:
    case VIPSTypeXYZ:
    case VIPSTypeYXY:
      ThrowReaderException(CoderError,"Unsupported colorspace");
  }
  image->units=PixelsPerCentimeterResolution;
  image->resolution.x=ReadBlobFloat(image)*10;
  image->resolution.y=ReadBlobFloat(image)*10;
  /*
    Legacy, offsets, future
  */
  (void) ReadBlobLongLong(image);
  (void) ReadBlobLongLong(image);
  (void) ReadBlobLongLong(image);
  if (image_info->ping != MagickFalse)
    return(image);
  if (IsSupportedCombination(format,type) == MagickFalse)
    ThrowReaderException(CoderError,
      "Unsupported combination of band format and colorspace");
  if (channels == 0 || channels > 5)
    ThrowReaderException(CoderError,"Unsupported number of channels");
  if (coding == VIPSCodingNONE)
    status=ReadVIPSPixelsNONE(image,format,type,channels,exception);
  else
    ThrowReaderException(CoderError,"Unsupported coding");
  metadata=(char *) NULL;
  while ((n=ReadBlob(image,MagickPathExtent-1,(unsigned char *) buffer)) != 0)
  {
    buffer[n]='\0';
    if (metadata == (char *) NULL)
      metadata=ConstantString(buffer);
    else
      (void) ConcatenateString(&metadata,buffer);
  }
  if (metadata != (char *) NULL)
    { 
      SetImageProperty(image,"vips:metadata",metadata,exception);
      metadata=(char *) RelinquishMagickMemory(metadata);
    }
  (void) CloseBlob(image);
  if (status == MagickFalse)
    return((Image *) NULL);
  return(image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r V I P S I m a g e                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  RegisterVIPSmage() adds attributes for the VIPS 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 RegisterVIPSImage method is:
%
%      size_t RegisterVIPSImage(void)
%
*/
ModuleExport size_t RegisterVIPSImage(void)
{
  MagickInfo
    *entry;

  entry=AcquireMagickInfo("VIPS","VIPS","VIPS image");
  entry->decoder=(DecodeImageHandler *) ReadVIPSImage;
  entry->encoder=(EncodeImageHandler *) WriteVIPSImage;
  entry->magick=(IsImageFormatHandler *) IsVIPS;
  entry->flags|=CoderEndianSupportFlag;
  (void) RegisterMagickInfo(entry);
  return(MagickImageCoderSignature);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r V I P S I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  UnregisterVIPSImage() removes format registrations made by the
%  VIPS module from the list of supported formats.
%
%  The format of the UnregisterVIPSImage method is:
%
%      UnregisterVIPSImage(void)
%
*/
ModuleExport void UnregisterVIPSImage(void)
{
  (void) UnregisterMagickInfo("VIPS");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e V I P S I m a g e                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  WriteVIPSImage() writes an image to a file in VIPS image format.
%
%  The format of the WriteVIPSImage method is:
%
%      MagickBooleanType WriteVIPSImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows.
%
%    o image_info: the image info.
%
%    o image:  The image.
%
*/

static inline void WriteVIPSPixel(Image *image, const Quantum value)
{
  if (image->depth == 16)
    (void) WriteBlobShort(image,ScaleQuantumToShort(value));
  else
    (void) WriteBlobByte(image,ScaleQuantumToChar(value));
}

static MagickBooleanType WriteVIPSImage(const ImageInfo *image_info,
  Image *image,ExceptionInfo *exception)
{
  const char
    *metadata;

  MagickBooleanType
    status;

  register const Quantum
    *p;

  register ssize_t
    x;

  ssize_t
    y;

  unsigned int
    channels;

  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);

  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
  if (status == MagickFalse)
    return(status);
  if (image->endian == LSBEndian)
    (void) WriteBlobLSBLong(image,VIPS_MAGIC_LSB);
  else
    (void) WriteBlobLSBLong(image,VIPS_MAGIC_MSB);
  (void) WriteBlobLong(image,(unsigned int) image->columns);
  (void) WriteBlobLong(image,(unsigned int) image->rows);
  (void) SetImageStorageClass(image,DirectClass,exception);
  channels=image->alpha_trait != UndefinedPixelTrait ? 4 : 3;
  if (SetImageGray(image,exception) != MagickFalse)
    channels=image->alpha_trait != UndefinedPixelTrait ? 2 : 1;
  else if (image->colorspace == CMYKColorspace)
    channels=image->alpha_trait != UndefinedPixelTrait ? 5 : 4;
  (void) WriteBlobLong(image,channels);
  (void) WriteBlobLong(image,0);
  if (image->depth == 16)
    (void) WriteBlobLong(image,(unsigned int) VIPSBandFormatUSHORT);
  else
    {
      image->depth=8;
      (void) WriteBlobLong(image,(unsigned int) VIPSBandFormatUCHAR);
    }
  (void) WriteBlobLong(image,VIPSCodingNONE);
  switch(image->colorspace)
  {
    case CMYKColorspace:
      (void) WriteBlobLong(image,VIPSTypeCMYK);
      break;
    case GRAYColorspace:
      if (image->depth == 16)
        (void) WriteBlobLong(image, VIPSTypeGREY16);
      else
        (void) WriteBlobLong(image, VIPSTypeB_W);
      break;
    case LabColorspace:
      (void) WriteBlobLong(image,VIPSTypeLAB);
      break;
    case LCHColorspace:
      (void) WriteBlobLong(image,VIPSTypeLCH);
      break;
    case RGBColorspace:
      if (image->depth == 16)
        (void) WriteBlobLong(image, VIPSTypeRGB16);
      else
        (void) WriteBlobLong(image, VIPSTypeRGB);
      break;
    case XYZColorspace:
      (void) WriteBlobLong(image,VIPSTypeXYZ);
      break;
    default:
    case sRGBColorspace:
      (void) SetImageColorspace(image,sRGBColorspace,exception);
      (void) WriteBlobLong(image,VIPSTypesRGB);
      break;
  }
  if (image->units == PixelsPerCentimeterResolution)
    {
      (void) WriteBlobFloat(image,(image->resolution.x / 10));
      (void) WriteBlobFloat(image,(image->resolution.y / 10));
    }
  else if (image->units == PixelsPerInchResolution)
    {
      (void) WriteBlobFloat(image,(image->resolution.x / 25.4));
      (void) WriteBlobFloat(image,(image->resolution.y / 25.4));
    }
  else
    {
      (void) WriteBlobLong(image,0);
      (void) WriteBlobLong(image,0);
    }
  /*
    Legacy, Offsets, Future
  */
  for (y=0; y < 24; y++)
    (void) WriteBlobByte(image,0);
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
    if (p == (const Quantum *) NULL)
      break;
    for (x=0; x < (ssize_t) image->columns; x++)
    {
      WriteVIPSPixel(image,GetPixelRed(image,p));
      if (channels == 2)
        WriteVIPSPixel(image,GetPixelAlpha(image,p));
      else
        {
          WriteVIPSPixel(image,GetPixelGreen(image,p));
          WriteVIPSPixel(image,GetPixelBlue(image,p));
          if (channels >= 4)
            {
              if (image->colorspace == CMYKColorspace)
                WriteVIPSPixel(image,GetPixelIndex(image,p));
              else
                WriteVIPSPixel(image,GetPixelAlpha(image,p));
            }
          else if (channels == 5)
            {
               WriteVIPSPixel(image,GetPixelIndex(image,p));
               WriteVIPSPixel(image,GetPixelAlpha(image,p));
            }
        }
      p+=GetPixelChannels(image);
    }
  }
  metadata=GetImageProperty(image,"vips:metadata",exception);
  if (metadata != (const char*) NULL)
    WriteBlobString(image,metadata);
  (void) CloseBlob(image);
  return(status);
}
