Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# TODO

- Add brightness setting (0-100) to allow brightening dark images
- Instead of using 2d vectors throughout the program, flatten them into single vectors
- Speed up GIF encoding (seems to take up the most time when converting GIFs)
- Probably should remove the "Internal" error since it's vague.

## Bugs

Expand Down
72 changes: 51 additions & 21 deletions src/conversion/converters/generic_converter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::{collections::HashMap, sync::Arc};

use crate::{
ImgiiOptions,
conversion::{image_data::ImageData, render_char_to_png::str_to_png},
error::{FontError, ImgiiError, ParseIntError},
error::{FontError, ImgiiError, ParseIntError, RenderError},
};

use super::super::render_char_to_png::{ColoredStr, str_to_transparent_png};
Expand All @@ -17,7 +19,7 @@ const FONT_BYTES: &[u8] = include_bytes!("../../../fonts/UbuntuMono.ttf");
/// Simple struct for holding a 2d image with its width and height.
#[derive(Clone, Debug)]
pub(crate) struct Imgii2dImage {
pub(crate) image_2d: Vec<ImageData>,
pub(crate) image_2d: Vec<Arc<ImageData>>,
pub(crate) width: usize,
pub(crate) height: usize,
}
Expand All @@ -41,14 +43,17 @@ pub(crate) fn render_ascii_generic(
// 2d Vec of images for each character
let mut image_2d_vec = Vec::new();

// create this once since it will always be the same
let transparent_png = str_to_transparent_png(imgii_options);

// width and height, in characters
// NOTE: we can know height beforehand but we have to wait until we have parsed a whole line of
// text to know the width
let (mut width, height) = (0, ascii_text.lines().count());

// hold already rendered images so we don't have to render them more than once! Rendering is
// slow
let mut rendered_images: HashMap<ColoredStr, Arc<ImageData>> = HashMap::new();
// create transparent image once since it will always be the same
let transparent_png = Arc::from(str_to_transparent_png(imgii_options));

// read every line in the file
for (i, line) in ascii_text.lines().enumerate() {
// we need to find each character that we are going to write
Expand Down Expand Up @@ -88,34 +93,59 @@ pub(crate) fn render_ascii_generic(
string: String::from(the_str),
};

str_to_png(colored, &font, imgii_options)
// check if this image was already rendered before
let rendered_img = rendered_images.get(&colored);
match rendered_img {
// we have rendered this image before, so clone it
Some(rendered_img) => rendered_img.clone(),
None => {
// we haven't rendered this image before, so render it
let image_data = Arc::from(str_to_png(&colored, &font, imgii_options));
let result = rendered_images.insert(colored, image_data.clone());
if result.is_some() {
return Err(RenderError::new(String::from(
"this image should not exist already in the hash map",
))
.into());
}
image_data
}
}
}
};

line_width += 1;
image_2d_vec.push(generated_png);
}

// check that this width is always the same now that we have the width
if i != 0 {
assert_eq!(
width, line_width,
"width {} is not equal to the current line width {}",
width, line_width
);
} else {
if i == 0 {
// get the width of the entire image. This should always be the same
width = line_width;
// now we can reserve the rest of the space for our vec
// now we can reserve the rest of the capacity we need for our vec
// NOTE: this can panic if the vec is too large
image_2d_vec.reserve(width * height);
} else {
// check that this width is always the same now that we have the width
if width != line_width {
return Err(RenderError::new(format!(
"width {} is not equal to the current line width {}",
width, line_width
))
.into());
}
}
}

assert!(
width * height == image_2d_vec.len(),
"expected length of the 2d vector was {} but got {}",
width * height,
image_2d_vec.len()
);
// Check that the length of the final vector is what we expect. If not, something has gone
// terribly wrong, and we should not continue.
if width * height != image_2d_vec.len() {
return Err(RenderError::new(format!(
"expected length of the 2d vector was {} but got {}",
width * height,
image_2d_vec.len()
))
.into());
}

Ok(Imgii2dImage {
image_2d: image_2d_vec,
Expand Down
6 changes: 4 additions & 2 deletions src/conversion/render_char_to_png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use imageproc::drawing::draw_text_mut;

/// Represents a colored string to write.
/// All characters are contiguous and share the same color.
#[derive(Debug, Clone)]
/// Is hashable to act as a key for already rendered
/// images, to prevent rendering them more than once.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct ColoredStr {
pub(crate) red: u8,
pub(crate) blue: u8,
Expand All @@ -18,7 +20,7 @@ const BACKGROUND_PIXEL: Rgba<u8> = Rgba([0, 0, 0, u8::MAX]);
/// Converts string data into a png.
/// Uses `imageproc` to render text.
pub(crate) fn str_to_png(
data: ColoredStr,
data: &ColoredStr,
font: &FontRef<'_>,
imgii_options: &ImgiiOptions,
) -> ImageData {
Expand Down
40 changes: 37 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub struct InternalError;
pub enum ImageError {
InvalidParameter(InvalidParameterError),
ParseImage(ParseImageError),
Render(RenderError),
}

/// Contains other errors. These are errors that can be emitted from other crates for various
Expand All @@ -105,7 +106,7 @@ pub struct InvalidParameterError {
parameter_name: String,
}

/// Represents an error that occurred while parsing an image (in a 2D fashion).
/// Represents an error that occurred while parsing an image.
///
/// Suberror of [`ImageError`].
#[derive(Debug, Clone)]
Expand All @@ -114,6 +115,16 @@ pub struct ParseImageError {
image_row_number: usize,
}

/// Represents an error that occurred while rendering an image.
///
/// Suberror of [`ImageError`].
#[derive(Debug, Clone)]
pub struct RenderError {
/// The reason for the render error. Since this error is intended to handle various internals
/// that aren't well represented by errors, this will explain why the error ocurred.
reason: String,
}

/*
* NOTE: Implement `Display` below for errors that are intended to also implement Error.
*/
Expand Down Expand Up @@ -190,6 +201,13 @@ impl Display for ImageError {
parse_image_error.image_row_number
)
}
ImageError::Render(render_error) => {
write!(
f,
"error occurred while rendering image ({})",
render_error.reason
)
}
}
}
}
Expand Down Expand Up @@ -223,8 +241,8 @@ impl Error for OtherError {}
// do another From impl for the errors that can be converted into a suberror type too.
//
// The suberrors can implement From for anything that can be converted into them specifically, then
// for each of those, a simple From can be implemented for ImgiiError with a call to `.into()`,
// which will convert into the suberror type, then it should convert into the ImgiiError type.
// for each of those, a simple From can be implemented for ImgiiError so we can call `.into()`
// to convert a suberror type into the main ImgiiError type.
//
// This makes it easier to maintain, as more errors are added. This pattern should be replicated for
// suberrors which have their own suberrors.
Expand Down Expand Up @@ -270,6 +288,12 @@ impl From<ParseImageError> for ImgiiError {
}
}

impl From<RenderError> for ImgiiError {
fn from(value: RenderError) -> Self {
Self::Image(ImageError::Render(value))
}
}

// for converting from errors boxed at runtime
impl From<BoxedDynErr> for ImgiiError {
fn from(value: BoxedDynErr) -> Self {
Expand Down Expand Up @@ -361,3 +385,13 @@ impl ParseImageError {
Self { image_row_number }
}
}

impl RenderError {
/// Creates a new [`RenderError`].
///
/// * `reason`: The reason for the render error.
#[must_use]
pub fn new(reason: String) -> Self {
Self { reason }
}
}