feat: qr code canister
This commit is contained in:
parent
717e1742e6
commit
9ca65d43f6
10 changed files with 1075 additions and 0 deletions
19
src/qr_backend/Cargo.toml
Normal file
19
src/qr_backend/Cargo.toml
Normal 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"] }
|
22
src/qr_backend/qr_backend.did
Normal file
22
src/qr_backend/qr_backend.did
Normal 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);
|
||||
}
|
BIN
src/qr_backend/src/assets/logo_transparent.png
Normal file
BIN
src/qr_backend/src/assets/logo_transparent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
src/qr_backend/src/assets/logo_white.png
Normal file
BIN
src/qr_backend/src/assets/logo_white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
95
src/qr_backend/src/core.rs
Normal file
95
src/qr_backend/src/core.rs
Normal 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
64
src/qr_backend/src/lib.rs
Normal 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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue