parent
3da78c8871
commit
0b3b8f2637
|
@ -2,22 +2,13 @@ extern crate qrcode;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
const SPACE: char = ' '; //' ';
|
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let arg = env::args().nth(1).unwrap();
|
let arg = env::args().nth(1).unwrap();
|
||||||
let code = qrcode::QrCode::new(arg.as_bytes()).unwrap();
|
let code = qrcode::QrCode::new(arg.as_bytes()).unwrap();
|
||||||
|
|
||||||
print!("\n\n\n\n\n{}{}{}{}{}", SPACE, SPACE, SPACE, SPACE, SPACE);
|
print!("{}", code.render()
|
||||||
|
.dark_color("\x1b[7m \x1b[0m")
|
||||||
for y in 0 .. code.width() {
|
.light_color("\x1b[49m \x1b[0m")
|
||||||
for x in 0 .. code.width() {
|
.build());
|
||||||
let block = code[(x, y)].select('█', SPACE);
|
|
||||||
print!("{}{}", block, block);
|
|
||||||
}
|
|
||||||
print!("\n{}{}{}{}{}", SPACE, SPACE, SPACE, SPACE, SPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\n\n\n\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
59
src/lib.rs
59
src/lib.rs
|
@ -2,26 +2,30 @@
|
||||||
//!
|
//!
|
||||||
//! This crate provides a QR code and Micro QR code encoder for binary data.
|
//! This crate provides a QR code and Micro QR code encoder for binary data.
|
||||||
//!
|
//!
|
||||||
//! ```
|
#![cfg_attr(feature="image", doc=" ```rust")]
|
||||||
|
#![cfg_attr(not(feature="image"), doc="```ignore")]
|
||||||
//! extern crate qrcode;
|
//! extern crate qrcode;
|
||||||
//! # #[cfg(feature="image")]
|
|
||||||
//! extern crate image;
|
//! extern crate image;
|
||||||
//!
|
//!
|
||||||
//! use qrcode::QrCode;
|
//! use qrcode::QrCode;
|
||||||
//! # #[cfg(feature="image")]
|
//! use image::Luma;
|
||||||
//! use image::GrayImage;
|
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
//! // Encode some data into bits.
|
//! // Encode some data into bits.
|
||||||
//! let code = QrCode::new(b"01234567").unwrap();
|
//! let code = QrCode::new(b"01234567").unwrap();
|
||||||
//!
|
//!
|
||||||
//! // Render the bits into an image.
|
//! // Render the bits into an image.
|
||||||
//! # #[cfg(feature="image")]
|
//! let image = code.render::<Luma<u8>>().build();
|
||||||
//! let image: GrayImage = code.render().to_image();
|
|
||||||
//!
|
//!
|
||||||
//! // Save the image.
|
//! // Save the image.
|
||||||
//! # #[cfg(feature="image")]
|
|
||||||
//! image.save("/tmp/qrcode.png").unwrap();
|
//! image.save("/tmp/qrcode.png").unwrap();
|
||||||
|
//!
|
||||||
|
//! // You can also render it into a string.
|
||||||
|
//! let string = code.render()
|
||||||
|
//! .light_color(' ')
|
||||||
|
//! .dark_color('#')
|
||||||
|
//! .build();
|
||||||
|
//! println!("{}", string);
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
@ -41,7 +45,7 @@ pub mod render;
|
||||||
|
|
||||||
pub use types::{QrResult, Color, EcLevel, Version};
|
pub use types::{QrResult, Color, EcLevel, Version};
|
||||||
|
|
||||||
#[cfg(feature="image")] use render::{BlankAndWhitePixel, Renderer};
|
use render::{Pixel, Renderer};
|
||||||
|
|
||||||
/// The encoded QR code symbol.
|
/// The encoded QR code symbol.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -171,17 +175,11 @@ impl QrCode {
|
||||||
/// Converts the QR code into a human-readable string. This is mainly for
|
/// Converts the QR code into a human-readable string. This is mainly for
|
||||||
/// debugging only.
|
/// debugging only.
|
||||||
pub fn to_debug_str(&self, on_char: char, off_char: char) -> String {
|
pub fn to_debug_str(&self, on_char: char, off_char: char) -> String {
|
||||||
let width = self.width;
|
self.render()
|
||||||
let mut k = 0;
|
.quiet_zone(false)
|
||||||
let mut res = String::with_capacity(width * (width + 1));
|
.dark_color(on_char)
|
||||||
for _ in 0 .. width {
|
.light_color(off_char)
|
||||||
res.push('\n');
|
.build()
|
||||||
for _ in 0 .. width {
|
|
||||||
res.push(self.content[k].select(on_char, off_char));
|
|
||||||
k += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the QR code to a vector of booleans. Each entry represents the
|
/// Converts the QR code to a vector of booleans. Each entry represents the
|
||||||
|
@ -214,7 +212,8 @@ impl QrCode {
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
#[cfg_attr(feature="image", doc=" ```rust")]
|
||||||
|
#[cfg_attr(not(feature="image"), doc=" ```ignore")]
|
||||||
/// # extern crate image;
|
/// # extern crate image;
|
||||||
/// # extern crate qrcode;
|
/// # extern crate qrcode;
|
||||||
/// # use qrcode::QrCode;
|
/// # use qrcode::QrCode;
|
||||||
|
@ -226,16 +225,15 @@ impl QrCode {
|
||||||
/// .dark_color(Rgb { data: [0, 0, 128] })
|
/// .dark_color(Rgb { data: [0, 0, 128] })
|
||||||
/// .light_color(Rgb { data: [224, 224, 224] }) // adjust colors
|
/// .light_color(Rgb { data: [224, 224, 224] }) // adjust colors
|
||||||
/// .quiet_zone(false) // disable quiet zone (white border)
|
/// .quiet_zone(false) // disable quiet zone (white border)
|
||||||
/// .min_width(300) // sets minimum image size
|
/// .min_dimensions(300, 300) // sets minimum image size
|
||||||
/// .to_image();
|
/// .build();
|
||||||
///
|
///
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note: the `image` crate itself also provides method to rotate the image,
|
/// Note: the `image` crate itself also provides method to rotate the image,
|
||||||
/// or overlay a logo on top of the QR code.
|
/// or overlay a logo on top of the QR code.
|
||||||
#[cfg(feature="image")]
|
pub fn render<P: Pixel>(&self) -> Renderer<P> {
|
||||||
pub fn render<P: BlankAndWhitePixel + 'static>(&self) -> Renderer<P> {
|
|
||||||
let quiet_zone = if self.version.is_micro() { 2 } else { 4 };
|
let quiet_zone = if self.version.is_micro() { 2 } else { 4 };
|
||||||
Renderer::new(&self.content, self.width, quiet_zone)
|
Renderer::new(&self.content, self.width, quiet_zone)
|
||||||
}
|
}
|
||||||
|
@ -258,7 +256,7 @@ mod tests {
|
||||||
fn test_annex_i_qr() {
|
fn test_annex_i_qr() {
|
||||||
// This uses the ISO Annex I as test vector.
|
// This uses the ISO Annex I as test vector.
|
||||||
let code = QrCode::with_version(b"01234567", Version::Normal(1), EcLevel::M).unwrap();
|
let code = QrCode::with_version(b"01234567", Version::Normal(1), EcLevel::M).unwrap();
|
||||||
assert_eq!(&*code.to_debug_str('#', '.'), "\n\
|
assert_eq!(&*code.to_debug_str('#', '.'), "\
|
||||||
#######..#.##.#######\n\
|
#######..#.##.#######\n\
|
||||||
#.....#..####.#.....#\n\
|
#.....#..####.#.....#\n\
|
||||||
#.###.#.#.....#.###.#\n\
|
#.###.#.#.....#.###.#\n\
|
||||||
|
@ -285,7 +283,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_annex_i_micro_qr() {
|
fn test_annex_i_micro_qr() {
|
||||||
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
|
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
|
||||||
assert_eq!(&*code.to_debug_str('#', '.'), "\n\
|
assert_eq!(&*code.to_debug_str('#', '.'), "\
|
||||||
#######.#.#.#\n\
|
#######.#.#.#\n\
|
||||||
#.....#.###.#\n\
|
#.....#.###.#\n\
|
||||||
#.###.#..##.#\n\
|
#.###.#..##.#\n\
|
||||||
|
@ -304,13 +302,13 @@ mod tests {
|
||||||
|
|
||||||
#[cfg(all(test, feature="image"))]
|
#[cfg(all(test, feature="image"))]
|
||||||
mod image_tests {
|
mod image_tests {
|
||||||
use image::{GrayImage, Rgb, load_from_memory};
|
use image::{Luma, Rgb, load_from_memory};
|
||||||
use {QrCode, Version, EcLevel};
|
use {QrCode, Version, EcLevel};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_annex_i_qr_as_image() {
|
fn test_annex_i_qr_as_image() {
|
||||||
let code = QrCode::new(b"01234567").unwrap();
|
let code = QrCode::new(b"01234567").unwrap();
|
||||||
let image: GrayImage = code.render().to_image();
|
let image = code.render::<Luma<u8>>().build();
|
||||||
let expected = load_from_memory(include_bytes!("test_annex_i_qr_as_image.png")).unwrap().to_luma();
|
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.dimensions(), expected.dimensions());
|
||||||
assert_eq!(image.into_raw(), expected.into_raw());
|
assert_eq!(image.into_raw(), expected.into_raw());
|
||||||
|
@ -319,10 +317,11 @@ mod image_tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_annex_i_micro_qr_as_image() {
|
fn test_annex_i_micro_qr_as_image() {
|
||||||
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
|
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
|
||||||
let image = code.render().min_width(200)
|
let image = code.render()
|
||||||
|
.min_dimensions(200, 200)
|
||||||
.dark_color(Rgb { data: [128, 0, 0] })
|
.dark_color(Rgb { data: [128, 0, 0] })
|
||||||
.light_color(Rgb { data: [255, 255, 128] })
|
.light_color(Rgb { data: [255, 255, 128] })
|
||||||
.to_image();
|
.build();
|
||||||
let expected = load_from_memory(include_bytes!("test_annex_i_micro_qr_as_image.png")).unwrap().to_rgb();
|
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.dimensions(), expected.dimensions());
|
||||||
assert_eq!(image.into_raw(), expected.into_raw());
|
assert_eq!(image.into_raw(), expected.into_raw());
|
||||||
|
|
228
src/render.rs
228
src/render.rs
|
@ -1,228 +0,0 @@
|
||||||
//! Render a QR code into image.
|
|
||||||
|
|
||||||
#![cfg(feature="image")]
|
|
||||||
|
|
||||||
use image::{Pixel, Rgb, Rgba, Luma, LumaA, Primitive, ImageBuffer};
|
|
||||||
use types::Color;
|
|
||||||
|
|
||||||
/// 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 [Color],
|
|
||||||
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 [Color], 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] != Color::Light {
|
|
||||||
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};
|
|
||||||
use types::Color;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_render_luma8_unsized() {
|
|
||||||
let image = Renderer::<Luma<u8>>::new(&[
|
|
||||||
Color::Light, Color::Dark, Color::Dark,
|
|
||||||
Color::Dark, Color::Light, Color::Light,
|
|
||||||
Color::Light, Color::Dark, Color::Light,
|
|
||||||
], 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(&[
|
|
||||||
Color::Light, Color::Dark,
|
|
||||||
Color::Dark, Color::Dark,
|
|
||||||
], 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(&[
|
|
||||||
Color::Dark, Color::Light,
|
|
||||||
Color::Light, Color::Dark,
|
|
||||||
], 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
135
src/render/image.rs
Normal file
135
src/render/image.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
#![cfg(feature="image")]
|
||||||
|
|
||||||
|
use render::{Pixel, Canvas};
|
||||||
|
use types::Color;
|
||||||
|
|
||||||
|
use image::{Pixel as ImagePixel, Rgb, Rgba, Luma, LumaA, Primitive, ImageBuffer};
|
||||||
|
|
||||||
|
macro_rules! impl_pixel_for_image_pixel {
|
||||||
|
($p:ident<$s:ident>: $c:pat => $d:expr) => {
|
||||||
|
impl<$s: Primitive + 'static> Pixel for $p<$s> {
|
||||||
|
type Image = ImageBuffer<Self, Vec<S>>;
|
||||||
|
type Canvas = (Self, Self::Image);
|
||||||
|
|
||||||
|
fn default_color(color: Color) -> Self {
|
||||||
|
match color.select($s::zero(), $s::max_value()) {
|
||||||
|
$c => $p { data: $d }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_pixel_for_image_pixel!{ Luma<S>: p => [p] }
|
||||||
|
impl_pixel_for_image_pixel!{ LumaA<S>: p => [p, S::max_value()] }
|
||||||
|
impl_pixel_for_image_pixel!{ Rgb<S>: p => [p, p, p] }
|
||||||
|
impl_pixel_for_image_pixel!{ Rgba<S>: p => [p, p, p, S::max_value()] }
|
||||||
|
|
||||||
|
impl<P: ImagePixel + 'static> Canvas for (P, ImageBuffer<P, Vec<P::Subpixel>>) {
|
||||||
|
type Pixel = P;
|
||||||
|
type Image = ImageBuffer<P, Vec<P::Subpixel>>;
|
||||||
|
|
||||||
|
fn new(width: u32, height: u32, dark_pixel: P, light_pixel: P) -> Self {
|
||||||
|
(dark_pixel, ImageBuffer::from_pixel(width, height, light_pixel))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_dark_pixel(&mut self, x: u32, y: u32) {
|
||||||
|
self.1.put_pixel(x, y, self.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_image(self) -> ImageBuffer<P, Vec<P::Subpixel>> {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod render_tests {
|
||||||
|
use render::Renderer;
|
||||||
|
use image::{Luma, Rgba};
|
||||||
|
use types::Color;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_luma8_unsized() {
|
||||||
|
let image = Renderer::<Luma<u8>>::new(&[
|
||||||
|
Color::Light, Color::Dark, Color::Dark,
|
||||||
|
Color::Dark, Color::Light, Color::Light,
|
||||||
|
Color::Light, Color::Dark, Color::Light,
|
||||||
|
], 3, 1).module_dimensions(1, 1).build();
|
||||||
|
|
||||||
|
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(&[
|
||||||
|
Color::Light, Color::Dark,
|
||||||
|
Color::Dark, Color::Dark,
|
||||||
|
], 2, 1).module_dimensions(1, 1).build();
|
||||||
|
|
||||||
|
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_min() {
|
||||||
|
let image = Renderer::<Luma<u8>>::new(&[
|
||||||
|
Color::Dark, Color::Light,
|
||||||
|
Color::Light, Color::Dark,
|
||||||
|
], 2, 1).min_dimensions(10, 10).build();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_resized_max() {
|
||||||
|
let image = Renderer::<Luma<u8>>::new(&[
|
||||||
|
Color::Dark, Color::Light,
|
||||||
|
Color::Light, Color::Dark,
|
||||||
|
], 2, 1).max_dimensions(10, 5).build();
|
||||||
|
|
||||||
|
let expected: &[u8] = &[
|
||||||
|
255,255, 255,255, 255,255, 255,255,
|
||||||
|
|
||||||
|
255,255, 0, 0, 255,255, 255,255,
|
||||||
|
|
||||||
|
255,255, 255,255, 0, 0, 255,255,
|
||||||
|
|
||||||
|
255,255, 255,255, 255,255, 255,255,
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(image.dimensions(), (8, 4));
|
||||||
|
assert_eq!(image.into_raw(), expected);
|
||||||
|
}
|
||||||
|
}
|
188
src/render/mod.rs
Normal file
188
src/render/mod.rs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
//! Render a QR code into image.
|
||||||
|
|
||||||
|
use std::cmp::max;
|
||||||
|
use types::Color;
|
||||||
|
|
||||||
|
pub mod image;
|
||||||
|
pub mod string;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//{{{ Pixel trait
|
||||||
|
|
||||||
|
/// Abstraction of an image pixel.
|
||||||
|
pub trait Pixel: Copy + Sized {
|
||||||
|
/// Type of the finalized image.
|
||||||
|
type Image: Sized + 'static;
|
||||||
|
|
||||||
|
/// The type that stores an intermediate buffer before finalizing to a
|
||||||
|
/// concrete image
|
||||||
|
type Canvas: Canvas<Pixel=Self, Image=Self::Image>;
|
||||||
|
|
||||||
|
/// Obtains the default module size. The result must be at least 1×1.
|
||||||
|
fn default_unit_size() -> (u32, u32) {
|
||||||
|
(8, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtains the default pixel color when a module is dark or light.
|
||||||
|
fn default_color(color: Color) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rendering canvas of a QR code image.
|
||||||
|
pub trait Canvas: Sized {
|
||||||
|
type Pixel: Sized;
|
||||||
|
type Image: Sized;
|
||||||
|
|
||||||
|
/// Constructs a new canvas of the given dimensions.
|
||||||
|
fn new(width: u32, height: u32, dark_pixel: Self::Pixel, light_pixel: Self::Pixel) -> Self;
|
||||||
|
|
||||||
|
/// Draws a single dark pixel at the (x, y) coordinate.
|
||||||
|
fn draw_dark_pixel(&mut self, x: u32, y: u32);
|
||||||
|
|
||||||
|
fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
|
||||||
|
for y in top .. (top + height) {
|
||||||
|
for x in left .. (left + width) {
|
||||||
|
self.draw_dark_pixel(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finalize the canvas to a real image.
|
||||||
|
fn into_image(self) -> Self::Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
//}}}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//{{{ Renderer
|
||||||
|
|
||||||
|
/// A QR code renderer. This is a builder type which converts a bool-vector into
|
||||||
|
/// an image.
|
||||||
|
pub struct Renderer<'a, P: Pixel> {
|
||||||
|
content: &'a [Color],
|
||||||
|
modules_count: u32, // <- we call it `modules_count` here to avoid ambiguity of `width`.
|
||||||
|
quiet_zone: u32,
|
||||||
|
module_size: (u32, u32),
|
||||||
|
|
||||||
|
dark_color: P,
|
||||||
|
light_color: P,
|
||||||
|
has_quiet_zone: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: Pixel> Renderer<'a, P> {
|
||||||
|
/// Creates a new renderer.
|
||||||
|
pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
|
||||||
|
assert!(modules_count * modules_count == content.len());
|
||||||
|
Renderer {
|
||||||
|
content,
|
||||||
|
modules_count: modules_count as u32,
|
||||||
|
quiet_zone,
|
||||||
|
module_size: P::default_unit_size(),
|
||||||
|
dark_color: P::default_color(Color::Dark),
|
||||||
|
light_color: P::default_color(Color::Light),
|
||||||
|
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.
|
||||||
|
#[deprecated(since="0.4.0", note="use `.module_dimensions(width, width)` instead")]
|
||||||
|
pub fn module_size(&mut self, width: u32) -> &mut Self {
|
||||||
|
self.module_dimensions(width, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the size of each module in pixels. Default is 8×8.
|
||||||
|
pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
|
||||||
|
self.module_size = (max(width, 1), max(height, 1));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated(since="0.4.0", note="use `.min_dimensions(width, width)` instead")]
|
||||||
|
pub fn min_width(&mut self, width: u32) -> &mut Self {
|
||||||
|
self.min_dimensions(width, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the minimum total image size 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 size ≥200×200, we get that each
|
||||||
|
/// module's size should be 11×11, so the actual image size will be 209×209.
|
||||||
|
pub fn min_dimensions(&mut self, width: u32, height: 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 unit_width = (width + width_in_modules - 1) / width_in_modules;
|
||||||
|
let unit_height = (height + width_in_modules - 1) / width_in_modules;
|
||||||
|
self.module_dimensions(unit_width, unit_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the maximum total image size in pixels, including the quiet zone if
|
||||||
|
/// applicable. The renderer will try to find the dimension as large 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 size ≤200×200, we get that each
|
||||||
|
/// module's size should be 10×10, so the actual image size will be 190×190.
|
||||||
|
///
|
||||||
|
/// The module size is at least 1×1, so if the restriction is too small, the
|
||||||
|
/// final image *can* be larger than the input.
|
||||||
|
pub fn max_dimensions(&mut self, width: u32, height: 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 unit_width = width / width_in_modules;
|
||||||
|
let unit_height = height / width_in_modules;
|
||||||
|
self.module_dimensions(unit_width, unit_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the QR code into an image.
|
||||||
|
#[deprecated(since="0.4.0", note="renamed to `.build()` to de-emphasize the image connection")]
|
||||||
|
pub fn to_image(&self) -> P::Image {
|
||||||
|
self.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the QR code into an image.
|
||||||
|
pub fn build(&self) -> P::Image {
|
||||||
|
let w = self.modules_count;
|
||||||
|
let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
|
||||||
|
let width = w + 2 * qz;
|
||||||
|
|
||||||
|
let (mw, mh) = self.module_size;
|
||||||
|
let real_width = width * mw;
|
||||||
|
let real_height = width * mh;
|
||||||
|
|
||||||
|
let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
|
||||||
|
let mut i = 0;
|
||||||
|
for y in 0 .. width {
|
||||||
|
for x in 0 .. width {
|
||||||
|
if qz <= x && x < w + qz && qz <= y && y < w + qz {
|
||||||
|
if self.content[i] != Color::Light {
|
||||||
|
canvas.draw_dark_rect(x*mw, y*mh, mw, mh);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.into_image()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//}}}
|
126
src/render/string.rs
Normal file
126
src/render/string.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
//! String rendering support.
|
||||||
|
|
||||||
|
use render::{Pixel, Canvas as RenderCanvas};
|
||||||
|
use types::Color;
|
||||||
|
|
||||||
|
pub trait Element: Copy {
|
||||||
|
fn default_color(color: Color) -> Self;
|
||||||
|
fn strlen(self) -> usize;
|
||||||
|
fn append_to_string(self, string: &mut String);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for char {
|
||||||
|
fn default_color(color: Color) -> Self {
|
||||||
|
color.select('#', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strlen(self) -> usize {
|
||||||
|
self.len_utf8()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_to_string(self, string: &mut String) {
|
||||||
|
string.push(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Element for &'a str {
|
||||||
|
fn default_color(color: Color) -> Self {
|
||||||
|
color.select("#", " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strlen(self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_to_string(self, string: &mut String) {
|
||||||
|
string.push_str(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Canvas<P: Element> {
|
||||||
|
buffer: Vec<P>,
|
||||||
|
width: usize,
|
||||||
|
dark_pixel: P,
|
||||||
|
dark_cap_inc: isize,
|
||||||
|
capacity: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Element> Pixel for P {
|
||||||
|
type Canvas = Canvas<P>;
|
||||||
|
type Image = String;
|
||||||
|
|
||||||
|
fn default_unit_size() -> (u32, u32) {
|
||||||
|
(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_color(color: Color) -> Self {
|
||||||
|
<Self as Element>::default_color(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Element> RenderCanvas for Canvas<P> {
|
||||||
|
type Pixel = P;
|
||||||
|
type Image = String;
|
||||||
|
|
||||||
|
fn new(width: u32, height: u32, dark_pixel: P, light_pixel: P) -> Self {
|
||||||
|
let width = width as usize;
|
||||||
|
let height = height as isize;
|
||||||
|
let dark_cap = dark_pixel.strlen() as isize;
|
||||||
|
let light_cap = light_pixel.strlen() as isize;
|
||||||
|
Canvas {
|
||||||
|
buffer: vec![light_pixel; width * (height as usize)],
|
||||||
|
width,
|
||||||
|
dark_pixel,
|
||||||
|
dark_cap_inc: dark_cap - light_cap,
|
||||||
|
capacity: light_cap * (width as isize) * height + (height - 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_dark_pixel(&mut self, x: u32, y: u32) {
|
||||||
|
let x = x as usize;
|
||||||
|
let y = y as usize;
|
||||||
|
self.capacity += self.dark_cap_inc;
|
||||||
|
self.buffer[x + y*self.width] = self.dark_pixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn into_image(self) -> String {
|
||||||
|
let mut result = String::with_capacity(self.capacity as usize);
|
||||||
|
for (i, pixel) in self.buffer.into_iter().enumerate() {
|
||||||
|
if i != 0 && i % self.width == 0 {
|
||||||
|
result.push('\n');
|
||||||
|
}
|
||||||
|
pixel.append_to_string(&mut result);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_render_to_string() {
|
||||||
|
use render::Renderer;
|
||||||
|
|
||||||
|
let colors = &[
|
||||||
|
Color::Dark, Color::Light,
|
||||||
|
Color::Light, Color::Dark,
|
||||||
|
];
|
||||||
|
let image: String = Renderer::<char>::new(colors, 2, 1).build();
|
||||||
|
assert_eq!(&image, " \n # \n # \n ");
|
||||||
|
|
||||||
|
let image2 = Renderer::new(colors, 2, 1)
|
||||||
|
.light_color("A")
|
||||||
|
.dark_color("!B!")
|
||||||
|
.module_dimensions(2, 2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(&image2, "\
|
||||||
|
AAAAAAAA\n\
|
||||||
|
AAAAAAAA\n\
|
||||||
|
AA!B!!B!AAAA\n\
|
||||||
|
AA!B!!B!AAAA\n\
|
||||||
|
AAAA!B!!B!AA\n\
|
||||||
|
AAAA!B!!B!AA\n\
|
||||||
|
AAAAAAAA\n\
|
||||||
|
AAAAAAAA");
|
||||||
|
}
|
Loading…
Reference in a new issue