blob: db1df64b581cd8f7c90688a8308faa34f4fda0d7 [file] [log] [blame]
use avm_stats::MAX_COEFFS_SIZE;
use anyhow::anyhow;
use egui::{pos2, vec2, Align2, Color32, PointerButton, Rect, RichText, Rounding, Shape, Stroke, TextStyle, Ui, Vec2};
use itertools::{Itertools, MinMaxResult};
use crate::app_state::AppState;
use crate::settings::CoeffViewSelect;
use crate::views::RenderView;
use crate::views::{ScreenBounds, SelectedObjectKind};
use crate::views::{MIN_BLOCK_HEIGHT_FOR_TEXT, MIN_BLOCK_WIDTH_FOR_TEXT};
pub struct CoeffsViewer;
impl RenderView for CoeffsViewer {
fn title(&self) -> String {
"Coeffs View".into()
}
fn render(&self, ui: &mut Ui, state: &mut AppState) -> anyhow::Result<()> {
let Some(stream) = &state.stream else {
return Ok(());
};
let Some(frame) = stream.current_frame() else {
return Ok(());
};
let Some(selected_object) = &state.settings.selected_object else {
return Ok(());
};
let SelectedObjectKind::TransformUnit(transform_unit_index) = selected_object.kind.clone() else {
return Ok(());
};
let transform_unit_ctx = transform_unit_index
.try_resolve(frame)
.ok_or(anyhow!("Invalid transform unit index"))?;
let transform_unit = transform_unit_ctx.transform_unit;
let coeff_view_options = [
CoeffViewSelect::Dequantized,
CoeffViewSelect::Quantized,
CoeffViewSelect::DequantValue,
];
ui.label(RichText::new("WARNING: Coeff data extraction is currently buggy.").color(Color32::RED));
ui.end_row();
egui::ComboBox::from_label("Coeff view")
.selected_text(state.settings.sharable.coeff_view_select.name())
.show_ui(ui, |ui| {
for coeff_option in coeff_view_options.iter() {
ui.selectable_value(
&mut state.settings.sharable.coeff_view_select,
*coeff_option,
coeff_option.name(),
);
}
});
ui.end_row();
let Some(mut object_rect) = selected_object.rect(frame) else {
return Err(anyhow!("Invalid selected object"));
};
if state.settings.sharable.current_plane.use_chroma() {
object_rect = object_rect * 0.5;
}
let size = ui.available_size_before_wrap();
let (screen_bounds, response) = ui.allocate_exact_size(size, egui::Sense::drag());
let mut world_bounds = state.settings.sharable.coeffs_viewer_bounds;
let scale = world_bounds.calc_scale(screen_bounds);
let mut hover_pos_world = None;
// TODO(comc): Factor out this common code with frame_viewer and detailed_pixel_viewer.
if response.dragged_by(PointerButton::Primary) {
let delta = response.drag_delta() / -scale;
world_bounds = world_bounds.translate(delta);
} else if response.hover_pos().is_some() && ui.input(|i| i.scroll_delta) != Vec2::ZERO {
let delta = ui.input(|i| i.scroll_delta);
if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
if screen_bounds.contains(mouse_pos) {
let zoom = if delta.y < 0.0 {
f32::powf(1.001, -delta.y)
} else {
1.0 / (f32::powf(1.001, delta.y))
};
let zoom_center = world_bounds.screen_pos_to_world(mouse_pos, screen_bounds);
world_bounds.zoom_point(zoom_center, zoom);
}
}
} else if response.hover_pos().is_some() {
if let Some(mouse_pos) = ui.input(|i| i.pointer.hover_pos()) {
hover_pos_world = Some(world_bounds.screen_pos_to_world(mouse_pos, screen_bounds));
}
}
state.settings.sharable.coeffs_viewer_bounds = world_bounds;
let painter = ui.painter().with_clip_rect(response.rect);
let clip_rect = painter.clip_rect();
let mut shapes = Vec::new();
let ui_style = ui.ctx().style();
let mut hover_text = None;
let mut min_coeff = 0;
let mut max_coeff = 255;
let coeff_view = match state.settings.sharable.coeff_view_select {
CoeffViewSelect::DequantValue => &transform_unit.dequantizer_values,
CoeffViewSelect::Dequantized => &transform_unit.dequantized_coeffs,
CoeffViewSelect::Quantized => &transform_unit.quantized_coeffs,
};
match coeff_view.iter().minmax() {
MinMaxResult::NoElements | MinMaxResult::OneElement(_) => {}
MinMaxResult::MinMax(&min_v, &max_v) => {
min_coeff = min_v;
max_coeff = max_v;
}
};
// TODO(comc): Manually doing an inverse DCT does not yield the expected pixels for some blocks. Verify that the Residual = (PreFiltered - Prediction) logic makes sense.
let coeffs_width = MAX_COEFFS_SIZE.min(object_rect.width() as usize);
let coeffs_height = MAX_COEFFS_SIZE.min(object_rect.height() as usize);
for y in 0..coeffs_height {
for x in 0..coeffs_width {
let stride = coeffs_width;
let index = y * stride + x;
let coeff = coeff_view.get(index).copied();
let have_coeff = coeff.is_some();
let coeff = coeff.unwrap_or_default();
let color = (coeff - min_coeff) as f32 / (max_coeff - min_coeff) as f32;
let world_rect = Rect::from_min_size(pos2(x as f32, y as f32), vec2(1.0, 1.0));
let screen_rect = world_bounds.world_rect_to_screen(world_rect, clip_rect);
if let Some(hover_pos_world) = hover_pos_world {
if world_rect.contains(hover_pos_world) && have_coeff {
hover_text = Some(format!("Coeff={} (x={}, y={})", coeff, x, y));
}
}
shapes.push(Shape::rect_filled(
screen_rect,
Rounding::ZERO,
Color32::from_gray((color * 255.0) as u8),
));
shapes.push(Shape::rect_stroke(
screen_rect,
Rounding::ZERO,
Stroke::new(1.0, Color32::from_gray(20)),
));
if have_coeff
&& screen_rect.height() >= MIN_BLOCK_HEIGHT_FOR_TEXT
&& screen_rect.width() >= MIN_BLOCK_WIDTH_FOR_TEXT
{
let overlay_style = &state.settings.persistent.style.overlay;
shapes.extend(painter.fonts(|f| {
let colors = if overlay_style.enable_text_shadows {
vec![Color32::BLACK, overlay_style.pixel_viewer_text_color]
} else {
vec![overlay_style.pixel_viewer_text_color]
};
colors
.into_iter()
.enumerate()
.map(|(i, color)| {
let pos_offset = vec2(i as f32, i as f32);
Shape::text(
f,
screen_rect.center() + pos_offset,
Align2::CENTER_CENTER,
format!("{coeff}"),
TextStyle::Body.resolve(&ui_style),
color,
)
})
.collect::<Vec<_>>()
}));
}
}
}
painter.extend(shapes);
if let Some(hover_text) = hover_text {
response.on_hover_text_at_pointer(hover_text);
}
Ok(())
}
}