roxify 1.13.4 → 1.13.6

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.
@@ -5,6 +5,8 @@ use rayon::prelude::*;
5
5
  use serde::Serialize;
6
6
  use walkdir::WalkDir;
7
7
 
8
+ use crate::png_chunk_writer::{ChunkedIdatWriter, write_png_chunk};
9
+
8
10
  const PNG_HEADER: &[u8] = &[137, 80, 78, 71, 13, 10, 26, 10];
9
11
  const PIXEL_MAGIC: &[u8] = b"PXL1";
10
12
  const MARKER_START: [(u8, u8, u8); 3] = [(255, 0, 0), (0, 255, 0), (0, 0, 255)];
@@ -20,6 +22,7 @@ const PARALLEL_IO_FILE_THRESHOLD: u64 = MB;
20
22
  const PARALLEL_IO_BATCH_BYTES: u64 = 128 * MB;
21
23
  const PARALLEL_IO_BATCH_FILES: usize = 512;
22
24
  const PARALLEL_IO_MIN_FILES: usize = 8;
25
+ const HEADER_VERSION_V2: u8 = 2;
23
26
 
24
27
  pub type ProgressCallback = Box<dyn Fn(u64, u64, &str) + Send>;
25
28
 
@@ -313,14 +316,24 @@ fn estimate_zst_capacity(total_bytes: u64) -> usize {
313
316
 
314
317
  fn select_zstd_threads(total_bytes: u64) -> u32 {
315
318
  let max_threads = num_cpus::get().max(1) as u32;
316
- if total_bytes <= 32 * MB {
319
+ let ram_mb = crate::parse_linux_mem_available_mb().unwrap_or(4096);
320
+
321
+ // Aggressive multi-threading for Pyxelze speed target (<10s)
322
+ if total_bytes <= 16 * MB {
323
+ // Small files: single thread to avoid overhead
317
324
  1
318
- } else if total_bytes <= 128 * MB {
325
+ } else if total_bytes <= 64 * MB {
326
+ // Small-medium files: 2 threads
319
327
  max_threads.min(2)
320
- } else if total_bytes <= 512 * MB {
328
+ } else if total_bytes <= 256 * MB || ram_mb >= 8192 {
329
+ // Medium files or high RAM: up to 4 threads
321
330
  max_threads.min(4)
322
- } else {
331
+ } else if total_bytes <= 1024 * MB || ram_mb >= 4096 {
332
+ // Large files or medium RAM: up to 8 threads
323
333
  max_threads.min(8)
334
+ } else {
335
+ // Very large files: use all available cores up to 16
336
+ max_threads.min(16)
324
337
  }
325
338
  }
326
339
 
@@ -345,12 +358,12 @@ fn write_png_from_zst_mem(
345
358
 
346
359
  let encrypted_payload_len = enc_header_len + zst_size + hmac_trailer_len;
347
360
 
348
- let version = 1u8;
361
+ let version = HEADER_VERSION_V2;
349
362
  let name_bytes = name.map(|n| n.as_bytes()).unwrap_or(&[]);
350
363
  let name_len = name_bytes.len().min(255) as u8;
351
- let payload_len_bytes = (encrypted_payload_len as u32).to_be_bytes();
364
+ let payload_len_bytes = (encrypted_payload_len as u64).to_be_bytes();
352
365
 
353
- let mut meta_header = Vec::with_capacity(1 + 1 + name_len as usize + 4);
366
+ let mut meta_header = Vec::with_capacity(1 + 1 + name_len as usize + 8);
354
367
  meta_header.push(version);
355
368
  meta_header.push(name_len);
356
369
  if name_len > 0 {
@@ -398,13 +411,6 @@ fn write_png_from_zst_mem(
398
411
 
399
412
  let header_bytes = build_header_bytes(&meta_header, &enc_header_bytes);
400
413
 
401
- let stride = row_bytes + 1;
402
- let scanlines_total = height * stride;
403
-
404
- const MAX_BLOCK: usize = 65535;
405
- let num_blocks = (scanlines_total + MAX_BLOCK - 1) / MAX_BLOCK;
406
- let idat_len = 2 + num_blocks * 5 + scanlines_total + 4;
407
-
408
414
  let out_file = File::create(output_path)?;
409
415
  let buf_capacity = if total_data_bytes > 256 * 1024 * 1024 { 16 * 1024 * 1024 }
410
416
  else if total_data_bytes > 16 * 1024 * 1024 { 8 * 1024 * 1024 }
@@ -418,7 +424,7 @@ fn write_png_from_zst_mem(
418
424
  ihdr[4..8].copy_from_slice(&(height as u32).to_be_bytes());
419
425
  ihdr[8] = 8;
420
426
  ihdr[9] = 2;
421
- write_chunk_hdr(&mut w, b"IHDR", &ihdr)?;
427
+ write_png_chunk(&mut w, b"IHDR", &ihdr)?;
422
428
 
423
429
  let mut zst_reader = std::io::Cursor::new(zst_buf);
424
430
 
@@ -433,15 +439,14 @@ fn write_png_from_zst_mem(
433
439
  height,
434
440
  row_bytes,
435
441
  marker_end_pos,
436
- idat_len,
437
442
  total_data_bytes,
438
443
  progress,
439
444
  )?;
440
445
 
441
446
  if let Some(fl) = file_list {
442
- write_chunk_hdr(&mut w, b"rXFL", fl.as_bytes())?;
447
+ write_png_chunk(&mut w, b"rXFL", fl.as_bytes())?;
443
448
  }
444
- write_chunk_hdr(&mut w, b"IEND", &[])?;
449
+ write_png_chunk(&mut w, b"IEND", &[])?;
445
450
  w.flush()?;
446
451
 
447
452
  Ok(())
@@ -470,22 +475,16 @@ fn write_idat_streaming<W: Write, R: Read>(
470
475
  height: usize,
471
476
  row_bytes: usize,
472
477
  marker_end_pos: usize,
473
- idat_len: usize,
474
478
  total_data_bytes: usize,
475
479
  progress: &Option<ProgressCallback>,
476
480
  ) -> anyhow::Result<()> {
477
- w.write_all(&(idat_len as u32).to_be_bytes())?;
478
- w.write_all(b"IDAT")?;
479
-
480
- let mut crc = crc32fast::Hasher::new();
481
- crc.update(b"IDAT");
481
+ let mut idat = ChunkedIdatWriter::new(w);
482
482
 
483
483
  let stride = row_bytes + 1;
484
484
  let scanlines_total = height * stride;
485
485
 
486
486
  let zlib = [0x78u8, 0x01];
487
- w.write_all(&zlib)?;
488
- crc.update(&zlib);
487
+ idat.write_all(&zlib)?;
489
488
 
490
489
  let fl_chunk_data = file_list_chunk.unwrap_or(&[]);
491
490
  let payload_total = header_bytes.len() + zst_size + hmac_trailer_len + fl_chunk_data.len();
@@ -524,14 +523,12 @@ fn write_idat_streaming<W: Write, R: Read>(
524
523
  !block_size as u8,
525
524
  (!(block_size >> 8)) as u8,
526
525
  ];
527
- w.write_all(&header)?;
528
- crc.update(&header);
526
+ idat.write_all(&header)?;
529
527
  deflate_block_remaining = block_size;
530
528
  }
531
529
 
532
530
  let filter_byte = [0u8];
533
- w.write_all(&filter_byte)?;
534
- crc.update(&filter_byte);
531
+ idat.write_all(&filter_byte)?;
535
532
  adler.write(&filter_byte);
536
533
  scanline_pos += 1;
537
534
  deflate_block_remaining -= 1;
@@ -549,8 +546,7 @@ fn write_idat_streaming<W: Write, R: Read>(
549
546
  !block_size as u8,
550
547
  (!(block_size >> 8)) as u8,
551
548
  ];
552
- w.write_all(&header)?;
553
- crc.update(&header);
549
+ idat.write_all(&header)?;
554
550
  deflate_block_remaining = block_size;
555
551
  }
556
552
 
@@ -567,8 +563,7 @@ fn write_idat_streaming<W: Write, R: Read>(
567
563
  let me_remaining = 9 - me_offset;
568
564
  let take = need.min(me_remaining);
569
565
  let slice = &marker_end_bytes[me_offset..me_offset + take];
570
- w.write_all(slice)?;
571
- crc.update(slice);
566
+ idat.write_all(slice)?;
572
567
  adler.write(slice);
573
568
  flat_pos += take;
574
569
  chunk_written += take;
@@ -582,8 +577,7 @@ fn write_idat_streaming<W: Write, R: Read>(
582
577
  let avail = header_bytes.len() - header_pos;
583
578
  let take = need.min(avail);
584
579
  let slice = &header_bytes[header_pos..header_pos + take];
585
- w.write_all(slice)?;
586
- crc.update(slice);
580
+ idat.write_all(slice)?;
587
581
  adler.write(slice);
588
582
  header_pos += take;
589
583
  flat_pos += take;
@@ -599,8 +593,7 @@ fn write_idat_streaming<W: Write, R: Read>(
599
593
  if let Some(ref mut enc) = encryptor {
600
594
  enc.encrypt_chunk(&mut transfer_buf[..got]);
601
595
  }
602
- w.write_all(&transfer_buf[..got])?;
603
- crc.update(&transfer_buf[..got]);
596
+ idat.write_all(&transfer_buf[..got])?;
604
597
  adler.write(&transfer_buf[..got]);
605
598
  zst_remaining -= got;
606
599
  flat_pos += got;
@@ -618,8 +611,7 @@ fn write_idat_streaming<W: Write, R: Read>(
618
611
  let avail = hmac_trailer_len - hmac_pos;
619
612
  let take = need.min(avail);
620
613
  let slice = &hmac_bytes[hmac_pos..hmac_pos + take];
621
- w.write_all(slice)?;
622
- crc.update(slice);
614
+ idat.write_all(slice)?;
623
615
  adler.write(slice);
624
616
  hmac_pos += take;
625
617
  flat_pos += take;
@@ -637,8 +629,7 @@ fn write_idat_streaming<W: Write, R: Read>(
637
629
  let avail = fl_chunk_data.len() - fl_pos;
638
630
  let take = need.min(avail);
639
631
  let slice = &fl_chunk_data[fl_pos..fl_pos + take];
640
- w.write_all(slice)?;
641
- crc.update(slice);
632
+ idat.write_all(slice)?;
642
633
  adler.write(slice);
643
634
  fl_pos += take;
644
635
  flat_pos += take;
@@ -654,8 +645,7 @@ fn write_idat_streaming<W: Write, R: Read>(
654
645
  };
655
646
  let take = need.min(zero_remaining).min(buf_size).min(max_before_marker);
656
647
  if take == 0 { break; }
657
- w.write_all(&zero_buf[..take])?;
658
- crc.update(&zero_buf[..take]);
648
+ idat.write_all(&zero_buf[..take])?;
659
649
  adler.write(&zero_buf[..take]);
660
650
  zero_remaining -= take;
661
651
  flat_pos += take;
@@ -678,11 +668,8 @@ fn write_idat_streaming<W: Write, R: Read>(
678
668
 
679
669
  let adler_val = adler.finish();
680
670
  let adler_bytes = adler_val.to_be_bytes();
681
- w.write_all(&adler_bytes)?;
682
- crc.update(&adler_bytes);
683
-
684
- w.write_all(&crc.finalize().to_be_bytes())?;
685
- Ok(())
671
+ idat.write_all(&adler_bytes)?;
672
+ idat.finish()
686
673
  }
687
674
 
688
675
  fn build_marker_end_bytes() -> [u8; 9] {
@@ -695,13 +682,3 @@ fn build_marker_end_bytes() -> [u8; 9] {
695
682
  buf
696
683
  }
697
684
 
698
- fn write_chunk_hdr<W: Write>(w: &mut W, chunk_type: &[u8; 4], data: &[u8]) -> anyhow::Result<()> {
699
- w.write_all(&(data.len() as u32).to_be_bytes())?;
700
- w.write_all(chunk_type)?;
701
- w.write_all(data)?;
702
- let mut h = crc32fast::Hasher::new();
703
- h.update(chunk_type);
704
- h.update(data);
705
- w.write_all(&h.finalize().to_be_bytes())?;
706
- Ok(())
707
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.13.4",
3
+ "version": "1.13.6",
4
4
  "type": "module",
5
5
  "description": "Ultra-lightweight PNG steganography with native Rust acceleration. Encode binary data into PNG images with zstd compression.",
6
6
  "main": "dist/index.js",