mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
Merge pull request #1419 from wiredfool/tiff-ifd-rewrite
Tiff ImageFileDirectory Rewrite
This commit is contained in:
commit
de34547f33
|
@ -217,7 +217,7 @@ def getiptcinfo(im):
|
||||||
while app[offset:offset+4] == b"8BIM":
|
while app[offset:offset+4] == b"8BIM":
|
||||||
offset += 4
|
offset += 4
|
||||||
# resource code
|
# resource code
|
||||||
code = JpegImagePlugin.i16(app, offset)
|
code = i16(app, offset)
|
||||||
offset += 2
|
offset += 2
|
||||||
# resource name (usually empty)
|
# resource name (usually empty)
|
||||||
name_len = i8(app[offset])
|
name_len = i8(app[offset])
|
||||||
|
@ -226,7 +226,7 @@ def getiptcinfo(im):
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
offset += 1
|
offset += 1
|
||||||
# resource data block
|
# resource data block
|
||||||
size = JpegImagePlugin.i32(app, offset)
|
size = i32(app, offset)
|
||||||
offset += 4
|
offset += 4
|
||||||
if code == 0x0404:
|
if code == 0x0404:
|
||||||
# 0x0404 contains IPTC/NAA data
|
# 0x0404 contains IPTC/NAA data
|
||||||
|
|
|
@ -36,7 +36,7 @@ import array
|
||||||
import struct
|
import struct
|
||||||
import io
|
import io
|
||||||
import warnings
|
import warnings
|
||||||
from struct import unpack
|
from struct import unpack_from
|
||||||
from PIL import Image, ImageFile, TiffImagePlugin, _binary
|
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
|
||||||
|
@ -394,13 +394,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
return _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
|
||||||
|
@ -414,12 +407,10 @@ def _getexif(self):
|
||||||
return None
|
return None
|
||||||
file = io.BytesIO(data[6:])
|
file = io.BytesIO(data[6:])
|
||||||
head = file.read(8)
|
head = file.read(8)
|
||||||
exif = {}
|
|
||||||
# process dictionary
|
# process dictionary
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
exif = dict(info)
|
||||||
exif[key] = _fixup(value)
|
|
||||||
# get exif extension
|
# get exif extension
|
||||||
try:
|
try:
|
||||||
# exif field 0x8769 is an offset pointer to the location
|
# exif field 0x8769 is an offset pointer to the location
|
||||||
|
@ -429,24 +420,21 @@ def _getexif(self):
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
for key, value in info.items():
|
exif.update(info)
|
||||||
exif[key] = _fixup(value)
|
|
||||||
# get gpsinfo extension
|
# get gpsinfo extension
|
||||||
try:
|
try:
|
||||||
# exif field 0x8825 is an offset pointer to the location
|
# exif field 0x8825 is an offset pointer to the location
|
||||||
# of the nested embedded gps exif ifd.
|
# of the nested embedded gps exif ifd.
|
||||||
# It should be a long, but may be corrupted.
|
# It should be a long, but may be corrupted.
|
||||||
file.seek(exif[0x8825])
|
file.seek(exif[0x8825])
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||||
info.load(file)
|
info.load(file)
|
||||||
exif[0x8825] = gps = {}
|
exif[0x8825] = dict(info)
|
||||||
for key, value in info.items():
|
|
||||||
gps[key] = _fixup(value)
|
|
||||||
return exif
|
return exif
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,23 +452,22 @@ def _getmp(self):
|
||||||
file_contents = io.BytesIO(data)
|
file_contents = io.BytesIO(data)
|
||||||
head = file_contents.read(8)
|
head = file_contents.read(8)
|
||||||
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
endianness = '>' if head[:4] == b'\x4d\x4d\x00\x2a' else '<'
|
||||||
mp = {}
|
|
||||||
# process dictionary
|
# process dictionary
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||||
info.load(file_contents)
|
info.load(file_contents)
|
||||||
for key, value in info.items():
|
mp = dict(info)
|
||||||
mp[key] = _fixup(value)
|
|
||||||
# it's an error not to have a number of images
|
# it's an error not to have a number of images
|
||||||
try:
|
try:
|
||||||
quant = mp[0xB001]
|
quant = mp[0xB001]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise SyntaxError("malformed MP Index (no number of images)")
|
raise SyntaxError("malformed MP Index (no number of images)")
|
||||||
# get MP entries
|
# get MP entries
|
||||||
|
mpentries = []
|
||||||
try:
|
try:
|
||||||
mpentries = []
|
rawmpentries = mp[0xB002]
|
||||||
for entrynum in range(0, quant):
|
for entrynum in range(0, quant):
|
||||||
rawmpentry = mp[0xB002][entrynum * 16:(entrynum + 1) * 16]
|
unpackedentry = unpack_from(
|
||||||
unpackedentry = unpack('{0}LLLHH'.format(endianness), rawmpentry)
|
'{0}LLLHH'.format(endianness), rawmpentries, entrynum * 16)
|
||||||
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
labels = ('Attribute', 'Size', 'DataOffset', 'EntryNo1',
|
||||||
'EntryNo2')
|
'EntryNo2')
|
||||||
mpentry = dict(zip(labels, unpackedentry))
|
mpentry = dict(zip(labels, unpackedentry))
|
||||||
|
|
|
@ -27,7 +27,6 @@ __version__ = "0.1"
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
i32 = _binary.i32le
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -24,7 +24,6 @@ __version__ = "0.2"
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16be
|
i16 = _binary.i16be
|
||||||
i32 = _binary.i32be
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
|
|
|
@ -21,7 +21,6 @@ from PIL import Image, ImageFile, ImagePalette, _binary
|
||||||
|
|
||||||
__version__ = "0.3"
|
__version__ = "0.3"
|
||||||
|
|
||||||
i16 = _binary.i16be
|
|
||||||
i32 = _binary.i32be
|
i32 = _binary.i32be
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ __version__ = "0.3"
|
||||||
|
|
||||||
i8 = _binary.i8
|
i8 = _binary.i8
|
||||||
i16 = _binary.i16le
|
i16 = _binary.i16le
|
||||||
i32 = _binary.i32le
|
|
||||||
|
|
||||||
|
|
||||||
MODES = {
|
MODES = {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
536
PIL/TiffTags.py
536
PIL/TiffTags.py
|
@ -17,291 +17,299 @@
|
||||||
# well-known TIFF tags.
|
# well-known TIFF tags.
|
||||||
##
|
##
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
class TagInfo(namedtuple("_TagInfo", "value name type length enum")):
|
||||||
|
__slots__ = []
|
||||||
|
|
||||||
|
def __new__(cls, value=None, name="unknown", type=4, length=0, enum=None):
|
||||||
|
return super(TagInfo, cls).__new__(
|
||||||
|
cls, value, name, type, length, enum or {})
|
||||||
|
|
||||||
|
def cvt_enum(self, value):
|
||||||
|
return self.enum.get(value, value)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Map tag numbers (or tag number, tag value tuples) to tag names.
|
# Map tag numbers to tag info.
|
||||||
|
#
|
||||||
|
# id: (Name, Type, Length, enum_values)
|
||||||
|
#
|
||||||
|
TAGS_V2 = {
|
||||||
|
|
||||||
TAGS = {
|
254: ("NewSubfileType", 4, 1),
|
||||||
|
255: ("SubfileType", 3, 1),
|
||||||
|
256: ("ImageWidth", 4, 1),
|
||||||
|
257: ("ImageLength", 4, 1),
|
||||||
|
258: ("BitsPerSample", 3, 0),
|
||||||
|
259: ("Compression", 3, 1,
|
||||||
|
{"Uncompressed": 1, "CCITT 1d": 2, "Group 3 Fax": 3, "Group 4 Fax": 4,
|
||||||
|
"LZW": 5, "JPEG": 6, "PackBits": 32773}),
|
||||||
|
|
||||||
254: "NewSubfileType",
|
262: ("PhotometricInterpretation", 3, 1,
|
||||||
255: "SubfileType",
|
{"WhiteIsZero": 0, "BlackIsZero": 1, "RGB": 2, "RBG Palette": 3,
|
||||||
256: "ImageWidth",
|
"Transparency Mask": 4, "CMYK": 5, "YCbCr": 6, "CieLAB": 8,
|
||||||
257: "ImageLength",
|
"CFA": 32803, # TIFF/EP, Adobe DNG
|
||||||
258: "BitsPerSample",
|
"LinearRaw": 32892}), # Adobe DNG
|
||||||
|
263: ("Thresholding", 3, 1),
|
||||||
|
264: ("CellWidth", 3, 1),
|
||||||
|
265: ("CellHeight", 3, 1),
|
||||||
|
266: ("FillOrder", 3, 1),
|
||||||
|
269: ("DocumentName", 2, 1),
|
||||||
|
|
||||||
259: "Compression",
|
270: ("ImageDescription", 2, 1),
|
||||||
(259, 1): "Uncompressed",
|
271: ("Make", 2, 1),
|
||||||
(259, 2): "CCITT 1d",
|
272: ("Model", 2, 1),
|
||||||
(259, 3): "Group 3 Fax",
|
273: ("StripOffsets", 4, 0),
|
||||||
(259, 4): "Group 4 Fax",
|
274: ("Orientation", 3, 1),
|
||||||
(259, 5): "LZW",
|
277: ("SamplesPerPixel", 3, 1),
|
||||||
(259, 6): "JPEG",
|
278: ("RowsPerStrip", 4, 1),
|
||||||
(259, 32773): "PackBits",
|
279: ("StripByteCounts", 4, 0),
|
||||||
|
|
||||||
262: "PhotometricInterpretation",
|
280: ("MinSampleValue", 4, 0),
|
||||||
(262, 0): "WhiteIsZero",
|
281: ("MaxSampleValue", 3, 0),
|
||||||
(262, 1): "BlackIsZero",
|
282: ("XResolution", 5, 1),
|
||||||
(262, 2): "RGB",
|
283: ("YResolution", 5, 1),
|
||||||
(262, 3): "RGB Palette",
|
284: ("PlanarConfiguration", 3, 1, {"Contigous": 1, "Separate": 2}),
|
||||||
(262, 4): "Transparency Mask",
|
285: ("PageName", 2, 1),
|
||||||
(262, 5): "CMYK",
|
286: ("XPosition", 5, 1),
|
||||||
(262, 6): "YCbCr",
|
287: ("YPosition", 5, 1),
|
||||||
(262, 8): "CieLAB",
|
288: ("FreeOffsets", 4, 1),
|
||||||
(262, 32803): "CFA", # TIFF/EP, Adobe DNG
|
289: ("FreeByteCounts", 4, 1),
|
||||||
(262, 32892): "LinearRaw", # Adobe DNG
|
|
||||||
|
|
||||||
263: "Thresholding",
|
290: ("GrayResponseUnit", 3, 1),
|
||||||
264: "CellWidth",
|
291: ("GrayResponseCurve", 3, 0),
|
||||||
265: "CellHeight",
|
292: ("T4Options", 4, 1),
|
||||||
266: "FillOrder",
|
293: ("T6Options", 4, 1),
|
||||||
269: "DocumentName",
|
296: ("ResolutionUnit", 3, 1, {"inch": 1, "cm": 2}),
|
||||||
|
297: ("PageNumber", 3, 2),
|
||||||
|
|
||||||
270: "ImageDescription",
|
301: ("TransferFunction", 3, 0),
|
||||||
271: "Make",
|
305: ("Software", 2, 1),
|
||||||
272: "Model",
|
306: ("DateTime", 2, 1),
|
||||||
273: "StripOffsets",
|
|
||||||
274: "Orientation",
|
|
||||||
277: "SamplesPerPixel",
|
|
||||||
278: "RowsPerStrip",
|
|
||||||
279: "StripByteCounts",
|
|
||||||
|
|
||||||
280: "MinSampleValue",
|
315: ("Artist", 2, 1),
|
||||||
281: "MaxSampleValue",
|
316: ("HostComputer", 2, 1),
|
||||||
282: "XResolution",
|
317: ("Predictor", 3, 1),
|
||||||
283: "YResolution",
|
318: ("WhitePoint", 5, 2),
|
||||||
284: "PlanarConfiguration",
|
319: ("PrimaryChromaticies", 3, 6),
|
||||||
(284, 1): "Contigous",
|
|
||||||
(284, 2): "Separate",
|
|
||||||
|
|
||||||
285: "PageName",
|
320: ("ColorMap", 3, 0),
|
||||||
286: "XPosition",
|
321: ("HalftoneHints", 3, 2),
|
||||||
287: "YPosition",
|
322: ("TileWidth", 4, 1),
|
||||||
288: "FreeOffsets",
|
323: ("TileLength", 4, 1),
|
||||||
289: "FreeByteCounts",
|
324: ("TileOffsets", 4, 0),
|
||||||
|
325: ("TileByteCounts", 4, 0),
|
||||||
|
|
||||||
290: "GrayResponseUnit",
|
332: ("InkSet", 3, 1),
|
||||||
291: "GrayResponseCurve",
|
333: ("InkNames", 2, 1),
|
||||||
292: "T4Options",
|
334: ("NumberOfInks", 3, 1),
|
||||||
293: "T6Options",
|
336: ("DotRange", 3, 0),
|
||||||
296: "ResolutionUnit",
|
337: ("TargetPrinter", 2, 1),
|
||||||
297: "PageNumber",
|
338: ("ExtraSamples", 1, 0),
|
||||||
|
339: ("SampleFormat", 3, 0),
|
||||||
|
|
||||||
301: "TransferFunction",
|
340: ("SMinSampleValue", 12, 0),
|
||||||
305: "Software",
|
341: ("SMaxSampleValue", 12, 0),
|
||||||
306: "DateTime",
|
342: ("TransferRange", 3, 6),
|
||||||
|
|
||||||
315: "Artist",
|
|
||||||
316: "HostComputer",
|
|
||||||
317: "Predictor",
|
|
||||||
318: "WhitePoint",
|
|
||||||
319: "PrimaryChromaticies",
|
|
||||||
|
|
||||||
320: "ColorMap",
|
|
||||||
321: "HalftoneHints",
|
|
||||||
322: "TileWidth",
|
|
||||||
323: "TileLength",
|
|
||||||
324: "TileOffsets",
|
|
||||||
325: "TileByteCounts",
|
|
||||||
|
|
||||||
332: "InkSet",
|
|
||||||
333: "InkNames",
|
|
||||||
334: "NumberOfInks",
|
|
||||||
336: "DotRange",
|
|
||||||
337: "TargetPrinter",
|
|
||||||
338: "ExtraSamples",
|
|
||||||
339: "SampleFormat",
|
|
||||||
|
|
||||||
340: "SMinSampleValue",
|
|
||||||
341: "SMaxSampleValue",
|
|
||||||
342: "TransferRange",
|
|
||||||
|
|
||||||
347: "JPEGTables",
|
|
||||||
|
|
||||||
# obsolete JPEG tags
|
# obsolete JPEG tags
|
||||||
512: "JPEGProc",
|
512: ("JPEGProc", 3, 1),
|
||||||
513: "JPEGInterchangeFormat",
|
513: ("JPEGInterchangeFormat", 4, 1),
|
||||||
514: "JPEGInterchangeFormatLength",
|
514: ("JPEGInterchangeFormatLength", 4, 1),
|
||||||
515: "JPEGRestartInterval",
|
515: ("JPEGRestartInterval", 3, 1),
|
||||||
517: "JPEGLosslessPredictors",
|
517: ("JPEGLosslessPredictors", 3, 0),
|
||||||
518: "JPEGPointTransforms",
|
518: ("JPEGPointTransforms", 3, 0),
|
||||||
519: "JPEGQTables",
|
519: ("JPEGQTables", 4, 0),
|
||||||
520: "JPEGDCTables",
|
520: ("JPEGDCTables", 4, 0),
|
||||||
521: "JPEGACTables",
|
521: ("JPEGACTables", 4, 0),
|
||||||
|
|
||||||
529: "YCbCrCoefficients",
|
529: ("YCbCrCoefficients", 5, 3),
|
||||||
530: "YCbCrSubSampling",
|
530: ("YCbCrSubSampling", 3, 2),
|
||||||
531: "YCbCrPositioning",
|
531: ("YCbCrPositioning", 3, 1),
|
||||||
532: "ReferenceBlackWhite",
|
532: ("ReferenceBlackWhite", 4, 0),
|
||||||
|
|
||||||
# XMP
|
33432: ("Copyright", 2, 1),
|
||||||
700: "XMP",
|
|
||||||
|
|
||||||
33432: "Copyright",
|
# FIXME add more tags here
|
||||||
|
34665: ("ExifIFD", 3, 1),
|
||||||
|
|
||||||
# various extensions (should check specs for "official" names)
|
# MPInfo
|
||||||
33723: "IptcNaaInfo",
|
45056: ("MPFVersion", 7, 1),
|
||||||
34377: "PhotoshopInfo",
|
45057: ("NumberOfImages", 4, 1),
|
||||||
|
45058: ("MPEntry", 7, 1),
|
||||||
|
45059: ("ImageUIDList", 7, 0),
|
||||||
|
45060: ("TotalFrames", 4, 1),
|
||||||
|
45313: ("MPIndividualNum", 4, 1),
|
||||||
|
45569: ("PanOrientation", 4, 1),
|
||||||
|
45570: ("PanOverlap_H", 5, 1),
|
||||||
|
45571: ("PanOverlap_V", 5, 1),
|
||||||
|
45572: ("BaseViewpointNum", 4, 1),
|
||||||
|
45573: ("ConvergenceAngle", 10, 1),
|
||||||
|
45574: ("BaselineLength", 5, 1),
|
||||||
|
45575: ("VerticalDivergence", 10, 1),
|
||||||
|
45576: ("AxisDistance_X", 10, 1),
|
||||||
|
45577: ("AxisDistance_Y", 10, 1),
|
||||||
|
45578: ("AxisDistance_Z", 10, 1),
|
||||||
|
45579: ("YawAngle", 10, 1),
|
||||||
|
45580: ("PitchAngle", 10, 1),
|
||||||
|
45581: ("RollAngle", 10, 1),
|
||||||
|
|
||||||
# Exif IFD
|
50741: ("MakerNoteSafety", 3, 1, {"Unsafe": 0, "Safe": 1}),
|
||||||
34665: "ExifIFD",
|
50780: ("BestQualityScale", 5, 1),
|
||||||
|
50838: ("ImageJMetaDataByteCounts", 4, 1),
|
||||||
# ICC Profile
|
50839: ("ImageJMetaData", 7, 1)
|
||||||
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
|
|
||||||
50706: "DNGVersion",
|
|
||||||
50707: "DNGBackwardVersion",
|
|
||||||
50708: "UniqueCameraModel",
|
|
||||||
50709: "LocalizedCameraModel",
|
|
||||||
50710: "CFAPlaneColor",
|
|
||||||
50711: "CFALayout",
|
|
||||||
50712: "LinearizationTable",
|
|
||||||
50713: "BlackLevelRepeatDim",
|
|
||||||
50714: "BlackLevel",
|
|
||||||
50715: "BlackLevelDeltaH",
|
|
||||||
50716: "BlackLevelDeltaV",
|
|
||||||
50717: "WhiteLevel",
|
|
||||||
50718: "DefaultScale",
|
|
||||||
50719: "DefaultCropOrigin",
|
|
||||||
50720: "DefaultCropSize",
|
|
||||||
50778: "CalibrationIlluminant1",
|
|
||||||
50779: "CalibrationIlluminant2",
|
|
||||||
50721: "ColorMatrix1",
|
|
||||||
50722: "ColorMatrix2",
|
|
||||||
50723: "CameraCalibration1",
|
|
||||||
50724: "CameraCalibration2",
|
|
||||||
50725: "ReductionMatrix1",
|
|
||||||
50726: "ReductionMatrix2",
|
|
||||||
50727: "AnalogBalance",
|
|
||||||
50728: "AsShotNeutral",
|
|
||||||
50729: "AsShotWhiteXY",
|
|
||||||
50730: "BaselineExposure",
|
|
||||||
50731: "BaselineNoise",
|
|
||||||
50732: "BaselineSharpness",
|
|
||||||
50733: "BayerGreenSplit",
|
|
||||||
50734: "LinearResponseLimit",
|
|
||||||
50735: "CameraSerialNumber",
|
|
||||||
50736: "LensInfo",
|
|
||||||
50737: "ChromaBlurRadius",
|
|
||||||
50738: "AntiAliasStrength",
|
|
||||||
50740: "DNGPrivateData",
|
|
||||||
50741: "MakerNoteSafety",
|
|
||||||
50780: "BestQualityScale",
|
|
||||||
|
|
||||||
# ImageJ
|
|
||||||
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
|
|
||||||
50839: "ImageJMetaData", # private tag registered with Adobe
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Legacy Tags structure
|
||||||
|
# these tags aren't included above, but were in the previous versions
|
||||||
|
TAGS = {347: 'JPEGTables',
|
||||||
|
700: 'XMP',
|
||||||
|
|
||||||
|
# Additional Exif Info
|
||||||
|
33434: 'ExposureTime',
|
||||||
|
33437: 'FNumber',
|
||||||
|
33723: 'IptcNaaInfo',
|
||||||
|
34377: 'PhotoshopInfo',
|
||||||
|
34675: 'ICCProfile',
|
||||||
|
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',
|
||||||
|
|
||||||
|
# Adobe DNG
|
||||||
|
50706: 'DNGVersion',
|
||||||
|
50707: 'DNGBackwardVersion',
|
||||||
|
50708: 'UniqueCameraModel',
|
||||||
|
50709: 'LocalizedCameraModel',
|
||||||
|
50710: 'CFAPlaneColor',
|
||||||
|
50711: 'CFALayout',
|
||||||
|
50712: 'LinearizationTable',
|
||||||
|
50713: 'BlackLevelRepeatDim',
|
||||||
|
50714: 'BlackLevel',
|
||||||
|
50715: 'BlackLevelDeltaH',
|
||||||
|
50716: 'BlackLevelDeltaV',
|
||||||
|
50717: 'WhiteLevel',
|
||||||
|
50718: 'DefaultScale',
|
||||||
|
50719: 'DefaultCropOrigin',
|
||||||
|
50720: 'DefaultCropSize',
|
||||||
|
50721: 'ColorMatrix1',
|
||||||
|
50722: 'ColorMatrix2',
|
||||||
|
50723: 'CameraCalibration1',
|
||||||
|
50724: 'CameraCalibration2',
|
||||||
|
50725: 'ReductionMatrix1',
|
||||||
|
50726: 'ReductionMatrix2',
|
||||||
|
50727: 'AnalogBalance',
|
||||||
|
50728: 'AsShotNeutral',
|
||||||
|
50729: 'AsShotWhiteXY',
|
||||||
|
50730: 'BaselineExposure',
|
||||||
|
50731: 'BaselineNoise',
|
||||||
|
50732: 'BaselineSharpness',
|
||||||
|
50733: 'BayerGreenSplit',
|
||||||
|
50734: 'LinearResponseLimit',
|
||||||
|
50735: 'CameraSerialNumber',
|
||||||
|
50736: 'LensInfo',
|
||||||
|
50737: 'ChromaBlurRadius',
|
||||||
|
50738: 'AntiAliasStrength',
|
||||||
|
50740: 'DNGPrivateData',
|
||||||
|
50778: 'CalibrationIlluminant1',
|
||||||
|
50779: 'CalibrationIlluminant2',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _populate():
|
||||||
|
for k, v in TAGS_V2.items():
|
||||||
|
# Populate legacy structure.
|
||||||
|
TAGS[k] = v[0]
|
||||||
|
if len(v) == 4:
|
||||||
|
for sk, sv in v[3].items():
|
||||||
|
TAGS[(k, sv)] = sk
|
||||||
|
|
||||||
|
TAGS_V2[k] = TagInfo(k, *v)
|
||||||
|
|
||||||
|
_populate()
|
||||||
##
|
##
|
||||||
# Map type numbers to type names.
|
# Map type numbers to type names -- defined in ImageFileDirectory.
|
||||||
|
|
||||||
TYPES = {
|
TYPES = {}
|
||||||
|
|
||||||
1: "byte",
|
# was:
|
||||||
2: "ascii",
|
# TYPES = {
|
||||||
3: "short",
|
# 1: "byte",
|
||||||
4: "long",
|
# 2: "ascii",
|
||||||
5: "rational",
|
# 3: "short",
|
||||||
6: "signed byte",
|
# 4: "long",
|
||||||
7: "undefined",
|
# 5: "rational",
|
||||||
8: "signed short",
|
# 6: "signed byte",
|
||||||
9: "signed long",
|
# 7: "undefined",
|
||||||
10: "signed rational",
|
# 8: "signed short",
|
||||||
11: "float",
|
# 9: "signed long",
|
||||||
12: "double",
|
# 10: "signed rational",
|
||||||
|
# 11: "float",
|
||||||
|
# 12: "double",
|
||||||
|
# }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -43,11 +43,6 @@ class PillowTestCase(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
print("=== orphaned temp file: %s" % path)
|
print("=== orphaned temp file: %s" % path)
|
||||||
|
|
||||||
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
|
|
||||||
self.assertLess(
|
|
||||||
abs(a-b), eps,
|
|
||||||
msg or "got %r, expected %r" % (a, b))
|
|
||||||
|
|
||||||
def assert_deep_equal(self, a, b, msg=None):
|
def assert_deep_equal(self, a, b, msg=None):
|
||||||
try:
|
try:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
@ -80,7 +80,7 @@ class TestImage(PillowTestCase):
|
||||||
ret = GimpGradientFile.sphere_increasing(middle, pos)
|
ret = GimpGradientFile.sphere_increasing(middle, pos)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_almost_equal(ret, 0.9682458365518543)
|
self.assertAlmostEqual(ret, 0.9682458365518543)
|
||||||
|
|
||||||
def test_sphere_decreasing(self):
|
def test_sphere_decreasing(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -370,7 +370,8 @@ class TestFileJpeg(PillowTestCase):
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
# Shouldn't raise error
|
# Shouldn't raise error
|
||||||
im = Image.open("Tests/images/sugarshack_bad_mpo_header.jpg")
|
fn = "Tests/images/sugarshack_bad_mpo_header.jpg"
|
||||||
|
im = self.assert_warning(UserWarning, lambda: Image.open(fn))
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assertEqual(im.format, "JPEG")
|
self.assertEqual(im.format, "JPEG")
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from helper import unittest, PillowTestCase, hopper, py3
|
from helper import unittest, PillowTestCase, hopper, py3
|
||||||
|
|
||||||
|
from ctypes import c_float
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
|
@ -123,43 +125,45 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
def test_write_metadata(self):
|
def test_write_metadata(self):
|
||||||
""" Test metadata writing through libtiff """
|
""" Test metadata writing through libtiff """
|
||||||
img = Image.open('Tests/images/hopper_g4.tif')
|
for legacy_api in [False, True]:
|
||||||
f = self.tempfile('temp.tiff')
|
img = Image.open('Tests/images/hopper_g4.tif')
|
||||||
|
f = self.tempfile('temp.tiff')
|
||||||
|
|
||||||
img.save(f, tiffinfo=img.tag)
|
img.save(f, tiffinfo=img.tag)
|
||||||
|
|
||||||
loaded = Image.open(f)
|
if legacy_api:
|
||||||
|
original = img.tag.named()
|
||||||
|
else:
|
||||||
|
original = img.tag_v2.named()
|
||||||
|
|
||||||
original = img.tag.named()
|
# PhotometricInterpretation is set from SAVE_INFO,
|
||||||
reloaded = loaded.tag.named()
|
# not the original image.
|
||||||
|
ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber',
|
||||||
|
'PhotometricInterpretation']
|
||||||
|
|
||||||
# PhotometricInterpretation is set from SAVE_INFO,
|
loaded = Image.open(f)
|
||||||
# not the original image.
|
if legacy_api:
|
||||||
ignored = [
|
reloaded = loaded.tag.named()
|
||||||
'StripByteCounts', 'RowsPerStrip',
|
else:
|
||||||
'PageNumber', 'PhotometricInterpretation']
|
reloaded = loaded.tag_v2.named()
|
||||||
|
|
||||||
for tag, value in reloaded.items():
|
for tag, value in itertools.chain(reloaded.items(),
|
||||||
if tag not in ignored:
|
original.items()):
|
||||||
if tag.endswith('Resolution'):
|
if tag not in ignored:
|
||||||
val = original[tag]
|
val = original[tag]
|
||||||
self.assert_almost_equal(
|
if tag.endswith('Resolution'):
|
||||||
val[0][0]/val[0][1], value[0][0]/value[0][1],
|
if legacy_api:
|
||||||
msg="%s didn't roundtrip" % tag)
|
self.assertEqual(
|
||||||
else:
|
c_float(val[0][0] / val[0][1]).value,
|
||||||
self.assertEqual(
|
c_float(value[0][0] / value[0][1]).value,
|
||||||
original[tag], value, "%s didn't roundtrip" % tag)
|
msg="%s didn't roundtrip" % tag)
|
||||||
|
else:
|
||||||
for tag, value in original.items():
|
self.assertEqual(
|
||||||
if tag not in ignored:
|
c_float(val).value, c_float(value).value,
|
||||||
if tag.endswith('Resolution'):
|
msg="%s didn't roundtrip" % tag)
|
||||||
val = reloaded[tag]
|
else:
|
||||||
self.assert_almost_equal(
|
self.assertEqual(
|
||||||
val[0][0]/val[0][1], value[0][0]/value[0][1],
|
val, value, msg="%s didn't roundtrip" % tag)
|
||||||
msg="%s didn't roundtrip" % tag)
|
|
||||||
else:
|
|
||||||
self.assertEqual(
|
|
||||||
value, reloaded[tag], "%s didn't roundtrip" % tag)
|
|
||||||
|
|
||||||
def test_g3_compression(self):
|
def test_g3_compression(self):
|
||||||
i = Image.open('Tests/images/hopper_g4_500.tif')
|
i = Image.open('Tests/images/hopper_g4_500.tif')
|
||||||
|
@ -228,7 +232,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
orig.save(out)
|
orig.save(out)
|
||||||
|
|
||||||
reread = Image.open(out)
|
reread = Image.open(out)
|
||||||
self.assertEqual('temp.tif', reread.tag[269])
|
self.assertEqual('temp.tif', reread.tag_v2[269])
|
||||||
|
self.assertEqual('temp.tif', reread.tag[269][0])
|
||||||
|
|
||||||
def test_12bit_rawmode(self):
|
def test_12bit_rawmode(self):
|
||||||
""" Are we generating the same interpretation
|
""" Are we generating the same interpretation
|
||||||
|
|
|
@ -74,14 +74,28 @@ class TestFileTiff(PillowTestCase):
|
||||||
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
||||||
filename = "Tests/images/pil168.tif"
|
filename = "Tests/images/pil168.tif"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
assert isinstance(im.tag.tags[X_RESOLUTION][0], tuple)
|
|
||||||
assert isinstance(im.tag.tags[Y_RESOLUTION][0], tuple)
|
#legacy api
|
||||||
# Try to read a file where X,Y_RESOLUTION are ints
|
self.assert_(isinstance(im.tag[X_RESOLUTION][0], tuple))
|
||||||
im.tag.tags[X_RESOLUTION] = (72,)
|
self.assert_(isinstance(im.tag[Y_RESOLUTION][0], tuple))
|
||||||
im.tag.tags[Y_RESOLUTION] = (72,)
|
|
||||||
im._setup()
|
#v2 api
|
||||||
|
self.assert_(isinstance(im.tag_v2[X_RESOLUTION], float))
|
||||||
|
self.assert_(isinstance(im.tag_v2[Y_RESOLUTION], float))
|
||||||
|
|
||||||
self.assertEqual(im.info['dpi'], (72., 72.))
|
self.assertEqual(im.info['dpi'], (72., 72.))
|
||||||
|
|
||||||
|
def test_int_resolution(self):
|
||||||
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
||||||
|
filename = "Tests/images/pil168.tif"
|
||||||
|
im = Image.open(filename)
|
||||||
|
|
||||||
|
# Try to read a file where X,Y_RESOLUTION are ints
|
||||||
|
im.tag_v2[X_RESOLUTION] = 71
|
||||||
|
im.tag_v2[Y_RESOLUTION] = 71
|
||||||
|
im._setup()
|
||||||
|
self.assertEqual(im.info['dpi'], (71., 71.))
|
||||||
|
|
||||||
def test_invalid_file(self):
|
def test_invalid_file(self):
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
@ -89,8 +103,9 @@ class TestFileTiff(PillowTestCase):
|
||||||
lambda: TiffImagePlugin.TiffImageFile(invalid_file))
|
lambda: TiffImagePlugin.TiffImageFile(invalid_file))
|
||||||
|
|
||||||
def test_bad_exif(self):
|
def test_bad_exif(self):
|
||||||
|
i = Image.open('Tests/images/hopper_bad_exif.jpg')
|
||||||
try:
|
try:
|
||||||
Image.open('Tests/images/hopper_bad_exif.jpg')._getexif()
|
self.assert_warning(UserWarning, lambda: i._getexif())
|
||||||
except struct.error:
|
except struct.error:
|
||||||
self.fail(
|
self.fail(
|
||||||
"Bad EXIF data passed incorrect values to _binary unpack")
|
"Bad EXIF data passed incorrect values to _binary unpack")
|
||||||
|
@ -98,7 +113,6 @@ class TestFileTiff(PillowTestCase):
|
||||||
def test_save_unsupported_mode(self):
|
def test_save_unsupported_mode(self):
|
||||||
im = hopper("HSV")
|
im = hopper("HSV")
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = self.tempfile("temp.tif")
|
||||||
|
|
||||||
self.assertRaises(IOError, lambda: im.save(outfile))
|
self.assertRaises(IOError, lambda: im.save(outfile))
|
||||||
|
|
||||||
def test_little_endian(self):
|
def test_little_endian(self):
|
||||||
|
@ -206,7 +220,6 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255))
|
self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255))
|
||||||
|
|
||||||
def test___str__(self):
|
def test___str__(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
|
|
||||||
|
@ -220,138 +233,82 @@ class TestFileTiff(PillowTestCase):
|
||||||
# Arrange
|
# Arrange
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
|
# v2 interface
|
||||||
# Act
|
|
||||||
ret = im.ifd.as_dict()
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertIsInstance(ret, dict)
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ret, {256: (55,), 257: (43,), 258: (8, 8, 8, 8), 259: (1,),
|
im.tag_v2.as_dict(),
|
||||||
262: (2,), 296: (2,), 273: (8,), 338: (1,), 277: (4,),
|
{256: 55, 257: 43, 258: (8, 8, 8, 8), 259: 1,
|
||||||
279: (9460,), 282: ((720000, 10000),),
|
262: 2, 296: 2, 273: (8,), 338: (1,), 277: 4,
|
||||||
283: ((720000, 10000),), 284: (1,)})
|
279: (9460,), 282: 72.0, 283: 72.0, 284: 1})
|
||||||
|
|
||||||
|
# legacy interface
|
||||||
|
self.assertEqual(
|
||||||
|
im.tag.as_dict(),
|
||||||
|
{256: (55,), 257: (43,), 258: (8, 8, 8, 8), 259: (1,),
|
||||||
|
262: (2,), 296: (2,), 273: (8,), 338: (1,), 277: (4,),
|
||||||
|
279: (9460,), 282: ((720000, 10000),),
|
||||||
|
283: ((720000, 10000),), 284: (1,)})
|
||||||
|
|
||||||
def test__delitem__(self):
|
def test__delitem__(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
len_before = len(im.ifd.as_dict())
|
len_before = len(im.ifd.as_dict())
|
||||||
|
|
||||||
# Act
|
|
||||||
del im.ifd[256]
|
del im.ifd[256]
|
||||||
|
|
||||||
# Assert
|
|
||||||
len_after = len(im.ifd.as_dict())
|
len_after = len(im.ifd.as_dict())
|
||||||
self.assertEqual(len_before, len_after + 1)
|
self.assertEqual(len_before, len_after + 1)
|
||||||
|
|
||||||
def test_load_byte(self):
|
def test_load_byte(self):
|
||||||
# Arrange
|
for legacy_api in [False, True]:
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
data = b"abc"
|
data = b"abc"
|
||||||
|
ret = ifd.load_byte(data, legacy_api)
|
||||||
# Act
|
self.assertEqual(ret, b"abc" if legacy_api else (97, 98, 99))
|
||||||
ret = ifd.load_byte(data)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, b"abc")
|
|
||||||
|
|
||||||
def test_load_string(self):
|
def test_load_string(self):
|
||||||
# Arrange
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
|
||||||
data = b"abc\0"
|
data = b"abc\0"
|
||||||
|
ret = ifd.load_string(data, False)
|
||||||
# Act
|
|
||||||
ret = ifd.load_string(data)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, "abc")
|
self.assertEqual(ret, "abc")
|
||||||
|
|
||||||
def test_load_float(self):
|
def test_load_float(self):
|
||||||
# Arrange
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
|
||||||
data = b"abcdabcd"
|
data = b"abcdabcd"
|
||||||
|
ret = ifd.load_float(data, False)
|
||||||
# Act
|
|
||||||
ret = ifd.load_float(data)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22))
|
self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22))
|
||||||
|
|
||||||
def test_load_double(self):
|
def test_load_double(self):
|
||||||
# Arrange
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
|
||||||
data = b"abcdefghabcdefgh"
|
data = b"abcdefghabcdefgh"
|
||||||
|
ret = ifd.load_double(data, False)
|
||||||
# Act
|
|
||||||
ret = ifd.load_double(data)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194))
|
self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194))
|
||||||
|
|
||||||
def test_seek(self):
|
def test_seek(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
|
|
||||||
# Act
|
|
||||||
im.seek(-1)
|
im.seek(-1)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(im.tell(), 0)
|
self.assertEqual(im.tell(), 0)
|
||||||
|
|
||||||
def test_seek_eof(self):
|
def test_seek_eof(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
self.assertEqual(im.tell(), 0)
|
self.assertEqual(im.tell(), 0)
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
self.assertRaises(EOFError, lambda: im.seek(1))
|
self.assertRaises(EOFError, lambda: im.seek(1))
|
||||||
|
|
||||||
def test__cvt_res_int(self):
|
def test__limit_rational_int(self):
|
||||||
# Arrange
|
from PIL.TiffImagePlugin import _limit_rational
|
||||||
from PIL.TiffImagePlugin import _cvt_res
|
|
||||||
value = 34
|
value = 34
|
||||||
|
ret = _limit_rational(value, 65536)
|
||||||
# Act
|
|
||||||
ret = _cvt_res(value)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (34, 1))
|
self.assertEqual(ret, (34, 1))
|
||||||
|
|
||||||
def test__cvt_res_float(self):
|
def test__limit_rational_float(self):
|
||||||
# Arrange
|
from PIL.TiffImagePlugin import _limit_rational
|
||||||
from PIL.TiffImagePlugin import _cvt_res
|
|
||||||
value = 22.3
|
value = 22.3
|
||||||
|
ret = _limit_rational(value, 65536)
|
||||||
# Act
|
self.assertEqual(ret, (223, 10))
|
||||||
ret = _cvt_res(value)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (1461452, 65536))
|
|
||||||
|
|
||||||
def test__cvt_res_sequence(self):
|
|
||||||
# Arrange
|
|
||||||
from PIL.TiffImagePlugin import _cvt_res
|
|
||||||
value = [0, 1]
|
|
||||||
|
|
||||||
# Act
|
|
||||||
ret = _cvt_res(value)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, [0, 1])
|
|
||||||
|
|
||||||
def test_4bit(self):
|
def test_4bit(self):
|
||||||
# Arrange
|
|
||||||
test_file = "Tests/images/hopper_gray_4bpp.tif"
|
test_file = "Tests/images/hopper_gray_4bpp.tif"
|
||||||
original = hopper("L")
|
original = hopper("L")
|
||||||
|
|
||||||
# Act
|
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.mode, "L")
|
self.assertEqual(im.mode, "L")
|
||||||
self.assert_image_similar(im, original, 7.3)
|
self.assert_image_similar(im, original, 7.3)
|
||||||
|
@ -361,52 +318,50 @@ class TestFileTiff(PillowTestCase):
|
||||||
# Test TIFF with tag 297 (Page Number) having value of 0 0.
|
# Test TIFF with tag 297 (Page Number) having value of 0 0.
|
||||||
# The first number is the current page number.
|
# The first number is the current page number.
|
||||||
# The second is the total number of pages, zero means not available.
|
# The second is the total number of pages, zero means not available.
|
||||||
|
|
||||||
# Arrange
|
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = self.tempfile("temp.tif")
|
||||||
|
|
||||||
# Created by printing a page in Chrome to PDF, then:
|
# Created by printing a page in Chrome to PDF, then:
|
||||||
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
|
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
|
||||||
# -dNOPAUSE /tmp/test.pdf -c quit
|
# -dNOPAUSE /tmp/test.pdf -c quit
|
||||||
infile = "Tests/images/total-pages-zero.tif"
|
infile = "Tests/images/total-pages-zero.tif"
|
||||||
im = Image.open(infile)
|
im = Image.open(infile)
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
# Should not divide by zero
|
# Should not divide by zero
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
def test_with_underscores(self):
|
def test_with_underscores(self):
|
||||||
# Arrange: use underscores
|
|
||||||
kwargs = {'resolution_unit': 'inch',
|
kwargs = {'resolution_unit': 'inch',
|
||||||
'x_resolution': 72,
|
'x_resolution': 72,
|
||||||
'y_resolution': 36}
|
'y_resolution': 36}
|
||||||
filename = self.tempfile("temp.tif")
|
filename = self.tempfile("temp.tif")
|
||||||
|
|
||||||
# Act
|
|
||||||
hopper("RGB").save(filename, **kwargs)
|
hopper("RGB").save(filename, **kwargs)
|
||||||
|
|
||||||
# Assert
|
|
||||||
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
self.assertEqual(im.tag.tags[X_RESOLUTION][0][0], 72)
|
|
||||||
self.assertEqual(im.tag.tags[Y_RESOLUTION][0][0], 36)
|
# legacy interface
|
||||||
|
self.assertEqual(im.tag[X_RESOLUTION][0][0], 72)
|
||||||
|
self.assertEqual(im.tag[Y_RESOLUTION][0][0], 36)
|
||||||
|
|
||||||
|
# v2 interface
|
||||||
|
self.assertEqual(im.tag_v2[X_RESOLUTION], 72)
|
||||||
|
self.assertEqual(im.tag_v2[Y_RESOLUTION], 36)
|
||||||
|
|
||||||
def test_deprecation_warning_with_spaces(self):
|
def test_deprecation_warning_with_spaces(self):
|
||||||
# Arrange: use spaces
|
|
||||||
kwargs = {'resolution unit': 'inch',
|
kwargs = {'resolution unit': 'inch',
|
||||||
'x resolution': 36,
|
'x resolution': 36,
|
||||||
'y resolution': 72}
|
'y resolution': 72}
|
||||||
filename = self.tempfile("temp.tif")
|
filename = self.tempfile("temp.tif")
|
||||||
|
|
||||||
# Act
|
|
||||||
self.assert_warning(DeprecationWarning,
|
self.assert_warning(DeprecationWarning,
|
||||||
lambda: hopper("RGB").save(filename, **kwargs))
|
lambda: hopper("RGB").save(filename, **kwargs))
|
||||||
|
|
||||||
# Assert
|
|
||||||
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
||||||
|
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
self.assertEqual(im.tag.tags[X_RESOLUTION][0][0], 36)
|
|
||||||
self.assertEqual(im.tag.tags[Y_RESOLUTION][0][0], 72)
|
# legacy interface
|
||||||
|
self.assertEqual(im.tag[X_RESOLUTION][0][0], 36)
|
||||||
|
self.assertEqual(im.tag[Y_RESOLUTION][0][0], 72)
|
||||||
|
|
||||||
|
# v2 interface
|
||||||
|
self.assertEqual(im.tag_v2[X_RESOLUTION], 36)
|
||||||
|
self.assertEqual(im.tag_v2[Y_RESOLUTION], 72)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
|
from __future__ import division
|
||||||
|
|
||||||
|
import io
|
||||||
|
import struct
|
||||||
|
|
||||||
from helper import unittest, PillowTestCase, hopper
|
from helper import unittest, PillowTestCase, hopper
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin, TiffTags
|
from PIL import Image, TiffImagePlugin, TiffTags
|
||||||
|
|
||||||
tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys()))
|
tag_ids = dict((info.name, info.value) for info in TiffTags.TAGS_V2.values())
|
||||||
|
|
||||||
|
|
||||||
class TestFileTiffMetadata(PillowTestCase):
|
class TestFileTiffMetadata(PillowTestCase):
|
||||||
|
@ -15,62 +20,95 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
|
|
||||||
img = hopper()
|
img = hopper()
|
||||||
|
|
||||||
|
# Behaviour change: re #1416
|
||||||
|
# Pre ifd rewrite, ImageJMetaData was being written as a string(2),
|
||||||
|
# Post ifd rewrite, it's defined as arbitrary bytes(7). It should
|
||||||
|
# roundtrip with the actual bytes, rather than stripped text
|
||||||
|
# of the premerge tests.
|
||||||
|
#
|
||||||
|
# For text items, we still have to decode('ascii','replace') because
|
||||||
|
# the tiff file format can't take 8 bit bytes in that field.
|
||||||
|
|
||||||
basetextdata = "This is some arbitrary metadata for a text field"
|
basetextdata = "This is some arbitrary metadata for a text field"
|
||||||
textdata = basetextdata + " \xff"
|
bindata = basetextdata.encode('ascii') + b" \xff"
|
||||||
|
textdata = basetextdata + " " + chr(255)
|
||||||
|
reloaded_textdata = basetextdata + " ?"
|
||||||
floatdata = 12.345
|
floatdata = 12.345
|
||||||
doubledata = 67.89
|
doubledata = 67.89
|
||||||
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory()
|
info = TiffImagePlugin.ImageFileDirectory()
|
||||||
|
|
||||||
info[tag_ids['ImageJMetaDataByteCounts']] = len(textdata)
|
ImageJMetaData = tag_ids['ImageJMetaData']
|
||||||
info[tag_ids['ImageJMetaData']] = textdata
|
ImageJMetaDataByteCounts = tag_ids['ImageJMetaDataByteCounts']
|
||||||
|
ImageDescription = tag_ids['ImageDescription']
|
||||||
|
|
||||||
|
info[ImageJMetaDataByteCounts] = len(bindata)
|
||||||
|
info[ImageJMetaData] = bindata
|
||||||
info[tag_ids['RollAngle']] = floatdata
|
info[tag_ids['RollAngle']] = floatdata
|
||||||
info.tagtype[tag_ids['RollAngle']] = 11
|
info.tagtype[tag_ids['RollAngle']] = 11
|
||||||
|
|
||||||
info[tag_ids['YawAngle']] = doubledata
|
info[tag_ids['YawAngle']] = doubledata
|
||||||
info.tagtype[tag_ids['YawAngle']] = 12
|
info.tagtype[tag_ids['YawAngle']] = 12
|
||||||
|
|
||||||
|
info[ImageDescription] = textdata
|
||||||
|
|
||||||
f = self.tempfile("temp.tif")
|
f = self.tempfile("temp.tif")
|
||||||
|
|
||||||
img.save(f, tiffinfo=info)
|
img.save(f, tiffinfo=info)
|
||||||
|
|
||||||
loaded = Image.open(f)
|
loaded = Image.open(f)
|
||||||
|
|
||||||
self.assertEqual(loaded.tag[50838], (len(basetextdata + " ?"),))
|
self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),))
|
||||||
self.assertEqual(loaded.tag[50839], basetextdata + " ?")
|
self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], len(bindata))
|
||||||
self.assertAlmostEqual(loaded.tag[tag_ids['RollAngle']][0], floatdata,
|
|
||||||
places=5)
|
self.assertEqual(loaded.tag[ImageJMetaData], bindata)
|
||||||
self.assertAlmostEqual(loaded.tag[tag_ids['YawAngle']][0], doubledata)
|
self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata)
|
||||||
|
|
||||||
|
self.assertEqual(loaded.tag[ImageDescription], (reloaded_textdata,))
|
||||||
|
self.assertEqual(loaded.tag_v2[ImageDescription], reloaded_textdata)
|
||||||
|
|
||||||
|
loaded_float = loaded.tag[tag_ids['RollAngle']][0]
|
||||||
|
self.assertAlmostEqual(loaded_float, floatdata, places=5)
|
||||||
|
loaded_double = loaded.tag[tag_ids['YawAngle']][0]
|
||||||
|
self.assertAlmostEqual(loaded_double, doubledata)
|
||||||
|
|
||||||
|
|
||||||
def test_read_metadata(self):
|
def test_read_metadata(self):
|
||||||
img = Image.open('Tests/images/hopper_g4.tif')
|
img = Image.open('Tests/images/hopper_g4.tif')
|
||||||
|
|
||||||
known = {'YResolution': ((4294967295, 113653537),),
|
self.assertEqual({'YResolution': 4294967295 / 113653537,
|
||||||
'PlanarConfiguration': (1,),
|
'PlanarConfiguration': 1,
|
||||||
'BitsPerSample': (1,),
|
'BitsPerSample': (1,),
|
||||||
'ImageLength': (128,),
|
'ImageLength': 128,
|
||||||
'Compression': (4,),
|
'Compression': 4,
|
||||||
'FillOrder': (1,),
|
'FillOrder': 1,
|
||||||
'RowsPerStrip': (128,),
|
'RowsPerStrip': 128,
|
||||||
'ResolutionUnit': (3,),
|
'ResolutionUnit': 3,
|
||||||
'PhotometricInterpretation': (0,),
|
'PhotometricInterpretation': 0,
|
||||||
'PageNumber': (0, 1),
|
'PageNumber': (0, 1),
|
||||||
'XResolution': ((4294967295, 113653537),),
|
'XResolution': 4294967295 / 113653537,
|
||||||
'ImageWidth': (128,),
|
'ImageWidth': 128,
|
||||||
'Orientation': (1,),
|
'Orientation': 1,
|
||||||
'StripByteCounts': (1968,),
|
'StripByteCounts': (1968,),
|
||||||
'SamplesPerPixel': (1,),
|
'SamplesPerPixel': 1,
|
||||||
'StripOffsets': (8,),
|
'StripOffsets': (8,)
|
||||||
}
|
}, img.tag_v2.named())
|
||||||
|
|
||||||
# self.assertEqual is equivalent,
|
self.assertEqual({'YResolution': ((4294967295, 113653537),),
|
||||||
# but less helpful in telling what's wrong.
|
'PlanarConfiguration': (1,),
|
||||||
named = img.tag.named()
|
'BitsPerSample': (1,),
|
||||||
for tag, value in named.items():
|
'ImageLength': (128,),
|
||||||
self.assertEqual(known[tag], value)
|
'Compression': (4,),
|
||||||
|
'FillOrder': (1,),
|
||||||
for tag, value in known.items():
|
'RowsPerStrip': (128,),
|
||||||
self.assertEqual(value, named[tag])
|
'ResolutionUnit': (3,),
|
||||||
|
'PhotometricInterpretation': (0,),
|
||||||
|
'PageNumber': (0, 1),
|
||||||
|
'XResolution': ((4294967295, 113653537),),
|
||||||
|
'ImageWidth': (128,),
|
||||||
|
'Orientation': (1,),
|
||||||
|
'StripByteCounts': (1968,),
|
||||||
|
'SamplesPerPixel': (1,),
|
||||||
|
'StripOffsets': (8,)
|
||||||
|
}, img.tag.named())
|
||||||
|
|
||||||
def test_write_metadata(self):
|
def test_write_metadata(self):
|
||||||
""" Test metadata writing through the python code """
|
""" Test metadata writing through the python code """
|
||||||
|
@ -81,8 +119,8 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
|
|
||||||
loaded = Image.open(f)
|
loaded = Image.open(f)
|
||||||
|
|
||||||
original = img.tag.named()
|
original = img.tag_v2.named()
|
||||||
reloaded = loaded.tag.named()
|
reloaded = loaded.tag_v2.named()
|
||||||
|
|
||||||
ignored = [
|
ignored = [
|
||||||
'StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets']
|
'StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets']
|
||||||
|
@ -101,6 +139,15 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
self.assertEqual(tag_ids['MakerNoteSafety'], 50741)
|
self.assertEqual(tag_ids['MakerNoteSafety'], 50741)
|
||||||
self.assertEqual(tag_ids['BestQualityScale'], 50780)
|
self.assertEqual(tag_ids['BestQualityScale'], 50780)
|
||||||
|
|
||||||
|
def test_empty_metadata(self):
|
||||||
|
f = io.BytesIO(b'II*\x00\x08\x00\x00\x00')
|
||||||
|
head = f.read(8)
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
|
try:
|
||||||
|
self.assert_warning(UserWarning, lambda: info.load(f))
|
||||||
|
except struct.error:
|
||||||
|
self.fail("Should not be struct errors there.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -144,14 +144,6 @@ can be found here.
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`TiffTags` Module
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
.. automodule:: PIL.TiffTags
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
:mod:`WalImageFile` Module
|
:mod:`WalImageFile` Module
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
|
|
@ -461,17 +461,37 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
|
||||||
**compression**
|
**compression**
|
||||||
Compression mode.
|
Compression mode.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0.0
|
||||||
|
|
||||||
**dpi**
|
**dpi**
|
||||||
Image resolution as an (xdpi, ydpi) tuple, where applicable. You can use
|
Image resolution as an ``(xdpi, ydpi)`` tuple, where applicable. You can use
|
||||||
the :py:attr:`~PIL.Image.Image.tag` attribute to get more detailed
|
the :py:attr:`~PIL.Image.Image.tag` attribute to get more detailed
|
||||||
information about the image resolution.
|
information about the image resolution.
|
||||||
|
|
||||||
.. versionadded:: 1.1.5
|
.. versionadded:: 1.1.5
|
||||||
|
|
||||||
In addition, the :py:attr:`~PIL.Image.Image.tag` attribute contains a
|
**resolution**
|
||||||
dictionary of decoded TIFF fields. Values are stored as either strings or
|
Image resolution as an ``(xres, yres)`` tuple, where applicable. This is a
|
||||||
tuples. Note that only short, long and ASCII tags are correctly unpacked by
|
measurement in whichever unit is specified by the file.
|
||||||
this release.
|
|
||||||
|
.. versionadded:: 1.1.5
|
||||||
|
|
||||||
|
|
||||||
|
The :py:attr:`~PIL.Image.Image.tag_v2` attribute contains a dictionary of
|
||||||
|
TIFF metadata. The keys are numerical indexes from `~PIL.TiffTags.TAGS_V2`.
|
||||||
|
Values are strings or numbers for single items, multiple values are returned
|
||||||
|
in a tuple of values. Rational numbers are returned as a single value.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0.0
|
||||||
|
|
||||||
|
For compatibility with legacy code, the
|
||||||
|
:py:attr:`~PIL.Image.Image.tag` attribute contains a dictionary of
|
||||||
|
decoded TIFF fields as returned prior to version 3.0.0. Values are
|
||||||
|
returned as either strings or tuples of numeric values. Rational
|
||||||
|
numbers are returned as a tuple of ``(numerator, denominator)``.
|
||||||
|
|
||||||
|
.. deprecated:: 3.0.0
|
||||||
|
|
||||||
|
|
||||||
Saving Tiff Images
|
Saving Tiff Images
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -479,17 +499,23 @@ Saving Tiff Images
|
||||||
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
|
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
|
||||||
|
|
||||||
**tiffinfo**
|
**tiffinfo**
|
||||||
A :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory` object or dict
|
A :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` object or dict
|
||||||
object containing tiff tags and values. The TIFF field type is
|
object containing tiff tags and values. The TIFF field type is
|
||||||
autodetected for Numeric and string values, any other types
|
autodetected for Numeric and string values, any other types
|
||||||
require using an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory`
|
require using an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
|
||||||
object and setting the type in
|
object and setting the type in
|
||||||
:py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory.tagtype` with
|
:py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` with
|
||||||
the appropriate numerical value from
|
the appropriate numerical value from
|
||||||
``TiffTags.TYPES``.
|
``TiffTags.TYPES``.
|
||||||
|
|
||||||
.. versionadded:: 2.3.0
|
.. versionadded:: 2.3.0
|
||||||
|
|
||||||
|
For compatibility with legacy code, a
|
||||||
|
`~PIL.TiffImagePlugin.ImageFileDirectory_v1` object may be passed
|
||||||
|
in this field. This will be deprecated in a future version.
|
||||||
|
|
||||||
|
..versionadded:: 3.0.0
|
||||||
|
|
||||||
**compression**
|
**compression**
|
||||||
A string containing the desired compression method for the
|
A string containing the desired compression method for the
|
||||||
file. (valid only with libtiff installed) Valid compression
|
file. (valid only with libtiff installed) Valid compression
|
||||||
|
|
|
@ -26,6 +26,7 @@ Reference
|
||||||
ImageTk
|
ImageTk
|
||||||
ImageWin
|
ImageWin
|
||||||
ExifTags
|
ExifTags
|
||||||
|
TiffTags
|
||||||
OleFileIO
|
OleFileIO
|
||||||
PSDraw
|
PSDraw
|
||||||
PixelAccess
|
PixelAccess
|
||||||
|
|
89
encode.c
89
encode.c
|
@ -721,7 +721,6 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TRACE(("new tiff encoder %s fp: %d, filename: %s \n", compname, fp, filename));
|
TRACE(("new tiff encoder %s fp: %d, filename: %s \n", compname, fp, filename));
|
||||||
|
|
||||||
encoder = PyImaging_EncoderNew(sizeof(TIFFSTATE));
|
encoder = PyImaging_EncoderNew(sizeof(TIFFSTATE));
|
||||||
|
@ -737,11 +736,9 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// While fails on 64 bit machines, complains that pos is an int instead of a Py_ssize_t
|
for (pos = 0; pos < d_size; pos++) {
|
||||||
// while (PyDict_Next(dir, &pos, &key, &value)) {
|
key = PyList_GetItem(keys, pos);
|
||||||
for (pos=0;pos<d_size;pos++){
|
value = PyList_GetItem(values, pos);
|
||||||
key = PyList_GetItem(keys,pos);
|
|
||||||
value = PyList_GetItem(values,pos);
|
|
||||||
status = 0;
|
status = 0;
|
||||||
TRACE(("Attempting to set key: %d\n", (int)PyInt_AsLong(key)));
|
TRACE(("Attempting to set key: %d\n", (int)PyInt_AsLong(key)));
|
||||||
if (PyInt_Check(value)) {
|
if (PyInt_Check(value)) {
|
||||||
|
@ -749,49 +746,53 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
|
||||||
status = ImagingLibTiffSetField(&encoder->state,
|
status = ImagingLibTiffSetField(&encoder->state,
|
||||||
(ttag_t) PyInt_AsLong(key),
|
(ttag_t) PyInt_AsLong(key),
|
||||||
PyInt_AsLong(value));
|
PyInt_AsLong(value));
|
||||||
} else if(PyBytes_Check(value)) {
|
|
||||||
TRACE(("Setting from Bytes: %d, %s \n", (int)PyInt_AsLong(key),PyBytes_AsString(value)));
|
|
||||||
status = ImagingLibTiffSetField(&encoder->state,
|
|
||||||
(ttag_t) PyInt_AsLong(key),
|
|
||||||
PyBytes_AsString(value));
|
|
||||||
} else if(PyList_Check(value)) {
|
|
||||||
int len,i;
|
|
||||||
float *floatav;
|
|
||||||
int *intav;
|
|
||||||
TRACE(("Setting from List: %d \n", (int)PyInt_AsLong(key)));
|
|
||||||
len = (int)PyList_Size(value);
|
|
||||||
if (len) {
|
|
||||||
if (PyInt_Check(PyList_GetItem(value,0))) {
|
|
||||||
TRACE((" %d elements, setting as ints \n", len));
|
|
||||||
intav = malloc(sizeof(int)*len);
|
|
||||||
if (intav) {
|
|
||||||
for (i=0;i<len;i++) {
|
|
||||||
intav[i] = (int)PyInt_AsLong(PyList_GetItem(value,i));
|
|
||||||
}
|
|
||||||
status = ImagingLibTiffSetField(&encoder->state,
|
|
||||||
(ttag_t) PyInt_AsLong(key),
|
|
||||||
intav);
|
|
||||||
free(intav);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TRACE((" %d elements, setting as floats \n", len));
|
|
||||||
floatav = malloc(sizeof(float)*len);
|
|
||||||
if (floatav) {
|
|
||||||
for (i=0;i<len;i++) {
|
|
||||||
floatav[i] = (float)PyFloat_AsDouble(PyList_GetItem(value,i));
|
|
||||||
}
|
|
||||||
status = ImagingLibTiffSetField(&encoder->state,
|
|
||||||
(ttag_t) PyInt_AsLong(key),
|
|
||||||
floatav);
|
|
||||||
free(floatav);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (PyFloat_Check(value)) {
|
} else if (PyFloat_Check(value)) {
|
||||||
TRACE(("Setting from Float: %d, %f \n", (int)PyInt_AsLong(key),PyFloat_AsDouble(value)));
|
TRACE(("Setting from Float: %d, %f \n", (int)PyInt_AsLong(key),PyFloat_AsDouble(value)));
|
||||||
status = ImagingLibTiffSetField(&encoder->state,
|
status = ImagingLibTiffSetField(&encoder->state,
|
||||||
(ttag_t) PyInt_AsLong(key),
|
(ttag_t) PyInt_AsLong(key),
|
||||||
(float)PyFloat_AsDouble(value));
|
(float)PyFloat_AsDouble(value));
|
||||||
|
} else if (PyBytes_Check(value)) {
|
||||||
|
TRACE(("Setting from Bytes: %d, %s \n", (int)PyInt_AsLong(key),PyBytes_AsString(value)));
|
||||||
|
status = ImagingLibTiffSetField(&encoder->state,
|
||||||
|
(ttag_t) PyInt_AsLong(key),
|
||||||
|
PyBytes_AsString(value));
|
||||||
|
} else if (PyTuple_Check(value)) {
|
||||||
|
int len,i;
|
||||||
|
float *floatav;
|
||||||
|
int *intav;
|
||||||
|
TRACE(("Setting from Tuple: %d \n", (int)PyInt_AsLong(key)));
|
||||||
|
len = (int)PyTuple_Size(value);
|
||||||
|
if (len) {
|
||||||
|
if (PyInt_Check(PyTuple_GetItem(value,0))) {
|
||||||
|
TRACE((" %d elements, setting as ints \n", len));
|
||||||
|
intav = malloc(sizeof(int)*len);
|
||||||
|
if (intav) {
|
||||||
|
for (i=0;i<len;i++) {
|
||||||
|
intav[i] = (int)PyInt_AsLong(PyTuple_GetItem(value,i));
|
||||||
|
}
|
||||||
|
status = ImagingLibTiffSetField(&encoder->state,
|
||||||
|
(ttag_t) PyInt_AsLong(key),
|
||||||
|
len, intav);
|
||||||
|
free(intav);
|
||||||
|
}
|
||||||
|
} else if (PyFloat_Check(PyTuple_GetItem(value,0))) {
|
||||||
|
TRACE((" %d elements, setting as floats \n", len));
|
||||||
|
floatav = malloc(sizeof(float)*len);
|
||||||
|
if (floatav) {
|
||||||
|
for (i=0;i<len;i++) {
|
||||||
|
floatav[i] = (float)PyFloat_AsDouble(PyTuple_GetItem(value,i));
|
||||||
|
}
|
||||||
|
status = ImagingLibTiffSetField(&encoder->state,
|
||||||
|
(ttag_t) PyInt_AsLong(key),
|
||||||
|
len, floatav);
|
||||||
|
free(floatav);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TRACE(("Unhandled type in tuple for key %d : %s \n",
|
||||||
|
(int)PyInt_AsLong(key),
|
||||||
|
PyBytes_AsString(PyObject_Str(value))));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
TRACE(("Unhandled type for key %d : %s \n",
|
TRACE(("Unhandled type for key %d : %s \n",
|
||||||
(int)PyInt_AsLong(key),
|
(int)PyInt_AsLong(key),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user