From 4c066a82962a9a0634dcfe0a305de7b2a4cacc5b Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Sat, 16 Jan 2016 20:55:45 +0100 Subject: Added the Colormap component. * The Colormap component is a simple rect with a texture mapped that displays a ColorScale with a fixed number of samples. This number of samples is exported as a member const, which is used on other components (such as VoronoiSplat). * The texture mapping is reflecting the colormap lookup used in VoronoiSplat. * The ColorScale class now has a method for sampling the color scale and outputs the samples to iterator-style objects, providing easy intergration with existing code. --- colormap.cpp | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ colormap.h | 40 ++++++++++++++++++++ colorscale.h | 23 +++++++++++ main.cpp | 33 +++++++++------- main_view.qml | 9 +++++ pm.pro | 2 + scatterplot.cpp | 6 ++- scatterplot.h | 2 + voronoisplat.cpp | 101 +++++++++++++++++++++++++------------------------ voronoisplat.h | 4 +- 10 files changed, 265 insertions(+), 68 deletions(-) create mode 100644 colormap.cpp create mode 100644 colormap.h diff --git a/colormap.cpp b/colormap.cpp new file mode 100644 index 0000000..545151f --- /dev/null +++ b/colormap.cpp @@ -0,0 +1,113 @@ +#include "colormap.h" + +#include +#include + +class ColormapTexture + : public QSGDynamicTexture +{ +public: + ColormapTexture(const std::vector *cmap); + ~ColormapTexture(); + + bool hasAlphaChannel() const { return false; } + bool hasMipmaps() const { return false; } + void bind(); + bool updateTexture(); + + int textureId() const { return m_texture; } + QSize textureSize() const { return m_size; } + +private: + QOpenGLFunctions gl; + + QSize m_size; + GLuint m_texture; + const std::vector *m_cmap; +}; + +ColormapTexture::ColormapTexture(const std::vector *cmap) + : gl(QOpenGLContext::currentContext()) + , m_size(Colormap::SAMPLES, 1) + , m_cmap(cmap) +{ + // Setup OpenGL texture + gl.glGenTextures(1, &m_texture); + gl.glBindTexture(GL_TEXTURE_2D, m_texture); + gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_size.width(), m_size.height(), + 0, GL_RGB, GL_FLOAT, 0); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +ColormapTexture::~ColormapTexture() +{ + gl.glDeleteTextures(1, &m_texture); +} + +void ColormapTexture::bind() +{ + gl.glBindTexture(GL_TEXTURE_2D, m_texture); +} + +bool ColormapTexture::updateTexture() +{ + gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_size.width(), m_size.height(), + GL_RGB, GL_FLOAT, m_cmap->data()); + return true; +} + +Colormap::Colormap(QQuickItem *parent) + : QQuickItem(parent) + , m_texture(0) + , m_shouldUpdateTexture(false) + , m_cmap(3*SAMPLES) +{ + setFlag(QQuickItem::ItemHasContents); +} + +Colormap::~Colormap() +{ + if (m_texture) { + delete m_texture; + } +} + + +void Colormap::setColorScale(const ColorScale &scale) +{ + scale.sample(SAMPLES, m_cmap.begin()); + emit colorScaleChanged(scale); + + m_shouldUpdateTexture = true; + update(); +} + +QSGNode *Colormap::newSceneGraph() +{ + QSGSimpleTextureNode *node = new QSGSimpleTextureNode; + m_texture = new ColormapTexture(&m_cmap); + + node->setTexture(m_texture); + node->setOwnsTexture(false); + + const QSize &texSize = m_texture->textureSize(); + node->setSourceRect(0, 0, texSize.width(), texSize.height()); + + return node; +} + +QSGNode *Colormap::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + QSGNode *root = oldNode ? oldNode : newSceneGraph(); + + QSGSimpleTextureNode *node = static_cast(root); + node->setRect(x(), y(), width(), height()); + + if (m_shouldUpdateTexture) { + m_texture->updateTexture(); + m_shouldUpdateTexture = false; + } + + return root; +} diff --git a/colormap.h b/colormap.h new file mode 100644 index 0000000..806624a --- /dev/null +++ b/colormap.h @@ -0,0 +1,40 @@ +#ifndef COLORMAP_H +#define COLORMAP_H + +#include + +#include +#include + +#include + +#include "colorscale.h" + +class Colormap + : public QQuickItem +{ + Q_OBJECT +public: + static const int SAMPLES = 128; + + Colormap(QQuickItem *parent = 0); + ~Colormap(); + +signals: + void colorScaleChanged(const ColorScale &scale) const; + +public slots: + void setColorScale(const ColorScale &scale); + +protected: + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); + +private: + QSGNode *newSceneGraph(); + + QSGDynamicTexture *m_texture; + bool m_shouldUpdateTexture; + std::vector m_cmap; +}; + +#endif // COLORMAP_H diff --git a/colorscale.h b/colorscale.h index af74b5c..90671eb 100644 --- a/colorscale.h +++ b/colorscale.h @@ -20,9 +20,32 @@ public: float min() const { return m_min; } float max() const { return m_max; } + template + void sample(int samples, OutputIterator it) const; + protected: float m_min, m_max; QList m_colors; }; +template +void ColorScale::sample(int samples, OutputIterator it) const +{ + if (samples < 1) { + return; + } + + float t = min(); + float step = (max() - min()) / samples; + qreal r, g, b; + for (unsigned i = 0; i < 3*samples; i += 3) { + color(t).getRgbF(&r, &g, &b); + *it = r; it++; + *it = g; it++; + *it = b; it++; + + t += step; + } +} + #endif // COLORSCALE_H diff --git a/main.cpp b/main.cpp index 4d43c4c..b79f6ff 100644 --- a/main.cpp +++ b/main.cpp @@ -15,6 +15,7 @@ #include "voronoisplat.h" #include "historygraph.h" #include "barchart.h" +#include "colormap.h" #include "interactionhandler.h" #include "selectionhandler.h" #include "skelft.h" @@ -97,6 +98,7 @@ int main(int argc, char **argv) qmlRegisterType("PM", 1, 0, "HistoryGraph"); qmlRegisterType("PM", 1, 0, "BarChart"); qmlRegisterType("PM", 1, 0, "VoronoiSplat"); + qmlRegisterType("PM", 1, 0, "Colormap"); qmlRegisterType("PM", 1, 0, "InteractionHandler"); qmlRegisterSingletonType
("PM", 1, 0, "Main", mainProvider); @@ -113,21 +115,22 @@ int main(int argc, char **argv) QQmlApplicationEngine engine(QUrl("qrc:///main_view.qml")); - //ColorScale colorScale{ - // QColor("#1f77b4"), - // QColor("#ff7f0e"), - // QColor("#2ca02c"), - // QColor("#d62728"), - // QColor("#9467bd"), - // QColor("#8c564b"), - // QColor("#e377c2"), - // QColor("#17becf"), - // QColor("#7f7f7f"), - //}; + ColorScale colorScale{ + QColor("#1f77b4"), + QColor("#ff7f0e"), + QColor("#2ca02c"), + QColor("#d62728"), + QColor("#9467bd"), + QColor("#8c564b"), + QColor("#e377c2"), + QColor("#17becf"), + QColor("#7f7f7f"), + }; + colorScale.setExtents(labels.min(), labels.max()); + + //ContinuousColorScale colorScale = ContinuousColorScale::builtin(ContinuousColorScale::RED_GRAY_BLUE); //colorScale.setExtents(labels.min(), labels.max()); - ContinuousColorScale colorScale = ContinuousColorScale::builtin(ContinuousColorScale::RED_GRAY_BLUE); - colorScale.setExtents(labels.min(), labels.max()); Scatterplot *cpPlot = engine.rootObjects()[0]->findChild("cpPlot"); cpPlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); // cpPlot->setColorData(arma::zeros(cpSize)); @@ -135,6 +138,7 @@ int main(int argc, char **argv) Scatterplot *plot = engine.rootObjects()[0]->findChild("plot"); VoronoiSplat *splat = engine.rootObjects()[0]->findChild("splat"); skelft2DInitialization(splat->width()); + Colormap *colormap = engine.rootObjects()[0]->findChild("colormap"); // Keep track of the current cp (in order to save them later, if requested) QObject::connect(cpPlot, SIGNAL(xyChanged(const arma::mat &)), @@ -176,10 +180,11 @@ int main(int argc, char **argv) barChart->setValues(arma::randn(100)); //history->addHistoryItem(Ys); + colormap->setColorScale(colorScale); plot->setColorScale(&colorScale); plot->setColorData(labels, false); + //splat->setColormap(colorScale); splat->setValues(labels); - splat->update(); cpPlot->setAutoScale(false); cpPlot->setColorData(labels(cpIndices), false); diff --git a/main_view.qml b/main_view.qml index dc3fe45..68d6fea 100644 --- a/main_view.qml +++ b/main_view.qml @@ -125,6 +125,15 @@ ApplicationWindow { y: parent.y anchors.fill: parent } + + Colormap { + id: colormap + objectName: "colormap" + x: parent.x + 5 + y: parent.y + 5 + width: 128 + height: 10 + } } Rectangle { diff --git a/pm.pro b/pm.pro index 54476c8..53ac0a5 100644 --- a/pm.pro +++ b/pm.pro @@ -11,6 +11,7 @@ HEADERS += main.h \ scale.h \ scatterplot.h \ voronoisplat.h \ + colormap.h \ historygraph.h \ barchart.h \ interactionhandler.h \ @@ -28,6 +29,7 @@ SOURCES += main.cpp \ geometry.cpp \ scatterplot.cpp \ voronoisplat.cpp \ + colormap.cpp \ historygraph.cpp \ barchart.cpp \ interactionhandler.cpp \ diff --git a/scatterplot.cpp b/scatterplot.cpp index 04a50cd..5b7e82d 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -12,7 +12,6 @@ static const QColor SELECTION_COLOR(128, 128, 128, 96); static const float GLYPH_SIZE = 8.0f; static const float GLYPH_OUTLINE_WIDTH = 2.0f; -static const float PADDING = 10.0f; Scatterplot::Scatterplot(QQuickItem *parent) : QQuickItem(parent) @@ -239,7 +238,6 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) updateGlyphs(node); node = node->nextSibling(); - // Change update hints to false; the splat and glyphs were just updated if (m_shouldUpdateGeometry) { m_shouldUpdateGeometry = false; } @@ -265,6 +263,10 @@ void Scatterplot::updateGlyphs(QSGNode *glyphsNode) { qreal x, y, tx, ty, moveTranslationF; + if (!m_shouldUpdateGeometry && !m_shouldUpdateMaterials) { + return; + } + if (m_currentInteractionState == INTERACTION_MOVING) { tx = m_dragCurrentPos.x() - m_dragOriginPos.x(); ty = m_dragCurrentPos.y() - m_dragOriginPos.y(); diff --git a/scatterplot.h b/scatterplot.h index 656ed09..f577819 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -25,6 +25,8 @@ public: void setAutoScale(bool autoScale); Q_INVOKABLE bool saveToFile(const QUrl &url); + static const int PADDING = 10; + signals: void xyChanged(const arma::mat &XY) const; void xyInteractivelyChanged(const arma::mat &XY) const; diff --git a/voronoisplat.cpp b/voronoisplat.cpp index a33844d..3b11619 100644 --- a/voronoisplat.cpp +++ b/voronoisplat.cpp @@ -7,12 +7,12 @@ #include #include +#include "colormap.h" #include "scale.h" +#include "scatterplot.h" #include "skelft.h" static const float RAD_BLUR = 5.0f; -static const int COLORMAP_SAMPLES = 128; -static const float PADDING = 10.0f; // in screen pixels static int nextPow2(int n) { @@ -64,7 +64,7 @@ private: VoronoiSplat::VoronoiSplat(QQuickItem *parent) : QQuickFramebufferObject(parent) - , m_cmap(3*COLORMAP_SAMPLES) + , m_cmap(3*Colormap::SAMPLES) { setTextureFollowsItemSize(false); } @@ -84,19 +84,20 @@ void VoronoiSplat::setSites(const arma::mat &points) // Coords are packed into 'm_sites' as [ x1, y1, x2, y2, ... ] m_sites.resize(2*points.n_rows); - LinearScale sx(minX, maxX, PADDING, width() - PADDING); + LinearScale sx(minX, maxX, Scatterplot::PADDING, width() - Scatterplot::PADDING); const double *col = points.colptr(0); for (unsigned i = 0; i < points.n_rows; i++) { m_sites[2*i] = sx(col[i]); } col = points.colptr(1); - LinearScale sy(minY, maxY, height() - PADDING, PADDING); + LinearScale sy(minY, maxY, height() - Scatterplot::PADDING, Scatterplot::PADDING); for (unsigned i = 0; i < points.n_rows; i++) { m_sites[2*i + 1] = sy(col[i]); } setSitesChanged(true); + update(); } void VoronoiSplat::setValues(const arma::vec &values) @@ -109,29 +110,19 @@ void VoronoiSplat::setValues(const arma::vec &values) m_values.resize(values.n_elem); LinearScale scale(values.min(), values.max(), 0, 1.0f); std::transform(values.begin(), values.end(), m_values.begin(), scale); + emit valuesChanged(values); setValuesChanged(true); + update(); } -void VoronoiSplat::setColormap(const ColorScale *scale) +void VoronoiSplat::setColormap(const ColorScale &scale) { - if (!scale) { - return; - } - - float t = scale->min(); - float step = (scale->max() - scale->min()) / COLORMAP_SAMPLES; - qreal r, g, b; - for (unsigned i = 0; i < m_cmap.size(); i += 3) { - scale->color(t).getRgbF(&r, &g, &b); - m_cmap[i + 0] = r; - m_cmap[i + 1] = g; - m_cmap[i + 2] = b; - - t += step; - } + scale.sample(Colormap::SAMPLES, m_cmap.begin()); + emit colormapChanged(scale); setColormapChanged(true); + update(); } QQuickFramebufferObject::Renderer *VoronoiSplat::createRenderer() const @@ -274,29 +265,12 @@ void VoronoiSplatRenderer::setupVAOs() void VoronoiSplatRenderer::setupTextures() { gl.glGenTextures(2, m_textures); - gl.glGenTextures(1, &m_colormapTex); -} - -void VoronoiSplatRenderer::resizeTextures() -{ - // textures[0] stores the DT values for each pixel - gl.glBindTexture(GL_TEXTURE_2D, m_textures[0]); - gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_size.width(), - m_size.height(), 0, GL_RG, GL_FLOAT, 0); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - // textures[1] is the result of the first pass - gl.glBindTexture(GL_TEXTURE_2D, m_textures[1]); - gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_size.width(), - m_size.height(), 0, GL_RGBA, GL_FLOAT, 0); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Used for colormap lookup in the frag shader // (2D texture for compatibility; used to be a 1D texture) + gl.glGenTextures(1, &m_colormapTex); gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex); - gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, COLORMAP_SAMPLES, 1, 0, GL_RGB, + gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Colormap::SAMPLES, 1, 0, GL_RGB, GL_FLOAT, 0); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -314,6 +288,23 @@ VoronoiSplatRenderer::~VoronoiSplatRenderer() delete m_program2; } +void VoronoiSplatRenderer::resizeTextures() +{ + // textures[0] stores the DT values for each pixel + gl.glBindTexture(GL_TEXTURE_2D, m_textures[0]); + gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_size.width(), + m_size.height(), 0, GL_RED, GL_FLOAT, 0); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // textures[1] is the result of the first pass + gl.glBindTexture(GL_TEXTURE_2D, m_textures[1]); + gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_size.width(), + m_size.height(), 0, GL_RGBA, GL_FLOAT, 0); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +} + QOpenGLFramebufferObject *VoronoiSplatRenderer::createFramebufferObject(const QSize &size) { int baseSize = nextPow2(std::min(size.width(), size.height())); @@ -339,6 +330,7 @@ void VoronoiSplatRenderer::render() int originalFBO; gl.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &originalFBO); + gl.glBindFramebuffer(GL_FRAMEBUFFER, m_FBO); // First pass @@ -370,6 +362,9 @@ void VoronoiSplatRenderer::render() m_program1->release(); + // For some reason this call makes the splat circle of the correct size + //m_window->resetOpenGLState(); + // Second pass m_program2->bind(); m_program2->setUniformValue("rad_max", 20.0f); @@ -408,6 +403,12 @@ void VoronoiSplatRenderer::synchronize(QQuickFramebufferObject *item) m_sitesChanged = splat->sitesChanged(); m_valuesChanged = splat->valuesChanged(); m_colormapChanged = splat->colormapChanged(); + + // Reset these so that by the next synchronize() we have the correct values + splat->setSitesChanged(false); + splat->setValuesChanged(false); + splat->setColormapChanged(false); + m_sites = &(splat->sites()); m_values = &(splat->values()); m_cmap = &(splat->colormap()); @@ -437,8 +438,11 @@ void VoronoiSplatRenderer::updateValues() void VoronoiSplatRenderer::updateColormap() { + gl.glActiveTexture(GL_TEXTURE0); gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex); - gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, COLORMAP_SAMPLES, 1, GL_RGB, + //gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, Colormap::SAMPLES, 1, GL_RGB, + // GL_FLOAT, m_cmap->data()); + gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Colormap::SAMPLES, 1, 0, GL_RGB, GL_FLOAT, m_cmap->data()); m_colormapChanged = false; @@ -452,21 +456,18 @@ void VoronoiSplatRenderer::computeDT() std::vector buf(w*h); const std::vector &sites = *m_sites; for (unsigned i = 0; i < sites.size(); i += 2) { - buf[int(sites[i + 1])*h + int(sites[i])] = (float) i/2 + 1; + buf[int(sites[i + 1])*h + int(sites[i])] = i/2.0f + 1.0f; } skelft2DFT(0, buf.data(), 0, 0, w, h, w); // Compute DT of the sites (from the resident FT) skelft2DDT(buf.data(), 0, 0, w, h); - std::vector dtTexData(2*w*h, 0.0f); - for (unsigned i = 0; i < buf.size(); i++) { - dtTexData[2*i] = buf[i]; - } - // Upload result to lookup texture + gl.glActiveTexture(GL_TEXTURE0); gl.glBindTexture(GL_TEXTURE_2D, m_textures[0]); - gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RG, GL_FLOAT, - dtTexData.data()); + //gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RED, GL_FLOAT, + // buf.data()); + gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_RED, GL_FLOAT, + buf.data()); } - diff --git a/voronoisplat.h b/voronoisplat.h index fb93f09..6061ef3 100644 --- a/voronoisplat.h +++ b/voronoisplat.h @@ -31,7 +31,7 @@ public: signals: void sitesChanged(const arma::mat &sites); void valuesChanged(const arma::vec &values); - void colormapChanged(const ColorScale *scale); + void colormapChanged(const ColorScale &scale); public slots: // 'points' should be a 2D points matrix (each point in a row) @@ -41,7 +41,7 @@ public slots: void setValues(const arma::vec &values); // Set colormap data based on the given color scale - void setColormap(const ColorScale *scale); + void setColormap(const ColorScale &scale); private: std::vector m_sites, m_values, m_cmap; -- cgit v1.2.3