feat: qr code canister

This commit is contained in:
Ahmet Kaan GÜMÜŞ 2024-02-17 15:23:48 +03:00
parent 717e1742e6
commit 9ca65d43f6
10 changed files with 1075 additions and 0 deletions

19
src/qr_backend/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "qr_backend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
anyhow = "1.0.79"
candid = "0.10"
colorgrad = "0.6.2"
ic-cdk = "0.12"
ic-cdk-timers = "0.6" # Feel free to remove this dependency if you don't need timers
image = { version = "0.24.8", features = ["png"], default-features = false }
qrcode-generator = "4.1.9"
serde = { version = "1.0.196", features = ["derive"] }

View file

@ -0,0 +1,22 @@
type Options = record
{
add_logo: bool;
add_gradient: bool;
add_transparency: opt bool;
};
type QRError = record
{
message: text;
};
type QRResult = variant
{
Image: blob;
Err: QRError;
}
service :
{
"qrcode_query": (input: text, options: Options) -> (QRResult) query;
"qrcode_update": (input: text, options: Options) -> (QRResult);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,95 @@
use image::{imageops, ImageBuffer, Rgba};
use qrcode_generator::QrCodeEcc;
use std::io::Cursor;
use crate::Options;
pub(super) fn generate(input: String, options: Options, logo: &[u8], image_size: usize) -> Result<Vec<u8>, anyhow::Error>
{
let mut qr = image::DynamicImage::ImageLuma8(qrcode_generator::to_image_buffer(input, QrCodeEcc::Quartile, image_size,)?).into_rgba8();
if options.add_transparency == Some(true)
{
make_transparent(&mut qr);
}
if options.add_logo
{
add_logo(&mut qr, logo);
}
if options.add_gradient
{
add_gradient(&mut qr);
}
let mut result = vec![];
qr.write_to(&mut Cursor::new(&mut result), image::ImageOutputFormat::Png)?;
Ok(result)
}
fn make_transparent(qr: &mut ImageBuffer<Rgba<u8>, Vec<u8>>)
{
for (_x, _y, pixel) in qr.enumerate_pixels_mut()
{
if pixel.0 == [255, 255, 255, 255]
{
*pixel = image::Rgba([255, 255, 255, 0]);
}
}
}
fn add_logo(qr: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, logo: &[u8])
{
let image_size = qr.width().min(qr.height()) as usize;
let element_size = get_qr_element_size(qr);
let mut logo_size = element_size;
while logo_size +2 * element_size <= 5 * image_size /16
{
logo_size += 2*element_size;
}
let mut logo = image::io::Reader::new(Cursor::new(logo)).with_guessed_format().unwrap().decode().unwrap();
logo = logo.resize(logo_size as u32, logo_size as u32, imageops::FilterType::Lanczos3);
imageops::replace(qr, &logo, ((image_size-logo_size)/2) as i64, ((image_size-logo_size)/2) as i64);
}
fn add_gradient(qr: &mut ImageBuffer<Rgba<u8>, Vec<u8>>)
{
let image_size = qr.width().min(qr.height()) as usize;
let gradient = colorgrad::CustomGradient::new().colors(&[colorgrad::Color::from_rgba8(100, 0, 100, 255), colorgrad::Color::from_rgba8(30, 5, 60, 225)]).build().unwrap();
let center = (image_size / 2) as u32;
for (x, y, pixel) in qr.enumerate_pixels_mut()
{
if pixel.0 == [0, 0, 0, 255]
{
let distance = x.abs_diff(center) + y.abs_diff(center);
let rgba = gradient.at(distance as f64 / image_size as f64).to_rgba8();
*pixel = image::Rgba(rgba);
}
}
}
fn get_qr_element_size(qr: &mut ImageBuffer<Rgba<u8>, Vec<u8>>) -> usize
{
const BLACK_PIXEL: [u8;4] = [0, 0, 0, 255];
let size = qr.width().min(qr.height());
let mut start = size;
for i in 0..size
{
if qr.get_pixel(i, i).0 == BLACK_PIXEL
{
start = i;
break;
}
}
let mut element_size = 1;
for i in 0..size-start
{
if qr.get_pixel(start+i, start+i).0 != BLACK_PIXEL
{
element_size = i;
break;
}
}
element_size as usize
}

64
src/qr_backend/src/lib.rs Normal file
View file

@ -0,0 +1,64 @@
use candid::{CandidType, Deserialize};
use std::include_bytes;
mod core;
const IMAGE_SIZE_IN_PIXEL: usize = 1024;
const LOGO_TRANSPARENT: &[u8] = include_bytes!("./assets/logo_transparent.png");
const LOGO_WHITE: &[u8] = include_bytes!("./assets/logo_white.png");
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
#[derive(CandidType, Deserialize)]
struct Options
{
add_logo:bool,
add_gradient:bool,
add_transparency: Option<bool>,
}
#[derive(CandidType, Deserialize)]
struct QRError
{
message: String,
}
#[derive(CandidType, Deserialize)]
enum QRResult
{
Image(Vec<u8>),
Err(QRError),
}
fn qrcode(input: String, options: Options) -> QRResult
{
let logo = if options.add_transparency == Some(true)
{
LOGO_TRANSPARENT
}
else
{
LOGO_WHITE
};
let result = match core::generate(input, options, logo, IMAGE_SIZE_IN_PIXEL)
{
Ok(blob) => QRResult::Image(blob),
Err(err) => QRResult::Err(QRError{message: err.to_string()}),
};
ic_cdk::println!("Executed Instructions: {}", ic_cdk::api::performance_counter(0));
result
}
#[ic_cdk::update]
fn qrcode_update(input: String, options: Options) -> QRResult
{
qrcode(input, options)
}
#[ic_cdk::query]
fn qrcode_query(input: String, options: Options) -> QRResult
{
qrcode(input, options)
}