@@ -24,23 +24,6 @@ pub(crate) fn decode_candidate(
2424 if let Ok ( decoded) = decode_fractional_ribbon_candidate ( image) {
2525 return Ok ( decoded) ;
2626 }
27- let target_module_px = 4 ;
28- let resized = image:: imageops:: resize (
29- image,
30- 104 * target_module_px,
31- 44 * target_module_px,
32- image:: imageops:: FilterType :: Triangle ,
33- ) ;
34- let resized = DynamicImage :: ImageRgba8 ( resized) ;
35- let normalized_region = ScanRegion {
36- x : 0 ,
37- y : 0 ,
38- width : 104 * target_module_px,
39- height : 44 * target_module_px,
40- } ;
41- if let Ok ( decoded) = decode_exact_ribbon_candidate ( & resized, normalized_region) {
42- return Ok ( decoded) ;
43- }
4427 return Err ( DecodeError :: AutoDetectFailed ) ;
4528 }
4629
@@ -117,92 +100,112 @@ fn decode_exact_ribbon_candidate(
117100 image : & DynamicImage ,
118101 region : ScanRegion ,
119102) -> std:: result:: Result < AutoDecodedSymbol , DecodeError > {
120- if region. width >= 104 && region. height >= 44 {
121- let module_px = ( region. width / 104 ) . max ( 1 ) ;
122- if region. width == 104 * module_px && region. height == 44 * module_px {
123- for threshold in [ 160 , 192 , 224 ] {
124- let exact = RasterDecoder :: new ( DecodeOptions {
125- module_px,
126- quiet_zone_modules : 4 ,
127- threshold,
128- layout : LayoutFamily :: RibbonWeave ,
103+ for module_px in ribbon_module_px_candidates ( region) {
104+ let symbol_width = region. width / module_px - 8 ;
105+ let symbol_height = region. height / module_px - 8 ;
106+ if !reference_ribbon_geometry ( symbol_width, symbol_height) {
107+ continue ;
108+ }
109+ for threshold in [ 160 , 192 , 224 ] {
110+ let exact = RasterDecoder :: new ( DecodeOptions {
111+ module_px,
112+ quiet_zone_modules : 4 ,
113+ threshold,
114+ layout : LayoutFamily :: RibbonWeave ,
115+ } ) ;
116+ if let Ok ( decoded) = exact. decode ( image) {
117+ return Ok ( AutoDecodedSymbol {
118+ decoded,
119+ info : glyphnet_decode:: AutoDecodeInfo {
120+ module_px,
121+ quiet_zone_modules : 4 ,
122+ threshold,
123+ layout : LayoutFamily :: RibbonWeave ,
124+ } ,
129125 } ) ;
130- if let Ok ( decoded) = exact. decode ( image) {
131- return Ok ( AutoDecodedSymbol {
132- decoded,
133- info : glyphnet_decode:: AutoDecodeInfo {
134- module_px,
135- quiet_zone_modules : 4 ,
136- threshold,
137- layout : LayoutFamily :: RibbonWeave ,
138- } ,
139- } ) ;
140- }
141126 }
142127 }
143128 }
144129 Err ( DecodeError :: AutoDetectFailed )
145130}
146131
132+ fn ribbon_module_px_candidates ( region : ScanRegion ) -> Vec < u32 > {
133+ let gcd = gcd_u32 ( region. width , region. height ) ;
134+ let mut candidates = Vec :: new ( ) ;
135+ for module_px in 1 ..=32 {
136+ if gcd % module_px != 0 {
137+ continue ;
138+ }
139+ let width_modules = region. width / module_px;
140+ let height_modules = region. height / module_px;
141+ if width_modules <= 8 || height_modules <= 8 {
142+ continue ;
143+ }
144+ if reference_ribbon_geometry ( width_modules - 8 , height_modules - 8 ) {
145+ candidates. push ( module_px) ;
146+ }
147+ }
148+ candidates. sort_unstable_by ( |a, b| b. cmp ( a) ) ;
149+ candidates
150+ }
151+
147152fn decode_fractional_ribbon_candidate (
148153 image : & DynamicImage ,
149154) -> std:: result:: Result < AutoDecodedSymbol , DecodeError > {
150- const SYMBOL_WIDTH : u16 = 96 ;
151- const SYMBOL_HEIGHT : u16 = 36 ;
152- const TOTAL_WIDTH_MODULES : f32 = 104.0 ;
153- const TOTAL_HEIGHT_MODULES : f32 = 44.0 ;
154155 const QUIET_MODULES : f32 = 4.0 ;
155156
156157 let luma = image. to_luma8 ( ) ;
157158 if luma. width ( ) < 104 || luma. height ( ) < 44 {
158159 return Err ( DecodeError :: AutoDetectFailed ) ;
159160 }
160- let base_scale_x = luma. width ( ) as f32 / TOTAL_WIDTH_MODULES ;
161- let base_scale_y = luma. height ( ) as f32 / TOTAL_HEIGHT_MODULES ;
162- if base_scale_x < 1.0 || base_scale_y < 1.0 {
163- return Err ( DecodeError :: AutoDetectFailed ) ;
164- }
165161
166162 let otsu = fractional_threshold ( & luma) ;
167163 let integral = IntegralGray :: new ( & luma) ;
168164 let mut thresholds = vec ! [ otsu, 160 , 192 , 224 ] ;
169165 thresholds. sort_unstable ( ) ;
170166 thresholds. dedup ( ) ;
171167
172- for scale_adjust in [ 1.0_f32 , 0.985 , 1.015 , 0.97 , 1.03 ] {
173- let scale_x = base_scale_x * scale_adjust ;
174- let scale_y = base_scale_y * scale_adjust ;
175- if scale_x < 1.0 || scale_y < 1.0 {
168+ for geometry in fractional_ribbon_geometry_candidates ( luma . width ( ) , luma . height ( ) ) {
169+ let base_scale_x = luma . width ( ) as f32 / geometry . total_width_modules ( ) as f32 ;
170+ let base_scale_y = luma . height ( ) as f32 / geometry . total_height_modules ( ) as f32 ;
171+ if base_scale_x < 1.0 || base_scale_y < 1.0 {
176172 continue ;
177173 }
178- for y_shift in module_shifts ( 3 ) {
179- for x_shift in module_shifts ( 2 ) {
180- let origin_x = QUIET_MODULES + x_shift;
181- let origin_y = QUIET_MODULES + y_shift;
182- if origin_x < -2.0 || origin_y < -8.0 {
183- continue ;
184- }
185- if !fractional_grid_fits (
186- & luma,
187- origin_x,
188- origin_y,
189- scale_x,
190- scale_y,
191- SYMBOL_WIDTH ,
192- SYMBOL_HEIGHT ,
193- ) {
194- continue ;
195- }
196- for & threshold in & thresholds {
197- if !fractional_header_precheck (
198- & integral, origin_x, origin_y, scale_x, scale_y, threshold,
199- ) {
174+ for scale_adjust in [ 1.0_f32 , 0.985 , 1.015 , 0.97 , 1.03 ] {
175+ let scale_x = base_scale_x * scale_adjust;
176+ let scale_y = base_scale_y * scale_adjust;
177+ if scale_x < 1.0 || scale_y < 1.0 {
178+ continue ;
179+ }
180+ for y_shift in module_shifts ( 3 ) {
181+ for x_shift in module_shifts ( 3 ) {
182+ let origin_x = QUIET_MODULES + x_shift;
183+ let origin_y = QUIET_MODULES + y_shift;
184+ if origin_x < -2.0 || origin_y < -8.0 {
200185 continue ;
201186 }
202- if let Ok ( decoded) = decode_fractional_with_params (
203- & integral, origin_x, origin_y, scale_x, scale_y, threshold,
187+ if !fractional_grid_fits (
188+ & luma,
189+ origin_x,
190+ origin_y,
191+ scale_x,
192+ scale_y,
193+ geometry. symbol_width ,
194+ geometry. symbol_height ,
204195 ) {
205- return Ok ( decoded) ;
196+ continue ;
197+ }
198+ for & threshold in & thresholds {
199+ if !fractional_header_precheck (
200+ & integral, geometry, origin_x, origin_y, scale_x, scale_y, threshold,
201+ ) {
202+ continue ;
203+ }
204+ if let Ok ( decoded) = decode_fractional_with_params (
205+ & integral, geometry, origin_x, origin_y, scale_x, scale_y, threshold,
206+ ) {
207+ return Ok ( decoded) ;
208+ }
206209 }
207210 }
208211 }
@@ -212,28 +215,77 @@ fn decode_fractional_ribbon_candidate(
212215 Err ( DecodeError :: AutoDetectFailed )
213216}
214217
218+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
219+ struct RibbonGeometry {
220+ symbol_width : u16 ,
221+ symbol_height : u16 ,
222+ }
223+
224+ impl RibbonGeometry {
225+ const fn total_width_modules ( self ) -> u16 {
226+ self . symbol_width + 8
227+ }
228+
229+ const fn total_height_modules ( self ) -> u16 {
230+ self . symbol_height + 8
231+ }
232+ }
233+
234+ fn fractional_ribbon_geometry_candidates (
235+ image_width : u32 ,
236+ image_height : u32 ,
237+ ) -> Vec < RibbonGeometry > {
238+ const DEFAULT_PRINT_RIBBON : RibbonGeometry = RibbonGeometry {
239+ symbol_width : 96 ,
240+ symbol_height : 36 ,
241+ } ;
242+
243+ let scale_x = image_width as f32 / DEFAULT_PRINT_RIBBON . total_width_modules ( ) as f32 ;
244+ let scale_y = image_height as f32 / DEFAULT_PRINT_RIBBON . total_height_modules ( ) as f32 ;
245+ if scale_x >= 1.0 && scale_y >= 1.0 && ( 0.6 ..=1.7 ) . contains ( & ( scale_x / scale_y) ) {
246+ vec ! [ DEFAULT_PRINT_RIBBON ]
247+ } else {
248+ Vec :: new ( )
249+ }
250+ }
251+
252+ fn reference_ribbon_geometry ( width : u32 , height : u32 ) -> bool {
253+ width >= 96
254+ && height >= 28
255+ && width % 4 == 0
256+ && height % 4 == 0
257+ && ( 2.0 ..=8.0 ) . contains ( & ( width as f32 / height. max ( 1 ) as f32 ) )
258+ }
259+
260+ fn gcd_u32 ( mut a : u32 , mut b : u32 ) -> u32 {
261+ while b != 0 {
262+ let tmp = a % b;
263+ a = b;
264+ b = tmp;
265+ }
266+ a
267+ }
268+
215269fn module_shifts ( radius : i32 ) -> impl Iterator < Item = f32 > {
216270 ( -radius * 2 ..=radius * 2 ) . map ( |value| value as f32 * 0.5 )
217271}
218272
219273fn fractional_header_precheck (
220274 integral : & IntegralGray ,
275+ geometry : RibbonGeometry ,
221276 origin_x_modules : f32 ,
222277 origin_y_modules : f32 ,
223278 scale_x : f32 ,
224279 scale_y : f32 ,
225280 threshold : u8 ,
226281) -> bool {
227- const SYMBOL_WIDTH : u16 = 96 ;
228- const SYMBOL_HEIGHT : u16 = 36 ;
229-
230282 let mut bits = Vec :: with_capacity ( HEADER_LEN * 8 ) ;
231- ' rows: for y in 0 ..SYMBOL_HEIGHT {
232- for x in 0 ..SYMBOL_WIDTH {
283+ ' rows: for y in 0 ..geometry . symbol_height {
284+ for x in 0 ..geometry . symbol_width {
233285 if !layout:: is_data_module_for (
234286 LayoutFamily :: RibbonWeave ,
235- SYMBOL_WIDTH ,
236- SYMBOL_HEIGHT ,
287+ geometry . symbol_width ,
288+ geometry . symbol_height ,
237289 x,
238290 y,
239291 ) {
@@ -279,23 +331,24 @@ fn fractional_grid_fits(
279331
280332fn decode_fractional_with_params (
281333 integral : & IntegralGray ,
334+ geometry : RibbonGeometry ,
282335 origin_x_modules : f32 ,
283336 origin_y_modules : f32 ,
284337 scale_x : f32 ,
285338 scale_y : f32 ,
286339 threshold : u8 ,
287340) -> std:: result:: Result < AutoDecodedSymbol , DecodeError > {
288- const SYMBOL_WIDTH : u16 = 96 ;
289- const SYMBOL_HEIGHT : u16 = 36 ;
290-
291- let mut matrix =
292- SymbolMatrix :: with_layout ( SYMBOL_WIDTH , SYMBOL_HEIGHT , LayoutFamily :: RibbonWeave ) ;
293- for y in 0 ..SYMBOL_HEIGHT {
294- for x in 0 ..SYMBOL_WIDTH {
341+ let mut matrix = SymbolMatrix :: with_layout (
342+ geometry . symbol_width ,
343+ geometry . symbol_height ,
344+ LayoutFamily :: RibbonWeave ,
345+ ) ;
346+ for y in 0 ..geometry . symbol_height {
347+ for x in 0 ..geometry . symbol_width {
295348 if let Some ( cell) = layout:: function_cell_for (
296349 LayoutFamily :: RibbonWeave ,
297- SYMBOL_WIDTH ,
298- SYMBOL_HEIGHT ,
350+ geometry . symbol_width ,
351+ geometry . symbol_height ,
299352 x,
300353 y,
301354 ) {
0 commit comments