mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 02:18:12 +08:00
Improve the QR Code node (#3765)
This commit is contained in:
Generated
+1
@@ -2216,6 +2216,7 @@ dependencies = [
|
|||||||
"text-nodes",
|
"text-nodes",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
"vector-nodes",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"wgpu-executor",
|
"wgpu-executor",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use graphene_std::raster::{
|
|||||||
use graphene_std::table::{Table, TableRow};
|
use graphene_std::table::{Table, TableRow};
|
||||||
use graphene_std::text::{Font, TextAlign};
|
use graphene_std::text::{Font, TextAlign};
|
||||||
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||||
|
use graphene_std::vector::QRCodeErrorCorrectionLevel;
|
||||||
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, MergeByDistanceAlgorithm, PointSpacingType, SpiralType};
|
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, MergeByDistanceAlgorithm, PointSpacingType, SpiralType};
|
||||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
|
||||||
@@ -226,6 +227,7 @@ pub(crate) fn property_from_type(
|
|||||||
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
|
||||||
|
Some(x) if x == TypeId::of::<QRCodeErrorCorrectionLevel>() => enum_choice::<QRCodeErrorCorrectionLevel>().for_socket(default_info).property_row(),
|
||||||
// =====
|
// =====
|
||||||
// OTHER
|
// OTHER
|
||||||
// =====
|
// =====
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ graphene-core = { workspace = true }
|
|||||||
graphene-application-io = { workspace = true }
|
graphene-application-io = { workspace = true }
|
||||||
rendering = { workspace = true }
|
rendering = { workspace = true }
|
||||||
raster-nodes = { workspace = true }
|
raster-nodes = { workspace = true }
|
||||||
|
vector-nodes = { workspace = true }
|
||||||
graphic-types = { workspace = true }
|
graphic-types = { workspace = true }
|
||||||
text-nodes = { workspace = true }
|
text-nodes = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ tagged_value! {
|
|||||||
Fill(vector::style::Fill),
|
Fill(vector::style::Fill),
|
||||||
BlendMode(core_types::blending::BlendMode),
|
BlendMode(core_types::blending::BlendMode),
|
||||||
LuminanceCalculation(raster_nodes::adjustments::LuminanceCalculation),
|
LuminanceCalculation(raster_nodes::adjustments::LuminanceCalculation),
|
||||||
|
QRCodeErrorCorrectionLevel(vector_nodes::generator_nodes::QRCodeErrorCorrectionLevel),
|
||||||
XY(graphene_core::extract_xy::XY),
|
XY(graphene_core::extract_xy::XY),
|
||||||
RedGreenBlue(raster_nodes::adjustments::RedGreenBlue),
|
RedGreenBlue(raster_nodes::adjustments::RedGreenBlue),
|
||||||
RedGreenBlueAlpha(raster_nodes::adjustments::RedGreenBlueAlpha),
|
RedGreenBlueAlpha(raster_nodes::adjustments::RedGreenBlueAlpha),
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),
|
||||||
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use core_types::Ctx;
|
|
||||||
use core_types::registry::types::{Angle, PixelSize};
|
use core_types::registry::types::{Angle, PixelSize};
|
||||||
use core_types::table::Table;
|
use core_types::table::Table;
|
||||||
|
use core_types::{Ctx, specta};
|
||||||
|
use dyn_any::DynAny;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use vector_types::subpath;
|
use vector_types::subpath;
|
||||||
@@ -186,48 +187,72 @@ fn star<T: AsU64>(
|
|||||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||||
|
#[widget(Radio)]
|
||||||
|
pub enum QRCodeErrorCorrectionLevel {
|
||||||
|
/// Allows recovery from up to 7% data loss.
|
||||||
|
#[default]
|
||||||
|
Low,
|
||||||
|
/// Allows recovery from up to 15% data loss.
|
||||||
|
Medium,
|
||||||
|
/// Allows recovery from up to 25% data loss.
|
||||||
|
Quartile,
|
||||||
|
/// Allows recovery from up to 30% data loss.
|
||||||
|
High,
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates a QR code from the input text.
|
/// Generates a QR code from the input text.
|
||||||
#[node_macro::node(category("Vector: Shape"), name("QR Code"))]
|
#[node_macro::node(category("Vector: Shape"), name("QR Code"))]
|
||||||
fn qr_code(
|
fn qr_code(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
#[default("https://graphite.art")] text: String,
|
#[widget(ParsedWidgetOverride::Custom = "text_area")]
|
||||||
/// Error correction level, from low (0) to high (3).
|
#[default("https://graphite.art")]
|
||||||
#[default(1)]
|
text: String,
|
||||||
error_correction: u32,
|
#[widget(ParsedWidgetOverride::Hidden)] has_size: bool,
|
||||||
|
#[unit(" px")]
|
||||||
|
#[hard_min(1.)]
|
||||||
|
#[widget(ParsedWidgetOverride::Custom = "optional_f64")]
|
||||||
|
size: f64,
|
||||||
|
error_correction: QRCodeErrorCorrectionLevel,
|
||||||
#[default(false)] individual_squares: bool,
|
#[default(false)] individual_squares: bool,
|
||||||
) -> Table<Vector> {
|
) -> Table<Vector> {
|
||||||
let ecc = match error_correction.min(3) {
|
let ecc = match error_correction {
|
||||||
0 => qrcodegen::QrCodeEcc::Low,
|
QRCodeErrorCorrectionLevel::Low => qrcodegen::QrCodeEcc::Low,
|
||||||
1 => qrcodegen::QrCodeEcc::Medium,
|
QRCodeErrorCorrectionLevel::Medium => qrcodegen::QrCodeEcc::Medium,
|
||||||
2 => qrcodegen::QrCodeEcc::Quartile,
|
QRCodeErrorCorrectionLevel::Quartile => qrcodegen::QrCodeEcc::Quartile,
|
||||||
3 => qrcodegen::QrCodeEcc::High,
|
QRCodeErrorCorrectionLevel::High => qrcodegen::QrCodeEcc::High,
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(qr_code) = qrcodegen::QrCode::encode_text(&text, ecc) else {
|
let Ok(qr_code) = qrcodegen::QrCode::encode_text(&text, ecc) else { return Table::default() };
|
||||||
return Table::default();
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = qr_code.size() as usize;
|
let mut vector = match individual_squares {
|
||||||
let mut vector = Vector::default();
|
true => {
|
||||||
|
let mut vector = Vector::default();
|
||||||
|
|
||||||
if individual_squares {
|
let dimension = qr_code.size() as usize;
|
||||||
for y in 0..size {
|
for y in 0..dimension {
|
||||||
for x in 0..size {
|
for x in 0..dimension {
|
||||||
if qr_code.get_module(x as i32, y as i32) {
|
if qr_code.get_module(x as i32, y as i32) {
|
||||||
let corner1 = DVec2::new(x as f64, y as f64);
|
let corner1 = DVec2::new(x as f64, y as f64);
|
||||||
let corner2 = corner1 + DVec2::splat(1.);
|
let corner2 = corner1 + DVec2::splat(1.);
|
||||||
vector.append_subpath(
|
vector.append_subpath(
|
||||||
subpath::Subpath::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true),
|
subpath::Subpath::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector
|
||||||
}
|
}
|
||||||
} else {
|
false => crate::merge_qr_squares::merge_qr_squares(&qr_code),
|
||||||
crate::merge_qr_squares::merge_qr_squares(&qr_code, &mut vector);
|
};
|
||||||
|
|
||||||
|
if has_size {
|
||||||
|
vector.transform(glam::DAffine2::from_scale(DVec2::splat(size.max(1.) / qr_code.size() as f64)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Table::new_from_element(vector)
|
Table::new_from_element(vector)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +432,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn qr_code_test() {
|
fn qr_code_test() {
|
||||||
let qr = qr_code((), (), "https://graphite.art".to_string(), 1, true);
|
let qr = qr_code((), (), "https://graphite.art".to_string(), false, 1., QRCodeErrorCorrectionLevel::Low, true);
|
||||||
assert!(qr.iter().next().unwrap().element.point_domain.ids().len() > 0);
|
assert!(qr.iter().next().unwrap().element.point_domain.ids().len() > 0);
|
||||||
assert!(qr.iter().next().unwrap().element.segment_domain.ids().len() > 0);
|
assert!(qr.iter().next().unwrap().element.segment_domain.ids().len() > 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,19 @@ use graphic_types::Vector;
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use vector_types::subpath;
|
use vector_types::subpath;
|
||||||
|
|
||||||
pub fn merge_qr_squares(qr_code: &qrcodegen::QrCode, vector: &mut Vector) {
|
pub fn merge_qr_squares(qr_code: &qrcodegen::QrCode) -> Vector {
|
||||||
|
let mut vector = Vector::default();
|
||||||
|
|
||||||
let size = qr_code.size() as usize;
|
let size = qr_code.size() as usize;
|
||||||
|
|
||||||
// 0 = empty
|
// 0 = empty
|
||||||
// 1 = black, unvisited
|
// 1 = filled, unvisited
|
||||||
// 2 = black, current island
|
// 2 = filled, current island
|
||||||
let mut remaining = vec![vec![0u8; size]; size];
|
let mut remaining = vec![vec![0u8; size]; size];
|
||||||
|
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
for y in 0..size {
|
for y in 0..size {
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
for x in 0..size {
|
for x in 0..size {
|
||||||
if qr_code.get_module(x as i32, y as i32) {
|
if qr_code.get_module(x as i32, y as i32) {
|
||||||
remaining[y][x] = 1;
|
remaining[y][x] = 1;
|
||||||
@@ -115,4 +119,6 @@ pub fn merge_qr_squares(qr_code: &qrcodegen::QrCode, vector: &mut Vector) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user