From 5d4c09da546c065378da48e2ac19fb7ad9b84cd4 Mon Sep 17 00:00:00 2001 From: stephena Date: Sun, 26 Feb 2012 18:58:41 +0000 Subject: [PATCH] Second pass at Blargg NTSC filtering. This code comes directly from the latest Atari800 drop, and is the most recent version I could find. As with the last commit, it still doesn't actually do anything (but it does compile). git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2395 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba --- src/common/tv_filters/NTSCFilter.cxx | 580 +++++++++--------------- src/common/tv_filters/NTSCFilter.hxx | 169 +++---- src/common/tv_filters/atari_ntsc.c | 397 ++++++++++++++++ src/common/tv_filters/atari_ntsc.h | 268 +++++++++++ src/common/tv_filters/atari_ntsc_impl.h | 478 +++++++++++++++++++ src/common/tv_filters/module.mk | 3 +- 6 files changed, 1417 insertions(+), 478 deletions(-) create mode 100644 src/common/tv_filters/atari_ntsc.c create mode 100644 src/common/tv_filters/atari_ntsc.h create mode 100644 src/common/tv_filters/atari_ntsc_impl.h diff --git a/src/common/tv_filters/NTSCFilter.cxx b/src/common/tv_filters/NTSCFilter.cxx index 0d7826cbf..84457d96b 100644 --- a/src/common/tv_filters/NTSCFilter.cxx +++ b/src/common/tv_filters/NTSCFilter.cxx @@ -25,28 +25,11 @@ #include #include -static float const rgb_unit = 0x1000; -static float const pi = 3.14159265358979323846f; - -/* important to use + and not | since values are signed */ -#define MAKE_KRGB( r, g, b ) \ - ( ((r + 16) >> 5 << 20) + ((g + 16) >> 5 << 10) + ((b + 16) >> 5) ) - -#define MAKE_KMASK( x ) (((x) << 20) | ((x) << 10) | (x)) - -/* clamp each RGB component to 0 to 0x7F range (low two bits are trashed) */ -#define CLAMP_RGB( io, adj ) {\ - ntsc_rgb_t sub = (io) >> (7 + adj) & MAKE_KMASK( 3 );\ - ntsc_rgb_t clamp = MAKE_KMASK( 0x202 ) - sub;\ - io = ((io) | clamp) & (clamp - sub);\ -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NTSCFilter::NTSCFilter() - : myNTSCEmu(NULL), - myCurrentModeNum(-1) + : mySetup(atari_ntsc_composite), + myCurrentModeNum(0) { - // Add various presets } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -55,398 +38,255 @@ NTSCFilter::~NTSCFilter() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const string& NTSCFilter::next() +void NTSCFilter::updateFilter() { + double yiq_table[768]; - return EmptyString; + updateYIQTable(yiq_table, mySetup.burst_phase * M_PI); + + /* The gamma setting is not used in atari_ntsc (palette generation is + placed in another module), so below we do not set the gamma field + of the mySetup structure. */ + + // According to how Atari800 defines 'external', we're using an external + // palette (since it is generated outside of the COLOUR_NTSC routines) + /* External palette must not be adjusted, so FILTER_NTSC + settings are set to defaults so they don't change the source + palette in any way. */ + mySetup.hue = 0.0; + mySetup.saturation = 0.0; + mySetup.contrast = 0.0; + mySetup.brightness = 0.0; + + mySetup.yiq_palette = yiq_table; + atari_ntsc_init(&myFilter, &mySetup); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void NTSCFilter::atari_ntsc_init(atari_ntsc_setup_t const* setup) +void NTSCFilter::restoreDefaults() { - /* init pixel renderer */ - int entry; - float burst_phase = (setup->burst_phase) * pi; - ntsc_to_rgb_t ntsc; - ntsc_to_rgb_init( &ntsc, setup, setup->hue * pi - burst_phase ); - - for ( entry = 0; entry < atari_ntsc_color_count; entry++ ) - { - /*NTSC PHASE CONSTANTS.*/ - float ntsc_phase_constant[16]={0,2.27,1.87,1.62, - 1.22,0.62,-0.31,-0.855, - -1.18,-1.43,-1.63,-1.93, - -2.38,-3.43,2.52,2.07}; - /*NTSC luma multipliers from gtia.pdf*/ - float 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}; - /* calculate yiq for color entry */ - int color = entry >> 4; - float angle = burst_phase + ntsc_phase_constant[color]; - float lumafactor = ( luma_mult[ entry & 0x0f] - luma_mult[0] )/( luma_mult[15] - luma_mult[0] ); - float adj_lumafactor = pow(lumafactor, 1 +setup->gamma_adj); - float y = adj_lumafactor * rgb_unit; - float base_saturation = (color ? rgb_unit * 0.35f: 0.0f); - float saturation_ramp = setup->saturation_ramp; - float saturation = base_saturation * ( 1 - saturation_ramp*lumafactor ); - float i = sin( angle ) * saturation; - float q = cos( angle ) * saturation; - ntsc_rgb_t* out = myNTSCEmu->table [entry]; - y = y * ntsc.contrast + ntsc.brightness; - - /* generate at four alignments with respect to output */ - ntsc.composite [composite_border + 0] = i + y; - ntsc.composite [composite_border + 1] = q + y; - out = gen_pixel( &ntsc, 0, 0, out ); - - ntsc.composite [composite_border + 0] = 0; - ntsc.composite [composite_border + 1] = 0; - ntsc.composite [composite_border + 2] = i - y; - ntsc.composite [composite_border + 3] = q - y; - out = gen_pixel( &ntsc, 2, 2, out ); - - ntsc.composite [composite_border + 2] = 0; - ntsc.composite [composite_border + 3] = 0; - ntsc.composite [composite_border + 4] = i + y; - ntsc.composite [composite_border + 5] = q + y; - out = gen_pixel( &ntsc, 4, 4, out ); - - ntsc.composite [composite_border + 4] = 0; - ntsc.composite [composite_border + 5] = 0; - ntsc.composite [composite_border + 6] = i - y; - ntsc.composite [composite_border + 7] = q - y; - out = gen_pixel( &ntsc, 6, 6, out ); - - ntsc.composite [composite_border + 6] = 0; - ntsc.composite [composite_border + 7] = 0; - - /* correct roundoff errors that would cause vertical bands in solid areas */ - { - float r = y + i * ntsc.to_rgb [0] + q * ntsc.to_rgb [1]; - float g = y + i * ntsc.to_rgb [2] + q * ntsc.to_rgb [3]; - float b = y + i * ntsc.to_rgb [4] + q * ntsc.to_rgb [5]; - ntsc_rgb_t correct = MAKE_KRGB( (int) r, (int) g, (int) b ) + MAKE_KMASK( 0x100 ); - int i; - out = myNTSCEmu->table [entry]; - for ( i = 0; i < rgb_kernel_size / 2; i++ ) - { - /* sum as would occur when outputting run of pixels using same color, - but don't sum first kernel; the difference between this and the correct - color is what the first kernel's entry should be */ - ntsc_rgb_t sum = out [(i+12)%14+14]+out[(i+10)%14+28]+out[(i+8)%14+42]+ - out[i+7]+out [ i+ 5 +14]+out[ i+ 3 +28]+out[ i+1 +42]; - out [i] = correct - sum; - } - } - } -//FIXME Log_print("atari_ntsc_init(): sharpness:%f saturation:%f brightness:%f contrast:%f gaussian_factor:%f burst_phase:%f, hue:%f gamma_adj:%f saturation_ramp:%f\n",setup->sharpness,setup->saturation,setup->brightness,setup->contrast,setup->gaussian_factor,setup->burst_phase,setup->hue,setup->gamma_adj,setup->saturation_ramp); + mySetup = atari_ntsc_composite; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void NTSCFilter::atari_ntsc_blit( unsigned char const* in, long in_pitch, - int width, int height, unsigned short* out, long out_pitch ) +void NTSCFilter::setPreset(int preset) { - int const chunk_count = (width - 10) / 7; - long next_in_line = in_pitch - chunk_count * 4; - long next_out_line = out_pitch - (chunk_count + 1) * (7 * sizeof *out); - while ( height-- ) - { - #define ENTRY( n ) myNTSCEmu->table [n] - ntsc_rgb_t const* k1 = ENTRY( 0 ); - ntsc_rgb_t const* k2 = k1; - ntsc_rgb_t const* k3 = k1; - ntsc_rgb_t const* k4 = k1; - ntsc_rgb_t const* k5 = k4; - ntsc_rgb_t const* k6 = k4; - ntsc_rgb_t const* k7 = k4; - int n; - - #if ATARI_NTSC_RGB_BITS == 16 - #define TO_RGB( in ) ((in >> 11 & 0xF800) | (in >> 6 & 0x07C0) | (in >> 2 & 0x001F)) - #elif ATARI_NTSC_RGB_BITS == 15 - #define TO_RGB( in ) ((in >> 12 & 0x7C00) | (in >> 7 & 0x03E0) | (in >> 2 & 0x001F)) - #endif - - #define PIXEL( a ) {\ - ntsc_rgb_t temp =\ - k0 [a ] + k1 [(a+5)%7+14] + k2 [(a+3)%7+28] + k3 [(a+1)%7+42] +\ - k4 [a+7] + k5 [(a+5)%7+21] + k6 [(a+3)%7+35] + k7 [(a+1)%7+49];\ - if ( a ) out [a-1] = rgb;\ - CLAMP_RGB( temp, 0 );\ - rgb = TO_RGB( temp );\ - } - - for ( n = chunk_count; n; --n ) - { - ntsc_rgb_t const* k0 = ENTRY( in [0] ); - int rgb; - PIXEL( 0 ); - PIXEL( 1 ); - k5 = k1; - k1 = ENTRY( in [1] ); - PIXEL( 2 ); - PIXEL( 3 ); - k6 = k2; - k2 = ENTRY( in [2] ); - PIXEL( 4 ); - PIXEL( 5 ); - k7 = k3; - k3 = ENTRY( in [3] ); - PIXEL( 6 ); - out [6] = rgb; - k4 = k0; - in += 4; - out += 7; - } - { - ntsc_rgb_t const* k0 = ENTRY( 0 ); - int rgb; - PIXEL( 0 ); - PIXEL( 1 ); - k5 = k1; - k1 = k0; - PIXEL( 2 ); - PIXEL( 3 ); - k6 = k2; - k2 = k0; - PIXEL( 4 ); - PIXEL( 5 ); - k7 = k3; - k3 = k0; - PIXEL( 6 ); - k4 = k0; - out [6] = rgb; - out += 7; - PIXEL( 0 ); - PIXEL( 1 ); - k5 = k0; - PIXEL( 2 ); - out [2] = rgb; - } - #undef PIXEL - - in += next_in_line; - out = (unsigned short*) ((char*) out + next_out_line); + if (preset < PRESET_CUSTOM) { + mySetup = *presets[preset]; + updateFilter(); + +#if 0 // FIXME - what are these items for?? + /* Copy settings from the preset to NTSC setup. */ + COLOURS_NTSC_specific_setup.hue = mySetup.hue; + COLOURS_NTSC_setup.saturation = mySetup.saturation; + COLOURS_NTSC_setup.contrast = mySetup.contrast; + COLOURS_NTSC_setup.brightness = mySetup.brightness; + COLOURS_NTSC_setup.gamma = mySetup.gamma; +#endif } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void NTSCFilter::rotate_matrix( float const* in, float s, float c, float* out ) +int NTSCFilter::getPreset() { - int n = 3; - while ( n-- ) - { - float i = *in++; - float q = *in++; - *out++ = i * c - q * s; - *out++ = i * s + q * c; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void NTSCFilter::ntsc_to_rgb_init( ntsc_to_rgb_t* ntsc, atari_ntsc_setup_t const* setup, float hue ) -{ - static float const to_rgb [6] = { 0.956, 0.621, -0.272, -0.647, -1.105, 1.702 }; - float gaussian_factor = 1+setup->gaussian_factor; /* 1 = normal, > 1 reduces echoes of bright objects (was 2.1)*/ - float const brightness_bias = rgb_unit / 128; /* reduces vert bands on artifact colored areas */ +// FIXME - for now just return composite +return 0; +#if 0 int i; - /* ranges need to be scaled a bit to avoid pixels overflowing at extremes */ - ntsc->brightness = (setup->brightness-0.0f) * (0.4f * rgb_unit) + brightness_bias; - ntsc->contrast = (setup->contrast-0.0f) * 0.4f + 1; - ntsc->sharpness = 1 + (setup->sharpness < 0 ? setup->sharpness * 0.5f : setup->sharpness); - - for ( i = 0; i < composite_size; i++ ) - ntsc->composite [i] = 0; - - /* Generate gaussian kernel, padded with zero */ - for ( i = 0; i < ntsc_kernel_size; i++ ) - ntsc->kernel [i] = 0; - for ( i = -composite_border; i <= composite_border; i++ ) - ntsc->kernel [ntsc_kernel_size / 2 + i] = exp( i * i * (-0.03125f * gaussian_factor) ); - - /* normalize kernel totals of every fourth sample (at all four phases) to 0.5, otherwise */ - /* i/q low-pass will favor one of the four alignments and cause repeating spots */ - for ( i = 0; i < 4; i++ ) - { - double sum = 0; - float scale; - int x; - for ( x = i; x < ntsc_kernel_size; x += 4 ) - sum += ntsc->kernel [x]; - scale = 0.5 / sum; - for ( x = i; x < ntsc_kernel_size; x += 4 ) - ntsc->kernel [x] *= scale; + + for (i = 0; i < PRESET_SIZE; i ++) { + if (Util_almostequal(mySetup.sharpness, presets[i]->sharpness, 0.001) && + Util_almostequal(mySetup.resolution, presets[i]->resolution, 0.001) && + Util_almostequal(mySetup.artifacts, presets[i]->artifacts, 0.001) && + Util_almostequal(mySetup.fringing, presets[i]->fringing, 0.001) && + Util_almostequal(mySetup.bleed, presets[i]->bleed, 0.001) && + Util_almostequal(mySetup.burst_phase, presets[i]->burst_phase, 0.001) && + Util_almostequal(COLOURS_NTSC_specific_setup.hue, presets[i]->hue, 0.001) && + Util_almostequal(COLOURS_NTSC_setup.saturation, presets[i]->saturation, 0.001) && + Util_almostequal(COLOURS_NTSC_setup.contrast, presets[i]->contrast, 0.001) && + Util_almostequal(COLOURS_NTSC_setup.brightness, presets[i]->brightness, 0.001) && + Util_almostequal(COLOURS_NTSC_setup.gamma, presets[i]->gamma, 0.001)) + return i; } - - /* adjust decoder matrix */ - { - float sat = setup->saturation + 1; - rotate_matrix( to_rgb, sin( hue ) * sat, cos( hue ) * sat, ntsc->to_rgb ); - } - - memset( ntsc->rgb, 0, sizeof ntsc->rgb ); + return PRESET_CUSTOM; +#endif } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void NTSCFilter::ntsc_to_rgb( ntsc_to_rgb_t const* ntsc, int offset, short* out ) +void NTSCFilter::nextPreset() { - float const* kernel = &ntsc->kernel [ntsc_kernel_size / 2 - offset]; - float f0 = ntsc->composite [offset]; - float f1 = ntsc->composite [offset + 1]; - float f2 = ntsc->composite [offset + 2]; - float f3 = ntsc->composite [offset + 3]; - int x = 0; - while ( x < composite_size ) - { - #define PIXEL( get_y ) \ - {\ - float i = kernel [ 0] * f0 + kernel [-2] * f2;\ - float q = kernel [-1] * f1 + kernel [-3] * f3;\ - float y = get_y;\ - float r = y + i * ntsc->to_rgb [0] + q * ntsc->to_rgb [1];\ - float g = y + i * ntsc->to_rgb [2] + q * ntsc->to_rgb [3];\ - float b = y + i * ntsc->to_rgb [4] + q * ntsc->to_rgb [5];\ - kernel++;\ - out [0] = (int) r;\ - out [1] = (int) g;\ - out [2] = (int) b;\ - out += 3;\ - } - - PIXEL( i - ntsc->composite [x + 0] ) - PIXEL( q - ntsc->composite [x + 1] ) - PIXEL( ntsc->composite [x + 2] - i ) - PIXEL( ntsc->composite [x + 3] - q ) - x += 4; - - #undef PIXEL - } + int preset = getPreset(); + + if (preset == PRESET_CUSTOM) + preset = PRESET_COMPOSITE; + else + preset = (preset + 1) % PRESET_SIZE; + setPreset(preset); +} + +#if 0 // FIXME +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +int NTSCFilter::FILTER_NTSC_ReadConfig(char *option, char *ptr) +{ + if (strcmp(option, "FILTER_NTSC_SHARPNESS") == 0) + return Util_sscandouble(ptr, &mySetup.sharpness); + else if (strcmp(option, "FILTER_NTSC_RESOLUTION") == 0) + return Util_sscandouble(ptr, &mySetup.resolution); + else if (strcmp(option, "FILTER_NTSC_ARTIFACTS") == 0) + return Util_sscandouble(ptr, &mySetup.artifacts); + else if (strcmp(option, "FILTER_NTSC_FRINGING") == 0) + return Util_sscandouble(ptr, &mySetup.fringing); + else if (strcmp(option, "FILTER_NTSC_BLEED") == 0) + return Util_sscandouble(ptr, &mySetup.bleed); + else if (strcmp(option, "FILTER_NTSC_BURST_PHASE") == 0) + return Util_sscandouble(ptr, &mySetup.burst_phase); + else + return FALSE; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void NTSCFilter::rescale( short const* in, int count, short* out ) +void NTSCFilter::FILTER_NTSC_WriteConfig(FILE *fp) { - do - { - int const accuracy = 16; - int const unit = 1 << accuracy; - int const step = unit / 8; - int left = unit - step; - int right = step; - int n = 7; - while ( n-- ) - { - int r = (in [0] * left + in [3] * right) >> accuracy; - int g = (in [1] * left + in [4] * right) >> accuracy; - int b = (in [2] * left + in [5] * right) >> accuracy; - *out++ = r; - *out++ = g; - *out++ = b; - left -= step; - right += step; - in += 3; - } - in += 3; - } - while ( (count -= 7) > 0 ); + fprintf(fp, "FILTER_NTSC_SHARPNESS=%g\n", mySetup.sharpness); + fprintf(fp, "FILTER_NTSC_RESOLUTION=%g\n", mySetup.resolution); + fprintf(fp, "FILTER_NTSC_ARTIFACTS=%g\n", mySetup.artifacts); + fprintf(fp, "FILTER_NTSC_FRINGING=%g\n", mySetup.fringing); + fprintf(fp, "FILTER_NTSC_BLEED=%g\n", mySetup.bleed); + fprintf(fp, "FILTER_NTSC_BURST_PHASE=%g\n", mySetup.burst_phase); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void NTSCFilter::sharpen( short const* in, float level, int count, short* out ) +int NTSCFilter::FILTER_NTSC_Initialise(int *argc, char *argv[]) { - /* to do: sharpen luma only? */ - int const accuracy = 16; - int const middle = (int) (level * (1 << accuracy)); - int const side = (middle - (1 << accuracy)) >> 1; - - *out++ = *in++; - *out++ = *in++; - *out++ = *in++; - - for ( count = (count - 2) * 3; count--; in++ ) - *out++ = (in [0] * middle - in [-3] * side - in [3] * side) >> accuracy; - *out++ = *in++; - *out++ = *in++; - *out++ = *in++; -} + int i; + int j; -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -NTSCFilter::ntsc_rgb_t* NTSCFilter::gen_pixel( ntsc_to_rgb_t* ntsc, int ntsc_pos, int rescaled_pos, ntsc_rgb_t* out ) -{ - ntsc_to_rgb( ntsc, composite_border + ntsc_pos, ntsc->rgb [rgb_pad] ); - rescale( ntsc->rgb [0], rescaled_size, ntsc->rescaled [1] ); - sharpen( ntsc->rescaled [1], ntsc->sharpness, rescaled_size, ntsc->rescaled [0] ); - - { - short const* in = ntsc->rescaled [rescaled_pos]; - int n = rgb_kernel_size; - while ( n-- ) - { - *out++ = MAKE_KRGB( in [0], in [1], in [2] ); - in += 3; - } - } - return out; -} - -#if 0 // FIXME - disabled for now -/* added for Atari800, by perrym*/ -void ATARI_NTSC_DEFAULTS_Initialise(int *argc, char *argv[], atari_ntsc_setup_t *atari_ntsc_setup) -{ - int i, j; - /* Adjust default values here */ - atari_ntsc_setup->brightness = -0.1; - atari_ntsc_setup->sharpness = -0.5; - atari_ntsc_setup->saturation = -0.1; - atari_ntsc_setup->gamma_adj = -0.15; - atari_ntsc_setup->burst_phase = -0.60; - atari_ntsc_setup->saturation_ramp = 0.25; for (i = j = 1; i < *argc; i++) { - if (strcmp(argv[i], "-ntsc_hue") == 0) { - atari_ntsc_setup->hue = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_sat") == 0){ - atari_ntsc_setup->saturation = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_cont") == 0){ - atari_ntsc_setup->contrast = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_bright") == 0){ - atari_ntsc_setup->brightness = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_sharp") == 0){ - atari_ntsc_setup->sharpness = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_burst") == 0){ - atari_ntsc_setup->burst_phase = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_gauss") == 0){ - atari_ntsc_setup->gaussian_factor = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_gamma") == 0){ - atari_ntsc_setup->gamma_adj = atof(argv[++i]); - }else if (strcmp(argv[i], "-ntsc_ramp") == 0){ - atari_ntsc_setup->saturation_ramp = atof(argv[++i]); + int i_a = (i + 1 < *argc); /* is argument available? */ + int a_m = FALSE; /* error, argument missing! */ + + if (strcmp(argv[i], "-ntsc-sharpness") == 0) { + if (i_a) + mySetup.sharpness = atof(argv[++i]); + else a_m = TRUE; + } + else if (strcmp(argv[i], "-ntsc-resolution") == 0) { + if (i_a) + mySetup.resolution = atof(argv[++i]); + else a_m = TRUE; + } + else if (strcmp(argv[i], "-ntsc-artifacts") == 0) { + if (i_a) + mySetup.artifacts = atof(argv[++i]); + else a_m = TRUE; + } + else if (strcmp(argv[i], "-ntsc-fringing") == 0) { + if (i_a) + mySetup.fringing = atof(argv[++i]); + else a_m = TRUE; + } + else if (strcmp(argv[i], "-ntsc-bleed") == 0) { + if (i_a) + mySetup.bleed = atof(argv[++i]); + else a_m = TRUE; + } + else if (strcmp(argv[i], "-ntsc-burstphase") == 0) { + if (i_a) + mySetup.burst_phase = atof(argv[++i]); + else a_m = TRUE; + } + else if (strcmp(argv[i], "-ntsc-filter-preset") == 0) { + if (i_a) { + int idx = CFG_MatchTextParameter(argv[++i], preset_cfg_strings, FILTER_NTSC_PRESET_SIZE); + if (idx < 0) { + Log_print("Invalid value for -ntsc-filter-preset"); + return FALSE; + } + setPreset(idx); + } else a_m = TRUE; } else { if (strcmp(argv[i], "-help") == 0) { - Log_print("\t-ntsc_hue Set NTSC hue -1..1 (default %.2g) (-ntsc_emu only)",atari_ntsc_setup->hue); - Log_print("\t-ntsc_sat Set NTSC saturation (default %.2g) (-ntsc_emu only)",atari_ntsc_setup->saturation); - Log_print("\t-ntsc_cont Set NTSC contrast (default %.2g) (-ntsc_emu only)",atari_ntsc_setup->contrast); - Log_print("\t-ntsc_bright Set NTSC brightness (default %.2g) (-ntsc_emu only)",atari_ntsc_setup->brightness); - Log_print("\t-ntsc_sharp Set NTSC sharpness (default %.2g) (-ntsc_emu only)",atari_ntsc_setup->sharpness); - Log_print("\t-ntsc_burst Set NTSC burst phase -1..1 (artif colours)(def: %.2g)",atari_ntsc_setup->burst_phase); - Log_print("\t (-ntsc_emu only)"); - Log_print("\t-ntsc_gauss Set NTSC Gaussian factor (default %.2g)",atari_ntsc_setup->gaussian_factor); - Log_print("\t (-ntsc_emu only)"); - Log_print("\t-ntsc_gamma Set NTSC gamma adjustment (default %.2g)",atari_ntsc_setup->gamma_adj); - Log_print("\t (-ntsc_emu only)"); - Log_print("\t-ntsc_ramp Set NTSC saturation ramp factor (default %.2g)",atari_ntsc_setup->saturation_ramp); - Log_print("\t (-ntsc_emu only)"); + Log_print("\t-ntsc-sharpness Set sharpness for NTSC filter (default %.2g)", mySetup.sharpness); + Log_print("\t-ntsc-resolution Set resolution for NTSC filter (default %.2g)", mySetup.resolution); + Log_print("\t-ntsc-artifacts Set luma artifacts ratio for NTSC filter (default %.2g)", mySetup.artifacts); + Log_print("\t-ntsc-fringing Set chroma fringing ratio for NTSC filter (default %.2g)", mySetup.fringing); + Log_print("\t-ntsc-bleed Set bleed for NTSC filter (default %.2g)", mySetup.bleed); + Log_print("\t-ntsc-burstphase Set burst phase (artifact colours) for NTSC filter (default %.2g)", mySetup.burst_phase); + Log_print("\t-ntsc-filter-preset composite|svideo|rgb|monochrome"); + Log_print("\t Use one of predefined NTSC filter adjustments"); } - argv[j++] = argv[i]; } + if (a_m) { + Log_print("Missing argument for '%s'", argv[i]); + return FALSE; + } } *argc = j; + + return TRUE; } #endif +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void NTSCFilter::updateYIQTable(double yiq_table[768], double start_angle) +{ + const double start_saturation = 0.0; // calculated internally + const double gamma = 1; // 1 - COLOURS_NTSC_setup.gamma / 2.0; + unsigned char *ext_ptr = NULL; //FIXME COLOURS_NTSC_external.palette; + int n; + + start_angle = - ((213.0f) * M_PI / 180.0f) - start_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; +#if 0 + /* Optionally adjust external palette. */ + if (COLOURS_NTSC_external.adjust) { + y = pow(y, gamma); + 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; + } +#endif + *yiq_table++ = y; + *yiq_table++ = i; + *yiq_table++ = q; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +atari_ntsc_setup_t const * const NTSCFilter::presets[NTSCFilter::PRESET_SIZE] = { + &atari_ntsc_composite, + &atari_ntsc_svideo, + &atari_ntsc_rgb, + &atari_ntsc_monochrome +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static char const * const preset_cfg_strings[NTSCFilter::PRESET_SIZE] = { + "COMPOSITE", + "SVIDEO", + "RGB", + "MONOCHROME" +}; + #endif // DISPLAY_TV diff --git a/src/common/tv_filters/NTSCFilter.hxx b/src/common/tv_filters/NTSCFilter.hxx index eae842b13..f372e75a1 100644 --- a/src/common/tv_filters/NTSCFilter.hxx +++ b/src/common/tv_filters/NTSCFilter.hxx @@ -24,21 +24,27 @@ #include "bspf.hxx" #include "Array.hxx" +#include "atari_ntsc.h" -// Although the blitter supports 15 and 16 bit, we always use 16-bit -#ifndef ATARI_NTSC_RGB_BITS - #define ATARI_NTSC_RGB_BITS 16 -#endif - +// Limits for the adjustable values. +#define FILTER_NTSC_SHARPNESS_MIN -1.0 +#define FILTER_NTSC_SHARPNESS_MAX 1.0 +#define FILTER_NTSC_RESOLUTION_MIN -1.0 +#define FILTER_NTSC_RESOLUTION_MAX 1.0 +#define FILTER_NTSC_ARTIFACTS_MIN -1.0 +#define FILTER_NTSC_ARTIFACTS_MAX 1.0 +#define FILTER_NTSC_FRINGING_MIN -1.0 +#define FILTER_NTSC_FRINGING_MAX 1.0 +#define FILTER_NTSC_BLEED_MIN -1.0 +#define FILTER_NTSC_BLEED_MAX 1.0 +#define FILTER_NTSC_BURST_PHASE_MIN -1.0 +#define FILTER_NTSC_BURST_PHASE_MAX 1.0 /** - This class is based on the Blargg NTSC filter code from Atari800MacX. + This class is based on the Blargg NTSC filter code from Atari800, + and is derived from 'filter_ntsc.(h|c)'. - Original code based on implementation from http://www.slack.net/~ant - Based on algorithm by NewRisingSun - License note by Perry: Expat License. - http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses - "This is a simple, permissive non-copyleft free software license, compatible with the GNU GPL." + Original code based on implementation from http://www.slack.net/~ant. Atari TIA NTSC composite video to RGB emulator/blitter. */ @@ -48,112 +54,61 @@ class NTSCFilter NTSCFilter(); virtual ~NTSCFilter(); + /* Set/get one of the available preset adjustments: Composite, S-Video, RGB, + Monochrome. */ + enum { + PRESET_COMPOSITE, + PRESET_SVIDEO, + PRESET_RGB, + PRESET_MONOCHROME, + PRESET_CUSTOM, + /* Number of "normal" (not including CUSTOM) values in enumerator */ + PRESET_SIZE = PRESET_CUSTOM + }; + public: - /** - Cycle through each available NTSC preset mode - @return A message explaining the current NTSC preset mode + /* Restores default values for NTSC-filter-specific colour controls. + updateFilter should be called afterwards to apply changes. */ + void restoreDefaults(); + + /* updateFilter should be called afterwards these functions to apply changes. */ + void setPreset(int preset); + int getPreset(); + void nextPreset(); + +#if 0 // FIXME + /* Read/write to configuration file. */ + int FILTER_NTSC_ReadConfig(char *option, char *ptr); + void FILTER_NTSC_WriteConfig(FILE *fp); + + /* NTSC filter initialisation and processing of command-line arguments. */ + int FILTER_NTSC_Initialise(int *argc, char *argv[]); +#endif + private: + /* Reinitialises the an NTSC filter. Should be called after changing + palette setup or loading/unloading an external palette. */ + void updateFilter(); + + // The following function is originally from colours_ntsc. + /* 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). */ - const string& next(); - - /* Blit one or more scanlines of Atari 8-bit palette values to 16-bit 5-6-5 RGB output. - For every 7 output pixels, reads approximately 4 source pixels. Use constants below for - definite input and output pixel counts. */ - void atari_ntsc_blit( unsigned char const* atari_in, long in_pitch, - int out_width, int out_height, unsigned short* rgb_out, long out_pitch ); - - // Atari800 Initialise function by perrym - // void ATARI_NTSC_DEFAULTS_Initialise(int *argc, char *argv[], atari_ntsc_setup_t *atari_ntsc_setup); + static void updateYIQTable(double yiq_table[768], double start_angle); private: - /* Picture parameters, ranging from -1.0 to 1.0 where 0.0 is normal. To easily - clear all fields, make it a static object then set whatever fields you want: - static snes_ntsc_setup_t setup; - setup.hue = ... - */ - struct atari_ntsc_setup_t - { - float hue; - float saturation; - float contrast; - float brightness; - float sharpness; - float burst_phase; // not in radians; -1.0 = -180 degrees, 1.0 = +180 degrees - float gaussian_factor; - float gamma_adj; // gamma adjustment - float saturation_ramp; // lower saturation for higher luma values - }; + // Pointer to the NTSC filter structure + atari_ntsc_t myFilter; - enum { - atari_ntsc_entry_size = 56, - atari_ntsc_color_count = 256, - - // Useful values to use for output width and number of input pixels read - atari_ntsc_min_out_width = 570, // minimum width that doesn't cut off active area - atari_ntsc_min_in_width = 320, - atari_ntsc_full_out_width = 598, // room for 8-pixel left & right overscan borders - atari_ntsc_full_in_width = 336, - - // Originally present in .c file - composite_border = 6, - center_offset = 1, - alignment_count = 4, // different pixel alignments with respect to yiq quads */ - rgb_kernel_size = atari_ntsc_entry_size / alignment_count, - - // Originally present in .c file - composite_size = composite_border + 8 + composite_border, - rgb_pad = (center_offset + composite_border + 7) / 8 * 8 - center_offset - composite_border, - rgb_size = (rgb_pad + composite_size + 7) / 8 * 8, - rescaled_size = rgb_size / 8 * 7, - ntsc_kernel_size = composite_size * 2 - }; - typedef uInt32 ntsc_rgb_t; - - // Caller must allocate space for blitter data, which uses 56 KB of memory. - struct atari_ntsc_t - { - ntsc_rgb_t table [atari_ntsc_color_count] [atari_ntsc_entry_size]; - }; - - // Originall present in .c file - struct ntsc_to_rgb_t - { - float composite [composite_size]; - float to_rgb [6]; - float brightness; - float contrast; - float sharpness; - short rgb [rgb_size] [3]; - short rescaled [rescaled_size + 1] [3]; /* extra space for sharpen */ - float kernel [ntsc_kernel_size]; - }; - - atari_ntsc_t* myNTSCEmu; + // Contains controls used to adjust the palette in the NTSC filter + atari_ntsc_setup_t mySetup; int myCurrentModeNum; Common::Array myModeList; - private: - /* Initialize and adjust parameters. Can be called multiple times on the same - atari_ntsc_t object. */ - void atari_ntsc_init( atari_ntsc_setup_t const* setup ); - - static void rotate_matrix( float const* in, float s, float c, float* out ); - static void ntsc_to_rgb_init( ntsc_to_rgb_t* ntsc, atari_ntsc_setup_t const* setup, float hue ); - - /* Convert NTSC composite signal to RGB, where composite signal contains - only four non-zero samples beginning at offset */ - static void ntsc_to_rgb( ntsc_to_rgb_t const* ntsc, int offset, short* out ); - - /* Rescale pixels to NTSC aspect ratio using linear interpolation, - with 7 output pixels for every 8 input pixels, linear interpolation */ - static void rescale( short const* in, int count, short* out ); - - /* Sharpen image using (level-1)/2, level, (level-1)/2 convolution kernel */ - static void sharpen( short const* in, float level, int count, short* out ); - - /* Generate pixel and capture into table */ - static ntsc_rgb_t* gen_pixel( ntsc_to_rgb_t* ntsc, int ntsc_pos, int rescaled_pos, ntsc_rgb_t* out ); + static atari_ntsc_setup_t const * const presets[PRESET_SIZE]; + static char const * const preset_cfg_strings[PRESET_SIZE]; }; #endif // DISPLAY_TV diff --git a/src/common/tv_filters/atari_ntsc.c b/src/common/tv_filters/atari_ntsc.c new file mode 100644 index 000000000..485166757 --- /dev/null +++ b/src/common/tv_filters/atari_ntsc.c @@ -0,0 +1,397 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2012 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id$ +//============================================================================ + +/* Based on nes_ntsc 0.2.2. http://www.slack.net/~ant/ */ + +#ifdef DISPLAY_TV + +#include "atari_ntsc.h" + +/* Copyright (C) 2006-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +/* Atari change: removal and addition of structure fields. + Values of resolution and sharpness adjusted to make NTSC artifacts look better. */ +atari_ntsc_setup_t const atari_ntsc_monochrome = { 0, -1, 0, 0, -.3, .3, .2, -.2, -.2, -1, 0, 0, 0, 0. }; +atari_ntsc_setup_t const atari_ntsc_composite = { 0, 0, 0, 0, -.5, .3, -.1, 0, 0, 0, 0, 0, 0, 0. }; +atari_ntsc_setup_t const atari_ntsc_svideo = { 0, 0, 0, 0, -.3, .3, .2, -1, -1, 0, 0, 0, 0, 0. }; +atari_ntsc_setup_t const atari_ntsc_rgb = { 0, 0, 0, 0, -.3, .3, .7, -1, -1, -1, 0, 0, 0, 0. }; + +#define alignment_count 4 +#define burst_count 1 +#define rescale_in 8 +#define rescale_out 7 + +#define artifacts_mid 1.0f +#define fringing_mid 1.0f +/* Atari change: default palette is already at correct hue. +#define std_decoder_hue -15 */ +#define std_decoder_hue 0 + +/* Atari change: only one palette - remove base_palete field. */ +#define STD_HUE_CONDITION( setup ) !(setup->palette) + +#include "atari_ntsc_impl.h" + +/* Atari change: adapted to 4/7 pixel ratio. */ +/* 4 input pixels -> 8 composite samples */ +pixel_info_t const atari_ntsc_pixels [alignment_count] = { + { PIXEL_OFFSET( -6, -6 ), { 0, 0, 1, 1 } }, + { PIXEL_OFFSET( -4, -4 ), { 0, 0, 1, 1 } }, + { PIXEL_OFFSET( -2, -2 ), { 0, 0, 1, 1 } }, + { PIXEL_OFFSET( 0, 0 ), { 0, 0, 1, 1 } }, +}; + +/* Atari change: no alternating burst phases - removed merge_kernel_fields function. */ + +static void correct_errors( atari_ntsc_rgb_t color, atari_ntsc_rgb_t* out ) +{ + int n; + for ( n = burst_count; n; --n ) + { + unsigned i; + for ( i = 0; i < rgb_kernel_size / 2; i++ ) + { + /* Atari change: adapted to 4/7 pixel ratio */ + atari_ntsc_rgb_t error = color - + out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] - out[(i+8)%14+42] - + out [i + 7] - out [i + 5 +14] - out [i + 3 +28] - out [ i+1 +42]; + DISTRIBUTE_ERROR( i+1+42, i+3+28, i+5+14, i+7 ); + } + out += alignment_count * rgb_kernel_size; + } +} + +void atari_ntsc_init( atari_ntsc_t* ntsc, atari_ntsc_setup_t const* setup ) +{ + /* Atari change: no alternating burst phases - remove merge_fields variable. */ + int entry; + init_t impl; + /* Atari change: NES palette generation and reading removed. + Atari palette generation is located in colours_ntsc.c, and colours are read + from setup->yiq_palette. */ + + if ( !setup ) + setup = &atari_ntsc_composite; + + init( &impl, setup ); + + /* Atari change: no alternating burst phases - remove code for merge_fields. */ + + for ( entry = 0; entry < atari_ntsc_palette_size; entry++ ) + { + /* Atari change: Instead of palette generation, load colours + from setup->yiq_palette. */ + double y; + double i; + double q; + + { + double *yiq_ptr = setup->yiq_palette + 3 * entry; + y = *yiq_ptr++; + i = *yiq_ptr++; + q = *yiq_ptr++; + } + + i *= rgb_unit; + q *= rgb_unit; + y *= rgb_unit; + y += rgb_offset; + + /* Generate kernel */ + { + int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g ); + /* blue tends to overflow, so clamp it */ + atari_ntsc_rgb_t rgb = PACK_RGB( r, g, (b < 0x3E0 ? b: 0x3E0) ); + + if ( setup->palette_out ) + RGB_PALETTE_OUT( rgb, &setup->palette_out [entry * 3] ); + + if ( ntsc ) + { + atari_ntsc_rgb_t* kernel = ntsc->table [entry]; + gen_kernel( &impl, y, i, q, kernel ); + /* Atari change: no alternating burst phases - remove code for merge_fields. */ + correct_errors( rgb, kernel ); + } + } + } +} + +#ifndef ATARI_NTSC_NO_BLITTERS + +/* Atari change: no alternating burst phases - remove burst_phase parameter. + Also removed the atari_ntsc_blit function and added specific blitters for various + pixel formats. */ + +#include + +#if USHRT_MAX == 0xFFFF + typedef unsigned short atari_ntsc_out16_t; +#else + #error "Need 16-bit int type" +#endif + +#if UINT_MAX == 0xFFFFFFFF + typedef unsigned int atari_ntsc_out32_t; +#elif ULONG_MAX == 0xFFFFFFFF + typedef unsigned long atari_ntsc_out32_t; +#else + #error "Need 32-bit int type" +#endif + +void atari_ntsc_blit_rgb16( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* input, long in_row_width, + int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 1) / atari_ntsc_in_chunk; + for ( ; in_height; --in_height ) + { + ATARI_NTSC_IN_T const* line_in = input; + /* Atari change: no alternating burst phases - remove burst_phase parameter; adjust to 4/7 pixel ratio. */ + ATARI_NTSC_BEGIN_ROW( ntsc, + atari_ntsc_black, atari_ntsc_black, atari_ntsc_black, ATARI_NTSC_ADJ_IN( *line_in ) ); + atari_ntsc_out16_t* restrict line_out = (atari_ntsc_out16_t*) rgb_out; + int n; + ++line_in; + + for ( n = chunk_count; n; --n ) + { + /* order of input and output pixels must not be altered */ + ATARI_NTSC_COLOR_IN( 0, ATARI_NTSC_ADJ_IN( line_in [0] ) ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_RGB16 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + ATARI_NTSC_COLOR_IN( 1, ATARI_NTSC_ADJ_IN( line_in [1] ) ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_RGB16 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + ATARI_NTSC_COLOR_IN( 2, ATARI_NTSC_ADJ_IN( line_in [2] ) ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_RGB16 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + ATARI_NTSC_COLOR_IN( 3, ATARI_NTSC_ADJ_IN( line_in [3] ) ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + line_in += 4; + line_out += 7; + } + + /* finish final pixels */ + ATARI_NTSC_COLOR_IN( 0, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_RGB16 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + ATARI_NTSC_COLOR_IN( 1, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_RGB16 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + ATARI_NTSC_COLOR_IN( 2, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_RGB16 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + ATARI_NTSC_COLOR_IN( 3, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_RGB16 ); + + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +void atari_ntsc_blit_bgr16( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* input, long in_row_width, + int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 1) / atari_ntsc_in_chunk; + for ( ; in_height; --in_height ) + { + ATARI_NTSC_IN_T const* line_in = input; + /* Atari change: no alternating burst phases - remove burst_phase parameter; adjust to 4/7 pixel ratio. */ + ATARI_NTSC_BEGIN_ROW( ntsc, + atari_ntsc_black, atari_ntsc_black, atari_ntsc_black, ATARI_NTSC_ADJ_IN( *line_in ) ); + atari_ntsc_out16_t* restrict line_out = (atari_ntsc_out16_t*) rgb_out; + int n; + ++line_in; + + for ( n = chunk_count; n; --n ) + { + /* order of input and output pixels must not be altered */ + ATARI_NTSC_COLOR_IN( 0, ATARI_NTSC_ADJ_IN( line_in [0] ) ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_BGR16 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + ATARI_NTSC_COLOR_IN( 1, ATARI_NTSC_ADJ_IN( line_in [1] ) ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_BGR16 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + ATARI_NTSC_COLOR_IN( 2, ATARI_NTSC_ADJ_IN( line_in [2] ) ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_BGR16 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + ATARI_NTSC_COLOR_IN( 3, ATARI_NTSC_ADJ_IN( line_in [3] ) ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + line_in += 4; + line_out += 7; + } + + /* finish final pixels */ + ATARI_NTSC_COLOR_IN( 0, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_BGR16 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + ATARI_NTSC_COLOR_IN( 1, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_BGR16 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + ATARI_NTSC_COLOR_IN( 2, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_BGR16 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + ATARI_NTSC_COLOR_IN( 3, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_BGR16 ); + + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +void atari_ntsc_blit_argb32( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* input, long in_row_width, + int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 1) / atari_ntsc_in_chunk; + for ( ; in_height; --in_height ) + { + ATARI_NTSC_IN_T const* line_in = input; + /* Atari change: no alternating burst phases - remove burst_phase parameter; adjust to 4/7 pixel ratio. */ + ATARI_NTSC_BEGIN_ROW( ntsc, + atari_ntsc_black, atari_ntsc_black, atari_ntsc_black, ATARI_NTSC_ADJ_IN( *line_in ) ); + atari_ntsc_out32_t* restrict line_out = (atari_ntsc_out32_t*) rgb_out; + int n; + ++line_in; + + for ( n = chunk_count; n; --n ) + { + /* order of input and output pixels must not be altered */ + ATARI_NTSC_COLOR_IN( 0, ATARI_NTSC_ADJ_IN( line_in [0] ) ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + ATARI_NTSC_COLOR_IN( 1, ATARI_NTSC_ADJ_IN( line_in [1] ) ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + ATARI_NTSC_COLOR_IN( 2, ATARI_NTSC_ADJ_IN( line_in [2] ) ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + ATARI_NTSC_COLOR_IN( 3, ATARI_NTSC_ADJ_IN( line_in [3] ) ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + line_in += 4; + line_out += 7; + } + + /* finish final pixels */ + ATARI_NTSC_COLOR_IN( 0, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + ATARI_NTSC_COLOR_IN( 1, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + ATARI_NTSC_COLOR_IN( 2, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + ATARI_NTSC_COLOR_IN( 3, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_ARGB32 ); + + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +void atari_ntsc_blit_bgra32( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* input, long in_row_width, + int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 1) / atari_ntsc_in_chunk; + for ( ; in_height; --in_height ) + { + ATARI_NTSC_IN_T const* line_in = input; + /* Atari change: no alternating burst phases - remove burst_phase parameter; adjust to 4/7 pixel ratio. */ + ATARI_NTSC_BEGIN_ROW( ntsc, + atari_ntsc_black, atari_ntsc_black, atari_ntsc_black, ATARI_NTSC_ADJ_IN( *line_in ) ); + atari_ntsc_out32_t* restrict line_out = (atari_ntsc_out32_t*) rgb_out; + int n; + ++line_in; + + for ( n = chunk_count; n; --n ) + { + /* order of input and output pixels must not be altered */ + ATARI_NTSC_COLOR_IN( 0, ATARI_NTSC_ADJ_IN( line_in [0] ) ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + ATARI_NTSC_COLOR_IN( 1, ATARI_NTSC_ADJ_IN( line_in [1] ) ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + ATARI_NTSC_COLOR_IN( 2, ATARI_NTSC_ADJ_IN( line_in [2] ) ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + ATARI_NTSC_COLOR_IN( 3, ATARI_NTSC_ADJ_IN( line_in [3] ) ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + line_in += 4; + line_out += 7; + } + + /* finish final pixels */ + ATARI_NTSC_COLOR_IN( 0, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 0, line_out [0], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + ATARI_NTSC_RGB_OUT( 1, line_out [1], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + ATARI_NTSC_COLOR_IN( 1, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 2, line_out [2], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + ATARI_NTSC_RGB_OUT( 3, line_out [3], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + ATARI_NTSC_COLOR_IN( 2, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 4, line_out [4], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + ATARI_NTSC_RGB_OUT( 5, line_out [5], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + ATARI_NTSC_COLOR_IN( 3, atari_ntsc_black ); + ATARI_NTSC_RGB_OUT( 6, line_out [6], ATARI_NTSC_RGB_FORMAT_BGRA32 ); + + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +#endif + +#endif // DISPLAY_TV diff --git a/src/common/tv_filters/atari_ntsc.h b/src/common/tv_filters/atari_ntsc.h new file mode 100644 index 000000000..657bb6cff --- /dev/null +++ b/src/common/tv_filters/atari_ntsc.h @@ -0,0 +1,268 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2012 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id$ +//============================================================================ + +#ifdef DISPLAY_TV + +/* Atari TIA, CTIA, GTIA and MARIA NTSC video filter */ + +/* based on nes_ntsc 0.2.2 */ +#ifndef ATARI_NTSC_H +#define ATARI_NTSC_H + +// The following come from "atari_ntsc_config.h", but since we'll eventually +// be using a custom blitter, most of the items will be redundant +////////////////////////////////////////////////////////////////////////////// +/* The following affect the built-in blitter only; a custom blitter can +handle things however it wants. */ + +/* Bits per pixel of output. Can be 15, 16, 32, or 24 (same as 32). */ +#define ATARI_NTSC_OUT_DEPTH 16 + +/* Type of input pixel values. You'll probably use unsigned short +if you enable emphasis above. */ +#define ATARI_NTSC_IN_T unsigned char + +/* Each raw pixel input value is passed through this. You might want to mask +the pixel index if you use the high bits as flags, etc. */ +#define ATARI_NTSC_ADJ_IN( in ) in +////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus + extern "C" { +#endif + +/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown +in parenthesis and should remain fairly stable in future versions. */ +typedef struct atari_ntsc_setup_t +{ + /* Basic parameters */ + double hue; /* -1 = -180 degrees +1 = +180 degrees */ + double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */ + double contrast; /* -1 = dark (0.5) +1 = light (1.5) */ + double brightness; /* -1 = dark (0.5) +1 = light (1.5) */ + double sharpness; /* edge contrast enhancement/blurring */ + + /* Advanced parameters */ + double gamma; /* -1 = dark (1.5) +1 = light (0.5) */ + double resolution; /* image resolution */ + double artifacts; /* artifacts caused by color changes */ + double fringing; /* color artifacts caused by brightness changes */ + double bleed; /* color bleed (color resolution reduction) */ +/* Atari change: no alternating burst phases - remove merge_fields field. */ + float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */ + + unsigned char* palette_out; /* optional RGB palette out, 3 bytes per color */ + + /* You can replace the standard NES color generation with an RGB palette. The + first replaces all color generation, while the second replaces only the core + 64-color generation and does standard color emphasis calculations on it. */ + unsigned char const* palette;/* optional 256-entry RGB palette in, 3 bytes per color */ +/* Atari change: only one palette - remove base_palette field. */ + +/* Atari change: additional setup fields */ + double burst_phase; /* Phase at which colorburst signal is turned on; + this defines colors of artifacts. + In radians; -1.0 = -180 degrees, 1.0 = +180 degrees */ + double *yiq_palette; +} atari_ntsc_setup_t; + +/* Video format presets */ +extern atari_ntsc_setup_t const atari_ntsc_composite; /* color bleeding + artifacts */ +extern atari_ntsc_setup_t const atari_ntsc_svideo; /* color bleeding only */ +extern atari_ntsc_setup_t const atari_ntsc_rgb; /* crisp image */ +extern atari_ntsc_setup_t const atari_ntsc_monochrome;/* desaturated + artifacts */ + +enum { atari_ntsc_palette_size = 256 }; + +/* Initializes and adjusts parameters. Can be called multiple times on the same +atari_ntsc_t object. Can pass NULL for either parameter. */ +typedef struct atari_ntsc_t atari_ntsc_t; +void atari_ntsc_init( atari_ntsc_t* ntsc, atari_ntsc_setup_t const* setup ); + +/* Filters one or more rows of pixels. Input pixels are 6/9-bit palette indicies. +In_row_width is the number of pixels to get to the next input row. Out_pitch +is the number of *bytes* to get to the next output row. Output pixel format +is set by ATARI_NTSC_OUT_DEPTH (defaults to 16-bit RGB). */ +/* Atari change: no alternating burst phases - remove burst_phase parameter. + Also removed the atari_ntsc_blit function and added specific blitters for various + pixel formats. */ +void atari_ntsc_blit_rgb16( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* atari_in, + long in_row_width, int in_width, int in_height, + void* rgb_out, long out_pitch ); +void atari_ntsc_blit_bgr16( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* atari_in, + long in_row_width, int in_width, int in_height, + void* rgb_out, long out_pitch ); +void atari_ntsc_blit_argb32( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* atari_in, + long in_row_width, int in_width, int in_height, + void* rgb_out, long out_pitch ); +void atari_ntsc_blit_bgra32( atari_ntsc_t const* ntsc, ATARI_NTSC_IN_T const* atari_in, + long in_row_width, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +/* Number of output pixels written by blitter for given input width. Width might +be rounded down slightly; use ATARI_NTSC_IN_WIDTH() on result to find rounded +value. Guaranteed not to round 256 down at all. */ +#define ATARI_NTSC_OUT_WIDTH( in_width ) \ + ((((in_width) - 1) / atari_ntsc_in_chunk + 1)* atari_ntsc_out_chunk) + +/* Number of input pixels that will fit within given output width. Might be +rounded down slightly; use ATARI_NTSC_OUT_WIDTH() on result to find rounded +value. */ +#define ATARI_NTSC_IN_WIDTH( out_width ) \ + (((out_width) / atari_ntsc_out_chunk - 1) * atari_ntsc_in_chunk + 1) + + +/* Interface for user-defined custom blitters. */ + +enum { atari_ntsc_in_chunk = 4 }; /* number of input pixels read per chunk */ +enum { atari_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */ +enum { atari_ntsc_black = 0 }; /* palette index for black */ +enum { atari_ntsc_burst_count = 1 }; /* burst phase cycles through 0, 1, and 2 */ + +/* Begins outputting row and starts three pixels. First pixel will be cut off a bit. +Use nes_ntsc_black for unused pixels. Declares variables, so must be before first +statement in a block (unless you're using C++). */ +/* Atari change: no alternating burst phases; adapted to 4/7 pixel ratio. */ +#define ATARI_NTSC_BEGIN_ROW( ntsc, pixel0, pixel1, pixel2, pixel3 ) \ + char const* const ktable = \ + (char const*) (ntsc)->table [0];\ + ATARI_NTSC_BEGIN_ROW_8_( pixel0, pixel1, pixel2, pixel3, ATARI_NTSC_ENTRY_, ktable ) + + +/* Begins input pixel */ +#define ATARI_NTSC_COLOR_IN( in_index, color_in ) \ + ATARI_NTSC_COLOR_IN_( in_index, color_in, ATARI_NTSC_ENTRY_, ktable ) + +/* Generates output pixel. Bits can be 24, 16, 15, 32 (treated as 24), or 0: +24: RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8 RGB) +16: RRRRRGGG GGGBBBBB (5-6-5 RGB) +15: RRRRRGG GGGBBBBB (5-5-5 RGB) + 0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */ +#define ATARI_NTSC_RGB_OUT( index, rgb_out, bits ) \ + ATARI_NTSC_RGB_OUT_14_( index, rgb_out, bits, 0 ) + + +/* private */ +enum { atari_ntsc_entry_size = 56 }; +typedef unsigned long atari_ntsc_rgb_t; +struct atari_ntsc_t { + atari_ntsc_rgb_t table [atari_ntsc_palette_size] [atari_ntsc_entry_size]; +}; +enum { atari_ntsc_burst_size = atari_ntsc_entry_size / atari_ntsc_burst_count }; + +#define ATARI_NTSC_ENTRY_( ktable, n ) \ + (atari_ntsc_rgb_t const*) (ktable + (n) * (atari_ntsc_entry_size * sizeof (atari_ntsc_rgb_t))) + +/* deprecated */ +#define ATARI_NTSC_RGB24_OUT( x, out ) ATARI_NTSC_RGB_OUT( x, out, 24 ) +#define ATARI_NTSC_RGB16_OUT( x, out ) ATARI_NTSC_RGB_OUT( x, out, 16 ) +#define ATARI_NTSC_RGB15_OUT( x, out ) ATARI_NTSC_RGB_OUT( x, out, 15 ) +#define ATARI_NTSC_RAW_OUT( x, out ) ATARI_NTSC_RGB_OUT( x, out, 0 ) + +enum { atari_ntsc_min_in_width = 320 }; /* minimum width that doesn't cut off active area */ +enum { atari_ntsc_min_out_width = ATARI_NTSC_OUT_WIDTH( atari_ntsc_min_in_width ) }; + +enum { atari_ntsc_640_in_width = 336 }; /* room for 8-pixel left & right overscan borders */ +enum { atari_ntsc_640_out_width = ATARI_NTSC_OUT_WIDTH( atari_ntsc_640_in_width ) }; +enum { atari_ntsc_640_overscan_left = 8 }; +enum { atari_ntsc_640_overscan_right = atari_ntsc_640_in_width - atari_ntsc_min_in_width - atari_ntsc_640_overscan_left }; + +enum { atari_ntsc_full_in_width = 384 }; /* room for full overscan */ +enum { atari_ntsc_full_out_width = ATARI_NTSC_OUT_WIDTH( atari_ntsc_full_in_width ) }; +enum { atari_ntsc_full_overscan_left = 32 }; +enum { atari_ntsc_full_overscan_right = atari_ntsc_full_in_width - atari_ntsc_min_in_width - atari_ntsc_full_overscan_left }; + +/* common 4->7 ntsc macros */ +/* Atari change: adapted to 4/7 pixel ratio. */ +#define ATARI_NTSC_BEGIN_ROW_8_( pixel0, pixel1, pixel2, pixel3, ENTRY, table ) \ + unsigned const atari_ntsc_pixel0_ = (pixel0);\ + atari_ntsc_rgb_t const* kernel0 = ENTRY( table, atari_ntsc_pixel0_ );\ + unsigned const atari_ntsc_pixel1_ = (pixel1);\ + atari_ntsc_rgb_t const* kernel1 = ENTRY( table, atari_ntsc_pixel1_ );\ + unsigned const atari_ntsc_pixel2_ = (pixel2);\ + atari_ntsc_rgb_t const* kernel2 = ENTRY( table, atari_ntsc_pixel2_ );\ + unsigned const atari_ntsc_pixel3_ = (pixel3);\ + atari_ntsc_rgb_t const* kernel3 = ENTRY( table, atari_ntsc_pixel3_ );\ + atari_ntsc_rgb_t const* kernelx0;\ + atari_ntsc_rgb_t const* kernelx1 = kernel0;\ + atari_ntsc_rgb_t const* kernelx2 = kernel0;\ + atari_ntsc_rgb_t const* kernelx3 = kernel0 + +/* Atari change: adapted to 4/7 pixel ratio. */ +#define ATARI_NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\ + atari_ntsc_rgb_t raw_ =\ + kernel0 [x ] + kernel1 [(x+5)%7+14] + kernel2 [(x+3)%7+28] + kernel3 [(x+1)%7+42] +\ + kernelx0 [(x+7)%14] + kernelx1 [(x+5)%7+21] + kernelx2 [(x+3)%7+35] + kernelx3 [(x+1)%7+49];\ + ATARI_NTSC_CLAMP_( raw_, shift );\ + ATARI_NTSC_RGB_OUT_( rgb_out, bits, shift );\ +} + +/* common ntsc macros */ +#define atari_ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1)) +#define atari_ntsc_clamp_mask (atari_ntsc_rgb_builder * 3 / 2) +#define atari_ntsc_clamp_add (atari_ntsc_rgb_builder * 0x101) +#define ATARI_NTSC_CLAMP_( io, shift ) {\ + atari_ntsc_rgb_t sub = (io) >> (9-(shift)) & atari_ntsc_clamp_mask;\ + atari_ntsc_rgb_t clamp = atari_ntsc_clamp_add - sub;\ + io |= clamp;\ + clamp -= sub;\ + io &= clamp;\ +} + +#define ATARI_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\ + unsigned color_;\ + kernelx##index = kernel##index;\ + kernel##index = (color_ = (color), ENTRY( table, color_ ));\ +} + +/* Atari change: modified ATARI_NTSC_RGB_OUT_ so its BITS parameter is + no longer a straight number of bits, but an enumerated value. Then + added a few additional bit formats. Also added the ATARI_NTSC_RGB_FORMAT + enumerated values. */ +enum { + ATARI_NTSC_RGB_FORMAT_RGB16, + ATARI_NTSC_RGB_FORMAT_BGR16, + ATARI_NTSC_RGB_FORMAT_ARGB32, + ATARI_NTSC_RGB_FORMAT_BGRA32, + ATARI_NTSC_RGB_FORMAT_RGB15 +}; + +/* x is always zero except in snes_ntsc library */ +#define ATARI_NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + if ( bits == ATARI_NTSC_RGB_FORMAT_RGB16 )\ + rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\ + else if ( bits == ATARI_NTSC_RGB_FORMAT_BGR16 )\ + rgb_out = (raw_>>(24-x)& 0x001F)|(raw_>>(8-x)&0x07E0)|(raw_<<(7+x)&0xF800);\ + else if ( bits == ATARI_NTSC_RGB_FORMAT_ARGB32 )\ + rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF) | 0xFF000000;\ + else if ( bits == ATARI_NTSC_RGB_FORMAT_BGRA32 )\ + rgb_out = (raw_>>(13-x)&0xFF00)|(raw_<<(5+x)&0xFF0000)|(raw_<<(23+x)&0xFF000000) | 0xFF;\ + else if ( bits == ATARI_NTSC_RGB_FORMAT_RGB15 )\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ + else if ( bits == 0 )\ + rgb_out = raw_ << x;\ +} + +#ifdef __cplusplus + } +#endif + +#endif + +#endif // DISPLAY_TV diff --git a/src/common/tv_filters/atari_ntsc_impl.h b/src/common/tv_filters/atari_ntsc_impl.h new file mode 100644 index 000000000..e04dd16b8 --- /dev/null +++ b/src/common/tv_filters/atari_ntsc_impl.h @@ -0,0 +1,478 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2012 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id$ +//============================================================================ + +#ifdef DISPLAY_TV + +/* Based on nes_ntsc 0.2.2. http://www.slack.net/~ant/ */ + +/* Common implementation of NTSC filters */ + +#include +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#define DISABLE_CORRECTION 0 + +#undef PI +#define PI 3.14159265358979323846f + +#ifndef LUMA_CUTOFF + #define LUMA_CUTOFF 0.20 +#endif +#ifndef gamma_size + #define gamma_size 1 +#endif +#ifndef rgb_bits + #define rgb_bits 8 +#endif +#ifndef artifacts_max + #define artifacts_max (artifacts_mid * 1.5f) +#endif +#ifndef fringing_max + #define fringing_max (fringing_mid * 2) +#endif +#ifndef STD_HUE_CONDITION + #define STD_HUE_CONDITION( setup ) 1 +#endif + +#define ext_decoder_hue (std_decoder_hue + 15) +#define rgb_unit (1 << rgb_bits) +#define rgb_offset (rgb_unit * 2 + 0.5f) + +enum { burst_size = atari_ntsc_entry_size / burst_count }; +enum { kernel_half = 16 }; +enum { kernel_size = kernel_half * 2 + 1 }; + +typedef struct init_t +{ + float to_rgb [burst_count * 6]; + float to_float [gamma_size]; + float contrast; + float brightness; + float artifacts; + float fringing; + float kernel [rescale_out * kernel_size * 2]; +} init_t; + +#define ROTATE_IQ( i, q, sin_b, cos_b ) {\ + float t;\ + t = i * cos_b - q * sin_b;\ + q = i * sin_b + q * cos_b;\ + i = t;\ +} + +static void init_filters( init_t* impl, atari_ntsc_setup_t const* setup ) +{ +#if rescale_out > 1 + float kernels [kernel_size * 2]; +#else + float* const kernels = impl->kernel; +#endif + + /* generate luma (y) filter using sinc kernel */ + { + /* sinc with rolloff (dsf) */ + float const rolloff = 1 + (float) setup->sharpness * (float) 0.032; + float const maxh = 32; + float const pow_a_n = (float) pow( rolloff, maxh ); + float sum; + int i; + /* quadratic mapping to reduce negative (blurring) range */ + float to_angle = (float) setup->resolution + 1; + to_angle = PI / maxh * (float) LUMA_CUTOFF * (to_angle * to_angle + 1); + + kernels [kernel_size * 3 / 2] = maxh; /* default center value */ + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = i - kernel_half; + float angle = x * to_angle; + /* instability occurs at center point with rolloff very close to 1.0 */ + if ( x || pow_a_n > (float) 1.056 || pow_a_n < (float) 0.981 ) + { + float rolloff_cos_a = rolloff * (float) cos( angle ); + float num = 1 - rolloff_cos_a - + pow_a_n * (float) cos( maxh * angle ) + + pow_a_n * rolloff * (float) cos( (maxh - 1) * angle ); + float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; + float dsf = num / den; + kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - (float) 0.5; + } + } + + /* apply blackman window and find sum */ + sum = 0; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + float x = PI * 2 / (kernel_half * 2) * i; + float blackman = 0.42f - 0.5f * (float) cos( x ) + 0.08f * (float) cos( x * 2 ); + sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman); + } + + /* normalize kernel */ + sum = 1.0f / sum; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = kernel_size * 3 / 2 - kernel_half + i; + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + + /* generate chroma (iq) filter using gaussian kernel */ + { + float const cutoff_factor = -0.03125f; + float cutoff = (float) setup->bleed; + int i; + + if ( cutoff < 0 ) + { + /* keep extreme value accessible only near upper end of scale (1.0) */ + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= -30.0f / 0.65f; + } + cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff; + + for ( i = -kernel_half; i <= kernel_half; i++ ) + kernels [kernel_size / 2 + i] = (float) exp( i * i * cutoff ); + + /* normalize even and odd phases separately */ + for ( i = 0; i < 2; i++ ) + { + float sum = 0; + int x; + for ( x = i; x < kernel_size; x += 2 ) + sum += kernels [x]; + + sum = 1.0f / sum; + for ( x = i; x < kernel_size; x += 2 ) + { + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + } + + /* + printf( "luma:\n" ); + for ( i = kernel_size; i < kernel_size * 2; i++ ) + printf( "%f\n", kernels [i] ); + printf( "chroma:\n" ); + for ( i = 0; i < kernel_size; i++ ) + printf( "%f\n", kernels [i] ); + */ + + /* generate linear rescale kernels */ + #if rescale_out > 1 + { + float weight = 1.0f; + float* out = impl->kernel; + int n = rescale_out; + do + { + float remain = 0; + int i; + weight -= 1.0f / rescale_in; + for ( i = 0; i < kernel_size * 2; i++ ) + { + float cur = kernels [i]; + float m = cur * weight; + *out++ = m + remain; + remain = cur - m; + } + } + while ( --n ); + } + #endif +} + +/* Atari change: more accurate values taken from + http://en.wikipedia.org/wiki/YIQ */ +static float const default_decoder [6] = + { 0.9563f, 0.6210f, -0.2721f, -0.6474f, -1.1070f, 1.7046f }; + +static void init( init_t* impl, atari_ntsc_setup_t const* setup ) +{ + impl->brightness = (float) setup->brightness * (0.5f * rgb_unit) + rgb_offset; + impl->contrast = (float) setup->contrast * (0.5f * rgb_unit) + rgb_unit; + #ifdef default_palette_contrast + if ( !setup->palette ) + impl->contrast *= default_palette_contrast; + #endif + + impl->artifacts = (float) setup->artifacts; + if ( impl->artifacts > 0 ) + impl->artifacts *= artifacts_max - artifacts_mid; + impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid; + + impl->fringing = (float) setup->fringing; + if ( impl->fringing > 0 ) + impl->fringing *= fringing_max - fringing_mid; + impl->fringing = impl->fringing * fringing_mid + fringing_mid; + + init_filters( impl, setup ); + + /* generate gamma table */ + if ( gamma_size > 1 ) + { + float const to_float = 1.0f / (gamma_size - (gamma_size > 1)); + float const gamma = 1.1333f - (float) setup->gamma * 0.5f; + /* match common PC's 2.2 gamma to TV's 2.65 gamma */ + int i; + for ( i = 0; i < gamma_size; i++ ) + impl->to_float [i] = + (float) pow( i * to_float, gamma ) * impl->contrast + impl->brightness; + } + + /* setup decoder matricies */ + { + /* Atari change: + 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 180+33 in YIQ. + (See http://en.wikipedia.org/wiki/YUV and + http://en.wikipedia.org/wiki/YIQ) */ + static float const colorburst_angle = (213.0f) * PI / 180.0f; + float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue - PI * setup->burst_phase - colorburst_angle; + float sat = (float) setup->saturation + 1; + float const* decoder = setup->decoder_matrix; + if ( !decoder ) + { + decoder = default_decoder; + if ( STD_HUE_CONDITION( setup ) ) + hue += PI / 180 * (std_decoder_hue - ext_decoder_hue); + } + + { + float s = (float) sin( hue ) * sat; + float c = (float) cos( hue ) * sat; + float* out = impl->to_rgb; + int n; + + n = burst_count; + do + { + float const* in = decoder; + int n = 3; + do + { + float i = *in++; + float q = *in++; + *out++ = i * c - q * s; + *out++ = i * s + q * c; + } + while ( --n ); + if ( burst_count <= 1 ) + break; + ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */ + } + while ( --n ); + } + } +} + +/* kernel generation */ + +/* Atari change: more accurate values taken from + http://en.wikipedia.org/wiki/YIQ */ +#define RGB_TO_YIQ( r, g, b, y, i ) (\ + (y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\ + (i = (r) * 0.595716f - (g) * 0.274453f - (b) * 0.321263f),\ + ((r) * 0.211456f - (g) * 0.522591f + (b) * 0.311135f)\ +) + +#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\ + r = (type) (y + to_rgb [0] * i + to_rgb [1] * q),\ + g = (type) (y + to_rgb [2] * i + to_rgb [3] * q),\ + (type) (y + to_rgb [4] * i + to_rgb [5] * q)\ +) + +#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1) + +enum { rgb_kernel_size = burst_size / alignment_count }; +enum { rgb_bias = rgb_unit * 2 * atari_ntsc_rgb_builder }; + +typedef struct pixel_info_t +{ + int offset; + float negate; + float kernel [4]; +} pixel_info_t; + +#if rescale_in > 1 + #define PIXEL_OFFSET_( ntsc, scaled ) \ + (kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \ + (kernel_size * 2 * scaled)) + + #define PIXEL_OFFSET( ntsc, scaled ) \ + PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\ + (((scaled) + rescale_out * 10) % rescale_out) ),\ + (1.0f - (((ntsc) + 100) & 2)) +#else + #define PIXEL_OFFSET( ntsc, scaled ) \ + (kernel_size / 2 + (ntsc) - (scaled)),\ + (1.0f - (((ntsc) + 100) & 2)) +#endif + +extern pixel_info_t const atari_ntsc_pixels [alignment_count]; + +/* Generate pixel at all burst phases and column alignments */ +static void gen_kernel( init_t* impl, float y, float i, float q, atari_ntsc_rgb_t* out ) +{ + /* generate for each scanline burst phase */ + float const* to_rgb = impl->to_rgb; + int burst_remain = burst_count; + + y -= rgb_offset; + do + { + /* Encode yiq into *two* composite signals (to allow control over artifacting). + Convolve these with kernels which: filter respective components, apply + sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack + into integer. Based on algorithm by NewRisingSun. */ + pixel_info_t const* pixel = atari_ntsc_pixels; + int alignment_remain = alignment_count; + do + { + /* negate is -1 when composite starts at odd multiple of 2 */ + float const yy = y * impl->fringing * pixel->negate; + float const ic0 = (i + yy) * pixel->kernel [0]; + float const qc1 = (q + yy) * pixel->kernel [1]; + float const ic2 = (i - yy) * pixel->kernel [2]; + float const qc3 = (q - yy) * pixel->kernel [3]; + + float const factor = impl->artifacts * pixel->negate; + float const ii = i * factor; + float const yc0 = (y + ii) * pixel->kernel [0]; + float const yc2 = (y - ii) * pixel->kernel [2]; + + float const qq = q * factor; + float const yc1 = (y + qq) * pixel->kernel [1]; + float const yc3 = (y - qq) * pixel->kernel [3]; + + float const* k = &impl->kernel [pixel->offset]; + int n; + ++pixel; + for ( n = rgb_kernel_size; n; --n ) + { + float i = k[0]*ic0 + k[2]*ic2; + float q = k[1]*qc1 + k[3]*qc3; + float y = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 + + k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset; + if ( rescale_out <= 1 ) + k--; + else if ( k < &impl->kernel [kernel_size * 2 * (rescale_out - 1)] ) + k += kernel_size * 2 - 1; + else + k -= kernel_size * 2 * (rescale_out - 1) + 2; + { + int r, g, b = YIQ_TO_RGB( y, i, q, to_rgb, int, r, g ); + *out++ = PACK_RGB( r, g, b ) - rgb_bias; + } + } + } + while ( alignment_count > 1 && --alignment_remain ); + + if ( burst_count <= 1 ) + break; + + to_rgb += 6; + + ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */ + } + while ( --burst_remain ); +} + +static void correct_errors( atari_ntsc_rgb_t color, atari_ntsc_rgb_t* out ); + +/* Atari change: adjust DISTRIBUTE_ERROR to 4/7 pixel ratio. */ +#if DISABLE_CORRECTION + #define CORRECT_ERROR( a ) { out [i] += rgb_bias; } + #define DISTRIBUTE_ERROR( a, b, c, d ) { out [i] += rgb_bias; } +#else + #define CORRECT_ERROR( a ) { out [a] += error; } + #define DISTRIBUTE_ERROR( a, b, c, d ) {\ + atari_ntsc_rgb_t fourth = (error + 2 * atari_ntsc_rgb_builder) >> 2;\ + fourth &= (rgb_bias >> 1) - atari_ntsc_rgb_builder;\ + fourth -= rgb_bias >> 2;\ + out [a] += fourth;\ + out [b] += fourth;\ + out [c] += fourth;\ + out [d] += fourth;\ + out [i] += error - (fourth * 4);\ + } +#endif + +#define RGB_PALETTE_OUT( rgb, out_ )\ +{\ + unsigned char* out = (out_);\ + atari_ntsc_rgb_t clamped = (rgb);\ + ATARI_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\ + out [0] = (unsigned char) (clamped >> 21);\ + out [1] = (unsigned char) (clamped >> 11);\ + out [2] = (unsigned char) (clamped >> 1);\ +} + +/* blitter related */ + +#ifndef restrict + #if defined (__GNUC__) + #define restrict __restrict__ + #elif defined (_MSC_VER) && _MSC_VER > 1300 + #define restrict __restrict + #else + /* no support for restricted pointers */ + #define restrict + #endif +#endif + +#include + +#if ATARI_NTSC_OUT_DEPTH <= 16 + #if USHRT_MAX == 0xFFFF + typedef unsigned short atari_ntsc_out_t; + #else + #error "Need 16-bit int type" + #endif + +#else + #if UINT_MAX == 0xFFFFFFFF + typedef unsigned int atari_ntsc_out_t; + #elif ULONG_MAX == 0xFFFFFFFF + typedef unsigned long atari_ntsc_out_t; + #else + #error "Need 32-bit int type" + #endif + +#endif + +#endif // DISPLAY_TV diff --git a/src/common/tv_filters/module.mk b/src/common/tv_filters/module.mk index b361c88d2..96c89b7a1 100644 --- a/src/common/tv_filters/module.mk +++ b/src/common/tv_filters/module.mk @@ -1,7 +1,8 @@ MODULE := src/common/tv_filters MODULE_OBJS := \ - src/common/tv_filters/NTSCFilter.o + src/common/tv_filters/NTSCFilter.o \ + src/common/tv_filters/atari_ntsc.o MODULE_DIRS += \ src/common/tv_filters