adeed manual processing using UVtools
This commit is contained in:
71
diagnose/decode_prev.py
Normal file
71
diagnose/decode_prev.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Decode the PREV (preview thumbnail) section from a pm4n file and save as PNG.
|
||||
Run: python3 decode_prev.py <file.pm4n>
|
||||
"""
|
||||
import struct, sys
|
||||
from pathlib import Path
|
||||
|
||||
path = sys.argv[1]
|
||||
data = open(path, 'rb').read()
|
||||
|
||||
prev_off = data.find(b'PREV')
|
||||
print(f"PREV at 0x{prev_off:06X}")
|
||||
|
||||
# PREV section layout (from ANYCUBIC format docs and common reverse-engineering):
|
||||
# +0x00 "PREV"
|
||||
# +0x04 u32 section_length
|
||||
# +0x08 u32 image_width
|
||||
# +0x0C u32 image_height
|
||||
# +0x10 u32 image_data_length
|
||||
# +0x14 image data (RGB565 or RLE)
|
||||
|
||||
for hdr_size in [0x14, 0x18, 0x10, 0x0C]:
|
||||
w = struct.unpack_from('<I', data, prev_off + 0x08)[0]
|
||||
h = struct.unpack_from('<I', data, prev_off + 0x0C)[0]
|
||||
dlen = struct.unpack_from('<I', data, prev_off + 0x10)[0]
|
||||
print(f" PREV+0x08: w={w}, h={h}, dlen={dlen}")
|
||||
if 100 < w < 2000 and 100 < h < 2000:
|
||||
print(f" -> plausible dimensions {w}x{h}, data at +0x14, {dlen} bytes")
|
||||
break
|
||||
|
||||
# Show first 64 bytes of PREV section
|
||||
print(f"\nPREV raw (first 64 bytes):")
|
||||
chunk = data[prev_off:prev_off+64]
|
||||
for i in range(0, 64, 16):
|
||||
row = chunk[i:i+16]
|
||||
hex_p = ' '.join(f'{b:02X}' for b in row)
|
||||
u_vals = [struct.unpack_from('<I', row, j)[0] for j in range(0, min(16,len(row))-3, 4)]
|
||||
print(f" +0x{i:02X} {hex_p:<48} {u_vals}")
|
||||
|
||||
# Try to decode as RGB565 (common for ANYCUBIC previews)
|
||||
# Each pixel = 2 bytes, little-endian RGB565
|
||||
# R = bits[15:11], G = bits[10:5], B = bits[4:0]
|
||||
img_data_off = prev_off + 0x14
|
||||
img_data_len = struct.unpack_from('<I', data, prev_off + 0x10)[0]
|
||||
w = struct.unpack_from('<I', data, prev_off + 0x08)[0]
|
||||
h = struct.unpack_from('<I', data, prev_off + 0x0C)[0]
|
||||
|
||||
print(f"\nAttempting RGB565 decode: {w}x{h}, {img_data_len} bytes at 0x{img_data_off:06X}")
|
||||
if 0 < w < 2000 and 0 < h < 2000 and img_data_len == w * h * 2:
|
||||
print(f" Size matches RGB565 ({w}*{h}*2={w*h*2}) ✓")
|
||||
try:
|
||||
from PIL import Image
|
||||
pixels = []
|
||||
raw = data[img_data_off:img_data_off + img_data_len]
|
||||
for i in range(0, len(raw)-1, 2):
|
||||
px = raw[i] | (raw[i+1] << 8)
|
||||
r = ((px >> 11) & 0x1F) << 3
|
||||
g = ((px >> 5) & 0x3F) << 2
|
||||
b = (px & 0x1F) << 3
|
||||
pixels.append((r, g, b))
|
||||
img = Image.new('RGB', (w, h))
|
||||
img.putdata(pixels)
|
||||
out = Path(path).with_suffix('.prev_thumb.png')
|
||||
img.save(out)
|
||||
print(f" Saved thumbnail to {out}")
|
||||
except Exception as e:
|
||||
print(f" Error: {e}")
|
||||
else:
|
||||
print(f" Size mismatch or bad dims, skipping decode")
|
||||
print(f" Expected {w*h*2} bytes for RGB565, got {img_data_len}")
|
||||
Reference in New Issue
Block a user