use std::sync::Arc;
use eframe::egui;
use log::trace;
use crate::image::{color_to_alpha, fit_image, to_egui_image};
use crate::image_pyramid::ImagePyramid;
use crate::texture_cache::TextureCache;
use crate::texture_request::{TextureRequest, TransformedTextureRequest};
use maps_io_ros::ValueInterpretation;
#[derive(Default)]
pub struct TextureState {
pub image_pyramid: Arc<ImagePyramid>,
pub image_response: Option<egui::Response>,
texture_cache: TextureCache,
pub texture_handle: Option<egui::TextureHandle>,
pub desired_size: egui::Vec2,
pub desired_crop_uv: [egui::Pos2; 2],
pub desired_color_to_alpha: Option<egui::Color32>,
pub desired_thresholding: Option<ValueInterpretation>,
pub used_level: u32,
pub texture_options: egui::TextureOptions,
}
impl TextureState {
pub fn new(image_pyramid: Arc<ImagePyramid>) -> TextureState {
TextureState {
image_pyramid,
texture_cache: TextureCache::new(),
..Default::default()
}
}
fn changed(&self, request: &TextureRequest) -> bool {
self.desired_size != request.desired_rect.size() || self.changed_appearance(request)
}
fn changed_appearance(&self, request: &TextureRequest) -> bool {
self.desired_color_to_alpha != request.color_to_alpha
|| self.desired_thresholding != request.thresholding
|| self.texture_options != request.texture_options.unwrap_or_default()
}
fn update(&mut self, ui: &egui::Ui, request: &TextureRequest) {
if self.changed(request) {
self.texture_handle = None;
}
self.desired_size = request.desired_rect.size();
self.desired_crop_uv = [egui::Pos2::ZERO, egui::pos2(1., 1.)];
self.desired_color_to_alpha = request.color_to_alpha;
self.desired_thresholding = request.thresholding;
self.texture_options = request.texture_options.unwrap_or_default();
self.texture_handle.get_or_insert_with(|| {
trace!("Fitting and reloading texture for {request:?}");
let mut image = fit_image(
self.image_pyramid.get_level(self.desired_size),
self.desired_size,
);
color_to_alpha(&mut image, request.color_to_alpha);
if let Some(thresholding) = &request.thresholding {
thresholding.apply(&mut image, self.image_pyramid.original_has_alpha);
}
ui.ctx().load_texture(
request.client.clone(),
to_egui_image(&image),
self.texture_options,
)
});
}
pub fn put(&mut self, ui: &mut egui::Ui, request: &TextureRequest) {
self.update(ui, request);
match &self.texture_handle {
Some(texture) => {
self.image_response = Some(
ui.add(egui::Image::new(texture).tint(request.tint))
.interact(request.sense),
);
}
None => {
panic!("Missing texture handle for {}", request.client)
}
}
}
fn changed_crop(&self, request: &TransformedTextureRequest) -> bool {
self.desired_crop_uv != request.crop_uv
}
fn try_use_cached_texture(
&mut self,
request: &TransformedTextureRequest,
desired_size: egui::Vec2,
) -> bool {
if !request.is_full_texture() {
return false;
}
let uncropped = self.image_pyramid.get_level(desired_size);
let level = uncropped.width().max(uncropped.height());
if let Some(texture_handle) =
self.texture_cache
.query(&request.base_request.client, level, &request.base_request)
{
self.texture_handle = Some(texture_handle);
self.used_level = level;
self.desired_size = desired_size;
self.desired_crop_uv = request.crop_uv;
self.desired_color_to_alpha = request.base_request.color_to_alpha;
self.desired_thresholding = request.base_request.thresholding;
self.texture_options = request.base_request.texture_options.unwrap_or_default();
return true;
}
false
}
fn maybe_update_crop(&mut self, ui: &mut egui::Ui, request: &TransformedTextureRequest) {
let desired_size = request.base_request.desired_rect.size();
if self.try_use_cached_texture(request, desired_size) {
return;
}
let changed_base_request = self.changed(&request.base_request);
let changed_crop = self.changed_crop(request);
let changed_appearance = self.changed_appearance(&request.base_request);
if !(changed_base_request || changed_crop || changed_appearance) {
return;
}
self.desired_size = desired_size;
self.desired_crop_uv = request.crop_uv;
self.desired_color_to_alpha = request.base_request.color_to_alpha;
self.desired_thresholding = request.base_request.thresholding;
self.texture_options = request.base_request.texture_options.unwrap_or_default();
if request.crop_rect.is_negative() || request.crop_uv[0] == request.crop_uv[1] {
self.texture_handle = None;
return;
}
let uncropped = self.image_pyramid.get_level(self.desired_size);
let level = uncropped.width().max(uncropped.height());
self.used_level = level;
trace!("Cropping and reloading texture for {request:?}");
let uv_min = request.crop_uv[0];
let uv_max = request.crop_uv[1];
let min_x = (uv_min.x * uncropped.width() as f32).round() as u32;
let min_y = (uv_min.y * uncropped.height() as f32).round() as u32;
let max_x = (uv_max.x * uncropped.width() as f32).round() as u32;
let max_y = (uv_max.y * uncropped.height() as f32).round() as u32;
let mut cropped_image = uncropped.crop_imm(min_x, min_y, max_x - min_x, max_y - min_y);
if cropped_image.width() == 0 || cropped_image.height() == 0 {
trace!("Crop resulted in empty image.");
self.texture_handle = None;
return;
}
color_to_alpha(&mut cropped_image, request.base_request.color_to_alpha);
if let Some(thresholding) = &request.base_request.thresholding {
thresholding.apply(&mut cropped_image, self.image_pyramid.original_has_alpha);
}
let texture_handle = ui.ctx().load_texture(
format!("{}_{}", request.base_request.client, level),
to_egui_image(&cropped_image),
self.texture_options,
);
if request.is_full_texture() {
self.texture_cache.store(
&request.base_request.client,
level,
texture_handle.clone(),
&request.base_request,
);
}
self.texture_handle = Some(texture_handle);
}
pub fn transform_and_put(&mut self, ui: &mut egui::Ui, request: &TransformedTextureRequest) {
self.maybe_update_crop(ui, request);
if let Some(texture) = &self.texture_handle {
let image = egui::Image::new(texture)
.rotate(request.rotation.angle(), request.rotation_center_in_uv)
.maintain_aspect_ratio(false)
.fit_to_exact_size(request.crop_rect.size())
.tint(request.base_request.tint);
image.paint_at(ui, request.crop_rect.translate(request.translation));
self.image_response = None;
}
}
}