// @(#)root/asimage:$Id: TASImage.cxx,v 1.54 2006/03/13 15:18:56 rdm E
// Author: Fons Rademakers, Reiner Rohlfs, Valeriy Onuchin   28/11/2001

/*************************************************************************
 * Copyright (C) 1995-2001, Rene Brun, Fons Rademakers and Reiner Rohlfs *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

/**************************************************************************
 * Some parts of this source are based on libAfterImage 2.00.00
 *          (http://www.afterstep.org/)
 *
 * Copyright (c) 2002 Sasha Vasko <sasha@aftercode.net>
 * Copyright (c) 1998, 1999 Ethan Fischer <allanon@crystaltokyo.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **************************************************************************/


//______________________________________________________________________________
/* Begin_Html
<center><h2>Image class</h2></center>
TASImage is the concrete interface to the image processing library
libAfterImage.
<p>
It allows reading and writing of images in different formats, several image
manipulations (scaling, tiling, merging, etc.) and displaying in pads. The size
of the image on the screen does not depend on the original size of the image but
on the size of the pad. Therefore it is very easy to resize the image on the
screen by resizing the pad.
<p>
Besides reading an image from a file an image can be defined by a two
dimensional array of values. A palette defines the color of each value.
<p>
The image can be zoomed by defining a rectangle with the mouse. The color
palette can be modified with a GUI, just select StartPaletteEditor() from the
context menu.
End_Html */

#include "TASImage.h"
#include "TASImagePlugin.h"
#include "TROOT.h"
#include "TMath.h"
#include "TSystem.h"
#include "TVirtualX.h"
#include "TVirtualPad.h"
#include "TArrayD.h"
#include "TVectorD.h"
#include "TVirtualPS.h"
#include "TGaxis.h"
#include "TColor.h"
#include "TObjArray.h"
#include "TArrayL.h"
#include "TPoint.h"
#include "TFrame.h"
#include "TTF.h"
#include "TRandom.h"
#include "Riostream.h"
#include "THashTable.h"
#include "TPluginManager.h"
#include "TEnv.h"
#include "TStyle.h"
#include "TText.h"


#ifndef WIN32
#   include <X11/Xlib.h>
#else
#   include "Windows4root.h"
#endif
extern "C" {
#ifndef WIN32
#   include <afterbase.h>
#else
#   include <win32/config.h>
#   include <win32/afterbase.h>
#   define X_DISPLAY_MISSING 1
#endif
#   include <afterimage.h>
#   include <bmp.h>
#   include <draw.h>
}

// auxilary functions for general polygon filling
#include "TASPolyUtils.c"


ASVisual *TASImage::fgVisual = 0;
Bool_t TASImage::fgInit = kFALSE;

static ASFontManager *gFontManager = 0;
static unsigned long kAllPlanes = ~0;
THashTable *TASImage::fgPlugList = new THashTable(50);

// default icon paths
static char *gIconPaths[7] = {0, 0, 0, 0, 0, 0, 0};

///////////////////////////// alphablending macros ///////////////////////////////

#if defined(__GNUC__) && __GNUC__ >= 4 && ((__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 1) || (__GNUC_MINOR__ >= 3)) && !__INTEL_COMPILER
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif

#ifdef R__BYTESWAP
typedef struct {
   unsigned char b;
   unsigned char g;
   unsigned char r;
   unsigned char a;
} __argb32__;
#else
typedef struct {
   unsigned char a;
   unsigned char r;
   unsigned char g;
   unsigned char b;
} __argb32__;
#endif


//______________________________________________________________________________
#define _alphaBlend(bot, top) {\
   __argb32__ *t = (__argb32__*)(top);\
   __argb32__ *b = (__argb32__*)(bot);\
   int aa = 255-t->a;\
   if (!aa) {\
      *bot = *top;\
   } else { \
      b->a = ((b->a*aa)>>8) + t->a;\
      b->r = (b->r*aa + t->r*t->a)>>8;\
      b->g = (b->g*aa + t->g*t->a)>>8;\
      b->b = (b->b*aa + t->b*t->a)>>8;\
   }\
}\


ClassImp(TASImage)
ClassImp(TASImagePlugin)


//______________________________________________________________________________
void TASImage::DestroyImage()
{
   // Destroy image.

   if (fImage) {
      destroy_asimage(&fImage);
   }

   if (fIsGray && fGrayImage) {
      destroy_asimage(&fGrayImage);
   }

   fIsGray     = kFALSE;
   fGrayImage  = 0;
   fImage      = 0;
}


//______________________________________________________________________________
void TASImage::SetDefaults()
{
   // Set default parameters.

   fImage         = 0;
   fScaledImage   = 0;
   fMaxValue      = 1;
   fMinValue      = 0;
   fEditable      = kFALSE;
   fPaintMode     = 1;
   fZoomOffX      = 0;
   fZoomOffY      = 0;
   fZoomWidth     = 0;
   fZoomHeight    = 0;
   fZoomUpdate    = kZoomOps;

   fGrayImage     = 0;
   fIsGray        = kFALSE;
   fPaletteEnabled = kFALSE;

   if (!fgInit) {
      set_application_name((char*)(gProgName ? gProgName : "ROOT"));
      fgInit = kTRUE;
   }
}


//______________________________________________________________________________
TASImage::TASImage()
{
   // Default image constructor.

   SetDefaults();
}


//______________________________________________________________________________
TASImage::TASImage(UInt_t w, UInt_t h) : TImage(w, h)
{
   // Create an empty image.

   SetDefaults();
   fImage = create_asimage(w ? w : 20, h ? h : 20, 0);
   UnZoom();
}


//______________________________________________________________________________
TASImage::TASImage(const char *file, EImageFileTypes) : TImage(file)
{
   // Create an image object and read from specified file.
   // For more information see description of function ReadImage()
   // which is called by this constructor.

   SetDefaults();
   TString fname = file;
   gSystem->ExpandPathName(fname);
   ReadImage(fname.Data());
}


//______________________________________________________________________________
TASImage::TASImage(const char *name, const Double_t *imageData, UInt_t width,
                   UInt_t height, TImagePalette *palette) : TImage(name)
{
   // Create an image depending on the values of imageData.
   // For more information see function SetImage() which is called
   // by this constructor.

   SetDefaults();
   SetImage(imageData, width, height, palette);
}


//______________________________________________________________________________
TASImage::TASImage(const char *name, const TArrayD &imageData, UInt_t width,
                   TImagePalette *palette) : TImage(name)
{
   // Create an image depending on the values of imageData.
   // The size of the image is width X (imageData.fN / width).
   // For more information see function SetImage() which is called by
   // this constructor.

   SetDefaults();
   SetImage(imageData, width, palette);
}


//______________________________________________________________________________
TASImage::TASImage(const char *name, const TVectorD &imageData, UInt_t width,
                   TImagePalette *palette) : TImage(name)
{
   // Create an image depending on the values of imageData.
   // The size of the image is width X (imageData.fN / width).
   // For more information see function SetImage() which is called by
   // this constructor.

   SetDefaults();
   SetImage(imageData, width, palette);
}


//______________________________________________________________________________
TASImage::TASImage(const TASImage &img) : TImage(img)
{
   // Image copy constructor.

   SetDefaults();

   if (img.IsValid()) {
      fImage = clone_asimage(img.fImage, SCL_DO_ALL);
      fScaledImage   = fScaledImage ? (TASImage*)img.fScaledImage->Clone("") : 0;
      fGrayImage     = fGrayImage ? clone_asimage(img.fGrayImage, SCL_DO_ALL) : 0;

      if (img.fImage->alt.vector) {
         Int_t size = img.fImage->width * img.fImage->height * sizeof(double);
         fImage->alt.vector = (double*)malloc(size);
         memcpy(fImage->alt.vector, img.fImage->alt.vector, size);
      }

      fZoomUpdate = kNoZoom;
      fZoomOffX   = img.fZoomOffX;
      fZoomOffY   = img.fZoomOffY;
      fZoomWidth  = img.fZoomWidth;
      fZoomHeight = img.fZoomHeight;
      fEditable   = img.fEditable;
      fIsGray     = img.fIsGray;
   }
}


//______________________________________________________________________________
TASImage &TASImage::operator=(const TASImage &img)
{
   // Image assignment operator.

   SetDefaults();

   if (this != &img && img.IsValid()) {
      TImage::operator=(img);

      DestroyImage();
      delete fScaledImage;
      fImage = clone_asimage(img.fImage, SCL_DO_ALL);
      fScaledImage = fScaledImage ? (TASImage*)img.fScaledImage->Clone("") : 0;
      fGrayImage = fGrayImage ? clone_asimage(img.fGrayImage, SCL_DO_ALL) : 0;

      if (img.fImage->alt.vector) {
         Int_t size = img.fImage->width * img.fImage->height * sizeof(double);
         fImage->alt.vector = (double*)malloc(size);
         memcpy(fImage->alt.vector, img.fImage->alt.vector, size);
      }

      fScaledImage = img.fScaledImage ? (TASImage*)img.fScaledImage->Clone("") : 0;
      fZoomUpdate = kNoZoom;
      fZoomOffX   = img.fZoomOffX;
      fZoomOffY   = img.fZoomOffY;
      fZoomWidth  = img.fZoomWidth;
      fZoomHeight = img.fZoomHeight;
      fEditable   = img.fEditable;
      fIsGray     = img.fIsGray;
      fPaintMode  = 1;
   }

   return *this;
}


//______________________________________________________________________________
TASImage::~TASImage()
{
   // Image destructor, clean up image and visual.

   DestroyImage();
   delete fScaledImage;
   fScaledImage = 0;
}


//______________________________________________________________________________
static void init_icon_paths()
{
   // Set icons paths.

   const char *icons = "/icons";
#ifdef R__WIN32
      icons = "\\icons";
#endif

   TString homeIcons = gSystem->HomeDirectory();
   homeIcons += icons;

   TString rootIcons = gSystem->Getenv("ROOTSYS");
   rootIcons += icons;

   TString guiIcons = gEnv->GetValue("Gui.IconPath", "");

   gIconPaths[0] = StrDup(".");
   gIconPaths[1] = StrDup(homeIcons.Data());
   gIconPaths[2] = StrDup(rootIcons.Data());
   gIconPaths[3] = StrDup(guiIcons.Data());

#ifdef ROOTICONPATH
   gIconPaths[4] = ROOTICONPATH;
#endif

#ifdef EXTRAICONPATH
   gIconPaths[5] = EXTRAICONPATH;
#endif

   gIconPaths[6] = 0;
}


//______________________________________________________________________________
const char *TASImage::TypeFromMagicNumber(const char *file)
{
   // Guess the file type from the first byte of file.

   UChar_t magic;
   FILE *fp = fopen(file, "rb");
   const char *ret = "";

   if (!fp) return 0;

   if (!fread(&magic, 1, 1, fp)) {
      fclose(fp);
      return 0;
   }

   switch (magic) {
      case 0x00:
      {
         if (!fread(&magic, 1, 1, fp)) return 0;
         if (!fread(&magic, 1, 1, fp)) return 0;

         ret = (magic == 1) ? "ico" : "cur";
         break;
      }
      case 0x25:
      {
         if (!fread(&magic, 1, 1, fp)) return 0;
         if (magic == 0x21) ret = "ps";
         else if (magic == 0x50) ret = "pdf";
         break;
      }
      case 0x42:
         ret = "bmp";
         break;
      case 0x47:
         ret = "gif";
         break;
      case 0x54:
         ret = "tga";
         break;
      case 0x49:
         ret = "tiff";
         break;
      case 0x89:
         ret = "png";
         break;
      case 0xff:
         ret = "jpg";
         break;
      default:
         ret = "";
   }

   fclose(fp);
   return ret;
}


//______________________________________________________________________________
void TASImage::ReadImage(const char *filename, EImageFileTypes /*type*/)
{
   // Read specified image file.
   // The file type is determined by the file extension (the type argument is
   // ignored). It will attempt to append .gz and then .Z to the filename and
   // find such a file. If the filename ends with extension consisting of digits
   // only, it will attempt to find the file with this extension stripped
   // off. On success this extension will be used to load subimage from
   // the file with that number. Subimage is supported for GIF files
   // (ICO, BMP, CUR, TIFF, XCF to be supported in future).
   //  For example,
   //    i1 = TImage::Open("anim.gif.0"); // read the first subimage
   //    i4 = TImage::Open("anim.gif.3"); // read the forth subimage
   //
   // It is also possible to put XPM raw string (see also SetImageBuffer) as
   // the first input parameter ("filename"), such string  is returned by
   // GetImageBuffer method.

   if (!InitVisual()) {
      Warning("Scale", "Visual not initiated");
      return;
   }

   Bool_t xpm = filename && (filename[0] == '/' &&
                filename[1] == '*') && filename[2] == ' ';

   if (xpm) {  // XPM strings in-memory array
      SetImageBuffer((char**)&filename, TImage::kXpm);
      fName = "XPM_image";
      return;
   }

   if (!gIconPaths[0]) {
      init_icon_paths();
   }
   // suppress the "root : looking for image ..." messages
   set_output_threshold(0);

   static ASImageImportParams iparams;
   iparams.flags = 0;
   iparams.width = 0;
   iparams.height = 0;
   iparams.filter = SCL_DO_ALL;
   iparams.gamma = SCREEN_GAMMA;
   iparams.gamma_table = NULL;
   iparams.compression = GetImageCompression();
   iparams.format = ASA_ASImage;
   iparams.search_path = gIconPaths;
   iparams.subimage = 0;
   iparams.return_animation_delay = -1;

   TString ext;
   const char *dot;
   if (filename) dot = strrchr(filename, '.');
   else          dot = 0;
   ASImage *image = 0;
   TString fname = filename;

   if (!dot) {
      if (filename) ext = TypeFromMagicNumber(filename);
      else ext = dot + 1;
   } else {
      ext = dot + 1;
   }

   if (!ext.IsNull() && ext.IsDigit()) { // read subimage
      iparams.subimage = ext.Atoi();
      fname = fname(0, fname.Length() - ext.Length() - 1);
      ext = strrchr(fname.Data(), '.') + 1;
   }

   image = file2ASImage_extra(fname.Data(), &iparams);

   if (image) { // it's OK
      goto end;
   } else {  // try to read it via plugin
      if (ext.IsNull()) {
         return;
      }
      ext.ToLower();
      ext.Strip();
      UInt_t w = 0;
      UInt_t h = 0;
      unsigned char *bitmap = 0;

      TImagePlugin *plug = (TImagePlugin*)fgPlugList->FindObject(ext.Data());

      if (!plug) {
         TPluginHandler *handler = gROOT->GetPluginManager()->FindHandler("TImagePlugin", ext);
         if (!handler || ((handler->LoadPlugin() == -1))) {
            return;
         }
         plug = (TImagePlugin*)handler->ExecPlugin(1, ext.Data());

         if (!plug) {
            return;
         }

         fgPlugList->Add(plug);
      }

      if (plug) {
         if (plug->InheritsFrom(TASImagePlugin::Class())) {
            image = ((TASImagePlugin*)plug)->File2ASImage(fname.Data());
            if (image) goto end;
         }
         bitmap = plug->ReadFile(fname.Data(), w, h);
         if (bitmap) {
            image = bitmap2asimage(bitmap, w, h, 0, 0);
         }
         if (!image) {
            return;
         }
      }
   }

end:
   fName.Form("%s.", gSystem->BaseName(fname.Data()));

   DestroyImage();
   delete fScaledImage;
   fScaledImage = 0;

   fImage      = image;
   fZoomUpdate = kNoZoom;
   fEditable   = kFALSE;
   fZoomOffX   = 0;
   fZoomOffY   = 0;
   fZoomWidth  = fImage->width;
   fZoomHeight = fImage->height;
   fPaintMode  = 1;
}


//______________________________________________________________________________
void TASImage::WriteImage(const char *file, EImageFileTypes type)
{
   // Write image to specified file.
   // If there is no file extension or if the file extension is unknown, the
   // type argument will be used to determine the file type. The quality and
   // compression is derived from the TAttImage values.
   // It's posiible to write image into an animated GIF file by specifying file
   // name as "myfile.gif+" of "myfile.gif+NN", where NN is delay of displaying
   // subimages during animation in 10ms seconds units.
   // If NN is ommitted the delay between subimages is zero.
   // For repeated animation the last subimage must be specified as
   // "myfile.gif++NN", where NN is number of cycles. If NN is ommitted the
   // animation will be infinite.
   //
   // The following macro creates animated gif from jpeg images with names
   //    imageNN.jpg, where 1<= NN <= 10
   // {
   //    TImage *img = 0;
   //    gSystem->Unlink("anim.gif");  // delete existing file
   //
   //    for (int i = 1; i <= 10; i++) {
   //       delete img; // delete previous image
   //
   //       // Read image data. Image can be in any format, e.g. png, gif, etc.
   //       img = TImage::Open(Form("image%d.jpg", i));
   //
   //       if (i < 10) {
   //          img->WriteImage("anim.gif+");
   //       } else { // the last image written.  "++" stands for infinit animation.
   //          img->WriteImage("anim.gif++");
   //       }
   //    }
   // }

   if (!IsValid()) {
      Error("WriteImage", "no image loaded");
      return;
   }

   if (!file || !*file) {
      Error("WriteImage", "no file name specified");
      return;
   }

   const char *s;
   if ((s = strrchr(file, '.'))) {
      s++;
      EImageFileTypes t = GetFileType(s);
      if (t == kUnknown) {
         Error("WriteImage", "cannot determine a valid file type");
         return;
      }
      if (t != kUnknown)
         type = t;
   }

   if (type == kUnknown) {
      Error("WriteImage", "not a valid file type was specified");
      return;
   }

   UInt_t mytype;
   MapFileTypes(type, mytype);
   ASImageFileTypes atype = (ASImageFileTypes)mytype;

   UInt_t aquality;
   EImageQuality quality = GetImageQuality();
   MapQuality(quality, aquality);

   static TString fname;
   fname = file;
   static ASImageExportParams parms;
   ASImage *im = fScaledImage ? fScaledImage->fImage : fImage;

   switch (type) {
   case kXpm:
      parms.xpm.type = atype;
      parms.xpm.flags = EXPORT_ALPHA;
      parms.xpm.dither = 4;
      parms.xpm.opaque_threshold = 127;
      parms.xpm.max_colors = 512;
      break;
   case kBmp:
      ASImage2bmp(im, fname.Data(), 0);
      return;
   case kXcf:
      ASImage2xcf(im, fname.Data(), 0);
      return;
   case kPng:
      parms.png.type = atype;
      parms.png.flags = EXPORT_ALPHA;
      parms.png.compression = !GetImageCompression() ? -1 : int(GetImageCompression());
      break;
   case kJpeg:
      parms.jpeg.type = atype;
      parms.jpeg.flags = 0;
      parms.jpeg.quality = aquality;
      break;
   case kGif:
      parms.gif.type = atype;
      parms.gif.flags = EXPORT_ALPHA;
      parms.gif.dither = 0;
      parms.gif.opaque_threshold = 0;
      break;
   case kAnimGif:
   {
      parms.gif.type = atype;
      parms.gif.flags = EXPORT_ALPHA | EXPORT_APPEND;
      parms.gif.dither = 0;
      parms.gif.opaque_threshold = 0;
      parms.gif.animate_repeats = 0;

      s += 4; // skip "gif+"
      int delay = atoi(s);

      if (delay < 0) {
         delay = 0;
      }
      if (s[0] == '+') { // repeat count
         parms.gif.flags |= EXPORT_ANIMATION_REPEATS;
         s++;
         parms.gif.animate_repeats = atoi(s);
      }

      parms.gif.animate_delay = delay;

      int i1 = fname.Index("gif+");
      if (i1 != kNPOS) {
         fname = fname(0, i1 + 3);
      }
      break;
   }
   case kTiff:
      parms.tiff.type = atype;
      parms.tiff.flags = EXPORT_ALPHA;
      parms.tiff.rows_per_strip = 0;
      parms.tiff.compression_type = aquality <= 50 ? TIFF_COMPRESSION_JPEG :
                                                     TIFF_COMPRESSION_NONE;
      parms.tiff.jpeg_quality = 100;
      parms.tiff.opaque_threshold = 0;
      break;
   default:
      Error("WriteImage", "file type %s not yet supported", s);
      return;
   }

   if (!ASImage2file(im, 0, fname.Data(), atype, &parms)) {
      Error("WriteImage", "error writing file %s", file);
   }
}


//______________________________________________________________________________
TImage::EImageFileTypes TASImage::GetFileType(const char *ext)
{
   // Return file type depending on specified extension.
   // Protected method.

   TString s(ext);
   s.Strip();
   s.ToLower();

   if (s == "xpm")
      return kXpm;
   if (s == "png")
      return kPng;
   if (s == "jpg" || s == "jpeg")
      return kJpeg;
   if (s == "xcf")
      return kXcf;
   if (s == "ppm")
      return kPpm;
   if (s == "pnm")
      return kPnm;
   if (s == "bmp")
      return kBmp;
   if (s == "ico")
      return kIco;
   if (s == "cur")
      return kCur;
   if (s == "gif")
      return kGif;
   if (s.Contains("gif+"))
      return kAnimGif;
   if (s == "tiff")
      return kTiff;
   if (s == "xbm")
      return kXbm;
   if (s == "tga")
      return kTga;
   if (s == "xml")
      return kXml;

   return kUnknown;
}


//______________________________________________________________________________
void TASImage::MapFileTypes(EImageFileTypes &type, UInt_t &astype, Bool_t toas)
{
   // Map file type to/from AfterImage types.
   // Protected method.

   if (toas) {
      switch (type) {
         case kXpm:
            astype = ASIT_Xpm; break;
         case kZCompressedXpm:
            astype = ASIT_ZCompressedXpm; break;
         case kGZCompressedXpm:
            astype = ASIT_GZCompressedXpm; break;
         case kPng:
            astype = ASIT_Png; break;
         case kJpeg:
            astype = ASIT_Jpeg; break;
         case kXcf:
            astype = ASIT_Xcf; break;
         case kPpm:
            astype = ASIT_Ppm; break;
         case kPnm:
            astype = ASIT_Pnm; break;
         case kBmp:
            astype = ASIT_Bmp; break;
         case kIco:
            astype = ASIT_Ico; break;
         case kCur:
            astype = ASIT_Cur; break;
         case kGif:
            astype = ASIT_Gif; break;
         case kAnimGif:
            astype = ASIT_Gif; break;
         case kTiff:
            astype = ASIT_Tiff; break;
         case kXbm:
            astype = ASIT_Xbm; break;
         case kTga:
            astype = ASIT_Targa; break;
         case kXml:
            astype = ASIT_XMLScript; break;
         default:
            astype = ASIT_Unknown;
      }
   } else {
      switch (astype) {
         case ASIT_Xpm:
            type = kXpm; break;
         case ASIT_ZCompressedXpm:
            type = kZCompressedXpm; break;
         case ASIT_GZCompressedXpm:
            type = kGZCompressedXpm; break;
         case ASIT_Png:
            type = kPng; break;
         case ASIT_Jpeg:
            type = kJpeg; break;
         case ASIT_Xcf:
            type = kXcf; break;
         case ASIT_Ppm:
            type = kPpm; break;
         case ASIT_Pnm:
            type = kPnm; break;
         case ASIT_Bmp:
            type = kBmp; break;
         case ASIT_Ico:
            type = kIco; break;
         case ASIT_Cur:
            type = kCur; break;
         case ASIT_Gif:
            type = kGif; break;
         case ASIT_Tiff:
            type = kTiff; break;
         case ASIT_Xbm:
            type = kXbm; break;
         case ASIT_XMLScript:
            type = kXml; break;
         case ASIT_Targa:
            type = kTga; break;
         default:
            type = kUnknown;
      }
   }
}


//______________________________________________________________________________
void TASImage::MapQuality(EImageQuality &quality, UInt_t &asquality, Bool_t toas)
{
   // Map quality to/from AfterImage quality.
   // Protected method.

   if (toas) {
      switch (quality) {
         case kImgPoor:
            asquality = 25; break;
         case kImgFast:
            asquality = 75; break;
         case kImgGood:
            asquality = 50; break;
         case kImgBest:
            asquality = 100; break;
         default:
            asquality = 0;
      }
   } else {
      quality = kImgDefault;
      if (asquality > 0  && asquality <= 25)
         quality = kImgPoor;
      if (asquality > 26 && asquality <= 50)
         quality = kImgFast;
      if (asquality > 51 && asquality <= 75)
         quality = kImgGood;
      if (asquality > 76 && asquality <= 100)
         quality = kImgBest;
   }
}


//______________________________________________________________________________
void TASImage::SetImage(const Double_t *imageData, UInt_t width, UInt_t height,
                        TImagePalette *palette)
{
   // Deletes the old image and creates a new image depending on the values
   // of imageData. The size of the image is width X height.
   // The color of each pixel depends on the imageData of the corresponding
   // pixel. The palette is used to convert an image value into its color.
   // If palette is not defined (palette = 0) a default palette is used.
   // Any previously defined zooming is reset.

   TAttImage::SetPalette(palette);

   if (!InitVisual()) {
      Warning("SetImage", "Visual not initiated");
      return;
   }

   DestroyImage();
   delete fScaledImage;
   fScaledImage = 0;

   // get min and max value of image
   fMinValue = fMaxValue = *imageData;
   for (Int_t pixel = 1; pixel < Int_t(width * height); pixel++) {
      if (fMinValue > *(imageData + pixel)) fMinValue = *(imageData + pixel);
      if (fMaxValue < *(imageData + pixel)) fMaxValue = *(imageData + pixel);
   }

   // copy ROOT palette to asImage palette
   const TImagePalette &pal = GetPalette();

   ASVectorPalette asPalette;

   asPalette.npoints = pal.fNumPoints;
   Int_t col;
   for (col = 0; col < 4; col++)
      asPalette.channels[col] = new UShort_t[asPalette.npoints];

   memcpy(asPalette.channels[0], pal.fColorBlue,  pal.fNumPoints * sizeof(UShort_t));
   memcpy(asPalette.channels[1], pal.fColorGreen, pal.fNumPoints * sizeof(UShort_t));
   memcpy(asPalette.channels[2], pal.fColorRed,   pal.fNumPoints * sizeof(UShort_t));
   memcpy(asPalette.channels[3], pal.fColorAlpha, pal.fNumPoints * sizeof(UShort_t));

   asPalette.points = new Double_t[asPalette.npoints];
   for (Int_t point = 0; point < Int_t(asPalette.npoints); point++)
      asPalette.points[point] = fMinValue + (fMaxValue - fMinValue) * pal.fPoints[point];

   fImage = create_asimage_from_vector(fgVisual, (Double_t*)imageData, width,
                                       height, &asPalette, ASA_ASImage,
                                       GetImageCompression(), GetImageQuality());

   delete [] asPalette.points;
   for (col = 0; col < 4; col++)
      delete [] asPalette.channels[col];

   fZoomUpdate = 0;
   fZoomOffX   = 0;
   fZoomOffY   = 0;
   fZoomWidth  = width;
   fZoomHeight = height;
   fPaletteEnabled = kTRUE;
}


//______________________________________________________________________________
void TASImage::SetImage(const TArrayD &imageData, UInt_t width, TImagePalette *palette)
{
   // Delete the old image and creates a new image depending on the values
   // of imageData. The size of the image is width X (imageData.fN / width).
   // The color of each pixel depends on the imageData of the corresponding
   // pixel. The palette is used to convert an image value into its color.
   // If palette is not defined (palette = 0) a default palette is used.
   // Any previously defined zooming is reset.

   SetImage(imageData.GetArray(), width, imageData.GetSize() / width, palette);
}


//______________________________________________________________________________
void TASImage::SetImage(const TVectorD &imageData, UInt_t width, TImagePalette *palette)
{
   // Delete the old image and creates a new image depending on the values
   // of imageData. The size of the image is width X (imageData.fN / width).
   // The color of each pixel depends on the imageData of the corresponding
   // pixel. The palette is used to convert an image value into its color.
   // If palette is not defined (palette = 0) a default palette is used.
   // Any previously defined zooming is reset.

   SetImage(imageData.GetMatrixArray(), width,
            imageData.GetNoElements() / width, palette);
}


//______________________________________________________________________________
void TASImage::FromPad(TVirtualPad *pad, Int_t x, Int_t y, UInt_t w, UInt_t h)
{
   // Create an image from the given pad, afterwards this image can be
   // saved in any of the supported image formats.

   if (!pad) {
      Error("FromPad", "pad cannot be 0");
      return;
   }

   if (!InitVisual()) {
      Warning("FromPad", "Visual not initiated");
      return;
   }

   SetName(pad->GetName());

   DestroyImage();
   delete fScaledImage;
   fScaledImage = 0;

   if (gROOT->IsBatch()) { // in batch mode
      TVirtualPS *psave = gVirtualPS;
      gVirtualPS = (TVirtualPS*)gROOT->ProcessLineFast("new TImageDump()");
      gVirtualPS->Open(pad->GetName(), 114); // in memory
      gVirtualPS->SetBit(BIT(11)); //kPrintingPS

      TASImage *itmp = (TASImage*)gVirtualPS->GetStream();

      if (itmp && itmp->fImage) {
         itmp->BeginPaint();
      }

      TVirtualPad *sav = gPad;
      gPad = pad;
      pad->Paint();
      gPad = sav;

      if (itmp && itmp->fImage && (itmp != this)) {
         fImage = clone_asimage(itmp->fImage, SCL_DO_ALL);
         if (itmp->fImage->alt.argb32) {
            UInt_t sz = itmp->fImage->width*itmp->fImage->height;
            fImage->alt.argb32 = (ARGB32*)safemalloc(sz*sizeof(ARGB32));
            memcpy(fImage->alt.argb32, itmp->fImage->alt.argb32, sz*4);
         }
      }
      delete gVirtualPS;
      gVirtualPS = psave;
      return;
   }

   if (w == 0) {
      w = TMath::Abs(pad->UtoPixel(1.));
   }

   if (h == 0) {
      h = pad->VtoPixel(0.);
   }
   // syncronization
   gVirtualX->Update(1);
   if (!gThreadXAR) {
      gSystem->ProcessEvents();
      gSystem->Sleep(10);
      gSystem->ProcessEvents();
   }

   TVirtualPad *canvas = (TVirtualPad*)pad->GetCanvas();
   Int_t wid = (pad == canvas) ? pad->GetCanvasID() : pad->GetPixmapID();
   gVirtualX->SelectWindow(wid);

   Window_t wd = (Window_t)gVirtualX->GetCurrentWindow();
   if (!wd) return;

   static int x11 = -1;
   if (x11 < 0) x11 = gVirtualX->InheritsFrom("TGX11");

   if (x11) { //use built-in optimized version
      fImage = pixmap2asimage(fgVisual, wd, x, y, w, h, kAllPlanes, 0, 0);
   } else {
      unsigned char *bits = gVirtualX->GetColorBits(wd, 0, 0, w, h);

      if (!bits) { // error
         return;
      }
      fImage = bitmap2asimage(bits, w, h, 0, 0);
      delete [] bits;
   }
}


//______________________________________________________________________________
void TASImage::Draw(Option_t *option)
{
   // Draw image.
   // Support the following drawing options:
   // "T[x,y[,tint]]" - tile image (use specified offset and tint),
   //                   e.g. "T100,100,#556655"
   //                   with this option the zooming is not possible
   //                   and disabled
   // "N"             - display in new canvas (of original image size)
   // "X"             - image is drawn expanded to pad size
   // "Z"             - image is vectorized and image palette is drawn
   //
   // The default is to display the image in the current gPad.

   if (!fImage) {
      Error("Draw", "no image set");
      return;
   }

   TString opt = option;
   opt.ToLower();
   if (opt.Contains("n") || !gPad || !gPad->IsEditable()) {
      Int_t w = -64;
      Int_t h = 64;
      w = (fImage->width > 64) ? (Int_t)fImage->width : w;
      h = (fImage->height > 64) ? (Int_t)fImage->height : h;

      Float_t cx = 1./gStyle->GetScreenFactor();
      w = Int_t(w*cx) + 4;
      h = Int_t(h*cx) + 28;
      TString rname = GetName();
      rname.ReplaceAll(".", "");
      rname += Form("\", \"%s (%d x %d)", rname.Data(), fImage->width, fImage->height);
      rname = "new TCanvas(\"" + rname + Form("\", %d, %d);", w, h);
      gROOT->ProcessLineFast(rname.Data());
   }

   if (!opt.Contains("x")) {
      Double_t left = gPad->GetLeftMargin();
      Double_t right = gPad->GetRightMargin();
      Double_t top = gPad->GetTopMargin();
      Double_t bottom = gPad->GetBottomMargin();

      gPad->Range(-left / (1.0 - left - right),
                  -bottom / (1.0 - top - bottom),
                  1 + right / (1.0 - left - right),
                  1 + top / ( 1.0 - top - bottom));
      gPad->RangeAxis(0, 0, 1, 1);
   }

   TFrame *frame = gPad->GetFrame();
   frame->SetBorderMode(0);
   frame->SetFillColor(gPad->GetFillColor());
   frame->SetLineColor(gPad->GetFillColor());
   frame->Draw();

   TObject::Draw(option);
}


//______________________________________________________________________________
void TASImage::Image2Drawable(ASImage *im, Drawable_t wid, Int_t x, Int_t y,
                              Int_t xsrc, Int_t ysrc, UInt_t wsrc, UInt_t hsrc,
                              Option_t *opt)
{
   // Draw asimage on drawable.

   if (!im) return;

   wsrc = wsrc ? wsrc : im->width;
   hsrc = hsrc ? hsrc : im->height;

   static int x11 = -1;
   if (x11 < 0) x11 = gVirtualX->InheritsFrom("TGX11");

   Pixmap_t mask = kNone;

   if (x11) {
      UInt_t hh = hsrc;
      UInt_t ow = wsrc%8;
      UInt_t ww = wsrc - ow + (ow ? 8 : 0);

      UInt_t bit = 0;
      int i = 0;
      UInt_t yy = 0;
      UInt_t xx = 0;

      char *bits = new char[ww*hh]; //an array of bits

      ASImageDecoder *imdec = start_image_decoding(fgVisual, im, SCL_DO_ALPHA,
                                                   xsrc, ysrc, ww, 0, 0);
      if (imdec) {
         for (yy = 0; yy < hh; yy++) {
            imdec->decode_image_scanline(imdec);
            CARD32 *a = imdec->buffer.alpha;

            for (xx = 0; xx < ww; xx++) {
               if (a[xx]) {
                  SETBIT(bits[i], bit);
               } else {
                  CLRBIT(bits[i], bit);
               }
               bit++;
               if (bit == 8) {
                  bit = 0;
                  i++;
               }
            }
         }
      }

      stop_image_decoding(&imdec);

      mask = gVirtualX->CreateBitmap(gVirtualX->GetDefaultRootWindow(),
                                          (const char *)bits, ww, hh);
      delete [] bits;
   }

   GCValues_t gv;
   static GContext_t gc = 0;

   gv.fMask = kGCClipMask | kGCClipXOrigin | kGCClipYOrigin;
   gv.fClipMask = mask;
   gv.fClipXOrigin = x;
   gv.fClipYOrigin = y;

   if (!gc) {
      gc = gVirtualX->CreateGC(gVirtualX->GetDefaultRootWindow(), &gv);
   } else {
      gVirtualX->ChangeGC(gc, &gv);
   }

   if (x11) { //use built-in optimized version
      asimage2drawable(fgVisual, wid, im, (GC)gc, xsrc, ysrc, x, y, wsrc, hsrc, 1);
   } else {
      ASImage *img = 0;
      unsigned char *bits = (unsigned char *)im->alt.argb32;
      if (!bits) {
         img = tile_asimage(fgVisual, im, xsrc, ysrc, wsrc, hsrc,
                            0, ASA_ARGB32, 0, ASIMAGE_QUALITY_DEFAULT);
         bits = (unsigned char *)img->alt.argb32;
      }

      Pixmap_t pic = gVirtualX->CreatePixmapFromData(bits, wsrc, hsrc);
      if (pic) {
         TString option = opt;
         option.ToLower();
         if (!option.Contains("opaque")) {
            SETBIT(wsrc,31);
            SETBIT(hsrc,31);
         }
         gVirtualX->CopyArea(pic, wid, gc, 0, 0, wsrc, hsrc, x, y);
         gVirtualX->DeletePixmap(pic);
      } else {
         return;
      }
      if (img) {
         destroy_asimage(&img);
      }
   }

   // free mask pixmap
   if (gv.fClipMask != kNone) gVirtualX->DeletePixmap(gv.fClipMask);

   gv.fMask = kGCClipMask;
   gv.fClipMask = kNone;
   if (gc) gVirtualX->ChangeGC(gc, &gv);
}


//______________________________________________________________________________
void TASImage::PaintImage(Drawable_t wid, Int_t x, Int_t y, Int_t xsrc, Int_t ysrc,
                          UInt_t wsrc, UInt_t hsrc, Option_t *opt)
{
   // Draw image on the drawable wid (pixmap, window) at x,y position.
   //
   // wid        : Drawable (pixmap or window) on which image is drawn.
   // x,y        : Window coordinates where image is drawn.
   // xsrc, ysrc : X and Y coordinates of an image area to be drawn.
   // wsrc, hsrc : Widh and height image area to be drawn.

   Image2Drawable(fScaledImage ? fScaledImage->fImage : fImage, wid, x, y,
                  xsrc, ysrc, wsrc, hsrc, opt);
}


//______________________________________________________________________________
void TASImage::Paint(Option_t *option)
{
   // Paint image.
   // Support the following drawing options:
   // "T[x,y[,tint]]" - tile image (use specified offset and tint),
   //                   e.g. "T100,100,#556655"
   //                   with this option the zooming is not possible
   //                   and disabled
   // "N"             - display in new canvas (of original image size)
   // "X"             - image is drawn expanded to pad size
   // "Z"             - image is vectorized and image palette is drawn
   //
   // The default is to display the image in the current gPad.

   if (!fImage) {
      Error("Paint", "no image set");
      return;
   }

   if (!InitVisual()) {
      Warning("Paint", "Visual not initiated");
      return;
   }

   Int_t   tile_x = 0, tile_y = 0;
   CARD32  tile_tint = 0;
   Bool_t  tile = kFALSE;
   Bool_t  expand = kFALSE;

   TString opt = option;
   opt.ToLower();

   if (opt.Contains("t")) {
      char stint[64];
      if (sscanf(opt.Data() + opt.Index("t"), "t%d,%d,%s", &tile_x, &tile_y,
                 stint) <= 3) {
         tile = kTRUE;
         if (parse_argb_color(stint, (CARD32*)&tile_tint) == stint)
            tile_tint = 0;
      } else {
         Error("Paint", "tile option error");
      }
   } else if (opt.Contains("x")) {
      expand = kTRUE;
      fConstRatio = kFALSE;
   } else if (opt.Contains("z")) {
      fPaletteEnabled = kTRUE;

      if (!fImage->alt.vector) {
         Vectorize(256);
      }
   }

   ASImage *image = fImage;

   // Get geometry of pad
   Int_t to_w = gPad->UtoPixel(1.);
   Int_t to_h = gPad->VtoPixel(0.);

   // remove the size by the margin of the pad
   if (!expand) {
      to_h  = (Int_t)(to_h * (1.0 - gPad->GetBottomMargin() - gPad->GetTopMargin() ) + 0.5);
      to_w  = (Int_t)(to_w * (1.0 - gPad->GetLeftMargin() - gPad->GetRightMargin() ) + 0.5);
   }

   if ((to_w < 25 || to_h < 25) && !expand) {
      Error("Paint", "pad too small to display an image");
      return;
   }

   if (GetConstRatio()) {
      if ((Double_t)to_w / (Double_t)fZoomWidth <
          (Double_t)to_h / (Double_t)fZoomHeight)
         to_h = Int_t(Double_t(fZoomHeight) * to_w / fZoomWidth);
      else
         to_w = Int_t(Double_t(fZoomWidth) * to_h / fZoomHeight);
   }
   // upper left corner and size of the palette in pixels
   Int_t pal_Ax = to_w + gPad->UtoAbsPixel(gPad->GetLeftMargin()) +
                 (gPad->UtoAbsPixel(gPad->GetRightMargin()) / 10);
   Int_t pal_Ay = gPad->YtoAbsPixel(1.0);
   Int_t pal_x = to_w + gPad->UtoPixel(gPad->GetLeftMargin()) +
                 (gPad->UtoPixel(gPad->GetRightMargin()) / 10);
   Int_t pal_y = gPad->YtoPixel(1.0);
   Int_t pal_w = gPad->UtoPixel(gPad->GetRightMargin()) / 3;
   Int_t pal_h = to_h;

   ASImage  *grad_im = 0;

   if (fImage->alt.vector && fPaletteEnabled) {
      // draw the palette
      ASGradient grad;
      const TImagePalette &pal = GetPalette();

      grad.npoints = pal.fNumPoints;
      grad.type    = GRADIENT_Top2Bottom;
      grad.color   = new ARGB32[grad.npoints];
      grad.offset  = new double[grad.npoints];

      for (Int_t pt = 0; pt < grad.npoints; pt++) {
         Int_t oldPt = grad.npoints - pt -1;
         grad.offset[pt] = 1 - pal.fPoints[oldPt];
         grad.color[pt] = (((ARGB32)(pal.fColorBlue[oldPt]  & 0xff00)) >>  8) |
                          (((ARGB32)(pal.fColorGreen[oldPt] & 0xff00))      ) |
                          (((ARGB32)(pal.fColorRed[oldPt]   & 0xff00)) <<  8) |
                          (((ARGB32)(pal.fColorAlpha[oldPt] & 0xff00)) << 16);
      }

      grad_im = make_gradient(fgVisual, &grad , UInt_t(pal_w),
                              pal_h, SCL_DO_COLOR,
                              ASA_ARGB32, GetImageCompression(), GetImageQuality());

      delete [] grad.color;
      delete [] grad.offset;
   }

   if (tile) {
      delete fScaledImage;
      fScaledImage = (TASImage*)TImage::Create();
      fScaledImage->fImage = tile_asimage(fgVisual, fImage, tile_x, tile_y,
                                          to_w, to_h, tile_tint, ASA_ASImage,
                                          GetImageCompression(), GetImageQuality());
      image = fScaledImage->fImage;

   } else if (fZoomUpdate == kZoomOps) {
      image = fImage;

   } else {
      // Scale and zoom image if needed
      if (Int_t(fImage->width) != to_w || Int_t(fImage->height) != to_h ||
          fImage->width != fZoomWidth || fImage->height != fZoomHeight) {

         if (fScaledImage && (Int_t(fScaledImage->GetWidth()) != to_w ||
                Int_t(fScaledImage->GetHeight()) != to_h ||
                fZoomUpdate)) {

            delete fScaledImage;
            fScaledImage = 0;
         }

         if (!fScaledImage) {
            fScaledImage = (TASImage*)TImage::Create();

            if (fZoomWidth && fZoomHeight &&
                ((fImage->width != fZoomWidth) || (fImage->height != fZoomHeight))) {
               // zoom and scale image
               ASImage *tmpImage = 0;

               tmpImage = tile_asimage(fgVisual, fImage, fZoomOffX,
                                          fImage->height - fZoomHeight - fZoomOffY,
                                          fZoomWidth, fZoomHeight, 0, ASA_ASImage,
                                          GetImageCompression(), GetImageQuality());

               if (tmpImage) {
                  fScaledImage->fImage = scale_asimage(fgVisual, tmpImage, to_w, to_h,
                                                       ASA_ASImage, GetImageCompression(),
                                                      GetImageQuality());
                  destroy_asimage(&tmpImage);
               }
            } else {
               // scale image, no zooming
               fScaledImage->fImage = scale_asimage(fgVisual, fImage, to_w, to_h,
                                                    ASA_ASImage, GetImageCompression(),
                                                    GetImageQuality());
            }
         }
         image = fScaledImage->fImage;
      }
   }
   fZoomUpdate = 0;

   if (!image) {
      Error("Paint", "image could not be rendered to display");
      return;
   }

   int tox = expand  ? 0 : int(gPad->UtoPixel(1.) * gPad->GetLeftMargin());
   int toy = expand  ? 0 : int(gPad->VtoPixel(0.) * gPad->GetTopMargin());

   if (!gROOT->IsBatch()) {
      Window_t wid = (Window_t)gVirtualX->GetWindowID(gPad->GetPixmapID());
      Image2Drawable(fScaledImage ? fScaledImage->fImage : fImage, wid, tox, toy);

      if (grad_im && fPaletteEnabled) {
         // draw color bar
         Image2Drawable(grad_im, wid, pal_x, pal_y);

         // values of palette
         TGaxis axis;
         Int_t ndiv = 510;
         double min = fMinValue;
         double max = fMaxValue;
         axis.SetLineColor(0);       // draw white ticks
         Double_t pal_Xpos = gPad->AbsPixeltoX(pal_Ax + pal_w);
         axis.PaintAxis(pal_Xpos, gPad->PixeltoY(pal_Ay + pal_h - 1),
                        pal_Xpos, gPad->PixeltoY(pal_Ay),
                        min, max, ndiv, "+LU");
         min = fMinValue;
         max = fMaxValue;
         axis.SetLineColor(1);       // draw black ticks
         axis.PaintAxis(pal_Xpos, gPad->AbsPixeltoY(pal_Ay + pal_h),
                        pal_Xpos, gPad->AbsPixeltoY(pal_Ay + 1),
                        min, max, ndiv, "+L");
      }
   }

   // loop over pxmap and draw image to PostScript
   if (gVirtualPS) {
      if (gVirtualPS->InheritsFrom("TImageDump")) { // PostScript is asimage
         TImage *dump = (TImage *)gVirtualPS->GetStream();
         dump->Merge(fScaledImage ? fScaledImage : this, "alphablend",
                     gPad->XtoAbsPixel(0), gPad->YtoAbsPixel(1));

         if (grad_im) {
            TASImage tgrad;
            tgrad.fImage = grad_im;
            dump->Merge(&tgrad, "alphablend", pal_Ax, pal_Ay);

            // values of palette
            TGaxis axis;
            Int_t ndiv = 510;
            double min = fMinValue;
            double max = fMaxValue;
            axis.SetLineColor(1);       // draw black ticks
            Double_t pal_Xpos = gPad->AbsPixeltoX(pal_Ax + pal_w);
            axis.PaintAxis(pal_Xpos, gPad->AbsPixeltoY(pal_Ay + pal_h),
                           pal_Xpos, gPad->AbsPixeltoY(pal_Ay + 1),
                           min, max, ndiv, "+L");
         }
         return;
      } else if (gVirtualPS->InheritsFrom("TPDF")) {
         Warning("Paint", "PDF not implemeted yet");
         return;
      } else if (gVirtualPS->InheritsFrom("TSVG")) {
         Warning("Paint", "SVG not implemeted yet");
         return;
      }

      // get special color cell to be reused during image printing
      TObjArray *colors = (TObjArray*) gROOT->GetListOfColors();
      TColor *color = 0;
      // Look for color by name
      if ((color = (TColor*)colors->FindObject("Image_PS")) == 0)
         color = new TColor(colors->GetEntries(), 1., 1., 1., "Image_PS");

      gVirtualPS->SetFillColor(color->GetNumber());
      gVirtualPS->SetFillStyle(1001);

      Double_t dx = gPad->GetX2()-gPad->GetX1();
      Double_t dy = gPad->GetY2()-gPad->GetY1();
      Double_t x1,x2,y1,y2;

      if (expand) {
         x1 = gPad->GetX1();
         x2 = x1+dx/image->width;
         y1 = gPad->GetY2();
         y2 = y1+dy/image->height;
      } else {
         x1 = gPad->GetX1()+dx*gPad->GetLeftMargin();
         x2 = x1+(dx*(1-gPad->GetRightMargin()-gPad->GetLeftMargin()))/image->width;
         y1 = gPad->GetY2()-dy*gPad->GetTopMargin();
         y2 = y1+(dy*(1-gPad->GetTopMargin()-gPad->GetBottomMargin()))/image->height;
      }

      gVirtualPS->CellArrayBegin(image->width, image->height, x1, x2, y1, y2);

      ASImageDecoder *imdec = start_image_decoding(fgVisual, image, SCL_DO_ALL,
                                                   0, 0, image->width, image->height, 0);
      for (Int_t yt = 0; yt < (Int_t)image->height; yt++) {
         imdec->decode_image_scanline(imdec);
         for (Int_t xt = 0; xt < (Int_t)image->width; xt++)
            gVirtualPS->CellArrayFill(imdec->buffer.red[xt],
                                      imdec->buffer.green[xt],
                                      imdec->buffer.blue[xt]);
      }
      stop_image_decoding(&imdec);
      gVirtualPS->CellArrayEnd();

      // print the color bar
      if (grad_im) {
         Double_t xconv = (gPad->AbsPixeltoX(pal_Ax + pal_w) - gPad->AbsPixeltoX(pal_Ax)) / grad_im->width;
         Double_t yconv = (gPad->AbsPixeltoY(pal_Ay - pal_h) - gPad->AbsPixeltoY(pal_Ay)) / grad_im->height;
         x1 = gPad->AbsPixeltoX(pal_Ax);
         x2 = x1 + xconv;
         y2 = gPad->AbsPixeltoY(pal_Ay);
         y1 = y2 - yconv;
         gVirtualPS->CellArrayBegin(grad_im->width, grad_im->height,
                                    x1, x2, y1, y2);

         imdec = start_image_decoding(fgVisual, grad_im, SCL_DO_ALL,
                                      0, 0, grad_im->width, grad_im->height, 0);
         for (Int_t yt = 0; yt < (Int_t)grad_im->height; yt++) {
            imdec->decode_image_scanline(imdec);
            for (Int_t xt = 0; xt < (Int_t)grad_im->width; xt++)
               gVirtualPS->CellArrayFill(imdec->buffer.red[xt],
                                         imdec->buffer.green[xt],
                                         imdec->buffer.blue[xt]);
         }
         stop_image_decoding(&imdec);
         gVirtualPS->CellArrayEnd();

         // values of palette
         TGaxis axis;
         Int_t ndiv = 510;
         double min = fMinValue;
         double max = fMaxValue;
         axis.SetLineColor(1);       // draw black ticks
         Double_t pal_Xpos = gPad->AbsPixeltoX(pal_Ax + pal_w);
         axis.PaintAxis(pal_Xpos, gPad->AbsPixeltoY(pal_Ay + pal_h),
                        pal_Xpos, gPad->AbsPixeltoY(pal_Ay + 1),
                        min, max, ndiv, "+L");

      }
   }

   if (grad_im) {
      destroy_asimage(&grad_im);
   }
}


//______________________________________________________________________________
Int_t TASImage::DistancetoPrimitive(Int_t px, Int_t py)
{
   // Is the mouse in the image ?

   Int_t pxl, pyl, pxt, pyt;

   Int_t px1 = gPad->XtoAbsPixel(0);
   Int_t py1 = gPad->YtoAbsPixel(0);
   Int_t px2 = gPad->XtoAbsPixel(1);
   Int_t py2 = gPad->YtoAbsPixel(1);

   if (px1 < px2) {pxl = px1; pxt = px2;}
   else           {pxl = px2; pxt = px1;}
   if (py1 < py2) {pyl = py1; pyt = py2;}
   else           {pyl = py2; pyt = py1;}

   if ((px > pxl && px < pxt) && (py > pyl && py < pyt))
      return 0;

   return 999999;
}


//______________________________________________________________________________
void TASImage::ExecuteEvent(Int_t event, Int_t px, Int_t py)
{
   // Execute mouse events.

   if (IsEditable()) {
      gPad->ExecuteEvent(event, px, py);
      return;
   }

   gPad->SetCursor(kCross);

   static Int_t stx, sty;
   static Int_t oldx, oldy;

   if (!IsValid()) return;

   if (event == kButton1Motion || event == kButton1Down  ||
       event == kButton1Up) {

      // convert to image pixel on screen
      Int_t imgX = px - gPad->XtoAbsPixel(0);
      Int_t imgY = py - gPad->YtoAbsPixel(1);

      if (imgX < 0)  px = px - imgX;
      if (imgY < 0)  py = py - imgY;

      ASImage *image = fImage;
      if (fScaledImage) image = fScaledImage->fImage;

      if (imgX >= (int)image->width)  px = px - imgX + image->width - 1;
      if (imgY >= (int)image->height) py = py - imgY + image->height - 1;

      switch (event) {

         case kButton1Down:
            gVirtualX->SetLineColor(-1);

            stx = oldx = px;
            sty = oldy = py;
            break;

         case kButton1Motion:
            gVirtualX->DrawBox(oldx, oldy, stx, sty, TVirtualX::kHollow);
            oldx = px;
            oldy = py;
            gVirtualX->DrawBox(oldx, oldy, stx, sty, TVirtualX::kHollow);
            break;

         case kButton1Up:
            // do nothing if zoom area is too small
            if ( TMath::Abs(stx - px) < 5 || TMath::Abs(sty - py) < 5)
               return;

            Double_t xfact = (fScaledImage) ? (Double_t)fScaledImage->fImage->width  / fZoomWidth  : 1;
            Double_t yfact = (fScaledImage) ? (Double_t)fScaledImage->fImage->height / fZoomHeight : 1;

            Int_t imgX1 = stx - gPad->XtoAbsPixel(0);
            Int_t imgY1 = sty - gPad->YtoAbsPixel(1);
            Int_t imgX2 = px  - gPad->XtoAbsPixel(0);
            Int_t imgY2 = py  - gPad->YtoAbsPixel(1);

            imgY1 = image->height - 1 - imgY1;
            imgY2 = image->height - 1 - imgY2;
            imgX1 = (Int_t)(imgX1 / xfact) + fZoomOffX;
            imgY1 = (Int_t)(imgY1 / yfact) + fZoomOffY;
            imgX2 = (Int_t)(imgX2 / xfact) + fZoomOffX;
            imgY2 = (Int_t)(imgY2 / yfact) + fZoomOffY;

            Zoom((imgX1 < imgX2) ? imgX1 : imgX2, (imgY1 < imgY2) ? imgY1 : imgY2,
                 TMath::Abs(imgX1 - imgX2) + 1, TMath::Abs(imgY1 - imgY2) + 1);

            gVirtualX->SetLineColor(-1);
            gPad->Modified(kTRUE);
            gPad->Update();
            break;
      }
   }
}


//______________________________________________________________________________
char *TASImage::GetObjectInfo(Int_t px, Int_t py) const
{
   // Get image pixel coordinates and the pixel value at the mouse pointer.

   static char info[64];
   info[0] = 0;

   if (!IsValid()) return info;

   // convert to image pixel on screen
   px -= gPad->XtoAbsPixel(0);
   py -= gPad->YtoAbsPixel(1);

   // no info if mouse is outside of image
   if (px < 0 || py < 0)  return info;

   ASImage *image = fImage;
   if (fScaledImage) image = fScaledImage->fImage;
   if (px >= (int)image->width || py >= (int)image->height)
      return info;

   py = image->height - 1 - py;
   // convert to original image size and take zooming into account
   if (fScaledImage) {
      px = (Int_t)(px / (Double_t)fScaledImage->fImage->width  * fZoomWidth ) + fZoomOffX;
      py = (Int_t)(py / (Double_t)fScaledImage->fImage->height * fZoomHeight) + fZoomOffY;
   }

   if (fImage->alt.vector) {
      snprintf(info,64,"x: %d  y: %d   %.5g",
              px, py, fImage->alt.vector[px + py * fImage->width]);
   } else {
      snprintf(info,64,"x: %d  y: %d", px, py);
   }

   return info;
}


//______________________________________________________________________________
void TASImage::SetPalette(const TImagePalette *palette)
{
   // Set a new palette to an image.
   // Only images that were created with the SetImage() functions can be
   // modified with this function. The previously used palette is destroyed.

   TAttImage::SetPalette(palette);

   if (!InitVisual()) {
      Warning("SetPalette", "Visual not initiated");
      return;
   }

   if (!IsValid()) {
      Warning("SetPalette", "Image not valid");
      return;
   }

   if (fImage->alt.vector == 0)
      return;

   // copy ROOT palette to asImage palette
   const TImagePalette &pal = GetPalette();

   ASVectorPalette asPalette;
   asPalette.npoints = pal.fNumPoints;
   asPalette.channels[0] = new CARD16 [asPalette.npoints];
   asPalette.channels[1] = new CARD16 [asPalette.npoints];
   asPalette.channels[2] = new CARD16 [asPalette.npoints];
   asPalette.channels[3] = new CARD16 [asPalette.npoints];
   memcpy(asPalette.channels[0], pal.fColorBlue,  pal.fNumPoints * sizeof(UShort_t));
   memcpy(asPalette.channels[1], pal.fColorGreen, pal.fNumPoints * sizeof(UShort_t));
   memcpy(asPalette.channels[2], pal.fColorRed,   pal.fNumPoints * sizeof(UShort_t));
   memcpy(asPalette.channels[3], pal.fColorAlpha, pal.fNumPoints * sizeof(UShort_t));

   asPalette.points = new double[asPalette.npoints];
   for (Int_t point = 0; point < Int_t(asPalette.npoints); point++)
      asPalette.points[point] = fMinValue + (fMaxValue - fMinValue) * pal.fPoints[point];

   // use the new palette in this image
   colorize_asimage_vector(fgVisual, fImage, &asPalette, ASA_ASImage, GetImageQuality());

   delete [] asPalette.points;
   for (Int_t col = 0; col < 4; col++)
      delete [] asPalette.channels[col];


   delete fScaledImage;
   fScaledImage = 0;
}


//______________________________________________________________________________
void TASImage::Scale(UInt_t toWidth, UInt_t toHeight)
{
   // Scale the original image.
   // The size of the image on the screen does not change because it is defined
   // by the size of the pad.
   // This function can be used to change the size of an image before writing
   // it into a file. The colors of the new pixels are interpolated.
   // An image created with the SetImage() functions cannot be modified with
   // the function SetPalette() any more after a call of this function!

   if (!IsValid()) {
      Warning("Scale", "Image not initiated");
      return;
   }

   if (!InitVisual()) {
      Warning("Scale", "Visual not initiated");
      return;
   }

   if (toWidth < 1)
       toWidth = 1;
   if (toHeight < 1 )
      toHeight = 1;
   if (toWidth > 30000)
      toWidth = 30000;
   if (toHeight > 30000)
      toHeight = 30000;

   ASImage *img = scale_asimage(fgVisual, fImage, toWidth, toHeight,
                                ASA_ASImage, GetImageCompression(),
                                GetImageQuality());
   DestroyImage();
   fImage = img;
   UnZoom();
   fZoomUpdate = kZoomOps;
}


//______________________________________________________________________________
void TASImage::Slice(UInt_t xStart, UInt_t xEnd, UInt_t yStart,  UInt_t yEnd,
                     UInt_t toWidth, UInt_t toHeight)
{
   // Another method of enlarging images where corners remain unchanged,
   // but middle part gets tiled.

   if (!IsValid()) {
      Warning("Scale", "Image not initiated");
      return;
   }

   if (!InitVisual()) {
      Warning("Scale", "Visual not initiated");
      return;
   }

   if (toWidth < 1)
       toWidth = 1;
   if (toHeight < 1 )
      toHeight = 1;
   if (toWidth > 30000)
      toWidth = 30000;
   if (toHeight > 30000)
      toHeight = 30000;

   ASImage *img = slice_asimage(fgVisual, fImage, xStart, xEnd,
                                yStart, yEnd, toWidth, toHeight,
                                ASA_ASImage, GetImageCompression(),
                                GetImageQuality());

   DestroyImage();
   fImage = img;
   UnZoom();
   fZoomUpdate = kZoomOps;
}


//______________________________________________________________________________
void TASImage::Tile(UInt_t toWidth, UInt_t toHeight)
{
   // Tile the original image.

   if (!IsValid()) {
      Warning("Tile", "Image not initiated");
      return;
   }

   if (!InitVisual()) {
      Warning("Tile", "Visual not initiated");
      return;
   }

   if (toWidth < 1)
       toWidth = 1;
   if (toHeight < 1 )
      toHeight = 1;
   if (toWidth > 30000)
      toWidth = 30000;
   if (toHeight > 30000)
      toHeight = 30000;

   ASImage *img = tile_asimage(fgVisual, fImage, 0, 0, toWidth, toHeight, 0,
                                ASA_ASImage, GetImageCompression(), GetImageQuality());
   DestroyImage();
   fImage = img;
   UnZoom();
   fZoomUpdate = kZoomOps;
}


//______________________________________________________________________________
void TASImage::Zoom(UInt_t offX, UInt_t offY, UInt_t width, UInt_t height)
{
   // The area of an image displayed in a pad is defined by this function.
   // Note: the size on the screen is defined by the size of the pad.
   // The original image is not modified by this function.
   // If width or height is larger than the original image they are reduced to
   // the width and height of the image.
   // If the off values are too large (off + width > image width) than the off
   // values are decreased. For example: offX = image width - width
   // Note: the parameters are always relative to the original image not to the
   // size of an already zoomed image.

   if (!IsValid()) {
      Warning("Zoom", "Image not valid");
      return;
   }
   fZoomUpdate = kZoom;

   fZoomWidth  = (width == 0) ? 1 : ((width > fImage->width) ? fImage->width : width);
   fZoomHeight = (height == 0) ? 1 : ((height > fImage->height) ? fImage->height : height);
   fZoomOffX   = offX;
   if (fZoomOffX + fZoomWidth > fImage->width)
      fZoomOffX = fImage->width - fZoomWidth;
   fZoomOffY   = offY;
   if (fZoomOffY + fZoomHeight > fImage->height)
      fZoomOffY = fImage->height - fZoomHeight;
}


//______________________________________________________________________________
void TASImage::UnZoom()
{
   // Un-zoom the image to original size.
   // UnZoom() - performs undo for Zoom,Crop,Scale actions

   if (!IsValid()) {
      Warning("UnZoom", "Image not valid");
      return;
   }
   fZoomUpdate = kZoom;
   fZoomOffX   = 0;
   fZoomOffY   = 0;
   fZoomWidth  = fImage->width;
   fZoomHeight = fImage->height;

   delete fScaledImage;
   fScaledImage = 0;
}


//______________________________________________________________________________
void TASImage::Flip(Int_t flip)
{
   // Flip image in place.
   // Flip is either 90, 180, 270, 180 is default.
   // This function manipulates the original image and destroys the
   // scaled and zoomed image which will be recreated at the next call of
   // the Draw function. If the image is zoomed the zoom - coordinates are
   // now relative to the new image.
   // This function cannot be used for images which were created with the
   // SetImage() functions, because the original pixel values would be
   // destroyed.

   if (!IsValid()) {
      Warning("Flip", "Image not valid");
      return;
   }
   if (!InitVisual()) {
      Warning("Flip", "Visual not initiated");
      return;
   }

   if (fImage->alt.vector) {
      Warning("Flip", "flip does not work for data images");
      return;
   }

   Int_t rflip = flip/90;

   UInt_t w = fImage->width;
   UInt_t h = fImage->height;

   if (rflip & 1) {
      w = fImage->height;
      h = fImage->width;
   }

   ASImage *img = flip_asimage(fgVisual, fImage, 0, 0, w, h, rflip,
                               ASA_ASImage, GetImageCompression(),
                               GetImageQuality());
   DestroyImage();
   fImage = img;
   UnZoom();
}


//______________________________________________________________________________
void TASImage::Mirror(Bool_t vert)
{
   // Mirror image in place.
   // If vert is true mirror in vertical axis, horizontal otherwise.
   // Vertical is default.
   // This function manipulates the original image and destroys the
   // scaled and zoomed image which will be recreated at the next call of
   // the Draw function. If the image is zoomed the zoom - coordinates are
   // now relative to the new image.
   // This function cannot be used for images which were created with the
   // SetImage() functions, because the original pixel values would be
   // destroyed.

   if (!IsValid()) {
      Warning("Mirror", "Image not valid");
      return;
   }

   if (!InitVisual()) {
      Warning("Mirrow", "Visual not initiated");
      return;
   }

   if (fImage->alt.vector) {
      Warning("Mirror", "mirror does not work for data images");
      return;
   }

   ASImage *img = mirror_asimage(fgVisual, fImage, 0, 0,
                                 fImage->width, fImage->height, vert,
                                 ASA_ASImage, GetImageCompression(),
                                 GetImageQuality());
   DestroyImage();
   fImage = img;
   UnZoom();
}


//______________________________________________________________________________
UInt_t TASImage::GetWidth() const
{
   // Return width of original image not of the displayed image.
   // (Number of image pixels)

   return fImage ? fImage->width : 0;
}


//______________________________________________________________________________
UInt_t TASImage::GetHeight() const
{
   // Return height of original image not of the displayed image.
   // (Number of image pixels)

   return fImage ? fImage->height : 0;
}


//______________________________________________________________________________
UInt_t TASImage::GetScaledWidth() const
{
   // Return width of the displayed image not of the original image.
   // (Number of screen pixels)

   return fScaledImage ? fScaledImage->fImage->width : GetWidth();
}


//______________________________________________________________________________
UInt_t TASImage::GetScaledHeight() const
{
   // Return height of the displayed image not of the original image.
   // (Number of screen pixels)

   return fScaledImage ? fScaledImage->fImage->height : GetHeight();
}


//______________________________________________________________________________
void TASImage::GetZoomPosition(UInt_t &x, UInt_t &y, UInt_t &w, UInt_t &h) const
{
   // Return the zoom parameters.
   // This is useful when the zoom has been done interactively using the mouse.

   x = fZoomOffX;
   y = fZoomOffY;
   w = fZoomWidth;
   h = fZoomHeight;
}


//______________________________________________________________________________
Bool_t TASImage::InitVisual()
{
   // Static function to initialize the ASVisual.

   Display *disp;

   Bool_t inbatch = fgVisual && (fgVisual->dpy == (void*)1); // was in batch
   Bool_t noX = gROOT->IsBatch() || gVirtualX->InheritsFrom("TGWin32");

   // was in batch, but switched to gui
   if (inbatch && !noX) {
      destroy_asvisual(fgVisual, kFALSE);
      fgVisual = 0;
   }

   if (fgVisual && fgVisual->dpy) { // already initialized
      return kTRUE;
   }

   // batch or win32 mode
   if (!fgVisual && noX) {
      disp = 0;
      fgVisual = create_asvisual(0, 0, 0, 0);
      fgVisual->dpy = (Display*)1; //fake (not used)
      return kTRUE;
   }

   disp = (Display*) gVirtualX->GetDisplay();
   Int_t screen  = gVirtualX->GetScreen();
   Int_t depth   = gVirtualX->GetDepth();
   Visual *vis   = (Visual*) gVirtualX->GetVisual();
   Colormap cmap = (Colormap) gVirtualX->GetColormap();
#ifndef WIN32
   if (vis == 0 || cmap == 0) {
      fgVisual = create_asvisual(0, 0, 0, 0);
   } else {
      fgVisual = create_asvisual_for_id(disp, screen, depth,
                                        XVisualIDFromVisual(vis), cmap, 0);
   }
#else
   fgVisual = create_asvisual(0, 0, 0, 0);
   fgVisual->dpy = (Display*)1; //fake (not used)
#endif

   return kTRUE;
}


//______________________________________________________________________________
void TASImage::StartPaletteEditor()
{
   // Start palette editor.

   if (!IsValid()) {
      Warning("StartPaletteEditor", "Image not valid");
      return;
   }
   if (fImage->alt.vector == 0) {
      Warning("StartPaletteEditor", "palette can be modified only for data images");
      return;
   }

   // Opens a GUI to edit the color palette
   TAttImage::StartPaletteEditor();
}


//______________________________________________________________________________
Pixmap_t TASImage::GetPixmap()
{
   // Returns image pixmap.
   // The pixmap must deleted by user.

   if (!InitVisual()) {
      Warning("GetPixmap", "Visual not initiated");
      return 0;
   }

   Pixmap_t ret;

   ASImage *img = fScaledImage ? fScaledImage->fImage : fImage;

   static int x11 = -1;
   if (x11 < 0) x11 = gVirtualX->InheritsFrom("TGX11");

   if (x11) {   // use builtin version
      ret = (Pixmap_t)asimage2pixmap(fgVisual, gVirtualX->GetDefaultRootWindow(),
                                       img, 0, kTRUE);
   } else {
      if (!fImage->alt.argb32) {
         BeginPaint();
      }
      ret = gVirtualX->CreatePixmapFromData((unsigned char*)fImage->alt.argb32,
                                             fImage->width, fImage->height);
   }

   return ret;
}


//______________________________________________________________________________
Pixmap_t TASImage::GetMask()
{
   // Returns image mask pixmap (alpha channel).
   // The pixmap must deleted by user.

   Pixmap_t pxmap = 0;

   if (!InitVisual()) {
      Warning("GetMask", "Visual not initiated");
      return pxmap;
   }

   ASImage *img = fScaledImage ? fScaledImage->fImage : fImage;

   if (!img) {
      Warning("GetMask", "No image");
      return pxmap;
   }

   UInt_t hh = img->height;
   UInt_t ow = img->width%8;
   UInt_t ww = img->width - ow + (ow ? 8 : 0);

   UInt_t bit = 0;
   int i = 0;
   UInt_t y = 0;
   UInt_t x = 0;

   char *bits = new char[ww*hh]; //an array of bits

   ASImageDecoder *imdec = start_image_decoding(fgVisual, img, SCL_DO_ALPHA,
                                                0, 0, ww, 0, 0);
   if(!imdec) {
      delete [] bits;
      return 0;
   }

   for (y = 0; y < hh; y++) {
      imdec->decode_image_scanline(imdec);
      CARD32 *a = imdec->buffer.alpha;

      for (x = 0; x < ww; x++) {
         if (a[x]) {
            SETBIT(bits[i], bit);
         } else {
            CLRBIT(bits[i], bit);
         }
         bit++;
         if (bit == 8) {
            bit = 0;
            i++;
         }
      }
   }

   stop_image_decoding(&imdec);
   pxmap = gVirtualX->CreateBitmap(gVirtualX->GetDefaultRootWindow(), (const char *)bits,
                                   ww, hh);
   delete [] bits;
   return pxmap;
}


//______________________________________________________________________________
void TASImage::SetImage(Pixmap_t pxm, Pixmap_t mask)
{
   // Create image from pixmap.

   if (!InitVisual()) {
      Warning("SetImage", "Visual not initiated");
      return;
   }

   DestroyImage();
   delete fScaledImage;
   fScaledImage = 0;

   Int_t xy;
   UInt_t w, h;
   gVirtualX->GetWindowSize(pxm, xy, xy, w, h);

   if (fName.IsNull()) fName.Form("img_%dx%d",w, h);

   static int x11 = -1;
   if (x11 < 0) x11 = gVirtualX->InheritsFrom("TGX11");

   if (x11) { //use built-in optimized version
      fImage = picture2asimage(fgVisual, pxm, mask, 0, 0, w, h, kAllPlanes, 1, 0);
   } else {
      unsigned char *bits = gVirtualX->GetColorBits(pxm, 0, 0, w, h);
      if (!bits) {   // error
         return;
      }

      // no mask
      if (!mask) {
         fImage = bitmap2asimage(bits, w, h, 0, 0);
         delete [] bits;
         return;
      }
      unsigned char *mask_bits = gVirtualX->GetColorBits(mask, 0, 0, w, h);
      fImage = bitmap2asimage(bits, w, h, 0, mask_bits);
      delete [] mask_bits;
      delete [] bits;
   }
}


//______________________________________________________________________________
TArrayL *TASImage::GetPixels(Int_t x, Int_t y, UInt_t width, UInt_t height)
{
   // Return 2D array of machine dependent pixel values.

   if (!fImage) {
      Warning("GetPixels", "Wrong Image");
      return 0;
   }

   ASImage *img =  fScaledImage ? fScaledImage->fImage : fImage;
   ASImageDecoder *imdec;

   width = !width  ? img->width : width;
   height = !height ? img->height : height;

   if (x < 0) {
      width -= x;
      x = 0 ;
   }
   if (y < 0) {
      height -= y;
      y = 0;
   }

   if ((x >= (int)img->width) || (y >= (int)img->height)) {
      return 0;
   }

   if ((int)(x + width) > (int)img->width) {
      width = img->width - x;
   }

   if ((int)(y + height) > (int)img->height) {
      height = img->height - y;
   }

   if ((imdec = start_image_decoding(0, fImage, SCL_DO_ALL, 0, y,
                                     img->width, height, 0)) == 0) {
      Warning("GetPixels", "Failed to create image decoder");
      return 0;
   }

   TArrayL *ret = new TArrayL(width * height);
   Int_t r = 0;
   Int_t g = 0;
   Int_t b = 0;
   Long_t p = 0;

   for (UInt_t k = 0; k < height; k++) {
      imdec->decode_image_scanline(imdec);

      for (UInt_t i = 0; i < width; ++i)  {
         if ((r == (Int_t)imdec->buffer.red[i]) &&
             (g == (Int_t)imdec->buffer.green[i]) &&
             (b == (Int_t)imdec->buffer.blue[i])) {
         } else {
            r = (Int_t)imdec->buffer.red[i];
            g = (Int_t)imdec->buffer.green[i];
            b = (Int_t)imdec->buffer.blue[i];
            p = (Long_t)TColor::RGB2Pixel(r, g, b);
         }
         ret->AddAt(p, k*width + i);
      }
   }

   stop_image_decoding(&imdec);
   return ret;
}


//______________________________________________________________________________
Double_t *TASImage::GetVecArray()
{
   // Return a pointer to internal array[width x height] of double values [0,1].
   // This array is directly accessible. That allows to manipulate/change the
   // image.

   if (!fImage) {
      Warning("GetVecArray", "Bad Image");
      return 0;
   }
   if (fImage->alt.vector) {
      return fImage->alt.vector;
   }
   // vectorize
   return 0;
}


//______________________________________________________________________________
TArrayD *TASImage::GetArray(UInt_t w, UInt_t h, TImagePalette *palette)
{
   // In case of vectorized image return an associated array of doubles
   // otherwise this method creates and returns a 2D array of doubles corresponding to palette.
   // If palette is ZERO a color converted to double value [0, 1] according to formula
   //   Double_t((r << 16) + (g << 8) + b)/0xFFFFFF
   // The returned array must be deleted after usage.

   if (!fImage) {
      Warning("GetArray", "Bad Image");
      return 0;
   }

   TArrayD *ret;

   if (fImage->alt.vector) {
      ret = new TArrayD(fImage->width*fImage->height, fImage->alt.vector);
      return ret;
   }

   ASImageDecoder *imdec;

   w = w ? w : fImage->width;
   h = h ? h : fImage->height;

   if ((fImage->width != w) || (fImage->height != h)) {
      Scale(w, h);
   }

   ASImage *img = fScaledImage ? fScaledImage->fImage : fImage;

   if ((imdec = start_image_decoding(0, img, SCL_DO_ALL, 0, 0,
                                     img->width, 0, 0)) == 0) {
      Warning("GetArray", "Failed to create image decoder");
      return 0;
   }

   ret = new TArrayD(w * h);
   CARD32 r = 0;
   CARD32 g = 0;
   CARD32 b = 0;
   Int_t p = 0;
   Double_t v = 0;

   for (UInt_t k = 0; k < h; k++) {
      imdec->decode_image_scanline(imdec);

      for (UInt_t i = 0; i < w; ++i)  {
         if ((r == imdec->buffer.red[i]) &&
             (g == imdec->buffer.green[i]) &&
             (b == imdec->buffer.blue[i])) {
         } else {
            r = imdec->buffer.red[i];
            g = imdec->buffer.green[i];
            b = imdec->buffer.blue[i];
            if (palette) p = palette->FindColor(r, g, b);
         }
         v = palette ? palette->fPoints[p] : Double_t((r << 16) + (g << 8) + b)/0xFFFFFF;
         ret->AddAt(v, (h-k-1)*w + i);
      }
   }

   stop_image_decoding(&imdec);
   return ret;
}


//______________________________________________________________________________
void TASImage::DrawText(Int_t x, Int_t y, const char *text, Int_t size,
                        const char *color, const char *font_name,
                        EText3DType type, const char *fore_file, Float_t angle)
{
   // Draw text of size (in pixels for TrueType fonts)
   // at position (x, y) with color  specified by hex string.
   //   font_name - TrueType font's filename or X font spec or alias.
   //   3D style of text is one of the following:
   //     0 - plain 2D text, 1 - embossed, 2 - sunken, 3 - shade above,
   //     4 - shade below, 5 - embossed thick, 6 - sunken thick.
   //     7 - ouline above, 8 - ouline below, 9 - full ouline.
   //  fore_file specifies foreground texture of text.

   UInt_t width, height;
   ARGB32 text_color = ARGB32_Black;
   ASImage *fore_im = 0;
   ASImage *text_im = 0;
   Bool_t ttfont = kFALSE;

   if (!InitVisual()) {
      Warning("DrawText", "Visual not initiated");
      return;
   }

   TString fn = font_name;
   fn.Strip();
   char *tmpstr = 0;

   if (fn.EndsWith(".ttf") || fn.EndsWith(".TTF")) {
      tmpstr = gSystem->ExpandPathName(fn.Data());
      fn = tmpstr;
      ttfont = kTRUE;
   }
   delete [] tmpstr;

   if (color) {
      parse_argb_color(color, &text_color);
   }

   if (fImage && fImage->alt.argb32 && ttfont) {
      DrawTextTTF(x, y, text, size, text_color, fn.Data(), angle);
      return;
   }

   if (!gFontManager) {
      gFontManager = create_font_manager(fgVisual->dpy, 0, 0);
   }

   if (!gFontManager) {
      Warning("DrawText", "cannot create Font Manager");
      return;
   }

   ASFont *font = get_asfont(gFontManager, fn.Data(), 0, size, ASF_GuessWho);

   if (!font) {
      font = get_asfont(gFontManager, "fixed", 0, size, ASF_GuessWho);
      if (!font) {
         Warning("DrawText", "cannot find a font %s", font_name);
         return;
      }
   }

   get_text_size(text, font, (ASText3DType)type, &width, &height);

   if (!fImage) {
      fImage = create_asimage(width, height, 0);
      fill_asimage(fgVisual, fImage, 0, 0, width, height, 0xFFFFFFFF);
   }

   text_im = draw_text(text, font, (ASText3DType)type, 0);

   ASImage *rimg = fImage;

   if (fore_file) {
      ASImage *tmp = file2ASImage(fore_file, 0xFFFFFFFF, SCREEN_GAMMA, 0, 0);
      if (tmp) {
         if ((tmp->width != width) || (tmp->height != height)) {
            fore_im = tile_asimage(fgVisual, tmp, 0, 0, width, height, 0,
                                   ASA_ASImage, GetImageCompression(), GetImageQuality());
         }
         destroy_asimage(&tmp);
      } else {
         fore_im = tmp;
      }
   }

   if (fore_im) {
      move_asimage_channel(fore_im, IC_ALPHA, text_im, IC_ALPHA);
      destroy_asimage(&text_im);
   } else {
      fore_im = text_im ;
   }

   release_font(font);

   if (fore_im) {
      ASImage *rendered_im;
      ASImageLayer layers[2];

      init_image_layers(&(layers[0]), 2);
      fore_im->back_color = text_color;
      layers[0].im = rimg;
      layers[0].dst_x = 0;
      layers[0].dst_y = 0;
      layers[0].clip_width = rimg->width;
      layers[0].clip_height = rimg->height;
      layers[0].bevel = 0;
      layers[1].im = fore_im;
      layers[1].dst_x = x;
      layers[1].dst_y = y;
      layers[1].clip_width = fore_im->width;
      layers[1].clip_height = fore_im->height;

      rendered_im = merge_layers(fgVisual, &(layers[0]), 2, rimg->width, rimg->height,
                                 ASA_ASImage, GetImageCompression(), GetImageQuality());

      destroy_asimage(&fore_im);
      DestroyImage();
      fImage = rendered_im;
      UnZoom();
   }
}


//______________________________________________________________________________
void TASImage::Merge(const TImage *im, const char *op, Int_t x, Int_t y)
{
   // Merge two images.
   //
   // op is string which specifies overlay operation. Supported operations are:
   //    add            - color addition with saturation
   //    alphablend     - alpha-blending
   //    allanon        - color values averaging
   //    colorize       - hue and saturate bottom image same as top image
   //    darken         - use lowest color value from both images
   //    diff           - use absolute value of the color difference between two images
   //    dissipate      - randomly alpha-blend images
   //    hue            - hue bottom image same as top image
   //    lighten        - use highest color value from both images
   //    overlay        - some wierd image overlaying(see GIMP)
   //    saturate       - saturate bottom image same as top image
   //    screen         - another wierd image overlaying(see GIMP)
   //    sub            - color substraction with saturation
   //    tint           - tinting image with image
   //    value          - value bottom image same as top image

   if (!im) return;

   if (!InitVisual()) {
      Warning("Merge", "Visual not initiated");
      return;
   }

   ASImage *rendered_im;
   ASImageLayer layers[2];

   init_image_layers(&(layers[0]), 2);
   layers[0].im = fImage;
   layers[0].dst_x = 0;
   layers[0].dst_y = 0;
   layers[0].clip_width = fImage->width;
   layers[0].clip_height = fImage->height;
   layers[0].bevel = 0;
   layers[1].im = ((TASImage*)im)->fImage;
   layers[1].dst_x = x;
   layers[1].dst_y = y;
   layers[1].clip_width  = im->GetWidth();
   layers[1].clip_height = im->GetHeight();
   layers[1].merge_scanlines = blend_scanlines_name2func(op ? op : "add");

   rendered_im = merge_layers(fgVisual, &(layers[0]), 2, fImage->width, fImage->height,
                              ASA_ASImage, GetImageCompression(), GetImageQuality());

   DestroyImage();
   fImage = rendered_im;
   UnZoom();
}


//______________________________________________________________________________
void TASImage::Blur(Double_t hr, Double_t vr)
{
   // Perform Gaussian blurr of the image (usefull for drop shadows).
   //    hr         - horizontal radius of the blurr
   //    vr         - vertical radius of the blurr

   if (!InitVisual()) {
      Warning("Blur", "Visual not initiated");
      return;
   }

   if (!fImage) {
      fImage = create_asimage(100, 100, 0);

      if (!fImage) {
         Warning("Blur", "Failed to create image");
         return;
      }

      fill_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height, ARGB32_White);
   }

   ASImage *rendered_im = blur_asimage_gauss(fgVisual, fImage, hr > 0 ? hr : 3,
                                             vr > 0 ? vr : 3, SCL_DO_ALL,
                                             ASA_ASImage, GetImageCompression(), GetImageQuality());
   DestroyImage();
   fImage = rendered_im;
   UnZoom();
}


//______________________________________________________________________________
TObject *TASImage::Clone(const char *newname) const
{
   // Clone image.

   if (!InitVisual() || !fImage) {
      Warning("Clone", "Image not initiated");
      return 0;
   }

   TASImage *im = (TASImage*)TImage::Create();

   if (!im) {
      Warning("Clone", "Failed to create image");
      return 0;
   }

   im->SetName(newname);

   im->fImage = clone_asimage(fImage, SCL_DO_ALL);
   im->fMaxValue = fMaxValue;
   im->fMinValue = fMinValue;
   im->fZoomOffX = fZoomOffX;
   im->fZoomOffY = fZoomOffY;
   im->fZoomWidth = fZoomWidth;
   im->fZoomHeight = fZoomHeight;
   im->fZoomUpdate = fZoomUpdate;
   im->fScaledImage = fScaledImage ? (TASImage*)fScaledImage->Clone("") : 0;

   if (fImage->alt.argb32) {
      UInt_t sz = fImage->width * fImage->height;
      im->fImage->alt.argb32 = (ARGB32*)safemalloc(sz*sizeof(ARGB32));
      memcpy(im->fImage->alt.argb32, fImage->alt.argb32, sz * sizeof(ARGB32));
   }

   return im;
}


//______________________________________________________________________________
Double_t *TASImage::Vectorize(UInt_t max_colors, UInt_t dither, Int_t opaque_threshold)
{
   // Reduce colordepth of an image and fills vector of "scientific data"
   // [0...1]
   //
   // Colors are reduced by allocating colorcells to most used colors first,
   // and then approximating other colors with those allocated.
   // max_colors       - maximum size of the colormap.
   // dither           - number of bits to strip off the color data ( 0...7 )
   // opaque_threshold - alpha channel threshold at which pixel should be
   //                    treated as opaque

   if (!InitVisual()) {
      Warning("Vectorize", "Visual not initiated");
      return 0;
   }

   if (!fImage) {
      fImage = create_asimage(100, 100, 0);

      if (!fImage) {
         Warning("Vectorize", "Failed to create image");
         return 0;
      }

      fill_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height, ARGB32_White);
   }

   ASColormap cmap;
   int *res;
   UInt_t r, g, b;

   dither = dither > 7 ? 7 : dither;

   res = colormap_asimage(fImage, &cmap, max_colors, dither, opaque_threshold);

   Double_t *vec = new Double_t[fImage->height*fImage->width];
   UInt_t v;
   Double_t tmp;
   fMinValue = 2;
   fMaxValue = -1;

   for (UInt_t y = 0; y < fImage->height; y++) {
      for (UInt_t x = 0; x < fImage->width; x++) {
         int i = y*fImage->width + x;
         g = INDEX_SHIFT_GREEN(cmap.entries[res[i]].green);
         b = INDEX_SHIFT_BLUE(cmap.entries[res[i]].blue);
         r = INDEX_SHIFT_RED(cmap.entries[res[i]].red);
         v = MAKE_INDEXED_COLOR24(r,g,b);
         v = (v>>12)&0x0FFF;
         tmp = Double_t(v)/0x0FFF;
         vec[(fImage->height - y - 1)*fImage->width + x] = tmp;
         if (fMinValue > tmp) fMinValue = tmp;
         if (fMaxValue < tmp) fMaxValue = tmp;
      }
   }
   TImagePalette *pal = new TImagePalette(cmap.count);

   for (UInt_t j = 0; j < cmap.count; j++) {
      g = INDEX_SHIFT_GREEN(cmap.entries[j].green);
      b = INDEX_SHIFT_BLUE(cmap.entries[j].blue);
      r = INDEX_SHIFT_RED(cmap.entries[j].red);
      v = MAKE_INDEXED_COLOR24(r,g,b);

      v = (v>>12) & 0x0FFF;
      pal->fPoints[j] = Double_t(v)/0x0FFF;

      pal->fColorRed[j] = cmap.entries[j].red << 8;
      pal->fColorGreen[j] = cmap.entries[j].green << 8;
      pal->fColorBlue[j] = cmap.entries[j].blue << 8;
      pal->fColorAlpha[j] = 0xFF00;
   }

   destroy_colormap(&cmap, kTRUE);

   fPalette = *pal;
   fImage->alt.vector = vec;
   UnZoom();
   if (res) delete res;
   return (Double_t*)fImage->alt.vector;
}


//______________________________________________________________________________
void TASImage::HSV(UInt_t hue, UInt_t radius, Int_t H, Int_t S, Int_t V,
                   Int_t x, Int_t y, UInt_t width, UInt_t height)
{
   // This function will tile original image to specified size with offsets
   // requested, and then it will go though it and adjust hue, saturation and
   // value of those pixels that have specific hue, set by affected_hue/
   // affected_radius parameters. When affected_radius is greater then 180
   // entire image will be adjusted. Note that since grayscale colors have
   // no hue - the will not get adjusted. Only saturation and value will be
   // adjusted in gray pixels.
   // Hue is measured as an angle on a 360 degree circle, The following is
   // relationship of hue values to regular color names :
   // red      - 0
   // yellow   - 60
   // green    - 120
   // cyan     - 180
   // blue     - 240
   // magenta  - 300
   // red      - 360
   //
   // All the hue values in parameters will be adjusted to fall withing 0-360 range.

   // hue - hue in degrees in range 0-360. This allows to limit
   //       impact of color adjustment to affect only limited range of hues.
   //
   // radius - value in degrees to be used in order to
   //          calculate the range of affected hues. Range is determined by
   //          substracting and adding this value from/to affected_hue.
   //
   // H -   value by which to change hues in affected range.
   // S -   value by which to change saturation of the pixels in affected hue range.
   // V -   value by which to change Value(brightness) of pixels in affected hue range.
   //
   // x,y - position on infinite surface tiled with original image, of the
   //       left-top corner of the area to be used for new image.
   //
   // width, height - size of the area of the original image to be used for new image.
   //                 Default is current width, height of the image.

   if (!InitVisual()) {
      Warning("HSV", "Visual not initiated");
      return;
   }

   if (!fImage) {
      fImage = create_asimage(width ? width : 20, height ? height : 20, 0);

      if (!fImage) {
         Warning("HSV", "Failed to create image");
         return;
      }

      x = 0;
      y = 0;
      fill_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height, ARGB32_White);
   }

   width = !width ? fImage->width : width;
   height = !height ? fImage->height : height;

   ASImage *rendered_im = 0;

   if (H || S || V) {
      rendered_im = adjust_asimage_hsv(fgVisual, fImage, x, y, width, height,
                                       hue, radius, H, S, V, ASA_ASImage, 100,
                                       ASIMAGE_QUALITY_TOP);
   }
   if (!rendered_im) {
      Warning("HSV", "Failed to create rendered image");
      return;
   }

   DestroyImage();
   fImage = rendered_im;
   UnZoom();
}


//______________________________________________________________________________
void TASImage::Gradient(UInt_t angle, const char *colors, const char *offsets,
                        Int_t x, Int_t y, UInt_t width, UInt_t height)
{
   // Render multipoint gradient inside rectangle of size (width, height)
   // at position (x,y) within the existing image.
   //
   // angle    Given in degrees.  Default is 0.  This is the
   //          direction of the gradient.  Currently the only supported
   //          values are 0, 45, 90, 135, 180, 225, 270, 315.  0 means left
   //          to right, 90 means top to bottom, etc.
   //
   // colors   Whitespace-separated list of colors.  At least two
   //          colors are required.  Each color in this list will be visited
   //          in turn, at the intervals given by the offsets attribute.
   //
   // offsets  Whitespace-separated list of floating point values
   //          ranging from 0.0 to 1.0.  The colors from the colors attribute
   //          are given these offsets, and the final gradient is rendered
   //          from the combination of the two.  If both colors and offsets
   //          are given but the number of colors and offsets do not match,
   //          the minimum of the two will be used, and the other will be
   //          truncated to match.  If offsets are not given, a smooth
   //          stepping from 0.0 to 1.0 will be used.

   if (!InitVisual()) {
      Warning("Gradient", "Visual not initiated");
      return;
   }

   ASImage *rendered_im = 0;
   ASGradient gradient;

   int reverse = 0, npoints1 = 0, npoints2 = 0;
   char *p;
   char *pb;
   char ch;
   TString str = colors;
   TString col;

   if ((angle > 2 * 180 * 15 / 16) || (angle < 2 * 180 * 1 / 16)) {
      gradient.type = GRADIENT_Left2Right;
   } else if (angle < 2 * 180 * 3 / 16) {
      gradient.type = GRADIENT_TopLeft2BottomRight;
   } else if (angle < 2 * 180 * 5 / 16) {
      gradient.type = GRADIENT_Top2Bottom;
   } else if (angle < 2 * 180 * 7 / 16) {
      gradient.type = GRADIENT_BottomLeft2TopRight; reverse = 1;
   } else if (angle < 2 * 180 * 9 / 16) {
      gradient.type = GRADIENT_Left2Right; reverse = 1;
   } else if (angle < 2 * 180 * 11 / 16) {
      gradient.type = GRADIENT_TopLeft2BottomRight; reverse = 1;
   } else if (angle < 2 * 180 * 13 / 16) {
      gradient.type = GRADIENT_Top2Bottom; reverse = 1;
   } else {
      gradient.type = GRADIENT_BottomLeft2TopRight;
   }

   for (p = (char*)colors; isspace((int)*p); p++) { }

   for (npoints1 = 0; *p; npoints1++) {
      if (*p) {
         for ( ; *p && !isspace((int)*p); p++) { }
      }
      for ( ; isspace((int)*p); p++) { }
   }
   if (offsets) {
      for (p = (char*)offsets; isspace((int)*p); p++) { }

      for (npoints2 = 0; *p; npoints2++) {
         if (*p) {
            for ( ; *p && !isspace((int)*p); p++) { }
         }
         for ( ; isspace((int)*p); p++) { }
      }
   }
   if (npoints1 > 1) {
      int i;
      if (offsets && (npoints1 > npoints2)) npoints1 = npoints2;

      if (!width) {
         width = fImage ? fImage->width : 20;
      }
      if (!height) {
         height = fImage ? fImage->height : 20;
      }

      gradient.color = new ARGB32[npoints1];
      gradient.offset = new double[npoints1];

      for (p = (char*)colors; isspace((int)*p); p++) { }

      for (npoints1 = 0; *p; ) {
         pb = p;

         if (*p) {
            for ( ; *p && !isspace((int)*p); p++) { }
         }
         for ( ; isspace((int)*p); p++) { }

         col = str(pb - colors, p - pb);

         if (parse_argb_color(col.Data(), gradient.color + npoints1) != col) {
            npoints1++;
         } else {
            Warning("Gradient", "Failed to parse color [%s] - defaulting to black", pb);
         }
      }

      if (offsets) {
         for (p = (char*)offsets; isspace((int)*p); p++) { }

         for (npoints2 = 0; *p; ) {
            pb = p;

            if (*p) {
               for ( ; *p && !isspace((int)*p); p++) { }
            }
            ch = *p; *p = '\0';
            gradient.offset[npoints2] = strtod(pb, &pb);

            if (pb == p) npoints2++;
            *p = ch;
            for ( ; isspace((int)*p); p++) { }
         }
      } else {
         for (npoints2 = 0; npoints2 < npoints1; npoints2++) {
            gradient.offset[npoints2] = (double)npoints2 / (npoints1 - 1);
         }
      }
      gradient.npoints = npoints1;

      if (npoints2 && (gradient.npoints > npoints2)) {
         gradient.npoints = npoints2;
      }
      if (reverse) {
         for (i = 0; i < gradient.npoints/2; i++) {
            int i2 = gradient.npoints - 1 - i;
            ARGB32 c = gradient.color[i];
            double o = gradient.offset[i];
            gradient.color[i] = gradient.color[i2];
            gradient.color[i2] = c;
            gradient.offset[i] = gradient.offset[i2];
            gradient.offset[i2] = o;
         }
         for (i = 0; i < gradient.npoints; i++) {
            gradient.offset[i] = 1.0 - gradient.offset[i];
         }
      }
      rendered_im = make_gradient(fgVisual, &gradient, width, height, SCL_DO_ALL,
                                  ASA_ASImage, GetImageCompression(), GetImageQuality());

      delete [] gradient.color;
      delete [] gradient.offset;
   }

   if (!rendered_im) {  // error
      Warning("Gradient", "Failed to create gradient image");
      return;
   }

   if (!fImage) {
      fImage = rendered_im;
      return;
   }

   ASImageLayer layers[2];

   init_image_layers(&(layers[0]), 2);
   layers[0].im = fImage;
   layers[0].dst_x = 0;
   layers[0].dst_y = 0;
   layers[0].clip_width = fImage->width;
   layers[0].clip_height = fImage->height;
   layers[0].bevel = 0;
   layers[1].im = rendered_im;
   layers[1].dst_x = x;
   layers[1].dst_y = y;
   layers[1].clip_width = width;
   layers[1].clip_height = height;
   layers[1].merge_scanlines = alphablend_scanlines;

   ASImage *merge_im = merge_layers(fgVisual, &(layers[0]), 2, fImage->width, fImage->height,
                                    ASA_ASImage, GetImageCompression(), GetImageQuality());
   if (!merge_im) {
      Warning("Gradient", "Failed to create merged image");
      return;
   }

   destroy_asimage(&rendered_im);
   DestroyImage();
   fImage = merge_im;
   UnZoom();
}


//______________________________________________________________________________
static CARD8 MakeComponentHilite(int cmp)
{
   // Make component hilite.
   // (used internally)

   if (cmp < 51) {
      cmp = 51;
   }
   cmp = (cmp * 12) / 10;

   return (cmp > 255) ? 255 : cmp;
}


//______________________________________________________________________________
static ARGB32 GetHilite(ARGB32 background)
{
   // Calculate highlite color.
   // (used internally)

   return ((MakeComponentHilite((background>>24) & 0x000000FF) << 24) & 0xFF000000) |
           ((MakeComponentHilite((background & 0x00FF0000) >> 16) << 16) & 0x00FF0000) |
           ((MakeComponentHilite((background & 0x0000FF00) >> 8) << 8) & 0x0000FF00) |
           ((MakeComponentHilite((background & 0x000000FF))) & 0x000000FF);
}


//______________________________________________________________________________
static ARGB32 GetShadow(ARGB32 background)
{
   // Calculate shadow color.
   // (used internally)

   return (background >> 1) & 0x7F7F7F7F;
}


//______________________________________________________________________________
static ARGB32 GetAverage(ARGB32 foreground, ARGB32 background)
{
   // Get average.
   // (used internally)

   CARD16   a, r, g, b;

   a = ARGB32_ALPHA8(foreground) + ARGB32_ALPHA8(background);
   a = (a<<3)/10;
   r = ARGB32_RED8(foreground) + ARGB32_RED8(background);
   r = (r<<3)/10;
   g = ARGB32_GREEN8(foreground) + ARGB32_GREEN8(background);
   g = (g<<3)/10;
   b = ARGB32_BLUE8(foreground) + ARGB32_BLUE8(background);
   b = (b<<3)/10;

   return MAKE_ARGB32(a, r, g, b);
}


//______________________________________________________________________________
void TASImage::Bevel(Int_t x, Int_t y, UInt_t width, UInt_t height,
                     const char *hi_color, const char *lo_color, UShort_t thick,
                     Bool_t reverse)
{
   // Bevel is used to create 3D effect while drawing buttons, or any other
   // image that needs to be framed. Bevel is drawn using 2 primary colors:
   // one for top and left sides - hi color, and another for bottom and
   // right sides - low color. Bevel can be drawn over exisiting image or
   // as newly created,  as it is shown in code below:
   //
   //  TImage *img = TImage::Create();
   //  img->Bevel(0, 0, 400, 300, "#dddddd", "#000000", 3);

   if (!InitVisual()) {
      Warning("Bevel", "Visual not initiated");
      return;
   }

   ASImageBevel bevel;
   bevel.type = 0;

   ARGB32 hi, lo;
   parse_argb_color(hi_color, &hi);
   parse_argb_color(lo_color, &lo);

   if (reverse) {
      bevel.lo_color = hi;
      bevel.lolo_color = GetHilite(hi);
      bevel.hi_color = lo;
      bevel.hihi_color = GetShadow(lo);
   } else {
      bevel.hi_color = hi;
      bevel.hihi_color = GetHilite(hi);
      bevel.lo_color = lo;
      bevel.lolo_color = GetShadow(lo);
   }
   bevel.hilo_color = GetAverage(hi, lo);

   int extra_hilite = 2;
   bevel.left_outline = bevel.top_outline = bevel.right_outline = bevel.bottom_outline = thick;
   bevel.left_inline = bevel.top_inline = bevel.right_inline = bevel.bottom_inline = extra_hilite + 1;

   if (bevel.top_outline > 1) {
      bevel.top_inline += bevel.top_outline - 1;
   }

   if (bevel.left_outline > 1) {
      bevel.left_inline += bevel.left_outline - 1;
   }

   if (bevel.right_outline > 1) {
      bevel.right_inline += bevel.right_outline - 1;
   }

   if (bevel.bottom_outline > 1) {
      bevel.bottom_inline += bevel.bottom_outline - 1;
   }

   ASImage *merge_im;
   ARGB32 fill = ((hi>>24) != 0xff) || ((lo>>24) != 0xff) ? bevel.hilo_color : (bevel.hilo_color | 0xff000000);

   if (!fImage) {
      fImage = create_asimage(width ? width : 20, height ? height : 20, 0);

      if (!fImage) {
         Warning("Bevel", "Failed to create image");
         return;
      }

      x = 0;
      y = 0;
      fill_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height, fill);
   }

   width = !width ? fImage->width : width;
   height = !height ? fImage->height : height;

   ASImageLayer layers[2];
   init_image_layers(&(layers[0]), 2);

   layers[0].im = fImage;
   layers[0].dst_x = 0;
   layers[0].dst_y = 0;
   layers[0].clip_width = fImage->width;
   layers[0].clip_height = fImage->height;
   layers[0].bevel = 0;

   UInt_t w = width - (bevel.left_outline + bevel.right_outline);
   UInt_t h = height - (bevel.top_outline + bevel.bottom_outline);
   ASImage *bevel_im = create_asimage(w, h, 0);

   if (!bevel_im) {
      Warning("Bevel", "Failed to create bevel image");
      return;
   }

   layers[1].im = bevel_im;
   fill_asimage(fgVisual, bevel_im, 0, 0, w, h, fill);

   layers[1].dst_x = x;
   layers[1].dst_y = y;
   layers[1].clip_width = width;
   layers[1].clip_height = height;
   layers[1].bevel = &bevel;
   layers[1].merge_scanlines = alphablend_scanlines;

   merge_im = merge_layers(fgVisual, &(layers[0]), 2, fImage->width, fImage->height,
                           ASA_ASImage, GetImageCompression(), GetImageQuality());
   destroy_asimage(&bevel_im);

   if (!merge_im) {
      Warning("Bevel", "Failed to image");
      return;
   }

   DestroyImage();
   fImage = merge_im;
   UnZoom();
}


//______________________________________________________________________________
void TASImage::Pad(const char *col, UInt_t l, UInt_t r, UInt_t t, UInt_t b)
{
   // Enlarge image, padding it with specified color on each side in
   // accordance with requested geometry.

   Int_t x, y;
   UInt_t w, h;

   if (!InitVisual()) {
      Warning("Pad", "Visual not initiated");
      return;
   }

   if (!fImage) {
      fImage = create_asimage(100, 100, 0);

      if (!fImage) {
         Warning("Pad", "Failed to create image");
         return;
      }

      x = 0;
      y = 0;
      fill_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height, ARGB32_White);
   }

   ARGB32 color;
   parse_argb_color(col, &color);

   x = l;
   y = t;
   w = l + fImage->width + r;
   h = t + fImage->height + b;

   ASImage *img = pad_asimage(fgVisual, fImage, x, y, w, h, color,
                              ASA_ASImage, GetImageCompression(), GetImageQuality());

   if (!img) {
      Warning("Pad", "Failed to create output image");
      return;
   }

   DestroyImage();
   fImage = img;
   UnZoom();
   fZoomUpdate = kZoomOps;
}


//______________________________________________________________________________
void TASImage::Crop(Int_t x, Int_t y, UInt_t width, UInt_t height)
{
   // Crop an image.

   if (!InitVisual()) {
      Warning("Crop", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("Crop", "No image");
      return;
   }

   x = x < 0 ? 0 : x;
   y = y < 0 ? 0 : y;

   width = x + width > fImage->width ? fImage->width - x : width;
   height = y + height > fImage->height ? fImage->height - y : height;

   if ((width == fImage->width) && (height == fImage->height)) {
      Warning("Crop", "input size larger than image");
      return;
   }
   ASImageDecoder *imdec = start_image_decoding(fgVisual, fImage, SCL_DO_ALL,
                                                x, y, width, height, 0);

   if (!imdec) {
      Warning("Crop", "Failed to start image decoding");
      return;
   }

   ASImage *img = create_asimage(width, height, 0);

   if (!img) {
      delete [] imdec;
      Warning("Crop", "Failed to create image");
      return;
   }

   ASImageOutput *imout = start_image_output(fgVisual, img, ASA_ASImage,
                                             GetImageCompression(), GetImageQuality());

   if (!imout) {
      Warning("Crop", "Failed to start image output");
      destroy_asimage(&img);
      if (imdec) delete [] imdec;
      return;
   }

#ifdef HAVE_MMX
   mmx_init();
#endif

   for (UInt_t i = 0; i < height; i++) {
      imdec->decode_image_scanline(imdec);
      imout->output_image_scanline(imout, &(imdec->buffer), 1);
   }

   stop_image_decoding(&imdec);
   stop_image_output(&imout);

#ifdef HAVE_MMX
   mmx_off();
#endif

   DestroyImage();
   fImage = img;
   UnZoom();
   fZoomUpdate = kZoomOps;
}


//______________________________________________________________________________
void TASImage::Append(const TImage *im, const char *option, const char *color )
{
   // Append image.
   //
   // option:
   //       "+" - appends to the right side
   //       "/" - appends to the bottom

   if (!im) return;

   if (!InitVisual()) {
      Warning("Append", "Visual not initiated");
      return;
   }

   if (!fImage) {
      fImage = ((TASImage*)im)->fImage;
      return;
   }

   TString opt = option;
   opt.Strip();

   UInt_t width = fImage->width;
   UInt_t height = fImage->height;

   if (opt == "+") {
      Pad(color, 0, im->GetWidth(), 0, 0);
      Merge(im, "alphablend", width, 0);
   } else if (opt == "/") {
      Pad(color, 0, 0, 0, im->GetHeight());
      Merge(im, "alphablend", 0, height);
   } else {
      return;
   }

   UnZoom();
}


//______________________________________________________________________________
void TASImage::BeginPaint(Bool_t mode)
{
   // BeginPaint initializes internal array[width x height] of ARGB32 pixel
   // values.
   // That provides quick access to image during paint operations.
   // To RLE compress image one needs to call EndPaint method when paintinig
   // is over.

   if (!InitVisual()) {
      Warning("BeginPaint", "Visual not initiated");
      return;
   }

   if (!fImage) {
      return;
   }

   fPaintMode = mode;

   if (!fPaintMode || fImage->alt.argb32) {
      return;
   }

   ASImage *img = tile_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height,
                               0, ASA_ARGB32, 0, ASIMAGE_QUALITY_DEFAULT);

   if (!img) {
      Warning("BeginPaint", "Failed to create image");
      return;
   }

   DestroyImage();
   fImage = img;
}


//______________________________________________________________________________
void TASImage::EndPaint()
{
   // EndPaint does internal RLE compression of image data.

   if (!fImage) {
      Warning("EndPaint", "no image");
      return;
   }

   if (!fImage->alt.argb32) return;

   ASImage *img = tile_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height,
                               0, ASA_ASImage, 0, ASIMAGE_QUALITY_DEFAULT);

   if (!img) {
      Warning("EndPaint", "Failed to create image");
      return;
   }

   fPaintMode = kFALSE;
   DestroyImage();
   fImage = img;
}


//______________________________________________________________________________
UInt_t *TASImage::GetArgbArray()
{
   // Return a pointer to internal array[width x height] of ARGB32 values
   // This array is directly accessible. That allows to manipulate/change the
   // image.

   if (!fImage) {
      Warning("GetArgbArray", "no image");
      return 0;
   }

   ASImage *img = fScaledImage ? fScaledImage->fImage : fImage;
   if (!img) return 0;

   if (!img->alt.argb32) {
      if (fScaledImage) {
         fScaledImage->BeginPaint();
         img = fScaledImage->fImage;
      } else {
         BeginPaint();
         img = fImage;
      }
   }

   return (UInt_t *)img->alt.argb32;
}


//______________________________________________________________________________
UInt_t *TASImage::GetRgbaArray()
{
   // Return a pointer to an array[width x height] of RGBA32 values.
   // This array is created from internal ARGB32 array,
   // must be deleted after usage.

   if (!fImage) {
      Warning("GetRgbaArray", "no image");
      return 0;
   }

   ASImage *img = fScaledImage ? fScaledImage->fImage : fImage;
   if (!img) return 0;

   if (!img->alt.argb32) {
      if (fScaledImage) {
         fScaledImage->BeginPaint();
         img = fScaledImage->fImage;
      } else {
         BeginPaint();
         img = fImage;
      }
   }

   UInt_t i, j;
   Int_t y = 0;
   Int_t idx = 0;
   UInt_t a, rgb, rgba, argb;
   y = 0;

   UInt_t *ret = new UInt_t[img->width*img->height];

   for (i = 0; i < img->height; i++) {
      for (j = 0; j < img->width; j++) {
         idx = y + j;
         argb = img->alt.argb32[idx];
         a = argb >> 24;
         rgb =  argb & 0x00ffffff;
         rgba = (rgb <<  8) + a;
         ret[idx] = rgba;
      }
      y += img->width;
   }

   return ret;
}


//______________________________________________________________________________
UInt_t *TASImage::GetScanline(UInt_t y)
{
   // Return a pointer to scanline.

   if (!fImage) {
      Warning("GetScanline", "no image");
      return 0;
   }

   ASImage *img = fScaledImage ? fScaledImage->fImage : fImage;
   CARD32 *ret = new CARD32[img->width];

   ASImageDecoder *imdec = start_image_decoding(fgVisual, img, SCL_DO_ALL,
                                                0, y, img->width, 1, 0);

   if (!imdec) {
      delete [] ret;
      Warning("GetScanline", "Failed to start image decoding");
      return 0;
   }

#ifdef HAVE_MMX
   mmx_init();
#endif

   imdec->decode_image_scanline(imdec);
   memcpy(imdec->buffer.buffer, ret, img->width*sizeof(CARD32));
   stop_image_decoding(&imdec);

#ifdef HAVE_MMX
   mmx_off();
#endif

   return (UInt_t*)ret;
}


//______________________________________________________________________________
//
// Vector graphics
// a couple of macros which can be "assembler accelerated"

#if defined(R__GNU) && defined(__i386__) && !defined(__sun)
#define _MEMSET_(dst, lng, val)   __asm__("movl  %0,%%eax \n"\
                                      "movl  %1,%%edi \n"              \
                                      "movl  %2,%%ecx \n"              \
                                      "cld \n"                         \
                                      "rep \n"                         \
                                      "stosl %%eax,(%%edi) \n"         \
                                      : /* no output registers */      \
                                      :"g" (val), "g" (dst), "g" (lng) \
                                      :"eax","edi","ecx"               \
                                     )

#else
 #define _MEMSET_(dst, lng, val) do {\
 for( UInt_t j=0; j < lng; j++) *((dst)+j) = val; } while (0)

#endif

#define FillSpansInternal(npt, ppt, widths, color) do {\
   UInt_t yy = ppt[0].fY*fImage->width;\
   for (UInt_t i = 0; i < npt; i++) {\
      _MEMSET_(&fImage->alt.argb32[yy + ppt[i].fX], widths[i], color);\
      yy += ((i+1 < npt) && (ppt[i].fY != ppt[i+1].fY) ? fImage->width : 0);\
   }\
} while (0)


//______________________________________________________________________________
void TASImage::FillRectangleInternal(UInt_t col, Int_t x, Int_t y, UInt_t width, UInt_t height)
{
   // Fill rectangle of size (width, height) at position (x,y)
   // within the existing image with specified color.

   ARGB32 color = (ARGB32)col;

   if (width  == 0) width = 1;
   if (height == 0) height = 1;

   if (x < 0) {
      width += x;
      x = 0;
   }
   if (y < 0) {
      height += y;
      y = 0;
   }

   Bool_t has_alpha = (color & 0xff000000) != 0xff000000;

   x = x > (int)fImage->width ? (Int_t)fImage->width : x;
   y = y > (int)fImage->height ? (Int_t)fImage->height : y;

   width = x + width > fImage->width ? fImage->width - x : width;
   height = y + height > fImage->height ? fImage->height - y : height;

   if (!fImage->alt.argb32) {
      fill_asimage(fgVisual, fImage, x, y, width, height, color);
   } else {
      int yyy = y*fImage->width;
      if (!has_alpha) { // use faster memset
         ARGB32 *p0 = fImage->alt.argb32 + yyy + x;
         ARGB32 *p = p0;
         for (UInt_t i = 0; i < height; i++) {
            _MEMSET_(p, width, color);
            p += fImage->width;
         }
      } else {
         for (UInt_t i = y; i < y + height; i++) {
            int j = x + width;
            while (j > x) {
               j--;
               _alphaBlend(&fImage->alt.argb32[yyy + j], &color);
            }
         }
         yyy += fImage->width;
      }
   }
}


//______________________________________________________________________________
void TASImage::FillRectangle(const char *col, Int_t x, Int_t y, UInt_t width, UInt_t height)
{
   // Fill rectangle of size (width, height) at position (x,y)
   // within the existing image with specified color.
   //
   // To create new image with Fill method the following code can be used:
   //
   //  TImage *img = TImage::Create();
   //  img->Fill("#FF00FF", 0, 0, 400, 300);

   if (!InitVisual()) {
      Warning("Fill", "Visual not initiated");
      return;
   }

   ARGB32 color = ARGB32_White;

   if (col) {
      parse_argb_color(col, &color);
   }

   if (!fImage) {
      fImage = create_asimage(width ? width : 20, height ? height : 20, 0);
      x = 0;
      y = 0;
   }

   FillRectangleInternal((UInt_t)color, x, y, width, height);
   UnZoom();
}


//______________________________________________________________________________
void TASImage::DrawVLine(UInt_t x, UInt_t y1, UInt_t y2, UInt_t col, UInt_t thick)
{
   // Draw a vertical line.

   ARGB32 color = (ARGB32)col;
   UInt_t half = 0;

   if (!thick)  thick = 1;

   if (thick > 1) {
      half = thick >> 1;
      if (x > half) {
         x =  x - half;
      } else {
         x = 0;
         thick += (x - half);
      }
   }

   y2 = y2 >= fImage->height ? fImage->height - 1 : y2;
   y1 = y1 >= fImage->height ? fImage->height - 1 : y1;
   x = x + thick >= fImage->width ? fImage->width - thick - 1 : x;

   int yy = y1*fImage->width;
   for (UInt_t y = y1; y <= y2; y++) {
      for (UInt_t w = 0; w < thick; w++) {
         if (x + w < fImage->width) {
            _alphaBlend(&fImage->alt.argb32[yy + (x + w)], &color);
         }
      }
      yy += fImage->width;
   }
}


//______________________________________________________________________________
void TASImage::DrawHLine(UInt_t y, UInt_t x1, UInt_t x2, UInt_t col, UInt_t thick)
{
   // Draw an horizontal line.

   ARGB32 color = (ARGB32)col;
   UInt_t half = 0;

   if (!thick)  thick = 1;

   if (thick > 1) {
      half = thick >> 1;
      if (y > half) {
         y =  y - half;
      } else {
         y = 0;
         thick += (y - half);
      }
   }

   y = y + thick >= fImage->height ? fImage->height - thick - 1 : y;
   x2 = x2 >= fImage->width ? fImage->width - 1 : x2;
   x1 = x1 >= fImage->width ? fImage->width - 1 : x1;

   int yy = y*fImage->width;
   for (UInt_t w = 0; w < thick; w++) {
      for (UInt_t x = x1; x <= x2; x++) {
         if (y + w < fImage->height) {
            _alphaBlend(&fImage->alt.argb32[yy + x], &color);
         }
      }
      yy += fImage->width;
   }
}


//______________________________________________________________________________
void TASImage::DrawLine(UInt_t x1,  UInt_t y1, UInt_t x2, UInt_t y2,
                        const char *col, UInt_t thick)
{
   // Draw a line.

   ARGB32 color;
   parse_argb_color(col, &color);
   DrawLineInternal(x1, y1, x2, y2, (UInt_t)color, thick);
}


//______________________________________________________________________________
void TASImage::DrawLineInternal(UInt_t x1, UInt_t y1, UInt_t x2, UInt_t y2,
                                UInt_t col, UInt_t thick)
{
   // Internal line drawing.

   int dx, dy, d;
   int i1, i2;
   int x, y, xend, yend;
   int xdir, ydir;
   int wid, q;
   int idx;
   int yy;

   if (!InitVisual()) {
      Warning("DrawLine", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("DrawLine", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("DrawLine", "Failed to get pixel array");
      return;
   }

   ARGB32 color = (ARGB32)col;

   dx = TMath::Abs(Int_t(x2) - Int_t(x1));
   dy = TMath::Abs(Int_t(y2) - Int_t(y1));

   if (!dx) {
      DrawVLine(x1, y2 > y1 ? y1 : y2,
                    y2 > y1 ? y2 : y1, color, thick);
      return;
   }

   if (!dy) {
      DrawHLine(y1, x2 > x1 ? x1 : x2,
                    x2 > x1 ? x2 : x1, color, thick);
      return;
   }

   if (thick > 1) {
      DrawWideLine(x1, y1, x2, y2, color, thick);
      return;
   }

   wid = 1;

   if (dy <= dx) {
      UInt_t ddy = dy << 1;
      i1 = ddy;
      i2 = i1 - (dx << 1);
      d = i1 - dx;

      if (x1 > x2) {
         x = x2;
         y = y2;
         ydir = -1;
         xend = x1;
      } else {
         x = x1;
         y = y1;
         ydir = 1;
         xend = x2;
      }

      yy = y*fImage->width;
      _alphaBlend(&fImage->alt.argb32[yy + x], &color);
      q = (y2 - y1) * ydir;

      if (q > 0) {
         while (x < xend) {
            idx = yy + x;
            _alphaBlend(&fImage->alt.argb32[idx], &color);
            x++;

            if (d >= 0) {
               yy += fImage->width;
               d += i2;
            } else {
               d += i1;
            }
         }
      } else {
         while (x < xend) {
            idx = yy + x;
            _alphaBlend(&fImage->alt.argb32[idx], &color);
            x++;

            if (d >= 0) {
               yy -= fImage->width;
               d += i2;
            } else {
               d += i1;
            }
         }
      }
   } else {
      UInt_t ddx = dx << 1;
      i1 = ddx;
      i2 = i1 - (dy << 1);
      d = i1 - dy;

      if (y1 > y2) {
         y = y2;
         x = x2;
         yend = y1;
         xdir = -1;
      } else {
         y = y1;
         x = x1;
         yend = y2;
         xdir = 1;
      }

      yy = y*fImage->width;
      _alphaBlend(&fImage->alt.argb32[yy + x], &color);
      q = (x2 - x1) * xdir;

      if (q > 0) {
         while (y < yend) {
            idx = yy + x;
            _alphaBlend(&fImage->alt.argb32[idx], &color);
            y++;
            yy += fImage->width;

            if (d >= 0) {
               x++;
               d += i2;
            } else {
               d += i1;
            }
         }
      } else {
         while (y < yend) {
            idx = yy + x;
            _alphaBlend(&fImage->alt.argb32[idx], &color);
            y++;
            yy += fImage->width;

            if (d >= 0) {
               x--;
               d += i2;
            } else {
               d += i1;
            }
         }
      }
   }
}


//______________________________________________________________________________
void TASImage::DrawRectangle(UInt_t x, UInt_t y, UInt_t w, UInt_t h,
                             const char *col, UInt_t thick)
{
   // Draw a rectangle.

   if (!InitVisual()) {
      Warning("DrawRectangle", "Visual not initiated");
      return;
   }

   if (!fImage) {
      w = w ? w : 20;
      h = h ? h : 20;
      x = 0;
      y = 0;
      fImage = create_asimage(w, h, 0);
      FillRectangle(col, 0, 0, w, h);
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("DrawRectangle", "Failed to get pixel array");
      return;
   }

   ARGB32 color;
   parse_argb_color(col, &color);

   DrawHLine(y, x, x + w, (UInt_t)color, thick);
   DrawVLine(x + w, y, y + h, (UInt_t)color, thick);
   DrawHLine(y + h, x, x + w, (UInt_t)color, thick);
   DrawVLine(x, y, y + h, (UInt_t)color, thick);
   UnZoom();
}


//______________________________________________________________________________
void TASImage::DrawBox(Int_t x1, Int_t y1, Int_t x2, Int_t y2, const char *col,
                       UInt_t thick, Int_t mode)
{
   // Draw a box.

   Int_t x = TMath::Min(x1, x2);
   Int_t y = TMath::Min(y1, y2);
   Int_t w = TMath::Abs(x2 - x1);
   Int_t h = TMath::Abs(y2 - y1);

   ARGB32 color;

   if (!fImage) {
      w = w ? x+w : x+20;
      h = h ? y+h : y+20;
      fImage = create_asimage(w, h, 0);
      FillRectangle(col, 0, 0, w, h);
      return;
   }

   if (x1 == x2) {
      parse_argb_color(col, &color);
      DrawVLine(x1, y1, y2, color, 1);
      return;
   }

   if (y1 == y2) {
      parse_argb_color(col, &color);
      DrawHLine(y1, x1, x2, color, 1);
      return;
   }


   switch (mode) {
      case TVirtualX::kHollow:
         DrawRectangle(x, y, w, h, col, thick);
         break;

      case TVirtualX::kFilled:
         FillRectangle(col, x, y, w, h);
         break;

      default:
         FillRectangle(col, x, y, w, h);
         break;
   }
}


//______________________________________________________________________________
void TASImage::DrawDashHLine(UInt_t y, UInt_t x1, UInt_t x2, UInt_t nDash,
                             const char *pDash, UInt_t col, UInt_t thick)
{
   // Draw a dashed horizontal line.

   UInt_t iDash = 0;    // index of current dash
   int i = 0;

   ARGB32 color = (ARGB32)col;

   UInt_t half = 0;

   if (thick > 1) {
      half = thick >> 1;
      if (y > half) {
         y =  y - half;
      } else {
         y = 0;
         thick += (y - half);
      }
   }
   thick = thick <= 0 ? 1 : thick;

   y = y + thick >= fImage->height ? fImage->height - thick - 1 : y;
   x2 = x2 >= fImage->width ? fImage->width - 1 : x2;
   x1 = x1 >= fImage->width ? fImage->width - 1 : x1;

   // switch x1, x2
   UInt_t tmp = x1;
   x1 = x2 < x1 ? x2 : x1;
   x2 = x2 < tmp ? tmp : x2;

   int yy = y*fImage->width;
   for (UInt_t w = 0; w < thick; w++) {
      for (UInt_t x = x1; x <= x2; x++) {
         if (y + w < fImage->height) {
            if ((iDash%2)==0) {
               _alphaBlend(&fImage->alt.argb32[yy + x], &color);
            }
         }
         i++;

         if (i >= pDash[iDash]) {
            iDash++;
            i = 0;
         }
         if (iDash >= nDash) {
            iDash = 0;
            i = 0;
         }
      }
      yy += fImage->width;
   }
}


//______________________________________________________________________________
void TASImage::DrawDashVLine(UInt_t x, UInt_t y1, UInt_t y2, UInt_t nDash,
                             const char *pDash, UInt_t col, UInt_t thick)
{
   // Draw a dashed vertical line.

   UInt_t iDash = 0;    // index of current dash
   int i = 0;

   ARGB32 color = (ARGB32)col;

   UInt_t half = 0;

   if (thick > 1) {
      half = thick >> 1;
      if (x > half) {
         x =  x - half;
      } else {
         x = 0;
         thick += (x - half);
      }
   }
   thick = thick <= 0 ? 1 : thick;

   y2 = y2 >= fImage->height ? fImage->height - 1 : y2;
   y1 = y1 >= fImage->height ? fImage->height - 1 : y1;

   // switch x1, x2
   UInt_t tmp = y1;
   y1 = y2 < y1 ? y2 : y1;
   y2 = y2 < tmp ? tmp : y2;

   x = x + thick >= fImage->width ? fImage->width - thick - 1 : x;

   int yy = y1*fImage->width;
   for (UInt_t y = y1; y <= y2; y++) {
      for (UInt_t w = 0; w < thick; w++) {
         if (x + w < fImage->width) {
            if ((iDash%2)==0) {
               _alphaBlend(&fImage->alt.argb32[yy + (x + w)], &color);
            }
         }
      }
      i++;

      if (i >= pDash[iDash]) {
         iDash++;
         i = 0;
      }
      if (iDash >= nDash) {
         iDash = 0;
         i = 0;
      }
      yy += fImage->width;
   }
}


//______________________________________________________________________________
void TASImage::DrawDashZLine(UInt_t x1, UInt_t y1, UInt_t x2, UInt_t y2,
                             UInt_t nDash, const char *tDash, UInt_t color)
{
   // Draw a dashed line with one pixel width.

   int dx, dy, d;
   int i, i1, i2;
   int x, y, xend, yend;
   int xdir, ydir;
   int q;
   UInt_t iDash = 0;    // index of current dash
   int yy;
   int idx;

   dx = TMath::Abs(Int_t(x2) - Int_t(x1));
   dy = TMath::Abs(Int_t(y2) - Int_t(y1));

   char *pDash = new char[nDash];

   if (dy <= dx) {
      double ac = TMath::Cos(TMath::ATan2(dy, dx));

      for (i = 0; i < (int)nDash; i++) {
         pDash[i] = TMath::Nint(tDash[i] * ac);
      }

      UInt_t ddy = dy << 1;
      i1 = ddy;
      i2 = i1 - (dx << 1);
      d = i1 - dx;
      i = 0;

      if (x1 > x2) {
         x = x2;
         y = y2;
         ydir = -1;
         xend = x1;
      } else {
         x = x1;
         y = y1;
         ydir = 1;
         xend = x2;
      }

      yy = y*fImage->width;
      _alphaBlend(&fImage->alt.argb32[y*fImage->width + x], &color);
      q = (y2 - y1) * ydir;

      if (q > 0) {
         while (x < xend) {
            idx = yy + x;
            if ((iDash%2) == 0) {
               _alphaBlend(&fImage->alt.argb32[idx], &color);
            }
            x++;
            if (d >= 0) {
               yy += fImage->width;
               d += i2;
            } else {
               d += i1;
            }

            i++;
            if (i >= pDash[iDash]) {
               iDash++;
               i = 0;
            }
            if (iDash >= nDash) {
               iDash = 0;
               i = 0;
            }
         }
      } else {
         while (x < xend) {
            idx = yy + x;
            if ((iDash%2) == 0) {
               _alphaBlend(&fImage->alt.argb32[idx], &color);
            }
            x++;
            if (d >= 0) {
               yy -= fImage->width;
               d += i2;
            } else {
               d += i1;
            }

            i++;
            if (i >= pDash[iDash]) {
               iDash++;
               i = 0;
            }
            if (iDash >= nDash) {
               iDash = 0;
               i = 0;
            }
         }
      }
   } else {
      double as = TMath::Sin(TMath::ATan2(dy, dx));

      for (i = 0; i < (int)nDash; i++) {
         pDash[i] = TMath::Nint(tDash[i] * as);
      }

      UInt_t ddx = dx << 1;
      i1 = ddx;
      i2 = i1 - (dy << 1);
      d = i1 - dy;
      i = 0;

      if (y1 > y2) {
         y = y2;
         x = x2;
         yend = y1;
         xdir = -1;
      } else {
         y = y1;
         x = x1;
         yend = y2;
         xdir = 1;
      }

      yy = y*fImage->width;
      _alphaBlend(&fImage->alt.argb32[y*fImage->width + x], &color);
      q = (x2 - x1) * xdir;

      if (q > 0) {
         while (y < yend) {
            idx = yy + x;
            if ((iDash%2) == 0) {
               _alphaBlend(&fImage->alt.argb32[idx], &color);
            }
            y++;
            yy += fImage->width;

            if (d >= 0) {
               x++;
               d += i2;
            } else {
               d += i1;
            }

            i++;
            if (i >= pDash[iDash]) {
               iDash++;
               i = 0;
            }
            if (iDash >= nDash) {
               iDash = 0;
               i = 0;
            }
         }
      } else {
         while (y < yend) {
            idx = yy + x;
            if ((iDash%2) == 0) {
               _alphaBlend(&fImage->alt.argb32[idx], &color);
            }
            y++;
            yy += fImage->width;

            if (d >= 0) {
               x--;
               d += i2;
            } else {
               d += i1;
            }

            i++;
            if (i >= pDash[iDash]) {
               iDash++;
               i = 0;
            }
            if (iDash >= nDash) {
               iDash = 0;
               i = 0;
            }
         }
      }
   }
   delete [] pDash;
}


//______________________________________________________________________________
void TASImage::DrawDashZTLine(UInt_t x1, UInt_t y1, UInt_t x2, UInt_t y2,
                             UInt_t nDash, const char *tDash, UInt_t color, UInt_t thick)
{
   // Draw a dashed line with thick pixel width.

   int dx, dy;
   int i;
   double x, y, xend=0, yend=0, x0, y0;
   int xdir, ydir;
   int q;
   UInt_t iDash = 0;    // index of current dash

   dx = TMath::Abs(Int_t(x2) - Int_t(x1));
   dy = TMath::Abs(Int_t(y2) - Int_t(y1));

   double *xDash = new double[nDash];
   double *yDash = new double[nDash];
   double a = TMath::ATan2(dy, dx);
   double ac = TMath::Cos(a);
   double as = TMath::Sin(a);

   for (i = 0; i < (int)nDash; i++) {
      xDash[i] = tDash[i] * ac;
      yDash[i] = tDash[i] * as;

      // dirty trick (must be fixed)
      if ((i%2) == 0) {
         xDash[i] = xDash[i]/2;
         yDash[i] = yDash[i]/2;
      } else {
         xDash[i] = xDash[i]*2;
         yDash[i] = yDash[i]*2;
      }
   }

   if (dy <= dx) {
      if (x1 > x2) {
         x = x2;
         y = y2;
         ydir = -1;
         xend = x1;
      } else {
         x = x1;
         y = y1;
         ydir = 1;
         xend = x2;
      }

      q = (y2 - y1) * ydir;
      x0 = x;
      y0 = y;
      iDash = 0;
      yend = y + q;

      if (q > 0) {
         while ((x < xend) && (y < yend)) {
            x += xDash[iDash];
            y += yDash[iDash];

            if ((iDash%2) == 0) {
              DrawWideLine(TMath::Nint(x0), TMath::Nint(y0),
                           TMath::Nint(x), TMath::Nint(y), color, thick);
            } else {
               x0 = x;
               y0 = y;
            }

            iDash++;

            if (iDash >= nDash) {
               iDash = 0;
            }
        }
      } else {
         while ((x < xend) && (y > yend)) {
            x += xDash[iDash];
            y -= yDash[iDash];

            if ((iDash%2) == 0) {
               DrawWideLine(TMath::Nint(x0), TMath::Nint(y0),
                            TMath::Nint(x), TMath::Nint(y), color, thick);
            } else {
               x0 = x;
               y0 = y;
            }

            iDash++;

            if (iDash >= nDash) {
               iDash = 0;
            }
         }
      }
   } else {

      if (y1 > y2) {
         y = y2;
         x = x2;
         yend = y1;
         xdir = -1;
      } else {
         y = y1;
         x = x1;
         yend = y2;
         xdir = 1;
      }

      q = (x2 - x1) * xdir;
      x0 = x;
      y0 = y;
      iDash = 0;

      if (q > 0) {
         while ((x < xend) && (y < yend)) {
            x += xDash[iDash];
            y += yDash[iDash];

            if ((iDash%2) == 0) {
               DrawWideLine(TMath::Nint(x0), TMath::Nint(y0),
                            TMath::Nint(x), TMath::Nint(y), color, thick);
            } else {
               x0 = x;
               y0 = y;
            }

            iDash++;

            if (iDash >= nDash) {
               iDash = 0;
            }
         }
      } else {
         while ((x > xend) && (y < yend)) {
            x -= xDash[iDash];
            y += yDash[iDash];

            if ((iDash%2) == 0) {
               DrawWideLine(TMath::Nint(x0), TMath::Nint(y0),
                            TMath::Nint(x), TMath::Nint(y), color, thick);
            } else {
               x0 = x;
               y0 = y;
            }

            iDash++;

            if (iDash >= nDash) {
               iDash = 0;
            }
         }
      }
   }
   delete [] xDash;
   delete [] yDash;
}


//______________________________________________________________________________
void TASImage::DrawDashLine(UInt_t x1,  UInt_t y1, UInt_t x2, UInt_t y2, UInt_t nDash,
                            const char *pDash, const char *col, UInt_t thick)

{
   // Draw a dashed line.

   if (!InitVisual()) {
      Warning("DrawDashLine", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("DrawDashLine", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("DrawDashLine", "Failed to get pixel array");
      return;
   }

   if ((nDash < 2) || !pDash || (nDash%2)) {
      Warning("DrawDashLine", "Wrong input parameters n=%d %ld", nDash, (Long_t)sizeof(pDash)-1);
      return;
   }

   ARGB32 color;
   parse_argb_color(col, &color);

   if (x1 == x2) {
      DrawDashVLine(x1, y1, y2, nDash, pDash, (UInt_t)color, thick);
   } else if (y1 == y2) {
      DrawDashHLine(y1, x1, x2, nDash, pDash, (UInt_t)color, thick);
   } else {
      if (thick < 2) DrawDashZLine(x1, y1, x2, y2, nDash, pDash, (UInt_t)color);
      else DrawDashZTLine(x1, y1, x2, y2, nDash, pDash, (UInt_t)color, thick);
   }
}


//______________________________________________________________________________
void TASImage::DrawPolyLine(UInt_t nn, TPoint *xy, const char *col, UInt_t thick,
                            TImage::ECoordMode mode)
{
   // Draw a polyline.

   ARGB32 color;
   parse_argb_color(col, &color);

   Int_t x0 = xy[0].GetX();
   Int_t y0 = xy[0].GetY();
   Int_t x = 0;
   Int_t y = 0;

   for (UInt_t i = 1; i < nn; i++) {
      x = (mode == kCoordModePrevious) ? x + xy[i].GetX() : xy[i].GetX();
      y = (mode == kCoordModePrevious) ? y + xy[i].GetY() : xy[i].GetY();

      DrawLineInternal(x0, y0, x, y, (UInt_t)color, thick);

      x0 = x;
      y0 = y;
   }
}


//______________________________________________________________________________
void TASImage::PutPixel(Int_t x, Int_t y, const char *col)
{
   // Draw a point at the specified position.

   if (!InitVisual()) {
      Warning("PutPixel", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("PutPixel", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("PutPixel", "Failed to get pixel array");
      return;
   }

   ARGB32 color;
   parse_argb_color(col, &color);

   if ((x < 0) || (y < 0) || (x >= (int)fImage->width) || (y >= (int)fImage->height)) {
      Warning("PutPixel", "Out of range width=%d x=%d, height=%d y=%d",
               fImage->width, x, fImage->height, y);
      return;
   }
   _alphaBlend(&fImage->alt.argb32[y*fImage->width + x], &color);
}


//______________________________________________________________________________
void TASImage::PolyPoint(UInt_t npt, TPoint *ppt, const char *col, TImage::ECoordMode mode)
{
   // Draw a poly point.

   if (!InitVisual()) {
      Warning("PolyPoint", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("PolyPoint", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("PolyPoint", "Failed to get pixel array");
      return;
   }

   if (!npt || !ppt) {
      Warning("PolyPoint", "No points specified");
      return;
   }

   TPoint *ipt = 0;
   UInt_t i = 0;
   ARGB32 color;
   parse_argb_color(col, &color);

   //make pointlist origin relative
   if (mode == kCoordModePrevious) {
      ipt = new TPoint[npt];

      for (i = 0; i < npt; i++) {
         ipt[i].fX += ppt[i].fX;
         ipt[i].fY += ppt[i].fY;
      }
   }
   int x, y;

   for (i = 0; i < npt; i++) {
      x = ipt ? ipt[i].fX : ppt[i].fX;
      y = ipt ? ipt[i].fY : ppt[i].fY;

      if ((x < 0) || (y < 0) || (x >= (int)fImage->width) || (y >= (int)fImage->height)) {
         continue;
      }
      _alphaBlend(&fImage->alt.argb32[y*fImage->width + x], &color);
   }

   if (ipt) {
      delete [] ipt;
   }
}


//______________________________________________________________________________
void TASImage::DrawSegments(UInt_t nseg, Segment_t *seg, const char *col, UInt_t thick)
{
   // Draw segments.

   if (!nseg || !seg) {
      Warning("DrawSegments", "Ivalid data nseg=%d seg=0x%lx", nseg, (Long_t)seg);
      return;
   }

   TPoint pt[2];

   for (UInt_t i = 0; i < nseg; i++) {
      pt[0].fX = seg->fX1;
      pt[1].fX = seg->fX2;
      pt[0].fY = seg->fY1;
      pt[1].fY = seg->fY2;

      DrawPolyLine(2, pt, col, thick, kCoordModeOrigin);
      seg++;
   }
}


//______________________________________________________________________________
void TASImage::FillSpans(UInt_t npt, TPoint *ppt, UInt_t *widths, const char *col,
                         const char *stipple, UInt_t w, UInt_t h)
{
   // Fill spans with specified color or/and stipple.

   if (!InitVisual()) {
      Warning("FillSpans", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("FillSpans", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("FillSpans", "Failed to get pixel array");
      return;
   }

   if (!npt || !ppt || !widths || (stipple && (!w || !h))) {
      Warning("FillSpans", "Invalid input data npt=%d ppt=0x%lx col=%s widths=0x%lx stipple=0x%lx w=%d h=%d",
              npt, (Long_t)ppt, col, (Long_t)widths, (Long_t)stipple, w, h);
      return;
   }

   ARGB32 color;
   parse_argb_color(col, &color);
   Int_t idx = 0;
   UInt_t x = 0;
   UInt_t yy;

   for (UInt_t i = 0; i < npt; i++) {
      yy = ppt[i].fY*fImage->width;
      for (UInt_t j = 0; j < widths[i]; j++) {
         if ((ppt[i].fX >= (Int_t)fImage->width) || (ppt[i].fX < 0) ||
             (ppt[i].fY >= (Int_t)fImage->height) || (ppt[i].fY < 0)) continue;

         x = ppt[i].fX + j;
         idx = yy + x;

         if (!stipple) {
            _alphaBlend(&fImage->alt.argb32[idx], &color);
         } else {
            Int_t ii = (ppt[i].fY%h)*w + x%w;

            if (stipple[ii >> 3] & (1 << (ii%8))) {
               _alphaBlend(&fImage->alt.argb32[idx], &color);
            }
         }
      }
   }
}


//______________________________________________________________________________
void TASImage::FillSpans(UInt_t npt, TPoint *ppt, UInt_t *widths, TImage *tile)
{
   // Fill spans with tile image.

   if (!InitVisual()) {
      Warning("FillSpans", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("FillSpans", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("FillSpans", "Failed to get pixel array");
      return;
   }

   if (!npt || !ppt || !widths || !tile) {
      Warning("FillSpans", "Invalid input data npt=%d ppt=0x%lx widths=0x%lx tile=0x%lx",
              npt, (Long_t)ppt, (Long_t)widths, (Long_t)tile);
      return;
   }

   Int_t idx = 0;
   Int_t ii = 0;
   UInt_t x = 0;
   UInt_t *arr = tile->GetArgbArray();
   UInt_t xx = 0;
   UInt_t yy = 0;
   UInt_t yyy = 0;

   for (UInt_t i = 0; i < npt; i++) {
      yyy = ppt[i].fY*fImage->width;

      for (UInt_t j = 0; j < widths[i]; j++) {
         if ((ppt[i].fX >= (Int_t)fImage->width) || (ppt[i].fX < 0) ||
             (ppt[i].fY >= (Int_t)fImage->height) || (ppt[i].fY < 0)) continue;
         x = ppt[i].fX + j;
         idx = yyy + x;
         xx = x%tile->GetWidth();
         yy = ppt[i].fY%tile->GetHeight();
         ii = yy*tile->GetWidth() + xx;
         _alphaBlend(&fImage->alt.argb32[idx], &arr[ii]);
      }
      yyy += fImage->width;;
   }
}


//______________________________________________________________________________
void TASImage::CropSpans(UInt_t npt, TPoint *ppt, UInt_t *widths)
{
   // Crop spans.

   if (!InitVisual()) {
      Warning("CropSpans", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("CropSpans", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("CropSpans", "Failed to get pixel array");
      return;
   }

   if (!npt || !ppt || !widths) {
      Warning("CropSpans", "No points specified npt=%d ppt=0x%lx widths=0x%lx", npt, (Long_t)ppt, (Long_t)widths);
      return;
   }

   int y0 = ppt[0].fY;
   int y1 = ppt[npt-1].fY;
   UInt_t y = 0;
   UInt_t x = 0;
   UInt_t i = 0;
   UInt_t idx = 0;
   UInt_t sz = fImage->width*fImage->height;
   UInt_t yy = y*fImage->width;

   for (y = 0; (int)y < y0; y++) {
      for (x = 0; x < fImage->width; x++) {
         idx = yy + x;
         if (idx < sz) fImage->alt.argb32[idx] = 0;
      }
      yy += fImage->width;
   }

   for (i = 0; i < npt; i++) {
      for (x = 0; (int)x < ppt[i].fX; x++) {
         idx = ppt[i].fY*fImage->width + x;
         if (idx < sz) fImage->alt.argb32[idx] = 0;
      }
      for (x = ppt[i].fX + widths[i] + 1; x < fImage->width; x++) {
         idx = ppt[i].fY*fImage->width + x;
         if (idx < sz) fImage->alt.argb32[idx] = 0;
      }
   }

   yy = y1*fImage->width;
   for (y = y1; y < fImage->height; y++) {
      for (x = 0; x < fImage->width; x++) {
         idx = yy + x;
         if (idx < sz) fImage->alt.argb32[idx] = 0;
      }
      yy += fImage->width;
   }
}


//______________________________________________________________________________
void TASImage::CopyArea(TImage *dst, Int_t xsrc, Int_t ysrc, UInt_t w,  UInt_t h,
                        Int_t xdst, Int_t ydst, Int_t gfunc, EColorChan)
{
   // Copy source region to the destination image. Copy is done according
   // to specified function:
   //
   // enum EGraphicsFunction {
   //    kGXclear = 0,               // 0
   //    kGXand,                     // src AND dst
   //    kGXandReverse,              // src AND NOT dst
   //    kGXcopy,                    // src (default)
   //    kGXandInverted,             // NOT src AND dst
   //    kGXnoop,                    // dst
   //    kGXxor,                     // src XOR dst
   //    kGXor,                      // src OR dst
   //    kGXnor,                     // NOT src AND NOT dst
   //    kGXequiv,                   // NOT src XOR dst
   //    kGXinvert,                  // NOT dst
   //    kGXorReverse,               // src OR NOT dst
   //    kGXcopyInverted,            // NOT src
   //    kGXorInverted,              // NOT src OR dst
   //    kGXnand,                    // NOT src OR NOT dst
   //    kGXset                      // 1
   // };

   if (!InitVisual()) {
      Warning("CopyArea", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("CopyArea", "no image");
      return;
   }
   if (!dst) return;

   ASImage *out = ((TASImage*)dst)->GetImage();

   int x = 0;
   int y = 0;
   int idx = 0;
   int idx2 = 0;
   xsrc = xsrc < 0 ? 0 : xsrc;
   ysrc = ysrc < 0 ? 0 : ysrc;

   if ((xsrc >= (int)fImage->width) || (ysrc >= (int)fImage->height)) return;

   w = xsrc + w > fImage->width ? fImage->width - xsrc : w;
   h = ysrc + h > fImage->height ? fImage->height - ysrc : h;
   UInt_t yy = (ysrc + y)*fImage->width;

   if (!fImage->alt.argb32) {
      BeginPaint();
   }
   if (!out->alt.argb32) {
      dst->BeginPaint();
      out = ((TASImage*)dst)->GetImage();
   }

   if (fImage->alt.argb32 && out->alt.argb32) {
      for (y = 0; y < (int)h; y++) {
         for (x = 0; x < (int)w; x++) {
            idx = yy + x + xsrc;
            if ((x + xdst < 0) || (ydst + y < 0) ||
                (x + xdst >= (int)out->width) || (y + ydst >= (int)out->height) ) continue;

            idx2 = (ydst + y)*out->width + x + xdst;

            switch ((EGraphicsFunction)gfunc) {
               case kGXclear:
                  out->alt.argb32[idx2] = 0;
                  break;
               case kGXand:
                  out->alt.argb32[idx2] &= fImage->alt.argb32[idx];
                  break;
               case kGXandReverse:
                  out->alt.argb32[idx2] = fImage->alt.argb32[idx] & (~out->alt.argb32[idx2]);
                  break;
               case kGXandInverted:
                  out->alt.argb32[idx2] &= ~fImage->alt.argb32[idx];
                  break;
               case kGXnoop:
                  break;
               case kGXxor:
                  out->alt.argb32[idx2] ^= fImage->alt.argb32[idx];
                  break;
               case kGXor:
                  out->alt.argb32[idx2] |= fImage->alt.argb32[idx];
                  break;
               case kGXnor:
                  out->alt.argb32[idx2] = (~fImage->alt.argb32[idx]) & (~out->alt.argb32[idx2]);
                  break;
               case kGXequiv:
                  out->alt.argb32[idx2] ^= ~fImage->alt.argb32[idx];
                  break;
               case kGXinvert:
                  out->alt.argb32[idx2] = ~out->alt.argb32[idx2];
                  break;
               case kGXorReverse:
                  out->alt.argb32[idx2] = fImage->alt.argb32[idx] | (~out->alt.argb32[idx2]);
                  break;
               case kGXcopyInverted:
                  out->alt.argb32[idx2] = ~fImage->alt.argb32[idx];
                  break;
               case kGXorInverted:
                  out->alt.argb32[idx2] |= ~fImage->alt.argb32[idx];
                  break;
               case kGXnand:
                  out->alt.argb32[idx2] = (~fImage->alt.argb32[idx]) | (~out->alt.argb32[idx2]);
                  break;
               case kGXset:
                  out->alt.argb32[idx2] = 0xFFFFFFFF;
                  break;
               case kGXcopy:
               default:
                  out->alt.argb32[idx2] = fImage->alt.argb32[idx];
                  break;
            }
         }
         yy += fImage->width;
      }
   }
}


//______________________________________________________________________________
void TASImage::DrawCellArray(Int_t x1, Int_t y1, Int_t x2, Int_t y2, Int_t nx,
                             Int_t ny, UInt_t *ic)
{
   // Draw a cell array.
   // x1,y1        : left down corner
   // x2,y2        : right up corner
   // nx,ny        : array size
   // ic           : array of ARGB32 colors
   //
   // Draw a cell array. The drawing is done with the pixel presicion
   // if (X2-X1)/NX (or Y) is not a exact pixel number the position of
   // the top rigth corner may be wrong.

   int i, j, ix, iy, w, h;

   ARGB32 color = 0xFFFFFFFF;
   ARGB32 icol;

   w  = TMath::Max((x2-x1)/(nx),1);
   h  = TMath::Max((y1-y2)/(ny),1);
   ix = x1;

   for (i = 0; i < nx; i++) {
      iy = y1 - h;
      for (j = 0; j < ny; j++) {
         icol = (ARGB32)ic[i + (nx*j)];
         if (icol != color) {
            color = icol;
         }
         FillRectangleInternal((UInt_t)color, ix, iy, w, h);
         iy = iy - h;
      }
      ix = ix + w;
   }
}


//______________________________________________________________________________
UInt_t TASImage::AlphaBlend(UInt_t bot, UInt_t top)
{
   // Return alphablended value computed from bottom and top pixel values.

   UInt_t ret = bot;

   _alphaBlend(&ret, &top);
   return ret;
}


//______________________________________________________________________________
const ASVisual *TASImage::GetVisual()
{
   // Return visual.

   return fgVisual;
}


//______________________________________________________________________________
static int GetPolyYBounds(TPoint *pts, int n, int *by, int *ty)
{
   // Get poly bounds along Y.

   register TPoint *ptMin;
   int ymin, ymax;
   TPoint *ptsStart = pts;

   ptMin = pts;
   ymin = ymax = (pts++)->fY;

   while (--n > 0) {
      if (pts->fY < ymin) {
         ptMin = pts;
         ymin = pts->fY;
      }
      if (pts->fY > ymax) {
         ymax = pts->fY;
      }
      pts++;
    }

    *by = ymin;
    *ty = ymax;
    return (ptMin - ptsStart);
}


//______________________________________________________________________________
Bool_t TASImage::GetPolygonSpans(UInt_t npt, TPoint *ppt, UInt_t *nspans,
                                 TPoint **outPoint, UInt_t **outWidth)
{
   // The code is based on Xserver/mi/mipolycon.c
   //    "Copyright 1987, 1998  The Open Group"

   int xl = 0;                   // x vals of leftedges
   int xr = 0;                   // x vals of right edges
   int dl = 0;                   // decision variables
   int dr = 0;                   // decision variables
   int ml = 0;                   // left edge slope
   int m1l = 0;                  // left edge slope+1
   int mr = 0, m1r = 0;          // right edge slope and slope+1
   int incr1l = 0, incr2l = 0;   // left edge error increments
   int incr1r = 0, incr2r = 0;   // right edge error increments
   int dy;                       // delta y
   int y;                        // current scanline
   int left, right;              // indices to first endpoints
   int i;                        // loop counter
   int nextleft, nextright;      // indices to second endpoints
   TPoint *ptsOut;               // output buffer
   UInt_t *width;                // output buffer
   TPoint *firstPoint=0;
   UInt_t *firstWidth=0;
   int imin;                     // index of smallest vertex (in y)
   int ymin;                     // y-extents of polygon
   int ymax;
   Bool_t  ret = kTRUE;

   *nspans = 0;

   if (!InitVisual()) {
      Warning("GetPolygonSpans", "Visual not initiated");
      return kFALSE;
   }

   if (!fImage) {
      Warning("GetPolygonSpans", "no image");
      return kFALSE;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("GetPolygonSpans", "Failed to get pixel array");
      return kFALSE;
   }

   if ((npt < 3) || !ppt) {
      Warning("GetPolygonSpans", "No points specified npt=%d ppt=0x%lx", npt, (Long_t)ppt);
      return kFALSE;
   }

   //  find leftx, bottomy, rightx, topy, and the index
   //  of bottomy. Also translate the points.
   imin = GetPolyYBounds(ppt, npt, &ymin, &ymax);

   dy = ymax - ymin + 1;
   if ((npt < 3) || (dy < 0)) return kFALSE;

   ptsOut = firstPoint = new TPoint[dy];
   width = firstWidth = new UInt_t[dy];
   ret = kTRUE;

   nextleft = nextright = imin;
   y = ppt[nextleft].fY;

   //  loop through all edges of the polygon
   do {
      // add a left edge if we need to
      if (ppt[nextleft].fY == y) {
         left = nextleft;

         //  find the next edge, considering the end
         //  conditions of the array.
         nextleft++;
         if (nextleft >= (int)npt) {
            nextleft = 0;
         }

         // now compute all of the random information
         // needed to run the iterative algorithm.
         BRESINITPGON(ppt[nextleft].fY - ppt[left].fY,
                      ppt[left].fX, ppt[nextleft].fX,
                      xl, dl, ml, m1l, incr1l, incr2l);
      }

      // add a right edge if we need to
      if (ppt[nextright].fY == y) {
         right = nextright;

         // find the next edge, considering the end
         // conditions of the array.
         nextright--;
         if (nextright < 0) {
            nextright = npt-1;
         }

         //  now compute all of the random information
         //  needed to run the iterative algorithm.
         BRESINITPGON(ppt[nextright].fY - ppt[right].fY,
                      ppt[right].fX, ppt[nextright].fX,
                      xr, dr, mr, m1r, incr1r, incr2r);
      }

      // generate scans to fill while we still have
      //  a right edge as well as a left edge.
      i = min(ppt[nextleft].fY, ppt[nextright].fY) - y;

      // in case of non-convex polygon
      if (i < 0) {
         delete [] firstWidth;
	 delete [] firstPoint;
         return kTRUE;
      }

      while (i-- > 0)  {
         ptsOut->fY = y;

         // reverse the edges if necessary
         if (xl < xr) {
            *(width++) = xr - xl;
            (ptsOut++)->fX = xl;
         } else {
            *(width++) = xl - xr;
            (ptsOut++)->fX = xr;
         }
         y++;

         // increment down the edges
         BRESINCRPGON(dl, xl, ml, m1l, incr1l, incr2l);
         BRESINCRPGON(dr, xr, mr, m1r, incr1r, incr2r);
      }
   }  while (y != ymax);

   *nspans = UInt_t(ptsOut - firstPoint);
   *outPoint = firstPoint;
   *outWidth = firstWidth;

   return ret;
}


//______________________________________________________________________________
void TASImage::FillPolygon(UInt_t npt, TPoint *ppt, const char *col,
                           const char *stipple, UInt_t w, UInt_t h)
{
   // Fill a convex polygon with background color or bitmap.
   // For non convex polygon one must use DrawFillArea method

   UInt_t  nspans = 0;
   TPoint *firstPoint = 0;   // output buffer
   UInt_t *firstWidth = 0;   // output buffer

   Bool_t del = GetPolygonSpans(npt, ppt, &nspans, &firstPoint, &firstWidth);
   ARGB32 color;
   parse_argb_color(col, &color);

   if (nspans) {
      if (!stipple && ((color & 0xff000000)==0xff000000)) { //no stipple no alpha
         FillSpansInternal(nspans, firstPoint, firstWidth, color);
      } else {
         FillSpans(nspans, firstPoint, firstWidth, col, stipple, w, h);
      }

      if (del) {
         delete [] firstWidth;
         delete [] firstPoint;
      }
   } else {
      if (firstWidth) delete [] firstWidth;
      if (firstPoint) delete [] firstPoint;
   }
}


//______________________________________________________________________________
void TASImage::FillPolygon(UInt_t npt, TPoint *ppt, TImage *tile)
{
   // Fill a convex polygon with background image.
   // For non convex polygon one must use DrawFillArea method

   UInt_t  nspans = 0;
   TPoint *firstPoint = 0;   // output buffer
   UInt_t *firstWidth = 0;   // output buffer

   Bool_t del = GetPolygonSpans(npt, ppt, &nspans, &firstPoint, &firstWidth);

   if (nspans) {
      FillSpans(nspans, firstPoint, firstWidth, tile);

      if (del) {
         delete [] firstWidth;
         delete [] firstPoint;
      }
   } else {
      if (firstWidth) delete [] firstWidth;
      if (firstPoint) delete [] firstPoint;
   }
}


//______________________________________________________________________________
void TASImage::CropPolygon(UInt_t npt, TPoint *ppt)
{
   // Crop a convex polygon.

   UInt_t  nspans = 0;
   TPoint *firstPoint = 0;
   UInt_t *firstWidth = 0;

   Bool_t del = GetPolygonSpans(npt, ppt, &nspans, &firstPoint, &firstWidth);

   if (nspans) {
      CropSpans(nspans, firstPoint, firstWidth);

      if (del) {
         delete [] firstWidth;
         delete [] firstPoint;
      }
   } else {
         if (firstWidth) delete [] firstWidth;
         if (firstPoint) delete [] firstPoint;
   }
}

static const UInt_t NUMPTSTOBUFFER = 512;


//______________________________________________________________________________
void TASImage::DrawFillArea(UInt_t count, TPoint *ptsIn, const char *col,
                           const char *stipple, UInt_t w, UInt_t h)
{
   // Fill a polygon (any type convex, non-convex).

   if (!InitVisual()) {
      Warning("DrawFillArea", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("DrawFillArea", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("DrawFillArea", "Failed to get pixel array");
      return;
   }

   if ((count < 3) || !ptsIn) {
      Warning("DrawFillArea", "No points specified npt=%d ppt=0x%lx", count, (Long_t)ptsIn);
      return;
   }

   if (count < 5) {
      FillPolygon(count, ptsIn, col, stipple, w, h);
      return;
   }

   ARGB32 color;
   parse_argb_color(col, &color);

   EdgeTableEntry *pAET;  // the Active Edge Table
   int y;                 // the current scanline
   UInt_t nPts = 0;          // number of pts in buffer

   ScanLineList *pSLL;   // Current ScanLineList
   TPoint *ptsOut;       // ptr to output buffers
   UInt_t *width;
   TPoint firstPoint[NUMPTSTOBUFFER];  // the output buffers
   UInt_t firstWidth[NUMPTSTOBUFFER];
   EdgeTableEntry *pPrevAET;       // previous AET entry
   EdgeTable ET;                   // Edge Table header node
   EdgeTableEntry AET;             // Active ET header node
   EdgeTableEntry *pETEs;          // Edge Table Entries buff
   ScanLineListBlock SLLBlock;     // header for ScanLineList
   Bool_t del = kTRUE;

   static const UInt_t gEdgeTableEntryCacheSize = 200;
   static EdgeTableEntry gEdgeTableEntryCache[gEdgeTableEntryCacheSize];

   if (count < gEdgeTableEntryCacheSize) {
      pETEs = (EdgeTableEntry*)&gEdgeTableEntryCache;
      del = kFALSE;
   } else {
      pETEs = new EdgeTableEntry[count];
      del = kTRUE;
   }

   ptsOut = firstPoint;
   width = firstWidth;
   CreateETandAET(count, ptsIn, &ET, &AET, pETEs, &SLLBlock);
   pSLL = ET.scanlines.next;

   for (y = ET.ymin; y < ET.ymax; y++) {
      if (pSLL && y == pSLL->scanline) {
         loadAET(&AET, pSLL->edgelist);
         pSLL = pSLL->next;
      }
      pPrevAET = &AET;
      pAET = AET.next;

      while (pAET) {
         ptsOut->fX = pAET->bres.minor_axis;
         ptsOut->fY = y;
         ptsOut++;
         nPts++;

         *width++ = pAET->next->bres.minor_axis - pAET->bres.minor_axis;

         if (nPts == NUMPTSTOBUFFER) {
            if (!stipple && ((color & 0xff000000)==0xff000000)) { //no stipple, no alpha
               FillSpansInternal(nPts, firstPoint, firstWidth, color);
            } else {
               FillSpans(nPts, firstPoint, firstWidth, col, stipple, w, h);
            }
            ptsOut = firstPoint;
            width = firstWidth;
            nPts = 0;
         }
         EVALUATEEDGEEVENODD(pAET, pPrevAET, y)
         EVALUATEEDGEEVENODD(pAET, pPrevAET, y)
      }
      InsertionSort(&AET);
   }

   if (nPts) {
      if (!stipple && ((color & 0xff000000)==0xff000000)) {  //no stipple, no alpha
         FillSpansInternal(nPts, firstPoint, firstWidth, color);
      } else {
         FillSpans(nPts, firstPoint, firstWidth, col, stipple, w, h);
      }
   }

   if (del) delete [] pETEs;
   FreeStorage(SLLBlock.next);
}


//______________________________________________________________________________
void TASImage::DrawFillArea(UInt_t count, TPoint *ptsIn, TImage *tile)
{
   // Fill a polygon (any type convex, non-convex).

   if (!InitVisual()) {
      Warning("DrawFillArea", "Visual not initiated");
      return;
   }

   if (!fImage) {
      Warning("DrawFillArea", "no image");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!fImage->alt.argb32) {
      Warning("DrawFillArea", "Failed to get pixel array");
      return;
   }

   if ((count < 3) || !ptsIn) {
      Warning("DrawFillArea", "No points specified npt=%d ppt=0x%lx", count, (Long_t)ptsIn);
      return;
   }

   if (count < 5) {
      FillPolygon(count, ptsIn, tile);
      return;
   }

   EdgeTableEntry *pAET;   // the Active Edge Table
   int y;                  // the current scanline
   UInt_t nPts = 0;       // number of pts in buffer

   ScanLineList *pSLL;    // Current ScanLineList
   TPoint *ptsOut;        // ptr to output buffers
   UInt_t *width;
   TPoint firstPoint[NUMPTSTOBUFFER]; // the output buffers
   UInt_t firstWidth[NUMPTSTOBUFFER];
   EdgeTableEntry *pPrevAET;       // previous AET entry
   EdgeTable ET;                   // Edge Table header node
   EdgeTableEntry AET;             // Active ET header node
   EdgeTableEntry *pETEs;          // Edge Table Entries buff
   ScanLineListBlock SLLBlock;     // header for ScanLineList

   pETEs = new EdgeTableEntry[count];

   ptsOut = firstPoint;
   width = firstWidth;
   CreateETandAET(count, ptsIn, &ET, &AET, pETEs, &SLLBlock);
   pSLL = ET.scanlines.next;

   for (y = ET.ymin; y < ET.ymax; y++) {
      if (pSLL && y == pSLL->scanline) {
         loadAET(&AET, pSLL->edgelist);
         pSLL = pSLL->next;
      }
      pPrevAET = &AET;
      pAET = AET.next;

      while (pAET) {
         ptsOut->fX = pAET->bres.minor_axis;
         ptsOut->fY = y;
         ptsOut++;
         nPts++;

         *width++ = pAET->next->bres.minor_axis - pAET->bres.minor_axis;

         if (nPts == NUMPTSTOBUFFER) {
            FillSpans(nPts, firstPoint, firstWidth, tile);
            ptsOut = firstPoint;
            width = firstWidth;
            nPts = 0;
         }
         EVALUATEEDGEEVENODD(pAET, pPrevAET, y)
         EVALUATEEDGEEVENODD(pAET, pPrevAET, y)
      }
      InsertionSort(&AET);
   }
   FillSpans(nPts, firstPoint, firstWidth, tile);

   delete [] pETEs;
   FreeStorage(SLLBlock.next);
}


//______________________________________________________________________________
static ASDrawContext *create_draw_context_argb32(ASImage *im, ASDrawTool *brush)
{
   // Create draw context.

   ASDrawContext *ctx = new ASDrawContext;

   ctx->canvas_width = im->width;
   ctx->canvas_height = im->height;
   ctx->canvas = im->alt.argb32;
   ctx->scratch_canvas = 0;

   ctx->flags = ASDrawCTX_CanvasIsARGB;
   asim_set_custom_brush_colored( ctx, brush);
   return ctx;
}


//______________________________________________________________________________
static void destroy_asdraw_context32( ASDrawContext *ctx )
{
   // Destroy asdraw context32.

   if (ctx) {
      if (ctx->scratch_canvas) free(ctx->scratch_canvas);
      delete ctx;
   }
}

static const UInt_t kBrushCacheSize = 20;
static CARD32 gBrushCache[kBrushCacheSize*kBrushCacheSize];


//______________________________________________________________________________
void TASImage::DrawWideLine(UInt_t x1, UInt_t y1, UInt_t x2, UInt_t y2,
                            UInt_t color, UInt_t thick)
{
   // Draw wide line.

   Int_t sz = thick*thick;
   CARD32 *matrix;
   Bool_t use_cache = thick < kBrushCacheSize;

   if (use_cache) {
      matrix = gBrushCache;
   } else {
      matrix = new CARD32[sz];
   }

   for (int i = 0; i < sz; i++) {
      matrix[i] = (CARD32)color;
   };

   ASDrawTool brush;
   brush.matrix = matrix;
   brush.width = thick;
   brush.height = thick;
   brush.center_y = brush.center_x = thick/2;

   ASDrawContext *ctx = create_draw_context_argb32(fImage, &brush);
   asim_move_to(ctx, x1, y1);
   asim_line_to(ctx, x2, y2);

   if (!use_cache) {
      delete [] matrix;
   }
   destroy_asdraw_context32(ctx);
}


//______________________________________________________________________________
void TASImage::DrawGlyph(void *bitmap, UInt_t color, Int_t bx, Int_t by)
{
   // Draw glyph bitmap.

   static UInt_t col[5];
   Int_t x, y, yy, y0, xx;

   ULong_t r, g, b;
   int idx = 0;
   FT_Bitmap *source = (FT_Bitmap*)bitmap;
   UChar_t d = 0, *s = source->buffer;

   Int_t dots = Int_t(source->width * source->rows);
   r = g = b = 0;
   Int_t bxx, byy;

   yy = y0 = by > 0 ? by * fImage->width : 0;
   for (y = 0; y < (int) source->rows; y++) {
      byy = by + y;
      if ((byy >= (int)fImage->height) || (byy <0)) continue;

      for (x = 0; x < (int) source->width; x++) {
         bxx = bx + x;
         if ((bxx >= (int)fImage->width) || (bxx < 0)) continue;

         idx = bxx + yy;
         r += ((fImage->alt.argb32[idx] & 0xff0000) >> 16);
         g += ((fImage->alt.argb32[idx] & 0x00ff00) >> 8);
         b += (fImage->alt.argb32[idx] & 0x0000ff);
      }
      yy += fImage->width;
   }
   if (dots != 0) {
      r /= dots;
      g /= dots;
      b /= dots;
   }

   col[0] = (r << 16) + (g << 8) + b;
   col[4] = color;
   Int_t col4r = (col[4] & 0xff0000) >> 16;
   Int_t col4g = (col[4] & 0x00ff00) >> 8;
   Int_t col4b = (col[4] & 0x0000ff);

   // interpolate between fore and backgound colors
   for (x = 3; x > 0; x--) {
      xx = 4-x;
      Int_t colxr = (col4r*x + r*xx) >> 2;
      Int_t colxg = (col4g*x + g*xx) >> 2;
      Int_t colxb = (col4b*x + b*xx) >> 2;
      col[x] = (colxr << 16) + (colxg << 8) + colxb;
   }

   yy = y0;
   for (y = 0; y < (int) source->rows; y++) {
      byy = by + y;
      if ((byy >= (int)fImage->height) || (byy <0)) continue;

      for (x = 0; x < (int) source->width; x++) {
         bxx = bx + x;
         //if ((bxx >= (int)fImage->width) || (bxx < 0)) continue;

         d = *s++ & 0xff;
         d = ((d + 10) * 5) >> 8;
         if (d > 4) d = 4;

         if (d && (x < (int) source->width) && (bxx < (int)fImage->width) && (bxx >= 0)) {
            idx = bxx + yy;
            fImage->alt.argb32[idx] = (ARGB32)col[d];
         }
      }
      yy += fImage->width;
   }
}


//______________________________________________________________________________
void TASImage::DrawText(TText *text, Int_t x, Int_t y)
{
   // Draw text at the pixel position (x,y).

   if (!text)   return;
   if (!fImage) return;
   if (!gPad)   return;

   if (!InitVisual()) {
      Warning("DrawText", "Visual not initiated");
      return;
   }

   if (!fImage->alt.argb32) {
      BeginPaint();
   }

   if (!TTF::IsInitialized()) TTF::Init();

   // set text font
   TTF::SetTextFont(text->GetTextFont());

   Int_t wh = gPad->XtoPixel(gPad->GetX2());
   Int_t hh = gPad->YtoPixel(gPad->GetY1());

   // set text size in pixels
   Int_t ttfsize;

   if (wh < hh) {
      ttfsize = (Int_t)(text->GetTextSize()*wh);
   } else {
      ttfsize = (Int_t)(text->GetTextSize()*hh);
   }
   TTF::SetTextSize(ttfsize);

   // set text angle
   TTF::SetRotationMatrix(text->GetTextAngle());

   // set text
   TTF::PrepareString(text->GetTitle());
   TTF::LayoutGlyphs();

   // color
   TColor *col = gROOT->GetColor(text->GetTextColor());
   if (!col) { // no color, make it black
      col = gROOT->GetColor(1);
   }
   ARGB32 color;
   parse_argb_color(col->AsHexString(), &color);

   // Align()
   Int_t align = 0;
   Int_t txalh = text->GetTextAlign()/10;
   Int_t txalv = text->GetTextAlign()%10;

   switch (txalh) {
      case 0 :
      case 1 :
         switch (txalv) {  //left
            case 1 :
               align = 7;   //bottom
               break;
            case 2 :
               align = 4;   //center
               break;
            case 3 :
               align = 1;   //top
               break;
         }
         break;
      case 2 :
         switch (txalv) { //center
            case 1 :
               align = 8;   //bottom
               break;
            case 2 :
               align = 5;   //center
               break;
            case 3 :
               align = 2;   //top
               break;
         }
         break;
      case 3 :
         switch (txalv) {  //right
            case 1 :
               align = 9;   //bottom
               break;
            case 2 :
               align = 6;   //center
               break;
            case 3 :
               align = 3;   //top
               break;
         }
         break;
   }

   FT_Vector ftal;

   // vertical alignment
   if (align == 1 || align == 2 || align == 3) {
      ftal.y = TTF::GetAscent();
   } else if (align == 4 || align == 5 || align == 6) {
      ftal.y = TTF::GetAscent()/2;
   } else {
      ftal.y = 0;
   }

   // horizontal alignment
   if (align == 3 || align == 6 || align == 9) {
      ftal.x = TTF::GetWidth();
   } else if (align == 2 || align == 5 || align == 8) {
      ftal.x = TTF::GetWidth()/2;
   } else {
      ftal.x = 0;
   }

   FT_Vector_Transform(&ftal, TTF::GetRotMatrix());
   ftal.x = (ftal.x >> 6);
   ftal.y = (ftal.y >> 6);

   TTGlyph *glyph = TTF::GetGlyphs();

   for (int n = 0; n < TTF::GetNumGlyphs(); n++, glyph++) {
      if (FT_Glyph_To_Bitmap(&glyph->fImage, ft_render_mode_normal, 0, 1 )) continue;

      FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyph->fImage;
      FT_Bitmap *source = &bitmap->bitmap;

      Int_t bx = x - ftal.x + bitmap->left;
      Int_t by = y + ftal.y - bitmap->top;

      DrawGlyph(source, color, bx, by);
   }
}


//______________________________________________________________________________
void TASImage::DrawTextTTF(Int_t x, Int_t y, const char *text, Int_t size,
                           UInt_t color, const char *font_name, Float_t angle)
{
   // Draw text using TrueType fonts.

   if (!TTF::IsInitialized()) TTF::Init();

   TTF::SetTextFont(font_name);
   TTF::SetTextSize(size);
   TTF::SetRotationMatrix(angle);
   TTF::PrepareString(text);
   TTF::LayoutGlyphs();

   TTGlyph *glyph = TTF::GetGlyphs();

   // compute the size and position  that will contain the text
   Int_t Xoff = 0; if (TTF::GetBox().xMin < 0) Xoff = -TTF::GetBox().xMin;
   Int_t Yoff = 0; if (TTF::GetBox().yMin < 0) Yoff = -TTF::GetBox().yMin;
   Int_t h    = TTF::GetBox().yMax + Yoff;

   for (int n = 0; n < TTF::GetNumGlyphs(); n++, glyph++) {
      if (FT_Glyph_To_Bitmap(&glyph->fImage, ft_render_mode_normal, 0, 1 )) continue;

      FT_BitmapGlyph bitmap = (FT_BitmapGlyph)glyph->fImage;
      FT_Bitmap *source = &bitmap->bitmap;

      Int_t bx = x + bitmap->left;
      Int_t by = y + h - bitmap->top;
      DrawGlyph(source, color, bx, by);
   }
}


//______________________________________________________________________________
void TASImage::GetImageBuffer(char **buffer, int *size, EImageFileTypes type)
{
   // Return in-memory buffer compressed according image type.
   // Buffer must be deallocated after usage.
   // This method can be used for sending images over network.

   static ASImageExportParams params;
   Bool_t ret = kFALSE;
   int   isize = 0;
   char *ibuff = 0;
   ASImage *img = fScaledImage ? fScaledImage->fImage : fImage;

   if (!img) return;

   switch (type) {
      case TImage::kXpm:
         ret = ASImage2xpmRawBuff(img, (CARD8 **)buffer, size, 0);
         break;
      default:
         ret = ASImage2PNGBuff(img, (CARD8 **)buffer, size, &params);
   }

   if (!ret) {
      *size = isize;
      *buffer = ibuff;
   }
}


//______________________________________________________________________________
Bool_t TASImage::SetImageBuffer(char **buffer, EImageFileTypes type)
{
   // Create image from compressed buffer.
   // Supported formats:
   //
   //    PNG - by default
   //    XPM - two options exist:
   //      1.  xpm as a single string (raw buffer). Such string
   //          is returned by GetImageBuffer method.
   //
   //    For example:
   //       char *buf;
   //       int sz;
   //       im1->GetImageBuffer(&buf, &int, TImage::kXpm); /*raw buffer*/
   //       TImage *im2 = TImage::Create();
   //       im2->SetImageBuffer(&buf, TImage::kXpm);
   //
   //      2.  xpm as an array of strigs (preparsed)
   //
   //    For example:
   //       char *xpm[] = {
   //          "64 28 58 1",
   //          "  c #0A030C",
   //          ". c #1C171B"
   //             ...
   //    TImage *im = TImage::Create();
   //    im->SetImageBuffer(xpm, TImage::kXpm);

   DestroyImage();

   static ASImageImportParams params;
   params.flags = 0;
   params.width = 0;
   params.height = 0 ;
   params.filter = SCL_DO_ALL;
   params.gamma = 0;
   params.gamma_table = 0;
   params.compression = 0;
   params.format = ASA_ASImage;
   params.search_path = 0;
   params.subimage = 0;

   switch (type) {
      case TImage::kXpm:
      {
         char *ptr = buffer[0];
         while (isspace((int)*ptr)) ++ptr;
         if (atoi(ptr)) {  // preparsed and preloaded data
            fImage = xpm_data2ASImage((const char**)buffer, &params);
         } else {
            fImage = xpmRawBuff2ASImage((const char*)*buffer, &params);
         }
         break;
      }
      default:
         fImage = PNGBuff2ASimage((CARD8 *)*buffer, &params);
         break;
   }

   if (!fImage) {
      return kFALSE;
   }

   if (fName.IsNull()) {
      fName.Form("img_%dx%d.%d", fImage->width, fImage->height, gRandom->Integer(1000));
   }
   UnZoom();
   return kTRUE;
}


//______________________________________________________________________________
void TASImage::CreateThumbnail()
{
   // Create image thumbnail.

   int size;
   const int sz = 64;

   if (!fImage) {
      return;
   }

   if (!InitVisual()) {
      return;
   }

   static char *buf = 0;
   int w, h;
   ASImage *img = 0;

   if (fImage->width > fImage->height) {
      w = sz;
      h = (fImage->height*sz)/fImage->width;
   } else {
      h = sz;
      w = (fImage->width*sz)/fImage->height;
   }

   w = w < 8 ? 8 : w;
   h = h < 8 ? 8 : h;

   img = scale_asimage(fgVisual, fImage, w, h, ASA_ASImage,
                       GetImageCompression(), GetImageQuality());
   if (!img) {
      return;
   }

   // contrasing
   ASImage *rendered_im;
   ASImageLayer layers[2];
   init_image_layers(&(layers[0]), 2);
   layers[0].im = img;
   layers[0].dst_x = 0;
   layers[0].dst_y = 0;
   layers[0].clip_width = img->width;
   layers[0].clip_height = img->height;
   layers[0].bevel = 0;
   layers[1].im = img;
   layers[1].dst_x = 0;
   layers[1].dst_y = 0;
   layers[1].clip_width = img->width;
   layers[1].clip_height = img->height;
   layers[1].merge_scanlines = blend_scanlines_name2func("tint");
   rendered_im = merge_layers(fgVisual, &(layers[0]), 2, img->width, img->height,
                              ASA_ASImage, GetImageCompression(), GetImageQuality());
   destroy_asimage(&img);
   img = rendered_im;

   // pad image
   ASImage *padimg = 0;
   int d = 0;

   if (w == sz) {
      d = (sz - h) >> 1;
      padimg = pad_asimage(fgVisual, img, 0, d, sz, sz, 0x00ffffff,
                           ASA_ASImage, GetImageCompression(), GetImageQuality());
   } else {
      d = (sz - w) >> 1;
      padimg = pad_asimage(fgVisual, img, d, 0, sz, sz, 0x00ffffff,
                           ASA_ASImage, GetImageCompression(), GetImageQuality());
   }

   if (!padimg) {
      destroy_asimage(&img);
      return;
   }

   void *ptr = &buf;
   ASImage2xpmRawBuff(padimg, (CARD8 **)ptr, &size, 0);
   fTitle = buf;

   destroy_asimage(&padimg);
}


//______________________________________________________________________________
void TASImage::Streamer(TBuffer &b)
{
   // Streamer for ROOT I/O.

   Bool_t image_type = 0;
   char *buffer = 0;
   int size = 0;
   int w, h;
   UInt_t R__s, R__c;

   if (b.IsReading()) {
      Version_t version = b.ReadVersion(&R__s, &R__c);
      if (version == 0) { //dumb prototype for scheema evolution
         return;
      }

      if ( version == 1 ) {
         Int_t fileVersion = b.GetVersionOwner();
         if (fileVersion > 0 && fileVersion < 50000 ) {
            TImage::Streamer(b);
            b >> fMaxValue;
            b >> fMinValue;
            b >> fZoomOffX;
            b >> fZoomOffY;
            b >> fZoomWidth;
            b >> fZoomHeight;
            if ( fileVersion < 40200 ) {
               Bool_t zoomUpdate;
               b >> zoomUpdate;
               fZoomUpdate = zoomUpdate;
            } else {
               b >> fZoomUpdate;
               b >> fEditable;
               Bool_t paintMode;
               b >> paintMode;
               fPaintMode = paintMode;
            }
            b.CheckByteCount(R__s, R__c, TASImage::IsA());
            return;
         }
      }

      TNamed::Streamer(b);
      b >> image_type;

      if (image_type != 0) {     // read PNG compressed image
         b >> size;
         buffer = new char[size];
         b.ReadFastArray(buffer, size);
         SetImageBuffer(&buffer, TImage::kPng);
         delete [] buffer;
      } else {                   // read vector with palette
         TAttImage::Streamer(b);
         b >> w;
         b >> h;
         size = w*h;
         Double_t *vec = new Double_t[size];
         b.ReadFastArray(vec, size);
         SetImage(vec, w, h, &fPalette);
         delete [] vec;
      }
      b.CheckByteCount(R__s, R__c, TASImage::IsA());
   } else {
      if (!fImage) {
         return;
      }
      R__c = b.WriteVersion(TASImage::IsA(), kTRUE);

      if (fName.IsNull()) {
         fName.Form("img_%dx%d.%d", fImage->width, fImage->height, gRandom->Integer(1000));
      }
      TNamed::Streamer(b);

      image_type = fImage->alt.vector ? 0 : 1;
      b << image_type;

      if (image_type != 0) {     // write PNG compressed image
         GetImageBuffer(&buffer, &size, TImage::kPng);
         b << size;
         b.WriteFastArray(buffer, size);
         delete buffer;
      } else {                   // write vector  with palette
         TAttImage::Streamer(b);
         b << fImage->width;
         b << fImage->height;
         b.WriteFastArray(fImage->alt.vector, fImage->width*fImage->height);
      }
      b.SetByteCount(R__c, kTRUE);
   }
}


//______________________________________________________________________________
void TASImage::Browse(TBrowser *)
{
   // Browse image.

   if (fImage->alt.vector) {
      Draw("n");
   } else {
      Draw("nxxx");
   }
   CreateThumbnail();
}


//______________________________________________________________________________
const char *TASImage::GetTitle() const
{
   // Title is used to keep 32x32 xpm image's thumbnail.

   if (!gDirectory || !gDirectory->IsWritable()) {
      return 0;
   }

   TASImage *mutble = (TASImage *)this;

   if (fTitle.IsNull()) {
      mutble->SetTitle(fName.Data());
   }

   return fTitle.Data();
}


//______________________________________________________________________________
void TASImage::SetTitle(const char *title)
{
   // Set a title for an image.

   if (fTitle.IsNull()) {
      CreateThumbnail();
   }

   if (fTitle.IsNull()) {
      return;
   }

   int start = fTitle.Index("/*") + 3;
   int stop = fTitle.Index("*/") - 1;

   if ((start > 0) && (stop - start > 0)) {
      fTitle.Replace(start, stop - start, title);
   }
}


//______________________________________________________________________________
void TASImage::DrawCubeBezier(Int_t x1, Int_t y1, Int_t x2, Int_t y2,
                             Int_t x3, Int_t y3, const char *col, UInt_t thick)
{
   // Draw a cubic bezier line.

   Int_t sz = thick*thick;
   CARD32 *matrix;
   Bool_t use_cache = thick < kBrushCacheSize;

   ARGB32 color;
   parse_argb_color(col, &color);

   if (use_cache) {
      matrix = gBrushCache;
   } else {
      matrix = new CARD32[sz];
   }

   for (int i = 0; i < sz; i++) {
      matrix[i] = (CARD32)color;
   };

   ASDrawTool brush;
   brush.matrix = matrix;
   brush.width = thick;
   brush.height = thick;
   brush.center_y = brush.center_x = thick/2;

   ASDrawContext *ctx = 0;

   ctx = create_draw_context_argb32(fImage, &brush);
   asim_cube_bezier(ctx, x1, y1, x2, y2, x3, y3);

   if (!use_cache) {
      delete [] matrix;
   }
   destroy_asdraw_context32(ctx);
}


//______________________________________________________________________________
void TASImage::DrawStraightEllips(Int_t x, Int_t y, Int_t rx, Int_t ry,
                                  const char *col, Int_t thick)
{
   // Draw a straight ellipse.
   // If thick < 0 - draw filled ellipse.

   thick = !thick ? 1 : thick;
   Int_t sz = thick*thick;
   CARD32 *matrix;
   Bool_t use_cache = (thick > 0) && ((UInt_t)thick < kBrushCacheSize);

   ARGB32 color;
   parse_argb_color(col, &color);

   if (use_cache) {
      matrix = gBrushCache;
   } else {
      matrix = new CARD32[sz];
   }

   for (int i = 0; i < sz; i++) {
      matrix[i] = (CARD32)color;
   };

   ASDrawTool brush;
   brush.matrix = matrix;
   brush.width = thick > 0 ? thick : 1;
   brush.height = thick > 0 ? thick : 1;
   brush.center_y = brush.center_x = thick > 0 ? thick/2 : 0;

   ASDrawContext *ctx = create_draw_context_argb32(fImage, &brush);
   asim_straight_ellips(ctx, x, y, rx, ry, thick < 0);

   if (!use_cache) {
      delete [] matrix;
   }
   destroy_asdraw_context32(ctx);
}


//______________________________________________________________________________
void TASImage::DrawCircle(Int_t x, Int_t y, Int_t r, const char *col, Int_t thick)
{
   // Draw a circle.
   // If thick < 0 - draw filled circle

   thick = !thick ? 1 : thick;
   Int_t sz = thick*thick;
   CARD32 *matrix;
   Bool_t use_cache = (thick > 0) && ((UInt_t)thick < kBrushCacheSize);

   ARGB32 color;
   parse_argb_color(col, &color);

///matrix = new CARD32[sz];
   if (use_cache) {
      matrix = gBrushCache;
   } else {
      matrix = new CARD32[sz];
   }

   for (int i = 0; i < sz; i++) {
      matrix[i] = (CARD32)color;
   }

   ASDrawTool brush;
   brush.matrix = matrix;
   brush.height = brush.width = thick > 0 ? thick : 1;
   brush.center_y = brush.center_x = thick > 0 ? thick/2 : 0;

   ASDrawContext *ctx = create_draw_context_argb32(fImage, &brush);
   asim_circle(ctx, x,  y, r, thick < 0);

///free (matrix);
   if (!use_cache) {
      delete [] matrix;
   }
   destroy_asdraw_context32(ctx);
}


//______________________________________________________________________________
void TASImage::DrawEllips(Int_t x, Int_t y, Int_t rx, Int_t ry, Int_t angle,
                           const char *col, Int_t thick)
{
   // Draw an ellipse.
   // If thick < 0 - draw filled ellips

   thick = !thick ? 1 : thick;
   Int_t sz = thick*thick;
   CARD32 *matrix;
   Bool_t use_cache = (thick > 0) && ((UInt_t)thick < kBrushCacheSize);

   ARGB32 color;
   parse_argb_color(col, &color);

   if (use_cache) {
      matrix = gBrushCache;
   } else {
      matrix = new CARD32[sz];
   }

   for (int i = 0; i < sz; i++) {
      matrix[i] = (CARD32)color;
   };

   ASDrawTool brush;
   brush.matrix = matrix;
   brush.width = thick > 0 ? thick : 1;
   brush.height = thick > 0 ? thick : 1;
   brush.center_y = brush.center_x = thick > 0 ? thick/2 : 0;

   ASDrawContext *ctx = create_draw_context_argb32(fImage, &brush);
   asim_ellips(ctx, x, y, rx, ry, angle, thick < 0);

   if (!use_cache) {
      delete [] matrix;
   }
   destroy_asdraw_context32(ctx);
}


//______________________________________________________________________________
void TASImage::DrawEllips2(Int_t x, Int_t y, Int_t rx, Int_t ry, Int_t angle,
                           const char *col, Int_t thick)
{
   // Draw an ellipse.
   // If thick < 0 - draw filled ellipse.

   thick = !thick ? 1 : thick;
   Int_t sz = thick*thick;
   CARD32 *matrix;
   Bool_t use_cache = (thick > 0) && ((UInt_t)thick < kBrushCacheSize);

   ARGB32 color;
   parse_argb_color(col, &color);

   if (use_cache) {
      matrix = gBrushCache;
   } else {
      matrix = new CARD32[sz];
   }

   for (int i = 0; i < sz; i++) {
      matrix[i] = (CARD32)color;
   };

   ASDrawTool brush;
   brush.matrix = matrix;
   brush.width = thick > 0 ? thick : 1;
   brush.height = thick > 0 ? thick : 1;
   brush.center_y = brush.center_x = thick > 0 ? thick/2 : 0;

   ASDrawContext *ctx = create_draw_context_argb32(fImage, &brush);
   asim_ellips2(ctx, x, y, rx, ry, angle, thick < 0);

   if (!use_cache) {
      delete [] matrix;
   }
   destroy_asdraw_context32(ctx);
}


//______________________________________________________________________________
void TASImage::FloodFill(Int_t /*x*/, Int_t /*y*/, const char * /*col*/,
                         const char * /*minc*/, const char * /*maxc*/)
{
   // Flood fill.
}


//______________________________________________________________________________
void TASImage::Gray(Bool_t on)
{
   // Convert RGB image to Gray image and vice versa.

   if (fIsGray == on) {
      return;
   }

   if (!IsValid()) {
      Warning("Gray", "Image not initiated");
      return;
   }

   if (!InitVisual()) {
      Warning("Gray", "Visual not initiated");
      return;
   }

   if (!fGrayImage && !on) {
      return;
   }
   ASImage *sav = 0;
   delete fScaledImage;
   fScaledImage = 0;

   if (fGrayImage)  {
      sav = fImage;
      fImage = fGrayImage;
      fGrayImage = sav;
      fIsGray = on;
      return;
   }

   if (!on) return;

   UInt_t l, r, g, b, idx;
   int y = 0;
   UInt_t i, j;

   if (fImage->alt.argb32) {
      fGrayImage = tile_asimage(fgVisual, fImage, 0, 0, fImage->width, fImage->height,
                                0, ASA_ARGB32, 0, ASIMAGE_QUALITY_DEFAULT);

      for (i = 0; i < fImage->height; i++) {
         for (j = 0; j < fImage->width; j++) {
            idx = y + j;

            r = ((fImage->alt.argb32[idx] & 0xff0000) >> 16);
            g = ((fImage->alt.argb32[idx] & 0x00ff00) >> 8);
            b = (fImage->alt.argb32[idx] & 0x0000ff);
            l = (57*r + 181*g + 18*b)/256;
            fGrayImage->alt.argb32[idx] = (l << 16) + (l << 8) + l;
         }
         y += fImage->width;
      }
   } else {
      fGrayImage = create_asimage(fImage->width, fImage->height, 0);

      ASImageDecoder *imdec = start_image_decoding(fgVisual, fImage, SCL_DO_ALL,
                                                   0, 0, fImage->width, fImage->height, 0);

      if (!imdec) {
         return;
      }
#ifdef HAVE_MMX
   mmx_init();
#endif
      ASImageOutput *imout = start_image_output(fgVisual, fGrayImage, ASA_ASImage,
                                                GetImageCompression(), GetImageQuality());
      if (!imout) {
         Warning("ToGray", "Failed to start image output");
         delete fScaledImage;
         fScaledImage = 0;
         delete [] imdec;
         return;
      }

      CARD32 *aa = imdec->buffer.alpha;
      CARD32 *rr = imdec->buffer.red;
      CARD32 *gg = imdec->buffer.green;
      CARD32 *bb = imdec->buffer.blue;

      ASScanline result;
      ASScanline *sl = prepare_scanline(fImage->width, 0, &result, fgVisual->BGR_mode);
      if (sl) delete sl;

      for (i = 0; i < fImage->height; i++) {
         imdec->decode_image_scanline(imdec);
         result.flags = imdec->buffer.flags;
         result.back_color = imdec->buffer.back_color;

         for (j = 0; j < fImage->width; j++) {
            l = (57*rr[j] + 181*gg[j]+ 18*bb[j])/256;
            result.alpha[j] = aa[j];
            result.red[j] = l;
            result.green[j] = l;
            result.blue[j] = l;
         }
         imout->output_image_scanline(imout, &result, 1);
      }

      stop_image_decoding(&imdec);
      stop_image_output(&imout);
#ifdef HAVE_MMX
   mmx_off();
#endif
   }

   sav = fImage;
   fImage = fGrayImage;
   fGrayImage = sav;
   fIsGray = kTRUE;
}


//______________________________________________________________________________
void TASImage::FromWindow(Drawable_t wid, Int_t x, Int_t y, UInt_t w, UInt_t h)
{
   // Create an image (screenshot) from specified window.

   Int_t xy;

   x = x < 0 ? 0 : x;
   y = y < 0 ? 0 : y;

   // syncronization
   gVirtualX->Update(1);
   if (!gThreadXAR) {
      gSystem->ProcessEvents();
      gSystem->Sleep(10);
      gSystem->ProcessEvents();
   }

   if (!w || !h) {
      gVirtualX->GetWindowSize(wid, xy, xy, w, h);
   }

   if ((x >= (Int_t)w) || (y >= (Int_t)h)) {
      return;
   }

   if (!InitVisual()) {
      Warning("FromWindow", "Visual not initiated");
      return;
   }

   DestroyImage();
   delete fScaledImage;
   fScaledImage = 0;

   static int x11 = -1;
   if (x11 < 0) x11 = gVirtualX->InheritsFrom("TGX11");

   if (x11) { //use built-in optimized version
      fImage = pixmap2asimage(fgVisual, wid, x, y, w, h, kAllPlanes, 0, 0);
   } else {
      unsigned char *bits = gVirtualX->GetColorBits(wid, 0, 0, w, h);

      if (!bits) { // error
         return;
      }
      fImage = bitmap2asimage(bits, w, h, 0, 0);
      delete [] bits;
   }
}


//______________________________________________________________________________
void TASImage::FromGLBuffer(UChar_t* buf, UInt_t w, UInt_t h)
{
   // Creates an image (screenshot) from a RGBA buffer.

   DestroyImage();
   delete fScaledImage;
   fScaledImage = 0;

   UChar_t* xx = new UChar_t[4*w];
   for (UInt_t i = 0; i < h/2; ++i) {
      memcpy(xx, buf + 4*w*i, 4*w);
      memcpy(buf + 4*w*i, buf + 4*w*(h-i-1), 4*w);
      memcpy(buf + 4*w*(h-i-1), xx, 4*w);
   }
   delete [] xx;

   fImage = bitmap2asimage(buf, w, h, 0, 0);
}


//______________________________________________________________________________
void TASImage::SetPaletteEnabled(Bool_t on)
{
   // Switch on/off the image palette.
   // That also invokes calling vectorization of image.

   if (!fImage) {
      return;
   }

   if (!fImage->alt.vector && on) {
      Vectorize();
   }
   fPaletteEnabled = on;

   if (on) {
      Double_t left = gPad->GetLeftMargin();
      Double_t right = gPad->GetRightMargin();
      Double_t top = gPad->GetTopMargin();
      Double_t bottom = gPad->GetBottomMargin();

      gPad->Range(-left / (1.0 - left - right),
                  -bottom / (1.0 - top - bottom),
                  1 + right / (1.0 - left - right),
                  1 + top / ( 1.0 - top - bottom));
      gPad->RangeAxis(0, 0, 1, 1);
   }

}


//______________________________________________________________________________
void TASImage::SavePrimitive(ostream &out, Option_t * /*= ""*/)
{
    // Save a primitive as a C++ statement(s) on output stream "out".

   char *buf;
   int sz;

   UInt_t w = GetWidth();
   UInt_t h = GetHeight();

   if (w > 500) { // workarond CINT limitations
      w = 500;
      Double_t scale = 500./GetWidth();
      h = TMath::Nint(GetHeight()*scale);
      Scale(w, h);
   }

   GetImageBuffer(&buf, &sz, TImage::kXpm);

   TString name = GetName();
   name.ReplaceAll(".", "_");
   TString str = buf;
   static int ii = 0;
   ii++;

   str.ReplaceAll("static", "");
   TString xpm = "xpm_";
   xpm += name;
   xpm += ii;
   str.ReplaceAll("asxpm", xpm.Data());
   out << endl << str << endl << endl;
   out << "   TImage *";
   out << name << " = TImage::Create();" << endl;
   out << "   " << name << "->SetImageBuffer(" << xpm << ", TImage::kXpm);" << endl;
   out << "   " << name << "->Draw();" << endl;
}


//______________________________________________________________________________
Bool_t TASImage::SetJpegDpi(const char *name, UInt_t set)
{
   // Set an image printing resolution in Dots Per Inch units.
   // name - the name of jpeg file.
   // set - dpi resolution.
   // Returns kFALSE in case of error.

   static char buf[32];
   FILE *fp = fopen(name, "rb+");

   if (!fp) {
      printf("file %s : failed to open\n", name);
      return kFALSE;
   }

   if (!fread(buf, 1, 20, fp)) {
      fclose(fp);
      return kFALSE;
   }

   char dpi1 = (set & 0xffff) >> 8;
   char dpi2 = set & 0xff;

   int i = 0;

   int dpi = 0; // start of dpi data
   for (i = 0; i < 20; i++) {
      if ((buf[i] == 0x4a) && (buf[i+1] == 0x46) &&  (buf[i+2] == 0x49) &&
          (buf[i+3] == 0x46) && (buf[i+4] == 0x00) ) {
         dpi = i + 7;
         break;
      }
   }

   if (i == 20 || dpi+4 >= 20) { // jpeg maker was not found
      fclose(fp);
      printf("file %s : wrong JPEG format\n", name);
      return kFALSE;
   }

   buf[dpi] = 1;   // format specified in  dots per inch

   // set x density in dpi units
   buf[dpi + 1] = dpi1;
   buf[dpi + 2] = dpi2;

   // set y density in dpi units
   buf[dpi + 3] = dpi1;
   buf[dpi + 4] = dpi2;

   rewind(fp);
   fwrite(buf, 1, 20, fp);
   fclose(fp);

   return kTRUE;
}
