From da13bd8f01f2c83da9098824c160db53d16dc2ef Mon Sep 17 00:00:00 2001 From: Ernie Pasveer Date: Sat, 29 Oct 2022 16:10:09 -0500 Subject: [PATCH] Image viewer in Memory Visualizer. (First pass - slow). --- src/CMakeLists.txt | 4 + src/QImageViewer.cpp | 158 +++++++++++++++++++++++++++++ src/QImageViewer.h | 44 ++++++++ src/SeerImageWidget.cpp | 93 +++++++++++++++++ src/SeerImageWidget.h | 29 ++++++ src/SeerImageWidget.ui | 127 +++++++++++++++++++++++ src/SeerMemoryVisualizerWidget.cpp | 42 +++++++- src/SeerMemoryVisualizerWidget.h | 1 + src/SeerMemoryVisualizerWidget.ui | 22 +++- 9 files changed, 514 insertions(+), 6 deletions(-) create mode 100644 src/QImageViewer.cpp create mode 100644 src/QImageViewer.h create mode 100644 src/SeerImageWidget.cpp create mode 100644 src/SeerImageWidget.h create mode 100644 src/SeerImageWidget.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2dd2bd9..682ea4f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,7 @@ set(HEADER_FILES SeerGdbWidget.h SeerHexWidget.h SeerAsmWidget.h + SeerImageWidget.h SeerArrayWidget.h SeerLogWidget.h SeerMainWindow.h @@ -101,6 +102,7 @@ set(HEADER_FILES QHistoryLineEdit.h QEditDelegate.h QHContainerWidget.h + QImageViewer.h ) set(SOURCE_FILES @@ -132,6 +134,7 @@ set(SOURCE_FILES SeerGdbWidget.cpp SeerHexWidget.cpp SeerAsmWidget.cpp + SeerImageWidget.cpp SeerArrayWidget.cpp SeerLogWidget.cpp SeerMainWindow.cpp @@ -184,6 +187,7 @@ set(SOURCE_FILES QHistoryLineEdit.cpp QEditDelegate.cpp QHContainerWidget.cpp + QImageViewer.cpp ) qt5_add_resources(SOURCE_FILES resource.qrc) diff --git a/src/QImageViewer.cpp b/src/QImageViewer.cpp new file mode 100644 index 0000000..91f7cb9 --- /dev/null +++ b/src/QImageViewer.cpp @@ -0,0 +1,158 @@ +#include "QImageViewer.h" +#include +#include +#include +#include +#include +#include + +// +// https://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-example.html +// https://stackoverflow.com/questions/53193010/how-to-resize-a-qlabel-with-pixmap-inside-a-qscrollarea +// + +QImageViewer::QImageViewer (QWidget* parent) : QWidget(parent) { + + _zoomFactor = 1.0; + + // Setup the widgets + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + _imageLabel = new QLabel(); + _imageLabel->setBackgroundRole(QPalette::Base); + _imageLabel->setScaledContents(true); + + _scrollArea = new QScrollArea(this); + _scrollArea->setBackgroundRole(QPalette::Dark); + _scrollArea->setWidget(_imageLabel); + _scrollArea->setWidgetResizable(false); + + layout->addWidget(_scrollArea); + + _imageLabel->setPixmap(QPixmap::fromImage(_image)); +} + +QImageViewer::~QImageViewer () { +} + +bool QImageViewer::loadFile (const QString& file) { + + QImageReader reader(file); + + reader.setAutoTransform(true); + + QImage image = reader.read(); + + if (image.isNull()) { + return false; + } + + setImage(image); + + return true; +} + +void QImageViewer::setImage (const QImage& image) { + + //qDebug() << image; + + _image = image; + _zoomFactor = 1.0; + + _imageLabel->setPixmap(QPixmap::fromImage(_image)); + + zoomReset(); +} + +const QImage& QImageViewer::image () const { + + return _image; +} + +void QImageViewer::setText (const QString& text) { + + _imageLabel->setText(text); +} + +double QImageViewer::zoomFactor () const { + + return _zoomFactor; +} + +void QImageViewer::zoom (double factor) { + + _zoomFactor = factor; + + _imageLabel->resize(_zoomFactor * _imageLabel->pixmap(Qt::ReturnByValue).size()); +} + +void QImageViewer::zoomIn () { + + zoom(zoomFactor() * 1.25); +} + +void QImageViewer::zoomOut () { + + zoom(zoomFactor() * 0.8); +} + +void QImageViewer::zoomReset () { + + zoom(1.0); +} + +void QImageViewer::print () { + + Q_ASSERT(_imageLabel->pixmap(Qt::ReturnByValue).isNull() == false); + + QPrintDialog dialog(&_printer, this); + + if (dialog.exec()) { + QPainter painter(&_printer); + QRect rect = painter.viewport(); + QSize size = _imageLabel->pixmap(Qt::ReturnByValue).size(); + size.scale(rect.size(), Qt::KeepAspectRatio); + painter.setViewport(rect.x(), rect.y(), size.width(), size.height()); + painter.setWindow(_imageLabel->pixmap(Qt::ReturnByValue).rect()); + painter.drawPixmap(0, 0, _imageLabel->pixmap(Qt::ReturnByValue)); + } +} + +void QImageViewer::keyPressEvent (QKeyEvent* event) { + + //qDebug() << "Key =" << event->key(); + + switch (event->key()) { + case Qt::Key_Escape: + zoomReset(); + break; + case Qt::Key_Plus: + zoomIn(); + break; + case Qt::Key_Minus: + zoomOut(); + break; + case Qt::Key_P: + if (event->modifiers() == Qt::ControlModifier) { + print(); + } + break; + default: + QWidget::keyPressEvent(event); + break; + } +} + +void QImageViewer::enterEvent (QEvent* event) { + + Q_UNUSED(event); + + setFocus(); +} + +void QImageViewer::leaveEvent (QEvent* event) { + + Q_UNUSED(event); +} + diff --git a/src/QImageViewer.h b/src/QImageViewer.h new file mode 100644 index 0000000..04bb411 --- /dev/null +++ b/src/QImageViewer.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class QImageViewer : public QWidget { + + Q_OBJECT + + public: + explicit QImageViewer (QWidget* parent = 0); + ~QImageViewer (); + + bool loadFile (const QString& file); + void setImage (const QImage& image); + const QImage& image () const; + void setText (const QString& text); + + double zoomFactor () const; + + public slots: + void zoom (double factor); + void zoomIn (); + void zoomOut (); + void zoomReset (); + void print (); + + protected slots: + void keyPressEvent (QKeyEvent* event); + void enterEvent (QEvent* event); + void leaveEvent (QEvent* event); + + private: + QImage _image; + QScrollArea* _scrollArea; + QLabel* _imageLabel; + double _zoomFactor; + QPrinter _printer; +}; + diff --git a/src/SeerImageWidget.cpp b/src/SeerImageWidget.cpp new file mode 100644 index 0000000..5976b31 --- /dev/null +++ b/src/SeerImageWidget.cpp @@ -0,0 +1,93 @@ +#include "SeerImageWidget.h" +#include "SeerUtl.h" +#include +#include + +SeerImageWidget::SeerImageWidget(QWidget* parent) : QWidget(parent) { + + // Construct the UI. + setupUi(this); + + // Setup the widgets + widthLineEdit->setValidator(new QIntValidator(1, 9999999, this)); + heightLineEdit->setValidator(new QIntValidator(1, 9999999, this)); + + // Connect things. + QObject::connect(redrawToolButton, &QToolButton::clicked, this, &SeerImageWidget::handleRedrawToolButton); +} + +SeerImageWidget::~SeerImageWidget() { +} + +void SeerImageWidget::setData (const QByteArray& data) { + + _data = data; + + refresh(); +} + +void SeerImageWidget::refresh () { + + // Clear the status. + statusLineEdit->setText(""); + + // Nothing to display? + if (_data.size() < 1) { + return; + } + + // Get image dimensions. + int w = widthLineEdit->text().toInt(); + int h = heightLineEdit->text().toInt(); + + if (w < 1 || h < 1) { + return; + } + + // Get image format. + QString format = formatComboBox->currentText(); + + // Validate the image dimensions vs. the data size. + int bytes = 0; + + if (format == "RGBA8888") { + bytes = w * h * 4; + }else if (format == "RGB888") { + bytes = w * h * 3; + }else{ + bytes = -1; + } + + // Unknown format. + if (bytes < 0) { + statusLineEdit->setText("Unknown image format of '" + format + "."); + return; + } + + // Data is not large enough for requested image dimensions. + if (bytes > _data.size()) { + statusLineEdit->setText(QString("The %1 memory bytes is not enough for the '%2' image (%3x%4). Need at least %5 bytes.").arg(_data.size()) + .arg(format) + .arg(w) + .arg(h) + .arg(bytes)); + return; + } + + // Construct image. + QImage image; + + if (format == "RGBA8888") { + image = QImage((const uchar*)_data.data(), w, h, QImage::Format_RGBA8888); + }else if (format == "RGB888") { + image = QImage((const uchar*)_data.data(), w, h, QImage::Format_RGB888); + } + + imageViewer->setImage(image); +} + +void SeerImageWidget::handleRedrawToolButton () { + + refresh(); +} + diff --git a/src/SeerImageWidget.h b/src/SeerImageWidget.h new file mode 100644 index 0000000..779e45c --- /dev/null +++ b/src/SeerImageWidget.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include "ui_SeerImageWidget.h" + +class SeerImageWidget: public QWidget, protected Ui::SeerImageWidgetForm { + + Q_OBJECT + + public: + + SeerImageWidget(QWidget* parent = 0); + ~SeerImageWidget(); + + signals: + + public slots: + void setData (const QByteArray& data); + void refresh (); + + protected: + + protected slots: + void handleRedrawToolButton (); + + private: + QByteArray _data; +}; + diff --git a/src/SeerImageWidget.ui b/src/SeerImageWidget.ui new file mode 100644 index 0000000..d0c8127 --- /dev/null +++ b/src/SeerImageWidget.ui @@ -0,0 +1,127 @@ + + + SeerImageWidgetForm + + + + 0 + 0 + 628 + 477 + + + + Seer Image Viewer + + + + + + + + + RGBA8888 + + + + + RGB888 + + + + + + + + The image width (pixels) + + + width + + + true + + + + + + + The image height (pixels) + + + height + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Redraw image. + + + + + + + :/seer/resources/RelaxLightIcons/view-refresh.svg:/seer/resources/RelaxLightIcons/view-refresh.svg + + + + + + + + + + 0 + 100 + + + + + + + + + + + true + + + + + + + + QImageViewer + QWidget +
QImageViewer.h
+
+
+ + formatComboBox + widthLineEdit + heightLineEdit + redrawToolButton + + + + + +
diff --git a/src/SeerMemoryVisualizerWidget.cpp b/src/SeerMemoryVisualizerWidget.cpp index 5e91ecb..8847f67 100644 --- a/src/SeerMemoryVisualizerWidget.cpp +++ b/src/SeerMemoryVisualizerWidget.cpp @@ -17,6 +17,7 @@ SeerMemoryVisualizerWidget::SeerMemoryVisualizerWidget (QWidget* parent) : QWidg _variableId = Seer::createID(); // Create two id's for queries. _memoryId = Seer::createID(); _asmId = Seer::createID(); + _imageId = Seer::createID(); // Set up UI. setupUi(this); @@ -83,6 +84,8 @@ void SeerMemoryVisualizerWidget::setVariableName (const QString& name) { QByteArray array; memoryHexEditor->setData(new SeerHexWidget::DataStorageArray(array)); + memoryAsmEditor->setData(""); + memoryImageEditor->setData(array); // Send signal to get variable address. emit evaluateVariableExpression(_variableId, variableNameLineEdit->text()); @@ -138,11 +141,14 @@ void SeerMemoryVisualizerWidget::setVariableAddress (const QString& address) { } QString SeerMemoryVisualizerWidget::variableAddress () const { + return variableAddressLineEdit->text(); } void SeerMemoryVisualizerWidget::handleText (const QString& text) { + QApplication::setOverrideCursor(Qt::BusyCursor); + //qDebug() << text; if (text.contains(QRegExp("^([0-9]+)\\^done,value="))) { @@ -183,8 +189,6 @@ void SeerMemoryVisualizerWidget::handleText (const QString& text) { QString contents_text = Seer::parseFirst(range_text, "contents=", '"', '"', false); - //qDebug() << contents_text; - // Convert hex string to byte array. QByteArray array; @@ -198,6 +202,35 @@ void SeerMemoryVisualizerWidget::handleText (const QString& text) { // Give the byte array to the hex widget. memoryHexEditor->setData(new SeerHexWidget::DataStorageArray(array)); + break; // Take just the first range for now. + } + + }else if (id_text.toInt() == _imageId) { + + //qDebug() << text; + + QString memory_text = Seer::parseFirst(text, "memory=", '[', ']', false); + + QStringList range_list = Seer::parse(memory_text, "", '{', '}', false); + + // Loop through the memory ranges. + for ( const auto& range_text : range_list ) { + + QString contents_text = Seer::parseFirst(range_text, "contents=", '"', '"', false); + + // Convert hex string to byte array. + QByteArray array; + + for (int i = 0; isetData(array); + break; // Take just the first range for now. } } @@ -242,6 +275,8 @@ void SeerMemoryVisualizerWidget::handleText (const QString& text) { }else{ // Ignore anything else. } + + QApplication::restoreOverrideCursor(); } void SeerMemoryVisualizerWidget::handleRefreshButton () { @@ -265,7 +300,8 @@ void SeerMemoryVisualizerWidget::handleRefreshButton () { } emit evaluateMemoryExpression(_memoryId, variableAddressLineEdit->text(), nbytes); - emit evaluateAsmExpression(_asmId, variableAddressLineEdit->text(), nbytes, 2); + emit evaluateAsmExpression(_asmId, variableAddressLineEdit->text(), nbytes, 2); + emit evaluateMemoryExpression(_imageId, variableAddressLineEdit->text(), nbytes); } void SeerMemoryVisualizerWidget::handleHelpButton () { diff --git a/src/SeerMemoryVisualizerWidget.h b/src/SeerMemoryVisualizerWidget.h index 91a59d5..597ba71 100644 --- a/src/SeerMemoryVisualizerWidget.h +++ b/src/SeerMemoryVisualizerWidget.h @@ -43,5 +43,6 @@ class SeerMemoryVisualizerWidget : public QWidget, protected Ui::SeerMemoryVisua int _variableId; int _memoryId; int _asmId; + int _imageId; }; diff --git a/src/SeerMemoryVisualizerWidget.ui b/src/SeerMemoryVisualizerWidget.ui index d023d23..55975d4 100644 --- a/src/SeerMemoryVisualizerWidget.ui +++ b/src/SeerMemoryVisualizerWidget.ui @@ -13,8 +13,8 @@ Form - - + + @@ -197,7 +197,7 @@ - + 0 @@ -236,6 +236,16 @@ + + + Image + + + + + + + @@ -258,6 +268,12 @@ QLineEdit
QHistoryLineEdit.h
+ + SeerImageWidget + QWidget +
SeerImageWidget.h
+ 1 +