roxify 1.9.3 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/dist/cli.js +4 -4
- package/dist/roxify_native.exe +0 -0
- package/native/png_utils.rs +13 -2
- package/native/reconstitution.rs +99 -18
- package/package.json +1 -1
- package/roxify_native-x86_64-pc-windows-msvc.node +0 -0
package/Cargo.toml
CHANGED
package/dist/cli.js
CHANGED
|
@@ -327,10 +327,10 @@ async function encodeCommand(args) {
|
|
|
327
327
|
inputSize = fstatSync(resolvedInputs[0]).size;
|
|
328
328
|
}
|
|
329
329
|
const outputSize = fstatSync(resolvedOutput).size;
|
|
330
|
-
const
|
|
330
|
+
const saved = (100 - (outputSize / inputSize) * 100).toFixed(1);
|
|
331
331
|
console.log(`\nSuccess!`);
|
|
332
332
|
console.log(` Input: ${(inputSize / 1024 / 1024).toFixed(2)} MB`);
|
|
333
|
-
console.log(` Output: ${(outputSize / 1024 / 1024).toFixed(2)} MB (${
|
|
333
|
+
console.log(` Output: ${(outputSize / 1024 / 1024).toFixed(2)} MB (${saved}% saved)`);
|
|
334
334
|
console.log(` Time: ${encodeTime}ms`);
|
|
335
335
|
console.log(` Saved: ${resolvedOutput}`);
|
|
336
336
|
console.log(' ');
|
|
@@ -541,10 +541,10 @@ async function encodeCommand(args) {
|
|
|
541
541
|
writeFileSync(resolvedOutput, output);
|
|
542
542
|
const outputSize = (output.length / 1024 / 1024).toFixed(2);
|
|
543
543
|
const inputSize = (inputSizeVal / 1024 / 1024).toFixed(2);
|
|
544
|
-
const
|
|
544
|
+
const saved = (100 - (output.length / inputSizeVal) * 100).toFixed(1);
|
|
545
545
|
console.log(`\nSuccess!`);
|
|
546
546
|
console.log(` Input: ${inputSize} MB`);
|
|
547
|
-
console.log(` Output: ${outputSize} MB (${
|
|
547
|
+
console.log(` Output: ${outputSize} MB (${saved}% saved)`);
|
|
548
548
|
console.log(` Time: ${encodeTime}ms`);
|
|
549
549
|
console.log(` Saved: ${resolvedOutput}`);
|
|
550
550
|
console.log(' ');
|
package/dist/roxify_native.exe
CHANGED
|
Binary file
|
package/native/png_utils.rs
CHANGED
|
@@ -142,8 +142,19 @@ pub fn extract_payload_from_png(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
|
142
142
|
return Ok(payload);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
let reconst = crate::reconstitution::crop_and_reconstitute(png_data)
|
|
146
|
-
|
|
145
|
+
if let Ok(reconst) = crate::reconstitution::crop_and_reconstitute(png_data) {
|
|
146
|
+
if let Ok(payload) = extract_payload_direct(&reconst) {
|
|
147
|
+
if validate_payload_deep(&payload) {
|
|
148
|
+
return Ok(payload);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
let unstretched = crate::reconstitution::unstretch_nn(png_data)?;
|
|
153
|
+
let payload = extract_payload_direct(&unstretched)?;
|
|
154
|
+
if validate_payload_deep(&payload) {
|
|
155
|
+
return Ok(payload);
|
|
156
|
+
}
|
|
157
|
+
Err("No valid payload found after all extraction attempts".to_string())
|
|
147
158
|
}
|
|
148
159
|
|
|
149
160
|
fn validate_payload_deep(payload: &[u8]) -> bool {
|
package/native/reconstitution.rs
CHANGED
|
@@ -44,14 +44,13 @@ fn intra_block_transitions_h(
|
|
|
44
44
|
get_pixel: &impl Fn(u32, u32) -> [u8; 4],
|
|
45
45
|
sx: u32, ex: u32, y: u32, candidate: u32,
|
|
46
46
|
) -> u32 {
|
|
47
|
-
let pw = (ex - sx) as
|
|
48
|
-
let lw = candidate as
|
|
49
|
-
let ratio = lw / pw;
|
|
47
|
+
let pw = (ex - sx) as f64;
|
|
48
|
+
let lw = candidate as f64;
|
|
50
49
|
let row: Vec<[u8; 4]> = (sx..ex).map(|x| get_pixel(x, y)).collect();
|
|
51
50
|
let mut count = 0u32;
|
|
52
51
|
for i in 1..row.len() {
|
|
53
|
-
let lx_prev = ((i as
|
|
54
|
-
let lx_curr = ((i as
|
|
52
|
+
let lx_prev = ((i as f64 - 0.5) * lw / pw) as u32;
|
|
53
|
+
let lx_curr = ((i as f64 + 0.5) * lw / pw) as u32;
|
|
55
54
|
if lx_prev == lx_curr && color_dist(row[i], row[i - 1]) > 0 {
|
|
56
55
|
count += 1;
|
|
57
56
|
}
|
|
@@ -63,14 +62,13 @@ fn intra_block_transitions_v(
|
|
|
63
62
|
get_pixel: &impl Fn(u32, u32) -> [u8; 4],
|
|
64
63
|
x: u32, sy: u32, ey: u32, candidate: u32,
|
|
65
64
|
) -> u32 {
|
|
66
|
-
let ph = (ey - sy) as
|
|
67
|
-
let lh = candidate as
|
|
68
|
-
let ratio = lh / ph;
|
|
65
|
+
let ph = (ey - sy) as f64;
|
|
66
|
+
let lh = candidate as f64;
|
|
69
67
|
let col: Vec<[u8; 4]> = (sy..ey).map(|y| get_pixel(x, y)).collect();
|
|
70
68
|
let mut count = 0u32;
|
|
71
69
|
for i in 1..col.len() {
|
|
72
|
-
let ly_prev = ((i as
|
|
73
|
-
let ly_curr = ((i as
|
|
70
|
+
let ly_prev = ((i as f64 - 0.5) * lh / ph) as u32;
|
|
71
|
+
let ly_curr = ((i as f64 + 0.5) * lh / ph) as u32;
|
|
74
72
|
if ly_prev == ly_curr && color_dist(col[i], col[i - 1]) > 0 {
|
|
75
73
|
count += 1;
|
|
76
74
|
}
|
|
@@ -185,6 +183,7 @@ pub fn crop_and_reconstitute(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
|
|
186
|
+
|
|
188
187
|
let mut best_logical_w = 0u32;
|
|
189
188
|
let mut best_logical_h = 0u32;
|
|
190
189
|
// Score: (size_err_permille, inverse_area) — lower is better
|
|
@@ -206,7 +205,9 @@ pub fn crop_and_reconstitute(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
|
206
205
|
|
|
207
206
|
let phys_w = ex - sx;
|
|
208
207
|
let phys_h = ey - sy;
|
|
209
|
-
if phys_w < 3 || phys_h < 3 || phys_w > 1800 || phys_h > 1800 {
|
|
208
|
+
if phys_w < 3 || phys_h < 3 || phys_w > 1800 || phys_h > 1800 {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
// Estimation depuis les marqueurs (start_marker_w ≈ 3 × scale_x, end_marker_w ≈ 3 × scale_x)
|
|
@@ -258,7 +259,9 @@ pub fn crop_and_reconstitute(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
|
258
259
|
// Vérifie cohérence grossière
|
|
259
260
|
let lw_diff_lo = (lw_cand_lo as f64 - est_lw_f).abs();
|
|
260
261
|
let lw_diff_hi = (lw_cand_hi as f64 - est_lw_f).abs();
|
|
261
|
-
if lw_diff_lo > est_lw_f * 0.25 + 3.0 && lw_diff_hi > est_lw_f * 0.25 + 3.0 {
|
|
262
|
+
if lw_diff_lo > est_lw_f * 0.25 + 3.0 && lw_diff_hi > est_lw_f * 0.25 + 3.0 {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
262
265
|
|
|
263
266
|
let n_scan = 7u32;
|
|
264
267
|
let lw = if lw_cand_lo == lw_cand_hi {
|
|
@@ -318,10 +321,14 @@ pub fn crop_and_reconstitute(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
|
318
321
|
};
|
|
319
322
|
|
|
320
323
|
// Filtre taille min logique
|
|
321
|
-
if lw < 3 || lh < 3 {
|
|
324
|
+
if lw < 3 || lh < 3 {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
322
327
|
|
|
323
328
|
let size_err = lw_size_err;
|
|
324
|
-
if size_err > 500 {
|
|
329
|
+
if size_err > 500 {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
325
332
|
|
|
326
333
|
// Filtre final : l'intra-block score pour le lw sélectionné doit être faible
|
|
327
334
|
// (pour une vraie paire avec l'image NN-scalée, = 0; pour une zone de fond aléatoire, >> 0)
|
|
@@ -333,9 +340,11 @@ pub fn crop_and_reconstitute(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
|
333
340
|
// Filtre : la zone encodée NN a des blocs monochromes → intra très bas
|
|
334
341
|
// Zone de fond aléatoire → intra ≈ lw × n_scan × 0.99 >> 0
|
|
335
342
|
// Seuil : lw/8 × n_scan pour permettre ≈ lw/8 pixels parasites
|
|
336
|
-
let intra_threshold = (lw as u32 /
|
|
343
|
+
let intra_threshold = ((lw as u32 / 4 + 2) * n_scan).max(n_scan * 3);
|
|
337
344
|
|
|
338
|
-
if intra_final > intra_threshold {
|
|
345
|
+
if intra_final > intra_threshold {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
339
348
|
|
|
340
349
|
// Favori : pas d'ajustement de bord, puis plus grande zone, puis plus petit size_err
|
|
341
350
|
let area = phys_w as u64 * phys_h as u64;
|
|
@@ -406,6 +415,77 @@ pub fn crop_and_reconstitute(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
|
406
415
|
Ok(output)
|
|
407
416
|
}
|
|
408
417
|
|
|
418
|
+
pub fn unstretch_nn(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
419
|
+
let img = image::load_from_memory(png_data)
|
|
420
|
+
.map_err(|e| format!("image load error: {}", e))?;
|
|
421
|
+
let rgba = img.to_rgba8();
|
|
422
|
+
let width = rgba.width() as usize;
|
|
423
|
+
let height = rgba.height() as usize;
|
|
424
|
+
if width == 0 || height == 0 {
|
|
425
|
+
return Err("empty image".to_string());
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
let get = |x: usize, y: usize| -> [u8; 4] {
|
|
429
|
+
let p = rgba.get_pixel(x as u32, y as u32);
|
|
430
|
+
[p[0], p[1], p[2], p[3]]
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
let mut unique_rows: Vec<usize> = Vec::new();
|
|
434
|
+
for y in 0..height {
|
|
435
|
+
if unique_rows.is_empty() {
|
|
436
|
+
unique_rows.push(y);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
let prev_y = *unique_rows.last().unwrap();
|
|
440
|
+
let mut same = true;
|
|
441
|
+
for x in 0..width {
|
|
442
|
+
if get(x, y) != get(x, prev_y) {
|
|
443
|
+
same = false;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if !same {
|
|
448
|
+
unique_rows.push(y);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
let logical_h = unique_rows.len();
|
|
453
|
+
if logical_h == 0 {
|
|
454
|
+
return Err("no unique rows".to_string());
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
let first_row_y = unique_rows[0];
|
|
458
|
+
let mut col_indices: Vec<usize> = Vec::new();
|
|
459
|
+
for x in 0..width {
|
|
460
|
+
if col_indices.is_empty() {
|
|
461
|
+
col_indices.push(x);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
let prev_x = *col_indices.last().unwrap();
|
|
465
|
+
if get(x, first_row_y) != get(prev_x, first_row_y) {
|
|
466
|
+
col_indices.push(x);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
let logical_w = col_indices.len();
|
|
471
|
+
if logical_w < 2 || logical_h < 2 {
|
|
472
|
+
return Err("unstretched image too small".to_string());
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
let mut out = RgbaImage::new(logical_w as u32, logical_h as u32);
|
|
476
|
+
for (ly, &py) in unique_rows.iter().enumerate() {
|
|
477
|
+
for (lx, &px) in col_indices.iter().enumerate() {
|
|
478
|
+
let p = get(px, py);
|
|
479
|
+
out.put_pixel(lx as u32, ly as u32, Rgba(p));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
let mut output = Vec::new();
|
|
484
|
+
out.write_to(&mut std::io::Cursor::new(&mut output), image::ImageFormat::Png)
|
|
485
|
+
.map_err(|e| format!("PNG write error: {}", e))?;
|
|
486
|
+
Ok(output)
|
|
487
|
+
}
|
|
488
|
+
|
|
409
489
|
#[cfg(test)]
|
|
410
490
|
mod tests {
|
|
411
491
|
use image::{RgbaImage, Rgba, imageops};
|
|
@@ -521,12 +601,13 @@ mod test_transitions {
|
|
|
521
601
|
[p[0], p[1], p[2], p[3]]
|
|
522
602
|
};
|
|
523
603
|
let intra_50 = super::intra_block_transitions_h(&get_pixel, 0, pw, 0, 50);
|
|
524
|
-
|
|
604
|
+
let tolerance = 5u32;
|
|
605
|
+
if intra_50 > tolerance {
|
|
525
606
|
println!("FAIL: scale_x={scale_x:.1} pw={pw} lw=50: intra={intra_50}");
|
|
526
607
|
had_failure = true;
|
|
527
608
|
}
|
|
528
609
|
}
|
|
529
|
-
assert!(!had_failure, "Some scale_x values gave intra >
|
|
610
|
+
assert!(!had_failure, "Some scale_x values gave intra > tolerance for true lw=50");
|
|
530
611
|
}
|
|
531
612
|
|
|
532
613
|
|
package/package.json
CHANGED
|
Binary file
|