Remove PNG loading

master
darktohka 6 days ago
parent 821435c8a3
commit adc456b8f6

@ -2,7 +2,7 @@ use crate::install_flags::{self, InstallFlags};
use crate::installer;
use clean_flash_common::{uninstaller, redirection, update_checker, ProgressCallback};
use clean_flash_ui::font::FontManager;
use clean_flash_ui::renderer::{Renderer, RgbaImage};
use clean_flash_ui::renderer::Renderer;
use clean_flash_ui::widgets::button::GradientButton;
use clean_flash_ui::widgets::checkbox::ImageCheckBox;
use clean_flash_ui::widgets::label::Label;
@ -59,10 +59,7 @@ pub struct InstallForm {
// Header
pub title_text: String,
pub subtitle_text: String,
pub flash_logo: RgbaImage,
// Checkbox images
pub checkbox_on: RgbaImage,
pub checkbox_off: RgbaImage,
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache,
// Navigation buttons
pub prev_button: GradientButton,
pub next_button: GradientButton,
@ -132,12 +129,6 @@ impl InstallForm {
let title_text = "Clean Flash Player".to_string();
let subtitle_text = format!("built from version {} (China)", version);
// Load images from the common resources folder.
// These are loaded from the C# project's assets alongside the binary.
let flash_logo = load_resource_image("flashLogo.png");
let checkbox_on = load_resource_image("checkboxOn.png");
let checkbox_off = load_resource_image("checkboxOff.png");
let fonts = FontManager::new();
Self {
@ -145,9 +136,7 @@ impl InstallForm {
panel: Panel::Disclaimer,
title_text,
subtitle_text,
flash_logo,
checkbox_on,
checkbox_off,
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache::new(),
prev_button: btn(90, 286, 138, 31, "QUIT"),
next_button: btn(497, 286, 138, 31, "AGREE"),
// Disclaimer panel
@ -240,10 +229,10 @@ The following details could be useful. Press the Retry button to try again.",
// ----- Draw -----
renderer.clear(BG_COLOR);
// Header: flash logo.
let lw = (self.flash_logo.width as f32 * self.scale) as i32;
let lh = (self.flash_logo.height as f32 * self.scale) as i32;
renderer.draw_image_scaled(self.s(90), self.s(36), lw, lh, &self.flash_logo);
// Header: flash logo (cached software render).
self.flash_logo_cache.draw(
renderer, self.s(90), self.s(36), self.s(109), self.s(107),
);
// Title.
self.fonts.draw_text(
@ -561,34 +550,27 @@ including Clean Flash Player and older versions of Adobe Flash Player."
// ---- Drawing helpers ----
fn draw_disclaimer(&self, r: &mut Renderer) {
self.disclaimer_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.disclaimer_box.draw(r);
self.disclaimer_label.draw(r, &self.fonts);
}
fn draw_choice(&self, r: &mut Renderer) {
self.browser_ask_label.draw(r, &self.fonts);
self.pepper_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.pepper_box.draw(r);
self.pepper_label.draw(r, &self.fonts);
self.netscape_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.netscape_box.draw(r);
self.netscape_label.draw(r, &self.fonts);
self.activex_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.activex_box.draw(r);
self.activex_label.draw(r, &self.fonts);
}
fn draw_player_choice(&self, r: &mut Renderer) {
self.player_ask_label.draw(r, &self.fonts);
self.player_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.player_box.draw(r);
self.player_label.draw(r, &self.fonts);
self.player_desktop_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.player_desktop_box.draw(r);
self.player_desktop_label.draw(r, &self.fonts);
self.player_start_menu_box
.draw(r, &self.checkbox_on, &self.checkbox_off);
self.player_start_menu_box.draw(r);
self.player_start_menu_label.draw(r, &self.fonts);
}
@ -664,13 +646,3 @@ fn join_with_and(items: &[&str]) -> String {
}
}
/// Try to load a resource image from the original C# project's asset folder.
fn load_resource_image(name: &str) -> RgbaImage {
let bytes: &[u8] = match name {
"flashLogo.png" => include_bytes!("../../../resources/flashLogo.png"),
"checkboxOn.png" => include_bytes!("../../../resources/checkboxOn.png"),
"checkboxOff.png" => include_bytes!("../../../resources/checkboxOff.png"),
_ => return RgbaImage::empty(),
};
RgbaImage::from_png_bytes(bytes)
}

@ -5,6 +5,5 @@ edition = "2021"
[dependencies]
ab_glyph = { workspace = true }
image = { workspace = true }
minifb = { workspace = true }
windows-sys = { workspace = true }

@ -0,0 +1,265 @@
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);
}
}
}
}

@ -1,3 +1,4 @@
pub mod flash_logo;
pub mod font;
pub mod renderer;
pub mod widgets;

@ -108,77 +108,6 @@ impl Renderer {
self.fill_rect(x + dx, y, 1, h, c);
}
}
/// Draw an RGBA image scaled to (w, h) at (x, y) using nearest-neighbor interpolation.
pub fn draw_image_scaled(&mut self, x: i32, y: i32, w: i32, h: i32, img: &RgbaImage) {
if w <= 0 || h <= 0 || img.width == 0 || img.height == 0 {
return;
}
for dy in 0..h {
for dx in 0..w {
let src_x = (dx as f32 * img.width as f32 / w as f32) as usize;
let src_y = (dy as f32 * img.height as f32 / h as f32) as usize;
let idx = (src_y * img.width + src_x) * 4;
let r = img.data[idx];
let g = img.data[idx + 1];
let b = img.data[idx + 2];
let a = img.data[idx + 3];
if a == 255 {
self.set_pixel(x + dx, y + dy, Self::rgb(r, g, b));
} else if a > 0 {
self.blend_pixel(x + dx, y + dy, Self::rgb(r, g, b), a);
}
}
}
}
/// Draw an RGBA image onto the framebuffer at (x, y).
pub fn draw_image(&mut self, x: i32, y: i32, img: &RgbaImage) {
for iy in 0..img.height as i32 {
for ix in 0..img.width as i32 {
let idx = (iy as usize * img.width + ix as usize) * 4;
let r = img.data[idx];
let g = img.data[idx + 1];
let b = img.data[idx + 2];
let a = img.data[idx + 3];
if a == 255 {
self.set_pixel(x + ix, y + iy, Self::rgb(r, g, b));
} else if a > 0 {
self.blend_pixel(x + ix, y + iy, Self::rgb(r, g, b), a);
}
}
}
}
}
/// Simple RGBA image stored as raw bytes.
pub struct RgbaImage {
pub width: usize,
pub height: usize,
pub data: Vec<u8>, // RGBA, row-major
}
impl RgbaImage {
/// Load a PNG from embedded bytes.
pub fn from_png_bytes(bytes: &[u8]) -> Self {
let img = image::load_from_memory_with_format(bytes, image::ImageFormat::Png)
.expect("Failed to decode PNG")
.to_rgba8();
Self {
width: img.width() as usize,
height: img.height() as usize,
data: img.into_raw(),
}
}
/// Create an empty (transparent) image.
pub fn empty() -> Self {
Self {
width: 0,
height: 0,
data: Vec::new(),
}
}
}
// ---- helpers ----

@ -1,7 +1,7 @@
use super::Rect;
use crate::renderer::{Renderer, RgbaImage};
use crate::renderer::Renderer;
/// An image-based checkbox matching the C# ImageCheckBox control.
/// A software-rendered checkbox with a gray gradient, black outline, and white checkmark.
pub struct ImageCheckBox {
pub rect: Rect,
pub checked: bool,
@ -32,59 +32,34 @@ impl ImageCheckBox {
}
}
pub fn draw(
&self,
renderer: &mut Renderer,
checked_img: &RgbaImage,
unchecked_img: &RgbaImage,
) {
pub fn draw(&self, renderer: &mut Renderer) {
if !self.visible {
return;
}
let img = if self.checked {
checked_img
} else {
unchecked_img
};
if img.width > 0 && img.height > 0 {
renderer.draw_image_scaled(self.rect.x, self.rect.y, self.rect.w, self.rect.h, img);
} else {
// Fallback: draw a simple square.
let bg = if self.checked {
Renderer::rgb(97, 147, 232)
} else {
Renderer::rgb(80, 80, 80)
};
renderer.fill_rect(self.rect.x, self.rect.y, self.rect.w, self.rect.h, bg);
renderer.draw_rect(
self.rect.x,
self.rect.y,
self.rect.w,
self.rect.h,
Renderer::rgb(160, 160, 160),
);
let r = self.rect;
// Gray gradient background (matching button style).
let color1 = Renderer::rgb(118, 118, 118);
let color2 = Renderer::rgb(81, 81, 81);
renderer.fill_gradient_v(r.x, r.y, r.w, r.h, color1, color2);
// Black 1px outline.
renderer.draw_rect(r.x, r.y, r.w, r.h, Renderer::rgb(0, 0, 0));
// White checkmark when checked.
if self.checked {
// Draw a simple checkmark.
let cx = self.rect.x + 5;
let cy = self.rect.y + 10;
for i in 0..4 {
renderer.set_pixel(cx + i, cy + i, Renderer::rgb(255, 255, 255));
renderer.set_pixel(cx + i, cy + i + 1, Renderer::rgb(255, 255, 255));
}
for i in 0..8 {
renderer.set_pixel(cx + 3 + i, cy + 3 - i, Renderer::rgb(255, 255, 255));
renderer.set_pixel(cx + 3 + i, cy + 4 - i, Renderer::rgb(255, 255, 255));
}
}
let white = Renderer::rgb(255, 255, 255);
Self::draw_checkmark(renderer, r.x, r.y, r.w, r.h, white);
}
if !self.enabled {
// Dim overlay.
for dy in 0..self.rect.h {
for dx in 0..self.rect.w {
for dy in 0..r.h {
for dx in 0..r.w {
renderer.blend_pixel(
self.rect.x + dx,
self.rect.y + dy,
r.x + dx,
r.y + dy,
Renderer::rgb(50, 51, 51),
100,
);
@ -92,4 +67,72 @@ impl ImageCheckBox {
}
}
}
/// Draw a checkmark scaled to fit within the given box.
fn draw_checkmark(renderer: &mut Renderer, bx: i32, by: i32, bw: i32, bh: i32, color: u32) {
// Checkmark geometry defined in a normalised coordinate space.
// The check goes from bottom-left, down to a valley, then up to top-right.
// Key points (in fractions of size):
// start: (0.20, 0.50)
// valley: (0.40, 0.72)
// end: (0.80, 0.25)
// We draw two thick line segments between these points.
let w = bw as f32;
let h = bh as f32;
// Absolute coordinates of the three key points.
let x0 = bx as f32 + w * 0.20;
let y0 = by as f32 + h * 0.48;
let x1 = bx as f32 + w * 0.40;
let y1 = by as f32 + h * 0.72;
let x2 = bx as f32 + w * 0.80;
let y2 = by as f32 + h * 0.25;
// Line thickness scales with checkbox size.
let thickness = (w * 0.14).max(1.5);
draw_thick_line(renderer, x0, y0, x1, y1, thickness, color);
draw_thick_line(renderer, x1, y1, x2, y2, thickness, color);
}
}
/// Draw a thick line between two points using filled circles along the path (round caps).
fn draw_thick_line(
renderer: &mut Renderer,
x0: f32,
y0: f32,
x1: f32,
y1: f32,
thickness: f32,
color: u32,
) {
let dx = x1 - x0;
let dy = y1 - y0;
let dist = (dx * dx + dy * dy).sqrt();
let steps = (dist * 2.0).ceil() as i32;
let half = thickness / 2.0;
for i in 0..=steps {
let t = i as f32 / steps.max(1) as f32;
let cx = x0 + dx * t;
let cy = y0 + dy * t;
// Fill a small circle at (cx, cy).
let min_x = (cx - half).floor() as i32;
let max_x = (cx + half).ceil() as i32;
let min_y = (cy - half).floor() as i32;
let max_y = (cy + half).ceil() as i32;
let r_sq = half * half;
for py in min_y..=max_y {
for px in min_x..=max_x {
let fdx = px as f32 + 0.5 - cx;
let fdy = py as f32 + 0.5 - cy;
if fdx * fdx + fdy * fdy <= r_sq {
renderer.set_pixel(px, py, color);
}
}
}
}
}

@ -1,6 +1,6 @@
use clean_flash_common::{redirection, uninstaller, update_checker, ProgressCallback};
use clean_flash_ui::font::FontManager;
use clean_flash_ui::renderer::{Renderer, RgbaImage};
use clean_flash_ui::renderer::Renderer;
use clean_flash_ui::widgets::button::GradientButton;
use clean_flash_ui::widgets::label::Label;
use clean_flash_ui::widgets::progress_bar::ProgressBar;
@ -43,7 +43,7 @@ pub struct UninstallForm {
panel: Panel,
title_text: String,
subtitle_text: String,
flash_logo: RgbaImage,
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache,
prev_button: GradientButton,
next_button: GradientButton,
// Before uninstall
@ -79,7 +79,7 @@ impl UninstallForm {
};
let version = update_checker::FLASH_VERSION;
let flash_logo = load_resource_image("flashLogo.png");
let fonts = FontManager::new();
Self {
@ -87,7 +87,7 @@ impl UninstallForm {
panel: Panel::BeforeInstall,
title_text: "Clean Flash Player".into(),
subtitle_text: format!("built from version {} (China)", version),
flash_logo,
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache::new(),
prev_button: btn(90, 286, 138, 31, "QUIT"),
next_button: btn(497, 286, 138, 31, "UNINSTALL"),
before_label: lbl(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 15.0),
@ -156,9 +156,9 @@ The following details could be useful. Press the Retry button to try again.",
// Draw.
renderer.clear(BG_COLOR);
let lw = (self.flash_logo.width as f32 * self.scale) as i32;
let lh = (self.flash_logo.height as f32 * self.scale) as i32;
renderer.draw_image_scaled(self.s(90), self.s(36), lw, lh, &self.flash_logo);
self.flash_logo_cache.draw(
renderer, self.s(90), self.s(36), self.s(109), self.s(107),
);
self.fonts
.draw_text(renderer, self.s(233), self.s(54), &self.title_text, self.sf(32.0), FG_COLOR);
@ -288,10 +288,3 @@ impl ProgressCallback for ThreadProgressCallback {
}
}
fn load_resource_image(name: &str) -> RgbaImage {
let bytes: &[u8] = match name {
"flashLogo.png" => include_bytes!("../../../resources/flashLogo.png"),
_ => return RgbaImage::empty(),
};
RgbaImage::from_png_bytes(bytes)
}

Loading…
Cancel
Save