/*
* HalfNES by Andrew Hoffman
* Licensed under the GNU GPL Version 3. See LICENSE file
*/
package com.grapeshot.halfnes.video;
import java.awt.image.BufferedImage;
import com.grapeshot.halfnes.utils;
import com.grapeshot.halfnes.utils;
/**
*
* @author Andrew
*/
//Direct port of Bisqwit's code on the wiki. (probably just as slow, we'll see.)
//yep, it's WORSE
//the expensive part is the Math function calls of course
public class AltNTSCRenderer extends Renderer {
public AltNTSCRenderer() {
frame_width = 604;
init_images();
// for (int i = 0; i < 12; ++i) {
// System.err.println(inColorPhase( 3, i));
// };
}
private final int[] frame = new int[width * 240];
private int frame_ptr = 0;
private int frame_ctr;
private int phase;
private final static double attenuation = 0.746;
private final static double[] levels = {
-0.117f, 0.000f, 0.308f, 0.715f,
0.397f, 0.681f, 1.0f, 1.0f
//0x00 0x10 0x20 0x30
};
private final static int SAMPLESPERPIXEL = 8;
@Override
public BufferedImage render(int[] nespixels, int[] bgcolors, boolean dotcrawl) {
++frame_ctr;
if ((frame_ctr & 1) == 0) {
phase = 0;
} else {
phase = 6;
}
for (int i = 0; i < 240; ++i) {
double phi = (phase + 3.9 - 0.3) % 12;
for (int j = 0; j < 256; ++j) {
ntsc_render(nespixels[i * 256 + j]);
}
ntsc_decode(phi);
ntsc_buf_ptr = 0;
}
frame_ptr = 0;
return getBufferedImage(frame);
}
private void ntsc_render(int pixel) {
int color = pixel & 0xf;
int level = (pixel >> 4) & 3;
int emphasis = (pixel >> 6);
if (color > 13) {
level = 1;
}
double low = levels[level];
double high = levels[4 + level];
if (color == 0) {
low = high;
} else if (color > 12) {
high = low;
}
for (int i = 0; i < SAMPLESPERPIXEL; ++i, ++phase) {
double signal = inColorPhase(color, phase) ? high : low;
if (emphasis != 0) {
if ((((emphasis & (utils.BIT0)) != 0) && inColorPhase(0, phase))
|| (((emphasis & (utils.BIT1)) != 0) && inColorPhase(4, phase))
|| (((emphasis & (utils.BIT2)) != 0) && inColorPhase(8, phase))) {
signal *= attenuation;
}
}
signal_levels[ntsc_buf_ptr++] = signal;
}
}
private static boolean inColorPhase(final int color, final int phase) {
return (color + phase) % 12 < 6;
}
private final double[] signal_levels = new double[256 * SAMPLESPERPIXEL];
private int ntsc_buf_ptr = 0;
private final static int width = 604;
private void ntsc_decode(final double phase) {
for (int x = 0; x < width; ++x) {
int center = x * (256 * SAMPLESPERPIXEL) / width + 0;
int begin = center - 6;
if (begin < 0) {
begin = 0;
}
int end = center + 6;
if (end > 256 * SAMPLESPERPIXEL) {
end = (256 * SAMPLESPERPIXEL);
}
double y = 0, i = 0, q = 0;
for (int p = begin; p < end; ++p) {
double level = signal_levels[p] / 12.;
y += level;
i += level * Math.cos(Math.PI * (phase + p) / 6.);
q += level * Math.sin(Math.PI * (phase + p) / 6.);
}
render_pixel(y, i, q);
}
}
private void render_pixel(final double y, final double i, final double q) {
final int rgb = 0xff000000
| 0x10000 * clamp(255.95 * gammafix(y + 0.946882f * i + 0.623557f * q))
+ 0x00100 * clamp(255.95 * gammafix(y + -0.274788f * i + -0.635691f * q))
+ 0x00001 * clamp(255.95 * gammafix(y + -1.108545f * i + 1.709007f * q));
frame[frame_ptr++] = rgb;
}
public static int clamp(final double a) {
return (int) ((a < 0) ? 0 : ((a > 255) ? 255 : a));
}
public static double gammafix(double luma) {
final float gamma = 2.0f; // Assumed display gamma
return luma <= 0.f ? 0.f : Math.pow(luma, 2.2f / gamma);
}
}