#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <unistd.h>
#include <sys/types.h>
#ifndef max
# define max(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef min
# define min(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef abs
# define abs(a) ((a) < 0 ? -(a) : (a))
#endif
float get_calibration(const char *prompt, float minval, float maxval);
char *safe_gets(const char *prompt, char *s, int size);
int safe_geti(const char *prompt, int defval);
void send_prolog(FILE *fp);
void send_pass1(FILE *fp);
void send_pass2(FILE *fp, float kd, float rd, float yellow);
void send_pass3(FILE *fp, float kd, float rd, float g, float yellow);
void send_pass4(FILE *fp, float red, float green, float blue, const char *p);
int
main(int argc,
char *argv[])
{
char printer[255];
char resolution[255];
char mediatype[255];
char *profile;
char cupsProfile[1024];
char command[1024];
char lpoptionscommand[1024];
char line[255];
char junk[255];
FILE *fp;
float kd, rd, g;
float color;
float red, green, blue;
float cyan, magenta, yellow;
float m[3][3];
puts("ESP Printer Calibration Tool v1.0");
puts("Copyright 1999-2000 by Easy Software Products, All Rights Reserved.");
puts("");
puts("This program allows you to calibrate the color output of printers");
puts("using the GIMP-Print CUPS or ESP Print Pro drivers.");
puts("");
puts("Please note that this program ONLY works with the GIMP-Print CUPS or");
puts("ESP Print Pro drivers. If you are using the GIMP-Print stp driver of");
puts("GhostScript or the drivers of the GIMP-Print plug-in of GIMP, this");
puts("calibration will not work.");
puts("");
puts("These drivers by the text \"CUPS+GIMP-print\" or \"ESP Print Pro\" in");
puts("the model description displayed by the CUPS web interface, KUPS,");
puts("the ESP Print Pro Printer Manager, or printerdrake.");
puts("");
puts("If you are not using the correct driver, press CTRL+C now and");
puts("reinstall your printer queue with the appropriate driver first.");
puts("");
puts("To make a calibration profile for all users, run this program as");
puts("the \"root\" user.");
puts("");
puts("");
safe_gets("Printer name [default]?", printer, sizeof(printer));
safe_gets("Resolution [default]?", resolution, sizeof(resolution));
safe_gets("Media type [default]?", mediatype, sizeof(mediatype));
strcpy(command, "lp -s");
if (printer[0])
{
strcat(command, " -d ");
strcat(command, printer);
}
if (resolution[0])
{
strcat(command, " -o resolution=");
strcat(command, resolution);
}
if (mediatype[0])
{
strcat(command, " -o media=");
strcat(command, mediatype);
}
strcat(command, " -o profile=");
profile = command + strlen(command);
strcpy(profile, "1000,1000,1000,0,0,0,1000,0,0,0,1000");
safe_gets("Press ENTER to print pass #1 or N to skip...", junk, sizeof(junk));
if (!junk[0])
{
puts("Sending calibration pass #1 for density/saturation levels...");
fp = popen(command, "w");
send_prolog(fp);
send_pass1(fp);
fputs("showpage\n", fp);
pclose(fp);
puts("Calibration pass #1 sent.");
}
puts("");
puts("Please select the character that corresponds to the black block that");
puts("is 100% saturated (dark) while not bleeding through the paper. If");
puts("the saturation point appears to occur between two characters, enter");
puts("both characters.");
puts("");
kd = get_calibration("Black density?", 0.25f, 1.0f);
puts("");
puts("Now select the character that corresponds to the yellow block that is");
puts("100% saturated (dark) while not bleeding through the paper. If the");
puts("saturation point appears to occur between two characters, enter both");
puts("characters.");
puts("");
cyan = kd;
magenta = kd;
yellow = get_calibration("Yellow density?", 0.25f, 1.0f);
puts("");
puts("Now select the character that corresponds to the red block that is");
puts("100% saturated (dark) while not bleeding through the paper. If the");
puts("saturation point appears to occur between two characters, enter both");
puts("characters.");
puts("");
rd = get_calibration("Red density?", 0.5f, 2.0f);
puts("");
puts("Thank you. Now insert the page back into the printer and press the");
puts("ENTER key to print calibration pass #2.");
puts("");
safe_gets("Press ENTER to print pass #2 or N to skip...", junk, sizeof(junk));
if (!junk[0])
{
puts("Sending calibration pass #2 for gamma levels...");
fp = popen(command, "w");
send_prolog(fp);
send_pass2(fp, kd, rd, yellow);
fputs("showpage\n", fp);
pclose(fp);
puts("Calibration pass #2 sent.");
}
puts("");
puts("Please select the character that corresponds to the column of gray");
puts("blocks that appear to be 1/2 and 1/4 as dark as the black blocks,");
puts("respectively. If the transition point appears to occur between two");
puts("characters, enter both characters.");
puts("");
g = get_calibration("Gamma?", 1.0f, 4.0f);
puts("");
puts("Thank you. Now insert the page back into the printer and press the");
puts("ENTER key to print calibration pass #3.");
puts("");
safe_gets("Press ENTER to print pass #3 or N to skip...", junk, sizeof(junk));
if (!junk[0])
{
puts("Sending calibration pass #3 for red, green, and blue adjustment...");
fp = popen(command, "w");
send_prolog(fp);
send_pass3(fp, kd, rd, g, yellow);
fputs("showpage\n", fp);
pclose(fp);
puts("Calibration pass #3 sent.");
}
puts("");
puts("Please select the character that corresponds to the correct red,");
puts("green, and blue colors. If the transition point appears to occur");
puts("between two characters, enter both characters.");
puts("");
red = get_calibration("Red color?", 0.35f, -0.40f);
green = get_calibration("Green color?", 0.35f, -0.40f);
blue = get_calibration("Blue color?", 0.35f, -0.40f);
color = 0.5f * rd / kd - kd;
cyan /= kd;
magenta /= kd;
yellow /= kd;
m[0][0] = cyan;
m[0][1] = cyan * (color + blue);
m[0][2] = cyan * (color - green);
m[1][0] = magenta * (color - blue);
m[1][1] = magenta;
m[1][2] = magenta * (color + red);
m[2][0] = yellow * (color + green);
m[2][1] = yellow * (color - red);
m[2][2] = yellow;
if (m[0][1] > 0.0f)
{
m[1][0] -= m[0][1];
m[0][1] = 0.0f;
}
else if (m[1][0] > 0.0f)
{
m[0][1] -= m[1][0];
m[1][0] = 0.0f;
}
if (m[0][2] > 0.0f)
{
m[2][0] -= m[0][2];
m[0][2] = 0.0f;
}
else if (m[2][0] > 0.0f)
{
m[0][2] -= m[2][0];
m[2][0] = 0.0f;
}
if (m[1][2] > 0.0f)
{
m[2][1] -= m[1][2];
m[1][2] = 0.0f;
}
else if (m[2][1] > 0.0f)
{
m[1][2] -= m[2][1];
m[2][1] = 0.0f;
}
sprintf(profile, "%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f",
kd * 1000.0f, g * 1000.0f,
m[0][0] * 1000.0f, m[0][1] * 1000.0f, m[0][2] * 1000.0f,
m[1][0] * 1000.0f, m[1][1] * 1000.0f, m[1][2] * 1000.0f,
m[2][0] * 1000.0f, m[2][1] * 1000.0f, m[2][2] * 1000.0f);
sprintf(cupsProfile, " *cupsColorProfile %s/%s: \"%.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f\"\n",
resolution[0] ? resolution : "-", mediatype[0] ? mediatype : "-",
kd, g, m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2]);
puts("");
puts("Thank you. Now insert the page back into the printer and press the");
puts("ENTER key to print the final calibration pass.");
puts("");
safe_gets("Press ENTER to continue...", junk, sizeof(junk));
puts("Sending calibration pass #4 for visual confirmation...");
fp = popen(command, "w");
send_prolog(fp);
send_pass4(fp, red, green, blue, cupsProfile);
fputs("showpage\n", fp);
pclose(fp);
puts("Calibration pass #4 sent.");
puts("");
puts("The basic color profile for these values is:");
puts("");
printf(" %s\n", cupsProfile);
puts("");
puts("You can add this to the PPD file for this printer to make this change");
puts("permanent, or use the following option with a printing command:");
puts("");
printf(" -o profile=%s\n", profile);
puts("");
puts("to use the profile for this job only.");
puts("");
puts("Calibration is complete.");
puts("");
if (getuid() == 0)
{
do
safe_gets("Would you like to save the profile as a system-wide default (y/n)? ",
line, sizeof(line));
while (tolower(line[0]) != 'n' && tolower(line[0]) != 'y');
}
else
line[0] = 'n';
if (line[0] == 'n')
{
do
safe_gets("Would you like to save the profile as a personal default (y/n)? ",
line, sizeof(line));
while (tolower(line[0]) != 'n' && tolower(line[0]) != 'y');
}
puts("");
if (tolower(line[0]) == 'n')
{
puts("Calibration profile NOT saved.");
return (0);
}
strcpy(lpoptionscommand, "lpoptions");
if (printer[0])
{
strcat(lpoptionscommand, " -p ");
strcat(lpoptionscommand, printer);
}
strcat(lpoptionscommand, " -o profile=");
strcat(lpoptionscommand, profile);
if (system(lpoptionscommand) == 0)
puts("Calibration profile successfully saved.");
else
puts("An error occured while saving the calibration profile.");
return (0);
}
float
get_calibration(const char *prompt,
float minval,
float maxval)
{
char line[4];
int val1, val2;
do
{
if (safe_gets(prompt, line, sizeof(line)) == NULL)
return (minval);
}
while (!isxdigit(line[0]) || (line[1] && !isxdigit(line[1])));
if (isdigit(line[0]))
val1 = line[0] - '0';
else
val1 = tolower(line[0]) - 'a' + 10;
if (!line[1])
val2 = val1;
else if (isdigit(line[1]))
val2 = line[1] - '0';
else
val2 = tolower(line[1]) - 'a' + 10;
return (minval + (maxval - minval) * (val1 + val2) / 30.0f);
}
int
safe_geti(const char *prompt,
int defval)
{
char line[255];
do
{
safe_gets(prompt, line, sizeof(line));
if (defval && !line[0])
return (defval);
}
while (!line[0] || !isdigit(line[0]));
return (atoi(line));
}
char *
safe_gets(const char *prompt,
char *s,
int size)
{
printf("%s ", prompt);
if (fgets(s, size, stdin) == NULL)
return (NULL);
if (s[0])
s[strlen(s) - 1] = '\0';
return (s);
}
void
send_prolog(FILE *fp)
{
fputs("%!\n", fp);
fputs("/Helvetica findfont 12 scalefont setfont\n", fp);
fputs("/BLOCK {\n"
" 14.4 mul neg 720 add exch\n"
" 14.4 mul 72 add exch\n"
" 14.4 14.4 rectfill\n"
"} bind def\n", fp);
fputs("/DIAMOND {\n"
" 14.4 mul neg 720 add 7.2 add exch\n"
" 14.4 mul 72 add exch\n"
" newpath\n"
" moveto\n"
" 7.2 7.2 rlineto\n"
" 7.2 -7.2 rlineto\n"
" -7.2 -7.2 rlineto\n"
" closepath\n"
" 0 ne { fill } { stroke } ifelse\n"
"} bind def\n", fp);
fputs("/PLUS {\n"
" 14.4 mul neg 720 add 7.2 add exch\n"
" 14.4 mul 72 add exch\n"
" newpath\n"
" moveto\n"
" 14.4 0 rlineto\n"
" -7.2 -7.2 rmoveto\n"
" 0 14.4 rlineto\n"
" closepath\n"
" fill\n"
"} bind def\n", fp);
fputs("/TEXT {\n"
" 14.4 mul neg 720 add 4 add exch\n"
" 14.4 mul 72 add 7.2 add exch\n"
" moveto\n"
" dup stringwidth pop 0.5 mul neg 0 rmoveto\n"
" show\n"
"} bind def\n", fp);
}
void
send_pass1(FILE *fp)
{
int i;
float p;
static const char *hex = "FEDCBA9876543210";
fputs("0 setgray", fp);
fputs("(K) 0 1 TEXT\n", fp);
fputs("(Y) 0 2 TEXT\n", fp);
fputs("(R) 0 4 TEXT\n", fp);
for (i = 0; i < 16; i ++)
{
fprintf(fp, "(%d) %d 0 TEXT\n", 100 - i * 5, i * 2 + 2);
fprintf(fp, "(%c) %d 3 TEXT\n", hex[i], i * 2 + 2);
fprintf(fp, "(%d) %d 5 TEXT\n", 200 - i * 10, i * 2 + 2);
}
for (i = 0; i < 16; i ++)
{
p = 0.01f * (100 - i * 5);
fprintf(fp, "%.2f setgray %d 1 BLOCK\n", 1.0 - p, i * 2 + 2);
fprintf(fp, "0 0 %.2f 0 setcmykcolor %d 2 BLOCK\n", p, i * 2 + 2);
fprintf(fp, "0 %.2f %.2f 0 setcmykcolor %d 4 BLOCK\n", p, p, i * 2 + 2);
}
}
void
send_pass2(FILE *fp,
float kd,
float rd,
float yellow)
{
int i;
float p;
float g;
static const char *hex = "FEDCBA9876543210";
rd *= 0.5f;
fprintf(fp, "0 0 %.2f 0 setcmykcolor\n", yellow);
fprintf(fp, "1 %.2f 6 DIAMOND\n", 2.0f + 30.0f * (1.0f - yellow) / 0.75f);
fprintf(fp, "0 %.2f %.2f 0 setcmykcolor\n", rd, rd);
fprintf(fp, "%d %.2f 6 DIAMOND\n", rd != yellow,
2.0f + 30.0f * (1.0f - rd) / 0.75f);
p = 1.0f - kd;
fprintf(fp, "%.2f setgray\n", p);
if (kd == rd && kd == yellow)
fprintf(fp, "%.2f 6 PLUS\n", 2.0f + 30.0f * (1.0f - kd) / 0.75f);
else
fprintf(fp, "%d %.2f 6 DIAMOND\n", kd != yellow && kd != rd,
2.0f + 30.0f * (1.0f - kd) / 0.75f);
fputs("(100%) 0 9 TEXT\n", fp);
fputs("(50%) 0 10 TEXT\n", fp);
fputs("(25%) 0 11 TEXT\n", fp);
for (i = 0; i < 16; i ++)
{
fprintf(fp, "(%.1f) %d 8 TEXT\n", 0.2f * (20 - i), i * 2 + 2);
fprintf(fp, "(%c) %d 12 TEXT\n", hex[i], i * 2 + 2);
}
for (i = 0; i < 16; i ++)
{
g = 0.2f * (20 - i);
p = 1.0f - kd * (float)pow(1.0f, g);
fprintf(fp, "%.2f setgray %d 9 BLOCK\n", p, i * 2 + 2);
p = 1.0f - kd * (float)pow(0.5f, g);
fprintf(fp, "%.2f setgray %d 10 BLOCK\n", p, i * 2 + 2);
p = 1.0f - kd * (float)pow(0.25f, g);
fprintf(fp, "%.2f setgray %d 11 BLOCK\n", p, i * 2 + 2);
}
}
void
send_pass3(FILE *fp,
float kd,
float rd,
float g,
float yellow)
{
int i;
float p;
float color;
float c, m, y;
float adj;
static const char *hex = "FEDCBA9876543210";
p = 1.0f - kd;
fprintf(fp, "%.2f setgray\n", p);
fprintf(fp, "1 %.2f 13 DIAMOND\n", 2.0f + 30.0f * (4.0f - g) / 3.0f);
color = 0.5f * rd / kd - kd;
yellow /= kd;
fputs("(R) 2 16 TEXT\n", fp);
fputs("(G) 2 17 TEXT\n", fp);
fputs("(B) 2 18 TEXT\n", fp);
for (i = 1; i < 16; i ++)
{
fprintf(fp, "(%+d) %d 15 TEXT\n", i * 5 - 40, i * 2 + 2);
fprintf(fp, "(%c) %d 19 TEXT\n", hex[i], i * 2 + 2);
}
for (i = 1; i < 16; i ++)
{
adj = i * 0.05f - 0.40f;
c = 0.0f;
m = color + adj;
y = color - adj;
if (m > 0)
{
y -= m;
m = 0;
}
else if (y > 0)
{
m -= y;
y = 0;
}
m += 1.0f;
y = yellow * (1.0f + y);
if (c <= 0.0f)
c = 0.0f;
else if (c >= 1.0f)
c = 1.0f;
else
c = (float)pow(c, g);
if (m <= 0.0f)
m = 0.0f;
else if (m >= 1.0f)
m = 1.0f;
else
m = (float)pow(m, g);
if (y <= 0.0f)
y = 0.0f;
else if (y >= 1.0f)
y = 1.0f;
else
y = (float)pow(y, g);
fprintf(fp, "%.2f %.2f %.2f 0 setcmykcolor %d 16 BLOCK\n",
c * kd, m * kd, y * kd, i * 2 + 2);
c = color - adj;
m = 0.0f;
y = color + adj;
if (c > 0)
{
y -= c;
c = 0;
}
else if (y > 0)
{
c -= y;
y = 0;
}
c += 1.0f;
y = yellow * (1.0f + y);
if (c <= 0.0f)
c = 0.0f;
else if (c >= 1.0f)
c = 1.0f;
else
c = (float)pow(c, g);
if (m <= 0.0f)
m = 0.0f;
else if (m >= 1.0f)
m = 1.0f;
else
m = (float)pow(m, g);
if (y <= 0.0f)
y = 0.0f;
else if (y >= 1.0f)
y = 1.0f;
else
y = (float)pow(y, g);
fprintf(fp, "%.2f %.2f %.2f 0 setcmykcolor %d 17 BLOCK\n",
c * kd, m * kd, y * kd, i * 2 + 2);
c = color + adj;
m = color - adj;
y = 0.0f;
if (c > 0)
{
m -= c;
c = 0;
}
else if (m > 0)
{
c -= m;
m = 0;
}
c += 1.0f;
m += 1.0f;
if (c <= 0.0f)
c = 0.0f;
else if (c >= 1.0f)
c = 1.0f;
else
c = (float)pow(c, g);
if (m <= 0.0f)
m = 0.0f;
else if (m >= 1.0f)
m = 1.0f;
else
m = (float)pow(m, g);
if (y <= 0.0f)
y = 0.0f;
else if (y >= 1.0f)
y = 1.0f;
else
y = (float)pow(y, g);
fprintf(fp, "%.2f %.2f %.2f 0 setcmykcolor %d 18 BLOCK\n",
c * kd, m * kd, y * kd, i * 2 + 2);
}
}
void
send_pass4(FILE *fp,
float red,
float green,
float blue,
const char *profile)
{
FILE *ppm;
int x, y, col, width, height;
int r, g, b;
char line[255];
fprintf(fp, "0 0 1 setrgbcolor 1 %.2f 20 DIAMOND\n",
2.0f + 30.0f * (0.40f + blue) / 0.75f);
fprintf(fp, "1 0 0 setrgbcolor %d %.2f 20 DIAMOND\n",
red != blue, 2.0f + 30.0f * (0.40f + red) / 0.75f);
if (green == red && green == blue)
fprintf(fp, "0 1 0 setrgbcolor %.2f 20 PLUS\n",
2.0f + 30.0f * (0.40f + green) / 0.75f);
else
fprintf(fp, "0 1 0 setrgbcolor %d %.2f 20 DIAMOND\n",
green != red && green != blue,
2.0f + 30.0f * (0.40f + green) / 0.75f);
fputs("0 setgray\n", fp);
fputs("currentfont 0.8 scalefont setfont\n", fp);
fprintf(fp, "(%s) 16 22 TEXT\n", profile);
if ((ppm = fopen(CUPS_DATADIR "/calibrate.ppm", "rb")) == NULL)
if ((ppm = fopen("calibrate.ppm", "rb")) == NULL)
return;
fgets(line, sizeof(line), ppm);
while (fgets(line, sizeof(line), ppm))
if (line[0] != '#')
break;
sscanf(line, "%d%d", &width, &height);
fgets(line, sizeof(line), ppm);
fputs("gsave\n", fp);
fprintf(fp, "72 %d translate\n", 504 * height / width + 72);
fprintf(fp, "504 -%d scale\n", 504 * height / width);
fprintf(fp, "/scanline %d string def\n", width * 3);
fprintf(fp, "%d %d 8\n", width, height);
fprintf(fp, "[%d 0 0 %d 0 0]\n", width, height);
fputs("{ currentfile scanline readhexstring pop } false 3 colorimage\n", fp);
for (y = 0, col = 0; y < height; y ++)
{
printf("Sending scanline %d of %d...\r", y + 1, height);
fflush(stdout);
for (x = 0; x < width; x ++)
{
r = getc(ppm);
g = getc(ppm);
b = getc(ppm);
fprintf(fp, "%02X%02X%02X", r, g, b);
col ++;
if (col >= 12)
{
col = 0;
putc('\n', fp);
}
}
}
if (col)
putc('\n', fp);
printf(" \r");
fclose(ppm);
fputs("grestore\n", fp);
}