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 <QBuffer>
#include <QButtonGroup>
#include <QDateTime>
#include <QMouseEvent>
#include <QPainter>
#include <QPixmap>
#include <QStyleOptionSlider>
#include <QTimeEdit>
#include <QToolTip>
#include <QVBoxLayout>
#include <QtConcurrent>
const int MIN_VIDEO_HEIGHT = 100;
const int THUMBNAIL_MARGIN = 3;
static const QColor timeline_colors[] = {
[(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");
}
VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0);
QFrame *frame = new QFrame(this);
frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
main_layout->addWidget(frame);
QVBoxLayout *frame_layout = new QVBoxLayout(frame);
VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
auto main_layout = new QVBoxLayout(this);
if (!can->liveStreaming()) {
frame_layout->addWidget(createCameraWidget());
main_layout->addWidget(createCameraWidget());
}
// btn controls
@ -57,7 +49,8 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) {
group->addButton(btn);
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(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
@ -91,12 +84,13 @@ QWidget *VideoWidget::createCameraWidget() {
QVBoxLayout *l = new QVBoxLayout(w);
l->setContentsMargins(0, 0, 0, 0);
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);
// slider controls
slider_layout = new QHBoxLayout();
time_label = new ElidedLabel("00:00");
time_label->setToolTip(tr("Click to set current time"));
time_label = new QLabel("00:00");
slider_layout->addWidget(time_label);
slider = new Slider(this);
@ -106,12 +100,6 @@ QWidget *VideoWidget::createCameraWidget() {
end_time_label = new QLabel(this);
slider_layout->addWidget(end_time_label);
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::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); });
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
@ -123,29 +111,7 @@ QWidget *VideoWidget::createCameraWidget() {
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) {
if (can->liveStreaming()) return;
if (!is_zoomed) {
min = 0;
max = can->totalSeconds();
@ -165,7 +131,7 @@ void VideoWidget::updatePlayBtnState() {
}
// 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]() {
timeline = can->getTimeline();
update();
@ -198,29 +164,18 @@ void Slider::loadThumbnails() {
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) {
auto thumb = (*ev)->event.getThumbnail();
QString str = getThumbnailString(thumb.getThumbnail());
std::lock_guard lk(thumbnail_lock);
thumbnails[thumb.getTimestampEof()] = str;
auto data = thumb.getThumbnail();
if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) {
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) {
if (change == QAbstractSlider::SliderValueChange) {
int x = width() * ((value() - minimum()) / double(maximum() - minimum()));
@ -266,21 +221,45 @@ void Slider::mousePressEvent(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);
auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9);
if (it != thumbnails.end()) {
thumb = it.value();
}
if (it != thumbnails.end()) thumb = it.value();
}
QPoint pt = mapToGlobal({e->pos().x() - thumbnail_size.width() / 2, -thumbnail_size.height() - 30});
QToolTip::showText(pt, thumb, this, rect());
int x = std::clamp(e->pos().x() - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN);
int y = -thumb.height() - THUMBNAIL_MARGIN - style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
thumbnail_label.showPixmap(mapToGlobal({x, y}), formatTime(seconds), thumb);
QSlider::mouseMoveEvent(e);
}
void Slider::leaveEvent(QEvent *event) {
QToolTip::hideText();
thumbnail_label.hide();
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 "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"
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 {
Q_OBJECT
@ -30,19 +37,18 @@ private:
void paintEvent(QPaintEvent *ev) override;
void streamStarted();
void loadThumbnails();
QString getThumbnailString(const capnp::Data::Reader &data);
int slider_x = -1;
std::vector<std::tuple<int, int, TimelineType>> timeline;
std::mutex thumbnail_lock;
std::atomic<bool> abort_load_thumbnail = false;
QMap<uint64_t, QString> thumbnails;
QMap<uint64_t, QPixmap> thumbnails;
QFuture<void> thumnail_future;
QSize thumbnail_size = {};
ThumbnailLabel thumbnail_label;
QTimer timer;
};
class VideoWidget : public QWidget {
class VideoWidget : public QFrame {
Q_OBJECT
public:
@ -52,12 +58,11 @@ public:
protected:
void updateState();
void updatePlayBtnState();
void timeLabelClicked();
QWidget *createCameraWidget();
CameraWidget *cam_widget;
QLabel *end_time_label;
ElidedLabel *time_label;
QLabel *time_label;
QHBoxLayout *slider_layout;
QPushButton *play_btn;
Slider *slider;

Loading…
Cancel
Save