diff --git a/CHANGES.rst b/CHANGES.rst index d36ca4ce8..6a9712da3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,9 @@ Changelog (Pillow) - Fixed opening and saving odd sized .pcx files, fixes #523 [wiredfool] +- Fixed palette handling when converting from mode P->RGB->P + [d_schmidt] + - Fixed saving mode P image as a PNG with transparency = palette color 0 [d-schmidt] diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index aed525824..c6d449425 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -237,7 +237,10 @@ def _save(im, fp, filename): # convert on the fly (EXPERIMENTAL -- I'm not sure PIL # should automatically convert images on save...) if Image.getmodebase(im.mode) == "RGB": - imOut = im.convert("P") + palette_size = 256 + if im.palette: + palette_size = len(im.palette.getdata()[1]) // 3 + imOut = im.convert("P", palette=1, colors=palette_size) rawmode = "P" else: imOut = im.convert("L") @@ -248,9 +251,13 @@ def _save(im, fp, filename): palette = im.encoderinfo["palette"] except KeyError: palette = None - if im.palette: - # use existing if possible - palette = im.palette.getdata()[1] + im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) + if im.encoderinfo["optimize"]: + # When the mode is L, and we optimize, we end up with + # im.mode == P and rawmode = L, which fails. + # If we're optimizing the palette, we're going to be + # in a rawmode of P anyway. + rawmode = 'P' header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo) for s in header: @@ -391,6 +398,9 @@ def getheader(im, palette=None, info=None): for i in range(len(imageBytes)): imageBytes[i] = newPositions[imageBytes[i]] im.frombytes(bytes(imageBytes)) + newPaletteBytes = paletteBytes + (768 - len(paletteBytes)) * b'\x00' + im.putpalette(newPaletteBytes) + im.palette = ImagePalette.ImagePalette("RGB", palette = paletteBytes, size = len(paletteBytes)) if not paletteBytes: paletteBytes = sourcePalette diff --git a/PIL/Image.py b/PIL/Image.py index 0d8a235eb..75e7efc75 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -737,7 +737,10 @@ class Image: if mode == "P" and palette == ADAPTIVE: im = self.im.quantize(colors) - return self._new(im) + new = self._new(im) + from PIL import ImagePalette + new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) + return new # colorspace conversion if dither is None: diff --git a/PIL/ImagePalette.py b/PIL/ImagePalette.py index 61affdb19..d5b9d04eb 100644 --- a/PIL/ImagePalette.py +++ b/PIL/ImagePalette.py @@ -23,13 +23,14 @@ from PIL import Image, ImageColor class ImagePalette: "Color palette for palette mapped images" - def __init__(self, mode = "RGB", palette = None): + def __init__(self, mode = "RGB", palette = None, size = 0): self.mode = mode self.rawmode = None # if set, palette contains raw data self.palette = palette or list(range(256))*len(self.mode) self.colors = {} self.dirty = None - if len(self.mode)*256 != len(self.palette): + if ((size == 0 and len(self.mode)*256 != len(self.palette)) or + (size != 0 and size != len(self.palette))): raise ValueError("wrong palette size") def getdata(self): diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index 18abf27d3..2bdf74608 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -505,7 +505,7 @@ def _save(im, fp, filename, chunk=putchunk, check=0): else: # check palette contents if im.palette: - colors = len(im.palette.getdata()[1])//3 + colors = max(min(len(im.palette.getdata()[1])//3, 256), 2) else: colors = 256 diff --git a/Tests/images/test.colors.gif b/Tests/images/test.colors.gif new file mode 100644 index 000000000..0faf96760 Binary files /dev/null and b/Tests/images/test.colors.gif differ diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 3a6478e2a..4318e178e 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -38,7 +38,7 @@ def test_roundtrip(): def test_roundtrip2(): #see https://github.com/python-imaging/Pillow/issues/403 - out = 'temp.gif'#tempfile('temp.gif') + out = tempfile('temp.gif') im = Image.open('Images/lena.gif') im2 = im.copy() im2.save(out) @@ -46,3 +46,42 @@ def test_roundtrip2(): assert_image_similar(reread.convert('RGB'), lena(), 50) + +def test_palette_handling(): + # see https://github.com/python-imaging/Pillow/issues/513 + + im = Image.open('Images/lena.gif') + im = im.convert('RGB') + + im = im.resize((100,100), Image.ANTIALIAS) + im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) + + f = tempfile('temp.gif') + im2.save(f, optimize=True) + + reloaded = Image.open(f) + + assert_image_similar(im, reloaded.convert('RGB'), 10) + +def test_palette_434(): + # see https://github.com/python-imaging/Pillow/issues/434 + + def roundtrip(im, *args, **kwargs): + out = tempfile('temp.gif') + im.save(out, *args, **kwargs) + reloaded = Image.open(out) + + return [im, reloaded] + + orig = "Tests/images/test.colors.gif" + im = Image.open(orig) + + assert_image_equal(*roundtrip(im)) + assert_image_equal(*roundtrip(im, optimize=True)) + + im = im.convert("RGB") + # check automatic P conversion + reloaded = roundtrip(im)[1].convert('RGB') + assert_image_equal(im, reloaded) + +