File indexing completed on 2024-04-28 04:52:23
0001 /* 0002 SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com> 0003 This file is part of kdenlive. See www.kdenlive.org. 0004 0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 /** 0009 0010 Vectorscope. 0011 0012 The basis matrix for converting RGB to YUV is 0013 (in this example for calculating the YUV value of red): 0014 0015 mRgb2Yuv = r = 0016 0017 0.29900 0.58700 0.11400 1.00000 0018 -0.14741 -0.28939 0.43680 x 0.00000 0019 0.61478 -0.51480 -0.09998 0.00000 0020 0021 The resulting YUV value is then drawn on the circle 0022 using U and V as coordinate values. 0023 0024 The maximum length of such an UV vector is reached 0025 for the colors Red and Cyan: 0.632. 0026 To make optimal use of space in the circle, this value 0027 can be used for scaling. 0028 0029 As we are dealing with RGB values in a range of {0,...,255} 0030 and the conversion values are made for [0,1], we already 0031 divide the conversion values by 255 previously, e.g. in 0032 GNU octave. 0033 0034 The basis matrix for converting RGB to YPbPr is: 0035 0036 mRgb2YPbPr = r = 0037 0038 0.299000 0.587000 0.114000 1.0000 0039 -0.168736 -0.331264 0.500000 x 0.0000 0040 0.500000 -0.418688 -0.081312 0.0000 0041 0042 Note that YUV and YPbPr are not the same. 0043 * YUV is used for analog transfer of PAL/NTSC 0044 * YPbPr is used for analog transfer of YCbCr sources 0045 like DVD/DVB. 0046 It does _not_ matter for the Vectorscope what color space 0047 the input video is; The used color space is just a tool 0048 to visualize the hue. The difference is merely that the 0049 scope looks slightly different when choosing the respectively 0050 other color space; The way the Vectorscope works stays the same. 0051 0052 YCbCr is basically YPbPr for digital use (it is defined 0053 on a range of {16,219} for Y and {16,240} for Cb/Cr 0054 components). 0055 0056 See also: 0057 http://www.poynton.com/ColorFAQ.html 0058 https://de.wikipedia.org/wiki/Vektorskop 0059 https://www.elektroniktutor.de/geraetetechnik/vektskop.html 0060 0061 */ 0062 0063 #include "vectorscopegenerator.h" 0064 #include <cmath> 0065 0066 // The maximum distance from the center for any RGB color is 0.63, so 0067 // no need to make the circle bigger than required. 0068 const double SCALING = 1 / .7; 0069 0070 const double VectorscopeGenerator::scaling = 1 / .7; 0071 0072 /** 0073 Input point is on [-1,1]², 0 being at the center, 0074 and positive directions are →top/→right. 0075 0076 Maps to the coordinates used in QImages with the 0 point 0077 at the top left corner. 0078 0079 -1 +1 0080 +1+-----------+ 0081 | + | 0082 | --0++ | 0083 | - | 0084 -1+-----------+ 0085 vvv 0086 mapped to 0087 v 0088 0 x 0089 0+------+ 0090 |0++ | 0091 |- | 0092 |- | 0093 y+------+ 0094 0095 With y: 0096 1. Scale from [-1,1] to [0,1] with y01 := (y+1)/2 0097 2. Invert (Orientation of the y axis changes) with y10 := 1-y01 0098 3. Scale from [1,0] to [height-1,0] with yy := (height-1) * y10 0099 x does not need to be inverted. 0100 0101 */ 0102 QPoint VectorscopeGenerator::mapToCircle(const QSize &targetSize, const QPointF &point) const 0103 { 0104 return {int((targetSize.width() - 1) * (point.x() + 1) / 2), int((targetSize.height() - 1) * (1 - (point.y() + 1) / 2))}; 0105 } 0106 0107 QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain, 0108 const VectorscopeGenerator::PaintMode &paintMode, const VectorscopeGenerator::ColorSpace &colorSpace, bool, 0109 uint accelFactor) const 0110 { 0111 if (vectorscopeSize.width() <= 0 || vectorscopeSize.height() <= 0 || image.width() <= 0 || image.height() <= 0) { 0112 // Invalid size 0113 return QImage(); 0114 } 0115 if (accelFactor < 1) { accelFactor = 1; } 0116 0117 // Prepare the vectorscope data 0118 const int cw = (vectorscopeSize.width() < vectorscopeSize.height()) ? vectorscopeSize.width() : vectorscopeSize.height(); 0119 QImage scope = QImage(cw, cw, QImage::Format_ARGB32); 0120 scope.fill(qRgba(0, 0, 0, 0)); 0121 0122 double dy, dr, dg, db, dmax; 0123 double /*y,*/ u, v; 0124 QPoint pt; 0125 QRgb px; 0126 0127 // Just an average for the number of image pixels per scope pixel. 0128 // NOTE: byteCount() has to be replaced by (img.bytesPerLine()*img.height()) for Qt 4.5 to compile, see: 0129 // https://doc.qt.io/qt-5/qimage.html#bytesPerLine 0130 double avgPxPerPx = double(image.depth()) / 8 * (image.bytesPerLine() * image.height()) / scope.size().width() / scope.size().height() / accelFactor; 0131 0132 // benchmarking code 0133 // const auto start = std::chrono::high_resolution_clock::now(); 0134 0135 const auto totalPixels = image.width() * image.height(); 0136 for (int i = 0; i < totalPixels; i += accelFactor) { 0137 const QRgb pixel = image.pixel(i % image.width(), i / image.width()); 0138 const int r = qRed(pixel); 0139 const int g = qGreen(pixel); 0140 const int b = qBlue(pixel); 0141 0142 switch (colorSpace) { 0143 case VectorscopeGenerator::ColorSpace_YUV: 0144 // y = (double) 0.001173 * r +0.002302 * g +0.0004471* b; 0145 u = -0.0005781 * r - 0.001135 * g + 0.001713 * b; 0146 v = 0.002411 * r - 0.002019 * g - 0.0003921 * b; 0147 break; 0148 case VectorscopeGenerator::ColorSpace_YPbPr: 0149 default: 0150 // y = (double) 0.001173 * r +0.002302 * g +0.0004471* b; 0151 u = -0.0006671 * r - 0.001299 * g + 0.0019608 * b; 0152 v = 0.001961 * r - 0.001642 * g - 0.0003189 * b; 0153 break; 0154 } 0155 0156 pt = mapToCircle(vectorscopeSize, QPointF(SCALING * double(gain) * u, SCALING * double(gain) * v)); 0157 0158 if (pt.x() >= scope.width() || pt.x() < 0 || pt.y() >= scope.height() || pt.y() < 0) { 0159 // Point lies outside (because of scaling), don't plot it 0160 0161 } else { 0162 0163 // Draw the pixel using the chosen draw mode. 0164 switch (paintMode) { 0165 case PaintMode_YUV: 0166 // see yuvColorWheel 0167 dy = 128; // Default Y value. Lower = darker. 0168 0169 // Calculate the RGB values from YUV/YPbPr 0170 switch (colorSpace) { 0171 case VectorscopeGenerator::ColorSpace_YUV: 0172 dr = dy + 290.8 * v; 0173 dg = dy - 100.6 * u - 148 * v; 0174 db = dy + 517.2 * u; 0175 break; 0176 case VectorscopeGenerator::ColorSpace_YPbPr: 0177 default: 0178 dr = dy + 357.5 * v; 0179 dg = dy - 87.75 * u - 182 * v; 0180 db = dy + 451.9 * u; 0181 break; 0182 } 0183 0184 if (dr < 0) { 0185 dr = 0; 0186 } 0187 if (dg < 0) { 0188 dg = 0; 0189 } 0190 if (db < 0) { 0191 db = 0; 0192 } 0193 if (dr > 255) { 0194 dr = 255; 0195 } 0196 if (dg > 255) { 0197 dg = 255; 0198 } 0199 if (db > 255) { 0200 db = 255; 0201 } 0202 0203 scope.setPixel(pt, qRgba(int(dr), int(dg), int(db), 255)); 0204 break; 0205 0206 case PaintMode_Chroma: 0207 dy = 200; // Default Y value. Lower = darker. 0208 0209 // Calculate the RGB values from YUV/YPbPr 0210 switch (colorSpace) { 0211 case VectorscopeGenerator::ColorSpace_YUV: 0212 dr = dy + 290.8 * v; 0213 dg = dy - 100.6 * u - 148 * v; 0214 db = dy + 517.2 * u; 0215 break; 0216 case VectorscopeGenerator::ColorSpace_YPbPr: 0217 default: 0218 dr = dy + 357.5 * v; 0219 dg = dy - 87.75 * u - 182 * v; 0220 db = dy + 451.9 * u; 0221 break; 0222 } 0223 0224 // Scale the RGB values back to max 255 0225 dmax = dr; 0226 if (dg > dmax) { 0227 dmax = dg; 0228 } 0229 if (db > dmax) { 0230 dmax = db; 0231 } 0232 dmax = 255 / dmax; 0233 0234 dr *= dmax; 0235 dg *= dmax; 0236 db *= dmax; 0237 0238 scope.setPixel(pt, qRgba(int(dr), int(dg), int(db), 255)); 0239 break; 0240 case PaintMode_Original: 0241 scope.setPixel(pt, pixel); 0242 break; 0243 case PaintMode_Green: 0244 px = scope.pixel(pt); 0245 scope.setPixel(pt, qRgba(qRed(px) + int((255 - qRed(px)) / (3 * avgPxPerPx)), qGreen(px) + int(20 * (255 - qGreen(px)) / (avgPxPerPx)), 0246 qBlue(px) + int((255 - qBlue(px)) / (avgPxPerPx)), qAlpha(px) + int((255 - qAlpha(px)) / (avgPxPerPx)))); 0247 break; 0248 case PaintMode_Green2: 0249 px = scope.pixel(pt); 0250 scope.setPixel(pt, qRgba(qRed(px) + int(ceil((255 - qRed(px)) / (4 * avgPxPerPx))), 255, 0251 qBlue(px) + int(ceil((255 - qBlue(px)) / (avgPxPerPx))), qAlpha(px) + int(ceil((255 - qAlpha(px)) / (avgPxPerPx))))); 0252 break; 0253 case PaintMode_Black: 0254 default: 0255 px = scope.pixel(pt); 0256 scope.setPixel(pt, qRgba(0, 0, 0, qAlpha(px) + (255 - qAlpha(px)) / 20)); 0257 break; 0258 } 0259 } 0260 } 0261 // const auto elapsed = std::chrono::high_resolution_clock::now() - start; 0262 // uint64_t us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count(); 0263 // qDebug() << "Vectorscope calculated in" << us << " microseconds"; 0264 return scope; 0265 }