rustmon/src/fetch.rs

361 lines
11 KiB
Rust
Raw Normal View History

2024-04-02 02:27:09 +08:00
use image::GenericImageView;
use std::io::Write;
2024-04-02 15:03:38 +08:00
pub fn fetch(extract_destination: &std::path::Path, verbose: bool) {
2024-04-02 02:27:09 +08:00
// prep working directory
match create_working_directory() {
Ok(_) => (),
2024-04-02 15:03:38 +08:00
Err(e) => {
eprintln!("Error creating working directory: {}", e);
}
2024-04-02 02:27:09 +08:00
};
// download colorscripts archive
2024-04-02 15:03:38 +08:00
match fetch_colorscripts_archive(crate::constants::TARGET_URL) {
2024-04-02 02:27:09 +08:00
Ok(_) => (),
2024-04-02 15:03:38 +08:00
Err(e) => {
eprintln!("Error fetching colorscripts archive: {}", e);
std::process::exit(1);
}
2024-04-02 02:27:09 +08:00
};
// 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
2024-04-02 15:03:38 +08:00
match convert_images_to_ascii(extract_destination, verbose) {
2024-04-02 02:27:09 +08:00
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!(
2024-04-02 15:03:38 +08:00
"Creating working directory at {:?}...",
&*crate::constants::CACHE_DIRECTORY
2024-04-02 02:27:09 +08:00
);
// create intermediate directories also
2024-04-02 15:03:38 +08:00
std::fs::create_dir(&*crate::constants::CACHE_DIRECTORY)?;
2024-04-02 02:27:09 +08:00
println!("Created working directory");
return Ok(());
}
2024-04-02 15:03:38 +08:00
fn fetch_colorscripts_archive(target_url: &str) -> Result<(), Box<dyn std::error::Error>> {
2024-04-02 02:27:09 +08:00
println!("Fetching colorscripts archive...");
let response = reqwest::blocking::get(target_url)?;
2024-04-02 15:03:38 +08:00
let mut dest = std::fs::File::create(
&*crate::constants::CACHE_DIRECTORY
.to_path_buf()
.join("pokesprite.zip"),
)?;
2024-04-02 02:27:09 +08:00
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...");
2024-04-02 15:03:38 +08:00
let archive_file = std::fs::File::open(
&*crate::constants::CACHE_DIRECTORY
.to_path_buf()
.join("pokesprite.zip"),
)?;
2024-04-02 02:27:09 +08:00
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();
2024-04-02 15:03:38 +08:00
let outpath = &*crate::constants::CACHE_DIRECTORY
2024-04-02 02:27:09 +08:00
.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(
2024-04-02 15:03:38 +08:00
&*crate::constants::CACHE_DIRECTORY
.to_path_buf()
.join("cropped_images"),
2024-04-02 02:27:09 +08:00
)?;
// do for both regular and shiny subdirectories
for subdirectory in ["regular", "shiny"].iter() {
2024-04-02 15:03:38 +08:00
let input_subdirectory_path = &*crate::constants::CACHE_DIRECTORY
.to_path_buf()
2024-04-02 02:27:09 +08:00
.join("raw_images")
.join(subdirectory);
2024-04-02 15:03:38 +08:00
let output_subdirectory_path = &*crate::constants::CACHE_DIRECTORY
.to_path_buf()
2024-04-02 02:27:09 +08:00
.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<image::DynamicImage> {
// 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<image::Rgba<u8>, Vec<u8>> =
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,
2024-04-02 15:03:38 +08:00
verbose: bool,
2024-04-02 02:27:09 +08:00
) -> 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() {
2024-04-02 15:03:38 +08:00
let input_subdirectory_path = &*crate::constants::CACHE_DIRECTORY
.join("cropped_images")
.join(subdirectory);
2024-04-02 02:27:09 +08:00
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
2024-04-02 15:03:38 +08:00
if verbose == true {
2024-04-02 02:27:09 +08:00
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<u8>, 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...");
2024-04-02 15:03:38 +08:00
std::fs::remove_dir_all(&*crate::constants::CACHE_DIRECTORY)?;
2024-04-02 02:27:09 +08:00
println!("Cleaned up");
return Ok(());
}