use image::GenericImageView; use std::io::Write; pub fn fetch(extract_destination: &std::path::Path, silent: bool) { // prep working directory match create_working_directory() { Ok(_) => (), Err(e) => eprintln!("Error creating working directory: {}", e), }; // download colorscripts archive match download_colorscripts_archive(crate::constants::TARGET_URL) { Ok(_) => (), Err(e) => eprintln!("Error downloading colorscripts archive: {}", e), }; // extract colorscripts archive // now we have the raw images match extract_colorscripts_archive() { Ok(_) => (), Err(e) => eprintln!("Error extracting colorscripts archive: {}", e), }; // crop images to content match crop_all_images_in_directory() { Ok(_) => (), Err(e) => eprintln!("Error cropping images: {}", e), }; // convert images to unicode, both small and big match convert_images_to_ascii(extract_destination, silent) { Ok(_) => (), Err(e) => eprintln!("Error converting images to ASCII: {}", e), }; // cleanup match cleanup() { Ok(_) => (), Err(e) => eprintln!("Error cleaning up: {}", e), }; } fn create_working_directory() -> std::io::Result<()> { println!( "Creating working directory at {}...", &crate::constants::WORKING_DIRECTORY ); // create intermediate directories also std::fs::create_dir(&crate::constants::WORKING_DIRECTORY)?; println!("Created working directory"); return Ok(()); } fn download_colorscripts_archive(target_url: &str) -> Result<(), Box> { println!("Fetching colorscripts archive..."); let response = reqwest::blocking::get(target_url)?; let mut dest = std::fs::File::create(format!( "{}pokesprite.zip", &crate::constants::WORKING_DIRECTORY ))?; let response_body = response.error_for_status()?.bytes()?; std::io::copy(&mut response_body.as_ref(), &mut dest)?; println!("Downloaded colorscripts archive"); return Ok(()); } fn extract_colorscripts_archive() -> zip::result::ZipResult<()> { println!("Extracting colorscripts archive..."); let archive_file = std::fs::File::open(std::path::Path::new( format!("{}pokesprite.zip", &crate::constants::WORKING_DIRECTORY).as_str(), ))?; let mut archive = zip::read::ZipArchive::new(std::io::BufReader::new(archive_file))?; // iterate over every single file in the archive for i in 0..archive.len() { let mut file = archive.by_index(i)?; let file_path = std::path::Path::new(file.name()); let parent_dir = file_path .parent() .and_then(std::path::Path::file_name) .and_then(std::ffi::OsStr::to_str) .unwrap_or(""); // check if the file is a in the correct directory that is NOT a directory if (file .name() .starts_with("pokesprite-master/pokemon-gen8/regular/") && parent_dir == "regular") || (file .name() .starts_with("pokesprite-master/pokemon-gen8/shiny/") && parent_dir == "shiny") { let file_name = file_path .file_name() .and_then(std::ffi::OsStr::to_str) .unwrap(); let outpath = std::path::Path::new(&crate::constants::WORKING_DIRECTORY) .join("raw_images") .join(parent_dir) .join(file_name); if !file.name().ends_with('/') { if let Some(p) = outpath.parent() { if !p.exists() { std::fs::create_dir_all(&p)?; } } let mut outfile = std::fs::File::create(&outpath)?; std::io::copy(&mut file, &mut outfile)?; }; }; } println!("Extracted colorscripts archive"); return Ok(()); } fn crop_all_images_in_directory() -> std::io::Result<()> { println!("Cropping images..."); // make sure the cropped_images directory exists std::fs::create_dir_all( std::path::Path::new(crate::constants::WORKING_DIRECTORY).join("cropped_images"), )?; // do for both regular and shiny subdirectories for subdirectory in ["regular", "shiny"].iter() { let input_subdirectory_path = std::path::Path::new(crate::constants::WORKING_DIRECTORY) .join("raw_images") .join(subdirectory); let output_subdirectory_path = std::path::Path::new(crate::constants::WORKING_DIRECTORY) .join("cropped_images") .join(subdirectory); std::fs::create_dir_all(&output_subdirectory_path)?; for entry in std::fs::read_dir(&input_subdirectory_path)? { let entry = entry?; let path = entry.path(); let output_path = output_subdirectory_path.join(path.file_name().unwrap()); crop_to_content(&path, &output_path).unwrap(); } } println!("Cropped images"); return Ok(()); } fn crop_to_content( input_path: &std::path::Path, output_path: &std::path::Path, ) -> image::ImageResult { // load the image let img = image::open(input_path)?; let (width, height) = img.dimensions(); let mut min_x = width; let mut min_y = height; let mut max_x = 0; let mut max_y = 0; for y in 0..height { for x in 0..width { let pixel = img.get_pixel(x, y); if pixel[3] != 0 { // if pixel is not transparent if x < min_x { min_x = x; } if y < min_y { min_y = y; } if x > max_x { max_x = x; } if y > max_y { max_y = y; } } } } let cropped_width = max_x - min_x + 1; let cropped_height = max_y - min_y + 1; let mut cropped_img: image::ImageBuffer, Vec> = image::ImageBuffer::new(cropped_width, cropped_height); for y in 0..cropped_height { for x in 0..cropped_width { let pixel = img.get_pixel(x + min_x, y + min_y); cropped_img.put_pixel(x, y, pixel); } } let cropped_img = image::DynamicImage::ImageRgba8(cropped_img); // write the cropped image cropped_img.save(output_path)?; return Ok(cropped_img); } fn convert_images_to_ascii( output_directory_path: &std::path::Path, silent: bool, ) -> std::io::Result<()> { println!("Extract destination: {:?}", output_directory_path); println!("Converting images to ASCII..."); std::fs::create_dir_all(output_directory_path)?; for size in ["small", "big"].iter() { for subdirectory in ["regular", "shiny"].iter() { let input_subdirectory_path = std::path::PathBuf::from(crate::constants::WORKING_DIRECTORY) .join("cropped_images") .join(subdirectory); let output_subdirectory_path = output_directory_path .join("colorscripts") .join(size) .join(subdirectory); std::fs::create_dir_all(&output_subdirectory_path)?; for entry in std::fs::read_dir(&input_subdirectory_path)? { let entry = entry?; let path = entry.path(); if path.is_file() { let img = image::open(&path).unwrap(); let ascii_art = if *size == "small" { convert_image_to_unicode_small(&img) } else { convert_image_to_unicode_big(&img) }; // print for fun if silent == false { println!("{}", ascii_art); }; let output_path = output_subdirectory_path.join(path.file_stem().unwrap()); let mut file = std::fs::File::create(output_path)?; file.write_all(ascii_art.as_bytes())?; } } } } println!("Converted images to ASCII"); return Ok(()); } fn convert_image_to_unicode_small(img: &image::DynamicImage) -> String { let mut unicode_sprite = String::new(); let (width, height) = img.dimensions(); for y in (0..height).step_by(2) { for x in 0..width { let upper_pixel = img.get_pixel(x, y); let lower_pixel = if y + 1 < height { img.get_pixel(x, y + 1) } else { upper_pixel // Fallback to upper pixel if there's no lower pixel. }; if upper_pixel[3] == 0 && lower_pixel[3] == 0 { unicode_sprite.push(' '); } else if upper_pixel[3] == 0 { unicode_sprite.push_str(&get_color_escape_code(lower_pixel, false)); unicode_sprite.push('▄'); } else if lower_pixel[3] == 0 { unicode_sprite.push_str(&get_color_escape_code(upper_pixel, false)); unicode_sprite.push('▀'); } else { unicode_sprite.push_str(&get_color_escape_code(upper_pixel, false)); unicode_sprite.push_str(&get_color_escape_code(lower_pixel, true)); unicode_sprite.push('▀'); } unicode_sprite.push_str("\x1b[0m"); // Reset ANSI code after each character } unicode_sprite.push('\n'); // New line for each row, plus reset might be added here too if colors extend beyond. } return unicode_sprite; } fn convert_image_to_unicode_big(img: &image::DynamicImage) -> String { let mut unicode_sprite = String::new(); let (width, height) = img.dimensions(); for y in 0..height { for x in 0..width { let pixel = img.get_pixel(x, y); if pixel[3] == 0 { unicode_sprite.push_str(" "); } else { unicode_sprite.push_str(&get_color_escape_code(pixel, false)); unicode_sprite.push_str("██"); } } unicode_sprite.push('\n'); } return unicode_sprite; } fn get_color_escape_code(pixel: image::Rgba, background: bool) -> String { if pixel[3] == 0 { return format!("{}", crossterm::style::ResetColor); } let color = crossterm::style::Color::Rgb { r: pixel[0], g: pixel[1], b: pixel[2], }; if background { format!("{}", crossterm::style::SetBackgroundColor(color)) } else { format!("{}", crossterm::style::SetForegroundColor(color)) } } fn cleanup() -> std::io::Result<()> { println!("Cleaning up..."); std::fs::remove_dir_all(std::path::Path::new(crate::constants::WORKING_DIRECTORY))?; println!("Cleaned up"); return Ok(()); }