diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index 6a7e2f70..4658cf4d 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -1057,73 +1057,82 @@ void JFJochImageReadingWorker::RenderThumbnails(QVector image_numbers, b } QImage JFJochImageReadingWorker::RenderThumbnail_i(int64_t image_number, bool show_spots) { - std::shared_ptr img; + // Everything runs inside one try: an uncaught exception here would be on the worker thread and + // would std::terminate the whole app. On any failure we just skip this thumbnail. try { - QMutexLocker locker(&m); - if (http_mode || image_number < 0 || image_number >= total_images) + std::shared_ptr img; + { + QMutexLocker locker(&m); + if (http_mode || image_number < 0 || image_number >= total_images) + return {}; + img = file_reader.LoadImage(image_number, 1); + } + if (!img) return {}; - img = file_reader.LoadImage(image_number, 1); - } catch (const std::exception &e) { - logger.Debug("Thumbnail load failed for image {}: {}", image_number, e.what()); - return {}; - } - if (!img) - return {}; - const int W = static_cast(img->Dataset().experiment.GetXPixelsNum()); - const int H = static_cast(img->Dataset().experiment.GetYPixelsNum()); - const auto &px = img->Image(); - if (W <= 0 || H <= 0 || static_cast(px.size()) < static_cast(W) * H) - return {}; + const int W = static_cast(img->Dataset().experiment.GetXPixelsNum()); + const int H = static_cast(img->Dataset().experiment.GetYPixelsNum()); + const auto &px = img->Image(); + if (W <= 0 || H <= 0 || static_cast(px.size()) < static_cast(W) * H) + return {}; - // Downsample by block-maximum (keeps sharp Bragg spots), then map through the colour-scale LUT. - constexpr int maxDim = 120; - const double s = static_cast(maxDim) / std::max(W, H); - const int tw = std::max(1, static_cast(W * s)); - const int th = std::max(1, static_cast(H * s)); - const int bx = std::max(1, W / tw); - const int by = std::max(1, H / th); + const auto &lut = thumb_color_scale_.LUTData(); + const int lutSize = static_cast(lut.size()); + if (lutSize <= 0) + return {}; - const float fg = std::max(1.0f, static_cast(img->GetAutoContrastValue())); - const auto &lut = thumb_color_scale_.LUTData(); - const int lutSize = static_cast(lut.size()); - const float invRange = fg > 0 ? (lutSize - 1) / fg : 0.0f; - const rgb gap = thumb_color_scale_.Apply(ColorScaleSpecial::Gap); + // Downsample by block-maximum (keeps sharp Bragg spots), then map through the LUT. + constexpr int maxDim = 120; + const double s = static_cast(maxDim) / std::max(W, H); + const int tw = std::max(1, static_cast(W * s)); + const int th = std::max(1, static_cast(H * s)); + const int bx = std::max(1, W / tw); + const int by = std::max(1, H / th); - QImage out(tw, th, QImage::Format_RGB32); - for (int ty = 0; ty < th; ++ty) { - QRgb *line = reinterpret_cast(out.scanLine(ty)); - const int y1 = std::min(H, ty * by + by); - for (int tx = 0; tx < tw; ++tx) { - const int x1 = std::min(W, tx * bx + bx); - int32_t best = 0; - bool any = false; - for (int yy = ty * by; yy < y1; ++yy) - for (int xx = tx * bx; xx < x1; ++xx) { - const int32_t v = px[yy * W + xx]; - if (v == GAP_PXL_VALUE || v == ERROR_PXL_VALUE) continue; - if (v == SATURATED_PXL_VALUE) { best = static_cast(fg); any = true; continue; } - if (!any || v > best) { best = v; any = true; } + const float fg = std::max(1.0f, static_cast(img->GetAutoContrastValue())); + const float invRange = (lutSize - 1) / fg; + const rgb gap = thumb_color_scale_.Apply(ColorScaleSpecial::Gap); + + QImage out(tw, th, QImage::Format_RGB32); + for (int ty = 0; ty < th; ++ty) { + QRgb *line = reinterpret_cast(out.scanLine(ty)); + const int y1 = std::min(H, ty * by + by); + for (int tx = 0; tx < tw; ++tx) { + const int x1 = std::min(W, tx * bx + bx); + int32_t best = 0; + bool any = false; + for (int yy = ty * by; yy < y1; ++yy) + for (int xx = tx * bx; xx < x1; ++xx) { + const int32_t v = px[yy * W + xx]; + if (v == GAP_PXL_VALUE || v == ERROR_PXL_VALUE) continue; + if (v == SATURATED_PXL_VALUE) { best = static_cast(fg); any = true; continue; } + if (!any || v > best) { best = v; any = true; } + } + rgb c; + if (!any) { + c = gap; + } else { + int idx = static_cast(std::max(0, best) * invRange + 0.5f); + idx = std::clamp(idx, 0, lutSize - 1); + c = lut[idx]; } - rgb c; - if (!any) { - c = gap; - } else { - int idx = static_cast(std::max(0, best) * invRange + 0.5f); - idx = std::clamp(idx, 0, lutSize - 1); - c = lut[idx]; + line[tx] = qRgb(c.r, c.g, c.b); } - line[tx] = qRgb(c.r, c.g, c.b); } - } - if (show_spots) { - QPainter p(&out); - p.setRenderHint(QPainter::Antialiasing); - for (const auto &sp : img->ImageData().spots) { - p.setPen(QPen(sp.indexed ? thumb_feature_color_ : thumb_spot_color_, 1.0)); - p.drawEllipse(QPointF(sp.x * s, sp.y * s), 1.6, 1.6); + if (show_spots) { + QPainter p(&out); + p.setRenderHint(QPainter::Antialiasing); + for (const auto &sp : img->ImageData().spots) { + p.setPen(QPen(sp.indexed ? thumb_feature_color_ : thumb_spot_color_, 1.0)); + p.drawEllipse(QPointF(sp.x * s, sp.y * s), 1.6, 1.6); + } } + return out; + } catch (const std::exception &e) { + logger.Debug("Thumbnail render failed for image {}: {}", image_number, e.what()); + return {}; + } catch (...) { + return {}; } - return out; }