diff --git a/.travis.yml b/.travis.yml index bdd2271..dbb1ad1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,7 @@ -# Copied from https://github.com/kennytm/extprim/blob/master/.travis.yml :) - language: rust -sudo: false +sudo: false +dist: trusty addons: apt: packages: @@ -10,6 +9,7 @@ addons: - libelf-dev - libdw-dev - binutils-dev + - libiberty-dev - gcc-multilib os: @@ -21,28 +21,22 @@ rust: - beta - nightly -env: - matrix: - - ARCH=x86_64 - - ARCH=i686 +matrix: + allow_failures: + - rust: beta install: - if [ "$TRAVIS_OS_NAME" = 'linux' ]; then OS=unknown-linux-gnu; else OS=apple-darwin; fi - - export HOST=$ARCH-$OS - - curl -SfLO "https://static.rust-lang.org/rustup/dist/$HOST/rustup-init" - - chmod u+x rustup-init - - ./rustup-init -y --default-host "$HOST" --default-toolchain "$TRAVIS_RUST_VERSION" - - export PATH=$HOME/.cargo/bin:$HOME/.local/bin:$PATH - - rustc -vV - - cargo -vV + - rustup target add i686-$OS script: - cargo test --no-default-features - cargo test - if [ "$TRAVIS_RUST_VERSION" = 'nightly' ]; then cargo bench --features=bench; fi + - cargo test --target i686-$OS after_success: - cargo install cargo-kcov - cargo kcov --print-install-kcov-sh | bash - - cargo kcov --coveralls -- --verify + - cargo kcov --coveralls diff --git a/Cargo.toml b/Cargo.toml index 6d272d6..142a27d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "qrcode" description = "QR code encoder in Rust" license = "MIT / Apache-2.0" -version = "0.3.0" +version = "0.4.0" authors = ["kennytm "] keywords = ["qrcode"] repository = "https://github.com/kennytm/qrcode-rust" @@ -16,12 +16,23 @@ exclude = [ travis-ci = { repository = "kennytm/qrcode-rust" } [dependencies] -num-traits = "0.1" image = { version = "0.13", optional = true } [features] -default = ["image"] +default = ["image", "svg"] bench = [] +svg = [] [[bin]] name = "qrencode" + +[[example]] +name = "encode_image" +required-features = ["image"] + +[[example]] +name = "encode_string" + +[[example]] +name = "encode_svg" +required-features = ["svg"] diff --git a/README.md b/README.md index 60db9e7..c4fd2b9 100644 --- a/README.md +++ b/README.md @@ -13,34 +13,34 @@ Cargo.toml ```toml [dependencies] -qrcode = "0.2.0" +qrcode = "0.4" ``` 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 } +qrcode = { version = "0.4", default-features = false } ``` Example ------- -This code: +## Image generation ```rust extern crate qrcode; extern crate image; use qrcode::QrCode; -use image::GrayImage; +use image::Luma; 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(); + let image = code.render::>().build(); // Save the image. image.save("/tmp/qrcode.png").unwrap(); @@ -51,3 +51,67 @@ Generates this image: ![Output](src/test_annex_i_qr_as_image.png) +## String generation + +```rust +extern crate qrcode; +use qrcode::QrCode; + +fn main() { + let code = QrCode::new(b"Hello").unwrap(); + let string = code.render::() + .quiet_zone(false) + .module_dimensions(2, 1) + .build(); + println!("{}", string); +} +``` + +Generates this output: + +```none +############## ######## ############## +## ## ## ## ## +## ###### ## ## ## ## ## ###### ## +## ###### ## ## ## ## ###### ## +## ###### ## #### ## ## ###### ## +## ## #### ## ## ## +############## ## ## ## ############## + ## ## +## ########## ## ## ########## + ## ## ######## #### ## + ########## #### ## #### ###### + ## ## #### ########## #### + ###### ########## ## ## ## + ## ## ## ## +############## ## ## ## ## #### +## ## ## ## ########## +## ###### ## ## ## ## ## ## +## ###### ## #### ########## ## +## ###### ## #### ## #### ## +## ## ## ######## ###### +############## #### ## ## ## +``` + +## SVG generation + +```rust +extern crate qrcode; + +use qrcode::{QrCode, Version, EcLevel}; +use qrcode::render::svg; + +fn main() { + let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap(); + let image = code.render() + .min_dimensions(200, 200) + .dark_color(svg::Color("#800000")) + .light_color(svg::Color("#ffff80")) + .build(); + println!("{}", string); +} +``` + +Generates this SVG: + +[![Output](src/test_annex_i_micro_qr_as_svg.svg)](src/test_annex_i_micro_qr_as_svg.svg) \ No newline at end of file diff --git a/examples/encode_image.rs b/examples/encode_image.rs new file mode 100644 index 0000000..6e32801 --- /dev/null +++ b/examples/encode_image.rs @@ -0,0 +1,16 @@ +extern crate qrcode; +extern crate image; + +use qrcode::QrCode; +use image::Luma; + +fn main() { + // Encode some data into bits. + let code = QrCode::new(b"01234567").unwrap(); + + // Render the bits into an image. + let image = code.render::>().build(); + + // Save the image. + image.save("/tmp/qrcode.png").unwrap(); +} diff --git a/examples/encode_string.rs b/examples/encode_string.rs new file mode 100644 index 0000000..f55dbe1 --- /dev/null +++ b/examples/encode_string.rs @@ -0,0 +1,11 @@ +extern crate qrcode; +use qrcode::QrCode; + +fn main() { + let code = QrCode::new(b"Hello").unwrap(); + let string = code.render::() + .quiet_zone(false) + .module_dimensions(2, 1) + .build(); + println!("{}", string); +} diff --git a/examples/encode_svg.rs b/examples/encode_svg.rs new file mode 100644 index 0000000..2b68018 --- /dev/null +++ b/examples/encode_svg.rs @@ -0,0 +1,14 @@ +extern crate qrcode; + +use qrcode::{QrCode, Version, EcLevel}; +use qrcode::render::svg; + +fn main() { + let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap(); + let image = code.render() + .min_dimensions(200, 200) + .dark_color(svg::Color("#800000")) + .light_color(svg::Color("#ffff80")) + .build(); + println!("{}", image); +} diff --git a/src/bin/qrencode.rs b/src/bin/qrencode.rs index db133ca..3e776db 100644 --- a/src/bin/qrencode.rs +++ b/src/bin/qrencode.rs @@ -2,22 +2,13 @@ extern crate qrcode; use std::env; -const SPACE: char = ' '; //' '; - pub fn main() { let arg = env::args().nth(1).unwrap(); let code = qrcode::QrCode::new(arg.as_bytes()).unwrap(); - print!("\n\n\n\n\n{}{}{}{}{}", SPACE, SPACE, SPACE, SPACE, SPACE); - - for y in 0 .. code.width() { - for x in 0 .. code.width() { - let block = if code[(x, y)] { '█' } else { SPACE }; - print!("{}{}", block, block); - } - print!("\n{}{}{}{}{}", SPACE, SPACE, SPACE, SPACE, SPACE); - } - - println!("\n\n\n\n"); + print!("{}", code.render() + .dark_color("\x1b[7m \x1b[0m") + .light_color("\x1b[49m \x1b[0m") + .build()); } diff --git a/src/canvas.rs b/src/canvas.rs index beacdf1..798f863 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -9,17 +9,14 @@ //! c.apply_mask(MaskPattern::Checkerboard); //! let bools = c.to_bools(); -use std::iter::repeat; use std::cmp::max; use std::ops::Range; -use num_traits::PrimInt; - -use types::{Version, EcLevel}; +use types::{Version, EcLevel, Color}; // TODO remove this after `p ... q` becomes stable. See rust-lang/rust#28237. -fn range_inclusive(from: N, to: N) -> Range { - from .. (to + N::one()) +fn range_inclusive(from: i16, to: i16) -> Range { + from .. (to + 1) } //------------------------------------------------------------------------------ @@ -31,49 +28,47 @@ pub enum Module { /// The module is empty. Empty, - /// The module is light (white), and cannot be masked. This mainly refers to - /// modules of functional patterns. - Light, + /// The module is of functional patterns which cannot be masked, or pixels + /// which have been masked. + Masked(Color), - /// The module is dark (black), and cannot be masked. This mainly refers to - /// modules of functional patterns. - Dark, + /// The module is of data and error correction bits before masking. + Unmasked(Color), +} - /// The module is light (white), but not yet masked. This mainly refers to - /// modules of data and error correction bits before masking. - LightUnmasked, - - /// The module is dark (black), but not yet masked. This mainly refers to - /// modules of data and error correction bits before masking. - DarkUnmasked, +impl From for Color { + fn from(module: Module) -> Color { + match module { + Module::Empty => Color::Light, + Module::Masked(c) | Module::Unmasked(c) => c, + } + } } impl Module { /// Checks whether a module is dark. - pub fn is_dark(&self) -> bool { - match *self { - Module::Dark | Module::DarkUnmasked => true, - _ => false, - } + pub fn is_dark(self) -> bool { + Color::from(self) == Color::Dark } /// Apply a mask to the unmasked modules. /// /// use qrcode::canvas::Module; + /// use qrcode::types::Color; /// - /// assert_eq!(Module::LightUnmasked.mask(true), Module::Dark); - /// assert_eq!(Module::DarkUnmasked.mask(true), Module::Light); - /// assert_eq!(Module::LightUnmasked.mask(false), Module::Light); - /// assert_eq!(Module::Dark.mask(true), Module::Dark); - /// assert_eq!(Module::Dark.mask(false), Module::Dark); + /// assert_eq!(Module::Unmasked(Color::Light).mask(true), Module::Masked(Color::Dark)); + /// assert_eq!(Module::Unmasked(Color::Dark).mask(true), Module::Masked(Color::Light)); + /// assert_eq!(Module::Unmasked(Color::Light).mask(false), Module::Masked(Color::Light)); + /// assert_eq!(Module::Masked(Color::Dark).mask(true), Module::Masked(Color::Dark)); + /// assert_eq!(Module::Masked(Color::Dark).mask(false), Module::Masked(Color::Dark)); /// - pub fn mask(&self, should_invert: bool) -> Module { - match (*self, should_invert) { - (Module::Empty, true) | (Module::LightUnmasked, true) => Module::Dark, - (Module::Empty, false) | (Module::LightUnmasked, false) => Module::Light, - (Module::DarkUnmasked, true) => Module::Light, - (Module::DarkUnmasked, false) => Module::Dark, - (a, _) => a, + pub fn mask(self, should_invert: bool) -> Module { + match (self, should_invert) { + (Module::Empty, true) => Module::Masked(Color::Dark), + (Module::Empty, false) => Module::Masked(Color::Light), + (Module::Unmasked(c), true) => Module::Masked(!c), + (Module::Unmasked(c), false) | + (Module::Masked(c), _) => Module::Masked(c), } } } @@ -108,7 +103,7 @@ impl Canvas { width: width, version: version, ec_level: ec_level, - modules: repeat(Module::Empty).take((width*width) as usize).collect() + modules: vec![Module::Empty; (width*width) as usize], } } @@ -122,10 +117,10 @@ impl Canvas { for x in 0 .. width { res.push(match self.get(x, y) { Module::Empty => '?', - Module::Light => '.', - Module::Dark => '#', - Module::LightUnmasked => '-', - Module::DarkUnmasked => '*', + Module::Masked(Color::Light) => '.', + Module::Masked(Color::Dark) => '#', + Module::Unmasked(Color::Light) => '-', + Module::Unmasked(Color::Dark) => '*', }); } } @@ -151,18 +146,17 @@ impl Canvas { &mut self.modules[index] } - /// Sets the color of a module at the given coordinates. For convenience, - /// negative coordinates will wrap around. - pub fn put(&mut self, x: i16, y: i16, module: Module) { - *self.get_mut(x, y) = module; + /// Sets the color of a functional module at the given coordinates. For + /// convenience, negative coordinates will wrap around. + pub fn put(&mut self, x: i16, y: i16, color: Color) { + *self.get_mut(x, y) = Module::Masked(color); } - } #[cfg(test)] mod basic_canvas_tests { use canvas::{Canvas, Module}; - use types::{Version, EcLevel}; + use types::{Version, EcLevel, Color}; #[test] fn test_index() { @@ -172,11 +166,11 @@ mod basic_canvas_tests { assert_eq!(c.get(-1, -7), Module::Empty); assert_eq!(c.get(21-1, 21-7), Module::Empty); - c.put(0, 0, Module::Dark); - c.put(-1, -7, Module::Light); - assert_eq!(c.get(0, 0), Module::Dark); - assert_eq!(c.get(21-1, -7), Module::Light); - assert_eq!(c.get(-1, 21-7), Module::Light); + c.put(0, 0, Color::Dark); + c.put(-1, -7, Color::Light); + assert_eq!(c.get(0, 0), Module::Masked(Color::Dark)); + assert_eq!(c.get(21-1, -7), Module::Masked(Color::Light)); + assert_eq!(c.get(-1, 21-7), Module::Masked(Color::Light)); } #[test] @@ -185,14 +179,14 @@ mod basic_canvas_tests { for i in 3i16 .. 20 { for j in 3i16 .. 20 { - c.put(i, j, match ((i * 3) ^ j) % 5 { + *c.get_mut(i, j) = match ((i * 3) ^ j) % 5 { 0 => Module::Empty, - 1 => Module::Light, - 2 => Module::Dark, - 3 => Module::LightUnmasked, - 4 => Module::DarkUnmasked, - _ => panic!(), - }); + 1 => Module::Masked(Color::Light), + 2 => Module::Masked(Color::Dark), + 3 => Module::Unmasked(Color::Light), + 4 => Module::Unmasked(Color::Dark), + _ => unreachable!(), + }; } } @@ -233,10 +227,10 @@ impl Canvas { for j in range_inclusive(dy_top, dy_bottom) { for i in range_inclusive(dx_left, dx_right) { self.put(x+i, y+j, match (i, j) { - (4, _) | (_, 4) | (-4, _) | (_, -4) => Module::Light, - (3, _) | (_, 3) | (-3, _) | (_, -3) => Module::Dark, - (2, _) | (_, 2) | (-2, _) | (_, -2) => Module::Light, - _ => Module::Dark, + (4, _) | (_, 4) | (-4, _) | (_, -4) => Color::Light, + (3, _) | (_, 3) | (-3, _) | (_, -3) => Color::Dark, + (2, _) | (_, 2) | (-2, _) | (_, -2) => Color::Light, + _ => Color::Dark, }); } } @@ -325,8 +319,8 @@ impl Canvas { for j in range_inclusive(-2, 2) { for i in range_inclusive(-2, 2) { self.put(x+i, y+j, match (i, j) { - (2, _) | (_, 2) | (-2, _) | (_, -2) | (0, 0) => Module::Dark, - _ => Module::Light, + (2, _) | (_, 2) | (-2, _) | (_, -2) | (0, 0) => Color::Dark, + _ => Color::Light, }); } } @@ -534,7 +528,7 @@ impl Canvas { /// drawn using this method. /// fn draw_line(&mut self, x1: i16, y1: i16, x2: i16, y2: i16, - color_even: Module, color_odd: Module) { + color_even: Color, color_odd: Color) { debug_assert!(x1 == x2 || y1 == y2); if y1 == y2 { // Horizontal line. @@ -559,8 +553,8 @@ impl Canvas { Version::Micro(_) => (0, 8, width-1), Version::Normal(_) => (6, 8, width-9), }; - self.draw_line(x1, y, x2, y, Module::Dark, Module::Light); - self.draw_line(y, x1, y, x2, Module::Dark, Module::Light); + self.draw_line(x1, y, x2, y, Color::Dark, Color::Light); + self.draw_line(y, x1, y, x2, Color::Dark, Color::Light); } } @@ -627,31 +621,30 @@ impl Canvas { /// `off_color`. The coordinates will be extracted from the `coords` /// iterator. It will start from the most significant bits first, so /// *trailing* zeros will be ignored. - fn draw_number(&mut self, number: N, - on_color: Module, off_color: Module, - coords: &[(i16, i16)]) { - let zero: N = N::zero(); - let mut mask: N = !(!zero >> 1); + fn draw_number(&mut self, number: u32, bits: u32, + on_color: Color, off_color: Color, coords: &[(i16, i16)]) { + let mut mask = 1 << (bits - 1); for &(x, y) in coords { - let color = if (mask & number) == zero { off_color } else { on_color }; + let color = if (mask & number) == 0 { off_color } else { on_color }; self.put(x, y, color); - mask = mask >> 1; + mask >>= 1; } } /// Draws the format info patterns for an encoded number. fn draw_format_info_patterns_with_number(&mut self, format_info: u16) { + let format_info = format_info as u32; match self.version { Version::Micro(_) => { - self.draw_number(format_info, Module::Dark, Module::Light, + self.draw_number(format_info, 15, Color::Dark, Color::Light, &FORMAT_INFO_COORDS_MICRO_QR); } Version::Normal(_) => { - self.draw_number(format_info, Module::Dark, Module::Light, + self.draw_number(format_info, 15, Color::Dark, Color::Light, &FORMAT_INFO_COORDS_QR_MAIN); - self.draw_number(format_info, Module::Dark, Module::Light, + self.draw_number(format_info, 15, Color::Dark, Color::Light, &FORMAT_INFO_COORDS_QR_SIDE); - self.put(8, -8, Module::Dark); // Dark module. + self.put(8, -8, Color::Dark); // Dark module. } } } @@ -666,10 +659,10 @@ impl Canvas { match self.version { Version::Micro(_) | Version::Normal(1...6) => { return; } Version::Normal(a) => { - let version_info = VERSION_INFOS[(a - 7) as usize] << 14; - self.draw_number(version_info, Module::Dark, Module::Light, + let version_info = VERSION_INFOS[(a - 7) as usize]; + self.draw_number(version_info, 18, Color::Dark, Color::Light, &VERSION_INFO_COORDS_BL); - self.draw_number(version_info, Module::Dark, Module::Light, + self.draw_number(version_info, 18, Color::Dark, Color::Light, &VERSION_INFO_COORDS_TR); } } @@ -678,13 +671,13 @@ impl Canvas { #[cfg(test)] mod draw_version_info_tests { - use canvas::{Canvas, Module}; - use types::{Version, EcLevel}; + use canvas::Canvas; + use types::{Version, EcLevel, Color}; #[test] fn test_draw_number() { let mut c = Canvas::new(Version::Micro(1), EcLevel::L); - c.draw_number(0b10101101u8, Module::Dark, Module::Light, + c.draw_number(0b10101101, 8, Color::Dark, Color::Light, &[(0,0), (0,-1), (-2,-2), (-2,0)]); assert_eq!(&*c.to_debug_str(), "\n\ #????????.?\n\ @@ -1284,14 +1277,14 @@ impl Canvas { 'outside: for j in range_inclusive(bits_end, 7).rev() { let color = if (*b & (1 << j)) != 0 { - Module::DarkUnmasked + Color::Dark } else { - Module::LightUnmasked + Color::Light }; while let Some((x, y)) = coords.next() { let r = self.get_mut(x, y); if *r == Module::Empty { - *r = color; + *r = Module::Unmasked(color); continue 'outside; } } @@ -1476,7 +1469,7 @@ impl Canvas { FORMAT_INFOS_MICRO_QR[simple_format_number] } }; - self.draw_format_info_patterns_with_number(format_number << 1); + self.draw_format_info_patterns_with_number(format_number); } } @@ -1648,35 +1641,28 @@ impl Canvas { /// Every pattern that looks like `#.###.#....` in any orientation will add /// 40 points. fn compute_finder_penalty_score(&self, is_horizontal: bool) -> u16 { - static PATTERN: [Module; 7] = [ - Module::Dark, Module::Light, Module::Dark, Module::Dark, - Module::Dark, Module::Light, Module::Dark, + static PATTERN: [Color; 7] = [ + Color::Dark, Color::Light, Color::Dark, Color::Dark, + Color::Dark, Color::Light, Color::Dark, ]; - // TODO remove this after `equals()` is stable. - fn equals(left: T, right: U) -> bool - where T: Iterator, U: Iterator, T::Item: PartialEq - { - left.zip(right).all(|(p, q)| p == q) - } - let mut total_score = 0; for i in 0 .. self.width { for j in 0 .. self.width-6 { // TODO a ref to a closure should be enough? - let get: Box Module> = if is_horizontal { - Box::new(|k: i16| self.get(k, i)) + let get: Box Color> = if is_horizontal { + Box::new(|k| self.get(k, i).into()) } else { - Box::new(|k: i16| self.get(i, k)) + Box::new(|k| self.get(i, k).into()) }; - if !equals((j .. j+7).map(|k| get(k)), PATTERN.iter().map(|m| *m)) { + if (j .. j+7).map(&*get).ne(PATTERN.iter().cloned()) { continue; } - let check = |k| { 0 <= k && k < self.width && get(k).is_dark() }; - if !(j-4 .. j).any(|k| check(k)) || !(j+7 .. j+11).any(|k| check(k)) { + let check = |k| 0 <= k && k < self.width && get(k) != Color::Light; + if !(j-4 .. j).any(&check) || !(j+7 .. j+11).any(&check) { total_score += 40; } } @@ -1721,7 +1707,6 @@ impl Canvas { /// Compute the total penalty scores. A QR code having higher points is less /// desirable. fn compute_total_penalty_scores(&self) -> u16 { - match self.version { Version::Normal(_) => { let s1a = self.compute_adjacent_penalty_score(true); @@ -1739,8 +1724,8 @@ impl Canvas { #[cfg(test)] mod penalty_tests { - use canvas::{Canvas, MaskPattern, Module}; - use types::{Version, EcLevel}; + use canvas::{Canvas, MaskPattern}; + use types::{Version, EcLevel, Color}; fn create_test_canvas() -> Canvas { let mut c = Canvas::new(Version::Normal(1), EcLevel::Q); @@ -1806,19 +1791,19 @@ mod penalty_tests { #[test] fn test_penalty_score_light_sides() { - static HORIZONTAL_SIDE: [Module; 17] = [ - Module::Dark, Module::Light, Module::Light, Module::Dark, - Module::Dark, Module::Dark, Module::Light, Module::Light, - Module::Dark, Module::Light, Module::Dark, Module::Light, - Module::Light, Module::Dark, Module::Light, Module::Light, - Module::Light, + static HORIZONTAL_SIDE: [Color; 17] = [ + Color::Dark, Color::Light, Color::Light, Color::Dark, + Color::Dark, Color::Dark, Color::Light, Color::Light, + Color::Dark, Color::Light, Color::Dark, Color::Light, + Color::Light, Color::Dark, Color::Light, Color::Light, + Color::Light, ]; - static VERTICAL_SIDE: [Module; 17] = [ - Module::Dark, Module::Dark, Module::Dark, Module::Light, - Module::Light, Module::Dark, Module::Dark, Module::Light, - Module::Dark, Module::Light, Module::Dark, Module::Light, - Module::Dark, Module::Light, Module::Light, Module::Dark, - Module::Light, + static VERTICAL_SIDE: [Color; 17] = [ + Color::Dark, Color::Dark, Color::Dark, Color::Light, + Color::Light, Color::Dark, Color::Dark, Color::Light, + Color::Dark, Color::Light, Color::Dark, Color::Light, + Color::Dark, Color::Light, Color::Light, Color::Dark, + Color::Light, ]; let mut c = Canvas::new(Version::Micro(4), EcLevel::Q); @@ -1876,9 +1861,15 @@ impl Canvas { } /// Convert the modules into a vector of booleans. + #[deprecated(since="0.4.0", note="use `into_colors()` instead")] pub fn to_bools(&self) -> Vec { self.modules.iter().map(|m| m.is_dark()).collect() } + + /// Convert the modules into a vector of colors. + pub fn into_colors(self) -> Vec { + self.modules.into_iter().map(Color::from).collect() + } } //}}} diff --git a/src/lib.rs b/src/lib.rs index 952253c..e2eb75c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,33 +2,36 @@ //! //! 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; -//! # #[cfg(feature="image")] //! extern crate image; //! //! use qrcode::QrCode; -//! # #[cfg(feature="image")] -//! use image::GrayImage; +//! use image::Luma; //! //! fn main() { //! // Encode some data into bits. //! let code = QrCode::new(b"01234567").unwrap(); //! //! // Render the bits into an image. -//! # #[cfg(feature="image")] -//! let image: GrayImage = code.render().to_image(); +//! let image = code.render::>().build(); //! //! // Save the image. -//! # #[cfg(feature="image")] //! 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); //! } //! ``` #![cfg_attr(feature="bench", feature(test))] // Unstable libraries #[cfg(feature="bench")] extern crate test; -extern crate num_traits; #[cfg(feature="image")] extern crate image; use std::ops::Index; @@ -40,14 +43,14 @@ pub mod ec; pub mod canvas; pub mod render; -pub use types::{QrResult, 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. #[derive(Clone)] pub struct QrCode { - content: Vec, + content: Vec, version: Version, ec_level: EcLevel, width: usize, @@ -132,7 +135,7 @@ impl QrCode { canvas.draw_data(&*encoded_data, &*ec_data); let canvas = canvas.apply_best_mask(); Ok(QrCode { - content: canvas.to_bools(), + content: canvas.into_colors(), version: version, ec_level: ec_level, width: version.width() as usize, @@ -172,28 +175,34 @@ impl QrCode { /// Converts the QR code into a human-readable string. This is mainly for /// debugging only. pub fn to_debug_str(&self, on_char: char, off_char: char) -> String { - let width = self.width; - let mut k = 0; - let mut res = String::with_capacity(width * (width + 1)); - for _ in 0 .. width { - res.push('\n'); - for _ in 0 .. width { - res.push(if self.content[k] { on_char } else { off_char }); - k += 1; - } - } - res + self.render() + .quiet_zone(false) + .dark_color(on_char) + .light_color(off_char) + .build() } /// Converts the QR code to a vector of booleans. Each entry represents the /// color of the module, with "true" means dark and "false" means light. + #[deprecated(since="0.4.0", note="use `to_colors()` instead")] pub fn to_vec(&self) -> Vec { + self.content.iter().map(|c| *c != Color::Light).collect() + } + + /// Converts the QR code to a vector of booleans. Each entry represents the + /// color of the module, with "true" means dark and "false" means light. + #[deprecated(since="0.4.0", note="use `into_colors()` instead")] + pub fn into_vec(self) -> Vec { + self.content.into_iter().map(|c| c != Color::Light).collect() + } + + /// Converts the QR code to a vector of colors. + pub fn to_colors(&self) -> Vec { self.content.clone() } - /// Converts the QR code to a vector of booleans. Each entry represents the - /// color of the module, with "true" means dark and "false" means light. - pub fn into_vec(self) -> Vec { + /// Converts the QR code to a vector of colors. + pub fn into_colors(self) -> Vec { self.content } @@ -203,7 +212,8 @@ impl QrCode { /// /// # Examples /// - /// ``` + #[cfg_attr(feature="image", doc=" ```rust")] + #[cfg_attr(not(feature="image"), doc=" ```ignore")] /// # extern crate image; /// # extern crate qrcode; /// # use qrcode::QrCode; @@ -214,26 +224,25 @@ impl QrCode { /// .render() /// .dark_color(Rgb { data: [0, 0, 128] }) /// .light_color(Rgb { data: [224, 224, 224] }) // adjust colors - /// .quiet_zone(false) // disable quiet zone (white border) - /// .min_width(300) // sets minimum image size - /// .to_image(); + /// .quiet_zone(false) // disable quiet zone (white border) + /// .min_dimensions(300, 300) // sets minimum image size + /// .build(); /// /// # } /// ``` /// /// Note: the `image` crate itself also provides method to rotate the image, /// or overlay a logo on top of the QR code. - #[cfg(feature="image")] - pub fn render(&self) -> Renderer

{ + pub fn render(&self) -> Renderer

{ 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 { - type Output = bool; + type Output = Color; - fn index(&self, (x, y): (usize, usize)) -> &bool { + fn index(&self, (x, y): (usize, usize)) -> &Color { let index = y * self.width + x; &self.content[index] } @@ -247,7 +256,7 @@ mod tests { fn test_annex_i_qr() { // This uses the ISO Annex I as test vector. 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\ @@ -274,7 +283,7 @@ mod tests { #[test] fn test_annex_i_micro_qr() { 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\ @@ -293,13 +302,13 @@ mod tests { #[cfg(all(test, feature="image"))] mod image_tests { - use image::{GrayImage, Rgb, load_from_memory}; + use image::{Luma, 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 image = code.render::>().build(); 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()); @@ -308,13 +317,40 @@ mod image_tests { #[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 image = code.render() + .min_dimensions(200, 200) + .dark_color(Rgb { data: [128, 0, 0] }) + .light_color(Rgb { data: [255, 255, 128] }) + .build(); 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()); } } +#[cfg(all(test, feature="svg"))] +mod svg_tests { + use render::svg::Color as SvgColor; + use {QrCode, Version, EcLevel}; + + #[test] + fn test_annex_i_qr_as_svg() { + let code = QrCode::new(b"01234567").unwrap(); + let image = code.render::().build(); + let expected = include_str!("test_annex_i_qr_as_svg.svg"); + assert_eq!(&image, expected); + } + + #[test] + fn test_annex_i_micro_qr_as_svg() { + let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap(); + let image = code.render() + .min_dimensions(200, 200) + .dark_color(SvgColor("#800000")) + .light_color(SvgColor("#ffff80")) + .build(); + let expected = include_str!("test_annex_i_micro_qr_as_svg.svg"); + assert_eq!(&image, expected); + } +} + diff --git a/src/render.rs b/src/render.rs deleted file mode 100644 index 958a03e..0000000 --- a/src/render.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! 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 BlankAndWhitePixel for Rgb { - fn black_color() -> Self { - Rgb { data: [S::zero(); 3] } - } - - fn white_color() -> Self { - Rgb { data: [S::max_value(); 3] } - } -} - -impl BlankAndWhitePixel for Rgba { - 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 BlankAndWhitePixel for Luma { - fn black_color() -> Self { - Luma { data: [S::zero()] } - } - - fn white_color() -> Self { - Luma { data: [S::max_value()] } - } -} - -impl BlankAndWhitePixel for LumaA { - 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> { - 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::>::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::>::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::>::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); - } -} - - diff --git a/src/render/image.rs b/src/render/image.rs new file mode 100644 index 0000000..02b8deb --- /dev/null +++ b/src/render/image.rs @@ -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>; + 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: p => [p] } +impl_pixel_for_image_pixel!{ LumaA: p => [p, S::max_value()] } +impl_pixel_for_image_pixel!{ Rgb: p => [p, p, p] } +impl_pixel_for_image_pixel!{ Rgba: p => [p, p, p, S::max_value()] } + +impl Canvas for (P, ImageBuffer>) { + type Pixel = P; + type Image = ImageBuffer>; + + 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> { + 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::>::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::>::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::>::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::>::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); + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..c09a38c --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,189 @@ +//! Render a QR code into image. + +use std::cmp::max; +use types::Color; + +pub mod image; +pub mod string; +pub mod svg; + +//------------------------------------------------------------------------------ +//{{{ 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; + + /// 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() + } +} + +//}}} \ No newline at end of file diff --git a/src/render/string.rs b/src/render/string.rs new file mode 100644 index 0000000..584684f --- /dev/null +++ b/src/render/string.rs @@ -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 { + buffer: Vec

, + width: usize, + dark_pixel: P, + dark_cap_inc: isize, + capacity: isize, +} + +impl Pixel for P { + type Canvas = Canvas

; + type Image = String; + + fn default_unit_size() -> (u32, u32) { + (1, 1) + } + + fn default_color(color: Color) -> Self { + ::default_color(color) + } +} + +impl RenderCanvas for Canvas

{ + 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::::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"); +} diff --git a/src/render/svg.rs b/src/render/svg.rs new file mode 100644 index 0000000..4779d69 --- /dev/null +++ b/src/render/svg.rs @@ -0,0 +1,69 @@ +//! SVG rendering support. +//! +//! # Example +//! +//! ``` +//! extern crate qrcode; +//! +//! use qrcode::QrCode; +//! use qrcode::render::svg; +//! +//! fn main() { +//! let code = QrCode::new(b"Hello").unwrap(); +//! let svg_xml = code.render::().build(); +//! println!("{}", svg_xml); +//! } + +#![cfg(feature="svg")] + +use std::fmt::Write; +use std::marker::PhantomData; + +use render::{Pixel, Canvas as RenderCanvas}; +use types::Color as ModuleColor; + +/// An SVG color. +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Color<'a>(pub &'a str); + +impl<'a> Pixel for Color<'a> { + type Canvas = Canvas<'a>; + type Image = String; + + fn default_color(color: ModuleColor) -> Self { + Color(color.select("#000", "#fff")) + } +} + +#[doc(hidden)] +pub struct Canvas<'a> { + svg: String, + marker: PhantomData>, +} + +impl<'a> RenderCanvas for Canvas<'a> { + type Pixel = Color<'a>; + type Image = String; + + fn new(width: u32, height: u32, dark_pixel: Color<'a>, light_pixel: Color<'a>) -> Self { + Canvas { + svg: format!( + r#" String { + self.svg + r#""/>"# + } +} diff --git a/src/test_annex_i_micro_qr_as_svg.svg b/src/test_annex_i_micro_qr_as_svg.svg new file mode 100644 index 0000000..7f67ecc --- /dev/null +++ b/src/test_annex_i_micro_qr_as_svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/test_annex_i_qr_as_svg.svg b/src/test_annex_i_qr_as_svg.svg new file mode 100644 index 0000000..72b7ff1 --- /dev/null +++ b/src/test_annex_i_qr_as_svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index fd14b8d..1f447f1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,7 @@ use std::default::Default; use std::cmp::{PartialOrd, Ordering}; use std::fmt::{Display, Formatter, Error}; +use std::ops::Not; //------------------------------------------------------------------------------ //{{{ QrResult @@ -42,6 +43,48 @@ impl Display for QrError { /// `QrResult` is a convenient alias for a QR code generation result. pub type QrResult = Result; +//}}} +//------------------------------------------------------------------------------ +//{{{ Color + +/// The color of a module. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum Color { + /// The module is light colored. + Light, + /// The module is dark colored. + Dark, +} + +impl Color { + /// Selects a value according to color of the module. Equivalent to + /// `if self != Color::Light { dark } else { light }`. + /// + /// # Examples + /// + /// ```rust + /// # use qrcode::types::Color; + /// assert_eq!(Color::Light.select(1, 0), 0); + /// assert_eq!(Color::Dark.select("black", "white"), "black"); + /// ``` + pub fn select(self, dark: T, light: T) -> T { + match self { + Color::Light => light, + Color::Dark => dark, + } + } +} + +impl Not for Color { + type Output = Color; + fn not(self) -> Color { + match self { + Color::Light => Color::Dark, + Color::Dark => Color::Light, + } + } +} + //}}} //------------------------------------------------------------------------------ //{{{ Error correction level