You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CleanFlashInstaller/rust/crates/clean_flash_ui/src/flash_logo.rs

266 lines
7.2 KiB
Rust

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

use crate::renderer::Renderer;
/// Cached flash logo bitmap. Stores a pre-rendered pixel buffer so the
/// expensive MSAA polygon fill only runs when the target size changes.
pub struct FlashLogoCache {
width: i32,
height: i32,
/// Pre-rendered pixels in 0x00RRGGBB format, row-major, size = width * height.
pixels: Vec<u32>,
/// Shadow offset derived from width.
shadow_off: i32,
}
impl FlashLogoCache {
pub fn new() -> Self {
Self {
width: 0,
height: 0,
pixels: Vec::new(),
shadow_off: 0,
}
}
/// Draw the flash logo at (x, y) with size (w, h).
/// Re-renders into the cache only if the size changed.
pub fn draw(&mut self, renderer: &mut Renderer, x: i32, y: i32, w: i32, h: i32) {
if w <= 0 || h <= 0 {
return;
}
if self.width != w || self.height != h {
self.rebuild(w, h);
}
// Draw subtle drop shadow first.
let shadow_color = Renderer::rgb(0, 0, 0);
let shadow_alpha: u8 = 40;
let so = self.shadow_off;
for dy in 0..h {
for dx in 0..w {
renderer.blend_pixel(x + dx + so, y + dy + so, shadow_color, shadow_alpha);
}
}
// Blit cached pixels onto the renderer.
for dy in 0..h {
for dx in 0..w {
let px = self.pixels[dy as usize * w as usize + dx as usize];
renderer.set_pixel(x + dx, y + dy, px);
}
}
}
/// Render the logo into the internal cache at the given size.
fn rebuild(&mut self, w: i32, h: i32) {
self.width = w;
self.height = h;
self.shadow_off = ((w as f32 * 0.02).round() as i32).max(1);
// Render into a temporary offscreen renderer.
let mut tmp = Renderer::new(w as usize, h as usize);
let vw = 500.0_f32;
let vh = 487.0_f32;
let sx = w as f32 / vw;
let sy = h as f32 / vh;
let blue = Renderer::rgb(0x00, 0x3A, 0x74);
tmp.fill_rect(0, 0, w, h, blue);
let white = Renderer::rgb(255, 255, 255);
let points = build_f_polygon();
fill_polygon(&mut tmp, &points, 0.0, 0.0, sx, sy, white);
self.pixels = tmp.buffer;
}
}
/// Evaluate a cubic bezier curve and return `segments` points (excluding the start point).
fn cubic_bezier(
p0: (f32, f32),
p1: (f32, f32),
p2: (f32, f32),
p3: (f32, f32),
segments: usize,
) -> Vec<(f32, f32)> {
let mut pts = Vec::with_capacity(segments);
for i in 1..=segments {
let t = i as f32 / segments as f32;
let u = 1.0 - t;
let x = u * u * u * p0.0
+ 3.0 * u * u * t * p1.0
+ 3.0 * u * t * t * p2.0
+ t * t * t * p3.0;
let y = u * u * u * p0.1
+ 3.0 * u * u * t * p1.1
+ 3.0 * u * t * t * p2.1
+ t * t * t * p3.1;
pts.push((x, y));
}
pts
}
/// Build the "f" shape polygon by tracing the SVG path data, flattening beziers
/// into line segments. Coordinates are in the SVG viewBox space (500×487).
fn build_f_polygon() -> Vec<(f32, f32)> {
let mut pts = Vec::with_capacity(128);
const N: usize = 16; // segments per bezier curve
// M 269.2,138.7
pts.push((269.2, 138.7));
// c -22.5,27.5 -35.8,61.8 -48.7,95
pts.extend(cubic_bezier(
(269.2, 138.7),
(246.7, 166.2),
(233.4, 200.5),
(220.5, 233.7),
N,
));
// c -26.1,67.3 -43.6,105.8 -99.7,105.8
pts.extend(cubic_bezier(
(220.5, 233.7),
(194.4, 301.0),
(176.9, 339.5),
(120.8, 339.5),
N,
));
// V 402
pts.push((120.8, 402.0));
// c 46.4,0 84.1,-17.2 112,-51.2
pts.extend(cubic_bezier(
(120.8, 402.0),
(167.2, 402.0),
(204.9, 384.8),
(232.8, 350.8),
N,
));
// c 17.9,-21.9 30.3,-49.3 40.9,-75.8
pts.extend(cubic_bezier(
(232.8, 350.8),
(250.7, 328.9),
(263.1, 301.5),
(273.7, 275.0),
N,
));
// h 74.1
pts.push((347.8, 275.0));
// v -62.5
pts.push((347.8, 212.5));
// h -48.8
pts.push((299.0, 212.5));
// c 18.9,-40.6 39.2,-62.5 82.1,-62.5
pts.extend(cubic_bezier(
(299.0, 212.5),
(317.9, 171.9),
(338.2, 150.0),
(381.1, 150.0),
N,
));
// V 87.5
pts.push((381.1, 87.5));
// C 334.8,87.5 297.1,104.7 269.2,138.7 (absolute cubic, closes the shape)
pts.extend(cubic_bezier(
(381.1, 87.5),
(334.8, 87.5),
(297.1, 104.7),
(269.2, 138.7),
N,
));
pts
}
/// Test whether a point is inside the polygon using the even-odd (parity) rule.
fn point_in_polygon(transformed: &[(f32, f32)], px: f32, py: f32) -> bool {
let n = transformed.len();
let mut inside = false;
let mut j = n - 1;
for i in 0..n {
let (xi, yi) = transformed[i];
let (xj, yj) = transformed[j];
if ((yi > py) != (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
inside = !inside;
}
j = i;
}
inside
}
/// Scanline-fill a polygon with 4×4 MSAA antialiasing after transforming from
/// viewBox to screen coordinates: screen_x = ox + vx * sx, screen_y = oy + vy * sy.
fn fill_polygon(
renderer: &mut Renderer,
points: &[(f32, f32)],
ox: f32,
oy: f32,
sx: f32,
sy: f32,
color: u32,
) {
if points.len() < 3 {
return;
}
// Transform to screen space.
let transformed: Vec<(f32, f32)> = points
.iter()
.map(|&(px, py)| (ox + px * sx, oy + py * sy))
.collect();
// Bounding box.
let mut min_x = f32::MAX;
let mut max_x = f32::MIN;
let mut min_y = f32::MAX;
let mut max_y = f32::MIN;
for &(x, y) in &transformed {
if x < min_x { min_x = x; }
if x > max_x { max_x = x; }
if y < min_y { min_y = y; }
if y > max_y { max_y = y; }
}
let x_start = (min_x.floor() as i32).max(0);
let x_end = (max_x.ceil() as i32).min(renderer.width as i32 - 1);
let y_start = (min_y.floor() as i32).max(0);
let y_end = (max_y.ceil() as i32).min(renderer.height as i32 - 1);
// 4×4 sub-pixel grid offsets (16 samples per pixel).
const GRID: usize = 4;
const SAMPLES: usize = GRID * GRID;
let step = 1.0 / GRID as f32;
let half_step = step / 2.0;
for y in y_start..=y_end {
for x in x_start..=x_end {
let mut hits = 0u32;
for sy_i in 0..GRID {
let sample_y = y as f32 + half_step + sy_i as f32 * step;
for sx_i in 0..GRID {
let sample_x = x as f32 + half_step + sx_i as f32 * step;
if point_in_polygon(&transformed, sample_x, sample_y) {
hits += 1;
}
}
}
if hits == SAMPLES as u32 {
renderer.set_pixel(x, y, color);
} else if hits > 0 {
let alpha = ((hits * 255 + SAMPLES as u32 / 2) / SAMPLES as u32) as u8;
renderer.blend_pixel(x, y, color, alpha);
}
}
}
}