workshop rework
This commit is contained in:
1 parent
a3a80dfa16
commit
fb39e290c0
115 files changed
+1865
-497
No files matched your search
@@ -0,0 +1,86 @@
|
||||
"""Minimal pure-Python PNG decoder (stdlib only: zlib + struct).
|
||||
|
||||
Supports 8-bit, non-interlaced grayscale / grayscale+alpha / RGB / RGBA PNGs -
|
||||
which covers ordinary exported heightmap/relief images. Deliberately does not
|
||||
depend on Pillow or any other package, since Unreal's embedded Python has no
|
||||
pip packages installed by default.
|
||||
"""
|
||||
import struct, zlib
|
||||
|
||||
_CHANNELS_BY_COLOR_TYPE = {0: 1, 2: 3, 4: 2, 6: 4} # 3 (palette) is unsupported
|
||||
|
||||
|
||||
def _paeth(a, b, c):
|
||||
p = a + b - c
|
||||
pa, pb, pc = abs(p - a), abs(p - b), abs(p - c)
|
||||
if pa <= pb and pa <= pc:
|
||||
return a
|
||||
if pb <= pc:
|
||||
return b
|
||||
return c
|
||||
|
||||
|
||||
def decode_png(path):
|
||||
"""Returns (width, height, channels, pixels) where pixels is a flat
|
||||
bytearray of width*height*channels bytes, row-major, top row first."""
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
if data[:8] != b"\x89PNG\r\n\x1a\n":
|
||||
raise ValueError(f"Not a PNG file: {path}")
|
||||
|
||||
width = height = bit_depth = color_type = interlace = None
|
||||
idat = bytearray()
|
||||
pos = 8
|
||||
while pos < len(data):
|
||||
length = struct.unpack(">I", data[pos:pos + 4])[0]
|
||||
ctype = data[pos + 4:pos + 8]
|
||||
chunk = data[pos + 8:pos + 8 + length]
|
||||
if ctype == b"IHDR":
|
||||
width, height, bit_depth, color_type, _comp, _filt, interlace = struct.unpack(">IIBBBBB", chunk)
|
||||
elif ctype == b"IDAT":
|
||||
idat.extend(chunk)
|
||||
elif ctype == b"IEND":
|
||||
break
|
||||
pos += 8 + length + 4 # length + type + data + crc
|
||||
|
||||
if bit_depth != 8:
|
||||
raise ValueError(f"Only 8-bit PNGs are supported (got bit depth {bit_depth}) - re-export as 8-bit: {path}")
|
||||
if interlace != 0:
|
||||
raise ValueError(f"Interlaced PNGs are not supported - re-export as non-interlaced: {path}")
|
||||
if color_type not in _CHANNELS_BY_COLOR_TYPE:
|
||||
raise ValueError(f"Unsupported PNG color type {color_type} (palette?) - re-export as grayscale/RGB/RGBA: {path}")
|
||||
channels = _CHANNELS_BY_COLOR_TYPE[color_type]
|
||||
|
||||
raw = zlib.decompress(bytes(idat))
|
||||
stride = width * channels
|
||||
pixels = bytearray(width * height * channels)
|
||||
|
||||
prev_row = bytearray(stride)
|
||||
row_start = 0
|
||||
for y in range(height):
|
||||
filter_type = raw[row_start]
|
||||
row = bytearray(raw[row_start + 1: row_start + 1 + stride])
|
||||
row_start += 1 + stride
|
||||
|
||||
if filter_type == 1: # Sub
|
||||
for i in range(channels, stride):
|
||||
row[i] = (row[i] + row[i - channels]) & 0xFF
|
||||
elif filter_type == 2: # Up
|
||||
for i in range(stride):
|
||||
row[i] = (row[i] + prev_row[i]) & 0xFF
|
||||
elif filter_type == 3: # Average
|
||||
for i in range(stride):
|
||||
a = row[i - channels] if i >= channels else 0
|
||||
row[i] = (row[i] + ((a + prev_row[i]) >> 1)) & 0xFF
|
||||
elif filter_type == 4: # Paeth
|
||||
for i in range(stride):
|
||||
a = row[i - channels] if i >= channels else 0
|
||||
b = prev_row[i]
|
||||
c = prev_row[i - channels] if i >= channels else 0
|
||||
row[i] = (row[i] + _paeth(a, b, c)) & 0xFF
|
||||
# filter_type == 0 (None): no-op
|
||||
|
||||
pixels[y * stride:(y + 1) * stride] = row
|
||||
prev_row = row
|
||||
|
||||
return width, height, channels, pixels
|
||||
Reference in new issue
Block a user