qred 0.1.2

A fast, light-footprint native QR code generator with custom color styling.
Documentation
//! # QRED
//! 
//! `qred` is a fast, light-footprint native QR code generator in Rust.
//! 
//! ## Example
//! ```rust
//! use qred::QrCode;
//! 
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let qrcode = QrCode::new("[https://github.com/aether-flux/qred](https://github.com/aether-flux/qred)")?;
//!     qrcode.save_image("output.png")?;
//!     Ok(())
//! }
//! ```

mod matrix;
mod qr_spec;
mod reed;
mod gf28;
mod bit_buffer;
mod mode_encoder;

use gf28::GF28;
use image::{ImageBuffer, Rgb};
use matrix::{Matrix, ModuleColor};
use mode_encoder::prepare_data;
use qr_spec::EcLevel;
use reed::{interleave_blocks, BlockSpec, ReedSolomon};

/// Configurable options for QR code rendering (scale, quiet zone border, and colors).
#[derive(Debug, Clone)]
pub struct QrOptions {
    /// Pixel scaling factor per QR module (default: 10)
    pub scale: u32,
    /// Quiet zone border size in modules (default: 4)
    pub border: usize,
    /// RGB color for dark modules
    pub dark_color: Rgb<u8>,
    /// RGB color for light modules (background)
    pub light_color: Rgb<u8>,
}

impl Default for QrOptions {
    /// Creates QrOptions object with default values.
    fn default() -> Self {
        Self {
            scale:          10,
            border:         2,
            dark_color:     Rgb([0, 0, 0]),
            light_color:    Rgb([255, 255, 255]),
        }
    }
}

impl QrOptions {
    /// Sets the dark module color using a 6-character hex string (e.g. `"#1E1E2E"`).
    pub fn set_dark_color(&mut self, color: &str) -> Result<(), Box<dyn std::error::Error>> {
        let color = parse_hex_color(color)?;
        self.dark_color = color;
        Ok(())
    }

    /// Sets the light module color using a 6-character hex string (e.g. `"#A6E3A1"`).
    pub fn set_light_color(&mut self, color: &str) -> Result<(), Box<dyn std::error::Error>> {
        let color = parse_hex_color(color)?;
        self.light_color = color;
        Ok(())
    }
}

/// Parses a hex color code (e.g. `"#1E1E2E"`) into Rgb<u8>, and errs otherwise.
pub fn parse_hex_color(hex: &str) -> Result<Rgb<u8>, String> {
    let hex = hex.trim_start_matches('#');
    if hex.len() != 6 {
        return Err("Hex color must be 6 characters (eg, #FF5733)".into());
    }

    let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Invalid red component")?;
    let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Invalid green component")?;
    let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Invalid blue component")?;

    Ok(Rgb([r, g, b]))
}

/// Represents a generated QR code matrix ready for export.
pub struct QrCode {
    matrix: Matrix,
}

impl QrCode {
    /// Creates a new `QrCode` using default Medium ('M') Error Correction level.
    ///
    /// # Errors
    /// Returns an error string if the data is too long for supported QR versions.
    pub fn new(text: &str) -> Result<Self, String> {
        Self::new_with_error_level(text, "M")
    }

    /// Creates a `QrCode` with explicit Error Correction level (`"L"`, `"M"`, `"Q"`, or `"H"`).
    pub fn new_with_error_level(text: &str, ec_str: &str) -> Result<Self, String> {
        let ec_level = match ec_str {
            "L" => EcLevel::L,
            "M" => EcLevel::M,
            "Q" => EcLevel::Q,
            "H" => EcLevel::H,
            _   => return Err("Invalid error correction level. Use L, M, Q, or H.".into()),
        };

        let (buf, spec) = prepare_data(text, Some(ec_level))?;
        let data_bytes = buf.to_bytes();

        let gf = GF28::new();
        let rs = ReedSolomon::new(&gf);

        let block_spec = spec.to_block_spec();

        let final_codewords = interleave_blocks(&data_bytes, &block_spec, &rs);

        let mut matrix = Matrix::new(spec.version);
        matrix.place_data_bits(&final_codewords);

        matrix.select_mask(ec_str);

        Ok(Self { matrix })
    }

    /// Exports QR code as an image file with standard default options.
    pub fn save_image(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
        self.save_image_with_options(path, &QrOptions::default())
    }

    /// Exports QR code as an image file with custom colors and other options.
    pub fn save_image_with_options(&self, path: &str, options: &QrOptions) -> Result<(), Box<dyn std::error::Error>> {
        let total_modules = self.matrix.size + options.border * 2;
        let img_size = (total_modules as u32) * options.scale;

        let mut img_buf = ImageBuffer::from_fn(img_size, img_size, |_x, _y| options.light_color);

        for r in 0..self.matrix.size {
            for c in 0..self.matrix.size {
                if self.matrix.grid[r][c].color == ModuleColor::Dark {
                    let start_x = ((c + options.border) as u32) * options.scale;
                    let start_y = ((r + options.border) as u32) * options.scale;

                    for py in start_y..(start_y+options.scale) {
                        for px in start_x..(start_x+options.scale) {
                            img_buf.put_pixel(px, py, options.dark_color);
                        }
                    }
                }
            }
        }

        img_buf.save(path)?;
        Ok(())
    }
}