1/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
2
3#include "snes_ntsc.h"
4
5/* Copyright (C) 2006-2007 Shay Green. This module is free software; you
6can redistribute it and/or modify it under the terms of the GNU Lesser
7General Public License as published by the Free Software Foundation; either
8version 2.1 of the License, or (at your option) any later version. This
9module is distributed in the hope that it will be useful, but WITHOUT ANY
10WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12details. You should have received a copy of the GNU Lesser General Public
13License along with this module; if not, write to the Free Software Foundation,
14Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
15
16snes_ntsc_setup_t const snes_ntsc_monochrome = { 0,-1, 0, 0,.2, 0,.2,-.2,-.2,-1, 1, 0, 0 };
17snes_ntsc_setup_t const snes_ntsc_composite = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 };
18snes_ntsc_setup_t const snes_ntsc_svideo = { 0, 0, 0, 0,.2, 0,.2, -1, -1, 0, 1, 0, 0 };
19snes_ntsc_setup_t const snes_ntsc_rgb = { 0, 0, 0, 0,.2, 0,.7, -1, -1,-1, 1, 0, 0 };
20
21#define alignment_count 3
22#define burst_count 3
23#define rescale_in 8
24#define rescale_out 7
25
26#define artifacts_mid 1.0f
27#define fringing_mid 1.0f
28#define std_decoder_hue 0
29
30#define rgb_bits 7 /* half normal range to allow for doubled hires pixels */
31#define gamma_size 32
32
33#include "snes_ntsc_impl.h"
34
35unsigned int snes_ntsc_scanline_offset = 0;
36unsigned short snes_ntsc_scanline_mask = 0xffff;
37
38/* 3 input pixels -> 8 composite samples */
39pixel_info_t const snes_ntsc_pixels [alignment_count] = {
40 { PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } },
41 { PIXEL_OFFSET( -2, -7 ), { .3333f, 1, 1, .3333f } },
42 { PIXEL_OFFSET( 0, -5 ), { 0, .6667f, 1, 1 } },
43};
44
45static void merge_kernel_fields( snes_ntsc_rgb_t* io )
46{
47 int n;
48 for ( n = burst_size; n; --n )
49 {
50 snes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias;
51 snes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias;
52 snes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias;
53 /* merge colors without losing precision */
54 io [burst_size * 0] =
55 ((p0 + p1 - ((p0 ^ p1) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
56 io [burst_size * 1] =
57 ((p1 + p2 - ((p1 ^ p2) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
58 io [burst_size * 2] =
59 ((p2 + p0 - ((p2 ^ p0) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
60 ++io;
61 }
62}
63
64static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out )
65{
66 int n;
67 for ( n = burst_count; n; --n )
68 {
69 unsigned i;
70 for ( i = 0; i < rgb_kernel_size / 2; i++ )
71 {
72 snes_ntsc_rgb_t error = color -
73 out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] -
74 out [i + 7] - out [i + 5 +14] - out [i + 3 +28];
75 DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 );
76 }
77 out += alignment_count * rgb_kernel_size;
78 }
79}
80
81void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup )
82{
83 int merge_fields;
84 int entry;
85 init_t impl;
86 if ( !setup )
87 setup = &snes_ntsc_composite;
88 init( &impl, setup );
89
90 merge_fields = setup->merge_fields;
91 if ( setup->artifacts <= -1 && setup->fringing <= -1 )
92 merge_fields = 1;
93
94 for ( entry = 0; entry < snes_ntsc_palette_size; entry++ )
95 {
96 /* Reduce number of significant bits of source color. Clearing the
97 low bits of R and B were least notictable. Modifying green was too
98 noticeable. */
99 int ir = entry >> 8 & 0x1E;
100 int ig = entry >> 4 & 0x1F;
101 int ib = entry << 1 & 0x1E;
102
103 #if SNES_NTSC_BSNES_COLORTBL
104 if ( setup->bsnes_colortbl )
105 {
106 int bgr15 = (ib << 10) | (ig << 5) | ir;
107 unsigned long rgb16 = setup->bsnes_colortbl [bgr15];
108 ir = rgb16 >> 11 & 0x1E;
109 ig = rgb16 >> 6 & 0x1F;
110 ib = rgb16 & 0x1E;
111 }
112 #endif
113
114 {
115 float rr = impl.to_float [ir];
116 float gg = impl.to_float [ig];
117 float bb = impl.to_float [ib];
118
119 float y, i, q = RGB_TO_YIQ( rr, gg, bb, y, i );
120
121 int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g );
122 snes_ntsc_rgb_t rgb = PACK_RGB( r, g, b );
123
124 snes_ntsc_rgb_t* out = ntsc->table [entry];
125 gen_kernel( &impl, y, i, q, out );
126 if ( merge_fields )
127 merge_kernel_fields( out );
128 correct_errors( rgb, out );
129 }
130 }
131}
132
133#ifndef SNES_NTSC_NO_BLITTERS
134
135void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
136 int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
137{
138 int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
139 for ( ; in_height; --in_height )
140 {
141 SNES_NTSC_IN_T const* line_in = input;
142 SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
143 snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
144 snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
145 int n;
146 ++line_in;
147
148 for ( n = chunk_count; n; --n )
149 {
150 /* order of input and output pixels must not be altered */
151 SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
152 SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
153 SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
154
155 SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
156 SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
157 SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
158
159 SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
160 SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
161 SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
162 SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
163
164 line_in += 3;
165 line_out += 7;
166 }
167
168 /* finish final pixels */
169 SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
170 SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
171 SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
172
173 SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
174 SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
175 SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
176
177 SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
178 SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
179 SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
180 SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
181
182 burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
183 input += in_row_width;
184 rgb_out = (char*) rgb_out + out_pitch;
185 }
186}
187
188void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
189 int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
190{
191 int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2);
192 for ( ; in_height; --in_height )
193 {
194 SNES_NTSC_IN_T const* line_in = input;
195 SNES_NTSC_HIRES_ROW( ntsc, burst_phase,
196 snes_ntsc_black, snes_ntsc_black, snes_ntsc_black,
197 SNES_NTSC_ADJ_IN( line_in [0] ),
198 SNES_NTSC_ADJ_IN( line_in [1] ) );
199 snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
200 int n;
201 line_in += 2;
202
203 for ( n = chunk_count; n; --n )
204 {
205 /* twice as many input pixels per chunk */
206 SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
207 SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
208
209 SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
210 SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
211
212 SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
213 SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
214
215 SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) );
216 SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
217
218 SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) );
219 SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
220
221 SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) );
222 SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
223 SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
224
225 line_in += 6;
226 line_out += 7;
227 }
228
229 SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
230 SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
231
232 SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
233 SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
234
235 SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
236 SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
237
238 SNES_NTSC_COLOR_IN( 3, snes_ntsc_black );
239 SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
240
241 SNES_NTSC_COLOR_IN( 4, snes_ntsc_black );
242 SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
243
244 SNES_NTSC_COLOR_IN( 5, snes_ntsc_black );
245 SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
246 SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
247
248 burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
249 input += in_row_width;
250 rgb_out = (char*) rgb_out + out_pitch;
251 }
252}
253
254/* 12.5% scanlines like in snes_ntsc example instead of zsnes's 25% */
255#define PIXEL_OUT( x ) \
256 SNES_NTSC_RGB_OUT( x, value, SNES_NTSC_OUT_DEPTH ); \
257 line_outa[x] = value; \
258 line_outb[x] = value - (value >> snes_ntsc_scanline_offset & snes_ntsc_scanline_mask);
259
260void snes_ntsc_blit_scanlines( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
261 int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
262{
263 unsigned value;
264 int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
265 for ( ; in_height; --in_height )
266 {
267 SNES_NTSC_IN_T const* line_in = input;
268 SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
269 snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
270 snes_ntsc_out_t * restrict line_outa = (snes_ntsc_out_t *) rgb_out;
271 snes_ntsc_out_t * restrict line_outb = (snes_ntsc_out_t *) ((char *) line_outa + out_pitch);
272 int n;
273 ++line_in;
274
275 for ( n = chunk_count; n; --n )
276 {
277 /* order of input and output pixels must not be altered */
278 SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
279 PIXEL_OUT (0);
280 PIXEL_OUT (1);
281
282 SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
283 PIXEL_OUT (2);
284 PIXEL_OUT (3);
285
286 SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
287 PIXEL_OUT (4);
288 PIXEL_OUT (5);
289 PIXEL_OUT (6);
290
291 line_in += 3;
292 line_outa += 7;
293 line_outb += 7;
294 }
295
296 /* finish final pixels */
297 SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
298 PIXEL_OUT (0);
299 PIXEL_OUT (1);
300
301 SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
302 PIXEL_OUT (2);
303 PIXEL_OUT (3);
304
305 SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
306 PIXEL_OUT (4);
307 PIXEL_OUT (5);
308 PIXEL_OUT (6);
309
310 burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
311 input += in_row_width;
312 rgb_out = (char*) rgb_out + 2 * out_pitch;
313 }
314}
315
316#define PIXEL_OUT_HIRES( x ) \
317 SNES_NTSC_HIRES_OUT( x, value, SNES_NTSC_OUT_DEPTH ); \
318 line_outa[x] = value; \
319 line_outb[x] = value - (value >> snes_ntsc_scanline_offset & snes_ntsc_scanline_mask);
320
321void snes_ntsc_blit_hires_scanlines( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
322 int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
323{
324 int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2);
325 unsigned value;
326 for ( ; in_height; --in_height )
327 {
328 SNES_NTSC_IN_T const* line_in = input;
329 SNES_NTSC_HIRES_ROW( ntsc, burst_phase,
330 snes_ntsc_black, snes_ntsc_black, snes_ntsc_black,
331 SNES_NTSC_ADJ_IN( line_in [0] ),
332 SNES_NTSC_ADJ_IN( line_in [1] ) );
333 snes_ntsc_out_t* restrict line_outa = (snes_ntsc_out_t*) rgb_out;
334 snes_ntsc_out_t* restrict line_outb = (snes_ntsc_out_t*) ((char *) line_outa + out_pitch);
335 int n;
336 line_in += 2;
337
338 for ( n = chunk_count; n; --n )
339 {
340 /* twice as many input pixels per chunk */
341 SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
342 PIXEL_OUT_HIRES (0);
343
344 SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
345 PIXEL_OUT_HIRES (1);
346
347 SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
348 PIXEL_OUT_HIRES (2);
349
350 SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) );
351 PIXEL_OUT_HIRES (3);
352
353 SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) );
354 PIXEL_OUT_HIRES (4);
355
356 SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) );
357 PIXEL_OUT_HIRES (5);
358 PIXEL_OUT_HIRES (6);
359
360 line_in += 6;
361 line_outa += 7;
362 line_outb += 7;
363 }
364
365 SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
366 PIXEL_OUT_HIRES (0);
367
368 SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
369 PIXEL_OUT_HIRES (1);
370
371 SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
372 PIXEL_OUT_HIRES (2);
373
374 SNES_NTSC_COLOR_IN( 3, snes_ntsc_black );
375 PIXEL_OUT_HIRES (3);
376
377 SNES_NTSC_COLOR_IN( 4, snes_ntsc_black );
378 PIXEL_OUT_HIRES (4);
379
380 SNES_NTSC_COLOR_IN( 5, snes_ntsc_black );
381 PIXEL_OUT_HIRES (5);
382 PIXEL_OUT_HIRES (6);
383
384 burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
385 input += in_row_width;
386 rgb_out = (char*) rgb_out + out_pitch * 2;
387 }
388}
389
390#endif
391