use image::{Rgba, RgbaImage};
use serde::Serialize;
use super::ax::{self, Frame};
use super::error::AicError;
use super::preview::{blend_pixel, draw_filled_rect, draw_rect};
use super::screenshot::{capture_screen, output_image_to_base64, save_image};
const SOM_COLORS: [Rgba<u8>; 8] = [
Rgba([255, 0, 0, 200]), Rgba([0, 180, 0, 200]), Rgba([0, 100, 255, 200]), Rgba([255, 165, 0, 200]), Rgba([180, 0, 255, 200]), Rgba([0, 200, 200, 200]), Rgba([255, 80, 180, 200]), Rgba([128, 128, 0, 200]), ];
const FONT_WIDTH: u32 = 5;
const FONT_HEIGHT: u32 = 7;
#[rustfmt::skip]
const DIGIT_BITMAPS: [[u8; 7]; 10] = [
[0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110],
[0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110],
[0b01110, 0b10001, 0b00001, 0b00110, 0b01000, 0b10000, 0b11111],
[0b01110, 0b10001, 0b00001, 0b00110, 0b00001, 0b10001, 0b01110],
[0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010],
[0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110],
[0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110],
[0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000],
[0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110],
[0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100],
];
fn draw_digit(img: &mut RgbaImage, digit: u8, x: i32, y: i32, scale: u32, color: Rgba<u8>) {
if digit > 9 {
return;
}
let bitmap = &DIGIT_BITMAPS[digit as usize];
for row in 0..FONT_HEIGHT {
let bits = bitmap[row as usize];
for col in 0..FONT_WIDTH {
if (bits >> (FONT_WIDTH - 1 - col)) & 1 == 1 {
for sy in 0..scale {
for sx in 0..scale {
let px = x + (col * scale + sx) as i32;
let py = y + (row * scale + sy) as i32;
blend_pixel(img, px, py, color);
}
}
}
}
}
}
fn draw_number(img: &mut RgbaImage, num: usize, x: i32, y: i32, scale: u32, color: Rgba<u8>) {
let s = num.to_string();
let mut cx = x;
for ch in s.chars() {
if let Some(d) = ch.to_digit(10) {
draw_digit(img, d as u8, cx, y, scale, color);
cx += (FONT_WIDTH * scale + scale) as i32; }
}
}
fn number_width(num: usize, scale: u32) -> u32 {
let digits = num.to_string().len() as u32;
digits * FONT_WIDTH * scale + (digits - 1) * scale
}
#[derive(Debug, Serialize)]
pub struct SomIndexEntry {
pub index: usize,
pub role: String,
pub title: Option<String>,
pub frame: Option<Frame>,
pub center_x: f64,
pub center_y: f64,
}
pub fn capture_som(
app: Option<&str>,
output_file: Option<&str>,
) -> Result<(String, Vec<SomIndexEntry>), AicError> {
let elements = ax::collect_interactive_elements(app)?;
let (mut img, scale) = capture_screen()?;
let font_scale = if scale > 1.5 { 3u32 } else { 2u32 };
let box_thickness = if scale > 1.5 { 3.0 } else { 2.0 };
let label_pad: i32 = 2;
let mut som_entries: Vec<SomIndexEntry> = Vec::with_capacity(elements.len());
for (i, elem) in elements.iter().enumerate() {
let index = i + 1;
let color = SOM_COLORS[i % SOM_COLORS.len()];
if let Some(ref frame) = elem.frame {
let px = frame.x * scale;
let py = frame.y * scale;
let pw = frame.w * scale;
let ph = frame.h * scale;
draw_rect(&mut img, px, py, pw, ph, box_thickness, color);
let num_w = number_width(index, font_scale) as i32;
let num_h = (FONT_HEIGHT * font_scale) as i32;
let label_w = num_w + label_pad * 2;
let label_h = num_h + label_pad * 2;
let label_x = px as i32;
let label_y = (py as i32 - label_h).max(0);
let bg_color = Rgba([0, 0, 0, 220]);
draw_filled_rect(&mut img, label_x, label_y, label_w, label_h, bg_color);
draw_number(
&mut img,
index,
label_x + label_pad,
label_y + label_pad,
font_scale,
Rgba([255, 255, 255, 255]),
);
}
som_entries.push(SomIndexEntry {
index,
role: elem.role.clone(),
title: elem.title.clone(),
frame: elem.frame.clone(),
center_x: elem.center_x,
center_y: elem.center_y,
});
}
let b64 = if let Some(path) = output_file {
save_image(&img, path)?;
output_image_to_base64(&img)?
} else {
output_image_to_base64(&img)?
};
Ok((b64, som_entries))
}
pub fn capture_plain_screenshot() -> Result<String, AicError> {
let (img, _) = capture_screen()?;
output_image_to_base64(&img)
}