Repository: python-pillow/Pillow

Issue:
RGB dds file's mode attribute is RGBA
<!--
Thank you for reporting an issue.

Follow these guidelines to ensure your issue is handled properly.

If you have a ...

1.  General question: consider asking the question on Stack Overflow
    with the python-imaging-library tag:

    * https://stackoverflow.com/questions/tagged/python-imaging-library

    Do not ask a question in both places.

    If you think you have found a bug or have an unexplained exception
    then file a bug report here.

2.  Bug report: include a self-contained, copy-pastable example that
    generates the issue if possible. Be concise with code posted.
    Guidelines on how to provide a good bug report:

    * https://stackoverflow.com/help/mcve

    Bug reports which follow these guidelines are easier to diagnose,
    and are often handled much more quickly.

3.  Feature request: do a quick search of existing issues
    to make sure this has not been asked before.

We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->

### What did you do?

```python
Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:57:15) [MSC v.1915 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import PIL
>>> PIL.__version__
'6.2.0'
>>> from PIL import Image
>>> img = Image.open('sample.dds')
>>> img.mode
'RGBA'
>>> img.tile
[('raw', (0, 0, 1024, 1024), 0, ('RGB', 0, 1))]
>>> img.load()
[('raw', (0, 0, 1024, 1024), 0, ('RGB', 0, 1))]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python37\lib\site-packages\PIL\ImageFile.py", line 224, in load
    self.mode, decoder_name, args, self.decoderconfig
  File "C:\Python37\lib\site-packages\PIL\Image.py", line 455, in _getdecoder
    return decoder(mode, *args + extra)
ValueError: unknown raw mode
```

img.mode, which is "RGBA", and img.tile[0][3][0], which is "RGB", mismatch.

### What did you expect to happen?

img.mode is expected to be "RGB".

### What actually happened?

Loading a DDS file containing no alpha data may raise ValueError. I cannot use img.mode attribute to check if the file contains alpha data.

### What are your OS, Python and Pillow versions?

OS: windows10 v10.0.17763
Python: 3.7.1
Pillow: 6.2.1

<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->

```
Code is attached in What did you do section.
```

I cannot upload the image to github due to company restriction.

But the 0x50--0x53 of the dds file is 0x40 0x00 x00 0x00.

From [this document](https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat), I think the pixel format is RGB but not RGBA.



Discussion:
Using ImageMagick (`convert -format dds -define dds:compression=none hopper.jpg hopper.dds`) I was able to create a file that reproduces your error. Do you agree this is a sufficient recreation of the file type you're dealing with?

[hopper.dds.zip](https://github.com/python-pillow/Pillow/files/3955291/hopper.dds.zip)
Yes.
The uint32 of [this file](https://github.com/python-pillow/Pillow/blob/master/Tests/images/uncompressed_rgb.dds) at address 0x1c824, which is the pixel at (361, 36), is 0x44FFADFF.

```
0x1c824 = 29161 * 4 + 0x80 = (800 * 36 + 361) * 4 + 0x80
```

From [this document](https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat), we know the fields (value is little endian) of DDS_PIXELFORMAT struct of dds header as following.

```
dwRBitMask = 0x0000FF00
dwGBitMask = 0x00FF0000
dwBBitMask = 0xFF000000
dwABitMask = 0x000000FF
```

Because of little endianness, the actual pixel rawmode is "ARGB". So the pixel content at position (361, 36) is A255 R173 G255 B68. (A0xFF R0xAD G0xFF B0x44)

I get the same value by this [Windows Texture Viewer](https://developer.nvidia.com/legacy-texture-tools) provided by nvidia.
![image](https://user-images.githubusercontent.com/3899797/72717146-7ca8b180-3bae-11ea-8a3a-a82176a7a203.png)

But when using Pillow parsing the uncompressed_rgb.dds file, I got different result.

```python
>>> from PIL import Image
>>> img_dds = Image.open('uncompressed_rgb.dds')
>>> img_dds.getpixel((361, 36))
(255, 173, 255, 68)
>>> img_dds.getbands()
('R', 'G', 'B', 'A')
```

Is this a bug?
I've created #5383 to resolve this.

As for why the mode of one of our test images is not ARGB, this is what Pillow considers to be the 'raw mode'. The final mode is RGBA. See https://pillow.readthedocs.io/en/stable/handbook/writing-your-own-file-decoder.html#the-raw-decoder
> **raw mode**
> The pixel layout used in the file, and is used to properly convert data to PIL’s internal layout. For a summary of the available formats, see the table below.

Resolving patch:
diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py
--- a/src/PIL/DdsImagePlugin.py
+++ b/src/PIL/DdsImagePlugin.py
@@ -133,9 +133,11 @@ def _open(self):
             rawmode = ""
             if bitcount == 32:
                 rawmode += masks[0xFF000000]
+            else:
+                self.mode = "RGB"
             rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
 
-            self.tile = [("raw", (0, 0) + self.size, 0, (rawmode, 0, 1))]
+            self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
         else:
             data_start = header_size + 4
             n = 0


Test patch:
diff --git a/Tests/images/hopper.dds b/Tests/images/hopper.dds
new file mode 100644
Binary files /dev/null and b/Tests/images/hopper.dds differ
diff --git a/Tests/images/uncompressed_rgb.png b/Tests/images/uncompressed_rgb.png
Binary files a/Tests/images/uncompressed_rgb.png and b/Tests/images/uncompressed_rgb.png 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
@@ -14,7 +14,8 @@
 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"
 TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB = "Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds"
-TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.dds"
+TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
+TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
 
 
 def test_sanity_dxt1():
@@ -124,15 +125,22 @@ def test_unimplemented_dxgi_format():
 def test_uncompressed_rgb():
     """Check uncompressed RGB images can be opened"""
 
+    # convert -format dds -define dds:compression=none hopper.jpg hopper.dds
     with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im:
-        im.load()
+        assert im.format == "DDS"
+        assert im.mode == "RGB"
+        assert im.size == (128, 128)
+
+        assert_image_equal_tofile(im, "Tests/images/hopper.png")
 
+    # Test image with alpha
+    with Image.open(TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA) as im:
         assert im.format == "DDS"
         assert im.mode == "RGBA"
         assert im.size == (800, 600)
 
         assert_image_equal_tofile(
-            im, TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")
+            im, TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA.replace(".dds", ".png")
         )