diff --git a/.travis.yml b/.travis.yml index bc7070a..4f7048d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,9 @@ matrix: - env: ARCH=i686 rust: 1.8.0 # Note: on 1.8.0, all i686 build panics with the message "Box", cause all `should_panic` tests to fail. + - env: ARCH=i686 + rust: nightly + # Note: nightly-i686 keeps failing, it seems that travis's nightly is outdated. install: - if [ "$TRAVIS_OS_NAME" = 'linux' ]; then OS=unknown-linux-gnu; else OS=apple-darwin; fi diff --git a/Cargo.toml b/Cargo.toml index a8b40ec..77ff301 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,23 @@ [package] name = "qrcode" description = "QR code encoder in Rust" -license = "Apache-2.0" -version = "0.1.8" +license = "MIT / Apache-2.0" +version = "0.2.0" authors = ["kennytm "] keywords = ["qrcode"] repository = "https://github.com/kennytm/qrcode-rust" readme = "README.md" -documentation = "http://kennytm.github.io/qrcode-rust/qrcode-rust/qrcode/" +documentation = "http://kennytm.github.io/qrcode-rust/" exclude = [ - "scripts/*", ".travis.yml", ".gitignore" + ".travis.yml", ".gitignore" ] [dependencies] -num = "0.1" +num-traits = "0.1.32" +image = { version = "0.10.0", optional = true } [features] +default = ["image"] bench = [] [[bin]] diff --git a/LICENSE b/LICENSE-APACHE.txt similarity index 100% rename from LICENSE rename to LICENSE-APACHE.txt diff --git a/LICENSE-MIT.txt b/LICENSE-MIT.txt new file mode 100644 index 0000000..fa50b0a --- /dev/null +++ b/LICENSE-MIT.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016 kennytm + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/README.md b/README.md index a822cc2..3aa64bd 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,50 @@ qrcode-rust [![Build status](https://travis-ci.org/kennytm/qrcode-rust.svg?branch=master)](https://travis-ci.org/kennytm/qrcode-rust) [![Coverage Status](https://coveralls.io/repos/github/kennytm/qrcode-rust/badge.svg?branch=coveralls)](https://coveralls.io/github/kennytm/qrcode-rust?branch=coveralls) [![crates.io](http://meritbadge.herokuapp.com/qrcode)](https://crates.io/crates/qrcode) -[![Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](./LICENSE) +[![MIT / Apache 2.0](https://img.shields.io/badge/license-MIT%20%2f%20Apache%202.0-blue.svg)](./LICENSE-APACHE.txt) -QR code and Micro QR code encoder in Rust. +QR code and Micro QR code encoder in Rust. [Documentation](https://kennytm.github.io/qrcode-rust). + +Cargo.toml +---------- ```toml -# Cargo.toml [dependencies] -qrcode = "0.1.7" +qrcode = "0.2.0" ``` +The default settings will depend on the `image` crate. If you don't need image generation capability, disable the `default-features`: + +```toml +[dependencies] +qrcode = { version = "0.2.0", default-features = false } +``` + +Example +------- + +This code: + +```rust +extern crate qrcode; +extern crate image; + +use qrcode::QrCode; +use image::GrayImage; + +fn main() { + // Encode some data into bits. + let code = QrCode::new(b"01234567").unwrap(); + + // Render the bits into an image. + let image: GrayImage = code.render().to_image(); + + // Save the image. + image.save("/tmp/qrcode.png").unwrap(); +} +``` + +Generates this image: + +![Output](src/test_annex_i_qr_as_image.png) diff --git a/src/bits.rs b/src/bits.rs index cc33fb8..ff20504 100644 --- a/src/bits.rs +++ b/src/bits.rs @@ -894,17 +894,4 @@ mod encode_auto_tests { //}}} //------------------------------------------------------------------------------ -// Copyright 2014 Kenny Chan -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. diff --git a/src/canvas.rs b/src/canvas.rs index 4f16934..beacdf1 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -11,13 +11,13 @@ use std::iter::repeat; use std::cmp::max; -use num::traits::PrimInt; use std::ops::Range; +use num_traits::PrimInt; + use types::{Version, EcLevel}; -// TODO remove this after it is decided whether we want `p ... q` or -// `(p .. q).inclusive()` +// 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()) } @@ -1884,17 +1884,4 @@ impl Canvas { //}}} //------------------------------------------------------------------------------ -// Copyright 2014 Kenny Chan -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. diff --git a/src/ec.rs b/src/ec.rs index bc91d84..10e10b9 100644 --- a/src/ec.rs +++ b/src/ec.rs @@ -444,17 +444,4 @@ static DATA_BYTES_PER_BLOCK: [[(usize, usize, usize, usize); 4]; 44] = [ //}}} -// Copyright 2014-2016 kennytm -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. diff --git a/src/lib.rs b/src/lib.rs index 843dd67..c79763c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,37 +2,46 @@ //! //! This crate provides a QR code and Micro QR code encoder for binary data. //! -//! use qrcode::QrCode; +//! ``` +//! extern crate image; +//! extern crate qrcode; //! -//! let code = QrCode::new(b"Some content here."); -//! match code { -//! Err(err) => panic!("Failed to encode the QR code: {:?}", err), -//! Ok(code) => { -//! for y in 0 .. code.width() { -//! for x in 0 .. code.width() { -//! let color = if code[(x, y)] { "black" } else { "white" }; -//! // render color at position (x, y) -//! } -//! } -//! } +//! use image::GrayImage; +//! use qrcode::QrCode; +//! +//! # fn main() { +//! +//! let code = QrCode::new(b"Some content here."); +//! match code { +//! Err(err) => panic!("Failed to encode the QR code: {:?}", err), +//! Ok(code) => { +//! let image: GrayImage = code.render().min_width(100).to_image(); +//! // render `image`... //! } +//! } +//! +//! # } +//! ``` //! #![cfg_attr(feature="bench", feature(test))] // Unstable libraries -#[cfg(feature="bench")] -extern crate test; -extern crate num; +#[cfg(feature="bench")] extern crate test; +extern crate num_traits; +#[cfg(feature="image")] extern crate image; use std::ops::Index; -pub use types::{QrResult, EcLevel, Version}; - pub mod types; pub mod bits; pub mod optimize; pub mod ec; pub mod canvas; +pub mod render; + +pub use types::{QrResult, EcLevel, Version}; + +#[cfg(feature="image")] use render::{BlankAndWhitePixel, Renderer}; /// The encoded QR code symbol. #[derive(Clone)] @@ -186,6 +195,12 @@ impl QrCode { pub fn into_vec(self) -> Vec { self.content } + + #[cfg(feature="image")] + 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 { @@ -249,17 +264,30 @@ mod tests { } } -// Copyright 2014 Kenny Chan -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. +#[cfg(all(test, feature="image"))] +mod image_tests { + use image::{GrayImage, Rgb, load_from_memory}; + use {QrCode, Version, EcLevel}; + + #[test] + fn test_annex_i_qr_as_image() { + let code = QrCode::new(b"01234567").unwrap(); + let image: GrayImage = code.render().to_image(); + let expected = load_from_memory(include_bytes!("test_annex_i_qr_as_image.png")).unwrap().to_luma(); + assert_eq!(image.dimensions(), expected.dimensions()); + assert_eq!(image.into_raw(), expected.into_raw()); + } + + #[test] + fn test_annex_i_micro_qr_as_image() { + let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap(); + let image = code.render().min_width(200) + .dark_color(Rgb { data: [128, 0, 0] }) + .light_color(Rgb { data: [255, 255, 128] }) + .to_image(); + let expected = load_from_memory(include_bytes!("test_annex_i_micro_qr_as_image.png")).unwrap().to_rgb(); + assert_eq!(image.dimensions(), expected.dimensions()); + assert_eq!(image.into_raw(), expected.into_raw()); + } +} diff --git a/src/optimize.rs b/src/optimize.rs index e79a185..017fe8a 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -676,17 +676,4 @@ static STATE_TRANSITION: [(State, Action); 70] = [ //}}} -// Copyright 2014 Kenny Chan -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..958a03e --- /dev/null +++ b/src/render.rs @@ -0,0 +1,222 @@ +//! Render a QR code into image. + +#![cfg(feature="image")] + +use image::{Pixel, Rgb, Rgba, Luma, LumaA, Primitive, ImageBuffer}; + +/// A pixel which can support black and white colors. +pub trait BlankAndWhitePixel: Pixel { + fn black_color() -> Self; + fn white_color() -> Self; +} + +impl 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/test_annex_i_micro_qr_as_image.png b/src/test_annex_i_micro_qr_as_image.png new file mode 100644 index 0000000..43cb1f7 Binary files /dev/null and b/src/test_annex_i_micro_qr_as_image.png differ diff --git a/src/test_annex_i_qr_as_image.png b/src/test_annex_i_qr_as_image.png new file mode 100644 index 0000000..4d5e5d6 Binary files /dev/null and b/src/test_annex_i_qr_as_image.png differ diff --git a/src/types.rs b/src/types.rs index 8327a66..fd14b8d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -279,17 +279,4 @@ mod mode_tests { //}}} -// Copyright 2014 Kenny Chan -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License.