diff --git a/src/bin/qrencode.rs b/src/bin/qrencode.rs index 58fd9da..3e776db 100644 --- a/src/bin/qrencode.rs +++ b/src/bin/qrencode.rs @@ -2,22 +2,13 @@ extern crate qrcode; use std::env; -const SPACE: char = ' '; //' '; - pub fn main() { let arg = env::args().nth(1).unwrap(); let code = qrcode::QrCode::new(arg.as_bytes()).unwrap(); - print!("\n\n\n\n\n{}{}{}{}{}", SPACE, SPACE, SPACE, SPACE, SPACE); - - for y in 0 .. code.width() { - for x in 0 .. code.width() { - let block = code[(x, y)].select('█', SPACE); - print!("{}{}", block, block); - } - print!("\n{}{}{}{}{}", SPACE, SPACE, SPACE, SPACE, SPACE); - } - - println!("\n\n\n\n"); + print!("{}", code.render() + .dark_color("\x1b[7m \x1b[0m") + .light_color("\x1b[49m \x1b[0m") + .build()); } diff --git a/src/lib.rs b/src/lib.rs index ef0ae10..de5411a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,26 +2,30 @@ //! //! This crate provides a QR code and Micro QR code encoder for binary data. //! -//! ``` +#![cfg_attr(feature="image", doc=" ```rust")] +#![cfg_attr(not(feature="image"), doc="```ignore")] //! extern crate qrcode; -//! # #[cfg(feature="image")] //! extern crate image; //! //! use qrcode::QrCode; -//! # #[cfg(feature="image")] -//! use image::GrayImage; +//! use image::Luma; //! //! fn main() { //! // Encode some data into bits. //! let code = QrCode::new(b"01234567").unwrap(); //! //! // Render the bits into an image. -//! # #[cfg(feature="image")] -//! let image: GrayImage = code.render().to_image(); +//! let image = code.render::>().build(); //! //! // Save the image. -//! # #[cfg(feature="image")] //! image.save("/tmp/qrcode.png").unwrap(); +//! +//! // You can also render it into a string. +//! let string = code.render() +//! .light_color(' ') +//! .dark_color('#') +//! .build(); +//! println!("{}", string); //! } //! ``` @@ -41,7 +45,7 @@ pub mod render; pub use types::{QrResult, Color, EcLevel, Version}; -#[cfg(feature="image")] use render::{BlankAndWhitePixel, Renderer}; +use render::{Pixel, Renderer}; /// The encoded QR code symbol. #[derive(Clone)] @@ -171,17 +175,11 @@ impl QrCode { /// Converts the QR code into a human-readable string. This is mainly for /// debugging only. pub fn to_debug_str(&self, on_char: char, off_char: char) -> String { - let width = self.width; - let mut k = 0; - let mut res = String::with_capacity(width * (width + 1)); - for _ in 0 .. width { - res.push('\n'); - for _ in 0 .. width { - res.push(self.content[k].select(on_char, off_char)); - k += 1; - } - } - res + self.render() + .quiet_zone(false) + .dark_color(on_char) + .light_color(off_char) + .build() } /// Converts the QR code to a vector of booleans. Each entry represents the @@ -214,7 +212,8 @@ impl QrCode { /// /// # Examples /// - /// ``` + #[cfg_attr(feature="image", doc=" ```rust")] + #[cfg_attr(not(feature="image"), doc=" ```ignore")] /// # extern crate image; /// # extern crate qrcode; /// # use qrcode::QrCode; @@ -225,17 +224,16 @@ impl QrCode { /// .render() /// .dark_color(Rgb { data: [0, 0, 128] }) /// .light_color(Rgb { data: [224, 224, 224] }) // adjust colors - /// .quiet_zone(false) // disable quiet zone (white border) - /// .min_width(300) // sets minimum image size - /// .to_image(); + /// .quiet_zone(false) // disable quiet zone (white border) + /// .min_dimensions(300, 300) // sets minimum image size + /// .build(); /// /// # } /// ``` /// /// Note: the `image` crate itself also provides method to rotate the image, /// or overlay a logo on top of the QR code. - #[cfg(feature="image")] - pub fn render(&self) -> Renderer

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

{ let quiet_zone = if self.version.is_micro() { 2 } else { 4 }; Renderer::new(&self.content, self.width, quiet_zone) } @@ -258,7 +256,7 @@ mod tests { fn test_annex_i_qr() { // This uses the ISO Annex I as test vector. let code = QrCode::with_version(b"01234567", Version::Normal(1), EcLevel::M).unwrap(); - assert_eq!(&*code.to_debug_str('#', '.'), "\n\ + assert_eq!(&*code.to_debug_str('#', '.'), "\ #######..#.##.#######\n\ #.....#..####.#.....#\n\ #.###.#.#.....#.###.#\n\ @@ -285,7 +283,7 @@ mod tests { #[test] fn test_annex_i_micro_qr() { let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap(); - assert_eq!(&*code.to_debug_str('#', '.'), "\n\ + assert_eq!(&*code.to_debug_str('#', '.'), "\ #######.#.#.#\n\ #.....#.###.#\n\ #.###.#..##.#\n\ @@ -304,13 +302,13 @@ mod tests { #[cfg(all(test, feature="image"))] mod image_tests { - use image::{GrayImage, Rgb, load_from_memory}; + use image::{Luma, Rgb, load_from_memory}; use {QrCode, Version, EcLevel}; #[test] fn test_annex_i_qr_as_image() { let code = QrCode::new(b"01234567").unwrap(); - let image: GrayImage = code.render().to_image(); + let image = code.render::>().build(); let expected = load_from_memory(include_bytes!("test_annex_i_qr_as_image.png")).unwrap().to_luma(); assert_eq!(image.dimensions(), expected.dimensions()); assert_eq!(image.into_raw(), expected.into_raw()); @@ -319,10 +317,11 @@ mod image_tests { #[test] fn test_annex_i_micro_qr_as_image() { let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap(); - let image = code.render().min_width(200) - .dark_color(Rgb { data: [128, 0, 0] }) - .light_color(Rgb { data: [255, 255, 128] }) - .to_image(); + let image = code.render() + .min_dimensions(200, 200) + .dark_color(Rgb { data: [128, 0, 0] }) + .light_color(Rgb { data: [255, 255, 128] }) + .build(); let expected = load_from_memory(include_bytes!("test_annex_i_micro_qr_as_image.png")).unwrap().to_rgb(); assert_eq!(image.dimensions(), expected.dimensions()); assert_eq!(image.into_raw(), expected.into_raw()); diff --git a/src/render.rs b/src/render.rs deleted file mode 100644 index bfa2a93..0000000 --- a/src/render.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Render a QR code into image. - -#![cfg(feature="image")] - -use image::{Pixel, Rgb, Rgba, Luma, LumaA, Primitive, ImageBuffer}; -use types::Color; - -/// A pixel which can support black and white colors. -pub trait BlankAndWhitePixel: Pixel { - fn black_color() -> Self; - fn white_color() -> Self; -} - -impl 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 [Color], - modules_count: u32, // <- we call it `modules_count` here to avoid ambiguity of `width`. - quiet_zone: u32, - module_size: u32, - - dark_color: P, - light_color: P, - has_quiet_zone: bool, -} - -impl<'a, P: BlankAndWhitePixel + 'static> Renderer<'a, P> { - /// Creates a new renderer. - pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> { - assert!(modules_count * modules_count == content.len()); - Renderer { - content: content, - modules_count: modules_count as u32, - quiet_zone: quiet_zone, - module_size: 8, - dark_color: P::black_color(), - light_color: P::white_color(), - has_quiet_zone: true, - } - } - - /// Sets color of a dark module. Default is opaque black. - pub fn dark_color(&mut self, color: P) -> &mut Self { - self.dark_color = color; - self - } - - /// Sets color of a light module. Default is opaque white. - pub fn light_color(&mut self, color: P) -> &mut Self { - self.light_color = color; - self - } - - /// Whether to include the quiet zone in the generated image. - pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self { - self.has_quiet_zone = has_quiet_zone; - self - } - - /// Sets the size of each module in pixels. Default is 8px. - pub fn module_size(&mut self, size: u32) -> &mut Self { - self.module_size = size; - self - } - - /// Sets the minimal total image width (and thus height) in pixels, - /// including the quiet zone if applicable. The renderer will try to find - /// the dimension as small as possible, such that each module in the QR code - /// has uniform size (no distortion). - /// - /// For instance, a version 1 QR code has 19 modules across including the - /// quiet zone. If we request an image of width ≥200px, we get that each - /// module's size should be 11px, so the actual image size will be 209px. - pub fn min_width(&mut self, width: u32) -> &mut Self { - let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone; - let width_in_modules = self.modules_count + quiet_zone; - let module_size = (width + width_in_modules - 1) / width_in_modules; - self.module_size(module_size) - } - - /// Renders the QR code into an image. - pub fn to_image(&self) -> ImageBuffer> { - let w = self.modules_count; - let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 }; - let width = w + 2 * qz; - - let ms = self.module_size; - let real_width = width * ms; - - let mut image = ImageBuffer::new(real_width, real_width); - let mut i = 0; - for y in 0 .. width { - for x in 0 .. width { - let color = if qz <= x && x < w + qz && qz <= y && y < w + qz { - let c = if self.content[i] != Color::Light { - self.dark_color - } else { - self.light_color - }; - i += 1; - c - } else { - self.light_color - }; - for yy in y * ms .. (y + 1) * ms { - for xx in x * ms .. (x + 1) * ms { - image.put_pixel(xx, yy, color); - } - } - } - } - - image - } -} - -#[cfg(test)] -mod render_tests { - use render::Renderer; - use image::{Luma, Rgba}; - use types::Color; - - #[test] - fn test_render_luma8_unsized() { - let image = Renderer::>::new(&[ - Color::Light, Color::Dark, Color::Dark, - Color::Dark, Color::Light, Color::Light, - Color::Light, Color::Dark, Color::Light, - ], 3, 1).module_size(1).to_image(); - - let expected = [ - 255, 255, 255, 255, 255, - 255, 255, 0, 0, 255, - 255, 0, 255, 255, 255, - 255, 255, 0, 255, 255, - 255, 255, 255, 255, 255, - ]; - assert_eq!(image.into_raw(), expected); - } - - #[test] - fn test_render_rgba_unsized() { - let image = Renderer::>::new(&[ - Color::Light, Color::Dark, - Color::Dark, Color::Dark, - ], 2, 1).module_size(1).to_image(); - - let expected: &[u8] = &[ - 255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255, - 255,255,255,255, 255,255,255,255, 0, 0, 0,255, 255,255,255,255, - 255,255,255,255, 0, 0, 0,255, 0, 0, 0,255, 255,255,255,255, - 255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255, - ]; - - assert_eq!(image.into_raw(), expected); - } - - #[test] - fn test_render_resized() { - let image = Renderer::>::new(&[ - Color::Dark, Color::Light, - Color::Light, Color::Dark, - ], 2, 1).min_width(10).to_image(); - - let expected: &[u8] = &[ - 255,255,255, 255,255,255, 255,255,255, 255,255,255, - 255,255,255, 255,255,255, 255,255,255, 255,255,255, - 255,255,255, 255,255,255, 255,255,255, 255,255,255, - - 255,255,255, 0, 0, 0, 255,255,255, 255,255,255, - 255,255,255, 0, 0, 0, 255,255,255, 255,255,255, - 255,255,255, 0, 0, 0, 255,255,255, 255,255,255, - - 255,255,255, 255,255,255, 0, 0, 0, 255,255,255, - 255,255,255, 255,255,255, 0, 0, 0, 255,255,255, - 255,255,255, 255,255,255, 0, 0, 0, 255,255,255, - - 255,255,255, 255,255,255, 255,255,255, 255,255,255, - 255,255,255, 255,255,255, 255,255,255, 255,255,255, - 255,255,255, 255,255,255, 255,255,255, 255,255,255, - ]; - - assert_eq!(image.dimensions(), (12, 12)); - assert_eq!(image.into_raw(), expected); - } -} - - diff --git a/src/render/image.rs b/src/render/image.rs new file mode 100644 index 0000000..02b8deb --- /dev/null +++ b/src/render/image.rs @@ -0,0 +1,135 @@ +#![cfg(feature="image")] + +use render::{Pixel, Canvas}; +use types::Color; + +use image::{Pixel as ImagePixel, Rgb, Rgba, Luma, LumaA, Primitive, ImageBuffer}; + +macro_rules! impl_pixel_for_image_pixel { + ($p:ident<$s:ident>: $c:pat => $d:expr) => { + impl<$s: Primitive + 'static> Pixel for $p<$s> { + type Image = ImageBuffer>; + type Canvas = (Self, Self::Image); + + fn default_color(color: Color) -> Self { + match color.select($s::zero(), $s::max_value()) { + $c => $p { data: $d } + } + } + } + } +} + +impl_pixel_for_image_pixel!{ Luma: p => [p] } +impl_pixel_for_image_pixel!{ LumaA: p => [p, S::max_value()] } +impl_pixel_for_image_pixel!{ Rgb: p => [p, p, p] } +impl_pixel_for_image_pixel!{ Rgba: p => [p, p, p, S::max_value()] } + +impl Canvas for (P, ImageBuffer>) { + type Pixel = P; + type Image = ImageBuffer>; + + fn new(width: u32, height: u32, dark_pixel: P, light_pixel: P) -> Self { + (dark_pixel, ImageBuffer::from_pixel(width, height, light_pixel)) + } + + fn draw_dark_pixel(&mut self, x: u32, y: u32) { + self.1.put_pixel(x, y, self.0); + } + + fn into_image(self) -> ImageBuffer> { + self.1 + } +} + +#[cfg(test)] +mod render_tests { + use render::Renderer; + use image::{Luma, Rgba}; + use types::Color; + + #[test] + fn test_render_luma8_unsized() { + let image = Renderer::>::new(&[ + Color::Light, Color::Dark, Color::Dark, + Color::Dark, Color::Light, Color::Light, + Color::Light, Color::Dark, Color::Light, + ], 3, 1).module_dimensions(1, 1).build(); + + let expected = [ + 255, 255, 255, 255, 255, + 255, 255, 0, 0, 255, + 255, 0, 255, 255, 255, + 255, 255, 0, 255, 255, + 255, 255, 255, 255, 255, + ]; + assert_eq!(image.into_raw(), expected); + } + + #[test] + fn test_render_rgba_unsized() { + let image = Renderer::>::new(&[ + Color::Light, Color::Dark, + Color::Dark, Color::Dark, + ], 2, 1).module_dimensions(1, 1).build(); + + let expected: &[u8] = &[ + 255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255, + 255,255,255,255, 255,255,255,255, 0, 0, 0,255, 255,255,255,255, + 255,255,255,255, 0, 0, 0,255, 0, 0, 0,255, 255,255,255,255, + 255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255, + ]; + + assert_eq!(image.into_raw(), expected); + } + + #[test] + fn test_render_resized_min() { + let image = Renderer::>::new(&[ + Color::Dark, Color::Light, + Color::Light, Color::Dark, + ], 2, 1).min_dimensions(10, 10).build(); + + let expected: &[u8] = &[ + 255,255,255, 255,255,255, 255,255,255, 255,255,255, + 255,255,255, 255,255,255, 255,255,255, 255,255,255, + 255,255,255, 255,255,255, 255,255,255, 255,255,255, + + 255,255,255, 0, 0, 0, 255,255,255, 255,255,255, + 255,255,255, 0, 0, 0, 255,255,255, 255,255,255, + 255,255,255, 0, 0, 0, 255,255,255, 255,255,255, + + 255,255,255, 255,255,255, 0, 0, 0, 255,255,255, + 255,255,255, 255,255,255, 0, 0, 0, 255,255,255, + 255,255,255, 255,255,255, 0, 0, 0, 255,255,255, + + 255,255,255, 255,255,255, 255,255,255, 255,255,255, + 255,255,255, 255,255,255, 255,255,255, 255,255,255, + 255,255,255, 255,255,255, 255,255,255, 255,255,255, + ]; + + assert_eq!(image.dimensions(), (12, 12)); + assert_eq!(image.into_raw(), expected); + } + + #[test] + fn test_render_resized_max() { + let image = Renderer::>::new(&[ + Color::Dark, Color::Light, + Color::Light, Color::Dark, + ], 2, 1).max_dimensions(10, 5).build(); + + let expected: &[u8] = &[ + 255,255, 255,255, 255,255, 255,255, + + 255,255, 0, 0, 255,255, 255,255, + + 255,255, 255,255, 0, 0, 255,255, + + 255,255, 255,255, 255,255, 255,255, + ]; + + assert_eq!(image.dimensions(), (8, 4)); + assert_eq!(image.into_raw(), expected); + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..03a46ee --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,188 @@ +//! Render a QR code into image. + +use std::cmp::max; +use types::Color; + +pub mod image; +pub mod string; + +//------------------------------------------------------------------------------ +//{{{ Pixel trait + +/// Abstraction of an image pixel. +pub trait Pixel: Copy + Sized { + /// Type of the finalized image. + type Image: Sized + 'static; + + /// The type that stores an intermediate buffer before finalizing to a + /// concrete image + type Canvas: Canvas; + + /// Obtains the default module size. The result must be at least 1×1. + fn default_unit_size() -> (u32, u32) { + (8, 8) + } + + /// Obtains the default pixel color when a module is dark or light. + fn default_color(color: Color) -> Self; +} + +/// Rendering canvas of a QR code image. +pub trait Canvas: Sized { + type Pixel: Sized; + type Image: Sized; + + /// Constructs a new canvas of the given dimensions. + fn new(width: u32, height: u32, dark_pixel: Self::Pixel, light_pixel: Self::Pixel) -> Self; + + /// Draws a single dark pixel at the (x, y) coordinate. + fn draw_dark_pixel(&mut self, x: u32, y: u32); + + fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) { + for y in top .. (top + height) { + for x in left .. (left + width) { + self.draw_dark_pixel(x, y); + } + } + } + + /// Finalize the canvas to a real image. + fn into_image(self) -> Self::Image; +} + +//}}} +//------------------------------------------------------------------------------ +//{{{ Renderer + +/// A QR code renderer. This is a builder type which converts a bool-vector into +/// an image. +pub struct Renderer<'a, P: Pixel> { + content: &'a [Color], + modules_count: u32, // <- we call it `modules_count` here to avoid ambiguity of `width`. + quiet_zone: u32, + module_size: (u32, u32), + + dark_color: P, + light_color: P, + has_quiet_zone: bool, +} + +impl<'a, P: Pixel> Renderer<'a, P> { + /// Creates a new renderer. + pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> { + assert!(modules_count * modules_count == content.len()); + Renderer { + content, + modules_count: modules_count as u32, + quiet_zone, + module_size: P::default_unit_size(), + dark_color: P::default_color(Color::Dark), + light_color: P::default_color(Color::Light), + has_quiet_zone: true, + } + } + + /// Sets color of a dark module. Default is opaque black. + pub fn dark_color(&mut self, color: P) -> &mut Self { + self.dark_color = color; + self + } + + /// Sets color of a light module. Default is opaque white. + pub fn light_color(&mut self, color: P) -> &mut Self { + self.light_color = color; + self + } + + /// Whether to include the quiet zone in the generated image. + pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self { + self.has_quiet_zone = has_quiet_zone; + self + } + + /// Sets the size of each module in pixels. Default is 8px. + #[deprecated(since="0.4.0", note="use `.module_dimensions(width, width)` instead")] + pub fn module_size(&mut self, width: u32) -> &mut Self { + self.module_dimensions(width, width) + } + + /// Sets the size of each module in pixels. Default is 8×8. + pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self { + self.module_size = (max(width, 1), max(height, 1)); + self + } + + #[deprecated(since="0.4.0", note="use `.min_dimensions(width, width)` instead")] + pub fn min_width(&mut self, width: u32) -> &mut Self { + self.min_dimensions(width, width) + } + + /// Sets the minimum total image size in pixels, including the quiet zone if + /// applicable. The renderer will try to find the dimension as small as + /// possible, such that each module in the QR code has uniform size (no + /// distortion). + /// + /// For instance, a version 1 QR code has 19 modules across including the + /// quiet zone. If we request an image of size ≥200×200, we get that each + /// module's size should be 11×11, so the actual image size will be 209×209. + pub fn min_dimensions(&mut self, width: u32, height: u32) -> &mut Self { + let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone; + let width_in_modules = self.modules_count + quiet_zone; + let unit_width = (width + width_in_modules - 1) / width_in_modules; + let unit_height = (height + width_in_modules - 1) / width_in_modules; + self.module_dimensions(unit_width, unit_height) + } + + /// Sets the maximum total image size in pixels, including the quiet zone if + /// applicable. The renderer will try to find the dimension as large as + /// possible, such that each module in the QR code has uniform size (no + /// distortion). + /// + /// For instance, a version 1 QR code has 19 modules across including the + /// quiet zone. If we request an image of size ≤200×200, we get that each + /// module's size should be 10×10, so the actual image size will be 190×190. + /// + /// The module size is at least 1×1, so if the restriction is too small, the + /// final image *can* be larger than the input. + pub fn max_dimensions(&mut self, width: u32, height: u32) -> &mut Self { + let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone; + let width_in_modules = self.modules_count + quiet_zone; + let unit_width = width / width_in_modules; + let unit_height = height / width_in_modules; + self.module_dimensions(unit_width, unit_height) + } + + /// Renders the QR code into an image. + #[deprecated(since="0.4.0", note="renamed to `.build()` to de-emphasize the image connection")] + pub fn to_image(&self) -> P::Image { + self.build() + } + + /// Renders the QR code into an image. + pub fn build(&self) -> P::Image { + let w = self.modules_count; + let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 }; + let width = w + 2 * qz; + + let (mw, mh) = self.module_size; + let real_width = width * mw; + let real_height = width * mh; + + let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color); + let mut i = 0; + for y in 0 .. width { + for x in 0 .. width { + if qz <= x && x < w + qz && qz <= y && y < w + qz { + if self.content[i] != Color::Light { + canvas.draw_dark_rect(x*mw, y*mh, mw, mh); + } + i += 1; + } + } + } + + canvas.into_image() + } +} + +//}}} \ No newline at end of file diff --git a/src/render/string.rs b/src/render/string.rs new file mode 100644 index 0000000..584684f --- /dev/null +++ b/src/render/string.rs @@ -0,0 +1,126 @@ +//! String rendering support. + +use render::{Pixel, Canvas as RenderCanvas}; +use types::Color; + +pub trait Element: Copy { + fn default_color(color: Color) -> Self; + fn strlen(self) -> usize; + fn append_to_string(self, string: &mut String); +} + +impl Element for char { + fn default_color(color: Color) -> Self { + color.select('#', ' ') + } + + fn strlen(self) -> usize { + self.len_utf8() + } + + fn append_to_string(self, string: &mut String) { + string.push(self) + } +} + +impl<'a> Element for &'a str { + fn default_color(color: Color) -> Self { + color.select("#", " ") + } + + fn strlen(self) -> usize { + self.len() + } + + fn append_to_string(self, string: &mut String) { + string.push_str(self) + } +} + +#[doc(hidden)] +pub struct Canvas { + buffer: Vec

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

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

{ + type Pixel = P; + type Image = String; + + fn new(width: u32, height: u32, dark_pixel: P, light_pixel: P) -> Self { + let width = width as usize; + let height = height as isize; + let dark_cap = dark_pixel.strlen() as isize; + let light_cap = light_pixel.strlen() as isize; + Canvas { + buffer: vec![light_pixel; width * (height as usize)], + width, + dark_pixel, + dark_cap_inc: dark_cap - light_cap, + capacity: light_cap * (width as isize) * height + (height - 1), + } + } + + fn draw_dark_pixel(&mut self, x: u32, y: u32) { + let x = x as usize; + let y = y as usize; + self.capacity += self.dark_cap_inc; + self.buffer[x + y*self.width] = self.dark_pixel; + } + + + fn into_image(self) -> String { + let mut result = String::with_capacity(self.capacity as usize); + for (i, pixel) in self.buffer.into_iter().enumerate() { + if i != 0 && i % self.width == 0 { + result.push('\n'); + } + pixel.append_to_string(&mut result); + } + result + } +} + +#[test] +fn test_render_to_string() { + use render::Renderer; + + let colors = &[ + Color::Dark, Color::Light, + Color::Light, Color::Dark, + ]; + let image: String = Renderer::::new(colors, 2, 1).build(); + assert_eq!(&image, " \n # \n # \n "); + + let image2 = Renderer::new(colors, 2, 1) + .light_color("A") + .dark_color("!B!") + .module_dimensions(2, 2) + .build(); + + assert_eq!(&image2, "\ + AAAAAAAA\n\ + AAAAAAAA\n\ + AA!B!!B!AAAA\n\ + AA!B!!B!AAAA\n\ + AAAA!B!!B!AA\n\ + AAAA!B!!B!AA\n\ + AAAAAAAA\n\ + AAAAAAAA"); +}