-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from archer884/release/beta-1
Do all the things
- Loading branch information
Showing
7 changed files
with
571 additions
and
150 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = "*" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.