//! Bitmap scaling. #![allow(dead_code)] use alloc::vec::Vec; #[cfg(feature = "libm")] use core_maths::CoreFloat; mod png; /// Decodes a PNG image. pub fn decode_png(data: &[u8], scratch: &mut Vec, target: &mut [u8]) -> Option<(u32, u32)> { png::decode(data, scratch, target) .map(|(w, h, _)| (w, h)) .ok() } pub fn blit( mask: &[u8], mask_width: u32, mask_height: u32, x: i32, y: i32, color: [u8; 4], target: &mut [u8], target_width: u32, target_height: u32, ) { if mask_width == 0 || mask_height == 0 || target_width == 0 || target_height == 0 { return; } let source_width = mask_width as usize; let source_height = mask_height as usize; let dest_width = target_width as usize; let dest_height = target_height as usize; let source_x = if x < 0 { -x as usize } else { 0 }; let source_y = if y < 0 { -y as usize } else { 0 }; if source_x >= source_width || source_y >= source_height { return; } let dest_x = if x < 0 { 0 } else { x as usize }; let dest_y = if y < 0 { 0 } else { y as usize }; if dest_x >= dest_width || dest_y >= dest_height { return; } let source_end_x = (source_width).min(dest_width - dest_x + source_x); let source_end_y = (source_height).min(dest_height - dest_y + source_y); let source_columns = source_y..source_end_y; let source_rows = source_x..source_end_x; let dest_pitch = target_width as usize * 4; let color_a = color[3] as u32; let mut dy = dest_y; for sy in source_columns { let src_row = &mask[sy * mask_width as usize..]; let dst_row = &mut target[dy * dest_pitch..]; dy += 1; let mut dx = dest_x * 4; for sx in source_rows.clone() { let a = (src_row[sx] as u32 * color_a) >> 8; if a >= 255 { dst_row[dx + 3] = 255; dst_row[dx..(dx + 3)].copy_from_slice(&color[..3]); dst_row[dx + 3] = 255; } else if a != 0 { let inverse_a = 255 - a; for i in 0..3 { let d = dst_row[dx + i] as u32; let c = ((inverse_a * d) + (a * color[i] as u32)) >> 8; dst_row[dx + i] = c as u8; } let d = dst_row[dx + 3] as u32; let c = ((inverse_a * d) + a * 255) >> 8; dst_row[dx + 3] = c as u8; } dx += 4; } } } #[derive(Copy, Clone, PartialEq)] pub enum Filter { Nearest, Bilinear, Bicubic, Mitchell, Lanczos3, Gaussian, } pub fn resize( image: &[u8], width: u32, height: u32, channels: u32, target: &mut [u8], target_width: u32, target_height: u32, filter: Filter, scratch: Option<&mut Vec>, ) -> bool { if target_width == 0 || target_height == 0 { return true; } let mut tmp = Vec::new(); let scratch = if let Some(scratch) = scratch { scratch } else { &mut tmp }; let image_size = (width * height * channels) as usize; if image.len() < image_size { return false; } let target_size = (target_width * target_height * channels) as usize; if target.len() < target_size { return false; } let scratch_size = (target_width * height * channels) as usize; scratch.resize(scratch_size, 0); use Filter::*; match filter { Nearest => resample( image, width, height, channels, target, target_width, target_height, scratch, 0., &nearest, ), Bilinear => resample( image, width, height, channels, target, target_width, target_height, scratch, 1., &bilinear, ), Bicubic => resample( image, width, height, channels, target, target_width, target_height, scratch, 2., &bicubic, ), Mitchell => resample( image, width, height, channels, target, target_width, target_height, scratch, 2., &mitchell, ), Lanczos3 => resample( image, width, height, channels, target, target_width, target_height, scratch, 3., &lanczos3, ), Gaussian => resample( image, width, height, channels, target, target_width, target_height, scratch, 3., &|x| gaussian(x, 0.5), ), } } fn resample( image: &[u8], width: u32, height: u32, channels: u32, target: &mut [u8], target_width: u32, target_height: u32, scratch: &mut [u8], support: f32, filter: &Filter, ) -> bool where Filter: Fn(f32) -> f32, { let tmp_width = target_width; let tmp_height = height; let s = 1. / 255.; if channels == 1 { sample_dir( &|x, y| [0., 0., 0., image[(y * width + x) as usize] as f32 * s], width, height, target_width, filter, support, &mut |x, y, p| scratch[(y * tmp_width + x) as usize] = (p[3] * 255.) as u8, ); sample_dir( &|y, x| [0., 0., 0., scratch[(y * tmp_width + x) as usize] as f32 * s], tmp_height, tmp_width, target_height, filter, support, &mut |y, x, p| target[(y * target_width + x) as usize] = (p[3] * 255.) as u8, ); true } else if channels == 4 { sample_dir( &|x, y| { let row = (y * width * channels + x * channels) as usize; [ image[row] as f32 * s, image[row + 1] as f32 * s, image[row + 2] as f32 * s, image[row + 3] as f32 * s, ] }, width, height, target_width, filter, support, &mut |x, y, p| { let row = (y * target_width * channels + x * channels) as usize; scratch[row] = (p[0] * 255.) as u8; scratch[row + 1] = (p[1] * 255.) as u8; scratch[row + 2] = (p[2] * 255.) as u8; scratch[row + 3] = (p[3] * 255.) as u8; }, ); sample_dir( &|y, x| { let row = (y * tmp_width * channels + x * channels) as usize; [ scratch[row] as f32 * s, scratch[row + 1] as f32 * s, scratch[row + 2] as f32 * s, scratch[row + 3] as f32 * s, ] }, tmp_height, tmp_width, target_height, filter, support, &mut |y, x, p| { let row = (y * target_width * channels + x * channels) as usize; target[row] = (p[0] * 255.) as u8; target[row + 1] = (p[1] * 255.) as u8; target[row + 2] = (p[2] * 255.) as u8; target[row + 3] = (p[3] * 255.) as u8; }, ); true } else { false } } fn sample_dir( input: &Input, width: u32, height: u32, new_width: u32, filter: &Filter, support: f32, output: &mut Output, ) where Input: Fn(u32, u32) -> [f32; 4], Output: FnMut(u32, u32, &[f32; 4]), Filter: Fn(f32) -> f32, { const MAX_WEIGHTS: usize = 64; let mut weights = [0f32; MAX_WEIGHTS]; let mut num_weights; let ratio = width as f32 / new_width as f32; let sratio = ratio.max(1.); let src_support = support * sratio; let isratio = 1. / sratio; for outx in 0..new_width { let inx = (outx as f32 + 0.5) * ratio; let left = (inx - src_support).floor() as i32; let mut left = left.max(0).min(width as i32 - 1) as usize; let right = (inx + src_support).ceil() as i32; let mut right = right.max(left as i32 + 1).min(width as i32) as usize; let inx = inx - 0.5; while right - left > MAX_WEIGHTS { right -= 1; left += 1; } num_weights = 0; let mut sum = 0.; for i in left..right { let w = filter((i as f32 - inx) * isratio); weights[num_weights] = w; num_weights += 1; sum += w; } let isum = 1. / sum; let weights = &weights[..num_weights]; for y in 0..height { let mut accum = [0f32; 4]; for (i, w) in weights.iter().enumerate() { let p = input((left + i) as u32, y); let a = p[3]; accum[0] += p[0] * w * a; accum[1] += p[1] * w * a; accum[2] += p[2] * w * a; accum[3] += p[3] * w; } if accum[3] != 0. { let a = 1. / accum[3]; accum[0] *= a; accum[1] *= a; accum[2] *= a; accum[3] *= isum; } output(outx, y, &accum); } } } fn sinc(t: f32) -> f32 { let a = t * core::f32::consts::PI; if t == 0. { 1. } else { a.sin() / a } } fn lanczos3(x: f32) -> f32 { if x.abs() < 3. { (sinc(x) * sinc(x / 3.)).abs() } else { 0. } } fn bilinear(x: f32) -> f32 { let x = x.abs(); if x < 1. { 1. - x } else { 0. } } fn bicubic(x: f32) -> f32 { let a = x.abs(); let b = 0.; let c = 0.5; let k = if a < 1. { (12. - 9. * b - 6. * c) * a.powi(3) + (-18. + 12. * b + 6. * c) * a.powi(2) + (6. - 2. * b) } else if a < 2. { (-b - 6. * c) * a.powi(3) + (6. * b + 30. * c) * a.powi(2) + (-12. * b - 48. * c) * a + (8. * b + 24. * c) } else { 0. }; (k / 6.).abs() } fn mitchell(x: f32) -> f32 { let x = x.abs(); if x < 1. { ((16. + x * x * (21. * x - 36.)) / 18.).abs() } else if x < 2. { ((32. + x * (-60. + x * (36. - 7. * x))) / 18.).abs() } else { 0. } } fn nearest(_x: f32) -> f32 { 1. } fn gaussian(x: f32, r: f32) -> f32 { ((2. * core::f32::consts::PI).sqrt() * r).recip() * (-x.powi(2) / (2. * r.powi(2))).exp() }