mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-05 06:00:58 +03:00
commit
ce3814189e
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -60,3 +60,9 @@ docs/_build/
|
||||||
\#*#
|
\#*#
|
||||||
.#*
|
.#*
|
||||||
|
|
||||||
|
#Komodo
|
||||||
|
*.komodoproject
|
||||||
|
|
||||||
|
#OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
|
|
@ -555,7 +555,6 @@ class Image:
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
|
|
||||||
def _dump(self, file=None, format=None):
|
def _dump(self, file=None, format=None):
|
||||||
import os
|
|
||||||
import tempfile
|
import tempfile
|
||||||
suffix = ''
|
suffix = ''
|
||||||
if format:
|
if format:
|
||||||
|
|
|
@ -36,7 +36,9 @@ __version__ = "0.6"
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import struct
|
import struct
|
||||||
from PIL import Image, ImageFile, _binary
|
import io
|
||||||
|
from struct import unpack
|
||||||
|
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
||||||
from PIL.JpegPresets import presets
|
from PIL.JpegPresets import presets
|
||||||
from PIL._util import isStringType
|
from PIL._util import isStringType
|
||||||
|
|
||||||
|
@ -110,6 +112,11 @@ def APP(self, marker):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.info["adobe_transform"] = adobe_transform
|
self.info["adobe_transform"] = adobe_transform
|
||||||
|
elif marker == 0xFFE2 and s[:4] == b"MPF\0":
|
||||||
|
# extract MPO information
|
||||||
|
self.info["mp"] = s[4:]
|
||||||
|
# offset is current location minus buffer size plus constant header size
|
||||||
|
self.info["mpoffset"] = self.fp.tell() - n + 4
|
||||||
|
|
||||||
|
|
||||||
def COM(self, marker):
|
def COM(self, marker):
|
||||||
|
@ -380,18 +387,22 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
return _getexif(self)
|
return _getexif(self)
|
||||||
|
|
||||||
|
def _getmp(self):
|
||||||
|
return _getmp(self)
|
||||||
|
|
||||||
|
|
||||||
|
def _fixup(value):
|
||||||
|
# Helper function for _getexif() and _getmp()
|
||||||
|
if len(value) == 1:
|
||||||
|
return value[0]
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self):
|
def _getexif(self):
|
||||||
# Extract EXIF information. This method is highly experimental,
|
# Extract EXIF information. This method is highly experimental,
|
||||||
# and is likely to be replaced with something better in a future
|
# and is likely to be replaced with something better in a future
|
||||||
# version.
|
# version.
|
||||||
from PIL import TiffImagePlugin
|
|
||||||
import io
|
|
||||||
|
|
||||||
def fixup(value):
|
|
||||||
if len(value) == 1:
|
|
||||||
return value[0]
|
|
||||||
return value
|
|
||||||
# The EXIF record consists of a TIFF file embedded in a JPEG
|
# The EXIF record consists of a TIFF file embedded in a JPEG
|
||||||
# application marker (!).
|
# application marker (!).
|
||||||
try:
|
try:
|
||||||
|
@ -405,7 +416,7 @@ def _getexif(self):
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
for key, value in info.items():
|
||||||
exif[key] = fixup(value)
|
exif[key] = _fixup(value)
|
||||||
# get exif extension
|
# get exif extension
|
||||||
try:
|
try:
|
||||||
file.seek(exif[0x8769])
|
file.seek(exif[0x8769])
|
||||||
|
@ -415,7 +426,7 @@ def _getexif(self):
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
for key, value in info.items():
|
||||||
exif[key] = fixup(value)
|
exif[key] = _fixup(value)
|
||||||
# get gpsinfo extension
|
# get gpsinfo extension
|
||||||
try:
|
try:
|
||||||
file.seek(exif[0x8825])
|
file.seek(exif[0x8825])
|
||||||
|
@ -426,9 +437,77 @@ def _getexif(self):
|
||||||
info.load(file)
|
info.load(file)
|
||||||
exif[0x8825] = gps = {}
|
exif[0x8825] = gps = {}
|
||||||
for key, value in info.items():
|
for key, value in info.items():
|
||||||
gps[key] = fixup(value)
|
gps[key] = _fixup(value)
|
||||||
return exif
|
return exif
|
||||||
|
|
||||||
|
|
||||||
|
def _getmp(self):
|
||||||
|
# Extract MP information. This method was inspired by the "highly
|
||||||
|
# experimental" _getexif version that's been in use for years now,
|
||||||
|
# itself based on the ImageFileDirectory class in the TIFF plug-in.
|
||||||
|
|
||||||
|
# The MP record essentially consists of a TIFF file embedded in a JPEG
|
||||||
|
# application marker.
|
||||||
|
try:
|
||||||
|
data = self.info["mp"]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
file = io.BytesIO(data)
|
||||||
|
head = file.read(8)
|
||||||
|
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||||
|
mp = {}
|
||||||
|
# process dictionary
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
|
info.load(file)
|
||||||
|
for key, value in info.items():
|
||||||
|
mp[key] = _fixup(value)
|
||||||
|
# it's an error not to have a number of images
|
||||||
|
try:
|
||||||
|
quant = mp[0xB001]
|
||||||
|
except KeyError:
|
||||||
|
raise SyntaxError("malformed MP Index (no number of images)")
|
||||||
|
# get MP entries
|
||||||
|
try:
|
||||||
|
mpentries = []
|
||||||
|
for entrynum in range(0, quant):
|
||||||
|
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
|
||||||
|
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
|
||||||
|
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1', 'EntryNo2')
|
||||||
|
mpentry = dict(zip(labels, unpackedentry))
|
||||||
|
mpentryattr = {
|
||||||
|
'DependentParentImageFlag': bool(mpentry['Attribute'] & (1<<31)),
|
||||||
|
'DependentChildImageFlag': bool(mpentry['Attribute'] & (1<<30)),
|
||||||
|
'RepresentativeImageFlag': bool(mpentry['Attribute'] & (1<<29)),
|
||||||
|
'Reserved': (mpentry['Attribute'] & (3<<27)) >> 27,
|
||||||
|
'ImageDataFormat': (mpentry['Attribute'] & (7<<24)) >> 24,
|
||||||
|
'MPType': mpentry['Attribute'] & 0x00FFFFFF
|
||||||
|
}
|
||||||
|
if mpentryattr['ImageDataFormat'] == 0:
|
||||||
|
mpentryattr['ImageDataFormat'] = 'JPEG'
|
||||||
|
else:
|
||||||
|
raise SyntaxError("unsupported picture format in MPO")
|
||||||
|
mptypemap = {
|
||||||
|
0x000000: 'Undefined',
|
||||||
|
0x010001: 'Large Thumbnail (VGA Equivalent)',
|
||||||
|
0x010002: 'Large Thumbnail (Full HD Equivalent)',
|
||||||
|
0x020001: 'Multi-Frame Image (Panorama)',
|
||||||
|
0x020002: 'Multi-Frame Image: (Disparity)',
|
||||||
|
0x020003: 'Multi-Frame Image: (Multi-Angle)',
|
||||||
|
0x030000: 'Baseline MP Primary Image'
|
||||||
|
}
|
||||||
|
mpentryattr['MPType'] = mptypemap.get(mpentryattr['MPType'],
|
||||||
|
'Unknown')
|
||||||
|
mpentry['Attribute'] = mpentryattr
|
||||||
|
mpentries.append(mpentry)
|
||||||
|
mp[0xB002] = mpentries
|
||||||
|
except KeyError:
|
||||||
|
raise SyntaxError("malformed MP Index (bad MP Entry)")
|
||||||
|
# Next we should try and parse the individual image unique ID list;
|
||||||
|
# we don't because I've never seen this actually used in a real MPO
|
||||||
|
# file and so can't test it.
|
||||||
|
return mp
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# stuff to save JPEG files
|
# stuff to save JPEG files
|
||||||
|
|
||||||
|
@ -611,10 +690,27 @@ def _save_cjpeg(im, fp, filename):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Factory for making JPEG and MPO instances
|
||||||
|
def jpeg_factory(fp=None, filename=None):
|
||||||
|
im = JpegImageFile(fp, filename)
|
||||||
|
mpheader = im._getmp()
|
||||||
|
try:
|
||||||
|
if mpheader[45057] > 1:
|
||||||
|
# It's actually an MPO
|
||||||
|
from .MpoImagePlugin import MpoImageFile
|
||||||
|
im = MpoImageFile(fp, filename)
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
# It is really a JPEG
|
||||||
|
pass
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------q-
|
# -------------------------------------------------------------------q-
|
||||||
# Registry stuff
|
# Registry stuff
|
||||||
|
|
||||||
Image.register_open("JPEG", JpegImageFile, _accept)
|
Image.register_open("JPEG", jpeg_factory, _accept)
|
||||||
Image.register_save("JPEG", _save)
|
Image.register_save("JPEG", _save)
|
||||||
|
|
||||||
Image.register_extension("JPEG", ".jfif")
|
Image.register_extension("JPEG", ".jfif")
|
||||||
|
|
86
PIL/MpoImagePlugin.py
Normal file
86
PIL/MpoImagePlugin.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#
|
||||||
|
# The Python Imaging Library.
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# MPO file handling
|
||||||
|
#
|
||||||
|
# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the
|
||||||
|
# Camera & Imaging Products Association)
|
||||||
|
#
|
||||||
|
# The multi-picture object combines multiple JPEG images (with a modified EXIF
|
||||||
|
# data format) into a single file. While it can theoretically be used much like
|
||||||
|
# a GIF animation, it is commonly used to represent 3D photographs and is (as
|
||||||
|
# of this writing) the most commonly used format by 3D cameras.
|
||||||
|
#
|
||||||
|
# History:
|
||||||
|
# 2014-03-13 Feneric Created
|
||||||
|
#
|
||||||
|
# See the README file for information on usage and redistribution.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.1"
|
||||||
|
|
||||||
|
from PIL import Image, JpegImagePlugin
|
||||||
|
|
||||||
|
def _accept(prefix):
|
||||||
|
return JpegImagePlugin._accept(prefix)
|
||||||
|
|
||||||
|
def _save(im, fp, filename):
|
||||||
|
return JpegImagePlugin._save(im, fp, filename)
|
||||||
|
|
||||||
|
##
|
||||||
|
# Image plugin for MPO images.
|
||||||
|
|
||||||
|
class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
|
|
||||||
|
format = "MPO"
|
||||||
|
format_description = "MPO (CIPA DC-007)"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
self.fp.seek(0) # prep the fp in order to pass the JPEG test
|
||||||
|
JpegImagePlugin.JpegImageFile._open(self)
|
||||||
|
self.mpinfo = self._getmp()
|
||||||
|
self.__framecount = self.mpinfo[0xB001]
|
||||||
|
self.__mpoffsets = [mpent['DataOffset'] + self.info['mpoffset'] \
|
||||||
|
for mpent in self.mpinfo[0xB002]]
|
||||||
|
self.__mpoffsets[0] = 0
|
||||||
|
# Note that the following assertion will only be invalid if something
|
||||||
|
# gets broken within JpegImagePlugin.
|
||||||
|
assert self.__framecount == len(self.__mpoffsets)
|
||||||
|
del self.info['mpoffset'] # no longer needed
|
||||||
|
self.__fp = self.fp # FIXME: hack
|
||||||
|
self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame
|
||||||
|
self.__frame = 0
|
||||||
|
self.offset = 0
|
||||||
|
# for now we can only handle reading and individual frame extraction
|
||||||
|
self.readonly = 1
|
||||||
|
|
||||||
|
def load_seek(self, pos):
|
||||||
|
self.__fp.seek(pos)
|
||||||
|
|
||||||
|
def seek(self, frame):
|
||||||
|
if frame < 0 or frame >= self.__framecount:
|
||||||
|
raise EOFError("no more images in MPO file")
|
||||||
|
else:
|
||||||
|
self.fp = self.__fp
|
||||||
|
self.offset = self.__mpoffsets[frame]
|
||||||
|
self.tile = [
|
||||||
|
("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))
|
||||||
|
]
|
||||||
|
self.__frame = frame
|
||||||
|
|
||||||
|
def tell(self):
|
||||||
|
return self.__frame
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------q-
|
||||||
|
# Registry stuff
|
||||||
|
|
||||||
|
# Note that since MPO shares a factory with JPEG, we do not need to do a
|
||||||
|
# separate registration for it here.
|
||||||
|
#Image.register_open("MPO", JpegImagePlugin.jpeg_factory, _accept)
|
||||||
|
Image.register_save("MPO", _save)
|
||||||
|
|
||||||
|
Image.register_extension("MPO", ".mpo")
|
||||||
|
|
||||||
|
Image.register_mime("MPO", "image/mpo")
|
|
@ -147,6 +147,100 @@ TAGS = {
|
||||||
# ICC Profile
|
# ICC Profile
|
||||||
34675: "ICCProfile",
|
34675: "ICCProfile",
|
||||||
|
|
||||||
|
# Additional Exif Info
|
||||||
|
33434: "ExposureTime",
|
||||||
|
33437: "FNumber",
|
||||||
|
34850: "ExposureProgram",
|
||||||
|
34852: "SpectralSensitivity",
|
||||||
|
34853: "GPSInfoIFD",
|
||||||
|
34855: "ISOSpeedRatings",
|
||||||
|
34856: "OECF",
|
||||||
|
34864: "SensitivityType",
|
||||||
|
34865: "StandardOutputSensitivity",
|
||||||
|
34866: "RecommendedExposureIndex",
|
||||||
|
34867: "ISOSpeed",
|
||||||
|
34868: "ISOSpeedLatitudeyyy",
|
||||||
|
34869: "ISOSpeedLatitudezzz",
|
||||||
|
36864: "ExifVersion",
|
||||||
|
36867: "DateTimeOriginal",
|
||||||
|
36868: "DateTImeDigitized",
|
||||||
|
37121: "ComponentsConfiguration",
|
||||||
|
37122: "CompressedBitsPerPixel",
|
||||||
|
37377: "ShutterSpeedValue",
|
||||||
|
37378: "ApertureValue",
|
||||||
|
37379: "BrightnessValue",
|
||||||
|
37380: "ExposureBiasValue",
|
||||||
|
37381: "MaxApertureValue",
|
||||||
|
37382: "SubjectDistance",
|
||||||
|
37383: "MeteringMode",
|
||||||
|
37384: "LightSource",
|
||||||
|
37385: "Flash",
|
||||||
|
37386: "FocalLength",
|
||||||
|
37396: "SubjectArea",
|
||||||
|
37500: "MakerNote",
|
||||||
|
37510: "UserComment",
|
||||||
|
37520: "SubSec",
|
||||||
|
37521: "SubSecTimeOriginal",
|
||||||
|
37522: "SubsecTimeDigitized",
|
||||||
|
40960: "FlashPixVersion",
|
||||||
|
40961: "ColorSpace",
|
||||||
|
40962: "PixelXDimension",
|
||||||
|
40963: "PixelYDimension",
|
||||||
|
40964: "RelatedSoundFile",
|
||||||
|
40965: "InteroperabilityIFD",
|
||||||
|
41483: "FlashEnergy",
|
||||||
|
41484: "SpatialFrequencyResponse",
|
||||||
|
41486: "FocalPlaneXResolution",
|
||||||
|
41487: "FocalPlaneYResolution",
|
||||||
|
41488: "FocalPlaneResolutionUnit",
|
||||||
|
41492: "SubjectLocation",
|
||||||
|
41493: "ExposureIndex",
|
||||||
|
41495: "SensingMethod",
|
||||||
|
41728: "FileSource",
|
||||||
|
41729: "SceneType",
|
||||||
|
41730: "CFAPattern",
|
||||||
|
41985: "CustomRendered",
|
||||||
|
41986: "ExposureMode",
|
||||||
|
41987: "WhiteBalance",
|
||||||
|
41988: "DigitalZoomRatio",
|
||||||
|
41989: "FocalLengthIn35mmFilm",
|
||||||
|
41990: "SceneCaptureType",
|
||||||
|
41991: "GainControl",
|
||||||
|
41992: "Contrast",
|
||||||
|
41993: "Saturation",
|
||||||
|
41994: "Sharpness",
|
||||||
|
41995: "DeviceSettingDescription",
|
||||||
|
41996: "SubjectDistanceRange",
|
||||||
|
42016: "ImageUniqueID",
|
||||||
|
42032: "CameraOwnerName",
|
||||||
|
42033: "BodySerialNumber",
|
||||||
|
42034: "LensSpecification",
|
||||||
|
42035: "LensMake",
|
||||||
|
42036: "LensModel",
|
||||||
|
42037: "LensSerialNumber",
|
||||||
|
42240: "Gamma",
|
||||||
|
|
||||||
|
# MP Info
|
||||||
|
45056: "MPFVersion",
|
||||||
|
45057: "NumberOfImages",
|
||||||
|
45058: "MPEntry",
|
||||||
|
45059: "ImageUIDList",
|
||||||
|
45060: "TotalFrames",
|
||||||
|
45313: "MPIndividualNum",
|
||||||
|
45569: "PanOrientation",
|
||||||
|
45570: "PanOverlap_H",
|
||||||
|
45571: "PanOverlap_V",
|
||||||
|
45572: "BaseViewpointNum",
|
||||||
|
45573: "ConvergenceAngle",
|
||||||
|
45574: "BaselineLength",
|
||||||
|
45575: "VerticalDivergence",
|
||||||
|
45576: "AxisDistance_X",
|
||||||
|
45577: "AxisDistance_Y",
|
||||||
|
45578: "AxisDistance_Z",
|
||||||
|
45579: "YawAngle",
|
||||||
|
45580: "PitchAngle",
|
||||||
|
45581: "RollAngle",
|
||||||
|
|
||||||
# Adobe DNG
|
# Adobe DNG
|
||||||
50706: "DNGVersion",
|
50706: "DNGVersion",
|
||||||
50707: "DNGBackwardVersion",
|
50707: "DNGBackwardVersion",
|
||||||
|
|
|
@ -36,6 +36,7 @@ _plugins = ['BmpImagePlugin',
|
||||||
'McIdasImagePlugin',
|
'McIdasImagePlugin',
|
||||||
'MicImagePlugin',
|
'MicImagePlugin',
|
||||||
'MpegImagePlugin',
|
'MpegImagePlugin',
|
||||||
|
'MpoImagePlugin',
|
||||||
'MspImagePlugin',
|
'MspImagePlugin',
|
||||||
'PalmImagePlugin',
|
'PalmImagePlugin',
|
||||||
'PcdImagePlugin',
|
'PcdImagePlugin',
|
||||||
|
|
BIN
Tests/images/frozenpond.mpo
Normal file
BIN
Tests/images/frozenpond.mpo
Normal file
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
BIN
Tests/images/sugarshack.mpo
Normal file
BIN
Tests/images/sugarshack.mpo
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
|
@ -216,6 +216,10 @@ class TestFileJpeg(PillowTestCase):
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
self.assertEqual(info[305], 'Adobe Photoshop CS Macintosh')
|
self.assertEqual(info[305], 'Adobe Photoshop CS Macintosh')
|
||||||
|
|
||||||
|
def test_mp(self):
|
||||||
|
im = Image.open("Tests/images/pil_sample_rgb.jpg")
|
||||||
|
self.assertIsNone(im._getmp())
|
||||||
|
|
||||||
def test_quality_keep(self):
|
def test_quality_keep(self):
|
||||||
im = Image.open("Tests/images/lena.jpg")
|
im = Image.open("Tests/images/lena.jpg")
|
||||||
f = self.tempfile('temp.jpg')
|
f = self.tempfile('temp.jpg')
|
||||||
|
|
114
Tests/test_file_mpo.py
Normal file
114
Tests/test_file_mpo.py
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
from helper import unittest, PillowTestCase
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileMpo(PillowTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
codecs = dir(Image.core)
|
||||||
|
if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs:
|
||||||
|
self.skipTest("jpeg support not available")
|
||||||
|
|
||||||
|
def roundtrip(self, im, **options):
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, "MPO", **options)
|
||||||
|
bytes = out.tell()
|
||||||
|
out.seek(0)
|
||||||
|
im = Image.open(out)
|
||||||
|
im.bytes = bytes # for testing only
|
||||||
|
return im
|
||||||
|
|
||||||
|
def test_sanity(self):
|
||||||
|
for test_file in test_files:
|
||||||
|
im = Image.open(test_file)
|
||||||
|
im.load()
|
||||||
|
self.assertEqual(im.mode, "RGB")
|
||||||
|
self.assertEqual(im.size, (640, 480))
|
||||||
|
self.assertEqual(im.format, "MPO")
|
||||||
|
|
||||||
|
def test_app(self):
|
||||||
|
for test_file in test_files:
|
||||||
|
# Test APP/COM reader (@PIL135)
|
||||||
|
im = Image.open(test_file)
|
||||||
|
self.assertEqual(im.applist[0][0], 'APP1')
|
||||||
|
self.assertEqual(im.applist[1][0], 'APP2')
|
||||||
|
self.assertEqual(im.applist[1][1][:16],
|
||||||
|
b'MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00')
|
||||||
|
self.assertEqual(len(im.applist), 2)
|
||||||
|
|
||||||
|
def test_exif(self):
|
||||||
|
for test_file in test_files:
|
||||||
|
im = Image.open(test_file)
|
||||||
|
info = im._getexif()
|
||||||
|
self.assertEqual(info[272], 'Nintendo 3DS')
|
||||||
|
self.assertEqual(info[296], 2)
|
||||||
|
self.assertEqual(info[34665], 188)
|
||||||
|
|
||||||
|
def test_mp(self):
|
||||||
|
for test_file in test_files:
|
||||||
|
im = Image.open(test_file)
|
||||||
|
mpinfo = im._getmp()
|
||||||
|
self.assertEqual(mpinfo[45056], b'0100')
|
||||||
|
self.assertEqual(mpinfo[45057], 2)
|
||||||
|
|
||||||
|
def test_mp_attribute(self):
|
||||||
|
for test_file in test_files:
|
||||||
|
im = Image.open(test_file)
|
||||||
|
mpinfo = im._getmp()
|
||||||
|
frameNumber = 0
|
||||||
|
for mpentry in mpinfo[45058]:
|
||||||
|
mpattr = mpentry['Attribute']
|
||||||
|
if frameNumber:
|
||||||
|
self.assertFalse(mpattr['RepresentativeImageFlag'])
|
||||||
|
else:
|
||||||
|
self.assertTrue(mpattr['RepresentativeImageFlag'])
|
||||||
|
self.assertFalse(mpattr['DependentParentImageFlag'])
|
||||||
|
self.assertFalse(mpattr['DependentChildImageFlag'])
|
||||||
|
self.assertEqual(mpattr['ImageDataFormat'], 'JPEG')
|
||||||
|
self.assertEqual(mpattr['MPType'],
|
||||||
|
'Multi-Frame Image: (Disparity)')
|
||||||
|
self.assertEqual(mpattr['Reserved'], 0)
|
||||||
|
frameNumber += 1
|
||||||
|
|
||||||
|
def test_seek(self):
|
||||||
|
for test_file in test_files:
|
||||||
|
im = Image.open(test_file)
|
||||||
|
self.assertEqual(im.tell(), 0)
|
||||||
|
# prior to first image raises an error, both blatant and borderline
|
||||||
|
self.assertRaises(EOFError, im.seek, -1)
|
||||||
|
self.assertRaises(EOFError, im.seek, -523)
|
||||||
|
# after the final image raises an error, both blatant and borderline
|
||||||
|
self.assertRaises(EOFError, im.seek, 2)
|
||||||
|
self.assertRaises(EOFError, im.seek, 523)
|
||||||
|
# bad calls shouldn't change the frame
|
||||||
|
self.assertEqual(im.tell(), 0)
|
||||||
|
# this one will work
|
||||||
|
im.seek(1)
|
||||||
|
self.assertEqual(im.tell(), 1)
|
||||||
|
# and this one, too
|
||||||
|
im.seek(0)
|
||||||
|
self.assertEqual(im.tell(), 0)
|
||||||
|
|
||||||
|
def test_image_grab(self):
|
||||||
|
for test_file in test_files:
|
||||||
|
im = Image.open(test_file)
|
||||||
|
self.assertEqual(im.tell(), 0)
|
||||||
|
im0 = im.tobytes()
|
||||||
|
im.seek(1)
|
||||||
|
self.assertEqual(im.tell(), 1)
|
||||||
|
im1 = im.tobytes()
|
||||||
|
im.seek(0)
|
||||||
|
self.assertEqual(im.tell(), 0)
|
||||||
|
im02 = im.tobytes()
|
||||||
|
self.assertEqual(im0, im02)
|
||||||
|
self.assertNotEqual(im0, im1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
|
# End of file
|
|
@ -588,6 +588,20 @@ PIL identifies and reads Microsoft Image Composer (MIC) files. When opened, the
|
||||||
first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
|
first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
|
||||||
:py:meth:`~file.tell` to read other sprites from the file.
|
:py:meth:`~file.tell` to read other sprites from the file.
|
||||||
|
|
||||||
|
MPO
|
||||||
|
^^^
|
||||||
|
|
||||||
|
Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary
|
||||||
|
image when first opened. The :py:meth:`~file.seek` and :py:meth:`~file.tell`
|
||||||
|
methods may be used to read other pictures from the file. The pictures are
|
||||||
|
zero-indexed and random access is supported.
|
||||||
|
|
||||||
|
MIC (read only)
|
||||||
|
|
||||||
|
Pillow identifies and reads Microsoft Image Composer (MIC) files. When opened, the
|
||||||
|
first sprite in the file is loaded. You can use :py:meth:`~file.seek` and
|
||||||
|
:py:meth:`~file.tell` to read other sprites from the file.
|
||||||
|
|
||||||
PCD
|
PCD
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user