1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Bridge to `re_renderer`

mod colormap;
mod image_to_gpu;
mod re_renderer_callback;

pub use colormap::{colormap_edit_or_view_ui, colormap_to_re_renderer};
pub use image_to_gpu::{
    image_data_range_heuristic, image_to_gpu, required_shader_decode,
    texture_creation_desc_from_color_image,
};
pub use re_renderer_callback::new_renderer_callback;

use crate::TensorStats;

// ----------------------------------------------------------------------------

use re_renderer::{
    renderer::{ColormappedTexture, RectangleOptions},
    resource_managers::{
        GpuTexture2D, ImageDataDesc, ImageDataToTextureError, TextureManager2DError,
    },
    RenderContext, ViewBuilder,
};

// ----------------------------------------------------------------------------

/// Return whether a tensor should be assumed to be encoded in sRGB color space ("gamma space", no EOTF applied).
pub fn tensor_decode_srgb_gamma_heuristic(
    tensor_stats: &TensorStats,
    data_type: re_types::tensor_data::TensorDataType,
    channels: u32,
) -> bool {
    if matches!(channels, 1 | 3 | 4) {
        let (min, max) = tensor_stats.finite_range;
        #[allow(clippy::if_same_then_else)]
        if 0.0 <= min && max <= 255.0 {
            // If the range is suspiciously reminding us of a "regular image", assume sRGB.
            true
        } else if data_type.is_float() && 0.0 <= min && max <= 1.0 {
            // Floating point images between 0 and 1 are often sRGB as well.
            true
        } else {
            false
        }
    } else {
        false
    }
}

// ----------------------------------------------------------------------------

pub fn viewport_resolution_in_pixels(clip_rect: egui::Rect, pixels_per_point: f32) -> [u32; 2] {
    let min = (clip_rect.min.to_vec2() * pixels_per_point).round();
    let max = (clip_rect.max.to_vec2() * pixels_per_point).round();
    let resolution = max - min;
    [resolution.x as u32, resolution.y as u32]
}

pub fn try_get_or_create_texture<'a, Err: std::fmt::Display>(
    render_ctx: &RenderContext,
    texture_key: u64,
    try_create_texture_desc: impl FnOnce() -> Result<ImageDataDesc<'a>, Err>,
) -> Result<GpuTexture2D, TextureManager2DError<Err>> {
    render_ctx.texture_manager_2d.get_or_try_create_with(
        texture_key,
        render_ctx,
        try_create_texture_desc,
    )
}

pub fn get_or_create_texture<'a>(
    render_ctx: &RenderContext,
    texture_key: u64,
    create_texture_desc: impl FnOnce() -> ImageDataDesc<'a>,
) -> Result<GpuTexture2D, ImageDataToTextureError> {
    render_ctx
        .texture_manager_2d
        .get_or_create_with(texture_key, render_ctx, create_texture_desc)
}

/// Render the given image, respecting the clip rectangle of the given painter.
pub fn render_image(
    render_ctx: &re_renderer::RenderContext,
    egui_painter: &egui::Painter,
    image_rect_on_screen: egui::Rect,
    colormapped_texture: ColormappedTexture,
    texture_options: egui::TextureOptions,
    debug_name: re_renderer::DebugLabel,
) -> anyhow::Result<()> {
    re_tracing::profile_function!();

    use re_renderer::renderer::{TextureFilterMag, TextureFilterMin};

    let viewport = egui_painter.clip_rect().intersect(image_rect_on_screen);
    if !viewport.is_positive() {
        return Ok(());
    }

    // Where in "world space" to paint the image.
    // This is an arbitrary selection.
    let space_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, image_rect_on_screen.size());

    let textured_rectangle = re_renderer::renderer::TexturedRect {
        top_left_corner_position: glam::vec3(space_rect.min.x, space_rect.min.y, 0.0),
        extent_u: glam::Vec3::X * space_rect.width(),
        extent_v: glam::Vec3::Y * space_rect.height(),
        colormapped_texture,
        options: RectangleOptions {
            texture_filter_magnification: match texture_options.magnification {
                egui::TextureFilter::Nearest => TextureFilterMag::Nearest,
                egui::TextureFilter::Linear => TextureFilterMag::Linear,
            },
            texture_filter_minification: match texture_options.minification {
                egui::TextureFilter::Nearest => TextureFilterMin::Nearest,
                egui::TextureFilter::Linear => TextureFilterMin::Linear,
            },
            multiplicative_tint: egui::Rgba::WHITE,
            ..Default::default()
        },
    };

    // ------------------------------------------------------------------------

    let pixels_per_point = egui_painter.ctx().pixels_per_point();
    let ui_from_space = egui::emath::RectTransform::from_to(space_rect, image_rect_on_screen);
    let space_from_ui = ui_from_space.inverse();
    let space_from_points = space_from_ui.scale().y;
    let points_from_pixels = 1.0 / egui_painter.ctx().pixels_per_point();
    let space_from_pixel = space_from_points * points_from_pixels;

    let resolution_in_pixel = viewport_resolution_in_pixels(viewport, pixels_per_point);
    anyhow::ensure!(resolution_in_pixel[0] > 0 && resolution_in_pixel[1] > 0);

    let camera_position_space = space_from_ui.transform_pos(viewport.min);

    let top_left_position = glam::vec2(camera_position_space.x, camera_position_space.y);

    let target_config = re_renderer::view_builder::TargetConfiguration {
        name: debug_name,
        resolution_in_pixel,
        view_from_world: re_math::IsoTransform::from_translation(-top_left_position.extend(0.0)),
        projection_from_view: re_renderer::view_builder::Projection::Orthographic {
            camera_mode: re_renderer::view_builder::OrthographicCameraMode::TopLeftCornerAndExtendZ,
            vertical_world_size: space_from_pixel * resolution_in_pixel[1] as f32,
            far_plane_distance: 1000.0,
        },
        viewport_transformation: re_renderer::RectTransform::IDENTITY,
        pixels_per_point,
        outline_config: None,
        blend_with_background: false,
    };

    let mut view_builder = ViewBuilder::new(render_ctx, target_config);

    view_builder.queue_draw(re_renderer::renderer::RectangleDrawData::new(
        render_ctx,
        &[textured_rectangle],
    )?);

    egui_painter.add(new_renderer_callback(
        view_builder,
        viewport,
        re_renderer::Rgba::TRANSPARENT,
    ));

    Ok(())
}