Repository: python-pillow/Pillow

Issue:
DDS BC6 decoding has a broken blue channel
I attempted to use the code in https://github.com/python-pillow/Pillow/blob/main/src/libImaging/BcnDecode.c for a project in order to decode BC6 textures. The red and green channels decoded correctly but the blue channel ended up being mangled somehow.

Here's the red channel of the image as displayed in renderdoc:
![20220601_16h17m43s_grim](https://user-images.githubusercontent.com/13566135/171426393-5108509f-d4a9-48f1-b14e-4fe94d370749.png)

and here's the blue channel:

![20220601_16h17m52s_grim](https://user-images.githubusercontent.com/13566135/171426461-19642041-da11-4a56-bc62-4d59fb826cc6.png)


This results in an image that looks like this:

![20220601_15h56m13s_grim](https://user-images.githubusercontent.com/13566135/171426539-849b126b-28bf-4f49-ac9c-c92ab9509888.png)



### What are your OS, Python and Pillow versions?

* OS: N/A
* Python: N/A
* Pillow: https://github.com/python-pillow/Pillow/commit/b261e179078a36adb030e029c9834d8c00dec4e0




Discussion:
Would we be able to get a copy of the image itself?
I've uploaded the source image (which is a cubemap with several mip levels) in 2 formats, .dds and .ktx2:

[broken.zip](https://github.com/python-pillow/Pillow/files/8823706/broken.zip)

As the broken output .dds file is quite large (128mb) it exceeds the 25mb upload limit unless I make a non-trivial modification. Let me know if you'd like me to send that to you by another means though!
Thanks for the image.

I'm not happy with it enough yet for a PR, but see what you think of https://github.com/radarhere/Pillow/commit/3b51e1cc498273d6c25afa8d29e60f77a9861518. It generates this from your image.

<img width="350" src="https://user-images.githubusercontent.com/3112309/171997260-da7398b4-5433-4876-9cd7-fb55414e55e2.png" />

Resolving patch:
diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py
--- a/src/PIL/DdsImagePlugin.py
+++ b/src/PIL/DdsImagePlugin.py
@@ -101,6 +101,8 @@
 DXGI_FORMAT_BC5_TYPELESS = 82
 DXGI_FORMAT_BC5_UNORM = 83
 DXGI_FORMAT_BC5_SNORM = 84
+DXGI_FORMAT_BC6H_UF16 = 95
+DXGI_FORMAT_BC6H_SF16 = 96
 DXGI_FORMAT_BC7_TYPELESS = 97
 DXGI_FORMAT_BC7_UNORM = 98
 DXGI_FORMAT_BC7_UNORM_SRGB = 99
@@ -181,6 +183,14 @@ def _open(self):
                     self.pixel_format = "BC5S"
                     n = 5
                     self.mode = "RGB"
+                elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
+                    self.pixel_format = "BC6H"
+                    n = 6
+                    self.mode = "RGB"
+                elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
+                    self.pixel_format = "BC6HS"
+                    n = 6
+                    self.mode = "RGB"
                 elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
                     self.pixel_format = "BC7"
                     n = 7


Test patch:
diff --git a/Tests/images/bc6h.dds b/Tests/images/bc6h.dds
new file mode 100644
Binary files /dev/null and b/Tests/images/bc6h.dds differ
diff --git a/Tests/images/bc6h.png b/Tests/images/bc6h.png
new file mode 100644
Binary files /dev/null and b/Tests/images/bc6h.png differ
diff --git a/Tests/images/bc6h_sf.dds b/Tests/images/bc6h_sf.dds
new file mode 100644
Binary files /dev/null and b/Tests/images/bc6h_sf.dds differ
diff --git a/Tests/images/bc6h_sf.png b/Tests/images/bc6h_sf.png
new file mode 100644
Binary files /dev/null and b/Tests/images/bc6h_sf.png differ
diff --git a/Tests/images/unimplemented_dxgi_format.dds b/Tests/images/unimplemented_dxgi_format.dds
Binary files a/Tests/images/unimplemented_dxgi_format.dds and b/Tests/images/unimplemented_dxgi_format.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
@@ -16,6 +16,8 @@
 TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
 TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
 TEST_FILE_BC5S = "Tests/images/bc5s.dds"
+TEST_FILE_BC6H = "Tests/images/bc6h.dds"
+TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
 TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
 TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
 TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds"
@@ -114,6 +116,20 @@ def test_dx10_bc5(image_path, expected_path):
         assert_image_equal_tofile(im, expected_path.replace(".dds", ".png"))
 
 
+@pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS))
+def test_dx10_bc6h(image_path):
+    """Check DX10 BC6H/BC6HS images can be opened"""
+
+    with Image.open(image_path) as im:
+        im.load()
+
+        assert im.format == "DDS"
+        assert im.mode == "RGB"
+        assert im.size == (128, 128)
+
+        assert_image_equal_tofile(im, image_path.replace(".dds", ".png"))
+
+
 def test_dx10_bc7():
     """Check DX10 images can be opened"""