mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-13 02:36:17 +03:00
Merge pull request #315 from d-schmidt/master
fix Issue #312 + gif optimize improvement
This commit is contained in:
commit
07f338f194
|
@ -54,7 +54,6 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
format = "GIF"
|
format = "GIF"
|
||||||
format_description = "Compuserve GIF"
|
format_description = "Compuserve GIF"
|
||||||
|
|
||||||
global_palette = None
|
global_palette = None
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
|
@ -71,13 +70,9 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
raise SyntaxError("not a GIF file")
|
raise SyntaxError("not a GIF file")
|
||||||
|
|
||||||
self.info["version"] = s[:6]
|
self.info["version"] = s[:6]
|
||||||
|
|
||||||
self.size = i16(s[6:]), i16(s[8:])
|
self.size = i16(s[6:]), i16(s[8:])
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
flags = i8(s[10])
|
flags = i8(s[10])
|
||||||
|
|
||||||
bits = (flags & 7) + 1
|
bits = (flags & 7) + 1
|
||||||
|
|
||||||
if flags & 128:
|
if flags & 128:
|
||||||
|
@ -122,7 +117,8 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.im = self.dispose
|
self.im = self.dispose
|
||||||
self.dispose = None
|
self.dispose = None
|
||||||
|
|
||||||
self.palette = self.global_palette
|
from copy import copy
|
||||||
|
self.palette = copy(self.global_palette)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
|
@ -252,6 +248,9 @@ def _save(im, fp, filename):
|
||||||
palette = im.encoderinfo["palette"]
|
palette = im.encoderinfo["palette"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
palette = None
|
palette = None
|
||||||
|
if im.palette:
|
||||||
|
# use existing if possible
|
||||||
|
palette = im.palette.getdata()[1]
|
||||||
|
|
||||||
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
header, usedPaletteColors = getheader(imOut, palette, im.encoderinfo)
|
||||||
for s in header:
|
for s in header:
|
||||||
|
@ -344,70 +343,68 @@ def getheader(im, palette=None, info=None):
|
||||||
|
|
||||||
optimize = info and info.get("optimize", 0)
|
optimize = info and info.get("optimize", 0)
|
||||||
|
|
||||||
# start of header
|
# Header Block
|
||||||
|
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||||
header = [
|
header = [
|
||||||
b"GIF87a" + # magic
|
b"GIF87a" + # signature + version
|
||||||
o16(im.size[0]) + # size
|
o16(im.size[0]) + # canvas width
|
||||||
o16(im.size[1])
|
o16(im.size[1]) # canvas height
|
||||||
]
|
]
|
||||||
|
|
||||||
# if the user adds a palette, use it
|
if im.mode == "P":
|
||||||
usedPaletteColors = None
|
if palette and isinstance(palette, bytes):
|
||||||
|
sourcePalette = palette[:768]
|
||||||
|
else:
|
||||||
|
sourcePalette = im.im.getpalette("RGB")[:768]
|
||||||
|
else: # L-mode
|
||||||
|
if palette and isinstance(palette, bytes):
|
||||||
|
sourcePalette = palette[:768]
|
||||||
|
else:
|
||||||
|
sourcePalette = bytearray([i//3 for i in range(768)])
|
||||||
|
|
||||||
if palette is not None and isinstance(palette, bytes):
|
usedPaletteColors = paletteBytes = None
|
||||||
paletteBytes = palette[:768]
|
|
||||||
else:
|
if optimize:
|
||||||
usedPaletteColors = []
|
usedPaletteColors = []
|
||||||
|
|
||||||
if optimize:
|
# check which colors are used
|
||||||
# minimize color palette if wanted
|
i = 0
|
||||||
|
for count in im.histogram():
|
||||||
|
if count:
|
||||||
|
usedPaletteColors.append(i)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# create the new palette if not every color is used
|
||||||
|
if len(usedPaletteColors) < 256:
|
||||||
|
paletteBytes = b""
|
||||||
|
newPositions = {}
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
for count in im.histogram():
|
# pick only the used colors from the palette
|
||||||
if count:
|
for oldPosition in usedPaletteColors:
|
||||||
usedPaletteColors.append(i)
|
paletteBytes += sourcePalette[oldPosition*3:oldPosition*3+3]
|
||||||
|
newPositions[oldPosition] = i
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
countUsedPaletteColors = len(usedPaletteColors)
|
# replace the palette color id of all pixel with the new id
|
||||||
|
|
||||||
# create the global palette
|
|
||||||
if im.mode == "P":
|
|
||||||
# colour palette
|
|
||||||
if countUsedPaletteColors > 0 and countUsedPaletteColors < 256:
|
|
||||||
paletteBytes = b"";
|
|
||||||
# pick only the used colors from the palette
|
|
||||||
for i in usedPaletteColors:
|
|
||||||
paletteBytes += im.im.getpalette("RGB")[i*3:i*3+3]
|
|
||||||
else :
|
|
||||||
paletteBytes = im.im.getpalette("RGB")[:768]
|
|
||||||
else:
|
|
||||||
# greyscale
|
|
||||||
if countUsedPaletteColors > 0 and countUsedPaletteColors < 256:
|
|
||||||
paletteBytes = b"";
|
|
||||||
# add only the used grayscales to the palette
|
|
||||||
for i in usedPaletteColors:
|
|
||||||
paletteBytes += o8(i)*3
|
|
||||||
else :
|
|
||||||
paletteBytes = bytearray([i//3 for i in range(768)])
|
|
||||||
|
|
||||||
# TODO improve this, maybe add numpy support
|
|
||||||
# replace the palette color id of all pixel with the new id
|
|
||||||
if countUsedPaletteColors > 0 and countUsedPaletteColors < 256:
|
|
||||||
imageBytes = bytearray(im.tobytes())
|
imageBytes = bytearray(im.tobytes())
|
||||||
for i in range(len(imageBytes)):
|
for i in range(len(imageBytes)):
|
||||||
for newI in range(countUsedPaletteColors):
|
imageBytes[i] = newPositions[imageBytes[i]]
|
||||||
if imageBytes[i] == usedPaletteColors[newI]:
|
|
||||||
imageBytes[i] = newI
|
|
||||||
break
|
|
||||||
|
|
||||||
im.frombytes(bytes(imageBytes))
|
im.frombytes(bytes(imageBytes))
|
||||||
|
|
||||||
|
if not paletteBytes:
|
||||||
|
paletteBytes = sourcePalette
|
||||||
|
|
||||||
|
# Logical Screen Descriptor
|
||||||
# calculate the palette size for the header
|
# calculate the palette size for the header
|
||||||
import math
|
import math
|
||||||
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
|
colorTableSize = int(math.ceil(math.log(len(paletteBytes)//3, 2)))-1
|
||||||
if colorTableSize < 0: colorTableSize = 0
|
if colorTableSize < 0: colorTableSize = 0
|
||||||
header.append(o8(colorTableSize + 128)) # size of global color table + global color table flag
|
# size of global color table + global color table flag
|
||||||
header.append(o8(0) + o8(0)) # background + reserved/aspect
|
header.append(o8(colorTableSize + 128))
|
||||||
# end of screen descriptor header
|
# background + reserved/aspect
|
||||||
|
header.append(o8(0) + o8(0))
|
||||||
|
# end of Logical Screen Descriptor
|
||||||
|
|
||||||
# add the missing amount of bytes
|
# add the missing amount of bytes
|
||||||
# the palette has to be 2<<n in size
|
# the palette has to be 2<<n in size
|
||||||
|
@ -415,7 +412,7 @@ def getheader(im, palette=None, info=None):
|
||||||
if actualTargetSizeDiff > 0:
|
if actualTargetSizeDiff > 0:
|
||||||
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
|
paletteBytes += o8(0) * 3 * actualTargetSizeDiff
|
||||||
|
|
||||||
# global color palette
|
# Header + Logical Screen Descriptor + Global Color Table
|
||||||
header.append(paletteBytes)
|
header.append(paletteBytes)
|
||||||
return header, usedPaletteColors
|
return header, usedPaletteColors
|
||||||
|
|
||||||
|
|
|
@ -499,26 +499,24 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
|
|
||||||
#
|
#
|
||||||
# attempt to minimize storage requirements for palette images
|
# attempt to minimize storage requirements for palette images
|
||||||
|
|
||||||
if "bits" in im.encoderinfo:
|
if "bits" in im.encoderinfo:
|
||||||
|
|
||||||
# number of bits specified by user
|
# number of bits specified by user
|
||||||
n = 1 << im.encoderinfo["bits"]
|
colors = 1 << im.encoderinfo["bits"]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# check palette contents
|
# check palette contents
|
||||||
n = 256 # FIXME
|
if im.palette:
|
||||||
|
colors = len(im.palette.getdata()[1])//3
|
||||||
|
else:
|
||||||
|
colors = 256
|
||||||
|
|
||||||
if n <= 2:
|
if colors <= 2:
|
||||||
bits = 1
|
bits = 1
|
||||||
elif n <= 4:
|
elif colors <= 4:
|
||||||
bits = 2
|
bits = 2
|
||||||
elif n <= 16:
|
elif colors <= 16:
|
||||||
bits = 4
|
bits = 4
|
||||||
else:
|
else:
|
||||||
bits = 8
|
bits = 8
|
||||||
|
|
||||||
if bits != 8:
|
if bits != 8:
|
||||||
mode = "%s;%d" % (mode, bits)
|
mode = "%s;%d" % (mode, bits)
|
||||||
|
|
||||||
|
@ -555,8 +553,11 @@ def _save(im, fp, filename, chunk=putchunk, check=0):
|
||||||
b'\0') # 12: interlace flag
|
b'\0') # 12: interlace flag
|
||||||
|
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
palette_bytes = (2 ** bits) * 3
|
palette_byte_number = (2 ** bits) * 3
|
||||||
chunk(fp, b"PLTE", im.im.getpalette("RGB")[:palette_bytes])
|
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
|
||||||
|
while len(palette_bytes) < palette_byte_number:
|
||||||
|
palette_bytes += b'\0'
|
||||||
|
chunk(fp, b"PLTE", palette_bytes)
|
||||||
|
|
||||||
if "transparency" in im.encoderinfo:
|
if "transparency" in im.encoderinfo:
|
||||||
if im.mode == "P":
|
if im.mode == "P":
|
||||||
|
|
Loading…
Reference in New Issue
Block a user