Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
Thumbs.db
node_modules/
pkg/
out/
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 84 additions & 1 deletion crates/glyphnet-decode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use glyphnet_core::{
};
use glyphnet_ecc::RecoveryTelemetry;
use image::{DynamicImage, GrayImage};
#[cfg(not(target_arch = "wasm32"))]
use std::path::PathBuf;
use thiserror::Error;

mod autodetect;
Expand Down Expand Up @@ -134,6 +136,7 @@ impl RasterDecoder {

/// Decode a rendered GlyphNet image and return the inferred parameters.
pub fn decode_auto_with_info(&self, image: &DynamicImage) -> Result<AutoDecodedSymbol> {
let mut debug = DecodeAutoDebug::from_env();
let luma = image.to_luma8();
let width = luma.width();
let height = luma.height();
Expand All @@ -142,22 +145,42 @@ impl RasterDecoder {
}
let gcd = gcd_u32(width, height);
let mut candidates = module_candidates(gcd, self.options.module_px);
debug.log(format!(
"auto_infer start width={} height={} gcd={} module_candidates={:?}",
width, height, gcd, candidates
));
if candidates.is_empty() {
debug.log("auto_infer no module candidates");
return Err(DecodeError::AutoDetectFailed);
}
let thresholds = threshold_candidates(self.options.threshold, &luma);
let layouts = layout_candidates(self.options.layout);
debug.log(format!("auto_infer thresholds={:?}", thresholds));
debug.log(format!("auto_infer layouts={:?}", layouts));
for module_px in candidates.drain(..) {
let width_modules = width / module_px;
let height_modules = height / module_px;
debug.log(format!(
"candidate module_px={} width_modules={} height_modules={}",
module_px, width_modules, height_modules
));
for (quiet_zone_x, quiet_zone_y) in quiet_zone_candidates(width_modules, height_modules)
{
debug.log(format!(
" quiet_zone_x={} quiet_zone_y={}",
quiet_zone_x, quiet_zone_y
));
if width_modules <= quiet_zone_x * 2 || height_modules <= quiet_zone_y * 2 {
debug.log(" rejected: quiet zone consumes full image");
continue;
}
let symbol_width = width_modules - quiet_zone_x * 2;
let symbol_height = height_modules - quiet_zone_y * 2;
if !plausible_symbol_geometry(symbol_width, symbol_height) {
debug.log(format!(
" rejected: implausible symbol geometry {}x{}",
symbol_width, symbol_height
));
continue;
}
for layout in &layouts {
Expand All @@ -176,6 +199,10 @@ impl RasterDecoder {
symbol_width as u16,
symbol_height as u16,
) {
debug.log(format!(
" precheck failed layout={:?} threshold={}",
layout, threshold
));
continue;
}
let (matrix, bit_confidence) =
Expand All @@ -186,14 +213,24 @@ impl RasterDecoder {
quiet_zone_y,
) {
Ok(matrix) => matrix,
Err(_) => continue,
Err(_) => {
debug.log(" sample_matrix failed");
continue;
}
};
let suspect_bytes = suspect_bytes_from_bit_confidence(
&matrix,
&bit_confidence,
MAX_SUSPECT_BYTES,
);
if let Ok(decoded) = decode_matrix_with_suspects(&matrix, &suspect_bytes) {
debug.log(format!(
" success layout={:?} module_px={} quiet={} threshold={}",
layout,
module_px,
quiet_zone_x.min(quiet_zone_y),
threshold
));
return Ok(AutoDecodedSymbol {
decoded,
info: AutoDecodeInfo {
Expand All @@ -204,10 +241,18 @@ impl RasterDecoder {
},
});
}
debug.log(format!(
" decode failed layout={:?} module_px={} quiet={} threshold={}",
layout,
module_px,
quiet_zone_x.min(quiet_zone_y),
threshold
));
}
}
}
}
debug.log("auto_infer exhausted candidates -> AutoDetectFailed");
Err(DecodeError::AutoDetectFailed)
}

Expand Down Expand Up @@ -325,6 +370,44 @@ impl RasterDecoder {
}
}

#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, Default)]
struct DecodeAutoDebug {
dir: Option<PathBuf>,
}

#[cfg(not(target_arch = "wasm32"))]
impl DecodeAutoDebug {
fn from_env() -> Self {
let dir = std::env::var_os("GLYPHNET_SCAN_DEBUG_DIR").map(PathBuf::from);
if let Some(dir) = &dir {
let _ = std::fs::create_dir_all(dir);
}
Self { dir }
}

fn log(&mut self, line: impl AsRef<str>) {
let Some(dir) = &self.dir else { return };
let path = dir.join("06_decode_auto_infer.log");
let mut current = std::fs::read_to_string(&path).unwrap_or_default();
current.push_str(line.as_ref());
current.push('\n');
let _ = std::fs::write(path, current);
}
}

#[cfg(target_arch = "wasm32")]
#[derive(Debug, Default)]
struct DecodeAutoDebug;

#[cfg(target_arch = "wasm32")]
impl DecodeAutoDebug {
fn from_env() -> Self {
Self
}
fn log(&mut self, _line: impl AsRef<str>) {}
}

impl Default for RasterDecoder {
fn default() -> Self {
Self::new(DecodeOptions::default())
Expand Down
1 change: 1 addition & 0 deletions crates/glyphnet-scanner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ async = ["tokio"]
glyphnet-core = { path = "../glyphnet-core", version = "0.1.0" }
glyphnet-cv = { path = "../glyphnet-cv", version = "0.1.0" }
glyphnet-decode = { path = "../glyphnet-decode", version = "0.1.0" }
glyphnet-ecc = { path = "../glyphnet-ecc", version = "0.1.0" }
image.workspace = true
thiserror.workspace = true
tokio = { workspace = true, optional = true }
Expand Down
Loading
Loading