/* Copyright 1998 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.
 */
/* prtablegen.c - create the new tables part of Black2 palettes */

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

#include "h.palgen.prtablegen"

#define Debug 0

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

calib_t default_ca =
       { 100, 0,0,0, {1,1},1, {1,1,1,1},
         {100,100,100,100},100,100, {100,100},{100,100},1 };

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

static double gamma(double x, double inv_gamma)
{
  /* inv_gamma = 1/gamma; x = 0..1, gamma(x) = 0..1 */

  return pow(x,inv_gamma);
}

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

static double contrast(double x, double contr)
{
  /* x = 0..1; contrast(x) = 0..1 */

  if (x > 0.5)
    return 1.0 - 0.5*pow(2.0*(1.0-x),contr);
  else
    return 0.5*pow(2.0*x,contr);
}

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

int validate_calib(calib_t *ca)
{
  int i, changed = 1;

  if (ca->ediff_density > 100.0F){ ca->ediff_density = 100.0F; changed = 0;}
  if (ca->ediff_density <   1.0F){ ca->ediff_density =   1.0F; changed = 0;}

  for (i=0; i<4; i++)
  {
    if (ca->gamma[i] > 10.0F){ ca->gamma[i] = 10.0F; changed = 0;}
    if (ca->gamma[i] <  0.1F){ ca->gamma[i] =  0.1F; changed = 0;}
  }

  if (ca->bright[0] > 10.0F){ ca->bright[0] = 10.0F; changed = 0;}
  if (ca->bright[0] <  0.1F){ ca->bright[0] =  0.1F; changed = 0;}
  if (ca->bright[1] > 10.0F){ ca->bright[1] = 10.0F; changed = 0;}
  if (ca->bright[1] <  0.1F){ ca->bright[1] =  0.1F; changed = 0;}

  if (ca->contrast > 10.0F){ ca->contrast = 10.0F; changed = 0;}
  if (ca->contrast <  0.1F){ ca->contrast =  0.1F; changed = 0;}

  if (ca->red_adj_my   < -50){ ca->red_adj_my   = -50; changed = 0;}
  if (ca->red_adj_my   >  50){ ca->red_adj_my   =  50; changed = 0;}
  if (ca->green_adj_cy < -50){ ca->green_adj_cy = -50; changed = 0;}
  if (ca->green_adj_cy >  50){ ca->green_adj_cy =  50; changed = 0;}
  if (ca->blue_adj_cm  < -50){ ca->blue_adj_cm  = -50; changed = 0;}
  if (ca->blue_adj_cm  >  50){ ca->blue_adj_cm  =  50; changed = 0;}

  for (i=0; i<4; i++)
  {
    if (ca->deplete1ink[i] > 100){ ca->deplete1ink[i] = 100; changed = 0;}
    if (ca->deplete1ink[i] <  10){ ca->deplete1ink[i] =  10; changed = 0;}
  }
  if (ca->deplete2ink > 100){ ca->deplete2ink = 100; changed = 0;}
  if (ca->deplete2ink <  10){ ca->deplete2ink =  10; changed = 0;}
  if (ca->deplete3ink > 100){ ca->deplete3ink = 100; changed = 0;}
  if (ca->deplete3ink <  10){ ca->deplete3ink =  10; changed = 0;}

  for (i=0; i<2; i++)
  {
    if (ca->k_gcr[i] > 100.0F){ ca->k_gcr[i] = 100.0F; changed = 0;}
    if (ca->k_gcr[i] <   0.0F){ ca->k_gcr[i] =   0.0F; changed = 0;}
    if (ca->k_gen[i] > 100.0F){ ca->k_gen[i] = 100.0F; changed = 0;}
    if (ca->k_gen[i] <   0.0F){ ca->k_gen[i] =   0.0F; changed = 0;}
  }

  if (ca->k_mnz <  1){ ca->k_mnz =  1; changed = 0;}
  if (ca->k_mnz > 126){ ca->k_mnz = 126; changed = 0;}

  return changed;
}

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

void write_calib(calib_t *ca, FILE *fp)
{

  fprintf(fp,"# printer palette parameters\n#\n"
             "error diff density = %5.1f %%\n"
             "bright             = %5.2f %5.2f\n"
             "contrast           = %5.2f\n"
             "red adjust M-Y     = %5.1f %%\n"
             "green adjust C-Y   = %5.1f %%\n"
             "blue adjust C-M    = %5.1f %%\n"
             "ink gamma(s)       = %5.2f %5.2f %5.2f %5.2f\n"
             "black GCR          = %7.2f %7.2f %%\n"
             "black generate     = %7.2f %7.2f %%\n"
             "black min-non-zero = %2d\n"
             "1-ink depletion    = %5.1f %5.1f %5.1f %5.1f\n"
             "2-ink depletion    = %5.1f\n"
             "3-ink depletion    = %5.1f\n",
             ca->ediff_density,
             ca->bright[0],ca->bright[1],ca->contrast,
             ca->red_adj_my,ca->green_adj_cy,ca->blue_adj_cm,
             ca->gamma[0],ca->gamma[1],ca->gamma[2],ca->gamma[3],
             ca->k_gcr[0],ca->k_gcr[1],ca->k_gen[0],ca->k_gen[1],
             ca->k_mnz,ca->deplete1ink[0],ca->deplete1ink[1],
             ca->deplete1ink[2],ca->deplete1ink[3],
             ca->deplete2ink,ca->deplete3ink );

}

void debug_calib(calib_t *ca, char *sp)
{

  sprintf(sp,"# printer palette parameters\n#\n"
             "error diff density = %5.1f %%\n"
             "bright             = %5.2f %5.2f\n"
             "contrast           = %5.2f\n"
             "red adjust M-Y     = %5.1f %%\n"
             "green adjust C-Y   = %5.1f %%\n"
             "blue adjust C-M    = %5.1f %%\n"
             "ink gamma(s)       = %5.2f %5.2f %5.2f %5.2f\n"
             "black GCR          = %7.2f %7.2f %%\n"
             "black generate     = %7.2f %7.2f %%\n"
             "black min-non-zero = %2d\n"
             "1-ink depletion    = %5.1f %5.1f %5.1f %5.1f\n"
             "2-ink depletion    = %5.1f\n"
             "3-ink depletion    = %5.1f\n",
             ca->ediff_density,
             ca->bright[0],ca->bright[1],ca->contrast,
             ca->red_adj_my,ca->green_adj_cy,ca->blue_adj_cm,
             ca->gamma[0],ca->gamma[1],ca->gamma[2],ca->gamma[3],
             ca->k_gcr[0],ca->k_gcr[1],ca->k_gen[0],ca->k_gen[1],
             ca->k_mnz,ca->deplete1ink[0],ca->deplete1ink[1],
             ca->deplete1ink[2],ca->deplete1ink[3],
             ca->deplete2ink,ca->deplete3ink );

}

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

static void read_calib(calib_t *ca, FILE *fp)
{
  char line[256];
  int  i;

  *ca = default_ca;

  do fgets(line,255,fp); while (line[0] == '#');
  sscanf(line,"error diff density = %f",&ca->ediff_density);
  fgets(line,255,fp);
  i = sscanf(line,"bright = %f %f",&ca->bright[0],&ca->bright[1]);
  if (i == 1) ca->bright[1] = ca->bright[0];
  fgets(line,255,fp);
  sscanf(line,"contrast = %f",&ca->contrast);
  fgets(line,255,fp);
  sscanf(line,"red adjust M-Y = %f",&ca->red_adj_my);
  fgets(line,255,fp);
  sscanf(line,"green adjust C-Y = %f",&ca->green_adj_cy);
  fgets(line,255,fp);
  sscanf(line,"blue adjust C-M = %f",&ca->blue_adj_cm);
  fgets(line,255,fp);
  i = sscanf(line,"ink gamma(s) = %f %f %f %f",
             ca->gamma,ca->gamma+1,ca->gamma+2,ca->gamma+3);
  if (i == 1) ca->gamma[1] = ca->gamma[2] = ca->gamma[3] = ca->gamma[0];
  fgets(line,255,fp);
  i = sscanf(line,"black GCR = %f %f",&ca->k_gcr[0],&ca->k_gcr[1]);
  if (i == 1) ca->k_gcr[1] = 100;
  fgets(line,255,fp);
  i = sscanf(line,"black generate = %f %f",&ca->k_gen,ca->k_gen+1);
  if (i == 1) ca->k_gen[1] = 100;
  fgets(line,255,fp);
  sscanf(line,"black min-non-zero = %d",&ca->k_mnz);
  fgets(line,255,fp);
  i = sscanf(line,"1-ink depletion = %f %f %f %f",
             ca->deplete1ink,ca->deplete1ink+1,
             ca->deplete1ink+2,ca->deplete1ink+3);
  if (i == 1) ca->deplete1ink[1] = ca->deplete1ink[2] =
              ca->deplete1ink[3] = ca->deplete1ink[0];
  fgets(line,255,fp);
  sscanf(line,"2-ink depletion = %f",&ca->deplete2ink);
  fgets(line,255,fp);
  sscanf(line,"3-ink depletion = %f",&ca->deplete3ink);

  validate_calib(ca);

#if Debug
  write_calib(ca, stderr);
#endif
}

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

static void set_cmyg_tables(calib_t *ca, tables_t *ta)
{
  int    i;
  double bED,bHT,c,bcon,max1ink[4],s,x,gam[4], inkc,inkm,inky,grey;

  bED = 1.0/ca->bright[0];
  bHT = 1.0/ca->bright[1];
  c   = ca->contrast;

  for (i=0; i<4; i++)
  {
    gam[i] = 1.0/ca->gamma[i];
    max1ink[i] = 255.0*ca->deplete1ink[i]/100.0;
  }

  s = 1.0/255.0; /* scaled to give x in range 0..1 */
  for (i=0; i<256; i++)
  {
    /* quantities, q are replaced by 1-q because of rgb -> cmy */
    x = s*(double)i;
    bcon = contrast(gamma(1.0-x,bED),c);
    inkc = 1.0 - gamma(bcon,gam[0]);
    inkm = 1.0 - gamma(bcon,gam[1]);
    inky = 1.0 - gamma(bcon,gam[2]);
    grey = 1.0 - gamma(bcon,gam[3]);
    ta->cyanED[i]    = (int)(max1ink[0]*inkc + 0.5);
    ta->magentaED[i] = (int)(max1ink[1]*inkm + 0.5);
    ta->yellowED[i]  = (int)(max1ink[2]*inky + 0.5);
    ta->greyED[i]    = (int)(max1ink[3]*grey + 0.5);
    bcon = contrast(gamma(1.0-x,bHT),c);
    inkc = 1.0 - gamma(bcon,gam[0]);
    inkm = 1.0 - gamma(bcon,gam[1]);
    inky = 1.0 - gamma(bcon,gam[2]);
    grey = 1.0 - gamma(bcon,gam[3]);
    ta->cyanHT[i]    = (int)(max1ink[0]*inkc + 0.5);
    ta->magentaHT[i] = (int)(max1ink[1]*inkm + 0.5);
    ta->yellowHT[i]  = (int)(max1ink[2]*inky + 0.5);
    ta->greyHT[i]    = (int)(max1ink[3]*grey + 0.5);
  }

}

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

static void set_gcr_tables(calib_t *ca, tables_t *ta)
{
  int    i;
  double s,x,k_gcr_gamma,k_gcr_scale,k_gen_gamma,k_gen_scale,u,cmy,deplete3;

  deplete3 = ca->deplete3ink/100.0;

  /* k_gcr[0] = 0 is special case (for 3-ink CMY) */
  if (ca->k_gcr[0] == 0.0F)
  {
#if Debug
    fprintf(stderr,"k_gcr[0] is 0 (3-ink special case)\n");
#endif
    s = 1.0/255.0;
    for (i=0; i<256; i++)
    {
      x = s*(double)i;
      u = x*(1.0F-deplete3);     /* take account of 3-ink depletion */
      ta->kGCR_kgen[i*2] = (uint8)(255.0*u + 0.4);
      ta->kGCR_kgen[i*2+1] = 0;  /* zero k generate */
    }
  }
  else
  {
    /* find gamma curve such that gamma(0.5) = k_gcr; ie. pass through
       k_gcr value at half way point, and same for k_gen */
    k_gcr_gamma = log(0.5*(double)ca->k_gcr[0]/100.0) / log(0.5);
    k_gcr_scale = 1.0*(double)ca->k_gcr[1]/100.0;
    k_gen_gamma = log(0.5*(double)ca->k_gen[0]/100.0) / log(0.5);
    k_gen_scale = 255.0*(double)ca->k_gen[1]/100.0;
#if Debug
    fprintf(stderr,"k_gcr_gamma = %1.1f\n",(float)k_gcr_gamma);
    fprintf(stderr,"k_gen_gamma = %1.1f\n",(float)k_gen_gamma);
#endif

    x = 256.0 - ca->k_mnz;
    if (x == 0) x = 255;		// avoid the obvious (1.0/0)
    s = 1.0/x;

    for (i = 0; i<ca->k_mnz; i++){
      ta->kGCR_kgen[i*2]   = 0;
      ta->kGCR_kgen[i*2+1] = 0;
    }
    for (i=0; i <= (256 - ca->k_mnz); i++)
    {
      x = s*(double)i;
      u = k_gcr_scale*gamma(x,k_gcr_gamma);   /* u = unit GCR */
      cmy = x - u;                /* fraction remaining as 3-ink cmy */
      u = u + (1.0-deplete3)*cmy; /* compensate for 3-ink depletion */
      ta->kGCR_kgen[i*2+ca->k_mnz]   = (uint8)(255.0*u + 0.4);
      ta->kGCR_kgen[i*2+1+ca->k_mnz] = (uint8)(k_gen_scale*gamma(x,k_gen_gamma) + 0.5);
    }
  }

}

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

static void set_2ink_table(double deplete1, double deplete2, double adj,
                           uint8 *table, double init_slope)
{
  int    i,j,imax1,imax2;
  double s,p,u,max1ink,max2ink, dtable[256];

  max1ink = 255.0*deplete1/100.0;
  max2ink = max1ink*deplete2/100.0;

  imax1 = (int)(max1ink + 0.5); if (imax1 > 255) imax1 = 255;
  imax2 = (int)(max2ink + 0.5); if (imax2 > 255) imax2 = 255;

  /* create curve with slope starting at init_slope and smoothly reducing
     to 0.5 (0.5 is full 2-ink compensation) */
  dtable[0] = 0;
  s = (init_slope-0.5)/imax1;
  for (i=1; i<=imax1; i++)
  {
    u = s*(double)i;
    p = init_slope - u; /* slope, init_slope..0.5 */
    dtable[i] = dtable[i-1] + p;
  }

  /* rescale to reach imax2 out for imax1 in */
  s = (double)imax2/dtable[imax1];
  for (i=1; i<=imax1; i++) dtable[i] *= s;

  for (i=0; i<=imax1; i++)
  {
    j = (int)(adj*dtable[i] + 0.5); if (j > i) j = i;
    table[i*2] = j;
  }

  /* plateau for rest of table (completeness only; should not be accessed) */
  for (i=imax1+1; i<256; i++) table[i*2] = table[imax1*2];

}

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

#define R_Init2InkSlope 0.5
#define G_Init2InkSlope 0.5
#define B_Init2InkSlope 0.5

static void set_2ink_tables(calib_t *ca, tables_t *ta)
{
  set_2ink_table(ca->deplete1ink[1],ca->deplete2ink,
                 1.0+ca->red_adj_my/100.0,ta->Cmin_M_Y,R_Init2InkSlope);
  set_2ink_table(ca->deplete1ink[2],ca->deplete2ink,
                 1.0-ca->red_adj_my/100.0,ta->Cmin_M_Y+1,R_Init2InkSlope);

  set_2ink_table(ca->deplete1ink[0],ca->deplete2ink,
                 1.0+ca->green_adj_cy/100.0,ta->Mmin_C_Y,G_Init2InkSlope);
  set_2ink_table(ca->deplete1ink[2],ca->deplete2ink,
                 1.0-ca->green_adj_cy/100.0,ta->Mmin_C_Y+1,G_Init2InkSlope);

  set_2ink_table(ca->deplete1ink[0],ca->deplete2ink,
                 1.0+ca->blue_adj_cm/100.0,ta->Ymin_C_M,B_Init2InkSlope);
  set_2ink_table(ca->deplete1ink[1],ca->deplete2ink,
                 1.0-ca->blue_adj_cm/100.0,ta->Ymin_C_M+1,B_Init2InkSlope);
}

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

void set_tables(calib_t *ca, tables_t *ta)
{
  float ed = ca->ediff_density/100.0F;

  ta->ediff_density = (int)(128.0F/ed + 0.5F);
#if Debug
  fprintf(stderr,"ediff level = %1d\n",ta->ediff_density);
#endif

  set_cmyg_tables(ca,ta);
  set_gcr_tables(ca,ta);
  set_2ink_tables(ca,ta);

  ta->calib = *ca;
}

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

void prtablegen(FILE *fp, calib_t *calib, tables_t *tables)
{
  /* read calibration parameters file fp, return calib,tables */

  read_calib(calib,fp);
  set_tables(calib,tables);
}
