mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-09 14:54:46 +03:00
Merge
This commit is contained in:
commit
d9cd57a5f7
|
@ -20,7 +20,8 @@ install:
|
||||||
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||||
- "travis_retry pip install cffi"
|
- "travis_retry pip install cffi"
|
||||||
- "travis_retry pip install coverage nose"
|
- "travis_retry pip install coverage nose"
|
||||||
- "travis_retry pip install pyroma"
|
# Pyroma tests sometimes hang on PyPy; skip for PyPy
|
||||||
|
- if [ $TRAVIS_PYTHON_VERSION != "pypy" ]; then travis_retry pip install pyroma; fi
|
||||||
|
|
||||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
|
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
|
||||||
|
|
||||||
|
|
18
CHANGES.rst
18
CHANGES.rst
|
@ -4,6 +4,18 @@ Changelog (Pillow)
|
||||||
2.9.0 (Unreleased)
|
2.9.0 (Unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Provide n_frames attribute to multi-frame formats #1261
|
||||||
|
[anntzer, radarhere]
|
||||||
|
|
||||||
|
- Add duration and loop set to GifImagePlugin #1172
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Ico files are little endian #1232
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Upgrade olefile from 0.30 to 0.42b #1226
|
||||||
|
[radarhere, decalage2]
|
||||||
|
|
||||||
- Setting transparency value to 0 when the tRNS contains only null byte(s) #1239
|
- Setting transparency value to 0 when the tRNS contains only null byte(s) #1239
|
||||||
[juztin]
|
[juztin]
|
||||||
|
|
||||||
|
@ -31,6 +43,12 @@ Changelog (Pillow)
|
||||||
- Tiff: allow writing floating point tag values #1113
|
- Tiff: allow writing floating point tag values #1113
|
||||||
[bpedersen2]
|
[bpedersen2]
|
||||||
|
|
||||||
|
2.8.2 (2015-06-06)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Bug fix: Fixed Tiff handling of bad EXIF data
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
2.8.1 (2015-04-02)
|
2.8.1 (2015-04-02)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
105
Makefile
105
Makefile
|
@ -1,28 +1,5 @@
|
||||||
.PHONY: pre clean install test inplace coverage test-dep help docs livedocs
|
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||||
|
.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test
|
||||||
help:
|
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " clean remove build products"
|
|
||||||
@echo " install make and install"
|
|
||||||
@echo " test run tests on installed pillow"
|
|
||||||
@echo " inplace make inplace extension"
|
|
||||||
@echo " coverage run coverage test (in progress)"
|
|
||||||
@echo " docs make html docs"
|
|
||||||
@echo " docserver run an http server on the docs directory"
|
|
||||||
@echo " test-dep install coveraget and test dependencies"
|
|
||||||
|
|
||||||
pre:
|
|
||||||
virtualenv .
|
|
||||||
bin/pip install -r requirements.txt
|
|
||||||
bin/python setup.py develop
|
|
||||||
bin/python selftest.py
|
|
||||||
bin/nosetests Tests/test_*.py
|
|
||||||
bin/python setup.py install
|
|
||||||
bin/python test-installed.py
|
|
||||||
check-manifest
|
|
||||||
pyroma .
|
|
||||||
viewdoc
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
python setup.py clean
|
python setup.py clean
|
||||||
|
@ -30,46 +7,70 @@ clean:
|
||||||
rm -r build || true
|
rm -r build || true
|
||||||
find . -name __pycache__ | xargs rm -r || true
|
find . -name __pycache__ | xargs rm -r || true
|
||||||
|
|
||||||
install:
|
|
||||||
python setup.py install
|
|
||||||
python selftest.py --installed
|
|
||||||
|
|
||||||
test:
|
|
||||||
python test-installed.py
|
|
||||||
|
|
||||||
inplace: clean
|
|
||||||
python setup.py build_ext --inplace
|
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
# requires nose-cov
|
|
||||||
coverage erase
|
coverage erase
|
||||||
coverage run --parallel-mode --include=PIL/* selftest.py
|
coverage run --parallel-mode --include=PIL/* selftest.py
|
||||||
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
|
nosetests --with-cov --cov='PIL/' --cov-report=html Tests/test_*.py
|
||||||
# doesn't combine properly before report,
|
# Doesn't combine properly before report, writing report instead of displaying invalid report.
|
||||||
# writing report instead of displaying invalid report
|
|
||||||
rm -r htmlcov || true
|
rm -r htmlcov || true
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage report
|
coverage report
|
||||||
|
|
||||||
test-dep:
|
doc:
|
||||||
pip install coveralls nose nose-cov pep8 pyflakes
|
|
||||||
|
|
||||||
docs:
|
|
||||||
$(MAKE) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
docserver:
|
docserve:
|
||||||
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null&
|
||||||
|
|
||||||
# Test sdist upload via test.pythonpackages.com, no creds required
|
help:
|
||||||
# .pypirc:
|
@echo "Welcome to Pillow development. Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " clean remove build products"
|
||||||
|
@echo " coverage run coverage test (in progress)"
|
||||||
|
@echo " doc make html docs"
|
||||||
|
@echo " docserve run an http server on the docs directory"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " inplace make inplace extension"
|
||||||
|
@echo " install make and install"
|
||||||
|
@echo " install-req install documentation and test dependencies"
|
||||||
|
@echo " release-test run code and package tests before release"
|
||||||
|
@echo " test run tests on installed pillow"
|
||||||
|
@echo " upload build and upload sdists to PyPI"
|
||||||
|
@echo " upload-test build and upload sdists to test.pythonpackages.com"
|
||||||
|
|
||||||
|
inplace: clean
|
||||||
|
python setup.py build_ext --inplace
|
||||||
|
|
||||||
|
install:
|
||||||
|
python setup.py install
|
||||||
|
python selftest.py --installed
|
||||||
|
|
||||||
|
install-req:
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
release-test:
|
||||||
|
$(MAKE) install-req
|
||||||
|
python setup.py develop
|
||||||
|
python selftest.py
|
||||||
|
nosetests Tests/test_*.py
|
||||||
|
python setup.py install
|
||||||
|
python test-installed.py
|
||||||
|
check-manifest
|
||||||
|
pyroma .
|
||||||
|
viewdoc
|
||||||
|
|
||||||
|
sdist:
|
||||||
|
python setup.py sdist --format=gztar,zip
|
||||||
|
|
||||||
|
test:
|
||||||
|
python test-installed.py
|
||||||
|
|
||||||
|
# https://docs.python.org/2/distutils/packageindex.html#the-pypirc-file
|
||||||
|
upload-test:
|
||||||
# [test]
|
# [test]
|
||||||
# username:
|
# username:
|
||||||
# password:
|
# password:
|
||||||
# repository = http://test.pythonpackages.com
|
# repository = http://test.pythonpackages.com
|
||||||
sdisttest:
|
python setup.py sdist --format=gztar,zip upload -r test
|
||||||
python setup.py sdist --format=zip upload -r test
|
|
||||||
sdistup:
|
upload:
|
||||||
python setup.py sdist --format=zip upload
|
python setup.py sdist --format=gztar,zip upload
|
||||||
python setup.py sdist upload
|
|
||||||
sdist:
|
|
||||||
python setup.py sdist --format=zip
|
|
||||||
|
|
|
@ -62,6 +62,10 @@ class DcxImageFile(PcxImageFile):
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return len(self._offset)
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame >= len(self._offset):
|
if frame >= len(self._offset):
|
||||||
raise EOFError("attempt to seek outside DCX directory")
|
raise EOFError("attempt to seek outside DCX directory")
|
||||||
|
|
|
@ -86,9 +86,10 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||||
|
|
||||||
# set things up to decode first frame
|
# set things up to decode first frame
|
||||||
self.frame = -1
|
self.__frame = -1
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
|
self.__rewind = self.fp.tell()
|
||||||
|
self._n_frames = None
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
def _palette(self, palette, shift):
|
def _palette(self, palette, shift):
|
||||||
|
@ -109,11 +110,35 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
palette[i] = (r, g, b)
|
palette[i] = (r, g, b)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
def seek(self, frame):
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
if self._n_frames is None:
|
||||||
|
current = self.tell()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
self.seek(self.tell() + 1)
|
||||||
|
except EOFError:
|
||||||
|
self._n_frames = self.tell() + 1
|
||||||
|
self.seek(current)
|
||||||
|
return self._n_frames
|
||||||
|
|
||||||
if frame != self.frame + 1:
|
def seek(self, frame):
|
||||||
|
if frame == self.__frame:
|
||||||
|
return
|
||||||
|
if frame < self.__frame:
|
||||||
|
self._seek(0)
|
||||||
|
for f in range(self.__frame + 1, frame + 1):
|
||||||
|
self._seek(f)
|
||||||
|
|
||||||
|
def _seek(self, frame):
|
||||||
|
if frame == 0:
|
||||||
|
self.__frame = -1
|
||||||
|
self.__fp.seek(self.__rewind)
|
||||||
|
self.__offset = 128
|
||||||
|
|
||||||
|
if frame != self.__frame + 1:
|
||||||
raise ValueError("cannot seek to frame %d" % frame)
|
raise ValueError("cannot seek to frame %d" % frame)
|
||||||
self.frame = frame
|
self.__frame = frame
|
||||||
|
|
||||||
# move to next frame
|
# move to next frame
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
|
@ -128,11 +153,10 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
self.decodermaxblock = framesize
|
self.decodermaxblock = framesize
|
||||||
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
|
self.tile = [("fli", (0, 0)+self.size, self.__offset, None)]
|
||||||
|
|
||||||
self.__offset = self.__offset + framesize
|
self.__offset += framesize
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
|
return self.__frame
|
||||||
return self.frame
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# registry
|
# registry
|
||||||
|
|
|
@ -87,9 +87,30 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
self.__rewind = self.fp.tell()
|
self.__rewind = self.fp.tell()
|
||||||
self.seek(0) # get ready to read first frame
|
self._n_frames = None
|
||||||
|
self._seek(0) # get ready to read first frame
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
if self._n_frames is None:
|
||||||
|
current = self.tell()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
self.seek(self.tell() + 1)
|
||||||
|
except EOFError:
|
||||||
|
self._n_frames = self.tell() + 1
|
||||||
|
self.seek(current)
|
||||||
|
return self._n_frames
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
if frame == self.__frame:
|
||||||
|
return
|
||||||
|
if frame < self.__frame:
|
||||||
|
self._seek(0)
|
||||||
|
for f in range(self.__frame + 1, frame + 1):
|
||||||
|
self._seek(f)
|
||||||
|
|
||||||
|
def _seek(self, frame):
|
||||||
|
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
# rewind
|
# rewind
|
||||||
|
@ -292,57 +313,10 @@ def _save(im, fp, filename):
|
||||||
for s in header:
|
for s in header:
|
||||||
fp.write(s)
|
fp.write(s)
|
||||||
|
|
||||||
flags = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
interlace = im.encoderinfo["interlace"]
|
|
||||||
except KeyError:
|
|
||||||
interlace = 1
|
|
||||||
|
|
||||||
# workaround for @PIL153
|
|
||||||
if min(im.size) < 16:
|
|
||||||
interlace = 0
|
|
||||||
|
|
||||||
if interlace:
|
|
||||||
flags = flags | 64
|
|
||||||
|
|
||||||
try:
|
|
||||||
transparency = im.encoderinfo["transparency"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
transparency = int(transparency)
|
|
||||||
# optimize the block away if transparent color is not used
|
|
||||||
transparent_color_exists = True
|
|
||||||
# adjust the transparency index after optimize
|
|
||||||
if used_palette_colors is not None and len(used_palette_colors) < 256:
|
|
||||||
for i in range(len(used_palette_colors)):
|
|
||||||
if used_palette_colors[i] == transparency:
|
|
||||||
transparency = i
|
|
||||||
transparent_color_exists = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
transparent_color_exists = False
|
|
||||||
|
|
||||||
# transparency extension block
|
|
||||||
if transparent_color_exists:
|
|
||||||
fp.write(b"!" +
|
|
||||||
o8(249) + # extension intro
|
|
||||||
o8(4) + # length
|
|
||||||
o8(1) + # transparency info present
|
|
||||||
o16(0) + # duration
|
|
||||||
o8(transparency) + # transparency index
|
|
||||||
o8(0))
|
|
||||||
|
|
||||||
# local image header
|
# local image header
|
||||||
fp.write(b"," +
|
get_local_header(fp, im)
|
||||||
o16(0) + o16(0) + # bounding box
|
|
||||||
o16(im.size[0]) + # size
|
|
||||||
o16(im.size[1]) +
|
|
||||||
o8(flags) + # flags
|
|
||||||
o8(8)) # bits
|
|
||||||
|
|
||||||
im_out.encoderconfig = (8, interlace)
|
im_out.encoderconfig = (8, get_interlace(im))
|
||||||
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
ImageFile._save(im_out, fp, [("gif", (0, 0)+im.size, 0,
|
||||||
RAWMODE[im_out.mode])])
|
RAWMODE[im_out.mode])])
|
||||||
|
|
||||||
|
@ -356,6 +330,85 @@ def _save(im, fp, filename):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_interlace(im):
|
||||||
|
try:
|
||||||
|
interlace = im.encoderinfo["interlace"]
|
||||||
|
except KeyError:
|
||||||
|
interlace = 1
|
||||||
|
|
||||||
|
# workaround for @PIL153
|
||||||
|
if min(im.size) < 16:
|
||||||
|
interlace = 0
|
||||||
|
|
||||||
|
return interlace
|
||||||
|
|
||||||
|
|
||||||
|
def get_local_header(fp, im, offset=(0, 0)):
|
||||||
|
transparent_color_exists = False
|
||||||
|
try:
|
||||||
|
transparency = im.encoderinfo["transparency"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
transparency = int(transparency)
|
||||||
|
# optimize the block away if transparent color is not used
|
||||||
|
transparent_color_exists = True
|
||||||
|
|
||||||
|
if _get_optimize(im, im.encoderinfo):
|
||||||
|
used_palette_colors = _get_used_palette_colors(im)
|
||||||
|
|
||||||
|
# adjust the transparency index after optimize
|
||||||
|
if len(used_palette_colors) < 256:
|
||||||
|
for i in range(len(used_palette_colors)):
|
||||||
|
if used_palette_colors[i] == transparency:
|
||||||
|
transparency = i
|
||||||
|
transparent_color_exists = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
transparent_color_exists = False
|
||||||
|
|
||||||
|
if "duration" in im.encoderinfo:
|
||||||
|
duration = int(im.encoderinfo["duration"] / 10)
|
||||||
|
else:
|
||||||
|
duration = 0
|
||||||
|
if transparent_color_exists or duration != 0:
|
||||||
|
transparency_flag = 1 if transparent_color_exists else 0
|
||||||
|
if not transparent_color_exists:
|
||||||
|
transparency = 0
|
||||||
|
|
||||||
|
fp.write(b"!" +
|
||||||
|
o8(249) + # extension intro
|
||||||
|
o8(4) + # length
|
||||||
|
o8(transparency_flag) + # transparency info present
|
||||||
|
o16(duration) + # duration
|
||||||
|
o8(transparency) + # transparency index
|
||||||
|
o8(0))
|
||||||
|
|
||||||
|
if "loop" in im.encoderinfo:
|
||||||
|
number_of_loops = im.encoderinfo["loop"]
|
||||||
|
fp.write(b"!" +
|
||||||
|
o8(255) + # extension intro
|
||||||
|
o8(11) +
|
||||||
|
b"NETSCAPE2.0" +
|
||||||
|
o8(3) +
|
||||||
|
o8(1) +
|
||||||
|
o16(number_of_loops) + # number of loops
|
||||||
|
o8(0))
|
||||||
|
|
||||||
|
flags = 0
|
||||||
|
|
||||||
|
if get_interlace(im):
|
||||||
|
flags = flags | 64
|
||||||
|
|
||||||
|
fp.write(b"," +
|
||||||
|
o16(offset[0]) + # offset
|
||||||
|
o16(offset[1]) +
|
||||||
|
o16(im.size[0]) + # size
|
||||||
|
o16(im.size[1]) +
|
||||||
|
o8(flags) + # flags
|
||||||
|
o8(8)) # bits
|
||||||
|
|
||||||
|
|
||||||
def _save_netpbm(im, fp, filename):
|
def _save_netpbm(im, fp, filename):
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -405,11 +458,26 @@ def _save_netpbm(im, fp, filename):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# GIF utilities
|
# GIF utilities
|
||||||
|
|
||||||
|
def _get_optimize(im, info):
|
||||||
|
return im.mode in ("P", "L") and info and info.get("optimize", 0)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_used_palette_colors(im):
|
||||||
|
used_palette_colors = []
|
||||||
|
|
||||||
|
# check which colors are used
|
||||||
|
i = 0
|
||||||
|
for count in im.histogram():
|
||||||
|
if count:
|
||||||
|
used_palette_colors.append(i)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return used_palette_colors
|
||||||
|
|
||||||
|
|
||||||
def getheader(im, palette=None, info=None):
|
def getheader(im, palette=None, info=None):
|
||||||
"""Return a list of strings representing a GIF header"""
|
"""Return a list of strings representing a GIF header"""
|
||||||
|
|
||||||
optimize = info and info.get("optimize", 0)
|
|
||||||
|
|
||||||
# Header Block
|
# Header Block
|
||||||
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
# http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||||
header = [
|
header = [
|
||||||
|
@ -431,15 +499,8 @@ def getheader(im, palette=None, info=None):
|
||||||
|
|
||||||
used_palette_colors = palette_bytes = None
|
used_palette_colors = palette_bytes = None
|
||||||
|
|
||||||
if im.mode in ("P", "L") and optimize:
|
if _get_optimize(im, info):
|
||||||
used_palette_colors = []
|
used_palette_colors = _get_used_palette_colors(im)
|
||||||
|
|
||||||
# check which colors are used
|
|
||||||
i = 0
|
|
||||||
for count in im.histogram():
|
|
||||||
if count:
|
|
||||||
used_palette_colors.append(i)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# create the new palette if not every color is used
|
# create the new palette if not every color is used
|
||||||
if len(used_palette_colors) < 256:
|
if len(used_palette_colors) < 256:
|
||||||
|
@ -510,13 +571,7 @@ def getdata(im, offset=(0, 0), **params):
|
||||||
im.encoderinfo = params
|
im.encoderinfo = params
|
||||||
|
|
||||||
# local image header
|
# local image header
|
||||||
fp.write(b"," +
|
get_local_header(fp, im, offset)
|
||||||
o16(offset[0]) + # offset
|
|
||||||
o16(offset[1]) +
|
|
||||||
o16(im.size[0]) + # size
|
|
||||||
o16(im.size[1]) +
|
|
||||||
o8(0) + # flags
|
|
||||||
o8(8)) # bits
|
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
ImageFile._save(im, fp, [("gif", (0, 0)+im.size, 0, RAWMODE[im.mode])])
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ def _save(im, fp, filename):
|
||||||
width, height = im.size
|
width, height = im.size
|
||||||
filter(lambda x: False if (x[0] > width or x[1] > height or
|
filter(lambda x: False if (x[0] > width or x[1] > height or
|
||||||
x[0] > 255 or x[1] > 255) else True, sizes)
|
x[0] > 255 or x[1] > 255) else True, sizes)
|
||||||
fp.write(struct.pack("H", len(sizes))) # idCount(2)
|
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
||||||
offset = fp.tell() + len(sizes)*16
|
offset = fp.tell() + len(sizes)*16
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
width, height = size
|
width, height = size
|
||||||
|
@ -57,7 +57,7 @@ def _save(im, fp, filename):
|
||||||
fp.write(b"\0") # bColorCount(1)
|
fp.write(b"\0") # bColorCount(1)
|
||||||
fp.write(b"\0") # bReserved(1)
|
fp.write(b"\0") # bReserved(1)
|
||||||
fp.write(b"\0\0") # wPlanes(2)
|
fp.write(b"\0\0") # wPlanes(2)
|
||||||
fp.write(struct.pack("H", 32)) # wBitCount(2)
|
fp.write(struct.pack("<H", 32)) # wBitCount(2)
|
||||||
|
|
||||||
image_io = BytesIO()
|
image_io = BytesIO()
|
||||||
tmp = im.copy()
|
tmp = im.copy()
|
||||||
|
@ -66,8 +66,8 @@ def _save(im, fp, filename):
|
||||||
image_io.seek(0)
|
image_io.seek(0)
|
||||||
image_bytes = image_io.read()
|
image_bytes = image_io.read()
|
||||||
bytes_len = len(image_bytes)
|
bytes_len = len(image_bytes)
|
||||||
fp.write(struct.pack("I", bytes_len)) # dwBytesInRes(4)
|
fp.write(struct.pack("<I", bytes_len)) # dwBytesInRes(4)
|
||||||
fp.write(struct.pack("I", offset)) # dwImageOffset(4)
|
fp.write(struct.pack("<I", offset)) # dwImageOffset(4)
|
||||||
current = fp.tell()
|
current = fp.tell()
|
||||||
fp.seek(offset)
|
fp.seek(offset)
|
||||||
fp.write(image_bytes)
|
fp.write(image_bytes)
|
||||||
|
|
|
@ -260,6 +260,10 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
self.tile = [("raw", (0, 0)+self.size, offs,
|
self.tile = [("raw", (0, 0)+self.size, offs,
|
||||||
(self.rawmode, 0, -1))]
|
(self.rawmode, 0, -1))]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return self.info[FRAMES]
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
|
||||||
if frame < 0 or frame >= self.info[FRAMES]:
|
if frame < 0 or frame >= self.info[FRAMES]:
|
||||||
|
|
|
@ -1810,7 +1810,7 @@ class Image(object):
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
|
|
||||||
# FIXME: the different tranform methods need further explanation
|
# FIXME: the different transform methods need further explanation
|
||||||
# instead of bloating the method docs, add a separate chapter.
|
# instead of bloating the method docs, add a separate chapter.
|
||||||
def transform(self, size, method, data=None, resample=NEAREST, fill=1):
|
def transform(self, size, method, data=None, resample=NEAREST, fill=1):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -64,7 +64,7 @@ pyCMS
|
||||||
|
|
||||||
0.0.2 alpha Jan 6, 2002
|
0.0.2 alpha Jan 6, 2002
|
||||||
|
|
||||||
Added try/except statements arount type() checks of
|
Added try/except statements around type() checks of
|
||||||
potential CObjects... Python won't let you use type()
|
potential CObjects... Python won't let you use type()
|
||||||
on them, and raises a TypeError (stupid, if you ask
|
on them, and raises a TypeError (stupid, if you ask
|
||||||
me!)
|
me!)
|
||||||
|
@ -123,8 +123,8 @@ FLAGS = {
|
||||||
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
"NOTCACHE": 64, # Inhibit 1-pixel cache
|
||||||
"NOTPRECALC": 256,
|
"NOTPRECALC": 256,
|
||||||
"NULLTRANSFORM": 512, # Don't transform anyway
|
"NULLTRANSFORM": 512, # Don't transform anyway
|
||||||
"HIGHRESPRECALC": 1024, # Use more memory to give better accurancy
|
"HIGHRESPRECALC": 1024, # Use more memory to give better accuracy
|
||||||
"LOWRESPRECALC": 2048, # Use less memory to minimize resouces
|
"LOWRESPRECALC": 2048, # Use less memory to minimize resources
|
||||||
"WHITEBLACKCOMPENSATION": 8192,
|
"WHITEBLACKCOMPENSATION": 8192,
|
||||||
"BLACKPOINTCOMPENSATION": 8192,
|
"BLACKPOINTCOMPENSATION": 8192,
|
||||||
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
"GAMUTCHECK": 4096, # Out of Gamut alarm
|
||||||
|
@ -573,7 +573,7 @@ def applyTransform(im, transform, inPlace=0):
|
||||||
This function applies a pre-calculated transform (from
|
This function applies a pre-calculated transform (from
|
||||||
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
|
||||||
to an image. The transform can be used for multiple images, saving
|
to an image. The transform can be used for multiple images, saving
|
||||||
considerable calcuation time if doing the same conversion multiple times.
|
considerable calculation time if doing the same conversion multiple times.
|
||||||
|
|
||||||
If you want to modify im in-place instead of receiving a new image as
|
If you want to modify im in-place instead of receiving a new image as
|
||||||
the return value, set inPlace to TRUE. This can only be done if
|
the return value, set inPlace to TRUE. This can only be done if
|
||||||
|
@ -858,7 +858,7 @@ def getDefaultIntent(profile):
|
||||||
If an error occurs while trying to obtain the default intent, a
|
If an error occurs while trying to obtain the default intent, a
|
||||||
PyCMSError is raised.
|
PyCMSError is raised.
|
||||||
|
|
||||||
Use this function to determine the default (and usually best optomized)
|
Use this function to determine the default (and usually best optimized)
|
||||||
rendering intent for this profile. Most profiles support multiple
|
rendering intent for this profile. Most profiles support multiple
|
||||||
rendering intents, but are intended mostly for one type of conversion.
|
rendering intents, but are intended mostly for one type of conversion.
|
||||||
If you wish to use a different intent than returned, use
|
If you wish to use a different intent than returned, use
|
||||||
|
@ -914,7 +914,7 @@ def isIntentSupported(profile, intent, direction):
|
||||||
|
|
||||||
see the pyCMS documentation for details on rendering intents and what
|
see the pyCMS documentation for details on rendering intents and what
|
||||||
they do.
|
they do.
|
||||||
:param direction: Integer specifing if the profile is to be used for input,
|
:param direction: Integer specifying if the profile is to be used for input,
|
||||||
output, or proof
|
output, or proof
|
||||||
|
|
||||||
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
|
INPUT = 0 (or use ImageCms.DIRECTION_INPUT)
|
||||||
|
|
|
@ -73,7 +73,7 @@ class Contrast(_Enhance):
|
||||||
class Brightness(_Enhance):
|
class Brightness(_Enhance):
|
||||||
"""Adjust image brightness.
|
"""Adjust image brightness.
|
||||||
|
|
||||||
This class can be used to control the brighntess of an image. An
|
This class can be used to control the brightness of an image. An
|
||||||
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
|
enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the
|
||||||
original image.
|
original image.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#
|
#
|
||||||
# JPEG (JFIF) file handling
|
# JPEG (JFIF) file handling
|
||||||
#
|
#
|
||||||
# See "Digital Compression and Coding of Continous-Tone Still Images,
|
# See "Digital Compression and Coding of Continuous-Tone Still Images,
|
||||||
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
|
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
|
||||||
#
|
#
|
||||||
# History:
|
# History:
|
||||||
|
|
|
@ -71,6 +71,10 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
|
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return len(self.images)
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -62,6 +62,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
def load_seek(self, pos):
|
def load_seek(self, pos):
|
||||||
self.__fp.seek(pos)
|
self.__fp.seek(pos)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return self.__framecount
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if frame < 0 or frame >= self.__framecount:
|
if frame < 0 or frame >= self.__framecount:
|
||||||
raise EOFError("no more images in MPO file")
|
raise EOFError("no more images in MPO file")
|
||||||
|
|
830
PIL/OleFileIO.py
830
PIL/OleFileIO.py
File diff suppressed because it is too large
Load Diff
|
@ -132,6 +132,10 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
self._fp = self.fp
|
self._fp = self.fp
|
||||||
self.frame = 0
|
self.frame = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return len(self.layers)
|
||||||
|
|
||||||
def seek(self, layer):
|
def seek(self, layer):
|
||||||
# seek to given layer (1..max)
|
# seek to given layer (1..max)
|
||||||
if layer == self.frame:
|
if layer == self.frame:
|
||||||
|
|
|
@ -127,12 +127,12 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
if self.istack == 0 and self.imgnumber == 0:
|
if self.istack == 0 and self.imgnumber == 0:
|
||||||
# stk=0, img=0: a regular 2D image
|
# stk=0, img=0: a regular 2D image
|
||||||
offset = hdrlen
|
offset = hdrlen
|
||||||
self.nimages = 1
|
self._nimages = 1
|
||||||
elif self.istack > 0 and self.imgnumber == 0:
|
elif self.istack > 0 and self.imgnumber == 0:
|
||||||
# stk>0, img=0: Opening the stack for the first time
|
# stk>0, img=0: Opening the stack for the first time
|
||||||
self.imgbytes = int(h[12]) * int(h[2]) * 4
|
self.imgbytes = int(h[12]) * int(h[2]) * 4
|
||||||
self.hdrlen = hdrlen
|
self.hdrlen = hdrlen
|
||||||
self.nimages = int(h[26])
|
self._nimages = int(h[26])
|
||||||
# Point to the first image in the stack
|
# Point to the first image in the stack
|
||||||
offset = hdrlen * 2
|
offset = hdrlen * 2
|
||||||
self.imgnumber = 1
|
self.imgnumber = 1
|
||||||
|
@ -154,6 +154,10 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
(self.rawmode, 0, 1))]
|
(self.rawmode, 0, 1))]
|
||||||
self.__fp = self.fp # FIXME: hack
|
self.__fp = self.fp # FIXME: hack
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
return self._nimages
|
||||||
|
|
||||||
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
# 1st image index is zero (although SPIDER imgnumber starts at 1)
|
||||||
def tell(self):
|
def tell(self):
|
||||||
if self.imgnumber < 1:
|
if self.imgnumber < 1:
|
||||||
|
@ -164,7 +168,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
if self.istack == 0:
|
if self.istack == 0:
|
||||||
return
|
return
|
||||||
if frame >= self.nimages:
|
if frame >= self._nimages:
|
||||||
raise EOFError("attempt to seek past end of file")
|
raise EOFError("attempt to seek past end of file")
|
||||||
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes)
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
|
|
|
@ -294,7 +294,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
|
|
||||||
def named(self):
|
def named(self):
|
||||||
"""
|
"""
|
||||||
Returns the complete tag dictionary, with named tags where posible.
|
Returns the complete tag dictionary, with named tags where possible.
|
||||||
"""
|
"""
|
||||||
from PIL import TiffTags
|
from PIL import TiffTags
|
||||||
result = {}
|
result = {}
|
||||||
|
@ -426,6 +426,11 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
for i in range(i16(fp.read(2))):
|
for i in range(i16(fp.read(2))):
|
||||||
|
|
||||||
ifd = fp.read(12)
|
ifd = fp.read(12)
|
||||||
|
if len(ifd) != 12:
|
||||||
|
warnings.warn("Possibly corrupt EXIF data. "
|
||||||
|
"Expecting to read 12 bytes but only got %d."
|
||||||
|
% (len(ifd)))
|
||||||
|
continue
|
||||||
|
|
||||||
tag, typ = i16(ifd), i16(ifd, 2)
|
tag, typ = i16(ifd), i16(ifd, 2)
|
||||||
|
|
||||||
|
@ -476,7 +481,14 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
else:
|
else:
|
||||||
print("- value:", self[tag])
|
print("- value:", self[tag])
|
||||||
|
|
||||||
self.next = i32(fp.read(4))
|
ifd = fp.read(4)
|
||||||
|
if len(ifd) != 4:
|
||||||
|
warnings.warn("Possibly corrupt EXIF data. "
|
||||||
|
"Expecting to read 4 bytes but only got %d."
|
||||||
|
% (len(ifd)))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.next = i32(ifd)
|
||||||
|
|
||||||
# save primitives
|
# save primitives
|
||||||
|
|
||||||
|
@ -636,6 +648,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.__first = self.__next = self.ifd.i32(ifh, 4)
|
self.__first = self.__next = self.ifd.i32(ifh, 4)
|
||||||
self.__frame = -1
|
self.__frame = -1
|
||||||
self.__fp = self.fp
|
self.__fp = self.fp
|
||||||
|
self._frame_pos = []
|
||||||
|
self._n_frames = None
|
||||||
|
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print("*** TiffImageFile._open ***")
|
print("*** TiffImageFile._open ***")
|
||||||
|
@ -645,28 +659,30 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# and load the first frame
|
# and load the first frame
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def n_frames(self):
|
||||||
|
if self._n_frames is None:
|
||||||
|
current = self.tell()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
self._seek(self.tell() + 1)
|
||||||
|
except EOFError:
|
||||||
|
self._n_frames = self.tell() + 1
|
||||||
|
self.seek(current)
|
||||||
|
return self._n_frames
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
"Select a given frame as current image"
|
"Select a given frame as current image"
|
||||||
if frame < 0:
|
self._seek(max(frame, 0)) # Questionable backwards compatibility.
|
||||||
frame = 0
|
|
||||||
self._seek(frame)
|
|
||||||
# Create a new core image object on second and
|
# Create a new core image object on second and
|
||||||
# subsequent frames in the image. Image may be
|
# subsequent frames in the image. Image may be
|
||||||
# different size/mode.
|
# different size/mode.
|
||||||
Image._decompression_bomb_check(self.size)
|
Image._decompression_bomb_check(self.size)
|
||||||
self.im = Image.core.new(self.mode, self.size)
|
self.im = Image.core.new(self.mode, self.size)
|
||||||
|
|
||||||
def tell(self):
|
|
||||||
"Return the current frame number"
|
|
||||||
return self._tell()
|
|
||||||
|
|
||||||
def _seek(self, frame):
|
def _seek(self, frame):
|
||||||
self.fp = self.__fp
|
self.fp = self.__fp
|
||||||
if frame < self.__frame:
|
while len(self._frame_pos) <= frame:
|
||||||
# rewind file
|
|
||||||
self.__frame = -1
|
|
||||||
self.__next = self.__first
|
|
||||||
while self.__frame < frame:
|
|
||||||
if not self.__next:
|
if not self.__next:
|
||||||
raise EOFError("no more images in TIFF file")
|
raise EOFError("no more images in TIFF file")
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
|
@ -676,14 +692,19 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# was passed to libtiff, invalidating the buffer
|
# was passed to libtiff, invalidating the buffer
|
||||||
self.fp.tell()
|
self.fp.tell()
|
||||||
self.fp.seek(self.__next)
|
self.fp.seek(self.__next)
|
||||||
|
self._frame_pos.append(self.__next)
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print("Loading tags, location: %s" % self.fp.tell())
|
print("Loading tags, location: %s" % self.fp.tell())
|
||||||
self.tag.load(self.fp)
|
self.tag.load(self.fp)
|
||||||
self.__next = self.tag.next
|
self.__next = self.tag.next
|
||||||
self.__frame += 1
|
self.__frame += 1
|
||||||
|
self.fp.seek(self._frame_pos[frame])
|
||||||
|
self.tag.load(self.fp)
|
||||||
|
self.__frame = frame
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _tell(self):
|
def tell(self):
|
||||||
|
"Return the current frame number"
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def _decoder(self, rawmode, layer, tile=None):
|
def _decoder(self, rawmode, layer, tile=None):
|
||||||
|
@ -749,7 +770,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
# Rearranging for supporting byteio items, since they have a fileno
|
# Rearranging for supporting byteio items, since they have a fileno
|
||||||
# that returns an IOError if there's no underlying fp. Easier to
|
# that returns an IOError if there's no underlying fp. Easier to
|
||||||
# dea. with here by reordering.
|
# deal with here by reordering.
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print("have getvalue. just sending in a string from getvalue")
|
print("have getvalue. just sending in a string from getvalue")
|
||||||
n, err = decoder.decode(self.fp.getvalue())
|
n, err = decoder.decode(self.fp.getvalue())
|
||||||
|
@ -985,7 +1006,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# Write TIFF files
|
# Write TIFF files
|
||||||
|
|
||||||
# little endian is default except for image modes with
|
# little endian is default except for image modes with
|
||||||
# explict big endian byte-order
|
# explicit big endian byte-order
|
||||||
|
|
||||||
SAVE_INFO = {
|
SAVE_INFO = {
|
||||||
# mode => rawmode, byteorder, photometrics,
|
# mode => rawmode, byteorder, photometrics,
|
||||||
|
|
12
README.rst
12
README.rst
|
@ -6,19 +6,23 @@ Python Imaging Library (Fork)
|
||||||
|
|
||||||
Pillow is the "friendly PIL fork" by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
Pillow is the "friendly PIL fork" by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
..
|
||||||
|
image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master
|
||||||
:target: https://travis-ci.org/python-pillow/Pillow
|
:target: https://travis-ci.org/python-pillow/Pillow
|
||||||
:alt: Travis CI build status (Linux)
|
:alt: Travis CI build status (Linux)
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/python-pillow/pillow-wheels.svg?branch=latest
|
..
|
||||||
|
image:: https://travis-ci.org/python-pillow/pillow-wheels.svg?branch=latest
|
||||||
:target: https://travis-ci.org/python-pillow/pillow-wheels
|
:target: https://travis-ci.org/python-pillow/pillow-wheels
|
||||||
:alt: Travis CI build status (OS X)
|
:alt: Travis CI build status (OS X)
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/pillow.svg
|
..
|
||||||
|
image:: https://img.shields.io/pypi/v/pillow.svg
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Latest PyPI version
|
:alt: Latest PyPI version
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/dm/pillow.svg
|
..
|
||||||
|
image:: https://img.shields.io/pypi/dm/pillow.svg
|
||||||
:target: https://pypi.python.org/pypi/Pillow/
|
:target: https://pypi.python.org/pypi/Pillow/
|
||||||
:alt: Number of PyPI downloads
|
:alt: Number of PyPI downloads
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,8 @@ def makedelta(fp, sequence):
|
||||||
|
|
||||||
for im in sequence:
|
for im in sequence:
|
||||||
|
|
||||||
#
|
# To specify duration, add the time in milliseconds to getdata(),
|
||||||
# FIXME: write graphics control block before each frame
|
# e.g. getdata(im, duration=1000)
|
||||||
|
|
||||||
if not previous:
|
if not previous:
|
||||||
|
|
||||||
|
|
BIN
Tests/images/hopper.im
Normal file
BIN
Tests/images/hopper.im
Normal file
Binary file not shown.
BIN
Tests/images/hopper_bad_exif.jpg
Normal file
BIN
Tests/images/hopper_bad_exif.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 KiB |
BIN
Tests/images/hopper_merged.psd
Normal file
BIN
Tests/images/hopper_merged.psd
Normal file
Binary file not shown.
|
@ -30,6 +30,10 @@ class TestFileDcx(PillowTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.assertEqual(frame, 0)
|
self.assertEqual(frame, 0)
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
self.assertEqual(im.n_frames, 1)
|
||||||
|
|
||||||
def test_seek_too_far(self):
|
def test_seek_too_far(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
|
|
|
@ -18,6 +18,10 @@ class TestFileFli(PillowTestCase):
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.format, "FLI")
|
self.assertEqual(im.format, "FLI")
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open(test_file)
|
||||||
|
self.assertEqual(im.n_frames, 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -134,6 +134,10 @@ class TestFileGif(PillowTestCase):
|
||||||
except EOFError:
|
except EOFError:
|
||||||
self.assertEqual(framecount, 5)
|
self.assertEqual(framecount, 5)
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open("Tests/images/iss634.gif")
|
||||||
|
self.assertEqual(im.n_frames, 43)
|
||||||
|
|
||||||
def test_dispose_none(self):
|
def test_dispose_none(self):
|
||||||
img = Image.open("Tests/images/dispose_none.gif")
|
img = Image.open("Tests/images/dispose_none.gif")
|
||||||
try:
|
try:
|
||||||
|
@ -169,6 +173,33 @@ class TestFileGif(PillowTestCase):
|
||||||
# first frame
|
# first frame
|
||||||
self.assertEqual(img.histogram()[img.info['transparency']], 0)
|
self.assertEqual(img.histogram()[img.info['transparency']], 0)
|
||||||
|
|
||||||
|
def test_duration(self):
|
||||||
|
duration = 1000
|
||||||
|
|
||||||
|
out = self.tempfile('temp.gif')
|
||||||
|
fp = open(out, "wb")
|
||||||
|
im = Image.new('L', (100, 100), '#000')
|
||||||
|
for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, duration=duration):
|
||||||
|
fp.write(s)
|
||||||
|
fp.write(b";")
|
||||||
|
fp.close()
|
||||||
|
reread = Image.open(out)
|
||||||
|
|
||||||
|
self.assertEqual(reread.info['duration'], duration)
|
||||||
|
|
||||||
|
def test_number_of_loops(self):
|
||||||
|
number_of_loops = 2
|
||||||
|
|
||||||
|
out = self.tempfile('temp.gif')
|
||||||
|
fp = open(out, "wb")
|
||||||
|
im = Image.new('L', (100, 100), '#000')
|
||||||
|
for s in GifImagePlugin.getheader(im)[0] + GifImagePlugin.getdata(im, loop=number_of_loops):
|
||||||
|
fp.write(s)
|
||||||
|
fp.write(b";")
|
||||||
|
fp.close()
|
||||||
|
reread = Image.open(out)
|
||||||
|
|
||||||
|
self.assertEqual(reread.info['loop'], number_of_loops)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
33
Tests/test_file_im.py
Normal file
33
Tests/test_file_im.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# sample im
|
||||||
|
TEST_IM = "Tests/images/hopper.im"
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileIm(PillowTestCase):
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
im = Image.open(TEST_IM)
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.mode, "RGB")
|
||||||
|
self.assertEqual(im.size, (128, 128))
|
||||||
|
self.assertEqual(im.format, "IM")
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open(TEST_IM)
|
||||||
|
self.assertEqual(im.n_frames, 1)
|
||||||
|
|
||||||
|
def test_roundtrip(self):
|
||||||
|
out = self.tempfile('temp.im')
|
||||||
|
im = hopper()
|
||||||
|
im.save(out)
|
||||||
|
reread = Image.open(out)
|
||||||
|
|
||||||
|
self.assert_image_equal(reread, im)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
|
@ -95,6 +95,10 @@ class TestFileMpo(PillowTestCase):
|
||||||
im.seek(0)
|
im.seek(0)
|
||||||
self.assertEqual(im.tell(), 0)
|
self.assertEqual(im.tell(), 0)
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open("Tests/images/sugarshack.mpo")
|
||||||
|
self.assertEqual(im.n_frames, 2)
|
||||||
|
|
||||||
def test_image_grab(self):
|
def test_image_grab(self):
|
||||||
for test_file in test_files:
|
for test_file in test_files:
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
|
|
@ -16,6 +16,13 @@ class TestImagePsd(PillowTestCase):
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.format, "PSD")
|
self.assertEqual(im.format, "PSD")
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open("Tests/images/hopper_merged.psd")
|
||||||
|
self.assertEqual(im.n_frames, 1)
|
||||||
|
|
||||||
|
im = Image.open(test_file)
|
||||||
|
self.assertEqual(im.n_frames, 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -42,6 +42,10 @@ class TestImageSpider(PillowTestCase):
|
||||||
# Assert
|
# Assert
|
||||||
self.assertEqual(index, 0)
|
self.assertEqual(index, 0)
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
self.assertEqual(im.n_frames, 1)
|
||||||
|
|
||||||
def test_loadImageSeries(self):
|
def test_loadImageSeries(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
not_spider_file = "Tests/images/hopper.ppm"
|
not_spider_file = "Tests/images/hopper.ppm"
|
||||||
|
|
|
@ -3,6 +3,8 @@ from helper import unittest, PillowTestCase, hopper, py3
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
class TestFileTiff(PillowTestCase):
|
class TestFileTiff(PillowTestCase):
|
||||||
|
|
||||||
|
@ -77,6 +79,12 @@ class TestFileTiff(PillowTestCase):
|
||||||
im._setup()
|
im._setup()
|
||||||
self.assertEqual(im.info['dpi'], (72., 72.))
|
self.assertEqual(im.info['dpi'], (72., 72.))
|
||||||
|
|
||||||
|
def test_bad_exif(self):
|
||||||
|
try:
|
||||||
|
Image.open('Tests/images/hopper_bad_exif.jpg')._getexif()
|
||||||
|
except struct.error:
|
||||||
|
self.fail("Bad EXIF data should not pass incorrect values to _binary unpack")
|
||||||
|
|
||||||
def test_little_endian(self):
|
def test_little_endian(self):
|
||||||
im = Image.open('Tests/images/16bit.cropped.tif')
|
im = Image.open('Tests/images/16bit.cropped.tif')
|
||||||
self.assertEqual(im.getpixel((0, 0)), 480)
|
self.assertEqual(im.getpixel((0, 0)), 480)
|
||||||
|
@ -142,6 +150,13 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
im.getextrema(), (-3.140936851501465, 3.140684127807617))
|
im.getextrema(), (-3.140936851501465, 3.140684127807617))
|
||||||
|
|
||||||
|
def test_n_frames(self):
|
||||||
|
im = Image.open('Tests/images/multipage-lastframe.tif')
|
||||||
|
self.assertEqual(im.n_frames, 1)
|
||||||
|
|
||||||
|
im = Image.open('Tests/images/multipage.tiff')
|
||||||
|
self.assertEqual(im.n_frames, 3)
|
||||||
|
|
||||||
def test_multipage(self):
|
def test_multipage(self):
|
||||||
# issue #862
|
# issue #862
|
||||||
im = Image.open('Tests/images/multipage.tiff')
|
im = Image.open('Tests/images/multipage.tiff')
|
||||||
|
|
|
@ -54,7 +54,7 @@ class TestImagingCoreResize(PillowTestCase):
|
||||||
# Make an image with one colored pixel, in one channel.
|
# Make an image with one colored pixel, in one channel.
|
||||||
# When resized, that channel should be the same as a GS image.
|
# When resized, that channel should be the same as a GS image.
|
||||||
# Other channels should be unaffected.
|
# Other channels should be unaffected.
|
||||||
# The R and A channels should not swap, which is indicitive of
|
# The R and A channels should not swap, which is indicative of
|
||||||
# an endianness issues.
|
# an endianness issues.
|
||||||
|
|
||||||
samples = {
|
samples = {
|
||||||
|
|
|
@ -300,32 +300,32 @@ class TestImageDraw(PillowTestCase):
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((5, 5, 14, 5), BLACK, 2)
|
draw.line((5, 5, 14, 5), BLACK, 2)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth horizontal normal 2px wide failed')
|
img, expected, 'line straight horizontal normal 2px wide failed')
|
||||||
expected = Image.open(os.path.join(IMAGES_PATH,
|
expected = Image.open(os.path.join(IMAGES_PATH,
|
||||||
'line_horizontal_w2px_inverted.png'))
|
'line_horizontal_w2px_inverted.png'))
|
||||||
expected.load()
|
expected.load()
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((14, 5, 5, 5), BLACK, 2)
|
draw.line((14, 5, 5, 5), BLACK, 2)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth horizontal inverted 2px wide failed')
|
img, expected, 'line straight horizontal inverted 2px wide failed')
|
||||||
expected = Image.open(os.path.join(IMAGES_PATH,
|
expected = Image.open(os.path.join(IMAGES_PATH,
|
||||||
'line_horizontal_w3px.png'))
|
'line_horizontal_w3px.png'))
|
||||||
expected.load()
|
expected.load()
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((5, 5, 14, 5), BLACK, 3)
|
draw.line((5, 5, 14, 5), BLACK, 3)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth horizontal normal 3px wide failed')
|
img, expected, 'line straight horizontal normal 3px wide failed')
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((14, 5, 5, 5), BLACK, 3)
|
draw.line((14, 5, 5, 5), BLACK, 3)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth horizontal inverted 3px wide failed')
|
img, expected, 'line straight horizontal inverted 3px wide failed')
|
||||||
expected = Image.open(os.path.join(IMAGES_PATH,
|
expected = Image.open(os.path.join(IMAGES_PATH,
|
||||||
'line_horizontal_w101px.png'))
|
'line_horizontal_w101px.png'))
|
||||||
expected.load()
|
expected.load()
|
||||||
img, draw = self.create_base_image_draw((200, 110))
|
img, draw = self.create_base_image_draw((200, 110))
|
||||||
draw.line((5, 55, 195, 55), BLACK, 101)
|
draw.line((5, 55, 195, 55), BLACK, 101)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth horizontal 101px wide failed')
|
img, expected, 'line straight horizontal 101px wide failed')
|
||||||
|
|
||||||
def test_line_h_s1_w2(self):
|
def test_line_h_s1_w2(self):
|
||||||
self.skipTest('failing')
|
self.skipTest('failing')
|
||||||
|
@ -344,32 +344,32 @@ class TestImageDraw(PillowTestCase):
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((5, 5, 5, 14), BLACK, 2)
|
draw.line((5, 5, 5, 14), BLACK, 2)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth vertical normal 2px wide failed')
|
img, expected, 'line straight vertical normal 2px wide failed')
|
||||||
expected = Image.open(os.path.join(IMAGES_PATH,
|
expected = Image.open(os.path.join(IMAGES_PATH,
|
||||||
'line_vertical_w2px_inverted.png'))
|
'line_vertical_w2px_inverted.png'))
|
||||||
expected.load()
|
expected.load()
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((5, 14, 5, 5), BLACK, 2)
|
draw.line((5, 14, 5, 5), BLACK, 2)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth vertical inverted 2px wide failed')
|
img, expected, 'line straight vertical inverted 2px wide failed')
|
||||||
expected = Image.open(os.path.join(IMAGES_PATH,
|
expected = Image.open(os.path.join(IMAGES_PATH,
|
||||||
'line_vertical_w3px.png'))
|
'line_vertical_w3px.png'))
|
||||||
expected.load()
|
expected.load()
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((5, 5, 5, 14), BLACK, 3)
|
draw.line((5, 5, 5, 14), BLACK, 3)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth vertical normal 3px wide failed')
|
img, expected, 'line straight vertical normal 3px wide failed')
|
||||||
img, draw = self.create_base_image_draw((20, 20))
|
img, draw = self.create_base_image_draw((20, 20))
|
||||||
draw.line((5, 14, 5, 5), BLACK, 3)
|
draw.line((5, 14, 5, 5), BLACK, 3)
|
||||||
self.assert_image_equal(
|
self.assert_image_equal(
|
||||||
img, expected, 'line straigth vertical inverted 3px wide failed')
|
img, expected, 'line straight vertical inverted 3px wide failed')
|
||||||
expected = Image.open(os.path.join(IMAGES_PATH,
|
expected = Image.open(os.path.join(IMAGES_PATH,
|
||||||
'line_vertical_w101px.png'))
|
'line_vertical_w101px.png'))
|
||||||
expected.load()
|
expected.load()
|
||||||
img, draw = self.create_base_image_draw((110, 200))
|
img, draw = self.create_base_image_draw((110, 200))
|
||||||
draw.line((55, 5, 55, 195), BLACK, 101)
|
draw.line((55, 5, 55, 195), BLACK, 101)
|
||||||
self.assert_image_equal(img, expected,
|
self.assert_image_equal(img, expected,
|
||||||
'line straigth vertical 101px wide failed')
|
'line straight vertical 101px wide failed')
|
||||||
expected = Image.open(os.path.join(IMAGES_PATH,
|
expected = Image.open(os.path.join(IMAGES_PATH,
|
||||||
'line_vertical_slope1px_w2px.png'))
|
'line_vertical_slope1px_w2px.png'))
|
||||||
expected.load()
|
expected.load()
|
||||||
|
|
|
@ -278,7 +278,7 @@ findLCMStype(char* PILmode)
|
||||||
return TYPE_YCbCr_8;
|
return TYPE_YCbCr_8;
|
||||||
}
|
}
|
||||||
else if (strcmp(PILmode, "LAB") == 0) {
|
else if (strcmp(PILmode, "LAB") == 0) {
|
||||||
// LabX equvalent like ALab, but not reversed -- no #define in lcms2
|
// LabX equivalent like ALab, but not reversed -- no #define in lcms2
|
||||||
return (COLORSPACE_SH(PT_LabV2)|CHANNELS_SH(3)|BYTES_SH(1)|EXTRA_SH(1));
|
return (COLORSPACE_SH(PT_LabV2)|CHANNELS_SH(3)|BYTES_SH(1)|EXTRA_SH(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -572,7 +572,7 @@ setup_module(PyObject* m) {
|
||||||
PyType_Ready(&Font_Type);
|
PyType_Ready(&Font_Type);
|
||||||
|
|
||||||
if (FT_Init_FreeType(&library))
|
if (FT_Init_FreeType(&library))
|
||||||
return 0; /* leave it uninitalized */
|
return 0; /* leave it uninitialized */
|
||||||
|
|
||||||
FT_Library_Version(library, &major, &minor, &patch);
|
FT_Library_Version(library, &major, &minor, &patch);
|
||||||
|
|
||||||
|
|
2
encode.c
2
encode.c
|
@ -737,7 +737,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// While failes on 64 bit machines, complains that pos is an int instead of a Py_ssize_t
|
// While fails on 64 bit machines, complains that pos is an int instead of a Py_ssize_t
|
||||||
// while (PyDict_Next(dir, &pos, &key, &value)) {
|
// while (PyDict_Next(dir, &pos, &key, &value)) {
|
||||||
for (pos=0;pos<d_size;pos++){
|
for (pos=0;pos<d_size;pos++){
|
||||||
key = PyList_GetItem(keys,pos);
|
key = PyList_GetItem(keys,pos);
|
||||||
|
|
|
@ -345,7 +345,7 @@ int quantize_octree(Pixel *pixelData,
|
||||||
/*
|
/*
|
||||||
Create two color cubes, one fine grained with 8x16x8=1024
|
Create two color cubes, one fine grained with 8x16x8=1024
|
||||||
colors buckets and a coarse with 4x4x4=64 color buckets.
|
colors buckets and a coarse with 4x4x4=64 color buckets.
|
||||||
The coarse one guarantes that there are color buckets available for
|
The coarse one guarantees that there are color buckets available for
|
||||||
the whole color range (assuming nQuantPixels > 64).
|
the whole color range (assuming nQuantPixels > 64).
|
||||||
|
|
||||||
For a quantization to 256 colors all 64 coarse colors will be used
|
For a quantization to 256 colors all 64 coarse colors will be used
|
||||||
|
@ -421,7 +421,7 @@ int quantize_octree(Pixel *pixelData,
|
||||||
/* add fine colors to the lookup cube */
|
/* add fine colors to the lookup cube */
|
||||||
add_lookup_buckets(lookupCube, paletteBuckets, nFineColors, nCoarseColors);
|
add_lookup_buckets(lookupCube, paletteBuckets, nFineColors, nCoarseColors);
|
||||||
|
|
||||||
/* create result pixles and map palatte indices */
|
/* create result pixels and map palette indices */
|
||||||
qp = malloc(sizeof(Pixel)*nPixels);
|
qp = malloc(sizeof(Pixel)*nPixels);
|
||||||
if (!qp) goto error;
|
if (!qp) goto error;
|
||||||
map_image_pixels(pixelData, nPixels, lookupCube, qp);
|
map_image_pixels(pixelData, nPixels, lookupCube, qp);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* The Python Imaging Library
|
* The Python Imaging Library
|
||||||
* $Id$
|
* $Id$
|
||||||
*
|
*
|
||||||
* Pillow image resamling support
|
* Pillow image resampling support
|
||||||
*
|
*
|
||||||
* history:
|
* history:
|
||||||
* 2002-03-09 fl Created (for PIL 1.1.3)
|
* 2002-03-09 fl Created (for PIL 1.1.3)
|
||||||
|
|
|
@ -245,7 +245,7 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
|
||||||
// back in. Can't use read encoded stripe.
|
// back in. Can't use read encoded stripe.
|
||||||
|
|
||||||
// This thing pretty much requires that I have the whole image in one shot.
|
// This thing pretty much requires that I have the whole image in one shot.
|
||||||
// Prehaps a stub version would work better???
|
// Perhaps a stub version would work better???
|
||||||
while(state->y < state->ysize){
|
while(state->y < state->ysize){
|
||||||
if (TIFFReadScanline(tiff, (tdata_t)state->buffer, (uint32)state->y, 0) == -1) {
|
if (TIFFReadScanline(tiff, (tdata_t)state->buffer, (uint32)state->y, 0) == -1) {
|
||||||
TRACE(("Decode Error, row %d\n", state->y));
|
TRACE(("Decode Error, row %d\n", state->y));
|
||||||
|
|
|
@ -808,7 +808,7 @@ unpackI12_I16(UINT8* out, const UINT8* in, int pixels){
|
||||||
|
|
||||||
FillOrder = 2 should be used only when BitsPerSample = 1 and
|
FillOrder = 2 should be used only when BitsPerSample = 1 and
|
||||||
the data is either uncompressed or compressed using CCITT 1D
|
the data is either uncompressed or compressed using CCITT 1D
|
||||||
or 2D compression, to avoid potentially ambigous situations.
|
or 2D compression, to avoid potentially ambiguous situations.
|
||||||
|
|
||||||
Yeah. I thought so. We'll see how well people read the spec.
|
Yeah. I thought so. We'll see how well people read the spec.
|
||||||
We've got several fillorder=2 modes in TiffImagePlugin.py
|
We've got several fillorder=2 modes in TiffImagePlugin.py
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
# Testing reqs
|
# Testing and documentation requirements
|
||||||
-e .
|
-e .
|
||||||
|
-r docs/requirements.txt
|
||||||
|
coveralls
|
||||||
nose
|
nose
|
||||||
|
nose-cov
|
||||||
|
pep8
|
||||||
|
pyflakes
|
||||||
|
|
Loading…
Reference in New Issue
Block a user