Support image generation.

pull/12/head
kennytm 2016-06-08 00:31:11 +08:00
parent a1786e0449
commit 0d2c56db62
No known key found for this signature in database
GPG Key ID: FEF6C8051D0E013C
6 changed files with 320 additions and 19 deletions

View File

@ -14,8 +14,10 @@ exclude = [
[dependencies]
num-traits = "0.1.32"
image = { version = "0.10.0", optional = true }
[features]
default = ["image"]
bench = []
[[bin]]

View File

@ -6,12 +6,48 @@ qrcode-rust
[![crates.io](http://meritbadge.herokuapp.com/qrcode)](https://crates.io/crates/qrcode)
[![MIT / Apache 2.0](https://img.shields.io/badge/license-MIT%20%2f%20Apache%202.0-blue.svg)](./LICENSE-APACHE.txt)
QR code and Micro QR code encoder in Rust.
QR code and Micro QR code encoder in Rust. [Documentation](https://kennytm.github.io/qrcode-rust).
Cargo.toml
----------
```toml
# Cargo.toml
[dependencies]
qrcode = "0.1.7"
qrcode = "0.2.0"
```
The default settings will depend on the `image` crate. If you don't need image generation capability, disable the `default-features`:
```toml
[dependencies]
qrcode = { version = "0.2.0", default-features = false }
```
Example
-------
This code:
```rust
extern crate qrcode;
extern crate image;
use qrcode::QrCode;
use image::GrayImage;
fn main() {
// Encode some data into bits.
let code = QrCode::new(b"01234567").unwrap();
// Render the bits into an image.
let image: GrayImage = code.render().to_image();
// Save the image.
image.save("/tmp/qrcode.png").unwrap();
}
```
Generates this image:
![Output](src/test_annex_i_qr_as_image.png)

View File

@ -2,37 +2,46 @@
//!
//! This crate provides a QR code and Micro QR code encoder for binary data.
//!
//! use qrcode::QrCode;
//! ```
//! extern crate image;
//! extern crate qrcode;
//!
//! let code = QrCode::new(b"Some content here.");
//! match code {
//! Err(err) => panic!("Failed to encode the QR code: {:?}", err),
//! Ok(code) => {
//! for y in 0 .. code.width() {
//! for x in 0 .. code.width() {
//! let color = if code[(x, y)] { "black" } else { "white" };
//! // render color at position (x, y)
//! }
//! }
//! }
//! use image::GrayImage;
//! use qrcode::QrCode;
//!
//! # fn main() {
//!
//! let code = QrCode::new(b"Some content here.");
//! match code {
//! Err(err) => panic!("Failed to encode the QR code: {:?}", err),
//! Ok(code) => {
//! let image: GrayImage = code.render().min_width(100).to_image();
//! // render `image`...
//! }
//! }
//!
//! # }
//! ```
//!
#![cfg_attr(feature="bench", feature(test))] // Unstable libraries
#[cfg(feature="bench")]
extern crate test;
#[cfg(feature="bench")] extern crate test;
extern crate num_traits;
#[cfg(feature="image")] extern crate image;
use std::ops::Index;
pub use types::{QrResult, EcLevel, Version};
pub mod types;
pub mod bits;
pub mod optimize;
pub mod ec;
pub mod canvas;
pub mod render;
pub use types::{QrResult, EcLevel, Version};
#[cfg(feature="image")] use render::{BlankAndWhitePixel, Renderer};
/// The encoded QR code symbol.
#[derive(Clone)]
@ -186,6 +195,12 @@ impl QrCode {
pub fn into_vec(self) -> Vec<bool> {
self.content
}
#[cfg(feature="image")]
pub fn render<P: BlankAndWhitePixel + 'static>(&self) -> Renderer<P> {
let quiet_zone = if self.version.is_micro() { 2 } else { 4 };
Renderer::new(&self.content, self.width, quiet_zone)
}
}
impl Index<(usize, usize)> for QrCode {
@ -249,4 +264,30 @@ mod tests {
}
}
#[cfg(all(test, feature="image"))]
mod image_tests {
use image::{GrayImage, Rgb, load_from_memory};
use {QrCode, Version, EcLevel};
#[test]
fn test_annex_i_qr_as_image() {
let code = QrCode::new(b"01234567").unwrap();
let image: GrayImage = code.render().to_image();
let expected = load_from_memory(include_bytes!("test_annex_i_qr_as_image.png")).unwrap().to_luma();
assert_eq!(image.dimensions(), expected.dimensions());
assert_eq!(image.into_raw(), expected.into_raw());
}
#[test]
fn test_annex_i_micro_qr_as_image() {
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
let image = code.render().min_width(200)
.dark_color(Rgb { data: [128, 0, 0] })
.light_color(Rgb { data: [255, 255, 128] })
.to_image();
let expected = load_from_memory(include_bytes!("test_annex_i_micro_qr_as_image.png")).unwrap().to_rgb();
assert_eq!(image.dimensions(), expected.dimensions());
assert_eq!(image.into_raw(), expected.into_raw());
}
}

222
src/render.rs Normal file
View File

@ -0,0 +1,222 @@
//! Render a QR code into image.
#![cfg(feature="image")]
use image::{Pixel, Rgb, Rgba, Luma, LumaA, Primitive, ImageBuffer};
/// A pixel which can support black and white colors.
pub trait BlankAndWhitePixel: Pixel {
fn black_color() -> Self;
fn white_color() -> Self;
}
impl<S: Primitive + 'static> BlankAndWhitePixel for Rgb<S> {
fn black_color() -> Self {
Rgb { data: [S::zero(); 3] }
}
fn white_color() -> Self {
Rgb { data: [S::max_value(); 3] }
}
}
impl<S: Primitive + 'static> BlankAndWhitePixel for Rgba<S> {
fn black_color() -> Self {
Rgba { data: [S::zero(), S::zero(), S::zero(), S::max_value()] }
}
fn white_color() -> Self {
Rgba { data: [S::max_value(); 4] }
}
}
impl<S: Primitive + 'static> BlankAndWhitePixel for Luma<S> {
fn black_color() -> Self {
Luma { data: [S::zero()] }
}
fn white_color() -> Self {
Luma { data: [S::max_value()] }
}
}
impl<S: Primitive + 'static> BlankAndWhitePixel for LumaA<S> {
fn black_color() -> Self {
LumaA { data: [S::zero(), S::max_value()] }
}
fn white_color() -> Self {
LumaA { data: [S::max_value(); 2] }
}
}
/// A QR code renderer. This is a builder type which converts a bool-vector into
/// an image.
pub struct Renderer<'a, P: BlankAndWhitePixel> {
content: &'a [bool],
modules_count: u32, // <- we call it `modules_count` here to avoid ambiguity of `width`.
quiet_zone: u32,
module_size: u32,
dark_color: P,
light_color: P,
has_quiet_zone: bool,
}
impl<'a, P: BlankAndWhitePixel + 'static> Renderer<'a, P> {
/// Creates a new renderer.
pub fn new(content: &'a [bool], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
assert!(modules_count * modules_count == content.len());
Renderer {
content: content,
modules_count: modules_count as u32,
quiet_zone: quiet_zone,
module_size: 8,
dark_color: P::black_color(),
light_color: P::white_color(),
has_quiet_zone: true,
}
}
/// Sets color of a dark module. Default is opaque black.
pub fn dark_color(&mut self, color: P) -> &mut Self {
self.dark_color = color;
self
}
/// Sets color of a light module. Default is opaque white.
pub fn light_color(&mut self, color: P) -> &mut Self {
self.light_color = color;
self
}
/// Whether to include the quiet zone in the generated image.
pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self {
self.has_quiet_zone = has_quiet_zone;
self
}
/// Sets the size of each module in pixels. Default is 8px.
pub fn module_size(&mut self, size: u32) -> &mut Self {
self.module_size = size;
self
}
/// Sets the minimal total image width (and thus height) in pixels,
/// including the quiet zone if applicable. The renderer will try to find
/// the dimension as small as possible, such that each module in the QR code
/// has uniform size (no distortion).
///
/// For instance, a version 1 QR code has 19 modules across including the
/// quiet zone. If we request an image of width ≥200px, we get that each
/// module's size should be 11px, so the actual image size will be 209px.
pub fn min_width(&mut self, width: u32) -> &mut Self {
let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
let width_in_modules = self.modules_count + quiet_zone;
let module_size = (width + width_in_modules - 1) / width_in_modules;
self.module_size(module_size)
}
/// Renders the QR code into an image.
pub fn to_image(&self) -> ImageBuffer<P, Vec<P::Subpixel>> {
let w = self.modules_count;
let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
let width = w + 2 * qz;
let ms = self.module_size;
let real_width = width * ms;
let mut image = ImageBuffer::new(real_width, real_width);
let mut i = 0;
for y in 0 .. width {
for x in 0 .. width {
let color = if qz <= x && x < w + qz && qz <= y && y < w + qz {
let c = if self.content[i] { self.dark_color } else { self.light_color };
i += 1;
c
} else {
self.light_color
};
for yy in y * ms .. (y + 1) * ms {
for xx in x * ms .. (x + 1) * ms {
image.put_pixel(xx, yy, color);
}
}
}
}
image
}
}
#[cfg(test)]
mod render_tests {
use render::Renderer;
use image::{Luma, Rgba};
#[test]
fn test_render_luma8_unsized() {
let image = Renderer::<Luma<u8>>::new(&[
false, true, true,
true, false, false,
false, true, false,
], 3, 1).module_size(1).to_image();
let expected = [
255, 255, 255, 255, 255,
255, 255, 0, 0, 255,
255, 0, 255, 255, 255,
255, 255, 0, 255, 255,
255, 255, 255, 255, 255,
];
assert_eq!(image.into_raw(), expected);
}
#[test]
fn test_render_rgba_unsized() {
let image = Renderer::<Rgba<u8>>::new(&[
false, true,
true, true,
], 2, 1).module_size(1).to_image();
let expected: &[u8] = &[
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
255,255,255,255, 255,255,255,255, 0, 0, 0,255, 255,255,255,255,
255,255,255,255, 0, 0, 0,255, 0, 0, 0,255, 255,255,255,255,
255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255,
];
assert_eq!(image.into_raw(), expected);
}
#[test]
fn test_render_resized() {
let image = Renderer::<Luma<u8>>::new(&[
true, false,
false, true,
], 2, 1).min_width(10).to_image();
let expected: &[u8] = &[
255,255,255, 255,255,255, 255,255,255, 255,255,255,
255,255,255, 255,255,255, 255,255,255, 255,255,255,
255,255,255, 255,255,255, 255,255,255, 255,255,255,
255,255,255, 0, 0, 0, 255,255,255, 255,255,255,
255,255,255, 0, 0, 0, 255,255,255, 255,255,255,
255,255,255, 0, 0, 0, 255,255,255, 255,255,255,
255,255,255, 255,255,255, 0, 0, 0, 255,255,255,
255,255,255, 255,255,255, 0, 0, 0, 255,255,255,
255,255,255, 255,255,255, 0, 0, 0, 255,255,255,
255,255,255, 255,255,255, 255,255,255, 255,255,255,
255,255,255, 255,255,255, 255,255,255, 255,255,255,
255,255,255, 255,255,255, 255,255,255, 255,255,255,
];
assert_eq!(image.dimensions(), (12, 12));
assert_eq!(image.into_raw(), expected);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B