Repository: python-pillow/Pillow

Issue:
Unable to open DDS image: Unimplemented pixel format
### What did you do?

Attempted to read the attached DDS image.

### What did you expect to happen?

Expected it to render in my tkinter GUI.

### What actually happened?

Received the following error:

```
NotImplementedError: Unimplemented pixel format b'\x00\x00\x00\x00'
```

### What are your OS, Python and Pillow versions?

* OS: Windows 11
* Python: 3.11 64-bit
* Pillow: 10.0.1

```python
from PIL import ImageTk

with open ("test.dds", "rb") as f:
  data = f.read()

image = ImageTk.PhotoImage(data=data)
```

### Additional notes

I'm guessing the version of DDS that I am attempting to render is not supported solely based off of the error message. I am passing byte data and not the files themselves, but am unsure of how else to get this to render. Attempted to use Pillow, but no luck.

Attached zip with dds image inside:
- [test.zip](https://github.com/python-pillow/Pillow/files/12721995/test.zip)



Discussion:
How was the DDS image file created?

"b'\x00\x00\x00\x00'" is not what I expect for a pixel format. Is there a program you know of that can view the file successfully? Does that program list any information about what the pixel format is? I'm expecting a short string starting with DXT, ATI or BC.
Hello and thanks for commenting so quickly.

This DDS file is used in a video game and dumped directly from its archive. I'm attempting to read and render the image in a GUI.

This image opens in both GIMP and paint.net correctly. In GIMP, I see the following:

![image](https://github.com/python-pillow/Pillow/assets/17505625/10087170-7442-4b00-ae06-49a3edf8d9ca)

![image](https://github.com/python-pillow/Pillow/assets/17505625/2424e669-8ccc-40ee-9411-43d213e956ac)

Hopefully this provides the answer you're looking for, but please let me know if I can provide more.
In having no idea what I'm doing, I came across [where PIL is reading](https://github.com/python-pillow/Pillow/blob/main/src/PIL/DdsImagePlugin.py#L163-L184) the `fourcc` value and updated where it's checking the dds file to each of the supported values (DXT1, DXT3, DXT5, ATI1, ATI2, BC5S, DX10) just in case the original image had left off this data or something.

Thinking that maybe GIMP/paint.net didn't care, but PIL does. GIMP and paint.net render the correct image for all of the different `fourcc` values, except for DX10 - which doesn't open correctly.

PIL renders an image when I use any of the above values (except for DX10), but it's just noise.

This is a different dds file, but the exact same format:

Expected (in GIMP): 
![image](https://github.com/python-pillow/Pillow/assets/17505625/e3cdbb30-ad34-475d-a89e-818114479510)

PIL (using DXT1 and rendered in my GUI. All formats show some variation of this):
![image](https://github.com/python-pillow/Pillow/assets/17505625/9a0925f7-5074-4be1-894c-025014c4bc95)

My guess is PIL just doesn't support this DDS format.

![image](https://github.com/python-pillow/Pillow/assets/17505625/762cbe57-3d82-4b40-94e3-7850f06283b5)

I've realised that [the spec describing the pixel format flags](https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat) states that DDPF_FOURCC indicates that "dwFourCC contains valid data" - the image does not have this flag in `pfflags`, and this explains why `fourcc` is empty.

`pfflags` instead lines up with
https://github.com/python-pillow/Pillow/blob/b723e9e62e4706a85f7e44cb42b3d838dae5e546/src/PIL/DdsImagePlugin.py#L50
meaning there is palette data in the image. Gimp agrees with this, telling me in the "Image" menu that the "Image" "Mode" is "Indexed..."

DDPF_PALETTEINDEXED8 is not mentioned in the latest spec, but is mentioned in https://learn.microsoft.com/en-us/previous-versions/ms783269(v=vs.85). I guess this file is something that Microsoft has removed official support for.

Resolving patch:
diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py
--- a/src/PIL/DdsImagePlugin.py
+++ b/src/PIL/DdsImagePlugin.py
@@ -13,7 +13,7 @@
 import struct
 from io import BytesIO
 
-from . import Image, ImageFile
+from . import Image, ImageFile, ImagePalette
 from ._binary import o32le as o32
 
 # Magic ("DDS ")
@@ -157,6 +157,10 @@ def _open(self):
             rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
 
             self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
+        elif pfflags & DDPF_PALETTEINDEXED8:
+            self._mode = "P"
+            self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
+            self.tile = [("raw", (0, 0) + self.size, 0, "L")]
         else:
             data_start = header_size + 4
             n = 0


Test patch:
diff --git a/Tests/images/palette.dds b/Tests/images/palette.dds
new file mode 100644
Binary files /dev/null and b/Tests/images/palette.dds differ
diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py
--- a/Tests/test_file_dds.py
+++ b/Tests/test_file_dds.py
@@ -298,6 +298,11 @@ def test_dxt5_colorblock_alpha_issue_4142():
         assert px[2] != 0
 
 
+def test_palette():
+    with Image.open("Tests/images/palette.dds") as im:
+        assert_image_equal_tofile(im, "Tests/images/transparent.gif")
+
+
 def test_unimplemented_pixel_format():
     with pytest.raises(NotImplementedError):
         with Image.open("Tests/images/unimplemented_pixel_format.dds"):