/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2016 F4EXB // // written by Edouard Griffiths // // // // 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 as 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 V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #if 0 //def USE_SSE2 #include #endif #include #include #include #include #include #include "gui/glspectrum.h" #include "util/messagequeue.h" #include MESSAGE_CLASS_DEFINITION(GLSpectrum::MsgReportSampleRate, Message) GLSpectrum::GLSpectrum(QWidget* parent) : QGLWidget(parent), m_cursorState(CSNormal), m_cursorChannel(0), m_mouseInside(false), m_changesPending(true), m_centerFrequency(100000000), m_referenceLevel(0), m_powerRange(100), m_linear(false), m_decay(1), m_sampleRate(500000), m_timingRate(1), m_fftSize(512), m_displayGrid(true), m_displayGridIntensity(5), m_displayTraceIntensity(50), m_invertedWaterfall(false), m_displayMaxHold(false), m_currentSpectrum(0), m_displayCurrent(false), m_leftMargin(0), m_rightMargin(0), m_topMargin(0), m_histogramHeight(20), m_waterfallHeight(0), m_frequencyScaleHeight(0), m_bottomMargin(0), m_waterfallBuffer(0), m_waterfallBufferPos(0), m_waterfallTextureHeight(-1), m_waterfallTexturePos(0), m_displayWaterfall(true), m_ssbSpectrum(false), m_lsbDisplay(false), m_histogramBuffer(0), m_histogram(0), m_displayHistogram(true), m_displayChanged(false), m_displaySourceOrSink(true), m_displayStreamIndex(0), m_matrixLoc(0), m_colorLoc(0), m_messageQueueToGUI(0) { setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent, true); setAttribute(Qt::WA_NoSystemBackground, true); setMouseTracking(true); setMinimumSize(200, 200); m_waterfallShare = 0.66; for (int i = 0; i <= 239; i++) { QColor c; c.setHsv(239 - i, 255, 15 + i); ((quint8*)&m_waterfallPalette[i])[0] = c.red(); ((quint8*)&m_waterfallPalette[i])[1] = c.green(); ((quint8*)&m_waterfallPalette[i])[2] = c.blue(); ((quint8*)&m_waterfallPalette[i])[3] = c.alpha(); } m_waterfallPalette[239] = 0xffffffff; m_histogramPalette[0] = 0; for (int i = 1; i < 240; i++) { QColor c; int light = i < 60 ? 128 + (60-i) : 128; int sat = i < 60 ? 140 + i : i < 180 ? 200 : 200 - (i-180); c.setHsl(239 - i, sat, light); ((quint8*)&m_histogramPalette[i])[0] = c.red(); ((quint8*)&m_histogramPalette[i])[1] = c.green(); ((quint8*)&m_histogramPalette[i])[2] = c.blue(); ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); } // 4.2.3 palette // for (int i = 1; i < 240; i++) // { // QColor c; // int val = i < 60 ? 255 : 200; // int sat = i < 60 ? 128 : i < 180 ? 255 : 180; // c.setHsv(239 - i, sat, val); // ((quint8*)&m_histogramPalette[i])[0] = c.red(); // ((quint8*)&m_histogramPalette[i])[1] = c.green(); // ((quint8*)&m_histogramPalette[i])[2] = c.blue(); // ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); // } // Original palette: // for(int i = 16; i < 240; i++) { // QColor c; // c.setHsv(239 - i, 255 - ((i < 200) ? 0 : (i - 200) * 3), 150 + ((i < 100) ? i : 100)); // ((quint8*)&m_histogramPalette[i])[0] = c.red(); // ((quint8*)&m_histogramPalette[i])[1] = c.green(); // ((quint8*)&m_histogramPalette[i])[2] = c.blue(); // ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); // } // for(int i = 1; i < 16; i++) { // QColor c; // c.setHsv(255, 128, 48 + i * 4); // ((quint8*)&m_histogramPalette[i])[0] = c.red(); // ((quint8*)&m_histogramPalette[i])[1] = c.green(); // ((quint8*)&m_histogramPalette[i])[2] = c.blue(); // ((quint8*)&m_histogramPalette[i])[3] = c.alpha(); // } m_decayDivisor = 1; m_decayDivisorCount = m_decayDivisor; m_histogramStroke = 30; m_timeScale.setFont(font()); m_timeScale.setOrientation(Qt::Vertical); m_timeScale.setRange(Unit::Time, 0, 1); m_powerScale.setFont(font()); m_powerScale.setOrientation(Qt::Vertical); m_frequencyScale.setFont(font()); m_frequencyScale.setOrientation(Qt::Horizontal); m_textOverlayFont = font(); // QFontDatabase::systemFont(QFontDatabase::FixedFont); m_textOverlayFont.setBold(true); // m_textOverlayFont.setPointSize(font().pointSize() - 1); connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); m_timer.start(50); } GLSpectrum::~GLSpectrum() { QMutexLocker mutexLocker(&m_mutex); if (m_waterfallBuffer) { delete m_waterfallBuffer; m_waterfallBuffer = nullptr; } if (m_histogramBuffer) { delete m_histogramBuffer; m_histogramBuffer = nullptr; } if (m_histogram) { delete[] m_histogram; m_histogram = nullptr; } } void GLSpectrum::setCenterFrequency(qint64 frequency) { m_mutex.lock(); m_centerFrequency = frequency; m_changesPending = true; m_mutex.unlock(); update(); } void GLSpectrum::setReferenceLevel(Real referenceLevel) { m_mutex.lock(); m_referenceLevel = referenceLevel; m_changesPending = true; m_mutex.unlock(); update(); } void GLSpectrum::setPowerRange(Real powerRange) { m_mutex.lock(); m_powerRange = powerRange; m_changesPending = true; m_mutex.unlock(); update(); } void GLSpectrum::setDecay(int decay) { m_decay = decay < 0 ? 0 : decay > 20 ? 20 : decay; } void GLSpectrum::setDecayDivisor(int decayDivisor) { m_decayDivisor = decayDivisor < 1 ? 1 : decayDivisor > 20 ? 20 : decayDivisor; } void GLSpectrum::setHistoStroke(int stroke) { m_histogramStroke = stroke < 1 ? 1 : stroke > 60 ? 60 : stroke; } void GLSpectrum::setSampleRate(qint32 sampleRate) { m_mutex.lock(); m_sampleRate = sampleRate; if (m_messageQueueToGUI) { m_messageQueueToGUI->push(new MsgReportSampleRate(m_sampleRate)); } m_changesPending = true; m_mutex.unlock(); update(); } void GLSpectrum::setTimingRate(qint32 timingRate) { m_mutex.lock(); m_timingRate = timingRate; m_changesPending = true; m_mutex.unlock(); update(); } void GLSpectrum::setDisplayWaterfall(bool display) { m_mutex.lock(); m_displayWaterfall = display; if (!display) { m_waterfallMarkers.clear(); } m_changesPending = true; stopDrag(); m_mutex.unlock(); update(); } void GLSpectrum::setSsbSpectrum(bool ssbSpectrum) { m_ssbSpectrum = ssbSpectrum; update(); } void GLSpectrum::setLsbDisplay(bool lsbDisplay) { m_lsbDisplay = lsbDisplay; update(); } void GLSpectrum::setInvertedWaterfall(bool inv) { m_mutex.lock(); m_invertedWaterfall = inv; m_changesPending = true; stopDrag(); m_mutex.unlock(); update(); } void GLSpectrum::setDisplayMaxHold(bool display) { m_mutex.lock(); m_displayMaxHold = display; if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram) { m_histogramMarkers.clear(); } m_changesPending = true; stopDrag(); m_mutex.unlock(); update(); } void GLSpectrum::setDisplayCurrent(bool display) { m_mutex.lock(); m_displayCurrent = display; if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram) { m_histogramMarkers.clear(); } m_changesPending = true; stopDrag(); m_mutex.unlock(); update(); } void GLSpectrum::setDisplayHistogram(bool display) { m_mutex.lock(); m_displayHistogram = display; if (!m_displayMaxHold && !m_displayCurrent && !m_displayHistogram) { m_histogramMarkers.clear(); } m_changesPending = true; stopDrag(); m_mutex.unlock(); update(); } void GLSpectrum::setDisplayGrid(bool display) { m_displayGrid = display; update(); } void GLSpectrum::setDisplayGridIntensity(int intensity) { m_displayGridIntensity = intensity; if (m_displayGridIntensity > 100) { m_displayGridIntensity = 100; } else if (m_displayGridIntensity < 0) { m_displayGridIntensity = 0; } update(); } void GLSpectrum::setDisplayTraceIntensity(int intensity) { m_displayTraceIntensity = intensity; if (m_displayTraceIntensity > 100) { m_displayTraceIntensity = 100; } else if (m_displayTraceIntensity < 0) { m_displayTraceIntensity = 0; } update(); } void GLSpectrum::setLinear(bool linear) { m_mutex.lock(); m_linear = linear; m_changesPending = true; m_mutex.unlock(); update(); } void GLSpectrum::addChannelMarker(ChannelMarker* channelMarker) { m_mutex.lock(); connect(channelMarker, SIGNAL(changedByAPI()), this, SLOT(channelMarkerChanged())); connect(channelMarker, SIGNAL(destroyed(QObject*)), this, SLOT(channelMarkerDestroyed(QObject*))); m_channelMarkerStates.append(new ChannelMarkerState(channelMarker)); m_changesPending = true; stopDrag(); m_mutex.unlock(); update(); } void GLSpectrum::removeChannelMarker(ChannelMarker* channelMarker) { m_mutex.lock(); for (int i = 0; i < m_channelMarkerStates.size(); ++i) { if (m_channelMarkerStates[i]->m_channelMarker == channelMarker) { channelMarker->disconnect(this); delete m_channelMarkerStates.takeAt(i); m_changesPending = true; stopDrag(); m_mutex.unlock(); update(); return; } } m_mutex.unlock(); } void GLSpectrum::newSpectrum(const std::vector& spectrum, int fftSize) { QMutexLocker mutexLocker(&m_mutex); m_displayChanged = true; if (m_changesPending) { m_fftSize = fftSize; return; } if (fftSize != m_fftSize) { m_fftSize = fftSize; m_changesPending = true; return; } updateWaterfall(spectrum); updateHistogram(spectrum); } void GLSpectrum::updateWaterfall(const std::vector& spectrum) { if (m_waterfallBufferPos < m_waterfallBuffer->height()) { quint32* pix = (quint32*)m_waterfallBuffer->scanLine(m_waterfallBufferPos); for (int i = 0; i < m_fftSize; i++) { int v = (int)((spectrum[i] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0); if (v > 239) { v = 239; } else if (v < 0) { v = 0; } *pix++ = m_waterfallPalette[(int)v]; } m_waterfallBufferPos++; } } void GLSpectrum::updateHistogram(const std::vector& spectrum) { quint8* b = m_histogram; int fftMulSize = 100 * m_fftSize; if ((m_displayHistogram || m_displayMaxHold) && (m_decay != 0)) { m_decayDivisorCount--; if ((m_decay > 1) || (m_decayDivisorCount <= 0)) { for (int i = 0; i < fftMulSize; i++) { if (*b > m_decay) { *b = *b - m_decay; } else { *b = 0; } b++; } m_decayDivisorCount = m_decayDivisor; } } m_currentSpectrum = &spectrum; // Store spectrum for current spectrum line display #if 0 //def USE_SSE2 if(m_decay >= 0) { // normal const __m128 refl = {m_referenceLevel, m_referenceLevel, m_referenceLevel, m_referenceLevel}; const __m128 power = {m_powerRange, m_powerRange, m_powerRange, m_powerRange}; const __m128 mul = {100.0f, 100.0f, 100.0f, 100.0f}; for(int i = 0; i < m_fftSize; i += 4) { __m128 abc = _mm_loadu_ps (&spectrum[i]); abc = _mm_sub_ps(abc, refl); abc = _mm_mul_ps(abc, mul); abc = _mm_div_ps(abc, power); abc = _mm_add_ps(abc, mul); __m128i result = _mm_cvtps_epi32(abc); for(int j = 0; j < 4; j++) { int v = ((int*)&result)[j]; if((v >= 0) && (v <= 99)) { b = m_histogram + (i + j) * 100 + v; if(*b < 220) *b += m_histogramStroke; // was 4 else if(*b < 239) *b += 1; } } } } else { // draw double pixels int add = -m_decay * 4; const __m128 refl = {m_referenceLevel, m_referenceLevel, m_referenceLevel, m_referenceLevel}; const __m128 power = {m_powerRange, m_powerRange, m_powerRange, m_powerRange}; const __m128 mul = {100.0f, 100.0f, 100.0f, 100.0f}; for(int i = 0; i < m_fftSize; i += 4) { __m128 abc = _mm_loadu_ps (&spectrum[i]); abc = _mm_sub_ps(abc, refl); abc = _mm_mul_ps(abc, mul); abc = _mm_div_ps(abc, power); abc = _mm_add_ps(abc, mul); __m128i result = _mm_cvtps_epi32(abc); for(int j = 0; j < 4; j++) { int v = ((int*)&result)[j]; if((v >= 1) && (v <= 98)) { b = m_histogram + (i + j) * 100 + v; if(b[-1] < 220) b[-1] += add; else if(b[-1] < 239) b[-1] += 1; if(b[0] < 220) b[0] += add; else if(b[0] < 239) b[0] += 1; if(b[1] < 220) b[1] += add; else if(b[1] < 239) b[1] += 1; } else if((v >= 0) && (v <= 99)) { b = m_histogram + (i + j) * 100 + v; if(*b < 220) *b += add; else if(*b < 239) *b += 1; } } } } #else for (int i = 0; i < m_fftSize; i++) { int v = (int)((spectrum[i] - m_referenceLevel) * 100.0 / m_powerRange + 100.0); if ((v >= 0) && (v <= 99)) { b = m_histogram + i * 100 + v; // capping to 239 as palette values are [0..239] if (*b + m_histogramStroke <= 239) { *b += m_histogramStroke; // was 4 } else { *b = 239; } } } #endif } void GLSpectrum::initializeGL() { QOpenGLContext *glCurrentContext = QOpenGLContext::currentContext(); if (glCurrentContext) { if (QOpenGLContext::currentContext()->isValid()) { qDebug() << "GLSpectrum::initializeGL: context:" << " major: " << (QOpenGLContext::currentContext()->format()).majorVersion() << " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion() << " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no"); } else { qDebug() << "GLSpectrum::initializeGL: current context is invalid"; } } else { qCritical() << "GLSpectrum::initializeGL: no current context"; return; } connect(glCurrentContext, &QOpenGLContext::aboutToBeDestroyed, this, &GLSpectrum::cleanup); // TODO: when migrating to QOpenGLWidget QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); glFunctions->initializeOpenGLFunctions(); //glDisable(GL_DEPTH_TEST); m_glShaderSimple.initializeGL(); m_glShaderLeftScale.initializeGL(); m_glShaderFrequencyScale.initializeGL(); m_glShaderWaterfall.initializeGL(); m_glShaderHistogram.initializeGL(); m_glShaderTextOverlay.initializeGL(); } void GLSpectrum::resizeGL(int width, int height) { QMutexLocker mutexLocker(&m_mutex); QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); glFunctions->glViewport(0, 0, width, height); m_changesPending = true; } void GLSpectrum::clearSpectrumHistogram() { if (!m_mutex.tryLock(2)) { return; } memset(m_histogram, 0x00, 100 * m_fftSize); m_mutex.unlock(); update(); } void GLSpectrum::paintGL() { if (!m_mutex.tryLock(2)) { return; } if (m_changesPending) { applyChanges(); m_changesPending = false; } if (m_fftSize <= 0) { m_mutex.unlock(); return; } QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions(); glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glFunctions->glClear(GL_COLOR_BUFFER_BIT); // paint waterfall if (m_displayWaterfall) { { GLfloat vtx1[] = { 0, m_invertedWaterfall ? 0.0f : 1.0f, 1, m_invertedWaterfall ? 0.0f : 1.0f, 1, m_invertedWaterfall ? 1.0f : 0.0f, 0, m_invertedWaterfall ? 1.0f : 0.0f }; if (m_waterfallTexturePos + m_waterfallBufferPos < m_waterfallTextureHeight) { m_glShaderWaterfall.subTexture(0, m_waterfallTexturePos, m_fftSize, m_waterfallBufferPos, m_waterfallBuffer->scanLine(0)); m_waterfallTexturePos += m_waterfallBufferPos; } else { int breakLine = m_waterfallTextureHeight - m_waterfallTexturePos; int linesLeft = m_waterfallTexturePos + m_waterfallBufferPos - m_waterfallTextureHeight; m_glShaderWaterfall.subTexture(0, m_waterfallTexturePos, m_fftSize, breakLine, m_waterfallBuffer->scanLine(0)); m_glShaderWaterfall.subTexture(0, 0, m_fftSize, linesLeft, m_waterfallBuffer->scanLine(breakLine)); m_waterfallTexturePos = linesLeft; } m_waterfallBufferPos = 0; float prop_y = m_waterfallTexturePos / (m_waterfallTextureHeight - 1.0); float off = 1.0 / (m_waterfallTextureHeight - 1.0); GLfloat tex1[] = { 0, prop_y + 1 - off, 1, prop_y + 1 - off, 1, prop_y, 0, prop_y }; m_glShaderWaterfall.drawSurface(m_glWaterfallBoxMatrix, tex1, vtx1, 4); } // paint channels if (m_mouseInside) { for (int i = 0; i < m_channelMarkerStates.size(); ++i) { ChannelMarkerState* dv = m_channelMarkerStates[i]; if (dv->m_channelMarker->getVisible() && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink) && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { { GLfloat q3[] { 0, 0, 1, 0, 1, 1, 0, 1, 0.5, 0, 0.5, 1, }; QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.3f); m_glShaderSimple.drawSurface(dv->m_glMatrixWaterfall, color, q3, 4); QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f); m_glShaderSimple.drawSegments(dv->m_glMatrixDsbWaterfall, colorLine, &q3[8], 2); } } } } // draw rect around { GLfloat q3[] { 1, 1, 0, 1, 0, 0, 1, 0 }; QVector4D color(1.0f, 1.0f, 1.0f, 0.5f); m_glShaderSimple.drawContour(m_glWaterfallBoxMatrix, color, q3, 4); } } // paint histogram if (m_displayHistogram || m_displayMaxHold || m_displayCurrent) { if (m_displayHistogram) { { // import new lines into the texture quint32* pix; quint8* bs = m_histogram; for (int y = 0; y < 100; y++) { quint8* b = bs; pix = (quint32*)m_histogramBuffer->scanLine(99 - y); for (int x = 0; x < m_fftSize; x++) { *pix = m_histogramPalette[*b]; pix++; b += 100; } bs++; } GLfloat vtx1[] = { 0, 0, 1, 0, 1, 1, 0, 1 }; GLfloat tex1[] = { 0, 0, 1, 0, 1, 1, 0, 1 }; m_glShaderHistogram.subTexture(0, 0, m_fftSize, 100, m_histogramBuffer->scanLine(0)); m_glShaderHistogram.drawSurface(m_glHistogramBoxMatrix, tex1, vtx1, 4); } } // paint channels if (m_mouseInside) { // Effective BW overlays for (int i = 0; i < m_channelMarkerStates.size(); ++i) { ChannelMarkerState* dv = m_channelMarkerStates[i]; if (dv->m_channelMarker->getVisible() && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink) && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { { GLfloat q3[] { 0, 0, 1, 0, 1, 1, 0, 1, 0.5, 0, 0.5, 1 }; QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.3f); m_glShaderSimple.drawSurface(dv->m_glMatrixHistogram, color, q3, 4); QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f); if (dv->m_channelMarker->getSidebands() != ChannelMarker::dsb) { q3[6] = 0.5; } m_glShaderSimple.drawSegments(dv->m_glMatrixDsbHistogram, colorLine, &q3[8], 2); m_glShaderSimple.drawSegments(dv->m_glMatrixFreqScale, colorLine, q3, 2); } } } } // draw rect around { GLfloat q3[] { 1, 1, 0, 1, 0, 0, 1, 0 }; QVector4D color(1.0f, 1.0f, 1.0f, 0.5f); m_glShaderSimple.drawContour(m_glHistogramBoxMatrix, color, q3, 4); } } // paint left scales (time and power) if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram ) { { GLfloat vtx1[] = { 0, 1, 1, 1, 1, 0, 0, 0 }; GLfloat tex1[] = { 0, 1, 1, 1, 1, 0, 0, 0 }; m_glShaderLeftScale.drawSurface(m_glLeftScaleBoxMatrix, tex1, vtx1, 4); } } // paint frequency scale if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram ) { { GLfloat vtx1[] = { 0, 1, 1, 1, 1, 0, 0, 0 }; GLfloat tex1[] = { 0, 1, 1, 1, 1, 0, 0, 0 }; m_glShaderFrequencyScale.drawSurface(m_glFrequencyScaleBoxMatrix, tex1, vtx1, 4); } // paint channels // Effective bandwidth overlays for (int i = 0; i < m_channelMarkerStates.size(); ++i) { ChannelMarkerState* dv = m_channelMarkerStates[i]; // frequency scale channel overlay if (dv->m_channelMarker->getVisible() && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink) && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { { GLfloat q3[] { 1, 0.2, 0, 0.2, 0, 0, 1, 0, 0.5, 0, 0.5, 1 }; QVector4D color(dv->m_channelMarker->getColor().redF(), dv->m_channelMarker->getColor().greenF(), dv->m_channelMarker->getColor().blueF(), 0.5f); m_glShaderSimple.drawSurface(dv->m_glMatrixFreqScale, color, q3, 4); if (dv->m_channelMarker->getHighlighted()) { QVector4D colorLine(0.8f, 0.8f, 0.6f, 1.0f); m_glShaderSimple.drawSegments(dv->m_glMatrixDsbFreqScale, colorLine, &q3[8], 2); m_glShaderSimple.drawSegments(dv->m_glMatrixFreqScale, colorLine, &q3[4], 2); } } } } } // paint max hold lines on top of histogram if (m_displayMaxHold) { if (m_maxHold.size() < (uint) m_fftSize) { m_maxHold.resize(m_fftSize); } for (int i = 0; i < m_fftSize; i++) { int j; quint8* bs = m_histogram + i * 100; for (j = 99; j >= 0; j--) { if (bs[j] > 0) { break; } } // m_referenceLevel : top // m_referenceLevel - m_powerRange : bottom m_maxHold[i] = ((j - 99) * m_powerRange) / 99.0 + m_referenceLevel; } { GLfloat *q3 = m_q3FFT.m_array; for (int i = 0; i < m_fftSize; i++) { Real v = m_maxHold[i] - m_referenceLevel; if (v >= 0) { v = 0; } else if (v < -m_powerRange) { v = -m_powerRange; } q3[2*i] = (Real) i; q3[2*i+1] = v; } QVector4D color(1.0f, 0.0f, 0.0f, (float) m_displayTraceIntensity / 100.0f); m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_fftSize); } } // paint current spectrum line on top of histogram if ((m_displayCurrent) && m_currentSpectrum) { { Real bottom = -m_powerRange; GLfloat *q3 = m_q3FFT.m_array; for (int i = 0; i < m_fftSize; i++) { Real v = (*m_currentSpectrum)[i] - m_referenceLevel; if (v > 0) { v = 0; } else if (v < bottom) { v = bottom; } q3[2*i] = (Real) i; q3[2*i+1] = v; } QVector4D color(1.0f, 1.0f, 0.25f, (float) m_displayTraceIntensity / 100.0f); m_glShaderSimple.drawPolyline(m_glHistogramSpectrumMatrix, color, q3, m_fftSize); } } drawMarkers(); // paint waterfall grid if (m_displayWaterfall && m_displayGrid) { const ScaleEngine::TickList* tickList; const ScaleEngine::Tick* tick; tickList = &m_timeScale.getTickList(); { GLfloat *q3 = m_q3TickTime.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; if (tick->major) { if (tick->textSize > 0) { float y = tick->pos / m_timeScale.getSize(); q3[4*effectiveTicks] = 0; q3[4*effectiveTicks+1] = y; q3[4*effectiveTicks+2] = 1; q3[4*effectiveTicks+3] = y; effectiveTicks++; } } } QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, color, q3, 2*effectiveTicks); } tickList = &m_frequencyScale.getTickList(); { GLfloat *q3 = m_q3TickFrequency.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; if (tick->major) { if (tick->textSize > 0) { float x = tick->pos / m_frequencyScale.getSize(); q3[4*effectiveTicks] = x; q3[4*effectiveTicks+1] = 0; q3[4*effectiveTicks+2] = x; q3[4*effectiveTicks+3] = 1; effectiveTicks++; } } } QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, color, q3, 2*effectiveTicks); } } // paint histogram grid if ((m_displayHistogram || m_displayMaxHold || m_displayCurrent) && (m_displayGrid)) { const ScaleEngine::TickList* tickList; const ScaleEngine::Tick* tick; tickList = &m_powerScale.getTickList(); { GLfloat *q3 = m_q3TickPower.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; if (tick->major) { if (tick->textSize > 0) { float y = tick->pos / m_powerScale.getSize(); q3[4*effectiveTicks] = 0; q3[4*effectiveTicks+1] = 1-y; q3[4*effectiveTicks+2] = 1; q3[4*effectiveTicks+3] = 1-y; effectiveTicks++; } } } QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, q3, 2*effectiveTicks); } tickList = &m_frequencyScale.getTickList(); { GLfloat *q3 = m_q3TickFrequency.m_array; int effectiveTicks = 0; for (int i= 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; if (tick->major) { if (tick->textSize > 0) { float x = tick->pos / m_frequencyScale.getSize(); q3[4*effectiveTicks] = x; q3[4*effectiveTicks+1] = 0; q3[4*effectiveTicks+2] = x; q3[4*effectiveTicks+3] = 1; effectiveTicks++; } } } QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f); m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, color, q3, 2*effectiveTicks); } } m_mutex.unlock(); } void GLSpectrum::drawMarkers() { QVector4D markerColor(1.0f, 1.0f, 1.0f, 0.3f); QVector4D markerTextColor(1.0f, 1.0f, 1.0f, 0.8f); // paint histogram markers if (m_histogramMarkers.size() > 0) { // crosshairs for (int i = 0; i < m_histogramMarkers.size(); i++) { GLfloat h[] { (float) m_histogramMarkers.at(i).m_point.x(), 0, (float) m_histogramMarkers.at(i).m_point.x(), 1 }; m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, markerColor, h, 2); GLfloat v[] { 0, (float) m_histogramMarkers.at(i).m_point.y(), 1, (float) m_histogramMarkers.at(i).m_point.y() }; m_glShaderSimple.drawSegments(m_glHistogramBoxMatrix, markerColor, v, 2); } // text for (int i = 0; i < m_histogramMarkers.size(); i++) { if (i == 0) { drawTextOverlay( m_histogramMarkers.at(i).m_frequencyStr, QColor(255, 255, 255, 192), m_textOverlayFont, m_histogramMarkers.at(i).m_point.x() * m_histogramRect.width(), (m_invertedWaterfall || (m_waterfallHeight == 0)) ? m_histogramRect.height() : 0, m_histogramMarkers.at(i).m_point.x() < 0.5f, !m_invertedWaterfall && (m_waterfallHeight != 0), m_histogramRect); drawTextOverlay( m_histogramMarkers.at(i).m_powerStr, QColor(255, 255, 255, 192), m_textOverlayFont, 0, m_histogramMarkers.at(i).m_point.y() * m_histogramRect.height(), true, m_histogramMarkers.at(i).m_point.y() < 0.5f, m_histogramRect); } else { drawTextOverlay( m_histogramMarkers.at(i).m_deltaFrequencyStr, QColor(255, 255, 255, 192), m_textOverlayFont, m_histogramMarkers.at(i).m_point.x() * m_histogramRect.width(), (m_invertedWaterfall || (m_waterfallHeight == 0)) ? 0 : m_histogramRect.height(), m_histogramMarkers.at(i).m_point.x() < 0.5f, (m_invertedWaterfall || (m_waterfallHeight == 0)), m_histogramRect); drawTextOverlay( m_histogramMarkers.at(i).m_deltaPowerStr, QColor(255, 255, 255, 192), m_textOverlayFont, m_histogramRect.width(), m_histogramMarkers.at(i).m_point.y() * m_histogramRect.height(), false, m_histogramMarkers.at(i).m_point.y() < 0.5f, m_histogramRect); } } } // paint waterfall markers if (m_waterfallMarkers.size() > 0) { // crosshairs for (int i = 0; i < m_waterfallMarkers.size(); i++) { GLfloat h[] { (float) m_waterfallMarkers.at(i).m_point.x(), 0, (float) m_waterfallMarkers.at(i).m_point.x(), 1 }; m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, markerColor, h, 2); GLfloat v[] { 0, (float) m_waterfallMarkers.at(i).m_point.y(), 1, (float) m_waterfallMarkers.at(i).m_point.y() }; m_glShaderSimple.drawSegments(m_glWaterfallBoxMatrix, markerColor, v, 2); } // text for (int i = 0; i < m_waterfallMarkers.size(); i++) { if (i == 0) { drawTextOverlay( m_waterfallMarkers.at(i).m_frequencyStr, QColor(255, 255, 255, 192), m_textOverlayFont, m_waterfallMarkers.at(i).m_point.x() * m_waterfallRect.width(), (!m_invertedWaterfall || (m_histogramHeight == 0)) ? m_waterfallRect.height() : 0, m_waterfallMarkers.at(i).m_point.x() < 0.5f, m_invertedWaterfall && (m_histogramHeight != 0), m_waterfallRect); drawTextOverlay( m_waterfallMarkers.at(i).m_timeStr, QColor(255, 255, 255, 192), m_textOverlayFont, 0, m_waterfallMarkers.at(i).m_point.y() * m_waterfallRect.height(), true, m_waterfallMarkers.at(i).m_point.y() < 0.5f, m_waterfallRect); } else { drawTextOverlay( m_waterfallMarkers.at(i).m_deltaFrequencyStr, QColor(255, 255, 255, 192), m_textOverlayFont, m_waterfallMarkers.at(i).m_point.x() * m_waterfallRect.width(), (!m_invertedWaterfall || (m_histogramHeight == 0)) ? 0 : m_waterfallRect.height(), m_waterfallMarkers.at(i).m_point.x() < 0.5f, !m_invertedWaterfall || (m_histogramHeight == 0), m_waterfallRect); drawTextOverlay( m_waterfallMarkers.at(i).m_deltaTimeStr, QColor(255, 255, 255, 192), m_textOverlayFont, m_waterfallRect.width(), m_waterfallMarkers.at(i).m_point.y() * m_waterfallRect.height(), false, m_waterfallMarkers.at(i).m_point.y() < 0.5f, m_waterfallRect); } } } } void GLSpectrum::stopDrag() { if (m_cursorState != CSNormal) { if ((m_cursorState == CSSplitterMoving) || (m_cursorState == CSChannelMoving)) { releaseMouse(); } setCursor(Qt::ArrowCursor); m_cursorState = CSNormal; } } void GLSpectrum::applyChanges() { if (m_fftSize <= 0) { return; } QFontMetrics fm(font()); int M = fm.width("-"); m_topMargin = fm.ascent() * 1.5; m_bottomMargin = fm.ascent() * 1.5; int waterfallTop = 0; m_frequencyScaleHeight = fm.height() * 3; // +1 line for marker frequency scale int frequencyScaleTop = 0; int histogramTop = 0; //int m_leftMargin; m_rightMargin = fm.width("000"); // displays both histogram and waterfall if (m_displayWaterfall && (m_displayHistogram | m_displayMaxHold | m_displayCurrent)) { m_waterfallHeight = height() * m_waterfallShare - 1; if (m_waterfallHeight < 0) { m_waterfallHeight = 0; } if (!m_invertedWaterfall) { waterfallTop = m_topMargin; frequencyScaleTop = waterfallTop + m_waterfallHeight + 1; histogramTop = waterfallTop + m_waterfallHeight + m_frequencyScaleHeight + 1; m_histogramHeight = height() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin; } else { histogramTop = m_topMargin; m_histogramHeight = height() - m_topMargin - m_waterfallHeight - m_frequencyScaleHeight - m_bottomMargin; waterfallTop = histogramTop + m_histogramHeight + m_frequencyScaleHeight + 1; frequencyScaleTop = histogramTop + m_histogramHeight + 1; } m_timeScale.setSize(m_waterfallHeight); if (m_sampleRate > 0) { float scaleDiv = ((float)m_sampleRate / (float)m_timingRate) * (m_ssbSpectrum ? 2 : 1); if (!m_invertedWaterfall) { m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, (m_waterfallHeight * m_fftSize) / scaleDiv, 0); } else { m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, (m_waterfallHeight * m_fftSize) / scaleDiv); } } else { m_timeScale.setRange(Unit::Time, 0, 1); } m_powerScale.setSize(m_histogramHeight); if (m_linear) { m_powerScale.setRange(Unit::Scientific, m_referenceLevel - m_powerRange, m_referenceLevel); } else { m_powerScale.setRange(Unit::Decibel, m_referenceLevel - m_powerRange, m_referenceLevel); } m_leftMargin = m_timeScale.getScaleWidth(); if (m_powerScale.getScaleWidth() > m_leftMargin) { m_leftMargin = m_powerScale.getScaleWidth(); } m_leftMargin += 2 * M; m_frequencyScale.setSize(width() - m_leftMargin - m_rightMargin); m_frequencyScale.setRange(Unit::Frequency, m_centerFrequency - m_sampleRate / 2, m_centerFrequency + m_sampleRate / 2); m_frequencyScale.setMakeOpposite(m_lsbDisplay); m_glWaterfallBoxMatrix.setToIdentity(); m_glWaterfallBoxMatrix.translate( -1.0f + ((float)(2*m_leftMargin) / (float) width()), 1.0f - ((float)(2*waterfallTop) / (float) height()) ); m_glWaterfallBoxMatrix.scale( ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(), (float) (-2*m_waterfallHeight) / (float) height() ); m_glHistogramBoxMatrix.setToIdentity(); m_glHistogramBoxMatrix.translate( -1.0f + ((float)(2*m_leftMargin) / (float) width()), 1.0f - ((float)(2*histogramTop) / (float) height()) ); m_glHistogramBoxMatrix.scale( ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(), (float) (-2*m_histogramHeight) / (float) height() ); m_glHistogramSpectrumMatrix.setToIdentity(); m_glHistogramSpectrumMatrix.translate( -1.0f + ((float)(2*m_leftMargin) / (float) width()), 1.0f - ((float)(2*histogramTop) / (float) height()) ); m_glHistogramSpectrumMatrix.scale( ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / ((float) width() * (float)(m_fftSize - 1)), ((float) 2*m_histogramHeight / height()) / m_powerRange ); // m_frequencyScaleRect = QRect( // 0, // frequencyScaleTop, // width(), // m_frequencyScaleHeight // ); m_glFrequencyScaleBoxMatrix.setToIdentity(); m_glFrequencyScaleBoxMatrix.translate ( -1.0f, 1.0f - ((float) 2*frequencyScaleTop / (float) height()) ); m_glFrequencyScaleBoxMatrix.scale ( 2.0f, (float) -2*m_frequencyScaleHeight / (float) height() ); m_glLeftScaleBoxMatrix.setToIdentity(); m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f); m_glLeftScaleBoxMatrix.scale( (float)(2*(m_leftMargin - 1)) / (float) width(), -2.0f ); } // displays waterfall only else if (m_displayWaterfall) { m_histogramHeight = 0; histogramTop = 0; m_bottomMargin = m_frequencyScaleHeight; m_waterfallHeight = height() - m_topMargin - m_frequencyScaleHeight; waterfallTop = m_topMargin; frequencyScaleTop = m_topMargin + m_waterfallHeight + 1; m_timeScale.setSize(m_waterfallHeight); if (m_sampleRate > 0) { float scaleDiv = ((float)m_sampleRate / (float)m_timingRate) * (m_ssbSpectrum ? 2 : 1); if (!m_invertedWaterfall) { m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, (m_waterfallHeight * m_fftSize) / scaleDiv, 0); } else { m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, (m_waterfallHeight * m_fftSize) / scaleDiv); } } else { if (!m_invertedWaterfall) { m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 10, 0); } else { m_timeScale.setRange(m_timingRate > 1 ? Unit::TimeHMS : Unit::Time, 0, 10); } } m_leftMargin = m_timeScale.getScaleWidth(); m_leftMargin += 2 * M; m_frequencyScale.setSize(width() - m_leftMargin - m_rightMargin); m_frequencyScale.setRange(Unit::Frequency, m_centerFrequency - m_sampleRate / 2.0, m_centerFrequency + m_sampleRate / 2.0); m_frequencyScale.setMakeOpposite(m_lsbDisplay); m_glWaterfallBoxMatrix.setToIdentity(); m_glWaterfallBoxMatrix.translate( -1.0f + ((float)(2*m_leftMargin) / (float) width()), 1.0f - ((float)(2*m_topMargin) / (float) height()) ); m_glWaterfallBoxMatrix.scale( ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(), (float) (-2*m_waterfallHeight) / (float) height() ); // m_frequencyScaleRect = QRect( // 0, // frequencyScaleTop, // width(), // m_frequencyScaleHeight // ); m_glFrequencyScaleBoxMatrix.setToIdentity(); m_glFrequencyScaleBoxMatrix.translate ( -1.0f, 1.0f - ((float) 2*frequencyScaleTop / (float) height()) ); m_glFrequencyScaleBoxMatrix.scale ( 2.0f, (float) -2*m_frequencyScaleHeight / (float) height() ); m_glLeftScaleBoxMatrix.setToIdentity(); m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f); m_glLeftScaleBoxMatrix.scale( (float)(2*(m_leftMargin - 1)) / (float) width(), -2.0f ); } // displays histogram only else if (m_displayHistogram || m_displayMaxHold || m_displayCurrent) { m_bottomMargin = m_frequencyScaleHeight; frequencyScaleTop = height() - m_bottomMargin; histogramTop = m_topMargin - 1; m_waterfallHeight = 0; m_histogramHeight = height() - m_topMargin - m_frequencyScaleHeight; m_powerScale.setSize(m_histogramHeight); m_powerScale.setRange(Unit::Decibel, m_referenceLevel - m_powerRange, m_referenceLevel); m_leftMargin = m_powerScale.getScaleWidth(); m_leftMargin += 2 * M; m_frequencyScale.setSize(width() - m_leftMargin - m_rightMargin); m_frequencyScale.setRange(Unit::Frequency, m_centerFrequency - m_sampleRate / 2, m_centerFrequency + m_sampleRate / 2); m_frequencyScale.setMakeOpposite(m_lsbDisplay); m_glHistogramSpectrumMatrix.setToIdentity(); m_glHistogramSpectrumMatrix.translate( -1.0f + ((float)(2*m_leftMargin) / (float) width()), 1.0f - ((float)(2*histogramTop) / (float) height()) ); m_glHistogramSpectrumMatrix.scale( ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / ((float) width() * (float)(m_fftSize - 1)), ((float) 2*(height() - m_topMargin - m_frequencyScaleHeight)) / (height()*m_powerRange) ); m_glHistogramBoxMatrix.setToIdentity(); m_glHistogramBoxMatrix.translate( -1.0f + ((float)(2*m_leftMargin) / (float) width()), 1.0f - ((float)(2*histogramTop) / (float) height()) ); m_glHistogramBoxMatrix.scale( ((float) 2 * (width() - m_leftMargin - m_rightMargin)) / (float) width(), (float) (-2*(height() - m_topMargin - m_frequencyScaleHeight)) / (float) height() ); // m_frequencyScaleRect = QRect( // 0, // frequencyScaleTop, // width(), // m_frequencyScaleHeight // ); m_glFrequencyScaleBoxMatrix.setToIdentity(); m_glFrequencyScaleBoxMatrix.translate ( -1.0f, 1.0f - ((float) 2*frequencyScaleTop / (float) height()) ); m_glFrequencyScaleBoxMatrix.scale ( 2.0f, (float) -2*m_frequencyScaleHeight / (float) height() ); m_glLeftScaleBoxMatrix.setToIdentity(); m_glLeftScaleBoxMatrix.translate(-1.0f, 1.0f); m_glLeftScaleBoxMatrix.scale( (float)(2*(m_leftMargin - 1)) / (float) width(), -2.0f ); } else { m_leftMargin = 2; m_waterfallHeight = 0; } // bounding boxes m_frequencyScaleRect = QRect( 0, frequencyScaleTop, width(), m_frequencyScaleHeight ); if ((m_invertedWaterfall) || (m_waterfallHeight == 0)) { m_histogramRect = QRectF( (float) m_leftMargin / (float) width(), (float) m_topMargin / (float) height(), (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) (m_histogramHeight) / (float) height() ); } else { m_histogramRect = QRectF( (float) m_leftMargin / (float) width(), (float) (waterfallTop + m_waterfallHeight + m_frequencyScaleHeight) / (float) height(), (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_histogramHeight / (float) height() ); } if (!m_invertedWaterfall || (m_histogramHeight == 0)) { m_waterfallRect = QRectF( (float) m_leftMargin / (float) width(), (float) m_topMargin / (float) height(), (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_waterfallHeight / (float) height() ); } else { m_waterfallRect = QRectF( (float) m_leftMargin / (float) width(), (float) (m_topMargin + m_histogramHeight + m_frequencyScaleHeight) / (float) height(), (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) (m_waterfallHeight) / (float) height() ); } // channel overlays for (int i = 0; i < m_channelMarkerStates.size(); ++i) { ChannelMarkerState* dv = m_channelMarkerStates[i]; qreal xc, pw, nw, dsbw; ChannelMarker::sidebands_t sidebands = dv->m_channelMarker->getSidebands(); xc = m_centerFrequency + dv->m_channelMarker->getCenterFrequency(); // marker center frequency dsbw = dv->m_channelMarker->getBandwidth(); if (sidebands == ChannelMarker::usb) { nw = dv->m_channelMarker->getLowCutoff(); // negative bandwidth int bw = dv->m_channelMarker->getBandwidth() / 2; pw = (qreal) bw; // positive bandwidth } else if (sidebands == ChannelMarker::lsb) { pw = dv->m_channelMarker->getLowCutoff(); int bw = dv->m_channelMarker->getBandwidth() / 2; nw = (qreal) bw; } else if (sidebands == ChannelMarker::vusb) { nw = -dv->m_channelMarker->getOppositeBandwidth(); // negative bandwidth pw = dv->m_channelMarker->getBandwidth(); // positive bandwidth } else if (sidebands == ChannelMarker::vlsb) { pw = dv->m_channelMarker->getOppositeBandwidth(); // positive bandwidth nw = -dv->m_channelMarker->getBandwidth(); // negative bandwidth } else { pw = dsbw / 2; nw = -pw; } // draw the DSB rectangle QMatrix4x4 glMatrixDsb; glMatrixDsb.setToIdentity(); glMatrixDsb.translate( -1.0f + 2.0f * ((m_leftMargin + m_frequencyScale.getPosFromValue(xc - (dsbw/2))) / (float) width()), 1.0f ); glMatrixDsb.scale( 2.0f * (dsbw / (float)m_sampleRate), -2.0f ); dv->m_glMatrixDsbWaterfall = glMatrixDsb; dv->m_glMatrixDsbWaterfall.translate( 0.0f, (float) waterfallTop / (float) height() ); dv->m_glMatrixDsbWaterfall.scale( (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_waterfallHeight / (float) height() ); dv->m_glMatrixDsbHistogram = glMatrixDsb; dv->m_glMatrixDsbHistogram.translate( 0.0f, (float) histogramTop / (float) height() ); dv->m_glMatrixDsbHistogram.scale( (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_histogramHeight / (float) height() ); dv->m_glMatrixDsbFreqScale = glMatrixDsb; dv->m_glMatrixDsbFreqScale.translate( 0.0f, (float) frequencyScaleTop / (float) height() ); dv->m_glMatrixDsbFreqScale.scale( (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_frequencyScaleHeight / (float) height() ); // draw the effective BW rectangle QMatrix4x4 glMatrix; glMatrix.setToIdentity(); glMatrix.translate( -1.0f + 2.0f * ((m_leftMargin + m_frequencyScale.getPosFromValue(xc + nw)) / (float) width()), 1.0f ); glMatrix.scale( 2.0f * ((pw-nw) / (float)m_sampleRate), -2.0f ); dv->m_glMatrixWaterfall = glMatrix; dv->m_glMatrixWaterfall.translate( 0.0f, (float) waterfallTop / (float) height() ); dv->m_glMatrixWaterfall.scale( (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_waterfallHeight / (float) height() ); dv->m_glMatrixHistogram = glMatrix; dv->m_glMatrixHistogram.translate( 0.0f, (float) histogramTop / (float) height() ); dv->m_glMatrixHistogram.scale( (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_histogramHeight / (float) height() ); dv->m_glMatrixFreqScale = glMatrix; dv->m_glMatrixFreqScale.translate( 0.0f, (float) frequencyScaleTop / (float) height() ); dv->m_glMatrixFreqScale.scale( (float) (width() - m_leftMargin - m_rightMargin) / (float) width(), (float) m_frequencyScaleHeight / (float) height() ); /* dv->m_glRect.setRect( m_frequencyScale.getPosFromValue(m_centerFrequency + dv->m_channelMarker->getCenterFrequency() - dv->m_channelMarker->getBandwidth() / 2) / (float)(width() - m_leftMargin - m_rightMargin), 0, (dv->m_channelMarker->getBandwidth() / (float)m_sampleRate), 1); */ if (m_displayHistogram || m_displayMaxHold || m_displayCurrent || m_displayWaterfall) { dv->m_rect.setRect(m_frequencyScale.getPosFromValue(xc) + m_leftMargin - 1, m_topMargin, 5, height() - m_topMargin - m_bottomMargin); } /* if(m_displayHistogram || m_displayMaxHold || m_displayWaterfall) { dv->m_rect.setRect(m_frequencyScale.getPosFromValue(m_centerFrequency + dv->m_channelMarker->getCenterFrequency()) + m_leftMargin - 1, m_topMargin, 5, height() - m_topMargin - m_bottomMargin); } */ } // prepare left scales (time and power) { m_leftMarginPixmap = QPixmap(m_leftMargin - 1, height()); m_leftMarginPixmap.fill(Qt::black); { QPainter painter(&m_leftMarginPixmap); painter.setPen(QColor(0xf0, 0xf0, 0xff)); painter.setFont(font()); const ScaleEngine::TickList* tickList; const ScaleEngine::Tick* tick; if (m_displayWaterfall) { tickList = &m_timeScale.getTickList(); for (int i = 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; if (tick->major) { if (tick->textSize > 0) painter.drawText(QPointF(m_leftMargin - M - tick->textSize, waterfallTop + fm.ascent() + tick->textPos), tick->text); } } } if (m_displayHistogram || m_displayMaxHold || m_displayCurrent) { tickList = &m_powerScale.getTickList(); for (int i = 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; if (tick->major) { if (tick->textSize > 0) painter.drawText(QPointF(m_leftMargin - M - tick->textSize, histogramTop + m_histogramHeight - tick->textPos - 1), tick->text); } } } } m_glShaderLeftScale.initTexture(m_leftMarginPixmap.toImage()); } // prepare frequency scale if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent){ m_frequencyPixmap = QPixmap(width(), m_frequencyScaleHeight); m_frequencyPixmap.fill(Qt::transparent); { QPainter painter(&m_frequencyPixmap); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.setBrush(Qt::transparent); painter.drawRect(m_leftMargin, 0, width() - m_leftMargin, m_frequencyScaleHeight); painter.setPen(QColor(0xf0, 0xf0, 0xff)); painter.setFont(font()); const ScaleEngine::TickList* tickList = &m_frequencyScale.getTickList(); const ScaleEngine::Tick* tick; for (int i = 0; i < tickList->count(); i++) { tick = &(*tickList)[i]; if (tick->major) { if (tick->textSize > 0) painter.drawText(QPointF(m_leftMargin + tick->textPos, fm.height() + fm.ascent() / 2 - 1), tick->text); } } // Frequency overlay on highlighted marker for (int i = 0; i < m_channelMarkerStates.size(); ++i) { ChannelMarkerState* dv = m_channelMarkerStates[i]; if (dv->m_channelMarker->getHighlighted() && (dv->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink) && dv->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { qreal xc; int shift; //ChannelMarker::sidebands_t sidebands = dv->m_channelMarker->getSidebands(); xc = m_centerFrequency + dv->m_channelMarker->getCenterFrequency(); // marker center frequency QString ftext; switch (dv->m_channelMarker->getFrequencyScaleDisplayType()) { case ChannelMarker::FScaleDisplay_freq: ftext = QString::number((m_centerFrequency + dv->m_channelMarker->getCenterFrequency())/1e6, 'f', 6); break; case ChannelMarker::FScaleDisplay_title: ftext = dv->m_channelMarker->getTitle(); break; case ChannelMarker::FScaleDisplay_addressSend: ftext = dv->m_channelMarker->getDisplayAddressSend(); break; case ChannelMarker::FScaleDisplay_addressReceive: ftext = dv->m_channelMarker->getDisplayAddressReceive(); break; default: ftext = QString::number((m_centerFrequency + dv->m_channelMarker->getCenterFrequency())/1e6, 'f', 6); break; } if (dv->m_channelMarker->getCenterFrequency() < 0) { // left half of scale ftext = " " + ftext; shift = 0; } else { // right half of scale ftext = ftext + " "; shift = - fm.width(ftext); } painter.drawText(QPointF(m_leftMargin + m_frequencyScale.getPosFromValue(xc) + shift, 2*fm.height() + fm.ascent() / 2 - 1), ftext); } } } m_glShaderFrequencyScale.initTexture(m_frequencyPixmap.toImage()); } bool fftSizeChanged = true; if (m_waterfallBuffer) { fftSizeChanged = m_waterfallBuffer->width() != m_fftSize; } bool windowSizeChanged = m_waterfallTextureHeight != m_waterfallHeight; if (fftSizeChanged || windowSizeChanged) { if (m_waterfallBuffer) { delete m_waterfallBuffer; } m_waterfallBuffer = new QImage(m_fftSize, m_waterfallHeight, QImage::Format_ARGB32); m_waterfallBuffer->fill(qRgb(0x00, 0x00, 0x00)); m_glShaderWaterfall.initTexture(*m_waterfallBuffer); m_waterfallBufferPos = 0; } if (fftSizeChanged) { if (m_histogramBuffer) { delete m_histogramBuffer; m_histogramBuffer = nullptr; } if (m_histogram) { delete[] m_histogram; m_histogram = nullptr; } m_histogramBuffer = new QImage(m_fftSize, 100, QImage::Format_RGB32); m_histogramBuffer->fill(qRgb(0x00, 0x00, 0x00)); m_glShaderHistogram.initTexture(*m_histogramBuffer, QOpenGLTexture::ClampToEdge); m_histogram = new quint8[100 * m_fftSize]; memset(m_histogram, 0x00, 100 * m_fftSize); m_q3FFT.allocate(2*m_fftSize); } if (fftSizeChanged || windowSizeChanged) { m_waterfallTextureHeight = m_waterfallHeight; m_waterfallTexturePos = 0; } m_q3TickTime.allocate(4*m_timeScale.getTickList().count()); m_q3TickFrequency.allocate(4*m_frequencyScale.getTickList().count()); m_q3TickPower.allocate(4*m_powerScale.getTickList().count()); } void GLSpectrum::mouseMoveEvent(QMouseEvent* event) { if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent) { if (m_frequencyScaleRect.contains(event->pos())) { if (m_cursorState == CSNormal) { setCursor(Qt::SizeVerCursor); m_cursorState = CSSplitter; return; } } else { if (m_cursorState == CSSplitter) { setCursor(Qt::ArrowCursor); m_cursorState = CSNormal; return; } } } if (m_cursorState == CSSplitterMoving) { QMutexLocker mutexLocker(&m_mutex); float newShare; if (!m_invertedWaterfall) { newShare = (float) (event->y() - m_frequencyScaleRect.height()) / (float) height(); } else { newShare = 1.0 - (float) (event->y() + m_frequencyScaleRect.height()) / (float) height(); } if (newShare < 0.1) { newShare = 0.1f; } else if (newShare > 0.8) { newShare = 0.8f; } m_waterfallShare = newShare; m_changesPending = true; update(); return; } else if (m_cursorState == CSChannelMoving) { Real freq = m_frequencyScale.getValueFromPos(event->x() - m_leftMarginPixmap.width() - 1) - m_centerFrequency; if (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getMovable() && (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink) && m_channelMarkerStates[m_cursorChannel]->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { m_channelMarkerStates[m_cursorChannel]->m_channelMarker->setCenterFrequencyByCursor(freq); channelMarkerChanged(); } } if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent) { for (int i = 0; i < m_channelMarkerStates.size(); ++i) { if ((m_channelMarkerStates[i]->m_channelMarker->getSourceOrSinkStream() != m_displaySourceOrSink) || !m_channelMarkerStates[i]->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { continue; } if (m_channelMarkerStates[i]->m_rect.contains(event->pos())) { if (m_cursorState == CSNormal) { setCursor(Qt::SizeHorCursor); m_cursorState = CSChannel; m_cursorChannel = i; m_channelMarkerStates[i]->m_channelMarker->setHighlightedByCursor(true); channelMarkerChanged(); return; } else if (m_cursorState == CSChannel) { return; } } else if (m_channelMarkerStates[i]->m_channelMarker->getHighlighted()) { m_channelMarkerStates[i]->m_channelMarker->setHighlightedByCursor(false); channelMarkerChanged(); } } } if (m_cursorState == CSChannel) { setCursor(Qt::ArrowCursor); m_cursorState = CSNormal; return; } } void GLSpectrum::mousePressEvent(QMouseEvent* event) { const QPointF& ep = event->localPos(); float waterfallShiftY = m_topMargin + m_frequencyScaleHeight + (m_waterfallHeight == 0 ? 0 : m_waterfallHeight + m_bottomMargin); float histogramShiftY = m_topMargin + m_frequencyScaleHeight + (m_histogramHeight == 0 ? 0 : m_histogramHeight + m_bottomMargin); if (event->button() == Qt::RightButton) { QPointF pHis = ep; bool doUpdate = false; pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width(); pHis.ry() = (ep.y()/height() - m_histogramRect.top()) / m_histogramRect.height(); if (event->modifiers() & Qt::ShiftModifier) { if ((pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1)) { m_histogramMarkers.clear(); doUpdate = true; } } else { if ((m_histogramMarkers.size() > 0) && (pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1)) { m_histogramMarkers.pop_back(); doUpdate = true; } } QPointF pWat = ep; pWat.rx() = (ep.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width(); pWat.ry() = (ep.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height(); if (event->modifiers() & Qt::ShiftModifier) { if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1)) { m_waterfallMarkers.clear(); doUpdate = true; } } else { if ((m_waterfallMarkers.size() > 0) && (pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1)) { m_waterfallMarkers.pop_back(); doUpdate = true; } } if (doUpdate) { update(); } } else if (event->button() == Qt::LeftButton) { if (event->modifiers() & Qt::ShiftModifier) { QPointF pHis = ep; bool doUpdate = false; pHis.rx() = (ep.x()/width() - m_histogramRect.left()) / m_histogramRect.width(); pHis.ry() = (ep.y()/height() - m_histogramRect.top()) / m_histogramRect.height(); float frequency = m_frequencyScale.getRangeMin() + pHis.x()*m_frequencyScale.getRange(); float power = m_powerScale.getRangeMax() - pHis.y()*m_powerScale.getRange(); if ((pHis.x() >= 0) && (pHis.x() <= 1) && (pHis.y() >= 0) && (pHis.y() <= 1)) { if (m_histogramMarkers.size() < 2) { m_histogramMarkers.push_back(HistogramMarker()); m_histogramMarkers.back().m_point = pHis; m_histogramMarkers.back().m_frequency = frequency; m_histogramMarkers.back().m_frequencyStr = displayScaled( frequency, 'f', getPrecision(m_centerFrequency/m_sampleRate), false); m_histogramMarkers.back().m_power = power; m_histogramMarkers.back().m_powerStr = displayScaledF( power, m_linear ? 'e' : 'f', m_linear ? 3 : 1, false); if (m_histogramMarkers.size() > 1) { int64_t deltaFrequency = frequency - m_histogramMarkers.at(0).m_frequency; m_histogramMarkers.back().m_deltaFrequencyStr = displayScaled( deltaFrequency, 'f', getPrecision(deltaFrequency/m_sampleRate), true); m_histogramMarkers.back().m_deltaPowerStr = displayScaledF( power - m_histogramMarkers.at(0).m_power, m_linear ? 'e' : 'f', m_linear ? 3 : 1, false); } doUpdate = true; } } QPointF pWat = ep; pWat.rx() = (ep.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width(); pWat.ry() = (ep.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height(); frequency = m_frequencyScale.getRangeMin() + pWat.x()*m_frequencyScale.getRange(); float time = m_timeScale.getRangeMin() + pWat.y()*m_timeScale.getRange(); if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1)) { if (m_waterfallMarkers.size() < 2) { m_waterfallMarkers.push_back(WaterfallMarker()); m_waterfallMarkers.back().m_point = pWat; m_waterfallMarkers.back().m_frequency = frequency; m_waterfallMarkers.back().m_frequencyStr = displayScaled( frequency, 'f', getPrecision(m_centerFrequency/m_sampleRate), false); m_waterfallMarkers.back().m_time = time; m_waterfallMarkers.back().m_timeStr = displayScaledF( time, 'f', 3, true); if (m_waterfallMarkers.size() > 1) { int64_t deltaFrequency = frequency - m_waterfallMarkers.at(0).m_frequency; m_waterfallMarkers.back().m_deltaFrequencyStr = displayScaled( deltaFrequency, 'f', getPrecision(deltaFrequency/m_sampleRate), true); m_waterfallMarkers.back().m_deltaTimeStr = displayScaledF( time - m_waterfallMarkers.at(0).m_time, 'f', 3, true); } doUpdate = true; } } if (doUpdate) { update(); } } if (m_cursorState == CSSplitter) { grabMouse(); m_cursorState = CSSplitterMoving; return; } else if (m_cursorState == CSChannel) { grabMouse(); m_cursorState = CSChannelMoving; return; } else if ((m_cursorState == CSNormal) && (m_channelMarkerStates.size() == 1) && !(event->modifiers() & Qt::ShiftModifier)) { grabMouse(); setCursor(Qt::SizeHorCursor); m_cursorState = CSChannelMoving; m_cursorChannel = 0; Real freq = m_frequencyScale.getValueFromPos(event->x() - m_leftMarginPixmap.width() - 1) - m_centerFrequency; if (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getMovable() && (m_channelMarkerStates[m_cursorChannel]->m_channelMarker->getSourceOrSinkStream() == m_displaySourceOrSink) && m_channelMarkerStates[m_cursorChannel]->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { m_channelMarkerStates[m_cursorChannel]->m_channelMarker->setCenterFrequencyByCursor(freq); channelMarkerChanged(); } return; } } } void GLSpectrum::mouseReleaseEvent(QMouseEvent*) { if (m_cursorState == CSSplitterMoving) { releaseMouse(); m_cursorState = CSSplitter; } else if (m_cursorState == CSChannelMoving) { releaseMouse(); m_cursorState = CSChannel; } } void GLSpectrum::wheelEvent(QWheelEvent *event) { int mul; if (event->modifiers() & Qt::ShiftModifier) { mul = 100; } else if (event->modifiers() & Qt::ControlModifier) { mul = 10; } else { mul = 1; } for (int i = 0; i < m_channelMarkerStates.size(); ++i) { if ((m_channelMarkerStates[i]->m_channelMarker->getSourceOrSinkStream() != m_displaySourceOrSink) || !m_channelMarkerStates[i]->m_channelMarker->streamIndexApplies(m_displayStreamIndex)) { continue; } if (m_channelMarkerStates[i]->m_rect.contains(event->pos())) { int freq = m_channelMarkerStates[i]->m_channelMarker->getCenterFrequency(); if (event->delta() > 0) { freq += 10 * mul; } else if (event->delta() < 0) { freq -= 10 * mul; } // calculate scale relative cursor position for new frequency float x_pos = m_frequencyScale.getPosFromValue(m_centerFrequency + freq); if ((x_pos >= 0.0) && (x_pos < m_frequencyScale.getSize())) // cursor must be in scale { m_channelMarkerStates[i]->m_channelMarker->setCenterFrequencyByCursor(freq); m_channelMarkerStates[i]->m_channelMarker->setCenterFrequency(freq); // cursor follow-up int xd = x_pos + m_leftMargin; QCursor c = cursor(); QPoint cp_a = c.pos(); QPoint cp_w = mapFromGlobal(cp_a); cp_w.setX(xd); cp_a = mapToGlobal(cp_w); c.setPos(cp_a); setCursor(c); } } } } void GLSpectrum::enterEvent(QEvent* event) { m_mouseInside = true; update(); QGLWidget::enterEvent(event); } void GLSpectrum::leaveEvent(QEvent* event) { m_mouseInside = false; update(); QGLWidget::enterEvent(event); } void GLSpectrum::tick() { if (m_displayChanged) { m_displayChanged = false; update(); } } void GLSpectrum::channelMarkerChanged() { QMutexLocker mutexLocker(&m_mutex); m_changesPending = true; update(); } void GLSpectrum::channelMarkerDestroyed(QObject* object) { removeChannelMarker((ChannelMarker*)object); } void GLSpectrum::setWaterfallShare(Real waterfallShare) { QMutexLocker mutexLocker(&m_mutex); if (waterfallShare < 0.1f) { m_waterfallShare = 0.1f; } else if (waterfallShare > 0.8f) { m_waterfallShare = 0.8f; } else { m_waterfallShare = waterfallShare; } m_changesPending = true; } void GLSpectrum::connectTimer(const QTimer& timer) { qDebug() << "GLSpectrum::connectTimer"; disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick())); connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); m_timer.stop(); } void GLSpectrum::cleanup() { //makeCurrent(); m_glShaderSimple.cleanup(); m_glShaderFrequencyScale.cleanup(); m_glShaderHistogram.cleanup(); m_glShaderLeftScale.cleanup(); m_glShaderWaterfall.cleanup(); m_glShaderTextOverlay.cleanup(); //doneCurrent(); } QString GLSpectrum::displayScaled(int64_t value, char type, int precision, bool showMult) { int64_t posValue = (value < 0) ? -value : value; if (posValue < 1000) { return tr("%1").arg(QString::number(value, type, precision)); } else if (posValue < 1000000) { return tr("%1%2").arg(QString::number(value / 1000.0, type, precision)).arg(showMult ? "k" : ""); } else if (posValue < 1000000000) { return tr("%1%2").arg(QString::number(value / 1000000.0, type, precision)).arg(showMult ? "M" : ""); } else if (posValue < 1000000000000) { return tr("%1%2").arg(QString::number(value / 1000000000.0, type, precision)).arg(showMult ? "G" : ""); } else { return tr("%1").arg(QString::number(value, 'e', precision)); } } QString GLSpectrum::displayScaledF(float value, char type, int precision, bool showMult) { float posValue = (value < 0) ? -value : value; if (posValue == 0) { return tr("%1").arg(QString::number(value, 'f', precision)); } else if (posValue < 1) { if (posValue > 0.001) { return tr("%1%2").arg(QString::number(value * 1000.0, type, precision)).arg(showMult ? "m" : ""); } else if (posValue > 0.000001) { return tr("%1%2").arg(QString::number(value * 1000000.0, type, precision)).arg(showMult ? "u" : ""); } else if (posValue > 1e-9) { return tr("%1%2").arg(QString::number(value * 1e9, type, precision)).arg(showMult ? "n" : ""); } else if (posValue > 1e-12) { return tr("%1%2").arg(QString::number(value * 1e12, type, precision)).arg(showMult ? "p" : ""); } else { return tr("%1").arg(QString::number(value, 'e', precision)); } } else { if (posValue < 1000) { return tr("%1").arg(QString::number(value, type, precision)); } else if (posValue < 1000000) { return tr("%1%2").arg(QString::number(value / 1000.0, type, precision)).arg(showMult ? "k" : ""); } else if (posValue < 1000000000) { return tr("%1%2").arg(QString::number(value / 1000000.0, type, precision)).arg(showMult ? "M" : ""); } else if (posValue < 1000000000000) { return tr("%1%2").arg(QString::number(value / 1000000000.0, type, precision)).arg(showMult ? "G" : ""); } else { return tr("%1").arg(QString::number(value, 'e', precision)); } } } int GLSpectrum::getPrecision(int value) { int posValue = (value < 0) ? -value : value; if (posValue < 1000) { return 3; } else if (posValue < 10000) { return 4; } else if (posValue < 100000) { return 5; } else { return 6; } } void GLSpectrum::drawTextOverlay( const QString &text, const QColor &color, const QFont& font, float shiftX, float shiftY, bool leftHalf, bool topHalf, const QRectF &glRect) { if (text.isEmpty()) { return; } QFontMetricsF metrics(font); QRectF textRect = metrics.boundingRect(text); QRectF overlayRect(0, 0, textRect.width() * 1.05f + 4.0f, textRect.height()); QPixmap channelOverlayPixmap = QPixmap(overlayRect.width(), overlayRect.height()); channelOverlayPixmap.fill(Qt::transparent); QPainter painter(&channelOverlayPixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, false); painter.fillRect(overlayRect, QColor(0, 0, 0, 0x80)); QColor textColor(color); textColor.setAlpha(0xC0); painter.setPen(textColor); painter.setFont(font); painter.drawText(QPointF(2.0f, overlayRect.height() - 4.0f), text); painter.end(); m_glShaderTextOverlay.initTexture(channelOverlayPixmap.toImage()); { GLfloat vtx1[] = { 0, 1, 1, 1, 1, 0, 0, 0}; GLfloat tex1[] = { 0, 1, 1, 1, 1, 0, 0, 0}; // float shiftX = glRect.width() - ((overlayRect.width() + 4.0f) / width()); // float shiftY = 4.0f / height(); float rectX = glRect.x() + shiftX - (leftHalf ? 0 : (overlayRect.width()+1)/width()); float rectY = glRect.y() + shiftY + (4.0f / height()) - (topHalf ? 0 : (overlayRect.height()+5)/height()); float rectW = overlayRect.width() / (float) width(); float rectH = overlayRect.height() / (float) height(); QMatrix4x4 mat; mat.setToIdentity(); mat.translate(-1.0f + 2.0f * rectX, 1.0f - 2.0f * rectY); mat.scale(2.0f * rectW, -2.0f * rectH); m_glShaderTextOverlay.drawSurface(mat, tex1, vtx1, 4); } }