From 2ff1b993a1f04dd864172f1b5ade06dda274b4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Poizl?= Date: Wed, 6 Apr 2022 13:12:47 +0200 Subject: [PATCH] Rework stretch to use just MTF --- image.frag | 39 +++++--------------- imagescrollareagl.cpp | 33 ++++------------- imagescrollareagl.h | 8 +--- mainwindow.cpp | 5 +-- stfslider.cpp | 35 +++++++++++++++++- stfslider.h | 6 +++ stretchpanel.cpp | 86 +++++++++++++++---------------------------- stretchpanel.h | 16 ++------ 8 files changed, 93 insertions(+), 135 deletions(-) diff --git a/image.frag b/image.frag index 6e4a59f..19b433a 100644 --- a/image.frag +++ b/image.frag @@ -2,43 +2,22 @@ uniform sampler2D qt_Texture0; varying vec2 qt_TexCoord0; -uniform vec2 scale; -uniform float a; -uniform int stretch; +uniform vec3 mtf_param; uniform bool bw; +vec4 MTF(vec4 x, vec3 m) +{ + x = (x - m.x) / (m.z - m.x); + x = clamp(x, vec4(0.0), vec4(1.0)); + return ((m.y - 1) * x) / ((2 * m.y - 1) * x - m.y); +} + void main(void) { vec4 color = texture2D(qt_Texture0, qt_TexCoord0); if(bw)color = color.rrra; - color = color*scale.x + scale.y; - color = max(color, vec4(0.0f)); + color = MTF(color, mtf_param); - switch(stretch) - { - case 0: - break; - case 1: - color = sqrt(color); - break; - case 2: - color = pow(color, vec4(a)); - break; - case 3: - color = log(a*color + 1.0) / log(vec4(a+1)); - break; - case 4: - { - //float l = color.r*0.2126f + color.g*0.7152f + color.b*0.0722f; - float l = (color.r+color.g+color.b)*0.33333; - float k = asinh(l*a)/(l*asinh(a)); - color *= k; - } - break; - } - - //color = color*scale.x + scale.y; - //float color = pow(c, 0.4545); if(any(lessThan(qt_TexCoord0, vec2(0.0))) || any(greaterThan(qt_TexCoord0, vec2(1.0)))) color = vec4(0.0); gl_FragColor = color; diff --git a/imagescrollareagl.cpp b/imagescrollareagl.cpp index 238760c..68f71e6 100644 --- a/imagescrollareagl.cpp +++ b/imagescrollareagl.cpp @@ -41,13 +41,13 @@ ImageWidget::ImageWidget(QWidget *parent) : QOpenGLWidget(parent) setFocusPolicy(Qt::ClickFocus); m_range = UINT16_MAX; m_low = 0; + m_mid = 0.5; m_high = 1; m_dx = m_dy = 0; m_scale = 1.0f; m_blockRepaint = false; m_range = UINT16_MAX; m_imgWidth = m_imgHeight = -1; - m_stretch = 0; } ImageWidget::~ImageWidget() @@ -67,6 +67,7 @@ void ImageWidget::setImage(RawImage *image) m_image->destroy(); m_image->setFormat(rawImageType.textureFormat); m_image->setSize(image->width(), image->height()); + m_image->setMipLevels([&](){ int c = 0; int s = std::min(m_imgWidth, m_imgHeight); while(s>>=1)c++; return c; }()); m_image->allocateStorage(); m_image->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear); m_image->setWrapMode(QOpenGLTexture::ClampToEdge); @@ -105,28 +106,11 @@ void ImageWidget::blockRepaint(bool block) if(!block)update(); } -void ImageWidget::setLow(int low) +void ImageWidget::setMTFParams(float low, float mid, float high) { - m_low = low/m_range; - update(); -} - -void ImageWidget::setHigh(int high) -{ - m_high = high/m_range; - update(); -} - -void ImageWidget::setStrech(int stretch) -{ - m_stretch = stretch; - update(); -} - -void ImageWidget::setStretchParam(float param) -{ - qDebug() << param; - m_param = param; + m_low = low; + m_mid = mid; + m_high = high; update(); } @@ -169,13 +153,10 @@ void ImageWidget::paintGL() if(height() > m_image->height()*m_scale) dy = -height()*0.5f + m_image->height()*m_scale*0.5f; - float s = 1.0f/(m_high-m_low); m_program->bind(); - m_program->setUniformValue("scale", s, -m_low*s); m_program->setUniformValue("viewport", (float)width(), (float)height()); m_program->setUniformValue("offset", dx, dy); - m_program->setUniformValue("stretch", m_stretch); - m_program->setUniformValue("a", m_param); + m_program->setUniformValue("mtf_param", m_low, m_mid, m_high); m_program->setUniformValue("zoom", 1.0f/m_scale); m_program->setUniformValue("bw", m_bwImg); diff --git a/imagescrollareagl.h b/imagescrollareagl.h index 61fd69d..c8f774d 100644 --- a/imagescrollareagl.h +++ b/imagescrollareagl.h @@ -33,12 +33,11 @@ class ImageWidget : public QOpenGLWidget int m_width, m_height; int m_imgWidth, m_imgHeight; float m_low; + float m_mid; float m_high; - int m_stretch; float m_range; float m_dx, m_dy; float m_scale; - float m_param; bool m_blockRepaint; bool m_bwImg; public: @@ -49,10 +48,7 @@ public: void setScale(float scale); void blockRepaint(bool block); public slots: - void setLow(int low); - void setHigh(int high); - void setStrech(int stretch); - void setStretchParam(float param); + void setMTFParams(float low, float mid, float high); void setOffset(int dx, int dy); QImage renderToImage(); protected: diff --git a/mainwindow.cpp b/mainwindow.cpp index 1218e67..6399212 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -43,10 +43,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) setCentralWidget(m_imageGL); StretchPanel *stretchPanel = new StretchPanel(this); - connect(stretchPanel, SIGNAL(lowChanged(int)), m_imageGL->imageWidget(), SLOT(setLow(int))); - connect(stretchPanel, SIGNAL(highChanged(int)), m_imageGL->imageWidget(), SLOT(setHigh(int))); - connect(stretchPanel, SIGNAL(stretchChanged(int)), m_imageGL->imageWidget(), SLOT(setStrech(int))); - connect(stretchPanel, SIGNAL(paramChanged(float)), m_imageGL->imageWidget(), SLOT(setStretchParam(float))); + connect(stretchPanel, SIGNAL(paramChanged(float,float,float)), m_imageGL->imageWidget(), SLOT(setMTFParams(float,float,float))); m_ringList = new ImageRingList(this); m_filesystem = new FilesystemWidget(m_ringList, this); diff --git a/stfslider.cpp b/stfslider.cpp index b797d98..59bbe30 100644 --- a/stfslider.cpp +++ b/stfslider.cpp @@ -3,9 +3,15 @@ #include #include +static float clamp(float x) +{ + return std::min(std::max(x, 0.0f), 1.0f); +} + STFSlider::STFSlider(QWidget *parent) : QWidget(parent) { setMinimumHeight(15); + setMaximumHeight(15); setMouseTracking(true); m_blackPoint = 0; m_midPoint = 0.5; @@ -13,6 +19,29 @@ STFSlider::STFSlider(QWidget *parent) : QWidget(parent) m_grabbed = -1; } +float STFSlider::blackPoint() const +{ + return m_blackPoint; +} + +float STFSlider::midPoint() const +{ + return m_midPoint; +} + +float STFSlider::whitePoint() const +{ + return m_whitePoint; +} + +void STFSlider::setMTFParams(float low, float mid, float high) +{ + m_blackPoint = clamp(low); + m_midPoint = clamp(mid); + m_whitePoint = clamp(high); + update(); +} + void STFSlider::paintEvent(QPaintEvent *event) { QPainter painter(this); @@ -77,7 +106,10 @@ void STFSlider::mouseMoveEvent(QMouseEvent *event) break; } if(m_grabbed >= 0) - repaint(); + { + emit paramChanged(m_blackPoint, midPoint(), m_whitePoint); + update(); + } } void STFSlider::mousePressEvent(QMouseEvent *event) @@ -95,4 +127,5 @@ void STFSlider::mousePressEvent(QMouseEvent *event) void STFSlider::mouseReleaseEvent(QMouseEvent *) { m_grabbed = -1; + emit paramChanged(m_blackPoint, midPoint(), m_whitePoint); } diff --git a/stfslider.h b/stfslider.h index a1519e9..29746a7 100644 --- a/stfslider.h +++ b/stfslider.h @@ -13,6 +13,12 @@ class STFSlider : public QWidget int m_grabbed; public: explicit STFSlider(QWidget *parent = nullptr); + float blackPoint() const; + float midPoint() const; + float whitePoint() const; + void setMTFParams(float low, float mid, float high); +signals: + void paramChanged(float blackPoint, float midPoint, float whitePoint); protected: void paintEvent(QPaintEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; diff --git a/stretchpanel.cpp b/stretchpanel.cpp index 6fbfe15..83c6142 100644 --- a/stretchpanel.cpp +++ b/stretchpanel.cpp @@ -1,42 +1,33 @@ #include "stretchpanel.h" #include #include +#include #include "imageringlist.h" -#include "stfslider.h" + +const float BLACK_POINT_SIGMA = -2.8f; +const float MAD_TO_SIGMA = 1.4826f; +const float TARGET_BACKGROUND = 0.25f; + +float MTF(float x, float m) +{ + if(x < 0)return 0; + if(x > 1)return 1; + return ((m - 1) * x) / ((2 * m - 1) * x - m); +} StretchPanel::StretchPanel(QWidget *parent) : QWidget(parent) { - QVBoxLayout *layout = new QVBoxLayout(this); + QHBoxLayout *layout = new QHBoxLayout(this); setLayout(layout); - m_lowSlider = new QSlider(Qt::Horizontal, this); - m_highSlider = new QSlider(Qt::Horizontal, this); - m_paramSlider = new QSlider(Qt::Horizontal, this); + m_stfSlider = new STFSlider(this); + layout->addWidget(m_stfSlider); + connect(m_stfSlider, SIGNAL(paramChanged(float, float, float)), this, SIGNAL(paramChanged(float,float,float))); - STFSlider *stfslider = new STFSlider(this); - - m_lowSlider->setRange(0, UINT16_MAX); - m_lowSlider->setPageStep(512); - m_highSlider->setRange(0, UINT16_MAX); - m_highSlider->setPageStep(512); - m_highSlider->setValue(UINT16_MAX); - m_paramSlider->setRange(0, UINT16_MAX); - m_paramSlider->setPageStep(512); - - m_stretchSelect = new QComboBox(this); - m_stretchSelect->addItems({tr("Linear"), tr("Square root"), tr("Power"), tr("Logarithm"), tr("Asinh")}); - - layout->addWidget(m_lowSlider); - layout->addWidget(m_highSlider); - layout->addWidget(m_paramSlider); - layout->addWidget(m_stretchSelect); - layout->addWidget(stfslider); - - connect(m_lowSlider, SIGNAL(valueChanged(int)), this, SIGNAL(lowChanged(int))); - connect(m_highSlider, SIGNAL(valueChanged(int)), this, SIGNAL(highChanged(int))); - connect(m_paramSlider, SIGNAL(valueChanged(int)), this, SLOT(calculateParam())); - connect(m_stretchSelect, SIGNAL(activated(int)), this, SIGNAL(stretchChanged(int))); - connect(m_stretchSelect, SIGNAL(activated(int)), this, SLOT(calculateParam())); + QPushButton *resetButton = new QPushButton(tr("Reset STF"), this); + resetButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + layout->addWidget(resetButton); + connect(resetButton, SIGNAL(pressed()), this, SLOT(resetMTF())); } void StretchPanel::imageLoaded(Image *img) @@ -47,36 +38,19 @@ void StretchPanel::imageLoaded(Image *img) { double mean, stdDev, median; img->rawImage()->imageStats(&mean, &stdDev, &median, nullptr, nullptr); - double mad = img->rawImage()->MAD(); - float l = median - mad; - m_lowSlider->setValue(l); - float p = std::log(0.25)/std::log(mean/UINT16_MAX); - m_paramSlider->setValue(p * UINT16_MAX); - qDebug() << "Low" << l << p; + median /= 65536; + double mad = img->rawImage()->MAD() / 65536; + float bp = median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA; + float mid = MTF(median - bp, TARGET_BACKGROUND); + m_stfSlider->setMTFParams(bp, mid, 1.0f); + emit paramChanged(m_stfSlider->blackPoint(), m_stfSlider->midPoint(), 1.0f); } } } -void StretchPanel::calculateParam() +void StretchPanel::resetMTF() { - float val = m_paramSlider->value(); - float param; - switch(m_stretchSelect->currentIndex()) - { - case 2: - param = val/UINT16_MAX; - param = 1.0f / (param * 5.0f + 1); - break; - case 3: - param = val; - break; - case 4: - param = 1.0f/std::max(0.00001f, 1.0f-(val/UINT16_MAX)); - //val += 100; - //param = val/100.0f; - break; - default: - return; - } - emit paramChanged(param); + m_stfSlider->setMTFParams(0, 0.5, 1); + emit paramChanged(0, 0.5, 1); } + diff --git a/stretchpanel.h b/stretchpanel.h index 09b0b88..793be62 100644 --- a/stretchpanel.h +++ b/stretchpanel.h @@ -2,29 +2,21 @@ #define STRETCHPANEL_H #include -#include -#include +#include "stfslider.h" class Image; class StretchPanel : public QWidget { Q_OBJECT - QSlider *m_lowSlider; - QSlider *m_highSlider; - QSlider *m_paramSlider; - QComboBox *m_stretchSelect; + STFSlider *m_stfSlider; public: explicit StretchPanel(QWidget *parent = nullptr); public slots: void imageLoaded(Image *img); + void resetMTF(); signals: - void lowChanged(int low); - void highChanged(int high); - void stretchChanged(int stretch); - void paramChanged(float param); -private slots: - void calculateParam(); + void paramChanged(float low, float mid, float high); }; #endif // STRETCHPANEL_H