diff options
Diffstat (limited to 'calibrator.c')
-rw-r--r-- | calibrator.c | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/calibrator.c b/calibrator.c new file mode 100644 index 0000000..9456f96 --- /dev/null +++ b/calibrator.c @@ -0,0 +1,655 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +/** + * Draw bars in different shades of grey, red, green and blue + * used for calibrating the contrast and brightness + */ +static void +draw_contrast_brightness(void) +{ + const int CONTRAST_BRIGHTNESS_LEVELS[21] = { + 0, 17, 27, 38, 48, 59, 70, 82, 94, 106, 119, 131, + 144, 158, 171, 185, 198, 212, 226, 241, 255 + }; + size_t f; + uint32_t y, x, colour; + framebuffer_t *restrict fb; + int v; + + for (f = 0; f < framebuffer_count; f++) { + fb = &framebuffers[f]; + for (y = 0; y < 4; y++) { + for (x = 0; x < 21; x++) { + v = CONTRAST_BRIGHTNESS_LEVELS[x]; + colour = fb_colour(v * ((y == 1) | (y == 0)), + v * ((y == 2) | (y == 0)), + v * ((y == 3) | (y == 0))); + fb_fill_rectangle(fb, colour, x * fb->width / 21, y * fb->height / 4, + (x + 1) * fb->width / 21 - x * fb->width / 21, + (y + 1) * fb->height / 4 - y * fb->height / 4); + } + } + } +} + + +/** + * Draw a seven segment display + * + * @param fb The framebuffer to draw on + * @param colour The intensity of the least intense colour to use + * @param x The X component of the top left corner of the seven segment display + * @param y The Y component of the top left corner of the seven segment display + */ +static void +draw_digit(framebuffer_t *restrict fb, int colour, uint32_t x, uint32_t y) +{ + uint32_t c; + + c = fb_colour(colour + 0, colour + 0, colour + 0); + fb_fill_rectangle(fb, c, x + 20, y, 80, 20); + + c = fb_colour(colour + 1, colour + 1, colour + 1); + fb_fill_rectangle(fb, c, x, y + 20, 20, 80); + + c = fb_colour(colour + 2, colour + 2, colour + 2); + fb_fill_rectangle(fb, c, x + 100, y + 20, 20, 80); + + c = fb_colour(colour + 3, colour + 3, colour + 3); + fb_fill_rectangle(fb, c, x + 20, y + 100, 80, 20); + + c = fb_colour(colour + 4, colour + 4, colour + 4); + fb_fill_rectangle(fb, c, x, y + 120, 20, 80); + + c = fb_colour(colour + 5, colour + 5, colour + 5); + fb_fill_rectangle(fb, c, x + 100, y + 120, 20, 80); + + c = fb_colour(colour + 6, colour + 6, colour + 6); + fb_fill_rectangle(fb, c, x + 20, y + 200, 80, 20); +} + + +/** + * Manipulate a CRT controllers gamma ramps to display a specific digit + * for one of the seven segment display on only that CRT controller's + * monitors + * + * @param crtc The CRT controller information + * @param colour The intensity of the least intense colour in the seven segment display + * @param value The valud of the digit to display + */ +static void +gamma_digit(drm_crtc_t *restrict crtc, int colour, size_t value) +{ +#define __ 0 + const int DIGITS[11] = { 1 | 2 | 4 | __ | 16 | 32 | 64, /* (0) */ + __ | __ | 4 | __ | __ | 32 | __, /* (1) */ + 1 | __ | 4 | 8 | 16 | __ | 64, /* (2) */ + 1 | __ | 4 | 8 | __ | 32 | 64, /* (3) */ + __ | 2 | 4 | 8 | __ | 32 | __, /* (4) */ + 1 | 2 | __ | 8 | __ | 32 | 64, /* (5) */ + 1 | 2 | __ | 8 | 16 | 32 | 64, /* (6) */ + 1 | __ | 4 | __ | __ | 32 | __, /* (7) */ + 1 | 2 | 4 | 8 | 16 | 32 | 64, /* (8) */ + 1 | 2 | 4 | 8 | __ | 32 | 64, /* (9) */ + __ | __ | __ | __ | __ | __ | __}; /* not visible */ +#undef __ + int i, j, digit = DIGITS[value]; + uint16_t c; + + for (i = 0; i < 7; i++) { + c = (digit & (1 << i)) ? 0xFFFF : 0; + j = i + colour; + crtc->red[j] = crtc->green[j] = crtc->blue[j] = c; + } +} + + +/** + * Draw an unique index on each monitor + * + * @return Zero on success, -1 on error + */ +static int +draw_id(void) +{ + size_t f, c, id = 0; + framebuffer_t *restrict fb; + drm_crtc_t *restrict crtc; + for (f = 0; f < framebuffer_count; f++) { + fb = &framebuffers[f]; + fb_fill_rectangle(fb, fb_colour(0, 0, 0), 0, 0, fb->width, fb->height); + draw_digit(fb, 1, 40, 40); + draw_digit(fb, 8, 180, 40); + } + for (c = 0; c < crtc_count; c++) { + crtc = &crtcs[c]; + if (drm_get_gamma(crtc) < 0) + return -1; + gamma_digit(crtc, 1, id < 10 ? 10 : (id / 10) % 10); + gamma_digit(crtc, 8, (id / 1) % 10); + id++; + if (drm_set_gamma(crtc) < 0) + return -1; + } + return 0; +} + + +/** + * Draw squares used as reference when tweeking the gamma correction + */ +static void +draw_gamma(void) +{ + size_t f; + uint32_t x, y, background, average, high, low, xoff; + framebuffer_t *restrict fb; + int r, g, b; + for (f = 0; f < framebuffer_count; f++) { + fb = framebuffers + f; + for (x = 0; x < 4; x++) { + r = (x == 1) || (x == 0); + g = (x == 2) || (x == 0); + b = (x == 3) || (x == 0); + background = fb_colour(128 * r, 128 * g, 128 * b); + average = fb_colour(188 * r, 188 * g, 188 * b); + high = fb_colour(255 * r, 255 * g, 255 * b); + low = fb_colour(0, 0, 0); + xoff = x * fb->width / 4; + fb_fill_rectangle(fb, background, xoff, 0, fb->width / 4, fb->height); + xoff += (fb->width / 4 - 200) / 2; + fb_fill_rectangle(fb, high, xoff, 40, 200, 200); + fb_fill_rectangle(fb, average, xoff + 50, 40, 100, 200); + fb_fill_rectangle(fb, average, xoff, 280, 200, 200); + for (y = 0; y < 200; y += 2) { + fb_draw_horizontal_line(fb, high, xoff + 50, 280 + y + 0, 100); + fb_draw_horizontal_line(fb, low , xoff + 50, 280 + y + 1, 100); + } + fb_fill_rectangle(fb, average, xoff, 520, 200, 200); + fb_fill_rectangle(fb, high, xoff + 50, 520, 100, 200); + } + } +} + + +/** + * Print a pattern on the screen that can be used when + * calibrating the convergence + */ +static void +draw_convergence(void) +{ + uint32_t black = fb_colour(0, 0, 0); + uint32_t white = fb_colour(255, 255, 255); + uint32_t x, y; + size_t f; + framebuffer_t *restrict fb; + for (f = 0; f < framebuffer_count; f++) { + fb = framebuffers + f; + fb_fill_rectangle(fb, black, 0, 0, fb->width, fb->height); + for (y = 0; y <= fb->height; y += 16) { + if (y == fb->height) + y = fb->height - 1; + for (x = 0; x <= fb->width; x += 16) { + if (x == fb->height) + x = fb->height - 1; + fb_draw_pixel(fb, white, x, y); + } + } + } +} + + +/** + * Print a pattern on the screen that can be used when + * calibrating the moiré cancellation + * + * @param gap The horizontal and vertical gap, in pixels, between the dots + * @param diagonal Whether to draw dots in a diagonal pattern + */ +static void +draw_moire(uint32_t gap, int diagonal) +{ + uint32_t black = fb_colour(0, 0, 0); + uint32_t white = fb_colour(255, 255, 255); + uint32_t x, y, gap2 = gap << 1; + size_t f; + framebuffer_t *restrict fb; + gap += (uint32_t)!diagonal; + if (diagonal) { + for (f = 0; f < framebuffer_count; f++) { + fb = framebuffers + f; + fb_fill_rectangle(fb, black, 0, 0, fb->width, fb->height); + for (y = 0; y < fb->height; y += gap) + for (x = (y % gap2); x < fb->width; x += gap2) + fb_draw_pixel(fb, white, x, y); + } + } else { + for (f = 0; f < framebuffer_count; f++) { + fb = framebuffers + f; + fb_fill_rectangle(fb, black, 0, 0, fb->width, fb->height); + for (y = 0; y < fb->height; y += gap) + for (x = 0; x < fb->width; x += gap) + fb_draw_pixel(fb, white, x, y); + } + } +} + + +/** + * Analyse the monitors calibrations + * + * @return Zero on success, -1 on error + */ +static int +read_calibs(void) +{ + size_t c; + for (c = 0; c < crtc_count; c++) { + if (drm_get_gamma(&crtcs[c]) < 0) + return -1; + gamma_analyse(crtcs[c].gamma_stops, crtcs[c].red, &gammas[0][c], &contrasts[0][c], &brightnesses[0][c]); + gamma_analyse(crtcs[c].gamma_stops, crtcs[c].green, &gammas[1][c], &contrasts[1][c], &brightnesses[1][c]); + gamma_analyse(crtcs[c].gamma_stops, crtcs[c].blue, &gammas[2][c], &contrasts[2][c], &brightnesses[2][c]); + } + return 0; +} + + +/** + * Apply the selected calibrations to the monitors + * + * @return Zero on success, -1 on error + */ +static int +apply_calibs(void) +{ + size_t c; + for (c = 0; c < crtc_count; c++) { + gamma_generate(crtcs[c].gamma_stops, crtcs[c].red, gammas[0][c], contrasts[0][c], brightnesses[0][c]); + gamma_generate(crtcs[c].gamma_stops, crtcs[c].green, gammas[1][c], contrasts[1][c], brightnesses[1][c]); + gamma_generate(crtcs[c].gamma_stops, crtcs[c].blue, gammas[2][c], contrasts[2][c], brightnesses[2][c]); + if (drm_set_gamma(&crtcs[c]) < 0) + return -1; + } + return 0; +} + + +/** + * Print calibrations into a file + * + * @param fp The file + * @return Zero on success, -1 on error + */ +static int +save_calibs(FILE *fp) +{ + size_t c; + for (c = 0; c < crtc_count; c++) { + if (fprintf(fp, "# index = %lu\n", c) < 0) + return -1; + if (fprintf(fp, "edid = %s\n", crtcs[c].edid) < 0) + return -1; + if (fprintf(fp, "brightness = %f:%f:%f\n", brightnesses[0][c], brightnesses[1][c], brightnesses[2][c]) < 0) + return -1; + if (fprintf(fp, "contrast = %f:%f:%f\n", contrasts[0][c], contrasts[1][c], contrasts[2][c]) < 0) + return -1; + if (fprintf(fp, "gamma = %f:%f:%f\n\n", gammas[0][c], gammas[1][c], gammas[2][c]) < 0) + return -1; + } + return 0; +} + + +int +main(int argc, char *argv[]) +{ + FILE *output_file = stdout; + int tty_configured = 0, rc = 0, in_fork = 0, status; + int c, b, d, at_contrast, red, green, blue; + struct termios stty, saved_stty; + uint32_t gap; + size_t mon; + pid_t pid; + + if (argc > 1 && argv[1][0] == '-') { + if (argv[1][1] == '-' && !argv[1][2]) { + argv[1] = argv[2]; + argc -= 1; + } else { + printf("usage: %s [output-file]\n", *argv); + return 1; + } + } + if (argc > 2) { + printf("usage: %s [output-file]\n", *argv); + return 0; + } + + if ((acquire_video() < 0) || + (tcgetattr(STDIN_FILENO, &saved_stty) < 0) || + (tcgetattr(STDIN_FILENO, &stty) < 0)) + goto fail; + + stty.c_lflag &= (tcflag_t)~(ICANON | ECHO); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty) < 0) + goto fail; + tty_configured = 1; + + printf("\033[?25l"); + fflush(stdout); + + pid = fork(); + if (pid > 0) { + while (waitpid(pid, &status, 0) < 0) + if (errno != EINTR) + perror(*argv); + rc = !!status; + goto done; + } else if (!pid) { + in_fork = 1; + } + + printf("\033[H\033[2J"); + printf("Please deactivate any program that dynamically\n"); + printf("applies filters to your monitors' colours\n"); + printf("and remove any existing filters.\n"); + printf("In doubt, you probably do not have any.\n"); + printf("Do not try to calibrate CRT monitors will\n"); + printf("they are cold.\n"); + printf("\n"); + printf("You will be presented with an image on each\n"); + printf("monitor. Please use the control panel on your\n"); + printf("to calibrate the contrast and brightness of\n"); + printf("each monitor. The contrasts adjusts the\n"); + printf("brightness of bright colours, and the\n"); + printf("brightness adjusts the brightness of dim\n"); + printf("colours. All rectangles are of equals size\n"); + printf("and they should be distrint from eachother.\n"); + printf("There should only be a slight difference\n"); + printf("between the two darkest colours for each\n"); + printf("colour. The grey colour does not need to\n"); + printf("be perfectly grey but should be close.\n"); + printf("The brightness should be as high as\n"); + printf("possible without the first square being\n"); + printf("any other colour than black or the two first\n"); + printf("square being too distinct from eachother. The\n"); + printf("contrast should be as high as possible without\n"); + printf("causing distortion.\n"); + printf("\n"); + printf("Press ENTER to continue, and ENTER again when\n"); + printf("your are done.\n"); + fflush(stdout); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + fflush(stdout); + draw_contrast_brightness(); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + printf("An index will be displayed on each monitor.\n"); + printf("It behoves you to memorise them. They will\n"); + printf("be used in the output when descibing the\n"); + printf("calibrations, and is the index of the monitors\n"); + printf("that are used when changing monitor to\n"); + printf("calibrate.\n"); + printf("\n"); + printf("Press ENTER to continue, and ENTER again when\n"); + printf("your are done.\n"); + fflush(stdout); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + fflush(stdout); + if (read_calibs() || draw_id()) + goto fail; + + while (getchar() != '\n'); + + if (apply_calibs()) + goto fail; + + printf("\033[H\033[2J"); + printf("You will not be given the opportunity to.\n"); + printf("calibrate your monitors' brightness and\n"); + printf("contrast using software incase your monitors\n"); + printf("could not be sufficiently calibrated using\n"); + printf("hardware.\n"); + printf("\n"); + printf("<Left> and <right> is used to change which\n"); + printf("monitor to calibrate. <Left> switches to the\n"); + printf("previous monitor (one lower in index) and\n"); + printf("<right> switches to the next monitor (one\n"); + printf("higher in index.)\n"); + printf("<Up> and <down> is used to increase and\n"); + printf("descrease the settings. respectively.\n"); + printf("<Shift+b> is used to switch to changing the.\n"); + printf("monitor's brightness and <shift+c> switches\n"); + printf("to contrast.\n"); + printf("<r> is used to switch to changing the red.\n"); + printf("channel, <g> switches to the green channel,\n"); + printf("<b> switches to the blue channel, and <a>\n"); + printf("is used to switch to change all channels.\n"); + printf("\n"); + printf("Press ENTER to continue, and ENTER again when\n"); + printf("your are done.\n"); + fflush(stdout); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + fflush(stdout); + draw_contrast_brightness(); + + b = at_contrast = 0; + red = green = blue = 1; + mon = 0; + while ((c = getchar()) != '\n') { + if (b) { + b = 0; + if (c == 'A' && at_contrast) { + contrasts[0][mon] += (double)red / 100; + contrasts[1][mon] += (double)green / 100; + contrasts[2][mon] += (double)blue / 100; + } else if (c == 'A') { + brightnesses[0][mon] += (double)red / 100; + brightnesses[1][mon] += (double)green / 100; + brightnesses[2][mon] += (double)blue / 100; + } else if (c == 'B' && at_contrast) { + contrasts[0][mon] -= (double)red / 100; + contrasts[1][mon] -= (double)green / 100; + contrasts[2][mon] -= (double)blue / 100; + } else if (c == 'B') { + brightnesses[0][mon] -= (double)red / 100; + brightnesses[1][mon] -= (double)green / 100; + brightnesses[2][mon] -= (double)blue / 100; + } else if (c == 'C') { + mon = (mon + 1) % crtc_count; + } else if (c == 'D') { + mon = (mon == 0 ? crtc_count : mon) - 1; + } + + if (c == 'A' || c == 'B') + apply_calibs(); + } + else if (c == '[') b = 1; + else if (c == 'B') at_contrast = 0; + else if (c == 'C') at_contrast = 1; + else if (c == 'r') red = 1, green = 0, blue = 0; + else if (c == 'g') red = 0, green = 1, blue = 0; + else if (c == 'b') red = 0, green = 0, blue = 1; + else if (c == 'a') red = 1, green = 1, blue = 1; + } + + printf("\033[H\033[2J"); + printf("You will now be presented with squares used\n"); + printf("to calibrate the gamma correction. There will\n"); + printf("be four stacks: grey, red, green and blue.\n"); + printf("Each stack has three squares: the upper square\n"); + printf("shows the characterics of how the middle square\n"); + printf("will look if the gamma is too high, and the\n"); + printf("lower shows how the middile will look if the\n"); + printf("gamma is too low. The middle square should\n"); + printf("look like it is one single colour if the gamma\n"); + printf("correction is configured correctly. You may\n"); + printf("have to look from a distance or not focus\n"); + printf("your eyes on the squares to compensate for\n"); + printf("the fact that there actually multiple colours\n"); + printf("is the square.\n"); + printf("The grey should look perfectly grey when you\n"); + printf("are done.\n"); + printf("\n"); + printf("<Left> and <right> is used to change which\n"); + printf("monitor to calibrate. <Left> switches to the\n"); + printf("previous monitor (one lower in index) and\n"); + printf("<right> switches to the next monitor (one\n"); + printf("higher in index.)\n"); + printf("<Up> and <down> is used to increase and\n"); + printf("descrease the gamma. respectively.\n"); + printf("<r> is used to switch to changing the red.\n"); + printf("channel, <g> switches to the green channel,\n"); + printf("<b> switches to the blue channel, and <a>\n"); + printf("is used to switch to change all channels.\n"); + printf("\n"); + printf("Press ENTER to continue, and ENTER again when\n"); + printf("your are done.\n"); + fflush(stdout); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + fflush(stdout); + draw_gamma(); + + b = 0; + red = green = blue = 1; + mon = 0; + while ((c = getchar()) != '\n') { + if (b) { + b = 0; + if (c == 'A') { + gammas[0][mon] += (double)red / 100; + gammas[1][mon] += (double)green / 100; + gammas[2][mon] += (double)blue / 100; + } else if (c == 'B') { + gammas[0][mon] -= (double)red / 100; + gammas[1][mon] -= (double)green / 100; + gammas[2][mon] -= (double)blue / 100; + if (gammas[0][mon] < 0) gammas[0][mon] = 0; + if (gammas[1][mon] < 0) gammas[1][mon] = 0; + if (gammas[2][mon] < 0) gammas[2][mon] = 0; + } else if (c == 'C') { + mon = (mon + 1) % crtc_count; + } else if (c == 'D') { + mon = (mon == 0 ? crtc_count : mon) - 1; + } + + if (c == 'A' || c == 'B') + apply_calibs(); + } + else if (c == '[') b = 1; + else if (c == 'r') red = 1, green = 0, blue = 0; + else if (c == 'g') red = 0, green = 1, blue = 0; + else if (c == 'b') red = 0, green = 0, blue = 1; + else if (c == 'a') red = 1, green = 1, blue = 1; + } + + printf("\033[H\033[2J"); + printf("The next step is to calibrate the monitors'\n"); + printf("convergence settings using the monitors'\n"); + printf("control panel. White dots will be printed on the\n"); + printf("screens, you should try to get as many as\n"); + printf("possible of them to appear as pure white dots\n"); + printf("rather than dots splitted in the red, green and\n"); + printf("blue dots. On most CRT monitors this is not\n"); + printf("possible to get all corners perfect.\n"); + printf("\n"); + printf("Press ENTER to continue, and ENTER again when\n"); + printf("your are done.\n"); + fflush(stdout); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + fflush(stdout); + draw_convergence(); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + printf("The final step is to calbirate the monitors' moiré\n"); + printf("cancellation. This too is done on the using the\n"); + printf("monitors' control panel.\n"); + printf("\n"); + printf("You can use <d> and the arrow keys to change the\n"); + printf("dot-pattern on the screens.\n"); + printf("\n"); + printf("Press ENTER to continue, and ENTER again when\n"); + printf("your are done.\n"); + fflush(stdout); + + while (getchar() != '\n'); + + printf("\033[H\033[2J"); + fflush(stdout); + draw_moire(1, 1); + + b = 0; + d = 1; + gap = 1; + while ((c = getchar()) != '\n') { + if (b) { + b = 0; + if (c == 'A' || c == 'C') { + draw_moire(++gap, d); + } else if (c == 'B' || c == 'D') { + if (--gap == 0) + gap = 1; + draw_moire(gap, d); + } + } else if (c == '[') { + b = 1; + } else if (c == 'd') { + draw_moire(gap, d ^= 1); + } + } + + printf("\033[H\033[2J"); + fflush(stdout); + + if (argc == 2) { + output_file = fopen(argv[1], "w"); + if (!output_file) + goto fail; + } + + if (save_calibs(output_file)) + goto fail; + fflush(output_file); + + if (argc == 2) + if (fclose(output_file)) + goto fail; + +done: + if (!in_fork) { + release_video(); + if (tty_configured) + tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty); + printf("\033[?25h"); + fflush(stdout); + } + return rc; +fail: + perror(*argv); + rc = 1; + goto done; +} |