o add bsnes

This commit is contained in:
optixx
2009-04-08 21:29:36 +02:00
parent bcb4a055e9
commit 27b58a09f2
413 changed files with 71887 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
Colortable colortable;
void Colortable::set_format(Format format_) { format = format_; }
void Colortable::set_contrast(signed contrast_) { contrast = contrast_; }
void Colortable::set_brightness(signed brightness_) { brightness = brightness_; }
void Colortable::set_gamma(signed gamma_) { gamma = gamma_; }
void Colortable::enable_gamma_ramp(bool value) { gamma_ramp = value; }
void Colortable::enable_sepia(bool value) { sepia = value; }
void Colortable::enable_grayscale(bool value) { grayscale = value; }
void Colortable::enable_invert(bool value) { invert = value; }
void Colortable::update() {
double kr = 0.2126, kb = 0.0722, kg = (1.0 - kr - kb); //luminance weights
for(unsigned i = 0; i < 32768; i++) {
unsigned color //bgr555->rgb888 conversion
= ((i & 0x001f) << 19) | ((i & 0x001c) << 14)
| ((i & 0x03e0) << 6) | ((i & 0x0380) << 1)
| ((i & 0x7c00) >> 7) | ((i & 0x7000) >> 12);
signed l;
signed r = (color >> 16) & 0xff;
signed g = (color >> 8) & 0xff;
signed b = (color ) & 0xff;
if(gamma_ramp == true) {
r = gamma_ramp_table[r >> 3];
g = gamma_ramp_table[g >> 3];
b = gamma_ramp_table[b >> 3];
}
if(contrast != 0) {
r = contrast_adjust(r);
g = contrast_adjust(g);
b = contrast_adjust(b);
}
if(brightness != 0) {
r = brightness_adjust(r);
g = brightness_adjust(g);
b = brightness_adjust(b);
}
if(gamma != 100) {
r = gamma_adjust(r);
g = gamma_adjust(g);
b = gamma_adjust(b);
}
if(sepia == true) {
l = (signed)((double)r * kr + (double)g * kg + (double)b * kb);
l = max(0, min(255, l));
r = (signed)((double)l * (1.0 + 0.300));
g = (signed)((double)l * (1.0 - 0.055));
b = (signed)((double)l * (1.0 - 0.225));
r = max(0, min(255, r));
g = max(0, min(255, g));
b = max(0, min(255, b));
}
if(grayscale == true) {
l = (signed)((double)r * kr + (double)g * kg + (double)b * kb);
l = max(0, min(255, l));
r = g = b = l;
}
if(invert == true) {
r ^= 0xff;
g ^= 0xff;
b ^= 0xff;
}
switch(format) {
case RGB555: {
r >>= 3;
g >>= 3;
b >>= 3;
table[i] = (r << 10) | (g << 5) | (b);
} break;
case RGB565: {
r >>= 3;
g >>= 2;
b >>= 3;
table[i] = (r << 11) | (g << 5) | (b);
} break;
case RGB888: {
table[i] = (r << 16) | (g << 8) | (b);
} break;
default: {
table[i] = ~0;
} break;
}
}
}
Colortable::Colortable() {
table = new uint32_t[32768];
contrast = 0;
brightness = 0;
gamma = 100;
gamma_ramp = false;
sepia = false;
grayscale = false;
invert = false;
}
Colortable::~Colortable() {
delete[] table;
}
const uint8_t Colortable::gamma_ramp_table[32] = {
0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78,
0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0,
0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff,
};
uint8_t Colortable::contrast_adjust(uint8_t input) {
signed result = input - contrast + (2 * contrast * input + 127) / 255;
return max(0, min(255, result));
}
uint8_t Colortable::brightness_adjust(uint8_t input) {
signed result = input + brightness;
return max(0, min(255, result));
}
uint8_t Colortable::gamma_adjust(uint8_t input) {
signed result = (signed)(pow(((double)input / 255.0), (double)gamma / 100.0) * 255.0 + 0.5);
return max(0, min(255, result));
}

View File

@@ -0,0 +1,44 @@
class Colortable {
public:
enum Format {
RGB555,
RGB565,
RGB888,
};
const inline uint32_t operator[](uint16_t index) const { return table[index]; }
void set_format(Format);
void set_contrast(signed);
void set_brightness(signed);
void set_gamma(signed);
void enable_gamma_ramp(bool);
void enable_sepia(bool);
void enable_grayscale(bool);
void enable_invert(bool);
void update();
Colortable();
~Colortable();
private:
uint32_t *table;
Format format;
signed contrast;
signed brightness;
signed gamma;
bool gamma_ramp;
bool sepia;
bool grayscale;
bool invert;
static const uint8_t gamma_ramp_table[32];
uint8_t contrast_adjust(uint8_t input);
uint8_t brightness_adjust(uint8_t input);
uint8_t gamma_adjust(uint8_t input);
};
extern Colortable colortable;

30
bsnes/lib/libfilter/direct.cpp Executable file
View File

@@ -0,0 +1,30 @@
DirectFilter filter_direct;
void DirectFilter::render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
) {
pitch >>= 1;
outpitch >>= 2;
for(unsigned y = 0; y < height; y++) {
if(width == 512 && line[y] == 256) {
for(unsigned x = 0; x < 256; x++) {
uint16_t p = *input++;
*output++ = colortable[p];
*output++ = colortable[p];
}
input += 256;
} else {
for(unsigned x = 0; x < width; x++) {
uint16_t p = *input++;
*output++ = colortable[p];
}
}
input += pitch - width;
output += outpitch - width;
}
outwidth = width;
outheight = height;
}

6
bsnes/lib/libfilter/direct.hpp Executable file
View File

@@ -0,0 +1,6 @@
class DirectFilter : public Filter {
public:
void render(uint32_t*, unsigned, unsigned&, unsigned&, uint16_t*, unsigned, unsigned*, unsigned, unsigned);
};
extern DirectFilter filter_direct;

34
bsnes/lib/libfilter/filter.cpp Executable file
View File

@@ -0,0 +1,34 @@
FilterInterface filter;
void FilterInterface::set(FilterInterface::FilterType type) {
active_filter = type;
}
void FilterInterface::render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
) {
switch(active_filter) { default:
case Direct: {
filter_direct.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
} break;
case Scanline: {
filter_scanline.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
} break;
case Scale2x: {
filter_scale2x.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
} break;
case HQ2x: {
filter_hq2x.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
} break;
case NTSC: {
filter_ntsc.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
} break;
}
}
FilterInterface::FilterInterface() : active_filter(FilterInterface::Direct) {}

32
bsnes/lib/libfilter/filter.hpp Executable file
View File

@@ -0,0 +1,32 @@
class Filter {
public:
virtual void render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
) = 0;
};
class FilterInterface : public Filter {
public:
enum FilterType {
Direct,
Scanline,
Scale2x,
HQ2x,
NTSC,
};
void set(FilterType type);
void render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
);
FilterInterface();
private:
FilterType active_filter;
};
extern FilterInterface filter;

160
bsnes/lib/libfilter/hq2x.cpp Executable file
View File

@@ -0,0 +1,160 @@
HQ2xFilter filter_hq2x;
#define diff(x, y) ((yuvtable[x] - yuvtable[y] + diff_offset) & diff_mask)
#define hdiff(x, y) ((x - yuvtable[y]) & diff_mask)
#define expand_rgb(n) { n |= n << 16; n &= 0x03e07c1f; }
#define pack_rgb(n) { n &= 0x03e07c1f; n |= n >> 16; }
static uint16_t blend1(uint32_t c1, uint32_t c2) {
expand_rgb(c1);
expand_rgb(c2);
c1 = (c1 * 3 + c2) >> 2;
pack_rgb(c1);
return c1;
}
static uint16_t blend2(uint32_t c1, uint32_t c2, uint32_t c3) {
//c1 = (c1 * 2 + c2 + c3) >> 2;
c2 = (c2 + c3 - ((c2 ^ c3) & 0x0421)) >> 1;
c1 = (c1 + c2 - ((c1 ^ c2) & 0x0421)) >> 1;
return c1;
}
static uint16_t blend6(uint32_t c1, uint32_t c2, uint32_t c3) {
expand_rgb(c1);
expand_rgb(c2);
expand_rgb(c3);
c1 = (c1 * 5 + c2 * 2 + c3) >> 3;
pack_rgb(c1);
return c1;
}
static uint16_t blend7(uint32_t c1, uint32_t c2, uint32_t c3) {
expand_rgb(c1);
expand_rgb(c2);
expand_rgb(c3);
c1 = (c1 * 6 + c2 + c3) >> 3;
pack_rgb(c1);
return c1;
}
static uint16_t blend9(uint32_t c1, uint32_t c2, uint32_t c3) {
expand_rgb(c1);
expand_rgb(c2);
expand_rgb(c3);
c1 = (c1 * 2 + (c2 + c3) * 3) >> 3;
pack_rgb(c1);
return c1;
}
static uint16_t blend10(uint32_t c1, uint32_t c2, uint32_t c3) {
expand_rgb(c1);
expand_rgb(c2);
expand_rgb(c3);
c1 = (c1 * 14 + c2 + c3) >> 4;
pack_rgb(c1);
return c1;
}
void HQ2xFilter::render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
) {
if(width > 256 || height > 240) {
filter_direct.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
return;
}
pitch >>= 1;
outpitch >>= 2;
uint32_t *out0 = output;
uint32_t *out1 = output + outpitch;
#define W1 input[-1 - (int)pitch]
#define W2 input[ 0 - (int)pitch]
#define W3 input[+1 - (int)pitch]
#define W4 input[-1]
#define W5 input[ 0]
#define W6 input[+1]
#define W7 input[-1 + (int)pitch]
#define W8 input[ 0 + (int)pitch]
#define W9 input[+1 + (int)pitch]
input += pitch;
memset(out0, 0, 2048); out0 += outpitch << 1;
memset(out1, 0, 2048); out1 += outpitch << 1;
for(int y = height - 2; y; --y) {
input++;
*out0++ = 0; *out0++ = 0;
*out1++ = 0; *out1++ = 0;
int32_t pattern = diff(W5, W4) ? 0x10 : 0x00;
for(int x = 256 - 2; x; --x) {
uint32_t center = yuvtable[W5] + diff_offset;
//W4 for pixel x+1 is the same as W6 for pixel x
pattern = (pattern & 0x10) >> 1;
pattern |= hdiff(center, W1) ? 0x01 : 0x00;
pattern |= hdiff(center, W2) ? 0x02 : 0x00;
pattern |= hdiff(center, W3) ? 0x04 : 0x00;
//pattern |= hdiff(center, W4) ? 0x08 : 0x00;
pattern |= hdiff(center, W6) ? 0x10 : 0x00;
pattern |= hdiff(center, W7) ? 0x20 : 0x00;
pattern |= hdiff(center, W8) ? 0x40 : 0x00;
pattern |= hdiff(center, W9) ? 0x80 : 0x00;
switch(pattern) {
#include "hq2x_table.hpp"
}
input++;
out0 += 2;
out1 += 2;
}
input++;
*out0++ = 0; *out0++ = 0;
*out1++ = 0; *out1++ = 0;
input += pitch - 256;
out0 += outpitch + outpitch - 512;
out1 += outpitch + outpitch - 512;
}
memset(out0, 0, 2048);
memset(out1, 0, 2048);
outwidth = width * 2;
outheight = height * 2;
}
HQ2xFilter::HQ2xFilter() {
yuvtable = new uint32_t[32768];
for(int i = 0; i < 32768; i++) {
int ir = (i) & 0x1f;
int ig = (i >> 5) & 0x1f;
int ib = (i >> 10) & 0x1f;
//bgr555->bgr888
double r = (ir << 3) | (ir >> 2);
double g = (ig << 3) | (ig >> 2);
double b = (ib << 3) | (ib >> 2);
//bgr888->yuv888
double y = (r + g + b) * (0.25f * (63.5f / 48.0f));
double u = ((r - b) * 0.25f + 128.0f) * (7.5f / 7.0f);
double v = ((g * 2.0f - r - b) * 0.125f + 128.0f) * (7.5f / 6.0f);
yuvtable[i] = (int(y) << 21) + (int(u) << 11) + (int(v));
}
diff_offset = (0x440 << 21) + (0x207 << 11) + 0x407;
diff_mask = (0x380 << 21) + (0x1f0 << 11) + 0x3f0;
}
HQ2xFilter::~HQ2xFilter() {
delete[] yuvtable;
}

14
bsnes/lib/libfilter/hq2x.hpp Executable file
View File

@@ -0,0 +1,14 @@
class HQ2xFilter : public Filter {
public:
void render(uint32_t*, unsigned, unsigned&, unsigned&, uint16_t*, unsigned, unsigned*, unsigned, unsigned);
HQ2xFilter();
~HQ2xFilter();
private:
uint32_t *yuvtable;
uint32_t diff_offset;
uint32_t diff_mask;
};
extern HQ2xFilter filter_hq2x;

2690
bsnes/lib/libfilter/hq2x_table.hpp Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
#include "libfilter.hpp"
using nall::min;
using nall::max;
namespace libfilter {
#include "colortable.cpp"
#include "filter.cpp"
#include "direct.cpp"
#include "scanline.cpp"
#include "scale2x.cpp"
#include "hq2x.cpp"
#include "ntsc.cpp"
} //namespace libfilter

View File

@@ -0,0 +1,22 @@
#ifndef LIBFILTER_H
#define LIBFILTER_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <nall/algorithm.hpp>
#include <nall/stdint.hpp>
namespace libfilter {
#include "colortable.hpp"
#include "filter.hpp"
#include "direct.hpp"
#include "scanline.hpp"
#include "scale2x.hpp"
#include "hq2x.hpp"
#include "ntsc.hpp"
} //namespace libfilter
#endif //ifndef LIBFILTER_H

73
bsnes/lib/libfilter/ntsc.cpp Executable file
View File

@@ -0,0 +1,73 @@
#include "snes_ntsc/snes_ntsc.c"
NTSCFilter filter_ntsc;
void NTSCFilter::render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
) {
if(!ntsc)return;
int const out_width = outwidth = SNES_NTSC_OUT_WIDTH(256);
int const out_height = outheight = height;
burst ^= burst_toggle;
//blit multiple scanlines of same width, rather than one at a time
int run_start = 0;
int run_width = line[0];
int l = 0;
while(1) {
if(run_width != line[l] || l >= out_height) {
uint16_t const *in = (uint16_t*)((uint8_t*)input + pitch * run_start);
uint16_t *out = (uint16_t*)((uint8_t*)output + outpitch * run_start);
int height = l - run_start;
int line_burst = (burst + run_start) % 3;
if(run_width == 256) {
snes_ntsc_blit(ntsc, in, (pitch >> 1), line_burst, out_width, height, out, outpitch);
} else {
snes_ntsc_blit_hires(ntsc, in, (pitch >> 1), line_burst, out_width, height, out, outpitch);
}
if(l >= out_height) break;
run_width = line[l];
run_start = l;
}
l++;
}
}
void NTSCFilter::adjust(
float hue, float saturation, float contrast,
float brightness, float sharpness, bool merge_fields
) {
static snes_ntsc_setup_t defaults;
snes_ntsc_setup_t setup = defaults;
setup.hue = hue;
setup.saturation = saturation;
setup.contrast = contrast;
setup.brightness = brightness;
setup.sharpness = sharpness;
setup.resolution = sharpness;
setup.merge_fields = merge_fields;
setup.bsnes_colortbl = 0;
if(!ntsc) {
ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
if(!ntsc) {
return; //to do: report out of memory error
}
}
burst = 0;
burst_toggle = (merge_fields ? 0 : 1); //don't toggle burst when fields are merged
snes_ntsc_init(ntsc, &setup);
}
NTSCFilter::NTSCFilter() {
ntsc = 0;
adjust(0, 0, 0, 0, 0, false);
}
NTSCFilter::~NTSCFilter() {
if(ntsc) free(ntsc);
}

16
bsnes/lib/libfilter/ntsc.hpp Executable file
View File

@@ -0,0 +1,16 @@
#include "snes_ntsc/snes_ntsc.h"
class NTSCFilter : public Filter {
public:
void render(uint32_t*, unsigned, unsigned&, unsigned&, uint16_t*, unsigned, unsigned*, unsigned, unsigned);
void adjust(float hue, float saturation, float contrast, float brightness, float sharpness, bool merge_fields);
NTSCFilter();
~NTSCFilter();
private:
struct snes_ntsc_t *ntsc;
int burst, burst_toggle;
};
extern NTSCFilter filter_ntsc;

47
bsnes/lib/libfilter/scale2x.cpp Executable file
View File

@@ -0,0 +1,47 @@
Scale2xFilter filter_scale2x;
void Scale2xFilter::render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
) {
if(width > 256 || height > 240) {
filter_direct.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
return;
}
pitch >>= 1;
outpitch >>= 2;
uint32_t A, B, C, D, P;
int prevline, nextline;
for(int y = 0; y < height; y++) {
prevline = (y > 0) ? -pitch : 0;
nextline = (y < height - 1) ? pitch : 0;
for(int x = 0; x < 256; x++) {
A = *(input + prevline);
B = (x > 0) ? *(input - 1) : *input;
C = (x < 255) ? *(input + 1) : *input;
D = *(input + nextline);
P = colortable[*input];
if(A != D && B != C) {
*(output) = A == B ? colortable[A] : P;
*(output + 1) = A == C ? colortable[A] : P;
*(output + outpitch) = D == B ? colortable[D] : P;
*(output + outpitch + 1) = D == C ? colortable[D] : P;
} else {
*(output) = P;
*(output + 1) = P;
*(output + outpitch) = P;
*(output + outpitch + 1) = P;
}
input++;
output += 2;
}
input += pitch - 256;
output += outpitch + outpitch - 512;
}
outwidth = width * 2;
outheight = height * 2;
}

View File

@@ -0,0 +1,6 @@
class Scale2xFilter : public Filter {
public:
void render(uint32_t*, unsigned, unsigned&, unsigned&, uint16_t*, unsigned, unsigned*, unsigned, unsigned);
};
extern Scale2xFilter filter_scale2x;

View File

@@ -0,0 +1,40 @@
ScanlineFilter filter_scanline;
void ScanlineFilter::render(
uint32_t *output, unsigned outpitch, unsigned &outwidth, unsigned &outheight,
uint16_t *input, unsigned pitch, unsigned *line, unsigned width, unsigned height
) {
if(height > 240) {
filter_direct.render(output, outpitch, outwidth, outheight, input, pitch, line, width, height);
return;
}
pitch >>= 1;
outpitch >>= 2;
for(unsigned y = 0; y < height; y++) {
uint32_t *out0 = output;
uint32_t *out1 = output + pitch;
if(width == 512 && line[y] == 256) {
for(unsigned x = 0; x < 256; x++) {
uint16_t p = *input++;
*out0++ = colortable[p];
*out0++ = colortable[p];
*out1++ = (colortable[p] >> 1) & 0x7f7f7f;
*out1++ = (colortable[p] >> 1) & 0x7f7f7f;
}
input += 256;
} else {
for(unsigned x = 0; x < width; x++) {
uint16_t p = *input++;
*out0++ = colortable[p];
*out1++ = (colortable[p] >> 1) & 0x7f7f7f;
}
}
input += pitch - width;
output += outpitch * 2;
}
outwidth = width;
outheight = height * 2;
}

View File

@@ -0,0 +1,6 @@
class ScanlineFilter : public Filter {
public:
void render(uint32_t*, unsigned, unsigned&, unsigned&, uint16_t*, unsigned, unsigned*, unsigned, unsigned);
};
extern ScanlineFilter filter_scanline;

View File

@@ -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

View File

@@ -0,0 +1,228 @@
/* 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 */
/*
#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;\
}
*/
/* custom bsnes routine -- hooks into bsnes colortable */
#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;\
}\
}
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,26 @@
/* 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 32
/* 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
/* For each pixel, this is the basic operation:
output_color = SNES_NTSC_ADJ_IN( SNES_NTSC_IN_T ) */
#endif

View File

@@ -0,0 +1,439 @@
/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
/* Common implementation of NTSC filters */
#include <assert.h>
#include <math.h>
/* 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 <limits.h>
#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