diff --git a/viewer/image_viewer/JFJochAzIntImage.cpp b/viewer/image_viewer/JFJochAzIntImage.cpp index 9b4073fa..e055f729 100644 --- a/viewer/image_viewer/JFJochAzIntImage.cpp +++ b/viewer/image_viewer/JFJochAzIntImage.cpp @@ -16,6 +16,7 @@ void JFJochAzIntImage::Clear() { image_fp.clear(); if (scene()) scene()->clear(); + resetScenePointers(); } void JFJochAzIntImage::imageLoaded(std::shared_ptr in_image) { @@ -111,4 +112,4 @@ void JFJochAzIntImage::mouseDoubleClickEvent(QMouseEvent *event) { auto coord = geom.ResPhiToPxl(2 * M_PI / q, phi / 180.0 * M_PI); emit zoomOnBin(QPointF(coord.first, coord.second)); } -} +} \ No newline at end of file diff --git a/viewer/image_viewer/JFJochDiffractionImage.cpp b/viewer/image_viewer/JFJochDiffractionImage.cpp index cf3e0b2b..b204a624 100644 --- a/viewer/image_viewer/JFJochDiffractionImage.cpp +++ b/viewer/image_viewer/JFJochDiffractionImage.cpp @@ -5,6 +5,7 @@ #include "../../common/DiffractionGeometry.h" #include +#include #include #include #include @@ -104,11 +105,12 @@ void JFJochDiffractionImage::DrawSpots() { QPen pen(pen_color, 3); pen.setCosmetic(true); - auto rect = scene()->addRect(s.x - spot_size + 0.5, + auto *rect = scene()->addRect(s.x - spot_size + 0.5, s.y - spot_size + 0.5, 2 * spot_size, 2 * spot_size, pen); + addOverlayItem(rect); } } @@ -132,11 +134,12 @@ void JFJochDiffractionImage::DrawPredictions() { if (!visibleRect.contains(QPointF{s.predicted_x, s.predicted_y})) continue; - auto rect = scene()->addEllipse(s.predicted_x - spot_size + 0.5f, + auto *ellipse = scene()->addEllipse(s.predicted_x - spot_size + 0.5f, s.predicted_y - spot_size + 0.5f, 2.0f * spot_size, 2.0f * spot_size, pen); + addOverlayItem(ellipse); // When zoomed in enough, draw "h k l" above the box if (scale_factor >= 10.0) { @@ -147,11 +150,13 @@ void JFJochDiffractionImage::DrawPredictions() { const qreal text_x = s.predicted_x - 5.5f; const qreal text_y = s.predicted_y - 10.0f; - // Add or update text in the scene - QGraphicsTextItem *textItem = scene()->addText(label, font); - textItem->setDefaultTextColor(pen_color); - textItem->setPos(text_x, text_y); // Position the text over the pixel - // textItem->setScale(1.0); // Scale down to 10% of the original size + // Use QGraphicsSimpleTextItem for much better performance + auto *textItem = new QGraphicsSimpleTextItem(label); + textItem->setFont(font); + textItem->setBrush(pen_color); + textItem->setPos(text_x, text_y); + scene()->addItem(textItem); + addOverlayItem(textItem); } } } @@ -244,7 +249,7 @@ void JFJochDiffractionImage::DrawResolutionRings() { auto y_max = std::max({y1, y2, y3, y4}); QRectF boundingRect(x_min, y_min, x_max - x_min, y_max - y_min); - scene()->addEllipse(boundingRect, pen); + addOverlayItem(scene()->addEllipse(boundingRect, pen)); auto [x5,y5] = geom.ResPhiToPxl(d, phi_offset + 0); auto [x6,y6] = geom.ResPhiToPxl(d, phi_offset + M_PI_2); @@ -272,10 +277,13 @@ void JFJochDiffractionImage::DrawResolutionRings() { const qreal f = std::clamp(scale_factor, 0.5, 50.0); font.setPointSizeF(16.0 / sqrt(f)); // base 12pt around scale_factor ~10 - QGraphicsTextItem *textItem = scene()->addText( - QString("%1 Å").arg(QString::number(d, 'f', 2)), font); - textItem->setDefaultTextColor(ring_color); + auto *textItem = new QGraphicsSimpleTextItem( + QString("%1 Å").arg(QString::number(d, 'f', 2))); + textItem->setFont(font); + textItem->setBrush(ring_color); textItem->setPos(point.value()); + scene()->addItem(textItem); + addOverlayItem(textItem); } phi_offset += 4.0 / 180.0 * M_PI; } @@ -351,6 +359,7 @@ void JFJochDiffractionImage::loadImage(std::shared_ptr W = 0; H = 0; if (scene()) scene()->clear(); + resetScenePointers(); hover_resolution = NAN; hover_resolution_item = nullptr; @@ -415,6 +424,9 @@ void JFJochDiffractionImage::DrawCross(float x, float y, float size, float width horizontalLine->setZValue(z); // Ensure it appears above other items verticalLine->setZValue(z); // Ensure it appears above other items + + addOverlayItem(horizontalLine); + addOverlayItem(verticalLine); } void JFJochDiffractionImage::showSaturation(bool input) { @@ -457,6 +469,7 @@ void JFJochDiffractionImage::DrawResolutionText() { QString("d = %1 Å").arg(QString::number(hover_resolution, 'f', 2)); // Create the item if it does not exist yet; otherwise reuse it + // NOTE: hover_resolution_item is NOT tracked in overlay_items_ — it is persistent if (!hover_resolution_item) { hover_resolution_item = scn->addText(label, font); hover_resolution_item->setZValue(10.0); @@ -480,9 +493,9 @@ void JFJochDiffractionImage::DrawResolutionText() { } void JFJochDiffractionImage::beforeOverlayCleared() { - // The scene is about to clear (and delete) all its items. - // Drop our non-owning pointer so we never touch a deleted item. - hover_resolution_item = nullptr; + // hover_resolution_item is NOT in overlay_items_, so the selective clear won't touch it. + // However, if scene()->clear() is ever called (e.g. on loadImage(nullptr)), + // the caller must also set hover_resolution_item = nullptr separately. } void JFJochDiffractionImage::leaveEvent(QEvent *event) { @@ -492,4 +505,4 @@ void JFJochDiffractionImage::leaveEvent(QEvent *event) { DrawResolutionText(); } JFJochImage::leaveEvent(event); -} +} \ No newline at end of file diff --git a/viewer/image_viewer/JFJochGridScanImage.cpp b/viewer/image_viewer/JFJochGridScanImage.cpp index 10e9b97c..a20b0ca8 100644 --- a/viewer/image_viewer/JFJochGridScanImage.cpp +++ b/viewer/image_viewer/JFJochGridScanImage.cpp @@ -3,6 +3,8 @@ #include "JFJochGridScanImage.h" +#include + JFJochGridScanImage::JFJochGridScanImage(QWidget *parent) : JFJochImage(parent) {} void JFJochGridScanImage::clear() { @@ -11,6 +13,7 @@ void JFJochGridScanImage::clear() { this->settings = std::nullopt; if (scene()) scene()->clear(); + resetScenePointers(); CalcROI(); } @@ -170,6 +173,6 @@ void JFJochGridScanImage::addCustomOverlay() { QPen pen(feature_color, 3); pen.setCosmetic(true); - auto rect = scene()->addRect(current_image_W, current_image_H, 1, 1, pen); -} - + auto *rect = scene()->addRect(current_image_W, current_image_H, 1, 1, pen); + addOverlayItem(rect); +} \ No newline at end of file diff --git a/viewer/image_viewer/JFJochImage.cpp b/viewer/image_viewer/JFJochImage.cpp index 4efa71aa..3a1a6948 100644 --- a/viewer/image_viewer/JFJochImage.cpp +++ b/viewer/image_viewer/JFJochImage.cpp @@ -4,6 +4,7 @@ #include "JFJochImage.h" #include +#include #include #include #include @@ -469,6 +470,10 @@ void JFJochImage::updateROI() { updateOverlay(); } +void JFJochImage::addOverlayItem(QGraphicsItem *item) { + overlay_items_.append(item); +} + void JFJochImage::DrawROI() { if (roiBox.isNull() || roiBox.width() <= 0 || roiBox.height() <= 0) return; @@ -485,14 +490,14 @@ void JFJochImage::DrawROI() { if (roi_type == RoiType::RoiCircle) { // Draw circle - scn->addEllipse(roiBox, pen); + addOverlayItem(scn->addEllipse(roiBox, pen)); // A single handle on the circle at the rightmost point const QPointF c = roiBox.center(); const qreal rad = 0.5 * (roiBox.width() + roiBox.height()) * 0.5; // average, should be equal QPointF hpos = QPointF(roiBox.right(), c.y()); - scn->addRect(QRectF(hpos.x() - handleSize, hpos.y() - handleSize, 2 * handleSize, 2 * handleSize), - QPen(feature_color, 1), QBrush(feature_color)); + addOverlayItem(scn->addRect(QRectF(hpos.x() - handleSize, hpos.y() - handleSize, 2 * handleSize, 2 * handleSize), + QPen(feature_color, 1), QBrush(feature_color))); // On hover near perimeter: draw in/out arrows along radius at handle if (hover_handle_ != ResizeHandle::None && hover_handle_ != ResizeHandle::Inside) { @@ -500,18 +505,18 @@ void JFJochImage::DrawROI() { apen.setCosmetic(true); const qreal arrowLen = 8.0 / std::sqrt(std::max(1e-4, f)); // Outward arrow - scn->addLine(QLineF(c, c + QPointF(rad + arrowLen, 0)), apen); + addOverlayItem(scn->addLine(QLineF(c, c + QPointF(rad + arrowLen, 0)), apen)); // Inward arrow - scn->addLine(QLineF(c, c + QPointF(rad - arrowLen, 0)), apen); + addOverlayItem(scn->addLine(QLineF(c, c + QPointF(rad - arrowLen, 0)), apen)); } } else { // Box - scn->addRect(roiBox, pen); + addOverlayItem(scn->addRect(roiBox, pen)); // Corner handles auto addHandle = [&](const QPointF& p) { - scn->addRect(QRectF(p.x() - handleSize, p.y() - handleSize, 2 * handleSize, 2 * handleSize), - QPen(feature_color, 1), QBrush(feature_color)); + addOverlayItem(scn->addRect(QRectF(p.x() - handleSize, p.y() - handleSize, 2 * handleSize, 2 * handleSize), + QPen(feature_color, 1), QBrush(feature_color))); }; addHandle(roiBox.topLeft()); addHandle(roiBox.topRight()); @@ -525,7 +530,7 @@ void JFJochImage::DrawROI() { const qreal arrowLen = 6.0 / std::sqrt(std::max(1e-4, f)); const qreal off = 10.0 / std::sqrt(std::max(1e-4, f)); auto drawArrow = [&](const QPointF& a, const QPointF& b) { - scn->addLine(QLineF(a, b), apen); + addOverlayItem(scn->addLine(QLineF(a, b), apen)); }; const QRectF r = roiBox; switch (hover_handle_) { @@ -566,9 +571,6 @@ void JFJochImage::Redraw() { if (W*H <= 0) return; - // Save the current transformation (zoom state) - QTransform currentTransform = this->transform(); - QGraphicsScene *currentScene = scene(); if (!currentScene) { // First time - create a new scene @@ -576,11 +578,9 @@ void JFJochImage::Redraw() { setScene(currentScene); // Reset initial-fit state for a new scene initial_fit_done_ = false; + pixmap_item_ = nullptr; // new scene, old pointer invalid } - // Restore the zoom level - this->setTransform(currentTransform, false); // "false" prevents resetting the view - // Perform initial fit only once per image size fitToViewShorterSideOnce(); @@ -588,7 +588,10 @@ void JFJochImage::Redraw() { } void JFJochImage::GeneratePixmap() { - QImage qimg(int(W), int(H), QImage::Format_RGB32); + // Reuse buffer if size matches, otherwise reallocate + if (qimg_buffer_.width() != int(W) || qimg_buffer_.height() != int(H)) + qimg_buffer_ = QImage(int(W), int(H), QImage::Format_RGB32); + image_rgb.resize(W * H); // Bad pixel color @@ -621,7 +624,7 @@ void JFJochImage::GeneratePixmap() { for (int y = 0; y < H; ++y) rows.push_back(y); QtConcurrent::blockingMap(rows, [&](int y) { - QRgb *scanLine = reinterpret_cast(qimg.scanLine(y)); + QRgb *scanLine = reinterpret_cast(qimg_buffer_.scanLine(y)); const float *row = &image_fp[y * W]; rgb *out = &image_rgb[y * W]; @@ -662,7 +665,7 @@ void JFJochImage::GeneratePixmap() { } }); - pixmap = QPixmap::fromImage(qimg); + pixmap = QPixmap::fromImage(qimg_buffer_); pixmap.setDevicePixelRatio(1.0); } @@ -693,7 +696,7 @@ void JFJochImage::writePixelLabels() { const int visW = std::max(0, endX - startX); const int visH = std::max(0, endY - startY); - int maxLabels = 1000; + int maxLabels = 5000; // Choose thresholds that fit your UI width constexpr float kMinFixed = 1e-3; @@ -731,26 +734,45 @@ void JFJochImage::writePixelLabels() { pText = &numBuf; } - QGraphicsTextItem* textItem = scene()->addText(*pText, font); + auto *textItem = new QGraphicsSimpleTextItem(*pText); + textItem->setFont(font); if (luminance(image_rgb[idx]) > 128.0) - textItem->setDefaultTextColor(Qt::black); + textItem->setBrush(Qt::black); else - textItem->setDefaultTextColor(Qt::white); + textItem->setBrush(Qt::white); textItem->setPos(x - 0.7, y - 0.8); - textItem->setScale(0.2); + textItem->setTransform(QTransform::fromScale(0.2, 0.2)); + scene()->addItem(textItem); + addOverlayItem(textItem); } } } } +void JFJochImage::resetScenePointers() { + pixmap_item_ = nullptr; + overlay_items_.clear(); +} + void JFJochImage::updateOverlay() { if (!scene() || W * H <= 0) return; beforeOverlayCleared(); - scene()->clear(); - scene()->addItem(new QGraphicsPixmapItem(pixmap)); + // Remove only overlay items, keep the pixmap item persistent + for (auto *item : overlay_items_) + scene()->removeItem(item); + qDeleteAll(overlay_items_); + overlay_items_.clear(); + + // Ensure pixmap item exists and is up-to-date + if (!pixmap_item_) { + pixmap_item_ = scene()->addPixmap(pixmap); + pixmap_item_->setZValue(0); + } else { + pixmap_item_->setPixmap(pixmap); + } if (scale_factor > 30.0) writePixelLabels(); diff --git a/viewer/image_viewer/JFJochImage.h b/viewer/image_viewer/JFJochImage.h index 9e136b89..4e8b1ad8 100644 --- a/viewer/image_viewer/JFJochImage.h +++ b/viewer/image_viewer/JFJochImage.h @@ -53,6 +53,15 @@ protected: std::vector image_rgb; std::vector image_fp; QPixmap pixmap; + QImage qimg_buffer_; // reusable image buffer — avoids 64MB alloc per frame + + // Persistent pixmap item — never destroyed/recreated on overlay update + QGraphicsPixmapItem *pixmap_item_ = nullptr; + // Overlay items managed separately + QList overlay_items_; + + // Helper: add an overlay item to the scene and track it for selective removal + void addOverlayItem(QGraphicsItem *item); enum class MouseEventType {None, Panning, DrawingROI, MovingROI, ResizingROI}; MouseEventType mouse_event_type = MouseEventType::None; @@ -79,6 +88,9 @@ protected: void Redraw(); void CalcROI(); + // Invalidate pixmap_item_ and overlay tracking after scene()->clear() + void resetScenePointers(); + // Perform initial fit-to-view (shorter direction), once per image size void fitToViewShorterSideOnce(); QSize last_fit_viewport_ = {}; @@ -112,4 +124,4 @@ public slots: public: explicit JFJochImage(QWidget *parent = nullptr); -}; +}; \ No newline at end of file diff --git a/viewer/image_viewer/JFJochSimpleImage.cpp b/viewer/image_viewer/JFJochSimpleImage.cpp index 24101ef1..700537f8 100644 --- a/viewer/image_viewer/JFJochSimpleImage.cpp +++ b/viewer/image_viewer/JFJochSimpleImage.cpp @@ -32,6 +32,7 @@ void JFJochSimpleImage::setImage(std::shared_ptr img) { W = 0; H = 0; if (scene()) scene()->clear(); + resetScenePointers(); CalcROI(); } } @@ -64,7 +65,7 @@ void JFJochSimpleImage::loadImageInternal(const uint8_t *input) { const size_t H = image_->image.GetHeight(); auto ptr = reinterpret_cast(input); - + for (int i = 0; i < W * H; i++) image_fp[i] = static_cast(ptr[i]); } @@ -110,4 +111,4 @@ void JFJochSimpleImage::loadImageInternal() { default: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Image format not supported"); } -} +} \ No newline at end of file