Files
argyllcms/target/filmtarg.c
2026-08-20 20:28:40 +01:00

451 lines
13 KiB
C

/*
* Argyll Color Correction System
* Film recorder chart generator module.
*
* Author: Neil Okamoto
* Date: 1/3/2001
*
* Copyright 2001, DreamWorks LLC
* All rights reserved.
*
* This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 :-
* see the License2.txt file for licencing details.
*/
/* This program generates a set of TIFF images containing color test patches,
* given the .ti1 file specifying what the colors are.
*
* The output is designed to facilitate output on a 35mm motion picture film
* recorder such as those manufactured by Cineon, Celco, MGI, ARRI, etc. */
/* Description:
*
*/
/* TTBD:
*
* - eliminate gamma hack -- do it correctly in targen.
* - write a proper description
* - multi-patch mosaics on a single tiff image
* - render text labels into images
*/
#undef DEBUG
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <ctype.h>
#include "copyright.h"
#include "aconfig.h"
#include "cgats.h"
#include "randix.h"
#include "tiffio.h"
#include <stdarg.h>
void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...);
/* A color structure */
/* This can hold all representations simultaniously */
typedef struct {
int t; /* Tag */
#define T_W 0x1
#define T_RGB 0x2
#define T_CMYK 0x4
double gy;
double r,g,b;
double c,m,y,k;
char *id; /* Id string */
char loc[5]; /* Location ID string */
} col;
void
generate_tiffs(
char *basename, /* Base file name for output tiffs */
col *cols, /* Array of colors to be put on target chart */
int npat, /* Number of test targets needed */
char *label, /* Per set label */
int pw, int ph, /* Patch width and height */
int rand, /* Randomise flag */
int rstart, /* Starting index for random */
int verb) /* Verbose flag */
{
randix *r = NULL; /* Random index order object */
int ix; /* Patch index in target list */
int i; /* Logical patch in target list */
char slab[6]; /* Strip label */
char fname[200]; /* File name */
TIFF *tiff; /* TIFF file handle */
unsigned short* scanline; /* scanline buffer */
int x,y; /* position in TIFF image */
if (rand)
r = new_randix(npat, rstart);
i = 0;
while (i < npat) {
if (rand)
ix = r->next(r); /* Next pseudo random index */
else
ix = i;
sprintf(slab, "P%04d",i+1);
strcpy(cols[ix].loc, slab);
sprintf(fname, "%s.%04d.tiff", basename, i+1);
if ((tiff = TIFFOpen(fname, "w")) == NULL) {
fprintf(stderr,"Failed to open output %s\n", fname);
exit(-1);
}
if (!(scanline = (unsigned short*)malloc(3*sizeof(short)*pw))) {
fprintf(stderr,"Failed to malloc a scanline buffer\n");
exit(-1);
}
TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, pw);
TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, ph);
TIFFSetField(tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3);
TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16);
TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
if (verb) {
printf("writing patch %4d (%4.2f %4.2f %4.2f) to %s\n",
ix, cols[ix].r, cols[ix].g, cols[ix].b, fname);
}
/* fill scanline */
for (x=0; x<pw; x++) {
int xx = 3*x;
scanline[xx++] = (unsigned short)((cols[ix].r * 65535.0) + 0.5);
scanline[xx++] = (unsigned short)((cols[ix].g * 65535.0) + 0.5);
scanline[xx] = (unsigned short)((cols[ix].b * 65535.0) + 0.5);
}
for (y=0; y<ph; y++) {
if (TIFFWriteScanline(tiff, (tdata_t)scanline, y, 0) < 0) {
fprintf(stderr,"WriteScanline Failed at line %d\n",y);
exit (-1);
}
}
TIFFClose(tiff);
free(scanline);
i++;
}
if (rand)
r->del(r);
}
/* film size structure */
typedef struct {
char *name; /* User name (lower case) */
int w,h; /* Width and height in pixels */
} film;
static film filmsizes[] = {
{ "Acad_Full", 3656, 2664 },
{ "Acad_Half", 1828, 1332 },
{ "Cscope_Full", 3656, 3112 },
{ "Cscope_Half", 1828, 1556 },
{ "FullAp_Full", 4096, 3112 },
{ "FullAp_Half", 2048, 1556 },
{ "Vista_Full", 4096, 6144 },
{ "Vista_Half", 2048, 3072 },
{ NULL, 0, 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;
}
}
void
usage(void) {
film *ff;
fprintf(stderr,"Generate Film Target image sequence, Version %s\n",ARGYLL_VERSION_STR);
fprintf(stderr,"Licensed under the GPL\n");
fprintf(stderr,"usage: filmtarg [-v] [-r] [-g gamma] [-f format] basename\n");
fprintf(stderr," -v Verbose mode\n");
fprintf(stderr," -r Don't randomise patch location\n");
fprintf(stderr," -g gamma Gamma correction / linearization\n");
fprintf(stderr," -f format Select format from:\n");
for (ff = filmsizes; ff->name != NULL; ff++)
fprintf(stderr," \t%s\t[%d x %d]\n", ff->name, ff->w, ff->h);
fprintf(stderr," basename Base name for input(.ti1)/output(.ti2)\n");
exit(1);
}
int
main(argc,argv)
int argc;
char *argv[];
{
int fa,nfa; /* current argument we're looking at */
int verb = 0;
int rand = 1;
double gamma = 1.0;
static char inname[MAXNAMEL+10+1] = { 0 }; /* Input cgats file base name */
static char outname[MAXNAMEL+10+1] = { 0 }; /* Output cgats file base name */
static char tiffname[MAXNAMEL+1] = { 0 }; /* Output postscrip file base name */
cgats *icg; /* input cgats structure */
cgats *ocg; /* output cgats structure */
int i;
int si, fi, wi; /* sample id index, field index, keyWord index */
char *label = "Argyll Color Correction System - Development chart";
film *flm = &filmsizes[1]; /* Default film format = Academy Half */
col *cols;
int npat; /* Number of patches */
time_t clk = time(0);
struct tm *tsp = localtime(&clk);
char *atm = asctime(tsp); /* Ascii time */
char buf[100]; /* general sprintf buffer */
int rstart = 0; /* Random sequence start value */
if (argc <= 1)
usage();
/* Process the 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) < argc)
{
if (argv[fa+1][0] != '-')
{
nfa = fa + 1;
na = argv[nfa]; /* next is seperate non-flag argument */
}
}
}
if (argv[fa][1] == '?')
usage();
/* Verbosity */
else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
verb = 1;
/* Randomisation */
else if (argv[fa][1] == 'r' || argv[fa][1] == 'R')
rand = 0;
/* Gamma */
else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') {
fa = nfa;
if (na == NULL) usage();
gamma = atof(na);
}
/* Image format */
else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') {
fa = nfa;
if (na == NULL) usage();
for (flm = filmsizes; flm->name != NULL; flm++) {
if (cistrcmp(na, flm->name) == 0)
break;
}
if (flm->name == NULL)
usage();
} else
usage();
}
else
break;
}
/* Get the file name argument */
if (fa >= argc || argv[fa][0] == '-') usage();
strncpy(inname,argv[fa],MAXNAMEL); inname[MAXNAMEL] = '\000';
strcat(inname,".ti1");
strncpy(outname,argv[fa],MAXNAMEL); outname[MAXNAMEL] = '\000';
strcat(outname,".ti2");
strncpy(tiffname,argv[fa++],MAXNAMEL); tiffname[MAXNAMEL] = '\000';
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->e.m);
if (icg->t[0].tt != tt_other || icg->t[0].oi != 0)
error ("Input file isn't a CTI1 format file");
if (icg->ntables < 1)
error ("Input file doesn't contain at least one table");
if ((npat = icg->t[0].nsets) <= 0)
error ("No sets of data");
if ((cols = (col *)malloc(sizeof(col) * npat)) == 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 filmtarg", NULL);
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
/* Note what instrument this chart is setup for */
ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", "GretagMacbeth SpectroScanT", 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);
/* 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 doesn't contain field SAMPLE_ID");
if (icg->t[0].ftype[si] != nqcs_t)
error ("Field SAMPLE_ID is wrong type");
/* Figure out the color space */
if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
error ("Input file doesn't contain keyword COLOR_REPS");
if (strcmp(icg->t[0].kdata[fi],"CMYK") == 0) {
error ("We don't support CMYK yet");
} else if (strcmp(icg->t[0].kdata[fi],"RGB") == 0) {
int ri, gi, bi;
if ((ri = icg->find_field(icg, 0, "RGB_R")) < 0)
error ("Input file doesn't contain field RGB_R");
if (icg->t[0].ftype[ri] != r_t)
error ("Field RGB_R is wrong type");
if ((gi = icg->find_field(icg, 0, "RGB_G")) < 0)
error ("Input file doesn't contain field RGB_G");
if (icg->t[0].ftype[gi] != r_t)
error ("Field RGB_G is wrong type");
if ((bi = icg->find_field(icg, 0, "RGB_B")) < 0)
error ("Input file doesn't contain field RGB_B");
if (icg->t[0].ftype[bi] != r_t)
error ("Field RGB_B is wrong type");
ocg->add_field(ocg, 0, "RGB_R", r_t);
ocg->add_field(ocg, 0, "RGB_G", r_t);
ocg->add_field(ocg, 0, "RGB_B", r_t);
ocg->add_kword(ocg, 0, "COLOR_REP","RGB", NULL);
if (gamma != 1.0) {
sprintf(buf, "%6.4f", gamma);
ocg->add_kword(ocg, 0, "GAMMA", buf, NULL);
}
for (i = 0; i < npat; i++) {
cols[i].t = T_RGB;
cols[i].id = ((char *)icg->t[0].fdata[i][si]);
/* normalized and possibly gamma corrected */
cols[i].r = pow(*((double *)icg->t[0].fdata[i][ri]) / 100.0, gamma);
cols[i].g = pow(*((double *)icg->t[0].fdata[i][gi]) / 100.0, gamma);
cols[i].b = pow(*((double *)icg->t[0].fdata[i][bi]) / 100.0, gamma);
}
} else if (strcmp(icg->t[0].kdata[fi],"W") == 0) {
error ("We don't support GRAY yet");
} else
error ("Input file keyword COLOR_REPS has unknown value");
if (verb)
printf("Film format chosen is %s [%d x %d]\n", flm->name, flm->w, flm->h);
if (rand) {
rstart = clk % npat;
sprintf(buf,"%d",rstart);
ocg->add_kword(ocg, 0, "RANDOM_START", buf, NULL);
}
generate_tiffs(tiffname, cols, npat, label, flm->w, flm->h, rand, rstart, verb);
/* Write out the patch info to the output CGATS file */
for (i = 0; i < npat; i++) {
if (cols[i].t & T_CMYK)
ocg->add_set(ocg, 0, cols[i].id, cols[i].loc, 100.0 * cols[i].c, 100.0 * cols[i].m,
100.0 * cols[i].y, 100.0 * cols[i].k);
else if (cols[i].t & T_RGB)
ocg->add_set(ocg, 0, cols[i].id, cols[i].loc, 100.0 * cols[i].r,
100.0 * cols[i].g, 100.0 * cols[i].b);
else if (cols[i].t & T_W)
ocg->add_set(ocg, 0, cols[i].id, cols[i].loc, 100.0 * cols[i].gy);
}
if (ocg->write_name(ocg, outname))
error("Write error : %s",ocg->e.m);
free(cols);
ocg->del(ocg); /* Clean up */
icg->del(icg); /* Clean up */
return 0;
}
/******************************************************************/
/* Error/debug output routines */
/******************************************************************/
/* Basic printf type error() and warning() routines */
void
error(char *fmt, ...)
{
va_list args;
fprintf(stderr,"filmtarg: Error - ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
exit (-1);
}
void
warning(char *fmt, ...)
{
va_list args;
fprintf(stderr,"filmtarg: Warning - ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}