From 90af90850a76515a6ba5a3563ed0608da37afada Mon Sep 17 00:00:00 2001 From: Ghoul Date: Sat, 24 Jan 2026 02:43:53 +0000 Subject: [PATCH] Feat: anime matrix gets custom image and gif support for Scar 2025 with other minor fixes --- .gitignore | 5 + asusctl/examples/anime-led-scan.rs | 547 +++++++++++++++++ asusctl/src/anime_cli.rs | 4 +- rog-anime/data/g835l/images/.gitkeep | 0 .../g835l/templates/custom-image-template.png | Bin 0 -> 518 bytes rog-anime/src/data.rs | 48 +- rog-anime/src/diagonal.rs | 78 +++ rog-anime/src/image.rs | 73 ++- .../tests/data/g835l-diagonal-fullbright.png | Bin 0 -> 205 bytes rog-anime/tests/data/g835l-diagonal.gif | Bin 0 -> 88859 bytes rog-anime/tests/data/g835l-diagonal.png | Bin 0 -> 156 bytes rog-anime/tests/g835l.rs | 556 ++++++++++++++++++ rog-dbus/src/zbus_anime.rs | 2 +- simulators/src/animatrix/map_g635l.rs | 70 +++ simulators/src/animatrix/map_g835l.rs | 74 +++ simulators/src/animatrix/mod.rs | 28 +- simulators/src/simulator.rs | 12 +- 17 files changed, 1448 insertions(+), 49 deletions(-) create mode 100644 asusctl/examples/anime-led-scan.rs create mode 100644 rog-anime/data/g835l/images/.gitkeep create mode 100644 rog-anime/data/g835l/templates/custom-image-template.png create mode 100644 rog-anime/tests/data/g835l-diagonal-fullbright.png create mode 100644 rog-anime/tests/data/g835l-diagonal.gif create mode 100644 rog-anime/tests/data/g835l-diagonal.png create mode 100644 rog-anime/tests/g835l.rs create mode 100644 simulators/src/animatrix/map_g635l.rs create mode 100644 simulators/src/animatrix/map_g835l.rs diff --git a/.gitignore b/.gitignore index e5209aae..65253858 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,8 @@ desktop-extensions/gnome*/@types/gir-generated desktop-extensions/gnome*/node_modules desktop-extensions/gnome*/schemas/gschemas.compiled desktop-extensions/gnome*/*.zip + +# agents and reference +CLAUDE.md +AGENTS.md +/reference diff --git a/asusctl/examples/anime-led-scan.rs b/asusctl/examples/anime-led-scan.rs new file mode 100644 index 00000000..c4ff9b94 --- /dev/null +++ b/asusctl/examples/anime-led-scan.rs @@ -0,0 +1,547 @@ +//! LED scanning tool for discovering AniMe Matrix buffer-to-LED mappings. +//! +//! This tool lights up one buffer index at a time, allowing you to observe +//! which physical LED corresponds to each buffer position. This is essential +//! for mapping new device types like G835L where the exact layout is unknown. +//! +//! You might want to use it slowly, as it sometimes doesn't work properly. +//! Maybe there's better ways to make this reliable but for now it works for my use case. +//! +//! # Usage +//! ``` +//! cargo run --example anime-led-scan -- [options] +//! ``` +//! +//! # Controls +//! - `n` or `Enter`: Next index +//! - `p` or `Backspace`: Previous index +//! - `j` followed by number: Jump to specific index +//! - `+` / `-`: Adjust step size (default 1) +//! - `s`: Save current index to notes file +//! - `r`: Mark current index as row start +//! - `q` or `Ctrl+C`: Quit +//! +//! # Output +//! Creates a `led-scan-notes.txt` file with recorded observations. + +use std::env; +use std::fs::OpenOptions; +use std::io::{self, BufRead, Write}; + +use rog_anime::usb::{get_anime_type, Brightness}; +use rog_anime::{AnimeDataBuffer, AnimeType}; +use rog_dbus::zbus_anime::AnimeProxyBlocking; +use zbus::blocking::Connection; + +/// Saved device state for restoration on exit +struct SavedState { + builtins_enabled: bool, + brightness: Brightness, + display_enabled: bool, +} + +fn print_help(scan_len: usize, buffer_len: usize) { + println!("\n=== LED Scan Tool ==="); + println!( + "Scan range: 0-{} (buffer size: {})", + scan_len - 1, + buffer_len + ); + println!("Commands:"); + println!(" n, Enter - Next index"); + println!(" p, Backspace - Previous index"); + println!(" j - Jump to index"); + println!(" + / - - Increase/decrease step size"); + println!(" s - Save note for current index"); + println!(" r - Mark as row start"); + println!(" a - Auto-scan (runs through all indices)"); + println!(" f - Fill all buffer bytes"); + println!(" f - Fill range (inclusive)"); + println!(" p1/p2/p3 - Fill pane 1/2/3 only (each is 627 bytes)"); + println!(" hold - Hold current LED (press Enter to release)"); + println!(" hold - Hold range (press Enter to release)"); + println!(" c - Clear display"); + println!(" row - Step through rows (G835L, provisional)"); + println!(" row - Show specific row (G835L, provisional)"); + println!(" allrows - Light all rows sequentially (G835L)"); + println!(" rowmap - Print the full row mapping (G835L)"); + println!(" h - Show this help"); + println!(" q - Quit and restore state"); + println!(); +} + +fn save_note(index: usize, note: &str) -> io::Result<()> { + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open("led-scan-notes.txt")?; + writeln!(file, "Index {}: {}", index, note)?; + Ok(()) +} + +fn write_single_led( + proxy: &AnimeProxyBlocking, + anime_type: AnimeType, + index: usize, + brightness: u8, +) { + let mut buffer = AnimeDataBuffer::new(anime_type); + let data = buffer.data_mut(); + if index < data.len() { + data[index] = brightness; + } + if let Err(e) = proxy.write(buffer) { + eprintln!("Error writing to device: {}", e); + } +} + +fn clear_display(proxy: &AnimeProxyBlocking, anime_type: AnimeType) { + let buffer = AnimeDataBuffer::new(anime_type); + let _ = proxy.write(buffer); +} + +fn fill_display(proxy: &AnimeProxyBlocking, anime_type: AnimeType, brightness: u8) { + let mut buffer = AnimeDataBuffer::new(anime_type); + let data = buffer.data_mut(); + for byte in data.iter_mut() { + *byte = brightness; + } + if let Err(e) = proxy.write(buffer) { + eprintln!("Error writing to device: {}", e); + } +} + +/// Fill a range of LEDs. Both start and end are INCLUSIVE. +fn fill_range( + proxy: &AnimeProxyBlocking, + anime_type: AnimeType, + start: usize, + end: usize, + brightness: u8, +) { + let mut buffer = AnimeDataBuffer::new(anime_type); + let data = buffer.data_mut(); + for i in start..=end.min(data.len().saturating_sub(1)) { + data[i] = brightness; + } + if let Err(e) = proxy.write(buffer) { + eprintln!("Error writing to device: {}", e); + } +} + +fn fill_pane(proxy: &AnimeProxyBlocking, anime_type: AnimeType, pane: usize, brightness: u8) { + const PANE_LEN: usize = 627; + let start = pane * PANE_LEN; + let end = start + PANE_LEN - 1; + fill_range(proxy, anime_type, start, end, brightness); +} + +/// G835L row pattern (PROVISIONAL - needs hardware verification): +/// - Rows 0-1: 1 LED each +/// - Rows 2-3: 2 LEDs each +/// - ... (pairs of rows with same length) +/// - Rows 26-27: 14 LEDs each +/// - Rows 28+: 15 LEDs each (constant) +/// +/// Returns (start_index, end_index_inclusive, row_length) +fn g835l_row_bounds(row: usize) -> (usize, usize, usize) { + let triangle_rows = 28; + let triangle_leds = 210; + + if row < triangle_rows { + let length = row / 2 + 1; + let mut start = 0usize; + for r in 0..row { + start += r / 2 + 1; + } + (start, start + length - 1, length) + } else { + let rows_after_triangle = row - triangle_rows; + let start = triangle_leds + rows_after_triangle * 15; + (start, start + 14, 15) + } +} + +fn g835l_total_rows() -> usize { + 28 + 40 +} + +fn save_state(proxy: &AnimeProxyBlocking) -> SavedState { + SavedState { + builtins_enabled: proxy.builtins_enabled().unwrap_or(false), + brightness: proxy.brightness().unwrap_or(Brightness::Med), + display_enabled: proxy.enable_display().unwrap_or(true), + } +} + +fn restore_state(proxy: &AnimeProxyBlocking, state: &SavedState) { + let _ = proxy.set_builtins_enabled(state.builtins_enabled); + let _ = proxy.set_brightness(state.brightness); + let _ = proxy.set_enable_display(state.display_enabled); + let _ = proxy.run_main_loop(true); +} + +fn main() { + let args: Vec = env::args().collect(); + + let mut start_index = 0usize; + let mut brightness = 200u8; + let mut scan_limit: Option = None; + + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "--start" | "-s" => { + if i + 1 < args.len() { + start_index = args[i + 1].parse().unwrap_or(0); + i += 1; + } + } + "--brightness" | "-b" => { + if i + 1 < args.len() { + brightness = args[i + 1].parse().unwrap_or(200); + i += 1; + } + } + "--limit" | "-l" => { + if i + 1 < args.len() { + scan_limit = args[i + 1].parse().ok(); + i += 1; + } + } + "--help" | "-h" => { + println!("LED Scan Tool for AniMe Matrix"); + println!(); + println!("Usage: anime-led-scan [options]"); + println!(); + println!("Options:"); + println!(" -s, --start Start at index N (default: 0)"); + println!(" -b, --brightness LED brightness 0-255 (default: 200)"); + println!(" -l, --limit Cap scan range to N indices (e.g. 810 for G835L)"); + println!(" -h, --help Show this help"); + return; + } + _ => {} + } + i += 1; + } + + let conn = match Connection::system() { + Ok(c) => c, + Err(e) => { + eprintln!("Failed to connect to D-Bus: {}", e); + eprintln!("Make sure asusd is running."); + return; + } + }; + + let proxy = match AnimeProxyBlocking::new(&conn) { + Ok(p) => p, + Err(e) => { + eprintln!("Failed to create Anime proxy: {}", e); + eprintln!("Make sure asusd supports your device."); + return; + } + }; + + let anime_type = get_anime_type(); + let buffer_len = anime_type.data_length(); + let scan_len = scan_limit.unwrap_or(buffer_len).min(buffer_len); + + println!("=== LED Scan Tool ==="); + println!("Device type: {:?}", anime_type); + println!("Buffer length: {} bytes", buffer_len); + println!("Scan range: 0-{}", scan_len - 1); + println!("Brightness: {}", brightness); + println!(); + + // Save current state for restoration + let saved_state = save_state(&proxy); + println!("Saved device state for restoration on exit."); + + // Stop system animations + if let Err(e) = proxy.run_main_loop(false) { + eprintln!("Warning: Could not stop main loop: {}", e); + } + println!("Stopped system animations."); + + print_help(scan_len, buffer_len); + + let mut current_index = start_index.min(scan_len - 1); + let mut step = 1usize; + + write_single_led(&proxy, anime_type, current_index, brightness); + println!(">>> Index: {} (step: {})", current_index, step); + + let stdin = io::stdin(); + let mut input = String::new(); + + loop { + input.clear(); + print!("> "); + io::stdout().flush().unwrap(); + + if stdin.lock().read_line(&mut input).is_err() { + break; + } + + let cmd = input.trim(); + + match cmd { + "q" | "quit" | "exit" => { + clear_display(&proxy, anime_type); + restore_state(&proxy, &saved_state); + println!("Restored device state. Goodbye!"); + break; + } + "n" | "" => { + current_index = (current_index + step).min(scan_len - 1); + write_single_led(&proxy, anime_type, current_index, brightness); + println!(">>> Index: {} (step: {})", current_index, step); + } + "p" => { + current_index = current_index.saturating_sub(step); + write_single_led(&proxy, anime_type, current_index, brightness); + println!(">>> Index: {} (step: {})", current_index, step); + } + "+" => { + step = step.saturating_mul(2).max(1); + println!("Step size: {}", step); + } + "-" => { + step = step.saturating_div(2).max(1); + println!("Step size: {}", step); + } + "r" => { + if let Err(e) = save_note(current_index, "ROW START") { + eprintln!("Error saving note: {}", e); + } else { + println!("Saved: Index {} marked as ROW START", current_index); + } + } + "h" | "help" | "?" => { + print_help(scan_len, buffer_len); + } + cmd if cmd.starts_with('j') => { + let num_str = cmd.trim_start_matches('j').trim(); + if let Ok(idx) = num_str.parse::() { + if idx < scan_len { + current_index = idx; + write_single_led(&proxy, anime_type, current_index, brightness); + println!(">>> Index: {} (step: {})", current_index, step); + } else { + println!("Index {} out of range (max: {})", idx, scan_len - 1); + } + } else { + println!("Usage: j "); + } + } + cmd if cmd.starts_with('s') && !cmd.starts_with("show") => { + let note = cmd.trim_start_matches('s').trim(); + let note = if note.is_empty() { "observed" } else { note }; + if let Err(e) = save_note(current_index, note) { + eprintln!("Error saving note: {}", e); + } else { + println!("Saved note for index {}", current_index); + } + } + "a" => { + println!("Auto-scan mode (0 to {})...", scan_len - 1); + let delay = std::time::Duration::from_millis(10); + for idx in current_index..scan_len { + write_single_led(&proxy, anime_type, idx, brightness); + print!("\rIndex: {} / {} ", idx, scan_len - 1); + io::stdout().flush().unwrap(); + std::thread::sleep(delay); + current_index = idx; + } + println!(); + println!("Auto-scan complete. Current index: {}", current_index); + } + "c" => { + clear_display(&proxy, anime_type); + println!("Display cleared"); + } + "f" => { + fill_display(&proxy, anime_type, brightness); + println!("All buffer bytes filled at brightness {}", brightness); + } + "p1" => { + fill_pane(&proxy, anime_type, 0, brightness); + println!("Pane 1 (indices 0-626) filled"); + } + "p2" => { + fill_pane(&proxy, anime_type, 1, brightness); + println!("Pane 2 (indices 627-1253) filled"); + } + "p3" => { + fill_pane(&proxy, anime_type, 2, brightness); + println!("Pane 3 (indices 1254-1880) filled"); + } + cmd if cmd.starts_with("f ") => { + let parts: Vec<&str> = cmd.split_whitespace().collect(); + if parts.len() == 3 { + if let (Ok(start), Ok(end)) = + (parts[1].parse::(), parts[2].parse::()) + { + fill_range(&proxy, anime_type, start, end, brightness); + println!("Filled indices {} to {}", start, end); + } else { + println!("Usage: f "); + } + } else { + println!("Usage: f "); + } + } + "show" => { + write_single_led(&proxy, anime_type, current_index, brightness); + println!(">>> Index: {} (step: {})", current_index, step); + } + "row" => { + if anime_type != AnimeType::G835L { + println!("Warning: Row commands use G835L mapping (provisional). You can add to this code to support other types. `examples/anime-led-scan.rs[402:425]`"); + } + println!("Row stepping mode. Press Enter for next row, 'q' to quit."); + let total = g835l_total_rows(); + for row_num in 0..total { + let (start, end, len) = g835l_row_bounds(row_num); + if end >= scan_len { + println!("Row {} exceeds scan limit, stopping.", row_num); + break; + } + println!("Row {}: indices {}-{} ({} LEDs)", row_num, start, end, len); + fill_range(&proxy, anime_type, start, end, brightness); + input.clear(); + print!("(Enter=next, q=quit) > "); + io::stdout().flush().unwrap(); + if stdin.lock().read_line(&mut input).is_err() { + break; + } + if input.trim() == "q" { + break; + } + clear_display(&proxy, anime_type); + } + println!("Row stepping done."); + } + cmd if cmd.starts_with("row ") => { + if anime_type != AnimeType::G835L { + println!("Warning: Row commands use G835L mapping (provisional)."); + } + let row_str = cmd.trim_start_matches("row ").trim(); + if let Ok(row_num) = row_str.parse::() { + let total = g835l_total_rows(); + if row_num < total { + let (start, end, len) = g835l_row_bounds(row_num); + if end < scan_len { + println!("Row {}: indices {}-{} ({} LEDs)", row_num, start, end, len); + fill_range(&proxy, anime_type, start, end, brightness); + } else { + println!("Row {} exceeds scan limit", row_num); + } + } else { + println!("Row {} out of range (max: {})", row_num, total - 1); + } + } else { + println!("Usage: row "); + } + } + "allrows" => { + if anime_type != AnimeType::G835L { + println!("Warning: Row commands use G835L mapping (provisional)."); + } + println!("Lighting all rows sequentially (200ms each)..."); + let total = g835l_total_rows(); + let delay = std::time::Duration::from_millis(200); + for row_num in 0..total { + let (start, end, len) = g835l_row_bounds(row_num); + if end >= scan_len { + println!("\nRow {} exceeds scan limit, stopping.", row_num); + break; + } + print!( + "\rRow {}/{}: indices {}-{} ({} LEDs) ", + row_num, + total - 1, + start, + end, + len + ); + io::stdout().flush().unwrap(); + fill_range(&proxy, anime_type, start, end, brightness); + std::thread::sleep(delay); + clear_display(&proxy, anime_type); + } + println!("\nDone."); + } + "rowmap" => { + if anime_type != AnimeType::G835L { + println!("Warning: Row map is for G835L (provisional)."); + } + println!("G835L Row Map:"); + let total = g835l_total_rows(); + for row_num in 0..total { + let (start, end, len) = g835l_row_bounds(row_num); + let marker = if end >= scan_len { + " (exceeds limit)" + } else { + "" + }; + println!( + " Row {:2}: indices {:4}-{:4} ({:2} LEDs){}", + row_num, start, end, len, marker + ); + } + } + "hold" => { + // Single write, wait for Enter to release + println!("Holding index {}. Press Enter to release...", current_index); + write_single_led(&proxy, anime_type, current_index, brightness); + input.clear(); + let _ = stdin.lock().read_line(&mut input); + clear_display(&proxy, anime_type); + println!("Released."); + } + cmd if cmd.starts_with("hold ") => { + let arg = cmd.trim_start_matches("hold ").trim(); + let (start, end): (usize, usize) = match arg { + "p1" | "1" => (0, 626), + "p2" | "2" => (627, 1253), + _ => { + let parts: Vec<&str> = arg.split_whitespace().collect(); + if parts.len() == 2 { + if let (Ok(s), Ok(e)) = (parts[0].parse(), parts[1].parse()) { + (s, e) + } else { + println!("Usage: hold p1, hold p2, or hold "); + continue; + } + } else { + println!("Usage: hold p1, hold p2, or hold "); + continue; + } + } + }; + println!("Holding range {}-{}. Press Enter to release...", start, end); + fill_range(&proxy, anime_type, start, end, brightness); + input.clear(); + let _ = stdin.lock().read_line(&mut input); + clear_display(&proxy, anime_type); + println!("Released."); + } + _ => { + if let Ok(idx) = cmd.parse::() { + if idx < scan_len { + current_index = idx; + write_single_led(&proxy, anime_type, current_index, brightness); + println!(">>> Index: {} (step: {})", current_index, step); + } else { + println!("Index {} out of range (max: {})", idx, scan_len - 1); + } + } else { + println!("Unknown command: '{}'. Type 'h' for help.", cmd); + } + } + } + } +} diff --git a/asusctl/src/anime_cli.rs b/asusctl/src/anime_cli.rs index 57065b0b..ea1dfa68 100644 --- a/asusctl/src/anime_cli.rs +++ b/asusctl/src/anime_cli.rs @@ -125,7 +125,7 @@ pub struct AnimeGif { pub bright: f32, #[argh( option, - default = "1", + default = "0", description = "how many loops to play - 0 is infinite" )] pub loops: u32, @@ -144,7 +144,7 @@ pub struct AnimeGifDiagonal { pub bright: f32, #[argh( option, - default = "1", + default = "0", description = "how many loops to play - 0 is infinite" )] pub loops: u32, diff --git a/rog-anime/data/g835l/images/.gitkeep b/rog-anime/data/g835l/images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/rog-anime/data/g835l/templates/custom-image-template.png b/rog-anime/data/g835l/templates/custom-image-template.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c56ec962d31f59fd2aec48b3341a12d52e9c17 GIT binary patch literal 518 zcmeAS@N?(olHy`uVBq!ia0y~yVB`m~PjCPUhRgS?qksZuJzX3_D(1Ys8kl!PLBPdP z`}_XS&$Kn7R1?-GO8D#*?g^M~5x3qszjE@W>u;E+?3UL)BXj&(fF$9y`cZ{L;sSW7msaa_ifyQZG zdsf^k(9WI-)14T0%>ZV_p|wxUpzP&b+b3P-(YKN{-t!{rVOSAR{q);6c=Ug*sLoMw>R!)y=xWKe{nJFrxGBs30JW@pv^K`^aQCOF*Ln0$Mednu3>3Tf z3Fy?F-P?nVffCb?^XNw{DbTJ5igBCX1#;FDXzvFGHS6rOHgVmk9aFD&i|fw)@9K5O YeCs)%e!~Q%O`r(&boFyt=akR{0GH7BdjJ3c literal 0 HcmV?d00001 diff --git a/rog-anime/src/data.rs b/rog-anime/src/data.rs index 1389fe7b..9a06d9a5 100644 --- a/rog-anime/src/data.rs +++ b/rog-anime/src/data.rs @@ -100,8 +100,10 @@ impl AnimeType { AnimeType::GA402 } else if board_name.contains("GU604V") { AnimeType::GU604 - } else if board_name.contains("G635L") || board_name.contains("G635L") { + } else if board_name.contains("G635L") { AnimeType::G635L + } else if board_name.contains("G835L") { + AnimeType::G835L } else { AnimeType::Unsupported } @@ -111,7 +113,9 @@ impl AnimeType { pub fn width(&self) -> usize { match self { AnimeType::GU604 => 70, - AnimeType::G835L => 74, + // TODO: Find G635L W*H + AnimeType::G635L => 68, + AnimeType::G835L => 68, _ => 74, } } @@ -121,7 +125,8 @@ impl AnimeType { match self { AnimeType::GA401 => 36, AnimeType::GU604 => 43, - AnimeType::G835L => 39, + AnimeType::G635L => 34, + AnimeType::G835L => 34, _ => 39, } } @@ -130,8 +135,9 @@ impl AnimeType { pub fn data_length(&self) -> usize { match self { AnimeType::GA401 => PANE_LEN * 2, - AnimeType::GU604 => PANE_LEN * 3, - AnimeType::G835L => PANE_LEN * 3, + // G835L has 810 LEDs: 210 (triangle) + 600 (staggered rectangle) + AnimeType::G635L => 810, // TODO: This is provisional until we have a G635L to test on + AnimeType::G835L => 810, _ => PANE_LEN * 3, } } @@ -195,29 +201,35 @@ impl TryFrom for AnimePacketType { } let mut buffers = match anime.anime { - AnimeType::GA401 => vec![[0; 640]; 2], - AnimeType::GA402 - | AnimeType::GU604 - | AnimeType::G635L - | AnimeType::G835L - | AnimeType::Unsupported => { + AnimeType::GA401 | AnimeType::G635L | AnimeType::G835L => vec![[0; 640]; 2], + AnimeType::GA402 | AnimeType::GU604 | AnimeType::Unsupported => { vec![[0; 640]; 3] } }; - for (idx, chunk) in anime.data.as_slice().chunks(PANE_LEN).enumerate() { - buffers[idx][BLOCK_START..BLOCK_END].copy_from_slice(chunk); + // G835L has different packing: 627 bytes in pane 1, 183 bytes in pane 2 + if anime.anime == AnimeType::G835L || anime.anime == AnimeType::G635L { + let data = anime.data.as_slice(); + // Pane 1: first 627 bytes + let pane1_len = PANE_LEN.min(data.len()); + buffers[0][BLOCK_START..BLOCK_START + pane1_len].copy_from_slice(&data[..pane1_len]); + // Pane 2: remaining bytes (183) + if data.len() > PANE_LEN { + let pane2_len = data.len() - PANE_LEN; + buffers[1][BLOCK_START..BLOCK_START + pane2_len].copy_from_slice(&data[PANE_LEN..]); + } + } else { + for (idx, chunk) in anime.data.as_slice().chunks(PANE_LEN).enumerate() { + buffers[idx][BLOCK_START..BLOCK_START + chunk.len()].copy_from_slice(chunk); + } } + buffers[0][..7].copy_from_slice(&USB_PREFIX1); buffers[1][..7].copy_from_slice(&USB_PREFIX2); if matches!( anime.anime, - AnimeType::GA402 - | AnimeType::GU604 - | AnimeType::G635L - | AnimeType::G835L - | AnimeType::Unsupported + AnimeType::GA402 | AnimeType::GU604 | AnimeType::Unsupported ) { buffers[2][..7].copy_from_slice(&USB_PREFIX3); } diff --git a/rog-anime/src/diagonal.rs b/rog-anime/src/diagonal.rs index a71f9f99..1d6d10ef 100644 --- a/rog-anime/src/diagonal.rs +++ b/rog-anime/src/diagonal.rs @@ -146,6 +146,8 @@ impl AnimeDiagonal { match anime_type { AnimeType::GA401 => self.to_ga401_packets(), AnimeType::GU604 => self.to_gu604_packets(), + AnimeType::G635L => self.to_g835l_packets(), // TODO: Verify with G635L model + AnimeType::G835L => self.to_g835l_packets(), _ => self.to_ga402_packets(), } } @@ -381,4 +383,80 @@ impl AnimeDiagonal { AnimeDataBuffer::from_vec(crate::AnimeType::GA402, buf) } + + /// G835L diagonal packing - inverted geometry (rows grow then constant) + /// Triangle (rows 0-27): pairs grow from 1→14 LEDs + /// Rectangle (rows 28-67): constant 15 LEDs + /// + /// Diagonal PNG layout for G835L: + /// - Image height = 34 (row pairs) + /// - Image width = 68 (half-step X grid) + /// - Even/odd rows are interleaved in X (staggered by 0.5 LED = 1 px) + fn to_g835l_packets(&self) -> Result { + use log::debug; + + let mut buf = vec![0u8; AnimeType::G835L.data_length()]; + let mut buf_idx = 0usize; + + debug!( + "G835L packing: image dimensions {}x{}, buffer size {}", + self.1.first().map(|r| r.len()).unwrap_or(0), + self.1.len(), + buf.len() + ); + + // Helper: get row length for G835L + fn row_length(row: usize) -> usize { + if row < 28 { + row / 2 + 1 + } else { + 15 + } + } + + // Helper: starting X (in LED units) for the row + fn first_x(row: usize) -> usize { + if row < 28 { + 0 + } else { + (row - 28) / 2 + } + } + + // Process all 68 rows + for row in 0..68 { + let len = row_length(row); + let img_y = row / 2; + let base_x = first_x(row); + let stagger = row % 2; + + for i in 0..len { + // Half-step X grid: even rows on even pixels, odd rows on odd pixels. + let img_x = (base_x + i) * 2 + stagger; + + // Read from image, clamp to bounds + let val = if img_y < self.1.len() && img_x < self.1[img_y].len() { + self.1[img_y][img_x] + } else { + 0 + }; + + // Log first LED of each row for debugging + if i == 0 { + debug!( + "Row {}: len={}, first LED at img[{}][{}] = {}", + row, len, img_y, img_x, val + ); + } + + if buf_idx < buf.len() { + buf[buf_idx] = val; + } + buf_idx += 1; + } + } + + debug!("G835L packing complete: {} bytes written", buf_idx); + AnimeDataBuffer::from_vec(AnimeType::G835L, buf) + } } diff --git a/rog-anime/src/image.rs b/rog-anime/src/image.rs index fee51da7..0f5aec91 100644 --- a/rog-anime/src/image.rs +++ b/rog-anime/src/image.rs @@ -137,8 +137,8 @@ impl AnimeImage { fn scale_y(anime_type: AnimeType) -> f32 { match anime_type { AnimeType::GA401 => 0.3, - AnimeType::GU604 => 0.28, - _ => 0.283, + AnimeType::GA402 => 0.283, + _ => 0.28, } } @@ -149,6 +149,7 @@ impl AnimeImage { /// square grid, so `first_x` is the x position on that grid where the /// LED is actually positioned in relation to the Y. /// + /// For GA401/GA402/GU604 (shrinking pattern - diagonal cuts in from left): /// ```text /// +------------+ /// | | @@ -162,6 +163,19 @@ impl AnimeImage { /// ^ ------+ /// first_x /// ``` + /// + /// For G835L/G635L (inverted pattern - triangle grows then rectangle shifts): + /// ```text + /// ● <- Row 0: first_x = 0, width = 1 + /// ● <- Row 1: first_x = 0 (stagger), width = 1 + /// ● ● <- Row 2: first_x = 0, width = 2 + /// ● ● <- Row 3: first_x = 0 (stagger), width = 2 + /// ... <- Triangle continues, first_x = 0 for rows 0-27 + /// ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● <- Row 28+: first_x grows + /// ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● <- Rectangle shifts right + /// ``` + /// Triangle (rows 0-27): first_x = 0 (no cumulative shift) + /// Rectangle (rows 28-67): first_x = (y - 28) / 2 (shifts right) fn first_x(anime_type: AnimeType, y: u32) -> u32 { match anime_type { AnimeType::GA401 => { @@ -179,6 +193,16 @@ impl AnimeImage { // and then their offset grows by one every two rows (y - 9) / 2 } + AnimeType::G635L | AnimeType::G835L => { + // G835L/G635L have inverted geometry - triangle at top-left, rectangle shifts right + // Triangle (rows 0-27): no cumulative shift, just alternating stagger + // Rectangle (rows 28-67): shifts right by ~0.5px per row + if y < 28 { + 0 + } else { + (y - 28) / 2 + } + } _ => { // first 11 rows start at zero if y <= 11 { @@ -221,6 +245,16 @@ impl AnimeImage { } 38 - Self::first_x(anime_type, y) + y % 2 } + AnimeType::G635L | AnimeType::G835L => { + // G835L/G635L rows GROW then stay constant (inverted from other devices) + // Triangle (rows 0-27): pairs of rows with same length, 1→14 + // Rectangle (rows 28-67): constant 15 LEDs + if y < 28 { + y / 2 + 1 + } else { + 15 + } + } _ => { if y <= 11 { return 34; @@ -235,8 +269,9 @@ impl AnimeImage { match anime_type { // 33.0 = Longest row LED count (physical) plus half-pixel offset AnimeType::GA401 => (33.0 + 0.5) * Self::scale_x(anime_type), - AnimeType::GU604 => (38.0 + 0.5) * Self::scale_x(anime_type), + AnimeType::G635L => (33.0 + 0.5) * Self::scale_x(anime_type), + AnimeType::G835L => (33.0 + 0.5) * Self::scale_x(anime_type), _ => (35.0 + 0.5) * Self::scale_x(anime_type), } } @@ -246,6 +281,8 @@ impl AnimeImage { match anime_type { AnimeType::GA401 => 55, AnimeType::GU604 => 62, + AnimeType::G635L => 68, + AnimeType::G835L => 68, _ => 61, } } @@ -256,6 +293,8 @@ impl AnimeImage { // 54.0 = End column LED count (physical) plus one dead pixel AnimeType::GA401 => (54.0 + 1.0) * Self::scale_y(anime_type), AnimeType::GU604 => 62.0 * Self::scale_y(anime_type), + AnimeType::G635L => 68.0 * Self::scale_y(anime_type), + AnimeType::G835L => 68.0 * Self::scale_y(anime_type), // GA402 may not have dead pixels and require only the physical LED count _ => 61.0 * Self::scale_y(anime_type), } @@ -269,8 +308,8 @@ impl AnimeImage { 1 | 3 => 35, // Some rows are padded _ => 36 - y / 2, }, - AnimeType::GU604 => AnimeImage::width(anime_type, y), - // GA402 does not have padding, equivalent to width + + // Other devices don't have dead pixels _ => AnimeImage::width(anime_type, y), } } @@ -405,13 +444,35 @@ impl AnimeImage { let transform = Mat3::from_scale_angle_translation(self.scale, self.angle, self.translation); - let pos_in_leds = Mat3::from_translation(Vec2::new(20.0, 20.0)); + let pos_in_leds = Mat3::from_translation(self.led_center()); // Get LED-to-image coords let led_from_px = pos_in_leds * led_from_cm * transform * cm_from_px * center; led_from_px.inverse() } + fn led_center(&self) -> Vec2 { + if !matches!(self.anime_type, AnimeType::G635L | AnimeType::G835L) { + return Vec2::new(20.0, 20.0); + } + + let mut min = Vec2::splat(f32::INFINITY); + let mut max = Vec2::splat(f32::NEG_INFINITY); + for led in self.led_pos.iter().flatten() { + let pos = Vec2::new(led.x(), led.y()); + min = min.min(pos); + max = max.max(pos); + } + + if min.x.is_finite() { + let mut center = (min + max) * 0.5; + center.y += 1.0; + center + } else { + Vec2::new(20.0, 20.0) + } + } + /// Generate the base image from inputs. The result can be displayed as is /// or updated via scale, position, or angle then displayed again after /// `update()`. diff --git a/rog-anime/tests/data/g835l-diagonal-fullbright.png b/rog-anime/tests/data/g835l-diagonal-fullbright.png new file mode 100644 index 0000000000000000000000000000000000000000..a308f1417a38f0c79583a0c8e0907475e78de0fe GIT binary patch literal 205 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⪙i2eXWn)B^H&-mvMKej!)@n^jI(to@OD zsLV&Nl1I9nm)PfpG3*hAJ|r zyf_hVmuomt9$%;krLGs3Pt`K%d^a(fRbF7VT}&;sziZDac0H4uUF=qtG>oF{AB`;_ z@ddGPyw4&Ij@#e`xa#js4+(Gl7r!J%*CPIiUm97#FF||hwS&ViXh#Q!3T$|h_2iq*3iS!fliQyWYOWF8L!A&PYfn}%^1K1$i#?OY<} z_j|OD@CUr_{xZl%+XX>nV9rJZHSyGd9#K+GEXt`QNRDA@8YUw=PAXG`HvoAF!1Q%* zmeET>yb~#JR4=5RN{|;b{$g+FC4ZQc)s4UV-Jrs9Cu^wCR@zIApk5GxFj2ik-bXLO zEp$62tRlB{y@E?nT`{W_U#~>!ouY<8QI0Y-A)?zUA#39W^=bCl)+`A`Mpq! zy*RktZBb`n(**9rQH9Gcys6ljVs|H_a1G*6>8gsZs_3eU{#ixMFaA+Qsi$f2%C{=& z;FhUdVjGu>p1ujVWH}P&wtGcI^$K&3J>>H2JrAisMFY`ne$fffcYl~oPQxSs6^(d3 zm{rh!Zmj|vV`z&QSyoY};Kv1z$YF&(F~f>06&D4n*|g9dJ;SxJr?!3+XX25-5SA>A z{A6(L-BELK`ILKY)cf70==2>39(7E6VmhWssHY~VX{a;#zTAd_O_s*NdeZHn~ZpHC2b~QNiji& zmwDZm(E6z*Bgx_R8C5_<(nf~5sUJyT5nc_rRIq!Q2n^_DVQ7!@o(=$_*`kxPY)V3Cw5akPN-dMuhJ`z_%S!4>$g8@sQ<) zSw;Q>_6B=Jh*{9AMnT-&-rVDIPFGjz`=lDM(EmO+^=QLV!=ZGf;wZkKR3p24qO2HSiZxf6{T($NW;56Mc`#%E|LTmAX^ae1AY&4UPiC& z5#^h)8#>;=A{cqt$wIQnDZM-7AfiRcXlO`@^7_&Ui}q;Gs57OV9$};HveROb*1N^{ zFuMce%?Idb@`j7SS$DFR@v&E3nA5iEWCb|umUrUc@tZK(r0XUQFzLw6U1bH%+G-FyhCjL=6d{`I0WtJ z2LeYh2uljHkg(+D?}*l?&@VJ(NBQ(3;O4A5bvoI<8B99BVCvN>0D}pt{11aE((6o3 zMelV|9z`$lQjUk1e|;sn+xerQHw(S4X-5q|Q_6JBJvJHS<`-t+(~?la{+x*-@(E&H zF`((U^tqnk8j76L8}tIA-yB44N}Sz~q#eyPC_T;%q(pi*w$V!5s>s>Rb}>vaglCtj zhQMKDk?#6cTzBpNPhJX8>E4o;AW+tz6m{q0$D963UdqW#Yc^W=sHHL!GXXg3TB_%Q zEdrE=>yypLB_bEQFA}@9&q|SAOe&hA)}s>#Wwr_)%16w{=_Cb#?Gucer-kg})+M26 zn#G=RgB1`|`m6@CQO73?s|z-?>G9GT1UHWtYs2ly3u5rEz*z?tKyF0&{CNu;~y190+DM29`#Evwx_-A*rCa-$G z`2M+-%RBqq|BXPh&yb4b{-JpP@qh^-++2ye z0=$_h6WnamwV+&oQVh!V4_F}<+{Jc~$Na4g0BnXyA!cf%xjvtOJ3~cLkE@6KS^c@b z$Af;IdknTl2H=+5Z%HrBJU(Rt_qh=iOze*^<$WHv=<(*<7wv~m1ijL_oLDd*yy;%<@7my+%{2Y=lQWIk5Z8< zFY&%f?7YvpMVX3RbD<4rX)aOTP2Q!^cac)GxtDI7m)7+&ujkZ7(m!1}S7+2Bt#;x7 z5h&KLyvugq7zjX}8<>2-D2pC0YC9A?QD7O>F_@3}<||02Wr%R1%l;$gLb1n}^!l4Of%9KB~VWH7Z!6a(2LigYlmObyIDJX(fyEJ|2t z0T8BCbKh?%Q`eA|6zvWuJZMgerq9m7ZFUXn!(C2G4W^#AI)+Ow%(a z`54};w7^=TdjnW|mEx9S=2;aDk778D1@Hg}ijI}GB&yamA+UN02;vKfly^I&F54a|KGVv(dn(3CQ>TulfquS>PF z9cEQFnD3xt6);}&(7q5pRwc=QRW)IHRNP7Xr*^$aJn8Z2D+G;>q!!Jl%K~>w45X{L zA~sHLahi+p-~^Y(O{Z9YE;R8NC#=q=tl|+?@d$syBba>tN5h$H=P7P`E1ckkQaE3+ zvOBhSgfy%$bJ%cvMK~2I=gqf`zq`06cmd(OFvzBx*##*)ic>x>-3Em70JpsEhX<6< z)4cLy1y^mBt1A5DO5~uku$+};04y*@u1Sq+7KxK>-FsA-6;Hf&(Rg9Xy&J77hCISw@ljaA%hp&V$>IZte`V zqAmmZa(c|qv36YybbS7pfwf^o=TZ9f4X9J}eQk4DQ%sTDJSNKp%f8Y8>wEWb0~<+7 z1h`f^Ku7JS=4P~@Jye zH?1EPzl>VbkXNCkWNGG2K1Jw*d+GTmFQfgZ6jKkm(5G@I3Hm$Qsv+C0cInr$4QAavW*>>lUJ9FEzV zA#vrV>xYttx-7#1ix9~W;S46OYD$QOFfq2<@%{4^rR)(Y7?u9(^zDBUm`wjgV1ii+ zCFNoNAuyR=`C2sGfrAccQjjOO>yi~bkBbU?uFGGDKd0QeFUESv+^30(IId@1ckAH8@C zM6}~(K*HLIr2|zR1o#5L=Zaw#w*pchEW?*fnQHVxA?Yvr|AjB z9$Pw-Z+}yRa572}6b%n)ua%*zb#Sg)Z&fAo+&D*dtr-q==ft9eYN%$LAacuH;(RM` z%;l+}#6nh~=UAdN zho?{&BEg3|ca88wyW?hDLEW7m)oB-ALRB@0`9dL3Q_*kCvK{Q*H~29ydQTM5*;kmM zR`c`_B+^GDYCfC7%^WzUf4-PudCogpY7|cdBMNZVak;(ob;bDNF58kf`GuKVuSO_m z8zvS6eaD?yc^%B`)JVK<;zHYY1T%g~@pwjl-|ZZ6p~qpbG&$5JCS%A*+S1ELQV%D| z(H>_8;`+U>_5zUY1zdJ7`H&_#%r3bp82t9r7mC~}z!{JIg}^1+zdHyX&1y;dwKn)* zUUC>Iy=iE3Z4-GxNPyF}w_7l6O09nb~4u88xF;;5G{<3hWP)Va4v(iLF@f)WFmMa-2lp ziT4--!Cc2Co&gIgFbLcCW+%{Y+PPtDz;v6-iH*N4MRp>oemp%C&ywhoZoPAG;;vrs zR9e>yQ*lN!0G0jcocv9G_ZT3ZdxcGbqM5TiLG@8$N+0vHS;Ku!rw|UX250%D^m~8_ z40Fm6<(qGgZU%5euQ%BkWv?v{prX`2ryc<3W_+bB&NE~s$idh_6u&+aIPB8Rm1rw!}(LgABB^8mc8fe zw=bMNyB_xHTYHyZI4@=f=kLWaV%wL6Q`=|b|B=l{Dc>Kk3jo3yfSC%zHHfrF+z)mC zyaNd5wWcfiNBkKb`>i5GCH1A{#bBp;QJw;Y{Kd-rK!&^Q}xD>E~0=yS0FGUfYJq7>f9uYIgDf#|g8QqfF{&1A(GSGx)_`S1D7a0gtNk%hrwNzQp?8?d9}?O8roM za7x2`x3eO{Q0#pZtZc2D9C>+sX z#~uSaIs@Olek{>S#`mjk~XqOs9@3GIH$%?q% zyJ#3l_-xM5=TV%L-9xcYAL$~cY1pXvBE@8%&rM80vMzDb zz`B_oR|7@5HmURRTdHHj+pmtN4ETLpbLe=`PXkt)0*9tcAP^JsxX#46Lk&VzZMESp z=Nht0*9Z0_svqSYZD%8Fv!tyh9^1TCev4xQoDP`)=nCf-07IOZ-IFLVC-P z%`yLZIT+K?=rQCbz>(TGUDD^FX;>(BJIylKkrR{jAWy_QA{zmyB`Aunn+vhpJ-uTE zwba+JCnwox>8_^e;Rr0~v?A$ZfLa>S7>WA?7hZU3gbU&1ccpU6G#&sMQ=q0K2;4#n zHErUV2&8HfSO*JFsI$72t#BIEm@r;bkY01rgE{rEUvxT5IM`cRloW+B4TRIZ2scK# zr=LWRi5P|AQs&?!G8DB1{B@)jEov=`hC3rgFgX*&(IOflABS*Qb(yO!bJb=3*=0Wd zAMShY8Q=8rw_?dTF#Z}})we8`UG06H%3+V~eJd_gMdnrUzXG|zZ`T2_Jbo(`{n&kH zS54N`rA$L3Sy-}Nrt zQRP&R1W~(KER!P8v^33Z^VgoSRk*KWL((J6ZOLB$Vjkh>YT1-`6wH&Eby%Py6gGlx{U|ggP;{5CT5oI0UbC!yt|T?);?Wc-zx%Q z`3%!5v<^Fk1`Y>>2Iq-fWaaPnTTOJ%)V1S*0@ zG%}@Ajo)O6+wCUs@(Bv`QSNr8u*u;4Rl$Y!d<MG+AC;g!ff?=Y9XaoRU(3K538*aGYN{xvdGC-JEq2@Fpxx?c zVFUh8+d!V^_a!jcu~74}ETOrYfT3nr$wCQ&5p~F?hzBWHzrSd#EH^GKrf%N&Q)9fj z|DExLVuoVz%@A0z6oN&~1d`t5G9<$=CO zKwXLi)Fqb@|L+rhH~D_En2q&#wQ!Y(bTmrxt+r~xC}tJujpO~3z2n&f@wK{1wA+W2 zx?5mP(tc(XYH@qkuIJ-QVVt*CUKjM{mF9jNZvefEY z=-Iin(5EwTmp5JXE0-HFqWXXjTQtGh1t1Z0l9OH`D8|vaM_LqkgR#H@=N-_nG`H>I z<(QK=y!`*Qccy<$pLzGsz7Ze^TYv!B*cStY9W)66!X{x+QPGA~6jVS|RMaGd5FlV! zR8-WkXi+H!L`91&`zBgI+EPnfwpwbj!(tuUVQzmH+WAfAzUNQy0WTlJ1JC!I>vP^` zR*pMM@!?g8MbjN{1j#Cw6ZuV|?dCGbH`ofxfl?BU>Pcon#A3J>D@Re*4In}{-QC8z zQIE?hPMlbA22}zI8CyaGR37~j~a)|*1SIrws?%7sY zvCCtHo{rzr;-NDeSK6XCyFYzR3bj@(PRorV^yB@tty&kYxjmwlaG#a=XS$pIIpX?- z=afUD(*9%qTdBeq7PU#s9q~^vg51j3uih6SUUR=WZ zlOa?=KA6On8;G*GeI|5mkO|)%Ssr$o%VC`H{`=aF|HLkFGftn?xD>m5p-kND_huC! zT~|9+ZfRSzPI0vW4j4{~RyYSyL8l~?HI|*|0EkO>zVKPjS$X38c?ci@TQp|)6St}) z1+)w5tWf8t8Qmq`)2+{*c#R}=YdK$UTpqY>pz44TLUL>REQ-_LN<$nzUz-8Xxsa7G z$8HrplzI8JL|}SExpYe>Mzj=YR^UtDX4|ZedTi6Y`stSxsSMh$AMX%PH{7!J97W3` zoO{Pv8n8TV)6!?QOBW2Z@>@In^Sr5}J%*`kA;H!c;-U)aMH7+&BQuX9{%Gq4cPPfJ z5t64Hn%XQkFIjt8Xj!@{2_o+z(D*ppu)lDYq<%0&w1*iSTW3U9e`;kT&b_OBW)pH9O zNd-MlD?!O0i?qbR!V0p7ccoY@=|o~J_#G3G-^^MDNG7X~)$;p_@x}xj2uB3ws*FO~ zFDnzapydakj&oh@x%%#8-(IvL1%n7TuaMc zARtQnwe2D&8nJAfD9i9!%Q=Pl{_Le5qUV%Kif2q@@CdGgc$x9ed160Z7Dpens)|b( zq(kZTC~^!y!Rv9uB72O49ne1Np_qgLn{$oJSo<1P%OLZ@bbOowb=bMjFiln#WU5?) z7BTOr02Os`UK-}~sJ%ACTBT1%n?y35;b1Q%Q$f8P8>MCc7;@){>yKf{Puzzo6V0T%Y4j0ixlYZpu1rl~+TALT@a&`W?QnCz=`4m3#`=?tXhrY;ZI*Wsf> z*`NOkh)V^+C^U6X$8^5&?u_elGeBq;*w(C;hq^tjdiKQU$M%&p^VsTU8NldE_y#e# zkr`A)4iC4XLsSv&voH^CoK~qjp5FGY7IoviCq;a{o%O`smbb>smF(FD|A>wMSj~&r z_)A0L6ZW=~&v)ZWoofP2Pxy`v+COs0EAe<)?+OYz;=HG>UkZ(jY-Ub|z_d4oE%j2w zBVXb+baLK;jgqGshr+(?t0ILmFRexnm}XZkrIF(6M+<0Y#R)?Wn%BIoc5UeZfl)OP zDFBEi(8bvuyOdA7kN)k-CZL^H#*&9k+77QQEPZbO9X0|vqs z*2%2w!3etB(MF{)=^CocXk!B8XP}*y{K6NL-;h!i<%aIwYBi)*iXk|q;w1oFOZUru zmFzaJ5L1@p0>&ByOrkPS4Gm8L+=P-!-(Md0{NXzD6`7u*lAlAPGLI#X4>l%SB;xeyVH8 zJoUL6vp$7HfuWbEe`U#Zuxwp$QyQTnXC=AmJ#S^ z${r;*&?r?LSzsccuL))}iI=q;h8CZPTHj#044R#;hxU{A08pXn{?%r*9$%E*IJ>Q_Blzd&j4d#}dVnfu`+-^g=6M@fZkm1+WmRs%^C2FpbsRONhci^GVy% zBjuD6zFlcn=Tm1emZvE#D6=hMC|_^GYY|M_a(D!$yNNByH$F-z54L@<_#9(#LKzuI z3hxlX(M(?z6mBF~?8LAZ)iT4qQ{8mmPTX#rM3Z)xJiT%P+$aeGv=svQgkl5-0=LSM z5H3Ub6N6}ngc){&7KJul0(U-QM$O#P~c+`WMfVlv|L)yzm{I#Q5Wr4=0UQZ7&&ZkNX z0Th*o5o3&<`P#m%BY9duU=x$HH+!^zQc3^~`YTB-SgSLhV+9r|^_>U>+O`I%mmC{e zV62#`8KmDxcL}v^RgV?fT#3oTP}8fYdq^%lYN!g;J3Tz^Z)u*UFp!%l4t z%Y{tII5g0JGs)qhb!|A1D&*akVb=G;x~Cqm#EClYUsAe@?Js+_1etDUHRowDMWDGz zq>7+wL|Nh)-?fQ~2;X01{^2h78?y@ON7Qf2@=4qQYB$C{dy$9nZq*JrS%oofcy#7R)OK}tH< zna-aqE2Q7bsC{hL1^M>>(3cJpW(wZZmmY$mj>l*&Jlw7051Jelb-=j5M^)2(6d8G@ z(E7ooONr}GVo=n1qX8wF?>PQ=P1tG&g0v+y9%Xh)P#f%U-`Dkt*UyuO&S>239(o%5 zX-^AQjWNkSRUsJm{(hQ#ovzT`-6JU2wiET@vI#*mutBF+~g(%+seX7XzE98;B_}FX_{{stEiDv@|ZB3=1Sa5Fyq{73Fem= z1r4mr-~l|}88X^UiUM68^Agd_kmE30)apx zkth@jjYeZI7%Ucx!{M~FwD5Smwzjs8j*hObE`dNG5{V=dNl#BtUtizAz`)SZ(8$P$ zOePx}8=IJzn3|eWC=@d@Gjnru3kwS>l}e-0=yW=R!C*3(mX?-QR#w*5);2aawzjr* zc6Rpm_6`mXj*gB_PEH#(Y+$ii&d$y*E-tRFu532j&CSi--QB~(!_(8#%gf8#+uO&- z$Jf`_&(Ck;#*LdcZQ^h^TrQW#_=2l@OUpZ`TZ!^i(aK8a4@6Ym|UTfyt}eeWG8&_ zYS%NjM*Uksl_0&{k#(Ud>2~hPeZ4l(S9p8pbGhBgj!(B;*%}Npz(ifzxO1F6%}c@& zZNpn~mTr|tS@<9OIQRF?xqvfzZks2>ONoL?r?fwWZ>rG4WJYM*_ylW4B1ak9DLlFG z0X^0M1lQ#5#agmu;0}#O)X9NCkT{1b4HsF$hq?gaEF}7J~kKmv#>r&~ka8f2i20v8sHKF4oXc zdi*w(6sbp6?>>)wG^S($UD1ghkINfowN^_3{8W{RBzn|OaglfjRtF3f(_zhhF}b3% z@o(+^n>|J`(T{D;BIsyxl)m%x6NZYnLI6me9O)umf{jFMeAAeyZV)2z%|VI!5{H`raq@4Q6_0~HDKaQ z%`0MAaRCCN_8#CYJ#%;#dU{t>jm%)1tQln{XY+7&odhr#v)BesqW(GQuj?}{I)vZQ zkdY;53K2m2Omlr(unwOB?4Z}Hi92BWa`KR__fLYAVaJwKdZESP>N0!sAxsq3_AY(} z?=jt4x7qJ!LGn6c=`Gkay+Y;1IZ7}|AVpACp@&ySOU{Ax%e?T#ppcnoi~0A z39-8esQC^ZYH6|64N5|=S)Pg*WSA^mLK?@L1&< ze8xd@_+5H8+5((ZM0_e_*7%-HpKaC_F#Xm6#eqj+dJ+|GlTY^NKt9OkhsCiEi(~(Lam=gjKV%aiT}R%_rmOtdq^O>E!L8+>cXHTy zx1{gi1-IMWQs=v#LL59 zTBjhCYW;Lz(&xa7i(9&Xw2!e3Q2q)i2(PN{ioQt2>isqpdEHMoF=AX1IJ~2}EpYH= zOwmZar?{i-0_l38q0P>Fs?3#b*DN)|^EGccxPY)55S)GiZonGr>Lebe-ba&7FOf z;L#H!3&7$CI6O5K!p#TFZmJ{!^;8{H9ra)aY5w)9%w0VqLE=4y5Ck?|#MQC~*Pur6 zQ-EJyp6U`}b1Szt#IjZ&1h)(|xU6NV7J(kig*j z1ThSg1Y~n=xZX1=bo3IanKv)^jC2&-(dKYnvM4zh7Yv+~5P zXFjUPDt{_dZWsZX?$!dI15>)rzBCzm+e9ad{;+&@*rlZ{VUUqK9FH=JWf76Y82#y9 zz5O{*3?;INhqb@jUi*u~`6QBnn$ZfHtBDz9DDsg;3C7~GFRa+6qX*1hK3?R7Io_Fn zjy5@<1pU`N9ZP}KQ{esBzJdHK)ct8?p0@XM8AF@hHCc|cdU#-+wbTk&OXrf_vz9im zvzESCXDxv*0e%0+wiWvHf~Fl+|H)c6_mJ3W1L^Z#d6^Xh+oCagy zPrZMge9MT~xG?Z6+;f0A8)9F{@9H<(r&tOwh?{>NZ2u|2RoiVQ^-!tXtV3eBQE1*Xe#7HAsnY&YPFKixV4M|Tma5k=OWC=HX_wPyez^V~ zv$QsiF+Gu5_FphdSz7F|>bf*q>Hsa@Xrlut>R7RN=B4_#e-UU=1a6r(436}9o-5bg z1>y?!h!|r|u*b)65Y`QnyPz|{fhAoH1#Ey(9^F^sbZMu~-Lt60b< z)LIgGJiukLOS!)-*=SIh-xT5r!T5^3d?B!O)A^2qz&MzGW81g($IEbW*-``L1mB#H z`QfZTjU<*)>1O^BzzF1$x;TH9c1OQf28a9q1X1>Kih0 zOc-gM26}mYW6{`Db5x(ez&!K_leo+;I?v9=b6vK4Q&(*pcbvLi0_PX&*b9amxII`S z_v4iP$M4?So>ml|G(JVB<>teQqETPE&=YZ1g^h>pV)7tJ0QUNkv~2V$sJE#vH3!8xYY z$5c?_Gz7zu7{gXpm#$|l6luB_0mCrML|s^`D;#MdRoPw>WSBw*51mfbGPq$#<5I6h zwr_&seRKXiyGvo7tB4OIMGUCB(Y8YH(7A_Mi?&uabPWpbRJY)4F7jP47JC$BJw(UW z3@!@IqC#M(-0dTF19Px&R-Q?omgDu*u>tdgsnC2Kmt62thK8}k5N%(A^eW<5_fwDA z7`+nKeKlC9JY40RPjqhpk+UB2Ed&ZP;)r+{-#HHGrL-N|>@h-cEx(kZ09{qTwnZ4m zE(653KJ6niLPAm|nqr<+RKjXkbq&%^wgO&4C~G=T75-WrK-!zySzvkwF&50cmfmIZ zaf2@?i5{+}0nU6hOKLnRnD# z0PM@0)n>orEIn$cgGH`IlR)yGfk-sHNJ$Ub@Gw>{+GNEYAPZ&{?B5CuHQ?gYl+Lf#)Lxp(T2`^J;CCTz~&PKxD5KSEfF}@ ziMMrMc)t=Ida|Kixs0XntpQTG)g}^o`wrq?jHO?vo`l6cim4i)ADO7>Gfv9QM3J-9 zG|cUOf@_KMmynfVhYm3y`Blw3511TMO626^hUP%?qn@=2TlH$!FOKiW(FMzW@5j-@ zlc4#AJ5@_^GX*;eAeJz9!rlM2xDOzf;&gjJLuU!l=P{uB@(!^SKGqe=o)8rHEHya`55-!Z!<~A$ZFT0eQ(q!5YVjM-DHu%z)m3%m zvtP#(FUR&eDr=PK!qTiKBk~R9HMrf-4hTlWLD6@x@|gT-d?vMr;a!H?wwNHl?}>6a zHF`NHN2*&C`@v>@kj)RW`Cnv{QT12Zj8|Iw_q~_R4dNLAo%v2SEqX~vhpjHV-qkrr z&b#lKYr4?=;+3&0kjy+BR{kGEo#hyv^4^(-HMp(k7C_btEwe1_qEqP0H0<=TKgBU+tdoOeukJ>tIA38mh(&@+}!$E!7E zFtj#J(5RHS&J%BJ>$!L=I-EF&GdAY?j!cQ*a*}-(xhCPdPHpIo0|uAwy_)oz!AFL@ zRSN-p2#z+1U0tN3^&IxUW{=5UZrzYF)Z#TO$LDrw;X%g*f+P?m2ut-MoLDza6fiF- zmy7H_?U(`VQ+%J|G{zBRw`MV5rkb~^2{1e~oT;FnP3;=Axu^myd#UF#%3^1I8KMAP z!|KuAYEu(PJ~c6op;rlO!QoI0h%&RLntRE7PiVIetyK*@gDQ(3FgwrW;jC`vmJeB9 ziR&7mW%o#shJLFY7>*bRE-%+>^yiTm=)(^EQ!ZhS_v%R@miY_ufkuIAY93aP!|4!1 z$tn)gv=!LQuf&We?A}$dAYBfj7Z^pZQsGz=feMU~Al~Lck#_Yg1aWsdj^jDOdiK~J zOfsWQce22Qv_lS+hor@*y3k%7Q!-*wZdrj@@qCfe;YOHLV7517T1N7r{G(edaFP>1 zOi8K%=fa3`Ec2>prm*S%VwM7FheiL!EMkpEYfFR7*6M^| z$5DKAiSri_J^cM@)^fke!*k7nrn!sGSY~52>8AT={p=H;*~Zy$k1s%<$L=DfJji$( zxyBw4mdZoy`>5-LrH;Q4mXwaQ>x3nQbu(P&p*%s0HF*QByK!xCHp1_>s<*DCu0z9J zA(knffUb1adD>s^c@$j7Yq1eP@|Ut_&$+gYgAR=p0t@QjEd!AJ9X*k9>cideCmvs9 zCl0gkAs9IOl9*aFHN69{iBqbUP^PEnxxtKE3X-y^W4r)sCy#LnqJA3B4K(;Dm4-4o z@EQuZN&_S<*FnXzaNmbkQn}d{RU`tS6>9uRJYV27_l;)?m>;)A4ZBP!U3LBbITf@i z`!K>_VhZj!-+>c=kEECzTKgxu8J6gIXD+>U62|* zCdk?1CFKr6o&FsaKUshJgX8=logbw0ze#8HU!{|Vx8}c>&R)lp-}{or-;ofE?=IJ; zeR(_f(YoWz`1)?rcUSf%zF64iv@V^OK0aXnFnjVQ?eSf^Xds;{zkgd0@x52Z7s_52 zgBvOeaGY=MoR9eK;lA;lY1p+JLJNoNolF$T95GD`uZ z^vXDu1Xq;M1M2@pI1+F9wy)GJfMJ`G&Whyn<$BiXtEpW9;a3B$?)ZI&e1F`-V@u#J zNPmn$w?g1}{qp?D`|Sr0jR0Xo1qmk||W)0k6>v#bHbniO%y3w{Cm zECEO-8=!2(V4s?CUL+l~QpL~(^aGQUUb4S0u^V7eS+FyxLT80p1$8;tvMwErEm!!$ z1}swRN5Lw`+@b_aNY#jugp5TXo%yv|_U-Lap?25Pmla#mgwrx@*KCahriGwRLA8Yh zDb8;(c*NxyfidiGBfUjnu{T@7*R@v&xp3gHIS{ca z!T<6BlOr&U89xIGH^sb`=&<>te3R6*=GvyaVjh;7r2ykDz6%}TMsy+rMsVSC5yso+ zmgID4ENRdx2nOQ-m+vbnUDPR+Az^Zb%HpJ!g^p!BLiK>bck1try zLI=tTumW-fhL+bPOC1|@yuZ#^n*Ecpq~rAs>tBqeU7pY$ZQFYFEr-E%#?o(pGM01! zW9hA4m{VO6Phfs63P6@TG0y(Phx`0XeHQ0Hbo)!kop6sH{kjl46|20TvL|=6SAVB3 z5oK^d3{^DErRSBp%`p8-9F1#RFwC5V=01}YR-l}cj;}>qm9&yVSP$vXa2u){$`zJJ zFj;|=lZTyA#;L==IxA$$fUy+T9p*LJQJ2Qp_nxr?)=&S%SPB|fPjzXzebrF6dE=YK zppA<)x0tPBv{eHXcYu>eqjutw6mS|f5*JJ8HvYjg$oJP8UFffMX0Hb1O)gUqZIK#b zKhR0MnIZRP!t`!-df6vx)T$|rE1g5meh)A9Hu zi}W5Oq*pV%C`kkXdix4WiDHt^Jh6;qoOU~&cwN)L-bGX)C-b#Grf^w2dR zpJV%=TGxt3>wQm8wA&`WP#SyghrMbB@_B<-`2u1Bx7NB~FM6oj4o4G$!tbW|-C7vc zF>ndE`C8QFA565}+5?zJ4eQLK>Jry&XMlX#9S~&J`tT_7!0e!&WX*;Qy91$#V30w->PywFgad$=xc2=_7au`AgD0%A_vLTK=`DO z8m9Z{7uBT48$S2+A7*!Q61AMK4UhmxNE^b1)5K!1w;VSOwmW=lv9{NHz|m#>aPkoC z+=#P+oEZk4;aXyv;f68oqkYso0Wa9r3%TC3GP=` zmD%N3t8+HB3bSK5&T?XSyVxIXBZPDzsM#7Aez)Oxp~JPF1f1QK*cpXIj$#@~^siUL zkVa&U7=on|xF|-?2weXp>Gfk)YdZ-C1|e%u1|j1kT_^(^5jgoEyamW{l1kO38<-s_ zH@^&<3U#RE(-o#7PpAxUnN8&)K#&bVk<^?nT|bc|fO3e^f=Rx73P^EUMtr)Con{J* zT((nTy1E8jl+m%_8U^jF!v(D6qZtY6U+5*Uak;~W*kqBxaZy;8SGHhntJ47?pkR2^ zp8QcPI1)}x2a9cm^P`~ElG~zX-!U-u*uJ@PtdJ?418X$=*T?(JGMV%N>vI!8Lm$P} zDLuYO?^e2gK2?jeE?thq7eS>2rF>bEN;uDu;mIHqi1X*<9!ah!B)S4Zr7tKVz@-ZMeW%LK z92sFyUSy!yj{D-ZHox8nw^B$5)l|6$o=&P&AUB-Lyqc!!HkoasZ78MDf|I&*^Kw39 zw;v4X2Lt+F4Ctnbzbfc-iLLNYZmr|7DF=GeI|Hg+aNxE-7Q<5AFzvH-w*MY^hstjxc10wURGL>xc9=?3se_=Jx8} z{^)DX-JKtwaqDF|<*3`5dHvdquzU5*6%FM#cMR4SMh9*9VdLplprH6A4qT>V8r88# zy!6C8=<@F0qU295JQN*CfXWtIiAYH%pe5UR4 z<$S`hZHoY$2qDA3k*Wi}j89H|9WSs{gLiEv`dTUv$E=2o2~1D4%X`4Sr6iB&yOuga7SAmy=v6TQC0;Ic!O#y*NCL=Q z)-(?QQdQrh9a}Zsgx!X+Lc6QgGlN!@RNjE;ejztN$44fFAq**t5Ex0g2*tU9?!ECz z61&*pS_TPAJtCF_5PT{LFoZT9MA9(I+YS!OqDR%Ox4o?l&A1Fx4_TGWcY>$PIbc8y zeOYP1?j@%TW4z=aVW&Q#3eVhkD}T{-W zzVgi=^HFK!S)%9aqV)5%t(gduor};QMqVmu(E37#w2%qzlLRJ@HcJB1!&ULf?eg_1 z%Z*e}(YaR-3$?8gbzx{34bHuK@WVR-1C*Ep>=E(qy zt79Vu3)2-HfV~~lRaX1&V95j`xY(|o|RW;{RQV0oXtHQOLp|?jzmLFT+u%TnkPc=>l z8mCmp2U7N9y81285W}Pw=Ravn8Hn;lt9v6Z@m@a+0FK40o8CR-BVL=^OqASH}U8#!|D!{HwR|W!z$p! z@bvG24ECX|>X&&+hmL2?}#5O0~s@4_hhow($VZW`T1QnXXQ+jw$H?%g%2 zbD(0|Hvfxk?!^BUi9l01#=XZ01#CF}(t-D0HnE2< z^7lP!2*2`Td_Ti4Zq_cyO7C<(=yU&GvD=@u8 zy#R$aFrJ^8-VZlsZB-lobMH!WNEy}TKvF=_W)pT?tNRw1EJ&N|7;&BK{1_Bc_S`wJ z|0TacMe*Fzr}|m?LhCX+e>oM%=2?2kQOFI6>kGWG>;C7f-?$Er+C_C|@2XoUi!gGJ z?(qGF*Be^JKC-$_KHB1vG~GU!24pk9+D$QlDW4UP>_vR*isr-#+0t ziw}=|OYY?M;>`T(6%toL)Q#;u>P(;hrYr5auU<$P{ou(HiRN%5XuEOE*T#pbS%bFs z0IrSwLsH^Xx3&20Q0I@^Nrk5|ZXH~V+ak5sFmquPEJFA2K$G5Py-B|Y8?rA+ukEMp z8(%^epa)0zdV3m{KxaQ`25VQ_Ix}cjA*KtM2Uk0LO~NxkZs*UbL7YL=&=eLbUum$_ z`Sj&?b^1WFEixiskF_e|XTI4z?OI@Dtrp)L=p6`>n$H;iGK zQJ0AUBv*}!i^MD(FLeCGK@aaSQkfU-^>uppuydPc2Fp0r&>2ATZKc9cI&9Ew{4@kW z^WU^r==v^K>uq-HpMNVIvMQL0?=uKqVEJRML@X)NCOhNH>+iWst$@2Uy2yJhI6V@H zG8AICd~HsTNTwZI8rf_1>5Ga|pNUkxFxGWd7lwYMJrZRU$8YY{ONfECP_ts96t?Gw zNgj^v0FFdgPDGwJNmiDjjI&!KF%}nnVUKOP$4O6EUsI!>G*ym36~-Ak@p7Z9H{$!v zlnzlvHlNQWfIe$BJi==yt=~=Ee9J*U9w$)43_i<{dm`w>tJHuEVo6DQYo6I3Z9$tB zBkyNr8w34~9V zp?Ws=Mo=4lyYw`Ry)K$*?ZfIs^Pg2{&!p@+~=wBU9(6m5YM7p*YQTTLW_Q_AFO36@Wza- zNdZHzVp&^i$=XGU^2d7Cobq|m*erk{4D7gjk$EG++&!f$iMB8BF~wE3Ao}ChGI$$2 zy4tumx({#Snl*`kS%D=rM)hU^&$)Dl?6G}71YNnNH1(3jOFj4UA#;(35$xe?i z%s)TzkLR7UeI-?^GClr?1hZjLjWF7`+BsnT$zrXN{X=edspqfRhn~8Ay+Fd-Hn4%` zB&!Mj#0anqMpLl_k6E3Z=rA|OuUv+0G@xl{A#aWBCtH}$bsNMDm-W*tva^P4@6=~K zwrPot3ZYdF5HaL~VMLVPcF!_!7G#1bpvl7$f~~HAaZbkh)iQ<2kr-z=A#$7pb}oA$ zeBDsNQh@0$!x*gfg;?GoO)(tbZz#glAhg^wGGIt*3>AiP8et*yKk8ZPXPzXr7BY+H zxIsocAWL~zYrc>R)xxVgVEWl_Go;$Q7+RlMeq2)gstm+%3p^lIC0rMu(?r1{hUhScu(~ zhN$4%KI8w!FtPmg1=RqRA5s@7c*jSwcBx4 z%Y7(&7Vkc|$PTqVM~y<8#84x`C10HNkny7@jD{!-B{{ zOgtRNh*eAArW_#*?f&}a|EDjlJhr>)FoOp4rAXO!54G?QUFRu-K!d}tkDu}SiNB(B zypswZf_?L9diA2o++3B^{`1fGJDQB4D|{d#Di**;2b zzf}py(OJ!ltMu7I{i`3pn&NF)SN0(kJ2f0DP z)jq1)Y#9gDN@n^~x-ttkzL1RBPVH<>o%zy0kk5;yl1zs@;}!+d z$_}dVYwg5~FqjYe`9VKF=;!~|&ui8Hp`YiyPLSQI1)ZzrGb#`lcQ0dTsCw)%8>d)gGA7-E|yGI(yHPDu*3c zQuos3xN4U4MGP>Xs&LhP(z6BX8*xCwpz5pZi8%8gR z{4r*14F>{)u*5K(43<=TlMp1;r};C%cK7F`VXOy+Tz?!zFv5XiFe4loC6(Zh5A}@i zGZB+{!PYgJtRaW`p7P)(n<}vcO_B+rTJ+aK|6Tj4mVzuweY1jtukgzUsU;nq{e}6%GeF#%??C39;`@1;Jqowu}(LED`j^HTmiRG_|HP7cm#A#HcbGk&N_4n0z+sx zP2&v!gg;f47w*=-I2K}^+5EPwm*msNfuRf?2vC@zF9+Nf&mE80xYVep<2E?if(rpV zIDhh%9NzJk+By&fK*JAeTFmV(aSoHWfCcfY&R)IEh_W7?@KsRQNlj`FGA)GwcYJ;B z_^@50fP}Tk0c{-ec5!_E`Y^hjmXq{;7`=#f2jNSZsXxXG4yR)CZxvm zQ|RDSo?%-~ANy&UPVQ8<#o9!H-}6kJALi+Xm&6E*?;-NI8k%mw1j3GlT%Vs_Uze{ znwq+I@7{g;_NAqzrKhLw-@pIBfddB*9?Zzd$jr>l%E~%)=umcc_Tj^aj~qF2^ytx? zoSb9FjvYUK{KSb9xw*L~Po6w=>eT7er^RBiL?V$&rFnUIGMOwtKVL4F7ZemI6pF&a z!lI(0;^JbZQdv?`Qd(Mi=FAzDN>x@?R$g9SQBhG@S$X#C*>mU4oj-rRs;cV3g$oxi zUc7YaQgwCp<;$0^T)A@f>Q%K`T~kw2TU%RKS65&EA+!B2neFJ}UuARp61UXzy=?j^ znR`hF?`kRr+goJ870giCyUaG)Cj1q7P)VDeA|l>G> zsTU8PS%0zHC;R*Qbp*VupxkQ5P>)1V^d&3#`@X?j{kK2bbZTWv@@>k(wwp`XyI|!a zI8Gu5E&3MRzgqNQa(Q837L@31R*LM;^2g9(%&QK*Ay{P=ST`>4hS}qax+h*=EAJ@X z9^ju{VN@=F68(WHa4Zx*K~ylWsH2J=K z<0@jn1?prkO`Krhu6IGFebiY z4s=Lou>($5X}Wc{<#5p!C2=sEedFA6$cCG9GlRE{)ms3237AqWXF(ibkkP>%c+XzC zxXxb6^Mw^y#I4y$wQVqwa05a8bbv)RJ8QtQB2^k}d5+p5Fgudo2K7@=9VZr7Mvj|YO7j|L0SrZzXKjJ0C~UMxmp2T;@W4qH zl9(I^ZitK5B!dj~+EB60RVu(PrR;J3+HkWnGJukR=k;3_sHFfCHPsU0cw_DFd+FH# zPy8?Jl0;!~c^$j7JIGb(*01VgDdM%7_%qv5R(Zx_8oBR0lvFARu$3*Dri%Auy@CXJ zUT;=@lKrD@+Ui;I1PATUmg?Uipq$$}wR74ME&e{=?>FCBnjxAcw{v=}bZXEKR)37n u757*%6Pk-0UZn-1Puat7286vnO^lsTyos`}{vgo2Zfi literal 0 HcmV?d00001 diff --git a/rog-anime/tests/data/g835l-diagonal.png b/rog-anime/tests/data/g835l-diagonal.png new file mode 100644 index 0000000000000000000000000000000000000000..e36be75f20bcc0471bc021c912f4099acb76a07c GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^EUsccqQpuh!jv*Cu-d?umI^e*;a^a@I z_x+nRolfM(N-XDE(K`Q8qOwxLakd{kjg1Ei1m`~tv}sF!Y_R8+)#2X(sXt`0dG2>} zeRkA3!*X$^<{~4`MTVM6ahrc7Jv=-+bJ@(af5(?dZe-36%6!g#KS2j%t*5J>%Q~lo FCIId1I~V`} literal 0 HcmV?d00001 diff --git a/rog-anime/tests/g835l.rs b/rog-anime/tests/g835l.rs new file mode 100644 index 00000000..97ce4049 --- /dev/null +++ b/rog-anime/tests/g835l.rs @@ -0,0 +1,556 @@ +// TODO: This is a provisional copy paste of GA401 + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use rog_anime::*; + + #[test] + fn g835l_image_edge_packet_check() { + let pkt0_check = [ + 0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let pkt1_check = [ + 0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut matrix = AnimeImage::new( + Vec2::new(1.0, 1.0), + 0.0, + Vec2::default(), + 0.0, + vec![Pixel::default(); 1000], + 100, + AnimeType::G835L, + ) + .unwrap(); + matrix.edge_outline(); + let data = AnimeDataBuffer::try_from(&matrix).unwrap(); + let pkt = AnimePacketType::try_from(data).unwrap(); + + assert_eq!(pkt[0], pkt0_check); + assert_eq!(pkt[1], pkt1_check); + } + + #[test] + fn g835l_diagonal_packet_check() { + let pkt0_check = [ + 0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let pkt1_check = [ + 0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests/data/g835l-diagonal.png"); + + let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::G835L).unwrap(); + let data = matrix.into_data_buffer(AnimeType::G835L).unwrap(); + let pkt = AnimePacketType::try_from(data).unwrap(); + + assert_eq!(pkt[0], pkt0_check); + assert_eq!(pkt[1], pkt1_check); + } + + #[test] + fn g835l_diagonal_fullbright_packet_check() { + let pkt0_check = [ + 0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let pkt1_check = [ + 0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests/data/g835l-diagonal-fullbright.png"); + + let matrix = AnimeDiagonal::from_png(&path, None, 255.0, AnimeType::G835L).unwrap(); + let data = matrix.into_data_buffer(AnimeType::G835L).unwrap(); + let pkt = AnimePacketType::try_from(data).unwrap(); + + assert_eq!(pkt[0], pkt0_check); + assert_eq!(pkt[1], pkt1_check); + } + + #[test] + fn g835l_diagonal_gif_wave_packet_check() { + let pkt0_frame0_check = [ + 0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02, 0x7f, 0x92, 0x84, 0xa2, 0x95, 0xac, 0x8a, + 0xa0, 0xaf, 0x97, 0xa4, 0xa8, 0x91, 0x9c, 0x9e, 0x99, 0x98, 0x98, 0x91, 0x82, 0x99, + 0x97, 0x8d, 0x7c, 0x67, 0x99, 0x8d, 0x7a, 0x63, 0x4a, 0xa0, 0x92, 0x7d, 0x63, 0x48, + 0x2e, 0x9a, 0x83, 0x67, 0x4a, 0x2e, 0x17, 0xa6, 0x8d, 0x70, 0x50, 0x32, 0x19, 0x08, + 0x9a, 0x7b, 0x5a, 0x3b, 0x20, 0x0c, 0x02, 0xa8, 0x89, 0x67, 0x46, 0x2a, 0x15, 0x09, + 0x07, 0x97, 0x75, 0x54, 0x37, 0x21, 0x13, 0x10, 0x16, 0xa6, 0x84, 0x63, 0x45, 0x2e, + 0x20, 0x1c, 0x21, 0x2e, 0x91, 0x71, 0x54, 0x3d, 0x2f, 0x2a, 0x2e, 0x3a, 0x4c, 0x9d, + 0x7d, 0x61, 0x4c, 0x3e, 0x39, 0x3d, 0x49, 0x5a, 0x6d, 0x87, 0x6d, 0x58, 0x4c, 0x48, + 0x4c, 0x58, 0x69, 0x7c, 0x8e, 0x8e, 0x75, 0x63, 0x58, 0x55, 0x5a, 0x67, 0x78, 0x8b, + 0x9d, 0xab, 0x7a, 0x69, 0x60, 0x5f, 0x66, 0x74, 0x86, 0x9a, 0xac, 0xba, 0xc1, 0x7a, + 0x6c, 0x65, 0x66, 0x6f, 0x7f, 0x93, 0xa7, 0xba, 0xc9, 0xd0, 0xce, 0x6a, 0x65, 0x69, + 0x74, 0x86, 0x9c, 0xb2, 0xc7, 0xd6, 0xde, 0xdd, 0xd2, 0x63, 0x61, 0x67, 0x75, 0x89, + 0xa1, 0xba, 0xd0, 0xe1, 0xea, 0xea, 0xe0, 0xcc, 0x59, 0x61, 0x71, 0x88, 0xa2, 0xbd, + 0xd5, 0xe8, 0xf3, 0xf4, 0xec, 0xd9, 0xbf, 0x4e, 0x58, 0x6a, 0x82, 0x9e, 0xbc, 0xd6, + 0xeb, 0xf8, 0xfc, 0xf4, 0xe4, 0xca, 0xac, 0x4b, 0x5f, 0x79, 0x97, 0xb6, 0xd3, 0xea, + 0xf9, 0xfe, 0xf9, 0xea, 0xd3, 0xb6, 0x96, 0x3d, 0x51, 0x6c, 0x8c, 0xad, 0xcb, 0xe4, + 0xf6, 0xfd, 0xfa, 0xed, 0xd8, 0xbd, 0x9f, 0x82, 0x43, 0x5e, 0x7f, 0xa1, 0xc0, 0xdb, + 0xee, 0xf7, 0xf6, 0xeb, 0xd8, 0xc0, 0xa4, 0x89, 0x71, 0x50, 0x70, 0x92, 0xb3, 0xcf, + 0xe3, 0xee, 0xee, 0xe6, 0xd5, 0xbe, 0xa4, 0x8c, 0x76, 0x67, 0x62, 0x84, 0xa5, 0xc1, + 0xd5, 0xe1, 0xe3, 0xdc, 0xcd, 0xb8, 0xa1, 0x8a, 0x77, 0x6a, 0x64, 0x76, 0x96, 0xb2, + 0xc7, 0xd3, 0xd5, 0xcf, 0xc1, 0xae, 0x99, 0x84, 0x73, 0x68, 0x65, 0x6a, 0x89, 0xa4, + 0xb8, 0xc4, 0xc6, 0xc0, 0xb3, 0xa1, 0x8d, 0x7a, 0x6b, 0x63, 0x62, 0x69, 0x78, 0x98, + 0xab, 0xb5, 0xb8, 0xb1, 0xa4, 0x93, 0x7f, 0x6e, 0x60, 0x59, 0x5b, 0x64, 0x75, 0x8c, + 0xa0, 0xa9, 0xaa, 0xa3, 0x95, 0x83, 0x70, 0x5f, 0x53, 0x4d, 0x50, 0x5b, 0x6e, 0x87, + 0xa4, 0xa0, 0x9f, 0x96, 0x87, 0x74, 0x61, 0x50, 0x44, 0x3f, 0x42, 0x4f, 0x64, 0x7e, + 0x9d, 0xbc, 0x97, 0x8c, 0x7c, 0x68, 0x53, 0x41, 0x34, 0x2f, 0x33, 0x41, 0x56, 0x73, + 0x93, 0xb3, 0xd1, 0x86, 0x74, 0x5e, 0x47, 0x34, 0x27, 0x21, 0x25, 0x32, 0x48, 0x65, + 0x86, 0xa7, 0xc6, 0xe0, 0x70, 0x57, 0x3f, 0x2a, 0x1b, 0x14, 0x17, 0x24, 0x39, 0x56, + 0x77, 0x99, 0xb9, 0xd4, 0xe6, 0x56, 0x3b, 0x24, 0x13, 0x0a, 0x0c, 0x17, 0x2c, 0x48, + 0x69, 0x8b, 0xab, 0xc6, 0xd9, 0xe3, 0x3b, 0x22, 0x0f, 0x04, 0x04, 0x0e, 0x21, 0x3c, + 0x5b, 0x7d, 0x9c, 0xb7, 0xca, 0xd5, 0xd6, 0x25, 0x0f, 0x03, 0x00, 0x08, 0x19, 0x32, + 0x50, 0x70, 0x8f, 0xa9, 0xbc, 0xc6, 0xc7, 0xc0, 0x14, 0x05, 0x01, 0x06, 0x15, 0x2c, + 0x49, 0x67, 0x84, 0x9c, 0xae, 0xb7, 0xb8, 0xb0, 0xa2, 0x0c, 0x05, 0x09, 0x16, 0x2b, + 0x45, 0x61, 0x7c, 0x93, 0xa3, 0xab, 0xaa, 0xa2, 0x93, 0x81, 0x0e, 0x10, 0x1b, 0x2e, + 0x46, 0x60, 0x78, 0x8d, 0x9b, 0xa1, 0x9e, 0x94, 0x85, 0x72, 0x5f, 0x1b, 0x24, 0x35, + 0x4b, 0x62, 0x79, 0x8b, 0x96, 0x9a, 0x96, 0x8a, 0x79, 0x65, 0x50, 0x3f, 0x30, 0x40, + 0x54, 0x6a, 0x7e, 0x8d, 0x97, 0x98, 0x91, 0x84, 0x70, 0x5a, 0x44, 0x32, 0x26, 0x4d, + 0x60, 0x74, 0x87, 0x94, 0x9b, 0x9a, 0x91, 0x81, 0x6c, 0x53, 0x3c, 0x28, 0x1a, 0x15, + 0x6e, 0x82, 0x93, 0x9f, 0xa4, 0xa1, 0x96, 0x83, 0x6b, 0x51, 0x37, 0x21, 0x11, 0x0a, + 0x0e, 0x91, 0xa1, 0xac, 0xb0, 0xab, 0x9e, 0x89, 0x6f, 0x53, 0x37, 0x1e, 0x0d, 0x04, + 0x05, 0x11, 0xb0, 0xbb, 0xbe, 0xb8, 0xaa, 0x93, 0x77, 0x59, 0x3a, 0x20, 0x0c, 0x01, + 0x01, 0x0a, 0x1d, 0xca, 0xcd, 0xc7, 0xb7, 0xa0, 0x83, 0x62, 0x42, 0x26, 0x10, 0x03, + 0x01, 0x08, 0x19, 0x31, 0xdc, 0xd6, 0xc6, 0xae, 0x90, 0x6f, 0x4e, 0x30, 0x18, 0x0a, + 0x05, 0x0a, 0x19, 0x2f, 0x4a, 0xe4, 0xd4, 0xbd, 0x9f, 0x7d, 0x5b, 0x3c, 0x24, 0x14, + 0x0d, 0x11, 0x1e, 0x32, 0x4a, 0x64, 0xe1, 0xcb, 0xad, 0x8c, 0x6a, 0x4b, 0x31, 0x20, + 0x19, 0x1b, 0x26, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let pkt1_frame0_check = [ + 0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02, 0x4f, 0x66, 0x7c, 0xd6, 0xba, 0x99, 0x78, + 0x59, 0x40, 0x2f, 0x27, 0x28, 0x32, 0x42, 0x57, 0x6d, 0x80, 0x8f, 0xc4, 0xa5, 0x85, + 0x67, 0x4f, 0x3e, 0x35, 0x37, 0x40, 0x4f, 0x63, 0x77, 0x89, 0x96, 0x9b, 0xad, 0x8f, + 0x73, 0x5c, 0x4c, 0x44, 0x46, 0x4f, 0x5e, 0x71, 0x84, 0x94, 0xa0, 0xa3, 0x9f, 0x96, + 0x7b, 0x66, 0x58, 0x52, 0x54, 0x5e, 0x6e, 0x80, 0x93, 0xa2, 0xac, 0xaf, 0xa9, 0x9a, + 0x80, 0x6d, 0x61, 0x5c, 0x60, 0x6c, 0x7c, 0x8f, 0xa2, 0xb2, 0xbb, 0xbd, 0xb5, 0xa5, + 0x8e, 0x70, 0x66, 0x64, 0x6a, 0x77, 0x89, 0x9d, 0xb1, 0xc1, 0xca, 0xcb, 0xc4, 0xb3, + 0x9a, 0x7c, 0x66, 0x67, 0x6f, 0x7e, 0x92, 0xa8, 0xbd, 0xce, 0xd9, 0xda, 0xd3, 0xc1, + 0xa8, 0x8a, 0x68, 0x65, 0x70, 0x82, 0x98, 0xb0, 0xc7, 0xd9, 0xe5, 0xe8, 0xe1, 0xd0, + 0xb7, 0x98, 0x76, 0x54, 0x6d, 0x81, 0x99, 0xb3, 0xcd, 0xe1, 0xee, 0xf3, 0xed, 0xdd, + 0xc5, 0xa6, 0x85, 0x63, 0x45, 0x7b, 0x96, 0xb3, 0xce, 0xe5, 0xf4, 0xfa, 0xf6, 0xe8, + 0xd1, 0xb4, 0x93, 0x71, 0x53, 0x3b, 0x8f, 0xad, 0xcb, 0xe4, 0xf5, 0xfe, 0xfc, 0xef, + 0xda, 0xbe, 0x9f, 0x7f, 0x61, 0x4a, 0x3b, 0xa5, 0xc4, 0xdf, 0xf2, 0xfd, 0xfd, 0xf2, + 0xdf, 0xc5, 0xa8, 0x89, 0x6e, 0x57, 0x49, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let pkt0_frame16_check = [ + 0x5e, 0xc0, 0x02, 0x01, 0x00, 0x73, 0x02, 0x95, 0x88, 0x92, 0x75, 0x81, 0x60, 0x8f, + 0x6d, 0x4c, 0x7c, 0x5a, 0x3c, 0x8a, 0x69, 0x4a, 0x32, 0x77, 0x59, 0x41, 0x32, 0x83, + 0x66, 0x50, 0x40, 0x3a, 0x72, 0x5c, 0x4e, 0x49, 0x4c, 0x79, 0x66, 0x59, 0x56, 0x5a, + 0x65, 0x6c, 0x61, 0x5f, 0x66, 0x72, 0x84, 0x6d, 0x66, 0x66, 0x6e, 0x7d, 0x90, 0xa5, + 0x65, 0x68, 0x72, 0x83, 0x99, 0xaf, 0xc5, 0x61, 0x65, 0x72, 0x86, 0x9d, 0xb6, 0xcd, + 0xdf, 0x5f, 0x6e, 0x84, 0x9d, 0xb9, 0xd2, 0xe6, 0xf2, 0x55, 0x66, 0x7d, 0x99, 0xb7, + 0xd2, 0xe8, 0xf7, 0xfc, 0x5a, 0x73, 0x91, 0xb1, 0xce, 0xe6, 0xf7, 0xfe, 0xfb, 0x4c, + 0x67, 0x86, 0xa7, 0xc6, 0xe0, 0xf3, 0xfc, 0xfb, 0xf0, 0x58, 0x78, 0x9a, 0xbb, 0xd6, + 0xeb, 0xf6, 0xf7, 0xed, 0xdc, 0x4a, 0x6a, 0x8c, 0xad, 0xca, 0xdf, 0xec, 0xee, 0xe7, + 0xd7, 0xc1, 0x5b, 0x7d, 0x9f, 0xbc, 0xd2, 0xdf, 0xe3, 0xdd, 0xcf, 0xbb, 0xa4, 0x4f, + 0x70, 0x90, 0xad, 0xc3, 0xd0, 0xd5, 0xd0, 0xc3, 0xb1, 0x9b, 0x87, 0x64, 0x83, 0x9f, + 0xb4, 0xc1, 0xc6, 0xc1, 0xb5, 0xa3, 0x90, 0x7c, 0x6d, 0x5c, 0x79, 0x93, 0xa7, 0xb3, + 0xb7, 0xb2, 0xa6, 0x95, 0x81, 0x6f, 0x61, 0x59, 0x73, 0x8b, 0x9d, 0xa7, 0xa9, 0xa3, + 0x97, 0x85, 0x72, 0x60, 0x53, 0x4c, 0x4e, 0x70, 0x86, 0x96, 0x9e, 0x9f, 0x97, 0x89, + 0x77, 0x63, 0x51, 0x44, 0x3e, 0x40, 0x4b, 0x86, 0x93, 0x99, 0x97, 0x8e, 0x7e, 0x6a, + 0x55, 0x43, 0x35, 0x2f, 0x31, 0x3d, 0x51, 0x8a, 0x95, 0x99, 0x94, 0x88, 0x76, 0x61, + 0x4a, 0x36, 0x27, 0x20, 0x22, 0x2e, 0x43, 0x5f, 0x9b, 0x9c, 0x96, 0x87, 0x73, 0x5b, + 0x43, 0x2d, 0x1c, 0x14, 0x15, 0x20, 0x34, 0x50, 0x71, 0xa4, 0x9b, 0x8b, 0x74, 0x5a, + 0x3f, 0x27, 0x15, 0x0b, 0x0a, 0x14, 0x27, 0x42, 0x62, 0x84, 0xa5, 0x92, 0x7a, 0x5d, + 0x40, 0x26, 0x11, 0x05, 0x03, 0x0b, 0x1d, 0x36, 0x55, 0x76, 0x96, 0x9d, 0x83, 0x64, + 0x45, 0x29, 0x12, 0x04, 0x00, 0x06, 0x15, 0x2d, 0x4b, 0x6a, 0x89, 0xa4, 0x8f, 0x6f, + 0x4e, 0x30, 0x18, 0x07, 0x01, 0x05, 0x12, 0x28, 0x44, 0x62, 0x7f, 0x98, 0xab, 0x7c, + 0x5a, 0x3b, 0x21, 0x0f, 0x06, 0x08, 0x14, 0x27, 0x41, 0x5c, 0x77, 0x8f, 0xa0, 0xa9, + 0x68, 0x48, 0x2d, 0x1a, 0x10, 0x10, 0x19, 0x2b, 0x42, 0x5c, 0x74, 0x89, 0x98, 0xa0, + 0x9e, 0x57, 0x3b, 0x27, 0x1c, 0x1b, 0x23, 0x33, 0x48, 0x5f, 0x76, 0x88, 0x95, 0x9a, + 0x97, 0x8c, 0x4a, 0x36, 0x2a, 0x28, 0x2f, 0x3e, 0x51, 0x67, 0x7b, 0x8c, 0x96, 0x99, + 0x93, 0x86, 0x73, 0x44, 0x39, 0x37, 0x3e, 0x4b, 0x5e, 0x72, 0x85, 0x93, 0x9b, 0x9c, + 0x94, 0x84, 0x6f, 0x57, 0x48, 0x46, 0x4d, 0x5b, 0x6c, 0x80, 0x91, 0x9e, 0xa4, 0xa3, + 0x99, 0x87, 0x70, 0x55, 0x3b, 0x54, 0x5c, 0x6a, 0x7c, 0x8f, 0xa0, 0xac, 0xb1, 0xad, + 0xa1, 0x8e, 0x74, 0x58, 0x3b, 0x22, 0x69, 0x78, 0x8b, 0x9e, 0xaf, 0xbb, 0xbf, 0xbb, + 0xad, 0x98, 0x7d, 0x5e, 0x40, 0x24, 0x0f, 0x84, 0x98, 0xac, 0xbe, 0xca, 0xce, 0xc9, + 0xbb, 0xa5, 0x89, 0x68, 0x48, 0x2b, 0x14, 0x05, 0xa2, 0xb8, 0xcb, 0xd8, 0xdd, 0xd8, + 0xca, 0xb3, 0x96, 0x75, 0x54, 0x35, 0x1c, 0x0c, 0x05, 0xc0, 0xd5, 0xe3, 0xea, 0xe6, + 0xd8, 0xc2, 0xa5, 0x84, 0x61, 0x42, 0x28, 0x16, 0x0e, 0x10, 0xdb, 0xec, 0xf4, 0xf1, + 0xe5, 0xd0, 0xb3, 0x92, 0x70, 0x50, 0x36, 0x23, 0x1a, 0x1b, 0x24, 0xf0, 0xfa, 0xfa, + 0xef, 0xdb, 0xc0, 0xa0, 0x7e, 0x5f, 0x45, 0x32, 0x28, 0x28, 0x31, 0x40, 0xfc, 0xfe, + 0xf5, 0xe3, 0xc9, 0xab, 0x8b, 0x6c, 0x53, 0x41, 0x37, 0x37, 0x3f, 0x4e, 0x61, 0xfd, + 0xf7, 0xe7, 0xcf, 0xb3, 0x94, 0x78, 0x60, 0x4f, 0x46, 0x46, 0x4e, 0x5d, 0x6f, 0x82, + 0xf4, 0xe7, 0xd1, 0xb7, 0x9b, 0x80, 0x6a, 0x5a, 0x53, 0x54, 0x5d, 0x6c, 0x7e, 0x91, + 0xa1, 0xe2, 0xce, 0xb6, 0x9c, 0x84, 0x70, 0x62, 0x5d, 0x60, 0x6a, 0x7a, 0x8d, 0xa0, + 0xb1, 0xbb, 0xc8, 0xb2, 0x9a, 0x84, 0x72, 0x67, 0x64, 0x69, 0x75, 0x86, 0x9b, 0xaf, + 0xbf, 0xca, 0xcd, 0xa9, 0x93, 0x7f, 0x70, 0x67, 0x66, 0x6d, 0x7c, 0x8f, 0xa5, 0xbb, + 0xcd, 0xd8, 0xdc, 0xd5, 0x89, 0x76, 0x69, 0x63, 0x64, 0x6e, 0x7e, 0x94, 0xac, 0xc4, + 0xd8, 0xe5, 0xe9, 0xe3, 0xd4, 0x6b, 0x5f, 0x5a, 0x5e, 0x6a, 0x7d, 0x95, 0xaf, 0xc9, + 0xdf, 0xed, 0xf3, 0xef, 0xe1, 0xca, 0x52, 0x4f, 0x54, 0x62, 0x77, 0x91, 0xae, 0xca, + 0xe1, 0xf2, 0xfa, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let pkt1_frame16_check = [ + 0x5e, 0xc0, 0x02, 0x74, 0x02, 0x73, 0x02, 0xeb, 0xd6, 0xb9, 0x41, 0x47, 0x56, 0x6d, + 0x89, 0xa8, 0xc6, 0xe0, 0xf3, 0xfd, 0xfd, 0xf2, 0xde, 0xc4, 0xa4, 0x39, 0x49, 0x61, + 0x7e, 0x9f, 0xbe, 0xda, 0xef, 0xfb, 0xfd, 0xf5, 0xe3, 0xca, 0xad, 0x8f, 0x3a, 0x53, + 0x71, 0x92, 0xb3, 0xd1, 0xe7, 0xf5, 0xf9, 0xf3, 0xe3, 0xcd, 0xb2, 0x95, 0x7b, 0x44, + 0x62, 0x84, 0xa6, 0xc4, 0xdc, 0xec, 0xf1, 0xed, 0xdf, 0xcb, 0xb2, 0x98, 0x80, 0x6d, + 0x54, 0x76, 0x97, 0xb6, 0xcf, 0xdf, 0xe6, 0xe3, 0xd7, 0xc5, 0xae, 0x96, 0x80, 0x6f, + 0x66, 0x68, 0x89, 0xa7, 0xc0, 0xd1, 0xd8, 0xd6, 0xcc, 0xbb, 0xa6, 0x90, 0x7c, 0x6e, + 0x66, 0x67, 0x7c, 0x9a, 0xb2, 0xc2, 0xc9, 0xc8, 0xbe, 0xae, 0x9a, 0x86, 0x74, 0x68, + 0x63, 0x65, 0x70, 0x8e, 0xa4, 0xb4, 0xba, 0xb8, 0xaf, 0x9f, 0x8c, 0x79, 0x69, 0x5e, + 0x5b, 0x60, 0x6d, 0x81, 0x9a, 0xa8, 0xad, 0xaa, 0x9f, 0x90, 0x7d, 0x6a, 0x5b, 0x52, + 0x50, 0x57, 0x66, 0x7c, 0x96, 0x9e, 0xa2, 0x9d, 0x92, 0x81, 0x6e, 0x5b, 0x4c, 0x43, + 0x42, 0x4a, 0x5b, 0x73, 0x8f, 0xae, 0x9a, 0x94, 0x86, 0x74, 0x60, 0x4c, 0x3d, 0x34, + 0x33, 0x3c, 0x4d, 0x66, 0x85, 0xa5, 0xc4, 0x8e, 0x7f, 0x6a, 0x54, 0x40, 0x2f, 0x25, + 0x24, 0x2d, 0x3f, 0x58, 0x78, 0x99, 0xba, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests/data/g835l-diagonal.gif"); + + let gif = + AnimeGif::from_diagonal_gif(&path, AnimTime::Count(1), 1.0, AnimeType::G835L).unwrap(); + assert_eq!(gif.frame_count(), 48); + + let pkt = AnimePacketType::try_from(gif.frames()[0].frame().clone()).unwrap(); + assert_eq!(pkt[0], pkt0_frame0_check); + assert_eq!(pkt[1], pkt1_frame0_check); + + let pkt = AnimePacketType::try_from(gif.frames()[16].frame().clone()).unwrap(); + assert_eq!(pkt[0], pkt0_frame16_check); + assert_eq!(pkt[1], pkt1_frame16_check); + } +} diff --git a/rog-dbus/src/zbus_anime.rs b/rog-dbus/src/zbus_anime.rs index ed0df009..2fdec4b4 100644 --- a/rog-dbus/src/zbus_anime.rs +++ b/rog-dbus/src/zbus_anime.rs @@ -5,7 +5,7 @@ use zbus::proxy; #[proxy( interface = "xyz.ljones.Anime", default_service = "xyz.ljones.Asusd", - default_path = "/xyz/ljones" + default_path = "/xyz/ljones/aura/anime" )] pub trait Anime { /// DeviceState method diff --git a/simulators/src/animatrix/map_g635l.rs b/simulators/src/animatrix/map_g635l.rs new file mode 100644 index 00000000..3615a79d --- /dev/null +++ b/simulators/src/animatrix/map_g635l.rs @@ -0,0 +1,70 @@ +use super::Row; + +// TODO: This is a placeholder for G635L map +pub const G635L: [Row; 63] = [ + Row(0x01, 7, 32, 0), + Row(0x01, 7 + 34, 32, 0), + Row(0x01, 7 + 68, 32, 0), + Row(0x01, 7 + 102, 32, 0), // 34 len + Row(0x01, 7 + 136, 32, 0), + Row(0x01, 7 + 170, 34, 0), + Row(0x01, 7 + 204, 34, 0), + Row(0x01, 7 + 238, 34, 0), + Row(0x01, 7 + 272, 34, 0), + Row(0x01, 7 + 306, 34, 0), + Row(0x01, 7 + 340, 34, 0), + Row(0x01, 7 + 374, 34, 0), + Row(0x01, 7 + 408, 33, 1), + Row(0x01, 7 + 441, 33, 1), + Row(0x01, 7 + 474, 32, 2), + Row(0x01, 7 + 506, 32, 2), + Row(0x01, 7 + 538, 31, 3), + Row(0x01, 7 + 569, 31, 3), + Row(0x01, 7 + 600, 28, 4), + // + Row(0x74, 7 + 1, 3, 28 + 4), // adds to end of previous + Row(0x74, 7 + 3, 30, 4), + Row(0x74, 7 + 33, 29, 5), + Row(0x74, 7 + 62, 29, 5), + Row(0x74, 7 + 91, 28, 6), + Row(0x74, 7 + 119, 28, 6), + Row(0x74, 7 + 147, 27, 7), + Row(0x74, 7 + 174, 27, 7), + Row(0x74, 7 + 202, 26, 9), + Row(0x74, 7 + 228, 26, 9), + Row(0x74, 7 + 254, 25, 10), + Row(0x74, 7 + 278, 25, 9), // WEIRD OFFSET + Row(0x74, 7 + 303, 24, 10), + Row(0x74, 7 + 327, 24, 10), + Row(0x74, 7 + 351, 23, 11), + Row(0x74, 7 + 374, 23, 11), + Row(0x74, 7 + 397, 22, 12), + Row(0x74, 7 + 419, 22, 12), + Row(0x74, 7 + 441, 21, 13), + Row(0x74, 7 + 462, 21, 13), + Row(0x74, 7 + 483, 20, 14), + Row(0x74, 7 + 503, 20, 14), + Row(0x74, 7 + 523, 19, 15), + Row(0x74, 7 + 542, 19, 15), + Row(0x74, 7 + 561, 18, 16), + Row(0x74, 7 + 579, 18, 16), + Row(0x74, 7 + 597, 17, 17), + Row(0x74, 7 + 614, 13, 17), + // + Row(0xe7, 7 + 1, 4, 13 + 18), // adds to end of previous + Row(0xe7, 7 + 4, 16, 18), + Row(0xe7, 7 + 20, 16, 18), + Row(0xe7, 7 + 36, 15, 19), + Row(0xe7, 7 + 51, 15, 19), + Row(0xe7, 7 + 66, 14, 20), + Row(0xe7, 7 + 80, 12, 20), // too long? 14 + Row(0xe7, 7 + 94, 13, 21), + Row(0xe7, 7 + 107, 13, 21), + Row(0xe7, 7 + 120, 12, 12), // Actual display end + Row(0xe7, 7 + 132, 12, 22), + Row(0xe7, 7 + 144, 11, 23), + Row(0xe7, 7 + 155, 11, 23), + Row(0xe7, 7 + 166, 10, 24), + Row(0xe7, 7 + 176, 10, 24), + Row(0xe7, 7 + 186, 9, 25), +]; diff --git a/simulators/src/animatrix/map_g835l.rs b/simulators/src/animatrix/map_g835l.rs new file mode 100644 index 00000000..e26df88f --- /dev/null +++ b/simulators/src/animatrix/map_g835l.rs @@ -0,0 +1,74 @@ +use super::Row; + +// G835L layout: 68 rows (triangle + rectangle), 2 packets +pub const G835L: [Row; 69] = [ + Row(0x01, 7, 0, 0), + Row(0x01, 8, 0, 0), + Row(0x01, 9, 1, 0), + Row(0x01, 11, 1, 0), + Row(0x01, 13, 2, 0), + Row(0x01, 16, 2, 0), + Row(0x01, 19, 3, 0), + Row(0x01, 23, 3, 0), + Row(0x01, 27, 4, 0), + Row(0x01, 32, 4, 0), + Row(0x01, 37, 5, 0), + Row(0x01, 43, 5, 0), + Row(0x01, 49, 6, 0), + Row(0x01, 56, 6, 0), + Row(0x01, 63, 7, 0), + Row(0x01, 71, 7, 0), + Row(0x01, 79, 8, 0), + Row(0x01, 88, 8, 0), + Row(0x01, 97, 9, 0), + Row(0x01, 107, 9, 0), + Row(0x01, 117, 10, 0), + Row(0x01, 128, 10, 0), + Row(0x01, 139, 11, 0), + Row(0x01, 151, 11, 0), + Row(0x01, 163, 12, 0), + Row(0x01, 176, 12, 0), + Row(0x01, 189, 13, 0), + Row(0x01, 203, 13, 0), + Row(0x01, 217, 14, 0), + Row(0x01, 232, 14, 0), + Row(0x01, 247, 14, 1), + Row(0x01, 262, 14, 1), + Row(0x01, 277, 14, 2), + Row(0x01, 292, 14, 2), + Row(0x01, 307, 14, 3), + Row(0x01, 322, 14, 3), + Row(0x01, 337, 14, 4), + Row(0x01, 352, 14, 4), + Row(0x01, 367, 14, 5), + Row(0x01, 382, 14, 5), + Row(0x01, 397, 14, 6), + Row(0x01, 412, 14, 6), + Row(0x01, 427, 14, 7), + Row(0x01, 442, 14, 7), + Row(0x01, 457, 14, 8), + Row(0x01, 472, 14, 8), + Row(0x01, 487, 14, 9), + Row(0x01, 502, 14, 9), + Row(0x01, 517, 14, 10), + Row(0x01, 532, 14, 10), + Row(0x01, 547, 14, 11), + Row(0x01, 562, 14, 11), + Row(0x01, 577, 14, 12), + Row(0x01, 592, 14, 12), + Row(0x01, 607, 14, 13), + Row(0x01, 622, 11, 13), + Row(0x74, 7, 2, 25), + Row(0x74, 10, 14, 14), + Row(0x74, 25, 14, 14), + Row(0x74, 40, 14, 15), + Row(0x74, 55, 14, 15), + Row(0x74, 70, 14, 16), + Row(0x74, 85, 14, 16), + Row(0x74, 100, 14, 17), + Row(0x74, 115, 14, 17), + Row(0x74, 130, 14, 18), + Row(0x74, 145, 14, 18), + Row(0x74, 160, 14, 19), + Row(0x74, 175, 14, 19), +]; diff --git a/simulators/src/animatrix/mod.rs b/simulators/src/animatrix/mod.rs index efc8c9b2..0317a616 100644 --- a/simulators/src/animatrix/mod.rs +++ b/simulators/src/animatrix/mod.rs @@ -1,9 +1,13 @@ use rog_anime::AnimeType; +use self::map_g635l::G635L; +use self::map_g835l::G835L; use self::map_ga401::GA401; use self::map_ga402::GA402; use self::map_gu604::GU604; +mod map_g635l; +mod map_g835l; mod map_ga401; mod map_ga402; mod map_gu604; @@ -38,30 +42,18 @@ pub struct AniMatrix { impl AniMatrix { pub fn new(model: AnimeType) -> Self { - let led_shape = match model { - AnimeType::GA401 => LedShape { - vertical: 2, - horizontal: 5, - }, - AnimeType::GA402 | AnimeType::G635L | AnimeType::G835L | AnimeType::Unsupported => { - LedShape { - vertical: 2, - horizontal: 5, - } - } - AnimeType::GU604 => LedShape { - vertical: 2, - horizontal: 5, - }, + let led_shape = LedShape { + vertical: 2, + horizontal: 5, }; // Do a hard mapping of each (derived from wireshardk captures) let rows = match model { AnimeType::GA401 => GA401.to_vec(), - AnimeType::GA402 | AnimeType::G635L | AnimeType::G835L | AnimeType::Unsupported => { - GA402.to_vec() - } + AnimeType::GA402 | AnimeType::Unsupported => GA402.to_vec(), AnimeType::GU604 => GU604.to_vec(), + AnimeType::G635L => G635L.to_vec(), + AnimeType::G835L => G835L.to_vec(), }; Self { rows, led_shape } diff --git a/simulators/src/simulator.rs b/simulators/src/simulator.rs index 03ed7fc6..2bf5ae22 100644 --- a/simulators/src/simulator.rs +++ b/simulators/src/simulator.rs @@ -2,7 +2,6 @@ use std::env; use std::error::Error; use std::str::FromStr; -use log::error; use rog_anime::usb::{PROD_ID, VENDOR_ID}; use rog_anime::{AnimeType, USB_PREFIX2}; use sdl2::event::Event; @@ -20,6 +19,7 @@ pub struct VirtAnimeMatrix { animatrix: AniMatrix, } +// TODO: This isn't working impl VirtAnimeMatrix { pub fn new(model: AnimeType) -> Self { VirtAnimeMatrix { @@ -86,8 +86,12 @@ impl VirtAnimeMatrix { ] .to_vec(), }) - .map_err(|err| error!("Could not create virtual device: {:?}", err)) - .expect("Could not create virtual device"), + .unwrap_or_else(|err| { + panic!( + "Could not create virtual device: {err:?}. \ + Try loading the uhid module and ensure you have the necessary permissions." + ) + }), } } @@ -114,7 +118,7 @@ impl VirtAnimeMatrix { fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); if args.len() <= 1 { - println!("Must supply arg, one of "); + println!("Must supply arg, one of "); return Ok(()); } let anime_type = AnimeType::from_str(&args[1])?;