Skip to content

Commit f500d7f

Browse files
committed
add Icon::from_bytes
1 parent 0472517 commit f500d7f

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

src/conf.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,85 @@ impl Icon {
248248
big: crate::default_icon::BIG,
249249
}
250250
}
251+
252+
/// Constructs an icon from the given bytes.
253+
/// The bytes must be RGBA pixels (each 4 * u8) in row-major order.
254+
/// The image must be a square and its side length must be a power of two.
255+
///
256+
/// Returns `None` if the above conditions are not fulfilled.
257+
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
258+
fn adjust<const N: usize>(bytes: &[u8], side_len: usize, desired_len: usize) -> [u8; N] {
259+
let diff = desired_len.ilog2() as i32 - side_len.ilog2() as i32;
260+
261+
let mut arr = [0u8; N];
262+
if diff > 0 {
263+
// upscale
264+
let n = diff as u16 + 1;
265+
let mut write_index = 0;
266+
let mut current_line_length = 0;
267+
268+
for pixel in bytes.chunks(4) {
269+
// repeat n times horizontally
270+
for _ in 0..n {
271+
arr[write_index] = pixel[0];
272+
arr[write_index + 1] = pixel[1];
273+
arr[write_index + 2] = pixel[2];
274+
arr[write_index + 3] = pixel[3];
275+
write_index += 4;
276+
}
277+
current_line_length += 1;
278+
279+
if current_line_length == side_len {
280+
// repeat n - 1 times vertically, because one line already exists
281+
let last_line =
282+
arr[(write_index - (4 * side_len * n as usize))..write_index].to_vec();
283+
for _ in 0..n - 1 {
284+
for i in 0..last_line.len() {
285+
arr[write_index] = last_line[i];
286+
write_index += 1;
287+
}
288+
}
289+
current_line_length = 0;
290+
}
291+
}
292+
} else if diff == 0 {
293+
arr.copy_from_slice(bytes);
294+
} else {
295+
// downscale
296+
let n = (-diff) as usize + 1;
297+
let mut write_index = 0;
298+
299+
for line in 0..side_len {
300+
for column in 0..side_len {
301+
if line % n == 0 && column % n == 0 {
302+
let index = usize::from(side_len * line + column) * 4;
303+
arr[write_index] = bytes[index];
304+
arr[write_index + 1] = bytes[index + 1];
305+
arr[write_index + 2] = bytes[index + 2];
306+
arr[write_index + 3] = bytes[index + 3];
307+
write_index += 4;
308+
}
309+
}
310+
}
311+
};
312+
313+
arr
314+
}
315+
316+
if bytes.len() % 4 != 0 {
317+
return None;
318+
}
319+
let pixel_amount = bytes.len() / 4;
320+
let side_len = pixel_amount.isqrt();
321+
if side_len * side_len != pixel_amount || !side_len.is_power_of_two() {
322+
return None;
323+
}
324+
Some(Self {
325+
small: adjust(bytes, side_len, 16),
326+
medium: adjust(bytes, side_len, 32),
327+
big: adjust(bytes, side_len, 64),
328+
})
329+
}
251330
}
252331
// Printing 64x64 array with a default formatter is not meaningfull,
253332
// so debug will skip the data fields of an Icon

0 commit comments

Comments
 (0)