Skip to content

Commit

Permalink
Merge pull request #14 from archer884/feature/multi-size
Browse files Browse the repository at this point in the history
Add in-band size option to annotations
  • Loading branch information
archer884 committed Aug 18, 2018
2 parents 1a91261 + 68c5168 commit f4777bb
Show file tree
Hide file tree
Showing 8 changed files with 488 additions and 366 deletions.
660 changes: 307 additions & 353 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "annatar"
version = "0.3.4"
version = "0.4.0"
authors = ["J/A <[email protected]>"]
license = "MIT/Apache-2.0"
keywords = ["image", "image-manipulation", "meme", "pepe", "kek"]
Expand All @@ -10,9 +10,12 @@ homepage = "https://github.com/archer884/annatar"
categories = ["multimedia::images"]
description = """
A command line tool for making memes.
Full readme available at: https://github.com/archer884/annatar
"""

[dependencies]
artano = "0.2.6"
clap = "2.31"
clap = "2.32"
regex = "1.0"
reqwest = "0.8"
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,54 @@
This crate is named for the Dark Lord Sauron. The name is taken from the Quenya for 'Lord of Gifts,' which has a hilarious mispronunciation that seems to apply almost directly to this case. Like the library it's based on, this is clearly for making evil things.

## Usage

```shell
annatar foo.jpg \
--top "This text will appear near the top of the image." \
--middle "This text will appear near the middle of the image." \
--bottom "This text will appear near the bottom of the image."
```

Per the usual conventions, `-t`, `-m`, and `-b` are also available as arguments. Additionally, a `-c --caption` argument is available as a synonym for `-b --bottom`.

Images may be provided as either local paths or URLs; annatar is happy to fetch your picture from the internet for you.

### Annotation size

By default, annatar sizes the text used for your captions on the basis of the height of the image itself. The exact algorithm used for this purpose was selected by a team of scientists working round the clock for weeks on end at the Vatican, and we didn't let them out until we saw white smoke. Rumors that the members of our text scaling enclave were able to agree only once the majority of members had starved or been bludgeoned to death by the others are, as far as you know, unfounded.

The important thing is that, normally, the text will look ok. For images with strange aspect ratios (either very wide or very narrow relative to their height), text can look either too large or too small. In that case, or in the case wherein you prefer to express greater emphasis, you may prefer to pass the `-s --scale` flag with a scaling multiplier.

```shell
annatar doge.png \
--scale 2.0 \
--top "SUCH BIG" \
--bottom "SO SCALE"
```

This scale multiplier acts (surprisingly) as a *multiplier* for the scaling value selected by annatar. So, text scaled at `2.0` will be twice as tall (annatar scales text by height, proportionally) as it would have been otherwise.

> Note: You will probably find a value like `2.0` to be excessive under most circumstances; I usually scale by about 30%—`0.7` or `1.3`—at most.
#### In-band annotation scaling

The `-s --scale` mutliplier is set for *all* annotations, top, middle, and bottom. To allow annotations of different size, an in-band scaling format is provided.

```shell
annatar doge.png \
--top "this one is normal" \
--bottom "\1.3 this one is bigger!"
```

White space between the scaling modifier (`\1.3` above) and the annotation (`this one is bigger`) will be ignored. However, some amount of intervening white space *is required.*

> Note: for those of you who are plagued by morbid curiosity, here's the regular expression used: `\\(?P<scale>\d+(\.\d+)?)\s+(?P<caption>.+)`.
## Version history

- **0.4.0** Add in-band annotation scaling

## License

Licensed under either of
Expand Down
8 changes: 6 additions & 2 deletions src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ impl App {
.map_err(|e| Error::not_found("Base image not found", e))
.and_then(|buf| Canvas::read_from_buffer(&buf).map_err(Error::bad_image))?;

for annotation in &options.annotations {
canvas.add_annotation(annotation, &font, options.scale_mult);
for scaled_annotation in &options.annotations {
canvas.add_annotation(
&scaled_annotation.annotation,
&font,
scaled_annotation.scale_multiplier,
);
}

canvas.render();
Expand Down
1 change: 1 addition & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod options;
mod scaled_annotation;

pub mod resource;

Expand Down
26 changes: 17 additions & 9 deletions src/config/options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use artano::Annotation;
use config::resource::Resource;
use config::scaled_annotation::{ScaledAnnotation, ScaledAnnotationParser};
use error::Cause;
use std::borrow::Cow;
use std::error;
Expand All @@ -15,10 +15,9 @@ type Result<T> = result::Result<T, BuildOptionsError>;
#[derive(Debug)]
pub struct Options {
pub base_image: Resource,
pub annotations: Vec<Annotation>,
pub annotations: Vec<ScaledAnnotation>,
pub output_path: PathBuf,
pub output_format: OutputFormat,
pub scale_mult: f32,
pub font_path: PathBuf,
pub debug: bool,
}
Expand All @@ -37,7 +36,7 @@ impl Options {

pub struct OptionsBuilder {
base_image: Option<String>,
annotations: Vec<Annotation>,
annotations: Vec<ScaledAnnotation>,
output_path: Option<String>,
output_format: OutputFormat,
scale_mult: f32,
Expand Down Expand Up @@ -93,7 +92,6 @@ impl OptionsBuilder {
annotations,
output_path,
output_format,
scale_mult: self.scale_mult,
font_path: self.font_path.to_string().into(),
debug: self.debug,
})
Expand Down Expand Up @@ -181,20 +179,30 @@ fn read_command() -> Result<Options> {
options.font_path = Cow::from(font_path.to_string());
}

let parser = ScaledAnnotationParser::new();

if let Some(caption) = matches.value_of("caption") {
options.annotations.push(Annotation::bottom(caption));
options
.annotations
.push(parser.bottom(options.scale_mult, caption));
}

if let Some(caption) = matches.value_of("top") {
options.annotations.push(Annotation::top(caption));
options
.annotations
.push(parser.top(options.scale_mult, caption));
}

if let Some(caption) = matches.value_of("middle") {
options.annotations.push(Annotation::middle(caption));
options
.annotations
.push(parser.middle(options.scale_mult, caption));
}

if let Some(caption) = matches.value_of("bottom") {
options.annotations.push(Annotation::bottom(caption));
options
.annotations
.push(parser.bottom(options.scale_mult, caption));
}

options.output_format = {
Expand Down
103 changes: 103 additions & 0 deletions src/config/scaled_annotation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use artano::Annotation;
use regex::Regex;

#[derive(Debug)]
pub struct ScaledAnnotation {
pub scale_multiplier: f32,
pub annotation: Annotation,
}

impl ScaledAnnotation {
fn new(scale_multiplier: f32, annotation: Annotation) -> Self {
Self {
scale_multiplier,
annotation,
}
}
}

pub struct ScaledAnnotationParser {
pattern: Regex,
}

impl ScaledAnnotationParser {
pub fn new() -> Self {
Self {
pattern: Regex::new(r#"\\(?P<scale>\d+(\.\d+)?)\s+(?P<caption>.+)"#).unwrap(),
}
}

pub fn bottom(&self, scale: f32, s: &str) -> ScaledAnnotation {
let (annotation_scale, annotation) = parse_scaled_annotation(&self.pattern, s);
ScaledAnnotation::new(
annotation_scale.unwrap_or(scale),
Annotation::bottom(annotation),
)
}

pub fn middle(&self, scale: f32, s: &str) -> ScaledAnnotation {
let (annotation_scale, annotation) = parse_scaled_annotation(&self.pattern, s);
ScaledAnnotation::new(
annotation_scale.unwrap_or(scale),
Annotation::middle(annotation),
)
}

pub fn top(&self, scale: f32, s: &str) -> ScaledAnnotation {
let (annotation_scale, annotation) = parse_scaled_annotation(&self.pattern, s);
ScaledAnnotation::new(
annotation_scale.unwrap_or(scale),
Annotation::top(annotation),
)
}
}

fn parse_scaled_annotation(pattern: &Regex, s: &str) -> (Option<f32>, String) {
// The plan here is to provide in-band scaling per-annotation via the following format: we
// check the beginning of any given annotation for \<float>. If we find that, we treat it
// as entirely separate from the annotation, up to and including all trailing white space.
//
// For instance, an annotation of the form `\1.2 frenchmen can't spell` would equate to
// the message "frenchmen can't spell" at a size multiplier of 1.2.
//
// It's valid to escape the leading \ with \\.

// Annotation does not contain in-band scaling information.
if !(s.starts_with('\\') && !s.starts_with("\\\\")) {
return (None, s.into());
}

match pattern.captures(s) {
None => (None, s.into()),

// These unwrap assumptions may look horrendously unsafe, but the design of the regular
// expression itself makes failure here very unlikely.
Some(captures) => (
Some(
captures
.name("scale")
.and_then(|s| s.as_str().parse().ok())
.unwrap(),
),
captures.name("caption").map(|s| s.as_str().into()).unwrap(),
),
}
}

#[cfg(test)]
mod tests {
use super::{ScaledAnnotation, ScaledAnnotationParser};

#[test]
fn scaled_annotation_parser_works() {
let parser = ScaledAnnotationParser::new();
let caption = "\\2.0 Hello, world!";
let ScaledAnnotation {
scale_multiplier,
annotation,
} = parser.top(1.0, caption);

assert_eq!(2.0, scale_multiplier);
assert_eq!("Hello, world!", annotation.text);
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
extern crate clap;

extern crate artano;
extern crate regex;
extern crate reqwest;

mod application;
Expand Down

0 comments on commit f4777bb

Please sign in to comment.