From ea19d8a1210d01acf92d0d3ccddb81d74c967c51 Mon Sep 17 00:00:00 2001 From: cyrgani Date: Fri, 11 Apr 2025 12:15:04 +0200 Subject: [PATCH] add `Icon::from_bytes` --- src/conf.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/conf.rs b/src/conf.rs index 09a4244a..4fd444ec 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -248,6 +248,85 @@ impl Icon { big: crate::default_icon::BIG, } } + + /// Constructs an icon from the given bytes. + /// The bytes must be RGBA pixels (each 4 * u8) in row-major order. + /// The image must be a square and its side length must be a power of two. + /// + /// Returns `None` if the above conditions are not fulfilled. + pub fn from_bytes(bytes: &[u8]) -> Option { + fn adjust(bytes: &[u8], side_len: usize, desired_len: usize) -> [u8; N] { + let diff = desired_len.ilog2() as i32 - side_len.ilog2() as i32; + + let mut arr = [0u8; N]; + if diff > 0 { + // upscale + let n = diff as u16 + 1; + let mut write_index = 0; + let mut current_line_length = 0; + + for pixel in bytes.chunks(4) { + // repeat n times horizontally + for _ in 0..n { + arr[write_index] = pixel[0]; + arr[write_index + 1] = pixel[1]; + arr[write_index + 2] = pixel[2]; + arr[write_index + 3] = pixel[3]; + write_index += 4; + } + current_line_length += 1; + + if current_line_length == side_len { + // repeat n - 1 times vertically, because one line already exists + let last_line = + arr[(write_index - (4 * side_len * n as usize))..write_index].to_vec(); + for _ in 0..n - 1 { + for i in 0..last_line.len() { + arr[write_index] = last_line[i]; + write_index += 1; + } + } + current_line_length = 0; + } + } + } else if diff == 0 { + arr.copy_from_slice(bytes); + } else { + // downscale + let n = (-diff) as usize + 1; + let mut write_index = 0; + + for line in 0..side_len { + for column in 0..side_len { + if line % n == 0 && column % n == 0 { + let index = usize::from(side_len * line + column) * 4; + arr[write_index] = bytes[index]; + arr[write_index + 1] = bytes[index + 1]; + arr[write_index + 2] = bytes[index + 2]; + arr[write_index + 3] = bytes[index + 3]; + write_index += 4; + } + } + } + }; + + arr + } + + if bytes.len() % 4 != 0 { + return None; + } + let pixel_amount = bytes.len() / 4; + let side_len = (pixel_amount as f32).sqrt().floor() as usize; + if side_len * side_len != pixel_amount || !side_len.is_power_of_two() { + return None; + } + Some(Self { + small: adjust(bytes, side_len, 16), + medium: adjust(bytes, side_len, 32), + big: adjust(bytes, side_len, 64), + }) + } } // Printing 64x64 array with a default formatter is not meaningfull, // so debug will skip the data fields of an Icon