如何使用Pillow读取一个原始的RGBA4444图像?

huangapple go评论114阅读模式
英文:

How would I read a raw RGBA4444 image using Pillow?

问题

我尝试读取一个'.waltex'图像,这是一种'walaber图像'。它基本上是以原始图像格式存在的。问题是它使用'RGBA8888'、'RGBA4444'、'RGB565'和'RGB5551'(这些都可以从文件头中确定),但我找不到在PIL中使用这些颜色规格的方法。

我尝试过以下方法:

  1. from PIL import Image
  2. with open('Carl.waltex', 'rb') as file:
  3. rawdata = file.read()
  4. image = Image.frombytes('RGBA', (1024,1024), rawdata, 'raw', 'RGBA;4B')
  5. 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

  1. from PIL import Image
  2. with open('Carl.waltex', 'rb') as file:
  3. rawdata = file.read()
  4. image = Image.frombytes('RGBA', (1024,1024), rawdata, 'raw', 'RGBA;4B')
  5. 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,就像您最新分享的示例图像一样

所以,代码如下所示:

  1. #!/usr/bin/env python3
  2. import struct
  3. import sys
  4. import numpy as np
  5. from PIL import Image
  6. def loadWaltex(filename):
  7. # 以二进制模式打开输入文件
  8. with open(filename, 'rb') as fd:
  9. # 读取16字节的标头并提取元数据
  10. # https://zenhax.com/viewtopic.php?t=14164
  11. header = fd.read(16)
  12. magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
  13. if magic != b'WALT':
  14. sys.exit(f'ERROR: {filename} does not start with "WALT" magic string')
  15. # 检查格式是否为0(RGBA8888)或3(RGBA4444)
  16. if fmt == 0:
  17. fmtdesc = "RGBA8888"
  18. # 读取文件余下的部分(标头后面的部分)
  19. data = np.fromfile(fd, dtype=np.uint8)
  20. R = data[0::4].reshape((h, w))
  21. G = data[1::4].reshape((h, w))
  22. B = data[2::4].reshape((h, w))
  23. A = data[3::4].reshape((h, w))
  24. # 堆叠通道以生成RGBA图像
  25. RGBA = np.dstack((R, G, B, A))
  26. else:
  27. fmtdesc = "RGBA4444"
  28. # 读取文件余下的部分(标头后面的部分)
  29. data = np.fromfile(fd, dtype=np.uint16).reshape((h, w))
  30. # 从uint16中提取RGBA4444
  31. R = (data >> 12) & 0xf
  32. G = (data >> 8) & 0xf
  33. B = (data >> 4) & 0xf
  34. A = data & 0xf
  35. # 堆叠通道以生成RGBA图像
  36. RGBA = np.dstack((R, G, B, A)).astype(np.uint8) << 4
  37. # 用户的调试信息
  38. print(f'Filename: {filename}, version: {vers}, format: {fmtdesc} ({fmt}), w: {w}, h: {h}')
  39. # 转化为PIL图像
  40. im = Image.fromarray(RGBA)
  41. return im
  42. if __name__ == "__main__":
  43. # 加载由第一个参数指定的图像
  44. im = loadWaltex(sys.argv[1])
  45. im.save('result.png')

当您运行它时:

  1. ./decodeRGBA.py objects.waltex

您将得到:

如何使用Pillow读取一个原始的RGBA4444图像?

对于您的两个示例图像,调试输出如下:

  1. Filename: Carl.waltex, version: 1, format: RGBA4444 (3), w: 1024, h: 1024
  2. Filename: objects.waltex, version: 1, format: RGBA8888 (0), w: 256, h: 1024

原始答案

我发现使用Numpy是处理这种情况的最简单方法,而且性能非常好:

  1. #!/usr/bin/env python3
  2. import numpy as np
  3. from PIL import Image
  4. # 定义图像的已知参数并读入Numpy数组
  5. h, w, offset = 1024, 1024, 16
  6. data = np.fromfile('Carl.waltex', dtype=np.uint16, offset=offset).reshape((h,w))
  7. # 从uint16中提取RGBA4444
  8. R = (data >> 12) & 0xf
  9. G = (data >> 8) & 0xf
  10. B = (data >> 4) & 0xf
  11. A = data & 0xf
  12. # 堆叠4个单独的通道以生成RGBA图像
  13. RGBA = np.dstack((R, G, B, A)).astype(np.uint8) << 4
  14. # 转化为PIL图像
  15. im = Image.fromarray(RGBA)
  16. im.save('result.png')

如何使用Pillow读取一个原始的RGBA4444图像?

注意: 您的图像在开头有16字节的填充。有时候这个数量是可变的。在这种情况下的一个有用技巧是读取整个文件,计算出有多少个有效的像素数据样本(在您的情况下是1024 * 1024),然后切片数据以取最后N个样本,从而忽略开头的可变填充。这将看起来像这样:

  1. # 定义图像的已知参数并读入Numpy数组
  2. h, w = 1024, 1024
  3. data = np.fromfile('Carl.waltex', dtype=np.uint16)[-h*w:].reshape((h,w))

如果您不喜欢Numpy,更喜欢使用列表和结构来处理,可以使用以下代码获得完全相同的结果:

  1. #!/usr/bin/env python3
  2. import struct
  3. from PIL import Image
  4. # 定义图像的已知参数
  5. h, w, offset = 1024, 1024, 16
  6. data = open('Carl.waltex', 'rb').read()[offset:]
  7. # 解包成一堆h*w个无符号短整数
  8. uint16s = struct.unpack("H" * h * w, data)
  9. # 创建一个RGBA元组的列表
  10. pixels = []
  11. for RGBA4444 in uint16s:
  12. R = (RGBA4444 >> 8) & 0xf0
  13. G = (RGBA4444 >> 4) & 0xf0
  14. B = RGBA4444 & 0xf0
  15. A = (RGBA4444 & 0xf) << 4
  16. pixels.append((R, G, B, A))
  17. # 将RGBA元组的列表推入空图像中
  18. RGBA = Image.new('RGBA', (w,h))
  19. RGBA.putdata(pixels)
  20. 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:

  1. #!/usr/bin/env python3
  2. import struct
  3. import sys
  4. import numpy as np
  5. from PIL import Image
  6. def loadWaltex(filename):
  7. # Open input file in binary mode
  8. with open(filename, &#39;rb&#39;) as fd:
  9. # Read 16 byte header and extract metadata
  10. # https://zenhax.com/viewtopic.php?t=14164
  11. header = fd.read(16)
  12. magic, vers, fmt, w, h, _ = struct.unpack(&#39;4sBBHH6s&#39;, header)
  13. if magic != b&#39;WALT&#39;:
  14. sys.exit(f&#39;ERROR: {filename} does not start with &quot;WALT&quot; magic string&#39;)
  15. #&#160;Check if fmt=0 (RGBA8888) or fmt=3 (RGBA4444)
  16. if fmt == 0:
  17. fmtdesc = &quot;RGBA8888&quot;
  18. #&#160;Read remainder of file (part following header)
  19. data = np.fromfile(fd, dtype=np.uint8)
  20. R = data[0::4].reshape((h,w))
  21. G = data[1::4].reshape((h,w))
  22. B = data[2::4].reshape((h,w))
  23. A = data[3::4].reshape((h,w))
  24. #&#160;Stack the channels to make RGBA image
  25. RGBA = np.dstack((R,G,B,A))
  26. else:
  27. fmtdesc = &quot;RGBA4444&quot;
  28. #&#160;Read remainder of file (part following header)
  29. data = np.fromfile(fd, dtype=np.uint16).reshape((h,w))
  30. #&#160;Split the RGBA444 out from the uint16
  31. R = (data&gt;&gt;12) &amp; 0xf
  32. G = (data&gt;&gt;8) &amp; 0xf
  33. B = (data&gt;&gt;4) &amp; 0xf
  34. A = data &amp; 0xf
  35. #&#160;Stack the channels to make RGBA image
  36. RGBA = np.dstack((R,G,B,A)).astype(np.uint8) &lt;&lt; 4
  37. #&#160;Debug info for user
  38. print(f&#39;Filename: {filename}, version: {vers}, format: {fmtdesc} ({fmt}), w: {w}, h: {h}&#39;)
  39. # Make into PIL Image
  40. im = Image.fromarray(RGBA)
  41. return im
  42. if __name__ == &quot;__main__&quot;:
  43. # Load image specified by first parameter
  44. im = loadWaltex(sys.argv[1])
  45. im.save(&#39;result.png&#39;)

And when you run it with:

  1. ./decodeRGBA.py objects.waltex

You get:

如何使用Pillow读取一个原始的RGBA4444图像?

The debug output for your two sample images is:

  1. Filename: Carl.waltex, version: 1, format: RGBA4444 (3), w: 1024, h: 1024
  2. 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:

  1. #!/usr/bin/env python3
  2. import numpy as np
  3. from PIL import Image
  4. # Define the known parameters of the image and read into Numpy array
  5. h, w, offset = 1024, 1024, 16
  6. data = np.fromfile(&#39;Carl.waltex&#39;, dtype=np.uint16, offset=offset).reshape((h,w))
  7. #&#160;Split the RGBA4444 out from the uint16
  8. R = (data &gt;&gt; 12) &amp; 0xf
  9. G = (data &gt;&gt; 8) &amp; 0xf
  10. B = (data &gt;&gt; 4) &amp; 0xf
  11. A = data &amp; 0xf
  12. #&#160;Stack the 4 individual channels to make an RGBA image
  13. RGBA = np.dstack((R,G,B,A)).astype(np.uint8) &lt;&lt; 4
  14. # Make into PIL Image
  15. im = Image.fromarray(RGBA)
  16. im.save(&#39;result.png&#39;)

如何使用Pillow读取一个原始的RGBA4444图像?


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:

  1. # Define the known parameters of the image and read into Numpy array
  2. h, w = 1024, 1024
  3. data = np.fromfile(&#39;Carl.waltex&#39;, 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:

  1. #!/usr/bin/env python3
  2. import struct
  3. from PIL import Image
  4. # Define the known parameters of the image
  5. h, w, offset = 1024, 1024, 16
  6. data = open(&#39;Carl.waltex&#39;, &#39;rb&#39;).read()[offset:]
  7. # Unpack into bunch of h*w unsigned shorts
  8. uint16s = struct.unpack(&quot;H&quot; * h *w, data)
  9. #&#160;Build a list of RGBA tuples
  10. pixels = []
  11. for RGBA4444 in uint16s:
  12. R = (RGBA4444 &gt;&gt; 8) &amp; 0xf0
  13. G = (RGBA4444 &gt;&gt; 4) &amp; 0xf0
  14. B = RGBA4444 &amp; 0xf0
  15. A = ( RGBA4444 &amp; 0xf) &lt;&lt; 4
  16. pixels.append((R,G,B,A))
  17. #&#160;Push the list of RGBA tuples into an empty image
  18. RGBA = Image.new(&#39;RGBA&#39;, (w,h))
  19. RGBA.putdata(pixels)
  20. RGBA.save(&#39;result.png&#39;)

Note that the Numpy approach is 60x faster than the list-based approach:

  1. Numpy: 3.6 ms &#177; 73.2 &#181;s per loop (mean &#177; std. dev. of 7 runs, 100 loops each)
  2. listy: 213 ms &#177; 712 &#181;s per loop (mean &#177; 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

这是您要翻译的代码部分:

  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.
  2. You use it very simply like this:
  3. from PIL import Image
  4. import WaltexImagePlugin
  5. im = Image.open('objects.waltex')
  6. im.show()
  1. You need to save the following as `WaltexImagePlugin.py` in the directory beside your main Python program:
  2. from PIL import Image, ImageFile
  3. import struct
  4. def _accept(prefix):
  5. return prefix[:4] == b"WALT"
  6. class WaltexImageFile(ImageFile.ImageFile):
  7. format = "Waltex"
  8. format_description = "Waltex texture image"
  9. def _open(self):
  10. header = self.fp.read(HEADER_LENGTH)
  11. magic, vers, fmt, w, h, _ = struct.unpack('4sBBHH6s', header)
  12. # size in pixels (width, height)
  13. self._size = w, h
  14. # mode setting
  15. self.mode = 'RGBA'
  16. # Decoder
  17. if fmt == 0:
  18. # RGBA8888
  19. # Just use built-in raw decoder
  20. self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, (self.mode,
  21. 0, 1))]
  22. elif fmt == 3:
  23. # RGBA4444
  24. # Use raw decoder with custom RGBA;4B unpacker
  25. self.tile = [("raw", (0, 0) + self.size, HEADER_LENGTH, ('RGBA;4B',
  26. 0, 1))]
  27. Image.register_open(WaltexImageFile.format, WaltexImageFile, _accept)
  28. Image.register_extensions(
  29. WaltexImageFile.format,
  30. [
  31. ".waltex"
  32. ],
  33. )
  34. HEADER_LENGTH = 16
  1. 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:
  2. ...
  3. ...
  4. im = Image.open(...)
  5. # Split channels and recombine in correct order
  6. a, b, c, d = im.split()
  7. 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:

  1. from PIL import Image
  2. import WaltexImagePlugin
  3. im = Image.open(&#39;objects.waltex&#39;)
  4. im.show()

You need to save the following as WaltexImagePlugin.py in the directory beside your main Python program:

  1. from PIL import Image, ImageFile
  2. import struct
  3. def _accept(prefix):
  4. return prefix[:4] == b&quot;WALT&quot;
  5. class WaltexImageFile(ImageFile.ImageFile):
  6. format = &quot;Waltex&quot;
  7. format_description = &quot;Waltex texture image&quot;
  8. def _open(self):
  9. header = self.fp.read(HEADER_LENGTH)
  10. magic, vers, fmt, w, h, _ = struct.unpack(&#39;4sBBHH6s&#39;, header)
  11. # size in pixels (width, height)
  12. self._size = w, h
  13. # mode setting
  14. self.mode = &#39;RGBA&#39;
  15. # Decoder
  16. if fmt == 0:
  17. # RGBA8888
  18. # Just use built-in raw decoder
  19. self.tile = [(&quot;raw&quot;, (0, 0) + self.size, HEADER_LENGTH, (self.mode,
  20. 0, 1))]
  21. elif fmt == 3:
  22. # RGBA4444
  23. # Use raw decoder with custom RGBA;4B unpacker
  24. self.tile = [(&quot;raw&quot;, (0, 0) + self.size, HEADER_LENGTH, (&#39;RGBA;4B&#39;,
  25. 0, 1))]
  26. Image.register_open(WaltexImageFile.format, WaltexImageFile, _accept)
  27. Image.register_extensions(
  28. WaltexImageFile.format,
  29. [
  30. &quot;.waltex&quot;
  31. ],
  32. )
  33. 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:

  1. ...
  2. ...
  3. im = Image.open(...)
  4. # Split channels and recombine in correct order
  5. a, b, c, d = im.split()
  6. 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.

huangapple
  • 本文由 发表于 2023年2月18日 07:47:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75490202.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定