diff --git a/hqflt/ntsc.c b/hqflt/ntsc.c new file mode 100644 index 0000000000..3f66859d6e --- /dev/null +++ b/hqflt/ntsc.c @@ -0,0 +1,13 @@ +#include "ntsc.h" +#include + +void ntsc_filter(uint16_t * restrict out, const uint16_t * restrict in, int width, int height) +{ + static int phase = 0; + snes_ntsc_t *ntsc = calloc(1, sizeof(snes_ntsc_t)); + snes_ntsc_init(ntsc, &snes_ntsc_composite); + + snes_ntsc_blit(ntsc, in, width, phase, width, height, out, SNES_NTSC_OUT_WIDTH(width) * sizeof(uint16_t)); + free(ntsc); + phase = (phase + 1) % 4; +} diff --git a/hqflt/ntsc.h b/hqflt/ntsc.h new file mode 100644 index 0000000000..02520c7ebf --- /dev/null +++ b/hqflt/ntsc.h @@ -0,0 +1,9 @@ + +#ifndef _NTSC_FILTER_H +#define _NTSC_FILTER_H +#include +#include "snes_ntsc/snes_ntsc.h" + +void ntsc_filter(uint16_t * restrict out, const uint16_t * restrict in, int width, int height); + +#endif diff --git a/hqflt/snes_ntsc/snes_ntsc.c b/hqflt/snes_ntsc/snes_ntsc.c new file mode 100755 index 0000000000..f622baf841 --- /dev/null +++ b/hqflt/snes_ntsc/snes_ntsc.c @@ -0,0 +1,251 @@ +/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */ + +#include "snes_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 */ + +snes_ntsc_setup_t const snes_ntsc_monochrome = { 0,-1, 0, 0,.2, 0,.2,-.2,-.2,-1, 1, 0, 0 }; +snes_ntsc_setup_t const snes_ntsc_composite = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }; +snes_ntsc_setup_t const snes_ntsc_svideo = { 0, 0, 0, 0,.2, 0,.2, -1, -1, 0, 1, 0, 0 }; +snes_ntsc_setup_t const snes_ntsc_rgb = { 0, 0, 0, 0,.2, 0,.7, -1, -1,-1, 1, 0, 0 }; + +#define alignment_count 3 +#define burst_count 3 +#define rescale_in 8 +#define rescale_out 7 + +#define artifacts_mid 1.0f +#define fringing_mid 1.0f +#define std_decoder_hue 0 + +#define rgb_bits 7 /* half normal range to allow for doubled hires pixels */ +#define gamma_size 32 + +#include "snes_ntsc_impl.h" + +/* 3 input pixels -> 8 composite samples */ +pixel_info_t const snes_ntsc_pixels [alignment_count] = { + { PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } }, + { PIXEL_OFFSET( -2, -7 ), { .3333f, 1, 1, .3333f } }, + { PIXEL_OFFSET( 0, -5 ), { 0, .6667f, 1, 1 } }, +}; + +static void merge_kernel_fields( snes_ntsc_rgb_t* io ) +{ + int n; + for ( n = burst_size; n; --n ) + { + snes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias; + snes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias; + snes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias; + /* merge colors without losing precision */ + io [burst_size * 0] = + ((p0 + p1 - ((p0 ^ p1) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias; + io [burst_size * 1] = + ((p1 + p2 - ((p1 ^ p2) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias; + io [burst_size * 2] = + ((p2 + p0 - ((p2 ^ p0) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias; + ++io; + } +} + +static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out ) +{ + int n; + for ( n = burst_count; n; --n ) + { + unsigned i; + for ( i = 0; i < rgb_kernel_size / 2; i++ ) + { + snes_ntsc_rgb_t error = color - + out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] - + out [i + 7] - out [i + 5 +14] - out [i + 3 +28]; + DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 ); + } + out += alignment_count * rgb_kernel_size; + } +} + +void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup ) +{ + int merge_fields; + int entry; + init_t impl; + if ( !setup ) + setup = &snes_ntsc_composite; + init( &impl, setup ); + + merge_fields = setup->merge_fields; + if ( setup->artifacts <= -1 && setup->fringing <= -1 ) + merge_fields = 1; + + for ( entry = 0; entry < snes_ntsc_palette_size; entry++ ) + { + /* Reduce number of significant bits of source color. Clearing the + low bits of R and B were least notictable. Modifying green was too + noticeable. */ + int ir = entry >> 8 & 0x1E; + int ig = entry >> 4 & 0x1F; + int ib = entry << 1 & 0x1E; + + #if SNES_NTSC_BSNES_COLORTBL + if ( setup->bsnes_colortbl ) + { + int bgr15 = (ib << 10) | (ig << 5) | ir; + unsigned long rgb16 = setup->bsnes_colortbl [bgr15]; + ir = rgb16 >> 11 & 0x1E; + ig = rgb16 >> 6 & 0x1F; + ib = rgb16 & 0x1E; + } + #endif + + { + float rr = impl.to_float [ir]; + float gg = impl.to_float [ig]; + float bb = impl.to_float [ib]; + + float y, i, q = RGB_TO_YIQ( rr, gg, bb, y, i ); + + int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g ); + snes_ntsc_rgb_t rgb = PACK_RGB( r, g, b ); + + snes_ntsc_rgb_t* out = ntsc->table [entry]; + gen_kernel( &impl, y, i, q, out ); + if ( merge_fields ) + merge_kernel_fields( out ); + correct_errors( rgb, out ); + } + } +} + +#ifndef SNES_NTSC_NO_BLITTERS + +void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width, + int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 1) / snes_ntsc_in_chunk; + for ( ; in_height; --in_height ) + { + SNES_NTSC_IN_T const* line_in = input; + SNES_NTSC_BEGIN_ROW( ntsc, burst_phase, + snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) ); + snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out; + int n; + ++line_in; + + for ( n = chunk_count; n; --n ) + { + /* order of input and output pixels must not be altered */ + SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) ); + SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) ); + SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) ); + SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + line_in += 3; + line_out += 7; + } + + /* finish final pixels */ + SNES_NTSC_COLOR_IN( 0, snes_ntsc_black ); + SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, snes_ntsc_black ); + SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, snes_ntsc_black ); + SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + burst_phase = (burst_phase + 1) % snes_ntsc_burst_count; + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width, + int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2); + for ( ; in_height; --in_height ) + { + SNES_NTSC_IN_T const* line_in = input; + SNES_NTSC_HIRES_ROW( ntsc, burst_phase, + snes_ntsc_black, snes_ntsc_black, snes_ntsc_black, + SNES_NTSC_ADJ_IN( line_in [0] ), + SNES_NTSC_ADJ_IN( line_in [1] ) ); + snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out; + int n; + line_in += 2; + + for ( n = chunk_count; n; --n ) + { + /* twice as many input pixels per chunk */ + SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) ); + SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) ); + SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) ); + SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) ); + SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) ); + SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) ); + SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + line_in += 6; + line_out += 7; + } + + SNES_NTSC_COLOR_IN( 0, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 3, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 4, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 5, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + burst_phase = (burst_phase + 1) % snes_ntsc_burst_count; + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +#endif diff --git a/hqflt/snes_ntsc/snes_ntsc.h b/hqflt/snes_ntsc/snes_ntsc.h new file mode 100755 index 0000000000..99ae34c9bc --- /dev/null +++ b/hqflt/snes_ntsc/snes_ntsc.h @@ -0,0 +1,236 @@ +/* SNES NTSC video filter */ + +/* snes_ntsc 0.2.2 */ +#ifndef SNES_NTSC_H +#define SNES_NTSC_H + +#include "snes_ntsc_config.h" + +#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 snes_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) */ + int merge_fields; /* if 1, merges even and odd fields together to reduce flicker */ + float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */ + + unsigned long const* bsnes_colortbl; /* undocumented; set to 0 */ +} snes_ntsc_setup_t; + +/* Video format presets */ +extern snes_ntsc_setup_t const snes_ntsc_composite; /* color bleeding + artifacts */ +extern snes_ntsc_setup_t const snes_ntsc_svideo; /* color bleeding only */ +extern snes_ntsc_setup_t const snes_ntsc_rgb; /* crisp image */ +extern snes_ntsc_setup_t const snes_ntsc_monochrome;/* desaturated + artifacts */ + +/* Initializes and adjusts parameters. Can be called multiple times on the same +snes_ntsc_t object. Can pass NULL for either parameter. */ +typedef struct snes_ntsc_t snes_ntsc_t; +void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup ); + +/* Filters one or more rows of pixels. Input pixel format is set by SNES_NTSC_IN_FORMAT +and output RGB depth is set by SNES_NTSC_OUT_DEPTH. Both default to 16-bit RGB. +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. */ +void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, + long in_row_width, int burst_phase, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, + long in_row_width, int burst_phase, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +/* Number of output pixels written by low-res blitter for given input width. Width +might be rounded down slightly; use SNES_NTSC_IN_WIDTH() on result to find rounded +value. Guaranteed not to round 256 down at all. */ +#define SNES_NTSC_OUT_WIDTH( in_width ) \ + ((((in_width) - 1) / snes_ntsc_in_chunk + 1) * snes_ntsc_out_chunk) + +/* Number of low-res input pixels that will fit within given output width. Might be +rounded down slightly; use SNES_NTSC_OUT_WIDTH() on result to find rounded +value. */ +#define SNES_NTSC_IN_WIDTH( out_width ) \ + (((out_width) / snes_ntsc_out_chunk - 1) * snes_ntsc_in_chunk + 1) + + +/* Interface for user-defined custom blitters */ + +enum { snes_ntsc_in_chunk = 3 }; /* number of input pixels read per chunk */ +enum { snes_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */ +enum { snes_ntsc_black = 0 }; /* palette index for black */ +enum { snes_ntsc_burst_count = 3 }; /* burst phase cycles through 0, 1, and 2 */ + +/* Begins outputting row and starts three pixels. First pixel will be cut off a bit. +Use snes_ntsc_black for unused pixels. Declares variables, so must be before first +statement in a block (unless you're using C++). */ +#define SNES_NTSC_BEGIN_ROW( ntsc, burst, pixel0, pixel1, pixel2 ) \ + char const* ktable = \ + (char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\ + SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, SNES_NTSC_IN_FORMAT, ktable ) + +/* Begins input pixel */ +#define SNES_NTSC_COLOR_IN( index, color ) \ + SNES_NTSC_COLOR_IN_( index, color, SNES_NTSC_IN_FORMAT, ktable ) + +/* Generates output pixel. Bits can be 24, 16, 15, 14, 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) +14: BBBBBGG GGGRRRRR (5-5-5 BGR, native SNES format) + 0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */ +#define SNES_NTSC_RGB_OUT( index, rgb_out, bits ) \ + SNES_NTSC_RGB_OUT_14_( index, rgb_out, bits, 1 ) + +/* Hires equivalents */ +#define SNES_NTSC_HIRES_ROW( ntsc, burst, pixel1, pixel2, pixel3, pixel4, pixel5 ) \ + char const* ktable = \ + (char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\ + unsigned const snes_ntsc_pixel1_ = (pixel1);\ + snes_ntsc_rgb_t const* kernel1 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel1_ );\ + unsigned const snes_ntsc_pixel2_ = (pixel2);\ + snes_ntsc_rgb_t const* kernel2 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel2_ );\ + unsigned const snes_ntsc_pixel3_ = (pixel3);\ + snes_ntsc_rgb_t const* kernel3 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel3_ );\ + unsigned const snes_ntsc_pixel4_ = (pixel4);\ + snes_ntsc_rgb_t const* kernel4 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel4_ );\ + unsigned const snes_ntsc_pixel5_ = (pixel5);\ + snes_ntsc_rgb_t const* kernel5 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel5_ );\ + snes_ntsc_rgb_t const* kernel0 = kernel1;\ + snes_ntsc_rgb_t const* kernelx0;\ + snes_ntsc_rgb_t const* kernelx1 = kernel1;\ + snes_ntsc_rgb_t const* kernelx2 = kernel1;\ + snes_ntsc_rgb_t const* kernelx3 = kernel1;\ + snes_ntsc_rgb_t const* kernelx4 = kernel1;\ + snes_ntsc_rgb_t const* kernelx5 = kernel1 + +#define SNES_NTSC_HIRES_OUT( x, rgb_out, bits ) {\ + snes_ntsc_rgb_t raw_ =\ + kernel0 [ x ] + kernel2 [(x+5)%7+14] + kernel4 [(x+3)%7+28] +\ + kernelx0 [(x+7)%7+7] + kernelx2 [(x+5)%7+21] + kernelx4 [(x+3)%7+35] +\ + kernel1 [(x+6)%7 ] + kernel3 [(x+4)%7+14] + kernel5 [(x+2)%7+28] +\ + kernelx1 [(x+6)%7+7] + kernelx3 [(x+4)%7+21] + kernelx5 [(x+2)%7+35];\ + SNES_NTSC_CLAMP_( raw_, 0 );\ + SNES_NTSC_RGB_OUT_( rgb_out, (bits), 0 );\ +} + + +/* private */ +enum { snes_ntsc_entry_size = 128 }; +enum { snes_ntsc_palette_size = 0x2000 }; +typedef unsigned long snes_ntsc_rgb_t; +struct snes_ntsc_t { + snes_ntsc_rgb_t table [snes_ntsc_palette_size] [snes_ntsc_entry_size]; +}; +enum { snes_ntsc_burst_size = snes_ntsc_entry_size / snes_ntsc_burst_count }; + +#define SNES_NTSC_RGB16( ktable, n ) \ + (snes_ntsc_rgb_t const*) (ktable + ((n & 0x001E) | (n >> 1 & 0x03E0) | (n >> 2 & 0x3C00)) * \ + (snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t))) + +#define SNES_NTSC_BGR15( ktable, n ) \ + (snes_ntsc_rgb_t const*) (ktable + ((n << 9 & 0x3C00) | (n & 0x03E0) | (n >> 10 & 0x001E)) * \ + (snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t))) + +/* common 3->7 ntsc macros */ +#define SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, ENTRY, table ) \ + unsigned const snes_ntsc_pixel0_ = (pixel0);\ + snes_ntsc_rgb_t const* kernel0 = ENTRY( table, snes_ntsc_pixel0_ );\ + unsigned const snes_ntsc_pixel1_ = (pixel1);\ + snes_ntsc_rgb_t const* kernel1 = ENTRY( table, snes_ntsc_pixel1_ );\ + unsigned const snes_ntsc_pixel2_ = (pixel2);\ + snes_ntsc_rgb_t const* kernel2 = ENTRY( table, snes_ntsc_pixel2_ );\ + snes_ntsc_rgb_t const* kernelx0;\ + snes_ntsc_rgb_t const* kernelx1 = kernel0;\ + snes_ntsc_rgb_t const* kernelx2 = kernel0 + +#define SNES_NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\ + snes_ntsc_rgb_t raw_ =\ + kernel0 [x ] + kernel1 [(x+12)%7+14] + kernel2 [(x+10)%7+28] +\ + kernelx0 [(x+7)%14] + kernelx1 [(x+ 5)%7+21] + kernelx2 [(x+ 3)%7+35];\ + SNES_NTSC_CLAMP_( raw_, shift );\ + SNES_NTSC_RGB_OUT_( rgb_out, bits, shift );\ +} + +/* common ntsc macros */ +#define snes_ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1)) +#define snes_ntsc_clamp_mask (snes_ntsc_rgb_builder * 3 / 2) +#define snes_ntsc_clamp_add (snes_ntsc_rgb_builder * 0x101) +#define SNES_NTSC_CLAMP_( io, shift ) {\ + snes_ntsc_rgb_t sub = (io) >> (9-(shift)) & snes_ntsc_clamp_mask;\ + snes_ntsc_rgb_t clamp = snes_ntsc_clamp_add - sub;\ + io |= clamp;\ + clamp -= sub;\ + io &= clamp;\ +} + +#define SNES_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\ + unsigned color_;\ + kernelx##index = kernel##index;\ + kernel##index = (color_ = (color), ENTRY( table, color_ ));\ +} + +/* x is always zero except in snes_ntsc library */ +/* original routine */ +#if 0 +#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + if ( bits == 16 )\ + rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 24 || bits == 32 )\ + rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\ + if ( bits == 15 )\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 14 )\ + rgb_out = (raw_>>(24-x)& 0x001F)|(raw_>>(9-x)&0x03E0)|(raw_<<(6+x)&0x7C00);\ + if ( bits == 0 )\ + rgb_out = raw_ << x;\ +} +#endif + +#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ +} + +// Custom SSNES routine for 15 bit. + +/* custom bsnes routine -- hooks into bsnes colortable */ +#if 0 +#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + if ( bits == 16 ) {\ + rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\ + rgb_out = ((rgb_out&0xf800)>>11)|((rgb_out&0x07c0)>>1)|((rgb_out&0x001f)<<10);\ + rgb_out = colortable[rgb_out];\ + } else if ( bits == 24 || bits == 32 ) {\ + rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\ + rgb_out = ((rgb_out&0xf80000)>>19)|((rgb_out&0x00f800)>>6)|((rgb_out&0x0000f8)<<7);\ + rgb_out = colortable[rgb_out];\ + } else if ( bits == 15 ) {\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ + rgb_out = ((rgb_out&0x7c00)>>10)|((rgb_out&0x03e0))|((rgb_out&0x001f)<<10);\ + rgb_out = colortable[rgb_out];\ + } else {\ + rgb_out = raw_ << x;\ + }\ +} +#endif + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/hqflt/snes_ntsc/snes_ntsc_config.h b/hqflt/snes_ntsc/snes_ntsc_config.h new file mode 100755 index 0000000000..175da8bbd5 --- /dev/null +++ b/hqflt/snes_ntsc/snes_ntsc_config.h @@ -0,0 +1,27 @@ +/* Configure library by modifying this file */ + +#ifndef SNES_NTSC_CONFIG_H +#define SNES_NTSC_CONFIG_H + +/* Format of source pixels */ +/* #define SNES_NTSC_IN_FORMAT SNES_NTSC_RGB16 */ +#define SNES_NTSC_IN_FORMAT SNES_NTSC_BGR15 + +/* 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 SNES_NTSC_OUT_DEPTH 15 + +/* Type of input pixel values */ +#define SNES_NTSC_IN_T unsigned short + +/* 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 SNES_NTSC_ADJ_IN( in ) (((in >> 10) & 0x1F) | (in & 0x3E0) | ((in << 10) & 0x7C00)) + + +/* For each pixel, this is the basic operation: +output_color = SNES_NTSC_ADJ_IN( SNES_NTSC_IN_T ) */ + +#endif diff --git a/hqflt/snes_ntsc/snes_ntsc_impl.h b/hqflt/snes_ntsc/snes_ntsc_impl.h new file mode 100755 index 0000000000..1d7adc78b8 --- /dev/null +++ b/hqflt/snes_ntsc/snes_ntsc_impl.h @@ -0,0 +1,439 @@ +/* snes_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 = snes_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, snes_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 +} + +static float const default_decoder [6] = + { 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f }; + +static void init( init_t* impl, snes_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 */ + { + float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue; + 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 */ + +#define RGB_TO_YIQ( r, g, b, y, i ) (\ + (y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\ + (i = (r) * 0.596f - (g) * 0.275f - (b) * 0.321f),\ + ((r) * 0.212f - (g) * 0.523f + (b) * 0.311f)\ +) + +#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 * snes_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 snes_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, snes_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 = snes_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( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out ); + +#if DISABLE_CORRECTION + #define CORRECT_ERROR( a ) { out [i] += rgb_bias; } + #define DISTRIBUTE_ERROR( a, b, c ) { out [i] += rgb_bias; } +#else + #define CORRECT_ERROR( a ) { out [a] += error; } + #define DISTRIBUTE_ERROR( a, b, c ) {\ + snes_ntsc_rgb_t fourth = (error + 2 * snes_ntsc_rgb_builder) >> 2;\ + fourth &= (rgb_bias >> 1) - snes_ntsc_rgb_builder;\ + fourth -= rgb_bias >> 2;\ + out [a] += fourth;\ + out [b] += fourth;\ + out [c] += fourth;\ + out [i] += error - (fourth * 3);\ + } +#endif + +#define RGB_PALETTE_OUT( rgb, out_ )\ +{\ + unsigned char* out = (out_);\ + snes_ntsc_rgb_t clamped = (rgb);\ + SNES_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 SNES_NTSC_OUT_DEPTH <= 16 + #if USHRT_MAX == 0xFFFF + typedef unsigned short snes_ntsc_out_t; + #else + #error "Need 16-bit int type" + #endif + +#else + #if UINT_MAX == 0xFFFFFFFF + typedef unsigned int snes_ntsc_out_t; + #elif ULONG_MAX == 0xFFFFFFFF + typedef unsigned long snes_ntsc_out_t; + #else + #error "Need 32-bit int type" + #endif + +#endif