/* * colours_pal.c - Atari PAL colour palette generation and adjustment * * Copyright (C) 2009-2014 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_pal.h" #include "atari.h" /* for TRUE/FALSE */ #include "colours.h" #include "log.h" #include "util.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif Colours_setup_t COLOURS_PAL_setup; /* PAL-specific default setup. */ static struct { double color_delay; } const default_setup = { 23.2, /* chosen by eye to give a smooth rainbow */ }; COLOURS_EXTERNAL_t COLOURS_PAL_external = { "", FALSE, FALSE }; /* Fills YUV_TABLE from external palette. External palette is not adjusted if COLOURS_PAL_external.adjust is false. */ static void GetYUVFromExternal(double yuv_table[256*5]) { unsigned char *ext_ptr = COLOURS_PAL_external.palette; int n; double const hue = COLOURS_PAL_setup.hue * M_PI; double const s = sin(hue); double const c = cos(hue); for (n = 0; n < 256; n ++) { /* Convert RGB values from external palette to YUV. */ double r = (double)*ext_ptr++ / 255.0; double g = (double)*ext_ptr++ / 255.0; double b = (double)*ext_ptr++ / 255.0; double y, u, v, tmp_u; Colours_RGB2YUV(r, g, b, &y, &u, &v); tmp_u = u; u = tmp_u * c - v * s; v = tmp_u * s + v * c; /* Optionally adjust external palette. */ if (COLOURS_PAL_external.adjust) { y *= COLOURS_PAL_setup.contrast * 0.5 + 1; y += COLOURS_PAL_setup.brightness * 0.5; if (y > 1.0) y = 1.0; else if (y < 0.0) y = 0.0; u *= COLOURS_PAL_setup.saturation + 1.0; v *= COLOURS_PAL_setup.saturation + 1.0; } *yuv_table++ = y; /* Cannot retrieve different U/V values for even and odd lines from an external palette - instead write each value twice. */ *yuv_table++ = u; *yuv_table++ = u; *yuv_table++ = v; *yuv_table++ = v; } } /* Below are conclusions from analysis of oscillograms of PAL GTIA output. The oscillograms are available at: http://www.atari.org.pl/forum/viewtopic.php?pid=164037#p164037 Like the NTSC GTIA, the PAL GTIA produces color signal by delaying (phase-shifting) a base chrominance subcarrier by a specified time - different for each displayed hue. The subcarrier's frequency is 4433618.75 Hz (4.43 MHz in short). The subcarrier is provided at the chip's PAL pin; output signal is produced at the COL pin. As in NTSC, the delay introduced by the PAL GTIA can be adjusted to a certain extent by changing voltage applied at the DEL pin. This is where the similarities between the PAL GTIA and its NTSC counterpart end. In NTSC, each screen line contains, in its back porch, a burst of about 10 cycles of color subcarrier (known as the coloburst). The colorburst is used by the TV as a reference for decoding colors in each line. Signal with phase shift equal to the colorburst's (ie. equal to the subcarrier's) is decoded as 180 deg angle in the YUV colorspace (-U). Delaying the signal is interpreted as rotating the angle clockwise in YUV. In PAL, there's also colorburst, but it is not equal to the color subcarrier. In even lines it lags the subcarrier by 45 deg, and in odd lines it leads it subcarrier by 45 deg. The resulting 90 deg difference is used by the TV to distinguish even and odd lines. The original subcarrier is recreated by the receiver by summing colorbursts from two consecutive lines (and adjusting its amplitude, since sum of two sine waves shifted by 90 deg has higher amplitude than the original waves). Then, as in NTSC, signals equal in phase to the (recreated) subcarrier are decoded as 180 deg angle in YUV. But other signals are decoded differently in even and in odd lines. In even lines (or "NTSC lines"), delaying the signal is interpreted as rotating the angle clockwise in YUV, just like in NTSC. But in odd lines (or "PAL lines"), delaying the signal is akin to rotating the angle counter-clockwise. So, to achieve the same color in even and odd lines, the transmitter must send signal delayed wrt. the subcarrier in even lines but "rushed" (anti-delayed?) in odd lines. PAL GTIA can only delay input signal; it can't "rush" it, it would involve creating output before input has arrived. So the requirements of the PAL system are achieved in the chip by delaying the signal differently in even and odd lines. Measurements indicate that the PAL GTIA delays the input subcarrier by 95.2ns; then it might delay it by further 100.7ns if needed; and finally can delay it further by X, 2*X, 3*X, ..., 7*X, where X is a delay adjusted by voltage at the DEL pin. The general formula is: DELAY(hue) = 95.2ns + ADD(hue)*100.7ns + MULT(hue)*X where ADD(hue) depends on a given hue and can be 0 or 1, MULT(hue) depends on a given hue and can be 0, 1, 2, ..., 7, X depends on voltage at the DEL pin. So, the chip can shift the input signal by 8*2=16 different delays. Mapping of ADD and MULT values to specific hues is random - it is implemented in the source code as the del_coeffs structure. The relation between DEL pin voltage and the resulting delay is not linear - it was measured to be: 17.37ns at 5V, 10.60ns at 7V, 7.44ns at 9V. The PAL GTIA apparently contains a series of 7 delay elements similarly to the NTSC version (which contains 15 of them), and an additional 100.7ns delay element. (When looking at the oscillograms, for each hue that passes through this 100.7ns delay, the resulting waveform is different than for hues that don't involve the delay - it looks like it was inverted vertically. This gives a hint about how the delay is implemented internally.) The end result is, PAL GTIA actually produces more than 256 colors. It produces different hues in odd and even lines, but it is not normally visible since PAL TVs average hues on each two consecutive lines - but it can be made visible with specially-crafted screens. Currently this feature of the chip is not implemented - only 256 colors are produced, each one being an average of "even" and "odd" hues. In PAL GTIA, just as in NTSC, the colorburst signals have the same phase as Hue 1. But by changing voltage at the DEL pin, we also change the relative phase shift of the colorburst between even and odd lines - it is not always 90 degrees. It has the following consequences - all of them observed on a real machine, and all currently emulated: 1. Changing phase shift between odd and even colorbursts also changes amplitude of the resulting subcarrier, which is recreated by summing the two colorbursts. Since amplitude of the color subcarrier is used as a reference for decoding color saturation, changing voltage at DEL not only changes hues of displayed colors; it also changes their saturation. 2. With appropriately chosen DEL voltage, phase shift between even and odd colorbursts can reach 180 deg. At this point the PAL TV can no longer distinguish even and odd lines, and stops interpreting color signal, displaying screen as black-and-white with visible chroma dots. 3. Changing DEL voltage further causes phase shift between even and odd colorbursts to to be larger than 180 deg, at which point the TV starts interpreting color signal "backwards", as if the color subcarrier suddenly shifted by 180 deg. */ /* Generates PAL palette into YUV_TABLE. */ static void GetYUVFromGenerated(double yuv_table[256*5]) { struct del_coeff { int add; int mult; }; /* Delay coefficients for each hue. */ static struct { struct del_coeff even[15]; struct del_coeff odd[15]; } const del_coeffs = { { { 1, 5 }, /* Hue $1 in even lines */ { 1, 6 }, /* Hue $2 in even lines */ { 1, 7 }, { 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 5 }, { 0, 6 }, { 0, 7 }, { 1, 1 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 } /* Hue $F in even lines */ }, { { 1, 1 }, /* Hue $1 in odd lines */ { 1, 0 }, /* Hue $2 in odd lines */ { 0, 7 }, { 0, 6 }, { 0, 5 }, { 0, 4 }, { 0, 2 }, { 0, 1 }, { 0, 0 }, { 1, 7 }, { 1, 5 }, { 1, 4 }, { 1, 3 }, { 1, 2 }, { 1, 1 } /* Hue $F in odd lines */ } }; int cr, lm; double const scaled_black_level = (double)COLOURS_PAL_setup.black_level / 255.0f; double const scaled_white_level = (double)COLOURS_PAL_setup.white_level / 255.0f; /* NTSC luma multipliers from CGIA.PDF */ static double const 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}; /* When phase shift between even and odd colorbursts is close to 180 deg, the TV stops interpreting color signal. This value determines how close to 180 deg that phase shift must be. It is specific to a TV set. */ static double const color_disable_threshold = 0.05; /* Base delay - 1/4.43MHz * base_del = ca. 95.2ns */ static double const base_del = 0.421894970414201; /* Additional delay - 1/4.43MHz * add_del = ca. 100.7ns */ static double const add_del = 0.446563064859117; /* Delay introduced by the DEL pin voltage. */ double const del_adj = COLOURS_PAL_setup.color_delay / 360.0; /* Phase delays of colorbursts in even and odd lines. They are equal to Hue 1. */ double const even_burst_del = base_del + add_del * del_coeffs.even[0].add + del_adj * del_coeffs.even[0].mult; double const odd_burst_del = base_del + add_del * del_coeffs.odd[0].add + del_adj * del_coeffs.odd[0].mult; /* Reciprocal of the recreated subcarrier's amplitude. */ double saturation_mult; /* Phase delay of the recreated amplitude. */ double subcarrier_del = (even_burst_del + odd_burst_del + COLOURS_PAL_setup.hue) / 2.0; /* Phase difference between colorbursts in even and odd lines. */ double burst_diff = even_burst_del - odd_burst_del; burst_diff -= floor(burst_diff); /* Normalize to 0..1. */ if (burst_diff > 0.5 - color_disable_threshold && burst_diff < 0.5 + color_disable_threshold) /* Shift between colorbursts close to 180 deg. Don't produce color. */ saturation_mult = 0.0; else { /* Subcarrier is a sum of two waves with equal frequency and amplitude, but phase-shifted by 2pi*burst_diff. The formula is derived from http://2000clicks.com/mathhelp/GeometryTrigEquivPhaseShift.aspx */ double subcarrier_amplitude = sqrt(2.0 * cos(burst_diff*2.0*M_PI) + 2.0); /* Normalise saturation_mult by multiplying by sqrt(2), so that it equals 1.0 when odd & even colorbursts are shifted by 90 deg (ie. burst_diff == 0.25). */ saturation_mult = sqrt(2.0) / subcarrier_amplitude; } for (cr = 0; cr < 16; cr ++) { double even_u = 0.0; double odd_u = 0.0; double even_v = 0.0; double odd_v = 0.0; if (cr) { struct del_coeff const *even_delay = &(del_coeffs.even[cr - 1]); struct del_coeff const *odd_delay = &(del_coeffs.odd[cr - 1]); double even_del = base_del + add_del * even_delay->add + del_adj * even_delay->mult; double odd_del = base_del + add_del * odd_delay->add + del_adj * odd_delay->mult; double even_angle = (0.5 - (even_del - subcarrier_del)) * 2.0 * M_PI; double odd_angle = (0.5 + (odd_del - subcarrier_del)) * 2.0 * M_PI; double saturation = (COLOURS_PAL_setup.saturation + 1) * 0.175 * saturation_mult; even_u = cos(even_angle) * saturation; even_v = sin(even_angle) * saturation; odd_u = cos(odd_angle) * saturation; odd_v = sin(odd_angle) * saturation; } for (lm = 0; lm < 16; lm ++) { /* calculate yuv for color entry */ double y = (luma_mult[lm] - luma_mult[0]) / (luma_mult[15] - luma_mult[0]); y *= COLOURS_PAL_setup.contrast * 0.5 + 1; y += COLOURS_PAL_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; */ *yuv_table++ = y; *yuv_table++ = even_u; *yuv_table++ = odd_u; *yuv_table++ = even_v; *yuv_table++ = odd_v; } } } void COLOURS_PAL_GetYUV(double yuv_table[256*5]) { if (COLOURS_PAL_external.loaded) GetYUVFromExternal(yuv_table); else GetYUVFromGenerated(yuv_table); } /* Averages YUV values from YUV_TABLE and converts them to RGB values. Stores them in COLOURTABLE. */ static void YUV2RGB(int colourtable[256], double const yuv_table[256*5]) { double const *yuv_ptr = yuv_table; int n; for (n = 0; n < 256; ++n) { double y = *yuv_ptr++; double even_u = *yuv_ptr++; double odd_u = *yuv_ptr++; double even_v = *yuv_ptr++; double odd_v = *yuv_ptr++; double r, g, b; /* The different colors in odd and even lines are not emulated - instead the palette contains averaged values. */ double u = (even_u + odd_u) / 2.0; double v = (even_v + odd_v) / 2.0; Colours_YUV2RGB(y, u, v, &r, &g, &b); if (!COLOURS_PAL_external.loaded || COLOURS_PAL_external.adjust) { /* The r, g, b values derived from the YUV signal are non-linear (ie. gamma-corrected). We convert them to linear values, assuming the CRT TV's gamma = COLOURS_PAL_setup.gamma. */ r = Colours_Gamma2Linear(r, COLOURS_PAL_setup.gamma); g = Colours_Gamma2Linear(g, COLOURS_PAL_setup.gamma); b = Colours_Gamma2Linear(b, COLOURS_PAL_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_PAL_Update(int colourtable[256]) { double yuv_table[256*5]; COLOURS_PAL_GetYUV(yuv_table); YUV2RGB(colourtable, yuv_table); } void COLOURS_PAL_RestoreDefaults(void) { COLOURS_PAL_setup.color_delay = default_setup.color_delay; } Colours_preset_t COLOURS_PAL_GetPreset() { if (Util_almostequal(COLOURS_PAL_setup.color_delay, default_setup.color_delay, 0.001)) return COLOURS_PRESET_STANDARD; return COLOURS_PRESET_CUSTOM; } int COLOURS_PAL_ReadConfig(char *option, char *ptr) { if (strcmp(option, "COLOURS_PAL_SATURATION") == 0) return Util_sscandouble(ptr, &COLOURS_PAL_setup.saturation); else if (strcmp(option, "COLOURS_PAL_CONTRAST") == 0) return Util_sscandouble(ptr, &COLOURS_PAL_setup.contrast); else if (strcmp(option, "COLOURS_PAL_BRIGHTNESS") == 0) return Util_sscandouble(ptr, &COLOURS_PAL_setup.brightness); else if (strcmp(option, "COLOURS_PAL_GAMMA") == 0) return Util_sscandouble(ptr, &COLOURS_PAL_setup.gamma); else if (strcmp(option, "COLOURS_PAL_HUE") == 0) return Util_sscandouble(ptr, &COLOURS_PAL_setup.hue); else if (strcmp(option, "COLOURS_PAL_GTIA_DELAY") == 0) return Util_sscandouble(ptr, &COLOURS_PAL_setup.color_delay); else if (strcmp(option, "COLOURS_PAL_EXTERNAL_PALETTE") == 0) Util_strlcpy(COLOURS_PAL_external.filename, ptr, sizeof(COLOURS_PAL_external.filename)); else if (strcmp(option, "COLOURS_PAL_EXTERNAL_PALETTE_LOADED") == 0) /* Use the "loaded" flag to indicate that the palette must be loaded later. */ return (COLOURS_PAL_external.loaded = Util_sscanbool(ptr)) != -1; else if (strcmp(option, "COLOURS_PAL_ADJUST_EXTERNAL_PALETTE") == 0) return (COLOURS_PAL_external.adjust = Util_sscanbool(ptr)) != -1; else return FALSE; return TRUE; } void COLOURS_PAL_WriteConfig(FILE *fp) { fprintf(fp, "COLOURS_PAL_SATURATION=%g\n", COLOURS_PAL_setup.saturation); fprintf(fp, "COLOURS_PAL_CONTRAST=%g\n", COLOURS_PAL_setup.contrast); fprintf(fp, "COLOURS_PAL_BRIGHTNESS=%g\n", COLOURS_PAL_setup.brightness); fprintf(fp, "COLOURS_PAL_GAMMA=%g\n", COLOURS_PAL_setup.gamma); fprintf(fp, "COLOURS_PAL_HUE=%g\n", COLOURS_PAL_setup.hue); fprintf(fp, "COLOURS_PAL_GTIA_DELAY=%g\n", COLOURS_PAL_setup.color_delay); fprintf(fp, "COLOURS_PAL_EXTERNAL_PALETTE=%s\n", COLOURS_PAL_external.filename); fprintf(fp, "COLOURS_PAL_EXTERNAL_PALETTE_LOADED=%d\n", COLOURS_PAL_external.loaded); fprintf(fp, "COLOURS_PAL_ADJUST_EXTERNAL_PALETTE=%d\n", COLOURS_PAL_external.adjust); } int COLOURS_PAL_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], "-pal-saturation") == 0) { if (i_a) COLOURS_PAL_setup.saturation = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-pal-contrast") == 0) { if (i_a) COLOURS_PAL_setup.contrast = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-pal-brightness") == 0) { if (i_a) COLOURS_PAL_setup.brightness = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-pal-gamma") == 0) { if (i_a) COLOURS_PAL_setup.gamma = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-pal-tint") == 0) { if (i_a) COLOURS_PAL_setup.hue = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-pal-colordelay") == 0) { if (i_a) COLOURS_PAL_setup.color_delay = atof(argv[++i]); else a_m = TRUE; } else if (strcmp(argv[i], "-palettep") == 0) { if (i_a) { Util_strlcpy(COLOURS_PAL_external.filename, argv[++i], sizeof(COLOURS_PAL_external.filename)); /* Use the "loaded" flag to indicate that the palette must be loaded later. */ COLOURS_PAL_external.loaded = TRUE; } else a_m = TRUE; } else if (strcmp(argv[i], "-palettep-adjust") == 0) COLOURS_PAL_external.adjust = TRUE; else { if (strcmp(argv[i], "-help") == 0) { Log_print("\t-pal-saturation Set PAL color saturation"); Log_print("\t-pal-contrast Set PAL contrast"); Log_print("\t-pal-brightness Set PAL brightness"); Log_print("\t-pal-gamma Set PAL color gamma factor"); Log_print("\t-pal-tint Set PAL tint"); Log_print("\t-pal-colordelay Set PAL GTIA color delay"); Log_print("\t-palettep Load PAL external palette"); Log_print("\t-palettep-adjust Apply adjustments to PAL 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_PAL_external.loaded && !COLOURS_EXTERNAL_Read(&COLOURS_PAL_external)) Log_print("Cannot read PAL palette from %s", COLOURS_PAL_external.filename); return TRUE; }