/**********************************************************************
 * File:        imgs.c  (Formerly images.c)
 * Description: Main image manipulation functions.
 * Author:      Ray Smith
 * Created:     Thu Jun 07 16:25:02 BST 1990
 *
 * (C) Copyright 1990, Hewlett-Packard 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.
 *
 **********************************************************************/

#include          "mfcpch.h"     //precompiled headers
#ifdef __MSW32__
#include          <io.h>
#else
#include          <unistd.h>
#endif
#include          <string.h>
#ifdef __UNIX__
#include          <assert.h>
#endif
#include          "stderr.h"
#include          "tprintf.h"
#include          "imgerrs.h"
#include          "memry.h"
#include          "imgs.h"
#include          "imgio.h"
#include          "imgunpk.h"

#define FIXED_COLOURS   32       /*number of fixed colours */
#define MIN_4BIT      48         /*4bpp range */
#define MAX_4BIT      64
#define MIN_6BIT      64         /*6bpp range */
#define MAX_6BIT      128
#define BLACK_PIX     0

static UINT8 grey_scales[FIXED_COLOURS] = {
  0, 255, 76, 227, 151, 179, 28, 104,
  149, 72, 215, 67, 53, 44, 156, 137,
  110, 153, 79, 181, 166, 218, 55, 81,
  129, 105, 179, 149, 168, 69, 84, 126
};

#define EXTERN

EXTERN INT_VAR (image_default_resolution, 300, "Image resolution dpi");

/**********************************************************************
 * IMAGE
 *
 * Contructor for an IMAGE class. Makes the image definitely illegal.
 **********************************************************************/

IMAGE::IMAGE() {  //construct an image
  bpp = 0;                       //all illegal
  fd = -1;
  image = NULL;
  photo_interp = 1;
  res = image_default_resolution;
}


/**********************************************************************
 * IMAGE::operator=
 *
 * Assign an IMAGE to another. The dest becomes the owner of the memory.
 **********************************************************************/

IMAGE & IMAGE::operator= (       //assignment
IMAGE & source                   //source image
) {
  destroy();
  bpp = source.bpp;
  photo_interp = source.photo_interp;
  bps = source.bps;
  bytespp = (bpp + 7) / 8;
  lineskip = source.lineskip;    //copy everything
  captured = source.captured;
  xsize = source.xsize;
  ysize = source.ysize;
  res = source.res;
  image = source.image;
  xdim = source.xdim;
  bufheight = source.bufheight;
  fd = source.fd;
  reader = source.reader;
  ymin = source.ymin;
  ymax = source.ymax;

  source.captured = TRUE;        //source now captured
  source.fd = -1;

  return *this;
}


/**********************************************************************
 * create
 *
 * Create an image (allocate memory) of a specific size and bpp.
 **********************************************************************/

INT8 IMAGE::create(                     //get rest of image
                   INT32 x,             //x size required
                   INT32 y,             //ysize required
                   INT8 bits_per_pixel  //bpp required
                  ) {
  UINT8 *pixels;                 //memory for image

  xdim = check_legal_image_size (x, y, bits_per_pixel);
  if (xdim < 0)
    return -1;
  pixels = (UINT8 *) alloc_big_zeros ((size_t) (xdim * y * sizeof (UINT8)));
  if (pixels == NULL) {
    MEMORY_OUT.error ("IMAGE::create", ABORT, "Size=(%d,%d)", xdim, y);
    return -1;
  }
                                 //allocate to image
  this->capture (pixels, x, y, bits_per_pixel);
  captured = FALSE;
  res = image_default_resolution;
  return 0;                      //success
}


/**********************************************************************
 * destroy
 *
 * Destroy an image, freeing memory and closing any open file.
 **********************************************************************/

void IMAGE::destroy() {  //get rid of image
  if (image != NULL && !captured) {
    free_big_mem(image);
  }
  image = NULL;
  if (fd >= 0) {
    close(fd);
    fd = -1;
  }
  bpp = 0;
}


/**********************************************************************
 * capture
 *
 * Assign a given memory area to an image to use as an image of
 * given size and bpp.
 **********************************************************************/

INT8 IMAGE::capture(                     //get rest of image
                    UINT8 *pixels,       //image memory
                    INT32 x,             //x size required
                    INT32 y,             //ysize required
                    INT8 bits_per_pixel  //bpp required
                   ) {
  destroy();
  xdim = check_legal_image_size (x, y, bits_per_pixel);
  if (xdim < 0)
    return -1;
  xsize = x;
  ysize = y;
  bufheight = y;
  bpp = bits_per_pixel;
  bps = bpp == 24 ? 8 : bpp;
  photo_interp = 1;
  bytespp = (bpp + 7) / 8;
  image = pixels;                //assign image area
  ymin = 0;
  ymax = bufheight;              //read it all
  captured = TRUE;
  res = image_default_resolution;
  return 0;                      //success
}


/**********************************************************************
 * pixel
 *
 * Get a single pixel out of the image.
 **********************************************************************/

UINT8 IMAGE::pixel(          //get rest of image
                   INT32 x,  //x coord
                   INT32 y   //y coord
                  ) {
  if (x < 0)
    x = 0;                       //silently clip
  else if (x >= xsize)
    x = xsize - 1;
  if (y < 0)
    y = 0;
  else if (y >= ysize)
    y = ysize - 1;
  check_legal_access (x, y, 1);
  switch (bpp) {
    case 5:
    case 6:
    case 8:
      return image[(ymax - 1 - y) * xdim + x];
    case 4:
      return bpp4table[image[(ymax - 1 - y) * xdim + x / 2]][x & 1];
    case 2:
      return bpp2table[image[(ymax - 1 - y) * xdim + x / 4]][x & 3];
    case 1:
      return bpp1table[image[(ymax - 1 - y) * xdim + x / 8]][x & 7];
    default:
      tprintf ("Unexpected bits per pixel %d\n", bpp);
      return 0;
  }
}


/**********************************************************************
 * check_legal_image_size
 *
 * Check that the supplied image sizes are legal.  If they are,
 * the xdim is returned, else -1.
 **********************************************************************/

INT32 check_legal_image_size(                     //get rest of image
                             INT32 x,             //x size required
                             INT32 y,             //ysize required
                             INT8 bits_per_pixel  //bpp required
                            ) {
  if (x <= 0 || y <= 0) {
    BADIMAGESIZE.error ("check_legal_image_size", TESSLOG, "(%d,%d)", x, y);
    return -1;                   //failed
  }
  if (bits_per_pixel != 1 && bits_per_pixel != 2
    && bits_per_pixel != 4 && bits_per_pixel != 5
  && bits_per_pixel != 6 && bits_per_pixel != 8 && bits_per_pixel != 24) {
    BADBPP.error ("check_legal_image_size", TESSLOG, "%d", bits_per_pixel);
    return -1;
  }
                                 //bytes per line
  return COMPUTE_IMAGE_XDIM (x, bits_per_pixel);
}


/**********************************************************************
 * copy_sub_image
 *
 * Copy a portion of one image to a portion of another image.
 * If the bpps are different, the position of the most significant
 * bit is preserved.
 **********************************************************************/

DLLSYM void copy_sub_image(                   //copy rectangle
                           IMAGE *source,     //source image
                           INT32 xstart,      //start coords
                           INT32 ystart,
                           INT32 xext,        //extent to copy
                           INT32 yext,
                           IMAGE *dest,       //destination image
                           INT32 xdest,       //destination coords
                           INT32 ydest,
                           BOOL8 adjust_grey  //shift to new bpp
                          ) {
  IMAGELINE copyline;            //copy of line
  UINT8 *copy;                   //source pointer
  INT8 shift;                    //shift factor
  INT32 pixel;                   //pixel index
  INT32 y;                       //line index
  INT32 yoffset;                 //current adjusted offset
  INT32 bytesize;                //no of bytes to copy
  INT32 srcppb;                  //pixels per byte
  BOOL8 aligned;

  if (xstart < 0 || ystart < 0 || xdest < 0 || ydest < 0)
    return;
  if (xext <= 0)
    xext = source->xsize;        //default to all
  if (xext > source->xsize - xstart)
                                 //clip to smallest
      xext = source->xsize - xstart;
  if (xext > dest->xsize - xdest)
    xext = dest->xsize - xdest;
  if (yext <= 0)
    yext = source->ysize;        //default to all
  if (yext > source->ysize - ystart)
                                 //clip to smallest
      yext = source->ysize - ystart;
  if (yext > dest->ysize - ydest)
    yext = dest->ysize - ydest;
  if (xext <= 0 || yext <= 0)
    return;                      //nothing to do

  srcppb = 8 / source->bpp;      //pixels per byte
  if (source->bpp == dest->bpp || !adjust_grey)
    shift = 0;                   //no adjustment
  else {
    shift = source->bps - dest->bps;
    if (shift < 0)
      shift = -shift;            //keep positive
  }
  aligned = source->bpp == dest->bpp;
  if (aligned && srcppb != 0) {
    aligned = xstart % srcppb == 0
      && xdest % srcppb == 0
      && (xext % srcppb == 0 || xdest + xext == dest->xsize);
  }
  for (y = 0; y < yext; y++) {
    if (ystart >= ydest)
      yoffset = y;               //top down
    else
      yoffset = yext - y - 1;    //bottom up
    source->check_legal_access (xstart, ystart + yoffset, xext);
    dest->check_legal_access (xdest, ydest + yoffset, xext);
    if (aligned) {
      bytesize = COMPUTE_IMAGE_XDIM (xext, source->bpp);
      //get bytes per line
      if (srcppb == 0)
                                 //do cheap move
        memmove (dest->image + (dest->ymax - 1 - ydest - yoffset) * dest->xdim + xdest * 3, source->image + (source->ymax - 1 - ystart - yoffset) * source->xdim + xstart * 3, (unsigned) bytesize);
      else
                                 //do cheap move
        memmove (dest->image + (dest->ymax - 1 - ydest - yoffset) * dest->xdim + xdest / srcppb, source->image + (source->ymax - 1 - ystart - yoffset) * source->xdim + xstart / srcppb, (unsigned) bytesize);
    }
    else {
      if (shift == 0) {
        source->fast_get_line (xstart, ystart + yoffset, xext,
          &copyline);
      }
      else if (source->bpp < dest->bpp) {
        source->get_line (xstart, ystart + yoffset, xext, &copyline, 0);
        if (source->bpp <= shift
        && (source->bpp == 1 || source->bpp == 4)) {
          if (source->bpp == 1) {
            for (pixel = 0, copy = copyline.pixels; pixel < xext;
              pixel++, copy++)
            if (*copy)
              *copy = 0xff;
          }
          else {
            for (pixel = 0, copy = copyline.pixels; pixel < xext;
              pixel++, copy++)
                                 //scale up
            *copy = (*copy << shift) | *copy;
          }
        }
        else {
          for (pixel = 0, copy = copyline.pixels; pixel < xext;
            pixel++)
          *copy++ <<= shift;     //scale up
        }
      }
      else {
        source->get_line (xstart, ystart + yoffset, xext, &copyline, 0);
        if (source->bpp == 24) {
          for (pixel = 0, copy = copyline.pixels + 1; pixel < xext;
          pixel++) {
            *copy >>= shift;
            copy += 3;
          }
        }
        else {
          for (pixel = 0, copy = copyline.pixels; pixel < xext;
            pixel++)
          *copy++ >>= shift;     //scale down
        }
      }
      dest->put_line (xdest, ydest + yoffset, xext, &copyline, 0);
    }
  }
}


/**********************************************************************
 * enlarge_sub_image
 *
 * Enlarge a portion of one image to a portion of another image.
 * If the bpps are different, the position of the most significant
 * bit is preserved.
 **********************************************************************/

DLLSYM void enlarge_sub_image(                   //enlarge rectangle
                              IMAGE *source,     //source image
                              INT32 xstart,      //scaled start coords
                              INT32 ystart,
                              IMAGE *dest,       //destination image
                              INT32 xdest,       //dest coords
                              INT32 ydest,
                              INT32 xext,        //destination extent
                              INT32 yext,
                              INT32 scale,       //scale factor
                              BOOL8 adjust_grey  //shift to new bpp
                             ) {
  INT8 shift;                    //shift factor
  UINT8 pixel;                   //current pixel
  INT32 srcext;                  //source extent
  INT32 xoffset;                 //column index
  INT32 yoffset;                 //line index
  INT32 xindex, yindex;          //index in super pixel
  INT32 startxindex;             //initial x index
  INT32 xscale;                  //x scale factor
  UINT8 *src;                    //source pixels
  UINT8 *destpix;                //dest pixels
  IMAGELINE copyline;            //copy of line
  IMAGELINE bigline;             //expanded line

  if (xstart < 0 || ystart < 0 || xdest < 0 || ydest < 0)
    return;

  if (xext <= 0)
    xext = dest->xsize;          //default to all
  if (xext > source->xsize * scale - xstart)
                                 //clip to smallest
    xext = source->xsize * scale - xstart;
  if (xext > dest->xsize - xdest)
    xext = dest->xsize - xdest;
  if (yext <= 0)
    yext = dest->ysize;          //default to all
  if (yext > source->ysize * scale - ystart)
    yext = source->ysize * scale - ystart;
  if (yext > dest->ysize - ydest)
    yext = dest->ysize - ydest;
  if (xext <= 0 || yext <= 0)
    return;                      //nothing to do

  xindex = xstart % scale;       //offset in super pixel
  startxindex = xindex;
  yindex = ystart % scale;
                                 //no of source pixels
  srcext = (xext + xindex + scale - 1) / scale;
  xstart /= scale;               //actual start
  ystart /= scale;
  if (adjust_grey) {
    shift = dest->bps - source->bps;
  }
  else
    shift = 0;                   //no adjustment
  bigline.init (xext * 3);
  bigline.bpp = dest->bpp == 24 ? source->bpp : dest->bpp;

  for (yoffset = 0; yoffset < yext; ystart++) {
    source->check_legal_access (xstart, ystart, srcext);
    dest->check_legal_access (xdest, ydest + yoffset, xext);
    source->fast_get_line (xstart, ystart, srcext, &copyline);
    src = copyline.pixels;
    destpix = bigline.pixels;
    xscale = scale;              //enlargement factor
    if (source->bpp == 24 && dest->bpp == 24) {
      for (xoffset = 0, xindex = startxindex; xoffset < xext;
      src += source->bytespp) {
        xoffset += xscale - xindex;
        if (xoffset > xext)
          xscale -= xoffset - xext;
        for (; xindex < xscale; xindex++) {
          *destpix++ = *src;
          *destpix++ = *(src + 1);
          *destpix++ = *(src + 2);
        }
        xindex = 0;
      }
    }
    else {
      if (source->bpp == 24)
        src++;
      for (xoffset = 0, xindex = startxindex; xoffset < xext;
      src += source->bytespp) {
        xoffset += xscale - xindex;
        if (xoffset > xext)
                                 //clip to dest limit
            xscale -= xoffset - xext;
        if (shift == 0)
          pixel = *src;
        else if (shift > 0)
          pixel = *src << shift;
        else
          pixel = *src >> (-shift);
        for (; xindex < xscale; xindex++)
          *destpix++ = pixel;    //duplicate pixel
        xindex = 0;
      }
    }
    for (; yoffset < yext && yindex < scale; yindex++, yoffset++) {
      dest->put_line (xdest, ydest + yoffset, xext, &bigline, 0);
    }
    yindex = 0;
  }
}


/**********************************************************************
 * fast_reduce_sub_image
 *
 * Reduce a portion of one image to a portion of another image.
 * If the bpps are different, the position of the most significant
 * bit is preserved.
 * This is a fast but dirty version, which simply sub-samples.
 * It does not smooth as it reduces.
 **********************************************************************/

DLLSYM void fast_reduce_sub_image(                   //reduce rectangle
                                  IMAGE *source,     //source image
                                  INT32 xstart,      //start coords
                                  INT32 ystart,
                                  INT32 xext,        //extent to copy
                                  INT32 yext,
                                  IMAGE *dest,       //destination image
                                  INT32 xdest,       //destination coords
                                  INT32 ydest,
                                  INT32 scale,       //reduction factor
                                  BOOL8 adjust_grey  //shift to new bpp
                                 ) {
  INT8 shift;                    //shift factor
  INT32 xfactor;                 //run on x coord
  INT32 divisor;                 //total cell area
  INT32 xindex, yindex;          //into averaging square
  INT32 xcoord;                  //current x coord
  INT32 destext;                 //destination size
  INT32 yoffset;                 //current adjusted offset
  UINT8 *pixel;                  //ptr to source pixels
  INT32 *sums;                   //ptr to sums array
  IMAGELINE copyline;            //copy of line
  INT32 *linesums;               //averaging sums

  if (xstart < 0 || ystart < 0 || xdest < 0 || ydest < 0)
    return;
  if (xext <= 0)
    xext = source->xsize;        //default to all
  if (xext > source->xsize - xstart)
                                 //clip to smallest
      xext = source->xsize - xstart;
  if (xext > (dest->xsize - xdest) * scale)
    xext = (dest->xsize - xdest) * scale;
  if (yext <= 0)
    yext = source->ysize;        //default to all
  if (yext > source->ysize - ystart)
                                 //clip to smallest
      yext = source->ysize - ystart;
  if (yext > (dest->ysize - ydest) * scale)
    yext = (dest->ysize - ydest) * scale;
  if (xext <= 0 || yext <= 0)
    return;                      //nothing to do

  xfactor = xext % scale;        //left overs
  if (xfactor == 0)
    xfactor = scale;
                                 //destination pixels
  destext = (xext + scale - 1) / scale;
  if (adjust_grey)
                                 //shift factor
    shift = dest->bps - source->bps;
  else
    shift = 0;                   //no adjustment
  linesums = new INT32[destext * source->bytespp];

  for (yoffset = 0; yoffset < yext; ydest++) {
    source->check_legal_access (xstart, ystart + yoffset, xext);
    dest->check_legal_access (xdest, ydest, destext);
    for (xindex = destext * source->bytespp - 1; xindex >= 0; xindex--)
      linesums[xindex] = 0;      //zero sums
    for (yindex = 0; yindex < scale
    && ystart + yoffset < source->ysize; yindex += 3) {
      source->fast_get_line (xstart, ystart + yoffset, xext, &copyline);
      pixel = copyline.pixels;   //start of line
      if (source->bpp == 24) {
        for (xcoord = 1, sums = linesums; xcoord < destext;
        xcoord++, sums += 3) {
          for (xindex = 0; xindex < scale; xindex += 2) {
            *sums += *pixel++;
            *(sums + 1) += *pixel++;
            *(sums + 2) += *pixel++;
            pixel += 3;
          }
          if (scale & 1)
            pixel -= 3;          //correct position
        }
        for (xindex = 0; xindex < xfactor; xindex += 2) {
          *sums += *pixel++;
          *(sums + 1) += *pixel++;
          *(sums + 2) += *pixel++;
          pixel += 3;
        }
      }
      else {
        for (xcoord = 1, sums = linesums; xcoord < destext;
        xcoord++, sums++) {
          for (xindex = 0; xindex < scale; xindex += 2) {
            *sums += *pixel;
            pixel += 2;
          }
          if (scale & 1)
            pixel--;             //correct position
        }
        for (xindex = 0; xindex < xfactor; xindex += 2) {
          *sums += *pixel;
          pixel += 2;
        }
      }
      yoffset += 3;              //every 3 lines
    }
    if (yindex > scale)
      yoffset -= yindex - scale; //back on right scale
    copyline.init ();            //set pixels back to array
    copyline.bpp = source->bpp;
    pixel = copyline.pixels;
                                 //pixels in block
    divisor = ((yindex + 2) / 3) * ((scale + 1) / 2);
    if (shift <= 0) {
      divisor <<= (-shift);      //do greyscale correction
      for (sums = linesums, xindex = (destext - 1) * source->bytespp;
        xindex > 0; xindex--)
                                 //turn to destination value
      *pixel++ = (UINT8) (*sums++ / divisor);
      for (xindex = source->bytespp; xindex > 0; xindex--)
        *pixel++ = *sums++
          / (((yindex + 2) / 3) * ((xfactor + 1) / 2) << (-shift));
      //lastone different
    }
    else {
      for (sums = linesums, xindex = (destext - 1) * source->bytespp;
        xindex > 0; xindex--)
      *pixel++ = (UINT8) ((*sums++ << shift) / divisor);
      //destination value
      for (xindex = source->bytespp; xindex > 0; xindex--)
                                 //last one different
        *pixel++ = (*(sums++) << shift) / (((yindex + 2) / 3) * ((xfactor + 1) / 2));
    }
                                 //put in destination
    dest->put_line (xdest, ydest, destext, &copyline, 0);
  }
  delete linesums;
}


/**********************************************************************
 * reduce_sub_image
 *
 * Reduce a portion of one image to a portion of another image.
 * If the bpps are different, the position of the most significant
 * bit is preserved.
 **********************************************************************/

DLLSYM void reduce_sub_image(                   //reduce rectangle
                             IMAGE *source,     //source image
                             INT32 xstart,      //start coords
                             INT32 ystart,
                             INT32 xext,        //extent to copy
                             INT32 yext,
                             IMAGE *dest,       //destination image
                             INT32 xdest,       //destination coords
                             INT32 ydest,
                             INT32 scale,       //reduction factor
                             BOOL8 adjust_grey  //shift to new bpp
                            ) {
  INT8 shift;                    //shift factor
  INT32 xfactor;                 //run on x coord
  INT32 divisor;                 //total cell area
  INT32 div2;                    //total cell area divided by 2
  INT32 xindex, yindex;          //into averaging square
  INT32 xcoord;                  //current x coord
  INT32 destext;                 //destination size
  INT32 yoffset;                 //current adjusted offset
  UINT8 *pixel;                  //ptr to source pixels
  INT32 *sums;                   //ptr to sums array
  IMAGELINE copyline;            //copy of line
  INT32 *linesums;               //averaging sums

  if (xstart < 0 || ystart < 0 || xdest < 0 || ydest < 0)
    return;
  if (xext <= 0)
    xext = source->xsize;        //default to all
  if (xext > source->xsize - xstart)
                                 //clip to smallest
      xext = source->xsize - xstart;
  if (xext > (dest->xsize - xdest) * scale)
    xext = (dest->xsize - xdest) * scale;
  if (yext <= 0)
    yext = source->ysize;        //default to all
  if (yext > source->ysize - ystart)
                                 //clip to smallest
      yext = source->ysize - ystart;
  if (yext > (dest->ysize - ydest) * scale)
    yext = (dest->ysize - ydest) * scale;
  if (xext <= 0 || yext <= 0)
    return;                      //nothing to do

  xfactor = xext % scale;        //left overs
  if (xfactor == 0)
    xfactor = scale;
                                 //destination pixels
  destext = (xext + scale - 1) / scale;
  if (adjust_grey)
                                 //shift factor
    shift = dest->bps - source->bps;
  else
    shift = 0;                   //no adjustment
  linesums = new INT32[destext * source->bytespp];

  for (yoffset = 0; yoffset < yext; ydest++) {
    source->check_legal_access (xstart, ystart + yoffset, xext);
    dest->check_legal_access (xdest, ydest, destext);
    for (xindex = 0; xindex < (destext) * source->bytespp; xindex++)
      linesums[xindex] = 0;      //zero sums
    for (yindex = 0; yindex < scale && ystart + yoffset < source->ysize;
    yindex++) {
      source->fast_get_line (xstart, ystart + yoffset, xext, &copyline);
      pixel = copyline.pixels;   //start of line
      if (source->bpp == 24) {
        for (xcoord = 1, sums = linesums; xcoord < destext;
        xcoord++, sums += 3) {
          for (xindex = 0; xindex < scale; xindex++) {
            *sums += *pixel++;
            *(sums + 1) += *pixel++;
            *(sums + 2) += *pixel++;
          }
        }
        for (xindex = 0; xindex < xfactor; xindex++) {
          *sums += *pixel++;
          *(sums + 1) += *pixel++;
          *(sums + 2) += *pixel++;
        }
      }
      else {
        for (xcoord = 1, sums = linesums; xcoord < destext;
        xcoord++, sums++) {
          for (xindex = 0; xindex < scale; xindex++)
            *sums += *pixel++;
        }
        for (xindex = 0; xindex < xfactor; xindex++)
          *sums += *pixel++;
      }
      yoffset++;                 //next line
    }
    copyline.init ();            //set pixels back to array
    copyline.set_bpp (source->bpp);
    pixel = copyline.pixels;
    divisor = yindex * scale;
    if (divisor == 0) {
      tprintf
        ("Impossible:divisor=0!, yindex=%d, scale=%d, yoffset=%d,yext=%d\n",
        yindex, scale, yoffset, yext);
      break;
    }
    if (shift <= 0) {
      divisor <<= (-shift);      //do greyscale correction
      div2 = divisor / 2;
      for (sums = linesums, xindex = (destext - 1) * source->bytespp;
        xindex > 0; xindex--)
      *pixel++ = (UINT8) ((div2 + *sums++) / divisor);
      //turn to destination value
      div2 = (yindex * xfactor << (-shift)) / 2;
      for (xindex = source->bytespp; xindex > 0; xindex--)
        *pixel++ =
          (UINT8) ((div2 + *sums++) / (yindex * xfactor << (-shift)));
      //lastone different
    }
    else {
      div2 = divisor / 2;
      for (sums = linesums, xindex = (destext - 1) * source->bytespp;
        xindex > 0; xindex--)
      *pixel++ = (UINT8) ((div2 + (*sums++ << shift)) / divisor);
      //destination value
      div2 = (yindex * xfactor) / 2;
      for (xindex = source->bytespp; xindex > 0; xindex--)
        *pixel++ =
          (UINT8) ((div2 + (*sums++ << shift)) / (yindex * xfactor));
      //last one different
    }
                                 //put in destination
    dest->put_line (xdest, ydest, destext, &copyline, 0);
  }
  delete linesums;
}


/**********************************************************************
 * invert_image
 *
 * Invert the given image (the slow way.)
 **********************************************************************/

DLLSYM void invert_image(              /*invert the image */
                         IMAGE *image  /*image ot invert */
                        ) {
  UINT8 mask;                    //bit mask
  UINT8 bytespp;                 //bytes per pixel
  INT32 xsize, ysize;            /*size of image */
  INT32 xindex, yindex;          /*index into image */
  UINT8 *pixel;                  /*current pixel */
  IMAGELINE line;                /*line of image */

  bytespp = image->get_bpp () == 24 ? 3 : 1;
  xsize = image->get_xsize ();   /*find sizes */
  ysize = image->get_ysize ();
                                 //pixel mask
  mask = (1 << image->get_bpp ()) - 1;
                                 /*do each line */
  for (yindex = ysize - 1; yindex >= 0; yindex--) {
    image->fast_get_line (0, yindex, xsize, &line);
    for (pixel = line.pixels, xindex = xsize * bytespp; xindex > 0;
      xindex--) {
      *pixel = (*pixel) ^ mask;  //invert image only
      ++pixel;
    }
                                 /*put it back */
    image->fast_put_line (0, yindex, xsize, &line);
  }
}


/**********************************************************************
 * bias_sub_image
 *
 * Add a constant to a portion of an image.
 **********************************************************************/

DLLSYM void bias_sub_image(                //bias rectangle
                           IMAGE *source,  //source image
                           INT32 xstart,   //start coords
                           INT32 ystart,
                           INT32 xext,     //extent to copy
                           INT32 yext,
                           UINT8 bias      //number to add
                          ) {
  IMAGELINE copyline;            //copy of line
  UINT8 *copy;                   //source pointer
  INT32 pixel;                   //pixel index
  INT32 y;                       //line index
  UINT8 bytespp;                 //bytes per pixel

  if (xstart < 0 || ystart < 0)
    return;
  if (xext <= 0)
    xext = source->get_xsize (); //default to all
  if (xext > source->get_xsize () - xstart)
                                 //clip to smallest
    xext = source->get_xsize () - xstart;
  if (yext <= 0)
    yext = source->get_ysize (); //default to all
  if (yext > source->get_ysize () - ystart)
                                 //clip to smallest
    yext = source->get_ysize () - ystart;
  if (xext <= 0 || yext <= 0)
    return;                      //nothing to do

  bytespp = source->get_bpp () == 24 ? 3 : 1;
  for (y = 0; y < yext; y++) {
    source->check_legal_access (xstart, ystart + y, xext);
    source->fast_get_line (xstart, ystart + y, xext, &copyline);
    for (pixel = xext * bytespp, copy = copyline.pixels; pixel > 0;
      pixel--, copy++)
    *copy += bias;               //add bias

    source->fast_put_line (xstart, ystart + y, xext, &copyline);
  }
}


/**********************************************************************
 * starbase_to_normal
 *
 * Copy a portion of one image to a portion of another image.
 * This function maps the colour tables used on the screen to
 * greyscale values in the way "normally" expected.
 **********************************************************************/

DLLSYM void starbase_to_normal(                     //copy rectangle
                               IMAGE *source,       //source image
                               INT32 xstart,        //start coords
                               INT32 ystart,
                               INT32 xext,          //extent to copy
                               INT32 yext,
                               IMAGE *dest,         //destination image
                               INT32 xdest,         //destination coords
                               INT32 ydest,
                               BOOL8 preserve_grey  //shift to new bpp
                              ) {
  IMAGELINE copyline;            //copy of line
  UINT8 *copy;                   //source pointer
  INT8 shift4;                   //shift factor
  INT8 shift6;                   //shift factor
  INT8 colour_shift;             //shift of colours
  UINT8 white_level;             //dest white value
  INT32 pixel;                   //pixel index
  INT32 y;                       //line index
  INT32 yoffset;                 //current adjusted offset
  INT8 srcppb;                   //pixels per byte

  if (xstart < 0 || ystart < 0 || xdest < 0 || ydest < 0)
    return;
  if (xext <= 0)
    xext = source->get_xsize (); //default to all
  if (xext > source->get_xsize () - xstart)
                                 //clip to smallest
    xext = source->get_xsize () - xstart;
  if (xext > dest->get_xsize () - xdest)
    xext = dest->get_xsize () - xdest;
  if (yext <= 0)
    yext = source->get_ysize (); //default to all
  if (yext > source->get_ysize () - ystart)
                                 //clip to smallest
    yext = source->get_ysize () - ystart;
  if (yext > dest->get_ysize () - ydest)
    yext = dest->get_ysize () - ydest;
  if (xext <= 0 || yext <= 0)
    return;                      //nothing to do

                                 //pixels per byte
  srcppb = 8 / source->get_bpp ();
  shift4 = 4 - dest->get_bpp (); //for different bpps
  shift6 = 6 - dest->get_bpp ();
                                 //for grey preserve
  colour_shift = 8 - dest->get_bpp ();
  white_level = dest->get_white_level ();
  for (y = 0; y < yext; y++) {
    if (ystart >= ydest)
      yoffset = y;               //top down
    else
      yoffset = yext - y - 1;    //bottom up
    source->check_legal_access (xstart, ystart + yoffset, xext);
    dest->check_legal_access (xdest, ydest + yoffset, xext);
    source->get_line (xstart, ystart + yoffset, xext, &copyline, 0);
    for (pixel = 0, copy = copyline.pixels; pixel < xext; pixel++) {
      if (*copy < FIXED_COLOURS && preserve_grey)
        *copy = grey_scales[*copy] >> colour_shift;
      else if (*copy < FIXED_COLOURS) {
        if (*copy == BLACK_PIX)
          *copy = white_level;   //black->white
        else
          *copy = 0;             //others->black
      }
      else if (*copy >= MIN_4BIT && *copy < MAX_4BIT) {
        if (shift4 < 0)
          *copy = (*copy - MIN_4BIT) << (-shift4);
        else
          *copy = (*copy - MIN_4BIT) >> shift4;
      }
      else if (*copy >= MIN_6BIT && *copy < MAX_6BIT) {
        if (shift6 < 0)
          *copy = (*copy - MIN_6BIT) << (-shift6);
        else
          *copy = (*copy - MIN_6BIT) >> shift6;
      }
      else {
        *copy = white_level;     //white the rest
      }
      copy++;
    }
    dest->put_line (xdest, ydest + yoffset, xext, &copyline, 0);
  }
}


/**********************************************************************
 * fast_get_line
 *
 * Get a line of image into the supplied image line buffer.
 * The image is converted to 8bpp by simple assignment.
 * If the image is aleady 8 or 6bpp, no copy is done and a pointer
 * to the correct image section is put in the line buffer.
 **********************************************************************/

void IMAGE::fast_get_line(                    //get image line
                          INT32 x,            //coord to start at
                          INT32 y,            //line to get
                          INT32 width,        //no of pixels to get
                          IMAGELINE *linebuf  //line to copy to
                         ) {
  if (width > 0 && bpp > 4) {
    check_legal_access(x, y, width);
                                 //get pointer only
    linebuf->pixels = image + xdim * (ymax - 1 - y) + x * bytespp;
  }
  else
                                 //just copy it
    this->get_line (x, y, width, linebuf, 0);
  linebuf->bpp = bpp;
}


/**********************************************************************
 * get_line
 *
 * Get a line of image into the supplied image line buffer.
 * The image is converted to 8bpp by simple assignment.
 **********************************************************************/

void IMAGE::get_line(                     //get image line
                     INT32 x,             //coord to start at
                     INT32 y,             //line to get
                     INT32 width,         //no of pixels to get
                     IMAGELINE *linebuf,  //line to copy to
                     INT32 margins        //size of margins
                    ) {
  UINT8 *src;                    //source pointer
  UINT8 *dest;                   //destination pointer
  UINT8 *unpacksrc;              //unpacking pointer
  INT8 bit;                      //bit index
  INT8 pixperbyte;               //pixels per byte
  UINT8 white;                   //white colour
  INT32 pixel;                   //pixel index

                                 //test coords
  this->check_legal_access (x, y, width);
  if (width > xsize - x)
    width = xsize - x;           //clip to image
  width *= bytespp;
  linebuf->init (width + margins * bytespp * 2);
  linebuf->bpp = bpp;
                                 //start of line
  src = image + xdim * (ymax - 1 - y);
  dest = linebuf->line;          //destination line
  linebuf->pixels = dest;
  white = (1 << bpp) - 1;        //max value of pixel
  for (pixel = margins * bytespp; pixel > 0; pixel--) {
    *dest++ = white;             //margins are white
  }
  if (width > 0) {
    if (bpp > 4) {
      src += x;                  //offset
                                 //easy way
      memmove (dest, src, (unsigned) width);
    }
    else if (bpp == 4) {
      src += x / 2;              //offset on line
      if (x & 1) {
                                 //get coded nibble
        *dest++ = bpp4table[*src++][1];
        width--;
      }
      while (width >= 2) {
                                 //get coded bits
        unpacksrc = bpp4table[*src++];
        *dest++ = *unpacksrc++;
        *dest++ = *unpacksrc++;  //copy nibbles
        width -= 2;
      }
      if (width) {
                                 //get coded nibble
        *dest++ = bpp4table[*src++][0];
      }
    }
    else if (bpp == 2) {
      pixperbyte = 4;
      src += x / 4;              //offset on line
      bit = (INT8) (x % 4);      //offset in byte
      width += bit;
      while (width > 0) {        //until all done
        if (width < pixperbyte)
                                 //less on last byte
          pixperbyte = (INT8) width;
                                 //get coded bits
        unpacksrc = &bpp2table[*src++][bit];
        for (; bit < pixperbyte; bit++)
          *dest++ = *unpacksrc++;//copy bytes
        width -= pixperbyte;
        bit = 0;
      }
    }
    else {
      pixperbyte = 8;
      src += x / 8;              //offset on line
      bit = (INT8) (x % 8);      //offset in byte
      width += bit;
      while (width > 0) {        //until all done
        if (width < pixperbyte)
                                 //less on last byte
          pixperbyte = (INT8) width;
                                 //get coded bits
        unpacksrc = &bpp1table[*src++][bit];
        for (; bit < pixperbyte; bit++)
          *dest++ = *unpacksrc++;//copy bytes
        width -= pixperbyte;
        bit = 0;
      }
    }
  }
  for (pixel = margins * bytespp; pixel > 0; pixel--) {
    *dest++ = white;             //margins are white
  }
}


/**********************************************************************
 * get_column
 *
 * Get a column of image into the supplied image line buffer.
 * The image is converted to 8bpp by simple assignment.
 **********************************************************************/

void IMAGE::get_column(                     //get image column
                       INT32 x,             //coord to start at
                       INT32 y,             //line to get
                       INT32 height,        //no of pixels to get
                       IMAGELINE *linebuf,  //line to copy to
                       INT32 margins        //size of margins
                      ) {
  UINT8 *src;                    //source pointer
  UINT8 *dest;                   //destination pointer
  INT8 bit;                      //bit index
  INT8 pixperbyte;               //pixels per byte
  UINT8 white;                   //white colour
  INT32 pixel;                   //pixel index

                                 //test coords
  this->check_legal_access (x, y, 1);
                                 //test coords
  this->check_legal_access (x, y + height - 1, 1);
  if (height > ysize - y)
    height = ysize - y;          //clip to image
  linebuf->init (height * bytespp + margins * bytespp * 2);
                                 //start of line
  src = image + xdim * (ymax - 1 - y);
  dest = linebuf->line;          //destination line
  linebuf->pixels = dest;
  white = (1 << bpp) - 1;        //max value of pixel
  for (pixel = margins * bytespp; pixel > 0; pixel--) {
    *dest++ = white;             //margins are white
  }
  if (height > 0) {
    if (bpp == 24) {
      src += x * bytespp;        //offset
      for (; height > 0; --height) {
        *dest++ = *src;          //copy bytes
        *dest++ = *(src + 1);
        *dest++ = *(src + 2);
        src -= xdim;
      }
    }
    else if (bpp > 4) {
      src += x;
      for (; height > 0; --height) {
        *dest++ = *src;          //copy bytes
        src -= xdim;
      }
    }
    else if (bpp == 4) {
      src += x / 2;              //offset on line
      if (x & 1) {
        for (; height > 0; --height) {
                                 //get coded nibble
          *dest++ = bpp4table[*src][1];
          src -= xdim;
        }
      }
      else {
        for (; height > 0; --height) {
                                 //get coded nibble
          *dest++ = bpp4table[*src][0];
          src -= xdim;
        }
      }
    }
    else if (bpp == 2) {
      pixperbyte = 4;
      src += x / 4;              //offset on line
      bit = (INT8) (x % 4);      //offset in byte
      for (; height > 0; --height) {
                                 //get coded bits
        *dest++ = bpp2table[*src][bit];
        src -= xdim;
      }
    }
    else {
      pixperbyte = 8;
      src += x / 8;              //offset on line
      bit = (INT8) (x % 8);      //offset in byte
      for (; height > 0; --height) {
                                 //get coded bits
        *dest++ = bpp1table[*src][bit];
        src -= xdim;
      }
    }
  }
  for (pixel = margins * bytespp; pixel > 0; pixel--) {
    *dest++ = white;             //margins are white
  }
}


/**********************************************************************
 * fast_put_line
 *
 * Put a line buffer back into the image.
 * If the line buffer merely points back into the image, nothing is done.
 * Otherwise, put_line is used to copy the line back.
 **********************************************************************/

void IMAGE::fast_put_line(                    //put image line
                          INT32 x,            //coord to start at
                          INT32 y,            //line to get
                          INT32 width,        //no of pixels to put
                          IMAGELINE *linebuf  //line to copy to
                         ) {
  if (width > 0 && (bpp <= 4 || linebuf->pixels == linebuf->line))
                                 //just copy it
    put_line (x, y, width, linebuf, 0);
}


/**********************************************************************
 * put_line
 *
 * Put the supplied line buffer into the image.
 * The image is converted from 8bpp by simple assignment.
 **********************************************************************/

void IMAGE::put_line(                     //put image line
                     INT32 x,             //coord to start at
                     INT32 y,             //line to get
                     INT32 width,         //no of pixels to get
                     IMAGELINE *linebuf,  //line to copy to
                     INT32 margins        //margins in buffer
                    ) {
  UINT8 *src;                    //source pointer
  UINT8 *dest;                   //destination pointer
  INT8 bit;                      //bit index
  UINT8 pixel;                   //collected bits
  INT8 pixperbyte;               //pixels in a byte
  INT8 bytesperpix;              //in source

  this->check_legal_access (x, y, width);
  if (width > xsize - x)
    width = xsize - x;           //clip to image
  if (width <= 0)
    return;                      //nothing to do
                                 //source line
  src = linebuf->pixels + margins;
                                 //start of line
  dest = image + xdim * (ymax - 1 - y);

  if (linebuf->bpp == 24) {
    src++;
    bytesperpix = 3;
  }
  else
    bytesperpix = 1;
  if (bpp == 24 && linebuf->bpp == 24) {
    dest += x * bytespp;
    width *= bytespp;
    memmove (dest, src - 1, (unsigned) width);
  }
  else if (bpp == 24) {
    src--;
    dest += x * bytespp;
    while (width > 0) {
      pixel = *src++;
      *dest++ = pixel;
      *dest++ = pixel;
      *dest++ = pixel;
      width--;
    }
  }
  else if (bpp > 4) {
    dest += x;                   //offset
    if (linebuf->bpp == 24) {
      while (width > 0) {
        *dest++ = *src;
        src += 3;
        width--;
      }
    }
    else
                                 //easy way
      memmove (dest, src, (unsigned) width);
  }
  else if (bpp == 4) {
    dest += x / 2;               //offset on line
    if (x & 1) {
      *dest &= 0xf0;             //clean odd byte
      *dest++ |= *src & 0x0f;    //and copy it
      src += bytesperpix;
      width--;
    }
    while (width >= 2) {
      pixel = *src << 4;         //left pixel
      src += bytesperpix;
      pixel |= *src & 0x0f;      //right pixel
      src += bytesperpix;
      *dest++ = pixel;
      width -= 2;
    }
    if (width) {
      *dest &= 0x0f;             //clean odd byte
      *dest |= *src << 4;
    }
  }
  else if (bpp == 2) {
    pixperbyte = 4;
    dest += x / 4;               //offset on line
    bit = (INT8) (x % 4);        //offset in byte
    width += bit;
    pixel = *dest >> (8 - bit - bit);
    while (width >= 4) {         //until all done
      for (; bit < 4; bit++) {
        pixel <<= 2;             //make space for new one
        pixel |= *src & 3;
        src += bytesperpix;
      }
      *dest++ = pixel;           //new pixel
      width -= 4;
      bit = 0;
    }
    if (width > 0) {             //until all done
      for (bit = 0; bit < width; bit++) {
        pixel <<= 2;             //make space for new one
        pixel |= *src & 3;
        src += bytesperpix;
      }
      pixel <<= (8 - bit - bit); //shift rest
                                 //keep trainling bits
      pixel |= *dest & ((1 << (8 - bit - bit)) - 1);
      *dest++ = pixel;           //new pixel
    }
  }
  else {
    pixperbyte = 8;
    dest += x / 8;               //offset on line
    bit = (INT8) (x % 8);        //offset in byte
    width += bit;
    pixel = *dest >> (8 - bit);
    while (width >= 8) {         //until all done
      for (; bit < 8; bit++) {
        pixel <<= 1;             //make space for new one
        pixel |= *src & 1;
        src += bytesperpix;
      }
      *dest++ = pixel;           //new pixel
      width -= 8;
      bit = 0;
    }
    width -= bit;
    if (width > 0) {             //until all done
      while (width > 0) {
        pixel <<= 1;             //make space for new one
        pixel |= *src & 1;
        src += bytesperpix;
        bit++;
        width--;
      }
      pixel <<= (8 - bit);       //shift rest
                                 //keep trainling bits
      pixel |= *dest & ((1 << (8 - bit)) - 1);
      *dest++ = pixel;           //new pixel
    }
  }
}


/**********************************************************************
 * put_column
 *
 * Put the supplied column buffer into the image.
 * The image is converted from 8bpp by simple assignment.
 **********************************************************************/

void IMAGE::put_column(                     //put image column
                       INT32 x,             //coord to start at
                       INT32 y,             //line to get
                       INT32 height,        //no of pixels to get
                       IMAGELINE *linebuf,  //line to copy to
                       INT32 margins        //margins in buffer
                      ) {
  UINT8 *src;                    //source pointer
  UINT8 *dest;                   //destination pointer
  INT8 bit;                      //bit index
  UINT8 pixel;                   //collected bits
  INT8 bytesperpix;              //in source

  this->check_legal_access (x, y, 1);
  this->check_legal_access (x, y + height - 1, 1);
  if (height > ysize - y)
    height = ysize - y;          //clip to image
  if (height <= 0)
    return;                      //nothing to do
                                 //source line
  src = linebuf->pixels + margins;
                                 //start of line
  dest = image + xdim * (ymax - 1 - y);

  if (linebuf->bpp == 24) {
    src++;
    bytesperpix = 3;
  }
  else
    bytesperpix = 1;

  if (bpp == 24 && linebuf->bpp == 24) {
    dest += x * bytesperpix;
    src--;
    for (; height > 0; --height) {
      *dest = *src++;
      *(dest + 1) = *src++;
      *(dest + 2) = *src++;
      dest -= xdim;
    }
  }
  else if (bpp == 24) {
    src--;
    dest += x * bytesperpix;
    for (; height > 0; --height) {
      pixel = *src++;
      *dest = pixel;
      *(dest + 1) = pixel;
      *(dest + 2) = pixel;
      dest -= xdim;
    }
  }
  else if (bpp > 4) {
    dest += x;                   //offset
    for (; height > 0; --height) {
      *dest = *src;
      src += bytesperpix;
      dest -= xdim;
    }
  }
  else if (bpp == 4) {
    dest += x / 2;               //offset on line
    if (x & 1) {
      for (; height > 0; --height) {
        *dest &= 0xf0;           //clean odd byte
        *dest |= *src & 0x0f;    //and copy it
        src += bytesperpix;
        dest -= xdim;
      }
    }
    else {
      for (; height > 0; --height) {
        *dest &= 0x0f;           //clean odd byte
        *dest |= *src << 4;
        src += bytesperpix;
        dest -= xdim;
      }
    }
  }
  else if (bpp == 2) {
    dest += x / 4;               //offset on line
    bit = (INT8) (x % 4);        //offset in byte
    bit = 6 - bit - bit;         //bit shift
    pixel = ~(3 << bit);         //mask
    for (; height > 0; --height) {
                                 //change 2 bits
      *dest = (*dest & pixel) | ((*src & 3) << bit);
      src += bytesperpix;
      dest -= xdim;
    }
  }
  else {
    dest += x / 8;               //offset on line
    bit = (INT8) (x % 8);        //offset in byte
    bit = 7 - bit;
    pixel = ~(1 << bit);
    for (; height > 0; --height) {
                                 //change 1 bit
      *dest = (*dest & pixel) | ((*src & 1) << bit);
      src += bytesperpix;
      dest -= xdim;
    }
  }
}


/**********************************************************************
 * check_legal_access
 *
 * Check that x,y are within the bounds of the image.
 * Call bufread if necessary to get the image into memory.
 **********************************************************************/

void IMAGE::check_legal_access(            //check coords are legal
                               INT32 x,    //coords to check
                               INT32 y,
                               INT32 xext  //xextent
                              ) {
  if (x < 0 || x >= xsize || y < 0 || y >= ysize || x + xext > xsize)
    BADIMAGECOORDS.error ("IMAGE::check_legal_access",
      ABORT, "(%d+%d,%d)", x, xext, y);
  if (y >= ymax)
    BADIMAGESEEK.error ("IMAGE::check_legal_access", ABORT, "(%d,%d)", x, y);
  if (y < ymin)
    bufread(y);  //read some more
}


/*************************************************************************
 * convolver()
 *
 * Calls the specified function for each pixel in the image, passing in an m x n
 * window of the image, centred on the pixel.  The convolution function returns
 * a new value for the pixel, based on the window.
 *
 * At the edges of the image, the window is padded to white pixels.
 *************************************************************************/

void
IMAGE::convolver (               //Map fn over window
INT32 win_width,                 //Window width
INT32 win_height,                //Window height
void (*convolve) (               //Conv Function
UINT8 ** pixels,                 //Of window
UINT8 bytespp,                   //1 or 3 for colour
INT32 win_wd,                    //Window width
INT32 win_ht,                    //Window height
UINT8 ret_white_value,           //White value to RETURN
UINT8 * result)                  //Ptr to result pix
) {
  IMAGELINE new_row;             //Replacement pixels
  IMAGELINE *old_rows;           //Rows being processed
  INT32 oldest_imline;           //Next imline to replace
  UINT8 **window;                //ptrs to pixel rows
  UINT8 **winmax;                //ptrs to pixel rows
  UINT8 **win;                   //ptrs to pixel rows
  INT32 current_row;             //Row being calculated
  INT32 current_col;             //Col being calculated
  INT32 row = 0;                 //Next row to get

  INT32 i, j;
  UINT8 *pix;
  UINT8 *max;
  INT32 xmargin = win_width / 2;
  INT32 ymargin = win_height / 2;
  UINT8 white = get_white_level ();
  const UINT8 max_white = 255;
  float white_scale = (float) 255 / get_white_level ();

  if (((win_width % 2) == 0) ||
    ((win_height % 2) == 0) ||
    (win_height < 3) ||
    (win_width < 3) || (win_height > ysize / 2) || (win_width > xsize / 2))
    BADWINDOW.error ("IMAGE::convolver",
      ABORT, "(%d x %d)", win_width, win_height);

  new_row.init (xsize * bytespp);
  new_row.set_bpp (bpp);
  old_rows = new IMAGELINE[win_height];
  for (i = 0; i < win_height; i++) {
    old_rows[i].init ((xsize + 2 * xmargin) * bytespp);
    old_rows[i].set_bpp (bpp);
  }

  window = (UINT8 **) alloc_mem (win_height * sizeof (UINT8 *));
  winmax = window + win_height;

  /* Make bottom border */
  for (oldest_imline = 0; oldest_imline < ymargin; oldest_imline++) {
    pix = old_rows[oldest_imline].pixels;
    max = pix + (xsize + 2 * xmargin) * bytespp;
    while (pix < max)
      *pix++ = max_white;
  }
  /* Initialise remaining rows but one*/
  for (; oldest_imline < win_height - 1; oldest_imline++) {
    get_line (0, row++, xsize, &old_rows[oldest_imline], xmargin);
    if (max_white != white) {
      pix = old_rows[oldest_imline].pixels;
      max = pix + (xsize + 2 * xmargin) * bytespp;
      while (pix < max) {
        *pix = (UINT8) (*pix * white_scale);
        ++pix;
      }
    }
  }

  /* Image Processing */

  for (current_row = 0; current_row < ysize;) {
    /* Get next row and re-initialise window array */
    if (row < ysize) {
      get_line (0, row++, xsize, &old_rows[oldest_imline], xmargin);
      if (max_white != white) {
        pix = old_rows[oldest_imline].pixels;
        max = pix + (xsize + 2 * xmargin) * bytespp;
        while (pix < max) {
          *pix = (UINT8) (*pix * white_scale);
          ++pix;
        }
      }
    }
    else {
      pix = old_rows[oldest_imline].pixels;
      max = pix + (xsize + 2 * xmargin) * bytespp;
      while (pix < max)
        *pix++ = max_white;
    }
    oldest_imline++;
    if (oldest_imline >= win_height)
      oldest_imline = 0;

    /* Process line */
    pix = new_row.pixels;
    for (current_col = 0; current_col < xsize;) {
      /* Set up window ptrs */
      if (current_col == 0) {
        j = oldest_imline;
        for (i = 0; i < win_height; i++) {
          window[i] = old_rows[j++].pixels;
          if (j >= win_height)
            j = 0;
        }
      }
      else {
        for (win = window; win < winmax; (*win++) += bytespp);
        //Move along rows
      }

      convolve(window, bytespp, win_width, win_height, white, pix);
      pix += bytespp;
      current_col++;
    }

    put_line (0, current_row, xsize, &new_row, 0);
    new_row.init ();
    new_row.set_bpp (bpp);
    current_row++;
  }
}
