/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Images.c                               */
/*                                                 */
/* Purpose: Image related functions.               */
/*                                                 */
/* Author : Merlyn Kline for Customer browser     */
/*          This source adapted by A.D.Hodgkinson  */
/*                                                 */
/* History: 28-Nov-96: Created with dummy function */
/*                     for temporary use in other  */
/*                     routines.                   */
/*          20-Jan-97: Filled in with most of the  */
/*                     functions present and       */
/*                     converted to the new data   */
/*                     structures where needed.    */
/*          15-Mar-97: Reorganised a bit, putting  */
/*                     functions in a more logical */
/*                     order to aid legibility.    */
/*          17-Dec-97: Added support for saving as */
/*                     a Draw file.                */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "swis.h"
#include "flex.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "MiscDefs.h"
#include "Utils.h"

#include "Browser.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "FetchHTML.h"
#include "FetchPage.h"
#include "FontManage.h"
#include "ImgHistory.h"
#include "PrintStyle.h"
#include "Protocols.h"
#include "Redraw.h"
#include "Reformat.h"
#include "Save.h"
#include "SaveDraw.h"
#include "TokenUtils.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "URLveneer.h"
#include "Windows.h"

#include "Images.h"

/* Statics */

static _kernel_oserror * image_load_chunk             (browser_data * b, int image, char * buffer, int bytes);
static void              image_remove_data            (browser_data * b, int image);
static void              image_remove_all_data        (browser_data * b, int image);
static void              image_delay                  (browser_data * b, int image);
static void              image_abandon                (browser_data * b, int image);
static void              image_delete_image_entry     (int image);
static void              image_refetch                (browser_data * b, int image, int priority, int redraw);

static _kernel_oserror * image_update_area            (browser_data * b, int x, int y, BBox * ubox, int image, int redraw);
static _kernel_oserror * image_update_image           (browser_data * b, int image, BBox * box);

static _kernel_oserror * image_register_filler        (int xref);

static int               image_data_offset            (browser_data * b, int image);
static int               image_count_fetches          (browser_data * b);

static int               image_get_token_image_xref   (browser_data * b, HStream * token);
static int               image_get_token_image_actual (browser_data * b, HStream * token);

static void              image_lock_image_size        (browser_data * b, int image);
static void              image_unlock_image_size      (browser_data * b, int image);
static _kernel_oserror * image_get_image_size         (browser_data * b, int image, BBox * box, int ignore_stored);
static _kernel_oserror * image_set_image_size         (browser_data * b, int image, BBox * box);

static int               image_can_be_saved_as_sprite (browser_data * b, int i);

/* Locals */

static image_info * idata       = NULL;
static char       * ddata       = NULL;
static int          nimages     = 0;
static int          lastimage   = 0;
static int          animhandler = 0;

static char       * unique_name = NULL;

/* For animated images, the filler function for the background of a   */
/* transparent image will get called only for the main image with the */
/* associated image data - not for any cross referenced images. These */
/* statics hold the browser and image being redrawn at any time, so   */
/* the filler function knows which cross referencing image is         */
/* actually being redrawn.                                            */

static int            image_redrawing   = -1;
static browser_data * browser_redrawing = NULL;

/* Local compilation options */

#undef TRACE_FETCH_STORE

// Unimplemented functions:

// /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// static _kernel_oserror * image_savesprite_start_save1(image_info * ip,int * isize)
// {
//   int        size;
//
//   * isize=-1;
//   if(ip && ip->istore && ip->istore->width>0)
//   {
//     ERROUT((_kernel_oserror*)ip->istore->StartExport(ip->istore,&size));
//     size-=12; /* subtract the size of the sprite area header */
//   }
//   else
//   {
//     sprite_header * p;
//     sprite_area   * sa;
//     sprite_id     sid;
//
//     image_get_broken_sprite(&sa,&sid);
//     p=(sprite_header*)sid.s.addr;
//     size=WORDALIGN(p->next);
//   }
//   * isize=size;
//   return(NULL);
// }
//
// /* ----------------------------------------------------------------------*/
// _kernel_oserror * image_savesprite_start_save(browser_data * b,int token,int * isize)
// {
//   image_info * ip;
//
//   ip=image_get_token_image_xref(b,token);
//   return(image_savesprite_start_save1(ip,isize));
// }
//
//
// /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// static int image_savesprite_save_image1(image_info * ip)
// {
//   sprite_header * p;
//   sprite_area   * sa;
//   sprite_id     sid;
//
//   if(ip && ip->istore && ip->istore->width>0)
//   {
//     int  len;
//     char buffer[2048];
//     int  skip=12; /* skip first 12 bytes (sprite area header) */
//
//     do
//     {
//       len=sizeof(buffer);
//       if(wimpt_complain((_kernel_oserror*)ip->istore->Export(ip->istore,buffer,&len))) return(0);
//       if(len-skip>0 && !save_write_bytes(buffer+skip,len-skip)) return(0);
//       skip-=len;
//       if(skip<0) skip=0;
//     } while(len);
//     return(1);
//   }
//   image_get_broken_sprite(&sa,&sid);
//   p=(sprite_header*)sid.s.addr;
//   return(save_write_bytes((void*)p,WORDALIGN(p->next)));
// }
//
// /* ----------------------------------------------------------------------*/
// int image_savesprite_save_image(browser_data * b,int token)
// {
//   image_info    * ip;
//
//   ip=image_get_token_image_xref(b,token);
//   return(image_savesprite_save_image1(ip));
// }
//
// /* ----------------------------------------------------------------------*/
// void image_savesprite_end_save(browser_data * b,int token)
// {
//   image_info * ip;
//
//   ip=image_get_token_image_xref(b,token);
//   if(ip && ip->istore && ip->istore->width>0) ip->istore->EndExport(ip->istore);
// }
//
// /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// static int image_picture_saver(void * handle,char * pathname)
// {
//   browser       * b;
//   int           * si,token;
//   image_info    * ip;
//
//   pathname=pathname;
//   si=(int*)handle;
//   b=(browser*)si[0];
//   token=si[1];
//   ip=NULL;
//   if(token>0) ip=image_get_token_image_xref(b,token);
//   else if(b->background_image>0) ip=image_info_addr(b,b->background_image);
//   if(ip && ip->istore && ip->istore->width>0)
//   {
//     int  len;
//     char buffer[2048];
//
//     do
//     {
//       len=sizeof(buffer);
//       if(wimpt_complain((_kernel_oserror*)ip->istore->Export(ip->istore,buffer,&len))) return(0);
//       if(len && !save_write_bytes(buffer,len)) return(0);
//     } while(len);
//   }
//   else
//   {
//     sprite_area   area,* sa;
//     sprite_id     sid;
//     sprite_header * p;
//
//     image_get_broken_sprite(&sa,&sid);
//     p=(sprite_header*)sid.s.addr;
//     area.size=sizeof(sprite_area)+p->next;
//     area.number=1;
//     area.sproff=sizeof(sprite_area);
//     area.freeoff=area.size;
//     if(!save_write_bytes((void*)(&area.number),sizeof(area)-4)) return(0);
//     return(save_write_bytes((void*)p,p->next));
//   }
//   return(1);
// }
//
// /* ----------------------------------------------------------------------*/
// _kernel_oserror * image_save_picture(browser_data * b,int token)
// {
//   int        size,saveinfo[2];
//   char       pathname[256];
//   image_info * ip;
//
//   ip=NULL;
//   if(token && fetch_token_address(b,token)->style&IMG) ip=image_get_token_image_xref(b,token);
//   if(!ip && b->background_image>0)
//   {
//     ip=image_info_addr(b,b->background_image);
//     token=0;
//   }
//   if(ip && ip->istore && ip->istore->width>0) ip->istore->StartExport(ip->istore,&size);
//   else
//   {
//     sprite_area   * sa;
//     sprite_id     sid;
//     sprite_header * p;
//
//     image_get_broken_sprite(&sa,&sid);
//     p=(sprite_header*)sid.s.addr;
//     size=sizeof(sprite_area)+p->next-4;
//   }
//   saveinfo[0]=(int)b;
//   saveinfo[1]=token;
//   strcpy(pathname,msgs_lookup("sprfile:SpriteFile"));
//   save_saveas(FILETYPE_SPRITE,pathname,size,image_picture_saver,NULL,(void*)saveinfo);
//   if(ip && ip->istore && ip->istore->width>0) ip->istore->EndExport(ip->istore);
//   return(NULL);
// }

// /* ----------------------------------------------------------------------*/
// _kernel_oserror * image_saveback_start_save(browser_data * b,int * isize)
// {
//   image_info * ip;
//
//   ip=image_info_addr(b,b->background_image);
//   return(image_savesprite_start_save1(ip,isize));
// }
//
// /* ----------------------------------------------------------------------*/
// int image_saveback_save_image(browser_data * b)
// {
//   image_info    * ip;
//
//   ip=image_info_addr(b,b->background_image);
//   return(image_savesprite_save_image1(ip));
// }
//
// /* ----------------------------------------------------------------------*/
// void image_saveback_end_save(browser_data * b)
// {
//   image_info * ip;
//
//   ip=image_info_addr(b,b->background_image);
//   if(ip && idata[i].istore && idata[i].istore->width>0) idata[i].istore->EndExport(idata[i].istore);
// }

// **** static const char * image_get_url(HStream * t)
// **** {
// ****   if ISBODY(t)
// ****   {
// ****     if (t->tagno == TAG_TABLE) return NULL;
// ****     else if (t->style & IMG)   return t->src;
// ****     else if ISOBJECT(t)        return NULL; // Eeek
// ****     else if (t->style & FORM)
// ****     {
// ****       if (t->tagno == TAG_INPUT)
// ****       {
// ****         switch (HtmlINPUTtype(t))
// ****         {
// ****           case inputtype_IMAGE: return HtmlINPUTsrc(t);
// ****           default:              return NULL;
// ****         }
// ****       }
// ****       else return NULL;
// ****     }
// ****   }
// ****   else if ISFRAMESET(t) return NULL;
// ****   else if ISHEAD(t)
// ****   {
// ****     if (t->tag == BODY) return HtmlBODYbackground(t);
// ****   }
// ****
// ****   return NULL;
// **** }

/*************************************************/
/* image_load_chunk()                            */
/*                                               */
/* Tell the image library to deal with some of   */
/* image data we've fetched for a given image.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1;                      */
/*                                               */
/*             Pointer to a buffer containing    */
/*             the data, as a char *;            */
/*                                               */
/*             The amount of data in that        */
/*             buffer.                           */
/*************************************************/

static _kernel_oserror * image_load_chunk(browser_data * b, int image, char * buffer, int bytes)
{
  BBox              box;
  _kernel_oserror * e = NULL;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_load_chunk: Called for image %d\n",image);
  #endif

  /* If the library has filled in the istore field, tell it to */
  /* get some of the data we've fetched.                       */

  if (idata[image].istore) e = idata[image].istore -> Load(idata[image].istore,
                                                           buffer,
                                                           bytes,
                                                           &box);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_load_chunk: Exitting with error\n");
  #endif

  /* Return errors for STRICT_PARSER builds only */

  #ifdef STRICT_PARSER
    if (e) return e;
  #else
    if (e) return NULL;
  #endif

  /* Update the image on the page to reflect the new data */

  image_update_image(b, image, &box);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_load_chunk: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_remove_data()                           */
/*                                               */
/* Discards the fetched image data associated    */
/* with an image, but leaves the URL information */
/* alone.                                        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_remove_data(browser_data * b, int image)
{
  int p, oldsize, remove;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_data: Called for image %d\n",image);
  #endif

  /* Get the current data size */

  oldsize = flex_size((flex_ptr) &ddata);

  /* The alloc field holds the total amount of data associated */
  /* with the image, and the ualloc field holds the amount     */
  /* that the URL uses. So subtract ualloc from alloc to get   */
  /* the amount that must be removed.                          */

  remove = idata[image].alloc - idata[image].ualloc;

  /* Need to shuffle the rest of the flex block */
  /* down and shrink the block, if this isn't   */
  /* the last image (i.e. p != oldsize).        */

  p = image_data_offset(b,image) + idata[image].alloc;

  if (p != oldsize) memmove(ddata + p - remove, ddata + p, oldsize - p);

  flex_extend((flex_ptr) &ddata, oldsize - remove);

  #ifdef TRACE
    flexcount -= remove;
    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
  #endif

  /* Subtract the amount removed from the alloc field */
  /* and set isize to zero, i.e. we're not storing    */
  /* any image data anymore.                          */

  idata[image].alloc -= remove;
  idata[image].isize  = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_data: Successful\n");
  #endif
}

/*************************************************/
/* image_remove_all_data()                       */
/*                                               */
/* Discards the fetched image data associated    */
/* with an image, including URL information.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_remove_all_data(browser_data * b, int image)
{
  int p, oldsize, remove;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_all_data: Called for image %d\n",image);
  #endif

  /* Get the current data size */

  oldsize = flex_size((flex_ptr) &ddata);

  /* The alloc field holds the total amount of data associated */
  /* with the image.                                           */

  remove = idata[image].alloc;

  if (remove)
  {
    /* Need to shuffle the rest of the flex block */
    /* down and shrink the block, if this isn't   */
    /* the last image (i.e. p != oldsize).        */

    p = image_data_offset(b, image);

    if (p != oldsize) memmove(ddata + p, ddata + p + remove, oldsize - p - remove);

    flex_extend((flex_ptr) &ddata, oldsize - remove);

    #ifdef TRACE
      flexcount -= remove;
      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    #endif
  }

  /* Subtract the amount removed from the alloc field */
  /* and set isize to zero, i.e. we're not storing    */
  /* any image data anymore.                          */

  idata[image].alloc = 0;
  idata[image].isize = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_remove_all_data: Successful\n");
  #endif
}

/*************************************************/
/* image_delay()                                 */
/*                                               */
/* Stop fetching data for an image, even if it   */
/* hasn't all been fetched, but allow the fetch  */
/* to continue later on.                         */
/*                                               */
/* Call with care. Keeping part of an image is   */
/* dangerous - ImageLib may object. Normally,    */
/* this should only be called for images which   */
/* have not started to fetch. Stopping an image  */
/* mid-fetch is more safely done through         */
/* image_abandon.                                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_delay(browser_data * b, int image)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_delay: Called for image %d\n",image);
  #endif

  /* Stop any fetching for the image */

  if (idata[image].handle) html_close(idata[image].handle);

  idata[image].handle  = 0;
  idata[image].delayed = 1;

  /* If this is a background, go back to the plain background */

  if (image == b->background_image) b->antialias_colour = redraw_backcol(b);

  if (idata[image].istore) image_update_image(b, image, NULL);

  /* There may be a new overall fetch status, so ensure the */
  /* status bar is up to date                               */

  toolbars_cancel_status(b, Toolbars_Status_GetPics);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_delay: Successful\n");
  #endif
}

/*************************************************/
/* image_delay_fetches()                         */
/*                                               */
/* Stops any images which have not started to    */
/* fetch for a given browser from fetching at    */
/* all, by setting the image's 'delayed' flag.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

void image_delay_fetches(browser_data * b)
{
  if (nimages)
  {
    int image;

    for (image = 0; image < nimages; image++)
    {
      /* Only interested in non-cross referencing images */
      /* owned by this browser which are not fetching.   */

      if (
           idata[image].owner == b &&
           idata[image].xref < 0   &&
           !idata[image].handle
         )
         image_delay(b, image);
    }
  }
}

/*************************************************/
/* image_abandon()                               */
/*                                               */
/* Stop fetching data for an image, even if it   */
/* hasn't all been fetched.                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*************************************************/

static void image_abandon(browser_data * b, int image)
{
  BBox end;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_abandon: Called for image %d\n",image);
  #endif

  /* Stop any fetching for the image */

  if (idata[image].handle) html_close(idata[image].handle);

  idata[image].handle  = 0;
  idata[image].fetched = 1;

  /* If this is a background, go back to the plain background */

  if (image == b->background_image) b->antialias_colour = redraw_backcol(b);

  if (idata[image].istore)
  {
    /* Let this fail silently */

    idata[image].istore -> EndLoad(idata[image].istore, &end);

    /* Redraw the image */

    image_update_image(b, image, &end);
  }

  /* There may be a new overall fetch status, so ensure the */
  /* status bar is up to date                               */

  toolbars_cancel_status(b, Toolbars_Status_GetPics);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_abandon: Successful\n");
  #endif
}

/*************************************************/
/* image_abort_fetches()                         */
/*                                               */
/* Stop fetching data for all images belonging   */
/* to a given browser, discarding all data for   */
/* those images. The fetches cannot be restarted */
/* after this!                                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

void image_abort_fetches(browser_data * b)
{
  int i;

  /* Loop through the images */

  for (i = 0; i < nimages; i++)
  {
    /* If owned by this browser... */

    if (idata[i].owner == b)
    {
      /* Mark the image as fetched (so pending images don't then */
      /* start to fetch after fetching images stop).             */

      idata[i].fetched = 1;

      /* Stop any images which are fetching */

      if (idata[i].handle)
      {
        html_close(idata[i].handle);
        idata[i].handle   = 0;
        idata[i].bytesgot = 0;

        /* If this is a background, go back to the plain background */

        if (i == b->background_image) b->antialias_colour = redraw_backcol(b);

        if (idata[i].istore)
        {
          #ifdef STRICT_PARSER

            /* Report any errors as Continue-only in strict parser mode */

            _kernel_oserror * e;

            e = idata[i].istore -> Delete(idata[i].istore);

            if (e)
            {
              e->errnum = Utils_Error_Custom_Message;
              show_error_ret(e);
            }

          #else

            /* If not in Strict mode, ignore any errors */

            idata[i].istore -> Delete(idata[i].istore);

          #endif

          idata[i].istore = NULL;

          /* Remove any fetched data (not URL information though) */

          image_remove_data(b, i);

          /* Redraw the item */

          browser_update_token(b, idata[i].token, 0, 0);
        }
      }
    }
  }

  /* There may be a new overall fetch status, so ensure the */
  /* status bar is up to date                               */

  toolbars_cancel_status(b, Toolbars_Status_GetPics);
}

/*************************************************/
/* image_new_image()                             */
/*                                               */
/* Starts fetching a new image. Creates a record */
/* in the idata flex block and increments the    */
/* image counter. If an image is already being   */
/* fetched with the same URL then a cross        */
/* reference is set up to point to that, else a  */
/* fetch is initiated.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image to fetch;   */
/*                                               */
/*             Pointer to the URL to fetch;      */
/*                                               */
/*             The address of the token that led */
/*             to this function call, i.e. the   */
/*             token representing that image;    */
/*                                               */
/*             1 if the image is for the         */
/*             background (it will be turned     */
/*             into a sprite for fast plotting), */
/*             else 0. If 2, then the            */
/*             background_image field of the     */
/*             browser_data structure is filled  */
/*             in with the relevant image number */
/*             and the image will again be       */
/*             turned into a sprite for fast     */
/*             plotting.                         */
/*************************************************/

_kernel_oserror * image_new_image(browser_data * b, const char * url, HStream * token, int background)
{
  int       size, ok, ulen, xref;
  HStream * tp;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_new_image: Called\n");
  #endif

  /* Can't proceed without the URL to fetch! */

  if (!url) url = ""; /* Point to *something*, even if it is blank */

  xref = -1;
  tp   = token;

  /* Need to make sure we don't already have an image with this URL */
  /* if there are already some images present.                      */

  if (nimages)
  {
    int test = 1;

    /* If this is a File URL, check it's not scrap - don't allow cross */
    /* referencing if it is.                                           */

    if (!strncmp(url, FileMethod, sizeof(FileMethod) - 1))
    {
      char test_url[Limits_URL];

      /* Build the URL we'd be on for a scrap file fetch */

      StrNCpy0(test_url, Save_ScrapFile);

      urlutils_pathname_to_url(test_url, sizeof(test_url));

      /* If it's the same as the URL we've been given, don't */
      /* cross reference the image.                          */

      if (!strcmp(url, test_url)) test = 0;
    }

    if (test)
    {
      int i;

      for (i = 0; i < nimages; i++)
      {
        /* Can we simply create a cross reference to another image? */

        if (
             idata[i].alloc                                            &&  /* The image has data allocated for it                 */
             !strcmp(url, ddata + image_data_offset(b,i))              &&  /* This holds a URL which matches that passed in       */
             idata[i].xref < 0                                         &&  /* The image doesn't cross reference things itself     */
             idata[i].background == !!background                       &&  /* We're not trying to make a b/g an f/g or vice versa */
             (
               !background ||                                              /* This isn't a background image, or...                */
               (
                 background &&                                             /* If this is a background image, the background       */
                 idata[i].owner->background_colour == b->background_colour /* colours match in the two owner browsers             */
               )
             )
           )
        {
          xref = i;       /* If all of the above are satisfied, cross reference */
          i    = nimages; /* this image with the one it matches.                */
        }
      }
    }
  }

  if (xref >= 0) ulen = 0; /* Don't need any extra data for a cross referenced image. */
  else
  {
    ulen = (strlen(url) + 1);
    ulen = (int) WordAlign(ulen);
  }

  size = ulen;

  /* Allocate space for the image_info structure */

  if (idata)
  {
    int oldsize;

    oldsize = flex_size  ((flex_ptr) &idata);
    ok      = flex_extend((flex_ptr) &idata, oldsize + sizeof(image_info));
  }
  else ok   = flex_alloc ((flex_ptr) &idata, sizeof(image_info));

  /* Allocate space for the image data */

  if (ok && ddata)
  {
    int oldsize;

    #ifdef TRACE
      flexcount += sizeof(image_info);
      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    #endif

    oldsize = flex_size  ((flex_ptr) &ddata);
    ok      = flex_extend((flex_ptr) &ddata, oldsize + size);
  }
  else ok   = flex_alloc ((flex_ptr) &ddata, size);

  /* Throw back an error if out of memory */

  if (!ok)
  {
    erb.errnum = Utils_Error_Custom_Message;

    StrNCpy0(erb.errmess,
             lookup_token("NoMemImg:There is not enough free memory for any new images (%0).",
                          0,
                          "1"));

    return &erb;
  }

  #ifdef TRACE
    flexcount += size;
    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
  #endif

  /* Increment the image counter, zero the contents of the image_info structure */
  /* and fill in some of the fields where contents are known now.               */

  memset(&idata[nimages], 0, sizeof(image_info)); /* Precautionary catch-all */

  idata[nimages].owner      = b;
  idata[nimages].x          = -1;
  idata[nimages].y          = -1;

  idata[nimages].alloc      = size;
  idata[nimages].ualloc     = ulen;
  idata[nimages].isize      = 0;

  idata[nimages].token      = tp;
  idata[nimages].xref       = xref;

  idata[nimages].background = !!background;
  idata[nimages].hadfiller  = 0;

  /* Set the 'delayed' flag if it's not a background image and */
  /* images are flagged as not being shown right now, or if it */
  /* is a backgrond image and backgrounds are flagged to be    */
  /* plain only.                                               */

  if (background)
  {
    idata[nimages].delayed   = !b->show_background;
    idata[nimages].canredraw = 1;

    /* Set the background image number */

    if (background == 2) b->background_image = nimages;
  }
  else
  {
    idata[nimages].delayed = (!b->show_foreground && b->displayed != Display_External_Image);
  }

  /* If there's data for a URL, copy it into the above allocated block */
  /* and ImageLib about the new image.                                 */

  if (ulen)
  {
    strcpy(ddata + image_data_offset(b, nimages), url);

    idata[nimages].istore = NewImage(NULL, background ? IMAGE_FAST : 0);
  }

  // This works, but you've then got to alter the dataoffset fields
  // of higher images whenever you remove any data from one lower in
  // the flex block. That's not implemented yet, and the speed penalty
  // of this may offset the gains in image_data_offset.
  //
  // if (!nimages) idata[nimages].dataoffset = 0;
  // else idata[nimages].dataoffset = idata[nimages - 1].dataoffset + alloc;

  /* If the image is not a background one, need to update the */
  /* width and height fields according to any details in the  */
  /* HTML source.                                             */

  if (!background)
  {
    /* Need to flag if the HTML gives us an absolute size */

    if (
         OBJECT_HAS_WIDTH   (token)                 &&
         OBJECT_HAS_HEIGHT  (token)                 &&
         OBJECT_WIDTH_UNITS (token) == UNITS_PIXELS &&
         OBJECT_HEIGHT_UNITS(token) == UNITS_PIXELS
       )
       idata[nimages].fixedsize = 1, idata[nimages].canredraw = 1;

    else
    {
      /* If reformats aren't immediate, image_update_image may ask for */
      /* one and then if canredraw is set, will start updating the     */
      /* image. This is invalid unless the reformat has definitely     */
      /* occurred (a full size image plots inside a default size       */
      /* placeholder...). However, if reformats happen straight away,  */
      /* we'll always be able to redraw.                               */

      if (!choices.refo_wait) idata[nimages].canredraw = 1;
      else                    idata[nimages].canredraw = 0;
    }

    idata[nimages].currw = idata[nimages].currh = -1;
  }

  nimages++;

  if (!ulen)
  {
    /* Since we aren't starting a fetch for this image, must call image_update_image  */
    /* on it to make sure any required reformats etc. (e.g. because a width or height */
    /* but not both was specified in the HTML) are carried out.                       */

    image_update_image(b, nimages - 1, NULL);
  }

  /* Reflect any change of status */

  toolbars_update_status(b, Toolbars_Status_GetPics);
  toolbars_set_button_states(b);

  /* Finished. */

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_new_image: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_process_null()                          */
/*                                               */
/* The main engine of the image loading system.  */
/* Allows up to choices.maximages images to be   */
/* simultaneously fetched. Handles redraw,       */
/* memory allocation, out-of-memory fetch stops, */
/* and so-forth.                                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

_kernel_oserror * image_process_null(browser_data * b)
{
  _kernel_oserror * e;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_process_null: Called\n");
  #endif

  /* If there are not maximages images being fetched and there are still  */
  /* images which have not started fetching, then start a fetch for one.  */
  /* Then do a 'get next chunk' on the next image with a fetch going      */
  /* on. Cause a screen update as appropriate.                            */

  if (nimages && b->fetch_status != BS_DATAFETCH)
  {
    int image, fetches;

    fetches = image_count_fetches(b);

    if (lastimage < 0 || lastimage >= nimages) lastimage = nimages - 1;

    image = lastimage;

    /* For all images up to and including the last one dealt with in a */
    /* null event, initiate fetches if they haven't already started.   */

    do
    {
      image ++;
      if (image >= nimages) image = 0;

      if (
           !idata[image].handle        &&
           !idata[image].fetched       &&
           !idata[image].delayed       &&
           idata[image].xref < 0       &&
           fetches < choices.maximages
         )
      {
        e = html_get(ddata + image_data_offset(b,image), /* Document to fetch                */
                     NULL,                               /* Pointer to any extra data        */
                     (int *) &idata[image].handle,       /* Place to return the fetch handle */
                     URL_Method_http_GET,                /* Fetch method                     */

                     #ifndef SINGLE_USER
                       user.name,                        /* User name for MailServ, if using */
                     #else                               /* a multiuser version              */
                       NULL,
                     #endif

                     0,                                  /* 0 = Don't parse fetched data     */
                     !b->reloading);                     /* 1 = Proxy can be used, 0 = can't */

        if (e)
        {
          /* Don't report it; countless 'I've failed' reports for */
          /* the very beginning of a fetch are not very helpful.  */
          /* Particularly, 'file not found' for file fetches, can */
          /* be major pain.                                       */

          image_abandon(b, image);
        }
      }
    }
    while (image != lastimage && !idata[image].handle);

    lastimage = image;

    /* If the image we're now on has a fetch handle... */

    if (idata[image].handle)
    {
      char buffer[10240];

      /* ...And a fetch has already started... */

      if (idata[image].started)
      {
        int done, bytes;

        /* ...Then get the next chunk of data. If the fetch */
        /* call gives an error, abandon the image fetch and */
        /* exit.                                            */

        e = fetch_get_raw_data(NULL,                /* The browser_data structure */
                               idata[image].handle, /* Fetch handle */
                               buffer,              /* Buffer for fetched data */
                               sizeof(buffer),      /* Buffer's size */
                               &done,               /* 'done' = 1 for fetch completed, else 0 */
                               &bytes);             /* Number of bytes put into buffer */

        if (e)
        {
          image_abandon(b, image);
          return e;
        }

        idata[image].bytesgot += bytes;

        /* If the fetch_get_raw_data call resulted in data being fetched, */
        /* send that to the image library and reflect the fetched data in */
        /* the status bar.                                                */

        if (bytes)
        {
          e = image_load_chunk(b, image, buffer, bytes);

          if (e)
          {
            image_abandon(b, image);
            return e;
          }

          toolbars_update_progress(b);
        }

        /* If the fetch has finished, update everything as appropriate */

        if (done)
        {
          BBox end;

          html_close(idata[image].handle);

          idata[image].handle  = 0;
          idata[image].fetched = 1;
          idata[image].success = 1;

          if (idata[image].istore)
          {
            #ifdef STRICT_PARSER

              /* Report any errors as Continue-only in strict parser mode */

              e = idata[image].istore -> EndLoad(idata[image].istore, &end);

              if (e)
              {
                e->errnum = Utils_Error_Custom_Message;
                show_error_ret(e);
              }

            #else

              /* If not in Strict mode, ignore any errors */

              idata[image].istore -> EndLoad(idata[image].istore, &end);

            #endif
          }

          /* If the image is a background image and the library has */
          /* dealt with it (this is assumed to be the case if the   */
          /* library has assigned a width to the image) then get    */
          /* the background text colour from the library on the     */
          /* basis of the image's contents. Else, use the plain     */
          /* background colour.                                     */
          /*                                                        */
          /* Note that it is not until the call to BGCol that       */
          /* ImageLib actually turns the background image into a    */
          /* sprite in the current colour depth, so if this isn't   */
          /* called, the sprite will plot slowly resulting in       */
          /* (typically) painfully slow redraws.                    */

          if (image == idata[image].owner->background_image)
          {
            b->antialias_colour = redraw_backcol(b);

            if (idata[image].istore->width > 0)
            {
              #ifdef STRICT_PARSER

                /* Report any errors as Continue-only in strict parser mode */

                e = idata[image].istore -> BGCol(idata[image].istore,
                                                 &b->antialias_colour,
                                                 b->background_colour != -1 && b->use_source_cols);

                if (e)
                {
                  e->errnum = Utils_Error_Custom_Message;
                  show_error_ret(e);
                }

              #else

                /* If not in Strict mode, ignore any errors */

                idata[image].istore -> BGCol(idata[image].istore,
                                             &b->antialias_colour,
                                             b->background_colour != -1 && b->use_source_cols);

              #endif
            }
          }

          image_update_image(b, image, &end);
        }
      }
      else
      {
        /* In this block of code, the current image has had a fetch */
        /* started for it, but hasn't fetched any data yet - so we  */
        /* haven't (for example) got a full header for the request. */

        int       remn, sofar, waiting;
        int       realsize;
        HStream * tptr;
        void    * store;

        /* Go in with no store; only interested in new data, as  */
        /* anything fetched so far goes into the ddata block. */

        store = NULL;

        /* At this stage, must put the data fetched so far back into the  */
        /* store. This is because headers may come in over several        */
        /* passes through the fetcher - if we give the fetcher an empty   */
        /* block each time, it won't be able to see the whole header      */
        /* block and the fetch fails (well, the image library keeps       */
        /* waiting for something to happen). At the time of writing this  */
        /* is demonstrated by the images at http://www.debenhams.co.uk/'. */

        if (idata[image].isize)
        {
          int p = image_data_offset(b, image) + idata[image].ualloc;

          /* Complain if the flex allocation fails, else copy the data across */

          if (!flex_alloc(&store, idata[image].isize))
          {
            image_abandon(b, image);

            return NULL;
          }
          else
          {
            #ifdef TRACE
              flexcount += idata[image].isize;
              if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
            #endif

            memcpy(store, ddata + p, idata[image].isize);
          }
        }

        #ifdef TRACE_FETCH_STORE

          if (store)
          {
            int  offset;
            int  size = flex_size(&store);
            char cha;
            int  nl   = 0;

            Printf("Image %d pre-fetch store:\n\n",image);

            for (offset = 0; offset < size; offset ++)
            {
              cha = ((char *) store)[offset];

              if (cha > 31) Printf("%c",cha);
              if (cha == 13 || cha == 10)
              {
                if (!nl)
                {
                  Printf("\n");
                  nl = 1;
                }
                else nl = 0;
              }
            }

            Printf("\nEnd of store\n\n");
          }

        #endif

        /* Call the fetch routine */

        e = html_get_next_token(b,
                                idata[image].handle,
                                &remn,
                                &sofar,
                                &tptr,
                                &waiting,
                                &store,
                                ddata + image_data_offset(b, image),
                                1);

        if (e)
        {
          /* If there's an error, free the store and ditch the image */

          if (store)
          {
            #ifdef TRACE
              flexcount -= flex_size(&store);
              if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
            #endif

            flex_free(&store);
          }

          image_abandon(b, image);

          #ifdef TRACE
            if (tl & (1u<<15)) Printf("image_process_null: Exitting with error\n");
          #endif

          return e;
        }

        #ifdef TRACE_FETCH_STORE

          if (store)
          {
            int  offset;
            int  size = flex_size(&store);
            char cha;
            int  nl   = 0;

            Printf("Image %d post-fetch store:\n\n",image);

            for (offset = 0; offset < size; offset ++)
            {
              cha = ((char *) store)[offset];

              if (cha > 31) Printf("%c",cha);
              if (cha == 13 || cha == 10)
              {
                if (!nl)
                {
                  Printf("\n");
                  nl = 1;
                }
                else nl = 0;
              }
            }

            Printf("\nEnd of store\n\n");
          }

        #endif

        /* How much data was added? */

        if (store) realsize = flex_size(&store) - idata[image].isize; /* We already had 'isize' bytes before the fetcher was entered */
        else       realsize = 0;

        /* If we've got data in the local store, copy it to the image store. */

        if (realsize >= 0)
        {
          int p, oldsize;

          /* oldsize holds the whole image data block size (for all images) */

          oldsize = flex_size((flex_ptr) &ddata);

          /* Allocate 'size' extra bytes, doing the relevant error handling */
          /* code if the allocation fails                                   */

          if (!flex_extend((flex_ptr) &ddata, oldsize + realsize))
          {
            #ifdef TRACE
              flexcount -= flex_size(&store);
              if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
            #endif

            flex_free(&store);

            image_abandon(b,image);

            erb.errnum = Utils_Error_Custom_Message;

            StrNCpy0(erb.errmess,
                     lookup_token("NoMemImg:There is not enough free memory for any new images (%0).",
                                  0,
                                  "2"));
            return &erb;
          }

          #ifdef TRACE

          else
          {
            flexcount += realsize;
            if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
          }

          #endif

          /* p points to the first byte after the data used by this image */

          p = image_data_offset(b, image) + idata[image].alloc;

          /* Move all data from p up to the end of the block (as was, hence */
          /* oldsize) along the block by size bytes, effectively creating   */
          /* a gap of that many bytes just after this image's data. If p    */
          /* = oldsize, this was the last image in the block and there's    */
          /* no data above it to move (so don't do the memmove).            */

          if (p != oldsize) memmove(ddata + p + realsize, ddata + p, oldsize - p);

          /* Copy the new data into that gap */

          memcpy(ddata + p, ((char *) store) + idata[image].isize, realsize);

          /* Update the image's image data and total data pointers */

          idata[image].alloc += realsize;
          idata[image].isize += realsize;
        }

        /* Act according to the fetch status */

        switch (waiting)
        {
          case 1: break; /* Nothing happened yet, or the HTTP response header is still coming in */
          case 2: break; /* Redirected, we can ignore and wait for the fetcher to cope */
          case 0: /* Not awaiting delivery (shouldn't happen in this context, but here 'just in case') */
          case 3: /* We have some data */
          {
            int i, n, n1;

            /* Flag that a fetch has started to happen */

            idata[image].started = 1;

            /* Set 'n' to the size of data that was fetched */

            if (store) n = flex_size(&store);
            else       n = 0;

            /* Increment the 'bytes so far' counter for the image */

            idata[image].bytesgot += n;

            /* Push the data into the library, in chunks defined by */
            /* the size of 'buffer' (allocated as a char[] above).  */

            i = 0;

            while (n)
            {
              n1 = n;

              if (n1 > sizeof(buffer)) n1 = sizeof(buffer);
              memcpy(buffer, (char *) store + i, n1);

              i += n1;
              n -= n1;

              e = image_load_chunk(b, image, buffer, n1);

              if (e)
              {
                /* If there's an error, free the store and ditch the image */

                if (store)
                {
                  #ifdef TRACE
                    flexcount -= flex_size(&store);
                    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
                  #endif

                  flex_free(&store);
                }

                image_abandon(b, image);

                #ifdef TRACE
                  if (tl & (1u<<15)) Printf("image_process_null: Exitting with error\n");
                #endif

                return e;
              }
            }

            /* Get rid of any accumulated data and update the status bar */

            image_remove_data(b, image);
            toolbars_update_progress(b);
          }
          break;
        }

        /* Free the temporary store, if it was used */

        if (store)
        {
          #ifdef TRACE
            flexcount -= flex_size(&store);
            if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
          #endif

          flex_free(&store);
        }

        /* Closure of long 'if' that deals with either continuing  */
        /* to fetch data for an image, or getting the first chunk  */
        /* of data for a fetch. (The code immediately above is for */
        /* getting the first chunk).                               */
      }
      /* Closure of long 'if' that sees if a fetch has started */
      /* for an image. (The code immediately above excutes if  */
      /* it has, else a fetch is started).                     */
    }

    /* Closure of long 'if' that checks to see if there are */
    /* images to be fetched, and that the fetch status is   */
    /* not BS_DATAFETCH (i.e. other fetch activity has      */
    /* ceased).                                             */
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_process_null: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_delete_image_entry()                    */
/*                                               */
/* Deletes an image_info structure from the      */
/* array of structures. The cross reference      */
/* image numbers of subsequent images are        */
/* adjusted appropriately.                       */
/*                                               */
/* The idata flex block is not shrunk; this is   */
/* expected to be done externally, to avoid      */
/* possibly freeing many small blocks            */
/* consecutively (which can be slow with flex).  */
/*                                               */
/* Parameters: Image number to remove.           */
/*************************************************/

static void image_delete_image_entry(int image)
{
  browser_data * b;
  int            i;

  nimages--;

  if (lastimage == image) lastimage--;
  if (lastimage >= nimages) lastimage = nimages - 1;

  /* Only need to alter xref numbers or move flex data */
  /* around if this wasn't the last image in the block */

  if (image < nimages) /* (Not 'nimages - 1' as nimages has been decremented by this point) */
  {
    memmove(&idata[image], &idata[image + 1], sizeof(image_info) * (nimages - image));

    for (i = image; i < nimages; i++)
    {
      #ifdef TRACE
        if (idata[i].xref == image) Printf("JUST DELETED IMAGE %d WHICH WAS XREF'D AND SHOULDN'T HAVE BEEN\n");
      #endif

      /* Did this cross reference an image which will have changed number? */

      if (idata[i].xref > image) idata[i].xref--;

      /* Did this have a filler function registered for it? */

      if (idata[i].hadfiller)
      {
        /* If so, then provided the filler function is still available, */
        /* reregister with the new image number.                        */

        if (!idata[i].istore->RegisterFiller)
        {
          idata[i].hadfiller = 0;
        }
        else
        {
          show_error_ret(image_register_filler(i));
        }
      }
    }
  }

  /* Need to also alter browser_data structures' background image */
  /* number fields.                                               */

  b = last_browser;

  while (b)
  {
    if (b->background_image > image) b->background_image--;

    b = b->previous;
  }
}

/*************************************************/
/* image_discard()                               */
/*                                               */
/* Discards all images for a given browser       */
/* window, freeing the memory and shutting down  */
/* any active fetches.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

_kernel_oserror * image_discard(browser_data * b)
{
  int newsize, i, xref;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("\nimage_discard: Called for %p\n\n", b);
  #endif

  if (!nimages) return NULL;

  /* See if any images cross reference those in the browser */
  /* for which images will be discarded. If so, 'move' the  */
  /* cross referencee over to be owned by the cross         */
  /* referencer.                                            */

  _swix(Hourglass_Start, _IN(0), 1);

  #ifdef TRACE
    if (tl & (1u<<15))
    {
      Printf("image_discard: First pass, checking for cross referencees owned by %p\n", b);
      Printf("               There are %d images before this pass.\n\n", nimages);
    }
  #endif

  for (i = nimages - 1; i >= 0; i--) /* Going backwards helps out flex */
  {
    xref = idata[i].xref;

    if (idata[i].owner != b && xref >= 0 && idata[xref].owner == b)
    {
      /* Keep the xref image, since that holds the real data and */
      /* is doing all the fetching, etc. - it works out as less  */
      /* effort overall (especially in the second pass below) to */
      /* delete the cross referencing image. This may seem a bit */
      /* odd as though we've been called to delete the images    */
      /* owned by browser 'b', in the first pass we end up       */
      /* deleting structures owned by completely different       */
      /* browsers...                                             */

      #ifdef TRACE
        if (tl & (1u<<15)) Printf("image_discard: Transferring cross referencer %d to cross referencee %d\n", i, xref);
      #endif

      idata[xref].owner      = idata[i].owner;
      idata[xref].x          = idata[i].x;
      idata[xref].y          = idata[i].y;

      idata[xref].token      = idata[i].token;

      idata[xref].currw      = idata[i].currw;
      idata[xref].currh      = idata[i].currh;
      idata[xref].xref       = -1;

      idata[xref].delayed    = idata[i].delayed;

      idata[xref].fixedsize  = idata[i].fixedsize;
      idata[xref].background = idata[i].background;

      idata[xref].hadfirst   = idata[i].hadfirst;
      idata[xref].canredraw  = idata[i].canredraw;
      idata[xref].priority   = idata[i].priority;

      /* Did the image we're transferring a new owner to have */
      /* RegisterFiller called for it, with the old owner?    */

      if (idata[xref].hadfiller)
      {
        /* If an image became a background, ImageLib may have withdrawn the */
        /* RegisterFiller function...                                       */

        if (!idata[xref].istore->RegisterFiller)
        {
          idata[xref].hadfiller = 0;
        }
        else
        {
          /* Recall the RegisterFiller function with the new owner details */

          show_error_ret(image_register_filler(xref));
        }
      }

      /* If this is a background image for the cross referencer... */

      if (i == idata[i].owner->background_image)
      {
        /* Update the browser_data 'background_image' field to point to the */
        /* new cross reference image number.                                */

        idata[i].owner->background_image = xref;
      }

      /* If the cross referencee was being fetched, may need to */
      /* claim fetch nulls in the cross referencer to finish    */
      /* the job.                                               */

      if (idata[xref].handle && !idata[i].owner->fetch_handler)
      {
        fetchpage_claim_nulls(idata[i].owner);
        toolbars_update_status(idata[i].owner, Toolbars_Status_GetPics);
      }

      /* Delete the cross referencer */

      #ifdef TRACE
        if (tl & (1u<<15)) Printf("image_discard: Deleting entry owned by %p that cross references image %d\n", idata[i].owner, idata[i].xref);
      #endif

      image_remove_all_data(idata[i].owner, i);
      image_delete_image_entry(i);
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15))
    {
      Printf("\nimage_discard: First pass complete, there are now %d images\n", nimages);
      Printf("               Second pass, removing remaining images owned by %p\n\n", b);
    }
  #endif

  /* Now delete any remaining images owned by the given browser */

  for (i = nimages - 1; i >= 0; i--) /* Going backwards helps out flex */
  {
    if (idata[i].owner == b)
    {
      /* If the image was being fetched, close that session */

      if (idata[i].handle)
      {
        html_close(idata[i].handle);
        idata[i].handle = 0;
      }

      if (idata[i].istore)
      {
        #ifdef STRICT_PARSER

          /* Report any errors as Continue-only in strict parser mode */

          _kernel_oserror * e;

          e = idata[i].istore -> Delete(idata[i].istore);

          if (e)
          {
            e->errnum = Utils_Error_Custom_Message;
            show_error_ret(e);
          }

        #else

          /* If not in Strict mode, ignore any errors */

          idata[i].istore -> Delete(idata[i].istore);

        #endif
      }

      image_remove_all_data(b, i);
      image_delete_image_entry(i);
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("\nimage_discard: Second pass complete, there are now %d images\n\n", nimages);
  #endif

  /* If there is an animation handler present, but no animated */
  /* images remain, remove that handler.                       */

  if (animhandler)
  {
    int found = 0;

    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_discard: There is an animation handler present; checking to see if it can be removed\n");
    #endif

    for (i = 0; i < nimages; i++)
    {
      if (idata[i].istore && idata[i].istore->animated)
      {
        found = 1;
        break;
      }
    }

    if (!found)
    {
      #ifdef TRACE
        if (tl & (1u<<15)) Printf("image_discard: Removing animation handler\n\n");
      #endif

      deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) image_animate_images, NULL);
      animhandler = 0;
    }

    #ifdef TRACE

      else
      {
        if (tl & (1u<<15)) Printf("image_discard: The handler is still required\n\n");
      }

    #endif
  }

  /* Ensure the image_info data block matches the size of the images now present */

  newsize = nimages * sizeof(image_info);

  #ifdef TRACE
    {
      int oldsize;

      oldsize    = flex_size((flex_ptr) &idata);
      flexcount -= (oldsize - newsize);

      if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
    }
  #endif

  if (idata)
  {
    if (newsize) flex_extend((flex_ptr) &idata, newsize);
    else         flex_free((flex_ptr) &idata);
  }

  _swix(Hourglass_Off, 0);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_discard: Successful\n\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_refetch()                               */
/*                                               */
/* Reloads an image, redrawing as required and   */
/* setting the priority flag as asked.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images;           */
/*                                               */
/*             The image number;                 */
/*                                               */
/*             Value to set Priority flag to (so */
/*             that if 'show foreground images'  */
/*             is turned off, the image will     */
/*             override it);                     */
/*                                               */
/*             1 to redraw the image, 0 to not   */
/*             bother (e.g. for many images,     */
/*             may want to redraw the whole      */
/*             window instead).                  */
/*************************************************/

static void image_refetch(browser_data * b, int image, int priority, int redraw)
{
  int i;

  /* Sanity check */

  if (image < 0 || image >= nimages || !b) return;

  /* Use the cross referenced base image if necessary */

  if (idata[image].xref >= 0) image = idata[image].xref;

  if (
       (
         !idata[image].fetched &&  /* If the image isn't fetched...  */
         idata[image].delayed      /* ...and was flagged as delayed, */
       )
       ||
       (
         idata[image].fetched  &&  /* or has fetched but...       */
         !idata[image].success &&  /* ...not successfully, and... */
         !idata[image].istore      /* ...it has no ImageLib entry */
       )
     )
  {
    /* If the image says it isn't fetched, reset its 'bytesgot' counter */

    if (!idata[image].fetched) idata[image].bytesgot = 0;

    /* Set up the flags */

    idata[image].success  = 0;
    idata[image].delayed  = 0;
    idata[image].fetched  = 0;
    idata[image].priority = priority;

    /* If we deleted the ImageLib entry, need to recreate it */

    if (!idata[image].istore && idata[image].xref == -1)
    {
      idata[image].istore = NewImage(NULL, image == b->background_image ? IMAGE_FAST : 0);
    }

    /* Now handle the fetching */

    if (!b->fetch_handler)
    {
      fetchpage_claim_nulls(b);
      toolbars_update_status(b, Toolbars_Status_GetPics);
    }
  }
  else
  {
    /* If the image is already loaded, just redraw it */

    idata[image].priority = priority;
    idata[image].hadfirst = 0;

    if (redraw) image_update_image(b, image, NULL);
  }

  /* Deal with all cross referencers of the base image */

  for (i = 0; i < nimages; i++)
  {
    if (idata[i].xref == image)
    {
      idata[i].priority = priority;
      idata[i].hadfirst = 0;

      if (redraw) image_update_image(b, i, NULL);
    }
  }
}

/*************************************************/
/* image_reload()                                */
/*                                               */
/* Reloads an image, redrawing as required and   */
/* setting the priority flag so that even if a   */
/* browser has 'show foreground images' turned   */
/* off, the reloading image (and all that cross  */
/* reference it) will still be shown.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images;           */
/*                                               */
/*             Address of the token representing */
/*             the image.                        */
/*************************************************/

void image_reload(browser_data * b, HStream * token)
{
  int image = image_get_token_image_xref(b, token);

  if (image < 0) return;

  image_refetch(b, image, 1, 1);
}

/*************************************************/
/* image_restart_fetches()                       */
/*                                               */
/* If image loading has been suspended for any   */
/* reason, it can be resumed by calling this     */
/* function. Any images already loaded will be   */
/* reshown. The priority flag for images is not  */
/* set, so the browser must be set to display    */
/* foreground images for anything to show up.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images;           */
/*                                               */
/*             1 to restart foreground fetches,  */
/*             else 0;                           */
/*                                               */
/*             1 to restart background fetches,  */
/*             else 0.                           */
/*************************************************/

void image_restart_fetches(browser_data * b, int foreground, int background)
{
  if (nimages)
  {
    int image;

    /* Loop round all the images */

    for (image = 0; image < nimages; image++)
    {
      /* If an image has not been fetched and is flagged as being */
      /* delayed, or an image has been fetched but has not been   */
      /* flagged as succesfully fetched and has not been dealt    */
      /* with by the image library (the istore field is NULL),    */
      /* then clear all the fetch flags for the image so that it  */
      /* will later be fetched.                                   */

      if (idata[image].owner == b)
      {
        /* Only fetch foreground or background images if asked to */

        if (
             (idata[image].background  && background) ||
             (!idata[image].background && foreground)
           )
           image_refetch(idata[image].owner, image, 0, 0);
      }
    }
  }
}

/*************************************************/
/* image_update_area()                           */
/*                                               */
/* Updates (redraws) part of an image, which     */
/* will reflect any new data fetched for it if   */
/* the whole image hasn't been fetched yet.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The left hand X window coordinate */
/*             in OS units for the image;        */
/*                                               */
/*             The bottom Y window coordinate in */
/*             OS units for the image;           */
/*                                               */
/*             A BBox defining a rectangle       */
/*             relative to X and Y which is the  */
/*             area to update;                   */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1;                      */
/*                                               */
/*             1 to force a redraw (i.e. clear   */
/*             any previously drawn data first), */
/*             zero to not clear the region.     */
/*************************************************/

static _kernel_oserror * image_update_area(browser_data * b, int x, int y, BBox * ubox,
                                           int image, int redraw)
{
  _kernel_oserror       * e;
  WimpRedrawWindowBlock   r;
  int                     more, sx, sy;
  int                     xref;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_area: Called for image %d\n",image);
  #endif

  /* If the image cannot be redrawn yet, don't redraw it... */

  if (!idata[image].canredraw)
  {
    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_update_area: Image %d can't be redrawn yet (canredraw = 0)\n",image);
    #endif

    return NULL;
  }

  r.window_handle = b->window_handle;

  r.visible_area  = *ubox;

  r.visible_area.xmin += x;
  r.visible_area.ymin += y;
  r.visible_area.xmax += x;
  r.visible_area.ymax += (y + 4);

  /* Ensure that 'xref' holds the number of the given image, which may well */
  /* cross reference another, 'image', that has the actual render data.     */

  xref = image;
  if (idata[image].xref >= 0) image = idata[image].xref;

  /* If the image may acquire a mask during rendering, even if it is not    */
  /* initially transparent, need to force the area to redraw to clear first */

  if (
       idata[image].istore->mutable_mask ||
       redraw
     )
     wimp_force_redraw(r.window_handle,
                       r.visible_area.xmin,
                       r.visible_area.ymin,
                       r.visible_area.xmax,
                       r.visible_area.ymax);
  else
  {
    /* Otherwise, do a less flickery non-clearing update */

    wimp_update_window(&r, &more);

    sx = coords_x_toscreen(x, &r);
    sy = coords_y_toscreen(y, &r);

    while(more)
    {
      if (
           b->show_foreground                     ||
           b->displayed == Display_External_Image ||
           idata[xref].priority
         )
      {
        browser_redrawing = b;
        image_redrawing   = xref;

        e = idata[image].istore -> Render(idata[image].istore,
                                          sx,
                                          sy,
                                          100,
                                          idata[xref].currw,
                                          idata[xref].currh);

        browser_redrawing = NULL;
        image_redrawing   = -1;

        #ifdef STRICT_PARSER

          if (e)
          {
            e->errnum = Utils_Error_Custom_Message;
            show_error_ret(e);
          }

        #endif
      }

      #ifdef ANTI_TWITTER

        if (!printing) anti_twitter(&r);

      #endif

      wimp_get_rectangle(&r,&more);
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_area: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_update_image()                          */
/*                                               */
/* Update the specified image number on screen,  */
/* including all which cross reference it. If    */
/* the image has changed size on screen then     */
/* start a reformat from the first occurrence    */
/* (i.e. smallest token number), otherwise just  */
/* do an update on the relevant rectangle for    */
/* each occurrence.                              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images;           */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1;                      */
/*                                               */
/*             Pointer (which may be NULL) to a  */
/*             BBox into which the image's size  */
/*             will be returned.                 */
/*************************************************/

static _kernel_oserror * image_update_image(browser_data * b, int image, BBox * box)
{
  /* Update the specified image number on the screen, including all       */
  /* which xref to it. If the image has changed size on screen then start */
  /* a reformat from the first occurrence (ie the smallest token number), */
  /* otherwise just do an update of the relevant rectangle for each       */
  /* occurrence.                                                          */

  int  i, actual;
  BBox whole, partial;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_image: Called for image %d\n",image);
  #endif

  /* If a BBox is passed in here, then the image will be redrawn in that */
  /* specific region. Otherwise, the region is derived from the image's  */
  /* size. The BBox 'whole' is used for the latter case, so by setting   */
  /* 'box' to the address of this if a BBox was *not* given, we can then */
  /* transparently use this elsewhere and update either the requested    */
  /* area or the whole image without needing special case code.          */
  /*                                                                     */
  /* If the image's 'hadfirst' flag is clear, it hasn't been through     */
  /* this function yet. In that case, want to ensure that the whole      */
  /* image is redrawn to clear any placeholder that will be plotted in   */
  /* its place; otherwise, only the part of the placeholder that         */
  /* corresponds to the requested redraw box will be cleared.            */

  if (!box || !idata[image].hadfirst) box = &whole;

  /* Now ensure that 'image' is the image with the render information, as */
  /* the rest of the code has to deal with all xref images to this one    */
  /* anyway.                                                              */
  /*                                                                      */
  /* So 'actual' is now the image number given to image_update_image, and */
  /* 'image' is the number of the image with associated render data; this */
  /* may be the same as 'actual' or may be equal to the contents of its   */
  /* 'xref' field.                                                        */

  actual = image;
  if (idata[image].xref >= 0) image = idata[image].xref;

  /* If there appears to be no data for the image, or it is less than */
  /* one pixel wide (i.e. not displayable for whatever reason), exit. */

  if (!idata[image].istore || (idata[image].istore && idata[image].istore->width < 1)) return NULL;

  /* Check if the image has become animated */

  if (idata[image].istore->animated)
  {
    /* If the image is animated and we haven't got a null handler */
    /* for animations, install one now.                           */

    if (!animhandler)
    {
      register_null_claimant(Wimp_ENull, (WimpEventHandler *) image_animate_images, NULL);
      animhandler = 1;
    }
  }

  if (idata[image].istore -> RegisterFiller && !idata[image].hadfiller)
  {
    show_error_ret(image_register_filler(image));
  }

  /* When the image library gets some data for an image it will fill */
  /* in the width_os and height_os fields of the istore structure    */
  /* that 'image' points to. If the HTML source didn't specify a     */
  /* size for this or cross referencing images and the size is not   */
  /* the same as stored in currw / currh, flag that a reformat is    */
  /* needed for this image.                                          */

  if (
       !idata[actual].hadfirst             &&
       idata[image].istore->width_os  >= 0 &&
       idata[image].istore->height_os >= 0
     )
  {
    for (i = 0; i < nimages; i++)
    {
      if (
           (
             idata[i].xref == image ||
             i == image
           )
           && !idata[i].fixedsize
         )
      {
        BBox size;

        /* Ignore any existing currw and currh values - find the image */
        /* size based entirely on the HTML and the image itself        */

        image_get_image_size(b,
                             i,
                             &size,
                             1);

        /* Does the returned value differ from the stored size? If so, */
        /* flag that we must do a reformat for this image. It is up to */
        /* the reformatter to call back to routines in this source     */
        /* file and update currw and currh once the image is           */
        /* incorporated into a line array.                             */

        if (
             size.xmax != idata[i].currw ||
             size.ymax != idata[i].currh
           )
           idata[i].reformat = 1;
      }
    }
  }

  /* If the image is for the background, just refresh the window contents */
  /* with browser_update_window (remembering to update those windows that */
  /* cross reference the same background image, too).                     */

  {
    int            isbackground = 0;
    browser_data * check        = last_browser;

    /* First check if the image is the background of this browser. If */
    /* not then check all browsers.                                   */

    if (actual == b->background_image || image == b->background_image) isbackground = 1;
    else
    {
      while (check && !isbackground)
      {
        if (actual == check->background_image || image == check->background_image) isbackground = 1;

        check = check->previous;
      }
    }

    /* If this is a background, update all instances of the image */
    /* and exit the update routine here.                          */

    if (isbackground)
    {
      for (i = 0; i < nimages; i++)
      {
        if ((i == image || idata[i].xref == image) && idata[image].fetched)
        {
          browser_update_bottom(idata[i].owner, NULL);
        }
      }

      return NULL;
    }
  }

  /* Look through all images for cross references */

  whole.xmin = whole.ymin = 0;

  for (i = 0; i < nimages; i++)
  {
    /* If we're on the current image, or on one which  */
    /* references it, then proceed.                    */

    if (i == image || idata[i].xref == image)
    {
      int       l, x, y;
      int       noplot = 0;
      HStream * tp;
      BBox      ibox;

      /* Set the 'whole' BBox to the size of the base image in OS units */

      image_get_image_size(b, i, &whole, 0);

      /* Ensure the redraw box returned by the image Load call */
      /* (or the BBox 'whole' from above) is scaled to fit the */
      /* actual image's dimensions.                            */
      /*                                                       */
      /* We don't scale if box = &whole, as in that case the   */
      /* bbox is already scaled, or if for some reason the     */
      /* width or height fields in the ImageLib image struct   */
      /* seem to be invalid.                                   */

      if (
           box != &whole                      &&
           idata[image].istore->width_os  > 0 &&
           idata[image].istore->height_os > 0
         )
      {
        partial.xmin = (box->xmin * whole.xmax) / idata[image].istore->width_os;
        partial.ymin = (box->ymin * whole.ymax) / idata[image].istore->height_os;
        partial.xmax = (box->xmax * whole.xmax) / idata[image].istore->width_os;
        partial.ymax = (box->ymax * whole.ymax) / idata[image].istore->height_os;
      }
      else partial = *box;

      /* If the image doesn't have position information, can't plot it */

      if (idata[i].x == -1 && idata[i].y == -1) noplot = 1;

      /* Only continue if the line containing a chunk */
      /* relating to the token which relates to this  */
      /* image (!) can be found.                      */

      l = tokenutils_find_ancestor_line(idata[i].owner, idata[i].token);
      if (l < 0) noplot = 1;

      /* May not be redrawing the image, but might still want to */
      /* reformat the page here if the size has changed.         */

      if (noplot)
      {
        if (!idata[i].hadfirst && !idata[i].fixedsize)
        {
          /* This is the first time we've known how big the image is since it   */
          /* was encountered in the HTML; record it in the image history, then. */

          if (idata[i].istore)
          {
            /* Ignore errors, this isn't vital */

            imghistory_record((const char **) &ddata,
                              image_data_offset(idata[i].owner, i),
                              idata[i].istore->width_os,
                              idata[i].istore->height_os);
          }

          /* Now do the reformat, if the size changed */

          idata[i].hadfirst = 1;

          if (idata[i].reformat)
          {
            idata[i].reformat = 0;

            reformat_format_from(idata[i].owner, l - 1, 0, i);
          }

         /* If we don't need to reformat, we can (potentially!) redraw */

          else idata[i].canredraw = 1;
        }
      }
      else
      {
        x  = idata[i].x;
        y  = idata[i].y;
        tp = idata[i].token;

        if (
             !reformat_get_image_size(idata[i].owner,
                                      tp,
                                      &ibox)
           )
        {
          /* If the image size was obtained without error, */
          /* correct its X and Y coordinates appropriately */

          if (!idata[i].hadfirst)
          {
            int do_redraw = 1;

            idata[i].hadfirst = 1;

            /* Is the image size fixed in the HTML? If not, may need to do a */
            /* reformat.                                                     */

            if (!idata[i].fixedsize)
            {
              /* As above, this is the first time we've known how big the image is */
              /* since it was encountered in the HTML, so record it in the image   */
              /* history.                                                          */

              if (idata[i].istore)
              {
                /* Ignore errors, this isn't vital */

                imghistory_record((const char **) &ddata,
                                  image_data_offset(idata[i].owner, i),
                                  idata[i].istore->width_os,
                                  idata[i].istore->height_os);
              }

              /* If the size changed, do a reformat */

              if (idata[i].reformat)
              {
                idata[i].reformat = 0;
                do_redraw         = 0; /* Reformatting - no extra redraws needed */

                reformat_format_from(idata[i].owner, l - 1, 0, i);
              }

              /* If we don't need to reformat, we can redraw */

              else idata[i].canredraw = 1;
            }

            /* If no reformat was needed, just do a redraw (which clears the  */
            /* background first; see the very large comment earlier on).      */

            if (do_redraw)
            {
              image_update_area(idata[i].owner,
                                x,
                                y,
                                &partial,
                                i,
                                1);
            }
          }

          /* Work out what region to redraw (may only require */
          /* a partial area update)                           */

          else if (partial.ymax < partial.ymin)
          {
            BBox ubox = partial;

            /* If the y max coordinate is less than the y min coordinate, */
            /* redraw from ymin to the top of the image, and from ymax to */
            /* the bottom. That way can miss out a middle section.        */

            ubox.ymin = partial.ymin;
            ubox.ymax = whole.ymax;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);

            ubox.ymax = partial.ymax;
            ubox.ymin = 0;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);
          }
          else if (partial.xmax < partial.xmin)
          {
            BBox ubox = partial;

            /* Similarly, if xmax is less than xmin, draw from xmin to the */
            /* right hand side of the image and from xmax to the left, so  */
            /* a vertical strip in the middle may be untouched.            */

            ubox.xmin = partial.xmin;
            ubox.xmax = whole.xmax;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);

            ubox.xmax = partial.xmax;
            ubox.xmin = 0;

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &ubox,
                              i,
                              0);
          }
          else
          {
            /* Otherwise, redraw the requested box */

            image_update_area(idata[i].owner,
                              x,
                              y,
                              &partial,
                              i,
                              0);
          }

        /* Closure of 'if (!reformat_get_image_size(...))' */
        }

      /* Closure of 'if (noplot)' - code immediately above is */
      /* executed in the 'else' section.                      */
      }

    /* Closure of 'if (i == image || idata[i].xref == image)' */
    }

  /* Closure of loop running round all the images */
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_update_image: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_animate_images()                        */
/*                                               */
/* Checks all images to see if they need         */
/* updating. Designed to be called on null       */
/* events.                                       */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int image_animate_images(int eventcode, WimpPollBlock * block, IdBlock * idb, browser_data * handle)
{
  int i, image, redraw;

  /* Scan round all images */

  for (i = 0; i < nimages; i++)
  {
    /* Find the image with associated render data */

    if (idata[i].xref >= 0) image = idata[i].xref;
    else                    image = i;

    /* Skip this image if it as already been dealt with */

    if (image >= i)
    {
      /* If this image does have such data, is flagged as */
      /* animated, and has a pointer to a NeedRedraw      */
      /* function, proceed.                               */

      if (
           idata[image].istore             &&
           idata[image].istore->animated   &&
           idata[image].istore->NeedRedraw
         )
      {
        BBox extent; // Not actually used yet...!

        #ifdef STRICT_PARSER

          /* Report any errors as Continue-only in strict parser mode */

          _kernel_oserror * e;

          e = idata[image].istore -> NeedRedraw(idata[image].istore, &redraw, &extent);

          if (e)
          {
            e->errnum = Utils_Error_Custom_Message;
            show_error_ret(e);
          }

        #else

          /* If not in Strict mode, ignore any errors */

          idata[image].istore -> NeedRedraw(idata[image].istore, &redraw, &extent);

        #endif

        /* Redraw the image, if required */

        if (redraw) image_update_image(idata[i].owner, i, NULL);
      }
    }
  }

  return 0;
}

/*************************************************/
/* image_register_filler()                       */
/*                                               */
/* Registers the given image and it's owning     */
/* browser with ImageLib. Must be called for     */
/* cross referencing images only.                */
/*                                               */
/* Parameters: The image number to register.     */
/*************************************************/

static _kernel_oserror * image_register_filler(int xref)
{
  #ifdef STRICT_PARSER

    {
      _kernel_oserror * e;

      /* Report any errors as Continue-only in strict parser mode */

      e = idata[xref].istore -> RegisterFiller(idata[xref].istore,
                                               (FillerFunction *) image_fill_background,
                                               idata[xref].owner,
                                               (int *) xref);

      if (e)
      {
        e->errnum = Utils_Error_Custom_Message;
        return e;
      }
    }

  #else

    /* If not in Strict mode, don't report errors */

    idata[xref].istore -> RegisterFiller(idata[xref].istore,
                                         (FillerFunction *) image_fill_background,
                                         idata[xref].owner,
                                         (int *) xref);
  #endif

  idata[xref].hadfiller = 1;

  return NULL;
}

/*************************************************/
/* image_fill_background()                       */
/*                                               */
/* Called as a FillerFunction for ImageLib,      */
/* this redraws the background image. Output     */
/* will probably have been redirected by         */
/* ImageLib at this point.                       */
/*                                               */
/* The way the function is registered should     */
/* leave void * handle pointing to the browser   */
/* who's background will be redrawn, with int *  */
/* i actually being an int (not an int *)        */
/* holding the image number for which the        */
/* function was registered in the first place.   */
/*************************************************/

_kernel_oserror * image_fill_background(void * handle, int * i)
{
  WimpGetWindowStateBlock   state;
  _kernel_oserror         * e        = NULL;
  browser_data            * b        = (browser_data *) handle;

  int                       image;
  int                       ximage   = (int) i;

  int                       noplot   = 0;
  int                       bimage   = -1;
  int                       bgcolour = -1;

  int                       x, y, xorigin, yorigin;
  int                       w, h;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fill_background: Called with %p, %d\n", handle, i);
  #endif

  state.window_handle = b->window_handle;
  e = wimp_get_window_state(&state);
  if (e) return e;

  /* Should never happen, but you never know... */

  if (idata[ximage].xref >= 0) ximage = idata[ximage].xref;

  /* See the top of the file for details on browser_redrawing */
  /* and image_redrawing                                      */

  if (browser_redrawing) b = browser_redrawing;

  if (image_redrawing >= -1) image = image_redrawing;
  else                       image = ximage;

  #ifdef TRACE
    if (tl & (1u<<15))
    {
      Printf("image_fill_background: Decided to use %p, %d\n", b, image);
      Printf("image_fill_background: Image w, h = %d, %d\n",idata[image].currw,idata[image].currh);
    }
  #endif

  /* The image may exist in a table, and we should check what */
  /* background colour to draw if this is the case.           */

  if (idata[image].token->parent)
  {
    table_headdata * head  = idata[image].token->parent;
    table_row      * row   = NULL;
    table_stream   * table = NULL;

    if (head) row   = head->parent;
    if (row)  table = row->parent;

    if (table)
    {
      if (TABLE_HAS_BGCOL(table)) bgcolour = TABLE_BGCOL(table), noplot = 1;

      /* Individual cells can override the table */

      if (TD_HAS_BGCOL(head))     bgcolour = TD_BGCOL(head),     noplot = 1;
    }
  }

  /* We may still have no defined background colour, in which */
  /* case check the main page background.                     */

  if (bgcolour == -1)
  {
    /* Can't plot a background image if there's no such image or */
    /* it's not fully fetched.                                   */

    if (b->background_image >= 0)
    {
      bimage = b->background_image;
      if (idata[bimage].xref >= 0) bimage = idata[bimage].xref;
    }
    else bimage = -1, noplot = 1;

    #ifdef TRACE
      if (tl & (1u<<15))
      {
        if (bimage >= 0) Printf("image_fill_background: Background image = %d\n",bimage);
        else             Printf("image_fill_background: No background image\n");
      }
    #endif

    if (
         !noplot &&
         (
           !idata[bimage].fetched ||
           !idata[bimage].istore
         )
       )
       noplot = 1;

    if (
         !noplot              &&
         idata[bimage].istore &&
         (
           idata[bimage].istore->width_os  <= 0 ||
           idata[bimage].istore->height_os <= 0
         )
       )
       noplot = 1;

    bgcolour = redraw_backcol(b);
  }

  xorigin = -idata[image].x;
  yorigin = -idata[image].y;

  /* Ensure dithered background colours have a dithering */
  /* (ECF) pattern that starts in the right place        */

  e = _swix(OS_SetECFOrigin,
            _INR(0,1),

            -xorigin,
            -yorigin);

  if (e) return e;

  /* Do a blank rectangle if the image is transparent (has a mask) */
  /* or the image itself can't be plotted, or the browser window   */
  /* set up not to plot background images, or we're printing and   */
  /* shouldn't plot backgrounds (we must plot something at this    */
  /* stage!).                                                      */

  if (
       noplot                            ||
       idata[bimage].istore->transparent ||
       !b->show_background               ||
       (
         printing &&
         !printstyle_show_all()
       )
     )
  {
    redraw_set_colour(bgcolour);

    bbc_rectanglefill(0,
                      0,
                      idata[ximage].istore -> width_os,
                      idata[ximage].istore -> height_os);
  }

  /* If there's no background image that can be plotted, exit here */

  if (
       !b->show_background ||
       noplot              ||
       (
         printing &&
         !printstyle_show_all()
       )
     )
  {
    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_fill_background: No background image to plot, so successful (exitting)\n");
    #endif

    return NULL;
  }

  /* Find the scale for the background plotting; if the foreground */
  /* image is scaled, then plotting the background at 1:1 will     */
  /* make it look scaled by the same amount, and things won't      */
  /* match up. We could either prescale the foreground image and   */
  /* then plot into that prescaled image, but this takes a lot     */
  /* of memory, or we could try to stick with a post-scaled image. */
  /* In that case, the background can either be ignored or we can  */
  /* try to scale it down so though it will look blocky and low    */
  /* resolution, it will at least roughly line up with the things  */
  /* around the foreground. This code does the last of the three.  */
  /*                                                               */
  /* Note the faster, simpler routine for scalings of 1:1 - avoids */
  /* floating point.                                               */

  if (
       idata[image].currw != idata[ximage].istore->width_os  ||
       idata[image].currh != idata[ximage].istore->height_os
     )
  {
    BBox  redraw;
    float scalex, scaley;
    float tempx,  tempy;
    float tempw,  temph;
    int   htop;

    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_fill_background: Plotting scaled background\n");
    #endif

    tempx = (float) idata[ximage].istore->width_os, tempy = (float) idata[ximage].istore->height_os;
    tempw = (float) idata[image].currw,             temph = (float) idata[image].currh;

    scalex = tempx / tempw;
    scaley = tempy / temph;

    /* Round the background image's width and height down to pixel boundaries */

    tempw = idata[bimage].istore->width_os;
    temph = idata[bimage].istore->height_os;

    w = ((int) (tempw * scalex + (float) 0.5)) & ~(wimpt_dx() - 1);
    h = ((int) (temph * scaley + (float) 0.5)) & ~(wimpt_dy() - 1);

    /* Work out the coordinates over which to tile the image */

    tempx = - (float) (idata[image].x);

    if (!controls.swap_bars) htop = toolbars_button_height(b) + toolbars_url_height(b);
    else                     htop = toolbars_status_height(b);

    if (htop) htop += wimpt_dy();

    tempy = - (float) idata[image].y
            - (float) htop;

    xorigin = (int) (tempx * scalex + (float) 0.5) + 1;
    yorigin = (int) (tempy * scaley + (float) 0.5) + 1;

    /* Exit if the background size appears to be too small */

    if (w < 1 || h < 1) return NULL;

    redraw.xmin = -((-xorigin) % w);
    redraw.ymin = -h;
    redraw.xmax = idata[ximage].istore->width_os;
    redraw.ymax = idata[ximage].istore->height_os + (yorigin - idata[ximage].istore->height_os) % h - 1;

    idata[bimage].istore -> ModeChange(idata[bimage].istore);

    /* Render the image over the redraw region, with a scaled plot */

    for (y = redraw.ymax - h + 4; y >= redraw.ymin; y -= h)
    {
      for (x = redraw.xmin; x <= redraw.xmax; x += w)
      {
        e = (idata[bimage].istore -> Render(idata[bimage].istore,
                                            x,
                                            y,
                                            100,
                                            w,
                                            h));
        if (e) break;
      }

      if (e) break;
    }

    idata[bimage].istore -> ModeChange(idata[bimage].istore);
  }
  else
  {
    BBox redraw;
    int  htop;

    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_fill_background: Plotting unscaled background\n");
    #endif

    w = (idata[bimage].istore->width_os)  & ~(wimpt_dx() - 1);
    h = (idata[bimage].istore->height_os) & ~(wimpt_dy() - 1);

    xorigin = -idata[image].x;

    if (!controls.swap_bars) htop = toolbars_button_height(b) + toolbars_url_height(b);
    else                     htop = toolbars_status_height(b);

    if (htop) htop += wimpt_dy();

    yorigin = -idata[image].y - htop;

    /* Exit if the background size appears to be too small */

    if (w < 1 || h < 1) return NULL;

    redraw.xmin = -((-xorigin) % w);
    redraw.ymin = -h;
    redraw.xmax = idata[ximage].istore->width_os;
    redraw.ymax = idata[ximage].istore->height_os + (yorigin - idata[ximage].istore->height_os) % h - 1;

    idata[bimage].istore -> ModeChange(idata[bimage].istore);

    /* Render the image over the redraw region, with an unscaled plot */

    for (y = redraw.ymax - h + 4; y >= redraw.ymin; y -= h)
    {
      for (x = redraw.xmin; x <= redraw.xmax; x += w)
      {
        e = (idata[bimage].istore -> Render(idata[bimage].istore,
                                            x,
                                            y,
                                            100,
                                            -1,
                                            -1));
        if (e) break;
      }

      if (e) break;
    }

    idata[bimage].istore -> ModeChange(idata[bimage].istore);
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fill_background: Successful\n");
  #endif

  return e;
}

/*************************************************/
/* image_redraw()                                */
/*                                               */
/* Does a high level redraw of an image, with    */
/* an outline to show where the image should be  */
/* if it isn't all plotted, or a slabbed box to  */
/* display broken or unfetched images.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a RedrawWindowBlock    */
/*             struct which holds information    */
/*             about the current redraw session; */
/*                                               */
/*             Address of the token representing */
/*             the image;                        */
/*                                               */
/*             The X offset in window coords (so */
/*             OS units) of the left hand edge   */
/*             of the image;                     */
/*                                               */
/*             The Y offset in window coords (so */
/*             OS units) of the bottom edge of   */
/*             the image.                        */
/*************************************************/

_kernel_oserror * image_redraw(browser_data * b, WimpRedrawWindowBlock * r, HStream * token, int x, int y)
{
  int image, actual;
  int plotted = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_redraw: Called with token %d\n", token);
  #endif

  /* Get the image number for the given token; 'actual' holds the actual */
  /* image for the given token, which may cross reference 'image', which */
  /* holds the actual render data.                                       */

  image = image_get_token_image_actual(b, token);

  if (image < 0) return NULL;

  actual = image;
  if (idata[actual].xref >= 0) image = idata[actual].xref;

  /* If the image has a fetched width and height, */
  /* and the browser choices specify that images  */
  /* should be shown (or the image itself has a   */
  /* high priority show bit set), display part or */
  /* all of the image.                            */

  if (
       image  >= 0 &&
       actual >= 0 &&
       (
         b->show_foreground                     ||
         b->displayed == Display_External_Image ||
         idata[actual].priority
       )
       && idata[actual].canredraw
       && idata[image].istore             /* Must check istore here, as if width / height are <= 0, */
       && idata[image].istore->width  > 0 /* the image library has no data for the image and will   */
       && idata[image].istore->height > 0 /* not have filled in (e.g.) the Render field. So you'd   */
     )                                    /* get a rather nasty abort trying to call it!            */
  {
    _kernel_oserror * e;

    browser_redrawing = b;
    image_redrawing   = actual;

    e = idata[image].istore -> Render(idata[image].istore,
                                      x,
                                      y,
                                      100,
                                      idata[actual].currw,
                                      idata[actual].currh);

    browser_redrawing = NULL;
    image_redrawing   = -1;

    #ifdef STRICT_PARSER

      if (e)
      {
        e->errnum = Utils_Error_Custom_Message;
        show_error_ret(e);
      }

    #endif

    if (!e) plotted = 1;
  }

  if (!plotted)
  {
    /* If the image was not plotted, either due to error or because there's */
    /* no data yet, plot a placeholder and any present ALT text instead.    */

    BBox         box;
    const char * text;
    HStream    * tp   = NULL;

    image_get_image_size(b, actual, &box, 0);

    box.xmin += x;
    box.ymin += y;
    box.xmax += x - box.xmin;
    box.ymax += y - box.ymin;

    box.xmin &= ~(wimpt_dx() - 1);
    box.ymin &= ~(wimpt_dy() - 1);

    /* If an image, the ALT text wants to be from the actual image, not one */
    /* that might be being cross referenced.                                */

    tp   = (ISOBJECT(tp)) ? token                 : idata[actual].token;
    text = (ISOBJECT(tp)) ? HtmlOBJECTstandby(tp) : tp->text;

    redraw_draw_placeholder(b,
                            r,
                            &box,
                            tp,
                            text);
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_redraw: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_tile_window()                           */
/*                                               */
/* For a given redraw rectangle, tiles the       */
/* background image within that rectangle.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window and image; */
/*                                               */
/*             Pointer to a WimpRedrawWindow-    */
/*             Block structure defining the area */
/*             over which to tile the image;     */
/*                                               */
/*             The X and Y coordinates to take   */
/*             as the tile origin, in OS units.  */
/*                                               */
/* Returns:    1 for success, 0 for failure.     */
/*************************************************/

int image_tile_window(browser_data * b, WimpRedrawWindowBlock * r, int xorigin, int yorigin)
{
  _kernel_oserror * e;
  int               x, y, w, h;
  int               xmin, ymax;
  int               image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_tile_window: Called, background_image = %d\n",b->background_image);
  #endif

  /* Can only tile if there's a background image defined and fully fetched */

  if (b->background_image < 0) return 0;

  image = b->background_image;
  if (idata[image].xref >= 0) image = idata[image].xref;

  if (!idata[image].fetched || !idata[image].istore) return 0;

  /* Round the width and height to pixel boundaries */

  w = idata[image].istore->width_os  & ~(wimpt_dx() - 1);
  h = idata[image].istore->height_os & ~(wimpt_dy() - 1);

  if (w < 1 || h < 1) return 0;

  /* Work out the coordinates over which to tile the image */

  xmin = coords_x_toworkarea(r->redraw_area.xmin,r);
  ymax = coords_y_toworkarea(r->redraw_area.ymax,r);

  xmin -= ((xmin - xorigin) % w);
  ymax -= ((ymax - yorigin) % h) + 1;

  xmin = coords_x_toscreen(xmin, r);
  ymax = coords_y_toscreen(ymax, r);

  /* Do a blank rectangle if the image is transparent (has a mask) */

  if (idata[image].istore->transparent)
  {
    redraw_set_colour(redraw_backcol(b));

    bbc_rectanglefill(r->redraw_area.xmin,
                      r->redraw_area.ymin,
                      r->redraw_area.xmax - r->redraw_area.xmin,
                      r->redraw_area.ymax - r->redraw_area.ymin);
  }

  /* Render the image over the redraw region */

  for (y = ymax - h + 4; y >= r->redraw_area.ymin - h; y -= h)
  {
    for (x = xmin; x <= r->redraw_area.xmax; x += w)
    {
      browser_redrawing = b;
      image_redrawing   = image;

      e = idata[image].istore -> Render(idata[image].istore,
                                        x,
                                        y,
                                        100,
                                        w,
                                        h);

      browser_redrawing = NULL;
      image_redrawing   = -1;

      #ifdef STRICT_PARSER

        if (e)
        {
          e->errnum = Utils_Error_Custom_Message;
          show_error_ret(e);

          return 0;
        }

      #else

        if (e) return 0;

      #endif
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_tile_window: Successful\n");
  #endif

  return 1;
}

/*************************************************/
/* image_mark_as_redrawable()                    */
/*                                               */
/* Flags that an image may now be redrawn (this  */
/* is generally called because an unsized image  */
/* has found its size, asked for a reformat, and */
/* that reformat is about to take place).        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number.                 */
/*************************************************/

void image_mark_as_redrawable(browser_data * b, int image)
{
  /* Mark the image as redrawable */

  if (idata[image].owner == b) idata[image].canredraw = 1;

  return;
}

/*************************************************/
/* image_token_reformatted()                     */
/*                                               */
/* Called by a reformatter to signal an image is */
/* locked in a line array. Sets the image's      */
/* canredraw flag and locs the image's size.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the image.           */
/*************************************************/

void image_token_reformatted(browser_data * b, HStream * token)
{
  int  image;
  int  xref;

  /* Get the image number */

  image = image_get_token_image_actual(b, token);
  if (image < 0 || image > nimages) return;

  if (idata[image].xref >= 0) xref = idata[image].xref;
  else                        xref = image;

  /* Lock the image size */

  image_lock_image_size(b, image);

  /* Exit if the image has no known real size yet - even if its size */
  /* is fixed in the HTML, there's no point marking it as redrawable */
  /* if there is no image data to redraw...!                         */

  if (
       !idata[xref].istore                ||
       idata[xref].istore->width_os  <= 0 ||
       idata[xref].istore->height_os <= 0
     )
     return;

  /* We do have some data and the reformatter has fixed the image in */
  /* the line array, so mark it as redrawable now.                   */

  image_mark_as_redrawable(b, image);

  return;
}

/*************************************************/
/* image_mode_change()                           */
/*                                               */
/* Ensure images are up to date following a mode */
/* change.                                       */
/*************************************************/

_kernel_oserror * image_mode_change(void)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_mode_change: Called\n");
  #endif

  if (nimages)
  {
    int i;

    /* For each image, call the image library's mode change handling function */

    for (i = 0; i < nimages; i++)
    {
      if (idata[i].istore && idata[i].istore->width > 0 && idata[i].istore -> ModeChange)
      {
        #ifdef STRICT_PARSER

          /* Report any errors as Continue-only in strict parser mode */

          _kernel_oserror * e;

          e = idata[i].istore -> ModeChange(idata[i].istore);

          #ifdef TRACE
            if (tl & (1u<<15)) Printf("image_mode_change: Exitting with error\n");
          #endif

          if (e) return e;

        #else

          /* If not in Strict mode, ignore any errors */

          idata[i].istore -> ModeChange(idata[i].istore);

        #endif
      }
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_mode_change: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_data_offset()                           */
/*                                               */
/* Returns the offset into the ddata block of    */
/* the data associated with a given image.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image in          */
/*             question;                         */
/*                                               */
/*             An image number, from 0 to        */
/*             nimages - 1.                      */
/*                                               */
/* Returns:    Pointer to the data associated    */
/*             with that image (as a char *).    */
/*************************************************/

static int image_data_offset(browser_data * b, int image)
{
  int i, count = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_data_offset: Called for image %d, and exitting\n",image);
  #endif

  if (image <= 0) return 0; /* Image 0's data offset will be zero... */

  for (i = 0; i < image; count += idata[i].alloc, i++);

  return count;
}

/*************************************************/
/* image_count_fetches()                         */
/*                                               */
/* Counts how many fetches are being performed   */
/* for a given web page.                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page and images   */
/*             being fetched.                    */
/*                                               */
/* Returns:    The number of image fetches that  */
/*             are currently in progress (this   */
/*             may be zero, of course).          */
/*************************************************/

static int image_count_fetches(browser_data * b)
{
  int i, c = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_fetches: Called\n");
  #endif

  /* there is a fetch handle in their image_info structs. */

  for (i = 0; i < nimages; i++) if (idata[i].handle && idata[i].owner == b) c++;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_fetches: Exitting with %d\n",c);
  #endif

  return c;
}

/*************************************************/
/* image_fetching()                              */
/*                                               */
/* Returns 1 if there are any image fetches      */
/* going on at all, else 0 (faster than using    */
/* image_count_fetches above if you don't care   */
/* how many fetches there are).                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the inquiry.          */
/*                                               */
/* Returns:    1 if there are image fetches in   */
/*             progress, else 0.                 */
/*************************************************/

int image_fetching(browser_data * b)
{
  int i;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fetching: Called\n");
  #endif

  for (i = 0; i < nimages; i++)
  {
    if (idata[i].handle && idata[i].owner == b)
    {
      #ifdef TRACE
        if (tl & (1u<<15)) Printf("image_fetching: Exitting, there is at least 1 fetch in progress\n");
      #endif

      return 1;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fetching: Exitting, no fetches in progress\n");
  #endif

  return 0;
}

/*************************************************/
/* image_fetched()                               */
/*                                               */
/* Determines if an image has been completely    */
/* fetched or not.                               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1.                      */
/*                                               */
/* Returns:    1 if the image has been fetched   */
/*             completely, else 0.               */
/*************************************************/

int image_fetched(browser_data * b, int image)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fetched: Called for image %d\n",image);
  #endif

  if (image >= 0) return (idata[image].fetched);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_fetched: Exitting with 0 (failed)\n");
  #endif

  return 0;
}

/*************************************************/
/* image_token_fetched()                         */
/*                                               */
/* Determines if an image has been completely    */
/* fetched or not, based on a token representing */
/* the image.                                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a token representing   */
/*             the image.                        */
/*                                               */
/* Returns:    1 if the image has been fetched   */
/*             completely, else 0.               */
/*************************************************/

int image_token_fetched(browser_data * b, HStream * token)
{
  int image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_token_fetched: Called for token %p\n",token);
  #endif

  image = image_get_token_image_xref(b, token);

  #ifdef TRACE
    if (tl & (1u<<15))
    {
      Printf("image_token_fetched: Represents image %d\n",image);
      Printf("image_token_fetched: Exitting through image_fetched\n");
    }
  #endif

  return image_fetched(b, image);
}

/*************************************************/
/* image_total_bytes_fetched()                   */
/*                                               */
/* Works out the total number of bytes fetched   */
/* in images so far.                             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*                                               */
/* Returns:    The number of bytes fetched in    */
/*             image data at the moment of       */
/*             calling the function.             */
/*************************************************/

int image_total_bytes_fetched(browser_data * b)
{
  int count = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_total_bytes_fetched: Called\n");
  #endif

  /* Sum the fetched size of all images currently present, */
  /* if there are any to count.                            */

  if (nimages)
  {
    int image;

    for (image = 0; image < nimages; image++)
    {
      if (idata[image].owner == b) count += idata[image].bytesgot;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_total_bytes_fetched: Exitting with %d\n",count);
  #endif

  return count;
}

/*************************************************/
/* image_count_pending()                         */
/*                                               */
/* Counts how many images are waiting to be      */
/* fetched, but have been delayed for whatever   */
/* reason.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images. Any       */
/*             images in child windows are also  */
/*             included in the count.            */
/*                                               */
/* Returns:    The number of images waiting to   */
/*             be fetched, counting any images   */
/*             in any child windows (if present) */
/*************************************************/

int image_count_pending(browser_data * b)
{
  int i, c = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_pending: Called for %p\n",b);
  #endif

  if (b->nchildren)
  {
    for (i = 0; i < b->nchildren; i++)
    {
      c += image_count_pending(b->children[i]);
    }
  }
  else
  {
    for (i = 0; i < nimages; i++)
    {
      if (
           !idata[i].fetched   &&
           !idata[i].delayed   &&
           idata[i].xref < 0   &&
           idata[i].owner == b
         )
         c++;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_pending: Exitting with %d for %p\n",c,b);
  #endif

  return c;
}

/*************************************************/
/* image_count_specific_pending()                */
/*                                               */
/* Counts how many images are waiting to be      */
/* fetched, but have been delayed for whatever   */
/* reason, for just the specified browser (not   */
/* including any child windows).                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*                                               */
/* Returns:    The number of images waiting to   */
/*             be fetched in the given browser.  */
/*************************************************/

int image_count_specific_pending(browser_data * b)
{
  int i, c = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_specific_pending: Called for %p\n",b);
  #endif

  for (i = 0; i < nimages; i++)
  {
    if (
         !idata[i].fetched   &&
         !idata[i].delayed   &&
         idata[i].xref < 0   &&
         idata[i].owner == b
       )
       c++;
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_count_specific_pending: Exitting with %d for %p\n",c,b);
  #endif

  return c;
}

/*************************************************/
/* image_count_delayed()                         */
/*                                               */
/* Returns the number of images which have been  */
/* delayed (could be fetched, but have been set  */
/* up to not fetch yet).                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the images.           */
/*************************************************/

int image_count_delayed(browser_data * b)
{
  int count = 0;

  if (nimages)
  {
    int  i;

    for(i = 0; i < nimages; i++)
    {
      if (
           (
             !idata[i].fetched &&
             idata[i].delayed
           )
           ||
           (
             idata[i].fetched  &&
             !idata[i].success &&
             !idata[i].istore
           )
         )
         count++;
    }
  }

  return count;
}

/*************************************************/
/* image_plot_started()                          */
/*                                               */
/* Returns 1 if a given image in a given browser */
/* has had some plotting done for it (so the     */
/* placeholder box and possibly ALT text will no */
/* longer be visible for it).                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The number of the image.          */
/*                                               */
/* Returns:    1 if the image has been partially */
/*             or completely plotted, else 0.    */
/*************************************************/

int image_plot_started(browser_data * b, int image)
{
  if (image < 0) return 0;

  if (!idata[image].istore) return 0;

  if (!idata[image].canredraw) return 0;

  if (idata[image].istore->width > 0) return 1;
  else                                return 0;
}

/*************************************************/
/* image_token_plot_started()                    */
/*                                               */
/* Returns 1 if a given image in a given browser */
/* has had some plotting done for it (so the     */
/* placeholder box and possibly ALT text will no */
/* longer be visible for it).                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a token representing   */
/*             the image.                        */
/*                                               */
/* Returns:    1 if the image has been partially */
/*             or completely plotted, else 0.    */
/*************************************************/

int image_token_plot_started(browser_data * b, HStream * token)
{
  int image;

  image = image_get_token_image_xref(b, token);

  return image_plot_started(b, image);
}

/*************************************************/
/* image_get_token_image_xref()                  */
/*                                               */
/* Finds the image number associated with a      */
/* given token. The number will be the image     */
/* that actually has the associated image data,  */
/* not just one that cross references this data. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The token address.                */
/*                                               */
/* Returns:    The image number which references */
/*             that token, or -1 if none found.  */
/*************************************************/

static int image_get_token_image_xref(browser_data * b, HStream * token)
{
  int i, found = -1;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_xref: Called\n");
  #endif

  /* If there are any images to deal with... */

  if (nimages)
  {
    /* ...then look through them all, seeing if the */
    /* image is referred to by the given token.     */

    for (i = 0; i < nimages; i++)
    {

      if (idata[i].token == token && idata[i].owner == b)
      {
        found = i;
        break;
      }
    }

    /* If an appropriate image was found, ensure it */
    /* is the one with render data attached, not    */
    /* just a cross reference image.                */

    if (found >= 0)
    {
      if (idata[found].xref >= 0) found = idata[found].xref;
    }
  }

  /* Return the image number, or -1 for failure */

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_xref: Exitting with %d\n", found);
  #endif

  return found;
}

/*************************************************/
/* image_get_token_image_actual()                */
/*                                               */
/* As image_get_token_image, but returns the     */
/* exact image number represented by the given   */
/* token rather than the cross referenced one    */
/* with the pointers to the image data.          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The token address.                */
/*                                               */
/* Returns:    The image number which references */
/*             that token, or -1 if none found.  */
/*************************************************/

static int image_get_token_image_actual(browser_data * b, HStream * token)
{
  int i, found = -1;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_actual: Called\n");
  #endif

  /* If there are any images to deal with... */

  if (nimages)
  {
    /* ...then look through them all, seeing if the */
    /* image is referred to by the given token.     */

    for (i = 0; i < nimages; i++)
    {

      if (idata[i].token == token && idata[i].owner == b)
      {
        found = i;
        break;
      }
    }
  }

  /* Return the image number, or -1 for failure */

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_actual: Exitting with %d\n", found);
  #endif

  return found;
}

/*************************************************/
/* image_lock_image_size()                       */
/*                                               */
/* Ask image_get_image_size for the size of an   */
/* image, making sure currw and currh are -1 so  */
/* that it works the size out from first         */
/* principles, then set that size jn currw and   */
/* currh for future reference.                   */
/*                                               */
/* Designed to be called by the reformatter when */
/* it encounters an image and stores it in a     */
/* line array.                                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Number of the image.              */
/*************************************************/

static void image_lock_image_size(browser_data * b, int image)
{
  BBox size;

  /* Rediscover the image size - so ignore any stored */
  /* values in currw and currh.                       */

  if (image_get_image_size(b, image, &size, 1)) return;

  /* Now lock the size by (re)storing it in currw and currh */

  idata[image].currw = size.xmax;
  idata[image].currh = size.ymax;

  return;
}

/*************************************************/
/* image_lock_token_image_size()                 */
/*                                               */
/* As image_lock_image_size, but takes a pointer */
/* to an HStream rather than an image number.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the image.           */
/*************************************************/

void image_lock_token_image_size(browser_data * b, HStream * token)
{
  int  image = image_get_token_image_actual(b, token);

  if (image < 0) return;

  image_lock_image_size(b, image);

  return;
}

/*************************************************/
/* image_unlock_image_size()                     */
/*                                               */
/* Set currw and currh for an image to -1, so    */
/* that image_get_image_size will start working  */
/* the size out from first principles rather     */
/* than using any prestored value.               */
/*                                               */
/* Designed to be called by the reformatter when */
/* it destroys a line array with images in it.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Number of the image.              */
/*************************************************/

static void image_unlock_image_size(browser_data * b, int image)
{
  idata[image].currw = -1;
  idata[image].currh = -1;

  return;
}

/*************************************************/
/* image_unlock_token_image_size()               */
/*                                               */
/* As image_unlock_image_size, but takes a       */
/* pointer to an HStream rather than an image    */
/* number.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to an HStream struct      */
/*             representing the image.           */
/*************************************************/

void image_unlock_token_image_size(browser_data * b, HStream * token)
{
  int image = image_get_token_image_actual(b, token);

  if (image < 0) return;

  image_unlock_image_size(b, image);

  return;
}

/*************************************************/
/* image_get_image_size()                        */
/*                                               */
/* Returns the size of an image in OS units.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1, which may just be a  */
/*             cross referencing image (i.e. has */
/*             no directly associated data);     */
/*                                               */
/*             Pointer to a BBox into which the  */
/*             size information is placed (xmax  */
/*             and ymax fields filled with       */
/*             width and height currently - xmin */
/*             and ymin always zero);            */
/*                                               */
/*             1 to ignore the image's currw and */
/*             currh fields, else use those if   */
/*             they are present.                 */
/*                                               */
/* Assumes:    The BBox pointer is not NULL.     */
/*************************************************/

static _kernel_oserror * image_get_image_size(browser_data * b, int image, BBox * box, int ignore_stored)
{
  int       awidth      = 0; /* Available page / cell width */
  int       aheight     = 0; /* Same, but height            */

  int       real_width  = 0; /* The actual image dimensions (OS units) */
  int       real_height = 0; /* from ImageLib                          */

  int       width_os    = 0; /* The image size as it will appear on the page, taking  */
  int       height_os   = 0; /* account of any WIDTH or HEIGHT attributes in the HTML */

  int       actual;
  int       subtract;
  HStream * tp          = idata[image].token;

  #ifdef TRACE

    if (tl & (1u<<15)) Printf("image_get_image_size: Called for image %d\n",image);

    if (!box)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess,"Null pointer to BBox given to image_get_image_size");
      return &erb;
    }

    if (image < 0)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,"Invalid image number %d passed to image_get_image_size (nimages = %d)", image, nimages);
      return &erb;
    }
    if (image > nimages)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,"Out of range image number %d passed to image_get_image_size (nimages = %d)", image, nimages);
      return &erb;
    }

  #else

    if (!box || image < 0 || image >= nimages) return NULL;

  #endif

  /* Zero contents of the BBox */

  memset(box, 0, sizeof(BBox));

  /* Can we return a prestored size? */

  if (!ignore_stored && idata[image].currw >= 0 && idata[image].currh >= 0)
  {
    box->xmax = idata[image].currw;
    box->ymax = idata[image].currh;

    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_get_image_size: Successful\n");
    #endif

    return NULL;
  }

  /* If this cross references another image, get its number */

  if (idata[image].xref >= 0) actual = idata[image].xref;
  else                        actual = image;

  /* Work out the available width / height */

  if (!tp->parent)
  {
    awidth  = redraw_display_width (b, NULL);
    aheight = redraw_display_height(b, NULL);

    subtract = redraw_left_gap (b, b->cell, tp) +
               redraw_right_gap(b, b->cell, tp);
  }
  else
  {
    reformat_cell * cell = tokenutils_token_cell(b, tp);

    if (!cell) cell = b->cell;

    if (
         TD_HAS_WIDTH  (tp->parent) &&
         TD_WIDTH_UNITS(tp->parent) == UNITS_PIXELS
       )
       awidth = TD_WIDTH(tp->parent) * 2; /* 1 'web pixel' = 2 OS units */

    if (
         TD_HAS_HEIGHT  (tp->parent) &&
         TD_HEIGHT_UNITS(tp->parent) == UNITS_PIXELS
       )
       aheight = TD_HEIGHT(tp->parent) * 2; /* 1 'web pixel' = 2 OS units */

    subtract = redraw_left_gap (b, cell, tp) +
               redraw_right_gap(b, cell, tp);
  }

  convert_to_os(subtract, &subtract);
  awidth -= subtract;
  if (awidth < 0) awidth = 0;

  /* Try to get the image size from the actual image data (i.e. ImageLib) */

  if (idata[actual].istore)
  {
    real_width  = idata[actual].istore->width_os;
    real_height = idata[actual].istore->height_os;
  }

  /* If we don't seem to have anything, try the image history */

  if (real_width <= 0 && real_height <= 0)
  {
    imghistory_return_size((const char **) &ddata,
                           image_data_offset(idata[actual].owner, actual),
                           &real_width,
                           &real_height);
  }

  /* The image may have a specific width and height given in */
  /* the HTML.                                               */

  if (tp->tagno != TAG_INPUT)
  {
    /* The OBJECT macros work for IMGs as well as OBJECTs */

    if (OBJECT_HAS_WIDTH(tp))
    {
      switch (OBJECT_WIDTH_UNITS(tp))
      {
        default:
        case UNITS_PIXELS:  width_os = OBJECT_WIDTH(tp) * 2; break;
        case UNITS_PERCENT: width_os = awidth * OBJECT_WIDTH(tp) / 100; break;
      }
    }

    if (OBJECT_HAS_HEIGHT(tp))
    {
      switch (OBJECT_HEIGHT_UNITS(tp))
      {
        default:
        case UNITS_PIXELS:  height_os = OBJECT_HEIGHT(tp) * 2; break;
        case UNITS_PERCENT: height_os = aheight * OBJECT_HEIGHT(tp) / 100; break;
      }
    }
  }

  /* If width and height are both <= zero, try to find them out */
  /* from the image data itself                                 */

  if (width_os <= 0 && height_os <= 0)
  {
    if (real_width > 0 && real_height > 0)
    {
      width_os  = real_width;
      height_os = real_height;
    }
  }

  /* If width and height are still both <= zero, must go for a */
  /* default placeholder size instead.                         */

  if (width_os <= 0 && height_os <= 0) goto image_get_image_size_placeholder;

  /* We may have final values... */

  if (width_os > 0 && height_os > 0)
  {
    box->xmax = width_os;
    box->ymax = height_os;

    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_get_image_size: Successful\n");
    #endif

    return NULL;
  }

  /* Otherwise, scale one from the other if possible - for this we must */
  /* have a real image size to get the proportions from                 */

  if (real_width <= 0 || real_height <= 0) goto image_get_image_size_placeholder;

  /* We know the true image size and can thus scale whichever dimension is */
  /* missing for the on-page size from the proportions of the real image.  */

  if (width_os <= 0)
  {
    /* Work out width based on the height */

    width_os = (height_os * real_width) / real_height;
  }
  else
  {
    /* Work out height based on the width */

    height_os = (width_os * real_height) / real_width;
  }

  /* We should now have both values, but if not, drop through to the */
  /* placeholder size routine.                                       */

  if (width_os > 0 && height_os > 0)
  {
    box->xmax = width_os;
    box->ymax = height_os;

    #ifdef TRACE
      if (tl & (1u<<15)) Printf("image_get_image_size: Successful\n");
    #endif

    return NULL;
  }

image_get_image_size_placeholder:

  {
    /* Don't have precise image dimensions, so work it out from */
    /* any ALT text present or go for a default value.          */

    HStream    * tp = NULL;
    const char * text;

    if (image >= 0) tp = idata[image].token;

    text = (ISOBJECT(tp)) ? HtmlOBJECTstandby(tp) : tp->text;

    reformat_get_placeholder_size(b, tp, text, box);
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_image_size: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_get_token_image_size()                  */
/*                                               */
/* As image_get_image_size above, but takes a    */
/* token address rather than an image number.    */
/* The default 'unknown' image size will be      */
/* returned if no image could be associated with */
/* the token (no error will be raised).          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             A token address;                  */
/*                                               */
/*             Pointer to a BBox into which the  */
/*             size information is placed.       */
/*                                               */
/* Assumes:    The BBox pointer is not NULL.     */
/*************************************************/

_kernel_oserror * image_get_token_image_size(browser_data * b, HStream * token, BBox * box)
{
  int image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_size: Called\n");
  #endif

  image = image_get_token_image_actual(b, token);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_image_size: Exitting through image_get_image_size\n");
  #endif

  return image_get_image_size(b, image, box, 0);
}

/*************************************************/
/* image_set_image_size()                        */
/*                                               */
/* Sets the size of an image (currw, currh       */
/* fields) in OS units.                          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             The image number, from 0 to       */
/*             nimages - 1, which may just be a  */
/*             cross referencing image (i.e. has */
/*             no directly associated data);     */
/*                                               */
/*             Pointer to a BBox from which the  */
/*             size information is read (xmax -  */
/*             xmin, ymax - ymin).               */
/*                                               */
/* Assumes:    The BBox pointer is not NULL.     */
/*************************************************/

static _kernel_oserror * image_set_image_size(browser_data * b, int image, BBox * box)
{
  #ifdef TRACE

    if (tl & (1u<<15)) Printf("image_set_image_size: Called for image %d\n",image);

    if (!box)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess,"Null pointer to BBox given to image_set_image_size");
      return &erb;
    }

    if (image < 0)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,"Invalid image number %d passed to image_set_image_size (nimages = %d)", image, nimages);
      return &erb;
    }
    if (image > nimages)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,"Out of range image number %d passed to image_set_image_size (nimages = %d)", image, nimages);
      return &erb;
    }

  #else

    if (image < 0 || image >= nimages) return NULL;

  #endif

  idata[image].currw = box->xmax - box->xmin;
  idata[image].currh = box->ymax - box->ymin;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_set_image_size: Successful\n");
  #endif

  return NULL;
}

/*************************************************/
/* image_set_token_image_size()                  */
/*                                               */
/* As image_set_image_size above, but takes a    */
/* token address rather than an image number.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             A token address;                  */
/*                                               */
/*             Pointer to a BBox from which the  */
/*             size information is read (xmax -  */
/*             xmin, ymax - ymin).               */
/*                                               */
/* Assumes:    The BBox pointer is not NULL.     */
/*************************************************/

_kernel_oserror * image_set_token_image_size(browser_data * b, HStream * token, BBox * box)
{
  int image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_set_token_image_size: Called\n");
  #endif

  image = image_get_token_image_actual(b, token);

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_set_token_image_size: Exitting through image_set_image_size\n");
  #endif

  return image_set_image_size(b, image, box);
}

/*************************************************/
/* image_get_token_actual_size()                 */
/*                                               */
/* Returns the width and height of an image, in  */
/* pixels, or 0 and 0 if it the size isn't known */
/* yet.                                          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             A token address;                  */
/*                                               */
/*             Pointer to an int, in which the   */
/*             width is written;                 */
/*                                               */
/*             Pointer to an int, in which the   */
/*             height is written.                */
/*                                               */
/* Assumes:    All pointers are non-NULL and     */
/*             valid.                            */
/*************************************************/

void image_get_token_actual_size(browser_data * b, HStream * token, int * w, int * h)
{
  int image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_actual_size: Called\n");
  #endif

  image = image_get_token_image_xref(b, token);

  if (idata[image].istore)
  {
    *w = idata[image].istore -> width_os  / 2;
    *h = idata[image].istore -> height_os / 2;
  }
  else
  {
    *w = 0;
    *h = 0;
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_token_actual_size: Exitting with width %d, height %d\n", *w, *h);
  #endif

  return;
}

/*************************************************/
/* image_get_back_image_size()                   */
/*                                               */
/* Finds out the size of the background image in */
/* OS units.                                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the background image; */
/*             Pointer to a BBox into which the  */
/*             size information is placed.       */
/*                                               */
/* Assumes:    That neither pointer is NULL.     */
/*************************************************/

_kernel_oserror * image_get_back_image_size(browser_data * b, BBox * box)
{
  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_get_back_image_size: Called, exitting through image_get_image_size\n");
  #endif

  return (image_get_image_size(b, b->background_image, box, 1));
}

/*************************************************/
/* image_get_token_image_position()              */
/*                                               */
/* Returns the x and y fields of the image_info  */
/* structure for a given image.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to the token representing */
/*             the image;                        */
/*                                               */
/*             Pointer to an int, in which the   */
/*             X coordinate is returned;         */
/*                                               */
/*             Pointer to an int, in which the   */
/*             Y coordinate is returned.         */
/*                                               */
/* Returns:    1 if the image could not be found */
/*             from the given token, or 0 for    */
/*             success.                          */
/*                                               */
/* Assumes:    Neither pointer is NULL.          */
/*************************************************/

int image_get_token_image_position(browser_data * b, HStream * t, int * x, int * y)
{
  int image = image_get_token_image_actual(b, t);

  if (image < 0) return 1;

  *x = idata[image].x;
  *y = idata[image].y;

  return 0;
}

/*************************************************/
/* image_set_token_image_position()              */
/*                                               */
/* Sets the x and y fields of the image_info     */
/* structure for a given image, so that it may   */
/* be [partially] plotted during a fetch.        */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to the token representing */
/*             the image;                        */
/*                                               */
/*             X coordinate (window coords);     */
/*                                               */
/*             Y coordinate (window coords).     */
/*                                               */
/* Returns:    Number of the image that was      */
/*             changed, or -1 if none could be   */
/*             found for the given token.        */
/*************************************************/

int image_set_token_image_position(browser_data * b, HStream * t, int x, int y)
{
  int image = image_get_token_image_actual(b, t);

  /* If an image was found, set the x and y coordinates */

  if (image >= 0)
  {
    idata[image].x = x;
    idata[image].y = y;
  }

  return image;
}

/*************************************************/
/* image_get_background_image_url()              */
/*                                               */
/* Returns the URL of a browser's current        */
/* background image.                             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the background;       */
/*                                               */
/*             Pointer to a buffer to take the   */
/*             URL;                              */
/*                                               */
/*             Size of the buffer.               */
/*************************************************/

void image_get_background_image_url(browser_data * b, char * buffer, int size)
{
  int offset;
  int old_budge;
  int image = b ? b->background_image : -1;

  if (!buffer || size < 1) return;

  *buffer = 0;

  if (size < 2 || image < 0 || image >= nimages) return;

  /* Find the offset of the URL in the 'ddata' block */

  offset = image_data_offset(b, image);

  /* Lock flex */

  old_budge = flex_set_budge(0);

  /* Copy the URL in, ensuring termination */

  strncpy(buffer, ddata + offset, size);
  buffer[size - 1] = 0;

  /* Restore flex's previous budge state */

  flex_set_budge(old_budge);

  /* Finished */

  return;
}

/*************************************************/
/* image_convert_to_pixels()                     */
/*                                               */
/* Converts a given OS unit X and Y size to      */
/* pixel size, for a given image.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to the token representing */
/*             the image;                        */
/*                                               */
/*             Pointer to an int which already   */
/*             contains the X size in OS units,  */
/*             which will have the pixel size    */
/*             returned into it;                 */
/*                                               */
/*             A similar pointer for the Y size. */
/*                                               */
/* Assumes:    That no pointer is NULL.          */
/*************************************************/

void image_convert_to_pixels(browser_data * b, HStream * token, int * x, int * y)
{
  int image;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_convert_to_pixels: Called\n");
  #endif

  image = image_get_token_image_xref(b, token);

  /* If the library has got enough information to fill in the */
  /* width of the image, proceed with the conversion          */

  if (image >= 0 && idata[image].istore->width > 0)
  {
    *x = *x * idata[image].istore->width  / idata[image].istore->width_os;
    *y = *y * idata[image].istore->height / idata[image].istore->height_os;
  }
  else
  {
    /* Can't find image - maybe it's not loaded... Assume 2 OS units per pixel. */

    *x = *x / 2;
    *y = *y / 2;
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_convert_to_pixels: Successful\n");
  #endif
}

/*************************************************/
/* image_return_click_offset()                   */
/*                                               */
/* Calculate the coordinates of a click on an    */
/* image in pixels from the top left corner, as  */
/* required by image maps and image input        */
/* fields.                                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a token representing   */
/*             the image;                        */
/*                                               */
/*             A WimpGetPointerInfo block        */
/*             pointer describing the mouse      */
/*             pointer's position;               */
/*                                               */
/*             Pointer to an int, in which the   */
/*             x offset is returned;             */
/*                                               */
/*             Pointer to an int, in which the   */
/*             y offset is returned.             */
/*                                               */
/* Assumes:    That the int pointers are not     */
/*             NULL.                             */
/*************************************************/

_kernel_oserror * image_return_click_offset(browser_data * b, HStream * t, WimpGetPointerInfoBlock * i, int * x, int * y)
{
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   s;
  BBox                      box;

  /* Get the image's size and position on screen */

  s.window_handle = b->window_handle;

  e = wimp_get_window_state(&s);
  if (e) return e;

  e = image_get_token_image_size(b, t, &box);
  if (e) return e;

  if (image_get_token_image_position(b, t, x, y)) return NULL;

  *x = coords_x_toscreen(*x, (WimpRedrawWindowBlock *) &s);
  *y = coords_y_toscreen(*y, (WimpRedrawWindowBlock *) &s);

  /* Get the offset of the pointer position from the top left */
  /* of the image in ox and oy                                */

  *x = i->x - *x;
  *y = *y + (box.ymax - box.ymin) - i->y;

  image_convert_to_pixels(b, t, x, y);

  return NULL;
}

/*************************************************/
/* image_can_be_saved_as_sprite                  */
/*                                               */
/* Returns 1 if a given image has data and can   */
/* be saved (the DumpSprite function is          */
/* present), else 0.                             */
/*                                               */
/* The image may cross reference another with    */
/* the actual data. If so, the return value will */
/* be referring to that cross referenced image.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             owning the image;                 */
/*                                               */
/*             Number of the image.              */
/*                                               */
/* Returns:    1 if the image can be saved as a  */
/*             sprite, else 0.                   */
/*************************************************/

static int image_can_be_saved_as_sprite(browser_data * b, int i)
{
  if (
       idata[i].istore                &&
       idata[i].istore->DumpSprite    &&
       idata[i].istore->width_os > 0  &&
       idata[i].istore->height_os > 0
     )
     return 1;

  return 0;
}

/*************************************************/
/* image_token_can_be_saved_as_sprite            */
/*                                               */
/* Returns 1 if a given image has data and can   */
/* be saved (the DumpSprite function is          */
/* present), else 0.                             */
/*                                               */
/* The image may cross reference another with    */
/* the actual data. If so, the return value will */
/* be referring to that cross referenced image.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             owning the image;                 */
/*                                               */
/*             Pointer to the token representing */
/*             the image, or NULL for the page's */
/*             background image.                 */
/*                                               */
/* Returns:    1 if the image can be saved as a  */
/*             sprite, else 0.                   */
/*************************************************/

int image_token_can_be_saved_as_sprite(browser_data * b, HStream * image)
{
  int i;

  /* Find the image number */

  if (!image) i = b->background_image;
  else        i = image_get_token_image_xref(b, image);

  /* Complain if we can't in TRACE builds */

  if (i < 0)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "In image_token_can_be_saved_as_sprite, can't find an image that is represented by the token %p for the browser %p.",
              image,
              b);

      show_error_ret(&erb);

    #endif

    return 0;
  }

  /* Can it be saved? */

  return image_can_be_saved_as_sprite(b, i);
}

/*************************************************/
/* image_export_sprite()                         */
/*                                               */
/* Saves an image represented by the given token */
/* as a sprite at the given path.                */
/*                                               */
/* Parameters: Pointer to the path to save to;   */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             that owns the image;              */
/*                                               */
/*             Pointer to the token representing */
/*             the image, or NULL for the page's */
/*             background image.                 */
/*************************************************/

_kernel_oserror * image_export_sprite(char * path, browser_data * b, HStream * image)
{
  int i;

  if (!path || !*path) return NULL;

  save_record_path(path);

  /* Find the image number */

  if (!image) i = b->background_image;
  else        i = image_get_token_image_xref(b, image);

  /* Must have image data... */

  if (!image_can_be_saved_as_sprite(b, i))
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "There is no data for this image in image_export_sprite.");

      return &erb;

    #endif

    return NULL;
  }

  /* Export the image */

  return idata[i].istore -> DumpSprite(idata[i].istore,
                                       path,
                                       -1);
}

/*************************************************/
/* image_sprite_size()                           */
/*                                               */
/* Returns the size that the given image would   */
/* be as a sprite on disc.                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that owns the image;              */
/*                                               */
/*             Pointer to the token representing */
/*             the image, or NULL for the page's */
/*             background image.                 */
/*                                               */
/* Returns:    The size the image would be as a  */
/*             sprite file saved to disc, in     */
/*             bytes (a guess only).             */
/*************************************************/

int image_sprite_size(browser_data * b, HStream * image)
{
  /* ...And for now, we don't know. */

  return 4096;
}

/*************************************************/
/* image_export_original()                       */
/*                                               */
/* Saves an image represented by the given token */
/* in its original format to the given path.     */
/*                                               */
/* Parameters: Pointer to the path to save to;   */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             that owns the image;              */
/*                                               */
/*             Pointer to the token representing */
/*             the image, or NULL for the page's */
/*             background image.                 */
/*************************************************/

_kernel_oserror * image_export_original(char * path, browser_data * b, HStream * image)
{
  char url[Limits_URL];
  int  i, offset;

  if (!path || !*path) return NULL;

  save_record_path(path);

  /* Find the image number */

  if (!image) i = b->background_image;
  else        i = image_get_token_image_xref(b, image);

  /* Must have image data... */

  offset = image_data_offset(b, i);

  if (!ddata[offset])
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "There is no URL for this image in image_export_original.");

      return &erb;

    #endif

    return NULL;
  }

  /* Start a fetch for this URL */

  strncpy(url, ddata + offset, sizeof(url));
  url[sizeof(url) - 1] = 0;

  RetError(windows_create_browser(url,
                                  NULL,
                                  NULL,
                                  NULL,
                                  Windows_CreateBrowser_SaveToFile));

  /* Set the title to the save pathname */

  {
    char title[Limits_Title];

    StrNCpy0(title, path);

    /* Don't treat any errors here as fatal */

    show_error_ret(window_set_title(0, last_browser->self_id, title));
  }

  /* Open the file */

  last_browser->save_file = fopen(path, "wb");

  if (!last_browser->save_file)
  {
    fetch_stop(last_browser, 0);
    RetLastE;
  }
  else
  {
    int bytes;

    /* Set the filetype to DEADDEAD, to represent an incomplete */
    /* file (particularly good on later Filers, which display   */
    /* a special sprite for this).                              */

    _swix(OS_File,
          _INR(0,2),

          2, /* Set load address */
          path,
          0xdeaddead);

    _swix(OS_File,
          _INR(0,1) | _IN(3),

          3, /* Set exec address */
          path,
          0xdeaddead);

    if (last_browser->source)
    {
        /* Any data in the source store represents already */
        /* fetched bits of the file. Must lock flex down   */
        /* over the save to make sure the heap doesn't     */
        /* shift over the call to fwrite.                  */

        flex_set_budge(0);

        bytes = fwrite(last_browser->source,
                       1,
                       flex_size((flex_ptr) &last_browser->source),
                       last_browser->save_file);

        flex_set_budge(1);

      /* If we didn't transfer as much as we expected, complain */

      if (bytes != flex_size((flex_ptr) &last_browser->source))
      {
        /* Report any errors */

        fetch_stop(last_browser, 0);
        RetLastE;
      }
      else
      {
        /* Otherwise, get rid of the data in the source store */
        /* as it's been written to the file.                  */

        flex_free((flex_ptr) &last_browser->source);
        last_browser->source = NULL;
      }
    }
  }

  return NULL;
}

/*************************************************/
/* image_original_size()                         */
/*                                               */
/* Returns the size of a given image in its      */
/* original format.                              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that owns the image;              */
/*                                               */
/*             Pointer to the token representing */
/*             the image, or NULL for the page's */
/*             background image.                 */
/*                                               */
/* Returns:    The size of the original image,   */
/*             in bytes (a guess only).          */
/*************************************************/

int image_original_size(browser_data * b, HStream * image)
{
  int size = image_draw_file_size(b, image, 0);

  if (size <= 0) return 0;
  else           return size + 12;
}

/*************************************************/
/* image_to_draw_file()                          */
/*                                               */
/* As image_redraw, but will output a sprite     */
/* to the Draw file being output by SaveDraw.c   */
/* (which must be active at the time this is     */
/* called). Assumes that the appropriate Draw    */
/* file object header has already been written.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Pointer to a RedrawWindowBlock    */
/*             struct which holds information    */
/*             about the current redraw session; */
/*                                               */
/*             Address of the token representing */
/*             the image;                        */
/*                                               */
/*             The X offset in window coords (so */
/*             OS units) of the left hand edge   */
/*             of the image;                     */
/*                                               */
/*             The Y offset in window coords (so */
/*             OS units) of the bottom edge of   */
/*             the image;                        */
/*                                               */
/*             1 to assume the image is already  */
/*             present as a sprite in            */
/*             'unique_name' else create the     */
/*             image first.                      */
/*                                               */
/* Returns:    0 if failed, 1 if successful; if  */
/*             failed, caller should try some    */
/*             alternative representation (e.g.  */
/*             write out a placeholder).         */
/*************************************************/

int image_to_draw_file(browser_data * b, WimpRedrawWindowBlock * r, HStream * token, int x, int y, int dont_create)
{
  int image, actual;
  int plotted = 0;

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_to_draw_file: Called with token %d\n", token);
  #endif

  /* Get the image number for the given token; 'actual' holds the actual */
  /* image for the given token, which may cross reference 'image', which */
  /* holds the actual render data.                                       */

  image = image_get_token_image_actual(b, token);

  if (image < 0) return NULL;

  actual = image;
  if (idata[actual].xref >= 0) image = idata[actual].xref;

  /* If the image has a fetched width and height, */
  /* and the browser choices specify that images  */
  /* should be shown (or the image itself has a   */
  /* high priority show bit set), display part or */
  /* all of the image.                            */

  if (
       image  >= 0 &&
       actual >= 0 &&
       (
         b->show_foreground                     ||
         b->displayed == Display_External_Image ||
         idata[actual].priority
       )
       && idata[actual].canredraw
       && idata[image].istore             /* Must check istore here, as if width / height are <= 0, */
       && idata[image].istore->width  > 0 /* the image library has no data for the image and will   */
       && idata[image].istore->height > 0 /* not have filled in (e.g.) the Render field. So you'd   */
       && idata[image].istore->DumpSprite /* get a rather nasty abort trying to call it!            */
       && image_can_be_saved_as_sprite(b, image)

//       && idata[image].istore->StartExport
//       && idata[image].istore->Export
//       && idata[image].istore->EndExport
     )
  {
    FILE            * input = NULL;
    _kernel_oserror * e     = NULL;

    int               written, ok;
    char              buffer[10240];

//    int               image_size = 0;

    browser_redrawing = b;
    image_redrawing   = actual;

    if (!dont_create || !unique_name)
    {
      unique_name = malloc(Limits_OS_Pathname);

      if (unique_name)
      {
        protocols_util_make_unique_name(unique_name, Limits_OS_Pathname);

        /* Output the sprite, skipping the first 12 bytes of sprite area header */

        e = idata[image].istore -> DumpSprite(idata[image].istore,
                                              unique_name,
                                              -1);
      }
    }

    if (e || !unique_name) ok = 0;
    else
    {
      ok = 1;

      input = fopen(unique_name, "rb");

      if (!input || !input->__file) ok = 0;
    }

    if (ok)
    {
      e = _swix(OS_Args,
                _INR(0,2),

                1,
                input->__file,
                12);

      if (e) ok = 0;
    }

    if (ok)
    {
      do
      {
        e = _swix(OS_GBPB,
                  _INR(0,3) | _OUT(3),

                  4,
                  input->__file,
                  buffer,
                  sizeof(buffer),

                  &written);

        if (!e && written < sizeof(buffer)) ok = savedraw_write_bytes(buffer, sizeof(buffer) - written);
        else                                ok = e ? 0 : 1;
      }
      while (!e && written < sizeof(buffer));
    }

    if (input)
    {
      fclose(input);

      if (unique_name) remove(unique_name);

      free(unique_name);
      unique_name = NULL;
    }

//    e       = idata[image].istore -> StartExport(idata[image].istore, &image_size);
//    ok      = 1;
//
//    written = 12;
//    e       = idata[image].istore -> Export(idata[image].istore, buffer, &written);
//
//    while (!e && ok && written)
//    {
//      written = sizeof(buffer);
//
//      e = idata[image].istore -> Export(idata[image].istore, buffer, &written);
//
//      if (!e) ok = savedraw_write_bytes(buffer, written);
//      else    ok = 0;
//    }
//
//    e = idata[image].istore -> EndExport(idata[image].istore);

    /* Flag if we succeeded */

    browser_redrawing = NULL;
    image_redrawing   = -1;

    if (!e && ok) plotted = 1;
  }

  #ifdef TRACE
    if (tl & (1u<<15)) Printf("image_to_draw_file: Exitting with %d\n", plotted);
  #endif

  return plotted;
}

/*************************************************/
/* image_draw_file_size()                        */
/*                                               */
/* Returns the amount of data that               */
/* image_to_draw_file would write to the output  */
/* file.                                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the image;            */
/*                                               */
/*             Address of the token representing */
/*             the image;                        */
/*                                               */
/*             1 to not delete the scrap file    */
/*             in 'unique_name' that this        */
/*             writes, else 0.                   */
/*                                               */
/* Returns:    0 if failed, else the amount of   */
/*             data that image_to-draw_file      */
/*             would write to the output file.   */
/*************************************************/

int image_draw_file_size(browser_data * b, HStream * token, int dont_delete)
{
  int image, actual;

  /* Get the image number for the given token; 'actual' holds the actual */
  /* image for the given token, which may cross reference 'image', which */
  /* holds the actual render data.                                       */

  image = image_get_token_image_actual(b, token);

  if (image < 0) return NULL;

  actual = image;
  if (idata[actual].xref >= 0) image = idata[actual].xref;

  /* If the image has a fetched width and height, */
  /* and the browser choices specify that images  */
  /* should be shown (or the image itself has a   */
  /* high priority show bit set), display part or */
  /* all of the image.                            */

  if (
       image  >= 0 &&
       actual >= 0 &&
       (
         b->show_foreground                     ||
         b->displayed == Display_External_Image ||
         idata[actual].priority
       )
       && idata[actual].canredraw
       && idata[image].istore             /* Must check istore here, as if width / height are <= 0, */
       && idata[image].istore->width  > 0 /* the image library has no data for the image and will   */
       && idata[image].istore->height > 0 /* not have filled in (e.g.) the Render field. So you'd   */
       && idata[image].istore->DumpSprite /* get a rather nasty abort trying to call it!            */
       && image_can_be_saved_as_sprite(b, image)

//       && idata[image].istore->StartExport
//       && idata[image].istore->Export
//       && idata[image].istore->EndExport
     )
  {
    _kernel_oserror * e          = NULL;
    int               image_size = 0;

    if (unique_name)
    {
      remove(unique_name);

      free(unique_name);
      unique_name = NULL;
    }

    unique_name = malloc(Limits_OS_Pathname);

    if (unique_name)
    {
      protocols_util_make_unique_name(unique_name, Limits_OS_Pathname);

      e = idata[image].istore -> DumpSprite(idata[image].istore,
                                            unique_name,
                                            -1);
    }

    if (!e && unique_name)
    {
      e = _swix(OS_File,
                _INR(0,1) | _OUT(4),

                17,
                unique_name,

                &image_size);

      if (!dont_delete)
      {
        remove(unique_name);

        free(unique_name);
        unique_name = NULL;
      }
    }

//    e = idata[image].istore -> StartExport(idata[image].istore, &image_size);
//
//    if (!e) idata[image].istore -> EndExport(idata[image].istore);

    /* Remember, we'd skip the first 12 bytes of sprite area header */
    /* in the output Draw file.                                     */

    if (!e && image_size > 12) return image_size - 12;
  }

  return 0;
}

/*************************************************/
/* image_tile_to_draw()                          */
/*                                               */
/* For a given redraw rectangle, tiles the       */
/* background image within that rectangle to a   */
/* Draw file.                                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window and image; */
/*                                               */
/*             Pointer to a WimpRedrawWindow-    */
/*             Block structure defining the area */
/*             over which to tile the image;     */
/*                                               */
/*             The X and Y coordinates to take   */
/*             as the tile origin, in OS units;  */
/*                                               */
/*             Pointer to an int if wanting to   */
/*             know the size of the item only,   */
/*             else NULL (NB the int contents    */
/*             are not updated - the pointer is  */
/*             acting like a flag, basically).   */
/*                                               */
/* Returns:    0 if failed, else the amount of   */
/*             data that was output.             */
/*************************************************/

int image_tile_to_draw(browser_data * b, WimpRedrawWindowBlock * r, int xorigin, int yorigin, int * size)
{
  char              name[Limits_OS_Pathname];
  _kernel_oserror * e;
  FILE            * input = NULL;
  void            * block = NULL;
  int               image;
  int               tsize = 0;
  int               x, y, w, h;
  int               xmin, ymax;
  int               image_size;

  /* Can only tile if there's a background image defined and fully fetched */

  if (b->background_image < 0) return 0;

  image = b->background_image;
  if (idata[image].xref >= 0) image = idata[image].xref;

  if (!idata[image].fetched || !idata[image].istore) return 0;

  /* Round the width and height to pixel boundaries */

  w = idata[image].istore->width_os  & ~1;
  h = idata[image].istore->height_os & ~1;

  if (w < 1 || h < 1) return 0;

  /* Work out the coordinates over which to tile the image */

  xmin = coords_x_toworkarea(r->redraw_area.xmin,r);
  ymax = coords_y_toworkarea(r->redraw_area.ymax,r);

  xmin -= ((xmin - xorigin) % w);
  ymax -= ((ymax - yorigin) % h) + 1;

  xmin = coords_x_toscreen(xmin, r);
  ymax = coords_y_toscreen(ymax, r);

  /* Do a blank rectangle if the image is transparent (has a mask) */

  if (idata[image].istore->transparent)
  {
    redraw_set_colour(redraw_backcol(b));

    tsize += DSIZE_FRECT;

    if (!size)
    {
      if (!savedraw_rectangle_fill(OTD(r->redraw_area.xmin),
                                   OTD(r->redraw_area.ymin),
                                   OTD(r->redraw_area.xmax - r->redraw_area.xmin),
                                   OTD(r->redraw_area.ymax - r->redraw_area.ymin),
                                   redraw_backcol(b))) return 0;
    }
  }

  /* How big is each image going to be? */

  protocols_util_make_unique_name(name, sizeof(name));

  e = idata[image].istore -> DumpSprite(idata[image].istore,
                                        name,
                                        -1);

  if (!e)
  {
    e = _swix(OS_File,
              _INR(0,1) | _OUT(4),

              17,
              name,

              &image_size);
  }

  if (e || image_size <= 12) return 0;

  /* Subtract the sprite file header size */

  image_size -= 12;

  input = fopen(name, "rb");
  block = malloc(image_size);

  if (block && input && input->__file)
  {
    /* Load the image */

    e = _swix(OS_Args,
              _INR(0,2),

              1,
              input->__file,
              12);

    if (e) goto image_tile_to_draw_exit;

    e = _swix(OS_GBPB,
              _INR(0,3),

              4,
              input->__file,
              block,
              image_size);

    if (e) goto image_tile_to_draw_exit;

    fclose(input);
    input = NULL;

    /* Render the image over the redraw region */

    for (y = ymax - h + 4; y >= r->redraw_area.ymin - h; y -= h)
    {
      for (x = xmin; x <= r->redraw_area.xmax; x += w)
      {
        if (!size)
        {
          draw_spristrhdr hdr;
          int             ok;

          /* Write the item header */

          hdr.tag  = draw_OBJSPRITE;
          hdr.size = image_size + sizeof(hdr);

          hdr.bbox.xmin = OTD(x);
          hdr.bbox.ymin = OTD(y);
          hdr.bbox.xmax = OTD(x + w);
          hdr.bbox.ymax = OTD(y + h);

          if (!savedraw_write_bytes((char *) &hdr, sizeof(hdr))) goto image_tile_to_draw_exit;

          /* Write out the image */

          browser_redrawing = b;
          image_redrawing   = image;

          ok = savedraw_write_bytes(block, image_size);

          browser_redrawing = NULL;
          image_redrawing   = -1;

          if (!ok) goto image_tile_to_draw_exit;

          #ifdef STRICT_PARSER

            if (e)
            {
              e->errnum = Utils_Error_Custom_Message;
              show_error_ret(e);

              goto image_tile_to_draw_exit;
            }

          #else

            if (e) goto image_tile_to_draw_exit;

          #endif
        }

        tsize += image_size + sizeof(draw_spristrhdr);
      }
    }
  }

  /* Successful exit */

  if (input) fclose(input);
  if (block) free(block);

  remove(name);

  return tsize;

  /* Error condition exit */

image_tile_to_draw_exit:

  if (input) fclose(input);
  if (block) free(block);

  remove(name);

  return 0;
}