diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 7f8c2d316..68c623395 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -27,6 +27,8 @@ from . import Image, ImageFile, ImagePalette, ImageChops, ImageSequence from ._binary import i8, i16le as i16, o8, o16le as o16 +import itertools + __version__ = "0.9" @@ -333,15 +335,22 @@ def _normalize_palette(im, palette, info): :param info: encoderinfo :returns: Image object """ + source_palette = None + if palette: + # a bytes palette + if isinstance(palette, (bytes, bytearray, list)): + source_palette = bytearray(palette[:768]) + if isinstance(palette, ImagePalette.ImagePalette): + source_palette = bytearray(itertools.chain.from_iterable( + zip(palette.palette[:256], + palette.palette[256:512], + palette.palette[512:768]))) + if im.mode == "P": - if palette and isinstance(palette, bytes): - source_palette = palette[:768] - else: + if not source_palette: source_palette = im.im.getpalette("RGB")[:768] else: # L-mode - if palette and isinstance(palette, bytes): - source_palette = palette[:768] - else: + if not source_palette: source_palette = bytearray(i//3 for i in range(768)) im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette) @@ -436,7 +445,6 @@ def _save_all(im, fp, filename): def _save(im, fp, filename, save_all=False): im.encoderinfo.update(im.info) - # header try: palette = im.encoderinfo["palette"] diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index f9ba9364d..482a0a472 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -455,24 +455,40 @@ class TestFileGif(PillowTestCase): # generate an L mode image with a separate palette im = hopper('P') - im_l = im.split()[0] - palette = im.palette.getdata() + im_l = Image.frombytes('L', im.size, im.tobytes()) + palette = bytes(bytearray(im.getpalette())) out = self.tempfile('temp.gif') im_l.save(out, palette=palette) reloaded = Image.open(out) - self.assert_image_equal(reloaded, im) + self.assert_image_equal(reloaded.convert('RGB'), im.convert('RGB')) + def test_palette_save_P(self): # pass in a different palette, then construct what the image # would look like. + # Forcing a non-straight grayscale palette. im = hopper('P') - palette = ImagePalette.ImagePalette('RGB') + palette = bytes(bytearray([255-i//3 for i in range(768)])) out = self.tempfile('temp.gif') - im.save(out, palette=palette.getdata()) + im.save(out, palette=palette) + + reloaded = Image.open(out) + im.putpalette(palette) + self.assert_image_equal(reloaded, im) + + def test_palette_save_ImagePalette(self): + # pass in a different palette, as an ImagePalette.ImagePalette + # effectively the same as test_palette_save_P + + im = hopper('P') + palette = ImagePalette.ImagePalette('RGB', list(range(256))[::-1]*3) + + out = self.tempfile('temp.gif') + im.save(out, palette=palette) reloaded = Image.open(out) im.putpalette(palette) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 694027a47..760c6019d 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -124,8 +124,12 @@ are available:: eliminating unused colors. This is only useful if the palette can be compressed to the next smaller power of 2 elements. -**palette** - Use the specified palette for the saved image. +**palette** + Use the specified palette for the saved image. The palette should + be a bytes or bytearray object containing the palette entries in + RGBRGB... form. It should be no more than 768 bytes. Alternately, + the palette can be passed in as an + :py:class:`PIL.ImagePalette.ImagePalette` object. Reading local images ~~~~~~~~~~~~~~~~~~~~