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