From d205ebecf1b0d552bd5fe49ab7220cf1788878eb Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Fri, 23 May 2025 15:12:59 -0500 Subject: [PATCH] Fixing threshold to work over full range (0 to 1) --- src/effects/Sharpen.cpp | 119 ++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 48 deletions(-) diff --git a/src/effects/Sharpen.cpp b/src/effects/Sharpen.cpp index 6454a2c5..c886b397 100644 --- a/src/effects/Sharpen.cpp +++ b/src/effects/Sharpen.cpp @@ -80,52 +80,52 @@ static void blur_axis(const QImage& src, QImage& dst, int r, bool vertical) for (int y = 0; y < H; ++y) { const uchar* rowIn = in + y*bpl; uchar* rowOut = out + y*bpl; - double sumB = rowIn[0]*(r+1), sumG = rowIn[1]*(r+1), - sumR = rowIn[2]*(r+1), sumA = rowIn[3]*(r+1); + double sB = rowIn[0]*(r+1), sG = rowIn[1]*(r+1), + sR = rowIn[2]*(r+1), sA = rowIn[3]*(r+1); for (int x = 1; x <= r; ++x) { const uchar* p = rowIn + std::min(x, W-1)*4; - sumB += p[0]; sumG += p[1]; sumR += p[2]; sumA += p[3]; + sB += p[0]; sG += p[1]; sR += p[2]; sA += p[3]; } for (int x = 0; x < W; ++x) { uchar* o = rowOut + x*4; - o[0] = uchar(sumB / window + 0.5); - o[1] = uchar(sumG / window + 0.5); - o[2] = uchar(sumR / window + 0.5); - o[3] = uchar(sumA / window + 0.5); + o[0] = uchar(sB / window + 0.5); + o[1] = uchar(sG / window + 0.5); + o[2] = uchar(sR / window + 0.5); + o[3] = uchar(sA / window + 0.5); const uchar* addP = rowIn + std::min(x+r+1, W-1)*4; const uchar* subP = rowIn + std::max(x-r, 0)*4; - sumB += addP[0] - subP[0]; - sumG += addP[1] - subP[1]; - sumR += addP[2] - subP[2]; - sumA += addP[3] - subP[3]; + sB += addP[0] - subP[0]; + sG += addP[1] - subP[1]; + sR += addP[2] - subP[2]; + sA += addP[3] - subP[3]; } } } else { #pragma omp parallel for for (int x = 0; x < W; ++x) { - double sumB = 0, sumG = 0, sumR = 0, sumA = 0; + double sB = 0, sG = 0, sR = 0, sA = 0; const uchar* p0 = in + x*4; - sumB = p0[0]*(r+1); sumG = p0[1]*(r+1); - sumR = p0[2]*(r+1); sumA = p0[3]*(r+1); + sB = p0[0]*(r+1); sG = p0[1]*(r+1); + sR = p0[2]*(r+1); sA = p0[3]*(r+1); for (int y = 1; y <= r; ++y) { const uchar* p = in + std::min(y, H-1)*bpl + x*4; - sumB += p[0]; sumG += p[1]; sumR += p[2]; sumA += p[3]; + sB += p[0]; sG += p[1]; sR += p[2]; sA += p[3]; } for (int y = 0; y < H; ++y) { uchar* o = out + y*bpl + x*4; - o[0] = uchar(sumB / window + 0.5); - o[1] = uchar(sumG / window + 0.5); - o[2] = uchar(sumR / window + 0.5); - o[3] = uchar(sumA / window + 0.5); + o[0] = uchar(sB / window + 0.5); + o[1] = uchar(sG / window + 0.5); + o[2] = uchar(sR / window + 0.5); + o[3] = uchar(sA / window + 0.5); const uchar* addP = in + std::min(y+r+1, H-1)*bpl + x*4; const uchar* subP = in + std::max(y-r, 0)*bpl + x*4; - sumB += addP[0] - subP[0]; - sumG += addP[1] - subP[1]; - sumR += addP[2] - subP[2]; - sumA += addP[3] - subP[3]; + sB += addP[0] - subP[0]; + sG += addP[1] - subP[1]; + sR += addP[2] - subP[2]; + sA += addP[3] - subP[3]; } } } @@ -209,11 +209,30 @@ std::shared_ptr Sharpen::GetFrame( QImage blur(W, H, QImage::Format_ARGB32); gauss_blur(*img, blur, sigma); + // Precompute maximum luma difference for adaptive threshold int bplS = img->bytesPerLine(); int bplB = blur.bytesPerLine(); uchar* sBits = img->bits(); uchar* bBits = blur.bits(); + double maxDY = 0.0; + #pragma omp parallel for reduction(max:maxDY) + for (int y = 0; y < H; ++y) { + uchar* sRow = sBits + y * bplS; + uchar* bRow = bBits + y * bplB; + for (int x = 0; x < W; ++x) { + double dB = double(sRow[x*4+0]) - double(bRow[x*4+0]); + double dG = double(sRow[x*4+1]) - double(bRow[x*4+1]); + double dR = double(sRow[x*4+2]) - double(bRow[x*4+2]); + double dY = std::abs(0.114*dB + 0.587*dG + 0.299*dR); + maxDY = std::max(maxDY, dY); + } + } + + // Compute actual threshold in luma units + double thr = thrUI * maxDY; + + // Process pixels #pragma omp parallel for for (int y = 0; y < H; ++y) { uchar* sRow = sBits + y * bplS; @@ -222,45 +241,47 @@ std::shared_ptr Sharpen::GetFrame( uchar* sp = sRow + x*4; uchar* bp = bRow + x*4; - // Compute detail - double dB = double(sp[0]) - double(bp[0]); - double dG = double(sp[1]) - double(bp[1]); - double dR = double(sp[2]) - double(bp[2]); - double dY = 0.114*dB + 0.587*dG + 0.299*dR; - double dYn = std::abs(dY) / 255.0; + // Detail per channel + double dB = double(sp[0]) - double(bp[0]); + double dG = double(sp[1]) - double(bp[1]); + double dR = double(sp[2]) - double(bp[2]); + double dY = 0.114*dB + 0.587*dG + 0.299*dR; - // Skip below threshold - if (dYn < thrUI) + // Skip if below adaptive threshold + if (std::abs(dY) < thr) continue; - // Halo limiter for contrast - auto halo = [](double d){ return (255.0 - std::abs(d)) / 255.0; }; + // Halo limiter + auto halo = [](double d) { + return (255.0 - std::abs(d)) / 255.0; + }; double outC[3]; if (mode == 1) { - // High-Pass Blend: base = blurred + amt * detail + // High-Pass blend: base = blurred + amt * detail (no halo) if (channel == 1) { // Luma only - double inc = amt * dY * halo(dY); + double inc = amt * dY; for (int c = 0; c < 3; ++c) outC[c] = bp[c] + inc; } else if (channel == 2) { // Chroma only - double chroma[3] = { dB - dY, dG - dY, dR - dY }; + double l = dY; + double chroma[3] = { dB - l, dG - l, dR - l }; for (int c = 0; c < 3; ++c) - outC[c] = bp[c] + amt * chroma[c] * halo(chroma[c]); + outC[c] = bp[c] + amt * chroma[c]; } else { // All channels - double diff[3] = { dB, dG, dR }; - for (int c = 0; c < 3; ++c) - outC[c] = bp[c] + amt * diff[c] * halo(diff[c]); + outC[0] = bp[0] + amt * dB; + outC[1] = bp[1] + amt * dG; + outC[2] = bp[2] + amt * dR; } } else { - // Unsharp-Mask: base = original + amt * detail + // Unsharp-Mask: base = original + amt * detail * halo(detail) if (channel == 1) { // Luma only double inc = amt * dY * halo(dY); @@ -269,21 +290,23 @@ std::shared_ptr Sharpen::GetFrame( } else if (channel == 2) { // Chroma only - double chroma[3] = { dB - dY, dG - dY, dR - dY }; + double l = dY; + double chroma[3] = { dB - l, dG - l, dR - l }; for (int c = 0; c < 3; ++c) outC[c] = sp[c] + amt * chroma[c] * halo(chroma[c]); } else { // All channels - double diff[3] = { dB, dG, dR }; - for (int c = 0; c < 3; ++c) - outC[c] = sp[c] + amt * diff[c] * halo(diff[c]); + outC[0] = sp[0] + amt * dB * halo(dB); + outC[1] = sp[1] + amt * dG * halo(dG); + outC[2] = sp[2] + amt * dR * halo(dR); } } // Write back clamped - for (int c = 0; c < 3; ++c) + for (int c = 0; c < 3; ++c) { sp[c] = uchar(std::clamp(outC[c], 0.0, 255.0) + 0.5); + } } } @@ -325,12 +348,12 @@ void Sharpen::SetJsonValue(Json::Value root) if (!root["threshold"].isNull()) threshold.SetJsonValue(root["threshold"]); if (!root["mode"].isNull()) - mode = root["mode"].asInt(); + mode = root["mode"].asInt(); if (!root["channel"].isNull()) channel = root["channel"].asInt(); } -// Properties for UI sliders +// UI property definitions std::string Sharpen::PropertiesJSON(int64_t t) const { Json::Value root = BasePropertiesJSON(t);