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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use eframe::egui;
use eframe::emath::GuiRounding as _;
use crate::rect_helpers::{debug_paint, quantized_intersection, rotate_aabb};
use maps_io_ros::ValueInterpretation;
pub const NO_TINT: egui::Color32 = egui::Color32::WHITE;
/// Specifies a request to render an image as a texture inside a desired rectangle
/// with various display options.
#[derive(Debug)]
pub struct TextureRequest {
/// ID of the requesting client, for texture memory management.
pub client: String,
/// The rectangle into which the texture shall be scaled and placed to.
pub desired_rect: egui::Rect,
/// Color tint of the texture.
pub tint: egui::Color32,
/// Color of the image that shall be displayed as transparent.
pub color_to_alpha: Option<egui::Color32>,
/// Optional value-interpretation-based thresholding of the image.
pub thresholding: Option<ValueInterpretation>,
/// UI interactions that shall be registered by the image display.
pub sense: egui::Sense,
/// Optional overrides for texture rendering options.
pub texture_options: Option<egui::TextureOptions>,
}
impl TextureRequest {
pub fn new(client: String, desired_rect: egui::Rect) -> TextureRequest {
TextureRequest {
client,
desired_rect,
tint: NO_TINT,
color_to_alpha: None,
thresholding: None,
sense: egui::Sense::hover(),
texture_options: None,
}
}
pub fn with_sense(mut self, sense: egui::Sense) -> TextureRequest {
self.sense = sense;
self
}
pub fn with_tint(mut self, tint: Option<egui::Color32>) -> TextureRequest {
match tint {
Some(tint) => {
self.tint = tint;
}
None => {
self.tint = NO_TINT;
}
}
self
}
pub fn with_color_to_alpha(mut self, color_to_alpha: Option<egui::Color32>) -> TextureRequest {
self.color_to_alpha = color_to_alpha;
self
}
pub fn with_thresholding(
mut self,
thresholding: Option<&ValueInterpretation>,
) -> TextureRequest {
self.thresholding = thresholding.copied();
self
}
pub fn with_texture_options(mut self, texture_options: egui::TextureOptions) -> TextureRequest {
self.texture_options = Some(texture_options);
self
}
}
/// Extended request for rendering scaled textures with arbitrary rotated pose
/// and support for cropping (e.g. to viewport).
#[derive(Debug)]
pub struct TransformedTextureRequest {
/// Base texture request for the bare unrotated & untransformed image rect.
pub base_request: TextureRequest,
/// Rectangle in the scaled image coordinate space defining the
/// potentially cropped region to extract before applying transformations.
pub crop_rect: egui::Rect,
/// Image crop specified in UV image coordinates.
pub crop_uv: [egui::Pos2; 2],
/// Desired rotation of the texture.
pub rotation: eframe::emath::Rot2,
/// Desired translation of the texture.
pub translation: egui::Vec2,
/// Rotation center of the image in UV image coordinates.
pub rotation_center_in_uv: egui::Vec2,
/// Scale of the texture, i.e. desired screen points per texel.
pub points_per_texel: f32,
}
/// Information needed for placement of an image as a scaled texture at a 2D pose.
#[derive(Debug)]
pub struct ImagePlacement {
pub rotation: egui::emath::Rot2,
/// Position of the upper left image corner in points relative to the viewport.
pub translation: egui::Vec2,
/// Position of the image's rotation center in points relative to the viewport.
pub rotation_center: egui::Vec2,
/// Amount of points occupied by a texel of the image, for scaling.
pub points_per_texel: f32,
/// Size of the unscaled, uncropped source image in pixels.
pub original_image_size: egui::Vec2,
}
impl TransformedTextureRequest {
/// Returns true if this request represents a full texture (not a crop).
pub fn is_full_texture(&self) -> bool {
self.crop_uv[0] == egui::Pos2::ZERO && self.crop_uv[1] == egui::pos2(1.0, 1.0)
}
/// Pre-calculate the minimal, unrotated crop that is needed to show the rotated surface in the viewport.
/// I.e. neither clipping too much nor making the texture unnecessarily large / inefficient.
/// Enable trace log level to see what is going on (I spent too much time figuring this out).
fn min_crop(
paint_context: &egui::Painter,
scaled_rect: &egui::Rect,
rotation: eframe::emath::Rot2,
translation: egui::Vec2,
rotation_center_in_points: egui::Vec2,
points_per_texel: f32,
) -> egui::Rect {
let origin_in_points = (scaled_rect.min - rotation_center_in_points).to_vec2();
let transformed_aabb =
rotate_aabb(scaled_rect, rotation, origin_in_points).translate(translation);
debug_paint(
paint_context,
transformed_aabb,
egui::Color32::RED,
"transformed_aabb",
);
let transformed_aabb_visible = transformed_aabb.intersect(paint_context.clip_rect());
debug_paint(
paint_context,
transformed_aabb_visible,
egui::Color32::GOLD,
"transformed_aabb_visible",
);
let min_crop = rotate_aabb(
&transformed_aabb_visible.translate(-translation),
rotation.inverse(),
origin_in_points,
);
let ui_offset = paint_context.clip_rect().min.to_vec2();
debug_paint(
paint_context,
min_crop.translate(ui_offset),
egui::Color32::BLUE,
"min_crop",
);
// The minimal rectangle is the instersection of crop rectangle and image rectangle.
// The image cropping happens in pixel space, so we have to also quantize the rectangle
// to the next best multiple of the scaled pixel size.
// Otherwise the texture size/placement is not exact, especially at high zoom levels.
let crop_rect = quantized_intersection(scaled_rect, &min_crop, points_per_texel);
// Round crop_rect matching egui 0.32's "pixel-perfect" paint_at behavior.
// See also: https://github.com/emilk/egui/pull/7078
let crop_rect = crop_rect.round_to_pixels(paint_context.ctx().pixels_per_point());
debug_paint(
paint_context,
crop_rect.translate(ui_offset),
egui::Color32::GREEN,
"crop_rect_quantized",
);
crop_rect
}
/// Creates a request for displaying an image with the desired `placement`
/// in the visible viewport of the `ui`.
/// `crop_threshold` controls the maximum size of a texture before it gets
/// cropped to the viewport. Use this to support displaying large images
/// at high zoom levels as cropped textures to avoid texture buffer size limits.
pub fn from_visible(
paint_context: &egui::Painter,
base_request: TextureRequest,
placement: &ImagePlacement,
crop_threshold: u32,
) -> TransformedTextureRequest {
let scaled_rect = base_request.desired_rect;
let crop_rect = if scaled_rect.size().max_elem() as u32 <= crop_threshold
|| placement.original_image_size.max_elem() as u32 <= crop_threshold
{
// Desired texture is small enough to not need cropping.
scaled_rect
} else {
// Desired texture is large, crop to the viewport.
Self::min_crop(
paint_context,
&scaled_rect,
placement.rotation,
placement.translation,
placement.rotation_center,
placement.points_per_texel,
)
};
TransformedTextureRequest {
base_request,
crop_rect,
crop_uv: [
egui::Pos2::new(
(crop_rect.min.x - scaled_rect.min.x) / scaled_rect.width(),
(crop_rect.min.y - scaled_rect.min.y) / scaled_rect.height(),
),
egui::Pos2::new(
(crop_rect.max.x - scaled_rect.min.x) / scaled_rect.width(),
(crop_rect.max.y - scaled_rect.min.y) / scaled_rect.height(),
),
],
rotation: placement.rotation,
translation: placement.translation,
rotation_center_in_uv: egui::Vec2::new(
-(placement.rotation_center.x + (crop_rect.min.x - scaled_rect.min.x))
/ crop_rect.width(),
-(placement.rotation_center.y + (crop_rect.min.y - scaled_rect.min.y))
/ crop_rect.height(),
),
points_per_texel: placement.points_per_texel,
}
}
}