The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
/*
 * Icon.c -- Implementation of Icon item.
 *
 * Authors              : Patrick LECOANET
 * Creation date        : Sat Mar 25 13:53:39 1995
 */

/*
 *  Copyright (c) 1993 - 2005 CENA, Patrick Lecoanet --
 *
 * See the file "Copyright" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */


#include "Item.h"
#include "Geo.h"
#include "Draw.h"
#include "Types.h"
#include "Image.h"
#include "WidgetInfo.h"
#include "tkZinc.h"


static const char rcsid[] = "$Id: Icon.c,v 1.45 2005/05/10 07:59:48 lecoanet Exp $";
static const char compile_id[] = "$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $";


/*
 **********************************************************************************
 *
 * Specific Icon item record
 *
 **********************************************************************************
 */
typedef struct _IconItemStruct {
  ZnItemStruct  header;

  /* Public data */
  ZnPoint       pos;
  ZnImage       image;
  Tk_Anchor     anchor;
  Tk_Anchor     connection_anchor;
  ZnGradient    *color; /* Used only if the image is a bitmap (in GL alpha part
                         * is always meaningful). */
  
  /* Private data */
  ZnPoint       dev[4];
} IconItemStruct, *IconItem;


static ZnAttrConfig     icon_attrs[] = {
  { ZN_CONFIG_ANCHOR, "-anchor", NULL,
    Tk_Offset(IconItemStruct, anchor), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_GRADIENT, "-color", NULL,
    Tk_Offset(IconItemStruct, color), 0, ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-composealpha", NULL,
    Tk_Offset(IconItemStruct, header.flags), ZN_COMPOSE_ALPHA_BIT,
    ZN_DRAW_FLAG, False },
  { ZN_CONFIG_BOOL, "-composerotation", NULL,
    Tk_Offset(IconItemStruct, header.flags), ZN_COMPOSE_ROTATION_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BOOL, "-composescale", NULL,
    Tk_Offset(IconItemStruct, header.flags), ZN_COMPOSE_SCALE_BIT,
    ZN_COORDS_FLAG, False },
  { ZN_CONFIG_ITEM, "-connecteditem", NULL,
    Tk_Offset(IconItemStruct, header.connected_item), 0,
    ZN_COORDS_FLAG|ZN_ITEM_FLAG, False },
  { ZN_CONFIG_ANCHOR, "-connectionanchor", NULL,
    Tk_Offset(IconItemStruct, connection_anchor), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_IMAGE, "-image", NULL,
    Tk_Offset(IconItemStruct, image), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_BITMAP, "-mask", NULL,
    Tk_Offset(IconItemStruct, image), 0, ZN_COORDS_FLAG, False },
  { ZN_CONFIG_POINT, "-position", NULL, Tk_Offset(IconItemStruct, pos), 0,
    ZN_COORDS_FLAG, False},
  { ZN_CONFIG_PRI, "-priority", NULL,
    Tk_Offset(IconItemStruct, header.priority), 0,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG, False },
  { ZN_CONFIG_BOOL, "-sensitive", NULL,
    Tk_Offset(IconItemStruct, header.flags), ZN_SENSITIVE_BIT,
    ZN_REPICK_FLAG, False },
  { ZN_CONFIG_TAG_LIST, "-tags", NULL,
    Tk_Offset(IconItemStruct, header.tags), 0, 0, False },
  { ZN_CONFIG_BOOL, "-visible", NULL,
    Tk_Offset(IconItemStruct, header.flags), ZN_VISIBLE_BIT,
    ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False },
  
  { ZN_CONFIG_END, NULL, NULL, 0, 0, 0, False }
};



/*
 **********************************************************************************
 *
 * Init --
 *
 **********************************************************************************
 */
static int
Init(ZnItem             item,
     int                *argc,
     Tcl_Obj *CONST     *args[])
{
  ZnWInfo       *wi = item->wi;
  IconItem      icon = (IconItem) item;

  /*printf("size of an icon(header) = %d(%d)\n",
    sizeof(IconItemStruct), sizeof(ZnItemStruct));*/

  /* Init attributes */
  SET(item->flags, ZN_VISIBLE_BIT);
  SET(item->flags, ZN_SENSITIVE_BIT);
  SET(item->flags, ZN_COMPOSE_ALPHA_BIT);
  SET(item->flags, ZN_COMPOSE_ROTATION_BIT);
  SET(item->flags, ZN_COMPOSE_SCALE_BIT);
  item->priority = 1;

  icon->pos.x = icon->pos.y = 0.0;
  icon->image = ZnUnspecifiedImage;
  icon->anchor = TK_ANCHOR_NW;
  icon->connection_anchor = TK_ANCHOR_SW;
  icon->color = ZnGetGradientByValue(wi->fore_color);
  
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Clone --
 *
 **********************************************************************************
 */
static void
Clone(ZnItem    item)
{
  IconItem      icon = (IconItem) item;
  
  if (icon->image != ZnUnspecifiedImage) {
    icon->image = ZnGetImageByValue(icon->image, ZnUpdateItemImage, item);
  }
  icon->color = ZnGetGradientByValue(icon->color);
}


/*
 **********************************************************************************
 *
 * Destroy --
 *
 **********************************************************************************
 */
static void
Destroy(ZnItem  item)
{
  IconItem      icon = (IconItem) item;

  if (icon->image != ZnUnspecifiedImage) {
    ZnFreeImage(icon->image, ZnUpdateItemImage, item);
    icon->image = ZnUnspecifiedImage;
  }
  ZnFreeGradient(icon->color);
}


/*
 **********************************************************************************
 *
 * Configure --
 *
 **********************************************************************************
 */
static int
Configure(ZnItem        item,
          int           argc,
          Tcl_Obj *CONST argv[],
          int           *flags)
{
  ZnItem        old_connected;

  old_connected = item->connected_item;
  if (ZnConfigureAttributes(item->wi, item, item, icon_attrs,
                            argc, argv, flags) == TCL_ERROR) {
    return TCL_ERROR;
  }  

  if (ISSET(*flags, ZN_ITEM_FLAG)) {
    /*
     * If the new connected item is not appropriate back up
     * to the old one.
     */
    if ((item->connected_item == ZN_NO_ITEM) ||
        (ISSET(item->connected_item->class->flags, ZN_CLASS_HAS_ANCHORS) &&
         (item->parent == item->connected_item->parent))) {
      ZnITEM.UpdateItemDependency(item, old_connected);
    }
    else {
      item->connected_item = old_connected;
    }
  }  

  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Query --
 *
 **********************************************************************************
 */
static int
Query(ZnItem            item,
      int               argc,
      Tcl_Obj *CONST    argv[])
{
  if (ZnQueryAttribute(item->wi->interp, item, icon_attrs, argv[0]) == TCL_ERROR) {
    return TCL_ERROR;
  }  

  return TCL_OK;
}


/*
 * Compute the transformation to be used and the origin
 * of the icon (upper left point in item coordinates).
 */
static ZnTransfo *
ComputeTransfoAndOrigin(ZnItem    item,
                        ZnPoint   *origin)
{
  IconItem icon = (IconItem) item;
  int       w, h;
  ZnTransfo *t;

  ZnSizeOfImage(icon->image, &w, &h);

  /*
   * The connected item support anchors, this is checked by configure.
   */
  if (item->connected_item != ZN_NO_ITEM) {
    ZnTransfo inv;

    item->connected_item->class->GetAnchor(item->connected_item,
                                           icon->connection_anchor,
                                           origin);

    /* GetAnchor return a position in device coordinates not in
     * the item coordinate space. To compute the icon origin
     * (upper left corner), we must apply the inverse transform
     * to the ref point before calling anchor2origin.
     */
    ZnTransfoInvert(item->transfo, &inv);
    ZnTransformPoint(&inv, origin, origin);
    /*
     * The relevant transform in case of an attachment is the item
     * transform alone. This is case of local coordinate space where
     * only the translation is a function of the whole transform
     * stack, scale and rotation are reset.
     */
    t = item->transfo;
  }
  else {
    origin->x = origin->y = 0;
    t = item->wi->current_transfo;
  }

  ZnAnchor2Origin(origin, (ZnReal) w, (ZnReal) h, icon->anchor, origin);
  //origin->x = ZnNearestInt(origin->x);
  //origin->y = ZnNearestInt(origin->y);

  return t;
}

/*
 **********************************************************************************
 *
 * ComputeCoordinates --
 *
 **********************************************************************************
 */
static void
ComputeCoordinates(ZnItem       item,
                   ZnBool       force)
{
  //ZnWInfo   *wi = item->wi;
  IconItem  icon = (IconItem) item;
  int       width, height, i;
  ZnPoint   quad[4];
  ZnTransfo *t;
  
  ZnResetBBox(&item->item_bounding_box);

  /*
   * If there is no image then nothing to show.
   */
  if (icon->image == ZnUnspecifiedImage) {
    return;
  }
    
  ZnSizeOfImage(icon->image, &width, &height);
  t = ComputeTransfoAndOrigin(item, quad);

  quad[1].x = quad[0].x;
  quad[1].y = quad[0].y + height;
  quad[2].x = quad[0].x + width;
  quad[2].y = quad[1].y;
  quad[3].x = quad[2].x;
  quad[3].y = quad[0].y;
  ZnTransformPoints(t, quad, icon->dev, 4);
  
  for (i = 0; i < 4; i++) {
    icon->dev[i].x = ZnNearestInt(icon->dev[i].x);
    icon->dev[i].y = ZnNearestInt(icon->dev[i].y);
  }
  
  /*
   * Compute the bounding box.
   */
  ZnAddPointsToBBox(&item->item_bounding_box, icon->dev, 4);

  /*
   * Update connected items.
   */
  SET(item->flags, ZN_UPDATE_DEPENDENT_BIT);
}


/*
 **********************************************************************************
 *
 * ToArea --
 *      Tell if the object is entirely outside (-1),
 *      entirely inside (1) or in between (0).
 *
 **********************************************************************************
 */
static int
ToArea(ZnItem   item,
       ZnToArea ta)
{
  IconItem      icon = (IconItem) item;
  
  if (icon->image == ZnUnspecifiedImage) {
    return -1;
  }

  return ZnPolygonInBBox(icon->dev, 4, ta->area, NULL);
}


/*
 **********************************************************************************
 *
 * Draw --
 *
 **********************************************************************************
 */
static void
Draw(ZnItem     item)
{
  ZnWInfo       *wi = item->wi;
  IconItem      icon = (IconItem) item;
  XGCValues     values;
  unsigned int  gc_mask = 0;
  int           w, h;
  ZnBBox        box, inter, *clip_box;
  TkRegion      clip_region, photo_region, clip;
  ZnBool        simple;
  Pixmap        pixmap;
  
  if (icon->image == ZnUnspecifiedImage) {
    return;
  }

  ZnSizeOfImage(icon->image, &w, &h);
  box.orig = *icon->dev;
  box.corner.x = icon->dev->x + w;
  box.corner.y = icon->dev->y + h;
  if (!ZnImageIsBitmap(icon->image)) {
    if (ZnTransfoIsTranslation(item->wi->current_transfo)) {
      /*
       * The code below does not use of Tk_RedrawImage to be
       * able to clip with the current clip region.
       */
      ZnIntersectBBox(&box, &wi->damaged_area, &inter);
      box = inter;
      ZnCurrentClip(wi, &clip_region, NULL, NULL);
      pixmap = ZnImagePixmap(icon->image, wi->win);
      photo_region = ZnImageRegion(icon->image);
      clip = TkCreateRegion();
      /*
       * ZnImageRegion may fail: perl/Tk 800.24 doesn't support
       * some internal TkPhoto functions.
       * This is a workaround using a rectangular region based
       * on the image size.
       */
      if (photo_region == NULL) {
        XRectangle rect;
        rect.x = rect.y = 0;
        rect.width = w;
        rect.height = h;
        TkUnionRectWithRegion(&rect, clip, clip);
      }
      else {
        ZnUnionRegion(clip, photo_region, clip);
      }
      ZnOffsetRegion(clip, (int) icon->dev->x, (int) icon->dev->y);
      TkIntersectRegion(clip_region, clip, clip);
      TkSetRegion(wi->dpy, wi->gc, clip);
      XCopyArea(wi->dpy, pixmap, wi->draw_buffer, wi->gc,
                (int) (box.orig.x-icon->dev->x),
                (int) (box.orig.y-icon->dev->y),
                (unsigned int) (box.corner.x-box.orig.x),
                (unsigned int) (box.corner.y-box.orig.y),
                (int) box.orig.x,
                (int) box.orig.y);
      values.clip_x_origin = values.clip_y_origin = 0;
      XChangeGC(wi->dpy, wi->gc, GCClipXOrigin|GCClipYOrigin, &values);
      TkSetRegion(wi->dpy, wi->gc, clip_region);
      TkDestroyRegion(clip);
    }
    else {
      ZnPoint       box[4];
      int           i;
      XImage        *dest_im, *src_im;
      XImage        *dest_mask, *src_mask;
      Pixmap        drw, mask;
      unsigned int  dest_im_width, dest_im_height;
      unsigned int  max_width, max_height;
      GC            gc, mask_gc;
      TkRegion      current_clip;
      ZnBBox        *current_clip_box;

      dest_im_width = (unsigned int) (item->item_bounding_box.corner.x -
                                      item->item_bounding_box.orig.x);
      max_width = MAX(dest_im_width, (unsigned int) w);
      dest_im_height = (unsigned int) (item->item_bounding_box.corner.y -
                                       item->item_bounding_box.orig.y);
      max_height = MAX(dest_im_height, (unsigned int) h);

      mask = Tk_GetPixmap(wi->dpy, wi->draw_buffer, max_width, max_height, 1);

      drw = Tk_GetPixmap(wi->dpy, wi->draw_buffer, max_width, max_height,
                         Tk_Depth(wi->win));
      mask_gc = XCreateGC(wi->dpy, mask, 0, NULL);
      gc = XCreateGC(wi->dpy, drw, 0, NULL);
      dest_mask = XCreateImage(wi->dpy, Tk_Visual(wi->win), 1,
                             XYPixmap, 0, NULL, dest_im_width, dest_im_height,
                             8, 0);
      dest_mask->data = ZnMalloc(dest_mask->bytes_per_line * dest_mask->height);
      memset(dest_mask->data, 0, dest_mask->bytes_per_line * dest_mask->height);
      XSetForeground(wi->dpy, mask_gc, 0);
      XFillRectangle(wi->dpy, mask, mask_gc, 0, 0, max_width, max_height);
      dest_im = XCreateImage(wi->dpy, Tk_Visual(wi->win), Tk_Depth(wi->win),
                             ZPixmap, 0, NULL, dest_im_width, dest_im_height,
                             32, 0);
      dest_im->data = ZnMalloc(dest_im->bytes_per_line * dest_im->height);
      memset(dest_im->data, 0, dest_im->bytes_per_line * dest_im->height);

      pixmap = ZnImagePixmap(icon->image, wi->win);
      photo_region = ZnImageRegion(icon->image);
      clip = TkCreateRegion();
      /*
       * ZnImageRegion may fail: perl/Tk 800.24 doesn't support
       * some internal TkPhoto functions.
       * This is a workaround using a rectangular region based
       * on the image size.
       */
      if (photo_region == NULL) {
        XRectangle rect;
        rect.x = rect.y = 0;
        rect.width = w;
        rect.height = h;
        TkUnionRectWithRegion(&rect, clip, clip);
      }
      else {
        ZnUnionRegion(clip, photo_region, clip);
      }
      XSetForeground(wi->dpy, mask_gc, 1);
      TkSetRegion(wi->dpy, mask_gc, clip);
      XFillRectangle(wi->dpy, mask, mask_gc, 0, 0, w, h);
  
      src_mask = XGetImage(wi->dpy, mask, 0, 0, w, h, 1, XYPixmap);
      src_im = XGetImage(wi->dpy, pixmap, 0, 0, w, h, ~0L, ZPixmap);

      box[0] = icon->dev[0];
      box[1] = icon->dev[1];
      box[2] = icon->dev[3];
      box[3] = icon->dev[2];
      for (i = 0; i < 4; i++) {
        box[i].x -= item->item_bounding_box.orig.x;
        box[i].y -= item->item_bounding_box.orig.y;
        box[i].x = ZnNearestInt(box[i].x);
        box[i].y = ZnNearestInt(box[i].y);
      }

      ZnMapImage(src_mask, dest_mask, box);
      ZnMapImage(src_im, dest_im, box);

      ZnCurrentClip(wi, &current_clip, &current_clip_box, NULL);
      TkSetRegion(wi->dpy, mask_gc, current_clip);
      XSetClipOrigin(wi->dpy, mask_gc,
                     (int) -item->item_bounding_box.orig.x, (int) -item->item_bounding_box.orig.y);
      TkPutImage(NULL, 0,wi->dpy, mask, mask_gc, dest_mask,
                 0, 0, 0, 0, dest_im_width, dest_im_height);
      TkPutImage(NULL, 0, wi->dpy, drw, gc, dest_im,
                0, 0, 0, 0, dest_im_width, dest_im_height);

      XSetClipMask(wi->dpy, gc, mask);
      XSetClipOrigin(wi->dpy, gc,
                     (int) item->item_bounding_box.orig.x,
                     (int) item->item_bounding_box.orig.y);
      XCopyArea(wi->dpy, drw, wi->draw_buffer, gc,
                0, 0, dest_im_width, dest_im_height,
                (int) item->item_bounding_box.orig.x,
                (int) item->item_bounding_box.orig.y);

      XFreeGC(wi->dpy, gc);
      XFreeGC(wi->dpy, mask_gc);
      Tk_FreePixmap(wi->dpy, drw);
      Tk_FreePixmap(wi->dpy, mask);
      XDestroyImage(src_mask);
      XDestroyImage(dest_mask);
      XDestroyImage(src_im);
      XDestroyImage(dest_im);
    }
  }
  else {
    /*
     * If the current transform is a pure translation, it is
     * possible to optimize by directly drawing to the X back
     * buffer. Else, we draw in a temporary buffer, get
     * its content as an image, transform the image into another
     * one and use this last image as a mask to draw in the X
     * back buffer.
     */
    pixmap = ZnImagePixmap(icon->image, wi->win);
    if (ZnTransfoIsTranslation(item->wi->current_transfo)) {
      ZnCurrentClip(wi, NULL, &clip_box, &simple);
      if (simple) {
        ZnIntersectBBox(&box, clip_box, &inter);
        box = inter;
      }
      values.fill_style = FillStippled;
      values.stipple = pixmap;
      values.ts_x_origin = (int) icon->dev->x;
      values.ts_y_origin = (int) icon->dev->y;
      values.foreground = ZnGetGradientPixel(icon->color, 0.0);
      gc_mask |= GCFillStyle|GCStipple|GCTileStipXOrigin|GCTileStipYOrigin|GCForeground;
      XChangeGC(wi->dpy, wi->gc, gc_mask, &values);
      XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc,
                     (int) box.orig.x,
                     (int) box.orig.y,
                     (unsigned int) (box.corner.x-box.orig.x),
                     (unsigned int) (box.corner.y-box.orig.y));
    }
    else {
      ZnPoint       box[4];
      int           i;
      XImage        *dest_im, *src_im;
      Pixmap        drw;
      unsigned int  dest_im_width, dest_im_height;
      unsigned int  max_width, max_height;
      GC            gc;

      dest_im_width = (unsigned int) (item->item_bounding_box.corner.x -
                                      item->item_bounding_box.orig.x);
      max_width = MAX(dest_im_width, (unsigned int) w);
      dest_im_height = (unsigned int) (item->item_bounding_box.corner.y -
                                       item->item_bounding_box.orig.y);
      max_height = MAX(dest_im_height, (unsigned int) h);
      
      drw = Tk_GetPixmap(wi->dpy, wi->draw_buffer, max_width, max_height, 1);
      gc = XCreateGC(wi->dpy, drw, 0, NULL);
      XSetForeground(wi->dpy, gc, 0);
      XFillRectangle(wi->dpy, drw, gc, 0, 0, max_width, max_height);
      dest_im = XCreateImage(wi->dpy, Tk_Visual(wi->win), 1,
                             XYPixmap, 0, NULL, dest_im_width, dest_im_height,
                             8, 0);
      dest_im->data = ZnMalloc(dest_im->bytes_per_line * dest_im->height);
      memset(dest_im->data, 0, dest_im->bytes_per_line * dest_im->height);

      values.fill_style = FillStippled;
      values.stipple = pixmap;
      values.ts_x_origin = 0;
      values.ts_y_origin = 0;
      values.foreground = 1;
      gc_mask |= GCFillStyle|GCStipple|GCTileStipXOrigin|GCTileStipYOrigin|GCForeground;
      XChangeGC(wi->dpy, gc, gc_mask, &values);
      XFillRectangle(wi->dpy, drw, gc, 0, 0, w, h);

      src_im = XGetImage(wi->dpy, drw, 0, 0, w, h, 1, XYPixmap);

      box[0] = icon->dev[0];
      box[1] = icon->dev[1];
      box[2] = icon->dev[3];
      box[3] = icon->dev[2];
      for (i = 0; i < 4; i++) {
        box[i].x -= item->item_bounding_box.orig.x;
        box[i].y -= item->item_bounding_box.orig.y;
        box[i].x = ZnNearestInt(box[i].x);
        box[i].y = ZnNearestInt(box[i].y);
      }

      ZnMapImage(src_im, dest_im, box);

      TkPutImage(NULL, 0,wi->dpy, drw, gc, dest_im,
                 0, 0, 0, 0, dest_im_width, dest_im_height);

      values.foreground = ZnGetGradientPixel(icon->color, 0.0);
      values.stipple = drw;
      values.ts_x_origin = (int) item->item_bounding_box.orig.x;
      values.ts_y_origin = (int) item->item_bounding_box.orig.y;
      values.fill_style = FillStippled;
      XChangeGC(wi->dpy, wi->gc,
                GCFillStyle|GCStipple|GCTileStipXOrigin|GCTileStipYOrigin|GCForeground,
                &values);
      XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc,
                     (int) item->item_bounding_box.orig.x,
                     (int) item->item_bounding_box.orig.y,
                     (int) dest_im_width, (int) dest_im_height);

      XFreeGC(wi->dpy, gc);
      Tk_FreePixmap(wi->dpy, drw);
      XDestroyImage(src_im);
      XDestroyImage(dest_im);
    }
  }
}


/*
 **********************************************************************************
 *
 * Render --
 *
 **********************************************************************************
 */
#ifdef GL
static void
Render(ZnItem   item)
{
  ZnWInfo       *wi = item->wi;
  IconItem      icon = (IconItem) item;
  
  if (icon->image != ZnUnspecifiedImage) {
    ZnRenderImage(wi, icon->image, icon->color, icon->dev,
                  ZnImageIsBitmap(icon->image));
  }
}
#else
static void
Render(ZnItem   item)
{
}
#endif


/*
 **********************************************************************************
 *
 * IsSensitive --
 *
 **********************************************************************************
 */
static ZnBool
IsSensitive(ZnItem      item,
            int         item_part)
{
  return (ISSET(item->flags, ZN_SENSITIVE_BIT) &&
          item->parent->class->IsSensitive(item->parent, ZN_NO_PART));
}


/*
 **********************************************************************************
 *
 * Pick --
 *
 **********************************************************************************
 */
static double
Pick(ZnItem     item,
     ZnPick     ps)
{
  IconItem      icon = (IconItem) item;
  ZnWInfo       *wi = item->wi;
  double        dist;
  double        off_dist = MAX(1, wi->pick_aperture+1);
  int           x, y, width, height;
  ZnPoint       p;
  ZnBBox        bbox;
  ZnTransfo     t;

  if (icon->image == ZnUnspecifiedImage) {
    return 1.0e40;
  }

  ZnTransfoInvert(wi->current_transfo, &t);
  ZnTransformPoint(&t, ps->point, &p);
  ZnTransformPoint(&t, &icon->dev[0], &bbox.orig);
  ZnSizeOfImage(icon->image, &width, &height);
  bbox.corner.x = bbox.orig.x + width;
  bbox.corner.y = bbox.orig.y + height;
  dist = ZnRectangleToPointDist(&bbox, &p);
  x = (int) (p.x - bbox.orig.x);
  y = (int) (p.y - bbox.orig.y);
  /*printf("dist: %g\n", dist);*/

  /*
   * If inside the icon rectangle, try to see if the point
   * is actually on the image or not. If it lies in an
   * area that is between pick_aperture+1 around the external
   * rectangle and the actual shape, the distance will be reported
   * as pick_aperture+1. Inside the actual shape it will be
   * reported as 0. This is a kludge, there is currently
   * no means to compute the real distance in the icon's
   * vicinity.
   */
  if (dist <= 0) {
    dist = 0.0;
    if (icon->image != ZnUnspecifiedImage) {
      if (ZnPointInImage(icon->image, x, y)) {
        /*
         * The point is actually on the image shape.
         */
        return dist;
      }
      else {
        /*
         * The point is not on the shape but still
         * inside the image's bounding box.
         */
        return off_dist;
      }
    }
    else {
      return dist;
    }
  }
  else if (dist < off_dist) {
    dist = off_dist;
  }

  return dist;
}


/*
 **********************************************************************************
 *
 * PostScript --
 *
 **********************************************************************************
 */
static int
PostScript(ZnItem item,
           ZnBool prepass,
           ZnBBox *area)
{
  ZnWInfo       *wi = item->wi;
  IconItem      icon = (IconItem) item;
  int           w, h, result;
  ZnPoint       origin;
  char          path[500];

  if (prepass || (icon->image == ZnUnspecifiedImage)) {
    return TCL_OK;
  }
  
  ZnSizeOfImage(icon->image, &w, &h);

  ComputeTransfoAndOrigin(item, &origin);
  
  sprintf(path, "/InitialTransform load setmatrix\n"
          "[%.15g %.15g %.15g %.15g %.15g %.15g] concat\n"
          "1 -1 scale\n"
          "%.15g %.15g translate\n",
          wi->current_transfo->_[0][0], wi->current_transfo->_[0][1], 
          wi->current_transfo->_[1][0], wi->current_transfo->_[1][1], 
          wi->current_transfo->_[2][0], wi->current_transfo->_[2][1],
          origin.x, origin.y - h);
  Tcl_AppendResult(wi->interp, path, NULL);
  
  if (ZnImageIsBitmap(icon->image)) {
    if (Tk_PostscriptColor(wi->interp, wi->ps_info,
                           ZnGetGradientColor(icon->color, 0.0, NULL)) != TCL_OK) {
      return TCL_ERROR;
    }
    result = ZnPostscriptBitmap(wi->interp, wi->win, wi->ps_info,
                                icon->image, 0, 0, w, h);
  }
  else {
    result = Tk_PostscriptImage(ZnImageTkImage(icon->image), wi->interp, wi->win,
                                wi->ps_info, 0, 0, w, h, prepass);
  }
  
  return result;
}


/*
 **********************************************************************************
 *
 * GetAnchor --
 *
 **********************************************************************************
 */
static void
GetAnchor(ZnItem        item,
          Tk_Anchor     anchor,
          ZnPoint       *p)
{
  IconItem      icon = (IconItem) item;
  
  if (icon->image == ZnUnspecifiedImage) {
    *p = *icon->dev;
  }
  else {
    ZnPoint q[4];
    q[0] = icon->dev[0];
    q[1] = icon->dev[1];
    q[2] = icon->dev[3];
    q[3] = icon->dev[2];
    ZnRectOrigin2Anchor(q, anchor, p);
  }
}


/*
 **********************************************************************************
 *
 * GetClipVertices --
 *      Get the clipping shape.
 *      Never ever call ZnTriFree on the tristrip returned by GetClipVertices.
 *
 **********************************************************************************
 */
static ZnBool
GetClipVertices(ZnItem          item,
                ZnTriStrip      *tristrip)
{
  IconItem      icon = (IconItem) item;
  ZnPoint       *points;
  
  ZnListAssertSize(ZnWorkPoints, 4);
  points = ZnListArray(ZnWorkPoints);
  points[0] = icon->dev[0];
  points[1] = icon->dev[1];
  points[2] = icon->dev[3];
  points[3] = icon->dev[2];
  ZnTriStrip1(tristrip, points, 4, False);
  
  return False;
}


/*
 **********************************************************************************
 *
 * Coords --
 *      Return or edit the item origin. This doesn't take care of
 *      the possible attachment. The change will be effective at the
 *      end of the attachment.
 *
 **********************************************************************************
 */
static int
Coords(ZnItem           item,
       int              contour,
       int              index,
       int              cmd,
       ZnPoint          **pts,
       char             **controls,
       unsigned int     *num_pts)
{
  IconItem      icon = (IconItem) item;
  
  if ((cmd == ZN_COORDS_ADD) || (cmd == ZN_COORDS_ADD_LAST) || (cmd == ZN_COORDS_REMOVE)) {
    Tcl_AppendResult(item->wi->interp,
                     " icons can't add or remove vertices", NULL);
    return TCL_ERROR;
  }
  else if ((cmd == ZN_COORDS_REPLACE) || (cmd == ZN_COORDS_REPLACE_ALL)) {
    if (*num_pts == 0) {
      Tcl_AppendResult(item->wi->interp,
                       " coords command need 1 point on icons", NULL);
      return TCL_ERROR;
    }
    icon->pos = (*pts)[0];
    ZnITEM.Invalidate(item, ZN_COORDS_FLAG);
  }
  else if ((cmd == ZN_COORDS_READ) || (cmd == ZN_COORDS_READ_ALL)) {
    *num_pts = 1;
    *pts = &icon->pos;
  }
  return TCL_OK;
}


/*
 **********************************************************************************
 *
 * Exported functions struct --
 *
 **********************************************************************************
 */
static ZnItemClassStruct ICON_ITEM_CLASS = {
  "icon",
  sizeof(IconItemStruct),
  icon_attrs,
  0,                    /* num_parts */
  ZN_CLASS_HAS_ANCHORS|ZN_CLASS_ONE_COORD, /* flags */
  Tk_Offset(IconItemStruct, pos),
  Init,
  Clone,
  Destroy,
  Configure,
  Query,
  NULL,                 /* GetFieldSet */
  GetAnchor,
  GetClipVertices,
  NULL,                 /* GetContours */
  Coords,
  NULL,                 /* InsertChars */
  NULL,                 /* DeleteChars */
  NULL,                 /* Cursor */
  NULL,                 /* Index */
  NULL,                 /* Part */
  NULL,                 /* Selection */
  NULL,                 /* Contour */
  ComputeCoordinates,
  ToArea,
  Draw,
  Render,
  IsSensitive,
  Pick,
  NULL,                 /* PickVertex */
  PostScript
};

ZnItemClassId ZnIcon = (ZnItemClassId) &ICON_ITEM_CLASS;