Rawtherapee: RT crashes with HistogramMatching=true and ARW file

Created on 26 Mar 2019  路  22Comments  路  Source: Beep6581/RawTherapee

A Fedora user reported a crash on opening an ARW image. I could reproduce it and I discovered that this is due to HistogramMatching=true in the [Exposure] section, by setting it to false I can open the file without getting the crash.

The original ARW file and backtrace of the crash are on https://bugzilla.redhat.com/show_bug.cgi?id=1692572

patch provided bug

All 22 comments

Opening the file with HistogramMatching=true works fine on current dev branch on Win7

@mattiaverga What RT version was that? I don't see that from the link you provided, which makes it hard to reproduce and also wastes dev time......

Edit: rawtherapee-5.5-1.fc29 is not a version number used by us...

@heckflosse sorry, I forgot to write that, it's RT 5.5 from http://rawtherapee.com/shared/source/ with only the e491c42 patch applied.

At first I couldn't reproduce the crash, I had to delete my ~/.config/Rawtherapee folder to let RT resetting to its defaults and I also deleted the .pp3 file to let RT recreate it following its defaults. I'll attach it here.
DSC09189.ARW.pp3.zip

With this .pp3 file I get the crash every time I try to open the file from command line or from RT browser window (the preview in the browser works fine). If I change HistogramMatching to false the image is opened fine.

I don't know exactly what HistogramMatching does, but I suspect that particular function screws up with a completely black frame like this. I get no crash with HistogramMatching=true applied to other ARW samples downloaded from internet.

I'm trying the reproduce...

@mattiaverga @heckflosse I can't reproduce it with stock 5.5 on Debian (Fedora seems to utilize hardening flags), but I can get an abort if I replace mapping[...] with mapping.at(...). Digging deeper...

Could not reproduce in 5.5 nor in 5.5-263-g90e1480b7 in Sabayon (GCC7.3.0, deleted config and cache).

Here's a fix for dev:

diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc
index f91086285..b7a3a9666 100644
--- a/rtengine/histmatching.cc
+++ b/rtengine/histmatching.cc
@@ -122,7 +122,11 @@ void mappingToCurve(const std::vector<int> &mapping, std::vector<double> &curve)
         {
             if (!maxdelta) maxdelta = step * 2;
             int prev = start;
-            if (addstart && mapping[start] >= 0) {
+            if (
+                addstart
+                && start < static_cast<int>(mapping.size())
+                && mapping[start] >= 0
+            ) {
                 curve.push_back(coord(start));
                 curve.push_back(coord(mapping[start]));
             }

Preparing a full cleanup patch...

Here's the cleanup patch for mappingToCurve():

diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc
index f91086285..750bdc936 100644
--- a/rtengine/histmatching.cc
+++ b/rtengine/histmatching.cc
@@ -18,19 +18,21 @@
  *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
  */

-#include "rawimagesource.h"
-#include "rtthumbnail.h"
-#include "curves.h"
+#include <iostream>
+
 #include "color.h"
-#include "rt_math.h"
+#include "curves.h"
 #include "iccstore.h"
+#include "improcfun.h"
 #include "procparams.h"
+#include "rawimagesource.h"
+#include "rt_math.h"
+#include "rtthumbnail.h"
+
 #include "../rtgui/mydiagonalcurve.h"
-#include "improcfun.h"
+
 //#define BENCHMARK
 #include "StopWatch.h"
-#include <iostream>
-

 namespace rtengine {

@@ -98,42 +100,72 @@ int findMatch(int val, const std::vector<int> &cdf, int j)
 }


-void mappingToCurve(const std::vector<int> &mapping, std::vector<double> &curve)
+void mappingToCurve(const std::vector<int>& mapping, std::vector<double>& curve)
 {
     curve.clear();

-    int idx = 15;
-    for (; idx < int(mapping.size()); ++idx) {
-        if (mapping[idx] >= idx) {
+    std::size_t idx = 15;
+    for (; idx < mapping.size(); ++idx) {
+        if (mapping[idx] >= static_cast<int>(idx)) {
             break;
         }
     }
-    if (idx == int(mapping.size())) {
-        for (idx = 1; idx < int(mapping.size()); ++idx) {
-            if (mapping[idx] >= idx) {
+
+    if (idx == mapping.size()) {
+        for (idx = 1; idx < mapping.size(); ++idx) {
+            if (mapping[idx] >= static_cast<int>(idx)) {
                 break;
             }
         }
     }

-    auto coord = [](int v) -> double { return double(v)/255.0; };
-    auto doit =
-        [&](int start, int stop, int step, bool addstart, int maxdelta=0) -> void
+    const auto coord =
+        [](int v) -> double
+        {
+            return static_cast<double>(v) / 255.0;
+        };
+
+    const auto doit =
+        [&mapping, &curve, &coord](
+            std::size_t start,
+            std::size_t stop,
+            std::size_t step,
+            bool addstart,
+            std::size_t maxdelta = 0
+        )
         {
-            if (!maxdelta) maxdelta = step * 2;
-            int prev = start;
-            if (addstart && mapping[start] >= 0) {
+            if (!maxdelta) {
+                maxdelta = step * 2;
+            }
+
+            std::size_t prev = start;
+
+            if (
+                addstart
+                && start < mapping.size()
+                && mapping[start] >= 0
+            ) {
                 curve.push_back(coord(start));
                 curve.push_back(coord(mapping[start]));
             }
-            for (int i = start; i < stop; ++i) {
-                int v = mapping[i];
+
+            for (std::size_t i = start; i < stop; ++i) {
+                const int v = mapping[i];
+
                 if (v < 0) {
                     continue;
                 }
-                bool change = i > 0 && v != mapping[i-1];
-                int diff = i - prev;
-                if ((change && std::abs(diff - step) <= 1) || diff > maxdelta) {
+
+                const std::size_t diff = i - prev;
+
+                if (
+                    diff > maxdelta
+                    || (
+                        i > 0
+                        && v != mapping[i - 1]
+                        && std::abs(static_cast<int>(diff) - static_cast<int>(step)) <= 1
+                    )
+                ) {
                     curve.push_back(coord(i));
                     curve.push_back(coord(v));
                     prev = i;
@@ -144,24 +176,26 @@ void mappingToCurve(const std::vector<int> &mapping, std::vector<double> &curve)
     curve.push_back(0.0);
     curve.push_back(0.0);

-    int start = 0;
+    std::size_t start = 0;
     while (start < idx && (mapping[start] < 0 || start < idx / 2)) {
         ++start;
     }

-    const int npoints = 8;
-    int step = std::max(int(mapping.size())/npoints, 1);
-    int end = mapping.size();
+    constexpr std::size_t npoints = 8;
+
+    std::size_t step = std::max<std::size_t>(mapping.size() / npoints, 1);
+    std::size_t end = mapping.size();
+
     if (idx <= end / 3) {
         doit(start, idx, idx / 2, true);
         step = (end - idx) / 4;
         doit(idx, end, step, false, step);
     } else {
         doit(start, idx, idx > step ? step : idx / 2, true);
-        doit(idx, end, step, idx - step > step / 2 && std::abs(curve[curve.size()-2] - coord(idx)) > 0.01);
+        doit(idx, end, step, idx - step > step / 2 && std::abs(curve[curve.size() - 2] - coord(idx)) > 0.01);
     }

-    if (curve.size() > 2 && (1 - curve[curve.size()-2] <= coord(step) / 3)) {
+    if (curve.size() > 2 && 1 - curve[curve.size() - 2] <= coord(step) / 3) {
         curve.pop_back();
         curve.pop_back();
     }
@@ -178,29 +212,34 @@ void mappingToCurve(const std::vector<int> &mapping, std::vector<double> &curve)
             // (x - xa) / (xb - xa) = (y - ya) / (yb - ya)
             return (x - xa) / (xb - xa) * (yb - ya) + ya;
         };
-    idx = -1;
-    for (size_t i = curve.size()-1; i > 0; i -= 2) {
-        if (curve[i] <= 0.f) {
-            idx = i+1;
+
+    bool has_idx = false;
+    idx = 0;
+
+    for (ssize_t i = curve.size() - 1; i > 0; i -= 2) {
+        if (curve[i] <= 0.0) {
+            has_idx = true;
+            idx = i + 1;
             break;
         }
     }
-    if (idx >= 0 && size_t(idx) < curve.size()) {
+
+    if (has_idx && idx < curve.size()) {
         // idx is the position of the first point in the upper part of the S
         // for each 3 consecutive points (xa, ya), (x, y), (xb, yb) we check
         // that y is above the point at x of the line between the other two
         // if this is not the case, we remove (x, y) from the curve
-        while (size_t(idx+5) < curve.size()) {
-            float xa = curve[idx];
-            float ya = curve[idx+1];
-            float x = curve[idx+2];
-            float y = curve[idx+3];
-            float xb = curve[idx+4];
-            float yb = curve[idx+5];
-            float yy = getpos(x, xa, ya, xb, yb);
+        while (idx + 5 < curve.size()) {
+            const float xa = curve[idx];
+            const float ya = curve[idx + 1];
+            const float x = curve[idx + 2];
+            const float y = curve[idx + 3];
+            const float xb = curve[idx + 4];
+            const float yb = curve[idx + 5];
+            const float yy = getpos(x, xa, ya, xb, yb);
             if (yy > y) {
                 // we have to remove (x, y) from the curve
-                curve.erase(curve.begin()+(idx+2), curve.begin()+(idx+4));
+                curve.erase(curve.begin() + (idx + 2), curve.begin() + (idx + 4));
             } else {
                 // move on to the next point
                 idx += 2;
@@ -209,19 +248,23 @@ void mappingToCurve(const std::vector<int> &mapping, std::vector<double> &curve)
     }

     if (curve.size() < 4) {
-        curve = { DCT_Linear }; // not enough points, fall back to linear
+        curve = {DCT_Linear}; // not enough points, fall back to linear
     } else {
         curve.insert(curve.begin(), DCT_Spline);
-        DiagonalCurve c(curve);
+        const DiagonalCurve c(curve);
+
+        curve = {DCT_CatumullRom};
+
         double gap = 0.05;
         double x = 0.0;
-        curve = { DCT_CatumullRom };
+
         while (x < 1.0) {
             curve.push_back(x);
             curve.push_back(c.getVal(x));
             x += gap;
             gap *= 1.4;
         }
+
         curve.push_back(1.0);
         curve.push_back(c.getVal(1.0));
     }

@heckflosse Please give this a full review. I changed the indices to std::size_t and hope I didn't screw it. Some further observations:

  • There is a potential bug here which I tried to fix by using ssize_t.
  • We could assert(start > 0) instead of this here.
  • We could assert(curve.size() > 2) here.

HTH,
Fl枚ssie

@Floessie I didn't test yet because I'm on a different branch atm. But from what I read, it looks good.

@heckflosse I'm not able to push before Sunday evening. Feel free to commit.

Here's my take on the patch, FWIW:

diff --git a/rtengine/histmatching.cc b/rtengine/histmatching.cc
--- a/rtengine/histmatching.cc
+++ b/rtengine/histmatching.cc
@@ -109,7 +109,7 @@
         }
     }
     if (idx == int(mapping.size())) {
-        for (idx = 1; idx < int(mapping.size()); ++idx) {
+        for (idx = 1; idx < int(mapping.size())-1; ++idx) {
             if (mapping[idx] >= idx) {
                 break;
             }

Bump, let's resolve this to release 5.6-rc1. I can't reproduce the crash though.

@agriggio Wouldn't your fix still fail for mapping.size() < 15?

sure, but mapping has size 256

@agriggio Okay. Then, why not use a std::array<> for a fixed size array?

@Beep6581 To make some progress here before releasing 5.6 I suggest the following:

  • You pick Alberto's patch for 5.6 as it's the most concise one.
  • I still think there is another bug here which should be fixed in passing.
  • After the release we take my cleanup patch as a base, remove the initially proposed index check, turn the std::vector<>s into std::array<>s where feasible (no need to assert anymore, then), and let it brew in dev for 5.7.

Best,
Fl枚ssie

With the two above patches applied on top of RT 5.5 I get no more crashes. Thanks!

@mattiaverga Mattia, could also give dev a try. Should be fixed there, too.

If the patches work and are approved, please commit tomorrow, or remove the 5.7 milestone.

@Beep6581 AFAIK the fixes are already in dev. The cleanup patch is just what it is: a cleanup.

@Floessie hmm it seems this fix https://github.com/Beep6581/RawTherapee/issues/5249#issuecomment-477025043 is not in dev - https://github.com/Beep6581/RawTherapee/blob/dev/rtengine/histmatching.cc#L125

I opened a new issue (milestone 5.8) for the cleanup, leaving this one to address only the fix.

It's fixed by Alberto's patch.

Oh great, then closing.

Was this page helpful?
0 / 5 - 0 ratings