/* * colours_ntsc.c - Atari NTSC colour palette generation and adjustment * * Copyright (C) 2009-2010 Atari800 development team (see DOC/CREDITS) * * This file is part of the Atari800 emulator project which emulates * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers. * * Atari800 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Atari800 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Atari800; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "colours_ntsc.h" #include "config.h" #include "atari.h" /* for TRUE/FALSE */ #include "colours.h" #include "log.h" #include "util.h" Colours_setup_t COLOURS_NTSC_setup; /* NTSC-specific default setup. */ static struct { double color_delay; } const default_setup = { 26.8, /* color delay, chosen to match color names given in GTIA.PDF */ }; COLOURS_EXTERNAL_t COLOURS_NTSC_external = { "", FALSE, FALSE }; /* NTSC colorburst angle in YIQ colorspace. Colorburst is at * 180 degrees in YUV - that is, a gold color. In YIQ, gold is at * different angle. However, YIQ is actually YUV turned * 33 degrees. So by looking at screenshots at Wikipedia we can * conclude that the colorburst angle is 270+33 in YIQ. * (See http://en.wikipedia.org/wiki/YUV and * http://en.wikipedia.org/wiki/YIQ) */ static const double colorburst_angle = (303.0f) * M_PI / 180.0f; /* In COLOURS_NTSC colours are generated in 2 stages: 1. Generate Y, I and Q values for all 256 colours and store in an intermediate yiq_table; 2. Convert YIQ to RGB. Splitting the process to 2 stages is necessary to allow the NTSC_FILTER module to use the same palette generation routines. Without such requirement, NTSC palette generation would be performed in one stage, similarly to the COLOURS_PAL module. */ /* Creates YIQ_TABLE from external palette. START_ANGLE and START_SATURATIION are provided as parameters, because NTSC_FILTER needs to set these values according to its internal setup (burst_phase etc.). External palette is not adjusted if COLOURS_NTSC_external.adjust is false. */ static void UpdateYIQTableFromExternal(double yiq_table[768], double start_angle, const double start_saturation) { unsigned char *ext_ptr = COLOURS_NTSC_external.palette; int n; start_angle -= colorburst_angle; for (n = 0; n < 256; n ++) { /* Convert RGB values from external palette to YIQ. */ double r = (double)*ext_ptr++ / 255.0; double g = (double)*ext_ptr++ / 255.0; double b = (double)*ext_ptr++ / 255.0; double y = 0.299 * r + 0.587 * g + 0.114 * b; double i = 0.595716 * r - 0.274453 * g - 0.321263 * b; double q = 0.211456 * r - 0.522591 * g + 0.311135 * b; double s = sin(start_angle); double c = cos(start_angle); double tmp_i = i; i = tmp_i * c - q * s; q = tmp_i * s + q * c; /* Optionally adjust external palette. */ if (COLOURS_NTSC_external.adjust) { y *= COLOURS_NTSC_setup.contrast * 0.5 + 1; y += COLOURS_NTSC_setup.brightness * 0.5; if (y > 1.0) y = 1.0; else if (y < 0.0) y = 0.0; i *= start_saturation + 1; q *= start_saturation + 1; } *yiq_table++ = y; *yiq_table++ = i; *yiq_table++ = q; } } /* Generates NTSC palette into YIQ_TABLE. START_ANGLE and START_SATURATIION are provided as parameters, because NTSC_FILTER needs to set these values according to its internal setup (burst_phase etc.) */ static void UpdateYIQTableFromGenerated(double yiq_table[768], const double start_angle, const double start_saturation) { /* Difference between two consecutive chrominances, in radians. */ double color_diff = COLOURS_NTSC_setup.color_delay * M_PI / 180.0; int cr, lm; double scaled_black_level = (double)COLOURS_NTSC_setup.black_level / 255.0; double scaled_white_level = (double)COLOURS_NTSC_setup.white_level / 255.0; /* NTSC luma multipliers from CGIA.PDF */ double luma_mult[16]={ 0.6941, 0.7091, 0.7241, 0.7401, 0.7560, 0.7741, 0.7931, 0.8121, 0.8260, 0.8470, 0.8700, 0.8930, 0.9160, 0.9420, 0.9690, 1.0000}; for (cr = 0; cr < 16; cr ++) { double angle = start_angle + (cr - 1) * color_diff; double saturation = (cr ? (start_saturation + 1) * 0.175f: 0.0); double i = cos(angle) * saturation; double q = sin(angle) * saturation; for (lm = 0; lm < 16; lm ++) { /* calculate yiq for color entry */ double y = (luma_mult[lm] - luma_mult[0]) / (luma_mult[15] - luma_mult[0]); y *= COLOURS_NTSC_setup.contrast * 0.5 + 1; y += COLOURS_NTSC_setup.brightness * 0.5; /* Scale the Y signal's range from 0..1 to * scaled_black_level..scaled_white_level */ y = y * (scaled_white_level - scaled_black_level) + scaled_black_level; /* if (y < scaled_black_level) y = scaled_black_level; else if (y > scaled_white_level) y = scaled_white_level; */ *yiq_table++ = y; *yiq_table++ = i; *yiq_table++ = q; } } } /* Fills YIQ_TABLE with palette. Depending on the current setup, it is computed from an internally-generated or external palette. */ static void UpdateYIQTable(double yiq_table[768], double start_angle, const double start_saturation) { if (COLOURS_NTSC_external.loaded) UpdateYIQTableFromExternal(yiq_table, start_angle, start_saturation); else UpdateYIQTableFromGenerated(yiq_table, start_angle, start_saturation); } void COLOURS_NTSC_GetYIQ(double yiq_table[768], const double start_angle) { /* Set the generated palette's saturation to 0.0, because NTSC_FILTER applies the saturation setting internally. */ UpdateYIQTable(yiq_table, start_angle, 0.0); } /* Converts YIQ values from YIQ_TABLE to RGB values. Stores them in COLOURTABLE. */ static void YIQ2RGB(int colourtable[256], const double yiq_table[768]) { const double *yiq_ptr = yiq_table; int n; for (n = 0; n < 256; n ++) { double y = *yiq_ptr++; double i = *yiq_ptr++; double q = *yiq_ptr++; double r, g, b; r = y + 0.9563 * i + 0.6210 * q; g = y - 0.2721 * i - 0.6474 * q; b = y - 1.1070 * i + 1.7046 * q; if (!COLOURS_NTSC_external.loaded || COLOURS_NTSC_external.adjust) { /* The r, g, b values derived from the YIQ signal are non-linear (ie. gamma-corrected). We convert them to linear values, assuming the CRT TV's gamma = COLOURS_NTSC_setup.gamma. */ r = Colours_Gamma2Linear(r, COLOURS_NTSC_setup.gamma); g = Colours_Gamma2Linear(g, COLOURS_NTSC_setup.gamma); b = Colours_Gamma2Linear(b, COLOURS_NTSC_setup.gamma); /* Now we convert the linear values to the sRGB colourspace. */ r = Colours_Linear2sRGB(r); g = Colours_Linear2sRGB(g); b = Colours_Linear2sRGB(b); } Colours_SetRGB(n, (int) (r * 255), (int) (g * 255), (int) (b * 255), colourtable); } } void COLOURS_NTSC_Update(int colourtable[256]) { double yiq_table[768]; UpdateYIQTable(yiq_table, colorburst_angle + COLOURS_NTSC_setup.hue * M_PI, COLOURS_NTSC_setup.saturation); YIQ2RGB(colourtable, yiq_table); } void COLOURS_NTSC_RestoreDefaults(void) { COLOURS_NTSC_setup.color_delay = default_setup.color_delay; } Colours_preset_t COLOURS_NTSC_GetPreset() { if (Util_almostequal(COLOURS_NTSC_setup.color_delay, default_setup.color_delay, 0.001)) return COLOURS_PRESET_STANDARD; return COLOURS_PRESET_CUSTOM; } int COLOURS_NTSC_ReadConfig(char *option, char *ptr) { if (strcmp(option, "COLOURS_NTSC_SATURATION") == 0) return Util_sscandouble(ptr, &COLOURS_NTSC_setup.saturation); else if (strcmp(option, "COLOURS_NTSC_CONTRAST") == 0) return Util_sscandouble(ptr, &COLOURS_NTSC_setup.contrast); else if (strcmp(option, "COLOURS_NTSC_BRIGHTNESS") == 0) return Util_sscandouble(ptr, &COLOURS_NTSC_setup.brightness); else if (strcmp(option, "COLOURS_NTSC_GAMMA") == 0) return Util_sscandouble(ptr, &COLOURS_NTSC_setup.gamma); else if (strcmp(option, "COLOURS_NTSC_HUE") == 0) return Util_sscandouble(ptr, &COLOURS_NTSC_setup.hue); else if (strcmp(option, "COLOURS_NTSC_GTIA_DELAY") == 0) return Util_sscandouble(ptr, &COLOURS_NTSC_setup.color_delay); else if (strcmp(option, "COLOURS_NTSC_EXTERNAL_PALETTE") == 0) Util_strlcpy(COLOURS_NTSC_external.filename, ptr, sizeof(COLOURS_NTSC_external.filename)); else if (strcmp(option, "COLOURS_NTSC_EXTERNAL_PALETTE_LOADED") == 0) /* Use the "loaded" flag to indicate that the palette must be loaded later. */ return (COLOURS_NTSC_external.loaded = Util_sscanbool(ptr)) != -1; else if (strcmp(option, "COLOURS_NTSC_ADJUST_EXTERNAL_PALETTE") == 0) return (COLOURS_NTSC_external.adjust = Util_sscanbool(ptr)) != -1; else return FALSE; return TRUE; } void COLOURS_NTSC_WriteConfig(FILE *fp) { fprintf(fp, "COLOURS_NTSC_SATURATION=%g\n", COLOURS_NTSC_setup.saturation); fprintf(fp, "COLOURS_NTSC_CONTRAST=%g\n", COLOURS_NTSC_setup.contrast); fprintf(fp, "COLOURS_NTSC_BRIGHTNESS=%g\n", COLOURS_NTSC_setup.brightness); fprintf(fp, "COLOURS_NTSC_GAMMA=%g\n", COLOURS_NTSC_setup.gamma); fprintf(fp, "COLOURS_NTSC_HUE=%g\n", COLOURS_NTSC_setup.hue); fprintf(fp, "COLOURS_NTSC_GTIA_DELAY=%g\n", COLOURS_NTSC_setup.color_delay); fprintf(fp, "COLOURS_NTSC_EXTERNAL_PALETTE=%s\n", COLOURS_NTSC_external.filename); fprintf(fp, "COLOURS_NTSC_EXTERNAL_PALETTE_LOADED=%d\n", COLOURS_NTSC_external.loaded); fprintf(fp, "COLOURS_NTSC_ADJUST_EXTERNAL_PALETTE=%d\n", COLOURS_NTSC_external.adjust); } int COLOURS_NTSC_Initialise(int *argc, char *argv[]) { int i; int j; for (i = j = 1; i < *argc; i++) { int i_a = (i + 1 < *argc); /* is argument available? */ int a_m = FALSE; /* error, argument missing! */ if (strcmp(argv[i], "-ntsc-saturation") == 0) { if (i_a) COLOURS_NTSC_setup.saturation = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-ntsc-contrast") == 0) { if (i_a) COLOURS_NTSC_setup.contrast = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-ntsc-brightness") == 0) { if (i_a) COLOURS_NTSC_setup.brightness = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-ntsc-gamma") == 0) { if (i_a) COLOURS_NTSC_setup.gamma = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-ntsc-tint") == 0) { if (i_a) COLOURS_NTSC_setup.hue = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-ntsc-colordelay") == 0) { if (i_a) COLOURS_NTSC_setup.color_delay = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-paletten") == 0) { if (i_a) { Util_strlcpy(COLOURS_NTSC_external.filename, argv[++i], sizeof(COLOURS_NTSC_external.filename)); /* Use the "loaded" flag to indicate that the palette must be loaded later. */ COLOURS_NTSC_external.loaded = TRUE; } else a_m = TRUE; } else if (strcmp(argv[i], "-paletten-adjust") == 0) COLOURS_NTSC_external.adjust = TRUE; else { if (strcmp(argv[i], "-help") == 0) { Log_print("\t-ntsc-saturation Set NTSC color saturation"); Log_print("\t-ntsc-contrast Set NTSC contrast"); Log_print("\t-ntsc-brightness Set NTSC brightness"); Log_print("\t-ntsc-gamma Set NTSC color gamma factor"); Log_print("\t-ntsc-tint Set NTSC tint"); Log_print("\t-ntsc-colordelay Set NTSC GTIA color delay"); Log_print("\t-paletten Load NTSC external palette"); Log_print("\t-paletten-adjust Apply adjustments to NTSC external palette"); } argv[j++] = argv[i]; } if (a_m) { Log_print("Missing argument for '%s'", argv[i]); return FALSE; } } *argc = j; /* Try loading an external palette if needed. */ if (COLOURS_NTSC_external.loaded && !COLOURS_EXTERNAL_Read(&COLOURS_NTSC_external)) Log_print("Cannot read NTSC palette from %s", COLOURS_NTSC_external.filename); return TRUE; }