Skip to content

Commit

Permalink
Merge pull request #1 from archer884/release/beta-1
Browse files Browse the repository at this point in the history
Do all the things
  • Loading branch information
archer884 committed Jun 2, 2017
2 parents f3b7603 + c0b2e7b commit 2bb5f62
Show file tree
Hide file tree
Showing 7 changed files with 571 additions and 150 deletions.
83 changes: 79 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[package]
name = "annatar"
version = "0.1.0"
version = "0.1.1"
authors = ["J/A <[email protected]>"]

[dependencies]
clap = "*"
image = "*"
imageproc = { git = "https://github.com/archer884/imageproc", branch = "feature/new-rectangle" }
imageproc = { git = "https://github.com/archer884/imageproc", branch = "annatar" }
rusttype = "*"
133 changes: 133 additions & 0 deletions src/annotation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use application::AppRunError;
use image::{DynamicImage, GenericImage, ImageBuffer, imageops, Luma, Rgba, RgbaImage};
use imageproc::{drawing, edges};
use imageproc::rect::Rect;
use rusttype::{Font, Scale};

pub enum Annotation {
CaptionBottom(Text)
}

// Apparently, rendering cannot produce any errors?
pub struct AnnotationRenderError;

impl From<AnnotationRenderError> for AppRunError {
fn from(_: AnnotationRenderError) -> Self {
unimplemented!()
}
}

pub struct Text {
content: String,

// At some point, this will be used for something. I think.
#[allow(unused)]
style: TextStyle,
}

pub enum TextStyle {
/// Default font with standard issue black outline.
Default,
}

impl<T: Into<String>> From<T> for Text {
fn from(content: T) -> Self {
Text {
content: content.into(),
style: TextStyle::Default,
}
}
}

impl Annotation {
pub fn render<'a>(&'a self, pixels: &'a mut DynamicImage, font: &'a Font<'a>, scale_factor: f32) -> Result<(), AnnotationRenderError> {
match *self {
Annotation::CaptionBottom(ref text) => {
let _ = render_caption_bottom(text, pixels, font, scale_factor);
Ok(())
}
}
}

pub fn render_and_debug<'a>(&'a self, pixels: &'a mut DynamicImage, font: &'a Font<'a>, scale_factor: f32) -> Result<DynamicImage, AnnotationRenderError> {
match *self {
Annotation::CaptionBottom(ref text) => {
let debug = render_caption_bottom(text, pixels, font, scale_factor);
debug.map(|image| DynamicImage::ImageRgba8(image))
}
}
}
}

fn render_caption_bottom<'a>(text: &'a Text, pixels: &'a mut DynamicImage, font: &'a Font<'a>, scale_factor: f32) -> Result<RgbaImage, AnnotationRenderError> {
// The final value in the array here is the *opacity* of the pixel. Not the transparency.
// Apparently, this is not CSS...
let white_pixel = Rgba([255, 255, 255, 255]);
let black_pixel = Rgba([0, 0, 0, 255]);

let scale = Scale::uniform(scale_factor);
let (width, height) = pixels.dimensions();
let (text_width, text_height) = text_size(&text.content, font, scale);

// What follows is a little fourth grade math that attempts to stick the text at the center
// of the bottom fifth of the image. This, by the way, is the closest I have ever come to
// using anything I learned in Mrs. Vye's 9th grade keyboarding class. Thank God for the
// IBM Selectric III, huh?
let x = (width / 2) - (text_width / 2);
let y = height - ((height / 5) - (text_height / 2));

let mut edge_rendering = ImageBuffer::from_pixel(text_width, text_height, black_pixel);
drawing::draw_text_with_font_mut(
&mut edge_rendering, white_pixel, 0, 0, scale, &font, &text.content
);

// These thresholds are black magic to me.
//
// A further note: this step is independent of text style, with the exception that,
// obviously, we'll skip it if the user has requested no border.
for (idx, &pixel) in edges::canny(&imageops::grayscale(&edge_rendering), 255.0, 255.0).pixels().enumerate() {
if Luma([255u8]) == pixel {
let idx = idx as u32;
let x = idx % text_width + x;
let y = idx / text_width + y;

// I bet this isn't cheap, but... meh.
let rect_size = (0.1 * scale_factor) as u32;
let offset = (rect_size / 2) as i32;
let rect = Rect::at(x as i32 - offset, y as i32 - offset)
.of_size(rect_size, rect_size);

drawing::draw_filled_rect_mut(pixels, rect, Rgba([0, 0, 0, 255]));
}
}

drawing::draw_text_with_font_mut(pixels, white_pixel, x, y, scale, &font, &text.content);
Ok(edge_rendering)
}

/// Calculate the dimensions of the bounding box for a given string, font, and scale.
///
/// This works by summing the "advance width" of each glyph in the text, entirely ignoring
/// kerning as each character is considered in isolation. Because this is used just to center
/// text in the image, it's close enough for government work.
fn text_size<'a>(s: &'a str, font: &'a Font<'a>, scale: Scale) -> (u32, u32) {
use rusttype::VMetrics;

let text_width = font.glyphs_for(s.chars())
.map(|glyph| glyph.scaled(scale).h_metrics().advance_width)
.sum::<f32>();

// The "v-metrics" for any given letter in a font are the same for a given scale, so we don't
// need to check this for each glyph.
let text_height = {
let VMetrics { ascent, descent, ..} = font.v_metrics(scale);
(ascent - descent) as u32
};

// I know I'm truncating the length and this is probably wrong, but it's not wrong by enough
// to be noticeable when you print it to an image.
//
// The padding you see below is added to aid in edge detection, specifically because the
// exclamation point doesn't seem to have enough advance width. -.-
(text_width as u32 + 2, text_height)
}
Loading

0 comments on commit 2bb5f62

Please sign in to comment.