From 316eb1a54700905e988b7e33bba1872efbdedf45 Mon Sep 17 00:00:00 2001 From: "Simon A. Eugster" Date: Sun, 5 Sep 2010 14:38:09 +0000 Subject: [PATCH] Vectorscope: * Added support for YPbPr (or YCbCr) via context menu. Was YUV only before. * Background can be set to YPbPr as well. svn path=/trunk/kdenlive/; revision=4846 --- src/colorcorrection/vectorscopegenerator.cpp | 90 +++++++++++--- src/colorcorrection/vectorscopegenerator.h | 2 + src/colorcorrection/waveformgenerator.cpp | 3 +- src/vectorscope.cpp | 119 ++++++++++++++++--- src/vectorscope.h | 6 +- 5 files changed, 182 insertions(+), 38 deletions(-) diff --git a/src/colorcorrection/vectorscopegenerator.cpp b/src/colorcorrection/vectorscopegenerator.cpp index 15e42db0..9f3a9bd1 100644 --- a/src/colorcorrection/vectorscopegenerator.cpp +++ b/src/colorcorrection/vectorscopegenerator.cpp @@ -12,7 +12,8 @@ Vectorscope. - The basis matrix for converting RGB to YUV is: + The basis matrix for converting RGB to YUV is + (in this example for calculating the YUV value of red): mRgb2Yuv = r = @@ -33,14 +34,42 @@ mRgb2Yuv = r = divide the conversion values by 255 previously, e.g. in GNU octave. + + The basis matrix for converting RGB to YPbPr is: + +mRgb2YPbPr = r = + + 0.299000 0.587000 0.114000 1.0000 + -0.168736 -0.331264 0.500000 x 0.0000 + 0.500000 -0.418688 -0.081312 0.0000 + + + + Note that YUV and YPbPr are not the same. + * YUV is used for analog transfer of PAL/NTSC + * YPbPr is used for analog transfer of YCbCr sources + like DVD/DVB. + It does _not_ matter for the Vectorscope what color space + the input video is; The used color space is just a tool + to visualize the hue. The difference is merely that the + scope looks slightly different when choosing the respectively + other color space; The way the Vectorscope works stays the same. + + YCbCr is basically YPbPr for digital use (it is defined + on a range of {16,219} for Y and {16,240} for Cb/Cr + components). + + + + See also: + http://www.poynton.com/ColorFAQ.html http://de.wikipedia.org/wiki/Vektorskop http://www.elektroniktutor.de/techno/vektskop.html */ #include -#include #include #include "vectorscopegenerator.h" @@ -87,8 +116,9 @@ QPoint VectorscopeGenerator::mapToCircle(const QSize &targetSize, const QPointF } QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain, - const VectorscopeGenerator::PaintMode &paintMode, const bool&, - const uint &accelFactor) const + const VectorscopeGenerator::PaintMode &paintMode, + const VectorscopeGenerator::ColorSpace &colorSpace, + const bool &, const uint &accelFactor) const { if (vectorscopeSize.width() <= 0 || vectorscopeSize.height() <= 0 || image.width() <= 0 || image.height() <= 0) { // Invalid size @@ -113,7 +143,6 @@ QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, // Just an average for the number of image pixels per scope pixel. // NOTE: byteCount() has to be replaced by (img.bytesPerLine()*img.height()) for Qt 4.5 to compile, see: http://doc.trolltech.org/4.6/qimage.html#bytesPerLine double avgPxPerPx = (double) 4*(image.bytesPerLine()*image.height())/scope.size().width()/scope.size().height()/accelFactor; - qDebug() << "Expecting " << avgPxPerPx << " pixels per pixel."; for (int i = 0; i < (image.bytesPerLine()*image.height()); i+= stepsize) { QRgb *col = (QRgb *) bits; @@ -122,9 +151,19 @@ QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, g = qGreen(*col); b = qBlue(*col); - y = (double) 0.001173 * r +0.002302 * g +0.0004471* b; - u = (double) -0.0005781* r -0.001135 * g +0.001713 * b; - v = (double) 0.002411 * r -0.002019 * g -0.0003921* b; + switch (colorSpace) { + case VectorscopeGenerator::ColorSpace_YUV: + y = (double) 0.001173 * r +0.002302 * g +0.0004471* b; + u = (double) -0.0005781* r -0.001135 * g +0.001713 * b; + v = (double) 0.002411 * r -0.002019 * g -0.0003921* b; + break; + case VectorscopeGenerator::ColorSpace_YPbPr: + y = (double) 0.001173 * r +0.002302 * g +0.0004471* b; + u = (double) -0.0006671* r -0.001299 * g +0.0019608* b; + v = (double) 0.001961 * r -0.001642 * g -0.0003189* b; + break; + } + pt = mapToCircle(vectorscopeSize, QPointF(SCALING*gain*u, SCALING*gain*v)); @@ -140,10 +179,20 @@ QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, // see yuvColorWheel dy = 128; // Default Y value. Lower = darker. - // Calculate the RGB values from YUV - dr = dy + 290.8*v; - dg = dy - 100.6*u - 148*v; - db = dy + 517.2*u; + // Calculate the RGB values from YUV/YPbPr + switch (colorSpace) { + case VectorscopeGenerator::ColorSpace_YUV: + dr = dy + 290.8*v; + dg = dy - 100.6*u - 148*v; + db = dy + 517.2*u; + break; + case VectorscopeGenerator::ColorSpace_YPbPr: + dr = dy + 357.5*v; + dg = dy - 87.75*u - 182*v; + db = dy + 451.9*u; + break; + } + if (dr < 0) dr = 0; if (dg < 0) dg = 0; @@ -158,10 +207,19 @@ QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, case PaintMode_Chroma: dy = 200; // Default Y value. Lower = darker. - // Calculate the RGB values from YUV - dr = dy + 290.8*v; - dg = dy - 100.6*u - 148*v; - db = dy + 517.2*u; + // Calculate the RGB values from YUV/YPbPr + switch (colorSpace) { + case VectorscopeGenerator::ColorSpace_YUV: + dr = dy + 290.8*v; + dg = dy - 100.6*u - 148*v; + db = dy + 517.2*u; + break; + case VectorscopeGenerator::ColorSpace_YPbPr: + dr = dy + 357.5*v; + dg = dy - 87.75*u - 182*v; + db = dy + 451.9*u; + break; + } // Scale the RGB values back to max 255 dmax = dr; diff --git a/src/colorcorrection/vectorscopegenerator.h b/src/colorcorrection/vectorscopegenerator.h index 1f6851d2..15744e1a 100644 --- a/src/colorcorrection/vectorscopegenerator.h +++ b/src/colorcorrection/vectorscopegenerator.h @@ -23,10 +23,12 @@ class VectorscopeGenerator : public QObject Q_OBJECT public: + enum ColorSpace { ColorSpace_YUV, ColorSpace_YPbPr }; enum PaintMode { PaintMode_Green, PaintMode_Green2, PaintMode_Original, PaintMode_Chroma, PaintMode_YUV, PaintMode_Black }; QImage calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain, const VectorscopeGenerator::PaintMode &paintMode, + const VectorscopeGenerator::ColorSpace &colorSpace, const bool&, const uint &accelFactor = 1) const; QPoint mapToCircle(const QSize &targetSize, const QPointF &point) const; diff --git a/src/colorcorrection/waveformgenerator.cpp b/src/colorcorrection/waveformgenerator.cpp index 21d4b9cc..f1ea3115 100644 --- a/src/colorcorrection/waveformgenerator.cpp +++ b/src/colorcorrection/waveformgenerator.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -68,7 +67,7 @@ QImage WaveformGenerator::calculateWaveform(const QSize &waveformSize, const QIm // Must be a float because the acceleration factor can be high, leading to <1 expected px per px. const float pixelDepth = (float)((byteCount>>2) / accelFactor)/(ww*wh); const float gain = 255/(8*pixelDepth); - qDebug() << "Pixel depth: expected " << pixelDepth << "; Gain: using " << gain << " (acceleration: " << accelFactor << "x)"; + //qDebug() << "Pixel depth: expected " << pixelDepth << "; Gain: using " << gain << " (acceleration: " << accelFactor << "x)"; // Subtract 1 from sizes because we start counting from 0. diff --git a/src/vectorscope.cpp b/src/vectorscope.cpp index 6660105e..b046fa13 100644 --- a/src/vectorscope.cpp +++ b/src/vectorscope.cpp @@ -34,6 +34,13 @@ const QPointF YUV_Cy(.147, -.615); const QPointF YUV_Mg(.289, .515); const QPointF YUV_Yl(-.437, .100); +const QPointF YPbPr_R(-.169, .5); +const QPointF YPbPr_G(-.331, -.419); +const QPointF YPbPr_B(.5, -.081); +const QPointF YPbPr_Cy(.169, -.5); +const QPointF YPbPr_Mg(.331, .419); +const QPointF YPbPr_Yl(-.5, .081); + Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) : AbstractScopeWidget(projMonitor, clipMonitor, parent), @@ -56,6 +63,7 @@ Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *pa ui->backgroundMode->addItem(i18n("None"), QVariant(BG_NONE)); ui->backgroundMode->addItem(i18n("YUV"), QVariant(BG_YUV)); ui->backgroundMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(BG_CHROMA)); + ui->backgroundMode->addItem("YPbPr", QVariant(BG_YPbPr)); // TODO translate after 0.7.8 release ui->sliderGain->setMinimum(0); ui->sliderGain->setMaximum(40); @@ -86,6 +94,20 @@ Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *pa m_menu->addAction(m_aAxisEnabled); b &= connect(m_aAxisEnabled, SIGNAL(changed()), this, SLOT(forceUpdateBackground())); + // TODO use i18n after the 0.7.8 release + m_menu->addSeparator()->setText("Color Space"); + m_aColorSpace_YPbPr = new QAction("YPbPr", this); + m_aColorSpace_YPbPr->setCheckable(true); + m_aColorSpace_YUV = new QAction("YUV", this); + m_aColorSpace_YUV->setCheckable(true); + m_agColorSpace = new QActionGroup(this); + m_agColorSpace->addAction(m_aColorSpace_YPbPr); + m_agColorSpace->addAction(m_aColorSpace_YUV); + m_menu->addAction(m_aColorSpace_YPbPr); + m_menu->addAction(m_aColorSpace_YUV); + b &= connect(m_aColorSpace_YPbPr, SIGNAL(toggled(bool)), this, SLOT(slotColorSpaceChanged())); + b &= connect(m_aColorSpace_YUV, SIGNAL(toggled(bool)), this, SLOT(slotColorSpaceChanged())); + Q_ASSERT(b); // To make the 1.0x text show @@ -102,9 +124,12 @@ Vectorscope::~Vectorscope() delete m_colorPlaneExport; delete m_vectorscopeGenerator; + delete m_aColorSpace_YPbPr; + delete m_aColorSpace_YUV; delete m_aExportBackground; delete m_aAxisEnabled; delete m_a75PBox; + delete m_agColorSpace; } QString Vectorscope::widgetName() const { return QString("Vectorscope"); } @@ -120,6 +145,8 @@ void Vectorscope::readConfig() ui->backgroundMode->setCurrentIndex(scopeConfig.readEntry("backgroundmode").toInt()); ui->paintMode->setCurrentIndex(scopeConfig.readEntry("paintmode").toInt()); ui->sliderGain->setValue(scopeConfig.readEntry("gain", 1)); + m_aColorSpace_YPbPr->setChecked(scopeConfig.readEntry("colorspace_ypbpr", false)); + m_aColorSpace_YUV->setChecked(!m_aColorSpace_YPbPr->isChecked()); } void Vectorscope::writeConfig() @@ -131,6 +158,7 @@ void Vectorscope::writeConfig() scopeConfig.writeEntry("backgroundmode", ui->backgroundMode->currentIndex()); scopeConfig.writeEntry("paintmode", ui->paintMode->currentIndex()); scopeConfig.writeEntry("gain", ui->sliderGain->value()); + scopeConfig.writeEntry("colorspace_ypbpr", m_aColorSpace_YPbPr->isChecked()); scopeConfig.sync(); } @@ -224,9 +252,12 @@ QImage Vectorscope::renderScope(uint accelerationFactor, const QImage qimage) qDebug() << "Scope size not known yet. Aborting."; } else { + VectorscopeGenerator::ColorSpace colorSpace = m_aColorSpace_YPbPr->isChecked() ? + VectorscopeGenerator::ColorSpace_YPbPr : VectorscopeGenerator::ColorSpace_YUV; + VectorscopeGenerator::PaintMode paintMode = (VectorscopeGenerator::PaintMode) ui->paintMode->itemData(ui->paintMode->currentIndex()).toInt(); scope = m_vectorscopeGenerator->calculateVectorscope(m_scopeRect.size(), qimage, - m_gain, (VectorscopeGenerator::PaintMode) ui->paintMode->itemData(ui->paintMode->currentIndex()).toInt(), + m_gain, paintMode, colorSpace, m_aAxisEnabled->isChecked(), accelerationFactor); } @@ -250,6 +281,9 @@ QImage Vectorscope::renderBackground(uint) case BG_CHROMA: bg = m_colorTools->yuvColorWheel(m_scopeRect.size(), (unsigned char) 255, 1/VectorscopeGenerator::scaling, true, true); break; + case BG_YPbPr: + bg = m_colorTools->yPbPrColorWheel(m_scopeRect.size(), (unsigned char) 128, 1/VectorscopeGenerator::scaling, true); + break; default: qDebug() << "No background."; bg = QImage(cw, cw, QImage::Format_ARGB32); @@ -269,29 +303,55 @@ QImage Vectorscope::renderBackground(uint) davinci.drawEllipse(0, 0, cw, cw); // Draw RGB/CMY points with 100% chroma - vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_R); - davinci.drawEllipse(vinciPoint, 4,4); - davinci.drawText(vinciPoint-QPoint(20, -10), "R"); + if (m_aColorSpace_YUV->isChecked()) { + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_R); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint-QPoint(20, -10), "R"); + + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_G); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint-QPoint(20, 0), "G"); + + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_B); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint+QPoint(15, 10), "B"); + + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Cy); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint+QPoint(15, -5), "Cy"); + + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Mg); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint+QPoint(15, 10), "Mg"); + + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Yl); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint-QPoint(25, 0), "Yl"); + } else { + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_R); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint-QPoint(20, -10), "R"); - vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_G); - davinci.drawEllipse(vinciPoint, 4,4); - davinci.drawText(vinciPoint-QPoint(20, 0), "G"); + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_G); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint-QPoint(20, 0), "G"); - vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_B); - davinci.drawEllipse(vinciPoint, 4,4); - davinci.drawText(vinciPoint+QPoint(15, 10), "B"); + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_B); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint+QPoint(15, 10), "B"); - vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Cy); - davinci.drawEllipse(vinciPoint, 4,4); - davinci.drawText(vinciPoint+QPoint(15, -5), "Cy"); + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_Cy); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint+QPoint(15, -5), "Cy"); - vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Mg); - davinci.drawEllipse(vinciPoint, 4,4); - davinci.drawText(vinciPoint+QPoint(15, 10), "Mg"); + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_Mg); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint+QPoint(15, 10), "Mg"); - vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Yl); - davinci.drawEllipse(vinciPoint, 4,4); - davinci.drawText(vinciPoint-QPoint(25, 0), "Yl"); + vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YPbPr_Yl); + davinci.drawEllipse(vinciPoint, 4,4); + davinci.drawText(vinciPoint-QPoint(25, 0), "Yl"); + } switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) { case BG_NONE: @@ -389,3 +449,24 @@ void Vectorscope::slotBackgroundChanged() } forceUpdateBackground(); } + +void Vectorscope::slotColorSpaceChanged() +{ + int index; + if (m_aColorSpace_YPbPr->isChecked()) { + if (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt() == BG_YUV) { + index = ui->backgroundMode->findData(QVariant(BG_YPbPr)); + if (index >= 0) { + ui->backgroundMode->setCurrentIndex(index); + } + } + } else { + if (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt() == BG_YPbPr) { + index = ui->backgroundMode->findData(QVariant(BG_YUV)); + if (index >= 0) { + ui->backgroundMode->setCurrentIndex(index); + } + } + } + forceUpdate(); +} diff --git a/src/vectorscope.h b/src/vectorscope.h index 94995ad3..ed0a35d2 100644 --- a/src/vectorscope.h +++ b/src/vectorscope.h @@ -22,7 +22,7 @@ class Monitor; class Vectorscope_UI; class VectorscopeGenerator; -enum BACKGROUND_MODE { BG_NONE = 0, BG_YUV = 1, BG_CHROMA = 2 }; +enum BACKGROUND_MODE { BG_NONE = 0, BG_YUV = 1, BG_CHROMA = 2, BG_YPbPr = 3 }; class Vectorscope : public AbstractScopeWidget { Q_OBJECT @@ -53,6 +53,9 @@ private: ColorTools *m_colorTools; ColorPlaneExport *m_colorPlaneExport; + QActionGroup *m_agColorSpace; + QAction *m_aColorSpace_YUV; + QAction *m_aColorSpace_YPbPr; QAction *m_aExportBackground; QAction *m_aAxisEnabled; QAction *m_a75PBox; @@ -88,6 +91,7 @@ private slots: void slotGainChanged(int); void slotBackgroundChanged(); void slotExportBackground(); + void slotColorSpaceChanged(); }; #endif // VECTORSCOPE_H -- 2.39.2