Milán Major

netscape-revival

1 branch
Code

lib/libimg/if.c

/* -*- Mode: C; tab-width: 4 -*-
 *   if.c --- Top-level image library routines
 *   Copyright © 1997 Netscape Communications Corporation, all rights reserved.
 *   $Id: if.c,v 1.304.4.1.10.1 1997/03/06 13:34:32 jwz Exp $
 */

#include "xp.h"
#include "merrors.h"
#include "shist.h"
#include "if.h"

#ifdef MOCHA
#    include "libmocha.h"
#endif

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;

il_process *il_last_ip = NULL;
il_colorspace *il_last_cs = NULL;


#ifdef DEBUG_jwz
XP_Bool IL_AnimationLoopsEnabled = TRUE;   /* xfe/commands.c changes this */
XP_Bool IL_AnimationsEnabled = TRUE;
#endif /* DEBUG_jwz */


static int
il_init_scaling(il_container *ic)
{
    int scaled_width = ic->image->width;
        
    /* Allocate temporary scale space */
    if (scaled_width != ic->unscaled_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;
}
    
int
il_size(il_container *ic, uint32 unscaled_width, uint32 unscaled_height)
{
    float aspect;
	il_client *c;
    int status;
    int scaled_width, scaled_height;
    int32 image_bytes, old_image_bytes;
	IL_Image *im = ic->image;
    IL_ImageStatus size_mode;

	/* Ensure that the image has a sane area. */
	if (!unscaled_width || !unscaled_height    ||
        (unscaled_width    > MAX_IMAGE_WIDTH)  ||
        (unscaled_height   > MAX_IMAGE_HEIGHT) ||
        (ic->layout_width  > MAX_IMAGE_WIDTH)  ||
        (ic->layout_height > MAX_IMAGE_HEIGHT))
    {
		ILTRACE(1,("il: bad image size: %dx%d",
                   unscaled_width, unscaled_height));
		return MK_IMAGE_LOSSAGE;
	}

	ic->unscaled_height = unscaled_height;
	ic->unscaled_width  = unscaled_width;

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

	ic->state = IC_SIZED;

    /* 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.
     *
     * Macintoshes, however, do their own scaling so we need to reset the
     * MacFE to tell it that it's processing a different-sized image.
     */
    if (ic->sized) {
        status = il_init_scaling(ic);
#ifndef XP_MAC
        return status;
#else
        if (status)
            return status;
#endif
    }

    old_image_bytes = (int32)ic->image->widthBytes * ic->image->height;

    /* Scale the image according to the IMG tag attributes,
     * unless scaling is disabled.
     */
    aspect = (float)unscaled_width/(float)unscaled_height;
    if ((!ic->layout_width && !ic->layout_height) || ic->ip->dontscale)
    {
        scaled_width  = ic->unscaled_width;
        scaled_height = ic->unscaled_height;
    }
    else if (ic->layout_width && ic->layout_height)
    {
        scaled_width  = ic->layout_width;
        scaled_height = ic->layout_height;
    }
    else if (ic->layout_width)
    {
        scaled_width  = ic->layout_width;
        scaled_height = (int)((float)ic->layout_width / aspect + 0.5);
    }
    else
    {
        scaled_width  = (int)(aspect * (float)ic->layout_height + 0.5);
        scaled_height = ic->layout_height;
    }

    if (scaled_width == 0)
        scaled_width = 1;
    
    if (scaled_height == 0)
        scaled_height = 1;

    im->width  = scaled_width;
    im->height = scaled_height;

	ILTRACE(2,("il: size %dx%d, scaled from %dx%d, ic = 0x%08x\n",
               scaled_width, scaled_height, unscaled_width, unscaled_height,
               ic));

    if ((status = il_init_scaling(ic)) < 0)
        return status;

	/* set bytes per pixel based on real width */
	if (ic->cs->bytes_per_pixel && im->depth!=1)
    {

#ifdef XP_MAC
        /* don't expand 8-bit gifs to 16-bits if we're in "raw" mode */
		if (ic->type == IL_GIF)
        {
			im->bytesPerPixel = 1;
			im->depth = 8;
		}
        else
#endif /* XP_MAC */
		{
			im->bytesPerPixel = ic->cs->bytes_per_pixel;
			im->depth = ic->cs->depth;
		}
        
		im->widthBytes = im->width * ic->cs->bytes_per_pixel;
	}
	else
	{ /* mono */
		im->bytesPerPixel = 1;
		im->widthBytes = (im->width+7)/8;
		im->colors = 2;
		im->depth = 1;
	}

    /* Image data and mask must be quadlet aligned for optimizations */
	im->maskWidthBytes = ROUNDUP((im->width+7)/8, 4);
    im->widthBytes = ROUNDUP(im->widthBytes, 4);

    size_mode = ic->sized ? ilReset : ilSetSize;
    ic->sized = 1;
    image_bytes = (int32)im->widthBytes * im->height;
    if (image_bytes - old_image_bytes)
        il_adjust_cache_fullness(image_bytes - old_image_bytes);

    /* for debugging 
    if (ic->type == IL_GIF && !im->transparent)
        im->transparent = (void*)1;
    */

	XP_ASSERT(ic->clients);
	for (c=ic->clients; c; c=c->next)
    {
		int err = FE_ImageSize(c->cx, size_mode, ic->image, c->client);
		if (err < 0)
            return MK_OUT_OF_MEMORY;
	}

	if (!im->transparent && im->mask)
    {
        XP_ASSERT(0);
		im->mask = 0;
	}

/* for debugging
   if (im->transparent == (void*)1)
       im->transparent = NULL;
*/

	/* Get memory for quantizing.  (required amount depends on image width) */
	if (ic->cs->mode == ilCI)
    {
		if (!il_init_quantize(ic))
        {
			ILTRACE(0,("il: MEM il_init_quantize"));
			return MK_OUT_OF_MEMORY;
		}
	}

	/* If its an internal/external reconnect, fabricate a title */
    il_set_view_image_doc_title(ic);

	return 0;
}
	
#define IL_SIZE_CHUNK	128
#ifdef XP_MAC
#    if GENERATINGPOWERPC
#        define IL_PREFERRED_CHUNK 8048 
#        define IL_OFFSCREEN_CHUNK 2048
#    else	/* normal mac */
#        define IL_PREFERRED_CHUNK 4048 
#        define IL_OFFSCREEN_CHUNK 512
#    endif
#else /* !XP_MAC */
#    define IL_PREFERRED_CHUNK 2048 
#    define IL_OFFSCREEN_CHUNK 512 
#endif

unsigned int
il_write_ready(void *data_object)
{
    uint request_size = 1;
	il_container *ic = (il_container *)data_object;

    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;

    /* Layout is blocked until the backdrop is loaded, so load that ASAP. */
	if (ic->is_backdrop)
		return IL_PREFERRED_CHUNK;

    /* This is a non-backdrop image, but the backdrop hasn't loaded yet. */
    if (ic->ip->backdrop_loading)
        return IL_OFFSCREEN_CHUNK;

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

    if (ic->is_visible_on_screen)
        return IL_PREFERRED_CHUNK;
    
    /* Re-check to see if we are on-screen. */
    {
        il_client *c;
        for (c = ic->clients; c; c = c->next)
        {
            il_process *ip = c->cx->imageProcess; 
            if (FE_ImageOnScreen(c->cx, (LO_ImageStruct*)c->client))
            {
                ic->is_visible_on_screen = TRUE;
                ip->active++;
                ILTRACE(0,("il: %d active", ip->active));
                return IL_PREFERRED_CHUNK;
            }
		}

		/* This active stream is not currently on-screen;
         * Are other images now being loaded on-screen ?
         */
        for (c = ic->clients; c; c = c->next)
            if (c->cx->imageProcess->active)
                return IL_OFFSCREEN_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;
	}

	/* 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(void *data_object, const unsigned char *str, int32 len)
{
	int err = 0;
    il_container *ic = (il_container *)data_object;

    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);

    il_update_thermometer(ic);

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


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

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

	XP_ASSERT(ic);
	XP_ASSERT(ic->ip);
	XP_ASSERT(ic->image);

    /* 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_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(dobj, (const char*) str, len);
}

static void
il_bad_container(il_container *ic)
{
	il_client *c;

	ILTRACE(4,("il: bad container, sending icon"));
	for (c=ic->clients; c; c=c->next)
    {
        
		if (ic->type == IL_NOTFOUND)
        {
			ic->state = IC_MISSING;
			FE_ImageIcon(c->cx, IL_IMAGE_NOT_FOUND, c->client);
		}
		else
		{
			if (ic->state == IC_INCOMPLETE)
				FE_ImageIcon(c->cx, IL_IMAGE_DELAYED, c->client);
			else
				FE_ImageIcon(c->cx, IL_IMAGE_BAD_DATA, c->client);
		}
	}
	/* gets deleted later */
}


/*
 *	geturl completion callback
 */
static void
il_netgeturldone (URL_Struct *request, int status, MWContext *cx)
{
	il_container *ic = (il_container*)request->fe_data;
	XP_ASSERT(ic);

    /* CLM: Editor hack: 
     *      We want to load images while in a modal dialog (Apply button)
     *      we need a flag to allow front end to run the net until
     *      image is loaded.
     * TODO: This should not be needed when we convert to modeless dialogs in Dogbert
     */
    cx->edit_loading_url = FALSE;

	/*
     * It could be that layout aborted image loading by calling IL_FreeImage
     * 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, 1);
        NET_FreeURLStruct(request);
        return;
    }
    
	if (status < 0)
    {
		il_client *c;

		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 */
            }

#ifdef MOCHA
            {
                /* As far as JavaScript events go, we treat an image
                   that wasn't in the cache the same as if it was
                   actually loaded.  That way, a Javascript program
                   will run the same way with images turned on or off */
                LM_ImageEvent event =
                    (status == MK_INTERRUPTED) ? LM_IMGABORT :
                    (status == MK_OBJECT_NOT_IN_CACHE) ? LM_IMGLOAD :
                    LM_IMGERROR;
                    
                for (c = ic->clients; c; c = c->next)
                    LM_SendImageEvent(c->cx, c->client, event);
            }
#endif

			if (! ic->sized)
			{
				for (c=ic->clients; c; c=c->next)
				{
					if ((status == MK_INTERRUPTED) ||
					    (status == MK_OBJECT_NOT_IN_CACHE))
					{
						FE_ImageIcon(c->cx, IL_IMAGE_DELAYED, c->client);
					}
					else if (status == MK_UNABLE_TO_LOCATE_FILE)
					{
						FE_ImageIcon(c->cx, IL_IMAGE_NOT_FOUND, c->client);
					}
					else
					{
						FE_ImageIcon(c->cx, IL_IMAGE_BAD_DATA, c->client);
					}
				}
			}
		}

		/* for mac */
		if (status == MK_OUT_OF_MEMORY)
			NET_InterruptWindow(cx);
	}

	ic->forced = 0;

	NET_FreeURLStruct(request);
}

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;
    }

	ic->is_looping = FALSE;
}

void
il_stream_complete(void *data_object)
{
	il_container *ic = (il_container *)data_object;

	XP_ASSERT(ic);

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

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

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

    ic->stream = NULL;
}

void
il_image_complete(il_container *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)
        goto no_more_images_for_this_ic;

    /* If we didn't size the image, but the stream finished loading, the
       image must be corrupt or truncated. */
	if (ic->state < IC_SIZED)
	{
		ic->state = IC_BAD;
		il_bad_container(ic);

#ifdef MOCHA
        {
            il_client *c;
            for (c = ic->clients; c; c = c->next) {
                LM_SendImageEvent(c->cx, c->client, LM_IMGERROR);
            }
        }
#endif

        goto done;
	}
	else
	{
		Bool multi;

		XP_ASSERT(ic->state == IC_SIZED ||
                  ic->state == IC_HIRES ||
                  ic->state == IC_MULTI);
		XP_ASSERT(ic->image && ic->image->bits);

        /* Is there a next part in a multipart MIME message ? */
		multi = ic->stream && ic->stream->is_multipart;

		ILTRACE(1,("il: complete %d image width %d (%d) height %d,"
                   " depth %d, %d colors",
				   ic->multi,
				   ic->image->width, ic->image->widthBytes, ic->image->height,
                   ic->image->depth, 
				   ic->image->colors));

		/* 4 cases: simple, hi/lo, multipart MIME, multimage animation */
		if (ic->hiurl || multi || ic->loop_count)
		{
            il_client *c;
			URL_Struct *netRequest = NULL;

            /* Display the rest of the last image before starting a new one */
			il_flush_image_data(ic, ilPartialData);

            /* 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(ic->image->map);
            FREE_IF_NOT_NULL(ic->image->transparent);
            FREE_IF_NOT_NULL(ic->comment);
            ic->comment_length = 0;

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

                netRequest =
                    NET_CreateURLStruct (ic->url_address, NET_DONT_RELOAD);
                if (!netRequest)    /* OOM */
                    goto no_more_images_for_this_ic;

                /* 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->url_address)   ||
                     NET_IsURLInMemCache(netRequest)       ||
                     NET_IsURLInDiskCache(netRequest))          &&
                    (! il_image_stopped(ic))                    &&
#ifdef DEBUG_jwz
                    (IL_AnimationsEnabled && IL_AnimationLoopsEnabled) &&
#endif /* DEBUG_jwz */
                    ((ic->cx)                              &&
                     (ic->cx->type != MWContextPrint)      &&
                     (ic->cx->type != MWContextPostScript) &&
                     (ic->cx->type != MWContextMetaFile)))
                {
                    /* Suppress thermo & progress bar */
                    netRequest->load_background = TRUE;
                    netRequest->fe_data = (void *)ic;
                    ic->url = netRequest;

                    ic->bytes_consumed = 0;
                    
                    ic->state = IC_START;
                    ic->is_looping = TRUE;
                    (void) NET_GetURL(ic->url, FO_CACHE_AND_INTERNAL_IMAGE,
                                      ic->cx, &il_netgeturldone);
                    goto done;
                } else {
                    ic->loop_count = 0;
					ic->is_looping = FALSE;
                    NET_FreeURLStruct(netRequest);
                }
            }
			else
                ic->is_looping = FALSE;			 

			if (multi)
            {
				ic->multi++;
				ic->state = IC_MULTI;
                goto done;
			}
            else if (ic->hiurl)
			{
				ILTRACE(1,("il: get hi %s", ic->hiurl));

				netRequest = NET_CreateURLStruct (ic->hiurl, NET_DONT_RELOAD);
				if (!netRequest)
                    goto no_more_images_for_this_ic;

				/* Add the referer to the URL. */
                for (c = ic->clients; c; c = c->next)
				{
					History_entry *he =	SHIST_GetCurrent (&c->cx->hist);
					if (netRequest->referer)
						XP_FREE (netRequest->referer);
					if (he && he->address)
						netRequest->referer = XP_STRDUP (he->address);
					else
						netRequest->referer = 0;
				}

				/* save away the container */
				netRequest->fe_data = (void *)ic;
                ic->is_looping = FALSE;
				(void) NET_GetURL(netRequest, FO_CACHE_AND_INTERNAL_IMAGE,
                                  ic->cx, &il_netgeturldone);
				ic->url = netRequest;

				ic->state = IC_HIRES;
				if (ic->hiurl) { XP_FREE(ic->hiurl); ic->hiurl=0; }
			}

            goto done;
		}
	}
    
  no_more_images_for_this_ic:
    
    ic->stream = NULL;

    /* normal case */
    if (ic->is_visible_on_screen)
        {
            il_client *c;
            for (c = ic->clients; c; c = c->next) {
                il_process *ip = c->cx->imageProcess;
                if (--ip->active < 0)
                    ip->active = 0;
            }
            ic->is_visible_on_screen = 0;
        }

    /* XXX - This should be done on a per-client basis */
    if (ic->is_backdrop)
        ic->ip->backdrop_loading = FALSE;
    
    if (ic->state != IC_ABORT_PENDING)
        il_flush_image_data(ic, ilComplete);
    il_scour_container(ic);
    if (!ic->multi)
        ic->state = IC_COMPLETE;

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

#ifdef MOCHA
    {
        il_client *c;
        for (c = ic->clients; c; c = c->next) {
            LM_SendImageEvent(c->cx, c->client, LM_IMGLOAD);
        }
    }
#endif
}


void 
il_abort(void *data_object, int status)
{
    int old_state;
    il_client *c;
	il_container *ic = (il_container *)data_object;

	XP_ASSERT(ic);

	ILTRACE(4,("il: abort, status=%d ic->state=%d", status, ic->state))
    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->is_visible_on_screen)
	{
        for (c = ic->clients; c; c = c->next) {
            il_process *ip = c->cx->imageProcess;
            if (--ip->active < 0)
                ip->active = 0;
        }
		ic->is_visible_on_screen = FALSE;
	}

#ifdef MOCHA
    for (c = ic->clients; c; c = c->next) {
        LM_SendImageEvent(c->cx, c->client,
                          status == MK_INTERRUPTED ? LM_IMGABORT : LM_IMGERROR);
    }
#endif

	/*
     * 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;

    old_state = ic->state;
        
    if ((status == MK_INTERRUPTED) || (status == MK_OUT_OF_MEMORY))
        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,
              MWContext *cx)
{
	NET_StreamClass *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->type = (int)type;
	ic->stream = stream;
    XP_ASSERT(ic->cx == cx);
	ic->cx = cx;
	ic->state = IC_STREAM;
	ic->content_length = urls->content_length;

#ifdef XP_MAC
    ic->image->hasUniqueColormap = FALSE;
#endif
	
	ILTRACE(4,("il: new stream, type %d, %s", ic->type, urls->address));

	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	   = 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 61
/* 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;

    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;
}

void
IL_DeleteImages(MWContext *cx)
{
}


Bool
IL_PreferredStream(URL_Struct *urls)
{
	il_container *ic = 0;
	il_client *c;

	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 (c=ic->clients; c; c=c->next)
			{
				if ((LO_BlockedOnImage(c->cx,
                                      (LO_ImageStruct*)c->client) == TRUE) ||
				   FE_ImageOnScreen(c->cx, (LO_ImageStruct*)c->client) )
					return TRUE;
			}
		}
	}
	return FALSE;
}

int
IL_GetImage (const char* image_url,
             MWContext* cx,
             LO_ImageStruct *lo_image,
             NET_ReloadMethod cache_reload_policy)
{
    int layout_width, layout_height;
    IL_IRGB *background_color;

    URL_Struct *urls = NULL;
	void *clientData = (void *)lo_image;

	il_container *ic = NULL;
    il_process *ip;
    il_colorspace *cs = NULL;
	int err, is_backdrop;
    int is_internal_external_reconnect = FALSE;
    int is_view_image;
    const char *lowres_image_url = NULL;

	/* xxx generate a default processor */
	if (!cx->imageProcess)
		cx->imageProcess = il_last_ip;

	if (!cx->colorSpace)
		cx->colorSpace = il_last_cs;

    cs = cx->colorSpace;
    ip = cx->imageProcess;

    ILTRACE(1, ("il: IL_GetImage, url=%s\n", image_url));

    /* XXX - fur - When can this happen ? */
	if (!image_url)
	{
		ILTRACE(0,("il: no url, sending delayed icon"));
		FE_ImageIcon(cx, IL_IMAGE_DELAYED, clientData);
        lo_image->il_image = NULL;
		return 0;
	}

    /* 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 ? */
		if ((icon = il_internal_image(image_url)))
		{
			ILTRACE(4,("il: internal icon %d", icon));
			FE_ImageIcon(cx, icon, clientData);
            lo_image->il_image = NULL;
			lo_image->image_attr->attrmask |= LO_ATTR_INTERNAL_IMAGE;
			return 0;
		}

        /* 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;
        }
	}

    if (!ip->nolowsrc)
        lowres_image_url = (const char *)lo_image->lowres_image_url;

    /* Locate image in cache or currently loading page */
    layout_width  = 
        ip->dontscale ? 0 : (int)(lo_image->width   / cx->convertPixX);
    layout_height =
        ip->dontscale ? 0 : (int)(lo_image->height  / cx->convertPixY);
    background_color = &ip->transparent;
     
    is_backdrop = (lo_image->image_attr->attrmask & LO_ATTR_BACKDROP) != 0;

    ic = il_get_container(cx, cache_reload_policy, image_url,
                          lowres_image_url, background_color,
                          ip->dither_mode, LO_HasBGImage(cx) && !is_backdrop,
                          cs->depth, layout_width, layout_height);
    if (!ic)
    {
        ILTRACE(0,("il: MEM il_container"));
        FE_ImageIcon(cx, IL_IMAGE_DELAYED, clientData);
        if (is_internal_external_reconnect)
            il_abort_reconnect();
        lo_image->il_image = NULL;
        return -1;
    }
     
    /* Give layout a handle into the imagelib world. */
    lo_image->il_image = ic->image;
    
    /* A user-specified default backdrop image, presumably a local file. */
    if (lo_image->image_attr->attrmask & LO_ATTR_STICKY)
        ic->is_user_backdrop = TRUE;

    /* Is this a non-default backdrop image ? */
    ic->is_backdrop = is_backdrop;

    /* Is this a call to the image viewer ? */
    is_view_image = is_internal_external_reconnect &&
        (cx->type != MWContextNews) && (cx->type != MWContextMail);
    if (il_add_client(cx, ic, clientData, is_view_image))
    {
        FE_ImageIcon(cx, IL_IMAGE_DELAYED, clientData);
        if (is_internal_external_reconnect)
            il_abort_reconnect();
        return -1;
    }

    /* Record the last context and imageProcess used against. */
    ic->ip = ip;
    XP_ASSERT(cx->colorSpace);
    ic->cs = cx->colorSpace;
    

    /* If the image is already in memory ... */
	if (ic->state != IC_VIRGIN) {
        switch (ic->state) {
        case IC_BAD:
#ifdef MOCHA
            LM_SendImageEvent(cx, clientData, LM_IMGERROR);
#endif
            FE_ImageIcon(cx, IL_IMAGE_BAD_DATA, clientData);
            break;

        case IC_MISSING:
#ifdef MOCHA
            LM_SendImageEvent(cx, clientData, LM_IMGERROR);
#endif
			FE_ImageIcon(cx, IL_IMAGE_NOT_FOUND, clientData);
            XP_ASSERT(! is_internal_external_reconnect);
            break;

        case IC_INCOMPLETE:
#ifdef MOCHA
            LM_SendImageEvent(cx, clientData, LM_IMGERROR);
#endif
            FE_ImageIcon(cx, IL_IMAGE_DELAYED, clientData);
            break;

        case IC_SIZED:
        case IC_HIRES:
        case IC_MULTI:
			FE_ImageSize(cx, ilSetSize, ic->image, clientData);
            break;

        case IC_COMPLETE:
            FE_ImageSize(cx, ilSetSize, ic->image, clientData);
            il_set_color_palette(cx, ic);
            FE_ImageData(cx, ilComplete, ic->image, clientData, 0, 0, FALSE);
#ifdef MOCHA
            LM_SendImageEvent(cx, clientData, LM_IMGLOAD);
#endif
            if (is_view_image)
            {
                /* Fabricate a title for the document window. */
                il_set_view_image_doc_title(ic);
            }
            break;

        case IC_START:
        case IC_STREAM:
        case IC_ABORT_PENDING:
        case IC_NOCACHE:
            break;
            
        default:
            XP_ASSERT(0);
            return -1;
        }

        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 0;
    }

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

        /* Is this a non-default backdrop image ? */
		if (ic->is_backdrop)
            ic->ip->backdrop_loading = TRUE;
	}

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

    /* Record the context that actually initiates and controls the transfer. */
    ic->cx = cx;

	if (is_internal_external_reconnect)
	{
        /* "Reconnect" to the stream feeding IL_ViewStream(), already created. */
        il_reconnect(ic);
        return 0;
	}
        
    /* 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)
    {
        /* xxx clean up previously allocated objects */
        return MK_OUT_OF_MEMORY;
    }
    
    if (lowres_image_url && !NET_IsURLInDiskCache(urls))
    {
        /* do the lowsrc thing */
        ic->hiurl = (char*)XP_STRDUP(image_url);

        NET_FreeURLStruct(urls);
        urls=NULL;

        ILTRACE(1,("il: net request lowsrc %s",
                   (const char *)lo_image->lowres_image_url));

        /* lowres_image_url is really an XP_Block */
        urls = NET_CreateURLStruct (lowres_image_url, cache_reload_policy);
        
        if (!urls) 
            return MK_OUT_OF_MEMORY;
    }

    /* Add the referer to the URL. */
    {
        History_entry *he = SHIST_GetCurrent (&cx->hist);
        if (urls->referer)
			XP_FREE (urls->referer);
        if (he && he->address)
			urls->referer = XP_STRDUP (he->address);
        else
			urls->referer = 0;
    }

    /* save away the container */
    urls->fe_data = (void *)ic;
    ic->url = urls;

    /* CLM: Editor hack: 
     *      We want to load images while in a modal dialog (Apply button)
     *      we need a flag to allow front end to run the net until
     *      image is loaded.
     * TODO: This should not be needed when we convert to modeless dialogs in Dogbert
     */
    cx->edit_loading_url = TRUE;
    
    ic->is_looping = FALSE;
    err = NET_GetURL(urls, (cache_reload_policy == NET_CACHE_ONLY_RELOAD) ?
                     FO_ONLY_FROM_CACHE_AND_INTERNAL_IMAGE :
                     FO_CACHE_AND_INTERNAL_IMAGE, 
                     cx, &il_netgeturldone);
	return 0;
}

int
IL_SetTransparent(MWContext *cx, IL_IRGB *transparent)
{
	il_process *ip = cx->imageProcess;
    il_colorspace *cs = cx->colorSpace;

	if (!ip)
		return -1;

	if ((cs->mode == ilGrey) && (cs->depth == 1)) {
        /* Override */
        ip->transparent.red = 0xff;
        ip->transparent.green = 0xff;
        ip->transparent.blue = 0xff;
        return 0;
    }
    


	if (transparent)
	{
		XP_MEMCPY(&ip->transparent, transparent, sizeof(IL_IRGB));
	}
	return 0;
}

/* XXX - remove this API function - It is now totally ignored. */

void
IL_SetByteOrder(MWContext *cx, Bool ls_bit_first, Bool ls_byte_first)
{
}

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;
}

void
IL_SetPreferences(MWContext *cx, Bool incrementalDisplay,
                  IL_DitherMode dither_mode)
{
	il_process *ip;
    il_colorspace *cs;

	if (!cx)
		return;

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

    ip->progressive_display = incrementalDisplay;

    /* Dithering always on in monochrome mode. */
    if (cs->depth == 1)
        ip->dither_mode = ilDither;
    else
        ip->dither_mode = dither_mode;
}

/* Interrupts all image loading in a context.
   Specifically, stops all looping image animations.    */
void
IL_InterruptContext(MWContext *cx)
{
    il_container *ic;
    il_client *c;

    int stopped_count = 0;
	il_container_list *ic_list = cx->imageList;

	for (; ic_list; ic_list = ic_list->next) {
        ic = ic_list->ic;
        for (c = ic->clients; c; c = c->next) {
            if (c->cx == cx) {
                c->stopped = TRUE;
                stopped_count++;
            }
        }
    }

    if (stopped_count)
        FE_UpdateStopState(cx);
}

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

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

/* Checks to see if there are any looping images for the context */
Bool
IL_AreThereLoopingImagesForContext(MWContext *cx)
{
	il_container_list *ic_list = cx->imageList;

	for (; ic_list; ic_list = ic_list->next)
	  {
		/* If we're looping */
		if (ic_list->ic && IMAGE_CONTAINER_LOADING(ic_list->ic) &&
			ic_list->ic->is_looping)
		  {
			return TRUE;
		  }
	  }

	return FALSE;
}

#ifdef PROFILE
#pragma profile off
#endif