231 lines
7.8 KiB
C++
231 lines
7.8 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochSimpleImage.h"
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsPixmapItem>
|
|
#include <QMouseEvent>
|
|
#include <QtMath>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
|
|
#include "../../common/JFJochException.h"
|
|
|
|
JFJochSimpleImage::JFJochSimpleImage(QWidget *parent)
|
|
: JFJochImage(parent) {
|
|
auto *scn = new QGraphicsScene(this);
|
|
setScene(scn);
|
|
|
|
// Keep overlays in pixel units independent of zoom (for labels font sizing)
|
|
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
|
|
}
|
|
|
|
void JFJochSimpleImage::setImage(std::shared_ptr<const SimpleImage> img) {
|
|
if (img) {
|
|
image_ = std::move(img);
|
|
has_image_ = true;
|
|
Redraw();
|
|
} else {
|
|
has_image_ = false;
|
|
image_.reset();
|
|
if (scene())
|
|
scene()->clear();
|
|
}
|
|
}
|
|
|
|
void JFJochSimpleImage::Redraw() {
|
|
if (has_image_) {
|
|
renderImage();
|
|
updateOverlay();
|
|
}
|
|
}
|
|
|
|
void JFJochSimpleImage::mouseHover(QMouseEvent *event) {
|
|
const QPointF scenePos = mapToScene(event->pos());
|
|
// Hover feedback / status bar display
|
|
if ((scenePos.x() >= 0)
|
|
&& (scenePos.x() < image_->image.GetWidth())
|
|
&& (scenePos.y() >= 0)
|
|
&& (scenePos.y() < image_->image.GetHeight())) {
|
|
const auto ix = int(scenePos.x());
|
|
const auto iy = int(scenePos.y());
|
|
const auto idx = iy * int(image_->image.GetWidth()) + ix;
|
|
if (idx >= 0 && idx < int(image_values_.size()))
|
|
emit writeStatusBar(QString("x=%1 y=%2 I=%3")
|
|
.arg(scenePos.x(), 0, 'f', 1)
|
|
.arg(scenePos.y(), 0, 'f', 1)
|
|
.arg(image_values_[size_t(idx)]), 3000);
|
|
} else {
|
|
emit writeStatusBar("", 1000);
|
|
}
|
|
}
|
|
|
|
void JFJochSimpleImage::renderImage() {
|
|
const size_t W = image_->image.GetWidth();
|
|
const size_t H = image_->image.GetHeight();
|
|
if (W == 0 || H == 0) return;
|
|
|
|
// Prepare destination QImage (RGB888)
|
|
QImage qimg(int(W), int(H), QImage::Format_RGB888);
|
|
image_rgb.resize(W * H);
|
|
image_values_.resize(W * H);
|
|
|
|
std::vector<uint8_t> image_buffer;
|
|
|
|
// Access uncompressed data
|
|
const uint8_t *src = image_->image.GetUncompressedPtr(image_buffer);
|
|
const auto mode = image_->image.GetMode();
|
|
|
|
auto write_rgb_row = [&](int y) -> uchar * {
|
|
return qimg.scanLine(y);
|
|
};
|
|
|
|
auto write_pixel = [&](size_t idx, uchar *dstRow, const rgb &c) {
|
|
dstRow[idx * 3 + 0] = c.r;
|
|
dstRow[idx * 3 + 1] = c.g;
|
|
dstRow[idx * 3 + 2] = c.b;
|
|
image_rgb[idx] = c;
|
|
};
|
|
|
|
// Fast path: packed RGB (assumed 3 bytes per pixel)
|
|
if (mode == CompressedImageMode::RGB) {
|
|
for (int y = 0; y < int(H); ++y) {
|
|
const rgb *row = reinterpret_cast<const rgb *>(src + y * W * sizeof(rgb));
|
|
uchar *dst = write_rgb_row(y);
|
|
for (size_t x = 0; x < W; ++x) {
|
|
const rgb c = row[x];
|
|
write_pixel(x, dst, c);
|
|
}
|
|
}
|
|
} else {
|
|
switch (mode) {
|
|
case CompressedImageMode::Uint8:
|
|
renderImage<uint8_t>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Int8:
|
|
renderImage<int8_t>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Uint16:
|
|
renderImage<uint16_t>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Int16:
|
|
renderImage<int16_t>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Uint32:
|
|
renderImage<uint32_t>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Int32:
|
|
renderImage<int32_t>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Float32:
|
|
renderImage<float>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Float64:
|
|
renderImage<double>(qimg, src);
|
|
break;
|
|
case CompressedImageMode::Float16:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Float16 not supported");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Build pixmap
|
|
pixmap = QPixmap::fromImage(qimg);
|
|
}
|
|
|
|
void JFJochSimpleImage::updateOverlay() {
|
|
if (!scene()) return;
|
|
scene()->clear();
|
|
if (!has_image_) return;
|
|
|
|
// Add base image
|
|
scene()->addItem(new QGraphicsPixmapItem(pixmap));
|
|
|
|
QFont font("Arial", 1); // Font for pixel value text
|
|
font.setPixelSize(1); // This will render very small text (1-pixel high).
|
|
|
|
// Get the visible area in the scene coordinates
|
|
QRectF visibleRect = mapToScene(viewport()->geometry()).boundingRect();
|
|
|
|
// Calculate the range of pixels to process
|
|
const int W = int(image_->image.GetWidth());
|
|
const int H = int(image_->image.GetHeight());
|
|
|
|
const int startX = std::max(0, static_cast<int>(std::floor(visibleRect.left())));
|
|
const int endX = std::min(W, static_cast<int>(std::ceil(visibleRect.right())));
|
|
const int startY = std::max(0, static_cast<int>(std::floor(visibleRect.top())));
|
|
const int endY = std::min(H, static_cast<int>(std::ceil(visibleRect.bottom())));
|
|
|
|
// Only when zoomed in and region small (performance)
|
|
if (scale_factor > 20.0 && (endX - startX + 1) * (endY - startY + 1) < 500) {
|
|
const qreal f = std::clamp(scale_factor, 0.5, 50.0);
|
|
QFont font("Arial", 1);
|
|
font.setPixelSize(1); // 1 px font; we use scaling below as needed
|
|
|
|
for (int y = startY; y < endY; ++y) {
|
|
const size_t base = size_t(y) * size_t(W);
|
|
for (int x = startX; x < endX; ++x) {
|
|
const size_t idx = base + size_t(x);
|
|
|
|
const double raw = image_values_[idx];
|
|
const rgb c = image_rgb[idx];
|
|
|
|
// If no scalar was stored (e.g., RGB source), you can skip or compute some alt text
|
|
if (std::isnan(raw)) continue;
|
|
|
|
// Text content from original value
|
|
const QString pixelText = QString::number(raw);
|
|
|
|
// Contrast against colored pixel
|
|
QGraphicsTextItem *textItem = scene()->addText(pixelText, font);
|
|
if (luminance(c) > 128.0)
|
|
textItem->setDefaultTextColor(Qt::black);
|
|
else
|
|
textItem->setDefaultTextColor(Qt::white);
|
|
|
|
textItem->setPos(x - 0.7, y - 0.8);
|
|
textItem->setScale(0.2);
|
|
textItem->setZValue(10.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawROI();
|
|
}
|
|
|
|
template<class T>
|
|
void JFJochSimpleImage::renderImage(QImage &qimg, const uint8_t *input) {
|
|
const size_t W = image_->image.GetWidth();
|
|
const size_t H = image_->image.GetHeight();
|
|
|
|
auto ptr = reinterpret_cast<const T *>(input);
|
|
int64_t val;
|
|
for (int i = 0; i < W * H; i++) {
|
|
if (std::is_floating_point_v<T>)
|
|
val = std::lround(ptr[i]);
|
|
else
|
|
val = static_cast<int64_t>(ptr[i]);
|
|
image_values_[i] = val;
|
|
}
|
|
|
|
if (auto_fgbg) {
|
|
auto vec1(image_values_);
|
|
std::sort(vec1.begin(), vec1.end());
|
|
background = vec1[0];
|
|
foreground = vec1[vec1.size() - 1];
|
|
emit backgroundChanged(background);
|
|
emit foregroundChanged(foreground);
|
|
}
|
|
|
|
for (int y = 0; y < H; ++y) {
|
|
uchar *scanLine = qimg.scanLine(y); // Get writable pointer to the row
|
|
for (int x = 0; x < W; ++x) {
|
|
image_rgb[y * W + x] = color_scale.Apply(image_values_[y * W + x], background, foreground);
|
|
scanLine[x * 3 + 0] = image_rgb[y * W + x].r; // Red
|
|
scanLine[x * 3 + 1] = image_rgb[y * W + x].g; // Green
|
|
scanLine[x * 3 + 2] = image_rgb[y * W + x].b; // Blue
|
|
}
|
|
}
|
|
}
|
|
|