Skip to content

Commit

Permalink
feat: add full file visual preview with navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
hello-adam committed Apr 22, 2020
1 parent 727a6f1 commit f61210b
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/hobbits-core/actionprogress.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class HOBBITSCORESHARED_EXPORT ActionProgress : public QObject
signals:
void progressPercentChanged(int);

private:
protected:
int m_lastProgressPercent;
int m_progressPercent;
QMutex m_mutex;
Expand Down
15 changes: 15 additions & 0 deletions src/hobbits-core/actionrenderprogress.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include "actionrenderprogress.h"

ActionRenderProgress::ActionRenderProgress() :
ActionProgress()
{

}

void ActionRenderProgress::setRenderPreview(QImage preview)
{
QMutexLocker locker(&m_mutex);

m_preview = preview;
emit renderPreviewChanged(m_preview);
}
22 changes: 22 additions & 0 deletions src/hobbits-core/actionrenderprogress.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef ACTIONRENDERPROGRESS_H
#define ACTIONRENDERPROGRESS_H

#include "actionprogress.h"
#include <QImage>

class HOBBITSCORESHARED_EXPORT ActionRenderProgress : public ActionProgress
{
Q_OBJECT
public:
ActionRenderProgress();

void setRenderPreview(QImage preview);

signals:
void renderPreviewChanged(const QImage&);

private:
QImage m_preview;
};

#endif // ACTIONRENDERPROGRESS_H
2 changes: 1 addition & 1 deletion src/hobbits-core/bitarray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ char BitArray::byteAt(qint64 i) const
}
qint64 cacheIdx = i / CACHE_CHUNK_BYTE_SIZE;
if (!m_dataCaches[cacheIdx]) {
loadCacheAt(i);
loadCacheAt(i*8);
}
int index = int(i - cacheIdx * CACHE_CHUNK_BYTE_SIZE);
return m_dataCaches[cacheIdx][index];
Expand Down
6 changes: 6 additions & 0 deletions src/hobbits-core/bitinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ void BitInfo::initFrames()
}
else {
for (Range range: m_ranges) {
if (range.start() >= m_bits->sizeInBits()) {
break;
}
if (range.end() >= m_bits->sizeInBits()) {
range = Range(range.start(), m_bits->sizeInBits() - 1);
}
m_frames.push_back(Frame(m_bits, range));
m_maxFrameWidth = qMax(range.size(), m_maxFrameWidth);
}
Expand Down
4 changes: 4 additions & 0 deletions src/hobbits-core/hobbits-core.pro
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
actionprogress.cpp \
actionrenderprogress.cpp \
analyzeractor.cpp \
analyzerresult.cpp \
bitarray.cpp \
Expand Down Expand Up @@ -60,6 +61,7 @@ SOURCES += \
plugincallback.cpp \
pluginhelper.cpp \
pluginmanager.cpp \
previewscrollbar.cpp \
range.cpp \
rangehighlight.cpp \
settingsdata.cpp \
Expand All @@ -68,6 +70,7 @@ SOURCES += \

HEADERS += \
actionprogress.h \
actionrenderprogress.h \
actionwatcher.h \
analyzeractor.h \
analyzerinterface.h \
Expand Down Expand Up @@ -101,6 +104,7 @@ HEADERS += \
plugincallback.h \
pluginhelper.h \
pluginmanager.h \
previewscrollbar.h \
range.h \
rangehighlight.h \
settingsdata.h \
Expand Down
205 changes: 205 additions & 0 deletions src/hobbits-core/previewscrollbar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#include "previewscrollbar.h"
#include <QPainter>
#include <QtConcurrent/QtConcurrentRun>
#include "settingsmanager.h"
#include <QMouseEvent>

PreviewScrollBar::PreviewScrollBar(QWidget *parent) : QWidget(parent)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
setMinimumWidth(50);
}

int PreviewScrollBar::getFrameOffset()
{
return m_frameOffset;
}

void PreviewScrollBar::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.fillRect(QRect(QPoint(0,0), this->size()), Qt::black);

if (m_manager.isNull() || m_manager->getCurrentContainer().isNull()) {
return;
}

auto container = m_manager->getCurrentContainer();
auto containerPtr = reinterpret_cast<quint64>(container.data());

if (!m_imageCache.contains(containerPtr)) {
if (!m_renderWatchers.contains(container)) {
QSharedPointer<ActionRenderProgress> progress(new ActionRenderProgress());
QFuture<QImage> future = QtConcurrent::run(QThreadPool::globalInstance(),
PreviewScrollBar::renderPreview,
container, progress);

connect(progress.data(), &ActionRenderProgress::renderPreviewChanged, this, [containerPtr, this](const QImage& preview) {
m_imageCache.remove(containerPtr);
m_imageCache.insert(containerPtr, QImage(preview));
this->repaint();
}, Qt::QueuedConnection);

auto actionWatcher = QSharedPointer<ActionWatcher<QImage>>(
new ActionWatcher<QImage>(future, progress));

m_renderWatchers.insert(container, actionWatcher);
connect(actionWatcher->watcher(), SIGNAL(finished()), this, SLOT(checkRenderWatchers()));
}
}
else {
QImage preview = m_imageCache.value(containerPtr);
painter.drawImage(QRect(QPoint(0,0), this->size()), preview);
}

// draw frame offset
painter.setCompositionMode(QPainter::CompositionMode_Difference);
painter.setPen(QPen(QBrush(Qt::white), 2));
int pos = int(this->height() * double(m_frameOffset) / double(container->frames().size()));
painter.drawLine(0, pos, this->width(), pos);
}

void PreviewScrollBar::mouseMoveEvent(QMouseEvent *event)
{
if (m_mousePressing) {
getOffsetFromEvent(event);
}
}

void PreviewScrollBar::mousePressEvent(QMouseEvent *event)
{
m_mousePressing = true;
getOffsetFromEvent(event);
}

void PreviewScrollBar::mouseReleaseEvent(QMouseEvent *event)
{
m_mousePressing = false;
}

void PreviewScrollBar::leaveEvent(QEvent *event)
{
m_mousePressing = false;
}


void PreviewScrollBar::getOffsetFromEvent(QMouseEvent* event)
{
if (m_manager.isNull() || m_manager->getCurrentContainer().isNull()) {
return;
}
double percent = double(event->y()) / this->height();
setFrameOffset(int(double(m_manager->getCurrentContainer()->frames().size()) * percent));
}

void PreviewScrollBar::setFrameOffset(int offset)
{
if (offset != m_frameOffset) {
m_frameOffset = offset;
if (!m_displayHandle.isNull() && m_frameOffset != m_displayHandle->getFrameOffset()) {
m_displayHandle->setOffsets(m_displayHandle->getBitOffset(), m_frameOffset);
}
emit frameOffsetChanged(m_frameOffset);
repaint();
}
}

void PreviewScrollBar::setBitContainerManager(QSharedPointer<BitContainerManager> manager)
{
if (!m_manager.isNull()) {
disconnect(m_manager.data(), SIGNAL(currSelectionChanged(QSharedPointer<BitContainer>, QSharedPointer<BitContainer>)), this, SLOT(repaint()));
}

m_manager = manager;

connect(m_manager.data(), SIGNAL(currSelectionChanged(QSharedPointer<BitContainer>, QSharedPointer<BitContainer>)), this, SLOT(repaint()));

repaint();
}


void PreviewScrollBar::setDisplayHandle(QSharedPointer<DisplayHandle> displayHandle)
{
if (!m_displayHandle.isNull()) {
disconnect(m_displayHandle.data(), &DisplayHandle::newOffsets, this, &PreviewScrollBar::checkDisplayHandleOffset);
}

m_displayHandle = displayHandle;

connect(m_displayHandle.data(), &DisplayHandle::newOffsets, this, &PreviewScrollBar::checkDisplayHandleOffset);
checkDisplayHandleOffset();
}

void PreviewScrollBar::checkDisplayHandleOffset()
{
if (m_displayHandle.isNull()) {
return;
}

setFrameOffset(m_displayHandle->getFrameOffset());
}

void PreviewScrollBar::newContainer()
{
repaint();
}

void PreviewScrollBar::checkRenderWatchers()
{
QList<QSharedPointer<BitContainer>> removals;
for (auto container: m_renderWatchers.keys()) {
if (m_renderWatchers.value(container)->watcher()->isFinished()) {
auto containerPtr = reinterpret_cast<quint64>(container.data());
removals.append(container);
m_imageCache.remove(containerPtr);
m_imageCache.insert(containerPtr, m_renderWatchers.value(container)->result());
}
}

for (auto removal : removals) {
m_renderWatchers.remove(removal);
}

// TODO: clean up cache using weak ref checks and least-recently-used size limit

repaint();
}

QImage PreviewScrollBar::renderPreview(QSharedPointer<BitContainer> container, QSharedPointer<ActionRenderProgress> progress)
{
int width = int(container->bitInfo()->maxFrameWidth() / 8);
int height = qMin(10000, container->frames().size());
QImage image(width, height, QImage::Format_ARGB32);
image.fill(qRgb(50, 50, 90));
QPainter imagePainter(&image);
imagePainter.setRenderHint(QPainter::Antialiasing, true);

QColor c = SettingsManager::getInstance().getUiSetting(SettingsData::BYTE_HUE_SAT_KEY).value<QColor>();
int hue = c.hue();
int saturation = c.saturation();
int chunkSize = qMin(container->frames().size(), 10000);
double chunkHeightRatio = double(chunkSize)/double(container->frames().size());
double targetChunkHeight = chunkHeightRatio * height;
QImage bufferChunk(width, chunkSize, QImage::Format_ARGB32);
for (int frame = 0; frame < container->frames().size(); frame += chunkSize) {
bufferChunk.fill(qRgb(0, 0, 0));
int offset = 0;
for (; offset < chunkSize && offset + frame < container->frames().size(); offset++) {
Frame f = container->frames().at(offset + frame);
qint64 byteOffset = f.start()/8;
for (int i = 0; i < f.size()/8 && byteOffset + i < container->bits()->sizeInBytes(); i++) {
char byteValue = container->bits()->byteAt(byteOffset + i);
c.setHsl(hue, saturation, reinterpret_cast<quint8&>(byteValue));
bufferChunk.setPixel(i, offset, c.rgba());
}
}
double heightRatio = double(offset)/double(chunkSize);
QRectF target(0, targetChunkHeight * (frame / chunkSize), width, heightRatio * targetChunkHeight);
QRectF source(0, 0, width, offset);
imagePainter.drawImage(target, bufferChunk, source);
progress->setRenderPreview(image);
}

return image;
}
52 changes: 52 additions & 0 deletions src/hobbits-core/previewscrollbar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef PREVIEWSCROLLBAR_H
#define PREVIEWSCROLLBAR_H

#include <QWidget>
#include <QFuture>
#include "bitcontainermanager.h"
#include "actionwatcher.h"
#include "actionrenderprogress.h"
#include "displayhandle.h"

class HOBBITSCORESHARED_EXPORT PreviewScrollBar : public QWidget
{
Q_OBJECT
public:
explicit PreviewScrollBar(QWidget *parent = nullptr);
int getFrameOffset();

void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;

signals:
void frameOffsetChanged(int);

public slots:
void setFrameOffset(int);
void setBitContainerManager(QSharedPointer<BitContainerManager> manager);
void setDisplayHandle(QSharedPointer<DisplayHandle> displayHandle);

private slots:
void newContainer();
void checkRenderWatchers();
void checkDisplayHandleOffset();

private:
void getOffsetFromEvent(QMouseEvent* event);
static QImage renderPreview(QSharedPointer<BitContainer> bits, QSharedPointer<ActionRenderProgress> progress);

int m_frameOffset = 0;
bool m_mousePressing = false;
QSharedPointer<BitContainerManager> m_manager;
QSharedPointer<DisplayHandle> m_displayHandle;

QHash<quint64, QWeakPointer<BitContainer>> m_weakRefMap;
QHash<quint64, QImage> m_imageCache;

QHash<QSharedPointer<BitContainer>, QSharedPointer<ActionWatcher<QImage>>> m_renderWatchers;
};

#endif // PREVIEWSCROLLBAR_H
5 changes: 5 additions & 0 deletions src/hobbits-gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ MainWindow::MainWindow(QString extraPluginPath, QString configFilePath, QWidget
m_pluginActionProgress(new QProgressBar()),
m_pluginActionCancel(new QPushButton()),
m_displayTabsSplitter(new QSplitter(Qt::Horizontal)),
m_previewScroll(new PreviewScrollBar()),
m_splitViewMenu(new QMenu("Split View"))
{
ui->setupUi(this);
Expand Down Expand Up @@ -113,6 +114,7 @@ MainWindow::MainWindow(QString extraPluginPath, QString configFilePath, QWidget


// Configure display handle and plugin callback
ui->displayScrollLayout->addWidget(m_previewScroll);
m_displayHandle = QSharedPointer<DisplayHandle>(
new DisplayHandle(
m_bitContainerManager,
Expand All @@ -124,6 +126,9 @@ MainWindow::MainWindow(QString extraPluginPath, QString configFilePath, QWidget
this,
&MainWindow::setHoverBit);

m_previewScroll->setBitContainerManager(m_bitContainerManager);
m_previewScroll->setDisplayHandle(m_displayHandle);

m_pluginCallback = QSharedPointer<PluginCallback>(new PluginCallback(m_displayHandle));
connect(
m_pluginCallback.data(),
Expand Down
Loading

0 comments on commit f61210b

Please sign in to comment.