From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- target/printtarg.c | 4300 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4300 insertions(+) create mode 100644 target/printtarg.c (limited to 'target/printtarg.c') diff --git a/target/printtarg.c b/target/printtarg.c new file mode 100644 index 0000000..25d889a --- /dev/null +++ b/target/printtarg.c @@ -0,0 +1,4300 @@ + +/* + * Argyll Color Correction System + * PostScript print chart generator module. + * + * Author: Graeme W. Gill + * Date: 28/9/96 + * + * Copyright 1996 - 2009 Graeme W. Gill + * All rights reserved. + * + * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- + * see the License.txt file for licencing details. + */ + +/* + + TTBD: + + Add "single pixel patch" mode, for pure digital processing for + abstract profile creation. + + Add -h2 flag for Munki for super high-res chart ? + Note: i1Pro: Illum spot: 3.5mm Aperture: 4.5mm, Physical aperture: 4.55mm + Munki: Illum spot: 8.0mm Aperture: 6.0mm, Physical aperture: 7.63mm + + Add an option that allows including a scale gauge, to detect + accidental re-scaling. + + Make it aportion extra space evenly around the chart + rather than at the trailing edges. + + Add direct PDF support, including NChannel output. +*/ + +/* This program generates a PostScript or TIFF print target file, */ +/* containing color test patches, given the .ti1 file specifying */ +/* what the colors are. */ + +/* The output is designed to suite a general XY spectrometer (such as */ +/* the Gretag SpectrScan), a handheld, manual instrument, or */ +/* an Xrite DTP51, DTP41 or Eye-One strip spectrometer. */ + +/* Description: + + This program simply generates a PostScripto or TIFF file containing + the patches layed out for an Xrite DTP20/DTP22/DTP51/DTP41/SpectroScan/i1pro/Munki. + It allows them to be layed out on a choice of paper sizes, + with the appropriate contrasting color spacers between + each patch for the strip reading instruments. Unlike other + charts, Argyll charts are generated as required, rather + that being fixed. Also unlike most other strip reading charts, + the spacers may colored, so that the density contrast ratio is + guaranteed, even when two patches are about 50% density. + + Another feature is the pseudo random patch layout. This has + three purposes. One is to try and average out any variation + in the device response in relationship to the location of + the patch on the paper. Color copiers and printing presses + (for instance), are notorious in having side to side density + variations. + + Another purpose of the random patch layout, is that it gives + the reading program a good mechanism for detecting user error. + It can guess the expected values, compare them to the readings, + and complain if it seems that the strip is probably the wrong + one. It can also be used to identify and rectify a strip + that has been read in backwards. + + The final purpose of the random patch layout is to optimse the + contrast between patches in a strip, to improve the robustness + of the strip reading, and to be able to distinguish the directin + a strip has been read in. Using this, small charts may be even be + generated without any gaps between the test patches. + + */ + +/* + * Nomencalture: + * + * Largely due to how the strip readers name things, the following terms + * are used for how patches are grouped: + * + * Pass, Row: One row of patches in a strip. A pass is usually labeled + * with a unique alphabetic label. + * Strip: A group of passes that can be read by a strip reader. + * For an XY instrument, the strip is a complete sheet, and + * each pass is one column. The rows of an XY chart are + * the step numbers within a pass. + * Step: One test patch in a pass. + * Sheet: One sheet of paper, containing full and partial strips. + * For an XY instrument, there will be only one strip per sheet. + * + */ + +/* TTBD: + * + * Improve EPS support to add a preview to each eps file. + */ + +#undef DEBUG +#undef FORCEN /* For testing, force DeviceN */ +#define DEN_COMPRESS /* Compress density estimates > 1.0 */ + /* - this biases it towards white spacers */ + +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "cgats.h" +#include "icc.h" +#include "xicc.h" +#include "insttypes.h" +#include "render.h" +#include "randix.h" +#include "alphix.h" +#include "rspl.h" +#include "sort.h" + +#include + +/* Convert inches into mm */ +#define inch2mm(xx) ((xx) * 25.4) + +/* Convert mm into points */ +#define mm2pnt(xx) ((xx) * 72.0/25.4) + +/* A color structure */ +struct _col { + int nmask; /* colorant mask */ + int altrep; /* alternate grey or CMY representation type 0..8 */ + int i; /* cols list index */ + int ix; /* random list index */ + char *id; /* Id string */ + char loc[10]; /* Location ID string */ + int t; /* Tag */ +#define T_XYZ 0x0001 +#define T_LAB 0x0002 +#define T_DEN 0x0004 +#define T_RGB 0x0008 +#define T_N 0x0010 +#define T_NFB 0x2000 /* DeviceN fallback enabled */ +#define T_PRESET 0x4000 /* A preset color rather than a test patch */ +#define T_PAD 0x8000 /* A padding color patch */ + double XYZ[3]; /* Aproximate XYZ */ + double Lab[3]; /* Aproximate Lab */ + double den[4]; /* Approx statusT density + visual density */ + int dtp20_octval; /* DTP20 octal value */ + double dtp20_psize; /* DTP20 patch width */ + double rgb[3]; /* Aproximate sRGB */ + int n; /* Number of colorants */ + double dev[ICX_MXINKS]; /* Value of colorants */ + + struct _col *nc[2]; /* Neigborhood colors */ + struct _col *oc; /* Opposite direction color */ + double wnd; /* Worst neigborhood contrast density */ +}; typedef struct _col col; + +#define min2(a,b) ((a) < (b) ? (a) : (b)) +#define min3(a,b,c) (min2((a), min2((b),(c)))) +#define max2(a,b) ((a) > (b) ? (a) : (b)) +#define max3(a,b,c) (max2((a), max2((b),(c)))) + +/* Declare edge tracking functions */ +void et_init(void); +void et_height(double height); +void et_media(double *rgb); +void et_color(double *rgb); +void et_edge(int isx, int negh, double mj, double mi0, double mi1); +void et_patch(char *id, double xo, double yo, double w, double h); +void et_fiducial(double x, double y); +void et_write(char *fname, col *cols, int *rix, int si, int ei); +void et_clear(void); + +/* ====================================================== */ +/* Calibration Target rendering class */ +/* Outputs either PostScript or TIFF raster */ +/* test charts. */ +/* We just do an error() if something goes wrong */ + +/* Common class structure */ +#define TREND_STRUCT \ + /* Start a page */ \ + void (*startpage)(struct _trend *s, int pn); \ + /* End a page */ \ + void (*endpage)(struct _trend *s); \ + /* set the color */ \ + void (*setcolor)(struct _trend *s, xcal *cal, col *c); \ + /* A rectangle, with optional edge tracking */ \ + void (*rectangle)(struct _trend *s, /* Render a rectangle */ \ + double x, double y, /* Top left corner of rectangle in mm from origin */ \ + double w, double h, /* Width and height */ \ + char *id, /* Patch id, NULL if not a diagnostic mark */ \ + int et /* nz if use edge tracking on this */ \ + ); \ + /* A testpad hexagon. */ \ + void (*hexagon)(struct _trend *s, \ + double x, double y, /* Top left vertex of hex mm from origin */ \ + double w, double h, /* Width and height */ \ + int step, /* Step number from 0 to figure odd/even */ \ + char *id /* Patch id, NULL if not a diagnostic mark */ \ + ); \ + /* A centered string */ \ + void (*string)(struct _trend *s, \ + double x, double y, /* Bot Left Corner of rectangle in mm from origin */ \ + double w, double h, /* Width and height */ \ + char *str /* String */ \ + ); \ + /* A vertically centered string, rendered from bottom to top */ \ + void (*vstring)(struct _trend *s, \ + double x, double y, /* Bot Right Corner of rectangle in mm from origin */ \ + double w, double h, /* Width and height */ \ + char *str /* String */ \ + ); \ + /* A dotted line */ \ + void (*dline)(struct _trend *s, \ + double x0, double y0, /* Start of line */ \ + double x1, double y1, /* End of line */ \ + double w /* Width */ \ + ); \ + /* Delete the object */ \ + void (*del)(struct _trend *s); \ + +struct _trend { + TREND_STRUCT +}; typedef struct _trend trend; + +/* ==================================== */ +/* PostScript output class */ + +struct _ps_trend { + TREND_STRUCT + FILE *of; /* Postscript output file */ + int eps; /* EPS flag */ + char *fname; +}; typedef struct _ps_trend ps_trend; + +/* Start a page */ +static void ps_startpage(trend *ss, int pagen) { + ps_trend *s = (ps_trend *)ss; + + fprintf(s->of,"%%%%Page: (Page %d) %d\n",pagen,pagen); +} + +/* End a page */ +static void ps_endpage(trend *ss) { + ps_trend *s = (ps_trend *)ss; + + fprintf(s->of,"showpage\n"); + fprintf(s->of,"\n"); +} + +/* Set a device N color with fallback */ +static void +gen_ncolor(ps_trend *s, col *c) { + int i; + + /* define the colorspace */ + fprintf(s->of,"[ /DeviceN [ "); + for (i = 0; i < c->n; i++) { + int imask = icx_index2ink(c->nmask, i); + fprintf(s->of,"/%s ", icx_ink2psstring(imask)); + } + + if (c->t & T_NFB) { /* Use color fallback */ + fprintf(s->of,"] /DeviceRGB "); /* Fallback to RGB */ + fprintf(s->of,"{ "); + for (i = 0; i < c->n; i++) /* Remove N values */ + fprintf(s->of,"pop "); + for (i = 0; i < 3; i++) /* Set RGB values */ + fprintf(s->of,"%f ",c->rgb[i]); + } else { + fprintf(s->of,"] /DeviceGray "); /* Fallback to Gray */ + fprintf(s->of,"{ "); + for (i = 0; i < c->n; i++) /* Remove N values */ + fprintf(s->of,"pop "); + fprintf(s->of,"%f ",(c->rgb[0] + c->rgb[1] + c->rgb[2])/3.0); /* Set Gray value */ + } + + fprintf(s->of," } ] setcolorspace\n"); + + /* Set the color */ + for (i = 0; i < c->n; i++) + fprintf(s->of,"%f ",c->dev[i]); + fprintf(s->of,"setcolor\n"); +} + + +/* Set a device color */ +/* Set it by the rep with most components */ +static void ps_setcolor(trend *ss, xcal *cal, col *c) { + ps_trend *s = (ps_trend *)ss; + double cdev[ICX_MXINKS]; /* Calibrated device color */ + + if (cal != NULL) + cal->interp(cal, cdev, c->dev); + else { + int j; + for (j = 0; j < c->n; j++) + cdev[j] = c->dev[j]; + } + + if ((c->t & T_N) == 0) + error("ps_setcolor with no device values set"); + +#ifndef FORCEN + if (c->nmask == ICX_W) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f\n",c->id, c->loc, 100.0 * cdev[0]); + + if (c->altrep == 0) { /* DeviceGray */ + fprintf(s->of,"%f setgray\n",cdev[0]); + } else if (c->altrep == 4) { /* DeviceRGB */ + fprintf(s->of,"%f %f %f setrgbcolor\n",cdev[0],cdev[0],cdev[0]); + } else if (c->altrep == 5) { /* Separation */ + fprintf(s->of,"[ /Separation (White) /DeviceGray { pop %f } ] setcolorspace\n", + cdev[0]); + fprintf(s->of,"%f setcolor\n",cdev[0]); + } else if (c->altrep == 6) { /* DeviceN */ + gen_ncolor(s, c); + } else { + error("Device white encoding not approproate!"); + } + + } else if (c->nmask == ICX_K) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f\n",c->id, c->loc, 100.0 * cdev[0]); + if (c->altrep == 0) { /* DeviceGray */ + fprintf(s->of,"%f setgray\n",1.0 - cdev[0]); + } else if (c->altrep == 1) { /* DeviceCMYK */ + fprintf(s->of,"0.0 0.0 0.0 %f setcmykcolor\n",cdev[0]); + } else if (c->altrep == 2) { /* Separation */ + fprintf(s->of,"[ /Separation (Black) /DeviceGray { pop %f } ] setcolorspace\n", + 1.0 - cdev[0]); + fprintf(s->of,"%f setcolor\n",cdev[0]); + } else if (c->altrep == 3) { /* DeviceN */ + gen_ncolor(s, c); + } else { + error("Device black encoding not approproate!"); + } + + } else if (c->nmask == ICX_CMY) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f %f %f\n", c->id, c->loc, + 100.0 * cdev[0], 100.0 * cdev[1], 100.0 * cdev[2]); + + if (c->altrep == 0) { /* DeviceCMYK */ + fprintf(s->of,"%f %f %f 0.0 setcmykcolor\n",cdev[0],cdev[1],cdev[2]); + } else if (c->altrep == 7) { /* Inverted DeviceRGB */ + fprintf(s->of,"%f %f %f setrgbcolor\n",1.0-cdev[0],1.0-cdev[1],1.0-cdev[2]); + } else if (c->altrep == 8) { /* DeviceN */ + gen_ncolor(s, c); + } else { + error("Device CMY encoding not approproate!"); + } + + } else if (c->nmask == ICX_RGB || c->nmask == ICX_IRGB) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f %f %f\n",c->id, c->loc, + 100.0 * cdev[0], 100.0 *cdev[1], 100.0 *cdev[2]); + fprintf(s->of,"%f %f %f setrgbcolor\n",cdev[0],cdev[1],cdev[2]); + + } else if (c->nmask == ICX_CMYK) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f %f %f %f\n", c->id, c->loc, + 100.0 * cdev[0], 100.0 * cdev[1], 100.0 * cdev[2], 100.0 * cdev[3]); + fprintf(s->of,"%f %f %f %f setcmykcolor\n",cdev[0],cdev[1],cdev[2],cdev[3]); + + } else +#endif /* !FORCEN */ + { /* Device N */ + int i; + if ((c->t & T_PRESET) == 0) { + fprintf(s->of,"%% Ref %s %s",c->id, c->loc); + for (i = 0; i < c->n; i++) + fprintf(s->of,"%f ", 100.0 * cdev[i]); + fprintf(s->of,"\n"); + } + gen_ncolor(s, c); + } + + /* Remember edge tracking color */ + et_color(c->rgb); +} + +/* Generate a rectangle, with optional edge tracking */ +/* Note the page coordinate origin is bottom left. */ +static void ps_rectangle(trend *ss, + double x, double y, /* Top left corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *id, /* Patch id, NULL if not a diagnostic mark */ + int et /* nz if use edge tracking on this */ +) { + ps_trend *s = (ps_trend *)ss; + + if (w < 1e-6 || h < 1e-6) + return; /* Skip zero sized rectangle */ + y -= h; /* Convert to bottom left corner */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f %f %f %f rect\n",w,h,x,y); + + if (et) { + et_patch(id, x, y, w, h); + et_edge(1, 0, x, y, y + h); + et_edge(1, 1, x + w, y, y + h); + et_edge(0, 0, y, x, x + w); + et_edge(0, 1, y + h, x, x + w); + } +} + +/* Generate one testpad hexagon. */ +/* Note the page coordinate origin is bottom left. */ +/* The hex always has left/right sides */ +/* and peaks at the top and the bottom. */ +static void ps_hexagon(trend *ss, + double x, double y, /* Top left vertex of hex mm from origin */ + double w, double h, /* Width and height */ + int step, /* Step number from 0 to figure odd/even */ + char *id /* Patch id, NULL if not a diagnostic mark */ +) { + ps_trend *s = (ps_trend *)ss; + + if (w < 1e-6 || h < 1e-6) + return; /* Skip zero sized rectangle */ + if ((step & 1) == 0) /* Even so left side of stagger */ + x -= 0.25 * w; + else /* Odd so right side of stagger */ + x += 0.25 * w; + y = y - 5.0/6.0 * h; + h *= 2.0/3.0; /* Convert to hex side length */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f %f %f %f hex\n",w,h,x,y); +} + +/* A centered string */ +static void ps_string(trend *ss, + double x, double y, /* Bot Left Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + ps_trend *s = (ps_trend *)ss; + + if (fabs(w) < 1e-6 || fabs(h) < 1e-6) + return; /* Skip zero sized string */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f scaleTimes\n",h * 0.75); + fprintf(s->of,"(%s) %f %f centerShow\n",str,x+w/2.0,y+h/2.0); +} + +/* A vertically centered string */ +static void ps_vstring(trend *ss, + double x, double y, /* Bot Right Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + ps_trend *s = (ps_trend *)ss; + + if (fabs(w) < 1e-6 || fabs(h) < 1e-6) + return; /* Skip zero sized string */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f scaleTimes\n",w * 0.75); + fprintf(s->of,"(%s) %f %f vcenterShow\n",str,x-w/2.0,y+h/2.0); +} + +/* A dotted line */ +static void ps_dline(trend *ss, + double x1, double y1, /* Start of line */ + double x2, double y2, /* End of line */ + double w /* Width */ +) { + ps_trend *s = (ps_trend *)ss; + + if (fabs(w) < 1e-6 || fabs(y2 - y1) < 1e-6) + return; /* Skip zero sized line */ + x1 = mm2pnt(x1); + x2 = mm2pnt(x2); + y1 = mm2pnt(y1); + y2 = mm2pnt(y2); + w = mm2pnt(w); + fprintf(s->of,"[%f %f] %f setdash\n",mm2pnt(1.0),mm2pnt(2.0),mm2pnt(0.0)); + fprintf(s->of,"%f setlinewidth\n",w); + fprintf(s->of,"newpath %f %f moveto %f %f lineto stroke\n",x1,y1,x2,y2); +} + +/* Complete operations, then delete the object */ +static void ps_del(trend *ss) { + ps_trend *s = (ps_trend *)ss; + + if (s->of) { + fprintf(s->of,"\n"); + fprintf(s->of,"%%%%EOF\n"); + + if (fclose(s->of)) + error ("Unable to close output file '%s'",s->fname); + } + if (s->fname) + free(s->fname); + + free(s); +} + +trend *new_ps_trend( + char *fname, /* File name */ + int npages, /* Number of pages needed */ + int nmask, /* Non zero if we are doing a DeviceN chart */ + double pw, double ph, /* Page width and height in mm */ + int eps, /* EPS flag */ + int rand, /* randomize */ + int rstart /* Random start number/chart ID */ +) { + ps_trend *s; + + if ((s = (ps_trend *)calloc(1, sizeof(ps_trend))) == NULL) { + return NULL; + } + + s->startpage = ps_startpage; + s->endpage = ps_endpage; + s->setcolor = ps_setcolor; + s->rectangle = ps_rectangle; + s->hexagon = ps_hexagon; + s->string = ps_string; + s->vstring = ps_vstring; + s->dline = ps_dline; + s->del = ps_del; + + s->eps = eps; + + if ((s->of = fopen(fname,"w")) == NULL) { + error ("Unable to open output file '%s'",fname); + } + + if ((s->fname = strdup(fname)) == NULL) + error("stdup of fname failed"); + + /* Generate PS file prolog */ + { + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + int ipw, iph; + + ipw = (int)ceil(mm2pnt(pw)); + iph = (int)ceil(mm2pnt(ph)); + if (eps) + fprintf(s->of,"%%!PS-Adobe-3.0 EPSF-3.0\n"); + else + fprintf(s->of,"%%!PS-Adobe-3.0\n"); + fprintf(s->of,"%%%%Title: Argyll Color Calibration Target\n"); +#ifdef FORCEN + if (1) { +#else + if (nmask != ICX_W /* If not a Gray, RGB or CMYK device space */ + && nmask != ICX_K + && nmask != ICX_RGB + && nmask != ICX_IRGB + && nmask != ICX_CMYK) { +#endif + fprintf(s->of,"%%%%LanguageLevel: 3\n"); + } else { + fprintf(s->of,"%%%%LanguageLevel: 1\n"); + if (nmask == ICX_CMYK) + fprintf(s->of,"%%%%Extensions: CMYK\n"); + } + + fprintf(s->of,"%%%%Creator: Argyll target chart generator\n"); + fprintf(s->of,"%%%%For: The user who wants accurate color\n"); +// fprintf(s->of,"%%%%Version: %s\n",VERSION); + fprintf(s->of,"%%%%CreationDate: %s",asctime(tsp)); + fprintf(s->of,"%%%%DocumentData: Clean7Bit\n"); + if (eps) + fprintf(s->of,"%%%%Pages: %d\n",1); + else + fprintf(s->of,"%%%%Pages: %d\n",npages); + fprintf(s->of,"%%%%PageOrder: Ascend\n"); + fprintf(s->of,"%%%%BoundingBox: %d %d %d %d\n",0,0,ipw-1,iph-1); + fprintf(s->of,"%%%%Orientation: Portrait\n"); /* Rows are always virtical */ + fprintf(s->of,"%%%%EndComments\n"); + fprintf(s->of,"\n"); + if (!eps) { + fprintf(s->of,"<< /PageSize [%d %d] >> setpagedevice\n",ipw, iph); + fprintf(s->of,"\n"); + } + fprintf(s->of,"%%%%BeginProlog\n\n"); +#ifdef NEVER + fprintf(s->of,"%% Duplicate nth element of stack\n"); + fprintf(s->of,"%% arguments: n, the offset from the element bellow the n\n"); + fprintf(s->of,"/dupn { 2 add dup -1 roll dup 3 -1 roll 1 roll } bind def\n"); + fprintf(s->of,"\n"); +#endif + fprintf(s->of,"%% arbitrary rectangle\n"); + fprintf(s->of,"%% arguments: w h x y\n"); + fprintf(s->of,"/rect { gsave \n"); + fprintf(s->of,"newpath\n"); + fprintf(s->of,"moveto\n"); + fprintf(s->of,"dup 0.0 exch rlineto\n"); + fprintf(s->of,"exch 0.0 rlineto\n"); + fprintf(s->of,"0.0 exch neg rlineto\n"); + fprintf(s->of,"closepath\n"); + fprintf(s->of,"fill\n"); + fprintf(s->of,"grestore } bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% hexagon with bottom left origin\n"); + fprintf(s->of,"%% arguments: w h x y\n"); + fprintf(s->of,"/hex { gsave \n"); + fprintf(s->of,"newpath\n"); + fprintf(s->of,"moveto\n"); + fprintf(s->of,"0 1 index rlineto\n"); + fprintf(s->of,"1 index 2 div 1 index 2 div rlineto\n"); + fprintf(s->of,"1 index 2 div 1 index 2 div neg rlineto\n"); + fprintf(s->of,"0 1 index neg rlineto\n"); + fprintf(s->of,"1 index 2 div neg 1 index 2 div neg rlineto\n"); + fprintf(s->of,"pop pop\n"); + fprintf(s->of,"closepath\n"); + fprintf(s->of,"fill\n"); + fprintf(s->of,"grestore } bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% set times-roman font\n"); + fprintf(s->of,"%% argument: scale\n"); + fprintf(s->of,"/scaleTimes {\n"); + fprintf(s->of,"/Times-Roman findfont\n"); + fprintf(s->of,"exch scalefont\n"); + fprintf(s->of,"setfont } bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% Print a centered string\n"); + fprintf(s->of,"%% argument: string, x, y\n"); + fprintf(s->of,"/centerShow {\n"); + fprintf(s->of,"gsave translate\n"); + fprintf(s->of,"newpath 0.0 0.0 moveto dup true charpath pathbbox\n"); + fprintf(s->of,"3 -1 roll sub exch 3 -1 roll sub\n"); + fprintf(s->of,"-0.5 mul exch -0.5 mul\n"); + fprintf(s->of,"moveto show grestore} bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% Print a vertically centered string\n"); + fprintf(s->of,"%% argument: string, x, y\n"); + fprintf(s->of,"/vcenterShow {\n"); + fprintf(s->of,"gsave translate 90.0 rotate\n"); + fprintf(s->of,"newpath 0.0 0.0 moveto dup true charpath pathbbox\n"); + fprintf(s->of,"3 -1 roll sub exch 3 -1 roll sub\n"); + fprintf(s->of,"-0.5 mul exch -0.5 mul\n"); + fprintf(s->of,"moveto show grestore} bind def\n"); + + fprintf(s->of,"%%%%EndProlog\n"); + fprintf(s->of,"\n"); + + if (rand != 0) + fprintf(s->of,"%% RandomStart %d\n",rstart); + else + fprintf(s->of,"%% ChartID %d\n",rstart); + fprintf(s->of,"\n"); + } + + return (trend *)s; +} + +/* ==================================== */ +/* TIFF raster output class */ +/* We use the render library to do all the hard work. */ + +struct _tiff_trend { + TREND_STRUCT + + render2d *r; /* Raster renderer object */ + char *fname; + color2d c; /* Last set color */ + int comp; /* Flag, use compression */ + +}; typedef struct _tiff_trend tiff_trend; + +/* Start a page */ +static void tiff_startpage(trend *ss, int pn) { + tiff_trend *s = (tiff_trend *)ss; + + /* Nothing to do */ +} + +/* End a page */ +static void tiff_endpage(trend *ss) { + tiff_trend *s = (tiff_trend *)ss; + + /* Nothing to do */ +} + +/* set the color */ +static void tiff_setcolor(trend *ss, xcal *cal, col *c) { + tiff_trend *s = (tiff_trend *)ss; + double cdev[ICX_MXINKS]; /* Calibrated device color */ + + if (cal != NULL) + cal->interp(cal, cdev, c->dev); + else { + int j; + for (j = 0; j < c->n; j++) + cdev[j] = c->dev[j]; + } + + if ((c->t & T_N) == 0) + error("tiff_setcolor with no device values set"); + + if (c->nmask == ICX_W) { + if (c->altrep == 0) { /* DeviceGray */ + s->c[0] = cdev[0]; + } else if (c->altrep == 4) { /* DeviceRGB */ + s->c[0] = cdev[0]; + s->c[1] = cdev[0]; + s->c[2] = cdev[0]; + } else if (c->altrep == 5) { /* Separation */ + s->c[0] = cdev[0]; + } else if (c->altrep == 6) { /* DeviceN single channel */ + s->c[0] = cdev[0]; + } else { + error("Device white encoding not approproate!"); + } + + } else if (c->nmask == ICX_K) { + if (c->altrep == 0) { /* DeviceGray */ + s->c[0] = cdev[0]; + } else if (c->altrep == 1) { /* DeviceCMYK */ + s->c[0] = 0.0; + s->c[1] = 0.0; + s->c[2] = 0.0; + s->c[3] = cdev[0]; + } else if (c->altrep == 2) { /* Separation */ + s->c[0] = cdev[0]; + } else if (c->altrep == 3) { /* DeviceN single channel */ + s->c[0] = cdev[0]; + } else { + error("Device black encoding not approproate!"); + } + + } else if (c->nmask == ICX_CMY) { + if (c->altrep == 0) { /* DeviceCMYK */ + s->c[0] = cdev[0]; + s->c[1] = cdev[1]; + s->c[2] = cdev[2]; + s->c[3] = 0.0; + } else if (c->altrep == 7) { /* Inverted DeviceRGB */ + s->c[0] = 1.0-cdev[0]; + s->c[1] = 1.0-cdev[1]; + s->c[2] = 1.0-cdev[2]; + } else if (c->altrep == 8) { /* DeviceN three channel */ + s->c[0] = cdev[0]; + s->c[1] = cdev[1]; + s->c[2] = cdev[2]; + } else { + error("Device CMY encoding not approproate!"); + } + + } else { + int j; + for (j = 0; j < s->r->ncc; j++) + s->c[j] = cdev[j]; + } + + /* Remember edge tracking color */ + et_color(c->rgb); +} + +/* A rectangle, with optional edge tracking */ +static void tiff_rectangle(trend *ss, + double x, double y, /* Top left corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *id, /* Patch id, NULL if not a diagnostic mark */ + int et /* nz if use edge tracking on this */ +) { + tiff_trend *s = (tiff_trend *)ss; + + y -= h; /* Convert to bottom left corner */ + s->r->add(s->r, new_rect2d(s->r, x, y, w, h, s->c)); + + if (et) { + et_patch(id, x, y, w, h); + et_edge(1, 0, x, y, y + h); + et_edge(1, 1, x + w, y, y + h); + et_edge(0, 0, y, x, x + w); + et_edge(0, 1, y + h, x, x + w); + } +} + +/* A testpad hexagon. */ +static void tiff_hexagon(trend *ss, + double x, double y, /* Top left vertex of hex mm from origin */ + double w, double h, /* Width and height */ + int step, /* Step number from 0 to figure odd/even */ + char *id /* Patch id, NULL if not a diagnostic mark */ +) { + tiff_trend *s = (tiff_trend *)ss; + double vv[3][2]; + color2d cc[3]; + int i, j; + + if ((step & 1) == 0) /* Even so left side of stagger */ + x -= 0.25 * w; + else /* Odd so right side of stagger */ + x += 0.25 * w; + y = y - 5.0/6.0 * h; + h *= 2.0/3.0; /* Convert to hex side length */ + + /* Triangle color */ + for (i = 0; i < 3; i++) + for (j = 0; j < s->r->ncc; j++) + cc[i][j] = s->c[j]; + + /* Top triangle */ + vv[0][0] = x; + vv[0][1] = y + h; + vv[1][0] = x + w; + vv[1][1] = y + h; + vv[2][0] = x + 0.5 * w; + vv[2][1] = y + 1.5 * h; + s->r->add(s->r, new_trivs2d(s->r, vv, cc)); + + /* Center rectangle */ + s->r->add(s->r, new_rect2d(s->r, x, y, w, h, s->c)); + + /* Bottom triangle */ + vv[0][0] = x; + vv[0][1] = y; + vv[1][0] = x + w; + vv[1][1] = y; + vv[2][0] = x + 0.5 * w; + vv[2][1] = y - 0.5 * h; + s->r->add(s->r, new_trivs2d(s->r, vv, cc)); +} + +/* A centered string */ +static void tiff_string(trend *ss, + double x, double y, /* Bot Left Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + tiff_trend *s = (tiff_trend *)ss; + double sw = 0.0, sh = 0.0; + + sh = h * 0.57; + meas_string2d(s->r,&sw,NULL,timesr_b,str,sh,0); + /* Center the string within the recangle */ + x += 0.5 * (w - sw); + y += 0.5 * (h - sh); + add_string2d(s->r,NULL,NULL,timesr_b,str,x,y,sh,0,s->c); +} + +/* A vertically centered string */ +static void tiff_vstring(trend *ss, + double x, double y, /* Bot Right Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + tiff_trend *s = (tiff_trend *)ss; + double sw = 0.0, sh = 0.0; + + x -= w; /* Make it bot left corner */ + sw = w * 0.57; + meas_string2d(s->r,NULL, &sh,timesr_b,str,sw,3); + /* Center the string within the recangle */ + x += 0.5 * (w + sw); + y += 0.5 * (h - sh); + add_string2d(s->r,NULL,NULL,timesr_b,str,x,y,sw,3,s->c); +} + +/* A dotted line */ +static void tiff_dline(trend *ss, + double x0, double y0, /* Start of line */ + double x1, double y1, /* End of line */ + double w /* Width */ +) { + tiff_trend *s = (tiff_trend *)ss; + + add_dashed_line2d(s->r,x0,y0,x1,y1,w,1.0,2.0,0,s->c); +} + +/* Complete operations, then delete the object */ +static void tiff_del(trend *ss) { + tiff_trend *s = (tiff_trend *)ss; + + if (s->r != NULL) { + s->r->write(s->r, s->fname, s->comp); + s->r->del(s->r); + } + if (s->fname != NULL) + free(s->fname); + free(s); +} + +static trend *new_tiff_trend( + char *fname, /* File name */ + int nmask, /* Non zero if we are doing a DeviceN chart */ + depth2d dpth, /* 8 or 16 bit */ + double pw, double ph, /* Page width and height in mm */ + double marg, /* Page margine in mm */ + double hres, double vres, /* Resolution */ + int altrep, /* printer grey/CMY representation type 0..8 */ + int ncha, /* flag, use nchannel alpha */ + int comp, /* flag, use compression */ + int dith /* flag, use 8 bit dithering */ +) { + tiff_trend *s; + color2d c; /* Background color */ + colort2d csp; + double ma[4]; /* Page margins */ + int nc = 0; + int j; + + if ((s = (tiff_trend *)calloc(1, sizeof(tiff_trend))) == NULL) { + error("Failed to create a tiff target rendering object"); + } + + s->startpage = tiff_startpage; + s->endpage = tiff_endpage; + s->setcolor = tiff_setcolor; + s->rectangle = tiff_rectangle; + s->hexagon = tiff_hexagon; + s->string = tiff_string; + s->vstring = tiff_vstring; + s->dline = tiff_dline; + s->del = tiff_del; + + if (nmask == ICX_W) { + if (altrep == 0 /* DeviceGray */ + || altrep == 5) { /* Separation single channel */ + csp = w_2d; + nc = 1; + } else if (altrep == 4) { /* DeviceRGB */ + csp = rgb_2d; + nc = 3; + } else if (altrep == 6) { /* DeviceN single channel */ + csp = ncol_2d; + nc = icx_noofinks(nmask); + nc = 1; + } else { + error("Device white encoding not approproate"); + } + + } else if (nmask == ICX_K) { + if (altrep == 0 /* DeviceGray */ + || altrep == 2) { /* Separation single channel */ + csp = k_2d; + nc = 1; + } else if (altrep == 1) { /* DeviceCMYK */ + csp = cmyk_2d; + nc = 4; + } else if (altrep == 3) { /* DeviceN single channel */ + csp = ncol_2d; + nc = icx_noofinks(nmask); + nc = 1; + } else { + error("Device black encoding not approproate"); + } + + } else if (nmask == ICX_RGB || nmask == ICX_IRGB) { + csp = rgb_2d; + nc = 3; + + } else if (nmask == ICX_CMY) { + if (altrep == 0) { /* DeviceCMYK */ + csp = cmyk_2d; + nc = 4; + } else if (altrep == 7) { /* Inverted DeviceRGB */ + csp = rgb_2d; + nc = 3; + } else if (altrep == 8) { /* DeviceN three channel */ + csp = ncol_2d; + nc = icx_noofinks(nmask); + } else { + error("Device CMY encoding not approproate"); + } + + } else if (nmask == ICX_CMYK) { + csp = cmyk_2d; + nc = 4; + } else { /* Device N */ + if (ncha) + csp = ncol_a_2d; + else + csp = ncol_2d; + nc = icx_noofinks(nmask); + } + + if (marg > 0) + ma[0] = ma[1] = ma[2] = ma[3] = marg; + else + ma[0] = ma[1] = ma[2] = ma[3] = 0; + + if ((s->r = new_render2d(pw, ph, ma, hres, vres, csp, nc, dpth, dith)) == NULL) { + error("Failed to create a render2d object for tiff output"); + } + + /* We're goin to assume this is all printed output, so */ + /* the background should be white. */ + if ((nmask & ICX_ADDITIVE) + || (nmask == ICX_CMY && altrep == 7)) { /* CMY as inverted RGB */ + for (j = 0; j < nc; j++) + c[j] = 1.0; + } else { + for (j = 0; j < nc; j++) + c[j] = 0.0; + } + s->r->set_defc(s->r, c); + + s->comp = comp; + + if ((s->fname = strdup(fname)) == NULL) + error("stdup of fname failed"); + + return (trend *)s; +} + + +/* ====================================================== */ + +/* Convert XYZ represention into Lab, XYZ density and RGB */ +void +col_convert(col *cp, double *wp) { + + if ((cp->t & T_XYZ) == 0 || (cp->t & T_N) == 0) + error("gen_color needs XYZ and device colors set !"); + + if ((cp->t & T_LAB) == 0) { + icmXYZNumber w; + w.X = wp[0]; + w.Y = wp[1]; + w.Z = wp[2]; + icmXYZ2Lab(&w, cp->Lab, cp->XYZ); + cp->t |= T_LAB; + } + if ((cp->t & T_DEN) == 0) { + icx_XYZ2Tdens(cp->den, cp->XYZ); + +#ifdef DEN_COMPRESS + /* Compress densities > 1.0, 2:1 */ + { + int e; + for (e = 0; e < 3; e++) { + if (cp->den[e] > 1.0) + cp->den[e] = 1.0 + (cp->den[e] - 1.0) * 0.5; + } + } +#endif /* DEN_COMPRESS */ + cp->t |= T_DEN; + } + + if ((cp->t & T_RGB) == 0) { + icx_XYZ2sRGB(cp->rgb, wp, cp->XYZ); + cp->t |= T_RGB; + } +} + +/* return the middle of 3 values */ +double mid3(double a, double b, double c) { + + if ((a < b && a > c) || (a < c && a > b)) + return a; + if ((b < a && b > c) || (b < c && b > a)) + return b; + return c; +} + +/* return the vector difference of 3 values */ +double vec3(double a, double b, double c) { + + return sqrt(a * a + b * b + c * c); +} + +/* Type of density rule to use with color patches/spacers. */ +/* This really depends on the algorithm used by the strip */ +/* reading instrument. For an Xrite DTP41, it appears that */ +/* using max3 is the best. This agrees with their documentation, */ +/* that it is looking for the largest change in one of the three */ +/* channels. Another make of instrument might use a different */ +/* algorithm. */ +/* Choices are: */ +/* max3 - aim for largest change to be greatest */ +/* mid3 - aim for middle change to be greatest */ +/* min3 - aim for minimum change to be greatest */ +/* vec3 - aim for the vector change to be greatest */ + +#define RULE3 max3 + +/* Setup a suitable spacer color, and */ +/* Return the worst case density difference. */ +double setup_spacer( +col **psc, /* Return pointer to spacer color */ +col *pp, /* Previous patch color */ +col *cp, /* Current patch color */ +col *pcol, /* 8 pre-defined spacer colors */ +int sptype, /* Spacer type code, -1 = none, */ + /* 0 = No spacer, 1 = b&W spacer, */ + /* 2 = colored */ +int usede /* Aim for maximum delta E rather than density */ +) { + col *sc = NULL; /* Spacer chosen */ + double dd, pdd; + +//printf("~1\n"); +//printf("~1 setting spacer between %s (%s) and %s (%s)\n",pp->loc, pp->id, cp->loc, cp->id); + + /* Compute contrast between the patches */ + if (usede) { + pdd = icmLabDE(pp->Lab, cp->Lab); + } else { + /* return the density contrast between the patches */ + if (pp->nmask == ICX_W + || pp->nmask == ICX_K) { /* If only capable of single density */ + + pdd = fabs(pp->den[3] - cp->den[3]); +//printf("~1 computed B&W diff of %f\n",dd); + + } else { + pdd = RULE3(fabs(pp->den[0] - cp->den[0]), + fabs(pp->den[1] - cp->den[1]), + fabs(pp->den[2] - cp->den[2])); + +//printf("~1 computed color diff of %f\n",dd); + } + } + + if (sptype <= 0) /* No spacers */ + return pdd; + + if (pp->nmask == ICX_W + || pp->nmask == ICX_K) { /* If only capable of single density */ + sptype = 1; /* Force to B&W spacer */ + } + + if (sptype == 1) { /* B&W spacer */ + double d1, d2; + + if (usede) { + /* Choose whether space should be white or black */ + /* Shoose color that has greatest worst contrast */ + d1 = min2(icmLabDE(pcol[0].Lab, pp->Lab), icmLabDE(pcol[0].Lab, cp->Lab)); + d2 = min2(icmLabDE(pcol[7].Lab, pp->Lab), icmLabDE(pcol[7].Lab, cp->Lab)); + +//printf("~1 worst difference to white = %f\n", d1); +//printf("~1 worst difference to black = %f\n", d2); + + if (d1 > d2) { +//printf("~1 chosen white\n"); + sc = &pcol[0]; + dd = d1; + } else { +//printf("~1 chosen black\n"); + sc = &pcol[7]; + dd = d2; + } + + } else { + /* Choose whether space should be white or black */ + /* Shoose color that has greatest worst contrast */ + d1 = min2(fabs(pcol[0].den[3] - pp->den[3]), fabs(pcol[0].den[3] - cp->den[3])); + d2 = min2(fabs(pcol[7].den[3] - pp->den[3]), fabs(pcol[7].den[3] - cp->den[3])); + +//printf("~1 worst difference to white = %f\n", d1); +//printf("~1 worst difference to black = %f\n", d2); + + if (d1 > d2) { +//printf("~1 chosen white\n"); + sc = &pcol[0]; + dd = d1; + } else { +//printf("~1 chosen black\n"); + sc = &pcol[7]; + dd = d2; + } + } + + } else { /* else colored spacer */ + int ii, i; + + /* Check out all the possible space values for the one that gives the best */ + /* and second best contrast to each edge */ + + if (usede) { + + /* for all possible spacer colors */ + dd = -1.0; + for (i = 0; i < 8; i++) { + double bb; + + bb = min2(icmLabDE(pcol[i].Lab, pp->Lab), icmLabDE(pcol[i].Lab, cp->Lab)); + + /* Worst of two edges best is better than any previous */ + if (bb > dd) { + dd = bb; /* Worst color of worst edge */ + ii = i; + sc = &pcol[i]; + } + } + + } else { + /* for all possible spacer colors */ + dd = -1.0; + for (i = 0; i < 8; i++) { + double b1, b2, bb; + + b1 = RULE3(fabs(pcol[i].den[0] - pp->den[0]), + fabs(pcol[i].den[1] - pp->den[1]), + fabs(pcol[i].den[2] - pp->den[2])); + + b2 = RULE3(fabs(pcol[i].den[0] - cp->den[0]), + fabs(pcol[i].den[1] - cp->den[1]), + fabs(pcol[i].den[2] - cp->den[2])); + + bb = min2(b1, b2); /* Worst of two edges */ + + /* Worst of two edges best is better than any previous */ + if (bb > dd) { + dd = bb; /* Worst color of worst edge */ + ii = i; + sc = &pcol[i]; + } + } + } + } + +//printf("~1 returning spacer contrast %f + patch contrast %f\n",dd,pdd); + if (psc != NULL) + *psc = sc; /* Return pointer to chosen spacer */ + + return 0.6 * dd + 0.4 * pdd; /* Return propotion of spacer and patch contrast */ +} + +/* Given two patches, compute the density difference between them */ +double density_difference( +col *p1, /* Previous patch color */ +col *p2, /* Current patch color */ +int usede /* Aim for maximum delta E rather than density */ +) { + double dd, pdd; + + /* Compute contrast between the patches */ + if (usede) { + pdd = icmLabDE(p1->Lab, p2->Lab); + } else { + /* return the density contrast between the patches */ + if (p1->nmask == ICX_W + || p1->nmask == ICX_K) { /* If only capable of single density */ + + pdd = fabs(p1->den[3] - p2->den[3]); + + } else { + pdd = RULE3(fabs(p1->den[0] - p2->den[0]), + fabs(p1->den[1] - p2->den[1]), + fabs(p1->den[2] - p2->den[2])); + + } + } + + return pdd; +} + +/* Given a number, return the DTP20 SID patch encoding. */ +/* return nz on error */ +static int dtp20_enc( + col *pcol, /* pcol[8] of spacer patch colors */ + int ndig, /* Number of octal digits/patches */ + int lend, /* NZ if little endian order for SID row index */ + col **ppcol, /* return the pointers to pcol for the encoding */ + unsigned int rix /* Number to encode */ +) { + int si, ei, ii; + int i, wv = rix; + + if (lend) /* Little endian order */ + si = 0, ei = ndig, ii = 1; + else + si = ndig-1, ei = -1, ii = -1; + + /* LSB to MSB octal */ + for (i = si; i != ei; i += ii) { + int j, k; + j = wv % 8; /* Digit value */ + wv /= 8; + for (k = 0; k < 8; k++) { + if (pcol[k].dtp20_octval == j) + break; + } + if (k >= 8) + return 1; /* Something weird happened */ + ppcol[i] = &pcol[k]; + } + if (wv != 0) + return 1; /* Number is too big to be encoded */ +//printf("~1dtp20_enc %d ->",rix); +//for (i = 0; i < ndig; i++) +// printf(" %d",ppcol[i]->dtp20_octval); +//printf("\n"); + return 0; +} + + +/* Sort function for stree */ +static int cmp_eperr(const void *p1, const void *p2) { + return ((col *)p1)->wnd == ((col *)p2)->wnd ? 0 : + (((col *)p1)->wnd < ((col *)p2)->wnd ? -1 : 1); +} + +#define SYMWT 1.3 /* Amount to discount direction delta E compared to spacers */ + +/* Setup the randomised index. */ +/* The index only covers test sample patches, not TID or max/min/SID patches */ +void setup_randix( +int *rix, /* Index lookup array to fill in */ +int npat, /* Number of test targets needed */ +int rand, /* Randomise flag */ +int rstart, /* Starting index for random */ +int verb, /* Verbose flag */ +col *cols, /* Array of colors to be put on target chart */ +col *pcol, /* 8 spacer colors */ +int tpprow, /* Test sample patches per row */ +int spacer, /* Spacer code, 0 = None, 1 = b&w, 2 = colored */ +int needpc, /* Need patch to patch contrast in a row */ +int domaxmin, /* Top and tail strip with max/min or SID. 0 = DTP51, 2 = DTP20 SID */ +col *media, /* Alias for media color */ +int usede /* NZ to use delta E rather than density */ +) { + int i; + randix *r = NULL; /* Random index order object */ + + if (rand) + r = new_randix(npat, rstart); + + /* Setup initial linear or randomised index */ + for (i = 0; i < npat; i++) { + if (rand) { + rix[i] = r->next(r); + } else { + rix[i] = i; + } + cols[rix[i]].ix = i; /* This colors random index */ + } + rix[i] = 0; /* npat+1 may be read, so fill extra entry at end. */ + + if (rand) + r->del(r); + + /* Setup initial contrast check */ + { + col *pp, *cp, *np, *op; /* Previous, current, next and opposite patch */ + col *maxd; /* Alias for maximum density */ + col *mind; /* Alias for minimum density */ + aat_atree_t *stree; /* Tree holding colors sorted by worst case contrast */ + aat_atrav_t *aat_tr; /* Tree accessor */ + double temp, trate; /* Annealing temperature & rate */ + double tstart, tend;/* Annealing chedule range */ + + mind = &pcol[0]; /* White */ + maxd = &pcol[7]; /* Black */ + + /* Create sorted tree so as to be able to locate the largest wnd */ + if ((stree = aat_anew(cmp_eperr)) == NULL) + error("Allocating aat tree for colors failed"); + if ((aat_tr = aat_atnew()) == NULL) + error("aat_atnew returned NULL"); + + for (i = 0; i < npat; i++) { + int j, k; /* Patch index, Row index */ + int tpitr; /* Test patches in this row */ + double tt; + + j = (i % tpprow); + k = i / tpprow; + + /* Figure previous patch */ + if (j == 0) { /* First in row */ + if (domaxmin == 1) + pp = maxd; /* Maxd will be before first patch */ + else if (domaxmin == 2) { + col *ppcol[3]; + /* Compute the SID colors the DTP20 will use before row */ + if (dtp20_enc(pcol, 3, 1, ppcol, k+1) != 0) + error("Internal, dtp20 SID row id failed, val %d, digits %d",k+1,3); + pp = ppcol[2]; /* Barcode patch next to first test patch */ + } else + pp = media; /* Media will be before first patch */ + } else { + pp = &cols[rix[i-1]]; + } + + /* Current patch */ + cp = &cols[rix[i]]; + + /* Next patch */ + if (j == (tpprow-1) || i == (npat-1)) { /* Last in row or last patch */ + if ((j == (tpprow-1) || i == (npat-1)) && domaxmin == 1) + np = mind; + else if ((j == (tpprow-1) || i == (npat-1)) && domaxmin == 2) + np = mind; /* DTP20 has white trailing patch */ + else + np = media; + } else { + np = &cols[rix[i+1]]; + } + + /* Opposite patch */ + + if (k == npat/tpprow) /* Last row */ + tpitr = npat - tpprow * (npat/tpprow); + else + tpitr = tpprow; + op = &cols[rix[ k * tpprow + (tpitr -1 - j)]]; + + /* Setup pointers and worst case contrast */ + cp->nc[0] = pp; + cp->nc[1] = np; + cp->oc = op; + cp->wnd = setup_spacer(NULL, pp, cp, pcol, spacer, usede); + tt = setup_spacer(NULL, cp, np, pcol, spacer, usede); + if (tt < cp->wnd) + cp->wnd = tt; + if (cp != op) { + tt = SYMWT * density_difference(cp, op, usede); + if (tt < cp->wnd) + cp->wnd = tt; + } + + /* Insert it into the sorted list */ + if ((aat_ainsert(stree, (void *)cp)) == 0) + error("aat_ainsert color %d failed",i); + } + + if (verb) { + double wrdc = 1e300; + + if ((cp = aat_atfirst(aat_tr, stree)) == NULL) + error("There seem to be no colors in the tree"); + if (usede) + printf("Worst case delta E = %f\n", cp->wnd); + else + printf("Worst case density contrast = %f\n", cp->wnd); + + /* Evaluate each strips direction confusion */ + for (i = 0; ; i++) { + int j, tpitr; /* Test patches in this row */ + double tot = 0.0; + if ((i * tpprow) >= npat) + break; + if (i == npat/tpprow) /* Last row */ + tpitr = npat - tpprow * (npat/tpprow); + else + tpitr = tpprow; + + for (tot = 0.0, j = 0; j < tpitr; j++) { + tot += density_difference(&cols[rix[ i * tpprow + j]], + &cols[rix[ i * tpprow + (tpitr -1 - j)]], usede); + } + tot /= tpitr; + if (tot < wrdc) + wrdc = tot; + } + if (usede) + printf("Worst case direction distinction delta E = %f\n", wrdc); + else + printf("Worst case direction distinction density contrast = %f\n", wrdc); + } + + if (needpc == 0 || !rand || npat < 3) + return; /* Current order is sufficient */ + + if (verb) { + printf("Optimising layout for strip reader:\n"); + printf(" 0%%"); fflush(stdout); + } + + if (spacer == 2) { /* Colored spacer, don't optimise too hard */ + tstart = 0.4; + tend = 0.00001; + trate = 0.87; + } else { /* No spacer or B&W spacer, do more optimisation */ + tstart = 0.4; + tend = 0.000005; + trate = 0.95; + } + /* Simulated anealing */ + for (temp = tstart; temp > tend; temp *= trate) { + + int ii, itlim; /* Maximum passes at a temperature */ + int nsuc = 0; /* Number that succeed */ + int suclim; /* Number of successful changes before continuing */ + + if (spacer == 2) { /* Colored spacer, don't optimise too hard */ + itlim = npat * 10; + suclim = npat; + } else { + itlim = npat * 14; + suclim = npat; + } + + if (verb) { /* Output percent intervals */ + double pc; + + pc = (log(temp) - log(tstart))/(log(tend) - log(tstart)); + printf("%c%2d%%",cr_char,(int)(100.0 * pc+0.5)); fflush(stdout); + } + + /* Improve the ordering */ + for (ii = 0; ii < itlim ; ii++) { + col *p1, *p2; + double tt, de; + + /* Chose another patch to try swapping worst with */ + p1 = aat_atfirst(aat_tr, stree); /* worst */ + for (;;) { + tt = d_rand(0.0, 1.0); + p2 = &cols[(int)(tt * (npat-1.0))]; /* Swap candidate */ + if (p1 != p2 && p2 != p1->oc) + break; /* Swap is not the worst or opposite */ + } + + /* Check p1 in p2's place */ + de = setup_spacer(NULL, p2->nc[0], p1, pcol, spacer, usede); + tt = setup_spacer(NULL, p1, p2->nc[1], pcol, spacer, usede); + if (tt < de) + de = tt; + tt = SYMWT * density_difference(p2->oc, p1, usede); + if (tt < de) + de = tt; + + /* Check p2 in p1's place */ + tt = setup_spacer(NULL, p1->nc[0], p2, pcol, spacer, usede); + if (tt < de) + de = tt; + tt = setup_spacer(NULL, p2, p1->nc[1], pcol, spacer, usede); + if (tt < de) + de = tt; + tt = SYMWT * density_difference(p1->oc, p2, usede); + if (tt < de) + de = tt; + + de = de - p1->wnd; /* Increase in worst difference */ + + /* If this swap will improve things, or temp is high enough, */ + /* then actually do the swap. */ + if (de > 0.0 + || d_rand(0.0, 1.0) < exp(de/temp)) { + int t; + col *tp0, *tp1; + + nsuc++; + +//printf("~1 temp = %f, ii = %d, swapping %d and %d\n",temp,ii,p1->i, p2->i); + + /* Remove them from the tree */ + if ((aat_aerase(stree, (void *)p1)) == 0) + error("aat_aerase failed to find color no %d", p1->i); + if ((aat_aerase(stree, (void *)p2)) == 0) + error("aat_aerase failed to find color no %d", p2->i); + + /* Swap them in random index list */ + rix[p1->ix] = p2->i; + rix[p2->ix] = p1->i; + t = p1->ix; + p1->ix = p2->ix; + p2->ix = t; + + /* Swap their neighbors, taking care */ + /* of the situation if they are neigbors */ + tp0 = p1->nc[0]; + tp1 = p2->nc[0]; + if (tp0 == p1) + tp0 = p2; + else if (tp0 == p2) + tp0 = p1; + if (tp1 == p1) + tp1 = p2; + else if (tp1 == p2) + tp1 = p1; + p2->nc[0] = tp0; + p1->nc[0] = tp1; + + tp0 = p1->nc[1]; + tp1 = p2->nc[1]; + if (tp0 == p1) + tp0 = p2; + else if (tp0 == p2) + tp0 = p1; + if (tp1 == p1) + tp1 = p2; + else if (tp1 == p2) + tp1 = p1; + p2->nc[1] = tp0; + p1->nc[1] = tp1; + + /* Swap their opposites (they cannot be opposites of each other) */ + p1->oc->oc = p2; + p2->oc->oc = p1; + tp0 = p1->oc; + p1->oc = p2->oc; + p2->oc = tp0; + + /* Reset backwards references */ + p1->nc[0]->nc[1] = p1; + p1->nc[1]->nc[0] = p1; + p2->nc[0]->nc[1] = p2; + p2->nc[1]->nc[0] = p2; + + /* re-compute contrast to neighbors */ + p1->wnd = setup_spacer(NULL, p1->nc[0], p1, pcol, spacer, usede); + tt = setup_spacer(NULL, p1, p1->nc[1], pcol, spacer, usede); + if (tt < p1->wnd) + p1->wnd = tt; + if (p1 != p1->oc) { + tt = SYMWT * density_difference(p1, p1->oc, usede); + if (tt < p1->wnd) + p1->wnd = tt; + } + + p2->wnd = setup_spacer(NULL, p2->nc[0], p2, pcol, spacer, usede); + tt = setup_spacer(NULL, p2, p2->nc[1], pcol, spacer, usede); + if (tt < p2->wnd) + p2->wnd = tt; + if (p2 != p2->oc) { + tt = SYMWT * density_difference(p2, p2->oc, usede); + if (tt < p2->wnd) + p2->wnd = tt; + } + + /* (!!! We haven't recomputed the possible change in the ->oc's */ + /* ->wnd due to it's opposite haveing changed. !!!) */ + + /* Add them back to the tree */ + if ((aat_ainsert(stree, (void *)p1)) == 0) + error("aat_ainsert color no %d failed",p1->i); + if ((aat_ainsert(stree, (void *)p2)) == 0) + error("aat_ainsert color no %d failed",p2->i); + +#ifdef NEVER +printf("~1 current list = \n"); +for (cp = aat_atfirst(aat_tr, stree); cp != NULL; cp = aat_atnext(aat_tr)) + printf("%d: %f\n",cp->i,cp->wnd); +cp = aat_atfirst(aat_tr, stree); +printf("~1 worst case contrast = %f, list index %d, id '%s', loc '%s'\n", +cp->wnd, cp->i,cp->id,cp->loc); +printf("~1 neighbor list index %d, id '%s', loc '%s'\n", +cp->nc[0]->i,cp->nc[0]->id,cp->nc[0]->loc); +printf("~1 neighbor list index %d, id '%s', loc '%s'\n", +cp->nc[1]->i,cp->nc[1]->id,cp->nc[1]->loc); +#endif + + if (nsuc > suclim) + break; + } + } + } + + if (verb) { + double wrdc = 1e300; + if ((cp = aat_atfirst(aat_tr, stree)) == NULL) + error("There seem to be no colors in the tree"); + if (usede) + printf("%c100%%\nAfter optimisation, worst delta E = %f\n",cr_char,cp->wnd); + else + printf("%c100%%\nAfter optimisation, density contrast = %f\n",cr_char,cp->wnd); + + /* Evaluate each strips direction confusion */ + for (i = 0; ; i++) { + int j, tpitr; /* Test patches in this row */ + double tot = 0.0; + if ((i * tpprow) >= npat) + break; + if (i == npat/tpprow) /* Last row */ + tpitr = npat - tpprow * (npat/tpprow); + else + tpitr = tpprow; + + for (tot = 0.0, j = 0; j < tpitr; j++) { + tot += density_difference(&cols[rix[ i * tpprow + j]], + &cols[rix[ i * tpprow + (tpitr -1 - j)]], usede); + } + tot /= tpitr; + if (tot < wrdc) + wrdc = tot; + } + if (usede) + printf("Worst case direction distinction delta E = %f\n", wrdc); + else + printf("Worst case direction distinction density contrast = %f\n", wrdc); + } + + + aat_atdelete(aat_tr); + aat_adelete(stree); + + } + +} + +#define MAXPPROW 500 /* Absolute maximum patches per pass/row permitted */ +#define MAXROWLEN 2000.0 /* Absolute maximum row length */ + +void +generate_file( +instType itype, /* Target instrument type */ +char *bname, /* Output file basename */ +col *cols, /* Array of colors to be put on target chart */ +int npat, /* Number of test targets needed */ +xcal *cal, /* Optional printer calibration, NULL if none */ +char *label, /* Per strip label */ +double pw, /* Page width */ +double ph, /* Page height */ +double bord, /* Border margin in mm */ +int nosubmarg, /* NZ if bord is not to be subtracted from raster */ +int nollimit, /* NZ to not limit the strip length */ +int nolpcbord, /* NZ to suppress left paper clip border */ +int rand, /* Randomise flag */ +int rstart, /* Starting index for random */ +alphix *saix, /* Strip alpha index object */ +alphix *paix, /* Patch alpha index object */ +int ixord, /* Index order, 0 = strip then patch */ +double pscale, /* Test patch & spacers scale factor */ +double sscale, /* Spacers scale factor */ +int hflag, /* Spectroscan/Munki high density modified */ +int verb, /* Verbose flag */ +int scanc, /* Scan compatible bits, 1 = .cht gen, 2 = wide first row */ +int oft, /* PS/EPS/TIFF select (0,1,2) */ +depth2d tiffdpth, /* TIFF pixel depth */ +double tiffres, /* TIFF resolution in DPI */ +int ncha, /* flag, use nchannel alpha */ +int tiffdith, /* flag, nz to use TIFF 8 bit dithering */ +int tiffcomp, /* flag, nz to use TIFF compression */ +int spacer, /* Spacer code, -1 = default, 0 = None, 1 = b&w, 2 = colored */ +int nmask, /* DeviceN mask */ +int altrep, /* printer grey/CMY representation type 0..8 */ +col *pcol, /* 8 spacer colors or 8 barcode colors for DTP20 */ +double *wp, /* Approximate white XYZ point */ +int *ptpprow, /* Return Test sample patches per row */ +unsigned char **pprps, /* Return malloced array holding Passes per strip */ +double *p_patchlen, /* Return patch length in mm */ +double *p_gaplen, /* Return gap length in mm */ +double *p_taplen, /* Return trailer length in mm */ +int *p_npat /* Return number of patches including padding */ +) { + char psname[MAXNAMEL+20]; /* Name of output file */ + trend *tro = NULL; /* Target rendering object */ + double x1, y1, x2, y2; /* Bounding box in mm */ + double iw, ih; /* Imagable areas width and height in mm */ + double arowl; /* Available row length */ + + int hex = 0; /* Hexagon patches flag (Spectrolino) */ + double stagger = 0.0; /* Stagger alternate rows by half patch length */ + + /* Chart definition variables. Set to default and override */ + /* for particular instruments. */ + double lbord = 0.0; /* Additional left border */ + int domaxmin = 0; /* if == 1, Top and tail strip with max and min values (DTP51) */ + /* if == 2, Top and tail with DTP20 strip identification (SID) */ + int nmaxp = 0; /* Number of max (header) patches for max/min/sid */ + int nminp = 0; /* Number of min (trailer) patches for max/min/sid */ + int nextrap = 0; /* Number of extra patches for max and min = nmaxp + nminp */ + int needpc = 0; /* Need patch to patch contrast in a row */ + int dorspace = 0; /* Do a rrsp from center of last patch to cut line & print label. */ + int dopglabel = 0; /* Write a per page label */ + double pglth = 0.0; /* Page Label text height */ + int padlrow = 0; /* flag - Pad the last row with white */ + double lspa = 0.0; /* Leader space before first patch containint border, label, SID etc. */ + double lcar = 0.0; /* Leading clear area before first patch. Will be white */ + double plen = 0.0; /* Patch min length */ + double tidplen = 0.0; /* TID Patch min length */ + double pspa = 0.0; /* Patch to patch spacer */ + double tspa = 0.0; /* Clear space after last patch */ + double txhi = 0.0; /* Step/cut, row label text height */ + double txhisl = 0.0;/* Strip/column label text height */ + int docutmarks = 0; /* Generate strip cut marks */ + double clwi = 0.0; /* Cut line width */ + + int dorowlabel = 0; /* Generate a set of row labels */ + double rlwi = 0.0; /* Row label test width */ + + double hxew = 0.0; /* Hexagon chart extra width padding to the right of patches */ + double hxeh = 0.0; /* Hexagon chart extra height padding around patches */ + + double pwid = 0.0; /* Patch min width */ + double rrsp = 0.0; /* Row to row spacing */ + double pwex = 0.0; /* Patch width expansion between rows of a strip */ + + int mxpprow = 0; /* Maximum patches per row permitted (including min/max patches) */ + double mxrowl = 0; /* Maximum row length for patchs (including min/max patches) */ + /* Number of patches is strip by whichever is shorter */ + int tidrows = 0; /* Rows on first page for target ID (ie. DTP20) */ + int tidtype = 0; /* Target ID type. 0 = DTP20 */ + int tidminp = 0; /* Target ID minumum number of patches */ + int tidpad = 0; /* Initial padding to place TID near middle */ + int tidnpat = npat; /* Number of test targets needed, including TID row */ + int rpstrip = 0; /* Rows per strip */ + int usede = 0; /* Use delta E to maximize patch/spacer conrast rather than density */ + + double mints, minbs; /* Minimum top & bottom space from paper edges */ + double amints, aminbs; /* Actual mints & minbs, allowing for unused space */ + double swid; /* Whole strip width */ + int pprow; /* patches per row (inc. max/min/sid patches) */ + int tidpprow; /* TID patches per row (inc. max/min/sid patches) */ + int tpprow; /* test patches per row (excludes max/min/sid patches) */ + int sppage; /* whole & partial strips per page */ + int rppstrip; /* rows per partial strip on whole page */ + int npages; /* Number whole & partial pages */ + int lsppage; /* Last page whole & partial strips per page */ + int lrpstrip; /* Last strips whole & partial rows per strip */ + int lpprow; /* last row patches per row (inc. max/min/sid patches) */ + int ppstrip; /* Real patches per whole strip */ + int pppage; /* Real patches per whole page */ + int rem; /* temporary */ + double sxwi = 0.0; /* Scan compatible first row extra width */ + + int *rix; /* Random index lookup (Logical -> patch index) */ + int i; /* Logical patch in target list */ + int l_si; /* Last start of page value of i */ + int ix; /* Patch index in target list */ + int pir; /* Patch in row */ + int ris; /* Row in strip */ + int sip; /* Set in page (including partial strip) */ + int pif; /* Page in file */ + double x = 0.0, y = 0.0; /* Current position */ + col *pp = NULL, *cp = NULL; /* Previous and current printed patch colors */ + int cpf = 0; /* Current patch special flag. 1 = start bit, 2 = stop bit */ + int slix; /* Strip label index, -1 for TID */ + char *slab = NULL; /* Strip label string */ + unsigned char *rpsp; /* Rows per strip, pointer */ + col *mark; /* Alias for mark color */ + col *media; /* Alias for media color */ + col *maxd; /* Alias for minimum density */ + col *mind; /* Alias for maximum density */ + col *sc; /* Alias for current spacer color */ + + /* Note pcol[] is setup by targen to be: + 0 = white + 1 = Cyan + 2 = Magenta + 3 = Blue + 4 = Yellow + 5 = Green + 6 = Red + 7 = Black + 8 = 50/50/50 CMY Grey + (Should switch to symbols for these ??) + */ + + /* We assume that since this is intended for a printer, */ + /* the media is always white. This may not be the case */ + /* on other output media. */ + if (itype == instDTP20) + mark = &pcol[8]; /* Grey */ + else + mark = &pcol[7]; /* Black */ + media = &pcol[0]; /* White */ + mind = &pcol[0]; /* White */ + maxd = &pcol[7]; /* Black */ + + /* Setup DTP20 bar code encoding for spacers. */ + pcol[0].dtp20_octval = 0; /* White */ + pcol[0].dtp20_psize = 6.5; + pcol[1].dtp20_octval = 4; /* Cyan */ + pcol[1].dtp20_psize = 10.0; + pcol[2].dtp20_octval = 2; /* Magenta */ + pcol[2].dtp20_psize = 0.0; + pcol[3].dtp20_octval = 6; /* Blue */ + pcol[3].dtp20_psize = 12.5; + pcol[4].dtp20_octval = 1; /* Yellow */ + pcol[4].dtp20_psize = 7.0; + pcol[5].dtp20_octval = 5; /* Green */ + pcol[5].dtp20_psize = 0.0; + pcol[6].dtp20_octval = 3; /* Red */ + pcol[6].dtp20_psize = 0.0; + pcol[7].dtp20_octval = 7; /* Black */ + pcol[7].dtp20_psize = 13.0; + + /* Setup .cht edge tracking information */ + et_init(); + et_height(oft != 2 ? mm2pnt(ph) : ph); + et_media(media->rgb); + + /* Set Instrument specific parameters */ + if (itype == instDTP20) { /* Xrite DTP20 */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 2; /* Print SID patches */ + nmaxp = 4; /* Extra header patches */ + nminp = 1; /* Extra trailer patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Pad the last row with white */ + spacer = 0; /* No Spacer between patches */ + pspa = 0.0; /* No spacer width */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + needpc = 1; /* Helps to have patch to patch contrast in a row ? */ + lspa = bord + 5.0 + 5.0; /* Leader space before first patch = bord + pcar + yxhi */ + lcar = 5.0; /* Leading clear area before first patch */ + plen = pscale * (6.5); /* Patch min length */ + if(plen <= 6.75) /* Patch length must be one of 5 lengths */ + plen = 6.5; + else if(plen <= 8.0) + plen = 7.0; + else if(plen <= 11.25) + plen = 10.0; + else if(plen <= 12.75) + plen = 12.5; + else + plen = 13.0; + tidplen = 6.0; /* TID Patch length. Can't vary. */ + tspa = 5.0; /* Clear space after last patch */ + pwid = 10.0; /* Patch min width. (The guide slot is 12mm ?) */ + if (plen > pwid) + pwid = plen; /* Make patch at least as wide as long */ + rrsp = pwid; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = (240.0 - lcar - tspa); /* Maximum row length */ + } else { + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = MAXROWLEN; /* No row length */ + } + tidrows = 1; /* Rows on first page for target ID */ + tidtype = 0; /* Target ID type. 0 = DTP20 */ + tidminp = 21; /* Target ID minumum number of patches (+ white) */ + rpstrip = 999; /* Rows per strip */ + txhi = 5.0; /* Label Text Height */ + txhisl = 2.0; /* Strip Label Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + if (npat > 4095) + error ("Number of patchs %d exceeds maximum of 4095 for DTP20"); + + } else if (itype == instDTP22 ) { /* X-Rite DTP22 Digital Swatchbook */ + hex = hflag ? 1 : 0; /* Hex if requestested */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Do a rrsp from center of last patch to cut line */ + dopglabel = 1; /* Write a per page label */ + padlrow = 0; /* Pad the last row with white */ + spacer = 0; /* No spacer */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + needpc = 1; /* Need patch to patch contrast in a row */ + lspa = bord + 8.0; /* Leader space before first patch = border + text */ + lcar = 0.0; /* Leading clear area before first patch */ + if (hex) { + plen = pscale * sqrt(0.75) * 8.0; /* Patch min length */ + hxeh = 1.0/6.0 * plen; /* Extra border for hex tops & bottoms */ + hxew = pscale * 0.25 * 8.0; /* Extra border for hex sides */ + } else { + plen = pscale * 8.0; /* Patch min length */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + } + pspa = 0.0; /* Inbetween Patch spacer */ + tspa = 0.0; /* Clear space after last patch */ + pwid = pscale * 8.0; /* Patch min width */ + rrsp = pscale * 8.0; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 5.0; /* Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 1; /* Generate row labels */ + rlwi = 8.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instDTP41) { /* Xrite DTP41 */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 0; /* Don't print max and min patches */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Pad the last row with white */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + lspa = inch2mm(1.5); /* Leader space before first patch */ + lcar = inch2mm(0.5); /* Leading clear area before first patch */ + plen = pscale * inch2mm(0.29); /* Patch min length (should be 7.0 mm min.) */ + if (spacer > 0) + pspa = pscale * sscale * inch2mm(0.08); /* Inbetween Patch spacer (should be 2.0 mm min.)*/ + else + pspa = 0.0; /* No spacer */ + tspa = 2 * (plen + pspa); /* Clear space after last patch */ + pwid = inch2mm(0.5); /* Patch min width */ + rrsp = inch2mm(0.5); /* Row center to row center spacing */ + pwex = (rrsp - pwid)/2.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = 100; /* Maximum patches per row permitted */ + mxrowl = inch2mm(55.0); /* Maximum row length */ + } else { + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + } + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 8; /* Rows per strip */ + txhi = txhisl = 5.0; /* Text Height */ + docutmarks = 1; /* Generate strip cut marks */ + clwi = 0.3; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instDTP51) { /* Xrite DTP51 */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 1; /* Print max and min patches */ + nmaxp = nminp = 1; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 1; /* Do a rrsp from center of last patch to cut line */ + dopglabel = 0; /* No need for a per page label */ + padlrow = 1; /* Pad the last row with white */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + lspa = inch2mm(1.2); /* Leader space before first patch */ + lcar = inch2mm(0.25); /* Leading clear area before first patch */ + plen = pscale * inch2mm(0.4); /* Patch min length */ + if (spacer > 0) + pspa = pscale * sscale * inch2mm(0.07); /* Inbetween Patch spacer */ + else + pspa = 0.0; /* No spacer */ + tspa = inch2mm(0.0); /* Clear space after last patch */ + pwid = inch2mm(0.4); /* Patch min width */ + rrsp = inch2mm(0.5); /* Row center to row center spacing */ + pwex = (rrsp - pwid)/2.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = 72; /* Maximum patches per row permitted */ + mxrowl = inch2mm(40.0); /* Maximum row length */ + } else { + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + } + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 6; /* Rows per strip */ + txhi = txhisl = 5.0; /* Text Height */ + docutmarks = 1; /* Generate strip cut marks */ + clwi = 0.3; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instSpectroScan ) { /* GretagMacbeth SpectroScan */ + hex = hflag ? 1 : 0; /* Hex if requestested */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows */ + dopglabel = 1; /* Write a per page label */ + padlrow = 0; /* Pad the last row with white */ + spacer = 0; /* No spacer */ + needpc = 0; /* Don't need patch to patch contrast in a row */ + lspa = bord + 7.0; /* Leader space before first patch = border + text */ + lcar = 0.0; /* Leading clear area before first patch */ + if (hex) { + plen = pscale * sqrt(0.75) * 7.0; /* Patch min length */ + hxeh = 1.0/6.0 * plen; /* Extra border for hex tops & bottoms */ + hxew = pscale * 0.25 * 7.0; /* Extra border for hex sides */ + } else { + plen = pscale * 7.0; /* Patch min length */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + } + pspa = 0.0; /* Inbetween Patch spacer */ + tspa = 0.0; /* Clear space after last patch */ + pwid = pscale * 7.0; /* Patch min width */ + rrsp = pscale * 7.0; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 5.0; /* Row/Column Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 1; /* Generate row labels */ + rlwi = 7.5; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instI1Pro ) { /* GretagMacbeth Eye-One Pro */ + if (nolpcbord == 0 && bord < 26.0) + lbord = 26.0 - bord; /* need this for holder to grip paper and plastic spacer */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows by having no space between them */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Don't need to pad the last row for the i1, */ + /* but the strip read logic can't handle it. */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + lspa = bord + 7.0 + 10.0; /* Leader space before first patch = bord + txhisl + lcar */ + lcar = 10.0; /* Leading clear area before first patch */ + plen = pscale * 10.00; /* Patch min length - total 11 mm */ + if (spacer > 0) + pspa = pscale * sscale * 1.00; /* Inbetween Patch spacer 1mm */ + else + pspa = 0.0; /* No spacer */ + tspa = 10.0; /* Clear space after last patch - run off */ + pwid = pscale * 8.0; /* Patch min width */ + rrsp = pscale * 8.0; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = (260.0 - lcar - tspa); /* Maximum holder row length */ + } else { + mxpprow = MAXPPROW; /* Maximum */ + mxrowl = MAXROWLEN; /* Maximum */ + } + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 7.0; /* Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instColorMunki ) { /* X-Rite ColorMunki */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Put spaces between rows for guidance */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Don't need to pad the last row for the Munki, */ + /* but the strip read logic can't handle it. */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + lspa = bord + 7.0 + 20.0; /* Leader space before first patch = bord + txhisl + lcar */ + lcar = 20.0; /* Leading clear area before first patch */ + plen = pscale * 14.00; /* Patch min length - total 15 mm */ + if (spacer > 0) + pspa = pscale * sscale * 1.0; /* Inbetween Patch spacer 1mm */ + else + pspa = 0.0; /* No spacer */ + tspa = 25.0; /* Clear space after last patch - run off */ + if (hflag) { /* High density */ + pwid = pscale * 13.7; /* Patch min width */ + rrsp = pscale * 13.7; /* Row center to row center spacing */ + hxeh = 0.25 * plen; /* Extra space for row stagger */ + stagger = 0.5 * (plen + 0.5 * pspa); /* Do stagger */ + } else { + pwid = pscale * 28.0; /* Patch min width */ + rrsp = pscale * 28.0; /* Row center to row center spacing */ + } + pwex = 0.0; /* Patch width expansion between rows of a strip */ + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = MAXROWLEN; /* Maximum row length */ + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 7.0; /* Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + + } else { + error("Unsupported intrument type"); + } + + /* Compute page limits */ + x1 = bord + lbord; /* Bounding box in mm */ + y1 = bord; + x2 = pw - bord; + y2 = ph - bord; + iw = x2 - x1; /* Imagable areas width and height in mm */ + ih = y2 - y1; + + *p_patchlen = plen; /* Return patch lenth in mm */ + *p_gaplen = pspa; /* Return gap lenth in mm */ + *p_taplen = tspa; /* Return trailer lenth in mm */ + + if (scanc & 2) /* Scan compatiblity */ + sxwi = pwid/2.0; /* First row patches extra width */ + + /* Compute limits for this page size */ + /* Figure the available space for patches */ + mints = bord + txhisl + lcar; /* Minimum top space due to border, text and clear area */ + if (mints < lspa) + mints = lspa; /* Minimum top space due to leader */ + minbs = bord; /* Minimum botom space due to border */ + if (minbs < tspa) + minbs = tspa; /* Minimum botom space due to trailer */ + arowl = ph - mints - minbs - 2.0 * hxeh; /* Available space for printing test patches */ + if (arowl > mxrowl) + arowl = mxrowl; /* Limit maximum row length */ + + /* We are assuming that every patch may be surounded by a spacer */ + /* (ie. there are always pprow+1 gaps spacers are used) */ + pprow = (int)((arowl - pspa)/(plen + pspa)); /* Raw Patches per row */ + if (pprow > mxpprow) /* Limit to maximum */ + pprow = mxpprow; + + tidpprow = 0; + if (tidminp > 0 && tidplen > 0.0) { + tidpprow = (int)((arowl - pspa)/(tidplen + pspa)); /* Raw TID Patches per row */ + if (tidpprow < tidminp) /* TID doesn't use nextrap */ + error("Paper size not long enough for target identification row (need %.1f mm, got %.1f mm)!",tidminp * (tidplen + pspa) - pspa, arowl); + } + + tidpad = (pprow - tidminp)/2; /* Center TID */ + + if (pprow < (1+nextrap)) + error("Paper size not long enought for a single patch per row!"); + + *ptpprow = tpprow = pprow - nextrap; /* Test sample patches per row */ + + tidnpat = npat + (tidrows * tpprow); /* Total patches including TID row, but not max/min/sid */ + + /* (Sample patches per row including TID) */ + if ((*pprps = (unsigned char *)malloc(sizeof(unsigned char) * (2 + (tidnpat/tpprow)))) == NULL) + error("Malloc failed!"); + rpsp = *pprps; + + /* Compute actual lowest coordinate used */ + aminbs = ph - mints - pspa - pprow * (plen + pspa); + amints = mints + 0.5 * (aminbs - minbs); /* Distribute extra space */ + aminbs = minbs + 0.5 * (aminbs - minbs); + + /* Compute whole strip width */ + if (dorspace) + swid = rpstrip * rrsp + pwid/2.0; /* set gutter is rrsp - pwid/2 wide */ + else + swid = (rpstrip-1) * rrsp + pwid + clwi; /* set gutter is 0, but allow for cut line */ + + /* Compute strips per page. Number of whole strips + partial strips */ + sppage = (int)((iw - rlwi - sxwi - 2.0 * hxew - (dopglabel ? pglth : 0.0))/swid) + 1; + + /* Compute rows per partial strip on whole page */ + if (dorspace) + rppstrip = (int)((iw - rlwi - sxwi - 2.0 * hxew - (dopglabel ? pglth : 0.0) - swid * (sppage-1) - pwid/2.0)/rrsp); + else + rppstrip = (int)((iw - rlwi - sxwi - 2.0 * hxew - (dopglabel ? pglth : 0.0) - swid * (sppage-1) - pwid + rrsp)/rrsp); + if (rppstrip < 0) + rppstrip = 0; + if (rppstrip == 0) { /* Make last partial strip a full strip */ + sppage--; + rppstrip = rpstrip; + } + + if (sppage <= 0) + error("Not enough width for even one row!"); + + /* The number of pages needed */ + pppage = tpprow * ((sppage-1) * rpstrip + rppstrip);/* Real patches per page */ + npages = (tidnpat + pppage -1)/pppage; /* whole & partial pages */ + ppstrip = tpprow * rpstrip; /* Real patches per full strip */ + + rem = tidnpat; /* Total test patches to print */ + rem -= (npages-1) * pppage; /* Remaining patches to be printed on last page */ + + lsppage = (rem + ppstrip -1)/ppstrip; /* Last pages whole & partial strips per page */ + rem -= (lsppage - 1) * ppstrip; /* remaining patches to be printed in last strip */ + + lrpstrip = (rem + tpprow - 1)/tpprow; + /* Last strips whole & partial rows per strip */ + + rem -= (lrpstrip - 1) * tpprow; /* remaining patches to be printed in last row */ + + lpprow = rem + nextrap; /* Patches in last row of last strip of last page */ + + if (verb) { + fprintf(stderr,"Patches = %d\n",npat); + fprintf(stderr,"Test patches per row = %d\n",tpprow); + if (sppage == 1) + fprintf(stderr,"Rows per page = %d, patches per page = %d\n",rppstrip, pppage); + else + fprintf(stderr,"Strips per page = %d, rows per partial strip = %d, patches per page = %d\n",sppage, rppstrip, pppage); + if (tidrows > 0) + fprintf(stderr,"Target ID rows in first page = %d\n", tidrows); + fprintf(stderr,"Rows in last strip = %d, patches in last row = %d\n", lrpstrip, lpprow-nextrap); + fprintf(stderr,"Total pages needed = %d\n",npages); + } + + if (padlrow) { /* Add in extra padding patches */ + int i; + for (i = 0; lpprow < pprow; lpprow++, npat++, tidnpat++, i = (i + 1) & 7) { +#ifdef NEVER + if (needpc && rand) + cols[npat] = pcol[i]; /* structure copy */ + else + cols[npat] = *media; /* structure copy */ +#else + cols[npat] = *media; /* structure copy */ +#endif + cols[npat].i = npat; /* Now has an index */ + cols[npat].t &= ~T_PRESET; + cols[npat].t |= T_PAD; + cols[npat].id = "0"; /* Padding identification */ + } + } + *p_npat = npat; /* Return number of patches including padding */ +//printf("~1 padded no of patches = %d\n", npat); + + /* Setup logical to random patch mapping */ + if ((rix = (int *)malloc(sizeof(int) * (npat + 1))) == NULL) + error("Malloc failed!"); + + setup_randix(rix, npat, rand, rstart, verb, cols, pcol, + tpprow, spacer, needpc, domaxmin, media, usede); + rix[npat] = -1; /* Shouldn't use this */ + + /* Init everything */ + l_si = i = 0; /* Physical test patch index. */ + ix = rix[i]; /* First index */ + + pir = 1; /* Starting patch in row (includes max/min patches) */ + ris = 1; /* Starting row in strip. */ + sip = 1; /* Starting strip in page */ + pif = 1; /* Starting page in file */ + + /* slix is 0..n but is pre-incremented, so start at -1 */ + slix = -1 -tidrows; /* Start at -2 if there is a TID */ + + /* Until there are no more patches to do */ + for (;;) { + char *sp = NULL; /* String pointer - label */ + double w; /* Width of next patch */ + int flags; /* flags for current patch */ +#define IS_FPIR 0x00001 /* Is first patch in row (possibly max density patch) */ +#define IS_XPAT 0x00002 /* Is max density/SID patch */ +#define IS_NPAT 0x00004 /* Is min density/SID patch */ +#define IS_LPIR 0x00008 /* Is last patch in row (possibly min density patch) */ +#define IS_FRIS 0x00010 /* Is first row in strip */ +#define IS_LRIS 0x00020 /* Is last row in strip */ +#define IS_FSIP 0x00040 /* Is first strip in page */ +#define IS_LSIP 0x00080 /* Is last strip in page */ +#define IS_FPIF 0x00100 /* Is first page in file */ +#define IS_LPIF 0x00200 /* Is last page in file */ +#define IS_PAD 0x04000 /* Is fake padding patch, to round out very last row */ + + /* Init flags for this patch */ + flags = 0; + if (pir == 1) + flags |= IS_FPIR; + if (pir <= nmaxp) + flags |= IS_XPAT; + + if (slix < -1) { /* If TID row */ + + if (pir == tidpprow) + flags |= IS_LPIR; + + } else { + + if (pir > (pprow - nminp)) + flags |= IS_NPAT; + + if (pir == pprow) + flags |= IS_LPIR; + } + + if (ris == 1) + flags |= IS_FRIS; + if (ris == rpstrip) + flags |= IS_LRIS; + + if (sip == 1) + flags |= IS_FSIP; + if (sip == sppage) { + flags |= IS_LSIP; + if (ris == rppstrip) + flags |= IS_LRIS; + } + if (pif == 1) + flags |= IS_FPIF; + if (pif == npages) { + flags |= IS_LPIF; + if (sip == lsppage) { + flags |= IS_LSIP; + if (ris == lrpstrip) { + flags |= IS_LRIS; + + if (padlrow) { /* Last row in chart may be a runt */ + if (pir > lpprow) + flags |= IS_PAD; + } else { + if (pir > (lpprow - nminp)) + flags |= IS_NPAT; + if (pir == lpprow) + flags |= IS_LPIR; + } + } + } + } + +//printf("~1 pir %d, ris %d, sip %d, pif %d\n", pir, ris, sip, pif); + + /* Set initial patch width */ + w = pwid; + if (!(flags & IS_FRIS)) + w += pwex; /* Extend patch into previous row to row gap */ + if (!(flags & IS_LRIS)) + w += pwex; /* Extend patch into next row to row gap */ + + if ((flags & IS_FRIS) && (flags & IS_FSIP)) /* First row on page */ + w += sxwi; /* Make first row fatter for scan compatiblity */ + + if (flags & IS_FPIR) { /* Start of row */ + y = ph - amints; /* Start ready for spacer or patch */ + + if (flags & IS_FRIS) { /* Start of strip */ + if (flags & IS_FSIP) { /* Start of page */ + x = x1; /* Start at leftmost position */ + if (oft == 0) { /* PS */ + if (flags & IS_FPIF) { /* First page */ + sprintf(psname,"%s.ps",bname); + if ((tro = new_ps_trend(psname,npages,nmask,pw,ph,oft,rand,rstart)) == NULL) + error ("Unable to create output rendering object file '%s'",psname); + if (verb) + printf("Creating file '%s'\n",psname); + } + } else if (oft == 1) { /* EPS */ + if (npages > 1) + sprintf(psname,"%s_%02d.eps",bname,pif); + else + sprintf(psname,"%s.eps",bname); + if ((tro = new_ps_trend(psname,npages,nmask,pw,ph,oft,rand,rstart)) == NULL) + error ("Unable to create output rendering object file '%s'",psname); + if (verb) + printf("Creating file '%s'\n",psname); + } else { /* TIFF */ + double res; /* pix/mm */ + if (npages > 1) + sprintf(psname,"%s_%02d.tif",bname,pif); + else + sprintf(psname,"%s.tif",bname); + + res = tiffres/25.4; + if ((tro = new_tiff_trend(psname,nmask,tiffdpth,pw,ph, + nosubmarg ? 0 : bord, res,res,altrep,ncha,tiffcomp, tiffdith)) == NULL) + error ("Unable to create output rendering object file '%s'",psname); + if (verb) + printf("Creating file '%s'\n",psname); + } + tro->startpage(tro,pif); + + /* Print all the row labels */ + if (dorowlabel) { + double ty = y; /* Temp y coord */ + int tpir; /* Temp patch in row */ + + for (tpir = 0; tpir < pprow; tpir++) { + /* If we're within test sample patch range */ + if (tpir >= nmaxp && tpir < (pprow - nminp)) { + char *rlabl; + if ((rlabl = paix->aix(paix, tpir - nmaxp)) == NULL) + error ("Patch in row label %d out of range",tpir); + tro->setcolor(tro, cal, mark); + tro->string(tro, x, ty-plen, rlwi, plen, rlabl); + free(rlabl); + } + + ty -= plen + pspa; + } + + x += rlwi; + } + + x += hxew; /* Extra space on left for bits of hex */ + + /* Clear edge list tracking */ + et_clear(); + } + } + + /* Increment strip label 0..n */ + slix++; + + /* Print strip label */ + if ((lspa - lcar - bord) >= txhisl) { /* There is room for label */ + if (slix < 0) { /* TID */ + tro->setcolor(tro, cal, mark); + tro->string(tro,x,y2-txhisl,w,txhisl,"TID"); + } else { /* Not TID */ + if (slab != NULL) + free(slab); + if ((slab = saix->aix(saix, slix)) == NULL) + error("strip index %d out of range",slix); + + tro->setcolor(tro, cal, mark); + tro->string(tro,x,y2-txhisl,w,txhisl,slab); + } + } + + /* Start with background = media */ + pp = media; + + /* Stagger rows */ + if (stagger > 0.0) { + if (slix & 1) + y -= stagger; + } + } + + /* Figure the current patch color */ + cpf = 0; + if (slix < 0) { /* TID */ + cp = mind; /* Default padding color is white */ + if (pir > tidpad && pir <= (tidpad + tidminp) ) { + int opir = pir - tidpad -1; /* TID index 0 .. tidminp-1 */ + + if (opir == 0) { + cp = &pcol[1]; /* Cyan */ + + } else if (opir >= 1 && opir <= 2) { /* Patches in each strip */ + col *ppcol[2]; + if (dtp20_enc(pcol, 2, 0, ppcol, tpprow) != 0) + error("Internal, dtp20 TID ppstrip failed, val %d, digits %d",tpprow,2); + cp = ppcol[opir-1]; + + } else if (opir >= 3 && opir <= 6) { /* Total patches, including padding */ + col *ppcol[4]; + if (dtp20_enc(pcol, 4, 0, ppcol, npat) != 0) + error("Internal, dtp20 tot patches failed, val %d, digits %d",npat,4); + cp = ppcol[opir-3]; + + } else if (opir == 7) { /* Patch size */ + int j; + for (j = 0; j < 8; j++) { + if (fabs(pcol[j].dtp20_psize - plen) < 0.001) + break; + } + if (j >= 8) + error("Can't encode patch length for DTP20"); + cp = &pcol[j]; + + } else if (opir == 8) { /* Patch spacer width */ + int j, k; + k = (int)(pspa / 0.5 + 0.5); + for (j = 0; j < 8; j++) { + if (pcol[j].dtp20_octval == k) + break; + } + if (j >= 8) + error("Can't encode spacer length for DTP20"); + cp = &pcol[j]; + + } else if (opir >= 9 && opir <= 18) { /* User defined */ + + if (opir == 9) { /* Indicate what user defined is used for */ + cp = &pcol[0]; /* 0 = random seed format */ + } else if (opir >= 10 && opir <= 13) { /* Random seend value */ + col *ppcol[4]; + if (dtp20_enc(pcol, 4, 0, ppcol, rstart) != 0) + error("Internal, dtp20 chart id failed, val %d, digits %d",rstart,4); + cp = ppcol[opir-10]; + + } else { /* 14 .. 18 */ + cp = &pcol[0]; /* Currently unused */ + } + + } else if (opir == 19) { + cp = &pcol[4]; /* Yellow */ + } else if (opir == 20) { + cp = &pcol[2]; /* Magenta */ + } + } + } else { + if (flags & IS_XPAT) { /* Max or SID at start of row */ + sp = NULL; /* Not a test patch (no label) */ + if (domaxmin == 1) { + cp = maxd; /* Maximum density patch at start */ + } else if (domaxmin == 2) { + if (pir == 1) { /* At very start */ + cpf = 1; /* Create start bit */ + cp = &pcol[8]; /* Starts with 50/50/50 DTP20 Grey */ + } else { + col *ppcol[3]; + /* Compute the patch colors the DTP20 will use before row */ + if (dtp20_enc(pcol, 3, 1, ppcol, slix+1) != 0) + error("Internal, dtp20 SID row id failed, val %d, digits %d",slix+1,3); + cp = ppcol[pir-2]; + } + } + + } else if (flags & IS_NPAT) { /* Min at end of rows or stop bit */ + sp = NULL; /* Not a test patch (no label) */ + if (domaxmin == 1) { + cp = mind; + } else if (domaxmin == 2) { /* DTP20 end patch */ + cpf = 2; /* Create stop bit */ + cp = mind; /* Starts with mind */ + } + + } else if (flags & IS_PAD) { /* Fake blank patch */ + sp = NULL; /* Not a test patch (no label) */ + cp = media; + + } else { /* set test sample patch location and color */ + int apir = pir - nmaxp; /* Adjusted pir for max/min patches */ + if (sp != NULL) + free(sp); + if ((sp = patch_location(saix, paix, ixord, slix, apir-1)) == NULL) + error ("Patch location out of range, strip %d patch %d",slix,apir-1); + if (ix < 0) { + error("Internal, got -ve patch index for generating patch %d",i); + } + strcpy(cols[ix].loc, sp); /* Record location */ + cp = &cols[ix]; /* Get color for this patch */ + + i++; /* Consumed a test patch */ + if (i > npat) + error("Internal - ran out of test patches !"); + + ix = rix[i]; /* Next patch index */ + } + } + + /* Print a spacer in front of patch if requested */ + if (spacer > 0) { + setup_spacer(&sc, pp, cp, pcol, spacer, usede); + tro->setcolor(tro, cal, sc); + tro->rectangle(tro, x, y, w, pspa, NULL,1); + y -= pspa; + } + + /* Print patch */ + { + double wplen = plen; + + if (slix < 0) { + wplen = tidplen; /* TID can have a different length patch */ + } + + tro->setcolor(tro, cal, cp); /* Patch color set above */ + if (hex) { + int apir = pir - nmaxp; /* Adjusted pir for max/min patches */ + tro->hexagon(tro, x, y, w, wplen, apir-1, sp); + } else { + /* We hack in the twin patches for the DTP20 start and stop */ + /* Initial color is set above (as for regular patches) */ + if (cpf == 1) { /* DTP20 start bit */ + tro->rectangle(tro, x, y, w, 1.0, sp,1); /* 50/50/50 set above */ + tro->setcolor(tro, cal, mind); /* White */ + tro->rectangle(tro, x, y - 1.0, w, wplen - 1.0, sp,1); + } else if (cpf == 2) { /* DTP20 stop bit */ + tro->rectangle(tro, x, y, w, wplen - 3.0, sp,1); /* mind set above */ + tro->setcolor(tro, cal, &pcol[8]); /* 50/50/50 Grey for DTP20 */ + tro->rectangle(tro, x, y - wplen + 3.0, w, 3.0, sp,1); + } else { /* Normal patch */ + tro->rectangle(tro, x, y, w, wplen, sp,1); + } + } + y -= wplen; + } + if (sp != NULL) { /* Done with sp for the moment */ + free(sp); + sp = NULL; + } + + /* Advance the patch count */ + pir++; + pp = cp; /* Current color becomes last color */ + + /* If this is the last patch in the row, */ + /* print a possible last spacer. */ + if (flags & IS_LPIR) { /* End of row */ + cp = media; + if (spacer > 0) { + setup_spacer(&sc, pp, cp, pcol, spacer, usede); + tro->setcolor(tro, cal, sc); + tro->rectangle(tro, x, y, w, pspa, NULL,1); + y -= pspa; + } + } + + if (flags & IS_LPIR) { /* Last patch in row */ + pir = 1; + ris++; + + /* First step to the middle of the patch */ + if (flags & IS_FRIS) + x += pwid/2.0; + else + x += pwid/2.0 + pwex; + + /* Then step to the start of the next patch */ + if (flags & IS_LRIS) { + if (dorspace) + x += rrsp; /* row to row space, making gutter */ + else + x += pwid/2.0 + clwi; /* no gutter, but room for cut line */ + } else + x += pwid/2.0 + pwex; + + if ((flags & IS_FRIS) && (flags & IS_FSIP)) /* First row on page */ + x += sxwi; /* Allow for scan compatible fatter first row */ + + if (flags & IS_LRIS) { /* End of strip */ + /* Ignore TID rows */ + if ((flags & IS_FPIF) && (flags & IS_FSIP)) + *rpsp++ = ris-tidrows-1; /* Record how many rows in this strip */ + else + *rpsp++ = ris-1; /* Record how many rows in this strip */ + ris = 1; + sip++; + + /* Print end of strip crop line */ + tro->setcolor(tro, cal, mark); + if (docutmarks) /* Generate strip cut marks */ + tro->dline(tro,x-0.3/2.0,y1,x-0.3/2.0,y2,0.3); /* 0.3 wide dotted line */ + + /* Print end of strip identification if we've allowed space */ + if (dorspace) + tro->vstring(tro,x,y1,rrsp-pwid/2.0-pwex,y2-y1,label); + + if (flags & IS_LSIP) { /* End of page */ + sip = 1; + + x += hxew; /* Allow space for extra bits of hexagons */ + + /* Print per page label if we've allowed for it */ + if (dopglabel) { + //tro->vstring(tro,x2,y1,pglth,y2-y1,label); /* At end of page */ + tro->vstring(tro,x+pglth,y1,pglth,y2-y1,label); /* After last strip */ + } + + /* If we expect to scan this chart in, add some fiducial marks at the corners */ + if (scanc & 1) { + double lw = 0.5; /* Line width */ + double ll = 5.0; /* Line length */ + +// fprintf(of,"%% Fiducial marks\n"); + + tro->setcolor(tro, cal, mark); + + tro->rectangle(tro, x1, y2, ll, lw, NULL, 0); /* Top left */ + tro->rectangle(tro, x1, y2, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x1 + 0.5 * lw), mm2pnt(y2 - 0.5 * lw)); + else + et_fiducial(x1 + 0.5 * lw, y2 - 0.5 * lw); + + tro->rectangle(tro, x2 - ll, y2, ll, lw, NULL, 0); /* Top right */ + tro->rectangle(tro, x2 - lw, y2, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x2 - 0.5 * lw), mm2pnt(y2 - 0.5 * lw)); + else + et_fiducial(x2 - 0.5 * lw, y2 - 0.5 * lw); + + tro->rectangle(tro, x2 - ll, y1 + lw, ll, lw, NULL, 0); /* Bottom right */ + tro->rectangle(tro, x2 - lw, y1 + ll, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x2 - 0.5 * lw), mm2pnt(y1 + 0.5 * lw)); + else + et_fiducial(x2 - 0.5 * lw, y1 + 0.5 * lw); + + tro->rectangle(tro, x1, y1 + lw, ll, lw, NULL, 0); /* Bottom left */ + tro->rectangle(tro, x1, y1 + ll, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x1 + 0.5 * lw), mm2pnt(y1 + 0.5 * lw)); + else + et_fiducial(x1 + 0.5 * lw, y1 + 0.5 * lw); + } + + tro->endpage(tro); + if (oft != 0) { /* EPS or TIFF */ + tro->del(tro); + } + if (flags & IS_LPIF) { /* Last page in file */ + if (oft == 0) { /* PS */ + tro->del(tro); + } + } + + /* If we are anticipating that scanin may be used with the */ + /* chart, create the scanin recognition file for this page. */ + if (scanc & 1) { + char chtname[MAXNAMEL+20]; /* Name of .cht file */ + + if (npages > 1) + sprintf(chtname,"%s_%02d.cht",bname,pif); + else + sprintf(chtname,"%s.cht",bname); + et_write(chtname, cols, rix, l_si, i); + } + + l_si = i; /* New last start i */ + + if (flags & IS_LPIF) { /* Last page in file */ + break; /* Done */ + } + pif++; + } + } + } + } + if (slab != NULL) + free(slab); + free(rix); + + *rpsp++ = 0; /* End of rows per strip stuff */ + + et_clear(); /* Cleanup edge list structures */ +} + +/* A paper size structure */ +typedef struct { + char *name; /* User name (lower case) */ + double w,h; /* Width and height in mm */ + int def; /* Non-zero if default */ +} paper; + +static paper psizes[] = { + { "A4", 210.0, 297.0, 0 }, + { "A4R", 297.0, 210.0, 0 }, + { "A3", 297.0, 420.0, 1 }, + { "A2", 420.0, 594.0, 0 }, + { "Letter", 215.9, 279.4, 0 }, + { "LetterR", 279.4, 215.9, 0 }, + { "Legal", 215.9, 355.6, 0 }, + { "4x6", 101.6, 152.4, 0 }, + { "11x17", 279.4, 431.8, 0 }, + { NULL, 0.0, 0.0, 0 } +}; + +#define DEF_MARGINE 6.0 + +/* Case independent string compare */ +int +cistrcmp(char *s1, char *s2) { + for (;;s1++, s2++) { + if (tolower(*s1) != tolower(*s2)) + return 1; + if (*s1 == '\000') + return 0; + } +} + +#define DEF_SIXPAT "A-Z, A-Z" /* Default strip index pattern */ +#define DEF_PIXPAT "0-9,@-9,@-9;1-999" /* Default patch index pattern */ + +void usage(char *diag, ...) { + paper *pp; + fprintf(stderr,"Generate Target PostScrip file, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + if (diag != NULL) { + va_list args; + fprintf(stderr," Diagnostic: "); + va_start(args, diag); + vfprintf(stderr, diag, args); + va_end(args); + fprintf(stderr,"\n"); + } + fprintf(stderr,"usage: printtarg [-v] [-i instr] [-r] [-s] [-p size] basename\n"); + fprintf(stderr," -v Verbose mode\n"); + fprintf(stderr," -i 20 | 22 | 41 | 51 | SS | i1 | CM Select target instrument (default DTP41)\n"); + fprintf(stderr," 20 = DTP20, 22 = DTP22, 41 = DTP41, 51 = DTP51,\n"); + fprintf(stderr," SS = SpectroScan, i1 = i1Pro, CM = ColorMunki\n"); + fprintf(stderr," -h Use hexagon patches for SS, double density for CM\n"); + fprintf(stderr," -a scale Scale patch size and spacers by factor (e.g. 0.857 or 1.5 etc.)\n"); + fprintf(stderr," -A scale Scale spacers by additional factor (e.g. 0.857 or 1.5 etc.)\n"); + fprintf(stderr," -r Don't randomize patch location\n"); + fprintf(stderr," -s Create a scan image recognition (.cht) file\n"); + fprintf(stderr," -S Same as -s, but don't generate wide orientation strip.\n"); + fprintf(stderr," -c Force colored spacers\n"); + fprintf(stderr," -b Force B&W spacers\n"); + fprintf(stderr," -n Force no spacers\n"); + fprintf(stderr," -f Create PostScript DeviceN Color fallback\n"); + fprintf(stderr," -w g|r|s|n White colorspace encoding DeviceGray (def), DeviceRGB, Separation or DeviceN\n"); + fprintf(stderr," -k g|c|s|n Black colorspace encoding DeviceGray (def), DeviceCMYK, Separation or DeviceN\n"); + fprintf(stderr," -o k|r|n CMY colorspace encoding DefiveCMYK (def), inverted DeviceRGB or DeviceN\n"); + fprintf(stderr," -e Output EPS compatible file\n"); + fprintf(stderr," -t [res] Output 8 bit TIFF raster file, optional res DPI (default 100)\n"); + fprintf(stderr," -T [res] Output 16 bit TIFF raster file, optional res DPI (default 100)\n"); + fprintf(stderr," -C Don't use TIFF compression\n"); + fprintf(stderr," -N Use TIFF alpha N channels more than 4\n"); + fprintf(stderr," -D Dither 8 bit TIFF values down from 16 bit\n"); + fprintf(stderr," -Q nbits Quantize test values to fit in nbits\n"); + fprintf(stderr," -R rsnum Use given random start number\n"); + fprintf(stderr," -K file.cal Apply printer calibration to patch values and include in .ti2\n"); + fprintf(stderr," -I file.cal Include calibration in .ti2 (but don't apply it)\n"); + fprintf(stderr," -x pattern Use given strip indexing pattern (Default = \"%s\")\n",DEF_SIXPAT); + fprintf(stderr," -y pattern Use given patch indexing pattern (Default = \"%s\")\n",DEF_PIXPAT); + fprintf(stderr," -m margin Set a page margin in mm (default %3.1f mm)\n",DEF_MARGINE); + fprintf(stderr," -M margin Set a page margin in mm and include it in TIFF\n"); + fprintf(stderr," -P Don't limit strip length\n"); + fprintf(stderr," -L Suppress any left paper clip border\n"); + fprintf(stderr," -p size Select page size from:\n"); + for (pp = psizes; pp->name != NULL; pp++) + fprintf(stderr," %-8s [%.1f x %.1f mm]%s\n", pp->name, pp->w, pp->h, + pp->def ? " (default)" : ""); + fprintf(stderr," -p WWWxHHH Custom size, WWW mm wide by HHH mm high\n"); + fprintf(stderr," basname Base name for input(.ti1), output(.ti2) and output(.ps/.eps/.tif)\n"); + exit(1); + } + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int fa, nfa, mfa; /* argument we're looking at */ + int verb = 0; + int hflag = 0; /* Hexagon patches for SS, high density for CM */ + double pscale = 1.0; /* Patch size scale */ + double sscale = 1.0; /* Spacer size scale */ + int rand = 1; + int qbits = 0; /* Quantization bits */ + int oft = 0; /* Ouput File type, 0 = PS, 1 = EPS , 2 = TIFF */ + depth2d tiffdpth = bpc8_2d; /* TIFF pixel depth */ + double tiffres = 100.0; /* TIFF resolution in DPI */ + int ncha = 0; /* flag, use nchannel alpha */ + int tiffdith = 0; /* flag, use TIFF 8 bit dithering */ + int tiffcomp = 1; /* flag, use TIFF compression */ + int spacer = -1; /* -1 = default for instrument */ + /* 0 = forse no spacer, 1 = Force B&W spacers */ + /* 2 = Force colored spacer */ + int rstart = -1; /* Random sequence start value */ + char *sixpat = DEF_SIXPAT; /* Strip index pattern */ + char *pixpat = DEF_PIXPAT; /* Patch index pattern */ + alphix *saix, *paix; /* Strip and Patch index generators */ + int ixord = 0; /* Index order, 0 = strip then patch */ + int scanc = 0; /* Scan compatible bits, 1 = .cht, 2 = wide first row */ + int devnfb = 0; /* Add device N fallback colors */ + int altrep = 0; /* Device K/W/CMY color type 0..8 */ + int applycal = 0; /* NZ to apply calibration */ + static char inname[MAXNAMEL+20] = { 0 }; /* Input cgats file name */ + static char calname[MAXNAMEL+1] = { 0 }; /* Input printer calibration */ + static char psname[MAXNAMEL+1] = { 0 }; /* Output postscrip file base name */ + static char outname[MAXNAMEL+20] = { 0 }; /* Output cgats file name */ + cgats *icg; /* input cgats structure */ + cgats *ocg; /* output cgats structure */ + xcal *cal = NULL; /* printer calibration */ + instType itype = instDTP41; /* Default target instrument */ + int nmask = 0; /* Device colorant mask */ + int nchan = 0; /* Number of device chanels */ + int i; + int si, fi, wi; /* sample id index, field index, keyWord index */ + char label[400]; /* Space for chart label */ + double marg = DEF_MARGINE; /* Margin from paper edge in mm */ + int nosubmarg = 0; /* Don't subtract it from raster */ + int nolpcbord = 0; /* NZ to suppress left paper clip border */ + int nollimit = 0; /* NZ to release any strip length limits */ + paper *pap = NULL; /* Paper size pointer, NULL if custom */ + double cwidth, cheight; /* Custom paper width and height in mm */ + col *cols; /* test patch colors */ + int npat; /* Number of patches */ + int nppat; /* Number of patches including padding */ + col pcold[8]; /* pre-defined density extreme colors */ + int pcolvv = 0; /* pcolv valid if nz */ + col pcolv[9]; /* pre-defined device combination ecolors */ + col *pcol; /* Chosen color patches for device */ + double wp[3]; /* Approximate XYZ white point */ + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + int sip; /* Steps in Pass */ + unsigned char *pis; /* Passes in strip array */ + double plen, glen, tlen;/* Patch, gap and trailer length in mm */ + char *bp, buf[500]; /* general sprintf buffer */ + + error_program = "printtarg"; + check_if_not_interactive(); + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + if (argc <= 1) + usage("Not enough arguments"); + +#ifdef DEBUG + printf("target: DEBUG is #defined\n"); +#endif + + /* Find the default paper size */ + for (pap = psizes; pap->name != NULL; pap++) { + if (pap->def != 0) + break; + } + if (pap->name == NULL) + error ("Internal - can't find default paper size"); + + /* Process the arguments */ + mfa = 1; /* Minimum final arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1+mfa) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage("Requested usage"); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') + verb = 1; + + /* hflag patches */ + else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') + hflag = 1; + + /* Patch scaling */ + else if (argv[fa][1] == 'a') { + fa = nfa; + if (na == NULL) usage("Expected scale factor to -a"); + pscale = atof(na); + if (pscale < 0.1 || pscale > 4.0) + usage("Scale factor %f is outside expected range 0.1 - 4.0",pscale); + } + + /* Spacer scaling */ + else if (argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage("Expected scale factor to -a"); + sscale = atof(na); + if (sscale < 0.1 || sscale > 8.0) + usage("Scale factor %f is outside expected range 0.1 - 8.0",sscale); + } + + /* Scan compatible */ + else if (argv[fa][1] == 's') + scanc = 3; + + else if (argv[fa][1] == 'S') + scanc = 1; + + /* Force colored spacer */ + else if (argv[fa][1] == 'c') + spacer = 2; + + /* Force B&W spacer */ + else if (argv[fa][1] == 'b' || argv[fa][1] == 'B') + spacer = 1; + + /* No spacer */ + else if (argv[fa][1] == 'n') + spacer = 0; + + /* Randomisation off */ + else if (argv[fa][1] == 'r') + rand = 0; + + /* Specify random seed */ + else if (argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage("Expected argument to -R"); + rstart = atoi(na); + if (rstart < 0) + usage("Argument to -R must be positive"); + } + + /* Enable DeviceN color fallback */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') + devnfb = 1; + + /* Select the printer W color representation */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + fa = nfa; + if (na == NULL) usage("Expected argument to -w"); + switch(na[0]) { + case 'g': + case 'G': + altrep = 0; + break; + case 'r': + case 'R': + altrep = 4; + break; + case 's': + case 'S': + altrep = 5; + break; + case 'n': + case 'N': + altrep = 6; + break; + default: + usage("Unexpected argument to -w"); + } + } + + /* Select the printer K color representation */ + else if (argv[fa][1] == 'k') { + fa = nfa; + if (na == NULL) usage("Expected argument to -k"); + switch(na[0]) { + case 'g': + case 'G': + altrep = 0; + break; + case 'c': + case 'C': + altrep = 1; + break; + case 's': + case 'S': + altrep = 2; + break; + case 'n': + case 'N': + altrep = 3; + break; + default: + usage("Unexpected argument to -k"); + } + } + + /* Select the printer CMY color representation */ + else if (argv[fa][1] == 'o') { + fa = nfa; + if (na == NULL) usage("Expected argument to -o"); + switch(na[0]) { + case 'k': + case 'K': + altrep = 0; + break; + case 'r': + case 'R': + altrep = 7; + break; + case 'n': + case 'N': + altrep = 8; + break; + default: + usage("Unexpected argument to -o"); + } + } + + /* EPS */ + else if (argv[fa][1] == 'e' || argv[fa][1] == 'E') + oft = 1; + + /* TIFF */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + oft = 2; + if (argv[fa][1] == 'T') + tiffdpth = bpc16_2d; + else + tiffdpth = bpc8_2d; + + if (na != NULL) { /* Found an optional resolution */ + fa = nfa; + tiffres = atof(na); + if (tiffres <= 1.0 || tiffres > 1e6) + usage("TIFF resolution is out of range"); + } + } + /* use Nchannel alpha for TIFF */ + else if (argv[fa][1] == 'N') { + ncha = 1; + } + /* use 16->8 bit dithering for 8 bit TIFF */ + else if (argv[fa][1] == 'D') { + tiffdith = 1; + } + /* Don't use TIFF compression */ + else if (argv[fa][1] == 'C') { + tiffcomp = 0; + } + + /* Specify quantization bits */ + else if (argv[fa][1] == 'Q') { + fa = nfa; + if (na == NULL) usage("Expected argument to -Q"); + qbits = atoi(na); + if (qbits < 1 || qbits > 32) + usage("Argument to -Q must be between 1 and 32"); + } + + /* Specify strip index pattern */ + else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + fa = nfa; + if (na == NULL) usage("Expected argument to -x"); + sixpat = na; + } + + /* Specify patch index pattern */ + else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y') { + fa = nfa; + if (na == NULL) usage("Expected argument to -y"); + pixpat = na; + } + + /* Border margin */ + else if (argv[fa][1] == 'm' || argv[fa][1] == 'M') { + if (argv[fa][1] == 'M') + nosubmarg = 1; + fa = nfa; + if (na == NULL) usage("Expected border margine argument to -m"); + marg = atof(na); + if (marg < 0.0 || marg > 50.0) + usage("Border margin %f is outside expected range",marg); + } + + /* Don't limit the strip length */ + else if (argv[fa][1] == 'P') { + nollimit = 1; + } + + /* Suppress left paper clip border */ + else if (argv[fa][1] == 'L') { + nolpcbord = 1; + } + + /* Page size */ + else if (argv[fa][1] == 'p') { + fa = nfa; + if (na == NULL) usage("Expected an argument to -p"); + for (pap = psizes; pap->name != NULL; pap++) { + if (cistrcmp(na, pap->name) == 0) + break; + } + + if (pap->name == NULL) { /* See if it matches a custom size */ + if (sscanf(na,"%lfx%lf",&cwidth, &cheight) == 2) { + pap = NULL; /* Indicate custom */ + if (cwidth < 1.0 || cwidth > 4000.0 + || cheight < 1.0 || cheight > 4000.0) + usage("Argument to -p was of unexpected size"); /* Sanity check */ + } else { + usage("Failed to recognise argument to -p"); + } + } + } + /* Target Instrument type */ + else if (argv[fa][1] == 'i') { + fa = nfa; + if (na == NULL) usage("Expected an argument to -i"); + + if (strcmp("20", na) == 0) + itype = instDTP20; + else if (strcmp("22", na) == 0) + itype = instDTP22; + else if (strcmp("41", na) == 0) + itype = instDTP41; + else if (strcmp("51", na) == 0) + itype = instDTP51; + else if (strcmp("SS", na) == 0 || strcmp("ss", na) == 0) + itype = instSpectroScan; + else if (strcmp("i1", na) == 0 || strcmp("I1", na) == 0) + itype = instI1Pro; + else if (strcmp("cm", na) == 0 || strcmp("CM", na) == 0) + itype = instColorMunki; + else + usage("Argument to -i wasn't recognised"); + + /* Printer calibration */ + } else if (argv[fa][1] == 'K' || argv[fa][1] == 'I') { + if (argv[fa][1] == 'K') + applycal = 1; + else + applycal = 0; + fa = nfa; + if (na == NULL) usage("Expected an argument to -%c",argv[fa][1]); + strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000'; + } else + usage("Unknown flag"); + } + else + break; + } + + /* Get the file name argument */ + if (fa >= argc || argv[fa][0] == '-') usage("Expecting basename argument"); + strncpy(inname,argv[fa],MAXNAMEL); inname[MAXNAMEL] = '\000'; + strcpy(outname,inname); + strcpy(psname,inname); + strcat(inname,".ti1"); + strcat(outname,".ti2"); + + if (calname[0] != '\000') { + if ((cal = new_xcal()) == NULL) + error("new_xcal failed"); + if ((cal->read(cal, calname)) != 0) + error("%s",cal->err); + } + + /* Set default qantization for known output */ + if (qbits == 0 && oft == 2) { + if (tiffdpth == bpc16_2d || tiffdith != 0) + qbits = 16; + else if (tiffdpth == bpc8_2d) + qbits = 8; + } + + if (itype == instSpectroScan) { + if (scanc) { + if (verb) + printf("Can only select hexagonal patches if no scan recognition is needed - ignored!\n"); + hflag = 0; + } + } else if (itype == instColorMunki) { + /* OK */ + } else if (hflag) { + if (verb) + printf("Can only select h flag for SpectrScan or ColorMunki - ignored!\n"); + hflag = 0; + } + + if ((saix = new_alphix(sixpat)) == NULL) + error("Strip indexing pattern '%s' doesn't parse",sixpat); + + if ((paix = new_alphix(pixpat)) == NULL) + error("Patch in strip indexing pattern '%s' doesn't parse",pixpat); + + icg = new_cgats(); /* Create a CGATS structure */ + icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */ + + if (icg->read_name(icg, inname)) + error("CGATS file read error : %s",icg->err); + + if (icg->t[0].tt != tt_other || icg->t[0].oi != 0) + error ("Input file isn't a CTI1 format file"); + if (icg->ntables < 2 || icg->ntables > 3) + error ("Input file doesn't contain two or three tables"); + + if ((npat = icg->t[0].nsets) <= 0) + error ("No sets of data"); + + /* Allocate room for test patches and maximum padding patches */ + if ((cols = (col *)malloc(sizeof(col) * (npat + MAXPPROW))) == NULL) + error("Malloc failed!"); + + /* Setup output cgats file */ + ocg = new_cgats(); /* Create a CGATS structure */ + ocg->add_other(ocg, "CTI2"); /* our special type is Calibration Target Information 2 */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 2",NULL); + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll printtarg", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + + /* Note what instrument the chart is setup for */ + ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(itype) , NULL); + + /* Copy various parameters through */ + if ((wi = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0) + ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "ACCURATE_EXPECTED_VALUES")) >= 0) + ocg->add_kword(ocg, 0, "ACCURATE_EXPECTED_VALUES",icg->t[0].kdata[wi], NULL); + + /* Fields we want */ + ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t); + ocg->add_field(ocg, 0, "SAMPLE_LOC", cs_t); + + if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0) + error ("Input file '%s' doesn't contain field SAMPLE_ID in first table",inname); + if (icg->t[0].ftype[si] != nqcs_t) + error ("Field SAMPLE_ID is wrong type"); + + /* Read the approximate white point */ + if ((fi = icg->find_kword(icg, 0, "APPROX_WHITE_POINT")) < 0) + error ("Input file doesn't contain keyword APPROX_WHITE_POINT"); + if (sscanf(icg->t[0].kdata[fi], "%lf %lf %lf", &wp[0], &wp[1], &wp[2]) != 3) + error ("Couldn't parse the white point data correctly"); + wp[0] /= 100.0; wp[1] /= 100.0; wp[2] /= 100.0; + ocg->add_kword(ocg, 0, "APPROX_WHITE_POINT",icg->t[0].kdata[fi], NULL); + +//printf("~1 got approx white point of %f %f %f\n",wp[0],wp[1],wp[2]); + + /* Figure out the color space */ + if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0) + error ("Input file '%s' doesn't contain keyword COLOR_REPS",inname); + + if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) != 0) { + int i, j, ii; + int chix[ICX_MXINKS]; /* Device chanel indexes */ + int xyzix[3]; /* XYZ chanel indexes */ + char *ident; /* Full ident */ + char *bident; /* Base ident */ + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + double qscale = (1 << qbits) - 1.0; + + if (cal != NULL && nmask != cal->devmask) + error ("Calibration colorspace %s doesn't match .ti1 %s",icx_inkmask2char(cal->devmask, 1),icx_inkmask2char(nmask, 1)); + + if ((ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) + ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL); + + nchan = icx_noofinks(nmask); + ident = icx_inkmask2char(nmask, 1); + bident = icx_inkmask2char(nmask, 0); + + for (j = 0; j < nchan; j++) { + int imask; + char fname[100]; + + imask = icx_index2ink(nmask, j); + sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident, + icx_ink2char(imask)); + + if ((ii = icg->find_field(icg, 0, fname)) < 0) + error ("Input file '%s' doesn't contain field %s in first table",inname,fname); + if (icg->t[0].ftype[ii] != r_t) + error ("Field %s is wrong type",fname); + + ocg->add_field(ocg, 0, fname, r_t); + chix[j] = ii; + } + + for (j = 0; j < 3; j++) { + if ((ii = icg->find_field(icg, 0, xyzfname[j])) < 0) + error ("Input '%s' file doesn't contain field %s in first table",inname,xyzfname[j]); + if (icg->t[0].ftype[ii] != r_t) + error ("Field %s is wrong type",xyzfname[j]); + + ocg->add_field(ocg, 0, xyzfname[j], r_t); + xyzix[j] = ii; + } + + ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL); + + /* Read all the test patches in, and quantize them */ + for (i = 0; i < npat; i++) { + cols[i].i = i; + cols[i].t = T_N | T_XYZ; + if (devnfb) + cols[i].t |= T_NFB; + cols[i].nmask = nmask; + cols[i].altrep = altrep; + cols[i].n = nchan; + cols[i].id = ((char *)icg->t[0].fdata[i][si]); + sprintf(cols[i].loc, "???"); + for (j = 0; j < nchan; j++) { + double vr, vv = *((double *)icg->t[0].fdata[i][chix[j]]) / 100.0; + if (qbits > 0) { + vv *= qscale; + vr = floor(vv + 0.5); + if ((vr - vv) == 0.5 && (((int)vr) & 1) != 0) /* Round to even */ + vr -= 1.0; + vv = vr/qscale; + } + cols[i].dev[j] = vv; + } + for (j = 0; j < 3; j++) + cols[i].XYZ[j] = *((double *)icg->t[0].fdata[i][xyzix[j]]) / 100.0; + col_convert(&cols[i], wp); /* Ensure other representations */ + } + + free(ident); + free(bident); + } else + error ("Input file keyword COLOR_REPS has unknown value"); + + /* Load up the pre-defined density extreme spacer colors */ + { + int i, j, ii; + int nsp; + int chix[ICX_MXINKS]; /* Device chanel indexes */ + int xyzix[3]; /* XYZ chanel indexes */ + char *bident; + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + + nchan = icx_noofinks(nmask); + bident = icx_inkmask2char(nmask, 0); + + if ((nsp = icg->t[1].nsets) <= 0) + error ("No sets of data in second table"); + + for (j = 0; j < nchan; j++) { + int imask; + char fname[100]; + + imask = icx_index2ink(nmask, j); + sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident, + icx_ink2char(imask)); + + if ((ii = icg->find_field(icg, 1, fname)) < 0) + error ("Input file '%s' doesn't contain field %s in second table",inname,fname); + if (icg->t[1].ftype[ii] != r_t) + error ("Field %s is wrong type",fname); + chix[j] = ii; + } + + for (j = 0; j < 3; j++) { + if ((ii = icg->find_field(icg, 1, xyzfname[j])) < 0) + error ("Input file '%s' doesn't contain field %s in second table",inname,xyzfname[j]); + if (icg->t[1].ftype[ii] != r_t) + error ("Field %s is wrong type",xyzfname[j]); + xyzix[j] = ii; + } + + if (nsp != 8) + error ("Expect second set of data to have 8 sets, found %d",nsp); + + /* Read all the density spacer patches in */ + for (i = 0; i < nsp; i++) { + pcold[i].i = -1; + pcold[i].t = T_N | T_XYZ | T_PRESET; + if (devnfb) + pcold[i].t |= T_NFB; + pcold[i].nmask = nmask; + pcold[i].altrep = altrep; + pcold[i].n = nchan; + pcold[i].id = ""; + sprintf(cols[i].loc, "???"); + for (j = 0; j < nchan; j++) + pcold[i].dev[j] = *((double *)icg->t[1].fdata[i][chix[j]]) / 100.0; + for (j = 0; j < 3; j++) + pcold[i].XYZ[j] = *((double *)icg->t[1].fdata[i][xyzix[j]]) / 100.0; + col_convert(&pcold[i], wp); /* Ensure other representations */ + } + + free(bident); + } + + /* Load up the pre-defined device combination barcode colors */ + if (icg->ntables >= 3) { + int i, j, ii; + int nsp; + int chix[ICX_MXINKS]; /* Device chanel indexes */ + int xyzix[3]; /* XYZ chanel indexes */ + char *bident; /* Base ident */ + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + + nchan = icx_noofinks(nmask); + bident = icx_inkmask2char(nmask, 0); + + if ((nsp = icg->t[2].nsets) > 0) { + + for (j = 0; j < nchan; j++) { + int imask; + char fname[100]; + + imask = icx_index2ink(nmask, j); + sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident, + icx_ink2char(imask)); + + if ((ii = icg->find_field(icg, 2, fname)) < 0) + error ("Input file '%s' doesn't contain field %s in third table",inname,fname); + if (icg->t[2].ftype[ii] != r_t) + error ("Field %s is wrong type",fname); + chix[j] = ii; + } + + for (j = 0; j < 3; j++) { + if ((ii = icg->find_field(icg, 2, xyzfname[j])) < 0) + error ("Input file '%s' doesn't contain field %s in third table",inname,xyzfname[j]); + if (icg->t[2].ftype[ii] != r_t) + error ("Field %s is wrong type",xyzfname[j]); + xyzix[j] = ii; + } + + if (nsp != 9) + error ("Expect third set of data to have 9 sets, found %d",nsp); + + /* Read all the barcode CMY color patches in */ + for (i = 0; i < nsp; i++) { + pcolv[i].i = -1; + pcolv[i].t = T_N | T_XYZ | T_PRESET; + if (devnfb) + pcolv[i].t |= T_NFB; + pcolv[i].nmask = nmask; + pcolv[i].altrep = altrep; + pcolv[i].n = nchan; + pcolv[i].id = ""; + sprintf(cols[i].loc, "???"); + for (j = 0; j < nchan; j++) + pcolv[i].dev[j] = *((double *)icg->t[2].fdata[i][chix[j]]) / 100.0; + for (j = 0; j < 3; j++) + pcolv[i].XYZ[j] = *((double *)icg->t[2].fdata[i][xyzix[j]]) / 100.0; + col_convert(&pcolv[i], wp); /* Ensure other representations */ + } + + free(bident); + pcolvv = 1; + } + } + + if (verb) { + if (pap != NULL) + printf("Paper chosen is %s [%.1f x %.1f mm]\n", pap->name, pap->w, pap->h); + else + printf("Paper chosen is custom %.1f x %.1f mm\n", cwidth, cheight); + } + + if (rstart == -1) { + rstart = clk % npat; + } else { + rstart = rstart % npat; + } + sprintf(buf,"%d",rstart); + if (rand) + ocg->add_kword(ocg, 0, "RANDOM_START", buf, NULL); + else + ocg->add_kword(ocg, 0, "CHART_ID", buf, NULL); + + if (itype == instSpectroScan && hflag) { + ocg->add_kword(ocg, 0, "HEXAGON_PATCHES", "True", NULL); + } + + if (itype == instDTP20) { + if (pcolvv == 0) + error("Input file '%s' doesn't contain device combination table needed for DTP20",inname); + pcol = pcolv; /* Barcode color values */ + } else + pcol = pcold; /* Density spacer alues */ + + + sprintf(label, "Argyll Color Management System - Test chart \"%s\" (%s %d) %s", + psname, rand ? "Random Start" : "Chart ID", rstart, atm); + generate_file(itype, psname, cols, npat, applycal ? cal : NULL, label, + pap != NULL ? pap->w : cwidth, pap != NULL ? pap->h : cheight, + marg, nosubmarg, nollimit, nolpcbord, rand, rstart, saix, paix, ixord, + pscale, sscale, hflag, verb, scanc, oft, tiffdpth, tiffres, ncha, tiffdith, + tiffcomp, spacer, nmask, altrep, pcol, wp, + &sip, &pis, &plen, &glen, &tlen, &nppat); + + if (itype == instDTP20 + || itype == instDTP41) { /* DTP20/41 needs this */ + sprintf(buf,"%f",plen); + ocg->add_kword(ocg, 0, "PATCH_LENGTH", buf, NULL); + sprintf(buf,"%f",glen); + ocg->add_kword(ocg, 0, "GAP_LENGTH", buf, NULL); + if (itype == instDTP41) { /* DTP41 needs this */ + sprintf(buf,"%f",tlen); + ocg->add_kword(ocg, 0, "TRAILER_LENGTH", buf, NULL); + } + } + + sprintf(buf,"%d",sip); + ocg->add_kword(ocg, 0, "STEPS_IN_PASS", buf, NULL); + + /* Convert pass in strips count to base 62 */ + buf[0] = '\000'; + bp = buf; + for (i = 0; ;i++) { + if (pis[i] == 0) + break; + sprintf(bp, "%s%d", i > 0 ? "," : "", pis[i]); + bp += strlen(bp); + } + ocg->add_kword(ocg, 0, "PASSES_IN_STRIPS2", buf, NULL); + + /* Output the default Argyll style strip and patch numbering */ + ocg->add_kword(ocg, 0, "STRIP_INDEX_PATTERN", sixpat, NULL); + ocg->add_kword(ocg, 0, "PATCH_INDEX_PATTERN", pixpat, NULL); + ocg->add_kword(ocg, 0, "INDEX_ORDER", ixord ? "PATCH_THEN_STRIP" : "STRIP_THEN_PATCH", NULL); + + /* Write out the patch info to the output CGATS file */ + for (i = 0; i < nppat; i++) { + cgats_set_elem ary[2 + ICX_MXINKS + 3]; + int j; + + if (strcmp(cols[i].loc, "???") == 0) + warning ("Internal, patch %s (%d) wasn't given a valid location string",cols[i].id,i+1); + ary[0].c = cols[i].id; + ary[1].c = cols[i].loc; + for (j = 0; j < nchan; j++) + ary[2 + j].d = 100.0 * cols[i].dev[j]; + for (j = 0; j < 3; j++) + ary[2 + nchan + j].d = 100.0 * cols[i].XYZ[j]; + ocg->add_setarr(ocg, 0, ary); + } + + /* If there is a calibration, append it to the .ti2 file */ + if (cal != NULL) { + if (cal->write_cgats(cal, ocg) != 0) + error("Writing cal error : %s",cal->err); + } + + if (ocg->write_name(ocg, outname)) + error("Write error : %s",ocg->err); + + if (cal != NULL) + cal->del(cal); + paix->del(paix); + saix->del(saix); + free(pis); + free(cols); + ocg->del(ocg); /* Clean up */ + icg->del(icg); /* Clean up */ + + return 0; +} + +/******************************************************************/ +/* Edge tracking support, for generating the scanner image */ +/* recognition reference chart file. */ + +/* Establish width and height to convert between topleft and */ +/* bottom left origin ??? ~~9 */ + +/* + Basic algorithm strategy: + + First we simply accumulate the raw recognition and patch + identification information. Once the chart is generated, we: + sort into horizontal and vertical half edges + sort into +ve and -ve edges + match +ve and -ve edges + for each match, generate a delta edge segment + Assume any non-matched edges are against the media. + Coalesce delta edges into X & Y edge lists + Compute normalised strength. + Compute crossing count. + Figure average box size, and compute shrink. + +*/ + +/* A half edge structure */ +/* coordinate origin is top left */ +struct _hedge { + double rgb[3]; /* Color this half edge transitions to */ + int negh; /* 1 if this is a -ve major coordinate side half edge, 0 otherwise */ + double mj; /* Major coordinate offset (ie. X coord for vertical edge) */ + double mi0; /* Minor coordinate smaller value (ie. Y for vertical edge) */ + double mi1; /* Minor coordinate larger value (ie. Y for vertical edge) */ + struct _hedge *next; /* Next in linked list */ +}; typedef struct _hedge hedge; + +/* A patch identifier */ +/* coordinate origin is top left */ +struct _patch { + char id[20]; /* ID string, Zeri length if a diagnostic rectangle */ + double xo; /* Location of the rectangle origin (bottom left ???) */ + double yo; + double w; /* Size of the patch */ + double h; + struct _patch *next; /* Next in linked list */ +}; typedef struct _patch patch; + + +/* Structure to one edge */ +struct _edge { + double p1,p2; /* Start and end of line in orthogonal direction */ + struct _edge *next; /* next in the linked list */ +}; typedef struct _edge edge; + +/* Structure of an edge list */ +struct _elist { + double pos; /* Position of edges along major axis */ + double len; /* Total length of edges atthis position */ + double cc; /* Crossing count */ + int ne; /* Count of edges */ + edge *e; /* Head of linked list of edges at this position */ + struct _elist *next; /* Next in linked list */ +}; typedef struct _elist elist; + +/* - - - - - - - - - - - - - - - - - - - */ +/* Structure to track recognition edges */ +struct { + double height; /* Height of the page */ + double mrgb[3]; /* Media RGB */ + double rgb[3]; /* Currently set RGB */ + + int nfid; /* Number of fiducial marks. Must be 4 to cause output */ + double fx[4]; /* Fiducial mark X coordinates */ + double fy[4]; /* Fiducial mark Y coordinates */ + + /* Raw half edge lists, [vertical, horizontal] */ + int nhe[2]; + hedge *he[2]; + + /* Patch identity information */ + int npatches; + patch *patches; + + /* Processed information */ + hedge **she[2]; /* Sorted half edges */ + + int nel[2]; /* Number of edge positions */ + elist *el[2]; /* Head of edge linked list */ + elist **nelp; /* Next edge to append to */ +} et; + +/* Initialise the structure */ +void et_init(void) { + memset(&et, 0, sizeof(et)); +} + +/* Tell et of the height, so the Y coordinate can be flipped */ +void et_height(double height) { + et.height = height; +//printf("~1 media height set to %f\n",height); +} + +/* Tell et of the media color */ +void et_media(double *rgb) { + int e; + for (e = 0; e < 3; e++) + et.mrgb[e] = rgb[e]; +} + +/* Track the current GC color */ +void et_color( +double *rgb /* New RGB values */ +) { + int e; + for (e = 0; e < 3; e++) + et.rgb[e] = rgb[e]; +} + +/* Track a drawn object half edge */ +/* We assume that no object is written over any other object, */ +/* and that each half edge has a perfect opposite edge (ie. same */ +/* mi0 and mi1), or no matching half edge (it is over the media) - */ +/* ie. no partialy overlapping half edges. */ +/* The arguments origin is assumed to be bottom left */ +void et_edge( +int isx, /* NZ if this is a vertical edge */ +int negh, /* NZ if this is a -ve major coordinate side half edge */ +double mj, /* Major coordinate offset (ie. X coord for vertical edge) */ +double mi0, /* Minor coordinate smaller value (ie. Y for vertical edge) */ +double mi1 /* Minor coordinate larger value (ie. Y for vertical edge) */ +) { + int e, h; + hedge *he; + + if (mi1 < mi0) + error ("et_edge, minor coords wern't ordered"); + + if ((he = (hedge *)calloc(sizeof(hedge), 1)) == NULL) + error("Malloc of half edge structure failed"); + + for (e = 0; e < 3; e++) + he->rgb[e] = et.rgb[e]; + + /* Flip the Y coordinate */ + if (isx) { + double tmi0, tmi1; + tmi0 = et.height - mi1; /* swap to keep smallest small */ + tmi1 = et.height - mi0; + mi0 = tmi0; + mi1 = tmi1; + } else { + mj = et.height - mj; + } + + he->negh = negh ? 1 : 0; + he->mj = mj; + he->mi0 = mi0; + he->mi1 = mi1; + + /* Add half edges to the list */ + h = isx ? 0 : 1; + et.nhe[h]++; + he->next = et.he[h]; + et.he[h] = he; +} + +/* Track a patch identity */ +/* The arguments origin is assumed to be bottom left */ +void et_patch( +char *id, /* ID string, NULL if a diagnostic mark */ +double xo, /* Bottom left of the rectangle */ +double yo, +double w, /* Size of the patch */ +double h +) { + patch *p; + +//printf("~1 got patch at %f %f, w %f, h %f\n", xo, yo, w, h); + + if ((p = (patch *)calloc(sizeof(patch), 1)) == NULL) + error("Malloc of patch structure failed"); + + /* Flip Y */ + yo = et.height - (yo + h); + + if (id != NULL) { + strncpy(p->id, id, 19); + p->id[19] = '\000'; + } else { + p->id[0] = '\000'; + } + p->xo = xo; + p->yo = yo; + p->h = h; + p->w = w; + + /* Add patch to list */ + et.npatches++; + p->next = et.patches; + et.patches = p; +} + +/* Add a fiducial mark location */ +/* It is an error to add more than 4, */ +/* and exactly 4 have to be added to cause fiducials to be output. */ +void et_fiducial( +double x, /* Bottom left of the rectangle */ +double y +) { + if (et.nfid >= 4) + error("et_fiducial: too many fiducial marks"); + et.fx[et.nfid] = x; + et.fy[et.nfid] = et.height - y; /* Flip Y */ + et.nfid++; +} + +/* Compute the image recognition information, and write the */ +/* .cht file. */ +void et_write(char *fname, col *cols, int *rix, int si, int ei) { + FILE *of; + hedge *ep; + int i, h; + +//printf("~1 et has %d vertical and %d horizontal half edges\n", et.nhe[0], et.nhe[1]); +//printf("~1 et has %d patches\n", et.npatches); + + for (h = 0; h < 2; h++) { + /* Create sorted list of vertical half edges */ + if ((et.she[h] = (hedge **)malloc(sizeof(patch*) * et.nhe[h])) == NULL) + error("Malloc of array of vertical halfedge pointers failed"); + + for (ep = et.he[h], i = 0; i < et.nhe[h]; i++, ep = ep->next) + et.she[h][i] = ep; + + /* Sort helf edges by their X location, then their Y0 location */ +#define HEAP_COMPARE(A,B) (fabs(A->mj - B->mj) < 1e-6 ? A->mi0 < B->mi0 : A->mj < B->mj) + HEAPSORT(hedge *, et.she[h], et.nhe[h]); +#undef HEAP_COMPARE + +#ifdef NEVER +for (i = 0; i < et.nhe[h]; i++) { +printf("%s %d at %c = %f from %c = %f to %f\n", +h == 0 ? "Vert" : "Horiz", i, +h == 0 ? 'X' : 'Y', et.she[h][i]->mj, +h == 0 ? 'Y' : 'X', et.she[h][i]->mi0, et.she[h][i]->mi1); +} +#endif /* NEVER */ + + et.nel[h] = 0; + et.nelp = &et.el[h]; /* Append next edge list here */ + *et.nelp = NULL; /* No edge list at this position yet */ + + /* Create the edge list information */ + for (i = 0; i < et.nhe[h];) { + int j, ii, nj; + double *rgb = NULL; /* Contrast RGB */ + elist *el; /* Current elist */ + + el = *et.nelp; + + /* Locate the end of the half edges at the same position */ + for (ii = i; ii < et.nhe[h]; ii++) { + if (fabs(et.she[h][i]->mj - et.she[h][ii]->mj) > 1e-6) + break; + } + +//printf("~1 doing group from %d to %d\n",i, ii); + /* Find half edge pairs */ + /* Note that we assume that the half edges match perfectly, */ + /* or not at all. This will be normaly be the case with targets */ + /* generated by printtarg. */ + for (j = i; j < ii; j = nj, j++) { + int e, k = j+1; + double vv; + + if (k < ii + && et.she[h][j]->negh != et.she[h][k]->negh + && fabs(et.she[h][j]->mi0 - et.she[h][k]->mi0) < 1e-5 + && fabs(et.she[h][j]->mi1 - et.she[h][k]->mi1) < 1e-5) { + /* Found a matching half edge */ + + nj = k; + rgb = et.she[h][k]->rgb; + + } else if (k < ii /* Assert */ + && ( (et.she[h][j]->mi0+1e-6) < et.she[h][k]->mi1 + && et.she[h][j]->mi1 > (et.she[h][k]->mi0+1e-6))) { + + /* Found an overlapping non-matching edge */ + nj = k; + +#ifdef NEVER +fprintf(stderr,"i = %d, j = %d\n",i,j); +fprintf(stderr,"%s %d at %c = %f from %c = %f to %f, half %s\n", +h == 0 ? "Vert" : "Horiz", i, +h == 0 ? 'X' : 'Y', et.she[h][j]->mj, +h == 0 ? 'Y' : 'X', et.she[h][j]->mi0, et.she[h][j]->mi1, +et.she[h][j]->negh ? "Neg" : "Pos"); +fprintf(stderr,"%s %d at %c = %f from %c = %f to %f, half %s\n", +h == 0 ? "Vert" : "Horiz", i, +h == 0 ? 'X' : 'Y', et.she[h][k]->mj, +h == 0 ? 'Y' : 'X', et.she[h][k]->mi0, et.she[h][k]->mi1, +et.she[h][k]->negh ? "Neg" : "Pos"); +#endif /* NEVER */ + error("Internal - half edges don't match"); + + } else { + /* Must be a non-matching edge */ + nj = j; + rgb = et.mrgb; /* Edge must be against media */ + } + + /* Compute vector delta in rgb */ + /* Add entry to edge list */ + for (e = 0, vv = 0.0; e < 3; e++) { + double tt = rgb[e] - et.she[h][j]->rgb[e]; + vv += tt * tt; + } + +//printf("~1 h %d, mj %f, mi %f, vv = %f\n",h, et.she[h][j]->mj, et.she[h][j]->mi0, vv); + /* If edge is of sufficient magnitude */ + // ~~99 + if (vv > 0.2) { + edge *ep; + + /* See if we need to add a first elist for this position */ + if (el == NULL) { /* We do */ + if ((el = (elist *)calloc(sizeof(elist), 1)) == NULL) + error("Malloc of elist structure failed"); + *et.nelp = el; + el->pos = et.she[h][j]->mj; + et.nel[h]++; + } + + /* Add another edge entry */ + if ((ep = (edge *)calloc(sizeof(edge), 1)) == NULL) + error("Malloc of edge structure failed"); + + ep->next = el->e; + ep->p1 = et.she[h][j]->mi0; + ep->p2 = et.she[h][j]->mi1; + + el->e = ep; /* Add to edge list */ + el->ne++; + } + } + + if (el != NULL) { + /* We've done that position, so get ready for next */ + et.nelp = &el->next; /* Append any more positions here */ + *et.nelp = NULL; + } + i = ii; /* Start search for next group here */ + } + } + + /* Figure the crossing count */ + for (h = 0; h < 2; h++) { + int oh = 1 - h; /* The other list */ + elist *el, *pl; /* Current, previous elist */ + + for (pl = NULL, el = et.el[h]; el != NULL; pl = el, el = el->next) { + edge *ep; + double pp, np; /* Window in pos direction for crossing */ + + if (pl != NULL) + pp = (pl->pos + el->pos)/2.0; /* Half distance to next line */ + else + pp = -1e6; + + if (el->next != NULL) + np = (el->next->pos + el->pos)/2.0; /* Half distance to next line */ + else + np = 1e6; + + /* For each edge on this edge position */ + for (ep = el->e; ep != NULL; ep = ep->next) { + elist *oel; /* Other edge list pointer */ + + /* For each edge in other list */ + for (oel = et.el[oh]; oel != NULL; oel = oel->next) { + edge *oep; + + if (oel->pos < ep->p1 || oel->pos > ep->p2) + continue; /* Other edge doesn't intersect this segment */ + + for (oep = oel->e; oep != NULL; oep = oep->next) { + + /* If crosses on this line within +-0.5 of line each side */ + if (oep->p1 <= np && oep->p2 >= pp) { + el->cc++; /* Count crossing */ + } + + } + } + } + } + } + + /* Compute and normalise the length (strength) & crossing count of each edge */ + for (h = 0; h < 2; h++) { + elist *el; /* Current elist */ + double maxlen = 0.0; + double maxcc = 0.0; + + for (el = et.el[h]; el != NULL; el = el->next) { + edge *ep; + double tlen; + + for (tlen = 0.0, ep = el->e; ep != NULL; ep = ep->next) { + tlen += ep->p2 - ep->p1; + } + el->len = tlen; + if (maxlen < tlen) + maxlen = tlen; + if (maxcc < el->cc) + maxcc = el->cc; + } + + /* Normalise */ + for (el = et.el[h]; el != NULL; el = el->next) { + el->len /= maxlen; + el->cc /= maxcc; + } + } + + /* Output the .cht file */ + if ((of = fopen(fname,"w")) == NULL) + error ("Unable to open output file '%s' for writing",fname); + + fprintf(of,"\n\n"); + fprintf(of, "BOXES %d\n",et.npatches); + + + /* Locate fiducials if they've been added to chart */ + if (et.nfid == 4) { + fprintf(of, " F _ _ %f %f %f %f %f %f %f %f\n", + et.fx[0], et.fy[0], et.fx[1], et.fy[1], et.fx[2], et.fy[2], et.fx[3], et.fy[3]); + } + + { + int fidc = 0; + patch *pp; + double mins; /* Minimum sample box size */ + + for (pp = et.patches; pp != NULL; pp = pp->next) { + + if (pp->id[0] == '\000') { + fprintf(of, " D MARK%d MARK%d _ _ %f %f %f %f 0 0\n", + fidc, fidc, pp->w, pp->h, pp->xo, pp->yo); + fidc++; + } + } + + mins = 1e6; + for (pp = et.patches; pp != NULL; pp = pp->next) { + + if (pp->id[0] != '\000') { + fprintf(of, " X %s %s _ _ %f %f %f %f 0 0\n", + pp->id, pp->id, pp->w, pp->h, pp->xo, pp->yo); + if (mins > pp->w) + mins = pp->w; + if (mins > pp->h) + mins = pp->h; + } + } + fprintf(of,"\n"); + + /* Use a 15% box shrink */ + fprintf(of, "BOX_SHRINK %f\n", mins * 0.15); + fprintf(of,"\n"); + + } + + fprintf(of,"REF_ROTATION %f\n", 0.0); + fprintf(of,"\n"); + + { + elist *el; /* Current elist */ + + fprintf(of,"XLIST %d\n",et.nel[0]); + for (el = et.el[0]; el != NULL; el = el->next) + fprintf(of," %f %f %f\n",el->pos, el->len, el->cc); + fprintf(of,"\n"); + + fprintf(of,"YLIST %d\n",et.nel[1]); + for (el = et.el[1]; el != NULL; el = el->next) + fprintf(of," %f %f %f\n",el->pos, el->len, el->cc); + fprintf(of,"\n"); + + fprintf(of,"\n"); + } + + + fprintf(of, "EXPECTED XYZ %d\n",ei - si); + + for (i = si; i < ei; i++) { + int ix = rix[i]; + fprintf(of, " %s %f %f %f\n", cols[ix].loc, 100.0 * cols[ix].XYZ[0], 100.0 * cols[ix].XYZ[1], 100.0 * cols[ix].XYZ[2]); + } + fprintf(of,"\n"); + + if (fclose(of)) + error ("Unable to close output file '%s'",fname); +} + + +/* Cleanup any allocation */ +void et_clear(void) { + int h; + patch *p; + + et.nfid = 0; + + for (h = 0; h < 2; h++) { + hedge *he; + elist *el; + + /* Free up half edges */ + he = et.he[h]; + while (he != NULL) { + hedge *ne = he->next; + free(he); + he = ne; + } + et.nhe[h] = 0; + et.he[h] = NULL; + + /* Free up sorted half edge lists */ + if (et.she[h] != NULL) + free(et.she[h]); + et.she[h] = NULL; + + /* Free up edge lists and edges */ + el = et.el[h]; + while (el != NULL) { + elist *nel; + edge *ep; + + ep = el->e; + while (ep != NULL) { + edge *nep; + + nep = ep->next; + free(ep); + ep = nep; + } + el->ne = 0; + el->e = NULL; + + nel = el->next; + free(el); + el = nel; + } + et.nel[h] = 0; + et.el[h] = NULL; + } + et.nelp = NULL; + + p = et.patches; + while (p != NULL) { + patch *np = p->next; + free(p); + p = np; + } + et.patches = NULL; + et.npatches = 0; +} + + + + + + + + -- cgit v1.2.3