英文:
How would I read a raw RGBA4444 image using Pillow?
问题
我尝试读取一个'.waltex'图像,这是一种'walaber图像'。它基本上是以原始图像格式存在的。问题是它使用'RGBA8888'、'RGBA4444'、'RGB565'和'RGB5551'(这些都可以从文件头中确定),但我找不到在PIL中使用这些颜色规格的方法。
我尝试过以下方法:
from PIL import Image
with open('Carl.waltex', 'rb') as file:
rawdata = file.read()
image = Image.frombytes('RGBA', (1024,1024), rawdata, 'raw', 'RGBA;4B')
image.show()
我尝试了所有16位原始模式,但都没有找到适用的。要明确一下,这个文件具体是'RGBA4444',采用小端序,每像素8个字节。
如果你需要这个文件,我可以提供链接。
英文:
I'm trying to read a '.waltex' image, which is a 'walaber image'. It's basically just in raw image format. The problem is, it uses 'RGBA8888', 'RGBA4444', 'RGB565' and 'RGB5551' (all of which can be determined from the header), and I could not find a way to use these color specs in PIL.
I've tried doing this
from PIL import Image
with open('Carl.waltex', 'rb') as file:
rawdata = file.read()
image = Image.frombytes('RGBA', (1024,1024), rawdata, 'raw', 'RGBA;4B')
image.show()
I've tried all the 16-bit raw modes in the last input, and I couldn't find a single one that worked. To be clear, this file is specifically 'RGBA4444' with little endian, 8 bytes per pixel.
If you need the file, then I can link it.
答案1
得分: 3
我已经对原始代码进行了一些更改,以便:
- 您可以传递要读取的文件名作为参数
- 它解析标头并检查魔术字符串,自动推导格式(RGBA8888或RGBA4444)、高度和宽度
- 它现在处理RGBA8888,就像您最新分享的示例图像一样
所以,代码如下所示:
#!/usr/bin/env python3
import struct
import sys
import numpy as np
from PIL import Image
def loadWaltex(filename):
# 以二进制模式打开输入文件
with open(filename, 'rb') as fd:
# 读取16字节的标头并提取元数据
# https://zenhax.com/viewtopic.php?t=14164
header = fd.read(16)
magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
if magic != b'WALT':
sys.exit(f'ERROR: {filename} does not start with "WALT" magic string')
# 检查格式是否为0(RGBA8888)或3(RGBA4444)
if fmt == 0:
fmtdesc = "RGBA8888"
# 读取文件余下的部分(标头后面的部分)
data = np.fromfile(fd, dtype=np.uint8)
R = data[0::4].reshape((h, w))
G = data[1::4].reshape((h, w))
B = data[2::4].reshape((h, w))
A = data[3::4].reshape((h, w))
# 堆叠通道以生成RGBA图像
RGBA = np.dstack((R, G, B, A))
else:
fmtdesc = "RGBA4444"
# 读取文件余下的部分(标头后面的部分)
data = np.fromfile(fd, dtype=np.uint16).reshape((h, w))
# 从uint16中提取RGBA4444
R = (data >> 12) & 0xf
G = (data >> 8) & 0xf
B = (data >> 4) & 0xf
A = data & 0xf
# 堆叠通道以生成RGBA图像
RGBA = np.dstack((R, G, B, A)).astype(np.uint8) << 4
# 用户的调试信息
print(f'Filename: {filename}, version: {vers}, format: {fmtdesc} ({fmt}), w: {w}, h: {h}')
# 转化为PIL图像
im = Image.fromarray(RGBA)
return im
if __name__ == "__main__":
# 加载由第一个参数指定的图像
im = loadWaltex(sys.argv[1])
im.save('result.png')
当您运行它时:
./decodeRGBA.py objects.waltex
您将得到:
对于您的两个示例图像,调试输出如下:
Filename: Carl.waltex, version: 1, format: RGBA4444 (3), w: 1024, h: 1024
Filename: objects.waltex, version: 1, format: RGBA8888 (0), w: 256, h: 1024
原始答案
我发现使用Numpy是处理这种情况的最简单方法,而且性能非常好:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
# 定义图像的已知参数并读入Numpy数组
h, w, offset = 1024, 1024, 16
data = np.fromfile('Carl.waltex', dtype=np.uint16, offset=offset).reshape((h,w))
# 从uint16中提取RGBA4444
R = (data >> 12) & 0xf
G = (data >> 8) & 0xf
B = (data >> 4) & 0xf
A = data & 0xf
# 堆叠4个单独的通道以生成RGBA图像
RGBA = np.dstack((R, G, B, A)).astype(np.uint8) << 4
# 转化为PIL图像
im = Image.fromarray(RGBA)
im.save('result.png')
注意: 您的图像在开头有16字节的填充。有时候这个数量是可变的。在这种情况下的一个有用技巧是读取整个文件,计算出有多少个有效的像素数据样本(在您的情况下是1024 * 1024),然后切片数据以取最后N个样本,从而忽略开头的可变填充。这将看起来像这样:
# 定义图像的已知参数并读入Numpy数组
h, w = 1024, 1024
data = np.fromfile('Carl.waltex', dtype=np.uint16)[-h*w:].reshape((h,w))
如果您不喜欢Numpy,更喜欢使用列表和结构来处理,可以使用以下代码获得完全相同的结果:
#!/usr/bin/env python3
import struct
from PIL import Image
# 定义图像的已知参数
h, w, offset = 1024, 1024, 16
data = open('Carl.waltex', 'rb').read()[offset:]
# 解包成一堆h*w个无符号短整数
uint16s = struct.unpack("H" * h * w, data)
# 创建一个RGBA元组的列表
pixels = []
for RGBA4444 in uint16s:
R = (RGBA4444 >> 8) & 0xf0
G = (RGBA4444 >> 4) & 0xf0
B = RGBA4444 & 0xf0
A = (RGBA4444 & 0xf) << 4
pixels.append((R, G, B, A))
# 将RGBA元组的列表推入空图像中
RGBA = Image.new('RGBA', (w,h))
RGBA.putdata(pixels)
RGBA.save('result.png')
请注意,与基于Numpy的方法相比,基于列表的方法速度要
英文:
Updated Answer
I have made some changes to my original code so that:
- you can pass a filename to read as parameter
- it parses the header and checks the magic string and derives the format (RGBA8888 or RGBA4444) and height and width automatically
- it now handles RGBA8888 like your newly-shared sample image
So, it looks like this:
#!/usr/bin/env python3
import struct
import sys
import numpy as np
from PIL import Image
def loadWaltex(filename):
# Open input file in binary mode
with open(filename, 'rb') as fd:
# Read 16 byte header and extract metadata
# https://zenhax.com/viewtopic.php?t=14164
header = fd.read(16)
magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
if magic != b'WALT':
sys.exit(f'ERROR: {filename} does not start with "WALT" magic string')
# Check if fmt=0 (RGBA8888) or fmt=3 (RGBA4444)
if fmt == 0:
fmtdesc = "RGBA8888"
# Read remainder of file (part following header)
data = np.fromfile(fd, dtype=np.uint8)
R = data[0::4].reshape((h,w))
G = data[1::4].reshape((h,w))
B = data[2::4].reshape((h,w))
A = data[3::4].reshape((h,w))
# Stack the channels to make RGBA image
RGBA = np.dstack((R,G,B,A))
else:
fmtdesc = "RGBA4444"
# Read remainder of file (part following header)
data = np.fromfile(fd, dtype=np.uint16).reshape((h,w))
# Split the RGBA444 out from the uint16
R = (data>>12) & 0xf
G = (data>>8) & 0xf
B = (data>>4) & 0xf
A = data & 0xf
# Stack the channels to make RGBA image
RGBA = np.dstack((R,G,B,A)).astype(np.uint8) << 4
# Debug info for user
print(f'Filename: {filename}, version: {vers}, format: {fmtdesc} ({fmt}), w: {w}, h: {h}')
# Make into PIL Image
im = Image.fromarray(RGBA)
return im
if __name__ == "__main__":
# Load image specified by first parameter
im = loadWaltex(sys.argv[1])
im.save('result.png')
And when you run it with:
./decodeRGBA.py objects.waltex
You get:
The debug output for your two sample images is:
Filename: Carl.waltex, version: 1, format: RGBA4444 (3), w: 1024, h: 1024
Filename: objects.waltex, version: 1, format: RGBA8888 (0), w: 256, h: 1024
Original Answer
I find using Numpy is the easiest approach for this type of thing, and it is also highly performant:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
# Define the known parameters of the image and read into Numpy array
h, w, offset = 1024, 1024, 16
data = np.fromfile('Carl.waltex', dtype=np.uint16, offset=offset).reshape((h,w))
# Split the RGBA4444 out from the uint16
R = (data >> 12) & 0xf
G = (data >> 8) & 0xf
B = (data >> 4) & 0xf
A = data & 0xf
# Stack the 4 individual channels to make an RGBA image
RGBA = np.dstack((R,G,B,A)).astype(np.uint8) << 4
# Make into PIL Image
im = Image.fromarray(RGBA)
im.save('result.png')
Note: Your image has 16 bytes of padding at the start. Sometimes that amount is variable. A useful technique in that case is to read the entire file, work out how many useful samples of pixel data there are (in your case 1024*1024), and then slice the data to take the last N samples - thereby ignoring any variable padding at the start. That would look like this:
# Define the known parameters of the image and read into Numpy array
h, w = 1024, 1024
data = np.fromfile('Carl.waltex', dtype=np.uint16)[-h*w:].reshape((h,w))
If you don't like Numpy and prefer messing about with lists and structs, you can get exactly the same result like this:
#!/usr/bin/env python3
import struct
from PIL import Image
# Define the known parameters of the image
h, w, offset = 1024, 1024, 16
data = open('Carl.waltex', 'rb').read()[offset:]
# Unpack into bunch of h*w unsigned shorts
uint16s = struct.unpack("H" * h *w, data)
# Build a list of RGBA tuples
pixels = []
for RGBA4444 in uint16s:
R = (RGBA4444 >> 8) & 0xf0
G = (RGBA4444 >> 4) & 0xf0
B = RGBA4444 & 0xf0
A = ( RGBA4444 & 0xf) << 4
pixels.append((R,G,B,A))
# Push the list of RGBA tuples into an empty image
RGBA = Image.new('RGBA', (w,h))
RGBA.putdata(pixels)
RGBA.save('result.png')
Note that the Numpy approach is 60x faster than the list-based approach:
Numpy: 3.6 ms ± 73.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
listy: 213 ms ± 712 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
Note: These images and the waltex file format seem to be from the games "Where's My Water?" and "Where's My Perry?". I got some hints as to the header format from ZENHAX.
答案2
得分: 1
这是您要翻译的代码部分:
Now that I have a better idea of how your Waltex files work, I attempted to write a custom PIL Plugin for them - a new experience for me. I've put it as a different answer because the approach is very different.
You use it very simply like this:
from PIL import Image
import WaltexImagePlugin
im = Image.open('objects.waltex')
im.show()
You need to save the following as `WaltexImagePlugin.py` in the directory beside your main Python program:
from PIL import Image, ImageFile
import struct
def _accept(prefix):
return prefix[:4] == b"WALT"
class WaltexImageFile(ImageFile.ImageFile):
format = "Waltex"
format_description = "Waltex texture image"
def _open(self):
header = self.fp.read(HEADER_LENGTH)
magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
# size in pixels (width, height)
self._size = w, h
# mode setting
self.mode = 'RGBA'
# Decoder
if fmt == 0:
# RGBA8888
# Just use built-in raw decoder
self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, (self.mode,
0, 1))]
elif fmt == 3:
# RGBA4444
# Use raw decoder with custom RGBA;4B unpacker
self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, ('RGBA;4B',
0, 1))]
Image.register_open(WaltexImageFile.format, WaltexImageFile, _accept)
Image.register_extensions(
WaltexImageFile.format,
[
".waltex"
],
)
HEADER_LENGTH = 16
It works perfectly for your RGBA888 images, but cannot quite handle the byte ordering of your RGBA444 file, so you need to reverse it for those images. I used this:
...
...
im = Image.open(...)
# Split channels and recombine in correct order
a, b, c, d = im.split()
im = Image.merge((c,d,a,b))
请注意,上述内容包括一些代码和注释,我已经按您的要求提供了代码部分的翻译。如果您有任何其他翻译需求或问题,请随时提出。
英文:
Now that I have a better idea of how your Waltex files work, I attempted to write a custom PIL Plugin for them - a new experience for me. I've put it as a different answer because the approach is very different.
You use it very simply like this:
from PIL import Image
import WaltexImagePlugin
im = Image.open('objects.waltex')
im.show()
You need to save the following as WaltexImagePlugin.py
in the directory beside your main Python program:
from PIL import Image, ImageFile
import struct
def _accept(prefix):
return prefix[:4] == b"WALT"
class WaltexImageFile(ImageFile.ImageFile):
format = "Waltex"
format_description = "Waltex texture image"
def _open(self):
header = self.fp.read(HEADER_LENGTH)
magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
# size in pixels (width, height)
self._size = w, h
# mode setting
self.mode = 'RGBA'
# Decoder
if fmt == 0:
# RGBA8888
# Just use built-in raw decoder
self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, (self.mode,
0, 1))]
elif fmt == 3:
# RGBA4444
# Use raw decoder with custom RGBA;4B unpacker
self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, ('RGBA;4B',
0, 1))]
Image.register_open(WaltexImageFile.format, WaltexImageFile, _accept)
Image.register_extensions(
WaltexImageFile.format,
[
".waltex"
],
)
HEADER_LENGTH = 16
It works perfectly for your RGBA888 images, but cannot quite handle the byte ordering of your RGBA444 file, so you need to reverse it for those images. I used this:
...
...
im = Image.open(...)
# Split channels and recombine in correct order
a, b, c, d = im.split()
im = Image.merge((c,d,a,b))
If anyone knows how to use something in the Unpack.c
file to do this correctly, please ping me. Thank you.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论