Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix failing to preview small images #3520

Merged
merged 6 commits into from
Sep 29, 2023
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 74 additions & 21 deletions crates/re_data_ui/src/image.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use egui::{Color32, Vec2};
use egui::{Color32, NumExt, Vec2};
use itertools::Itertools as _;

use re_data_store::{InstancePathHash, VersionedInstancePathHash};
Expand Down Expand Up @@ -109,26 +109,27 @@ fn tensor_ui(
UiVerbosity::Small => {
ui.horizontal_centered(|ui| {
if let Some(texture) = &texture_result {
let max_height = 24.0;
let max_size = Vec2::new(4.0 * max_height, max_height);
show_image_at_max_size(
let preview_size = Vec2::splat(24.0);
show_image_preview(
ctx.render_ctx,
ctx.re_ui,
ui,
texture.clone(),
&debug_name,
max_size,
preview_size,
PreviewSizeShrink::DontShrink,
)
.on_hover_ui(|ui| {
// Show larger image on hover
let max_size = Vec2::splat(400.0);
show_image_at_max_size(
// Show larger image on hover.
let preview_size = Vec2::splat(400.0);
show_image_preview(
Wumpf marked this conversation as resolved.
Show resolved Hide resolved
ctx.render_ctx,
ctx.re_ui,
ui,
texture.clone(),
&debug_name,
max_size,
preview_size,
PreviewSizeShrink::ShrinkToTextureAspectRatio,
);
});
}
Expand Down Expand Up @@ -171,17 +172,18 @@ fn tensor_ui(
);

if let Some(texture) = &texture_result {
let max_size = ui
let preview_size = ui
.available_size()
.min(texture_size(texture))
.min(egui::vec2(150.0, 300.0));
let response = show_image_at_max_size(
let response = show_image_preview(
ctx.render_ctx,
ctx.re_ui,
ui,
texture.clone(),
&debug_name,
max_size,
preview_size,
PreviewSizeShrink::DontShrink,
);

if let Some(pointer_pos) = ui.ctx().pointer_latest_pos() {
Expand Down Expand Up @@ -230,26 +232,77 @@ fn texture_size(colormapped_texture: &ColormappedTexture) -> Vec2 {
egui::vec2(w as f32, h as f32)
}

fn show_image_at_max_size(
#[derive(Clone, Copy, PartialEq, Eq)]
enum PreviewSizeShrink {
/// Stick to the desired size. May cause
DontShrink,
emilk marked this conversation as resolved.
Show resolved Hide resolved

/// The allocated size may shrink in one dimension to fit the aspect ratio of the image.
ShrinkToTextureAspectRatio,
}

/// Shows preview of an image.
///
/// Displays the image in inside the desired size.
/// Image will be stretched in the dimensions it is below a certain threshold.
///
/// TODO(andreas): Small images will be blown up to the desired size, this may not always be what we want.
/// Maybe add `PreviewSizeShrink::ShrinkToFitWithMin(egui::Vec2)`?
fn show_image_preview(
render_ctx: &mut re_renderer::RenderContext,
re_ui: &ReUi,
ui: &mut egui::Ui,
colormapped_texture: ColormappedTexture,
debug_name: &str,
max_size: Vec2,
desired_size: egui::Vec2,
image_fit: PreviewSizeShrink,
) -> egui::Response {
let desired_size = {
let mut desired_size = texture_size(&colormapped_texture);
desired_size *= (max_size.x / desired_size.x).min(1.0);
desired_size *= (max_size.y / desired_size.y).min(1.0);
desired_size
let texture_size = texture_size(&colormapped_texture);

// First we figure out how much we should scale (uniformly) the image.
let texture_scale_factor = {
// Make the image at least this thick both x and y (unless desired_size explicitly asks for smaller)
const MIN_SIZE: f32 = 2.0;
let min_size = egui::Vec2::splat(MIN_SIZE).at_most(desired_size);

// Ideally, we just scale down/up to fit into the available space.
let zoom = (desired_size / texture_size).min_elem();

// But it can happen that this makes one of the axis too small, so let's stretch it if needed.
egui::Vec2::splat(zoom).at_least(min_size / texture_size)
};

let scaled_texture_size = texture_size * texture_scale_factor;

// Request the desired size, but shrink one dimension if the texture is too wide/narrow if so required.
let requested_rect = match image_fit {
PreviewSizeShrink::DontShrink => desired_size,
PreviewSizeShrink::ShrinkToTextureAspectRatio => {
let aspect_ratio_texture = scaled_texture_size.x / scaled_texture_size.y;
emilk marked this conversation as resolved.
Show resolved Hide resolved
let aspect_ratio_desired = desired_size.x / desired_size.y;

// Each of these factors would make the aspect ratio equal to the texture's aspect ratio.
let x_shrink = aspect_ratio_texture * aspect_ratio_desired;
let y_shrink = aspect_ratio_desired / aspect_ratio_texture;

if x_shrink < y_shrink {
egui::vec2(desired_size.x * x_shrink, desired_size.y)
} else {
egui::vec2(desired_size.x, desired_size.y * y_shrink)
}
}
};
let (response, painter) = ui.allocate_painter(requested_rect, egui::Sense::hover());

// Place the image with the determined zoom factor into the middle of the allocated rect.
// If it doesn't fit, it will be cropped by the painter's clip rect.
let texture_on_screen =
egui::Rect::from_center_size(response.rect.center(), scaled_texture_size);

let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::hover());
if let Err(err) = gpu_bridge::render_image(
render_ctx,
&painter,
response.rect,
texture_on_screen,
colormapped_texture,
egui::TextureOptions::LINEAR,
debug_name,
Expand Down
Loading