mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-18 10:23:43 +08:00
cabana: fill video pane and reset video height (#38786)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user