cabana: improve video thumbnail (#27711)

pull/27720/head
Dean Lee 2 years ago committed by GitHub
parent 8903e03c88
commit a1fb8d2480
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 119
      tools/cabana/videowidget.cc
  2. 21
      tools/cabana/videowidget.h

@ -1,18 +1,15 @@
#include "tools/cabana/videowidget.h" #include "tools/cabana/videowidget.h"
#include <QBuffer>
#include <QButtonGroup> #include <QButtonGroup>
#include <QDateTime> #include <QDateTime>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
#include <QPixmap>
#include <QStyleOptionSlider> #include <QStyleOptionSlider>
#include <QTimeEdit>
#include <QToolTip>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QtConcurrent> #include <QtConcurrent>
const int MIN_VIDEO_HEIGHT = 100; const int MIN_VIDEO_HEIGHT = 100;
const int THUMBNAIL_MARGIN = 3;
static const QColor timeline_colors[] = { static const QColor timeline_colors[] = {
[(int)TimelineType::None] = QColor(111, 143, 175), [(int)TimelineType::None] = QColor(111, 143, 175),
@ -27,16 +24,11 @@ static inline QString formatTime(int seconds) {
return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
} }
VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
main_layout->setContentsMargins(0, 0, 0, 0); auto main_layout = new QVBoxLayout(this);
QFrame *frame = new QFrame(this);
frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
main_layout->addWidget(frame);
QVBoxLayout *frame_layout = new QVBoxLayout(frame);
if (!can->liveStreaming()) { if (!can->liveStreaming()) {
frame_layout->addWidget(createCameraWidget()); main_layout->addWidget(createCameraWidget());
} }
// btn controls // btn controls
@ -57,7 +49,8 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
group->addButton(btn); group->addButton(btn);
if (speed == 1.0) btn->setChecked(true); if (speed == 1.0) btn->setChecked(true);
} }
frame_layout->addLayout(control_layout); main_layout->addLayout(control_layout);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QObject::connect(play_btn, &QPushButton::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(play_btn, &QPushButton::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
@ -91,12 +84,13 @@ QWidget *VideoWidget::createCameraWidget() {
QVBoxLayout *l = new QVBoxLayout(w); QVBoxLayout *l = new QVBoxLayout(w);
l->setContentsMargins(0, 0, 0, 0); l->setContentsMargins(0, 0, 0, 0);
cam_widget = new CameraWidget("camerad", can->visionStreamType(), false); cam_widget = new CameraWidget("camerad", can->visionStreamType(), false);
cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT);
cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
l->addWidget(cam_widget); l->addWidget(cam_widget);
// slider controls // slider controls
slider_layout = new QHBoxLayout(); slider_layout = new QHBoxLayout();
time_label = new ElidedLabel("00:00"); time_label = new QLabel("00:00");
time_label->setToolTip(tr("Click to set current time"));
slider_layout->addWidget(time_label); slider_layout->addWidget(time_label);
slider = new Slider(this); slider = new Slider(this);
@ -106,12 +100,6 @@ QWidget *VideoWidget::createCameraWidget() {
end_time_label = new QLabel(this); end_time_label = new QLabel(this);
slider_layout->addWidget(end_time_label); slider_layout->addWidget(end_time_label);
l->addLayout(slider_layout); l->addLayout(slider_layout);
cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT);
cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QObject::connect(time_label, &ElidedLabel::clicked, this, &VideoWidget::timeLabelClicked);
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); });
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); });
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
@ -123,29 +111,7 @@ QWidget *VideoWidget::createCameraWidget() {
return w; return w;
} }
void VideoWidget::timeLabelClicked() {
auto time_edit = new QTimeEdit(this);
auto init_date_time = can->currentDateTime();
time_edit->setDateTime(init_date_time);
time_edit->setDisplayFormat("hh:mm:ss");
time_label->setVisible(false);
slider_layout->insertWidget(0, time_edit);
QTimer::singleShot(0, [=]() { time_edit->setFocus(); });
QObject::connect(time_edit, &QTimeEdit::editingFinished, [=]() {
if (time_edit->dateTime() != init_date_time) {
int seconds = can->route()->datetime().secsTo(time_edit->dateTime());
can->seekTo(seconds);
}
time_edit->setVisible(false);
time_label->setVisible(true);
time_edit->deleteLater();
});
}
void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) {
if (can->liveStreaming()) return;
if (!is_zoomed) { if (!is_zoomed) {
min = 0; min = 0;
max = can->totalSeconds(); max = can->totalSeconds();
@ -165,7 +131,7 @@ void VideoWidget::updatePlayBtnState() {
} }
// Slider // Slider
Slider::Slider(QWidget *parent) : timer(this), QSlider(Qt::Horizontal, parent) { Slider::Slider(QWidget *parent) : timer(this), thumbnail_label(this), QSlider(Qt::Horizontal, parent) {
timer.callOnTimeout([this]() { timer.callOnTimeout([this]() {
timeline = can->getTimeline(); timeline = can->getTimeline();
update(); update();
@ -198,29 +164,18 @@ void Slider::loadThumbnails() {
if (log.load(qlog, &abort_load_thumbnail, {cereal::Event::Which::THUMBNAIL}, true, 0, 3)) { if (log.load(qlog, &abort_load_thumbnail, {cereal::Event::Which::THUMBNAIL}, true, 0, 3)) {
for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_load_thumbnail; ++ev) { for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_load_thumbnail; ++ev) {
auto thumb = (*ev)->event.getThumbnail(); auto thumb = (*ev)->event.getThumbnail();
QString str = getThumbnailString(thumb.getThumbnail()); auto data = thumb.getThumbnail();
std::lock_guard lk(thumbnail_lock); if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) {
thumbnails[thumb.getTimestampEof()] = str; pm = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation);
std::lock_guard lk(thumbnail_lock);
thumbnails[thumb.getTimestampEof()] = pm;
}
} }
} }
} }
} }
} }
QString Slider::getThumbnailString(const capnp::Data::Reader &data) {
QPixmap thumb;
if (thumb.loadFromData(data.begin(), data.size(), "jpeg")) {
thumb = thumb.scaled({thumb.width()/3, thumb.height()/3}, Qt::KeepAspectRatio);
thumbnail_size = thumb.size();
QByteArray bytes;
QBuffer buffer(&bytes);
buffer.open(QIODevice::WriteOnly);
thumb.save(&buffer, "png");
return QString("<img src='data:image/png;base64, %0'>").arg(QString(bytes.toBase64()));
}
return {};
}
void Slider::sliderChange(QAbstractSlider::SliderChange change) { void Slider::sliderChange(QAbstractSlider::SliderChange change) {
if (change == QAbstractSlider::SliderValueChange) { if (change == QAbstractSlider::SliderValueChange) {
int x = width() * ((value() - minimum()) / double(maximum() - minimum())); int x = width() * ((value() - minimum()) / double(maximum() - minimum()));
@ -266,21 +221,45 @@ void Slider::mousePressEvent(QMouseEvent *e) {
} }
void Slider::mouseMoveEvent(QMouseEvent *e) { void Slider::mouseMoveEvent(QMouseEvent *e) {
QString thumb; QPixmap thumb;
double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0;
{ {
double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0;
std::lock_guard lk(thumbnail_lock); std::lock_guard lk(thumbnail_lock);
auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9); auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9);
if (it != thumbnails.end()) { if (it != thumbnails.end()) thumb = it.value();
thumb = it.value();
}
} }
QPoint pt = mapToGlobal({e->pos().x() - thumbnail_size.width() / 2, -thumbnail_size.height() - 30}); int x = std::clamp(e->pos().x() - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN);
QToolTip::showText(pt, thumb, this, rect()); int y = -thumb.height() - THUMBNAIL_MARGIN - style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
thumbnail_label.showPixmap(mapToGlobal({x, y}), formatTime(seconds), thumb);
QSlider::mouseMoveEvent(e); QSlider::mouseMoveEvent(e);
} }
void Slider::leaveEvent(QEvent *event) { void Slider::leaveEvent(QEvent *event) {
QToolTip::hideText(); thumbnail_label.hide();
QSlider::leaveEvent(event); QSlider::leaveEvent(event);
} }
// ThumbnailLabel
ThumbnailLabel::ThumbnailLabel(QWidget *parent) : QWidget(parent, Qt::Tool | Qt::FramelessWindowHint) {
setAttribute(Qt::WA_ShowWithoutActivating);
setVisible(false);
}
void ThumbnailLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm) {
pixmap = pm;
second = sec;
setVisible(!pm.isNull());
if (isVisible()) {
setGeometry({pt, pm.size()});
update();
}
}
void ThumbnailLabel::paintEvent(QPaintEvent *event) {
QPainter p(this);
p.drawPixmap(0, 0, pixmap);
p.setPen(QPen(Qt::white, 2));
p.drawRect(rect());
p.drawText(rect().adjusted(0, 0, 0, -THUMBNAIL_MARGIN), second, Qt::AlignHCenter | Qt::AlignBottom);
}

@ -11,10 +11,17 @@
#include <QTimer> #include <QTimer>
#include "selfdrive/ui/qt/widgets/cameraview.h" #include "selfdrive/ui/qt/widgets/cameraview.h"
#include "selfdrive/ui/qt/widgets/controls.h"
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/streams/abstractstream.h" #include "tools/cabana/streams/abstractstream.h"
class ThumbnailLabel : public QWidget {
public:
ThumbnailLabel(QWidget *parent);
void showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm);
void paintEvent(QPaintEvent *event) override;
QPixmap pixmap;
QString second;
};
class Slider : public QSlider { class Slider : public QSlider {
Q_OBJECT Q_OBJECT
@ -30,19 +37,18 @@ private:
void paintEvent(QPaintEvent *ev) override; void paintEvent(QPaintEvent *ev) override;
void streamStarted(); void streamStarted();
void loadThumbnails(); void loadThumbnails();
QString getThumbnailString(const capnp::Data::Reader &data);
int slider_x = -1; int slider_x = -1;
std::vector<std::tuple<int, int, TimelineType>> timeline; std::vector<std::tuple<int, int, TimelineType>> timeline;
std::mutex thumbnail_lock; std::mutex thumbnail_lock;
std::atomic<bool> abort_load_thumbnail = false; std::atomic<bool> abort_load_thumbnail = false;
QMap<uint64_t, QString> thumbnails; QMap<uint64_t, QPixmap> thumbnails;
QFuture<void> thumnail_future; QFuture<void> thumnail_future;
QSize thumbnail_size = {}; ThumbnailLabel thumbnail_label;
QTimer timer; QTimer timer;
}; };
class VideoWidget : public QWidget { class VideoWidget : public QFrame {
Q_OBJECT Q_OBJECT
public: public:
@ -52,12 +58,11 @@ public:
protected: protected:
void updateState(); void updateState();
void updatePlayBtnState(); void updatePlayBtnState();
void timeLabelClicked();
QWidget *createCameraWidget(); QWidget *createCameraWidget();
CameraWidget *cam_widget; CameraWidget *cam_widget;
QLabel *end_time_label; QLabel *end_time_label;
ElidedLabel *time_label; QLabel *time_label;
QHBoxLayout *slider_layout; QHBoxLayout *slider_layout;
QPushButton *play_btn; QPushButton *play_btn;
Slider *slider; Slider *slider;

Loading…
Cancel
Save