diff --git a/loadrunable.cpp b/loadrunable.cpp index 9978c4f..5d9c449 100644 --- a/loadrunable.cpp +++ b/loadrunable.cpp @@ -191,11 +191,17 @@ void ConvertRunable::run() QFileInfo info(m_outfile); info.dir().mkpath("."); + if(m_params.autostretch) + { + rawimage->calcStats(); + MTFParam mtfParam = rawimage->calcMTFParams(); + rawimage->applySTF(mtfParam); + } if(m_params.binning > 1) { rawimage->resizeInt(m_params.binning, m_params.average); } - else if(m_params.resize.isValid() && !m_params.resize.isEmpty()) + if(m_params.resize.isValid() && !m_params.resize.isEmpty()) { QSize imgSize(rawimage->width(), rawimage->height()); imgSize = imgSize.scaled(m_params.resize, m_params.aspect); @@ -363,4 +369,6 @@ ConvertRunable::ConvertParams::ConvertParams(const QVariantMap &map) aspect = Qt::IgnoreAspectRatio; } + if(map.contains("autostretch")) + autostretch = map["autostretch"].toBool(); } diff --git a/loadrunable.h b/loadrunable.h index 9f11439..cf83902 100644 --- a/loadrunable.h +++ b/loadrunable.h @@ -31,6 +31,7 @@ public: bool average = true; QSize resize; Qt::AspectRatioMode aspect = Qt::KeepAspectRatio; + bool autostretch = false; ConvertParams(){} ConvertParams(const QVariantMap &map); }; diff --git a/rawimage.cpp b/rawimage.cpp index 4620c44..7d4e954 100644 --- a/rawimage.cpp +++ b/rawimage.cpp @@ -759,7 +759,7 @@ void integerResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_ { const T *in = reinterpret_cast(in_); T *out = reinterpret_cast(out_); - uint32_t down2 = down * down; + const uint32_t down2 = down * down; U m = std::numeric_limits::max(); if constexpr(std::is_floating_point_v)m = down2; @@ -1104,6 +1104,112 @@ void RawImage::generateLUT() cmsCloseProfile(outProfile); } +void RawImage::applySTF(const MTFParam &mtfParams) +{ + auto applyMTF = [&](auto *src) -> void + { + float s = 1.0f; + if constexpr(std::numeric_limits>::is_integer) + s = (float)std::numeric_limits>::max(); + + float iscale = 1.0f / s; + size_t len = size() * m_ch; + for(size_t i = 0; i < len; i++) + { + float x = src[i] * iscale; + x = (x - mtfParams.blackPoint[0]) / (mtfParams.whitePoint[0] - mtfParams.blackPoint[0]); + x = std::clamp(x, 0.0f, 1.0f); + x = ((mtfParams.midPoint[0] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[0] - 1.0f) * x - mtfParams.midPoint[0]); + src[i] = x * s; + } + }; + + switch(m_type) + { + case UINT8: + applyMTF(reinterpret_cast(m_pixels.get())); + break; + case UINT16: + applyMTF(reinterpret_cast(m_pixels.get())); + break; + case UINT32: + applyMTF(reinterpret_cast(m_pixels.get())); + break; + case FLOAT32: + applyMTF(reinterpret_cast(m_pixels.get())); + break; + case FLOAT64: + applyMTF(reinterpret_cast(m_pixels.get())); + break; + default: + break; + } +} + +MTFParam RawImage::calcMTFParams(bool linked, bool debayer) const +{ + const float BLACK_POINT_SIGMA = -2.8f; + const float MAD_TO_SIGMA = 1.4826f; + const float TARGET_BACKGROUND = 0.25f; + + auto MTF = [](float x, float m) + { + if(x < 0)return 0.0f; + if(x > 1)return 1.0f; + return ((m - 1) * x) / ((2 * m - 1) * x - m); + }; + + MTFParam mtfParam; + + int i = 0; + int ch = m_channels; + int o = 0; + if(debayer) + { + i = 1; + ch = 4; + o = 1; + } + + float bp2 = 0; + float mid2 = 0; + float max2 = 0; + for(; i < ch; i++) + { + double median, mad, max; + median = m_stats.m_median[i]; + mad = m_stats.m_mad[i]; + median /= norm(); + bool a = median > 0.5 ? true : false; + mad /= norm(); + max = 1.0f; + float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0); + if(a && mad != 0.0f) + max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0); + + float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median); + mtfParam.blackPoint[i-o] = bp; + mtfParam.midPoint[i-o] = mid; + mtfParam.whitePoint[i-o] = max; + bp2 += bp; + mid2 += mid; + max2 = max > max2 ? max : max2; + } + if(ch == 1) + { + mtfParam.blackPoint[1] = mtfParam.blackPoint[2] = mtfParam.blackPoint[0]; + mtfParam.midPoint[1] = mtfParam.midPoint[2] = mtfParam.midPoint[0]; + mtfParam.whitePoint[1] = mtfParam.whitePoint[2] = mtfParam.whitePoint[0]; + } + if(linked) + { + mtfParam.blackPoint[0] = mtfParam.blackPoint[1] = mtfParam.blackPoint[2] = bp2 / ch; + mtfParam.midPoint[0] = mtfParam.midPoint[1] = mtfParam.midPoint[2] = mid2 / ch; + mtfParam.whitePoint[0] = mtfParam.whitePoint[1] = mtfParam.whitePoint[2] = max2; + } + return mtfParam; +} + const std::vector &RawImage::getLUT() const { return m_lut; diff --git a/rawimage.h b/rawimage.h index ed72e8f..a03bcf8 100644 --- a/rawimage.h +++ b/rawimage.h @@ -10,6 +10,7 @@ #ifndef NO_QT #include #endif +#include "mtfparam.h" extern int THUMB_SIZE; extern int THUMB_SIZE_BORDER; @@ -127,6 +128,8 @@ public: void setICCProfile(const LibXISF::ByteArray &icc); void convertTosRGB(); void generateLUT(); + void applySTF(const MTFParam &mtfParams); + MTFParam calcMTFParams(bool linked = false, bool debayer = false) const; const std::vector& getLUT() const; }; diff --git a/stretchtoolbar.cpp b/stretchtoolbar.cpp index 336e894..3968549 100644 --- a/stretchtoolbar.cpp +++ b/stretchtoolbar.cpp @@ -107,54 +107,11 @@ void StretchToolbar::stretchImage(Image *img) { if(img && img->rawImage()) { - const RawImage::Stats &stats = img->rawImage()->imageStats(); - int i = 0; - int ch = 1; - int o = 0; - if(m_stack->currentIndex() == 1 && img->rawImage()->channels() == 1 && m_debayer->isChecked()) - { - i = 1; - ch = 4; - o = 1; - } - if(img->rawImage()->channels() >= 3) - ch = 3; + m_mtfParam = img->rawImage()->calcMTFParams(m_stack->currentIndex() == 0, + m_stack->currentIndex() == 1 && img->rawImage()->channels() == 1 && m_debayer->isChecked()); - float bp2 = 0; - float mid2 = 0; - float max2 = 0; - for(; i < ch; i++) - { - double median, mad, max; - median = stats.m_median[i]; - mad = stats.m_mad[i]; - max = stats.m_max[i]; - median /= img->rawImage()->norm(); - bool a = median > 0.5 ? true : false; - mad /= img->rawImage()->norm(); - max = 1.0f;// /= img->rawImage()->norm(); - float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0); - if(a && mad != 0.0f) - max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0); - float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median); - m_mtfParam.blackPoint[i-o] = bp; - m_mtfParam.midPoint[i-o] = mid;// / max; - m_mtfParam.whitePoint[i-o] = max; - bp2 += bp; - mid2 += mid; - max2 = max > max2 ? max : max2; - } - if(ch == 1) - { - m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = m_mtfParam.blackPoint[0]; - m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = m_mtfParam.midPoint[0]; - m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = m_mtfParam.whitePoint[0]; - } if(m_stack->currentIndex() == 0) { - m_mtfParam.blackPoint[0] = m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = bp2 / ch; - m_mtfParam.midPoint[0] = m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = mid2 / ch; - m_mtfParam.whitePoint[0] = m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = max2; m_stfSlider->setMTFParams(m_mtfParam.blackPoint[0], m_mtfParam.midPoint[0], m_mtfParam.whitePoint[0]); } else