Epple-II/src/analogtv.cpp

487 lines
12 KiB
C++

/*
epple2
Copyright (C) 2008 by Christopher A. Mosher <cmosher01@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
This class was highly inspired by Trevor Blackwell's Analog TV
module ("hack") of the XScreenSaver collection, therefore, portions
Copyright (C) 2003, 2004, by Trevor Blackwell <tlb@tlb.org>
(Thanks, Trevor!)
For this class, I removed the "interference" type processing, and
just took the raw color processing functionality. See the drawTVOld
method definition in this file for the analog TV processing.
I also added processing for the other ("new") TV, and various monitors.
*/
#include "analogtv.h"
#include "screenimage.h"
#include "applentsc.h"
#include "filterluma.h"
#include "filterchroma.h"
#include <map>
#include <cmath>
#include <ctime>
#include <cstdlib>
AnalogTV::AnalogTV(ScreenImage& image):
image(image),
on(false),
noise(false),
type(TV_OLD_COLOR)
{
hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_GREEN]);
hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_ORANGE]);
hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_VIOLET]);
hirescolor.push_back(colors.c()[A2ColorsObserved::HIRES_BLUE]);
loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_BROWN]);
loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_MAGENTA]);
loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_BLUE]);
loreslightcolor.push_back(colors.c()[A2ColorsObserved::LIGHT_BLUE_GREEN]);
loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_BLUE_GREEN]);
loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_BROWN]);
loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_MAGENTA]);
loresdarkcolor.push_back(colors.c()[A2ColorsObserved::DARK_BLUE]);
}
AnalogTV::~AnalogTV()
{
}
void AnalogTV::powerOn(bool b)
{
this->on = b;
this->image.notifyObservers();
}
void AnalogTV::setType(DisplayType type)
{
this->type = type;
}
void AnalogTV::cycleType()
{
this->type = (DisplayType)((((int)this->type)+1)%NUM_DISPLAY_TYPES);
}
// void dump()
// {
// const int[] yiq = new int[AppleNTSC::H];
// for (int row = 0; row < 192; ++row)
// {
// const CB cb = get_cb(row);
// const IQ iq_factor = get_iq_factor(cb);
// ntsc_to_yiq(row*AppleNTSC::H+350,AppleNTSC::H-2-350,iq_factor,yiq);
// for (int col = 350; col < AppleNTSC::H-2; ++col)
// {
// const int sig = this->signal[row*AppleNTSC::H+col];
// System.out.printf(" %+04d",sig);
// const int yiqv = yiq[col-350];
// int y = (yiqv&0xFF)-IQINTOFF;
// int i = ((yiqv>>8)&0xFF)-IQINTOFF;
// int q = ((yiqv>>16)&0xFF)-IQINTOFF;
// System.out.printf("(%+04d,%+04d,%+04d)",y,i,q);
//
// const int rgb = yiq2rgb(yiqv);
// const int r = (rgb >> 16) & 0xff;
// const int g = (rgb >> 8) & 0xff;
// const int b = (rgb ) & 0xff;
// System.out.printf("[%06X:%03d,%03d,%03d]",rgb,r,g,b);
// }
// System.out.println();
// }
//
// }
class IQ
{
public:
double iq[4];
IQ()
{
for (int i = 0; i < 4; ++i)
iq[i] = 0;
}
IQ(double aiq[])
{
for (int i = 0; i < 4; ++i)
iq[i] = aiq[i];
}
double get(int i) const { return this->iq[i]; }
};
const IQ& AnalogTV::BLACK_AND_WHITE = IQ();
static const int CB_EXTRA(32);
class CB
{
public:
std::vector<int> cb;
CB(const int acb[]):
cb(AppleNTSC::CB_END-AppleNTSC::CB_START-CB_EXTRA)
{
for (std::vector<int>::size_type i(0); i < this->cb.size(); ++i)
{
this->cb[i] = acb[i];
}
}
int get(const int i) const { return this->cb[i]; }
int length() const { return (int)this->cb.size(); }
void getPhase(double phase[]) const
{
{
for (int i = 0; i < 4; ++i)
{
phase[i & 3] = 0;
}
}
{
for (int i = 0; i < length(); ++i)
{
phase[i & 3] += this->cb[i];
}
}
double tot = 0;
{
for (int i = 0; i < 4; ++i)
{
tot += phase[i] * phase[i];
}
}
const double tsrt = sqrt(tot);
if (tsrt < .0001)
{
return;
}
for (int i = 0; i < 4; i++)
{
phase[i] /= tsrt;
}
}
bool isColor() const
{
int tot = 0;
for (int i = 0; i < length(); ++i)
{
const int icb = this->cb[i];
if (icb < 0)
tot += -icb;
else
tot += icb;
}
return 220 < tot && tot < 260;
}
bool operator<(const CB& that) const
{
return this->cb < that.cb;
}
};
void AnalogTV::drawCurrent()
{
if (this->on)
{
switch (this->type)
{
case MONITOR_COLOR: drawMonitorColor(); break;
case MONITOR_GREEN: drawMonitorGreen(); break;
case TV_OLD_COLOR: drawTVOld(); break;
case NUM_DISPLAY_TYPES: break;
}
}
else
{
drawBlank();
}
this->image.notifyObservers();
}
static const int D_IP(AppleNTSC::H-2-350);
void AnalogTV::drawMonitorColor()
{
unsigned int *rgb = new unsigned int[AppleNTSC::H];
int ip = 0;
for (int row = 0; row < 192; ++row)
{
const CB cb = get_cb(row);
const bool removeColor = !cb.isColor();
ntsc_to_rgb_monitor(row*AppleNTSC::H+350,AppleNTSC::H-350,rgb);
for (int col = 350; col < AppleNTSC::H-2; ++col)
{
int rgbv = rgb[col-350];
if (removeColor && rgbv != 0)
{
rgbv = 0xFFFFFF;
}
this->image.setElem(ip,rgbv);
this->image.setElem(ip+D_IP,rgbv); // display same pixel on next row
++ip;
}
ip += D_IP;
}
delete [] rgb;
}
void AnalogTV::drawMonitorGreen()
{
drawMonitorMonochrome(colors.c()[A2ColorsObserved::HIRES_GREEN]);
}
void AnalogTV::drawMonitorMonochrome(const unsigned int color)
{
int ip = 0;
for (int row = 0; row < 192; ++row)
{
for (int col = 350; col < AppleNTSC::H-2; ++col)
{
const int is = row*AppleNTSC::H+col;
const unsigned int rgb = this->signal[is] > 50 ? color : 0;
this->image.setElem(ip,rgb);
this->image.setElem(ip+D_IP,rgb);
++ip;
}
ip += D_IP;
}
}
void AnalogTV::drawTVOld()
{
int *yiq = new int[AppleNTSC::H];
int ip = 0;
for (int row = 0; row < 192; ++row)
{
IQ iq_factor;
const CB cb = get_cb(row);
iq_factor = get_iq_factor(cb);
ntsc_to_yiq(row*AppleNTSC::H+350,AppleNTSC::H-350,iq_factor,yiq);
for (int col = 350; col < AppleNTSC::H-2; ++col)
{
const int rgb = yiq2rgb(yiq[col-348]); // shift display left 1 pixel
this->image.setElem(ip,rgb);
this->image.setElem(ip+D_IP,rgb);
++ip;
}
ip += D_IP;
}
delete [] yiq;
}
void AnalogTV::drawBlank()
{
this->image.blank();
}
void AnalogTV::ntsc_to_rgb_monitor(const int isignal, const int siglen, unsigned int rgb[])
{
int s0, s1, se;
s0 = s1 = isignal;
se = isignal+siglen;
while (s1 < se)
{
// no signal (black)
while (this->signal[s0] < 50 && s0<se) { rgb[s0-isignal] = 0; ++s0; }
// signal (white, grey, or color)
s1 = s0;
while (this->signal[s1] > 50 && s1<se) { ++s1; }
const int slen = s1-s0;
unsigned int c = 0;
if (slen >= 4)
{
c = 0xFFFFFF;
}
else if (slen == 1)
{
if (this->signal[s0-2] > 50 && this->signal[s0+2] > 50)
c = 0xFFFFFF;
else
c = loresdarkcolor[s0 % 4];
}
else if (slen == 2)
{
c = hirescolor[s0 % 4];
}
else if (slen == 3)
{
c = loreslightcolor[s0 % 4];
}
else
{
++s1;
}
for (int i = s0; i < s1; ++i)
rgb[i-isignal] = c;
s0 = s1;
}
}
int *AnalogTV::rcb = new int[AppleNTSC::CB_END-AppleNTSC::CB_START-CB_EXTRA];
CB AnalogTV::get_cb(int lineno)
{
const int isp = lineno * AppleNTSC::H;
for (int i = AppleNTSC::CB_START + CB_EXTRA/2; i < AppleNTSC::CB_END - CB_EXTRA/2; ++i)
{
this->rcb[i-(AppleNTSC::CB_START + CB_EXTRA/2)] = this->signal[isp + i];
}
return CB(this->rcb);
}
static std::map<CB,IQ> cacheCB;
const double AnalogTV::IQ_OFFSET_DEGREES = 33; //NTSC spec. 33 IQ is rotated 33 degrees
const double AnalogTV::IQ_OFFSET_RADIANS = AnalogTV::IQ_OFFSET_DEGREES * 3.1415927 / 180;
const double AnalogTV::TINT_I = -cos(AnalogTV::IQ_OFFSET_RADIANS);
const double AnalogTV::TINT_Q = +sin(AnalogTV::IQ_OFFSET_RADIANS);
const double AnalogTV::COLOR_THRESH(sqrt(2));
IQ AnalogTV::get_iq_factor(const CB& cb)
{
std::map<CB,IQ>::iterator hit = cacheCB.find(cb);
if (hit != cacheCB.end())
{
return hit->second;
}
double cb_phase[4];
cb.getPhase(cb_phase);
const double cb_i = cb_phase[2]-cb_phase[0];
const double cb_q = cb_phase[3]-cb_phase[1];
if ((cb_i*cb_i) + (cb_q*cb_q) < COLOR_THRESH)
{
return BLACK_AND_WHITE;
}
double iq_factor[4];
iq_factor[0] = cb_i * TINT_I + cb_q * TINT_Q;
iq_factor[2] = -iq_factor[0];
iq_factor[1] = cb_q * TINT_I - cb_i * TINT_Q;
iq_factor[3] = -iq_factor[1];
const IQ iq(iq_factor);
if (!this->noise)
{
cacheCB[cb] = iq;
}
return iq;
}
const int AnalogTV::IQINTOFF(130);
void AnalogTV::ntsc_to_yiq(const int isignal, const int siglen, const IQ& iq_factor, int yiq[]) {
FilterLuma filterY;
FilterChromaI filterI;
FilterChromaQ filterQ;
for (int off = 0; off < siglen; ++off) {
const int sig = this->signal[isignal + off];
const int y = filterY.next(sig);
const int i = filterI.next(sig) * iq_factor.get(off & 3);
const int q = filterQ.next(sig) * iq_factor.get((off + 3) & 3);
yiq[off] = (((q+IQINTOFF)&0xff) << 16) | (((i+IQINTOFF)&0xff) << 8) | ((y+IQINTOFF)&0xff);
}
}
int inline AnalogTV::yiq2rgb(const int yiq)
{
double r = (((yiq)&0xFF)-IQINTOFF) + 0.956 * (((yiq>>8)&0xFF)-IQINTOFF) + 0.621 * (((yiq>>16)&0xFF)-IQINTOFF);
double g = (((yiq)&0xFF)-IQINTOFF) - 0.272 * (((yiq>>8)&0xFF)-IQINTOFF) - 0.647 * (((yiq>>16)&0xFF)-IQINTOFF);
double b = (((yiq)&0xFF)-IQINTOFF) - 1.105 * (((yiq>>8)&0xFF)-IQINTOFF) + 1.702 * (((yiq>>16)&0xFF)-IQINTOFF);
const int rgb =
(calc_color(r) << 16)|
(calc_color(g) << 8)|
(calc_color(b) << 0);
return rgb;
}
int inline AnalogTV::color2bw(const int rgb)
{
const int y = rgb2y(rgb);
return y<<16 | y<<8 | y;
}
int inline AnalogTV::rgb2y(const int rgb) // y in range 0-255
{
return (int)((0.299*((rgb>>16)&0xFF) + 0.587*((rgb>>8)&0xFF) + 0.114*((rgb)&0xFF))/1.04);
}
int inline AnalogTV::calc_color(const double color)
{
int x = (int)(color * 0x100 / AppleNTSC::LEVEL_RANGE + .5);
x = clamp(0,x,0x100);
return x & 0xFF;
}
int inline AnalogTV::clamp(int min, int x, int lim)
{
if (x < min)
return min;
if (lim <= x)
return lim-1;
return x;
}