Skip to content

Commit

Permalink
Add chroma phase control.
Browse files Browse the repository at this point in the history
To be precise, this is a rotation applied to the I/Q or U/V components
after filtering and demodulation but (for PAL) before the V-switch.

For NTSC it acts as a hue control. For PAL, it can be adjusted to
minimise Hanover bars when using the Simple PAL decoder.

The full adjustment range (-180 to +180 degrees) is allowed, but since
the adjustment will generally be small in practice, the slider in
ld-analyse uses a non-linear scale.
  • Loading branch information
atsampson committed May 3, 2021
1 parent 13acde9 commit 6ba94f9
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 9 deletions.
49 changes: 49 additions & 0 deletions tools/ld-analyse/chromadecoderconfigdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,36 @@
#include "chromadecoderconfigdialog.h"
#include "ui_chromadecoderconfigdialog.h"

#include <cmath>

/*
* These two functions provide a non-linear mapping for sliders that control
* phase adjustments in degrees. The maximum range is from -180 to +180
* degrees, but phase errors are usually < 10 degrees so we need more precise
* adjustment in the middle.
*/

static constexpr double DEGREE_SLIDER_POWER = 3.0;
static constexpr qint32 DEGREE_SLIDER_SCALE = 1000;

static double degreesToSliderPos(double degrees) {
double sliderPos = pow(abs(degrees) / 180, 1 / DEGREE_SLIDER_POWER) * DEGREE_SLIDER_SCALE;
if (degrees < 0) {
return -sliderPos;
} else {
return sliderPos;
}
}

static double sliderPosToDegrees(double sliderPos) {
double degrees = pow(abs(sliderPos) / DEGREE_SLIDER_SCALE, DEGREE_SLIDER_POWER) * 180;
if (sliderPos < 0) {
return -degrees;
} else {
return degrees;
}
}

ChromaDecoderConfigDialog::ChromaDecoderConfigDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ChromaDecoderConfigDialog)
Expand All @@ -36,6 +66,9 @@ ChromaDecoderConfigDialog::ChromaDecoderConfigDialog(QWidget *parent) :
ui->chromaGainHorizontalSlider->setMinimum(0);
ui->chromaGainHorizontalSlider->setMaximum(200);

ui->chromaPhaseHorizontalSlider->setMinimum(-DEGREE_SLIDER_SCALE);
ui->chromaPhaseHorizontalSlider->setMaximum(DEGREE_SLIDER_SCALE);

ui->thresholdHorizontalSlider->setMinimum(0);
ui->thresholdHorizontalSlider->setMaximum(100);

Expand Down Expand Up @@ -65,13 +98,15 @@ void ChromaDecoderConfigDialog::setConfiguration(bool _isSourcePal, const PalCol
outputConfiguration = _outputConfiguration;

palConfiguration.chromaGain = qBound(0.0, palConfiguration.chromaGain, 2.0);
palConfiguration.chromaPhase = qBound(-180.0, palConfiguration.chromaPhase, 180.0);
palConfiguration.transformThreshold = qBound(0.0, palConfiguration.transformThreshold, 1.0);
palConfiguration.yNRLevel = qBound(0.0, yNRLevel, 10.0);
ntscConfiguration.cNRLevel = qBound(0.0, ntscConfiguration.cNRLevel, 10.0);
ntscConfiguration.yNRLevel = qBound(0.0, yNRLevel, 10.0);

// For settings that both decoders share, the PAL default takes precedence
ntscConfiguration.chromaGain = palConfiguration.chromaGain;
ntscConfiguration.chromaPhase = palConfiguration.chromaPhase;

// Select the tab corresponding to the current standard automatically
if (isSourcePal) {
Expand Down Expand Up @@ -108,6 +143,12 @@ void ChromaDecoderConfigDialog::updateDialog()

ui->chromaGainValueLabel->setEnabled(true);
ui->chromaGainValueLabel->setText(QString::number(palConfiguration.chromaGain, 'f', 2));

ui->chromaPhaseHorizontalSlider->setEnabled(true);
ui->chromaPhaseHorizontalSlider->setValue(static_cast<qint32>(degreesToSliderPos(palConfiguration.chromaPhase)));

ui->chromaPhaseValueLabel->setEnabled(true);
ui->chromaPhaseValueLabel->setText(QString::number(palConfiguration.chromaPhase, 'f', 1) + QChar(0xB0));

double yNRLevel = isSourcePal ? palConfiguration.yNRLevel : ntscConfiguration.yNRLevel;

Expand Down Expand Up @@ -205,6 +246,14 @@ void ChromaDecoderConfigDialog::on_chromaGainHorizontalSlider_valueChanged(int v
emit chromaDecoderConfigChanged();
}

void ChromaDecoderConfigDialog::on_chromaPhaseHorizontalSlider_valueChanged(int value)
{
palConfiguration.chromaPhase = sliderPosToDegrees(static_cast<double>(value));
ntscConfiguration.chromaPhase = palConfiguration.chromaPhase;
ui->chromaPhaseValueLabel->setText(QString::number(palConfiguration.chromaPhase, 'f', 1) + QChar(0xB0));
emit chromaDecoderConfigChanged();
}

void ChromaDecoderConfigDialog::on_palFilterButtonGroup_buttonClicked(QAbstractButton *button)
{
if (button == ui->palFilterPalColourRadioButton) {
Expand Down
1 change: 1 addition & 0 deletions tools/ld-analyse/chromadecoderconfigdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class ChromaDecoderConfigDialog : public QDialog

private slots:
void on_chromaGainHorizontalSlider_valueChanged(int value);
void on_chromaPhaseHorizontalSlider_valueChanged(int value);

void on_palFilterButtonGroup_buttonClicked(QAbstractButton *button);
void on_thresholdModeCheckBox_clicked();
Expand Down
27 changes: 27 additions & 0 deletions tools/ld-analyse/chromadecoderconfigdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,33 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="chromaPhaseLabel">
<property name="text">
<string>Chroma phase:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="chromaPhaseHorizontalSlider">
<property name="maximum">
<number>200</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="chromaPhaseValueLabel">
<property name="text">
<string>0.0°</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="yNRLabel">
<property name="text">
Expand Down
9 changes: 5 additions & 4 deletions tools/ld-chroma-decoder/comb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ void Comb::decodeFrames(const QVector<SourceField> &inputFields, qint32 startInd
currentFrameBuffer->doYNR();

// Transform I/Q to U/V
currentFrameBuffer->transformIQ(configuration.chromaGain);
currentFrameBuffer->transformIQ(configuration.chromaGain, configuration.chromaPhase);

// Overlay the map if required
if (configuration.dimensions == 3 && configuration.showMap) {
Expand Down Expand Up @@ -710,11 +710,12 @@ void Comb::FrameBuffer::doYNR()
}

// Transform I/Q into U/V, and apply chroma gain
void Comb::FrameBuffer::transformIQ(double chromaGain)
void Comb::FrameBuffer::transformIQ(double chromaGain, double chromaPhase)
{
// Compute components for the rotation vector
const double bp = sin((33 * M_PI) / 180) * chromaGain;
const double bq = cos((33 * M_PI) / 180) * chromaGain;
const double theta = ((33 + chromaPhase) * M_PI) / 180;
const double bp = sin(theta) * chromaGain;
const double bq = cos(theta) * chromaGain;

// Apply the vector to all the samples
for (qint32 lineNumber = videoParameters.firstActiveFrameLine; lineNumber < videoParameters.lastActiveFrameLine; lineNumber++) {
Expand Down
5 changes: 3 additions & 2 deletions tools/ld-chroma-decoder/comb.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
ld-chroma-decoder - Colourisation filter for ld-decode
Copyright (C) 2018 Chad Page
Copyright (C) 2018-2019 Simon Inns
Copyright (C) 2020 Adam Sampson
Copyright (C) 2020-2021 Adam Sampson
Copyright (C) 2021 Phillip Blucas
This file is part of ld-decode-tools.
Expand Down Expand Up @@ -47,6 +47,7 @@ class Comb
// Comb filter configuration parameters
struct Configuration {
double chromaGain = 1.0;
double chromaPhase = 0.0;
bool colorlpf = false;
bool colorlpf_hq = true;
qint32 dimensions = 2;
Expand Down Expand Up @@ -100,7 +101,7 @@ class Comb
void adjustY();
void doCNR();
void doYNR();
void transformIQ(double chromaGain);
void transformIQ(double chromaGain, double chromaPhase);

void overlayMap(const FrameBuffer &previousFrame, const FrameBuffer &nextFrame);

Expand Down
12 changes: 12 additions & 0 deletions tools/ld-chroma-decoder/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ int main(int argc, char *argv[])
QCoreApplication::translate("main", "number"));
parser.addOption(chromaGainOption);

// Option to specify chroma phase
QCommandLineOption chromaPhaseOption(QStringList() << "chroma-phase",
QCoreApplication::translate("main", "Phase rotation applied to chroma components (degrees; default 0.0)"),
QCoreApplication::translate("main", "number"));
parser.addOption(chromaPhaseOption);

// Option to select the output format (-p)
QCommandLineOption outputFormatOption(QStringList() << "p" << "output-format",
QCoreApplication::translate("main", "Output format (rgb, yuv, y4m; default rgb); RGB48, YUV444P16, GRAY16 pixel formats are supported"),
Expand Down Expand Up @@ -324,6 +330,12 @@ int main(int argc, char *argv[])
}
}

if (parser.isSet(chromaPhaseOption)) {
const double value = parser.value(chromaPhaseOption).toDouble();
palConfig.chromaPhase = value;
combConfig.chromaPhase = value;
}

bool bwMode = parser.isSet(setBwModeOption);
if (bwMode) {
palConfig.chromaGain = 0.0;
Expand Down
8 changes: 5 additions & 3 deletions tools/ld-chroma-decoder/palcolour.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,11 @@ void PalColour::decodeField(const SourceField &inputField, const double *chromaD
// Detect the colourburst from the composite signal
detectBurst(line, compPtr);

// Scale line.bp/line.bq to apply gain adjustment
line.bp *= configuration.chromaGain;
line.bq *= configuration.chromaGain;
// Rotate and scale line.bp/line.bq to apply gain and phase adjustment
const double oldBp = line.bp, oldBq = line.bq;
const double theta = (configuration.chromaPhase * M_PI) / 180;
line.bp = (oldBp * cos(theta) - oldBq * sin(theta)) * configuration.chromaGain;
line.bq = (oldBp * sin(theta) + oldBq * cos(theta)) * configuration.chromaGain;

if (configuration.chromaFilter == palColourFilter) {
// Decode chroma and luma from the composite signal
Expand Down
1 change: 1 addition & 0 deletions tools/ld-chroma-decoder/palcolour.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class PalColour

struct Configuration {
double chromaGain = 1.0;
double chromaPhase = 0.0;
double yNRLevel = 0.5;
bool simplePAL = false;
ChromaFilterMode chromaFilter = palColourFilter;
Expand Down

0 comments on commit 6ba94f9

Please sign in to comment.