~jpl8/text-to-piet

b26f1cdb103b88307ffb0bb9254dfec93d0b273d — jpl 1 year, 6 months ago 91883f1
Add checking if terminator can be placed safely.
5 files changed, 96 insertions(+), 37 deletions(-)

M Cargo.lock
M Cargo.toml
M src/instructions.rs
M src/main.rs
M test.png
M Cargo.lock => Cargo.lock +7 -0
@@ 15,6 15,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"

[[package]]
name = "anyhow"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"

[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 694,6 700,7 @@ dependencies = [
name = "text-to-piet"
version = "0.2.0"
dependencies = [
 "anyhow",
 "clap",
 "image",
 "rand",

M Cargo.toml => Cargo.toml +1 -0
@@ 10,4 10,5 @@ image = "~0.24"
rand = "~0.8"
clap = { version = "~3.1", features = ["derive"] }
ron = "~0.7"
anyhow = "~1.0"
serde = { version = "~1.0", features = ["derive"] }

M src/instructions.rs => src/instructions.rs +21 -7
@@ 9,6 9,8 @@ use Instructions::*;

use std::fs::OpenOptions;

use anyhow::{anyhow, Result};

use crate::spiral;

#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]


@@ 118,7 120,7 @@ impl PietEncoder for EncoderBFS {
    }
}

pub fn expected_size(instructions: &Vec<Instructions>) -> usize {
pub fn expected_size(instructions: &Vec<Instructions>) -> Result<usize> {
    // Num of extra blocks for corner_turn = 2 * num_corners
    // num_corners(side) = side - 3, for side > 2, 0 otherwise
    // num_blocks(instructions, side) = instructions.len() + corner_turn.len() * num_corners(side)


@@ 134,6 136,14 @@ pub fn expected_size(instructions: &Vec<Instructions>) -> usize {
        }
    });

    let num_pushes = instructions
        .iter()
        .filter(|instr| match instr {
            PUSH(_) => true,
            _ => false,
        })
        .count();

    eprintln!("NUM BLOCKS {num_blocks}");

    // TODO: Make the actual corner_turn vec accessible here


@@ 146,13 156,17 @@ pub fn expected_size(instructions: &Vec<Instructions>) -> usize {
            <= (spiral::Spiral2D::length(*side as u32) as usize)
    };

    let min_image_size = search_for_minimum_satisfier(eq_to_minimize, 1..usize::MAX);
    let lower_bound_on_min_img_size = search_for_minimum_satisfier(eq_to_minimize, 1..usize::MAX);

    match min_image_size {
        Some(img_size) => img_size,
        // heuristic
        None => 4 + (((2 * num_blocks - 8) as f64).sqrt() as usize) + 1,
    }
    let min_img_size_range = match search_for_minimum_satisfier(eq_to_minimize, 1..usize::MAX) {
        Some(lower_bound) => {
            let upper_bound = lower_bound + std::cmp::min(num_pushes, num_corners(&lower_bound));
            Ok((lower_bound, upper_bound))
        }
        None => Err(anyhow!("Unable to find a lower bound on the image size!")),
    };

    Ok(min_img_size_range?.0)

    // solving for side should work out to


M src/main.rs => src/main.rs +67 -30
@@ 5,6 5,8 @@ use clap::Parser;
use image::{Rgb, RgbImage};
use rand::seq::SliceRandom;

use anyhow::Result;

use colors::Color;
use instructions::{Instructions::*, *};



@@ 30,7 32,7 @@ struct Args {
    output: Option<String>,
}

fn main() {
fn main() -> Result<()> {
    let args = Args::parse();

    let input = fs::read_to_string(args.input_file).expect("Failed to read input file");


@@ 38,7 40,7 @@ fn main() {
    let encoder = instructions::EncoderBFS::new("instruction_cache/bfs_instructions.ron");
    let instrs = encoder.encode_text(&input);

    let img_size = args.img_size.unwrap_or(expected_size(&instrs) as u32);
    let img_size = args.img_size.unwrap_or(expected_size(&instrs)? as u32);
    eprintln!("img_size: {:?}", img_size);
    eprintln!("spiral_len: {:?}", Spiral2D::length(img_size));



@@ 47,6 49,8 @@ fn main() {
    let img = colors_to_img(colors, img_size);
    img.save(args.output.unwrap_or("test.png".to_string()))
        .expect("Failed to write output image");

    Ok(())
}

fn update_x_y_spiral(x: u32, y: u32, step: u32, dir: &Direction) -> (u32, u32) {


@@ 122,7 126,9 @@ fn instructions_to_blocks(instructions: &Vec<Instructions>, img_size: u32) -> Ve
            }
            _ => {
                colors.push(prev);
                remaining_space -= 1;
                remaining_space = remaining_space
                    .checked_sub(1)
                    .expect("Ran out of space to place more blocks in the image!");
            }
        }
        prev = colors::instruction_to_color(*instr, prev);


@@ 163,33 169,6 @@ fn colors_to_img(colors: Vec<Color>, img_size: u32) -> RgbImage {
        final_pos = (x, y);
        final_dir = dir;
    }
    let (mut x, mut y) = final_pos;

    // SAFE: There is always the starting color in colors
    let color = colors.last().unwrap();

    // Add terminator
    {
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        let color = colors::instruction_to_color(PUSH(1), *color);
        (x, y) = update_x_y_spiral(x, y, 1, &final_dir.clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        let color = colors::instruction_to_color(PUSH(1), color);
        (x, y) = update_x_y_spiral(x, y, 1, &final_dir.clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        (x, y) = update_x_y_spiral(x, y, 1, &final_dir);
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        (x, y) = update_x_y_spiral(x, y, 2, &final_dir.clockwise().clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        (x, y) = update_x_y_spiral(x, y, 1, &final_dir);
        (x, y) = update_x_y_spiral(x, y, 1, &final_dir.clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));
    }

    // Fill in remaining colors
    let filler_spiral = Spiral2D::new(


@@ 242,6 221,64 @@ fn colors_to_img(colors: Vec<Color>, img_size: u32) -> RgbImage {
        img.put_pixel(x, y, Rgb(*picked_color));
    }

    let (mut x, mut y) = final_pos;

    // SAFE: There is always the starting color in colors
    let color = colors.last().unwrap();

    // Check if it is safe to add the terminator
    let terminator_region_corner = {
        let (square_x, square_y) = update_x_y_spiral(x, y, 2, &final_dir);
        update_x_y_spiral(square_x, square_y, 1, &final_dir.clockwise())
    };

    let terminator_region = get_rect((5, 4), terminator_region_corner, &final_dir.clockwise());

    let all_pixels_in_region_are_black = terminator_region
        .iter()
        .all(|&(x, y)| img.get_pixel(x, y) == &Rgb(Color::Black.to_bytes()));

    // TODO: Add error handling with anyhow
    if !all_pixels_in_region_are_black {
        panic!("UNABLE TO ADD TERMINATOR!");
    }

    // Add terminator
    {
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        let color = colors::instruction_to_color(PUSH(1), *color);
        (x, y) = update_x_y_spiral(x, y, 1, &final_dir.clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        let color = colors::instruction_to_color(PUSH(1), color);
        (x, y) = update_x_y_spiral(x, y, 1, &final_dir.clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        (x, y) = update_x_y_spiral(x, y, 1, &final_dir);
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        (x, y) = update_x_y_spiral(x, y, 2, &final_dir.clockwise().clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));

        (x, y) = update_x_y_spiral(x, y, 1, &final_dir);
        (x, y) = update_x_y_spiral(x, y, 1, &final_dir.clockwise());
        img.put_pixel(x, y, Rgb(color.to_bytes()));
    }

    //img.save(img_name).expect("Failed to save image");
    img
}

/// Creates a square that starts at the given position, clockwise
/// with respect to the start position.
fn get_rect(sides: (u32, u32), start: (u32, u32), dir: &Direction) -> Vec<(u32, u32)> {
    let mut blocks = vec![];
    for i in 0..sides.0 {
        let (x, y) = update_x_y_spiral(start.0, start.1, i, &dir.clockwise());
        for j in 0..sides.1 {
            blocks.push(update_x_y_spiral(x, y, j, dir));
        }
    }
    blocks
}

M test.png => test.png +0 -0