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 2 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
85 changes: 65 additions & 20 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,
false,
)
.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,
true,
);
Wumpf marked this conversation as resolved.
Show resolved Hide resolved
});
}
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,
false,
);

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

fn show_image_at_max_size(
/// Shows preview of an image at a given size.
///
/// Displays the image in inside the desired size.
/// This may both cause zoom in or zoom out of the image, but will never stretch it.
/// For extremely thin images, this may cause only parts of the image to be seen.
///
/// If `shrink_to_texture_aspect_ratio` is set, the allocated size may shrink in one dimension to fit the aspect ratio of the image.
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: Vec2,
shrink_to_texture_aspect_ratio: bool,
) -> 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);
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 = MIN_SIZE.at_most(desired_size.x).at_most(desired_size.x);

// 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 we need to zoom back in again.
// Note that this causes the image to be cropped.
zoom.at_least(min_size / texture_size.x.min(texture_size.y))
};

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 = if shrink_to_texture_aspect_ratio {
let clipped_texture = scaled_texture_size.min(desired_size);
let aspect_ratio_texture = clipped_texture.x / clipped_texture.y;
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)
}
} else {
desired_size
};
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