/* RgbaImage.c : RGBA image handling routines
**
** Written and Copyright (C) 1994, 1996 by Michael J. Gourlay
**
** PROVIDED AS IS.  NO WARRANTEES, EXPRESS OR IMPLIED.
*/

#include "my_malloc.h"

#include "tga.h"

#include "RgbaImage.h"

#define MAX(x,y) ((x)>(y) ? (x) : (y))




/* --------------------------------------------------------------- */

/* rgbaImageInit: initialize members of a RgbaImage
*/
void
rgbaImageInit(RgbaImageT *imgP)
{
  imgP->nrows = 0;
  imgP->ncols = 0;
  imgP->compressed = 0;
  imgP->pixel_size = 0;
  imgP->color_mapped = 0;
  imgP->type = 0;
  imgP->ri = NULL;
  imgP->gi = NULL;
  imgP->bi = NULL;
  imgP->ai = NULL;
}




/* --------------------------------------------------------------- */

/* rgbaImageFree: free memory of the RgbaImage channels.
*/
void
rgbaImageFree(RgbaImageT *imgP)
{
  if(imgP->ri != NULL) {
    my_free(imgP->ri, "rgbaImageFree");
    imgP->ri = NULL;
    imgP->gi = NULL;
    imgP->bi = NULL;
    imgP->ai = NULL;
    imgP->nrows = 0;
    imgP->ncols = 0;
  }
}




/* --------------------------------------------------------------- */

/* rgbaImageAlloc: allocate memory for the RgbaImage channels
//
// proc (in): name of the calling procedure (for debugging)
//
// Return -1 if failed, 0 otherwize.
*/
int
rgbaImageAlloc(RgbaImageT *imgP, const char *proc)
{
  /* see whether there was un-freed memory here before */
  if((imgP->ri != NULL) || (imgP->gi != NULL) || (imgP->bi != NULL)) {
    fprintf(stderr,
            "rgbaImageAlloc: warning: allocating over un-freed rgbaImage from %s\n",
            proc);
  }

  /* Make sure the image size isn't zero */
  if((imgP->ncols * imgP->nrows) == 0) {
    fprintf(stderr,
            "rgbaImageAlloc: warning: zero size from %s\n",
            proc);
  }
  /* Use only one allocation to ensure that the image data is
  // contiguous.  This makes it easier to use other image format schemes
  // which have parameters such as "pitch" which is the address
  // difference between two vertically adjacent pixels, and "offset[3]"
  // which has the offsets from the address of a pixel to the addresses
  // of the bytes containing red, green, and blue components.  I.e. some
  // formats can use either XY interleaving or Z stacking, just by
  // altering these parameters.
  // -- 02aug96 MJG
  */
  if((imgP->ri=MY_CALLOC(imgP->ncols * imgP->nrows * 4,
                                   UCHAR, "rgbaImageAlloc")) == NULL)
  {
    fprintf(stderr, "rgbaImageAlloc: Bad Alloc from %s\n", proc);
    return(-1);
  }

  imgP->gi = & ( imgP->ri[imgP->ncols * imgP->nrows * 1] );
  imgP->bi = & ( imgP->ri[imgP->ncols * imgP->nrows * 2] );
  imgP->ai = & ( imgP->ri[imgP->ncols * imgP->nrows * 3] );

  return(0);
}




/* rgbaImageDissolve: Dissolve two images
//
// tiP (out): tween image.  Arrays allocated here.
//
// siP (in): "source" image.
//
// diP (in): "destination" image.  "Ignored" if NULL.
//           If diP is NULL then it is as if dest_image is black.
//
// dissolve (in): dissolve parameter
//   where out = (1-dissolve) * source_image + dissolve * dest_image
//   e.g. if dissolve==0, out=source_image.  If dissolve==1, out=dest_image.
//
*/
int
rgbaImageDissolve(RgbaImageT *tiP, const RgbaImageT *siP, const RgbaImageT *diP, double dissolve)
{
  int         nx;                 /* image x-size */
  int         xi, yi;             /* loop indices */
  int         rsi, gsi, bsi, asi; /* image channel pixel values */
  int         rdi, gdi, bdi, adi; /* image channel pixel values */

  /* See whether diP image exists */
  if(diP != NULL) {
    /* Make sure siP and diP images are the same size */
    if((siP->nrows != diP->nrows) || (siP->ncols != diP->ncols)) {
      fprintf(stderr, "rgbaImageDissolve: input image size mismatch\n");
      return -1;
    }

    if(siP->compressed || diP->compressed) tiP->compressed = 1;
    tiP->pixel_size = MAX(siP->pixel_size, diP->pixel_size);
    if(siP->color_mapped && diP->color_mapped) tiP->color_mapped = 1;
  } else {
    if(siP->compressed) tiP->compressed = 1;
    tiP->pixel_size = siP->pixel_size;
    if(siP->color_mapped) tiP->color_mapped = 1;
  }

  /* Initialize the dissolved image */
  tiP->nrows = siP->nrows;
  nx = tiP->ncols = siP->ncols;

  tiP->compressed = tiP->color_mapped = 0;
  tiP->pixel_size = 32;

  /* Allocate space for dissolved image data */
  if(rgbaImageAlloc(tiP, "rgbaImageDissolve")) return -1;

  /* Dissolve the two images according to the dissolve parameter */
  for(yi=0; yi< tiP->nrows; yi++) {
    for(xi=0; xi<nx; xi++) {
      rsi = (1.0-dissolve) * siP->ri[yi * nx + xi];
      gsi = (1.0-dissolve) * siP->gi[yi * nx + xi];
      bsi = (1.0-dissolve) * siP->bi[yi * nx + xi];
      asi = (1.0-dissolve) * siP->ai[yi * nx + xi];
      if((diP!=NULL) && (xi<diP->ncols) && (yi < diP->nrows)) {
        rdi = dissolve * diP->ri[yi * diP->ncols + xi];
        gdi = dissolve * diP->gi[yi * diP->ncols + xi];
        bdi = dissolve * diP->bi[yi * diP->ncols + xi];
        adi = dissolve * diP->ai[yi * diP->ncols + xi];
      } else {
        rdi = 0;
        gdi = 0;
        bdi = 0;
        adi = 0;
      }
      tiP->ri[yi*nx+xi] = (int)(rsi + rdi + 0.5);
      tiP->gi[yi*nx+xi] = (int)(gsi + gdi + 0.5);
      tiP->bi[yi*nx+xi] = (int)(bsi + bdi + 0.5);
      tiP->ai[yi*nx+xi] = (int)(asi + adi + 0.5);
    }
  }

  return 0;
}




/* --------------------------------------------------------------- */

/* rgbaImageRead: load image into memory.
// -- Frees old image channel space.
// -- Allocates new image channel space.
*/
int
rgbaImageRead(const char *fn, RgbaImageT *imgP)
{
  int           tga_return;
  tga_hdr_t     tga_hdr;
  FILE         *infP=NULL;

  /* Open the input file for binary reading */
  if(fn!=NULL && (infP=fopen(fn, "rb"))==NULL) {
    fprintf(stderr, "rgbaImageRead: could not open '%s' for input\n", fn);
    return -1;
  }

  /* Load the image header:
  // This will set imgP members such as ncols, nrows, etc.
  */
    /* Targa */
    if(tga_return = load_tga_header(&tga_hdr, imgP, infP)) {
      fprintf(stderr, "load_tga_header returned %i\n", tga_return);
      return(tga_return);
    }

  /* Free the memory for the previous image planes.
  // This must be done AFTER the load attempt, because if the load
  // fails, we want to keep the original image.
  */
  {
    int ncols = imgP->ncols; /* store geometry set by load_header */
    int nrows = imgP->nrows; /* store geometry set by load_header*/
    rgbaImageFree(imgP);     /* this sets ncols = nrows = 0 */
    imgP->ncols = ncols;     /* retrieve geometry */
    imgP->nrows = nrows;     /* retrieve geometry */
  }

  /* Allocate memory for the new image channels */
  if(rgbaImageAlloc(imgP, "rgbaImageRead")) return -1;

  /* Load the new image */
    /* Targa */
    load_tga_image(&tga_hdr, imgP, infP);

  /* Close the input file */
  fclose(infP);

  return 0;
}




/* --------------------------------------------------------------- */

/* rgbaImageWrite: dissolve 2 images and save dissolved image to file
//
// Dimensions of the output image are the same as the source_image.
//
// "source" and "destination" do NOT refer to the disk space where the
// image is being written.  They refer to the starting and finishing
// images in the dissolve.
//
// fn (in): file name to save image to
//
// siP (in): "source" image pointer
//
// diP (in): "destination" image pointer.  Ignored if NULL.
//           If diP is NULL then it is as if dest_image is black.
//
// dissolve (in): dissolve parameter
//   where out = (1-dissolve) * source_image + dissolve * dest_image
//   e.g. if dissolve==0, out=source_image.  If dissolve==1, out=dest_image.
*/
int
rgbaImageWrite(const char *fn, const RgbaImageT *siP, const RgbaImageT *diP, double dissolve)
{
  RgbaImageT  img;                /* temporary dissolved image */
  FILE        *outfP=NULL;        /* output file pointer */

  /* Dissolve the siP and diP images into img */
  rgbaImageInit(&img);
  if(rgbaImageDissolve(&img, siP, diP, dissolve)) {
    return -1;
  }

  /* Open the output image file for binary writing */
  if(fn!=NULL && (outfP=fopen(fn, "wb"))==NULL) {
    fprintf(stderr, "rgbaImageWrite: could not open '%s' for output\n", fn);
    return -1;
  }

  {
    /* Set the image header */
    tga_hdr_t   tga_hdr;

    /* Targa */
    tga_hdr.id_len = 0;

    /* cmap_type depends on the img_type */
    tga_hdr.cmap_type = 0;

    /* img_type comes from the user */
    tga_hdr.img_type = TGA_RGB;

    if(img.compressed) tga_hdr.img_type += TGA_RLE;

    tga_hdr.cmap_index = 0;

    /* cmap_len depends on the img_type and pixel_size */
    tga_hdr.cmap_len = 0;

    /* cmap_size depends on the img_type and pixel_size */
    tga_hdr.cmap_size = 0;

    tga_hdr.x_off = 0;
    tga_hdr.y_off = 0;

    /* pixel_size depends on the img_type */
    tga_hdr.pixel_size = img.pixel_size;

    tga_hdr.att_bits = 0;
    tga_hdr.reserved = 0;
    tga_hdr.origin_bit = 0;
    tga_hdr.interleave = TGA_IL_None;

    /* Save the image header */
    {
      int         tga_return; /* return values from save_tga_header */
      /* Targa */
      if(tga_return = save_tga_header(&tga_hdr, &img, outfP)) {
        fprintf(stderr, "save_tga_header returned %i\n", tga_return);
        return tga_return;
      }
    }

    /* Save the dissolved image */
      /* Targa */
      save_tga_image(&tga_hdr, &img, outfP);
  }

  /* Free the dissolved image */
  rgbaImageFree(&img);

  /* Close the output image file */
  fclose(outfP);

  return 0;
}




/* --------------------------------------------------------------- */

/* rgbaImageCreateTest: generate a test image
//
// Uses the incoming values of ncols and nrows to determine image size.
// If ncols or nrows are zero, default values are used instead.
//
// type: bitfield: which test image pattern to use.
//
// Memory for the images is allocated and imgP is set.
*/
int
rgbaImageCreateTest(RgbaImageT *imgP, int type)
{
  int   xi, yi;          /* pixel coordinate indices */
  UCHAR p;               /* pixel value */
  int   alloc_flag = 0;  /* whether to allocate an image */

  imgP->compressed = 1;
  imgP->color_mapped = 0;
  imgP->pixel_size = 24;
  imgP->type = TARGA_MAGIC;

  /* Test to see whether previous rgba image had any area.
  // If not, then create a default area.
  */
  if(imgP->ncols <= 0) {
    imgP->ncols = 320;
    alloc_flag = 1;
  }
  if(imgP->nrows <= 0) {
    imgP->nrows = 240;
    alloc_flag = 1;
  }

  /* Another possibility is that the size could have been set before
  // calling this routine, but no memory had yet been allocated.
  // In which case, memory ought to be allocated now.
  //
  // This might seem unusual-- allocating memory for the first time in
  // a routine which is not the object constructor.  But in a sense, this
  // is the rgba image constructor -- It generates an image, often for
  // the first time, simply to occupy screen space to indicate that the
  // image exists.  But sometimes, this routine is also used to simply
  // create a test image to erase a previous image, in which case this
  // routine does not act like a constructor.
  //
  // Strictly speaking, this routine should not allocate space.
  // Allocation ought to be done in a separate place entirely.
  // This routine ought to simply be a test image creator.
  */
  if((imgP->ri == NULL) || (imgP->gi == NULL) || (imgP->bi == NULL)) {
    alloc_flag = 1;
  }

  if(alloc_flag) {
    if(rgbaImageAlloc(imgP, "rgbaImageCreateTest")) return 1;
  }

  /* Create the test pattern */
  for(yi=0; yi < imgP->nrows; yi++) {
    for(xi=0; xi < imgP->ncols; xi++) {

      p = 15 + 240*((float)xi/imgP->ncols)*((float)yi/imgP->nrows);

      if((xi%40>20 && yi%40<20) || (xi%40<20 && yi%40>20))
        p=0;

      if(type & 1) {
        imgP->ri[yi*(imgP->ncols) + xi] = p;
      } else {
        imgP->ri[yi*(imgP->ncols) + xi] = RGBA_IMAGE_MAXVAL - p;
      }

      if(type & 2) {
        imgP->gi[yi*(imgP->ncols) + xi] = p;
      } else {
        imgP->gi[yi*(imgP->ncols) + xi] = RGBA_IMAGE_MAXVAL - p;
      }

      if(type & 4) {
        imgP->bi[yi*(imgP->ncols) + xi] = p;
      } else {
        imgP->bi[yi*(imgP->ncols) + xi] = RGBA_IMAGE_MAXVAL - p;
      }

      imgP->ai[yi*(imgP->ncols) + xi] = RGBA_IMAGE_OPAQUE;
    }
  }
  return(0);
}
