/* * 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); } }