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};
#[derive(Debug, Clone)]
pub struct QrOptions {
pub scale: u32,
pub border: usize,
pub dark_color: Rgb<u8>,
pub light_color: Rgb<u8>,
}
impl Default for QrOptions {
fn default() -> Self {
Self {
scale: 10,
border: 2,
dark_color: Rgb([0, 0, 0]),
light_color: Rgb([255, 255, 255]),
}
}
}
impl QrOptions {
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(())
}
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(())
}
}
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]))
}
pub struct QrCode {
matrix: Matrix,
}
impl QrCode {
pub fn new(text: &str) -> Result<Self, String> {
Self::new_with_error_level(text, "M")
}
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 })
}
pub fn save_image(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
self.save_image_with_options(path, &QrOptions::default())
}
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(())
}
}