Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parse_str, rasterize_to_raw_rgba, and optional image. #2

Merged
merged 2 commits into from
Jun 18, 2018
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.5.0
- Make `image` a default, but optional crate
- Increment `image` version to 0.19.0
- Add `rasterize_to_raw_rgba` for use when `image` is not included
- Add `parse_str` for parsing SVG images from memory

## 0.4.0
- Fix memory leak [#1](https://github.com/nickbrowne/nsvg/pull/1)
- More idiomatic error handling [#1](https://github.com/nickbrowne/nsvg/pull/1)
Expand Down
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
[package]
name = "nsvg"
version = "0.4.0"
version = "0.5.0"
authors = ["Nick Browne <nickbrowne@users.noreply.github.com>"]
repository = "https://github.com/nickbrowne/nsvg"
keywords = ["svg", "nanosvg"]
description = "A simple SVG parser and rasterizer using NanoSVG"
license = "Zlib/MIT"
readme = "README.md"

[features]
default = ["image"]

[dependencies]
image = "0.18.0"
image = { version = "0.19.0", optional = true }

[dev-dependencies]
tempfile = "3"
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ Include `nsvg` in your `Cargo.toml` dependencies.

```toml
[dependencies]
nsvg = "0.4.0"
nsvg = "0.5.0"
```

To include `nsvg` without the `image` dependency, add it to your `Cargo.toml` without default dependencies.

```toml
[dependencies.nsvg]
version = "0.5.0"
default-features = false
```

Now you can parse and rasterize SVGs. Use the scale argument to produce larger or smaller rasterised images. The aspect ratio will remain the same.
Expand Down
12 changes: 12 additions & 0 deletions examples/svg_to_raw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
extern crate nsvg;

fn main() {
// Load the SVG data
let svg = nsvg::parse_str(include_str!("spiral.svg"), nsvg::Units::Pixel, 96.0).unwrap();

// Rasterize the loaded SVG and return dimensions and a RGBA buffer
let (width, height, raw_rgba) = svg.rasterize_to_raw_rgba(2.0).unwrap();

println!("Rasterized to a RGBA buffer of size {}", raw_rgba.len());
println!("Image width is {}, and height is {}", width, height);
}
82 changes: 79 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ fn main() {
#![allow(non_upper_case_globals)]
mod bindings;

extern crate image;
#[cfg(feature = "image")]
pub extern crate image;
#[cfg(test)]
extern crate tempfile;

Expand Down Expand Up @@ -140,19 +141,57 @@ impl SvgImage {
}
}

/**
* Loads SVG data from the given SVG text contents.
*
* # Arguments
* - `svg_str` - Text contents of the SVG you want to load
* - `units` - The length unit identifier, you probably just want `nsvg::Units::Pixel`
* - `dpi` - Probably just want `96.0`.
*/
pub fn parse_str(svg_str: &str, units: Units, dpi: f32) -> Result<SvgImage, Error> {
let svg_c_string = CString::new(svg_str)?.into_raw();

let image = unsafe {
let image = bindings::nsvgParse(svg_c_string, units.as_c_str(), dpi);
CString::from_raw(svg_c_string);
image
};

if image.is_null() {
Err(Error::ParseError)
} else {
Ok(SvgImage { image })
}
}

/**
* Turns the loaded SVG into an RgbaImage bitmap
*
* # Argument
* - `scale` - The factor the vector will be scaled by when rasterizing.
* 1.0 is the original size.
*/
#[cfg(feature = "image")]
pub fn rasterize(&self, scale: f32) -> Result<image::RgbaImage, Error> {
let rasterizer = SVGRasterizer::new()?;

rasterizer.rasterize(self, scale)
}

/**
* Turns the loaded SVG into raw RGBA array data, along with width and height information.
*
* # Argument
* - `scale` - The factor the vector will be scaled by when rasterizing.
* 1.0 is the original size.
*/
pub fn rasterize_to_raw_rgba(&self, scale: f32) -> Result<(u32, u32, Vec<u8>), Error> {
let rasterizer = SVGRasterizer::new()?;

rasterizer.rasterize_to_raw_rgba(self, scale)
}

/**
* The width of the original SVG document.
*/
Expand Down Expand Up @@ -197,6 +236,18 @@ pub fn parse_file(filename: &Path, units: Units, dpi: f32) -> Result<SvgImage, E
SvgImage::parse_file(filename, units, dpi)
}

/**
* Loads SVG data from the given SVG text contents.
*
* # Arguments
* - `svg_str` - Text contents of the SVG you want to load
* - `units` - The length unit identifier, you probably just want `nsvg::Units::Pixel`
* - `dpi` - Probably just want `96.0`.
*/
pub fn parse_str(svg_str: &str, units: Units, dpi: f32) -> Result<SvgImage, Error> {
SvgImage::parse_str(svg_str, units, dpi)
}

struct SVGRasterizer {
rasterizer: *mut bindings::NSVGrasterizer
}
Expand All @@ -212,7 +263,15 @@ impl SVGRasterizer {
}
}

#[cfg(feature = "image")]
fn rasterize(&self, image: &SvgImage, scale: f32) -> Result<image::RgbaImage, Error> {
let (width, height, raw) = self.rasterize_to_raw_rgba(image, scale)?;

image::RgbaImage::from_raw(width, height, raw)
.ok_or(Error::RasterizeError)
}

fn rasterize_to_raw_rgba(&self, image: &SvgImage, scale: f32) -> Result<(u32, u32, Vec<u8>), Error> {
let width = (image.width() * scale) as usize;
let height = (image.height() * scale) as usize;
let capacity = BYTES_PER_PIXEL * width * height;
Expand All @@ -235,8 +294,7 @@ impl SVGRasterizer {
dst.set_len(capacity);
}

image::RgbaImage::from_raw(width as u32, height as u32, dst)
.ok_or(Error::RasterizeError)
Ok((width as u32, height as u32, dst))
}
}

Expand Down Expand Up @@ -265,6 +323,14 @@ mod tests {
assert_eq!(svg.height(), 256.0);
}

#[test]
fn can_parse_str() {
let svg = SvgImage::parse_str(include_str!("../examples/spiral.svg"), Units::Pixel, 96.0).unwrap();

assert_eq!(svg.width(), 256.0);
assert_eq!(svg.height(), 256.0);
}

#[test]
fn can_parse_file_at_non_ascii_path() {
let dir = tempdir().unwrap();
Expand Down Expand Up @@ -305,6 +371,7 @@ mod tests {
}

#[test]
#[cfg(feature = "image")]
fn can_rasterize() {
let svg = SvgImage::parse_file(Path::new("examples/spiral.svg"), Units::Pixel, 96.0).unwrap();
let image = svg.rasterize(1.0).unwrap();
Expand All @@ -313,6 +380,15 @@ mod tests {
}

#[test]
fn can_rasterize_to_raw_rgba() {
let svg = SvgImage::parse_file(Path::new("examples/spiral.svg"), Units::Pixel, 96.0).unwrap();
let (width, height, _raw_rgba) = svg.rasterize_to_raw_rgba(1.0).unwrap();

assert_eq!((width, height), (256, 256));
}

#[test]
#[cfg(feature = "image")]
fn can_rasterize_and_scale() {
let svg = SvgImage::parse_file(Path::new("examples/spiral.svg"), Units::Pixel, 96.0).unwrap();
let image = svg.rasterize(2.0).unwrap();
Expand Down