Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PNG: unnecessary padding in PLTE #2202

Closed
pdknsk opened this issue Nov 7, 2016 · 3 comments · Fixed by #5330
Closed

PNG: unnecessary padding in PLTE #2202

pdknsk opened this issue Nov 7, 2016 · 3 comments · Fixed by #5330
Assignees
Labels
Bug Any unexpected behavior, until confirmed feature.
Projects
Milestone

Comments

@pdknsk
Copy link

pdknsk commented Nov 7, 2016

Pillow incorrectly enforces a minimum of 2 palette entries.

from PIL import Image
img = Image.new(size = (1, 1), mode = 'P')
img.putpalette((1, 1, 1))
img.save('file.png')
$ pngcheck -v file.png 
...
    1 x 1 image, 1-bit palette, non-interlaced
  chunk PLTE at offset 0x00025, length 6: 2 palette entries
...

And pads the palette to bit ** 2 entries.

from PIL import Image
img = Image.new(size = (3, 3), mode = 'P')
img.putpalette((1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5)) # 5
img.save('file.png')
$ pngcheck -v file.png 
...
    3 x 3 image, 4-bit palette, non-interlaced
  chunk PLTE at offset 0x00025, length 48: 16 palette entries
...

It's neither required nor recommended.

It is permissible to have fewer entries than the bit depth would allow.

@aclark4life aclark4life added this to the Future milestone Jan 8, 2017
@pdknsk
Copy link
Author

pdknsk commented May 9, 2017

diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py
index 5e5eb14..9f883c9 100644
--- a/PIL/PngImagePlugin.py
+++ b/PIL/PngImagePlugin.py
@@ -671,7 +671,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
         else:
             # check palette contents
             if im.palette:
-                colors = max(min(len(im.palette.getdata()[1])//3, 256), 2)
+                colors = max(min(len(im.palette.getdata()[1])//3, 256), 1)
             else:
                 colors = 256
 
@@ -740,7 +740,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
                 chunk(fp, cid, data)
 
     if im.mode == "P":
-        palette_byte_number = (2 ** bits) * 3
+        palette_byte_number = min(2 ** bits, colors) * 3
         palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
         while len(palette_bytes) < palette_byte_number:
             palette_bytes += b'\0'
@@ -752,7 +752,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
     if transparency or transparency == 0:
         if im.mode == "P":
             # limit to actual palette size
-            alpha_bytes = 2**bits
+            alpha_bytes = min(2**bits, colors)
             if isinstance(transparency, bytes):
                 chunk(fp, b"tRNS", transparency[:alpha_bytes])
             else:
@@ -773,7 +773,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
     else:
         if im.mode == "P" and im.im.getpalettemode() == "RGBA":
             alpha = im.im.getpalette("RGBA", "A")
-            alpha_bytes = 2**bits
+            alpha_bytes = min(2**bits, colors)
             chunk(fp, b"tRNS", alpha[:alpha_bytes])
 
     dpi = im.encoderinfo.get("dpi")

Works, but it's not tested extensively.

img = Image.frombytes('P', (1, 1), '\x00')
img.putpalette('\xFF\x00\x00') # R
img.save('file.png')
# chunk PLTE at offset 0x00025, length 3: 1 palette entry

img = Image.frombytes('P', (3, 3), '\x00' * 3 + '\x01' * 3 + '\x02' * 3)
img.putpalette('\xFF\x00\x00' '\x00\xFF\x00' '\x00\x00\xFF') # RGB
img.save('file.png')
# chunk PLTE at offset 0x00025, length 9: 3 palette entries

img.save('file.png', transparency = 0)
# chunk PLTE at offset 0x00025, length 9: 3 palette entries
# chunk tRNS at offset 0x0003a, length 1: 1 transparency entry

img.save('file.png', transparency = '\x00\x80\xFF')
# chunk PLTE at offset 0x00025, length 9: 3 palette entries
# chunk tRNS at offset 0x0003a, length 3: 3 transparency entries

@radarhere
Copy link
Member

Just a note - the minimum of 2 was added in 6457eed

@aclark4life aclark4life added this to Backlog in Pillow May 11, 2019
@aclark4life aclark4life added Bug Any unexpected behavior, until confirmed feature. and removed Enhancement labels May 11, 2019
@aclark4life aclark4life moved this from Backlog to Icebox in Pillow May 11, 2019
@radarhere
Copy link
Member

I've created #5330 to resolve this.

Pillow automation moved this from Icebox to Closed Mar 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Any unexpected behavior, until confirmed feature.
Projects
Pillow
  
Closed
Development

Successfully merging a pull request may close this issue.

3 participants