cabana: fill video pane and reset video height (#38786)

This commit is contained in:
Trey Moen
2026-09-05 17:02:05 -07:00
committed by GitHub
parent cb0da74b41
commit 3bc6701f34
8 changed files with 54 additions and 21 deletions
+1
View File
@@ -21,6 +21,7 @@ struct CabanaSettingsState {
bool multiple_lines_hex = false;
bool log_livestream = true;
bool suppress_defined_signals = false;
bool crop_video = true;
std::string log_path;
std::string last_dir;
std::string last_route_dir;
+1
View File
@@ -488,6 +488,7 @@ void settingsOp(Store &s, SettingOperation op) {
op(s, "log_path", settings.log_path);
op(s, "drag_direction", (int &)settings.drag_direction);
op(s, "suppress_defined_signals", settings.suppress_defined_signals);
op(s, "crop_video", settings.crop_video);
op(s, "recent_dbc_file", settings.recent_dbc_file);
op(s, "active_msg_id", settings.active_msg_id);
op(s, "selected_msg_ids", settings.selected_msg_ids);
+2
View File
@@ -8,6 +8,8 @@ constexpr const char ARROW_DOWN_LEFT_SQUARE[] = "\xef\x84\x9c";
constexpr const char ARROW_UP_RIGHT_SQUARE[] = "\xef\x85\x82";
constexpr const char CHEVRON_LEFT[] = "\xef\x8a\x84";
constexpr const char CHEVRON_RIGHT[] = "\xef\x8a\x85";
constexpr const char ASPECT_RATIO[] = "\xef\x85\x90";
constexpr const char ASPECT_RATIO_FILL[] = "\xef\x85\x8f";
constexpr const char DASH[] = "\xef\x8b\xaa";
constexpr const char DASH_SQUARE[] = "\xef\x8b\xa8";
constexpr const char EXCLAMATION_TRIANGLE[] = "\xef\x8c\xba";
+1
View File
@@ -156,6 +156,7 @@ void MainWindow::drawMenuBar() {
ImGui::Separator();
if (ImGui::MenuItem("Reset Window Layout")) {
messages_visible_ = video_visible_ = true;
video_splitter_ratio_ = -1.0f;
reset_layout_ = true;
}
ImGui::EndMenu();
@@ -9,6 +9,7 @@
#include "common/yuv.h"
#include "tools/cabana/utils/util.h"
#include "tools/cabana/settings.h"
namespace {
constexpr GLenum GL_LINEAR_MIPMAP_LINEAR_ = 0x2703;
@@ -107,21 +108,12 @@ void CameraWidget::paint() {
frame_updated_ = false;
}
// Scale for aspect ratio
float widget_ratio = (float)width() / height();
float frame_ratio = (float)rgb_frame_.width / rgb_frame_.height;
int w = std::lround(width() * std::min(frame_ratio / widget_ratio, 1.0f));
int h = std::lround(height() * std::min(widget_ratio / frame_ratio, 1.0f));
ImVec2 video_min(rect_.Min.x + (int)(width() - w) / 2, rect_.Min.y + (int)(height() - h) / 2);
ImVec2 video_max(video_min.x + w, video_min.y + h);
ImVec2 uv0(0, 0), uv1(1, 1);
VideoPlacement placement = videoPlacement(rect_, frameAspectRatio(), settings.crop_video);
if (active_stream_type_ == VISION_STREAM_CABIN) {
// mirror cabin camera horizontally
uv0.x = 1;
uv1.x = 0;
std::swap(placement.uv0.x, placement.uv1.x);
}
p->AddImage(frame_texture_.ref(), video_min, video_max, uv0, uv1);
p->AddImage(frame_texture_.ref(), placement.min, placement.max, placement.uv0, placement.uv1);
}
void CameraWidget::vipcThread() {
@@ -1,6 +1,8 @@
#pragma once
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdint>
#include <memory>
#include <mutex>
@@ -16,6 +18,35 @@
#include "tools/cabana/core/observable.h"
#include "msgq/visionipc/visionipc_client.h"
// Center-crop the source to fill the destination without stretching.
inline ImVec2 videoFillUv(const ImVec2 &size, float aspect_ratio) {
const float ratio = size.x / size.y / aspect_ratio;
return ImVec2(0.5f * (1.0f - std::min(ratio, 1.0f)),
0.5f * (1.0f - std::min(1.0f / ratio, 1.0f)));
}
struct VideoPlacement {
ImVec2 min;
ImVec2 max;
ImVec2 uv0;
ImVec2 uv1;
};
inline VideoPlacement videoPlacement(const ImRect &rect, float source_aspect_ratio, bool crop) {
VideoPlacement placement{rect.Min, rect.Max, ImVec2(0, 0), ImVec2(1, 1)};
if (crop) {
placement.uv0 = videoFillUv(rect.GetSize(), source_aspect_ratio);
placement.uv1 = ImVec2(1.0f - placement.uv0.x, 1.0f - placement.uv0.y);
} else {
const float widget_aspect_ratio = rect.GetWidth() / rect.GetHeight();
const int width = std::lround(rect.GetWidth() * std::min(source_aspect_ratio / widget_aspect_ratio, 1.0f));
const int height = std::lround(rect.GetHeight() * std::min(widget_aspect_ratio / source_aspect_ratio, 1.0f));
placement.min = ImVec2(rect.Min.x + (int)(rect.GetWidth() - width) / 2, rect.Min.y + (int)(rect.GetHeight() - height) / 2);
placement.max = ImVec2(placement.min.x + width, placement.min.y + height);
}
return placement;
}
// tightly packed RGBA pixels
struct RgbImage {
int width = 0;
@@ -197,10 +197,14 @@ void VideoWidget::drawPlaybackController() {
item.in_menu = false;
return item;
};
const char *aspect_ratio_icon = settings.crop_video ? icon::ASPECT_RATIO_FILL : icon::ASPECT_RATIO;
items.push_back({toolbarButtonWidth(aspect_ratio_icon), [&]() {
if (toolButton("crop_video", aspect_ratio_icon, "Crop to fill")) cropVideoClicked();
}, "Crop to fill", [this]() { cropVideoClicked(); }});
if (!can->liveStreaming()) {
items.push_back(separator());
items.push_back({toolbarButtonWidth(loop_icon), [&]() { if (toolButton("loop", loop_icon, "Loop playback")) loopPlaybackClicked(); },
"Loop playback", [this]() { loopPlaybackClicked(); }});
items.push_back(separator());
}
items.push_back({speed_width, [&]() { drawSpeedDropdown(speed_width); }});
if (!can->liveStreaming()) {
@@ -313,6 +317,11 @@ void VideoWidget::loopPlaybackClicked() {
getReplay()->setLoop(!getReplay()->loop());
}
void VideoWidget::cropVideoClicked() {
settings.crop_video = !settings.crop_video;
settings.changed();
}
void VideoWidget::timeRangeChanged() {
const auto time_range = can->timeRange();
if (can->liveStreaming()) {
@@ -551,14 +560,9 @@ const RgbImage *StreamCameraView::thumbnailAt(double sec) {
void StreamCameraView::drawScrubThumbnail(ImDrawList *p, double sec) {
p->AddRectFilled(rect().Min, rect().Max, IM_COL32(0, 0, 0, 255));
if (const RgbImage *image = thumbnailAt(sec)) {
// scale to the widget size, keeping the aspect ratio
const float scale = std::min(width() / image->width, height() / image->height);
const ImVec2 scaled_size(std::floor(image->width * scale), std::floor(image->height * scale));
const ImVec2 center = rect().GetCenter();
const ImVec2 thumb_min(center.x - (int)(scaled_size.x / 2), center.y - (int)(scaled_size.y / 2));
ImRect thumb_rect(thumb_min, ImVec2(thumb_min.x + scaled_size.x, thumb_min.y + scaled_size.y));
p->AddImage(big_thumbnail_texture_.ref(), thumb_rect.Min, thumb_rect.Max);
drawTime(p, thumb_rect, sec);
const VideoPlacement placement = videoPlacement(rect(), (float)image->width / image->height, settings.crop_video);
p->AddImage(big_thumbnail_texture_.ref(), placement.min, placement.max, placement.uv0, placement.uv1);
drawTime(p, rect(), sec);
}
}
@@ -105,6 +105,7 @@ private:
void drawSpeedDropdown(float width);
void drawSpeedMenuItems();
void loopPlaybackClicked();
void cropVideoClicked();
void vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams);
void showRouteInfo();