Viewer: draw the rendered image directly instead of via a QPixmap
Every recolour ended with QPixmap::fromImage(), which allocates a second full-size buffer and converts the whole image into the screen format. That conversion was the largest single cost left in the colouring path. Replace QGraphicsPixmapItem with a small item that paints qimg_buffer_ with QPainter::drawImage. The buffer is already what the raster engine wants, so nothing is converted or copied. The item declares its opaque area, as the pixmap item did, so the view still skips the background fill underneath it, and it turns SmoothPixmapTransform off before drawing to keep the nearest-neighbour sampling QGraphicsPixmapItem gave us by default -- zoomed-in detector pixels stay sharp squares. GeneratePixmap is renamed RenderImage: it no longer makes a pixmap. 18.1 Mpx recolour: 22 -> 5.6 ms (28.0 ms before this series). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "JFJochImage.h"
|
||||
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
#include <QScrollBar>
|
||||
#include <QWheelEvent>
|
||||
@@ -20,6 +19,31 @@
|
||||
#include <QPainter>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
QRectF JFJochImageItem::boundingRect() const {
|
||||
return QRectF(0, 0, img_.width(), img_.height());
|
||||
}
|
||||
|
||||
QPainterPath JFJochImageItem::opaqueArea() const {
|
||||
// The buffer is RGB32, so the item fully covers its bounding rect
|
||||
QPainterPath path;
|
||||
path.addRect(boundingRect());
|
||||
return path;
|
||||
}
|
||||
|
||||
void JFJochImageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) {
|
||||
if (img_.isNull())
|
||||
return;
|
||||
// QGraphicsPixmapItem defaults to Qt::FastTransformation and turned this hint off before
|
||||
// drawing; keep doing that, so zoomed-in detector pixels stay sharp squares.
|
||||
painter->setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
painter->drawImage(0, 0, img_);
|
||||
}
|
||||
|
||||
void JFJochImageItem::refresh() {
|
||||
prepareGeometryChange();
|
||||
update();
|
||||
}
|
||||
|
||||
JFJochImage::JFJochImage(QWidget *parent) : QGraphicsView(parent) {
|
||||
setDragMode(QGraphicsView::NoDrag); // Disable default drag mode
|
||||
setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // Zoom anchors
|
||||
@@ -45,7 +69,7 @@ void JFJochImage::onScroll(int value) {
|
||||
|
||||
void JFJochImage::changeBackground(float val) {
|
||||
background = val;
|
||||
GeneratePixmap();
|
||||
RenderImage();
|
||||
Redraw();
|
||||
}
|
||||
|
||||
@@ -54,7 +78,7 @@ void JFJochImage::changeForeground(float val) {
|
||||
emit autoForegroundChanged(false);
|
||||
foreground = val;
|
||||
// Regenerate the image
|
||||
GeneratePixmap();
|
||||
RenderImage();
|
||||
Redraw();
|
||||
}
|
||||
|
||||
@@ -62,7 +86,7 @@ void JFJochImage::setColorMap(int color_map) {
|
||||
try {
|
||||
color_scale.Select(static_cast<ColorScaleEnum>(color_map));
|
||||
// Regenerate the image
|
||||
GeneratePixmap();
|
||||
RenderImage();
|
||||
Redraw();
|
||||
} catch (...) {
|
||||
}
|
||||
@@ -70,7 +94,7 @@ void JFJochImage::setColorMap(int color_map) {
|
||||
|
||||
void JFJochImage::setFeatureColor(QColor input) {
|
||||
feature_color = input;
|
||||
GeneratePixmap();
|
||||
RenderImage();
|
||||
Redraw();
|
||||
}
|
||||
|
||||
@@ -333,7 +357,7 @@ void JFJochImage::contextMenuEvent(QContextMenuEvent *event) {
|
||||
QAction *fitAct = menu.addAction(tr("Fit image to view"));
|
||||
QAction *clearRoiAct = menu.addAction(tr("Clear ROI"));
|
||||
|
||||
const bool hasImage = (W > 0 && H > 0 && !pixmap.isNull());
|
||||
const bool hasImage = (W > 0 && H > 0 && !qimg_buffer_.isNull());
|
||||
copyImageAct->setEnabled(hasImage);
|
||||
copyWithOverlayAct->setEnabled(hasImage && scene());
|
||||
saveImageAct->setEnabled(hasImage);
|
||||
@@ -393,7 +417,7 @@ QImage JFJochImage::renderToImage(bool with_overlay) {
|
||||
p.end();
|
||||
} else {
|
||||
// The underlying rendered image (no overlay)
|
||||
img = pixmap.toImage();
|
||||
img = qimg_buffer_;
|
||||
}
|
||||
// Ensure 1:1 pixel ratio and 96 DPI metadata to avoid rescaling in consumer apps
|
||||
img.setDevicePixelRatio(1.0);
|
||||
@@ -404,7 +428,7 @@ QImage JFJochImage::renderToImage(bool with_overlay) {
|
||||
}
|
||||
|
||||
void JFJochImage::copyImageToClipboard() {
|
||||
if (W == 0 || H == 0 || pixmap.isNull()) return;
|
||||
if (W == 0 || H == 0 || qimg_buffer_.isNull()) return;
|
||||
|
||||
setClipboardAsJpegAndImage(renderToImage(false), 95);
|
||||
emit writeStatusBar(tr("Image copied to clipboard"), 2000);
|
||||
@@ -418,7 +442,7 @@ void JFJochImage::copyImageWithOverlayToClipboard() {
|
||||
}
|
||||
|
||||
void JFJochImage::saveImageToFile(bool with_overlay) {
|
||||
if (W == 0 || H == 0 || pixmap.isNull()) return;
|
||||
if (W == 0 || H == 0 || qimg_buffer_.isNull()) return;
|
||||
if (with_overlay && !scene()) return;
|
||||
|
||||
const QString caption = with_overlay ? tr("Save image with overlay as JPEG")
|
||||
@@ -641,7 +665,7 @@ 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
|
||||
image_item_ = nullptr; // new scene, old pointer invalid
|
||||
}
|
||||
|
||||
// Perform initial fit only once per image size
|
||||
@@ -650,7 +674,7 @@ void JFJochImage::Redraw() {
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
void JFJochImage::GeneratePixmap() {
|
||||
void JFJochImage::RenderImage() {
|
||||
if (qimg_buffer_.width() != int(W) || qimg_buffer_.height() != int(H))
|
||||
qimg_buffer_ = QImage(int(W), int(H), QImage::Format_RGB32);
|
||||
|
||||
@@ -727,9 +751,6 @@ void JFJochImage::GeneratePixmap() {
|
||||
scanLine[x] = qRgb(c.r, c.g, c.b);
|
||||
}
|
||||
});
|
||||
|
||||
pixmap = QPixmap::fromImage(qimg_buffer_);
|
||||
pixmap.setDevicePixelRatio(1.0);
|
||||
}
|
||||
|
||||
void JFJochImage::centerOnSpot(QPointF point) {
|
||||
@@ -837,7 +858,7 @@ void JFJochImage::writePixelLabels() {
|
||||
}
|
||||
|
||||
void JFJochImage::resetScenePointers() {
|
||||
pixmap_item_ = nullptr;
|
||||
image_item_ = nullptr;
|
||||
overlay_items_.clear();
|
||||
}
|
||||
|
||||
@@ -846,18 +867,19 @@ void JFJochImage::updateOverlay() {
|
||||
|
||||
beforeOverlayCleared();
|
||||
|
||||
// Remove only overlay items, keep the pixmap item persistent
|
||||
// Remove only overlay items, keep the image 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);
|
||||
// Ensure the image item exists and is up-to-date
|
||||
if (!image_item_) {
|
||||
image_item_ = new JFJochImageItem(qimg_buffer_);
|
||||
image_item_->setZValue(0);
|
||||
scene()->addItem(image_item_);
|
||||
} else {
|
||||
pixmap_item_->setPixmap(pixmap);
|
||||
image_item_->refresh();
|
||||
}
|
||||
|
||||
if (scale_factor > 30.0)
|
||||
|
||||
Reference in New Issue
Block a user