/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/* -*- Mode: C; tab-width: 4 -*-
 *   if.c --- Top-level image library routines
 */

/*
#include "xp.h"
*/
#include "if.h"

#include "merrors.h"
#include "shist.h"
#include "xpgetstr.h"

#include "il_strm.h"            /* Stream converters. */

extern int XP_MSG_IMAGE_PIXELS;
extern int MK_UNABLE_TO_LOCATE_FILE;
extern int MK_OUT_OF_MEMORY;

#define HOWMANY(x, r)     (((x) + ((r) - 1)) / (r))
#define ROUNDUP(x, r)     (HOWMANY(x, r) * (r))

#ifdef PROFILE
#pragma profile on
#endif

int il_debug=0;

/* Global list of image group contexts. */
static IL_GroupContext *il_global_img_cx_list = NULL;


/*********************** Image Observer Notification. *************************
*
* These functions are used to send messages to registered observers of an
* individual image client i.e. an IL_ImageReq.
*
******************************************************************************/

/* Fabricate a descriptive title for the image and notify observers.  Used for
   calls to the image viewer. */
static void
il_description_notify(il_container *ic)
{
    char buf[36], buf2[12];
    IL_MessageData message_data;
    IL_ImageReq *image_req;
    NI_PixmapHeader *img_header = &ic->image->header;
    
    XP_BZERO(&message_data, sizeof(IL_MessageData));

    switch (ic->type) {
        /* XXX - This needs to be fixed for Image Plugins. */
    case IL_GIF : XP_STRCPY(buf2, "GIF"); break;
    case IL_XBM : XP_STRCPY(buf2, "XBM"); break;
    case IL_JPEG : XP_STRCPY(buf2, "JPEG"); break;
    case IL_PNG : XP_STRCPY(buf2, "PNG"); break;

    default : XP_STRCPY(buf2, "");
    }
    XP_SPRINTF(buf, XP_GetString(XP_MSG_IMAGE_PIXELS), buf2,
               img_header->width, img_header->height);

	XP_ASSERT(ic->clients);
	for (image_req = ic->clients; image_req; image_req = image_req->next) {
        if (image_req->is_view_image) {
            message_data.image_instance = image_req;
            message_data.description = buf; /* Note scope limitation.  Observer
                                               must copy if necessary. */
            XP_NotifyObservers(image_req->obs_list, IL_DESCRIPTION, &message_data);
        }
    }
}

/* Notify observers of the target dimensions of the image. */
static void
il_dimensions_notify(il_container *ic, int dest_width, int dest_height)
{
    IL_MessageData message_data;
    IL_ImageReq *image_req;
    
    XP_BZERO(&message_data, sizeof(IL_MessageData));

	XP_ASSERT(ic->clients);
    for (image_req = ic->clients; image_req; image_req = image_req->next) {
        message_data.image_instance = image_req;
        message_data.width = dest_width;   /* Note: these are stored as */
        message_data.height = dest_height; /* uint16s. */
        XP_NotifyObservers(image_req->obs_list, IL_DIMENSIONS, &message_data);
    }
}

/* Notify observers that the image is transparent and has a mask. */
static void
il_transparent_notify(il_container *ic)
{
    IL_MessageData message_data;
    IL_ImageReq *image_req;
    
    XP_BZERO(&message_data, sizeof(IL_MessageData));

	XP_ASSERT(ic->clients);
    for (image_req = ic->clients; image_req; image_req = image_req->next) {
        message_data.image_instance = image_req;
        XP_NotifyObservers(image_req->obs_list, IL_IS_TRANSPARENT,
                           &message_data);
    }
}

/* Notify observers that the image pixmap has been updated. */
void
il_pixmap_update_notify(il_container *ic)
{
    IL_MessageData message_data;
    IL_Rect *update_rect = &message_data.update_rect;
    IL_ImageReq *image_req;

    XP_BZERO(&message_data, sizeof(IL_MessageData));

    update_rect->x_origin = 0;
    update_rect->y_origin = (uint16)ic->update_start_row;
    update_rect->width = (uint16)ic->image->header.width;
    update_rect->height = (uint16)(ic->update_end_row-ic->update_start_row+1);

	XP_ASSERT(ic->clients);
    for(image_req = ic->clients; image_req; image_req = image_req->next) {
        /* The user aborted the image loading.  Don't display any more image
           data */
        if (image_req->stopped)
            continue;

        message_data.image_instance = image_req;
        XP_NotifyObservers(image_req->obs_list, IL_PIXMAP_UPDATE,
                           &message_data);
    }
}

/* Notify observers that the image has finished decoding. */
void
il_image_complete_notify(il_container *ic)
{
    IL_MessageData message_data;
    IL_ImageReq *image_req;
    
    XP_BZERO(&message_data, sizeof(IL_MessageData));

	XP_ASSERT(ic->clients);
    for (image_req = ic->clients; image_req; image_req = image_req->next) {
        message_data.image_instance = image_req;
        XP_NotifyObservers(image_req->obs_list, IL_IMAGE_COMPLETE,
                           &message_data);
    }
}

/* Notify observers that a frame of an image animation has finished
   decoding. */
void
il_frame_complete_notify(il_container *ic)
{
    IL_MessageData message_data;
    IL_ImageReq *image_req;
    
    XP_BZERO(&message_data, sizeof(IL_MessageData));

	XP_ASSERT(ic->clients);
    for (image_req = ic->clients; image_req; image_req = image_req->next) {
        message_data.image_instance = image_req;
        XP_NotifyObservers(image_req->obs_list, IL_FRAME_COMPLETE,
                           &message_data);
    }
}


/* Notify observers of image progress. */
void
il_progress_notify(il_container *ic)
{
    uint percent_done;
    static uint last_percent_done = 0;
    int row = ic->update_end_row;
    IL_MessageData message_data;
    IL_ImageReq *image_req;
    NI_PixmapHeader *img_header = &ic->image->header;
    
    XP_BZERO(&message_data, sizeof(IL_MessageData));

    /* No more progress bars for GIF animations after initial load. */
    if (ic->is_looping)
        return;

    /* Calculate the percentage of image decoded (not displayed) */
    if(ic->content_length) {
        percent_done =
            (uint32)100 * ic->bytes_consumed / ((uint32)ic->content_length);

    /* Some protocols, e.g. gopher, don't support content-length, so
     * show the percentage of the image displayed instead
     */
    } else {
        /* Could be zero if before il_size() is called. */
        if (img_header->height == 0)
            return;

        /* Interlaced GIFs are weird */
        if (ic->type == IL_GIF) {
            percent_done = il_gif_compute_percentage_complete(row, ic);
        }
        else
        {
            /* This isn't correct for progressive JPEGs, but there's
             * really nothing we can do about that because the number
             * of scans in a progressive JPEG isn't known until the
             * whole file has been read.
             */
            percent_done = (uint)(row * (uint32)100 / img_header->height);

        }
    }

    if (percent_done != last_percent_done) {
        XP_ASSERT(ic->clients);
        for (image_req = ic->clients; image_req; image_req = image_req->next) {
            if (image_req->is_view_image) { /* XXXM12N Eliminate this test? */
                message_data.image_instance = image_req;
                message_data.percent_progress = percent_done;
                
                XP_NotifyObservers(image_req->obs_list, IL_PROGRESS,
                                   &message_data);
            }
        }
        last_percent_done = percent_done;
    }
}


/* Notify observers of information pertaining to a cached image.  Note that
   this notification is per image request and not per image container. */
void
il_cache_return_notify(IL_ImageReq *image_req)
{
    IL_MessageData message_data;
    il_container *ic = image_req->ic;

    XP_BZERO(&message_data, sizeof(IL_MessageData));
    message_data.image_instance = image_req;

    /* This function should only be called if the dimensions are known. */
    XP_ASSERT(ic->state >= IC_SIZED);

    /* First notify observers of the image dimensions. */
    message_data.width = ic->dest_width;   /* Note: these are stored as */
    message_data.height = ic->dest_height; /* uint16s. */
    XP_NotifyObservers(image_req->obs_list, IL_DIMENSIONS, &message_data);
    message_data.width = message_data.height = 0;

    /* Fabricate a title for the image and notify observers.  Image Plugins
       will need to supply information on the image type. */
    il_description_notify(ic);

    /* If the image is transparent and has a mask, notify observers
       accordingly. */
    if (ic->mask)
        XP_NotifyObservers(image_req->obs_list, IL_IS_TRANSPARENT,
                           &message_data);

    /* Now send observers a pixmap update notification for the displayable
       area of the image. */
    XP_BCOPY(&ic->displayable_rect, &message_data.update_rect,
             sizeof(IL_Rect));
    XP_NotifyObservers(image_req->obs_list, IL_PIXMAP_UPDATE, &message_data);
    XP_BZERO(&message_data.update_rect, sizeof(IL_Rect));

    if (ic->state == IC_COMPLETE) {
        /* Send the observers a frame complete message. */
        XP_NotifyObservers(image_req->obs_list, IL_FRAME_COMPLETE,
                           &message_data);

        /* Finally send the observers a complete message. */
        XP_NotifyObservers(image_req->obs_list, IL_IMAGE_COMPLETE,
                           &message_data);
    }
}


/* Notify observers that the image request is being destroyed.  This
   provides an opportunity for observers to remove themselves from the
   observer callback list and to do any related cleanup. */
void
il_image_destroyed_notify(IL_ImageReq *image_req)
{
    IL_MessageData message_data;
    
    XP_BZERO(&message_data, sizeof(IL_MessageData));
    message_data.image_instance = image_req;
    XP_NotifyObservers(image_req->obs_list, IL_IMAGE_DESTROYED, &message_data);

    /* Now destroy the observer list itself. */
    XP_DisposeObserverList(image_req->obs_list);
}


/* Notify observers that an error has occured.  The observer should call
   IL_DisplaySubImage in order to display the icon.  Note that this function
   is called per image request and not per image. */
static void
il_icon_notify(IL_ImageReq *image_req, int icon_number,
               XP_ObservableMsg message)
{
    IL_GroupContext *img_cx = image_req->img_cx;
    int icon_width, icon_height;
    IL_MessageData message_data;

    XP_BZERO(&message_data, sizeof(IL_MessageData));
  
    /* Obtain the dimensions of the icon. */
    IMGCBIF_GetIconDimensions(img_cx->img_cb, img_cx->dpy_cx, &icon_width,
                              &icon_height, icon_number);

    /* Fill in the message data and notify observers. */
    message_data.image_instance = image_req;
    message_data.icon_number = icon_number;
    message_data.icon_width = icon_width;
    message_data.icon_height = icon_height;
    XP_NotifyObservers(image_req->obs_list, message, &message_data);
}

/********************* Image Group Observer Notification. *********************
*
* This function is used to send messages to registered observers of an image
* group i.e. an IL_GroupContext.
*
******************************************************************************/

/* Notify observers that images have started/stopped loading in the context,
   or started/stopped looping in the context. */
void
il_group_notify(IL_GroupContext *img_cx, XP_ObservableMsg message)
{
    IL_GroupMessageData message_data;
    
    XP_BZERO(&message_data, sizeof(IL_GroupMessageData));
    
    /* Fill in the message data and notify observers. */
    message_data.display_context = img_cx->dpy_cx;
    message_data.image_context = img_cx;
    XP_NotifyObservers(img_cx->obs_list, message, &message_data);
}


static int
il_init_scaling(il_container *ic)
{
    int scaled_width = ic->image->header.width;
        
    /* Allocate temporary scale space */
    if (scaled_width != ic->src_header->width)
	{
        if (ic->scalerow)
            XP_FREE(ic->scalerow);
        
        if (!(ic->scalerow = (unsigned char *)XP_ALLOC(scaled_width * 3)))
		{
            ILTRACE(0,("il: MEM scale row"));
            return MK_OUT_OF_MEMORY;
        }
    }
    return 0;
}

/* Allocate and initialize the destination image's transparent_pixel with
   the Image Library's preferred transparency color i.e. the background color
   passed into IL_GetImage.  The image decoder is encouraged to use this
   color, but may opt not to do so. */
int
il_init_image_transparent_pixel(il_container *ic)
{
    IL_IRGB *img_trans_pixel = ic->image->header.transparent_pixel;
    
    if (!img_trans_pixel) {
        img_trans_pixel = XP_NEW_ZAP(IL_IRGB);
        if (!img_trans_pixel)
            return FALSE;

        if (ic->background_color) {
            XP_MEMCPY(img_trans_pixel, ic->background_color, sizeof(IL_IRGB));
        }
        else {
            /* A mask will always be used if no background color was
               requested. */
        }
            
        ic->image->header.transparent_pixel = img_trans_pixel;
    }

    return TRUE;
}

/* Destroy the destination image's transparent pixel. */
void
il_destroy_image_transparent_pixel(il_container *ic)
{
    NI_PixmapHeader *img_header = &ic->image->header;

    if (img_header->transparent_pixel)
        XP_FREE(img_header->transparent_pixel);
    img_header->transparent_pixel = NULL;
}

/* Inform the Image Library of the source image's dimensions.  This function
   determines the size of the destination image, and calls the front end to
   allocate storage for the destination image's bits.  If the source image's
   transparent pixel is set, and a background color was not specified for this
   image request, then a mask will also be allocated for the destination
   image. */
int
il_size(il_container *ic)
{
    float aspect;
    int status;
    uint8 img_depth;
    uint32 src_width, src_height;
    int32 image_bytes, old_image_bytes;
    IL_GroupContext *img_cx = ic->img_cx;
    NI_PixmapHeader *src_header = ic->src_header; /* Source image header. */
    NI_PixmapHeader *img_header = &ic->image->header; /* Destination image
                                                         header. */
    
    /* Get the dimensions of the source image. */
    src_width = src_header->width;
    src_height = src_header->height;

	/* Ensure that the source image has a sane area. */
	if (!src_width || !src_height            ||
        (src_width > MAX_IMAGE_WIDTH)        ||
        (src_height > MAX_IMAGE_HEIGHT)) {            
		ILTRACE(1,("il: bad image size: %dx%d", src_width, src_height));
		return MK_IMAGE_LOSSAGE;
	}

	ic->state = IC_SIZED;
	if (ic->state == IC_MULTI)
		return 0;

    /* For now, we don't allow an image to change output size on the
     * fly, but we do allow the source image size to change, and thus
     * we may need to change the scaling factor to fit it into the
     * same size container on the display.
     */
    if (ic->sized) {
        status = il_init_scaling(ic);
        return status;
    }

    /* This must appear before il_init_img_header. */
    old_image_bytes = (int32)img_header->widthBytes * img_header->height;
    
    /* Initialize the dimensions of the destination image header structure
       to be the dimensions of the source image.  The Display Front End will
       reset these dimensions to be the target dimensions of the image if
       it does not handle scaling. */
    img_header->width = src_width;
    img_header->height = src_height;

    /* Determine the target dimensions of the image.  */
    aspect = (float)src_width/(float)src_height;
    if (!ic->dest_width && !ic->dest_height) {
        /* Both target dimensions were unspecified, so use the dimensions of
           the source image. */
        ic->dest_width  = src_width;
        ic->dest_height = src_height;
    }
    else if (ic->dest_width && ic->dest_height) {
        /* Both target dimensions were specified; determine if this causes
           aspect ratio distortion. */
        if (ic->dest_width * src_height != ic->dest_height * src_width)
            ic->aspect_distorted = PR_TRUE;
    }
    else if (ic->dest_width) {
        /* Only the target width was specified.  Determine the target height
           which preserves aspect ratio. */
        ic->dest_height = (int)((float)ic->dest_width / aspect + 0.5);
    }
    else {
        /* Only the target height was specified.  Determine the target width
           which preserves aspect ratio. */
        ic->dest_width  = (int)(aspect * (float)ic->dest_height + 0.5);
    }
    if (ic->dest_width == 0) ic->dest_width = 1;
    if (ic->dest_height == 0) ic->dest_height = 1;

    /* Determine if the image will be displayed at its natural size. */
    if (ic->dest_width == src_width && ic->dest_height == src_height)
        ic->natural_size = PR_TRUE;

    /* Check that the target image dimensions are reasonable. */
    if ((ic->dest_width > MAX_IMAGE_WIDTH)     ||
        (ic->dest_height > MAX_IMAGE_HEIGHT)) {
        return MK_IMAGE_LOSSAGE;
    }

	ILTRACE(2,("il: size %dx%d, scaled from %dx%d, ic = 0x%08x\n",
               ic->dest_width, ic->dest_height, src_width, src_height, ic));

    /* Determine the number of bytes per scan-line.  Image data must be
       quadlet aligned for optimizations. */
    img_depth = img_header->color_space->pixmap_depth;
    img_header->widthBytes = (img_header->width * img_depth + 7) / 8;
    img_header->widthBytes = ROUNDUP(img_header->widthBytes, 4);

    /* Create and initialize the mask pixmap structure, if required.  A mask
       is allocated only if the image is transparent and no background color
       was specified for this image request. */
    if (src_header->transparent_pixel && !ic->background_color) {
        if (!ic->mask) {
            NI_PixmapHeader *mask_header;
    
            if (!(ic->mask = XP_NEW_ZAP(IL_Pixmap))) {
                return MK_OUT_OF_MEMORY;
            }

            mask_header = &ic->mask->header;
            mask_header->color_space = IL_CreateGreyScaleColorSpace(1, 1);
            if (!mask_header->color_space)
                return MK_OUT_OF_MEMORY;
            mask_header->width = img_header->width;
            mask_header->height = img_header->height;
            mask_header->widthBytes = (mask_header->width + 7) / 8;

            /* Mask data must be quadlet aligned for optimizations */
            mask_header->widthBytes = ROUNDUP(mask_header->widthBytes, 4);
        }

        /* Notify observers that the image is transparent and has a mask. */
        il_transparent_notify(ic);
    }
    else {                      /* Mask not required. */

        /*  for png alpha*/
        if (ic->mask) {
                il_transparent_notify(ic);
                if(ic->background_color){
/*
                    src_header->transparent_pixel = ic->background_color;
                    img_header->transparent_pixel = ic->background_color;
               
                    XP_MEMCPY(img_trans_pixel, ic->background_color, sizeof(IL_IRGB)); 
*/
        }
/*
            il_destroy_pixmap(ic->img_cb, ic->mask);
            ic->mask = NULL;
*/
        }
        
    }

    ic->sized = 1;
    /* Notify observers of the target dimensions of the image. */
    il_dimensions_notify(ic, ic->dest_width, ic->dest_height);

    /* Fabricate a title for the image and notify observers.  Image Plugins
       will need to supply information on the image type. */
    il_description_notify(ic);


    /* If the display front-end doesn't support scaling, IMGCBIF_NewPixmap will
       set the image and mask dimensions to scaled_width and scaled_height. */
    IMGCBIF_NewPixmap(img_cx->img_cb, img_cx->dpy_cx, ic->dest_width,
                      ic->dest_height, ic->image, ic->mask);
    
	if (!ic->image->bits)
		return MK_OUT_OF_MEMORY;

	if (ic->mask && !ic->mask->bits)
		return MK_OUT_OF_MEMORY;

	/* Adjust the total cache byte count to reflect any departure from the
       original predicted byte count for this image. */
    image_bytes = (int32)img_header->widthBytes * img_header->height;
    if (image_bytes - old_image_bytes)
        il_adjust_cache_fullness(image_bytes - old_image_bytes);

    /* If we have a mask, initialize its bits. */
    if (ic->mask) {
        NI_PixmapHeader *mask_header = &ic->mask->header;
        uint32 mask_size = mask_header->widthBytes * mask_header->height;

        IMGCBIF_ControlPixmapBits(img_cx->img_cb, img_cx->dpy_cx, ic->mask,
                                  IL_LOCK_BITS);
                                
        memset (ic->mask->bits, ~0, mask_size);
        
        IMGCBIF_ControlPixmapBits(img_cx->img_cb, img_cx->dpy_cx, ic->mask,
                                  IL_UNLOCK_BITS);
    }
    
    if ((status = il_init_scaling(ic)) < 0)
        return status;


    /* XXXM12N Clean this up */
	/* Get memory for quantizing.  (required amount depends on image width) */
	if (img_header->color_space->type == NI_PseudoColor) {
		if (!il_init_quantize(ic)) {
			ILTRACE(0,("il: MEM il_init_quantize"));
			return MK_OUT_OF_MEMORY;
		}
	}

	return 0;
}


#ifdef XP_OS2
#define IL_SIZE_CHUNK   16384
#else
#define IL_SIZE_CHUNK	128
#endif
#ifdef XP_MAC
#    if GENERATINGPOWERPC
#        define IL_PREFERRED_CHUNK 8192
#        define IL_OFFSCREEN_CHUNK 128
#    else	/* normal mac */
#        define IL_PREFERRED_CHUNK 4096
#        define IL_OFFSCREEN_CHUNK 128
#    endif
#else /* !XP_MAC */
#ifdef XP_OS2
#    define IL_PREFERRED_CHUNK (12*1024)
#    define IL_OFFSCREEN_CHUNK ( 6*1024)
#else
#    define IL_PREFERRED_CHUNK  2048 
#    define IL_OFFSCREEN_CHUNK 128 
#endif
#endif

unsigned int
il_write_ready(NET_StreamClass *stream)
{
	il_container *ic = (il_container *)stream->data_object;
    uint request_size = 1;

    if (ic->write_ready)
        request_size = (*ic->write_ready)(ic);

    if (!request_size)
        return 0;

	/*
     * It could be that layout aborted image loading by calling IL_FreeImage
     * before the netlib finished transferring data.  Don't do anything.
     */
	if (ic->state == IC_ABORT_PENDING)
        return IL_OFFSCREEN_CHUNK;

	if (!ic->sized)
		/* A (small) default initial chunk */
		return IL_SIZE_CHUNK;

    return IL_PREFERRED_CHUNK;
}

/* Given the first few bytes of a stream, identify the image format */
static int
il_type(int suspected_type, const char *buf, int32 len)
{
	int i;

	if (len >= 4 && !strncmp(buf, "GIF8", 4)) 
	{
		return IL_GIF;
	}

  	/* for PNG */
	if (len >= 4 && ((unsigned char)buf[0]==0x89 &&
		             (unsigned char)buf[1]==0x50 &&
					 (unsigned char)buf[2]==0x4E &&
					 (unsigned char)buf[3]==0x47))
	{ 
		return IL_PNG;
	}


	/* JFIF files start with SOI APP0 but older files can start with SOI DQT
	 * so we test for SOI followed by any marker, i.e. FF D8 FF
	 * this will also work for SPIFF JPEG files if they appear in the future.
	 *
	 * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
     */
	if (len >= 3 &&
	   ((unsigned char)buf[0])==0xFF &&
	   ((unsigned char)buf[1])==0xD8 &&
	   ((unsigned char)buf[2])==0xFF)
	{
		return IL_JPEG;
	}

	/* no simple test for XBM vs, say, XPM so punt for now */
	if (len >= 8 && !strncmp(buf, "#define ", 8) ) 
	{
        /* Don't contradict the given type, since this ID isn't definitive */
        if ((suspected_type == IL_UNKNOWN) || (suspected_type == IL_XBM))
            return IL_XBM;
	}

	if (len < 35) 
	{
		ILTRACE(1,("il: too few bytes to determine type"));
		return suspected_type;
	}

	/* all the servers return different formats so root around */
	for (i=0; i<28; i++)
	{
		if (!strncmp(&buf[i], "Not Fou", 7))
			return IL_NOTFOUND;
	}
	
	return suspected_type;
}

/*
 *	determine what kind of image data we are dealing with
 */
int
IL_Type(const char *buf, int32 len)
{
    return il_type(IL_UNKNOWN, buf, len);
}

static int 
il_write(NET_StreamClass *stream, const unsigned char *str, int32 len)
{
    il_container *ic = (il_container *)stream->data_object;
	int err = 0;

    ILTRACE(4, ("il: write with %5d bytes for %s\n", len, ic->url_address));

    /*
     * Layout may have decided to abort this image in mid-stream,
     * but netlib doesn't know about it yet and keeps sending
     * us data.  Force the netlib to abort.
     */
    if (ic->state == IC_ABORT_PENDING)
        return -1;

    /* Has user hit the stop button ? */
    if (il_image_stopped(ic))
        return -1;

    ic->bytes_consumed += len;
    
	if (len)
		err = (*ic->write)(ic, (unsigned char *)str, len);

    /* Notify observers of image progress. */
    il_progress_notify(ic);

	if (err < 0)
		return err;
	else
		return len;
}


int
il_first_write(NET_StreamClass *stream, const unsigned char *str, int32 len)
{
	void *dobj=stream->data_object;
	il_container *ic = (il_container *)dobj;
	int (*init)(il_container *);

	XP_ASSERT(ic);
	XP_ASSERT(dobj == ic->stream->data_object);
	XP_ASSERT(ic->image);

    /* If URL redirection occurs, the url stored in the
    image container is the redirect url not the image file url.
    If the image is animated, the imglib will never match the
    file name in the cache unless you update ic->url_address.
    ic->fetch_url keeps the actual url for you.
     */	
    if((ic->url)&&(ic->url->address)){
	    ic->fetch_url = XP_STRDUP(ic->url->address);
    }
    else{
	if(ic->url_address) /* check needed because of mkicons.c */
        	ic->fetch_url = XP_STRDUP(ic->url_address);
	else
		ic->fetch_url = NULL;
    }
 
    /* Figure out the image type, possibly overriding the given MIME type */
    ic->type = il_type(ic->type, (const char*) str, len);
    ic->write_ready = NULL;

	/* Grab the URL's expiration date */
	if (ic->url)
	  ic->expires = ic->url->expires;

	switch (ic->type) 
	{
		case IL_GIF:           
			init = il_gif_init;
			ic->write = il_gif_write;
			ic->complete = il_gif_complete;
            		ic->write_ready = il_gif_write_ready;
            		ic->abort = il_gif_abort;
			break;

        case IL_XBM:
			init = il_xbm_init;
			ic->write = il_xbm_write;
			ic->abort = il_xbm_abort;
            ic->complete = il_xbm_complete;
			break;

		case IL_JPEG:
			init = il_jpeg_init;
			ic->write = il_jpeg_write;
			ic->abort = il_jpeg_abort;
            		ic->complete = il_jpeg_complete;
			break;

   		case IL_PNG:
			init = il_png_init;
			ic->write = il_png_write;
			ic->abort = il_png_abort;
            		ic->complete = il_png_complete;
			break;


		case IL_NOTFOUND:
			ILTRACE(1,("il: html image"));
			return MK_IMAGE_LOSSAGE;

		default: 
			ILTRACE(1,("il: ignoring unknown image type (%d)", ic->type));
			return MK_IMAGE_LOSSAGE;
	}

	if (!(*init)(ic))
	{
		ILTRACE(0,("il: image init failed"));
		return MK_OUT_OF_MEMORY;
	}

	ic->stream->put_block = (MKStreamWriteFunc)il_write;

	/* do first write */
	return ic->stream->put_block(stream, (const char*) str, len);
}

/* Called when a container is aborted by Netlib. */
static void
il_container_aborted(il_container *ic)
{
    IL_ImageReq *image_req;
    IL_GroupContext *img_cx;
    il_context_list *current;

    /* Keep track of the fact that this container was aborted by Netlib,
       and guard against multiple calls to this routine. */
    if (ic->is_aborted)
        return;
    ic->is_aborted = PR_TRUE;

    /* Notify image observers that the image was aborted. */
    for (image_req = ic->clients; image_req; image_req = image_req->next) {
        il_icon_notify(image_req, IL_IMAGE_DELAYED, IL_ABORTED);
    }

    /* Now handle image group observers. */
    for (current = ic->img_cx_list; current; current = current->next) {
        /* Increment the count of images in this context which were aborted
           by Netlib.  Observer notification takes place if there were
           previously no aborted images in this context. */
        img_cx = current->img_cx;
        if (!img_cx->num_aborted)
            il_group_notify(img_cx, IL_ABORTED_LOADING);
        img_cx->num_aborted++;

#ifdef DEBUG_GROUP_OBSERVER
        XP_TRACE(("1 img_cx=%x total=%d loading=%d looping=%d aborted=%d",
                  img_cx, img_cx->num_containers, img_cx->num_loading,
                  img_cx->num_looping, img_cx->num_aborted));
#endif /* DEBUG_GROUP_OBSERVER */
    }
}


static void
il_bad_container(il_container *ic)
{
      IL_ImageReq *image_req;

      ILTRACE(4,("il: bad container, sending icon"));
      if (ic->type == IL_NOTFOUND) {
          ic->state = IC_MISSING;
          for (image_req = ic->clients; image_req; image_req = image_req->next)
              il_icon_notify(image_req, IL_IMAGE_NOT_FOUND, IL_ERROR_NO_DATA);
      }
      else {
          if (ic->state == IC_INCOMPLETE) {
              il_container_aborted(ic);
          }
          else {
              for (image_req = ic->clients; image_req;
                   image_req = image_req->next)
                  il_icon_notify(image_req, IL_IMAGE_BAD_DATA,
                                 IL_ERROR_IMAGE_DATA_TRUNCATED);
          }
      }

      /* Image container gets deleted later. */
}

/* NET_GetURL completion callback. */
static void
il_netgeturldone (URL_Struct *urls, int status, OPAQUE_CONTEXT *cx)
{
	IL_ImageReq *image_req;
	il_container *ic = (il_container*)urls->fe_data;
	XP_ASSERT(ic);

	/* Record the fact that NetLib is done loading. */
	ic->is_url_loading = PR_FALSE;

    /*
     * It could be that layout aborted image loading by calling IL_DestroyImage
     * before the netlib finished transferring data.  If so, really do the
     * freeing of the data that was deferred there.
     */
	if (ic->state == IC_ABORT_PENDING) {
		il_delete_container(ic);
        NET_FreeURLStruct(urls);
        return;
    }
    
	if (status < 0) {
		ILTRACE(2,("il:net done ic=0x%08x, status=%d, ic->state=0x%02x\n",
                   ic, status, ic->state));

        /* Netlib detected failure before a stream was even created. */
		if (ic->state < IC_BAD)	{
			if (status == MK_OBJECT_NOT_IN_CACHE)
				ic->state = IC_NOCACHE;
			else if (status == MK_UNABLE_TO_LOCATE_FILE)
                ic->state = IC_MISSING;
            else {
                /* status is probably MK_INTERRUPTED */
				ic->state = IC_INCOMPLETE; /* try again on reload */
            }

			if (!ic->sized)	{
                if (status == MK_OBJECT_NOT_IN_CACHE) {
                    for (image_req = ic->clients; image_req;
                         image_req = image_req->next)
                        il_icon_notify(image_req, IL_IMAGE_DELAYED,
                                       IL_NOT_IN_CACHE); 
                }
                else if (status == MK_INTERRUPTED) {
                    il_container_aborted(ic);
                }
                else if (status == MK_UNABLE_TO_LOCATE_FILE) {
                    for (image_req = ic->clients; image_req;
                         image_req = image_req->next)
                        il_icon_notify(image_req, IL_IMAGE_NOT_FOUND,
                                       IL_ERROR_NO_DATA);
                }
                else {
                    for (image_req = ic->clients; image_req;
                         image_req = image_req->next)
                        il_icon_notify(image_req, IL_IMAGE_BAD_DATA,
                                       IL_ERROR_IMAGE_DATA_ILLEGAL);
                }
			}
        }

		/* for mac */
		if (status == MK_OUT_OF_MEMORY)
			NET_InterruptWindow(cx);
	}
	else {
        /* It is possible for NetLib to call the exit routine with a success status,
           even though the stream was never created (this can happen when fetching
           a mime image part which contains no image data.)  Show a missing image
           icon. */ 
	    if (ic->state < IC_STREAM) {
		    ic->state = IC_MISSING;
	        for (image_req = ic->clients; image_req; image_req = image_req->next)
                il_icon_notify(image_req, IL_IMAGE_NOT_FOUND, IL_ERROR_NO_DATA);
	    }
    }

	NET_FreeURLStruct(urls);
}


void
il_image_abort(il_container *ic)
{
    if (ic->abort)
        (*ic->abort)(ic); 

    /* Clear any pending timeouts */
    if (ic->row_output_timeout) {
        FE_ClearTimeout(ic->row_output_timeout);
        ic->row_output_timeout = NULL;
    }
}

void
il_stream_complete(NET_StreamClass *stream)
{
	void *data_object=stream->data_object;
	il_container *ic = (il_container *)data_object;

	XP_ASSERT(ic);
#ifdef XP_OS2_HACK 
	if (ic->stream) { /*DSR011696 - occasionally this goes NULL and causes 
						the assert to trap*/ 
		XP_ASSERT(data_object == ic->stream->data_object); 
	} 
#else 
    XP_ASSERT(data_object == ic->stream->data_object);
#endif /* !XP_OS2 */ 

    ILTRACE(1, ("il: complete: %d seconds for %s\n",
                XP_TIME() - ic->start_time, ic->url_address));

    ic->is_multipart = ic->stream->is_multipart;
    ic->stream = NULL;

	if (ic->complete)
        (*ic->complete)(ic);
    else
        il_image_complete(ic);
}


/* Called when the container has finished loading to update the number of
   actively loading image containers in each of the client contexts.
   Non-looping images are considered to have been loaded upon natural
   completion of network activity for the image.  Looping images, on the
   other hand, are deemed to have been loaded once the first iteration of
   the animation is complete. */
static void
il_container_loaded(il_container *ic)
{
    il_context_list *current;
    IL_GroupContext *img_cx;
    
    for (current = ic->img_cx_list; current; current = current->next) {
        /* Decrement the number of actively loading image containers in the
           client context. */
        img_cx = current->img_cx;
        img_cx->num_loading--;
        if (!img_cx->num_loading) {
            /* If this is the last image to have loaded in the group,
               notify observers that images stopped loading. */
            il_group_notify(img_cx, IL_FINISHED_LOADING);
        }

#ifdef DEBUG_GROUP_OBSERVER
        XP_TRACE(("2 img_cx=%x total=%d loading=%d looping=%d aborted=%d",
                  img_cx, img_cx->num_containers, img_cx->num_loading,
                  img_cx->num_looping, img_cx->num_aborted));
#endif /* DEBUG_GROUP_OBSERVER */
    }
}


/* Called when a container starts/stops looping to update the number of
   looping image containers in each of the client containers.  An animated
   image is considered to have started looping at the beginning of the
   second iteration. */
static void
il_container_looping(il_container *ic) 
{
    PRBool is_looping = ic->is_looping;
    il_context_list *current;
    IL_GroupContext *img_cx;

    for (current = ic->img_cx_list; current; current = current->next) {
        img_cx = current->img_cx;
        if (is_looping) {       /* Image has started looping. */
            /* Increment the number of looping image containers in the client
               context.  Observer notification takes place if the image group
               previously had no looping images. */
            if (!img_cx->num_looping)
                il_group_notify(img_cx, IL_STARTED_LOOPING);
            img_cx->num_looping++;

#ifdef DEBUG_GROUP_OBSERVER
            XP_TRACE(("3 img_cx=%x total=%d loading=%d looping=%d aborted=%d",
                      img_cx, img_cx->num_containers, img_cx->num_loading,
                      img_cx->num_looping, img_cx->num_aborted));
#endif /* DEBUG_GROUP_OBSERVER */
        }
        else {                  /* Image has stopped looping. */
            /* Decrement the number of looping image containers in the client
               context.  Observer notification takes place if this was
               the last looping image in the group. */
            img_cx->num_looping--;
            if (!img_cx->num_looping)
                il_group_notify(img_cx, IL_FINISHED_LOOPING);

#ifdef DEBUG_GROUP_OBSERVER
            XP_TRACE(("4 img_cx=%x total=%d loading=%d looping=%d aborted=%d",
                      img_cx, img_cx->num_containers, img_cx->num_loading,
                      img_cx->num_looping, img_cx->num_aborted));
#endif /* DEBUG_GROUP_OBSERVER */
        }
    }
}


/* Called when all activity within a container has successfully completed. */
static void
il_container_complete(il_container *ic)
{
    IL_GroupContext *img_cx = ic->img_cx;

    ic->stream = NULL;

    /* Update the image (and mask) pixmaps for the final time. */
    il_flush_image_data(ic);

    /* Tell the Front Ends that we will not modify the bits any further. */
    IMGCBIF_ControlPixmapBits(img_cx->img_cb, img_cx->dpy_cx, ic->image,
                              IL_RELEASE_BITS);
    if (ic->mask)
        IMGCBIF_ControlPixmapBits(img_cx->img_cb, img_cx->dpy_cx, ic->mask,
                                  IL_RELEASE_BITS);

    if (!ic->is_looping) {
        /* Tell the client contexts that the container has finished
           loading.  We don't do this for looping images which have just
           finished looping, since we called il_container_loaded at the
           time the first iteration completed. */
        il_container_loaded(ic);
    }
    else {
        /* This is a looping image whose loop count has reached zero, so
           set the container's state to indicate that it is no longer
           looping. */
        ic->is_looping = FALSE;			 

        /* Inform the client contexts that the container has stopped
           looping. */
        il_container_looping(ic);
    }
        
    /* Set the container's state to complete.  This indicates that the
       natural completion of network activity for the image. */
    ic->state = IC_COMPLETE;
    
}


/* XXXM12N This routine needs to be broken up into smaller components. */
void
il_image_complete(il_container *ic)
{
    NI_PixmapHeader *src_header = ic->src_header;
    IL_GroupContext *img_cx = ic->img_cx;
    IL_DisplayType display_type = ic->display_type;

	if (ic->state == IC_ABORT_PENDING) {
        /* It could be that layout aborted image loading by calling
           IL_DestroyImage() before the netlib finished transferring data.
           Don't do anything. */
        ic->stream = NULL;
        il_scour_container(ic);
    }
    else if (ic->state < IC_SIZED) {
        /* If we didn't size the image, but the stream finished loading, the
           image must be corrupt or truncated. */
        ic->state = IC_BAD;
        il_bad_container(ic);
	}
	else {
		XP_ASSERT(ic->state == IC_SIZED || ic->state == IC_MULTI);
		XP_ASSERT(ic->image && ic->image->bits);

		ILTRACE(1,("il: complete %d image width %d (%d) height %d,"
                   " depth %d, %d colors",
				   ic->multi,
				   ic->image->header.width,
                   ic->image->header.widthBytes,
                   ic->image->header.height,
                   ic->image->header.color_space->pixmap_depth, 
				   ic->image->header.color_space->cmap.num_colors));

		/* 3 cases: simple, multipart MIME, multi-image animation */
		if (!ic->loop_count && !ic->is_multipart) {
            /* A single frame, single part image. */
            il_container_complete(ic);
        }
        else {
            /* Display the rest of the last image before starting a new one */
			il_flush_image_data(ic);

            /* Force new colormap to be loaded in case its different from the
             * LOSRC or previous images in the multipart message.
             * XXX - fur - Shouldn't do this for COLORMAP case.
             */
            il_reset_palette(ic);

            FREE_IF_NOT_NULL(src_header->color_space->cmap.map);
            FREE_IF_NOT_NULL(src_header->transparent_pixel);
			il_destroy_image_transparent_pixel(ic);
            FREE_IF_NOT_NULL(ic->comment);
            ic->comment_length = 0;

            /* Handle looping, which can be used to replay an animation. */
            if (ic->loop_count) {
                int is_infinite_loop = (ic->loop_count == -1);
                IL_URL *netRequest = NULL;                
                if (!is_infinite_loop)
                    ic->loop_count--;
                
                ILTRACE(1,("il: loop %s", ic->url_address));

                netRequest =                   
                   NET_CreateURLStruct (ic->fetch_url, NET_DONT_RELOAD);
                if (!netRequest) {   /* OOM */
                    il_container_complete(ic);
                    goto done;
                }
                
                /* Only loop if the image stream is available locally.
                   Also, if the user hit the "stop" button, don't
                   allow the animation to loop. */
                if ((NET_IsLocalFileURL(ic->fetch_url)   ||
                     NET_IsURLInMemCache(netRequest)       ||
                     NET_IsURLInDiskCache(netRequest))          &&

                    (!il_image_stopped(ic))                &&
                    ic->net_cx &&
                    (display_type == IL_Console))
                {
                    if (!ic->is_looping) {
                        /* If this is the end of the first pass of the
                           animation, then set the state of the container
                           to indicate that we have started looping. */
                        ic->is_looping = TRUE;

                        /* At this point the animation is considered to have
                           loaded, so we need to tell the client contexts that
                           the container has loaded. */
                        il_container_loaded(ic);

                        /* This is also the point at which the animation is
                           considered to have started looping, so inform the
                           client contexts accordingly. */
                        il_container_looping(ic);
                    }
                    
                    ic->bytes_consumed = 0;
                    ic->state = IC_START;
                    ic->url = netRequest;
                    /* Record the fact that we are calling NetLib to load a URL. */
                    ic->is_url_loading = PR_TRUE;

                    /* Suppress thermo & progress bar */
                    netRequest->load_background = TRUE;
                    netRequest->fe_data = (void *)ic;
                    (void) NET_GetURL(ic->url, FO_CACHE_AND_INTERNAL_IMAGE,
                                      ic->net_cx->cx,
                                      (Net_GetUrlExitFunc*)&il_netgeturldone);
                } else {
                    ic->loop_count = 0;
                    NET_FreeURLStruct(netRequest);
                    il_container_complete(ic);
                }
            }
            else if (ic->is_multipart) {
				ic->multi++;
				ic->state = IC_MULTI;
			}
		}
	}
    
  done:
    
    /* Clear any pending timeouts */
    if (ic->row_output_timeout) {
        FE_ClearTimeout(ic->row_output_timeout);
        ic->row_output_timeout = NULL;
    }

    /* Notify observers that we are done decoding. */
    if (ic->state != IC_ABORT_PENDING && ic->state != IC_BAD)
        il_image_complete_notify(ic);
}


void 
il_abort(NET_StreamClass *stream, int status)
{
    il_container *ic = (il_container *)stream->data_object;
    int old_state;
    IL_ImageReq *image_req;	

	XP_ASSERT(ic);

	ILTRACE(4,("il: abort, status=%d ic->state=%d", status, ic->state));

    /* Abort the image. */
    il_image_abort(ic);

	/* It's possible that the stream is zero
	   because this container is scoured already */
	if (ic->stream)
		ic->stream->data_object = 0;
    ic->stream = NULL;

	if(ic->state >= IC_SIZED || (ic->state == IC_ABORT_PENDING)){
		if (status == MK_INTERRUPTED){
			il_container_aborted(ic);
		}
		else{
			for (image_req = ic->clients; image_req; image_req = image_req->next)
                il_icon_notify(image_req, IL_IMAGE_BAD_DATA,
                           IL_ERROR_IMAGE_DATA_ILLEGAL);
		}
	}
	/*
     * It could be that layout aborted image loading by calling IL_DestroyImage()
     * before the netlib finished transferring data.  Don't do anything.
     */
	if (ic->state == IC_ABORT_PENDING)
		return;

    old_state = ic->state;
        
    if (status == MK_INTERRUPTED)
        ic->state = IC_INCOMPLETE;
    else
        ic->state = IC_BAD;

    if (old_state < IC_SIZED)
        il_bad_container(ic);
}

NET_StreamClass *
IL_NewStream (FO_Present_Types format_out,
              void *type,
              URL_Struct *urls,
              OPAQUE_CONTEXT *cx)
{
	IL_Stream *stream = nil;
	il_container *ic = nil;

	/* recover the container */
	ic = (il_container*)urls->fe_data;

	XP_ASSERT(ic);

	/*
     * It could be that layout aborted image loading by calling IL_FreeImage()
     * before the netlib finished transferring data.  Don't do anything.
     */
	if (ic->state == IC_ABORT_PENDING)
		return NULL;
	
	/* Create stream object */
	if (!(stream = XP_NEW_ZAP(NET_StreamClass))) 
	{
		ILTRACE(0,("il: MEM il_newstream"));
		return 0;
	}

	ic->stream = stream;        /* XXXM12N We don't really need to hold on to
                                   the stream object anymore. The self pointer
                                   gets passed into the stream object's
                                   methods. */
    XP_ASSERT(ic->net_cx->cx == cx);

	ic->type = (int)type;
	ic->content_length = urls->content_length;
	ILTRACE(4,("il: new stream, type %d, %s", ic->type, urls->address));
	ic->state = IC_STREAM;

#ifndef M12N                    /* XXXM12N Fix me. */
#ifdef XP_MAC
    ic->image->hasUniqueColormap = FALSE;
#endif
#endif /* M12N */
	
	stream->name		   = "image decode";
	stream->complete	   = il_stream_complete;
	stream->abort		   = il_abort;
	stream->is_write_ready = il_write_ready;
	stream->data_object	   = (void *)ic;
	stream->window_id	   = ic->net_cx->cx;
	stream->put_block	   = (MKStreamWriteFunc) il_first_write;

	return stream;
}


/*	Phong's linear congruential hash  */
uint32
il_hash(const char *ubuf)
{
	unsigned char * buf = (unsigned char*) ubuf;
	uint32 h=1;
	while(*buf)
	{
		h = 0x63c63cd9*h + 0x9c39c33d + (int32)*buf;
		buf++;
	}
	return h;
}

#define IL_LAST_ICON 62
/* Extra factor of 7 is to account for duplications between
   mc-icons and ns-icons */
static uint32 il_icon_table[(IL_LAST_ICON + 7) * 2];

static void
il_setup_icon_table(void)
{
    int inum = 0;

	/* gopher/ftp icons */
	il_icon_table[inum++] = il_hash("internal-gopher-text");
	il_icon_table[inum++] = IL_GOPHER_TEXT;
	il_icon_table[inum++] = il_hash("internal-gopher-image");
	il_icon_table[inum++] = IL_GOPHER_IMAGE;
	il_icon_table[inum++] = il_hash("internal-gopher-binary");
	il_icon_table[inum++] = IL_GOPHER_BINARY;
	il_icon_table[inum++] = il_hash("internal-gopher-sound");
	il_icon_table[inum++] = IL_GOPHER_SOUND;
	il_icon_table[inum++] = il_hash("internal-gopher-movie");
	il_icon_table[inum++] = IL_GOPHER_MOVIE;
	il_icon_table[inum++] = il_hash("internal-gopher-menu");
	il_icon_table[inum++] = IL_GOPHER_FOLDER;
	il_icon_table[inum++] = il_hash("internal-gopher-index");
	il_icon_table[inum++] = IL_GOPHER_SEARCHABLE;
	il_icon_table[inum++] = il_hash("internal-gopher-telnet");
	il_icon_table[inum++] = IL_GOPHER_TELNET;
	il_icon_table[inum++] = il_hash("internal-gopher-unknown");
	il_icon_table[inum++] = IL_GOPHER_UNKNOWN;

	/* news icons */
	il_icon_table[inum++] = il_hash("internal-news-catchup-group");
	il_icon_table[inum++] = IL_NEWS_CATCHUP;
	il_icon_table[inum++] = il_hash("internal-news-catchup-thread");
	il_icon_table[inum++] = IL_NEWS_CATCHUP_THREAD;
	il_icon_table[inum++] = il_hash("internal-news-followup");
	il_icon_table[inum++] = IL_NEWS_FOLLOWUP;
	il_icon_table[inum++] = il_hash("internal-news-go-to-newsrc");
	il_icon_table[inum++] = IL_NEWS_GOTO_NEWSRC;
	il_icon_table[inum++] = il_hash("internal-news-next-article");
	il_icon_table[inum++] = IL_NEWS_NEXT_ART;
	il_icon_table[inum++] = il_hash("internal-news-next-article-gray");
	il_icon_table[inum++] = IL_NEWS_NEXT_ART_GREY;
	il_icon_table[inum++] = il_hash("internal-news-next-thread");
	il_icon_table[inum++] = IL_NEWS_NEXT_THREAD;
	il_icon_table[inum++] = il_hash("internal-news-next-thread-gray");
	il_icon_table[inum++] = IL_NEWS_NEXT_THREAD_GREY;
	il_icon_table[inum++] = il_hash("internal-news-post");
	il_icon_table[inum++] = IL_NEWS_POST;
	il_icon_table[inum++] = il_hash("internal-news-prev-article");
	il_icon_table[inum++] = IL_NEWS_PREV_ART;
	il_icon_table[inum++] = il_hash("internal-news-prev-article-gray");
	il_icon_table[inum++] = IL_NEWS_PREV_ART_GREY;
	il_icon_table[inum++] = il_hash("internal-news-prev-thread");
	il_icon_table[inum++] = IL_NEWS_PREV_THREAD;
	il_icon_table[inum++] = il_hash("internal-news-prev-thread-gray");
	il_icon_table[inum++] = IL_NEWS_PREV_THREAD_GREY;
	il_icon_table[inum++] = il_hash("internal-news-reply");
	il_icon_table[inum++] = IL_NEWS_REPLY;
	il_icon_table[inum++] = il_hash("internal-news-rtn-to-group");
	il_icon_table[inum++] = IL_NEWS_RTN_TO_GROUP;
	il_icon_table[inum++] = il_hash("internal-news-show-all-articles");
	il_icon_table[inum++] = IL_NEWS_SHOW_ALL_ARTICLES;
	il_icon_table[inum++] = il_hash("internal-news-show-unread-articles");
	il_icon_table[inum++] = IL_NEWS_SHOW_UNREAD_ARTICLES;
	il_icon_table[inum++] = il_hash("internal-news-subscribe");
	il_icon_table[inum++] = IL_NEWS_SUBSCRIBE;
	il_icon_table[inum++] = il_hash("internal-news-unsubscribe");
	il_icon_table[inum++] = IL_NEWS_UNSUBSCRIBE;
	il_icon_table[inum++] = il_hash("internal-news-newsgroup");
	il_icon_table[inum++] = IL_NEWS_FILE;
	il_icon_table[inum++] = il_hash("internal-news-newsgroups");
	il_icon_table[inum++] = IL_NEWS_FOLDER;

	/* httpd file icons */
	il_icon_table[inum++] = il_hash("/mc-icons/menu.gif");
	il_icon_table[inum++] = IL_GOPHER_FOLDER;
	il_icon_table[inum++] = il_hash("/mc-icons/unknown.gif");  
	il_icon_table[inum++] = IL_GOPHER_UNKNOWN;
	il_icon_table[inum++] = il_hash("/mc-icons/text.gif");	
	il_icon_table[inum++] = IL_GOPHER_TEXT;
	il_icon_table[inum++] = il_hash("/mc-icons/image.gif"); 
	il_icon_table[inum++] = IL_GOPHER_IMAGE;
	il_icon_table[inum++] = il_hash("/mc-icons/sound.gif");	 
	il_icon_table[inum++] = IL_GOPHER_SOUND;
	il_icon_table[inum++] = il_hash("/mc-icons/movie.gif");	 
	il_icon_table[inum++] = IL_GOPHER_MOVIE;
	il_icon_table[inum++] = il_hash("/mc-icons/binary.gif"); 
	il_icon_table[inum++] = IL_GOPHER_BINARY;

    /* Duplicate httpd icons, but using new naming scheme. */
	il_icon_table[inum++] = il_hash("/ns-icons/menu.gif");
	il_icon_table[inum++] = IL_GOPHER_FOLDER;
	il_icon_table[inum++] = il_hash("/ns-icons/unknown.gif");  
	il_icon_table[inum++] = IL_GOPHER_UNKNOWN;
	il_icon_table[inum++] = il_hash("/ns-icons/text.gif");	
	il_icon_table[inum++] = IL_GOPHER_TEXT;
	il_icon_table[inum++] = il_hash("/ns-icons/image.gif"); 
	il_icon_table[inum++] = IL_GOPHER_IMAGE;
	il_icon_table[inum++] = il_hash("/ns-icons/sound.gif");	 
	il_icon_table[inum++] = IL_GOPHER_SOUND;
	il_icon_table[inum++] = il_hash("/ns-icons/movie.gif");	 
	il_icon_table[inum++] = IL_GOPHER_MOVIE;
	il_icon_table[inum++] = il_hash("/ns-icons/binary.gif"); 
	il_icon_table[inum++] = IL_GOPHER_BINARY;

	/* ... and names for all the image icons */
	il_icon_table[inum++] = il_hash("internal-icon-delayed"); 
	il_icon_table[inum++] = IL_IMAGE_DELAYED;
	il_icon_table[inum++] = il_hash("internal-icon-notfound"); 
	il_icon_table[inum++] = IL_IMAGE_NOT_FOUND;
	il_icon_table[inum++] = il_hash("internal-icon-baddata"); 
	il_icon_table[inum++] = IL_IMAGE_BAD_DATA;
	il_icon_table[inum++] = il_hash("internal-icon-insecure"); 
	il_icon_table[inum++] = IL_IMAGE_INSECURE;
	il_icon_table[inum++] = il_hash("internal-icon-embed"); 
	il_icon_table[inum++] = IL_IMAGE_EMBED;

	/* This belongs up in the `news icons' section */
	il_icon_table[inum++] = il_hash("internal-news-followup-and-reply");
	il_icon_table[inum++] = IL_NEWS_FOLLOWUP_AND_REPLY;

	/* editor icons. */
	il_icon_table[inum++] = il_hash("internal-edit-named-anchor");
	il_icon_table[inum++] = IL_EDIT_NAMED_ANCHOR;
	il_icon_table[inum++] = il_hash("internal-edit-form-element");
	il_icon_table[inum++] = IL_EDIT_FORM_ELEMENT;
	il_icon_table[inum++] = il_hash("internal-edit-unsupported-tag");
	il_icon_table[inum++] = IL_EDIT_UNSUPPORTED_TAG;
	il_icon_table[inum++] = il_hash("internal-edit-unsupported-end-tag");
	il_icon_table[inum++] = IL_EDIT_UNSUPPORTED_END_TAG;
	il_icon_table[inum++] = il_hash("internal-edit-java");
	il_icon_table[inum++] = IL_EDIT_JAVA;
	il_icon_table[inum++] = il_hash("internal-edit-PLUGIN");
	il_icon_table[inum++] = IL_EDIT_PLUGIN;

	/* Security Advisor and S/MIME icons */
	il_icon_table[inum++] = il_hash("internal-sa-signed");
    il_icon_table[inum++] = IL_SA_SIGNED;
	il_icon_table[inum++] = il_hash("internal-sa-encrypted");
    il_icon_table[inum++] = IL_SA_ENCRYPTED;
	il_icon_table[inum++] = il_hash("internal-sa-nonencrypted");
    il_icon_table[inum++] = IL_SA_NONENCRYPTED;
	il_icon_table[inum++] = il_hash("internal-sa-signed-bad");
    il_icon_table[inum++] = IL_SA_SIGNED_BAD;
	il_icon_table[inum++] = il_hash("internal-sa-encrypted-bad");
    il_icon_table[inum++] = IL_SA_ENCRYPTED_BAD;
	il_icon_table[inum++] = il_hash("internal-smime-attached");
    il_icon_table[inum++] = IL_SMIME_ATTACHED;
	il_icon_table[inum++] = il_hash("internal-smime-signed");
    il_icon_table[inum++] = IL_SMIME_SIGNED;
	il_icon_table[inum++] = il_hash("internal-smime-encrypted");
    il_icon_table[inum++] = IL_SMIME_ENCRYPTED;
	il_icon_table[inum++] = il_hash("internal-smime-encrypted-signed");
    il_icon_table[inum++] = IL_SMIME_ENC_SIGNED;
	il_icon_table[inum++] = il_hash("internal-smime-signed-bad");
    il_icon_table[inum++] = IL_SMIME_SIGNED_BAD;
	il_icon_table[inum++] = il_hash("internal-smime-encrypted-bad");
    il_icon_table[inum++] = IL_SMIME_ENCRYPTED_BAD;
	il_icon_table[inum++] = il_hash("internal-smime-encrypted-signed-bad");
    il_icon_table[inum++] = IL_SMIME_ENC_SIGNED_BAD;

	/* LibMsg Attachment Icon */
	il_icon_table[inum++] = il_hash("internal-attachment-icon");
    il_icon_table[inum++] = IL_MSG_ATTACH;

    XP_ASSERT(inum <= (sizeof(il_icon_table) / sizeof(il_icon_table[0])));
}


static uint32
il_internal_image(const char *image_url)
{
	int i;
    uint32 hash = il_hash(image_url);
	if (il_icon_table[0]==0)
		il_setup_icon_table();

	for (i=0; i< (sizeof(il_icon_table) / sizeof(il_icon_table[0])); i++)
	{
		if (il_icon_table[i<<1] == hash)
		{
			return il_icon_table[(i<<1)+1];
		}
	}
	return 0;
}


PRBool
IL_PreferredStream(IL_URL *urls)
{
	il_container *ic = 0;
	IL_ImageReq *image_req;

	XP_ASSERT(urls);
	if (urls) {
		/* xxx this MUST be an image stream */
		ic = (il_container*)urls->fe_data;		

		XP_ASSERT(ic);
		if (ic) {
            /*
             * It could be that layout aborted image loading by
             * calling IL_FreeImage before the netlib finished
             * transferring data.  Don't do anything.
             */
            if (ic->state == IC_ABORT_PENDING)
                return FALSE;

			/* discover if layout is blocked on this image */
			for (image_req = ic->clients; image_req;
                 image_req = image_req->next) {
#ifndef M12N                    /* XXXM12N Fixme.  Observer for layout?
                                   Query mechanism for FE? */
				if ((LO_BlockedOnImage(c->cx,
                                       (LO_ImageStruct*)c->client) == TRUE) ||
                    FE_ImageOnScreen(c->cx, (LO_ImageStruct*)c->client) )
#endif /* M12N */
				return TRUE;
			}
		}
	}
	return FALSE;
}

IL_ImageReq *
IL_GetImage(const char* image_url,
            IL_GroupContext *img_cx,
            XP_ObserverList obs_list,
            NI_IRGB *background_color,
            uint32 req_width, uint32 req_height,
            uint32 flags,
            IL_NetContext *net_cx)
{
    NET_ReloadMethod cache_reload_policy = net_cx->cache_reload_policy;

   IL_URL *urls = NULL;

    IL_ImageReq *image_req;
	il_container *ic = NULL;
    int req_depth = img_cx->color_space->pixmap_depth;
	int err;
    int is_internal_external_reconnect = FALSE;
    int is_view_image;

    /* Create a new instance for this image request. */
    image_req = XP_NEW_ZAP(IL_ImageReq);
    if (!image_req)
        return NULL;
    image_req->img_cx = img_cx;
    /*
     * Remember the net context for this image request, It is possible that
     * the lifetime of the image container's net context will not be as
     * long as that of the image request itself (for example if the image
     * request for which the container was originally created happens to
     * be destroyed,) in which case we may need to give the container a
     * handle on this backup net context.
     */
    image_req->net_cx = IL_CloneDummyNetContext(net_cx);
	if (!image_req->net_cx) {
		XP_FREE(image_req);
		return NULL;
	}
    image_req->obs_list = obs_list;
    XP_SetObserverListObservable(obs_list, (void *)image_req);
    
    ILTRACE(1, ("il: IL_GetImage, url=%s\n", image_url));

	if (!image_url)
	{
		ILTRACE(0,("il: no url, sending delayed icon"));
        il_icon_notify(image_req, IL_IMAGE_DELAYED, IL_ERROR_NO_DATA);
        return image_req;
	}

    /* Check for any special internal-use URLs */
	if (*image_url == 'i'                  ||
        !XP_STRNCMP(image_url, "/mc-", 4)  ||
        !XP_STRNCMP(image_url, "/ns-", 4))
	{
		uint32 icon;

        /* A built-in icon ? */
        icon = il_internal_image(image_url);
		if (icon)
		{
			ILTRACE(4,("il: internal icon %d", icon));

            /* XXXM12N In response to this notification, layout should set
               lo_image->image_attr->attrmask |= LO_ATTR_INTERNAL_IMAGE; */
            il_icon_notify(image_req, icon, IL_INTERNAL_IMAGE);

            return image_req;
		}

        /* Image viewer URLs look like "internal-external-reconnect:REALURL.gif"
         * Strip off the prefix to get the real URL name.
         */
		if (!XP_STRNCMP(image_url, "internal-external-reconnect:", 28)) {
            image_url += 28;
			is_internal_external_reconnect = TRUE;
        }
	}

    ic = il_get_container(img_cx, cache_reload_policy, image_url,
                          background_color, img_cx->dither_mode, req_depth,
                          req_width, req_height);
    if (!ic)
    {
        ILTRACE(0,("il: MEM il_container"));
        il_icon_notify(image_req, IL_IMAGE_DELAYED, IL_ERROR_INTERNAL);
        if (is_internal_external_reconnect)
            il_abort_reconnect();
        return image_req;
    }
     
    /* Give the client a handle into the imagelib world. */
    image_req->ic = ic;

    /* Is this a call to the image viewer ? */
#ifndef M12N /* XXXM12N fix me. */
    is_view_image = is_internal_external_reconnect &&
        (cx->type != MWContextNews) && (cx->type != MWContextMail);
#else
    is_view_image = is_internal_external_reconnect;
#endif /* M12N */

    if (!il_add_client(img_cx, ic, image_req, is_view_image))
    {
        il_icon_notify(image_req, IL_IMAGE_DELAYED, IL_ERROR_INTERNAL);
        if (is_internal_external_reconnect)
            il_abort_reconnect();
        return image_req;
    }

    /* If the image is already in memory ... */
	if (ic->state != IC_VIRGIN) {
        switch (ic->state) {
        case IC_BAD:
            il_icon_notify(image_req, IL_IMAGE_BAD_DATA,
                           IL_ERROR_IMAGE_DATA_ILLEGAL);
            break;

        case IC_MISSING:
            il_icon_notify(image_req, IL_IMAGE_NOT_FOUND, IL_ERROR_NO_DATA);
            XP_ASSERT(! is_internal_external_reconnect);
            break;

        case IC_INCOMPLETE:
            il_icon_notify(image_req, IL_IMAGE_DELAYED,
                           IL_ERROR_IMAGE_DATA_TRUNCATED);
            break;

        case IC_SIZED:
        case IC_MULTI:
            /* This is a cached image that hasn't completed decoding. */
            il_cache_return_notify(image_req);
            break;

        case IC_COMPLETE:
#ifndef M12N
            /* M12N Fix custom colormaps. */
            il_set_color_palette(cx, ic);
#else
#endif /* M12N */
            /* This is a cached image that has already completed decoding. */
            il_cache_return_notify(image_req);

            break;

        case IC_START:
        case IC_STREAM:
        case IC_ABORT_PENDING:
        case IC_NOCACHE:
            break;
            
        default:
            XP_ASSERT(0);
            IL_DestroyDummyNetContext(image_req->net_cx);
            XP_FREE(image_req);
            return NULL;
        }

        if (is_internal_external_reconnect) {
            /* Since we found the image in the cache, we don't
             * need any of the data now streaming into the image viewer.
             */
            il_abort_reconnect();
        }

		/* NOCACHE falls through to be tried again */
        if (ic->state != IC_NOCACHE)
             return image_req;
    }

    /* This is a virgin (never-used) image container. */
	else
    {
		ic->forced = FORCE_RELOAD(cache_reload_policy);
	}

	ic->state = IC_START;
    
#ifdef DEBUG
    ic->start_time = XP_TIME();
#endif

    /* Record the context that actually initiates and controls the transfer. */
    ic->net_cx = IL_CloneDummyNetContext(net_cx);
 
	if (is_internal_external_reconnect)
	{
        /* "Reconnect" to the stream feeding IL_ViewStream(), already
           created. */
        il_reconnect(ic);
        return image_req;
    }
        
    /* need to make a net request */
    ILTRACE(1,("il: net request for %s, %s", image_url,
               ic->forced ? "force" : ""));

    urls = NET_CreateURLStruct(image_url, cache_reload_policy);

    if (!urls)
    {
#ifndef M12N /* XXXM12N fix me. */
        /* xxx clean up previously allocated objects */
        return MK_OUT_OF_MEMORY;
#else
        IL_DestroyDummyNetContext(image_req->net_cx);
        XP_FREE(image_req);
        return NULL;
#endif /* M12N */
    }        
    
    /* Add the referer to the URL. */
    IL_AddReferer(ic->net_cx, urls);
    
    ic->is_looping = FALSE;
    ic->url = urls;
	/* Record the fact that we are calling NetLib to load a URL. */
    ic->is_url_loading = PR_TRUE;

    /* save away the container */
    urls->fe_data = (void *)ic;
    err = NET_GetURL(urls, (cache_reload_policy == NET_CACHE_ONLY_RELOAD) ?
                     FO_ONLY_FROM_CACHE_AND_INTERNAL_IMAGE :
                     FO_CACHE_AND_INTERNAL_IMAGE, 
                     ic->net_cx->cx,
                     (Net_GetUrlExitFunc*)&il_netgeturldone);
	return image_req;
}


void
IL_ReloadImages(IL_GroupContext *img_cx, IL_NetContext *net_cx)
{
    /* XXXM12N - Implement me. */

}


void
IL_SetDisplayMode(IL_GroupContext *img_cx, uint32 display_flags,
                  IL_DisplayData *display_data)
{
    if (display_flags & IL_DISPLAY_CONTEXT)
        img_cx->dpy_cx = display_data->display_context;
    
    if (display_flags & IL_DISPLAY_TYPE)
        img_cx->display_type = display_data->display_type;

    if (display_flags & IL_COLOR_SPACE) {
        if (img_cx->color_space)
            IL_ReleaseColorSpace(img_cx->color_space);
        img_cx->color_space = display_data->color_space;
        IL_AddRefToColorSpace(img_cx->color_space);
    }

    if (display_flags & IL_PROGRESSIVE_DISPLAY)
        img_cx->progressive_display = display_data->progressive_display;

    if (display_flags & IL_DITHER_MODE)
        if (img_cx->color_space && img_cx->color_space->pixmap_depth == 1) {
            /* Dithering always on in monochrome mode. */
            img_cx->dither_mode = IL_Dither;
        }
        else {
            img_cx->dither_mode = display_data->dither_mode;
        }
}



/* Interrupts all images loading in a context.  Specifically, stops all
   looping image animations. */
void
IL_InterruptContext(IL_GroupContext *img_cx)
{
    il_container *ic;
    IL_ImageReq *image_req;
	il_container_list *ic_list;

    if (!img_cx)
        return;

    /* Mark all clients in this context as interrupted. */
	for (ic_list = img_cx->container_list; ic_list; ic_list = ic_list->next) {
        ic = ic_list->ic;
        for (image_req = ic->clients; image_req; image_req = image_req->next) {
            if (image_req->img_cx == img_cx) {
                image_req->stopped = TRUE;
            }
        }
    }
}

/* Has the user aborted the image load ? */
PRBool
il_image_stopped(il_container *ic)
{
    IL_ImageReq *image_req;
    for (image_req = ic->clients; image_req; image_req = image_req->next) {
        if (!image_req->stopped)
            return FALSE;
    }

    /* All clients must be stopped for image container to become dormant. */
    return TRUE;
}


/* Create an IL_GroupContext, which represents an aggregation of state
   for one or more images and which contains an interface to access
   external service functions and callbacks.  IL_NewGroupContext will use
   the IMGCBIF_AddRef callback to increment the reference count for the
   interface.

   The dpy_cx argument is opaque to the image library and is passed back to
   all of the callbacks in the IMGCBIF interface. */
IL_GroupContext*
IL_NewGroupContext(void *dpy_cx, IMGCBIF *img_cb)
{
    IL_GroupContext *img_cx;
    
    if (!img_cb)
        return NULL;

    img_cx = (IL_GroupContext*)XP_NEW_ZAP(IL_GroupContext);
    if (!img_cx)
        return NULL;

    img_cx->dpy_cx = dpy_cx;
    img_cx->img_cb = img_cb;

    img_cx->progressive_display = TRUE;

    /* Create an observer list for the image context. */
    if (XP_NewObserverList((void *)img_cx, &img_cx->obs_list)
        == MK_OUT_OF_MEMORY) {
        XP_FREE(img_cx);
        return NULL;
    }

    /* Add the context to the global image context list. */
    img_cx->next = il_global_img_cx_list;
    il_global_img_cx_list = img_cx;

    return img_cx;
}


/* Free an image context.  IL_DestroyGroupContext will make a call
   to the IMGCBIF_Release callback function of the JMC interface prior to
   releasing the IL_GroupContext structure.  The IMGCBIF_Release callback
   is expected to decrement the reference count for the IMGCBIF interface,
   and to free the callback vtable and the interface structure if the
   reference count is zero. */
void
IL_DestroyGroupContext(IL_GroupContext *img_cx)
{
    if (img_cx) {
        /* Remove ourself from the global image context list. */
        if (img_cx == il_global_img_cx_list) {
            il_global_img_cx_list = NULL;
        }
        else {
            IL_GroupContext *current, *next;
        
            for (current = il_global_img_cx_list; current; current = next) {
                next = current->next;
                if (next == img_cx) {
                    current->next = next->next;
                    break;
                }
            }
        }
        
        /* Destroy any images remaining in the context. */
        if (img_cx->num_containers) {
            XP_ASSERT(img_cx->container_list != NULL);
            IL_DestroyImageGroup(img_cx);
        }

        /* Destroy the observer list. */
        XP_DisposeObserverList(img_cx->obs_list);

        /* Release the image context's reference to the colorspace. */
        if (img_cx->color_space) {
            IL_ReleaseColorSpace(img_cx->color_space);
            img_cx->color_space = NULL;
        }

        /* Release the JMC callback interface. */
        IMGCBIF_release(img_cx->img_cb, NULL); /* XXXM12N Need to use
                                                  exceptions. */
        XP_FREE(img_cx);
    }
}


/* Add an observer/closure pair to an image group context's observer list.
   Returns PR_TRUE if the observer is successfully registered. */
PRBool
IL_AddGroupObserver(IL_GroupContext *img_cx, XP_ObserverProc observer,
                    void *closure)
{
    return !XP_AddObserver(img_cx->obs_list, observer, closure);    
}


/* Remove an observer/closure pair from an image group context's observer
   list.  Returns PR_TRUE if successful. */
PRBool
IL_RemoveGroupObserver(IL_GroupContext *img_cx, XP_ObserverProc observer,
                    void *closure)
{
    return XP_RemoveObserver(img_cx->obs_list, observer, closure);
}


/* Display a rectangular portion of an image.  x and y refer to the top left
   corner of the image, measured in pixels, with respect to the document origin.
   x_offset and y_offset are measured in pixels, with the upper-left-hand corner
   of the pixmap as the origin, increasing left-to-right, top-to-bottom.

   If the width and height values would otherwise cause the sub-image
   to extend off the edge of the source image, the function should
   perform tiling of the source image.  This is used to draw document,
   layer and table cell backdrops.  (Note: it is assumed this case will
   apply only to images which do not require any scaling.)

   If at any time the image library determines that an image request cannot
   be fulfilled or that the image has been delayed, it will notify the client
   synchronously through the observer mechanism.  The client may then choose to
   request that an icon be drawn instead by making a call to IL_DisplayIcon. */
void
IL_DisplaySubImage(IL_ImageReq *image_req, int x, int y, int x_offset,
                   int y_offset, int width, int height)
{
    IL_GroupContext *img_cx;
    void *dpy_cx;
    il_container *ic;
    IL_Rect *displayable_rect;
    NI_PixmapHeader *img_header;
    
    /* If we were passed a NULL image handle, don't do anything. */
    if (!image_req)
        return;

    /* Determine the image context and the display context for this display
       operation. */
    img_cx = image_req->img_cx;
    dpy_cx = img_cx->dpy_cx;
    if (!dpy_cx)
        return;
    
    /* Determine the image container. */
    ic = image_req->ic;
	
    if (!ic )
	return;

    /* Perform the drawing operation, but only do so for displayable areas
       of the image pixmap. */
    displayable_rect = &ic->displayable_rect;
    img_header = &ic->image->header;
        
    if (displayable_rect->width < img_header->width ||
        displayable_rect->height < img_header->height) {
        /* The image pixmap is only partially displayable (since the image
           is only partially decoded,) so draw the intersection of the
           requested area with the displayable area.  The client will
           receive observer notification as the pixmap is updated, and it
           is expected to call the image library again to display the
           updated area.  Note that tiling operations are not performed
           unless the entire image pixmap is displayable. */
        int32 display_left, display_top, display_bottom, display_right;
        int32 display_width, display_height;
            
        /* Determine the intersection of the requested area and the
           displayable area, in pixmap coordinates. */
        display_left = MAX(x_offset, displayable_rect->x_origin);
        display_top = MAX(y_offset, displayable_rect->y_origin);
        display_right = MIN(x_offset + width, displayable_rect->x_origin
                            + displayable_rect->width);
        display_bottom = MIN(y_offset + height, displayable_rect->y_origin
                             + displayable_rect->height);
        display_width = display_right - display_left;
        display_height = display_bottom - display_top;
        
        /* Draw the intersection of the requested area and the displayable
           area. */
        if ((display_width > 0) && (display_height > 0))
            IMGCBIF_DisplayPixmap(img_cx->img_cb, dpy_cx, ic->image, ic->mask,
                                  x, y, display_left, display_top,
                                  display_width, display_height);
    }
    else {
        /* The entire image pixmap is displayable, (although this does not
           necessarily mean that the image has finished decoding,) so draw
           the entire area requested.  In the event that the requested
           area extends beyond the bounds of the image pixmap, tiling will
           be performed. */
        if (width && height)
            IMGCBIF_DisplayPixmap(img_cx->img_cb, dpy_cx, ic->image, ic->mask,
                                  x, y, x_offset, y_offset, width, height);
    }
}


/* Display an icon.  x and y refer to the top left corner of the icon, measured
   in pixels, with respect to the document origin.  x_offset, y_offset, width
   and height specify a clipping rectangle for this drawing request. */
void
IL_DisplayIcon(IL_GroupContext *img_cx, int icon_number, int x, int y)
{
    /* Check for a NULL image context. */
    if (!img_cx)
        return;

    /* Ask the Front End to display the icon. */
    IMGCBIF_DisplayIcon(img_cx->img_cb, img_cx->dpy_cx, x, y, icon_number);
}


/* Return the dimensions of an icon. */
void
IL_GetIconDimensions(IL_GroupContext *img_cx, int icon_number, int *width,
                     int *height)
{
    /* Check for a NULL image context. */
    if (!img_cx)
        return;

    /* Obtain the dimensions of the icon. */
    IMGCBIF_GetIconDimensions(img_cx->img_cb, img_cx->dpy_cx, width, height,
                              icon_number);
}


/* Return the image IL_Pixmap associated with an image request. */
IL_Pixmap *
IL_GetImagePixmap(IL_ImageReq *image_req)
{
    if (image_req && image_req->ic)
        return image_req->ic->image;
    else
        return NULL;
}


/* Return the mask IL_Pixmap associated with an image request. */
IL_Pixmap *
IL_GetMaskPixmap(IL_ImageReq *image_req)
{
    if (image_req && image_req->ic)
        return image_req->ic->mask;
    else
        return NULL;
}


/* Return the natural dimensions of the image.  Returns 0,0 if the dimensions
   are unknown. */
void
IL_GetNaturalDimensions(IL_ImageReq *image_req, int *width, int *height)
{
	NI_PixmapHeader *src_header;

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

	if (!image_req || !image_req->ic)
        return;

	src_header = image_req->ic->src_header;

	if (src_header) {
		if (width)
			*width = src_header->width;
		if (height)
			*height = src_header->height;
	}
}


#ifndef M12N                    /* XXXM12N Get rid of these functions */
void
IL_DisableScaling(MWContext *cx)
{
	il_process *ip;

	if (!cx)
		return;

	if (!(ip=cx->imageProcess))
		return;

	ip->dontscale = 1;
}


void
IL_DisableLowSrc(MWContext *cx)
{
	il_process *ip;

	if (!cx)
		return;

	if (!(ip=cx->imageProcess))
		return;

	ip->nolowsrc = 1;
}
#endif /* M12N */

#ifdef PROFILE
#pragma profile off
#endif


