Repository: python-pillow/Pillow

Issue:
Endianness is inconsistent
Image class methods:

| Method        | Parameter      | Endianness  |
|---------------|----------------|-------------|
| blend         | alpha          | native      |
| new           | color          | native      |
| frombytes     | data           | mode        |
| frombuffer    | data           | mode        |
| getcolors     |                | native      |
| getdata all   |                | mode        |
| getdata band  |                | native      |
| getextrema    |                | native      |
| getpalette    |                | mode        |
| getpixel      |                | native      |
| point         |                | unsupported |
| putdata       | data           | native      |
| putpalette    | data           | mode        |
| putpixel      | value          | native      |
| remap_palette | source_palette | mode        |
| tobytes       |                | mode        |

Methods that use one of the above methods:
`eval` calls `point`
`fromarray` calls `frombuffer`
`rotate` calls `transform`
`transform` calls `new`

Related: #2228


Discussion:
Could you provide some more detail about how you worked out this list of endianness behaviour, perhaps some Python code to demonstrate the inconsistency?

I understand that 'native' means that the endianness of the environment is used, but what do you mean by 'mode'?
I worked these out by looking at the code, though it seems I got a few wrong, and some of them have mode checks that I missed. By 'mode' I mean it accepts/returns data in the endianness of the image's mode.

```python
from PIL import Image

def t(method, *args, **kwargs):
    print(method.__name__ + ' ', end='')
    try:
        print(method(*args, **kwargs))
    except Exception as e:
        print(e)

def callMethods(image):
    t(x.getcolors)
    t(lambda:list(x.getdata()))
    t(lambda:list(x.getdata(0)))
    t(x.getextrema)
    t(x.getpixel,(0,0))
    t(x.tobytes)

for mode in ('I;16L','I;16B'):
    print(f'\n{mode} New 123')
    with Image.new(mode, (1,1), color=123) as x:
        callMethods(x)
    
    print(f'\n{mode} From Bytes \\x00{{')
    with Image.frombytes(mode, (1,1), data=b'\x00{') as x:
        callMethods(x)
    
    print(f'\n{mode} From Bytes {{\\x00')
    with Image.frombytes(mode, (1,1), data=b'{\x00') as x:
        callMethods(x)
```

```
I;16L New 123
getcolors image has wrong mode
<lambda> [123]
<lambda> image has wrong mode
getextrema image has wrong mode
getpixel 123
tobytes b'{\x00'

I;16L From Bytes \x00{
getcolors image has wrong mode
<lambda> [31488]
<lambda> image has wrong mode
getextrema image has wrong mode
getpixel 31488
tobytes b'\x00{'

I;16L From Bytes {\x00
getcolors image has wrong mode
<lambda> [123]
<lambda> image has wrong mode
getextrema image has wrong mode
getpixel 123
tobytes b'{\x00'

I;16B New 123
getcolors image has wrong mode
<lambda> [123]
<lambda> image has wrong mode
getextrema image has wrong mode
getpixel 123
tobytes b'\x00{'

I;16B From Bytes \x00{
getcolors image has wrong mode
<lambda> [123]
<lambda> image has wrong mode
getextrema image has wrong mode
getpixel 123
tobytes b'\x00{'

I;16B From Bytes {\x00
getcolors image has wrong mode
<lambda> [31488]
<lambda> image has wrong mode
getextrema image has wrong mode
getpixel 31488
tobytes b'{\x00'
```
Here's a more interesting test:

```python
from PIL import Image

def t(method, *args, **kwargs):
    print(method.__name__ + ' ', end='')
    try:
        print(method(*args, **kwargs))
    except Exception as e:
        print(e)

def callMethods(image):
    t(image.getpixel,(0,0))
    image.putdata(image.getdata())
    t(image.getpixel,(0,0))

for mode in ('I;16L','I;16B'):
    print(f'\n{mode} New 123')
    with Image.new(mode, (1,1), color=123) as image:
        callMethods(image)
    
    print(f'\n{mode} From Bytes \\x00{{')
    with Image.frombytes(mode, (1,1), data=b'\x00{') as image:
        callMethods(image)
    
    print(f'\n{mode} From Bytes {{\\x00')
    with Image.frombytes(mode, (1,1), data=b'{\x00') as image:
        callMethods(image)
```

```
I;16L New 123
getpixel 123
getpixel 123

I;16L From Bytes \x00{
getpixel 31488
getpixel 31743

I;16L From Bytes {\x00
getpixel 123
getpixel 123

I;16B New 123
getpixel 123
getpixel 31611

I;16B From Bytes \x00{
getpixel 123
getpixel 31611

I;16B From Bytes {\x00
getpixel 31488
getpixel 65280
```
I created a branch with some tests: https://github.com/Yay295/Pillow/tree/bytes_tests

There are three failures:
FAILED Tests/test_image.py::TestImageBytes::test_get_pixel[I;16-2] - assert (256, 1284, 770, 1798) == (65535, 65535, 770, 1798)
FAILED Tests/test_image.py::TestImageBytes::test_get_pixel[I;16L-2] - assert (256, 1284, 770, 1798) == (65535, 65535, 770, 1798)
FAILED Tests/test_image.py::TestImageBytes::test_get_pixel[I;16B-2] - assert (1, 1029, 515, 1543) == (511, 65535, 515, 1543)

That test is just:
1. create image from bytes
2. store pixels in a tuple
3. `image.putdata(image.getdata())`
4. store pixels in a tuple
5. compare tuples from steps 2 and 4

Also apparently we can't create BGR images with `Image.new()`? And there was an error with "I;16N" too: "unknown raw mode for given image mode".
I've created PR #6825. With that, tests from bytes_tests pass, as well as your other code examples earlier.
I attempted to [test the problem with the `color` argument of `Image.new`](https://github.com/radarhere/Pillow/commit/689129ce74e78ed47b827e432393151b1d906474), but found that [it passed on both big and little endian.](https://github.com/radarhere/Pillow/actions/runs/3784925909)

Would you be able to post code examples of problems apart from `putdata()`?
I added another test to my branch for creating an image with the `color` argument and I don't see any error either (other than some problems with "I;16N"). I think it was just `putdata()` causing the issues.

Resolving patch:
diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py
--- a/src/PIL/PyAccess.py
+++ b/src/PIL/PyAccess.py
@@ -320,6 +320,7 @@ def set_pixel(self, x, y, color):
     "1": _PyAccess8,
     "L": _PyAccess8,
     "P": _PyAccess8,
+    "I;16N": _PyAccessI16_N,
     "LA": _PyAccess32_2,
     "La": _PyAccess32_2,
     "PA": _PyAccess32_2,


Test patch:
diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py
--- a/Tests/test_image_access.py
+++ b/Tests/test_image_access.py
@@ -275,15 +275,10 @@ def test_get_vs_c(self):
         # self._test_get_access(hopper('PA')) # PA -- how do I make a PA image?
         self._test_get_access(hopper("F"))
 
-        im = Image.new("I;16", (10, 10), 40000)
-        self._test_get_access(im)
-        im = Image.new("I;16L", (10, 10), 40000)
-        self._test_get_access(im)
-        im = Image.new("I;16B", (10, 10), 40000)
-        self._test_get_access(im)
-
-        im = Image.new("I", (10, 10), 40000)
-        self._test_get_access(im)
+        for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
+            im = Image.new(mode, (10, 10), 40000)
+            self._test_get_access(im)
+
         # These don't actually appear to be modes that I can actually make,
         # as unpack sets them directly into the I mode.
         # im = Image.new('I;32L', (10, 10), -2**10)
@@ -322,15 +317,10 @@ def test_set_vs_c(self):
         # self._test_set_access(i, (128, 128))  #PA  -- undone how to make
         self._test_set_access(hopper("F"), 1024.0)
 
-        im = Image.new("I;16", (10, 10), 40000)
-        self._test_set_access(im, 45000)
-        im = Image.new("I;16L", (10, 10), 40000)
-        self._test_set_access(im, 45000)
-        im = Image.new("I;16B", (10, 10), 40000)
-        self._test_set_access(im, 45000)
+        for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
+            im = Image.new(mode, (10, 10), 40000)
+            self._test_set_access(im, 45000)
 
-        im = Image.new("I", (10, 10), 40000)
-        self._test_set_access(im, 45000)
         # im = Image.new('I;32L', (10, 10), -(2**10))
         # self._test_set_access(im, -(2**13)+1)
         # im = Image.new('I;32B', (10, 10), 2**10)
diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py
--- a/Tests/test_lib_pack.py
+++ b/Tests/test_lib_pack.py
@@ -207,6 +207,9 @@ def test_I(self):
                 0x01000083,
             )
 
+    def test_I16(self):
+        self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
+
     def test_F_float(self):
         self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
 
@@ -761,10 +764,12 @@ def test_I16(self):
             self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605)
             self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605)
             self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605)
+            self.assert_unpack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
         else:
             self.assert_unpack("I;16", "I;16N", 2, 0x0102, 0x0304, 0x0506)
             self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506)
             self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506)
+            self.assert_unpack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506)
 
     def test_CMYK16(self):
         self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16))
diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py
--- a/Tests/test_mode_i16.py
+++ b/Tests/test_mode_i16.py
@@ -88,10 +88,7 @@ def tobytes(mode):
 def test_convert():
     im = original.copy()
 
-    verify(im.convert("I;16"))
-    verify(im.convert("I;16").convert("L"))
-    verify(im.convert("I;16").convert("I"))
-
-    verify(im.convert("I;16B"))
-    verify(im.convert("I;16B").convert("L"))
-    verify(im.convert("I;16B").convert("I"))
+    for mode in ("I;16", "I;16B", "I;16N"):
+        verify(im.convert(mode))
+        verify(im.convert(mode).convert("L"))
+        verify(im.convert(mode).convert("I"))