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