1#
2# The Python Imaging Library.
3# $Id$
4#
5# TIFF file handling
6#
7# TIFF is a flexible, if somewhat aged, image file format originally
8# defined by Aldus. Although TIFF supports a wide variety of pixel
9# layouts and compression methods, the name doesn't really stand for
10# "thousands of incompatible file formats," it just feels that way.
11#
12# To read TIFF data from a stream, the stream must be seekable. For
13# progressive decoding, make sure to use TIFF files where the tag
14# directory is placed first in the file.
15#
16# History:
17# 1995-09-01 fl Created
18# 1996-05-04 fl Handle JPEGTABLES tag
19# 1996-05-18 fl Fixed COLORMAP support
20# 1997-01-05 fl Fixed PREDICTOR support
21# 1997-08-27 fl Added support for rational tags (from Perry Stoll)
22# 1998-01-10 fl Fixed seek/tell (from Jan Blom)
23# 1998-07-15 fl Use private names for internal variables
24# 1999-06-13 fl Rewritten for PIL 1.0 (1.0)
25# 2000-10-11 fl Additional fixes for Python 2.0 (1.1)
26# 2001-04-17 fl Fixed rewind support (seek to frame 0) (1.2)
27# 2001-05-12 fl Added write support for more tags (from Greg Couch) (1.3)
28# 2001-12-18 fl Added workaround for broken Matrox library
29# 2002-01-18 fl Don't mess up if photometric tag is missing (D. Alan Stewart)
30# 2003-05-19 fl Check FILLORDER tag
31# 2003-09-26 fl Added RGBa support
32# 2004-02-24 fl Added DPI support; fixed rational write support
33# 2005-02-07 fl Added workaround for broken Corel Draw 10 files
34# 2006-01-09 fl Added support for float/double tags (from Russell Nelson)
35#
36# Copyright (c) 1997-2006 by Secret Labs AB. All rights reserved.
37# Copyright (c) 1995-1997 by Fredrik Lundh
38#
39# See the README file for information on usage and redistribution.
40#
41from __future__ import annotations
42
43import io
44import itertools
45import logging
46import math
47import os
48import struct
49import warnings
50from collections.abc import MutableMapping
51from fractions import Fraction
52from numbers import Number, Rational
53from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn
54
55from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
56from ._binary import i16be as i16
57from ._binary import i32be as i32
58from ._binary import o8
59from ._deprecate import deprecate
60from .TiffTags import TYPES
61
62logger = logging.getLogger(__name__)
63
64# Set these to true to force use of libtiff for reading or writing.
65READ_LIBTIFF = False
66WRITE_LIBTIFF = False
67IFD_LEGACY_API = True
68STRIP_SIZE = 65536
69
70II = b"II" # little-endian (Intel style)
71MM = b"MM" # big-endian (Motorola style)
72
73#
74# --------------------------------------------------------------------
75# Read TIFF files
76
77# a few tag names, just to make the code below a bit more readable
78OSUBFILETYPE = 255
79IMAGEWIDTH = 256
80IMAGELENGTH = 257
81BITSPERSAMPLE = 258
82COMPRESSION = 259
83PHOTOMETRIC_INTERPRETATION = 262
84FILLORDER = 266
85IMAGEDESCRIPTION = 270
86STRIPOFFSETS = 273
87SAMPLESPERPIXEL = 277
88ROWSPERSTRIP = 278
89STRIPBYTECOUNTS = 279
90X_RESOLUTION = 282
91Y_RESOLUTION = 283
92PLANAR_CONFIGURATION = 284
93RESOLUTION_UNIT = 296
94TRANSFERFUNCTION = 301
95SOFTWARE = 305
96DATE_TIME = 306
97ARTIST = 315
98PREDICTOR = 317
99COLORMAP = 320
100TILEWIDTH = 322
101TILELENGTH = 323
102TILEOFFSETS = 324
103TILEBYTECOUNTS = 325
104SUBIFD = 330
105EXTRASAMPLES = 338
106SAMPLEFORMAT = 339
107JPEGTABLES = 347
108YCBCRSUBSAMPLING = 530
109REFERENCEBLACKWHITE = 532
110COPYRIGHT = 33432
111IPTC_NAA_CHUNK = 33723 # newsphoto properties
112PHOTOSHOP_CHUNK = 34377 # photoshop properties
113ICCPROFILE = 34675
114EXIFIFD = 34665
115XMP = 700
116JPEGQUALITY = 65537 # pseudo-tag by libtiff
117
118# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java
119IMAGEJ_META_DATA_BYTE_COUNTS = 50838
120IMAGEJ_META_DATA = 50839
121
122COMPRESSION_INFO = {
123 # Compression => pil compression name
124 1: "raw",
125 2: "tiff_ccitt",
126 3: "group3",
127 4: "group4",
128 5: "tiff_lzw",
129 6: "tiff_jpeg", # obsolete
130 7: "jpeg",
131 8: "tiff_adobe_deflate",
132 32771: "tiff_raw_16", # 16-bit padding
133 32773: "packbits",
134 32809: "tiff_thunderscan",
135 32946: "tiff_deflate",
136 34676: "tiff_sgilog",
137 34677: "tiff_sgilog24",
138 34925: "lzma",
139 50000: "zstd",
140 50001: "webp",
141}
142
143COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()}
144
145OPEN_INFO = {
146 # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
147 # ExtraSamples) => mode, rawmode
148 (II, 0, (1,), 1, (1,), ()): ("1", "1;I"),
149 (MM, 0, (1,), 1, (1,), ()): ("1", "1;I"),
150 (II, 0, (1,), 2, (1,), ()): ("1", "1;IR"),
151 (MM, 0, (1,), 2, (1,), ()): ("1", "1;IR"),
152 (II, 1, (1,), 1, (1,), ()): ("1", "1"),
153 (MM, 1, (1,), 1, (1,), ()): ("1", "1"),
154 (II, 1, (1,), 2, (1,), ()): ("1", "1;R"),
155 (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"),
156 (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
157 (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
158 (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
159 (MM, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
160 (II, 1, (1,), 1, (2,), ()): ("L", "L;2"),
161 (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"),
162 (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
163 (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
164 (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
165 (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
166 (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
167 (MM, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
168 (II, 1, (1,), 1, (4,), ()): ("L", "L;4"),
169 (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"),
170 (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
171 (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
172 (II, 0, (1,), 1, (8,), ()): ("L", "L;I"),
173 (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"),
174 (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
175 (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
176 (II, 1, (1,), 1, (8,), ()): ("L", "L"),
177 (MM, 1, (1,), 1, (8,), ()): ("L", "L"),
178 (II, 1, (2,), 1, (8,), ()): ("L", "L"),
179 (MM, 1, (2,), 1, (8,), ()): ("L", "L"),
180 (II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
181 (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
182 (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),
183 (II, 0, (1,), 1, (16,), ()): ("I;16", "I;16"),
184 (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"),
185 (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"),
186 (II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"),
187 (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"),
188 (MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"),
189 (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"),
190 (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"),
191 (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"),
192 (II, 1, (2,), 1, (32,), ()): ("I", "I;32S"),
193 (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"),
194 (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"),
195 (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"),
196 (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
197 (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
198 (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
199 (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
200 (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
201 (MM, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
202 (II, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
203 (MM, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples
204 (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"),
205 (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"),
206 (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"),
207 (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"),
208 (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"),
209 (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"),
210 (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
211 (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
212 (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
213 (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
214 (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
215 (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
216 (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
217 (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
218 (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
219 (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
220 (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
221 (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
222 (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
223 (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10
224 (II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"),
225 (MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"),
226 (II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"),
227 (MM, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16B"),
228 (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16L"),
229 (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16B"),
230 (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16L"),
231 (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"),
232 (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"),
233 (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"),
234 (II, 3, (1,), 1, (1,), ()): ("P", "P;1"),
235 (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"),
236 (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
237 (MM, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
238 (II, 3, (1,), 1, (2,), ()): ("P", "P;2"),
239 (MM, 3, (1,), 1, (2,), ()): ("P", "P;2"),
240 (II, 3, (1,), 2, (2,), ()): ("P", "P;2R"),
241 (MM, 3, (1,), 2, (2,), ()): ("P", "P;2R"),
242 (II, 3, (1,), 1, (4,), ()): ("P", "P;4"),
243 (MM, 3, (1,), 1, (4,), ()): ("P", "P;4"),
244 (II, 3, (1,), 2, (4,), ()): ("P", "P;4R"),
245 (MM, 3, (1,), 2, (4,), ()): ("P", "P;4R"),
246 (II, 3, (1,), 1, (8,), ()): ("P", "P"),
247 (MM, 3, (1,), 1, (8,), ()): ("P", "P"),
248 (II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
249 (II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
250 (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
251 (II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
252 (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"),
253 (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
254 (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
255 (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
256 (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
257 (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
258 (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
259 (II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"),
260 (II, 6, (1,), 1, (8,), ()): ("L", "L"),
261 (MM, 6, (1,), 1, (8,), ()): ("L", "L"),
262 # JPEG compressed images handled by LibTiff and auto-converted to RGBX
263 # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
264 (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
265 (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
266 (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
267 (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
268}
269
270MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO)
271
272PREFIXES = [
273 b"MM\x00\x2A", # Valid TIFF header with big-endian byte order
274 b"II\x2A\x00", # Valid TIFF header with little-endian byte order
275 b"MM\x2A\x00", # Invalid TIFF header, assume big-endian
276 b"II\x00\x2A", # Invalid TIFF header, assume little-endian
277 b"MM\x00\x2B", # BigTIFF with big-endian byte order
278 b"II\x2B\x00", # BigTIFF with little-endian byte order
279]
280
281if not getattr(Image.core, "libtiff_support_custom_tags", True):
282 deprecate("Support for LibTIFF earlier than version 4", 12)
283
284
285def _accept(prefix: bytes) -> bool:
286 return prefix[:4] in PREFIXES
287
288
289def _limit_rational(val, max_val):
290 inv = abs(val) > 1
291 n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
292 return n_d[::-1] if inv else n_d
293
294
295def _limit_signed_rational(val, max_val, min_val):
296 frac = Fraction(val)
297 n_d = frac.numerator, frac.denominator
298
299 if min(n_d) < min_val:
300 n_d = _limit_rational(val, abs(min_val))
301
302 if max(n_d) > max_val:
303 val = Fraction(*n_d)
304 n_d = _limit_rational(val, max_val)
305
306 return n_d
307
308
309##
310# Wrapper for TIFF IFDs.
311
312_load_dispatch = {}
313_write_dispatch = {}
314
315
316def _delegate(op):
317 def delegate(self, *args):
318 return getattr(self._val, op)(*args)
319
320 return delegate
321
322
323class IFDRational(Rational):
324 """Implements a rational class where 0/0 is a legal value to match
325 the in the wild use of exif rationals.
326
327 e.g., DigitalZoomRatio - 0.00/0.00 indicates that no digital zoom was used
328 """
329
330 """ If the denominator is 0, store this as a float('nan'), otherwise store
331 as a fractions.Fraction(). Delegate as appropriate
332
333 """
334
335 __slots__ = ("_numerator", "_denominator", "_val")
336
337 def __init__(self, value, denominator=1):
338 """
339 :param value: either an integer numerator, a
340 float/rational/other number, or an IFDRational
341 :param denominator: Optional integer denominator
342 """
343 if isinstance(value, IFDRational):
344 self._numerator = value.numerator
345 self._denominator = value.denominator
346 self._val = value._val
347 return
348
349 if isinstance(value, Fraction):
350 self._numerator = value.numerator
351 self._denominator = value.denominator
352 else:
353 self._numerator = value
354 self._denominator = denominator
355
356 if denominator == 0:
357 self._val = float("nan")
358 elif denominator == 1:
359 self._val = Fraction(value)
360 else:
361 self._val = Fraction(value, denominator)
362
363 @property
364 def numerator(self):
365 return self._numerator
366
367 @property
368 def denominator(self):
369 return self._denominator
370
371 def limit_rational(self, max_denominator):
372 """
373
374 :param max_denominator: Integer, the maximum denominator value
375 :returns: Tuple of (numerator, denominator)
376 """
377
378 if self.denominator == 0:
379 return self.numerator, self.denominator
380
381 f = self._val.limit_denominator(max_denominator)
382 return f.numerator, f.denominator
383
384 def __repr__(self) -> str:
385 return str(float(self._val))
386
387 def __hash__(self) -> int:
388 return self._val.__hash__()
389
390 def __eq__(self, other: object) -> bool:
391 val = self._val
392 if isinstance(other, IFDRational):
393 other = other._val
394 if isinstance(other, float):
395 val = float(val)
396 return val == other
397
398 def __getstate__(self):
399 return [self._val, self._numerator, self._denominator]
400
401 def __setstate__(self, state):
402 IFDRational.__init__(self, 0)
403 _val, _numerator, _denominator = state
404 self._val = _val
405 self._numerator = _numerator
406 self._denominator = _denominator
407
408 """ a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',
409 'truediv', 'rtruediv', 'floordiv', 'rfloordiv',
410 'mod','rmod', 'pow','rpow', 'pos', 'neg',
411 'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool',
412 'ceil', 'floor', 'round']
413 print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a))
414 """
415
416 __add__ = _delegate("__add__")
417 __radd__ = _delegate("__radd__")
418 __sub__ = _delegate("__sub__")
419 __rsub__ = _delegate("__rsub__")
420 __mul__ = _delegate("__mul__")
421 __rmul__ = _delegate("__rmul__")
422 __truediv__ = _delegate("__truediv__")
423 __rtruediv__ = _delegate("__rtruediv__")
424 __floordiv__ = _delegate("__floordiv__")
425 __rfloordiv__ = _delegate("__rfloordiv__")
426 __mod__ = _delegate("__mod__")
427 __rmod__ = _delegate("__rmod__")
428 __pow__ = _delegate("__pow__")
429 __rpow__ = _delegate("__rpow__")
430 __pos__ = _delegate("__pos__")
431 __neg__ = _delegate("__neg__")
432 __abs__ = _delegate("__abs__")
433 __trunc__ = _delegate("__trunc__")
434 __lt__ = _delegate("__lt__")
435 __gt__ = _delegate("__gt__")
436 __le__ = _delegate("__le__")
437 __ge__ = _delegate("__ge__")
438 __bool__ = _delegate("__bool__")
439 __ceil__ = _delegate("__ceil__")
440 __floor__ = _delegate("__floor__")
441 __round__ = _delegate("__round__")
442 # Python >= 3.11
443 if hasattr(Fraction, "__int__"):
444 __int__ = _delegate("__int__")
445
446
447def _register_loader(idx, size):
448 def decorator(func):
449 from .TiffTags import TYPES
450
451 if func.__name__.startswith("load_"):
452 TYPES[idx] = func.__name__[5:].replace("_", " ")
453 _load_dispatch[idx] = size, func # noqa: F821
454 return func
455
456 return decorator
457
458
459def _register_writer(idx):
460 def decorator(func):
461 _write_dispatch[idx] = func # noqa: F821
462 return func
463
464 return decorator
465
466
467def _register_basic(idx_fmt_name):
468 from .TiffTags import TYPES
469
470 idx, fmt, name = idx_fmt_name
471 TYPES[idx] = name
472 size = struct.calcsize(f"={fmt}")
473 _load_dispatch[idx] = ( # noqa: F821
474 size,
475 lambda self, data, legacy_api=True: (
476 self._unpack(f"{len(data) // size}{fmt}", data)
477 ),
478 )
479 _write_dispatch[idx] = lambda self, *values: ( # noqa: F821
480 b"".join(self._pack(fmt, value) for value in values)
481 )
482
483
484if TYPE_CHECKING:
485 _IFDv2Base = MutableMapping[int, Any]
486else:
487 _IFDv2Base = MutableMapping
488
489
490class ImageFileDirectory_v2(_IFDv2Base):
491 """This class represents a TIFF tag directory. To speed things up, we
492 don't decode tags unless they're asked for.
493
494 Exposes a dictionary interface of the tags in the directory::
495
496 ifd = ImageFileDirectory_v2()
497 ifd[key] = 'Some Data'
498 ifd.tagtype[key] = TiffTags.ASCII
499 print(ifd[key])
500 'Some Data'
501
502 Individual values are returned as the strings or numbers, sequences are
503 returned as tuples of the values.
504
505 The tiff metadata type of each item is stored in a dictionary of
506 tag types in
507 :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types
508 are read from a tiff file, guessed from the type added, or added
509 manually.
510
511 Data Structures:
512
513 * ``self.tagtype = {}``
514
515 * Key: numerical TIFF tag number
516 * Value: integer corresponding to the data type from
517 :py:data:`.TiffTags.TYPES`
518
519 .. versionadded:: 3.0.0
520
521 'Internal' data structures:
522
523 * ``self._tags_v2 = {}``
524
525 * Key: numerical TIFF tag number
526 * Value: decoded data, as tuple for multiple values
527
528 * ``self._tagdata = {}``
529
530 * Key: numerical TIFF tag number
531 * Value: undecoded byte string from file
532
533 * ``self._tags_v1 = {}``
534
535 * Key: numerical TIFF tag number
536 * Value: decoded data in the v1 format
537
538 Tags will be found in the private attributes ``self._tagdata``, and in
539 ``self._tags_v2`` once decoded.
540
541 ``self.legacy_api`` is a value for internal use, and shouldn't be changed
542 from outside code. In cooperation with
543 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`, if ``legacy_api``
544 is true, then decoded tags will be populated into both ``_tags_v1`` and
545 ``_tags_v2``. ``_tags_v2`` will be used if this IFD is used in the TIFF
546 save routine. Tags should be read from ``_tags_v1`` if
547 ``legacy_api == true``.
548
549 """
550
551 _load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {}
552 _write_dispatch: dict[int, Callable[..., Any]] = {}
553
554 def __init__(
555 self,
556 ifh: bytes = b"II\052\0\0\0\0\0",
557 prefix: bytes | None = None,
558 group: int | None = None,
559 ) -> None:
560 """Initialize an ImageFileDirectory.
561
562 To construct an ImageFileDirectory from a real file, pass the 8-byte
563 magic header to the constructor. To only set the endianness, pass it
564 as the 'prefix' keyword argument.
565
566 :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets
567 endianness.
568 :param prefix: Override the endianness of the file.
569 """
570 if not _accept(ifh):
571 msg = f"not a TIFF file (header {repr(ifh)} not valid)"
572 raise SyntaxError(msg)
573 self._prefix = prefix if prefix is not None else ifh[:2]
574 if self._prefix == MM:
575 self._endian = ">"
576 elif self._prefix == II:
577 self._endian = "<"
578 else:
579 msg = "not a TIFF IFD"
580 raise SyntaxError(msg)
581 self._bigtiff = ifh[2] == 43
582 self.group = group
583 self.tagtype: dict[int, int] = {}
584 """ Dictionary of tag types """
585 self.reset()
586 (self.next,) = (
587 self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
588 )
589 self._legacy_api = False
590
591 prefix = property(lambda self: self._prefix)
592 offset = property(lambda self: self._offset)
593
594 @property
595 def legacy_api(self) -> bool:
596 return self._legacy_api
597
598 @legacy_api.setter
599 def legacy_api(self, value: bool) -> NoReturn:
600 msg = "Not allowing setting of legacy api"
601 raise Exception(msg)
602
603 def reset(self) -> None:
604 self._tags_v1: dict[int, Any] = {} # will remain empty if legacy_api is false
605 self._tags_v2: dict[int, Any] = {} # main tag storage
606 self._tagdata: dict[int, bytes] = {}
607 self.tagtype = {} # added 2008-06-05 by Florian Hoech
608 self._next = None
609 self._offset = None
610
611 def __str__(self) -> str:
612 return str(dict(self))
613
614 def named(self):
615 """
616 :returns: dict of name|key: value
617
618 Returns the complete tag dictionary, with named tags where possible.
619 """
620 return {
621 TiffTags.lookup(code, self.group).name: value
622 for code, value in self.items()
623 }
624
625 def __len__(self) -> int:
626 return len(set(self._tagdata) | set(self._tags_v2))
627
628 def __getitem__(self, tag):
629 if tag not in self._tags_v2: # unpack on the fly
630 data = self._tagdata[tag]
631 typ = self.tagtype[tag]
632 size, handler = self._load_dispatch[typ]
633 self[tag] = handler(self, data, self.legacy_api) # check type
634 val = self._tags_v2[tag]
635 if self.legacy_api and not isinstance(val, (tuple, bytes)):
636 val = (val,)
637 return val
638
639 def __contains__(self, tag):
640 return tag in self._tags_v2 or tag in self._tagdata
641
642 def __setitem__(self, tag, value):
643 self._setitem(tag, value, self.legacy_api)
644
645 def _setitem(self, tag, value, legacy_api):
646 basetypes = (Number, bytes, str)
647
648 info = TiffTags.lookup(tag, self.group)
649 values = [value] if isinstance(value, basetypes) else value
650
651 if tag not in self.tagtype:
652 if info.type:
653 self.tagtype[tag] = info.type
654 else:
655 self.tagtype[tag] = TiffTags.UNDEFINED
656 if all(isinstance(v, IFDRational) for v in values):
657 self.tagtype[tag] = (
658 TiffTags.RATIONAL
659 if all(v >= 0 for v in values)
660 else TiffTags.SIGNED_RATIONAL
661 )
662 elif all(isinstance(v, int) for v in values):
663 if all(0 <= v < 2**16 for v in values):
664 self.tagtype[tag] = TiffTags.SHORT
665 elif all(-(2**15) < v < 2**15 for v in values):
666 self.tagtype[tag] = TiffTags.SIGNED_SHORT
667 else:
668 self.tagtype[tag] = (
669 TiffTags.LONG
670 if all(v >= 0 for v in values)
671 else TiffTags.SIGNED_LONG
672 )
673 elif all(isinstance(v, float) for v in values):
674 self.tagtype[tag] = TiffTags.DOUBLE
675 elif all(isinstance(v, str) for v in values):
676 self.tagtype[tag] = TiffTags.ASCII
677 elif all(isinstance(v, bytes) for v in values):
678 self.tagtype[tag] = TiffTags.BYTE
679
680 if self.tagtype[tag] == TiffTags.UNDEFINED:
681 values = [
682 v.encode("ascii", "replace") if isinstance(v, str) else v
683 for v in values
684 ]
685 elif self.tagtype[tag] == TiffTags.RATIONAL:
686 values = [float(v) if isinstance(v, int) else v for v in values]
687
688 is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)
689 if not is_ifd:
690 values = tuple(info.cvt_enum(value) for value in values)
691
692 dest = self._tags_v1 if legacy_api else self._tags_v2
693
694 # Three branches:
695 # Spec'd length == 1, Actual length 1, store as element
696 # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
697 # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
698 # Don't mess with the legacy api, since it's frozen.
699 if not is_ifd and (
700 (info.length == 1)
701 or self.tagtype[tag] == TiffTags.BYTE
702 or (info.length is None and len(values) == 1 and not legacy_api)
703 ):
704 # Don't mess with the legacy api, since it's frozen.
705 if legacy_api and self.tagtype[tag] in [
706 TiffTags.RATIONAL,
707 TiffTags.SIGNED_RATIONAL,
708 ]: # rationals
709 values = (values,)
710 try:
711 (dest[tag],) = values
712 except ValueError:
713 # We've got a builtin tag with 1 expected entry
714 warnings.warn(
715 f"Metadata Warning, tag {tag} had too many entries: "
716 f"{len(values)}, expected 1"
717 )
718 dest[tag] = values[0]
719
720 else:
721 # Spec'd length > 1 or undefined
722 # Unspec'd, and length > 1
723 dest[tag] = values
724
725 def __delitem__(self, tag: int) -> None:
726 self._tags_v2.pop(tag, None)
727 self._tags_v1.pop(tag, None)
728 self._tagdata.pop(tag, None)
729
730 def __iter__(self):
731 return iter(set(self._tagdata) | set(self._tags_v2))
732
733 def _unpack(self, fmt, data):
734 return struct.unpack(self._endian + fmt, data)
735
736 def _pack(self, fmt, *values):
737 return struct.pack(self._endian + fmt, *values)
738
739 list(
740 map(
741 _register_basic,
742 [
743 (TiffTags.SHORT, "H", "short"),
744 (TiffTags.LONG, "L", "long"),
745 (TiffTags.SIGNED_BYTE, "b", "signed byte"),
746 (TiffTags.SIGNED_SHORT, "h", "signed short"),
747 (TiffTags.SIGNED_LONG, "l", "signed long"),
748 (TiffTags.FLOAT, "f", "float"),
749 (TiffTags.DOUBLE, "d", "double"),
750 (TiffTags.IFD, "L", "long"),
751 (TiffTags.LONG8, "Q", "long8"),
752 ],
753 )
754 )
755
756 @_register_loader(1, 1) # Basic type, except for the legacy API.
757 def load_byte(self, data, legacy_api=True):
758 return data
759
760 @_register_writer(1) # Basic type, except for the legacy API.
761 def write_byte(self, data):
762 if isinstance(data, IFDRational):
763 data = int(data)
764 if isinstance(data, int):
765 data = bytes((data,))
766 return data
767
768 @_register_loader(2, 1)
769 def load_string(self, data, legacy_api=True):
770 if data.endswith(b"\0"):
771 data = data[:-1]
772 return data.decode("latin-1", "replace")
773
774 @_register_writer(2)
775 def write_string(self, value):
776 # remerge of https://github.com/python-pillow/Pillow/pull/1416
777 if isinstance(value, int):
778 value = str(value)
779 if not isinstance(value, bytes):
780 value = value.encode("ascii", "replace")
781 return value + b"\0"
782
783 @_register_loader(5, 8)
784 def load_rational(self, data, legacy_api=True):
785 vals = self._unpack(f"{len(data) // 4}L", data)
786
787 def combine(a, b):
788 return (a, b) if legacy_api else IFDRational(a, b)
789
790 return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
791
792 @_register_writer(5)
793 def write_rational(self, *values):
794 return b"".join(
795 self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
796 )
797
798 @_register_loader(7, 1)
799 def load_undefined(self, data, legacy_api=True):
800 return data
801
802 @_register_writer(7)
803 def write_undefined(self, value):
804 if isinstance(value, IFDRational):
805 value = int(value)
806 if isinstance(value, int):
807 value = str(value).encode("ascii", "replace")
808 return value
809
810 @_register_loader(10, 8)
811 def load_signed_rational(self, data, legacy_api=True):
812 vals = self._unpack(f"{len(data) // 4}l", data)
813
814 def combine(a, b):
815 return (a, b) if legacy_api else IFDRational(a, b)
816
817 return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
818
819 @_register_writer(10)
820 def write_signed_rational(self, *values):
821 return b"".join(
822 self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
823 for frac in values
824 )
825
826 def _ensure_read(self, fp, size):
827 ret = fp.read(size)
828 if len(ret) != size:
829 msg = (
830 "Corrupt EXIF data. "
831 f"Expecting to read {size} bytes but only got {len(ret)}. "
832 )
833 raise OSError(msg)
834 return ret
835
836 def load(self, fp):
837 self.reset()
838 self._offset = fp.tell()
839
840 try:
841 tag_count = (
842 self._unpack("Q", self._ensure_read(fp, 8))
843 if self._bigtiff
844 else self._unpack("H", self._ensure_read(fp, 2))
845 )[0]
846 for i in range(tag_count):
847 tag, typ, count, data = (
848 self._unpack("HHQ8s", self._ensure_read(fp, 20))
849 if self._bigtiff
850 else self._unpack("HHL4s", self._ensure_read(fp, 12))
851 )
852
853 tagname = TiffTags.lookup(tag, self.group).name
854 typname = TYPES.get(typ, "unknown")
855 msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})"
856
857 try:
858 unit_size, handler = self._load_dispatch[typ]
859 except KeyError:
860 logger.debug("%s - unsupported type %s", msg, typ)
861 continue # ignore unsupported type
862 size = count * unit_size
863 if size > (8 if self._bigtiff else 4):
864 here = fp.tell()
865 (offset,) = self._unpack("Q" if self._bigtiff else "L", data)
866 msg += f" Tag Location: {here} - Data Location: {offset}"
867 fp.seek(offset)
868 data = ImageFile._safe_read(fp, size)
869 fp.seek(here)
870 else:
871 data = data[:size]
872
873 if len(data) != size:
874 warnings.warn(
875 "Possibly corrupt EXIF data. "
876 f"Expecting to read {size} bytes but only got {len(data)}."
877 f" Skipping tag {tag}"
878 )
879 logger.debug(msg)
880 continue
881
882 if not data:
883 logger.debug(msg)
884 continue
885
886 self._tagdata[tag] = data
887 self.tagtype[tag] = typ
888
889 msg += " - value: " + (
890 "<table: %d bytes>" % size if size > 32 else repr(data)
891 )
892 logger.debug(msg)
893
894 (self.next,) = (
895 self._unpack("Q", self._ensure_read(fp, 8))
896 if self._bigtiff
897 else self._unpack("L", self._ensure_read(fp, 4))
898 )
899 except OSError as msg:
900 warnings.warn(str(msg))
901 return
902
903 def tobytes(self, offset=0):
904 # FIXME What about tagdata?
905 result = self._pack("H", len(self._tags_v2))
906
907 entries = []
908 offset = offset + len(result) + len(self._tags_v2) * 12 + 4
909 stripoffsets = None
910
911 # pass 1: convert tags to binary format
912 # always write tags in ascending order
913 for tag, value in sorted(self._tags_v2.items()):
914 if tag == STRIPOFFSETS:
915 stripoffsets = len(entries)
916 typ = self.tagtype.get(tag)
917 logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
918 is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
919 if is_ifd:
920 if self._endian == "<":
921 ifh = b"II\x2A\x00\x08\x00\x00\x00"
922 else:
923 ifh = b"MM\x00\x2A\x00\x00\x00\x08"
924 ifd = ImageFileDirectory_v2(ifh, group=tag)
925 values = self._tags_v2[tag]
926 for ifd_tag, ifd_value in values.items():
927 ifd[ifd_tag] = ifd_value
928 data = ifd.tobytes(offset)
929 else:
930 values = value if isinstance(value, tuple) else (value,)
931 data = self._write_dispatch[typ](self, *values)
932
933 tagname = TiffTags.lookup(tag, self.group).name
934 typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
935 msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})"
936 msg += " - value: " + (
937 "<table: %d bytes>" % len(data) if len(data) >= 16 else str(values)
938 )
939 logger.debug(msg)
940
941 # count is sum of lengths for string and arbitrary data
942 if is_ifd:
943 count = 1
944 elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:
945 count = len(data)
946 else:
947 count = len(values)
948 # figure out if data fits into the entry
949 if len(data) <= 4:
950 entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
951 else:
952 entries.append((tag, typ, count, self._pack("L", offset), data))
953 offset += (len(data) + 1) // 2 * 2 # pad to word
954
955 # update strip offset data to point beyond auxiliary data
956 if stripoffsets is not None:
957 tag, typ, count, value, data = entries[stripoffsets]
958 if data:
959 msg = "multistrip support not yet implemented"
960 raise NotImplementedError(msg)
961 value = self._pack("L", self._unpack("L", value)[0] + offset)
962 entries[stripoffsets] = tag, typ, count, value, data
963
964 # pass 2: write entries to file
965 for tag, typ, count, value, data in entries:
966 logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data))
967 result += self._pack("HHL4s", tag, typ, count, value)
968
969 # -- overwrite here for multi-page --
970 result += b"\0\0\0\0" # end of entries
971
972 # pass 3: write auxiliary data to file
973 for tag, typ, count, value, data in entries:
974 result += data
975 if len(data) & 1:
976 result += b"\0"
977
978 return result
979
980 def save(self, fp):
981 if fp.tell() == 0: # skip TIFF header on subsequent pages
982 # tiff header -- PIL always starts the first IFD at offset 8
983 fp.write(self._prefix + self._pack("HL", 42, 8))
984
985 offset = fp.tell()
986 result = self.tobytes(offset)
987 fp.write(result)
988 return offset + len(result)
989
990
991ImageFileDirectory_v2._load_dispatch = _load_dispatch
992ImageFileDirectory_v2._write_dispatch = _write_dispatch
993for idx, name in TYPES.items():
994 name = name.replace(" ", "_")
995 setattr(ImageFileDirectory_v2, f"load_{name}", _load_dispatch[idx][1])
996 setattr(ImageFileDirectory_v2, f"write_{name}", _write_dispatch[idx])
997del _load_dispatch, _write_dispatch, idx, name
998
999
1000# Legacy ImageFileDirectory support.
1001class ImageFileDirectory_v1(ImageFileDirectory_v2):
1002 """This class represents the **legacy** interface to a TIFF tag directory.
1003
1004 Exposes a dictionary interface of the tags in the directory::
1005
1006 ifd = ImageFileDirectory_v1()
1007 ifd[key] = 'Some Data'
1008 ifd.tagtype[key] = TiffTags.ASCII
1009 print(ifd[key])
1010 ('Some Data',)
1011
1012 Also contains a dictionary of tag types as read from the tiff image file,
1013 :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`.
1014
1015 Values are returned as a tuple.
1016
1017 .. deprecated:: 3.0.0
1018 """
1019
1020 def __init__(self, *args, **kwargs):
1021 super().__init__(*args, **kwargs)
1022 self._legacy_api = True
1023
1024 tags = property(lambda self: self._tags_v1)
1025 tagdata = property(lambda self: self._tagdata)
1026
1027 # defined in ImageFileDirectory_v2
1028 tagtype: dict[int, int]
1029 """Dictionary of tag types"""
1030
1031 @classmethod
1032 def from_v2(cls, original):
1033 """Returns an
1034 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
1035 instance with the same data as is contained in the original
1036 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
1037 instance.
1038
1039 :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
1040
1041 """
1042
1043 ifd = cls(prefix=original.prefix)
1044 ifd._tagdata = original._tagdata
1045 ifd.tagtype = original.tagtype
1046 ifd.next = original.next # an indicator for multipage tiffs
1047 return ifd
1048
1049 def to_v2(self) -> ImageFileDirectory_v2:
1050 """Returns an
1051 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
1052 instance with the same data as is contained in the original
1053 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
1054 instance.
1055
1056 :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
1057
1058 """
1059
1060 ifd = ImageFileDirectory_v2(prefix=self.prefix)
1061 ifd._tagdata = dict(self._tagdata)
1062 ifd.tagtype = dict(self.tagtype)
1063 ifd._tags_v2 = dict(self._tags_v2)
1064 return ifd
1065
1066 def __contains__(self, tag):
1067 return tag in self._tags_v1 or tag in self._tagdata
1068
1069 def __len__(self) -> int:
1070 return len(set(self._tagdata) | set(self._tags_v1))
1071
1072 def __iter__(self):
1073 return iter(set(self._tagdata) | set(self._tags_v1))
1074
1075 def __setitem__(self, tag, value):
1076 for legacy_api in (False, True):
1077 self._setitem(tag, value, legacy_api)
1078
1079 def __getitem__(self, tag):
1080 if tag not in self._tags_v1: # unpack on the fly
1081 data = self._tagdata[tag]
1082 typ = self.tagtype[tag]
1083 size, handler = self._load_dispatch[typ]
1084 for legacy in (False, True):
1085 self._setitem(tag, handler(self, data, legacy), legacy)
1086 val = self._tags_v1[tag]
1087 if not isinstance(val, (tuple, bytes)):
1088 val = (val,)
1089 return val
1090
1091
1092# undone -- switch this pointer when IFD_LEGACY_API == False
1093ImageFileDirectory = ImageFileDirectory_v1
1094
1095
1096##
1097# Image plugin for TIFF files.
1098
1099
1100class TiffImageFile(ImageFile.ImageFile):
1101 format = "TIFF"
1102 format_description = "Adobe TIFF"
1103 _close_exclusive_fp_after_loading = False
1104
1105 def __init__(self, fp=None, filename=None):
1106 self.tag_v2 = None
1107 """ Image file directory (tag dictionary) """
1108
1109 self.tag = None
1110 """ Legacy tag entries """
1111
1112 super().__init__(fp, filename)
1113
1114 def _open(self) -> None:
1115 """Open the first image in a TIFF file"""
1116
1117 # Header
1118 ifh = self.fp.read(8)
1119 if ifh[2] == 43:
1120 ifh += self.fp.read(8)
1121
1122 self.tag_v2 = ImageFileDirectory_v2(ifh)
1123
1124 # legacy IFD entries will be filled in later
1125 self.ifd = None
1126
1127 # setup frame pointers
1128 self.__first = self.__next = self.tag_v2.next
1129 self.__frame = -1
1130 self._fp = self.fp
1131 self._frame_pos: list[int] = []
1132 self._n_frames: int | None = None
1133
1134 logger.debug("*** TiffImageFile._open ***")
1135 logger.debug("- __first: %s", self.__first)
1136 logger.debug("- ifh: %s", repr(ifh)) # Use repr to avoid str(bytes)
1137
1138 # and load the first frame
1139 self._seek(0)
1140
1141 @property
1142 def n_frames(self):
1143 if self._n_frames is None:
1144 current = self.tell()
1145 self._seek(len(self._frame_pos))
1146 while self._n_frames is None:
1147 self._seek(self.tell() + 1)
1148 self.seek(current)
1149 return self._n_frames
1150
1151 def seek(self, frame: int) -> None:
1152 """Select a given frame as current image"""
1153 if not self._seek_check(frame):
1154 return
1155 self._seek(frame)
1156 # Create a new core image object on second and
1157 # subsequent frames in the image. Image may be
1158 # different size/mode.
1159 Image._decompression_bomb_check(self.size)
1160 self.im = Image.core.new(self.mode, self.size)
1161
1162 def _seek(self, frame: int) -> None:
1163 self.fp = self._fp
1164
1165 # reset buffered io handle in case fp
1166 # was passed to libtiff, invalidating the buffer
1167 self.fp.tell()
1168
1169 while len(self._frame_pos) <= frame:
1170 if not self.__next:
1171 msg = "no more images in TIFF file"
1172 raise EOFError(msg)
1173 logger.debug(
1174 "Seeking to frame %s, on frame %s, __next %s, location: %s",
1175 frame,
1176 self.__frame,
1177 self.__next,
1178 self.fp.tell(),
1179 )
1180 if self.__next >= 2**63:
1181 msg = "Unable to seek to frame"
1182 raise ValueError(msg)
1183 self.fp.seek(self.__next)
1184 self._frame_pos.append(self.__next)
1185 logger.debug("Loading tags, location: %s", self.fp.tell())
1186 self.tag_v2.load(self.fp)
1187 if self.tag_v2.next in self._frame_pos:
1188 # This IFD has already been processed
1189 # Declare this to be the end of the image
1190 self.__next = 0
1191 else:
1192 self.__next = self.tag_v2.next
1193 if self.__next == 0:
1194 self._n_frames = frame + 1
1195 if len(self._frame_pos) == 1:
1196 self.is_animated = self.__next != 0
1197 self.__frame += 1
1198 self.fp.seek(self._frame_pos[frame])
1199 self.tag_v2.load(self.fp)
1200 if XMP in self.tag_v2:
1201 self.info["xmp"] = self.tag_v2[XMP]
1202 elif "xmp" in self.info:
1203 del self.info["xmp"]
1204 self._reload_exif()
1205 # fill the legacy tag/ifd entries
1206 self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
1207 self.__frame = frame
1208 self._setup()
1209
1210 def tell(self) -> int:
1211 """Return the current frame number"""
1212 return self.__frame
1213
1214 def get_photoshop_blocks(self):
1215 """
1216 Returns a dictionary of Photoshop "Image Resource Blocks".
1217 The keys are the image resource ID. For more information, see
1218 https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037727
1219
1220 :returns: Photoshop "Image Resource Blocks" in a dictionary.
1221 """
1222 blocks = {}
1223 val = self.tag_v2.get(ExifTags.Base.ImageResources)
1224 if val:
1225 while val[:4] == b"8BIM":
1226 id = i16(val[4:6])
1227 n = math.ceil((val[6] + 1) / 2) * 2
1228 size = i32(val[6 + n : 10 + n])
1229 data = val[10 + n : 10 + n + size]
1230 blocks[id] = {"data": data}
1231
1232 val = val[math.ceil((10 + n + size) / 2) * 2 :]
1233 return blocks
1234
1235 def load(self):
1236 if self.tile and self.use_load_libtiff:
1237 return self._load_libtiff()
1238 return super().load()
1239
1240 def load_end(self) -> None:
1241 # allow closing if we're on the first frame, there's no next
1242 # This is the ImageFile.load path only, libtiff specific below.
1243 if not self.is_animated:
1244 self._close_exclusive_fp_after_loading = True
1245
1246 # reset buffered io handle in case fp
1247 # was passed to libtiff, invalidating the buffer
1248 self.fp.tell()
1249
1250 # load IFD data from fp before it is closed
1251 exif = self.getexif()
1252 for key in TiffTags.TAGS_V2_GROUPS:
1253 if key not in exif:
1254 continue
1255 exif.get_ifd(key)
1256
1257 ImageOps.exif_transpose(self, in_place=True)
1258 if ExifTags.Base.Orientation in self.tag_v2:
1259 del self.tag_v2[ExifTags.Base.Orientation]
1260
1261 def _load_libtiff(self):
1262 """Overload method triggered when we detect a compressed tiff
1263 Calls out to libtiff"""
1264
1265 Image.Image.load(self)
1266
1267 self.load_prepare()
1268
1269 if not len(self.tile) == 1:
1270 msg = "Not exactly one tile"
1271 raise OSError(msg)
1272
1273 # (self._compression, (extents tuple),
1274 # 0, (rawmode, self._compression, fp))
1275 extents = self.tile[0][1]
1276 args = list(self.tile[0][3])
1277
1278 # To be nice on memory footprint, if there's a
1279 # file descriptor, use that instead of reading
1280 # into a string in python.
1281 try:
1282 fp = hasattr(self.fp, "fileno") and self.fp.fileno()
1283 # flush the file descriptor, prevents error on pypy 2.4+
1284 # should also eliminate the need for fp.tell
1285 # in _seek
1286 if hasattr(self.fp, "flush"):
1287 self.fp.flush()
1288 except OSError:
1289 # io.BytesIO have a fileno, but returns an OSError if
1290 # it doesn't use a file descriptor.
1291 fp = False
1292
1293 if fp:
1294 args[2] = fp
1295
1296 decoder = Image._getdecoder(
1297 self.mode, "libtiff", tuple(args), self.decoderconfig
1298 )
1299 try:
1300 decoder.setimage(self.im, extents)
1301 except ValueError as e:
1302 msg = "Couldn't set the image"
1303 raise OSError(msg) from e
1304
1305 close_self_fp = self._exclusive_fp and not self.is_animated
1306 if hasattr(self.fp, "getvalue"):
1307 # We've got a stringio like thing passed in. Yay for all in memory.
1308 # The decoder needs the entire file in one shot, so there's not
1309 # a lot we can do here other than give it the entire file.
1310 # unless we could do something like get the address of the
1311 # underlying string for stringio.
1312 #
1313 # Rearranging for supporting byteio items, since they have a fileno
1314 # that returns an OSError if there's no underlying fp. Easier to
1315 # deal with here by reordering.
1316 logger.debug("have getvalue. just sending in a string from getvalue")
1317 n, err = decoder.decode(self.fp.getvalue())
1318 elif fp:
1319 # we've got a actual file on disk, pass in the fp.
1320 logger.debug("have fileno, calling fileno version of the decoder.")
1321 if not close_self_fp:
1322 self.fp.seek(0)
1323 # 4 bytes, otherwise the trace might error out
1324 n, err = decoder.decode(b"fpfp")
1325 else:
1326 # we have something else.
1327 logger.debug("don't have fileno or getvalue. just reading")
1328 self.fp.seek(0)
1329 # UNDONE -- so much for that buffer size thing.
1330 n, err = decoder.decode(self.fp.read())
1331
1332 self.tile = []
1333 self.readonly = 0
1334
1335 self.load_end()
1336
1337 if close_self_fp:
1338 self.fp.close()
1339 self.fp = None # might be shared
1340
1341 if err < 0:
1342 raise OSError(err)
1343
1344 return Image.Image.load(self)
1345
1346 def _setup(self):
1347 """Setup this image object based on current tags"""
1348
1349 if 0xBC01 in self.tag_v2:
1350 msg = "Windows Media Photo files not yet supported"
1351 raise OSError(msg)
1352
1353 # extract relevant tags
1354 self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]
1355 self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)
1356
1357 # photometric is a required tag, but not everyone is reading
1358 # the specification
1359 photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)
1360
1361 # old style jpeg compression images most certainly are YCbCr
1362 if self._compression == "tiff_jpeg":
1363 photo = 6
1364
1365 fillorder = self.tag_v2.get(FILLORDER, 1)
1366
1367 logger.debug("*** Summary ***")
1368 logger.debug("- compression: %s", self._compression)
1369 logger.debug("- photometric_interpretation: %s", photo)
1370 logger.debug("- planar_configuration: %s", self._planar_configuration)
1371 logger.debug("- fill_order: %s", fillorder)
1372 logger.debug("- YCbCr subsampling: %s", self.tag.get(YCBCRSUBSAMPLING))
1373
1374 # size
1375 xsize = int(self.tag_v2.get(IMAGEWIDTH))
1376 ysize = int(self.tag_v2.get(IMAGELENGTH))
1377 self._size = xsize, ysize
1378
1379 logger.debug("- size: %s", self.size)
1380
1381 sample_format = self.tag_v2.get(SAMPLEFORMAT, (1,))
1382 if len(sample_format) > 1 and max(sample_format) == min(sample_format) == 1:
1383 # SAMPLEFORMAT is properly per band, so an RGB image will
1384 # be (1,1,1). But, we don't support per band pixel types,
1385 # and anything more than one band is a uint8. So, just
1386 # take the first element. Revisit this if adding support
1387 # for more exotic images.
1388 sample_format = (1,)
1389
1390 bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))
1391 extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())
1392 if photo in (2, 6, 8): # RGB, YCbCr, LAB
1393 bps_count = 3
1394 elif photo == 5: # CMYK
1395 bps_count = 4
1396 else:
1397 bps_count = 1
1398 bps_count += len(extra_tuple)
1399 bps_actual_count = len(bps_tuple)
1400 samples_per_pixel = self.tag_v2.get(
1401 SAMPLESPERPIXEL,
1402 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
1403 )
1404
1405 if samples_per_pixel > MAX_SAMPLESPERPIXEL:
1406 # DOS check, samples_per_pixel can be a Long, and we extend the tuple below
1407 logger.error(
1408 "More samples per pixel than can be decoded: %s", samples_per_pixel
1409 )
1410 msg = "Invalid value for samples per pixel"
1411 raise SyntaxError(msg)
1412
1413 if samples_per_pixel < bps_actual_count:
1414 # If a file has more values in bps_tuple than expected,
1415 # remove the excess.
1416 bps_tuple = bps_tuple[:samples_per_pixel]
1417 elif samples_per_pixel > bps_actual_count and bps_actual_count == 1:
1418 # If a file has only one value in bps_tuple, when it should have more,
1419 # presume it is the same number of bits for all of the samples.
1420 bps_tuple = bps_tuple * samples_per_pixel
1421
1422 if len(bps_tuple) != samples_per_pixel:
1423 msg = "unknown data organization"
1424 raise SyntaxError(msg)
1425
1426 # mode: check photometric interpretation and bits per pixel
1427 key = (
1428 self.tag_v2.prefix,
1429 photo,
1430 sample_format,
1431 fillorder,
1432 bps_tuple,
1433 extra_tuple,
1434 )
1435 logger.debug("format key: %s", key)
1436 try:
1437 self._mode, rawmode = OPEN_INFO[key]
1438 except KeyError as e:
1439 logger.debug("- unsupported format")
1440 msg = "unknown pixel mode"
1441 raise SyntaxError(msg) from e
1442
1443 logger.debug("- raw mode: %s", rawmode)
1444 logger.debug("- pil mode: %s", self.mode)
1445
1446 self.info["compression"] = self._compression
1447
1448 xres = self.tag_v2.get(X_RESOLUTION, 1)
1449 yres = self.tag_v2.get(Y_RESOLUTION, 1)
1450
1451 if xres and yres:
1452 resunit = self.tag_v2.get(RESOLUTION_UNIT)
1453 if resunit == 2: # dots per inch
1454 self.info["dpi"] = (xres, yres)
1455 elif resunit == 3: # dots per centimeter. convert to dpi
1456 self.info["dpi"] = (xres * 2.54, yres * 2.54)
1457 elif resunit is None: # used to default to 1, but now 2)
1458 self.info["dpi"] = (xres, yres)
1459 # For backward compatibility,
1460 # we also preserve the old behavior
1461 self.info["resolution"] = xres, yres
1462 else: # No absolute unit of measurement
1463 self.info["resolution"] = xres, yres
1464
1465 # build tile descriptors
1466 x = y = layer = 0
1467 self.tile = []
1468 self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw"
1469 if self.use_load_libtiff:
1470 # Decoder expects entire file as one tile.
1471 # There's a buffer size limit in load (64k)
1472 # so large g4 images will fail if we use that
1473 # function.
1474 #
1475 # Setup the one tile for the whole image, then
1476 # use the _load_libtiff function.
1477
1478 # libtiff handles the fillmode for us, so 1;IR should
1479 # actually be 1;I. Including the R double reverses the
1480 # bits, so stripes of the image are reversed. See
1481 # https://github.com/python-pillow/Pillow/issues/279
1482 if fillorder == 2:
1483 # Replace fillorder with fillorder=1
1484 key = key[:3] + (1,) + key[4:]
1485 logger.debug("format key: %s", key)
1486 # this should always work, since all the
1487 # fillorder==2 modes have a corresponding
1488 # fillorder=1 mode
1489 self._mode, rawmode = OPEN_INFO[key]
1490 # libtiff always returns the bytes in native order.
1491 # we're expecting image byte order. So, if the rawmode
1492 # contains I;16, we need to convert from native to image
1493 # byte order.
1494 if rawmode == "I;16":
1495 rawmode = "I;16N"
1496 if ";16B" in rawmode:
1497 rawmode = rawmode.replace(";16B", ";16N")
1498 if ";16L" in rawmode:
1499 rawmode = rawmode.replace(";16L", ";16N")
1500
1501 # YCbCr images with new jpeg compression with pixels in one plane
1502 # unpacked straight into RGB values
1503 if (
1504 photo == 6
1505 and self._compression == "jpeg"
1506 and self._planar_configuration == 1
1507 ):
1508 rawmode = "RGB"
1509
1510 # Offset in the tile tuple is 0, we go from 0,0 to
1511 # w,h, and we only do this once -- eds
1512 a = (rawmode, self._compression, False, self.tag_v2.offset)
1513 self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a))
1514
1515 elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
1516 # striped image
1517 if STRIPOFFSETS in self.tag_v2:
1518 offsets = self.tag_v2[STRIPOFFSETS]
1519 h = self.tag_v2.get(ROWSPERSTRIP, ysize)
1520 w = self.size[0]
1521 else:
1522 # tiled image
1523 offsets = self.tag_v2[TILEOFFSETS]
1524 w = self.tag_v2.get(TILEWIDTH)
1525 h = self.tag_v2.get(TILELENGTH)
1526
1527 for offset in offsets:
1528 if x + w > xsize:
1529 stride = w * sum(bps_tuple) / 8 # bytes per line
1530 else:
1531 stride = 0
1532
1533 tile_rawmode = rawmode
1534 if self._planar_configuration == 2:
1535 # each band on it's own layer
1536 tile_rawmode = rawmode[layer]
1537 # adjust stride width accordingly
1538 stride /= bps_count
1539
1540 a = (tile_rawmode, int(stride), 1)
1541 self.tile.append(
1542 (
1543 self._compression,
1544 (x, y, min(x + w, xsize), min(y + h, ysize)),
1545 offset,
1546 a,
1547 )
1548 )
1549 x = x + w
1550 if x >= self.size[0]:
1551 x, y = 0, y + h
1552 if y >= self.size[1]:
1553 x = y = 0
1554 layer += 1
1555 else:
1556 logger.debug("- unsupported data organization")
1557 msg = "unknown data organization"
1558 raise SyntaxError(msg)
1559
1560 # Fix up info.
1561 if ICCPROFILE in self.tag_v2:
1562 self.info["icc_profile"] = self.tag_v2[ICCPROFILE]
1563
1564 # fixup palette descriptor
1565
1566 if self.mode in ["P", "PA"]:
1567 palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
1568 self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
1569
1570
1571#
1572# --------------------------------------------------------------------
1573# Write TIFF files
1574
1575# little endian is default except for image modes with
1576# explicit big endian byte-order
1577
1578SAVE_INFO = {
1579 # mode => rawmode, byteorder, photometrics,
1580 # sampleformat, bitspersample, extra
1581 "1": ("1", II, 1, 1, (1,), None),
1582 "L": ("L", II, 1, 1, (8,), None),
1583 "LA": ("LA", II, 1, 1, (8, 8), 2),
1584 "P": ("P", II, 3, 1, (8,), None),
1585 "PA": ("PA", II, 3, 1, (8, 8), 2),
1586 "I": ("I;32S", II, 1, 2, (32,), None),
1587 "I;16": ("I;16", II, 1, 1, (16,), None),
1588 "I;16S": ("I;16S", II, 1, 2, (16,), None),
1589 "F": ("F;32F", II, 1, 3, (32,), None),
1590 "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
1591 "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
1592 "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
1593 "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
1594 "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
1595 "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
1596 "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
1597 "I;16B": ("I;16B", MM, 1, 1, (16,), None),
1598 "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
1599 "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
1600}
1601
1602
1603def _save(im, fp, filename):
1604 try:
1605 rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
1606 except KeyError as e:
1607 msg = f"cannot write mode {im.mode} as TIFF"
1608 raise OSError(msg) from e
1609
1610 ifd = ImageFileDirectory_v2(prefix=prefix)
1611
1612 encoderinfo = im.encoderinfo
1613 encoderconfig = im.encoderconfig
1614 try:
1615 compression = encoderinfo["compression"]
1616 except KeyError:
1617 compression = im.info.get("compression")
1618 if isinstance(compression, int):
1619 # compression value may be from BMP. Ignore it
1620 compression = None
1621 if compression is None:
1622 compression = "raw"
1623 elif compression == "tiff_jpeg":
1624 # OJPEG is obsolete, so use new-style JPEG compression instead
1625 compression = "jpeg"
1626 elif compression == "tiff_deflate":
1627 compression = "tiff_adobe_deflate"
1628
1629 libtiff = WRITE_LIBTIFF or compression != "raw"
1630
1631 # required for color libtiff images
1632 ifd[PLANAR_CONFIGURATION] = 1
1633
1634 ifd[IMAGEWIDTH] = im.size[0]
1635 ifd[IMAGELENGTH] = im.size[1]
1636
1637 # write any arbitrary tags passed in as an ImageFileDirectory
1638 if "tiffinfo" in encoderinfo:
1639 info = encoderinfo["tiffinfo"]
1640 elif "exif" in encoderinfo:
1641 info = encoderinfo["exif"]
1642 if isinstance(info, bytes):
1643 exif = Image.Exif()
1644 exif.load(info)
1645 info = exif
1646 else:
1647 info = {}
1648 logger.debug("Tiffinfo Keys: %s", list(info))
1649 if isinstance(info, ImageFileDirectory_v1):
1650 info = info.to_v2()
1651 for key in info:
1652 if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS:
1653 ifd[key] = info.get_ifd(key)
1654 else:
1655 ifd[key] = info.get(key)
1656 try:
1657 ifd.tagtype[key] = info.tagtype[key]
1658 except Exception:
1659 pass # might not be an IFD. Might not have populated type
1660
1661 legacy_ifd = {}
1662 if hasattr(im, "tag"):
1663 legacy_ifd = im.tag.to_v2()
1664
1665 supplied_tags = {**legacy_ifd, **getattr(im, "tag_v2", {})}
1666 for tag in (
1667 # IFD offset that may not be correct in the saved image
1668 EXIFIFD,
1669 # Determined by the image format and should not be copied from legacy_ifd.
1670 SAMPLEFORMAT,
1671 ):
1672 if tag in supplied_tags:
1673 del supplied_tags[tag]
1674
1675 # additions written by Greg Couch, gregc@cgl.ucsf.edu
1676 # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
1677 if hasattr(im, "tag_v2"):
1678 # preserve tags from original TIFF image file
1679 for key in (
1680 RESOLUTION_UNIT,
1681 X_RESOLUTION,
1682 Y_RESOLUTION,
1683 IPTC_NAA_CHUNK,
1684 PHOTOSHOP_CHUNK,
1685 XMP,
1686 ):
1687 if key in im.tag_v2:
1688 if key == IPTC_NAA_CHUNK and im.tag_v2.tagtype[key] not in (
1689 TiffTags.BYTE,
1690 TiffTags.UNDEFINED,
1691 ):
1692 del supplied_tags[key]
1693 else:
1694 ifd[key] = im.tag_v2[key]
1695 ifd.tagtype[key] = im.tag_v2.tagtype[key]
1696
1697 # preserve ICC profile (should also work when saving other formats
1698 # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
1699 icc = encoderinfo.get("icc_profile", im.info.get("icc_profile"))
1700 if icc:
1701 ifd[ICCPROFILE] = icc
1702
1703 for key, name in [
1704 (IMAGEDESCRIPTION, "description"),
1705 (X_RESOLUTION, "resolution"),
1706 (Y_RESOLUTION, "resolution"),
1707 (X_RESOLUTION, "x_resolution"),
1708 (Y_RESOLUTION, "y_resolution"),
1709 (RESOLUTION_UNIT, "resolution_unit"),
1710 (SOFTWARE, "software"),
1711 (DATE_TIME, "date_time"),
1712 (ARTIST, "artist"),
1713 (COPYRIGHT, "copyright"),
1714 ]:
1715 if name in encoderinfo:
1716 ifd[key] = encoderinfo[name]
1717
1718 dpi = encoderinfo.get("dpi")
1719 if dpi:
1720 ifd[RESOLUTION_UNIT] = 2
1721 ifd[X_RESOLUTION] = dpi[0]
1722 ifd[Y_RESOLUTION] = dpi[1]
1723
1724 if bits != (1,):
1725 ifd[BITSPERSAMPLE] = bits
1726 if len(bits) != 1:
1727 ifd[SAMPLESPERPIXEL] = len(bits)
1728 if extra is not None:
1729 ifd[EXTRASAMPLES] = extra
1730 if format != 1:
1731 ifd[SAMPLEFORMAT] = format
1732
1733 if PHOTOMETRIC_INTERPRETATION not in ifd:
1734 ifd[PHOTOMETRIC_INTERPRETATION] = photo
1735 elif im.mode in ("1", "L") and ifd[PHOTOMETRIC_INTERPRETATION] == 0:
1736 if im.mode == "1":
1737 inverted_im = im.copy()
1738 px = inverted_im.load()
1739 for y in range(inverted_im.height):
1740 for x in range(inverted_im.width):
1741 px[x, y] = 0 if px[x, y] == 255 else 255
1742 im = inverted_im
1743 else:
1744 im = ImageOps.invert(im)
1745
1746 if im.mode in ["P", "PA"]:
1747 lut = im.im.getpalette("RGB", "RGB;L")
1748 colormap = []
1749 colors = len(lut) // 3
1750 for i in range(3):
1751 colormap += [v * 256 for v in lut[colors * i : colors * (i + 1)]]
1752 colormap += [0] * (256 - colors)
1753 ifd[COLORMAP] = colormap
1754 # data orientation
1755 w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH]
1756 stride = len(bits) * ((w * bits[0] + 7) // 8)
1757 if ROWSPERSTRIP not in ifd:
1758 # aim for given strip size (64 KB by default) when using libtiff writer
1759 if libtiff:
1760 im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
1761 rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h)
1762 # JPEG encoder expects multiple of 8 rows
1763 if compression == "jpeg":
1764 rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h)
1765 else:
1766 rows_per_strip = h
1767 if rows_per_strip == 0:
1768 rows_per_strip = 1
1769 ifd[ROWSPERSTRIP] = rows_per_strip
1770 strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP]
1771 strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
1772 if strip_byte_counts >= 2**16:
1773 ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
1774 ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
1775 stride * h - strip_byte_counts * (strips_per_image - 1),
1776 )
1777 ifd[STRIPOFFSETS] = tuple(
1778 range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
1779 ) # this is adjusted by IFD writer
1780 # no compression by default:
1781 ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
1782
1783 if im.mode == "YCbCr":
1784 for tag, value in {
1785 YCBCRSUBSAMPLING: (1, 1),
1786 REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255),
1787 }.items():
1788 ifd.setdefault(tag, value)
1789
1790 blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS]
1791 if libtiff:
1792 if "quality" in encoderinfo:
1793 quality = encoderinfo["quality"]
1794 if not isinstance(quality, int) or quality < 0 or quality > 100:
1795 msg = "Invalid quality setting"
1796 raise ValueError(msg)
1797 if compression != "jpeg":
1798 msg = "quality setting only supported for 'jpeg' compression"
1799 raise ValueError(msg)
1800 ifd[JPEGQUALITY] = quality
1801
1802 logger.debug("Saving using libtiff encoder")
1803 logger.debug("Items: %s", sorted(ifd.items()))
1804 _fp = 0
1805 if hasattr(fp, "fileno"):
1806 try:
1807 fp.seek(0)
1808 _fp = os.dup(fp.fileno())
1809 except io.UnsupportedOperation:
1810 pass
1811
1812 # optional types for non core tags
1813 types = {}
1814 # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library
1815 # based on the data in the strip.
1816 # OSUBFILETYPE is deprecated.
1817 # The other tags expect arrays with a certain length (fixed or depending on
1818 # BITSPERSAMPLE, etc), passing arrays with a different length will result in
1819 # segfaults. Block these tags until we add extra validation.
1820 # SUBIFD may also cause a segfault.
1821 blocklist += [
1822 OSUBFILETYPE,
1823 REFERENCEBLACKWHITE,
1824 STRIPBYTECOUNTS,
1825 STRIPOFFSETS,
1826 TRANSFERFUNCTION,
1827 SUBIFD,
1828 ]
1829
1830 # bits per sample is a single short in the tiff directory, not a list.
1831 atts = {BITSPERSAMPLE: bits[0]}
1832 # Merge the ones that we have with (optional) more bits from
1833 # the original file, e.g x,y resolution so that we can
1834 # save(load('')) == original file.
1835 for tag, value in itertools.chain(ifd.items(), supplied_tags.items()):
1836 # Libtiff can only process certain core items without adding
1837 # them to the custom dictionary.
1838 # Custom items are supported for int, float, unicode, string and byte
1839 # values. Other types and tuples require a tagtype.
1840 if tag not in TiffTags.LIBTIFF_CORE:
1841 if not getattr(Image.core, "libtiff_support_custom_tags", False):
1842 continue
1843
1844 if tag in ifd.tagtype:
1845 types[tag] = ifd.tagtype[tag]
1846 elif not (isinstance(value, (int, float, str, bytes))):
1847 continue
1848 else:
1849 type = TiffTags.lookup(tag).type
1850 if type:
1851 types[tag] = type
1852 if tag not in atts and tag not in blocklist:
1853 if isinstance(value, str):
1854 atts[tag] = value.encode("ascii", "replace") + b"\0"
1855 elif isinstance(value, IFDRational):
1856 atts[tag] = float(value)
1857 else:
1858 atts[tag] = value
1859
1860 if SAMPLEFORMAT in atts and len(atts[SAMPLEFORMAT]) == 1:
1861 atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0]
1862
1863 logger.debug("Converted items: %s", sorted(atts.items()))
1864
1865 # libtiff always expects the bytes in native order.
1866 # we're storing image byte order. So, if the rawmode
1867 # contains I;16, we need to convert from native to image
1868 # byte order.
1869 if im.mode in ("I;16B", "I;16"):
1870 rawmode = "I;16N"
1871
1872 # Pass tags as sorted list so that the tags are set in a fixed order.
1873 # This is required by libtiff for some tags. For example, the JPEGQUALITY
1874 # pseudo tag requires that the COMPRESS tag was already set.
1875 tags = list(atts.items())
1876 tags.sort()
1877 a = (rawmode, compression, _fp, filename, tags, types)
1878 encoder = Image._getencoder(im.mode, "libtiff", a, encoderconfig)
1879 encoder.setimage(im.im, (0, 0) + im.size)
1880 while True:
1881 # undone, change to self.decodermaxblock:
1882 errcode, data = encoder.encode(16 * 1024)[1:]
1883 if not _fp:
1884 fp.write(data)
1885 if errcode:
1886 break
1887 if _fp:
1888 try:
1889 os.close(_fp)
1890 except OSError:
1891 pass
1892 if errcode < 0:
1893 msg = f"encoder error {errcode} when writing image file"
1894 raise OSError(msg)
1895
1896 else:
1897 for tag in blocklist:
1898 del ifd[tag]
1899 offset = ifd.save(fp)
1900
1901 ImageFile._save(
1902 im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
1903 )
1904
1905 # -- helper for multi-page save --
1906 if "_debug_multipage" in encoderinfo:
1907 # just to access o32 and o16 (using correct byte order)
1908 im._debug_multipage = ifd
1909
1910
1911class AppendingTiffWriter:
1912 fieldSizes = [
1913 0, # None
1914 1, # byte
1915 1, # ascii
1916 2, # short
1917 4, # long
1918 8, # rational
1919 1, # sbyte
1920 1, # undefined
1921 2, # sshort
1922 4, # slong
1923 8, # srational
1924 4, # float
1925 8, # double
1926 4, # ifd
1927 2, # unicode
1928 4, # complex
1929 8, # long8
1930 ]
1931
1932 Tags = {
1933 273, # StripOffsets
1934 288, # FreeOffsets
1935 324, # TileOffsets
1936 519, # JPEGQTables
1937 520, # JPEGDCTables
1938 521, # JPEGACTables
1939 }
1940
1941 def __init__(self, fn, new=False):
1942 if hasattr(fn, "read"):
1943 self.f = fn
1944 self.close_fp = False
1945 else:
1946 self.name = fn
1947 self.close_fp = True
1948 try:
1949 self.f = open(fn, "w+b" if new else "r+b")
1950 except OSError:
1951 self.f = open(fn, "w+b")
1952 self.beginning = self.f.tell()
1953 self.setup()
1954
1955 def setup(self) -> None:
1956 # Reset everything.
1957 self.f.seek(self.beginning, os.SEEK_SET)
1958
1959 self.whereToWriteNewIFDOffset = None
1960 self.offsetOfNewPage = 0
1961
1962 self.IIMM = iimm = self.f.read(4)
1963 if not iimm:
1964 # empty file - first page
1965 self.isFirst = True
1966 return
1967
1968 self.isFirst = False
1969 if iimm == b"II\x2a\x00":
1970 self.setEndian("<")
1971 elif iimm == b"MM\x00\x2a":
1972 self.setEndian(">")
1973 else:
1974 msg = "Invalid TIFF file header"
1975 raise RuntimeError(msg)
1976
1977 self.skipIFDs()
1978 self.goToEnd()
1979
1980 def finalize(self) -> None:
1981 if self.isFirst:
1982 return
1983
1984 # fix offsets
1985 self.f.seek(self.offsetOfNewPage)
1986
1987 iimm = self.f.read(4)
1988 if not iimm:
1989 # Make it easy to finish a frame without committing to a new one.
1990 return
1991
1992 if iimm != self.IIMM:
1993 msg = "IIMM of new page doesn't match IIMM of first page"
1994 raise RuntimeError(msg)
1995
1996 ifd_offset = self.readLong()
1997 ifd_offset += self.offsetOfNewPage
1998 self.f.seek(self.whereToWriteNewIFDOffset)
1999 self.writeLong(ifd_offset)
2000 self.f.seek(ifd_offset)
2001 self.fixIFD()
2002
2003 def newFrame(self) -> None:
2004 # Call this to finish a frame.
2005 self.finalize()
2006 self.setup()
2007
2008 def __enter__(self) -> AppendingTiffWriter:
2009 return self
2010
2011 def __exit__(self, *args: object) -> None:
2012 if self.close_fp:
2013 self.close()
2014
2015 def tell(self) -> int:
2016 return self.f.tell() - self.offsetOfNewPage
2017
2018 def seek(self, offset, whence=io.SEEK_SET):
2019 if whence == os.SEEK_SET:
2020 offset += self.offsetOfNewPage
2021
2022 self.f.seek(offset, whence)
2023 return self.tell()
2024
2025 def goToEnd(self) -> None:
2026 self.f.seek(0, os.SEEK_END)
2027 pos = self.f.tell()
2028
2029 # pad to 16 byte boundary
2030 pad_bytes = 16 - pos % 16
2031 if 0 < pad_bytes < 16:
2032 self.f.write(bytes(pad_bytes))
2033 self.offsetOfNewPage = self.f.tell()
2034
2035 def setEndian(self, endian: str) -> None:
2036 self.endian = endian
2037 self.longFmt = f"{self.endian}L"
2038 self.shortFmt = f"{self.endian}H"
2039 self.tagFormat = f"{self.endian}HHL"
2040
2041 def skipIFDs(self) -> None:
2042 while True:
2043 ifd_offset = self.readLong()
2044 if ifd_offset == 0:
2045 self.whereToWriteNewIFDOffset = self.f.tell() - 4
2046 break
2047
2048 self.f.seek(ifd_offset)
2049 num_tags = self.readShort()
2050 self.f.seek(num_tags * 12, os.SEEK_CUR)
2051
2052 def write(self, data: bytes) -> int | None:
2053 return self.f.write(data)
2054
2055 def readShort(self) -> int:
2056 (value,) = struct.unpack(self.shortFmt, self.f.read(2))
2057 return value
2058
2059 def readLong(self) -> int:
2060 (value,) = struct.unpack(self.longFmt, self.f.read(4))
2061 return value
2062
2063 def rewriteLastShortToLong(self, value: int) -> None:
2064 self.f.seek(-2, os.SEEK_CUR)
2065 bytes_written = self.f.write(struct.pack(self.longFmt, value))
2066 if bytes_written is not None and bytes_written != 4:
2067 msg = f"wrote only {bytes_written} bytes but wanted 4"
2068 raise RuntimeError(msg)
2069
2070 def rewriteLastShort(self, value: int) -> None:
2071 self.f.seek(-2, os.SEEK_CUR)
2072 bytes_written = self.f.write(struct.pack(self.shortFmt, value))
2073 if bytes_written is not None and bytes_written != 2:
2074 msg = f"wrote only {bytes_written} bytes but wanted 2"
2075 raise RuntimeError(msg)
2076
2077 def rewriteLastLong(self, value: int) -> None:
2078 self.f.seek(-4, os.SEEK_CUR)
2079 bytes_written = self.f.write(struct.pack(self.longFmt, value))
2080 if bytes_written is not None and bytes_written != 4:
2081 msg = f"wrote only {bytes_written} bytes but wanted 4"
2082 raise RuntimeError(msg)
2083
2084 def writeShort(self, value: int) -> None:
2085 bytes_written = self.f.write(struct.pack(self.shortFmt, value))
2086 if bytes_written is not None and bytes_written != 2:
2087 msg = f"wrote only {bytes_written} bytes but wanted 2"
2088 raise RuntimeError(msg)
2089
2090 def writeLong(self, value: int) -> None:
2091 bytes_written = self.f.write(struct.pack(self.longFmt, value))
2092 if bytes_written is not None and bytes_written != 4:
2093 msg = f"wrote only {bytes_written} bytes but wanted 4"
2094 raise RuntimeError(msg)
2095
2096 def close(self) -> None:
2097 self.finalize()
2098 self.f.close()
2099
2100 def fixIFD(self) -> None:
2101 num_tags = self.readShort()
2102
2103 for i in range(num_tags):
2104 tag, field_type, count = struct.unpack(self.tagFormat, self.f.read(8))
2105
2106 field_size = self.fieldSizes[field_type]
2107 total_size = field_size * count
2108 is_local = total_size <= 4
2109 offset: int | None
2110 if not is_local:
2111 offset = self.readLong() + self.offsetOfNewPage
2112 self.rewriteLastLong(offset)
2113
2114 if tag in self.Tags:
2115 cur_pos = self.f.tell()
2116
2117 if is_local:
2118 self.fixOffsets(
2119 count, isShort=(field_size == 2), isLong=(field_size == 4)
2120 )
2121 self.f.seek(cur_pos + 4)
2122 else:
2123 self.f.seek(offset)
2124 self.fixOffsets(
2125 count, isShort=(field_size == 2), isLong=(field_size == 4)
2126 )
2127 self.f.seek(cur_pos)
2128
2129 offset = cur_pos = None
2130
2131 elif is_local:
2132 # skip the locally stored value that is not an offset
2133 self.f.seek(4, os.SEEK_CUR)
2134
2135 def fixOffsets(
2136 self, count: int, isShort: bool = False, isLong: bool = False
2137 ) -> None:
2138 if not isShort and not isLong:
2139 msg = "offset is neither short nor long"
2140 raise RuntimeError(msg)
2141
2142 for i in range(count):
2143 offset = self.readShort() if isShort else self.readLong()
2144 offset += self.offsetOfNewPage
2145 if isShort and offset >= 65536:
2146 # offset is now too large - we must convert shorts to longs
2147 if count != 1:
2148 msg = "not implemented"
2149 raise RuntimeError(msg) # XXX TODO
2150
2151 # simple case - the offset is just one and therefore it is
2152 # local (not referenced with another offset)
2153 self.rewriteLastShortToLong(offset)
2154 self.f.seek(-10, os.SEEK_CUR)
2155 self.writeShort(TiffTags.LONG) # rewrite the type to LONG
2156 self.f.seek(8, os.SEEK_CUR)
2157 elif isShort:
2158 self.rewriteLastShort(offset)
2159 else:
2160 self.rewriteLastLong(offset)
2161
2162
2163def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
2164 encoderinfo = im.encoderinfo.copy()
2165 encoderconfig = im.encoderconfig
2166 append_images = list(encoderinfo.get("append_images", []))
2167 if not hasattr(im, "n_frames") and not append_images:
2168 return _save(im, fp, filename)
2169
2170 cur_idx = im.tell()
2171 try:
2172 with AppendingTiffWriter(fp) as tf:
2173 for ims in [im] + append_images:
2174 ims.encoderinfo = encoderinfo
2175 ims.encoderconfig = encoderconfig
2176 if not hasattr(ims, "n_frames"):
2177 nfr = 1
2178 else:
2179 nfr = ims.n_frames
2180
2181 for idx in range(nfr):
2182 ims.seek(idx)
2183 ims.load()
2184 _save(ims, tf, filename)
2185 tf.newFrame()
2186 finally:
2187 im.seek(cur_idx)
2188
2189
2190#
2191# --------------------------------------------------------------------
2192# Register
2193
2194Image.register_open(TiffImageFile.format, TiffImageFile, _accept)
2195Image.register_save(TiffImageFile.format, _save)
2196Image.register_save_all(TiffImageFile.format, _save_all)
2197
2198Image.register_extensions(TiffImageFile.format, [".tif", ".tiff"])
2199
2200Image.register_mime(TiffImageFile.format, "image/tiff")