Diff for /parser3/src/classes/image.C between versions 1.85 and 1.181

version 1.85, 2002/11/28 08:38:14 version 1.181, 2020/12/23 15:01:13
Line 1 Line 1
 /** @file  /** @file
         Parser: @b image parser class.          Parser: @b image parser class.
   
         Copyright(c) 2001, 2002 ArtLebedev Group (http://www.artlebedev.com)          Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
 */  */
   
 static const char* IDENT_IMAGE_C="$Date$";  
   
 /*  /*
         jpegsize: gets the width and height (in pixels) of a jpeg file          jpegsize: gets the width and height (in pixels) of a jpeg file
         Andrew Tong, werdna@ugcs.caltech.edu           February 14, 1995          Andrew Tong, werdna@ugcs.caltech.edu           February 14, 1995
Line 17  static const char* IDENT_IMAGE_C="$Date$ Line 15  static const char* IDENT_IMAGE_C="$Date$
 */  */
   
 #include "pa_config_includes.h"  #include "pa_config_includes.h"
   
 #include "gif.h"  #include "gif.h"
   
   #include "pa_vmethod_frame.h"
 #include "pa_common.h"  #include "pa_common.h"
 #include "pa_request.h"  #include "pa_request.h"
 #include "pa_vfile.h"  #include "pa_vfile.h"
 #include "pa_vimage.h"  #include "pa_vimage.h"
   #include "pa_vdate.h"
   #include "pa_table.h"
   #include "pa_charsets.h"
   
   volatile const char * IDENT_IMAGE_C="$Id$";
   
   // defines
   
   static const String spacebar_width_name("space");
   static const String monospace_width_name("width");
   static const String letter_spacing_name("spacing");
   
 // class  // class
   
 class MImage : public Methoded {  class MImage: public Methoded {
 public: // VStateless_class  public: // VStateless_class
         Value *create_new_value(Pool& pool) { return new(pool) VImage(pool); }          Value* create_new_value(Pool&) { return new VImage(); }
   
 public:  public:
         MImage(Pool& pool);          MImage();
   };
   
 public: // Methoded  // globals
         bool used_directly() { return true; }  
   
 };  DECLARE_CLASS_VAR(image, new MImage);
   
 // helpers  // helpers
   
 #ifndef DOXYGEN  #define EXIF_TAG(tag, name) put(tag, #name);
   
   /// value of exif tag -> it's value
   class EXIF_tag_value2name: public Hash<int, const char*> {
   public:
           EXIF_tag_value2name() {
           // image JPEG Exif
                   // Tags used by IFD0 (main image)
                   EXIF_TAG(0x010e,        ImageDescription);
                   EXIF_TAG(0x010f,        Make);
                   EXIF_TAG(0x0110,        Model);
                   EXIF_TAG(0x0112,        Orientation);
                   EXIF_TAG(0x011a,        XResolution);
                   EXIF_TAG(0x011b,        YResolution);
                   EXIF_TAG(0x0128,        ResolutionUnit);
                   EXIF_TAG(0x0131,        Software);
                   EXIF_TAG(0x0132,        DateTime);
                   EXIF_TAG(0x013e,        WhitePoint);
                   EXIF_TAG(0x013f,        PrimaryChromaticities);
                   EXIF_TAG(0x0211,        YCbCrCoefficients);
                   EXIF_TAG(0x0213,        YCbCrPositioning);
                   EXIF_TAG(0x0214,        ReferenceBlackWhite);
                   EXIF_TAG(0x8298,        Copyright);
                   EXIF_TAG(0x8769,        ExifOffset);
                   // Tags used by Exif SubIFD
                   EXIF_TAG(0x829a,        ExposureTime);
                   EXIF_TAG(0x829d,        FNumber);
                   EXIF_TAG(0x8822,        ExposureProgram);
                   EXIF_TAG(0x8827,        ISOSpeedRatings);
                   EXIF_TAG(0x9000,        ExifVersion);
                   EXIF_TAG(0x9003,        DateTimeOriginal);
                   EXIF_TAG(0x9004,        DateTimeDigitized);
                   EXIF_TAG(0x9101,        ComponentsConfiguration);
                   EXIF_TAG(0x9102,        CompressedBitsPerPixel);
                   EXIF_TAG(0x9201,        ShutterSpeedValue);
                   EXIF_TAG(0x9202,        ApertureValue);
                   EXIF_TAG(0x9203,        BrightnessValue);
                   EXIF_TAG(0x9204,        ExposureBiasValue);
                   EXIF_TAG(0x9205,        MaxApertureValue);
                   EXIF_TAG(0x9206,        SubjectDistance);
                   EXIF_TAG(0x9207,        MeteringMode);
                   EXIF_TAG(0x9208,        LightSource);
                   EXIF_TAG(0x9209,        Flash);
                   EXIF_TAG(0x920a,        FocalLength);
                   EXIF_TAG(0x927c,        MakerNote);
                   EXIF_TAG(0x9286,        UserComment);
                   EXIF_TAG(0x9290,        SubsecTime);
                   EXIF_TAG(0x9291,        SubsecTimeOriginal);
                   EXIF_TAG(0x9292,        SubsecTimeDigitized);
                   EXIF_TAG(0xa000,        FlashPixVersion);
                   EXIF_TAG(0xa001,        ColorSpace);
                   EXIF_TAG(0xa002,        ExifImageWidth);
                   EXIF_TAG(0xa003,        ExifImageHeight);
                   EXIF_TAG(0xa004,        RelatedSoundFile);
                   EXIF_TAG(0xa005,        ExifInteroperabilityOffset);
                   EXIF_TAG(0xa20e,        FocalPlaneXResolution);
                   EXIF_TAG(0xa20f,        FocalPlaneYResolution);
                   EXIF_TAG(0xa210,        FocalPlaneResolutionUnit);
                   EXIF_TAG(0xa215,        ExposureIndex);
                   EXIF_TAG(0xa217,        SensingMethod);
                   EXIF_TAG(0xa300,        FileSource);
                   EXIF_TAG(0xa301,        SceneType);
                   EXIF_TAG(0xa302,        CFAPattern);
                   // Misc Tags
                   EXIF_TAG(0x00fe,        NewSubfileType);
                   EXIF_TAG(0x00ff,        SubfileType);
                   EXIF_TAG(0x012d,        TransferFunction);
                   EXIF_TAG(0x013b,        Artist);
                   EXIF_TAG(0x013d,        Predictor);
                   EXIF_TAG(0x0142,        TileWidth);
                   EXIF_TAG(0x0143,        TileLength);
                   EXIF_TAG(0x0144,        TileOffsets);
                   EXIF_TAG(0x0145,        TileByteCounts);
                   EXIF_TAG(0x014a,        SubIFDs);
                   EXIF_TAG(0x015b,        JPEGTables);
                   EXIF_TAG(0x828d,        CFARepeatPatternDim);
                   EXIF_TAG(0x828e,        CFAPattern);
                   EXIF_TAG(0x828f,        BatteryLevel);
                   EXIF_TAG(0x83bb,        IPTC/NAA);
                   EXIF_TAG(0x8773,        InterColorProfile);
                   EXIF_TAG(0x8824,        SpectralSensitivity);
                   //EXIF_TAG(0x8825,      GPSInfo);
                   EXIF_TAG(0x8828,        OECF);
                   EXIF_TAG(0x8829,        Interlace);
                   EXIF_TAG(0x882a,        TimeZoneOffset);
                   EXIF_TAG(0x882b,        SelfTimerMode);
                   EXIF_TAG(0x920b,        FlashEnergy);
                   EXIF_TAG(0x920c,        SpatialFrequencyResponse);
                   EXIF_TAG(0x920d,        Noise);
                   EXIF_TAG(0x9211,        ImageNumber);
                   EXIF_TAG(0x9212,        SecurityClassification);
                   EXIF_TAG(0x9213,        ImageHistory);
                   EXIF_TAG(0x9214,        SubjectLocation);
                   EXIF_TAG(0x9215,        ExposureIndex);
                   EXIF_TAG(0x9216,        TIFF/EPStandardID);
                   EXIF_TAG(0xa20b,        FlashEnergy);
                   EXIF_TAG(0xa20c,        SpatialFrequencyResponse);
                   EXIF_TAG(0xa214,        SubjectLocation);
   
                   // additional things added by misha@
                   EXIF_TAG(0x0100,        ImageWidth);
                   EXIF_TAG(0x0101,        ImageLength);
                   EXIF_TAG(0x0102,        BitsPerSample);
                   EXIF_TAG(0x0103,        Compression);
                   EXIF_TAG(0x0106,        PhotometricInterpretation);
                   EXIF_TAG(0x010a,        FillOrder);
                   EXIF_TAG(0x010d,        DocumentName);
                   EXIF_TAG(0x0111,        StripOffsets);
                   EXIF_TAG(0x0115,        SamplesPerPixel);
                   EXIF_TAG(0x0116,        RowsPerStrip);
                   EXIF_TAG(0x0117,        StripByteCounts);
                   EXIF_TAG(0x011c,        PlanarConfiguration);
                   EXIF_TAG(0x0156,        TransferRange);
                   EXIF_TAG(0x0200,        JPEGProc);
                   EXIF_TAG(0x0201,        JPEGInterchangeFormat);
                   EXIF_TAG(0x0202,        JPEGInterchangeFormatLength);
                   EXIF_TAG(0x0212,        YCbCrSubSampling);
                   EXIF_TAG(0xa401,        CustomRendered);
                   EXIF_TAG(0xa402,        ExposureMode);
                   EXIF_TAG(0xa403,        WhiteBalance);
                   EXIF_TAG(0xa404,        DigitalZoomRatio);
                   EXIF_TAG(0xa405,        FocalLengthIn35mmFilm);
                   EXIF_TAG(0xa406,        SceneCaptureType);
                   EXIF_TAG(0xa407,        GainControl);
                   EXIF_TAG(0xa408,        Contrast);
                   EXIF_TAG(0xa409,        Saturation);
                   EXIF_TAG(0xa40a,        Sharpness);
                   EXIF_TAG(0xa40b,        DeviceSettingDescription);
                   EXIF_TAG(0xa40c,        SubjectDistanceRange);
                   EXIF_TAG(0xa420,        ImageUniqueID);
   
                   // other tags
                   EXIF_TAG(0xa430,        CameraOwnerName);
                   EXIF_TAG(0xa431,        BodySerialNumber);
                   EXIF_TAG(0xa432,        LensSpecification);
                   EXIF_TAG(0xa433,        LensManufactor);
                   EXIF_TAG(0xa434,        LensModel);
                   EXIF_TAG(0xa435,        LensSerialNumber);
           }
   } exif_tag_value2name;
   
   class EXIF_gps_tag_value2name: public Hash<int, const char*> {
   public:
                   EXIF_gps_tag_value2name() {
                   EXIF_TAG(0x0,   GPSVersionID);
                   EXIF_TAG(0x1,   GPSLatitudeRef);
                   EXIF_TAG(0x2,   GPSLatitude);
                   EXIF_TAG(0x3,   GPSLongitudeRef);
                   EXIF_TAG(0x4,   GPSLongitude);
                   EXIF_TAG(0x5,   GPSAltitudeRef);
                   EXIF_TAG(0x6,   GPSAltitude);
                   EXIF_TAG(0x7,   GPSTimeStamp);
                   EXIF_TAG(0x8,   GPSSatellites);
                   EXIF_TAG(0x9,   GPSStatus);
                   EXIF_TAG(0xA,   GPSMeasureMode);
                   EXIF_TAG(0xB,   GPSDOP);
                   EXIF_TAG(0xC,   GPSSpeedRef);
                   EXIF_TAG(0xD,   GPSSpeed);
                   EXIF_TAG(0xE,   GPSTrackRef);
                   EXIF_TAG(0xF,   GPSTrack);
                   EXIF_TAG(0x10,  GPSImgDirectionRef);
                   EXIF_TAG(0x11,  GPSImgDirection);
                   EXIF_TAG(0x12,  GPSMapDatum);
                   EXIF_TAG(0x13,  GPSDestLatitudeRef);
                   EXIF_TAG(0x14,  GPSDestLatitude);
                   EXIF_TAG(0x15,  GPSDestLongitudeRef);
                   EXIF_TAG(0x16,  GPSDestLongitude);
                   EXIF_TAG(0x17,  GPSDestBearingRef);
                   EXIF_TAG(0x18,  GPSDestBearing);
                   EXIF_TAG(0x19,  GPSDestDistanceRef);
                   EXIF_TAG(0x1A,  GPSDestDistance);
                   EXIF_TAG(0x1B,  GPSProcessingMethod);
                   EXIF_TAG(0x1C,  GPSAreaInformation);
                   EXIF_TAG(0x1D,  GPSDateStamp);
                   EXIF_TAG(0x1E,  GPSDifferential);
           }
   } exif_gps_tag_value2name;
   
   
   ///*********************************************** support functions
   
 class Measure_reader {  class Measure_reader {
 public:  public:
         virtual size_t read(const void *&buf, size_t limit)=0;          virtual size_t read(const char* &buf, size_t limit)=0;
         virtual void seek(long value, int whence)=0;          virtual void seek(uint64_t value)=0;
         virtual long tell()=0;          virtual uint64_t tell()=0;
           virtual uint64_t length()=0;
 };  };
   
 class Measure_file_reader: public Measure_reader {  class Measure_file_reader: public Measure_reader {
           const String& file_name;
           int f;
   
 public:  public:
         Measure_file_reader(Pool& apool, int af, const String& afile_name, const char *afname):           Measure_file_reader(int af, const String& afile_name):
                 pool(apool), file_name(afile_name), fname(afname), f(af) {                  file_name(afile_name), f(af) {
         }          }
   
         /*override*/size_t read(const void *&abuf, size_t limit) {          override size_t read(const char* &abuf, size_t limit) {
                 if(limit==0)                  if(limit==0)
                         return 0;                          return 0;
   
                 void *lbuf=pool.malloc(limit);                  char* lbuf=new(PointerFreeGC) char[limit];
                 size_t read_size=(size_t)::read(f, lbuf, limit);  abuf=lbuf;                  ssize_t read_size=::read(f, lbuf, limit);  abuf=lbuf;
                 if(ssize_t(read_size)<0 || read_size>limit)                  if(read_size<0)
                         throw Exception(0,                          throw Exception(0, &file_name, "measure read failed: %s (%d)", strerror(errno), errno);
                                 &file_name,   
                                 "measure failed: actually read %lu bytes count not in [0..%lu] valid range",   
                         read_size, limit);  
   
                 return read_size;                  return read_size;
         }          }
   
         /*override*/void seek(long value, int whence) {          override void seek(uint64_t value) {
                 if(lseek(f, value, whence)<0)                  if(pa_lseek(f, value, SEEK_SET)<0)
                         throw Exception("file.seek",                          throw Exception(IMAGE_FORMAT, &file_name, "seek to %.15g failed: %s (%d)", (double)value, strerror(errno), errno);
                                 &file_name,   
                                 "seek(value=%ld, whence=%d) failed: %s (%d), actual filename '%s'",   
                                         value, whence, strerror(errno), errno, fname);  
         }          }
   
         /*override*/long tell() { return lseek(f, 0, SEEK_CUR); }          override uint64_t tell() { return pa_lseek(f, 0, SEEK_CUR); }
   
 private:          override uint64_t length() { return pa_lseek(f, 0, SEEK_END); }
         Pool& pool;  
         const String& file_name; const char *fname;  
         int f;  
 };  };
   
 class Measure_buf_reader: public Measure_reader {  class Measure_buf_reader: public Measure_reader {
   
           const char* buf; size_t size;
           const String& file_name; 
   
           size_t offset;
   
 public:  public:
         Measure_buf_reader(const void *abuf, size_t asize, const String& afile_name):           Measure_buf_reader(const char* abuf, size_t asize, const String& afile_name): 
                 buf(abuf), size(asize), file_name(afile_name), offset(0) {                  buf(abuf), size(asize), file_name(afile_name), offset(0) {
         }          }
                   
         /*override*/size_t read(const void *&abuf, size_t limit) {          override size_t read(const char* &abuf, size_t limit) {
                 size_t to_read=min(limit, size-offset);                  size_t to_read=min(limit, size-offset);
                 abuf=(const char*)buf+offset;                  abuf=buf+offset;
                 offset+=to_read;                  offset+=to_read;
                 return to_read;                  return to_read;
         }          }
   
         /*override*/void seek(long value, int whence) {          override void seek(uint64_t value) {
                 size_t new_offset;                  if(value>(uint64_t)size)
                 switch(whence) {                          throw Exception(IMAGE_FORMAT, &file_name, "seek to %.15g failed: out of buffer (%.15g)", value, size);
                 case SEEK_CUR: new_offset=offset+value; break;                  offset=(size_t)value;
                 case SEEK_SET: new_offset=(size_t)value; break;  
                 default: throw Exception("file.seek", 0, "whence #%d not supported", 0, whence); break;  
                 }  
                   
                 if((ssize_t)new_offset<0 || new_offset>size)  
                         throw Exception("file.seek",  
                                 &file_name,   
                                 "seek(value=%l, whence=%d) failed: out of buffer, new_offset>size (%l>%l) or new_offset<0",   
                                         value, whence, new_offset, size);  
                 offset=new_offset;  
         }          }
   
         /*override*/long tell() { return offset; }          override uint64_t tell() { return offset; }
   
 private:          override uint64_t length() { return size; }
   
         const void *buf; size_t size;  
         const String& file_name;   
   
         size_t offset;  
 };  };
   
 #endif  
   
 /// PNG file header  struct Measure_info {
 struct PNG_Header {          ushort width;
         char dummy[12];          ushort height;
         char signature[4]; //< must be "IHDR"          Value** exif;
         uchar high_width[2]; //< image width high bytes [we ignore for now]          Value** xmp;
         uchar width[2]; //< image width low bytes          Charset* xmp_charset;
         uchar high_height[2]; //< image height high bytes [we ignore for now]          bool    video;
         uchar height[4]; //< image height  
 };  };
   
 /// GIF file header  
 struct GIF_Header {  
         char       signature[3];         // 'GIF'  
         char       version[3];  
         uchar       width[2];  
         uchar       height[2];  
         char       dif;  
         char       fonColor;  
         char       nulls;  
 };  
   
 /// JPEG record head  inline ushort x_endian_to_ushort(uchar b0, uchar b1) {
           return (ushort)((b1<<8) + b0);
   }
   
   inline uint x_endian_to_uint(uchar b0, uchar b1, uchar b2, uchar b3) {
           return (uint)(((((b3<<8) + b2)<<8)+b1)<<8)+b0;
   }
   
   inline ushort endian_to_ushort(bool is_big, const uchar *b/* [2] */) {
           return is_big ? x_endian_to_ushort(b[1], b[0]) : x_endian_to_ushort(b[0], b[1]);
   }
   
   inline uint endian_to_uint(bool is_big, const uchar *b /* [4] */) {
           return is_big ? x_endian_to_uint(b[3], b[2], b[1], b[0]) : x_endian_to_uint(b[0], b[1], b[2], b[3]);
   }
   
   
   ///*********************************************** JPEG
   
 struct JPG_Segment_head {  struct JPG_Segment_head {
         uchar marker;          uchar marker;
         uchar code;          uchar code;
Line 156  struct JPG_Segment_head { Line 338  struct JPG_Segment_head {
 };  };
 /// JPEG frame header  /// JPEG frame header
 struct JPG_Size_segment_body {  struct JPG_Size_segment_body {
         char data;                    //< data precision of bits/sample          char data;              //< data precision of bits/sample
         uchar height[2];               //< image height          uchar height[2];        //< image height
         uchar width[2];                //< image width          uchar width[2];         //< image width
         char numComponents;           //< number of color components          char numComponents;     //< number of color components
 };  
   
 /// JPEG frame header  
 struct JPG_Exif_segment_start {  
         char signature[6]; // Exif\0\0  
 };  };
   
 /// JPEG Exif TIFF Header  /// JPEG Exif TIFF Header
 struct JPG_Exif_TIFF_header {  struct JPG_Exif_TIFF_header {
         uchar byte_align_identifier[2];          char byte_align_identifier[2];
         char dummy[2]; // always 000A [or 0A00]          uchar signature[2]; // always 000A [or 0A00]
         uchar first_IFD_offset[4]; // Usually the first IFD starts immediately next to TIFF header, so this offset has value '0x00000008'.          uchar first_IFD_offset[4]; // Usually the first IFD starts immediately next to TIFF header, so this offset has value '0x00000008'.
 };  };
   
 // JPEG Exif IFD start  // JPEG Exif IFD start
 struct JPG_Exif_IFD_start {  struct JPG_Exif_IFD_begin {
         uchar directory_entry_count[2]; // the number of directory entry contains in this IFD          uchar directory_entry_count[2]; // the number of directory entry contains in this IFD
 };  };
   
Line 189  struct JPG_Exif_IFD_entry { Line 366  struct JPG_Exif_IFD_entry {
   
 #define JPG_IFD_TAG_EXIF_OFFSET 0x8769  #define JPG_IFD_TAG_EXIF_OFFSET 0x8769
   
 //  #define JPG_IFD_TAG_EXIF_GPS_OFFSET 0x8825
   
 inline ushort x_endian_to_ushort(uchar b0, uchar b1) {  #define JPEG_EXIF_DATE_CHARS 20
         return (ushort)((b1<<8) + b0);  
 }  
   
 inline uint x_endian_to_uint(uchar b0, uchar b1, uchar b2, uchar b3) {  static Value* parse_IFD_entry_formatted_one_value(bool is_big, ushort format, size_t component_size, const uchar *value) {
         return (uint)(((((b3<<8) + b2)<<8)+b1)<<8)+b0;  
 }  
   
 inline ushort endian_to_ushort(bool is_big, const uchar *b/* [2] */) {  
         return is_big?x_endian_to_ushort(b[1], b[0]):  
                 x_endian_to_ushort(b[0], b[1]);  
 }  
   
 inline uint endian_to_uint(bool is_big, const uchar *b /* [4] */) {  
         return is_big?x_endian_to_uint(b[3], b[2], b[1], b[0]):  
                 x_endian_to_uint(b[0], b[1], b[2], b[3]);  
 }  
   
 static void measure_gif(Pool& pool, const String *origin_string,   
                          Measure_reader& reader, ushort& width, ushort& height) {  
   
         const void *buf;  
         const int head_size=sizeof(GIF_Header);  
         if(reader.read(buf, head_size)<head_size)  
                 throw Exception("image.format",   
                         origin_string,   
                         "not GIF file - too small");  
         GIF_Header *head=(GIF_Header *)buf;  
   
         if(strncmp(head->signature, "GIF", 3)!=0)  
                 throw Exception("image.format",   
                         origin_string,   
                         "not GIF file - wrong signature");        
   
         width=endian_to_ushort(false, head->width);  
         height=endian_to_ushort(false, head->height);  
 }  
   
 static Value *parse_IFD_entry_formatted_one_value(Pool& pool,  
                                                                                                   bool is_big,  
                                                                                                   ushort format,   
                                                                                                   size_t component_size,   
                                                                                                   const uchar *value) {  
         switch(format) {          switch(format) {
         case 1: // unsigned byte          case 1: // unsigned byte
                 return new(pool) VInt(pool, (uchar)value[0]);                  return new VInt((uchar)value[0]);
         case 3: // unsigned short          case 3: // unsigned short
                 return new(pool) VInt(pool, endian_to_ushort(is_big, value));                  return new VInt(endian_to_ushort(is_big, value));
         case 4: // unsigned long          case 4: // unsigned long
                  // 'double' because parser's Int is signed                   // 'double' because parser's Int is signed
                 return new(pool) VDouble(pool, endian_to_uint(is_big, value));                  return new VDouble(endian_to_uint(is_big, value));
         case 5: // unsigned rational          case 5: // unsigned rational
                 {                  {
                         uint numerator=endian_to_uint(is_big, value); value+=component_size/2;                          uint numerator=endian_to_uint(is_big, value); value+=component_size/2;
                         uint denominator=endian_to_uint(is_big, value);                          uint denominator=endian_to_uint(is_big, value);
                         if(!denominator)                          if(!denominator)
                                 return 0;                                  return 0;
                         return new(pool) VDouble(pool, ((double)numerator)/denominator);                          return new VDouble(((double)numerator)/denominator);
                 }                  }
         case 6: // signed byte          case 6: // signed byte
                 return new(pool) VInt(pool, (signed char)value[0]);                  return new VInt((signed char)value[0]);
         case 8: // signed short          case 8: // signed short
                 return new(pool) VInt(pool, (signed short)endian_to_ushort(is_big, value));                  return new VInt((signed short)endian_to_ushort(is_big, value));
         case 9: // signed long          case 9: // signed long
                 return new(pool) VInt(pool, (signed int)endian_to_uint(is_big, value));                  return new VInt((signed int)endian_to_uint(is_big, value));
         case 10: // signed rational          case 10: // signed rational
                 {                  {
                         signed int numerator=(signed int)endian_to_uint(is_big, value); value+=component_size/2;                          signed int numerator=(signed int)endian_to_uint(is_big, value); value+=component_size/2;
                         uint denominator=endian_to_uint(is_big, value);                          uint denominator=endian_to_uint(is_big, value);
                         if(!denominator)                          if(!denominator)
                                 return 0;                                  return 0;
                         return new(pool) VDouble(pool, numerator/denominator);                          return new VDouble(numerator/denominator);
                 }                  }
                 /*                  /*
         case 11: // single float          case 11: // single float
                 todo                  @todo
         case 12: // double float          case 12: // double float
                 todo                  @todo
                 */                  */
         };                };      
                   
         return 0;          return 0;
 }  }
   
 static Value *parse_IFD_entry_formatted_value(Pool& pool,  // date.C
                                                                                           bool is_big, ushort format,   tm cstr_to_time_t(char *, const char **);
                                                                                           size_t component_size, uint components_count,   
                                                                                           const uchar *value) {  static Value* parse_IFD_entry_formatted_value(bool is_big, ushort format, size_t component_size, uint components_count, const uchar *value) {
         if(format==2) { // ascii string, exception: the only type with varying size          if(format==2) { // ascii string, exception: the only type with varying size
                 const char *cstr=(const char *)value;                  const char* cstr=(const char* )value;
                 size_t size=components_count;                  size_t length=components_count;
                 if(const char *premature_zero_pos=(const char *)memchr(cstr, 0, size))                  // Data format is "YYYY:MM:DD HH:MM:SS"+0x00, total 20bytes
                         size=premature_zero_pos-cstr;                  if(length==JPEG_EXIF_DATE_CHARS && isdigit((unsigned char)cstr[0]) && cstr[length-1]==0) {
                 return new(pool) VString(*new(pool) String(pool, cstr, size, true/*tainted*/));                          char cstr_writable[JPEG_EXIF_DATE_CHARS];
                           strcpy(cstr_writable, cstr);
   
                           try {
                                   tm tmIn=cstr_to_time_t(cstr_writable, 0);
                                   return new VDate(tmIn);
                           }
                           catch(...) { /*ignore bad date times*/ }
                   }
   
                   return new VString(*new String(cstr, String::L_TAINTED));
         }          }
   
         if(components_count==1)          if(components_count==1)
                 return parse_IFD_entry_formatted_one_value(pool, is_big, format, component_size, value);                  return parse_IFD_entry_formatted_one_value(is_big, format, component_size, value);
   
         VHash& result=*new(pool) VHash(pool);          VHash* result=new VHash;
         Hash& hash=result.hash(0);          HashStringValue& hash=result->hash();
         for(uint i=0; i<components_count; i++, value+=component_size) {          for(uint i=0; i<components_count; i++, value+=component_size) {
                 String& skey=*new(pool) String(pool);                  hash.put(
                 {                          String::Body::Format(i),
                         char *buf=(char *)pool.malloc(MAX_NUMBER);                          parse_IFD_entry_formatted_one_value(is_big, format, component_size, value));
                         snprintf(buf, MAX_NUMBER, "%d", i);  
                         skey << buf;  
                 }  
                 hash.put(skey, parse_IFD_entry_formatted_one_value(pool, is_big, format, component_size, value));  
         }          }
   
         return &result;          return result;
 }  }
   
 static Value *parse_IFD_entry_value(Pool& pool,  static Value* parse_IFD_entry_value(bool is_big, Measure_reader& reader, uint64_t tiff_base, JPG_Exif_IFD_entry& entry) {
                                                                         bool is_big, Measure_reader& reader, long tiff_base,  
                                                                         JPG_Exif_IFD_entry& entry) {  
         size_t format2component_size[]={          size_t format2component_size[]={
                 0, // undefined                  0, // undefined
                 1, // unsigned byte                  1, // unsigned byte
Line 337  static Value *parse_IFD_entry_value(Pool Line 478  static Value *parse_IFD_entry_value(Pool
         // You can get the total data byte length by multiplies           // You can get the total data byte length by multiplies 
         // a 'bytes/components' value (see above chart) by number of components stored 'NNNNNNNN' area          // a 'bytes/components' value (see above chart) by number of components stored 'NNNNNNNN' area
         uint components_count=endian_to_uint(is_big, entry.components_count);          uint components_count=endian_to_uint(is_big, entry.components_count);
         size_t value_size=component_size*components_count;          uint value_size=component_size*components_count;
         // If its size is over 4bytes, 'DDDDDDDD' contains the offset to data stored address          // If its size is over 4bytes, 'DDDDDDDD' contains the offset to data stored address
         Value *result;          Value* result;
   
         if(value_size<=4)          if(value_size<=4)
                 result=parse_IFD_entry_formatted_value(pool,                  result=parse_IFD_entry_formatted_value(is_big, format, component_size, components_count, entry.value_or_offset_to_it);
                         is_big, format,   
                         component_size, components_count,   
                         entry.value_or_offset_to_it);  
         else {          else {
                 long remembered=reader.tell();                  uint64_t remembered=reader.tell();
                 {                  {
                         reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it), SEEK_SET);                          reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it));
                         const void *value;                          const char* value;
                         if(reader.read(value, value_size)<sizeof(value_size))                          if(reader.read(value, value_size)<value_size)
                                 return 0;                                  return 0;
                         result=parse_IFD_entry_formatted_value(pool,                          result=parse_IFD_entry_formatted_value(is_big, format, component_size, components_count, (const uchar*)value);
                                 is_big, format,   
                                 component_size, components_count,   
                                 (const uchar*)value);  
                 }                  }
                 reader.seek(remembered, SEEK_SET);                  reader.seek(remembered);
         }          }
   
         return result;          return result;
 }  }
   
 static void parse_IFD(Pool& pool,  static void parse_IFD(HashStringValue& hash, bool is_big, Measure_reader& reader, uint64_t tiff_base, bool gps=false);
                                           Hash& hash,  
                                           bool is_big, Measure_reader& reader, long tiff_base);  static void parse_IFD_entry(HashStringValue& hash, bool is_big, Measure_reader& reader, uint64_t tiff_base, JPG_Exif_IFD_entry& entry, bool gps=false) {
   
 static void parse_IFD_entry(Pool& pool, Hash& hash,  
                                                         bool is_big, Measure_reader& reader, long tiff_base,  
                                                         JPG_Exif_IFD_entry& entry) {  
         ushort tag=endian_to_ushort(is_big, entry.tag);          ushort tag=endian_to_ushort(is_big, entry.tag);
         if(tag==JPG_IFD_TAG_EXIF_OFFSET) {  
                 long remembered=reader.tell();          if(tag==JPG_IFD_TAG_EXIF_OFFSET || tag==JPG_IFD_TAG_EXIF_GPS_OFFSET){
                   uint64_t remembered=reader.tell();
                 {                  {
                         reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it), SEEK_SET);                          reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it));
                         parse_IFD(pool, hash, is_big, reader, tiff_base);                          parse_IFD(hash, is_big, reader, tiff_base, (tag==JPG_IFD_TAG_EXIF_GPS_OFFSET)?true:gps);
                 }                  }
                 reader.seek(remembered, SEEK_SET);                  reader.seek(remembered);
                 return;                  return;
         }          }
           
         if(Value *value=parse_IFD_entry_value(pool, is_big, reader, tiff_base, entry)) {  
                 String& skey=*new(pool) String(pool);  
                 {  
                         char *buf=(char *)pool.malloc(MAX_NUMBER);  
                         snprintf(buf, MAX_NUMBER, "%u", tag);  
                         skey << buf;  
                 }  
   
                 if(const char *name=(const char *)exif_tag_value2name->get(skey))          if(Value* value=parse_IFD_entry_value(is_big, reader, tiff_base, entry)) {
                         hash.put(*new(pool) String(pool, name), value);                  if(const char* name=(gps)?exif_gps_tag_value2name.get(tag):exif_tag_value2name.get(tag))
                           hash.put(name, value);
                 else                  else
                         hash.put(skey, value);                          hash.put(String::Body::Format(tag), value);
         }          }
 }  }
   
 static void parse_IFD(Pool& pool,  static void parse_IFD(HashStringValue& hash, bool is_big, Measure_reader& reader, uint64_t tiff_base, bool gps) {
                                           Hash& hash,          const char* buf;
                                           bool is_big, Measure_reader& reader, long tiff_base) {          if(reader.read(buf, sizeof(JPG_Exif_IFD_begin))<sizeof(JPG_Exif_IFD_begin))
         const void *buf;  
         if(reader.read(buf, sizeof(JPG_Exif_IFD_start))<sizeof(JPG_Exif_IFD_start))  
                 return;                  return;
         JPG_Exif_IFD_start *start=(JPG_Exif_IFD_start *)buf;          JPG_Exif_IFD_begin *start=(JPG_Exif_IFD_begin *)buf;
   
         ushort directory_entry_count=endian_to_ushort(is_big, start->directory_entry_count);          ushort directory_entry_count=endian_to_ushort(is_big, start->directory_entry_count);
         for(int i=0; i<directory_entry_count; i++) {          for(int i=0; i<directory_entry_count; i++) {
                 if(reader.read(buf, sizeof(JPG_Exif_IFD_entry))<sizeof(JPG_Exif_IFD_entry))                  if(reader.read(buf, sizeof(JPG_Exif_IFD_entry))<sizeof(JPG_Exif_IFD_entry))
                         return;                          return;
   
                 parse_IFD_entry(pool, hash, is_big, reader, tiff_base, *(JPG_Exif_IFD_entry *)buf);                  parse_IFD_entry(hash, is_big, reader, tiff_base, *(JPG_Exif_IFD_entry *)buf, gps);
         }          }
         // then goes: LLLLLLLL Offset to next IFD [not going there]          // then goes: LLLLLLLL Offset to next IFD [not going there]
 }  }
   
 static Value *parse_exif(Pool& pool,  static Value* parse_exif(Measure_reader& reader) {
                                            Measure_reader& reader,          const char* buf;
                                            const String *origin_string) {  
         const void *buf;  
         if(reader.read(buf, sizeof(JPG_Exif_segment_start))<sizeof(JPG_Exif_segment_start))  
                 throw Exception("image.format",   
                         origin_string,   
                         "not JPEG file - can not fully read Exif segment start");  
   
         JPG_Exif_segment_start *start=(JPG_Exif_segment_start *)buf;  
         if(memcmp(start->signature, "Exif\0\0", 4+2)!=0) //signature invalid?  
                 return 0; // ignore invalid block  
   
         uint tiff_base=reader.tell();          uint64_t tiff_base=reader.tell();
         if(reader.read(buf, sizeof(JPG_Exif_TIFF_header))<sizeof(JPG_Exif_TIFF_header))          if(reader.read(buf, sizeof(JPG_Exif_TIFF_header))<sizeof(JPG_Exif_TIFF_header))
                 return 0;                  return 0;
   
Line 436  static Value *parse_exif(Pool& pool, Line 549  static Value *parse_exif(Pool& pool,
         bool is_big=head->byte_align_identifier[0]=='M'; // [M]otorola vs [I]ntel          bool is_big=head->byte_align_identifier[0]=='M'; // [M]otorola vs [I]ntel
   
         uint first_IFD_offset=endian_to_uint(is_big, head->first_IFD_offset);          uint first_IFD_offset=endian_to_uint(is_big, head->first_IFD_offset);
         reader.seek(tiff_base+first_IFD_offset, SEEK_SET);          reader.seek(tiff_base+first_IFD_offset);
   
         VHash& vhash=*new(pool) VHash(pool);          VHash* vhash=new VHash;
   
         // IFD          // IFD
         parse_IFD(pool, vhash.hash(0), is_big, reader, tiff_base);          parse_IFD(vhash->hash(), is_big, reader, tiff_base);
   
         return &vhash;          return vhash;
 }  }
   
 static void measure_jpeg(Pool& pool, const String *origin_string,   static Value* parse_xmp(Measure_reader& reader, ushort xmp_length, Measure_info &info) {
                          Measure_reader& reader, ushort& width, ushort& height, Value ** exif) {          const char* buf;
           if(reader.read(buf, xmp_length)<xmp_length)
                   return 0;
   
           String::C xmp = Charset::transcode(String::C(pa_strdup(buf, xmp_length), xmp_length), *info.xmp_charset, pa_thread_request().charsets.source());
           return new VString(*new String(xmp, String::L_TAINTED));
   }
   
   static void measure_jpeg(const String& origin_string, Measure_reader& reader, Measure_info &info) {
         // JFIF format markers          // JFIF format markers
         const uchar MARKER=0xFF;          const uchar MARKER=0xFF;
         const uchar CODE_SIZE_A=0xC0;          const uchar CODE_SIZE_A=0xC0;
         const uchar CODE_SIZE_B=0xC1;          const uchar CODE_SIZE_B=0xC1;
         const uchar CODE_SIZE_C=0xC2;          const uchar CODE_SIZE_C=0xC2;
         const uchar CODE_SIZE_D=0xC3;          const uchar CODE_SIZE_D=0xC3;
         const uchar CODE_EXIF=0xE1;          const uchar CODE_APP1=0xE1;
   
         const void *buf;          const char* buf;
         const size_t prefix_size=2;          const size_t prefix_size=2;
         if(reader.read(buf, prefix_size)<prefix_size)          if(reader.read(buf, prefix_size)<prefix_size)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - too small");
                         origin_string,   
                         "not JPEG file - too small");  
         uchar *signature=(uchar *)buf;          uchar *signature=(uchar *)buf;
                   
         if(!(signature[0]==0xFF && signature[1]==0xD8))           if(!(signature[0]==0xFF && signature[1]==0xD8)) 
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - wrong signature");
                         origin_string,   
                         "not JPEG file - wrong signature");  
   
         while(true) {          while(true) {
                 uint segment_base=reader.tell()+2/*marker,code*/;                  uint64_t segment_base=reader.tell()+2/*marker,code*/;
                 if(reader.read(buf, sizeof(JPG_Segment_head))<sizeof(JPG_Segment_head))                  if(reader.read(buf, sizeof(JPG_Segment_head))<sizeof(JPG_Segment_head))
                         break;                          break;
                 JPG_Segment_head *head=(JPG_Segment_head *)buf;                  JPG_Segment_head *head=(JPG_Segment_head *)buf;
   
         // Verify that it's a valid segment.                  // Verify that it's a valid segment.
                 if(head->marker!=MARKER)                  if(head->marker!=MARKER)
                         throw Exception("image.format",                           throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - marker not found");
                                 origin_string,   
                                 "not JPEG file - marker not found");                  ushort segment_length=endian_to_ushort(true, head->length);
   
                 switch(head->code) {                  switch(head->code) {
                 // http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html                  // http://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_JPEG_files
                 case CODE_EXIF:                  case CODE_APP1:
                         if(exif && !*exif) // seen .jpg with some xml under EXIF tag, after real exif block :)                          {
                                 *exif=parse_exif(pool, reader, origin_string);                                  const size_t EXIF_SIG_LEN=6; // Exif\0\0
                                   const size_t XMP_SIG_LEN=29; // http://ns.adobe.com/xap/1.0/\0
   
                                   if(segment_length<EXIF_SIG_LEN+2 || reader.read(buf, EXIF_SIG_LEN)<EXIF_SIG_LEN)
                                           break;
                                   if(memcmp(buf, "Exif\0\0", EXIF_SIG_LEN)==0){
                                           if(info.exif && !*info.exif) // backward compatibility: using first segment
                                                   *info.exif=parse_exif(reader);
                                           break;
                                   }
   
                                   if(memcmp(buf, "http:/", EXIF_SIG_LEN))
                                           break;
                                   if(segment_length<XMP_SIG_LEN+2 || reader.read(buf, XMP_SIG_LEN-EXIF_SIG_LEN)<XMP_SIG_LEN-EXIF_SIG_LEN)
                                           break;
                                   if(memcmp(buf, "/ns.adobe.com/xap/1.0/\0", XMP_SIG_LEN-EXIF_SIG_LEN)==0){
                                           if(info.xmp && !*info.xmp) // backward compatibility: using first segment
                                                   *info.xmp=parse_xmp(reader, segment_length - XMP_SIG_LEN - 2 /* segment_length */, info);
                                   }
   
                           }
                         break;                          break;
   
                 case CODE_SIZE_A:                  case CODE_SIZE_A:
Line 495  static void measure_jpeg(Pool& pool, con Line 632  static void measure_jpeg(Pool& pool, con
                         {                          {
                                 // Segments that contain size info                                  // Segments that contain size info
                                 if(reader.read(buf, sizeof(JPG_Size_segment_body))<sizeof(JPG_Size_segment_body))                                  if(reader.read(buf, sizeof(JPG_Size_segment_body))<sizeof(JPG_Size_segment_body))
                                         throw Exception("image.format",                                           throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - can not fully read Size segment");
                                                 origin_string,   
                                                 "not JPEG file - can not fully read Size segment");  
                                 JPG_Size_segment_body *body=(JPG_Size_segment_body *)buf;                                  JPG_Size_segment_body *body=(JPG_Size_segment_body *)buf;
                                                                   
                                 width=endian_to_ushort(true, body->width);                                  info.width=endian_to_ushort(true, body->width);
                                 height=endian_to_ushort(true, body->height);                                  info.height=endian_to_ushort(true, body->height);
                         }                                                 }
                         return;                          return;
                 };                  };
   
                 reader.seek(segment_base+endian_to_ushort(true, head->length), SEEK_SET);                  reader.seek(segment_base + segment_length);
         }          }
   
         throw Exception("image.format",           throw Exception(IMAGE_FORMAT, &origin_string, "broken JPEG file - size frame not found");
                 origin_string,   
                 "broken JPEG file - size frame not found");  
 }  }
   
 static void measure_png(Pool& pool, const String *origin_string,   ///*********************************************** TIFF
                          Measure_reader& reader, ushort& width, ushort& height) {  
   static bool parse_tiff_IFD(bool is_big, Measure_reader& reader, Measure_info &info) {
           const char* buf;
           if(reader.read(buf, sizeof(JPG_Exif_IFD_begin))<sizeof(JPG_Exif_IFD_begin))
                   return false;
           JPG_Exif_IFD_begin *start=(JPG_Exif_IFD_begin *)buf;
   
           ushort directory_entry_count=endian_to_ushort(is_big, start->directory_entry_count);
           for(int i=0; i<directory_entry_count; i++) {
                   if(reader.read(buf, sizeof(JPG_Exif_IFD_entry))<sizeof(JPG_Exif_IFD_entry))
                           return false;
   
                   JPG_Exif_IFD_entry *entry=(JPG_Exif_IFD_entry *)buf;
                   ushort entry_tag=endian_to_ushort(is_big, entry->tag);
   
                   if(entry_tag == 256 || entry_tag == 257){
                           ushort entry_format=endian_to_ushort(is_big, entry->format);
                           if(entry_format != 3 && entry_format != 4 || endian_to_uint(is_big, entry->components_count) != 1)
                                   return false;
                           uint value = (entry_format == 3) ? endian_to_ushort(is_big, entry->value_or_offset_to_it) : endian_to_uint(is_big, entry->value_or_offset_to_it);
                           (entry_tag == 256) ? info.width=(short)value : info.height=(short)value;
                           if(info.width && info.height)
                                   return true;
                   }
           }
   
           return false;
           // then goes: LLLLLLLL Offset to next IFD [not going there]
   }
   
   static void measure_tiff(const String& origin_string, Measure_reader& reader, Measure_info &info) {
           const char* buf;
   
           if(reader.read(buf, sizeof(JPG_Exif_TIFF_header))<sizeof(JPG_Exif_TIFF_header))
                   throw Exception(IMAGE_FORMAT, &origin_string, "not TIFF file - too small");
   
           JPG_Exif_TIFF_header *head=(JPG_Exif_TIFF_header *)buf;
   
           if(strncmp(head->byte_align_identifier, "II", 2)!=0 && strncmp(head->byte_align_identifier, "MM", 2)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not TIFF file - wrong signature");
   
           bool is_big=head->byte_align_identifier[0]=='M'; // [M]otorola vs [I]ntel
   
           if(endian_to_ushort(is_big, head->signature) != 42)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not TIFF file - wrong signature");
   
           reader.seek(endian_to_uint(is_big, head->first_IFD_offset));
           if(!parse_tiff_IFD(is_big, reader, info))
                   throw Exception(IMAGE_FORMAT, &origin_string, "broken TIFF file - size field entry not found");
   }
   
   
   ///*********************************************** GIF
   
         const void *buf;  struct GIF_Header {
         const int head_size=sizeof(PNG_Header);          char    signature[3];   // 'GIF'
           char    version[3];
           uchar   width[2];
           uchar   height[2];
           char    dif;
           char    fonColor;
           char    nulls;
   };
   
   static void measure_gif(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
   
           const char* buf;
           const size_t head_size=sizeof(GIF_Header);
           if(reader.read(buf, head_size)<head_size)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not GIF file - too small");
           GIF_Header *head=(GIF_Header *)buf;
   
           if(strncmp(head->signature, "GIF", 3)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not GIF file - wrong signature");
   
           width=endian_to_ushort(false, head->width);
           height=endian_to_ushort(false, head->height);
   }
   
   
   ///*********************************************** PNG
   
   struct PNG_Header {
           char    dummy[12];
           char    signature[4];   //< must be "IHDR"
           uchar   high_width[2];  //< image width high bytes [we ignore for now]
           uchar   width[2];       //< image width low bytes
           uchar   high_height[2]; //< image height high bytes [we ignore for now]
           uchar   height[4];      //< image height
   };
   
   static void measure_png(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
   
           const char* buf;
           const size_t head_size=sizeof(PNG_Header);
         if(reader.read(buf, head_size)<head_size)          if(reader.read(buf, head_size)<head_size)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not PNG file - too small");
                         origin_string,   
                         "not PNG file - too small");  
         PNG_Header *head=(PNG_Header *)buf;          PNG_Header *head=(PNG_Header *)buf;
   
         if(strncmp(head->signature, "IHDR", 4)!=0)          if(strncmp(head->signature, "IHDR", 4)!=0)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not PNG file - wrong signature");
                         origin_string,   
                         "not PNG file - wrong signature");        
   
         width=endian_to_ushort(true, head->width);          width=endian_to_ushort(true, head->width);
         height=endian_to_ushort(true, head->height);          height=endian_to_ushort(true, head->height);
 }  }
   
 // measure center  
   
 static void measure(Pool& pool, const String& file_name,   ///*********************************************** BMP
                          Measure_reader& reader, ushort& width, ushort& height, Value ** exif) {  
         if(const char *cext=strrchr(file_name.cstr(String::UL_FILE_SPEC), '.')) {  struct BMP_Header {
           char    signature[2];   // 'BM'
           uchar   file_size[4];
           uchar   reserved[4];
           uchar   bitmap_offset[4];
           uchar   header_size[4];
           uchar   width[2];
           uchar   high_width[2];  //< image width high bytes [we ignore for now]
           uchar   height[2];
           uchar   high_height[2]; //< image height high bytes [we ignore for now]
   };
   
   static void measure_bmp(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
   
           const char* buf;
           const size_t head_size=sizeof(BMP_Header);
           if(reader.read(buf, head_size)<head_size)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not BMP file - too small");
           BMP_Header *head=(BMP_Header *)buf;
   
           if(strncmp(head->signature, "BM", 2)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not BMP file - wrong signature");
   
           if((uint)reader.length() != endian_to_uint(false, head->file_size))
                   throw Exception(IMAGE_FORMAT, &origin_string, "not BMP file - length header and file size do not match");
   
           width=endian_to_ushort(false, head->width);
           height=endian_to_ushort(false, head->height);
   }
   
   
   ///*********************************************** WEBP
   
   struct WEBP_Header {
           char signature_riff[4]; // 'RIFF'
           uchar file_size[4];
           char signature[4];      // 'WEBP'
           char format[4];         // 'VP8 ' or 'VP8L' or 'VP8X'
   };
   
   struct WEBP_VP8_Chunk {
           uchar size[4];
           char tag[3];
           uchar signature[3];    // 0x9D 0x01 0x2A
           uchar width[2];        // 14 bits each
           uchar height[2];       // 14 bits each
   };
   
   struct WEBP_VP8L_Chunk {
           uchar size[4];
           char signature;        // 0x2F
           uchar width_height[4]; // 14 bits each
   };
   
   struct WEBP_X_Chunk {
           uchar size[4];
           char reserved[4];
           uchar width[3];
           uchar height[3];
   };
   
   static void measure_webp(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
           const char* buf;
   
           if(reader.read(buf, sizeof(WEBP_Header))<sizeof(WEBP_Header))
                   throw Exception(IMAGE_FORMAT, &origin_string, "not WEBP file - too small");
   
           WEBP_Header *head=(WEBP_Header *)buf;
   
           if(strncmp(head->signature_riff, "RIFF", 4)!=0 || strncmp(head->signature, "WEBP", 4)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not WEBP file - wrong signature");
   
           if(strncmp(head->format, "VP8 ", 4)==0){
                   if(reader.read(buf, sizeof(WEBP_VP8_Chunk))<sizeof(WEBP_VP8_Chunk))
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - too small VP8 chunk");
   
                   WEBP_VP8_Chunk *chunk=(WEBP_VP8_Chunk *)buf;
                   if (chunk->signature[0] != 0x9D || chunk->signature[1] != 0x01 || chunk->signature[2] != 0x2A)
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - wrong VP8 chunk signature");
   
                   width=endian_to_ushort(false, chunk->width) & 0x3FFF;
                   height=endian_to_ushort(false, chunk->height) & 0x3FFF;
           } else if(strncmp(head->format, "VP8L", 4)==0){
                   if(reader.read(buf, sizeof(WEBP_VP8L_Chunk))<sizeof(WEBP_VP8L_Chunk))
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - too small VP8L chunk");
   
                   WEBP_VP8L_Chunk *chunk=(WEBP_VP8L_Chunk *)buf;
                   if(chunk->signature != 0x2F)
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - wrong VP8L chunk signature");
   
                   uint wh=endian_to_uint(false, chunk->width_height);
                   width=(wh & 0x3FFF) + 1;
                   height=((wh >> 14) & 0x3FFF) + 1;
           } else if (strncmp(head->format, "VP8X", 4)==0){
                   if(reader.read(buf, sizeof(WEBP_X_Chunk))<sizeof(WEBP_X_Chunk))
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - too small VP8X chunk");
   
                   WEBP_X_Chunk *chunk=(WEBP_X_Chunk *)buf;
   
                   width=endian_to_ushort(false, chunk->width) + 1;   // we ignore third byte to simplify code
                   height=endian_to_ushort(false, chunk->height) + 1; // we ignore third byte to simplify code
           } else throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - invalid chunk signature");
   }
   
   
   ///*********************************************** MP4
   
   struct MP4_Header {
           uchar size[4];
           char signature[4];   // 'ftyp' in first chunk
   };
   
   struct MP4_ExtSize {
           uchar high[4];
           uchar low[4];
   };
   
   struct MP4_Tkhd {
           uchar width[4];
           uchar height[4];
   };
   
   static bool measure_mp4(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height, uint64_t anext, const char* lastTkhd=NULL) {
           for(bool first=anext==0;;){
                   const char* buf;
                   uint64_t next=reader.tell();
   
                   if(reader.read(buf, sizeof(MP4_Header))<sizeof(MP4_Header))
                           throw Exception(IMAGE_FORMAT, &origin_string, first ? "not MP4 file - too small" : "broken MP4 file - truncated chunk header");
   
                   MP4_Header *head=(MP4_Header *)buf;
                   uint64_t size=endian_to_uint(true, head->size);
   
                   if(size==1){
                           if(reader.read(buf, sizeof(MP4_ExtSize))<sizeof(MP4_ExtSize))
                                   throw Exception(IMAGE_FORMAT, &origin_string, "broken MP4 file - truncated chunk extended size header");
                           MP4_ExtSize *ext_size=(MP4_ExtSize *)buf;
                           size=((uint64_t)endian_to_uint(true, ext_size->high) << 32) + endian_to_uint(true, ext_size->low);
                   }
                   next+=size;
   
                   if(first){
                           if(strncmp(head->signature, "ftyp", 4)!=0)
                                   throw Exception(IMAGE_FORMAT, &origin_string, "not MP4 file - wrong signature");
                           first=false;
                           anext=reader.length(); // to avoid reading beyond EOF
                   } else if(strncmp(head->signature, "moov", 4)==0 || strncmp(head->signature, "mdia", 4)==0 || strncmp(head->signature, "trak", 4)==0) {
                           if(measure_mp4(origin_string, reader, width, height, next, lastTkhd))
                                   return true;
                   } else if(strncmp(head->signature, "tkhd", 4)==0) {
                           if(size>8){
                                   reader.seek(next-8);
                                   if(reader.read(lastTkhd, sizeof(MP4_Tkhd))<sizeof(MP4_Tkhd))
                                           throw Exception(IMAGE_FORMAT, &origin_string, "broken MP4 file - bad tkhd chunk");
                           }
                   } else if (strncmp(head->signature, "hdlr", 4)==0) {
                           if(size>12){
                                   const char* hdlr;
                                   if(reader.read(hdlr, 12)<12)
                                           throw Exception(IMAGE_FORMAT, &origin_string, "broken MP4 file - bad hdlr chunk");
                                   if(lastTkhd && strncmp(hdlr+8, "vide", 4)==0) {
                                           MP4_Tkhd *tkhd=(MP4_Tkhd *)lastTkhd;
                                           width=endian_to_ushort(true, tkhd->width);
                                           height=endian_to_ushort(true, tkhd->height);
                                           return true;
                                   }
                           }
                   }
                   if(anext && next>=anext)
                           break;
                   reader.seek(next);
           }
           return false;
   }
   
   static void measure_mp4(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
           if(!measure_mp4(origin_string, reader, width, height, 0))
                   throw Exception(IMAGE_FORMAT, &origin_string, "unsupported MP4 file - size not found");
   }
   
   ///*********************************************** measure center
   
   static void measure(const String& file_name, Measure_reader& reader, Measure_info &info) {
           const char* file_name_cstr=file_name.taint_cstr(String::L_FILE_SPEC);
           if(const char* cext=strrchr(file_name_cstr, '.')) {
                 cext++;                  cext++;
                 if(strcasecmp(cext, "GIF")==0)                  if(strcasecmp(cext, "GIF")==0)
                         measure_gif(pool, &file_name, reader, width, height);                          measure_gif(file_name, reader, info.width, info.height);
                 else if(strcasecmp(cext, "JPG")==0 || strcasecmp(cext, "JPEG")==0)                   else if(strcasecmp(cext, "JPG")==0 || strcasecmp(cext, "JPEG")==0)
                         measure_jpeg(pool, &file_name, reader, width, height, exif);                          measure_jpeg(file_name, reader, info);
                 else if(strcasecmp(cext, "PNG")==0)                  else if(strcasecmp(cext, "PNG")==0)
                         measure_png(pool, &file_name, reader, width, height);                          measure_png(file_name, reader, info.width, info.height);
                   else if(strcasecmp(cext, "BMP")==0)
                           measure_bmp(file_name, reader, info.width, info.height);
                   else if(strcasecmp(cext, "WEBP")==0)
                           measure_webp(file_name, reader, info.width, info.height);
                   else if(strcasecmp(cext, "TIF")==0 || strcasecmp(cext, "TIFF")==0)
                           measure_tiff(file_name, reader, info);
                   else if(strcasecmp(cext, "MP4")==0 || strcasecmp(cext, "MOV")==0)
                           if(info.video)
                                   measure_mp4(file_name, reader, info.width, info.height);
                           else
                                   throw Exception(IMAGE_FORMAT, &file_name, "handling disabled for file name extension '%s'", cext);
                 else                  else
                         throw Exception("image.format",                           throw Exception(IMAGE_FORMAT, &file_name, "unhandled file name extension '%s'", cext);
                                 &file_name,   
                                 "unhandled image file name extension '%s'", cext);  
         } else          } else
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &file_name, "can not determine file type - no file name extension");
                         &file_name,   
                         "can not determine image type - no file name extension");  
 }  }
   
 // methods  // methods
   
 #ifndef DOXYGEN  static void file_measure_action(struct stat& /*finfo*/, int f, const String& file_spec, void *context) {
 struct File_measure_action_info {          Measure_file_reader reader(f, file_spec);
         ushort *width;          measure(file_spec, reader, *static_cast<Measure_info *>(context));
         ushort *height;  }
         Value ** exif;  
         const String *file_name;  static void _measure(Request& r, MethodParams& params) {
 };          Value& data=params.as_no_junction(0, "data must not be code");
 #endif  
 static void file_measure_action(Pool& pool,          Value* exif=0;
                                                                 struct stat& finfo, int f,           Value* xmp=0;
                                                                 const String& file_spec, const char *fname, bool as_text,          Measure_info info={ 0, 0, 0, 0, &pa_UTF8_charset, false };
                                                                 void *context) {  
         File_measure_action_info& info=*static_cast<File_measure_action_info *>(context);          if(params.count()>1)
                   if(HashStringValue* options=params.as_hash(1, "methods options")) {
         Measure_file_reader reader(pool, f, *info.file_name, fname);                          int valid_options=0;
         measure(pool, *info.file_name, reader, *info.width, *info.height, info.exif);                          for(HashStringValue::Iterator i(*options); i; i.next() ){
 }                                  String::Body key=i.key();
                                   Value* value=i.value();
 static void _measure(Request& r, const String& method_name, MethodParams *params) {                                  if(key == "exif") {
         Pool& pool=r.pool();                                          if(r.process(*value).as_bool())
                                                   info.exif=&exif;
                                           valid_options++;
                                   }
                                   if(key == "xmp") {
                                           if(r.process(*value).as_bool())
                                                   info.xmp=&xmp;
                                           valid_options++;
                                   }
                                   if(key == "xmp-charset") {
                                           info.xmp_charset=&pa_charsets.get(value->as_string());
                                           valid_options++;
                                   }
                                   if(key == "video") {
                                           info.video=r.process(*value).as_bool();
                                           valid_options++;
                                   }
                           }
                           if(valid_options!=options->count())
                                   throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                   }
   
         Value& data=params->as_no_junction(0, "data must not be code");          const String* file_name;
   
         ushort width=0;  
         ushort height=0;  
         Value *exif=0;  
         const String *file_name;  
         if(file_name=data.get_string()) {          if(file_name=data.get_string()) {
                 File_measure_action_info info={&width, &height, &exif, file_name};                  file_read_action_under_lock(r.full_disk_path(*file_name), "measure", file_measure_action, &info);
                 file_read_action_under_lock(pool, r.absolute(*file_name),   
                         "measure", file_measure_action, &info);  
         } else {          } else {
                 const VFile& vfile=*data.as_vfile();                  VFile* vfile=data.as_vfile(String::L_AS_IS);
                 file_name=&static_cast<Value *>(vfile.fields().get(*name_name))->as_string();                  file_name=&vfile->fields().get(name_name)->as_string();
                 Measure_buf_reader reader(                  Measure_buf_reader reader(vfile->value_ptr(), vfile->value_size(), *file_name);
                         vfile.value_ptr(),                  measure(*file_name, reader, info);
                         vfile.value_size(),  
                         *file_name  
                 );  
                 measure(pool, *file_name, reader, width, height, &exif);  
         }          }
   
         VImage &vimage=*static_cast<VImage *>(r.get_self());          GET_SELF(r, VImage).set(file_name, info.width, info.height, 0, exif, xmp);
         vimage.set(file_name, width, height, 0, exif);  
 }  }
   
 #ifndef DOXYGEN  static void append_attrib_pair(String &tag, String::Body key, Value* value){
 struct Attrib_info {          // skip border attribute with empty value
         String *tag; ///< html tag being constructed          if(key=="border" && !value->is_defined())
         Hash *skip; ///< tag attributes not to append to tag string [to skip]  
 };  
 #endif  
 static void append_attrib_pair(const Hash::Key& key, Hash::Val *val, void *info) {  
         Attrib_info& ai=*static_cast<Attrib_info *>(info);  
   
         // skip user-specified and internal(starting with "line-") attributes   
         if(ai.skip && ai.skip->get(key) || key.pos("line-")==0)  
                 return;                  return;
   
         Value& value=*static_cast<Value *>(val);          // src="a.gif" width="123" ismap[=-1]
         // src="a.gif" width=123 ismap[=-1]          tag << " " << key;
         *ai.tag << " " << key;          if(value->is_string() || value->as_int()>=0)
         if(value.is_string() || value.as_int()>=0)                  tag << "=\"" << value->as_string() << "\"";
                 *ai.tag << "=\"" << value.as_string() << "\"";  
 }  }
 static void _html(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         String tag(pool);  static void _html(Request& r, MethodParams& params) {
           String tag;
         tag << "<img";          tag << "<img";
   
         const Hash& fields=static_cast<VImage *>(r.get_self())->fields();          const HashStringValue& fields=GET_SELF(r, VImage).fields();
         Hash *attribs=0;          HashStringValue* attribs=0;
   
         if(params->size()) {          if(params.count()) {
                 // for backward compatibility: someday was ^html{}                  // for backward compatibility: someday was ^html{}
                 Value &vattribs=r.process_to_value(params->get(0),                  Value& vattribs=r.process(params[0]);
                         /*0/*no name* /,*/                  if(!vattribs.is_string()) { // allow empty
                         false/*don't intercept string*/);                          if((attribs=vattribs.get_hash())) {
                 if(!vattribs.is_string()) // allow empty                                  for(HashStringValue::Iterator i(*attribs); i; i.next() )
                         if(attribs=vattribs.get_hash(&method_name)) {                                          append_attrib_pair(tag, i.key(), i.value());
                                 Attrib_info attrib_info={&tag, 0};  
                                 attribs->for_each(append_attrib_pair, &attrib_info);  
                         } else                          } else
                                 throw Exception("parser.runtime",                                   throw Exception(PARSER_RUNTIME, 0, "attributes must be hash");
                                         &method_name,                   }
                                         "attributes must be hash");          }
   
           for(HashStringValue::Iterator i(fields); i; i.next() ){
                   String::Body key=i.key();
                   // skip user-specified attributes
                   if(attribs && attribs->get(key))
                           continue;
                   // allow only html attributes (to exclude exif, line-*)
                   if(key=="src" || key=="width" || key=="height" || key=="border")
                           append_attrib_pair(tag, key, i.value());
         }          }
   
         Attrib_info attrib_info={&tag, attribs};  
         fields.for_each(append_attrib_pair, &attrib_info);  
         tag << " />";          tag << " />";
         r.write_pass_lang(tag);          r.write(tag);
 }  }
   
 /// @test wrap FILE to auto-object  /// @test wrap FILE to auto-object
 static gdImage *load(Request& r, const String& method_name,   static gdImage* load(Request& r, const String& file_name){
                                          const String& file_name){          const char* file_name_cstr=r.full_disk_path(file_name).taint_cstr(String::L_FILE_SPEC);
         Pool& pool=r.pool();          if(FILE *f=pa_fopen(file_name_cstr, "rb")) {
                   gdImage* image=new gdImage;
         const char *file_name_cstr=r.absolute(file_name).cstr(String::UL_FILE_SPEC);                  bool ok=image->CreateFromGif(f);
         if(FILE *f=fopen(file_name_cstr, "rb")) {  
                 gdImage& image=*new(pool) gdImage(pool);  
                 bool ok=image.CreateFromGif(f);  
                 fclose(f);                  fclose(f);
                 if(!ok)                  if(!ok)
                         throw Exception("image.format",                           throw Exception(IMAGE_FORMAT, &file_name, "is not in GIF format");
                                 &file_name,                  return image;
                                 "is not in GIF format");  
                 return &image;  
         } else {          } else {
                 throw Exception("file.missing",                   throw Exception("file.missing", 0, "can not open '%s'", file_name_cstr);
                         &method_name,   
                         "can not open '%s'", file_name_cstr);  
                 return 0;  
         }          }
 }  }
   
   
 static void _load(Request& r, const String& method_name, MethodParams *params) {  static void _load(Request& r, MethodParams& params) {
         Pool& pool=r.pool();          const String& file_name=params.as_string(0, FILE_NAME_MUST_NOT_BE_CODE);
   
         const String& file_name=params->as_string(0, "file name must not be code");  
   
         gdImage& image=*load(r, method_name, file_name);          gdImage* image=load(r, file_name);
         int width=image.SX();          GET_SELF(r, VImage).set(&file_name, image->SX(), image->SY(), image);
         int height=image.SY();  
         static_cast<VImage *>(r.get_self())->set(&file_name, width, height, &image);  
 }  }
   
 static void _create(Request& r, const String& method_name, MethodParams *params) {  static void _create(Request& r, MethodParams& params) {
         Pool& pool=r.pool();          int width=params.as_int(0, "width must be int", r);
           int height=params.as_int(1, "height must be int", r);
         int width=params->as_int(0, "width must be int", r);  
         int height=params->as_int(1, "height must be int", r);  
         int bgcolor_value=0xffFFff;          int bgcolor_value=0xffFFff;
         if(params->size()>2)          if(params.count()>2)
                 bgcolor_value=params->as_int(2, "color must be int", r);                  bgcolor_value=params.as_int(2, "color must be int", r);
         gdImage& image=*new(pool) gdImage(pool);          gdImage* image=new gdImage;
         image.Create(width, height);          image->Create(width, height);
         image.FilledRectangle(0, 0, width-1, height-1, image.Color(bgcolor_value));          image->FilledRectangle(0, 0, width-1, height-1, image->Color(bgcolor_value));
         static_cast<VImage *>(r.get_self())->set(0, width, height, &image);          GET_SELF(r, VImage).set(0, width, height, image);
 }  }
   
 static void _gif(Request& r, const String& method_name, MethodParams *params) {  static void _gif(Request& r, MethodParams& params) {
         Pool& pool=r.pool();          gdImage& image=GET_SELF(r, VImage).image();
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;  
         if(!image)  
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         // could _ but don't thing it's wise to use $image.src for vfile.name          const String *file_name=params.count()>0?&params.as_string(0, FILE_NAME_MUST_BE_STRING):0;
   
         String out(pool); image->Gif(out);          gdBuf buf=image.Gif();
                   
         VFile& vfile=*new(pool) VFile(pool);          VFile& vfile=*new VFile;
         Value *content_type=new(pool) VString(*new(pool) String(pool, "image/gif"));  
         vfile.set(false/*not tainted*/,           vfile.set_binary(false/*not tainted*/, (const char *)buf.ptr, buf.size, file_name, new VString(*new String("image/gif")));
                 out.cstr(), out.size(), 0, content_type);  
           r.write(vfile);
         r.write_no_lang(vfile);  }
 }  
   static void _line(Request& r, MethodParams& params) {
 static void _line(Request& r, const String& method_name, MethodParams *params) {          gdImage& image=GET_SELF(r, VImage).image();
         Pool& pool=r.pool();  
           image.Line(
         gdImage *image=static_cast<VImage *>(r.get_self())->image;                  params.as_int(0, "x0 must be int", r), 
         if(!image)                  params.as_int(1, "y0 must be int", r), 
                 throw Exception(0,                   params.as_int(2, "x1 must be int", r), 
                         &method_name,                   params.as_int(3, "y1 must be int", r), 
                         "does not contain an image");                  image.Color(params.as_int(4, "color must be int", r)));
   }
         image->Line(  
                 params->as_int(0, "x0 must be int", r),   static void _fill(Request& r, MethodParams& params) {
                 params->as_int(1, "y0 must be int", r),           gdImage& image=GET_SELF(r, VImage).image();
                 params->as_int(2, "x1 must be int", r),   
                 params->as_int(3, "y1 must be int", r),           image.Fill(
                 image->Color(params->as_int(4, "color must be int", r)));                  params.as_int(0, "x must be int", r), 
 }                  params.as_int(1, "y must be int", r), 
                   image.Color(params.as_int(2, "color must be int", r)));
 static void _fill(Request& r, const String& method_name, MethodParams *params) {  }
         Pool& pool=r.pool();  
   static void _rectangle(Request& r, MethodParams& params) {
         gdImage *image=static_cast<VImage *>(r.get_self())->image;          gdImage& image=GET_SELF(r, VImage).image();
         if(!image)  
                 throw Exception(0,           image.Rectangle(
                         &method_name,                   params.as_int(0, "x0 must be int", r), 
                         "does not contain an image");                  params.as_int(1, "y0 must be int", r), 
                   params.as_int(2, "x1 must be int", r), 
         image->Fill(                  params.as_int(3, "y1 must be int", r), 
                 params->as_int(0, "x must be int", r),                   image.Color(params.as_int(4, "color must be int", r)));
                 params->as_int(1, "y must be int", r),   }
                 image->Color(params->as_int(2, "color must be int", r)));  
 }  static void _bar(Request& r, MethodParams& params) {
           gdImage& image=GET_SELF(r, VImage).image();
 static void _rectangle(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();          image.FilledRectangle(
                   params.as_int(0, "x0 must be int", r), 
         gdImage *image=static_cast<VImage *>(r.get_self())->image;                  params.as_int(1, "y0 must be int", r), 
         if(!image)                  params.as_int(2, "x1 must be int", r), 
                 throw Exception(0,                   params.as_int(3, "y1 must be int", r), 
                         &method_name,                   image.Color(params.as_int(4, "color must be int", r)));
                         "does not contain an image");  
   
         image->Rectangle(  
                 params->as_int(0, "x0 must be int", r),   
                 params->as_int(1, "y0 must be int", r),   
                 params->as_int(2, "x1 must be int", r),   
                 params->as_int(3, "y1 must be int", r),   
                 image->Color(params->as_int(4, "color must be int", r)));  
 }  
   
 static void _bar(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;  
         if(!image)  
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         image->FilledRectangle(  
                 params->as_int(0, "x0 must be int", r),   
                 params->as_int(1, "y0 must be int", r),   
                 params->as_int(2, "x1 must be int", r),   
                 params->as_int(3, "y1 must be int", r),   
                 image->Color(params->as_int(4, "color must be int", r)));  
 }  }
   
 #ifndef DOXYGEN  #ifndef DOXYGEN
 static void add_point(Array::Item *value, void *info) {  static void add_point(Table::element_type row, gdImage::Point **p) {
         Array& row=*static_cast<Array *>(value);          if(row->count()!=2)
         gdImage::Point **p=static_cast<gdImage::Point **>(info);                  throw Exception(0, 0, "coordinates table must contain two columns: x and y values");
                   (**p).x=row->get(0)->as_int();
         (**p).x=row.get_string(0)->as_int();          (**p).y=row->get(1)->as_int();
         (**p).y=row.get_string(1)->as_int();          (*p)++;
   }
   #endif
   #ifndef DOXYGEN
   static void add_point(int x, int y, gdImage::Point **p) {
           (**p).x=x;
           (**p).y=y;
         (*p)++;          (*p)++;
 }  }
 #endif  #endif
 static void _replace(Request& r, const String& method_name, MethodParams *params) {  static void _replace(Request& r, MethodParams& params) {
         Pool& pool=r.pool();          int src_color=params.as_int(0, "src color must be int", r);
           int dest_color=params.as_int(1, "dest color must be int", r);
   
           gdImage& image=GET_SELF(r, VImage).image();
   
           gdImage::Point* all_p=0;
           size_t count=0;
           if(params.count() == 3){
                   if(Table* table=params.as_table(2, "coordinates")){
                           count=table->count();
                           all_p=new(PointerFreeGC) gdImage::Point[count];
                           gdImage::Point* add_p=all_p;
                           table->for_each(add_point, &add_p);
                   }
           } else {
                   int max_x=image.SX()-1;
                   int max_y=image.SY()-1;
                   if(max_x > 0 && max_y > 0){
                           count=4;
                           all_p=new(PointerFreeGC) gdImage::Point[count];
                           gdImage::Point* add_p=all_p;
                           add_point(0, 0, &add_p);
                           add_point(max_x, 0, &add_p);
                           add_point(max_x, max_y, &add_p);
                           add_point(0, max_y, &add_p);
                   }
           }
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;          if(count)
         if(!image)                  image.FilledPolygonReplaceColor(all_p, count, image.Color(src_color), image.Color(dest_color));
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         Table *table=params->as_no_junction(2, "coordinates must not be code").get_table();  
         if(!table)   
                 throw Exception(0,  
                         &method_name,  
                         "coordinates must be table");  
   
         gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size());  
         gdImage::Point *add_p=all_p;      
         table->for_each(add_point, &add_p);  
         image->FilledPolygonReplaceColor(all_p, table->size(),   
                 image->Color(params->as_int(0, "src color must be int", r)),  
                 image->Color(params->as_int(1, "dest color must be int", r)));  
 }  
   
 static void _polyline(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;  
         if(!image)  
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         Table *table=params->as_no_junction(1, "coordinates must not be code").get_table();  
         if(!table)   
                 throw Exception(0,  
                         &method_name,  
                         "coordinates must be table");  
   
         gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size());  
         gdImage::Point *add_p=all_p;      
         table->for_each(add_point, &add_p);  
         image->Polygon(all_p, table->size(),   
                 image->Color(params->as_int(0, "color must be int", r)),  
                 false/*not closed*/);  
 }  
   
 static void _polygon(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;  
         if(!image)  
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         Table *table=params->as_no_junction(1, "coordinates must not be code").get_table();  
         if(!table)   
                 throw Exception(0,  
                         &method_name,  
                         "coordinates must be table");  
   
         gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size());  
         gdImage::Point *add_p=all_p;      
         table->for_each(add_point, &add_p);  
         image->Polygon(all_p, table->size(),   
                 image->Color(params->as_int(0, "color must be int", r)));  
 }  
   
 static void _polybar(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;  
         if(!image)  
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         Table *table=params->as_no_junction(1, "coordinates must not be code").get_table();  
         if(!table)   
                 throw Exception("parser.runtime",  
                         &method_name,  
                         "coordinates must be table");  
   
         gdImage::Point *all_p=(gdImage::Point *)pool.malloc(sizeof(gdImage::Point)*table->size());  
         gdImage::Point *add_p=all_p;      
         table->for_each(add_point, &add_p);  
         image->FilledPolygon(all_p, table->size(),   
                 image->Color(params->as_int(0, "color must be int", r)));  
 }  }
   
 // font  static void _polyline(Request& r, MethodParams& params) {
           gdImage& image=GET_SELF(r, VImage).image();
   
 #define Y(y)(y+index*height)          if(Table* table=params.as_table(1, "coordinates")){
                   gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];
                   gdImage::Point *add_p=all_p;
                   table->for_each(add_point, &add_p);
                   image.Polygon(all_p, table->count(), image.Color(params.as_int(0, "color must be int", r)), false/*not closed*/);
           }
   }
   
 /// simple gdImage-based font storage & text output   static void _polygon(Request& r, MethodParams& params) {
 class Font: public Pooled {          gdImage& image=GET_SELF(r, VImage).image();
 public:  
                   if(Table* table=(Table*)params.as_table(1, "coordinates")){
         const static int letter_spacing;                  gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];
         int height;         ///< Font heigth                  gdImage::Point *add_p=all_p;
         int monospace;      ///< Default char width                  table->for_each(add_point, &add_p);
         int spacebarspace; ///< spacebar width                  image.Polygon(all_p, table->count(), image.Color(params.as_int(0, "color must be int", r)));
         gdImage& ifont;  
         const String& alphabet;  
           
         Font(Pool& pool,   
                 const String& aalphabet,   
                 gdImage& aifont, int aheight, int amonospace, int aspacebarspace): Pooled(pool),   
                 alphabet(aalphabet),   
                 height(aheight), monospace(amonospace),  spacebarspace(aspacebarspace),  
                 ifont(aifont) {  
         }          }
           }
         /* ******************************** char ********************************** */  
           static void _polybar(Request& r, MethodParams& params) {
         int index_of(char ch) {          gdImage& image=GET_SELF(r, VImage).image();
                 if(ch==' ') return -1;  
                 return alphabet.pos(&ch, 1);          if(Table* table=(Table*)params.as_table(1, "coordinates")){
                   gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];
                   gdImage::Point *add_p=all_p;
                   table->for_each(add_point, &add_p);
                   image.FilledPolygon(all_p, table->count(), image.Color(params.as_int(0, "color must be int", r)));
         }          }
           }
         int index_width(int index) {  
                 if(index<0)  // font
                         return spacebarspace;  
                 int tr=ifont.GetTransparent();  #define Y(y)(y+index*height)
                 for(int x=ifont.SX()-1; x>=0; x--) {  
                         for(int y=0; y<height-1; y++)  // Font class
                                 if(ifont.GetPixel(x, Y(y))!=tr)   
                                         return x+1;  Font::Font(Charset& asource_charset, const String& aalphabet, gdImage* aifont, int aheight, int amonospace, int aspacebarspace, int aletterspacing):
                 }          letterspacing(aletterspacing),
                 return 0;          height(aheight),
           monospace(amonospace),
           spacebarspace(aspacebarspace),
           ifont(aifont),
           alphabet(aalphabet),
           fsource_charset(asource_charset){
   
           if(fsource_charset.isUTF8()){
                   size_t index=0;
                   for(UTF8_string_iterator i(alphabet); i.has_next(); )
                           fletter2index.put_dont_replace(i.next(), index++);
         }          }
           }
         void index_display(gdImage& image, int x, int y, int index){  
                 if(index>=0)   /* ******************************** char ********************************** */
                         ifont.Copy(image, x, y, 0, Y(0), index_width(index), height);  
   size_t Font::index_of(char ch) {
           if(ch==' ') return STRING_NOT_FOUND;
           return alphabet.pos(ch);
   }
   
   size_t Font::index_of(XMLCh ch) {
           if(ch==' ') return STRING_NOT_FOUND;
           return fletter2index.get(ch);
   }
   
   int Font::index_width(size_t index) {
           if(index==STRING_NOT_FOUND)
                   return spacebarspace;
           int tr=ifont->GetTransparent();
           for(int x=ifont->SX()-1; x>=0; x--) {
                   for(int y=0; y<height; y++)
                           if(ifont->GetPixel(x, Y(y))!=tr) 
                                   return x+1;
         }          }
                   return 0;
         /* ******************************** string ********************************** */  }
           
         int string_width(const String& s){  void Font::index_display(gdImage& image, int x, int y, size_t index){
                 const char *cstr=s.cstr();          if(index!=STRING_NOT_FOUND) 
                 int result=0;                  ifont->Copy(image, x, y, 0, Y(0), index_width(index), height);
                 for(; *cstr; cstr++)  }
                         result+=index_width(index_of(*cstr));  
                 return result;  /* ******************************** string ********************************** */
   
   int Font::step_width(int index) {
           return letterspacing + (monospace ? monospace : index_width(index));
   }
   
   // counts trailing letter_spacing, consider this OK. useful for contiuations
   int Font::string_width(const String& s){
           const char* cstr=s.cstr();
           int result=0;
   
           if(fsource_charset.isUTF8()){
                   for(UTF8_string_iterator i(s); i.has_next(); )
                           result+=step_width(index_of(i.next()));
           } else {
                   for(const char* current=cstr; *current; current++)
                           result+=step_width(index_of(*current));
         }          }
           
         void string_display(gdImage& image, int x, int y, const String& s){          return result;
                 const char *cstr=s.cstr();  }
                 if(cstr) for(; *cstr; cstr++) {  
                         int index=index_of(*cstr);  void Font::string_display(gdImage& image, int x, int y, const String& s){
           const char* cstr=s.cstr();
   
           if(fsource_charset.isUTF8()){
                   for(UTF8_string_iterator i(s); i.has_next(); ){
                           size_t index=index_of(i.next());
                         index_display(image, x, y, index);                          index_display(image, x, y, index);
                         x+=letter_spacing + (monospace ? monospace : index_width(index));                          x+=step_width(index);
                   }
           } else {
                   for(const char* current=cstr; *current; current++) {
                           size_t index=index_of(*current);
                           index_display(image, x, y, index);
                           x+=step_width(index);
                 }                  }
         }          }
           }
 };  
 const int Font::letter_spacing=1;  
   
 static void _font(Request& r, const String& method_name, MethodParams *params) {  //
         Pool& pool=r.pool();  
   
         const String& alphabet=params->as_string(0, "alphabet must not be code");  
         gdImage& image=*load(r, method_name, params->as_string(1, "file_name must not be code"));  
         int spacebar_width=params->as_int(2, "spacebar_width must be int", r);  
         int monospace_width;  
         if(params->size()>3) {  
                 monospace_width=params->as_int(3, "monospace_width must be int", r);  
                 if(!monospace_width)  
                         monospace_width=image.SX();  
         } else  
                 monospace_width=0;  
   
         if(!alphabet.size())  static void _font(Request& r, MethodParams& params) {
                 throw Exception("parser.runtime",          const String& alphabet=params.as_string(0, "alphabet must not be code");
                         &method_name,          size_t alphabet_length=alphabet.length(r.charsets.source());
                         "alphabet must not be empty");          if(!alphabet_length)
                   throw Exception(PARSER_RUNTIME, 0, "alphabet must not be empty");
         if(int remainder=image.SY() % alphabet.size())  
                 throw Exception("parser.runtime",          gdImage* image=load(r, params.as_string(1, FILE_NAME_MUST_NOT_BE_CODE));
                         &method_name,  
                         "font-file height(%d) not divisable by alphabet size(%d), remainder=%d",          int spacebar_width=image->SX();
                                 image.SY(), alphabet.size(), remainder);          int monospace_width=0; // proportional
           int letter_spacing=1;
           if(params.count()>2){
                   if(HashStringValue* options=params[2].get_hash()){
                           // third option is hash
                           if(params.count()>3)
                                   throw Exception(PARSER_RUNTIME, 0, "too many params were specified");
                           int valid_options=0;
                           if(Value* vspacebar_width=options->get(spacebar_width_name)){
                                   valid_options++;
                                   spacebar_width=r.process(*vspacebar_width).as_int();
                           }
                           if(Value* vmonospace_width=options->get(monospace_width_name)){
                                   valid_options++;
                                   monospace_width=r.process(*vmonospace_width).as_int();
                                   if(!monospace_width)
                                           monospace_width=image->SX();
                           }
                           if(Value* vletter_spacing=options->get(letter_spacing_name)){
                                   valid_options++;
                                   letter_spacing=r.process(*vletter_spacing).as_int();
                           }
                           if(valid_options!=options->count())
                                   throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                   } else {
                           // backward
                           spacebar_width=params.as_int(2, "param must be int or hash", r);
                           if(params.count()>3) {
                                   monospace_width=params.as_int(3, "monospace_width must be int", r);
                                   if(!monospace_width)
                                           monospace_width=image->SX();
                           }
                   }
           }
   
           if(int remainder=image->SY() % alphabet_length)
                   throw Exception(PARSER_RUNTIME, 0, "font-file height(%d) not divisable by alphabet size(%d), remainder=%d", image->SY(), alphabet_length, remainder);
                   
         static_cast<VImage *>(r.get_self())->font=new(pool) Font(pool,           GET_SELF(r, VImage).set_font(new Font(r.charsets.source(), alphabet, image, image->SY() / alphabet_length, monospace_width, spacebar_width, letter_spacing));
                 alphabet,   }
                 image,   
                 image.SY() / alphabet.size(), monospace_width, spacebar_width);  static void _text(Request& r, MethodParams& params) {
 }          int x=params.as_int(0, "x must be int", r);
           int y=params.as_int(1, "y must be int", r);
 static void _text(Request& r, const String& method_name, MethodParams *params) {          const String& s=params.as_string(2, "text must not be code");
         Pool& pool=r.pool();  
           VImage& vimage=GET_SELF(r, VImage);
         int x=params->as_int(0, "x must be int", r);          vimage.font().string_display(vimage.image(), x, y, s);
         int y=params->as_int(1, "y must be int", r);  }
         const String& s=params->as_string(2, "text must not be code");  
   static void _length(Request& r, MethodParams& params) {
         VImage& vimage=*static_cast<VImage *>(r.get_self());          const String& s=params.as_string(0, "text must not be code");
         if(vimage.image)  
                 if(vimage.font)          VImage& vimage=GET_SELF(r, VImage);
                         vimage.font->string_display(*vimage.image, x, y, s);          r.write(*new VInt(vimage.font().string_width(s)));
                 else  }
                         throw Exception("parser.runtime",  
                                 &method_name,  static void _arc(Request& r, MethodParams& params) {
                                 "set the font first");          gdImage& image=GET_SELF(r, VImage).image();
         else  
                 throw Exception(0,           image.Arc(
                         &method_name,                   params.as_int(0, "center_x must be int", r), 
                         "does not contain an image");                  params.as_int(1, "center_y must be int", r), 
 }                  params.as_int(2, "width must be int", r), 
                   params.as_int(3, "height must be int", r), 
 static void _length(Request& r, const String& method_name, MethodParams *params) {                  params.as_int(4, "start degrees must be int", r), 
         Pool& pool=r.pool();                  params.as_int(5, "end degrees must be int", r), 
                   image.Color(params.as_int(6, "cx must be int", r)));
         const String& s=params->as_string(0, "text must not be code");  }
   
         VImage& vimage=*static_cast<VImage *>(r.get_self());  static void _sector(Request& r, MethodParams& params) {
         if(vimage.image)          gdImage& image=GET_SELF(r, VImage).image();
                 if(vimage.font) {  
                         r.write_no_lang(*new(pool) VInt(pool, vimage.font->string_width(s)));          image.Sector(
                 } else                  params.as_int(0, "center_x must be int", r), 
                         throw Exception("parser.runtime",                  params.as_int(1, "center_y must be int", r), 
                                 &method_name,                  params.as_int(2, "width must be int", r), 
                                 "set the font first");                  params.as_int(3, "height must be int", r), 
         else                  params.as_int(4, "start degrees must be int", r), 
                 throw Exception(0,                   params.as_int(5, "end degrees must be int", r), 
                         &method_name,                   image.Color(params.as_int(6, "color must be int", r)));
                         "does not contain an image");  }
 }  
   static void _circle(Request& r, MethodParams& params) {
 static void _arc(Request& r, const String& method_name, MethodParams *params) {          gdImage& image=GET_SELF(r, VImage).image();
         Pool& pool=r.pool();  
           int size=params.as_int(2, "radius must be int", r)*2;
         gdImage *image=static_cast<VImage *>(r.get_self())->image;          image.Arc(
         if(!image)                  params.as_int(0, "center_x must be int", r), 
                 throw Exception(0,                   params.as_int(1, "center_y must be int", r), 
                         &method_name,   
                         "does not contain an image");  
   
         image->Arc(  
                 params->as_int(0, "center_x must be int", r),   
                 params->as_int(1, "center_y must be int", r),   
                 params->as_int(2, "width must be int", r),   
                 params->as_int(3, "height must be int", r),   
                 params->as_int(4, "start degrees must be int", r),   
                 params->as_int(5, "end degrees must be int", r),   
                 image->Color(params->as_int(6, "cx must be int", r)));  
 }  
   
 static void _sector(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;  
         if(!image)  
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         image->Sector(  
                 params->as_int(0, "center_x must be int", r),   
                 params->as_int(1, "center_y must be int", r),   
                 params->as_int(2, "width must be int", r),   
                 params->as_int(3, "height must be int", r),   
                 params->as_int(4, "start degrees must be int", r),   
                 params->as_int(5, "end degrees must be int", r),   
                 image->Color(params->as_int(6, "color must be int", r)));  
 }  
   
 static void _circle(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         gdImage *image=static_cast<VImage *>(r.get_self())->image;  
         if(!image)  
                 throw Exception(0,   
                         &method_name,   
                         "does not contain an image");  
   
         int size=params->as_int(2, "radius must be int", r)*2;  
         image->Arc(  
                 params->as_int(0, "center_x must be int", r),   
                 params->as_int(1, "center_y must be int", r),   
                 size, //w                  size, //w
                 size, //h                  size, //h
                 0, //s                  0, //s
                 360, //e                  360, //e
                 image->Color(params->as_int(3, "color must be int", r)));                  image.Color(params.as_int(3, "color must be int", r)));
 }  }
   
 gdImage& as_image(Pool& pool, const String& method_name, MethodParams *params,   gdImage& as_image(MethodParams& params, int index, const char* msg) {
                                                 int index, const char *msg) {          Value& value=params.as_no_junction(index, msg);
         gdImage *src=0;  
   
         Value& value=params->as_no_junction(index, msg);  
   
         if(Value *vimage=value.as(VIMAGE_TYPE, false)) {          if(Value* vimage=value.as(VIMAGE_TYPE)) {
                 src=static_cast<VImage *>(vimage)->image;                  return static_cast<VImage *>(vimage)->image();
                 if(!src)  
                         throw Exception("parser.runtime",   
                                 &method_name,   
                                 msg);  
         } else          } else
                 throw Exception("parser.runtime",                   throw Exception(PARSER_RUNTIME, 0, msg);
                         &method_name,   }
                         msg);  
   static void _copy(Request& r, MethodParams& params) {
         return *src;          gdImage& dest=GET_SELF(r, VImage).image();
 }  
   
 static void _copy(Request& r, const String& method_name, MethodParams *params) {  
         Pool& pool=r.pool();  
   
         gdImage *dest=static_cast<VImage *>(r.get_self())->image;  
         if(!dest)  
                 throw Exception(0,   
                         &method_name,   
                         "self does not contain an image");  
   
         gdImage& src=as_image(pool, method_name, params, 0, "src must be image");  
   
         int sx=params->as_int(1, "src_x must be int", r);  
         int sy=params->as_int(2, "src_y must be int", r);  
         int sw=params->as_int(3, "src_w must be int", r);  
         int sh=params->as_int(4, "src_h must be int", r);  
         int dx=params->as_int(5, "dest_x must be int", r);  
         int dy=params->as_int(6, "dest_y must be int", r);  
         if(params->size()>1+2+2+2) {  
                 int dw=params->as_int(1+2+2+2, "dest_w must be int", r);  
                 int dh=(int)(params->size()>1+2+2+2+1?  
                         params->as_int(1+2+2+2+1, "dest_h must be int", r):sh*(((double)dw)/((double)sw)));  
                 int tolerance=params->size()>1+2+2+2+2?  
                         params->as_int(1+2+2+2+2, "tolerance must be int", r):150;  
   
                 src.CopyResampled(*dest, dx, dy, sx, sy, dw, dh, sw, sh, tolerance);          gdImage& src=as_image(params, 0, "src must be image");
   
           int sx=params.as_int(1, "src_x must be int", r);
           int sy=params.as_int(2, "src_y must be int", r);
           int sw=params.as_int(3, "src_w must be int", r);
           int sh=params.as_int(4, "src_h must be int", r);
           int dx=params.as_int(5, "dest_x must be int", r);
           int dy=params.as_int(6, "dest_y must be int", r);
           if(params.count()>1+2+2+2) {
                   int dw=params.as_int(1+2+2+2, "dest_w must be int", r);
                   int dh=(int)(params.count()>1+2+2+2+1?
                           params.as_int(1+2+2+2+1, "dest_h must be int", r):sh*(((double)dw)/((double)sw)));
                   int tolerance=params.count()>1+2+2+2+2?
                           params.as_int(1+2+2+2+2, "tolerance must be int", r):150;
   
                   src.CopyResampled(dest, dx, dy, sx, sy, dw, dh, sw, sh, tolerance);
         } else          } else
                 src.Copy(*dest, dx, dy, sx, sy, sw, sh);                  src.Copy(dest, dx, dy, sx, sy, sw, sh);
   }
   
   static void _pixel(Request& r, MethodParams& params) {
           gdImage& image=GET_SELF(r, VImage).image();
   
           int x=params.as_int(0, "x must be int", r); 
           int y=params.as_int(1, "y must be int", r);
   
           if(params.count()>2) {
                   image.SetPixel(x, y, 
                           image.Color(params.as_int(2, "color must be int", r)));
           } else 
                   r.write(*new VInt(image.DecodeColor(image.GetPixel(x, y))));
 }  }
   
   
 // constructor  // constructor
   
 MImage::MImage(Pool& apool) : Methoded(apool, "image") {  MImage::MImage(): Methoded("image") {
         // ^image:measure[DATA]          // ^image:measure[DATA]
         add_native_method("measure", Method::CT_DYNAMIC, _measure, 1, 1);          // ^image:measure[DATA; $.exif(false) $.xmp(false) $.xmp-charset[UTF-8] ]
           add_native_method("measure", Method::CT_DYNAMIC, _measure, 1, 2);
   
         // ^image.html[]          // ^image.html[]
         // ^image.html[hash]          // ^image.html[hash]
Line 1155  MImage::MImage(Pool& apool) : Methoded(a Line 1505  MImage::MImage(Pool& apool) : Methoded(a
         add_native_method("create", Method::CT_DYNAMIC, _create, 2, 3);          add_native_method("create", Method::CT_DYNAMIC, _create, 2, 3);
   
         // ^image.gif[]          // ^image.gif[]
         add_native_method("gif", Method::CT_DYNAMIC, _gif, 0, 0);          add_native_method("gif", Method::CT_DYNAMIC, _gif, 0, 1);
   
         // ^image.line(x0;y0;x1;y1;color)          // ^image.line(x0;y0;x1;y1;color)
         add_native_method("line", Method::CT_DYNAMIC, _line, 5, 5);          add_native_method("line", Method::CT_DYNAMIC, _line, 5, 5);
Line 1170  MImage::MImage(Pool& apool) : Methoded(a Line 1520  MImage::MImage(Pool& apool) : Methoded(a
         add_native_method("bar", Method::CT_DYNAMIC, _bar, 5, 5);          add_native_method("bar", Method::CT_DYNAMIC, _bar, 5, 5);
   
         // ^image.replace(color-source;color-dest)[table x:y]          // ^image.replace(color-source;color-dest)[table x:y]
         add_native_method("replace", Method::CT_DYNAMIC, _replace, 3, 3);          // ^image.replace(color-source;color-dest)
           add_native_method("replace", Method::CT_DYNAMIC, _replace, 2, 3);
   
         // ^image.polyline(color)[table x:y]          // ^image.polyline(color)[table x:y]
         add_native_method("polyline", Method::CT_DYNAMIC, _polyline, 2, 2);          add_native_method("polyline", Method::CT_DYNAMIC, _polyline, 2, 2);
Line 1181  MImage::MImage(Pool& apool) : Methoded(a Line 1532  MImage::MImage(Pool& apool) : Methoded(a
         // ^image.polybar(color)[table x:y]          // ^image.polybar(color)[table x:y]
         add_native_method("polybar", Method::CT_DYNAMIC, _polybar, 2, 2);          add_native_method("polybar", Method::CT_DYNAMIC, _polybar, 2, 2);
   
     // ^image.font[alPHAbet;font-file-name.gif](spacebar_width)          // ^image.font[alPHAbet;font-file-name.gif]
     // ^image.font[alPHAbet;font-file-name.gif](spacebar_width;width)          // ^image.font[alPHAbet;font-file-name.gif](spacebar_width)
         add_native_method("font", Method::CT_DYNAMIC, _font, 3, 4);          // ^image.font[alPHAbet;font-file-name.gif](spacebar_width;letter_width)
           // ^image.font[alPHAbet;font-file-name.gif][$.space-width(.) $.letter-width(.) $.letter-space(.)]
           add_native_method("font", Method::CT_DYNAMIC, _font, 2, 4);
   
     // ^image.text(x;y)[text]          // ^image.text(x;y)[text]
         add_native_method("text", Method::CT_DYNAMIC, _text, 3, 3);          add_native_method("text", Method::CT_DYNAMIC, _text, 3, 3);
                   
     // ^image.ngth[text]          // ^image.length[text]
         add_native_method("length", Method::CT_DYNAMIC, _length, 1, 1);          add_native_method("length", Method::CT_DYNAMIC, _length, 1, 1);
                   
         // ^image.arc(center x;center y;width;height;start in degrees;end in degrees;color)          // ^image.arc(center x;center y;width;height;start in degrees;end in degrees;color)
Line 1202  MImage::MImage(Pool& apool) : Methoded(a Line 1555  MImage::MImage(Pool& apool) : Methoded(a
   
         // ^image.copy[source](src x;src y;src w;src h;dst x;dst y[;dest w[;dest h[;tolerance]]])          // ^image.copy[source](src x;src y;src w;src h;dst x;dst y[;dest w[;dest h[;tolerance]]])
         add_native_method("copy", Method::CT_DYNAMIC, _copy, 1+2+2+2, (1+2+2+2)+2+1);          add_native_method("copy", Method::CT_DYNAMIC, _copy, 1+2+2+2, (1+2+2+2)+2+1);
 }  
   
 // global variable  
   
 Methoded *image_class;  
   
 // creator  
   
 Methoded *MImage_create(Pool& pool) {          // ^image.pixel(x;y)[(color)]
         return image_class=new(pool) MImage(pool);          add_native_method("pixel", Method::CT_DYNAMIC, _pixel, 2, 3);
 }  }

Removed from v.1.85  
changed lines
  Added in v.1.181


E-mail: