commit
6cbc50bb38
22
.travis.yml
22
.travis.yml
|
@ -1,8 +1,7 @@
|
||||||
# Copied from https://github.com/kennytm/extprim/blob/master/.travis.yml :)
|
|
||||||
|
|
||||||
language: rust
|
language: rust
|
||||||
sudo: false
|
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
@ -10,6 +9,7 @@ addons:
|
||||||
- libelf-dev
|
- libelf-dev
|
||||||
- libdw-dev
|
- libdw-dev
|
||||||
- binutils-dev
|
- binutils-dev
|
||||||
|
- libiberty-dev
|
||||||
- gcc-multilib
|
- gcc-multilib
|
||||||
|
|
||||||
os:
|
os:
|
||||||
|
@ -21,28 +21,22 @@ rust:
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
env:
|
|
||||||
matrix:
|
matrix:
|
||||||
- ARCH=x86_64
|
allow_failures:
|
||||||
- ARCH=i686
|
- rust: beta
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- if [ "$TRAVIS_OS_NAME" = 'linux' ]; then OS=unknown-linux-gnu; else OS=apple-darwin; fi
|
- if [ "$TRAVIS_OS_NAME" = 'linux' ]; then OS=unknown-linux-gnu; else OS=apple-darwin; fi
|
||||||
- export HOST=$ARCH-$OS
|
- rustup target add i686-$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
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo test --no-default-features
|
- cargo test --no-default-features
|
||||||
- cargo test
|
- cargo test
|
||||||
- if [ "$TRAVIS_RUST_VERSION" = 'nightly' ]; then cargo bench --features=bench; fi
|
- if [ "$TRAVIS_RUST_VERSION" = 'nightly' ]; then cargo bench --features=bench; fi
|
||||||
|
- cargo test --target i686-$OS
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- cargo install cargo-kcov
|
- cargo install cargo-kcov
|
||||||
- cargo kcov --print-install-kcov-sh | bash
|
- cargo kcov --print-install-kcov-sh | bash
|
||||||
- cargo kcov --coveralls -- --verify
|
- cargo kcov --coveralls
|
||||||
|
|
||||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -2,7 +2,7 @@
|
||||||
name = "qrcode"
|
name = "qrcode"
|
||||||
description = "QR code encoder in Rust"
|
description = "QR code encoder in Rust"
|
||||||
license = "MIT / Apache-2.0"
|
license = "MIT / Apache-2.0"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
authors = ["kennytm <kennytm@gmail.com>"]
|
authors = ["kennytm <kennytm@gmail.com>"]
|
||||||
keywords = ["qrcode"]
|
keywords = ["qrcode"]
|
||||||
repository = "https://github.com/kennytm/qrcode-rust"
|
repository = "https://github.com/kennytm/qrcode-rust"
|
||||||
|
@ -16,12 +16,23 @@ exclude = [
|
||||||
travis-ci = { repository = "kennytm/qrcode-rust" }
|
travis-ci = { repository = "kennytm/qrcode-rust" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num-traits = "0.1"
|
|
||||||
image = { version = "0.13", optional = true }
|
image = { version = "0.13", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["image"]
|
default = ["image", "svg"]
|
||||||
bench = []
|
bench = []
|
||||||
|
svg = []
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "qrencode"
|
name = "qrencode"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "encode_image"
|
||||||
|
required-features = ["image"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "encode_string"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "encode_svg"
|
||||||
|
required-features = ["svg"]
|
||||||
|
|
74
README.md
74
README.md
|
@ -13,34 +13,34 @@ Cargo.toml
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[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`:
|
The default settings will depend on the `image` crate. If you don't need image generation capability, disable the `default-features`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
qrcode = { version = "0.2.0", default-features = false }
|
qrcode = { version = "0.4", default-features = false }
|
||||||
```
|
```
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------
|
-------
|
||||||
|
|
||||||
This code:
|
## Image generation
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate qrcode;
|
extern crate qrcode;
|
||||||
extern crate image;
|
extern crate image;
|
||||||
|
|
||||||
use qrcode::QrCode;
|
use qrcode::QrCode;
|
||||||
use image::GrayImage;
|
use image::Luma;
|
||||||
|
|
||||||
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.
|
||||||
let image: GrayImage = code.render().to_image();
|
let image = code.render::<Luma<u8>>().build();
|
||||||
|
|
||||||
// Save the image.
|
// Save the image.
|
||||||
image.save("/tmp/qrcode.png").unwrap();
|
image.save("/tmp/qrcode.png").unwrap();
|
||||||
|
@ -51,3 +51,67 @@ Generates this image:
|
||||||
|
|
||||||
![Output](src/test_annex_i_qr_as_image.png)
|
![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::<char>()
|
||||||
|
.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)
|
16
examples/encode_image.rs
Normal file
16
examples/encode_image.rs
Normal file
|
@ -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::<Luma<u8>>().build();
|
||||||
|
|
||||||
|
// Save the image.
|
||||||
|
image.save("/tmp/qrcode.png").unwrap();
|
||||||
|
}
|
11
examples/encode_string.rs
Normal file
11
examples/encode_string.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
extern crate qrcode;
|
||||||
|
use qrcode::QrCode;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let code = QrCode::new(b"Hello").unwrap();
|
||||||
|
let string = code.render::<char>()
|
||||||
|
.quiet_zone(false)
|
||||||
|
.module_dimensions(2, 1)
|
||||||
|
.build();
|
||||||
|
println!("{}", string);
|
||||||
|
}
|
14
examples/encode_svg.rs
Normal file
14
examples/encode_svg.rs
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -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 = if code[(x, y)] { '█' } else { SPACE };
|
|
||||||
print!("{}{}", block, block);
|
|
||||||
}
|
|
||||||
print!("\n{}{}{}{}{}", SPACE, SPACE, SPACE, SPACE, SPACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\n\n\n\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
231
src/canvas.rs
231
src/canvas.rs
|
@ -9,17 +9,14 @@
|
||||||
//! c.apply_mask(MaskPattern::Checkerboard);
|
//! c.apply_mask(MaskPattern::Checkerboard);
|
||||||
//! let bools = c.to_bools();
|
//! let bools = c.to_bools();
|
||||||
|
|
||||||
use std::iter::repeat;
|
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use num_traits::PrimInt;
|
use types::{Version, EcLevel, Color};
|
||||||
|
|
||||||
use types::{Version, EcLevel};
|
|
||||||
|
|
||||||
// TODO remove this after `p ... q` becomes stable. See rust-lang/rust#28237.
|
// TODO remove this after `p ... q` becomes stable. See rust-lang/rust#28237.
|
||||||
fn range_inclusive<N: PrimInt>(from: N, to: N) -> Range<N> {
|
fn range_inclusive(from: i16, to: i16) -> Range<i16> {
|
||||||
from .. (to + N::one())
|
from .. (to + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
@ -31,49 +28,47 @@ pub enum Module {
|
||||||
/// The module is empty.
|
/// The module is empty.
|
||||||
Empty,
|
Empty,
|
||||||
|
|
||||||
/// The module is light (white), and cannot be masked. This mainly refers to
|
/// The module is of functional patterns which cannot be masked, or pixels
|
||||||
/// modules of functional patterns.
|
/// which have been masked.
|
||||||
Light,
|
Masked(Color),
|
||||||
|
|
||||||
/// The module is dark (black), and cannot be masked. This mainly refers to
|
/// The module is of data and error correction bits before masking.
|
||||||
/// modules of functional patterns.
|
Unmasked(Color),
|
||||||
Dark,
|
}
|
||||||
|
|
||||||
/// The module is light (white), but not yet masked. This mainly refers to
|
impl From<Module> for Color {
|
||||||
/// modules of data and error correction bits before masking.
|
fn from(module: Module) -> Color {
|
||||||
LightUnmasked,
|
match module {
|
||||||
|
Module::Empty => Color::Light,
|
||||||
/// The module is dark (black), but not yet masked. This mainly refers to
|
Module::Masked(c) | Module::Unmasked(c) => c,
|
||||||
/// modules of data and error correction bits before masking.
|
}
|
||||||
DarkUnmasked,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module {
|
impl Module {
|
||||||
/// Checks whether a module is dark.
|
/// Checks whether a module is dark.
|
||||||
pub fn is_dark(&self) -> bool {
|
pub fn is_dark(self) -> bool {
|
||||||
match *self {
|
Color::from(self) == Color::Dark
|
||||||
Module::Dark | Module::DarkUnmasked => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a mask to the unmasked modules.
|
/// Apply a mask to the unmasked modules.
|
||||||
///
|
///
|
||||||
/// use qrcode::canvas::Module;
|
/// use qrcode::canvas::Module;
|
||||||
|
/// use qrcode::types::Color;
|
||||||
///
|
///
|
||||||
/// assert_eq!(Module::LightUnmasked.mask(true), Module::Dark);
|
/// assert_eq!(Module::Unmasked(Color::Light).mask(true), Module::Masked(Color::Dark));
|
||||||
/// assert_eq!(Module::DarkUnmasked.mask(true), Module::Light);
|
/// assert_eq!(Module::Unmasked(Color::Dark).mask(true), Module::Masked(Color::Light));
|
||||||
/// assert_eq!(Module::LightUnmasked.mask(false), Module::Light);
|
/// assert_eq!(Module::Unmasked(Color::Light).mask(false), Module::Masked(Color::Light));
|
||||||
/// assert_eq!(Module::Dark.mask(true), Module::Dark);
|
/// assert_eq!(Module::Masked(Color::Dark).mask(true), Module::Masked(Color::Dark));
|
||||||
/// assert_eq!(Module::Dark.mask(false), Module::Dark);
|
/// assert_eq!(Module::Masked(Color::Dark).mask(false), Module::Masked(Color::Dark));
|
||||||
///
|
///
|
||||||
pub fn mask(&self, should_invert: bool) -> Module {
|
pub fn mask(self, should_invert: bool) -> Module {
|
||||||
match (*self, should_invert) {
|
match (self, should_invert) {
|
||||||
(Module::Empty, true) | (Module::LightUnmasked, true) => Module::Dark,
|
(Module::Empty, true) => Module::Masked(Color::Dark),
|
||||||
(Module::Empty, false) | (Module::LightUnmasked, false) => Module::Light,
|
(Module::Empty, false) => Module::Masked(Color::Light),
|
||||||
(Module::DarkUnmasked, true) => Module::Light,
|
(Module::Unmasked(c), true) => Module::Masked(!c),
|
||||||
(Module::DarkUnmasked, false) => Module::Dark,
|
(Module::Unmasked(c), false) |
|
||||||
(a, _) => a,
|
(Module::Masked(c), _) => Module::Masked(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +103,7 @@ impl Canvas {
|
||||||
width: width,
|
width: width,
|
||||||
version: version,
|
version: version,
|
||||||
ec_level: ec_level,
|
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 {
|
for x in 0 .. width {
|
||||||
res.push(match self.get(x, y) {
|
res.push(match self.get(x, y) {
|
||||||
Module::Empty => '?',
|
Module::Empty => '?',
|
||||||
Module::Light => '.',
|
Module::Masked(Color::Light) => '.',
|
||||||
Module::Dark => '#',
|
Module::Masked(Color::Dark) => '#',
|
||||||
Module::LightUnmasked => '-',
|
Module::Unmasked(Color::Light) => '-',
|
||||||
Module::DarkUnmasked => '*',
|
Module::Unmasked(Color::Dark) => '*',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,18 +146,17 @@ impl Canvas {
|
||||||
&mut self.modules[index]
|
&mut self.modules[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the color of a module at the given coordinates. For convenience,
|
/// Sets the color of a functional module at the given coordinates. For
|
||||||
/// negative coordinates will wrap around.
|
/// convenience, negative coordinates will wrap around.
|
||||||
pub fn put(&mut self, x: i16, y: i16, module: Module) {
|
pub fn put(&mut self, x: i16, y: i16, color: Color) {
|
||||||
*self.get_mut(x, y) = module;
|
*self.get_mut(x, y) = Module::Masked(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod basic_canvas_tests {
|
mod basic_canvas_tests {
|
||||||
use canvas::{Canvas, Module};
|
use canvas::{Canvas, Module};
|
||||||
use types::{Version, EcLevel};
|
use types::{Version, EcLevel, Color};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_index() {
|
fn test_index() {
|
||||||
|
@ -172,11 +166,11 @@ mod basic_canvas_tests {
|
||||||
assert_eq!(c.get(-1, -7), Module::Empty);
|
assert_eq!(c.get(-1, -7), Module::Empty);
|
||||||
assert_eq!(c.get(21-1, 21-7), Module::Empty);
|
assert_eq!(c.get(21-1, 21-7), Module::Empty);
|
||||||
|
|
||||||
c.put(0, 0, Module::Dark);
|
c.put(0, 0, Color::Dark);
|
||||||
c.put(-1, -7, Module::Light);
|
c.put(-1, -7, Color::Light);
|
||||||
assert_eq!(c.get(0, 0), Module::Dark);
|
assert_eq!(c.get(0, 0), Module::Masked(Color::Dark));
|
||||||
assert_eq!(c.get(21-1, -7), Module::Light);
|
assert_eq!(c.get(21-1, -7), Module::Masked(Color::Light));
|
||||||
assert_eq!(c.get(-1, 21-7), Module::Light);
|
assert_eq!(c.get(-1, 21-7), Module::Masked(Color::Light));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -185,14 +179,14 @@ mod basic_canvas_tests {
|
||||||
|
|
||||||
for i in 3i16 .. 20 {
|
for i in 3i16 .. 20 {
|
||||||
for j 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,
|
0 => Module::Empty,
|
||||||
1 => Module::Light,
|
1 => Module::Masked(Color::Light),
|
||||||
2 => Module::Dark,
|
2 => Module::Masked(Color::Dark),
|
||||||
3 => Module::LightUnmasked,
|
3 => Module::Unmasked(Color::Light),
|
||||||
4 => Module::DarkUnmasked,
|
4 => Module::Unmasked(Color::Dark),
|
||||||
_ => panic!(),
|
_ => unreachable!(),
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,10 +227,10 @@ impl Canvas {
|
||||||
for j in range_inclusive(dy_top, dy_bottom) {
|
for j in range_inclusive(dy_top, dy_bottom) {
|
||||||
for i in range_inclusive(dx_left, dx_right) {
|
for i in range_inclusive(dx_left, dx_right) {
|
||||||
self.put(x+i, y+j, match (i, j) {
|
self.put(x+i, y+j, match (i, j) {
|
||||||
(4, _) | (_, 4) | (-4, _) | (_, -4) => Module::Light,
|
(4, _) | (_, 4) | (-4, _) | (_, -4) => Color::Light,
|
||||||
(3, _) | (_, 3) | (-3, _) | (_, -3) => Module::Dark,
|
(3, _) | (_, 3) | (-3, _) | (_, -3) => Color::Dark,
|
||||||
(2, _) | (_, 2) | (-2, _) | (_, -2) => Module::Light,
|
(2, _) | (_, 2) | (-2, _) | (_, -2) => Color::Light,
|
||||||
_ => Module::Dark,
|
_ => Color::Dark,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,8 +319,8 @@ impl Canvas {
|
||||||
for j in range_inclusive(-2, 2) {
|
for j in range_inclusive(-2, 2) {
|
||||||
for i in range_inclusive(-2, 2) {
|
for i in range_inclusive(-2, 2) {
|
||||||
self.put(x+i, y+j, match (i, j) {
|
self.put(x+i, y+j, match (i, j) {
|
||||||
(2, _) | (_, 2) | (-2, _) | (_, -2) | (0, 0) => Module::Dark,
|
(2, _) | (_, 2) | (-2, _) | (_, -2) | (0, 0) => Color::Dark,
|
||||||
_ => Module::Light,
|
_ => Color::Light,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -534,7 +528,7 @@ impl Canvas {
|
||||||
/// drawn using this method.
|
/// drawn using this method.
|
||||||
///
|
///
|
||||||
fn draw_line(&mut self, x1: i16, y1: i16, x2: i16, y2: i16,
|
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);
|
debug_assert!(x1 == x2 || y1 == y2);
|
||||||
|
|
||||||
if y1 == y2 { // Horizontal line.
|
if y1 == y2 { // Horizontal line.
|
||||||
|
@ -559,8 +553,8 @@ impl Canvas {
|
||||||
Version::Micro(_) => (0, 8, width-1),
|
Version::Micro(_) => (0, 8, width-1),
|
||||||
Version::Normal(_) => (6, 8, width-9),
|
Version::Normal(_) => (6, 8, width-9),
|
||||||
};
|
};
|
||||||
self.draw_line(x1, y, x2, y, Module::Dark, Module::Light);
|
self.draw_line(x1, y, x2, y, Color::Dark, Color::Light);
|
||||||
self.draw_line(y, x1, y, x2, Module::Dark, Module::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`
|
/// `off_color`. The coordinates will be extracted from the `coords`
|
||||||
/// iterator. It will start from the most significant bits first, so
|
/// iterator. It will start from the most significant bits first, so
|
||||||
/// *trailing* zeros will be ignored.
|
/// *trailing* zeros will be ignored.
|
||||||
fn draw_number<N: PrimInt>(&mut self, number: N,
|
fn draw_number(&mut self, number: u32, bits: u32,
|
||||||
on_color: Module, off_color: Module,
|
on_color: Color, off_color: Color, coords: &[(i16, i16)]) {
|
||||||
coords: &[(i16, i16)]) {
|
let mut mask = 1 << (bits - 1);
|
||||||
let zero: N = N::zero();
|
|
||||||
let mut mask: N = !(!zero >> 1);
|
|
||||||
for &(x, y) in coords {
|
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);
|
self.put(x, y, color);
|
||||||
mask = mask >> 1;
|
mask >>= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws the format info patterns for an encoded number.
|
/// Draws the format info patterns for an encoded number.
|
||||||
fn draw_format_info_patterns_with_number(&mut self, format_info: u16) {
|
fn draw_format_info_patterns_with_number(&mut self, format_info: u16) {
|
||||||
|
let format_info = format_info as u32;
|
||||||
match self.version {
|
match self.version {
|
||||||
Version::Micro(_) => {
|
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);
|
&FORMAT_INFO_COORDS_MICRO_QR);
|
||||||
}
|
}
|
||||||
Version::Normal(_) => {
|
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);
|
&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);
|
&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 {
|
match self.version {
|
||||||
Version::Micro(_) | Version::Normal(1...6) => { return; }
|
Version::Micro(_) | Version::Normal(1...6) => { return; }
|
||||||
Version::Normal(a) => {
|
Version::Normal(a) => {
|
||||||
let version_info = VERSION_INFOS[(a - 7) as usize] << 14;
|
let version_info = VERSION_INFOS[(a - 7) as usize];
|
||||||
self.draw_number(version_info, Module::Dark, Module::Light,
|
self.draw_number(version_info, 18, Color::Dark, Color::Light,
|
||||||
&VERSION_INFO_COORDS_BL);
|
&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);
|
&VERSION_INFO_COORDS_TR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -678,13 +671,13 @@ impl Canvas {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod draw_version_info_tests {
|
mod draw_version_info_tests {
|
||||||
use canvas::{Canvas, Module};
|
use canvas::Canvas;
|
||||||
use types::{Version, EcLevel};
|
use types::{Version, EcLevel, Color};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_draw_number() {
|
fn test_draw_number() {
|
||||||
let mut c = Canvas::new(Version::Micro(1), EcLevel::L);
|
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)]);
|
&[(0,0), (0,-1), (-2,-2), (-2,0)]);
|
||||||
assert_eq!(&*c.to_debug_str(), "\n\
|
assert_eq!(&*c.to_debug_str(), "\n\
|
||||||
#????????.?\n\
|
#????????.?\n\
|
||||||
|
@ -1284,14 +1277,14 @@ impl Canvas {
|
||||||
'outside:
|
'outside:
|
||||||
for j in range_inclusive(bits_end, 7).rev() {
|
for j in range_inclusive(bits_end, 7).rev() {
|
||||||
let color = if (*b & (1 << j)) != 0 {
|
let color = if (*b & (1 << j)) != 0 {
|
||||||
Module::DarkUnmasked
|
Color::Dark
|
||||||
} else {
|
} else {
|
||||||
Module::LightUnmasked
|
Color::Light
|
||||||
};
|
};
|
||||||
while let Some((x, y)) = coords.next() {
|
while let Some((x, y)) = coords.next() {
|
||||||
let r = self.get_mut(x, y);
|
let r = self.get_mut(x, y);
|
||||||
if *r == Module::Empty {
|
if *r == Module::Empty {
|
||||||
*r = color;
|
*r = Module::Unmasked(color);
|
||||||
continue 'outside;
|
continue 'outside;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1476,7 +1469,7 @@ impl Canvas {
|
||||||
FORMAT_INFOS_MICRO_QR[simple_format_number]
|
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
|
/// Every pattern that looks like `#.###.#....` in any orientation will add
|
||||||
/// 40 points.
|
/// 40 points.
|
||||||
fn compute_finder_penalty_score(&self, is_horizontal: bool) -> u16 {
|
fn compute_finder_penalty_score(&self, is_horizontal: bool) -> u16 {
|
||||||
static PATTERN: [Module; 7] = [
|
static PATTERN: [Color; 7] = [
|
||||||
Module::Dark, Module::Light, Module::Dark, Module::Dark,
|
Color::Dark, Color::Light, Color::Dark, Color::Dark,
|
||||||
Module::Dark, Module::Light, Module::Dark,
|
Color::Dark, Color::Light, Color::Dark,
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO remove this after `equals()` is stable.
|
|
||||||
fn equals<T, U>(left: T, right: U) -> bool
|
|
||||||
where T: Iterator, U: Iterator, T::Item: PartialEq<U::Item>
|
|
||||||
{
|
|
||||||
left.zip(right).all(|(p, q)| p == q)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut total_score = 0;
|
let mut total_score = 0;
|
||||||
|
|
||||||
for i in 0 .. self.width {
|
for i in 0 .. self.width {
|
||||||
for j in 0 .. self.width-6 {
|
for j in 0 .. self.width-6 {
|
||||||
// TODO a ref to a closure should be enough?
|
// TODO a ref to a closure should be enough?
|
||||||
let get: Box<Fn(i16) -> Module> = if is_horizontal {
|
let get: Box<Fn(i16) -> Color> = if is_horizontal {
|
||||||
Box::new(|k: i16| self.get(k, i))
|
Box::new(|k| self.get(k, i).into())
|
||||||
} else {
|
} 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let check = |k| { 0 <= k && k < self.width && get(k).is_dark() };
|
let check = |k| 0 <= k && k < self.width && get(k) != Color::Light;
|
||||||
if !(j-4 .. j).any(|k| check(k)) || !(j+7 .. j+11).any(|k| check(k)) {
|
if !(j-4 .. j).any(&check) || !(j+7 .. j+11).any(&check) {
|
||||||
total_score += 40;
|
total_score += 40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1721,7 +1707,6 @@ impl Canvas {
|
||||||
/// Compute the total penalty scores. A QR code having higher points is less
|
/// Compute the total penalty scores. A QR code having higher points is less
|
||||||
/// desirable.
|
/// desirable.
|
||||||
fn compute_total_penalty_scores(&self) -> u16 {
|
fn compute_total_penalty_scores(&self) -> u16 {
|
||||||
|
|
||||||
match self.version {
|
match self.version {
|
||||||
Version::Normal(_) => {
|
Version::Normal(_) => {
|
||||||
let s1a = self.compute_adjacent_penalty_score(true);
|
let s1a = self.compute_adjacent_penalty_score(true);
|
||||||
|
@ -1739,8 +1724,8 @@ impl Canvas {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod penalty_tests {
|
mod penalty_tests {
|
||||||
use canvas::{Canvas, MaskPattern, Module};
|
use canvas::{Canvas, MaskPattern};
|
||||||
use types::{Version, EcLevel};
|
use types::{Version, EcLevel, Color};
|
||||||
|
|
||||||
fn create_test_canvas() -> Canvas {
|
fn create_test_canvas() -> Canvas {
|
||||||
let mut c = Canvas::new(Version::Normal(1), EcLevel::Q);
|
let mut c = Canvas::new(Version::Normal(1), EcLevel::Q);
|
||||||
|
@ -1806,19 +1791,19 @@ mod penalty_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_penalty_score_light_sides() {
|
fn test_penalty_score_light_sides() {
|
||||||
static HORIZONTAL_SIDE: [Module; 17] = [
|
static HORIZONTAL_SIDE: [Color; 17] = [
|
||||||
Module::Dark, Module::Light, Module::Light, Module::Dark,
|
Color::Dark, Color::Light, Color::Light, Color::Dark,
|
||||||
Module::Dark, Module::Dark, Module::Light, Module::Light,
|
Color::Dark, Color::Dark, Color::Light, Color::Light,
|
||||||
Module::Dark, Module::Light, Module::Dark, Module::Light,
|
Color::Dark, Color::Light, Color::Dark, Color::Light,
|
||||||
Module::Light, Module::Dark, Module::Light, Module::Light,
|
Color::Light, Color::Dark, Color::Light, Color::Light,
|
||||||
Module::Light,
|
Color::Light,
|
||||||
];
|
];
|
||||||
static VERTICAL_SIDE: [Module; 17] = [
|
static VERTICAL_SIDE: [Color; 17] = [
|
||||||
Module::Dark, Module::Dark, Module::Dark, Module::Light,
|
Color::Dark, Color::Dark, Color::Dark, Color::Light,
|
||||||
Module::Light, Module::Dark, Module::Dark, Module::Light,
|
Color::Light, Color::Dark, Color::Dark, Color::Light,
|
||||||
Module::Dark, Module::Light, Module::Dark, Module::Light,
|
Color::Dark, Color::Light, Color::Dark, Color::Light,
|
||||||
Module::Dark, Module::Light, Module::Light, Module::Dark,
|
Color::Dark, Color::Light, Color::Light, Color::Dark,
|
||||||
Module::Light,
|
Color::Light,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut c = Canvas::new(Version::Micro(4), EcLevel::Q);
|
let mut c = Canvas::new(Version::Micro(4), EcLevel::Q);
|
||||||
|
@ -1876,9 +1861,15 @@ impl Canvas {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the modules into a vector of booleans.
|
/// Convert the modules into a vector of booleans.
|
||||||
|
#[deprecated(since="0.4.0", note="use `into_colors()` instead")]
|
||||||
pub fn to_bools(&self) -> Vec<bool> {
|
pub fn to_bools(&self) -> Vec<bool> {
|
||||||
self.modules.iter().map(|m| m.is_dark()).collect()
|
self.modules.iter().map(|m| m.is_dark()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the modules into a vector of colors.
|
||||||
|
pub fn into_colors(self) -> Vec<Color> {
|
||||||
|
self.modules.into_iter().map(Color::from).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//}}}
|
//}}}
|
||||||
|
|
114
src/lib.rs
114
src/lib.rs
|
@ -2,33 +2,36 @@
|
||||||
//!
|
//!
|
||||||
//! 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);
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
#![cfg_attr(feature="bench", feature(test))] // Unstable libraries
|
#![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;
|
#[cfg(feature="image")] extern crate image;
|
||||||
|
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
|
@ -40,14 +43,14 @@ pub mod ec;
|
||||||
pub mod canvas;
|
pub mod canvas;
|
||||||
pub mod render;
|
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.
|
/// The encoded QR code symbol.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct QrCode {
|
pub struct QrCode {
|
||||||
content: Vec<bool>,
|
content: Vec<Color>,
|
||||||
version: Version,
|
version: Version,
|
||||||
ec_level: EcLevel,
|
ec_level: EcLevel,
|
||||||
width: usize,
|
width: usize,
|
||||||
|
@ -132,7 +135,7 @@ impl QrCode {
|
||||||
canvas.draw_data(&*encoded_data, &*ec_data);
|
canvas.draw_data(&*encoded_data, &*ec_data);
|
||||||
let canvas = canvas.apply_best_mask();
|
let canvas = canvas.apply_best_mask();
|
||||||
Ok(QrCode {
|
Ok(QrCode {
|
||||||
content: canvas.to_bools(),
|
content: canvas.into_colors(),
|
||||||
version: version,
|
version: version,
|
||||||
ec_level: ec_level,
|
ec_level: ec_level,
|
||||||
width: version.width() as usize,
|
width: version.width() as usize,
|
||||||
|
@ -172,28 +175,34 @@ 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(if self.content[k] { on_char } else { 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
|
||||||
/// color of the module, with "true" means dark and "false" means light.
|
/// 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<bool> {
|
pub fn to_vec(&self) -> Vec<bool> {
|
||||||
|
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<bool> {
|
||||||
|
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<Color> {
|
||||||
self.content.clone()
|
self.content.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the QR code to a vector of booleans. Each entry represents the
|
/// Converts the QR code to a vector of colors.
|
||||||
/// color of the module, with "true" means dark and "false" means light.
|
pub fn into_colors(self) -> Vec<Color> {
|
||||||
pub fn into_vec(self) -> Vec<bool> {
|
|
||||||
self.content
|
self.content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,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;
|
||||||
|
@ -215,25 +225,24 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<(usize, usize)> for QrCode {
|
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;
|
let index = y * self.width + x;
|
||||||
&self.content[index]
|
&self.content[index]
|
||||||
}
|
}
|
||||||
|
@ -247,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\
|
||||||
|
@ -274,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\
|
||||||
|
@ -293,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());
|
||||||
|
@ -308,13 +317,40 @@ 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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::<SvgColor>().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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
222
src/render.rs
222
src/render.rs
|
@ -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<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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
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);
|
||||||
|
}
|
||||||
|
}
|
189
src/render/mod.rs
Normal file
189
src/render/mod.rs
Normal file
|
@ -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<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");
|
||||||
|
}
|
69
src/render/svg.rs
Normal file
69
src/render/svg.rs
Normal file
|
@ -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::<svg::Color>().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<Color<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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#"<?xml version="1.0" standalone="yes"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{w}" height="{h}" shape-rendering="crispEdges"><rect x="0" y="0" width="{w}" height="{h}" fill="{bg}"/><path fill="{fg}" d=""#,
|
||||||
|
w=width, h=height, fg=dark_pixel.0, bg=light_pixel.0
|
||||||
|
),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_dark_pixel(&mut self, x: u32, y: u32) {
|
||||||
|
self.draw_dark_rect(x, y, 1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
|
||||||
|
write!(self.svg, "M{l} {t}h{w}v{h}H{l}V{t}", l=left, t=top, w=width, h=height).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_image(self) -> String {
|
||||||
|
self.svg + r#""/></svg>"#
|
||||||
|
}
|
||||||
|
}
|
1
src/test_annex_i_micro_qr_as_svg.svg
Normal file
1
src/test_annex_i_micro_qr_as_svg.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="yes"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="204" height="204" shape-rendering="crispEdges"><rect x="0" y="0" width="204" height="204" fill="#ffff80"/><path fill="#800000" d="M24 24h12v12H24V24M36 24h12v12H36V24M48 24h12v12H48V24M60 24h12v12H60V24M72 24h12v12H72V24M84 24h12v12H84V24M96 24h12v12H96V24M120 24h12v12H120V24M144 24h12v12H144V24M168 24h12v12H168V24M24 36h12v12H24V36M96 36h12v12H96V36M120 36h12v12H120V36M132 36h12v12H132V36M144 36h12v12H144V36M168 36h12v12H168V36M24 48h12v12H24V48M48 48h12v12H48V48M60 48h12v12H60V48M72 48h12v12H72V48M96 48h12v12H96V48M132 48h12v12H132V48M144 48h12v12H144V48M168 48h12v12H168V48M24 60h12v12H24V60M48 60h12v12H48V60M60 60h12v12H60V60M72 60h12v12H72V60M96 60h12v12H96V60M132 60h12v12H132V60M144 60h12v12H144V60M156 60h12v12H156V60M168 60h12v12H168V60M24 72h12v12H24V72M48 72h12v12H48V72M60 72h12v12H60V72M72 72h12v12H72V72M96 72h12v12H96V72M120 72h12v12H120V72M132 72h12v12H132V72M144 72h12v12H144V72M24 84h12v12H24V84M96 84h12v12H96V84M120 84h12v12H120V84M168 84h12v12H168V84M24 96h12v12H24V96M36 96h12v12H36V96M48 96h12v12H48V96M60 96h12v12H60V96M72 96h12v12H72V96M84 96h12v12H84V96M96 96h12v12H96V96M132 96h12v12H132V96M144 96h12v12H144V96M156 96h12v12H156V96M168 96h12v12H168V96M132 108h12v12H132V108M144 108h12v12H144V108M24 120h12v12H24V120M36 120h12v12H36V120M60 120h12v12H60V120M120 120h12v12H120V120M168 120h12v12H168V120M36 132h12v12H36V132M48 132h12v12H48V132M72 132h12v12H72V132M96 132h12v12H96V132M120 132h12v12H120V132M144 132h12v12H144V132M168 132h12v12H168V132M24 144h12v12H24V144M36 144h12v12H36V144M48 144h12v12H48V144M84 144h12v12H84V144M96 144h12v12H96V144M108 144h12v12H108V144M120 144h12v12H120V144M132 144h12v12H132V144M144 144h12v12H144V144M156 144h12v12H156V144M60 156h12v12H60V156M84 156h12v12H84V156M144 156h12v12H144V156M156 156h12v12H156V156M24 168h12v12H24V168M36 168h12v12H36V168M48 168h12v12H48V168M72 168h12v12H72V168M108 168h12v12H108V168M120 168h12v12H120V168M144 168h12v12H144V168M156 168h12v12H156V168M168 168h12v12H168V168"/></svg>
|
After Width: | Height: | Size: 2 KiB |
1
src/test_annex_i_qr_as_svg.svg
Normal file
1
src/test_annex_i_qr_as_svg.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="yes"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="232" height="232" shape-rendering="crispEdges"><rect x="0" y="0" width="232" height="232" fill="#fff"/><path fill="#000" d="M32 32h8v8H32V32M40 32h8v8H40V32M48 32h8v8H48V32M56 32h8v8H56V32M64 32h8v8H64V32M72 32h8v8H72V32M80 32h8v8H80V32M104 32h8v8H104V32M120 32h8v8H120V32M128 32h8v8H128V32M144 32h8v8H144V32M152 32h8v8H152V32M160 32h8v8H160V32M168 32h8v8H168V32M176 32h8v8H176V32M184 32h8v8H184V32M192 32h8v8H192V32M32 40h8v8H32V40M80 40h8v8H80V40M104 40h8v8H104V40M112 40h8v8H112V40M120 40h8v8H120V40M128 40h8v8H128V40M144 40h8v8H144V40M192 40h8v8H192V40M32 48h8v8H32V48M48 48h8v8H48V48M56 48h8v8H56V48M64 48h8v8H64V48M80 48h8v8H80V48M96 48h8v8H96V48M144 48h8v8H144V48M160 48h8v8H160V48M168 48h8v8H168V48M176 48h8v8H176V48M192 48h8v8H192V48M32 56h8v8H32V56M48 56h8v8H48V56M56 56h8v8H56V56M64 56h8v8H64V56M80 56h8v8H80V56M96 56h8v8H96V56M104 56h8v8H104V56M144 56h8v8H144V56M160 56h8v8H160V56M168 56h8v8H168V56M176 56h8v8H176V56M192 56h8v8H192V56M32 64h8v8H32V64M48 64h8v8H48V64M56 64h8v8H56V64M64 64h8v8H64V64M80 64h8v8H80V64M96 64h8v8H96V64M112 64h8v8H112V64M120 64h8v8H120V64M128 64h8v8H128V64M144 64h8v8H144V64M160 64h8v8H160V64M168 64h8v8H168V64M176 64h8v8H176V64M192 64h8v8H192V64M32 72h8v8H32V72M80 72h8v8H80V72M96 72h8v8H96V72M128 72h8v8H128V72M144 72h8v8H144V72M192 72h8v8H192V72M32 80h8v8H32V80M40 80h8v8H40V80M48 80h8v8H48V80M56 80h8v8H56V80M64 80h8v8H64V80M72 80h8v8H72V80M80 80h8v8H80V80M96 80h8v8H96V80M112 80h8v8H112V80M128 80h8v8H128V80M144 80h8v8H144V80M152 80h8v8H152V80M160 80h8v8H160V80M168 80h8v8H168V80M176 80h8v8H176V80M184 80h8v8H184V80M192 80h8v8H192V80M96 88h8v8H96V88M120 88h8v8H120V88M128 88h8v8H128V88M32 96h8v8H32V96M48 96h8v8H48V96M56 96h8v8H56V96M64 96h8v8H64V96M72 96h8v8H72V96M80 96h8v8H80V96M104 96h8v8H104V96M128 96h8v8H128V96M144 96h8v8H144V96M152 96h8v8H152V96M160 96h8v8H160V96M168 96h8v8H168V96M176 96h8v8H176V96M56 104h8v8H56V104M72 104h8v8H72V104M88 104h8v8H88V104M96 104h8v8H96V104M112 104h8v8H112V104M128 104h8v8H128V104M152 104h8v8H152V104M168 104h8v8H168V104M176 104h8v8H176V104M48 112h8v8H48V112M80 112h8v8H80V112M88 112h8v8H88V112M104 112h8v8H104V112M120 112h8v8H120V112M136 112h8v8H136V112M160 112h8v8H160V112M168 112h8v8H168V112M176 112h8v8H176V112M184 112h8v8H184V112M192 112h8v8H192V112M64 120h8v8H64V120M104 120h8v8H104V120M152 120h8v8H152V120M160 120h8v8H160V120M168 120h8v8H168V120M176 120h8v8H176V120M56 128h8v8H56V128M64 128h8v8H64V128M72 128h8v8H72V128M80 128h8v8H80V128M88 128h8v8H88V128M96 128h8v8H96V128M120 128h8v8H120V128M136 128h8v8H136V128M160 128h8v8H160V128M96 136h8v8H96V136M112 136h8v8H112V136M120 136h8v8H120V136M128 136h8v8H128V136M136 136h8v8H136V136M144 136h8v8H144V136M168 136h8v8H168V136M176 136h8v8H176V136M32 144h8v8H32V144M40 144h8v8H40V144M48 144h8v8H48V144M56 144h8v8H56V144M64 144h8v8H64V144M72 144h8v8H72V144M80 144h8v8H80V144M104 144h8v8H104V144M112 144h8v8H112V144M128 144h8v8H128V144M144 144h8v8H144V144M152 144h8v8H152V144M32 152h8v8H32V152M80 152h8v8H80V152M96 152h8v8H96V152M112 152h8v8H112V152M120 152h8v8H120V152M128 152h8v8H128V152M136 152h8v8H136V152M144 152h8v8H144V152M176 152h8v8H176V152M192 152h8v8H192V152M32 160h8v8H32V160M48 160h8v8H48V160M56 160h8v8H56V160M64 160h8v8H64V160M80 160h8v8H80V160M96 160h8v8H96V160M128 160h8v8H128V160M152 160h8v8H152V160M168 160h8v8H168V160M176 160h8v8H176V160M32 168h8v8H32V168M48 168h8v8H48V168M56 168h8v8H56V168M64 168h8v8H64V168M80 168h8v8H80V168M96 168h8v8H96V168M104 168h8v8H104V168M128 168h8v8H128V168M152 168h8v8H152V168M32 176h8v8H32V176M48 176h8v8H48V176M56 176h8v8H56V176M64 176h8v8H64V176M80 176h8v8H80V176M96 176h8v8H96V176M112 176h8v8H112V176M120 176h8v8H120V176M136 176h8v8H136V176M160 176h8v8H160V176M176 176h8v8H176V176M32 184h8v8H32V184M80 184h8v8H80V184M152 184h8v8H152V184M160 184h8v8H160V184M176 184h8v8H176V184M184 184h8v8H184V184M32 192h8v8H32V192M40 192h8v8H40V192M48 192h8v8H48V192M56 192h8v8H56V192M64 192h8v8H64V192M72 192h8v8H72V192M80 192h8v8H80V192M96 192h8v8H96V192M104 192h8v8H104V192M112 192h8v8H112V192M120 192h8v8H120V192M136 192h8v8H136V192M160 192h8v8H160V192M176 192h8v8H176V192"/></svg>
|
After Width: | Height: | Size: 4.1 KiB |
43
src/types.rs
43
src/types.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::cmp::{PartialOrd, Ordering};
|
use std::cmp::{PartialOrd, Ordering};
|
||||||
use std::fmt::{Display, Formatter, Error};
|
use std::fmt::{Display, Formatter, Error};
|
||||||
|
use std::ops::Not;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
//{{{ QrResult
|
//{{{ QrResult
|
||||||
|
@ -42,6 +43,48 @@ impl Display for QrError {
|
||||||
/// `QrResult` is a convenient alias for a QR code generation result.
|
/// `QrResult` is a convenient alias for a QR code generation result.
|
||||||
pub type QrResult<T> = Result<T, QrError>;
|
pub type QrResult<T> = Result<T, QrError>;
|
||||||
|
|
||||||
|
//}}}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//{{{ 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<T>(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
|
//{{{ Error correction level
|
||||||
|
|
Loading…
Reference in a new issue