DEV Community

Abhishek2010DevSingh
Abhishek2010DevSingh

Posted on

ASCII Art Code in Rust

Hi, everyone! I'm Abhishek, a 13-year-old tech enthusiast who loves exploring all things creative and coding. Today, I'm excited to share something that's both artistic and geeky—ASCII art! ASCII art is a way of creating images using characters from the ASCII standard. It's an awesome way to express creativity using just your keyboard.

In this post, I'll be diving into the world of ASCII art, sharing some cool examples, and showing you how you can create your own. Whether you're a beginner or a seasoned artist, there's something for everyone. Let's get started on this fun journey of turning text into masterpieces!

Let create a new project:

Cargo new ascii_art

Let’s get to the main point. The following are the dependencies:

[dependencies]
clap = { version = "4.5.8", features = ["derive"] }
image = "0.24.9"
Enter fullscreen mode Exit fullscreen mode

Explanations:

  1. clap: This dependency is for the clap crate, which is used for command-line argument parsing. The derive feature allows you to use clap's derive macros to simplify the process of defining command-line arguments.
  2. image : This dependency is for the image crate, which provides functionality for image processing. In your ASCII art app, this crate might be used to handle image files, which you can then convert into ASCII art.

Code section:

use clap::Parser;

/// Structure to hold the command-line arguments
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct ImageValue {
    /// Path of the image to convert
    #[arg(short, long)]
    path: String,

    /// Scale factor for ASCII art (default is 3)
    #[arg(short, long, default_value_t = 3)]
    scale: u32,
}

Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. #[derive(Parser, Debug)] : This line uses clap's Parser derive macro to automatically generate the code needed to parse command-line arguments based on the structure's fields. The Debug trait allows you to print the structure for debugging purposes.

  2. #[command(version, about, long_about = None)]: This attribute configures the command-line parser. version includes the version information from Cargo.toml, about adds a brief description of the application, and long_about = None specifies that no extended description is provided.

  3. path Field:
    #[arg(short, long)]: This specifies that the path field can be provided via the -p (short) or --path (long) command-line arguments.
    path: String: This field will hold the path to the image file that you want to convert to ASCII art.

  4. scale Field:
    #[arg(short, long, default_value_t = 3)]: This specifies that the scale field can be provided via the -s (short) or --scale (long) command-line arguments. The default_value_t = 3 part sets a default value of 3 if the scale argument is not provided.
    scale: u32: This field will hold the scale factor for the ASCII art conversion.

fn get_str_ascii(intent: u8) -> &'static str {
    let index: u8 = intent / 32;
    let ascii: [&str; 8] = [" ", ".", ",", "-", "~", "+", "=", "@"];
    return ascii[index as usize];
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

The get_str_ascii function is designed to map a numerical value (intent) to a specific ASCII character based on its intensity. The function accepts an intent value of type u8 (an 8-bit unsigned integer) and calculates an index to select an ASCII character from a predefined array.

Here’s how it works: First, the function calculates the index into the ascii array by dividing the intent by 32. This division maps the intent values, which range from 0 to 255, into a smaller range of indices from 0 to 7. For instance, an intent value of 10 will result in an index of 0, selecting the character " " (a space). An intent value of 50 will yield an index of 1, selecting ".", and so forth. The array ascii contains a set of eight characters that vary from a space to the "@" symbol, which represent different levels of density or shading.

The function then returns the ASCII character corresponding to the calculated index. This approach is useful for generating ASCII art, where different characters represent varying levels of brightness or detail.

The get_image function is designed to process an image file and print an ASCII representation of it to the console. Here’s a detailed explanation of how this function works:

Function Breakdown

fn get_image(dir: &str, scale: u32) {
    let img: DynamicImage = image::open(dir).unwrap();
    let (width, height) = img.dimensions();

    for y in 0..height {
        for x in 0..width {
            if y % (scale * 2) == 0 && x % scale == 0 {
                let pix = img.get_pixel(x, y);
                let mut intent: u8 = pix[0] / 3 + pix[1] / 3 + pix[2] / 3;
                if pix[3] == 0 {
                    intent = 0;
                }
                print!("{}", get_str_ascii(intent));
            }
        }
        if y % (scale * 2) == 0 {
            println!("");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation

  1. Open the Image:

    • let img: DynamicImage = image::open(dir).unwrap();: This line uses the image crate to open the image file specified by dir. unwrap() is used here to panic if the image cannot be opened, which is useful for debugging but could be replaced with proper error handling in a production application.
  2. Get Image Dimensions:

    • let (width, height) = img.dimensions();: This retrieves the width and height of the image.
  3. Iterate Over Image Pixels:

    • for y in 0..height {: This outer loop iterates over each row of pixels in the image.
    • for x in 0..width {: This inner loop iterates over each column of pixels in the current row.
  4. Pixel Sampling and Scaling:

    • if y % (scale * 2) == 0 && x % scale == 0 {: This condition checks if the current pixel should be sampled based on the scale value. This effectively reduces the image resolution by skipping pixels according to the scale factor.
    • let pix = img.get_pixel(x, y);: This retrieves the pixel data at position (x, y).
    • let mut intent: u8 = pix[0] / 3 + pix[1] / 3 + pix[2] / 3;: This line calculates a grayscale intensity value from the RGB pixel values. It averages the red, green, and blue channels to create a single intensity value.
    • if pix[3] == 0 { intent = 0; }: This checks if the pixel is fully transparent (alpha channel pix[3] is 0) and sets the intensity to 0 if true.
  5. Print ASCII Representation:

    • print!("{}", get_str_ascii(intent));: This prints the ASCII character corresponding to the calculated intensity value.
  6. Newline Handling:

    • if y % (scale * 2) == 0 { println!(""); }: This prints a newline every scale * 2 rows to ensure the output aligns correctly.

Usage

This function processes an image to produce a textual representation where each character represents a pixel’s intensity in the ASCII art. The scale parameter controls the resolution of the output, with higher values resulting in lower resolution (fewer characters). For example, calling get_image("path/to/image.png", 2) will display a scaled-down ASCII version of the image.

Here is main function

fn main() {
    let value = ImageValue::parse();
    get_image(&value.path, value.scale)
}
Enter fullscreen mode Exit fullscreen mode

Example using this app:

cargo run --- -p [path to the image"]
Enter fullscreen mode Exit fullscreen mode

Cute Ferris Image

Cute Ferris Image

Cute Ferris ASCII Image

Cute Ferris ASCII

Image Without Full Zoom

Image Without Zoom

Full code

use clap::Parser;
use image::{DynamicImage, GenericImageView};

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct ImageValue {
    /// Path of image
    #[arg(short, long)]
    path: String,

    /// Scale for Ascii art
    #[arg(short, long, default_value_t = 3)]
    scale: u32,
}

fn get_str_ascii(intent: u8) -> &'static str {
    let index: u8 = intent / 32;
    let ascii: [&str; 8] = [" ", ".", ",", "-", "~", "+", "=", "@"];
    return ascii[index as usize];
}

fn get_image(dir: &str, scale: u32) {
    let img: DynamicImage = image::open(dir).unwrap();
    let (width, height) = img.dimensions();

    for y in 0..height {
        for x in 0..width {
            if y % (scale * 2) == 0 && x % scale == 0 {
                let pix = img.get_pixel(x, y);
                let mut intent: u8 = pix[0] / 3 + pix[1] / 3 + pix[2] / 3;
                if pix[3] == 0 {
                    intent = 0;
                }
                print!("{}", get_str_ascii(intent));
            }
        }
        if y % (scale * 2) == 0 {
            println!("");
        }
    }
}

fn main() {
    let value = ImageValue::parse();
    get_image(&value.path, value.scale)
}
Enter fullscreen mode Exit fullscreen mode

git repositories = https://github.com/Abhishek2010DevSingh/ascii_art

Top comments (0)