roxify 1.12.8 → 1.12.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "roxify_native"
3
- version = "1.12.8"
3
+ version = "1.12.9"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
package/native/encoder.rs CHANGED
@@ -199,7 +199,7 @@ fn encode_to_png_with_encryption_name_and_filelist_internal(
199
199
  let padded_len = raw_payload_len + padding_needed;
200
200
 
201
201
  let marker_start_len = 12;
202
- let marker_end_len = 9;
202
+ let marker_end_bytes = 9;
203
203
 
204
204
  let data_with_markers_len = marker_start_len + padded_len;
205
205
  let data_pixels = (data_with_markers_len + 2) / 3;
@@ -212,9 +212,9 @@ fn encode_to_png_with_encryption_name_and_filelist_internal(
212
212
  let height = side;
213
213
 
214
214
  let total_data_bytes = width * height * 3;
215
- let marker_end_pos = (height - 1) * width * 3 + (width - end_marker_pixels) * 3;
215
+ let marker_end_pos = total_data_bytes - marker_end_bytes;
216
216
 
217
- let flat = build_flat_pixel_buffer(&meta_pixel, padding_needed, marker_end_pos, marker_end_len, total_data_bytes);
217
+ let flat = build_flat_pixel_buffer(&meta_pixel, marker_end_pos, total_data_bytes);
218
218
  drop(meta_pixel);
219
219
 
220
220
  let row_bytes = width * 3;
@@ -226,9 +226,7 @@ fn encode_to_png_with_encryption_name_and_filelist_internal(
226
226
 
227
227
  fn build_flat_pixel_buffer(
228
228
  meta_pixel: &[u8],
229
- _padding_needed: usize,
230
229
  marker_end_pos: usize,
231
- marker_end_len: usize,
232
230
  total_data_bytes: usize,
233
231
  ) -> Vec<u8> {
234
232
  let mut flat = vec![0u8; total_data_bytes];
@@ -246,24 +244,14 @@ fn build_flat_pixel_buffer(
246
244
 
247
245
  flat[pos..pos + meta_pixel.len()].copy_from_slice(meta_pixel);
248
246
 
249
- if marker_end_pos + marker_end_len <= total_data_bytes {
250
- for (i, m) in MARKER_END.iter().enumerate() {
251
- let off = marker_end_pos + i * 3;
252
- flat[off] = m.0; flat[off + 1] = m.1; flat[off + 2] = m.2;
253
- }
247
+ for (i, m) in MARKER_END.iter().enumerate() {
248
+ let off = marker_end_pos + i * 3;
249
+ flat[off] = m.0; flat[off + 1] = m.1; flat[off + 2] = m.2;
254
250
  }
255
251
 
256
252
  flat
257
253
  }
258
254
 
259
- fn build_meta_pixel(payload: &[u8]) -> Result<Vec<u8>> {
260
- build_meta_pixel_with_name(payload, None)
261
- }
262
-
263
- fn build_meta_pixel_with_name(payload: &[u8], name: Option<&str>) -> Result<Vec<u8>> {
264
- build_meta_pixel_with_name_and_filelist(payload, name, None)
265
- }
266
-
267
255
  fn build_meta_pixel_with_name_and_filelist(payload: &[u8], name: Option<&str>, file_list: Option<&str>) -> Result<Vec<u8>> {
268
256
  let version = 1u8;
269
257
  let name_bytes = name.map(|n| n.as_bytes()).unwrap_or(&[]);
@@ -333,38 +321,6 @@ fn write_chunk(out: &mut Vec<u8>, chunk_type: &[u8; 4], data: &[u8]) -> Result<(
333
321
  Ok(())
334
322
  }
335
323
 
336
- fn create_raw_deflate(data: &[u8]) -> Vec<u8> {
337
- const MAX_BLOCK: usize = 65535;
338
- let num_blocks = (data.len() + MAX_BLOCK - 1) / MAX_BLOCK;
339
- let total_size = 2 + num_blocks * 5 + data.len() + 4;
340
- let mut result = Vec::with_capacity(total_size);
341
-
342
- result.push(0x78);
343
- result.push(0x01);
344
-
345
- let mut offset = 0;
346
- while offset < data.len() {
347
- let chunk_size = (data.len() - offset).min(MAX_BLOCK);
348
- let is_last = offset + chunk_size >= data.len();
349
-
350
- let header = [
351
- if is_last { 0x01 } else { 0x00 },
352
- chunk_size as u8,
353
- (chunk_size >> 8) as u8,
354
- !chunk_size as u8,
355
- (!(chunk_size >> 8)) as u8,
356
- ];
357
- result.extend_from_slice(&header);
358
- result.extend_from_slice(&data[offset..offset + chunk_size]);
359
- offset += chunk_size;
360
- }
361
-
362
- let adler = crate::core::adler32_bytes(data);
363
- result.extend_from_slice(&adler.to_be_bytes());
364
-
365
- result
366
- }
367
-
368
324
  fn create_raw_deflate_from_rows(flat: &[u8], row_bytes: usize, height: usize) -> Vec<u8> {
369
325
  let stride = row_bytes + 1;
370
326
  let scanlines_total = height * stride;
@@ -410,35 +366,6 @@ fn create_raw_deflate_from_rows(flat: &[u8], row_bytes: usize, height: usize) ->
410
366
  result
411
367
  }
412
368
 
413
- fn predict_best_format(data: &[u8]) -> ImageFormat {
414
- if data.len() < 2048 {
415
- return ImageFormat::Png;
416
- }
417
-
418
- let sample_size = data.len().min(4096);
419
- let sample = &data[..sample_size];
420
-
421
- let entropy = calculate_shannon_entropy(sample);
422
- let repetition_score = detect_repetition_patterns(sample);
423
-
424
- let unique_bytes = count_unique_bytes(sample);
425
- let unique_ratio = unique_bytes as f64 / 256.0;
426
-
427
- let is_sequential = detect_sequential_pattern(sample);
428
-
429
- if entropy > 7.8 {
430
- ImageFormat::Png
431
- } else if is_sequential || repetition_score > 0.2 {
432
- ImageFormat::JpegXL
433
- } else if unique_ratio < 0.3 && entropy < 6.5 {
434
- ImageFormat::JpegXL
435
- } else if entropy < 5.5 {
436
- ImageFormat::JpegXL
437
- } else {
438
- ImageFormat::Png
439
- }
440
- }
441
-
442
369
  fn detect_sequential_pattern(data: &[u8]) -> bool {
443
370
  if data.len() < 256 {
444
371
  return false;
@@ -548,6 +475,39 @@ mod tests {
548
475
  if decompressed.starts_with(b"ROX1") { decompressed = decompressed[4..].to_vec(); }
549
476
  assert_eq!(decompressed, data);
550
477
  }
478
+
479
+ #[test]
480
+ fn test_marker_end_in_last_3_pixels() {
481
+ use image::ImageReader;
482
+ use std::io::Cursor;
483
+
484
+ for size in &[11, 100, 1000, 5000, 50000] {
485
+ let data: Vec<u8> = (0..*size).map(|i| (i % 256) as u8).collect();
486
+
487
+ let png_raw = encode_to_png_raw(&data, 3).expect("encode raw");
488
+ let png_auto = encode_to_png(&data, 3).expect("encode auto");
489
+
490
+ for (label, png) in &[("raw", &png_raw), ("auto", &png_auto)] {
491
+ let reader = ImageReader::new(Cursor::new(*png))
492
+ .with_guessed_format().unwrap();
493
+ let img = reader.decode().unwrap();
494
+ let rgb = img.to_rgb8();
495
+ let w = rgb.width();
496
+ let h = rgb.height();
497
+
498
+ let p1 = rgb.get_pixel(w - 3, h - 1);
499
+ let p2 = rgb.get_pixel(w - 2, h - 1);
500
+ let p3 = rgb.get_pixel(w - 1, h - 1);
501
+
502
+ assert_eq!([p1[0], p1[1], p1[2]], [0, 0, 255],
503
+ "MARKER_END pixel 0 (blue) wrong for {}@size={}, got {:?}", label, size, p1);
504
+ assert_eq!([p2[0], p2[1], p2[2]], [0, 255, 0],
505
+ "MARKER_END pixel 1 (green) wrong for {}@size={}, got {:?}", label, size, p2);
506
+ assert_eq!([p3[0], p3[1], p3[2]], [255, 0, 0],
507
+ "MARKER_END pixel 2 (red) wrong for {}@size={}, got {:?}", label, size, p3);
508
+ }
509
+ }
510
+ }
551
511
  }
552
512
 
553
513
  fn detect_repetition_patterns(data: &[u8]) -> f64 {
@@ -575,27 +535,6 @@ fn detect_repetition_patterns(data: &[u8]) -> f64 {
575
535
  }
576
536
  }
577
537
 
578
- fn optimize_format(png_data: &[u8]) -> Result<Vec<u8>> {
579
- let formats = [
580
- ("webp", optimize_to_webp(png_data)),
581
- ("jxl", optimize_to_jxl(png_data)),
582
- ];
583
-
584
- let mut best = png_data.to_vec();
585
- let mut best_size = png_data.len();
586
-
587
- for (_name, result) in formats {
588
- if let Ok(optimized) = result {
589
- if optimized.len() < best_size {
590
- best = optimized;
591
- best_size = best.len();
592
- }
593
- }
594
- }
595
-
596
- Ok(best)
597
- }
598
-
599
538
  fn optimize_to_webp(png_data: &[u8]) -> Result<Vec<u8>> {
600
539
  use std::fs;
601
540
 
@@ -41,6 +41,7 @@ pub fn encode_to_png_file(
41
41
  let padded_len = raw_payload_len + padding_needed;
42
42
 
43
43
  let marker_start_len = 12;
44
+ let marker_end_bytes = 9;
44
45
  let data_with_markers_len = marker_start_len + padded_len;
45
46
  let data_pixels = (data_with_markers_len + 2) / 3;
46
47
  let end_marker_pixels = 3;
@@ -52,9 +53,9 @@ pub fn encode_to_png_file(
52
53
  let height = side;
53
54
  let row_bytes = width * 3;
54
55
  let total_data_bytes = width * height * 3;
55
- let marker_end_pos = (height - 1) * width * 3 + (width - end_marker_pixels) * 3;
56
+ let marker_end_pos = total_data_bytes - marker_end_bytes;
56
57
 
57
- let flat = build_flat_buffer(&meta_pixel, padding_needed, marker_end_pos, total_data_bytes);
58
+ let flat = build_flat_buffer(&meta_pixel, marker_end_pos, total_data_bytes);
58
59
  drop(meta_pixel);
59
60
 
60
61
  let stride = row_bytes + 1;
@@ -149,7 +150,6 @@ fn write_idat_direct<W: Write>(
149
150
 
150
151
  fn build_flat_buffer(
151
152
  meta_pixel: &[u8],
152
- _padding_needed: usize,
153
153
  marker_end_pos: usize,
154
154
  total_data_bytes: usize,
155
155
  ) -> Vec<u8> {
@@ -166,11 +166,9 @@ fn build_flat_buffer(
166
166
  pos += PIXEL_MAGIC.len();
167
167
  flat[pos..pos + meta_pixel.len()].copy_from_slice(meta_pixel);
168
168
 
169
- if marker_end_pos + 9 <= total_data_bytes {
170
- for (i, m) in MARKER_END.iter().enumerate() {
171
- let off = marker_end_pos + i * 3;
172
- flat[off] = m.0; flat[off + 1] = m.1; flat[off + 2] = m.2;
173
- }
169
+ for (i, m) in MARKER_END.iter().enumerate() {
170
+ let off = marker_end_pos + i * 3;
171
+ flat[off] = m.0; flat[off + 1] = m.1; flat[off + 2] = m.2;
174
172
  }
175
173
 
176
174
  flat
@@ -163,6 +163,7 @@ fn write_png_from_zst(
163
163
  let padded_len = raw_payload_len + padding_needed;
164
164
 
165
165
  let marker_start_len = 12;
166
+ let marker_end_bytes = 9;
166
167
  let data_with_markers_len = marker_start_len + padded_len;
167
168
  let data_pixels = (data_with_markers_len + 2) / 3;
168
169
  let end_marker_pixels = 3;
@@ -174,7 +175,7 @@ fn write_png_from_zst(
174
175
  let height = side;
175
176
  let row_bytes = width * 3;
176
177
  let total_data_bytes = width * height * 3;
177
- let marker_end_pos = (height - 1) * width * 3 + (width - end_marker_pixels) * 3;
178
+ let marker_end_pos = total_data_bytes - marker_end_bytes;
178
179
 
179
180
  let enc_header_bytes = if let Some(ref enc) = encryptor {
180
181
  enc.header.clone()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roxify",
3
- "version": "1.12.8",
3
+ "version": "1.12.9",
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",