/* See jspnm_image.h */
/* Last edited on 2012-06-09 19:28:40 by stolfilocal */

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

#include <affirm.h>
#include <jsfile.h>
#include <jspnm.h>
#include <jspnm_image.h>

pnm_image_t *pnm_image_new(int cols, int rows, int chns)
  { /* Allocate header: */
    pnm_image_t *img = pnm_image_new_header(cols, rows, chns);
    /* Allocate pixels: */
    if ((cols != 0) && (rows != 0))
      { img->smp = pnm_image_alloc_pixel_array(cols, rows, chns); }
    return(img);
  }
  
void pnm_image_free(pnm_image_t *img)
  { if (img == NULL) return;
    if (img->smp != NULL) 
      { pnm_image_free_pixel_array(img->smp, img->cols, img->rows, img->chns); }
    free(img);
  }

pnm_image_t *pnm_image_new_header(int cols, int rows, int chns)
  { /* Allocate header: */
    pnm_image_t *img = (pnm_image_t *)pnm_malloc(sizeof(pnm_image_t));
    assert(img != NULL);
    /* Initialize fields: */
    img->cols = cols;
    img->rows = rows;
    img->chns = chns;
    /* Leave sample arrays as NULL: */
    img->smp = (pnm_sample_t**)NULL;
    /* Initialize the {maxval} field as 0 (invalid): */
    img->maxval = 0;
    return(img);
  }
  
pnm_sample_t** pnm_image_alloc_pixel_array(int cols, int rows, int chns)
  { int row;
    pnm_sample_t **smp = (pnm_sample_t **)pnm_malloc(rows*sizeof(pnm_sample_t *));
    for (row = 0; row < rows; row++)
      { smp[row] = pnm_image_alloc_pixel_row(cols, chns); }
    return smp;
  }
  
void pnm_image_free_pixel_array(pnm_sample_t **smp, int cols, int rows, int chns)
  { int row;
    if (smp == NULL) { return; }
    for (row = 0; row < rows; row++) 
      { pnm_image_free_pixel_row(smp[row], cols, chns); }
    free(smp);
  }

pnm_sample_t* pnm_image_alloc_pixel_row(int cols, int chns)
  { if (cols == 0)
      { return (pnm_sample_t *)NULL; }
    else
      { return (pnm_sample_t *)pnm_malloc(cols*chns*sizeof(pnm_sample_t)); }
  }
  
void pnm_image_free_pixel_row(pnm_sample_t *row, int cols, int chns)
  { 
    if (row != NULL) { free(row); }
  }

pnm_sample_t pnm_image_get_sample(pnm_image_t *img, int c, int x, int y)
  { if ((c < 0) || (c >= img->chns)) { return 0; }
    if ((x < 0) || (x >= img->cols)) { return 0; }
    if ((y < 0) || (y >= img->rows)) { return 0; }
    return img->smp[y][x*img->chns + c];
  }

void pnm_image_set_sample(pnm_image_t *img, int c, int x, int y, pnm_sample_t pv)
  { if ((c < 0) || (c >= img->chns)) { pnm_error("invalid channel"); }
    if ((x < 0) || (x >= img->cols)) { pnm_error("invalid column"); }
    if ((y < 0) || (y >= img->rows)) { pnm_error("invalid row"); }
    if (pv > img->maxval) { pnm_error("invalid sample value"); }
    img->smp[y][x*img->chns + c] = pv;
  }

pnm_image_t *pnm_image_crop(pnm_image_t *img, int ic, int nc, int ix, int nx, int iy, int ny)
  { demand((ic >= 0) && (ic+nc <= img->chns), "invalid channel range");
    demand((ix >= 0) && (ix+nx <= img->cols), "invalid column range");
    demand((iy >= 0) && (iy+ny <= img->rows), "invalid row range");
    pnm_image_t *omg = pnm_image_new(nx, ny, nc);
    omg->maxval = img->maxval;
    int chn, col, row;
    for (row = 0; row < ny; row++)
      { for (col = 0; col < nx; col++)
          { pnm_sample_t *pi = &(img->smp[iy + row][(ix + col)*img->chns + ic]);
            pnm_sample_t *po = &(omg->smp[row][col*nc]);
            for (chn = 0; chn < nc; chn++) { (*po) = (*pi); po++; pi++; }
          }
      }
    return omg;
  }        

pnm_image_t *pnm_image_read(char *name, bool_t verbose)
  { FILE *rd = open_read(name, verbose);
    pnm_image_t *img = pnm_image_fread(rd);
    if (rd != stdin) { fclose(rd); }
    return img;
  }

pnm_image_t *pnm_image_fread(FILE *rd)
  { /* We cannot rely on {pnm_readpnm} because PBMPLUS has different pixel layout. */
    /* Read file header: */
    int cols, rows, chns;
    pnm_sample_t maxval;
    bool_t raw, bits;
    pnm_format_t format;
    pnm_read_header(rd, &cols, &rows, &chns, &maxval, &raw, &bits, &format);
    /* Allocate image and set maxval: */
    pnm_image_t *img = pnm_image_new(cols, rows, chns);
    img->maxval = maxval;
    /* Read pixels: */
    int row;
    for (row = 0; row < rows; row++)
      { pnm_read_pixels(rd, img->smp[row], cols, chns, maxval, raw, bits); }
    return(img);
  }
  
void pnm_image_write(char *name, pnm_image_t *img, bool_t forceplain, bool_t verbose)
  { FILE *wr = open_write(name, verbose);
    pnm_image_fwrite(wr, img, forceplain);
    if (wr != stdout) { fclose(wr); }
  }
    
void pnm_image_fwrite(FILE *wr, pnm_image_t *img, bool_t forceplain)
  { /* We cannot rely on {pnm_writepnm} because PBMPLUS has different pixel layout. */
    if (img->maxval <= 0){ pnm_error("invalid maxval (%u)", img->maxval); }
    pnm_format_t format;
    bool_t raw;
    bool_t bits; 
    pnm_choose_output_format(img->maxval, img->chns, forceplain, &format, &raw, &bits);
    pnm_write_header(wr, img->cols, img->rows, img->maxval, format);
    int row;
    for (row = 0; row < img->rows; row++)
      { pnm_write_pixels(wr, img->smp[row], img->cols, img->chns, img->maxval, raw, bits); }
    fflush(wr);
  }

void pnm_image_describe(FILE *wr, char *name, pnm_image_t *img)
  {
    fprintf(stderr, "image %s\n", name);
    fprintf(stderr, "\n");
    fprintf(stderr, "cols =   %5d\n", img->cols);
    fprintf(stderr, "rows =   %5d\n", img->rows);
    fprintf(stderr, "chns =   %5d\n", img->chns);
    fprintf(stderr, "maxval = %5d\n", img->maxval);
    /* Determine min and max pixel value: */
    pnm_sample_t minsmp = img->maxval;
    pnm_sample_t maxsmp = 0;
    int x, y, c;
    for (y = 0; y < img->rows; y++)
      { pnm_sample_t *ip = img->smp[y];
        for (x = 0; x < img->cols; x++)
          { for (c = 0; c < img->chns; c++)
              { pnm_sample_t smp = (*ip);
                assert(smp <= img->maxval);
                if (smp < minsmp) { minsmp = smp; }
                if (smp > maxsmp) { maxsmp = smp; }
                ip++;
              }
          }
      }
    fprintf(wr, "minsmp = %5d\n", minsmp);
    fprintf(wr, "maxsmp = %5d\n", maxsmp);
  }
