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(float(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\052\0\0\0\0\0",
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 self.tagtype[tag] = (
689 TiffTags.RATIONAL
690 if all(v >= 0 for v in values)
691 else TiffTags.SIGNED_RATIONAL
692 )
693 elif all(isinstance(v, int) for v in values):
694 if all(0 <= v < 2**16 for v in values):
695 self.tagtype[tag] = TiffTags.SHORT
696 elif all(-(2**15) < v < 2**15 for v in values):
697 self.tagtype[tag] = TiffTags.SIGNED_SHORT
698 else:
699 self.tagtype[tag] = (
700 TiffTags.LONG
701 if all(v >= 0 for v in values)
702 else TiffTags.SIGNED_LONG
703 )
704 elif all(isinstance(v, float) for v in values):
705 self.tagtype[tag] = TiffTags.DOUBLE
706 elif all(isinstance(v, str) for v in values):
707 self.tagtype[tag] = TiffTags.ASCII
708 elif all(isinstance(v, bytes) for v in values):
709 self.tagtype[tag] = TiffTags.BYTE
710
711 if self.tagtype[tag] == TiffTags.UNDEFINED:
712 values = [
713 v.encode("ascii", "replace") if isinstance(v, str) else v
714 for v in values
715 ]
716 elif self.tagtype[tag] == TiffTags.RATIONAL:
717 values = [float(v) if isinstance(v, int) else v for v in values]
718
719 is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)
720 if not is_ifd:
721 values = tuple(info.cvt_enum(value) for value in values)
722
723 dest = self._tags_v1 if legacy_api else self._tags_v2
724
725 # Three branches:
726 # Spec'd length == 1, Actual length 1, store as element
727 # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
728 # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
729 # Don't mess with the legacy api, since it's frozen.
730 if not is_ifd and (
731 (info.length == 1)
732 or self.tagtype[tag] == TiffTags.BYTE
733 or (info.length is None and len(values) == 1 and not legacy_api)
734 ):
735 # Don't mess with the legacy api, since it's frozen.
736 if legacy_api and self.tagtype[tag] in [
737 TiffTags.RATIONAL,
738 TiffTags.SIGNED_RATIONAL,
739 ]: # rationals
740 values = (values,)
741 try:
742 (dest[tag],) = values
743 except ValueError:
744 # We've got a builtin tag with 1 expected entry
745 warnings.warn(
746 f"Metadata Warning, tag {tag} had too many entries: "
747 f"{len(values)}, expected 1"
748 )
749 dest[tag] = values[0]
750
751 else:
752 # Spec'd length > 1 or undefined
753 # Unspec'd, and length > 1
754 dest[tag] = values
755
756 def __delitem__(self, tag: int) -> None:
757 self._tags_v2.pop(tag, None)
758 self._tags_v1.pop(tag, None)
759 self._tagdata.pop(tag, None)
760
761 def __iter__(self) -> Iterator[int]:
762 return iter(set(self._tagdata) | set(self._tags_v2))
763
764 def _unpack(self, fmt: str, data: bytes) -> tuple[Any, ...]:
765 return struct.unpack(self._endian + fmt, data)
766
767 def _pack(self, fmt: str, *values: Any) -> bytes:
768 return struct.pack(self._endian + fmt, *values)
769
770 list(
771 map(
772 _register_basic,
773 [
774 (TiffTags.SHORT, "H", "short"),
775 (TiffTags.LONG, "L", "long"),
776 (TiffTags.SIGNED_BYTE, "b", "signed byte"),
777 (TiffTags.SIGNED_SHORT, "h", "signed short"),
778 (TiffTags.SIGNED_LONG, "l", "signed long"),
779 (TiffTags.FLOAT, "f", "float"),
780 (TiffTags.DOUBLE, "d", "double"),
781 (TiffTags.IFD, "L", "long"),
782 (TiffTags.LONG8, "Q", "long8"),
783 ],
784 )
785 )
786
787 @_register_loader(1, 1) # Basic type, except for the legacy API.
788 def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes:
789 return data
790
791 @_register_writer(1) # Basic type, except for the legacy API.
792 def write_byte(self, data: bytes | int | IFDRational) -> bytes:
793 if isinstance(data, IFDRational):
794 data = int(data)
795 if isinstance(data, int):
796 data = bytes((data,))
797 return data
798
799 @_register_loader(2, 1)
800 def load_string(self, data: bytes, legacy_api: bool = True) -> str:
801 if data.endswith(b"\0"):
802 data = data[:-1]
803 return data.decode("latin-1", "replace")
804
805 @_register_writer(2)
806 def write_string(self, value: str | bytes | int) -> bytes:
807 # remerge of https://github.com/python-pillow/Pillow/pull/1416
808 if isinstance(value, int):
809 value = str(value)
810 if not isinstance(value, bytes):
811 value = value.encode("ascii", "replace")
812 return value + b"\0"
813
814 @_register_loader(5, 8)
815 def load_rational(
816 self, data: bytes, legacy_api: bool = True
817 ) -> tuple[tuple[int, int] | IFDRational, ...]:
818 vals = self._unpack(f"{len(data) // 4}L", data)
819
820 def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
821 return (a, b) if legacy_api else IFDRational(a, b)
822
823 return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
824
825 @_register_writer(5)
826 def write_rational(self, *values: IFDRational) -> bytes:
827 return b"".join(
828 self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
829 )
830
831 @_register_loader(7, 1)
832 def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes:
833 return data
834
835 @_register_writer(7)
836 def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
837 if isinstance(value, IFDRational):
838 value = int(value)
839 if isinstance(value, int):
840 value = str(value).encode("ascii", "replace")
841 return value
842
843 @_register_loader(10, 8)
844 def load_signed_rational(
845 self, data: bytes, legacy_api: bool = True
846 ) -> tuple[tuple[int, int] | IFDRational, ...]:
847 vals = self._unpack(f"{len(data) // 4}l", data)
848
849 def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
850 return (a, b) if legacy_api else IFDRational(a, b)
851
852 return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
853
854 @_register_writer(10)
855 def write_signed_rational(self, *values: IFDRational) -> bytes:
856 return b"".join(
857 self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
858 for frac in values
859 )
860
861 def _ensure_read(self, fp: IO[bytes], size: int) -> bytes:
862 ret = fp.read(size)
863 if len(ret) != size:
864 msg = (
865 "Corrupt EXIF data. "
866 f"Expecting to read {size} bytes but only got {len(ret)}. "
867 )
868 raise OSError(msg)
869 return ret
870
871 def load(self, fp: IO[bytes]) -> None:
872 self.reset()
873 self._offset = fp.tell()
874
875 try:
876 tag_count = (
877 self._unpack("Q", self._ensure_read(fp, 8))
878 if self._bigtiff
879 else self._unpack("H", self._ensure_read(fp, 2))
880 )[0]
881 for i in range(tag_count):
882 tag, typ, count, data = (
883 self._unpack("HHQ8s", self._ensure_read(fp, 20))
884 if self._bigtiff
885 else self._unpack("HHL4s", self._ensure_read(fp, 12))
886 )
887
888 tagname = TiffTags.lookup(tag, self.group).name
889 typname = TYPES.get(typ, "unknown")
890 msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})"
891
892 try:
893 unit_size, handler = self._load_dispatch[typ]
894 except KeyError:
895 logger.debug("%s - unsupported type %s", msg, typ)
896 continue # ignore unsupported type
897 size = count * unit_size
898 if size > (8 if self._bigtiff else 4):
899 here = fp.tell()
900 (offset,) = self._unpack("Q" if self._bigtiff else "L", data)
901 msg += f" Tag Location: {here} - Data Location: {offset}"
902 fp.seek(offset)
903 data = ImageFile._safe_read(fp, size)
904 fp.seek(here)
905 else:
906 data = data[:size]
907
908 if len(data) != size:
909 warnings.warn(
910 "Possibly corrupt EXIF data. "
911 f"Expecting to read {size} bytes but only got {len(data)}."
912 f" Skipping tag {tag}"
913 )
914 logger.debug(msg)
915 continue
916
917 if not data:
918 logger.debug(msg)
919 continue
920
921 self._tagdata[tag] = data
922 self.tagtype[tag] = typ
923
924 msg += " - value: " + (
925 "<table: %d bytes>" % size if size > 32 else repr(data)
926 )
927 logger.debug(msg)
928
929 (self.next,) = (
930 self._unpack("Q", self._ensure_read(fp, 8))
931 if self._bigtiff
932 else self._unpack("L", self._ensure_read(fp, 4))
933 )
934 except OSError as msg:
935 warnings.warn(str(msg))
936 return
937
938 def tobytes(self, offset: int = 0) -> bytes:
939 # FIXME What about tagdata?
940 result = self._pack("H", len(self._tags_v2))
941
942 entries: list[tuple[int, int, int, bytes, bytes]] = []
943 offset = offset + len(result) + len(self._tags_v2) * 12 + 4
944 stripoffsets = None
945
946 # pass 1: convert tags to binary format
947 # always write tags in ascending order
948 for tag, value in sorted(self._tags_v2.items()):
949 if tag == STRIPOFFSETS:
950 stripoffsets = len(entries)
951 typ = self.tagtype[tag]
952 logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
953 is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
954 if is_ifd:
955 if self._endian == "<":
956 ifh = b"II\x2A\x00\x08\x00\x00\x00"
957 else:
958 ifh = b"MM\x00\x2A\x00\x00\x00\x08"
959 ifd = ImageFileDirectory_v2(ifh, group=tag)
960 values = self._tags_v2[tag]
961 for ifd_tag, ifd_value in values.items():
962 ifd[ifd_tag] = ifd_value
963 data = ifd.tobytes(offset)
964 else:
965 values = value if isinstance(value, tuple) else (value,)
966 data = self._write_dispatch[typ](self, *values)
967
968 tagname = TiffTags.lookup(tag, self.group).name
969 typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
970 msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})"
971 msg += " - value: " + (
972 "<table: %d bytes>" % len(data) if len(data) >= 16 else str(values)
973 )
974 logger.debug(msg)
975
976 # count is sum of lengths for string and arbitrary data
977 if is_ifd:
978 count = 1
979 elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:
980 count = len(data)
981 else:
982 count = len(values)
983 # figure out if data fits into the entry
984 if len(data) <= 4:
985 entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
986 else:
987 entries.append((tag, typ, count, self._pack("L", offset), data))
988 offset += (len(data) + 1) // 2 * 2 # pad to word
989
990 # update strip offset data to point beyond auxiliary data
991 if stripoffsets is not None:
992 tag, typ, count, value, data = entries[stripoffsets]
993 if data:
994 size, handler = self._load_dispatch[typ]
995 values = [val + offset for val in handler(self, data, self.legacy_api)]
996 data = self._write_dispatch[typ](self, *values)
997 else:
998 value = self._pack("L", self._unpack("L", value)[0] + offset)
999 entries[stripoffsets] = tag, typ, count, value, data
1000
1001 # pass 2: write entries to file
1002 for tag, typ, count, value, data in entries:
1003 logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data))
1004 result += self._pack("HHL4s", tag, typ, count, value)
1005
1006 # -- overwrite here for multi-page --
1007 result += b"\0\0\0\0" # end of entries
1008
1009 # pass 3: write auxiliary data to file
1010 for tag, typ, count, value, data in entries:
1011 result += data
1012 if len(data) & 1:
1013 result += b"\0"
1014
1015 return result
1016
1017 def save(self, fp: IO[bytes]) -> int:
1018 if fp.tell() == 0: # skip TIFF header on subsequent pages
1019 # tiff header -- PIL always starts the first IFD at offset 8
1020 fp.write(self._prefix + self._pack("HL", 42, 8))
1021
1022 offset = fp.tell()
1023 result = self.tobytes(offset)
1024 fp.write(result)
1025 return offset + len(result)
1026
1027
1028ImageFileDirectory_v2._load_dispatch = _load_dispatch
1029ImageFileDirectory_v2._write_dispatch = _write_dispatch
1030for idx, name in TYPES.items():
1031 name = name.replace(" ", "_")
1032 setattr(ImageFileDirectory_v2, f"load_{name}", _load_dispatch[idx][1])
1033 setattr(ImageFileDirectory_v2, f"write_{name}", _write_dispatch[idx])
1034del _load_dispatch, _write_dispatch, idx, name
1035
1036
1037# Legacy ImageFileDirectory support.
1038class ImageFileDirectory_v1(ImageFileDirectory_v2):
1039 """This class represents the **legacy** interface to a TIFF tag directory.
1040
1041 Exposes a dictionary interface of the tags in the directory::
1042
1043 ifd = ImageFileDirectory_v1()
1044 ifd[key] = 'Some Data'
1045 ifd.tagtype[key] = TiffTags.ASCII
1046 print(ifd[key])
1047 ('Some Data',)
1048
1049 Also contains a dictionary of tag types as read from the tiff image file,
1050 :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`.
1051
1052 Values are returned as a tuple.
1053
1054 .. deprecated:: 3.0.0
1055 """
1056
1057 def __init__(self, *args: Any, **kwargs: Any) -> None:
1058 super().__init__(*args, **kwargs)
1059 self._legacy_api = True
1060
1061 tags = property(lambda self: self._tags_v1)
1062 tagdata = property(lambda self: self._tagdata)
1063
1064 # defined in ImageFileDirectory_v2
1065 tagtype: dict[int, int]
1066 """Dictionary of tag types"""
1067
1068 @classmethod
1069 def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
1070 """Returns an
1071 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
1072 instance with the same data as is contained in the original
1073 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
1074 instance.
1075
1076 :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
1077
1078 """
1079
1080 ifd = cls(prefix=original.prefix)
1081 ifd._tagdata = original._tagdata
1082 ifd.tagtype = original.tagtype
1083 ifd.next = original.next # an indicator for multipage tiffs
1084 return ifd
1085
1086 def to_v2(self) -> ImageFileDirectory_v2:
1087 """Returns an
1088 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
1089 instance with the same data as is contained in the original
1090 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
1091 instance.
1092
1093 :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
1094
1095 """
1096
1097 ifd = ImageFileDirectory_v2(prefix=self.prefix)
1098 ifd._tagdata = dict(self._tagdata)
1099 ifd.tagtype = dict(self.tagtype)
1100 ifd._tags_v2 = dict(self._tags_v2)
1101 return ifd
1102
1103 def __contains__(self, tag: object) -> bool:
1104 return tag in self._tags_v1 or tag in self._tagdata
1105
1106 def __len__(self) -> int:
1107 return len(set(self._tagdata) | set(self._tags_v1))
1108
1109 def __iter__(self) -> Iterator[int]:
1110 return iter(set(self._tagdata) | set(self._tags_v1))
1111
1112 def __setitem__(self, tag: int, value: Any) -> None:
1113 for legacy_api in (False, True):
1114 self._setitem(tag, value, legacy_api)
1115
1116 def __getitem__(self, tag: int) -> Any:
1117 if tag not in self._tags_v1: # unpack on the fly
1118 data = self._tagdata[tag]
1119 typ = self.tagtype[tag]
1120 size, handler = self._load_dispatch[typ]
1121 for legacy in (False, True):
1122 self._setitem(tag, handler(self, data, legacy), legacy)
1123 val = self._tags_v1[tag]
1124 if not isinstance(val, (tuple, bytes)):
1125 val = (val,)
1126 return val
1127
1128
1129# undone -- switch this pointer
1130ImageFileDirectory = ImageFileDirectory_v1
1131
1132
1133##
1134# Image plugin for TIFF files.
1135
1136
1137class TiffImageFile(ImageFile.ImageFile):
1138 format = "TIFF"
1139 format_description = "Adobe TIFF"
1140 _close_exclusive_fp_after_loading = False
1141
1142 def __init__(
1143 self,
1144 fp: StrOrBytesPath | IO[bytes],
1145 filename: str | bytes | None = None,
1146 ) -> None:
1147 self.tag_v2: ImageFileDirectory_v2
1148 """ Image file directory (tag dictionary) """
1149
1150 self.tag: ImageFileDirectory_v1
1151 """ Legacy tag entries """
1152
1153 super().__init__(fp, filename)
1154
1155 def _open(self) -> None:
1156 """Open the first image in a TIFF file"""
1157
1158 # Header
1159 ifh = self.fp.read(8)
1160 if ifh[2] == 43:
1161 ifh += self.fp.read(8)
1162
1163 self.tag_v2 = ImageFileDirectory_v2(ifh)
1164
1165 # setup frame pointers
1166 self.__first = self.__next = self.tag_v2.next
1167 self.__frame = -1
1168 self._fp = self.fp
1169 self._frame_pos: list[int] = []
1170 self._n_frames: int | None = None
1171
1172 logger.debug("*** TiffImageFile._open ***")
1173 logger.debug("- __first: %s", self.__first)
1174 logger.debug("- ifh: %s", repr(ifh)) # Use repr to avoid str(bytes)
1175
1176 # and load the first frame
1177 self._seek(0)
1178
1179 @property
1180 def n_frames(self) -> int:
1181 current_n_frames = self._n_frames
1182 if current_n_frames is None:
1183 current = self.tell()
1184 self._seek(len(self._frame_pos))
1185 while self._n_frames is None:
1186 self._seek(self.tell() + 1)
1187 self.seek(current)
1188 assert self._n_frames is not None
1189 return self._n_frames
1190
1191 def seek(self, frame: int) -> None:
1192 """Select a given frame as current image"""
1193 if not self._seek_check(frame):
1194 return
1195 self._seek(frame)
1196 if self._im is not None and (
1197 self.im.size != self._tile_size or self.im.mode != self.mode
1198 ):
1199 # The core image will no longer be used
1200 self._im = None
1201
1202 def _seek(self, frame: int) -> None:
1203 self.fp = self._fp
1204
1205 # reset buffered io handle in case fp
1206 # was passed to libtiff, invalidating the buffer
1207 self.fp.tell()
1208
1209 while len(self._frame_pos) <= frame:
1210 if not self.__next:
1211 msg = "no more images in TIFF file"
1212 raise EOFError(msg)
1213 logger.debug(
1214 "Seeking to frame %s, on frame %s, __next %s, location: %s",
1215 frame,
1216 self.__frame,
1217 self.__next,
1218 self.fp.tell(),
1219 )
1220 if self.__next >= 2**63:
1221 msg = "Unable to seek to frame"
1222 raise ValueError(msg)
1223 self.fp.seek(self.__next)
1224 self._frame_pos.append(self.__next)
1225 logger.debug("Loading tags, location: %s", self.fp.tell())
1226 self.tag_v2.load(self.fp)
1227 if self.tag_v2.next in self._frame_pos:
1228 # This IFD has already been processed
1229 # Declare this to be the end of the image
1230 self.__next = 0
1231 else:
1232 self.__next = self.tag_v2.next
1233 if self.__next == 0:
1234 self._n_frames = frame + 1
1235 if len(self._frame_pos) == 1:
1236 self.is_animated = self.__next != 0
1237 self.__frame += 1
1238 self.fp.seek(self._frame_pos[frame])
1239 self.tag_v2.load(self.fp)
1240 if XMP in self.tag_v2:
1241 self.info["xmp"] = self.tag_v2[XMP]
1242 elif "xmp" in self.info:
1243 del self.info["xmp"]
1244 self._reload_exif()
1245 # fill the legacy tag/ifd entries
1246 self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
1247 self.__frame = frame
1248 self._setup()
1249
1250 def tell(self) -> int:
1251 """Return the current frame number"""
1252 return self.__frame
1253
1254 def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]:
1255 """
1256 Returns a dictionary of Photoshop "Image Resource Blocks".
1257 The keys are the image resource ID. For more information, see
1258 https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037727
1259
1260 :returns: Photoshop "Image Resource Blocks" in a dictionary.
1261 """
1262 blocks = {}
1263 val = self.tag_v2.get(ExifTags.Base.ImageResources)
1264 if val:
1265 while val[:4] == b"8BIM":
1266 id = i16(val[4:6])
1267 n = math.ceil((val[6] + 1) / 2) * 2
1268 size = i32(val[6 + n : 10 + n])
1269 data = val[10 + n : 10 + n + size]
1270 blocks[id] = {"data": data}
1271
1272 val = val[math.ceil((10 + n + size) / 2) * 2 :]
1273 return blocks
1274
1275 def load(self) -> Image.core.PixelAccess | None:
1276 if self.tile and self.use_load_libtiff:
1277 return self._load_libtiff()
1278 return super().load()
1279
1280 def load_prepare(self) -> None:
1281 if self._im is None:
1282 Image._decompression_bomb_check(self._tile_size)
1283 self.im = Image.core.new(self.mode, self._tile_size)
1284 ImageFile.ImageFile.load_prepare(self)
1285
1286 def load_end(self) -> None:
1287 # allow closing if we're on the first frame, there's no next
1288 # This is the ImageFile.load path only, libtiff specific below.
1289 if not self.is_animated:
1290 self._close_exclusive_fp_after_loading = True
1291
1292 # reset buffered io handle in case fp
1293 # was passed to libtiff, invalidating the buffer
1294 self.fp.tell()
1295
1296 # load IFD data from fp before it is closed
1297 exif = self.getexif()
1298 for key in TiffTags.TAGS_V2_GROUPS:
1299 if key not in exif:
1300 continue
1301 exif.get_ifd(key)
1302
1303 ImageOps.exif_transpose(self, in_place=True)
1304 if ExifTags.Base.Orientation in self.tag_v2:
1305 del self.tag_v2[ExifTags.Base.Orientation]
1306
1307 def _load_libtiff(self) -> Image.core.PixelAccess | None:
1308 """Overload method triggered when we detect a compressed tiff
1309 Calls out to libtiff"""
1310
1311 Image.Image.load(self)
1312
1313 self.load_prepare()
1314
1315 if not len(self.tile) == 1:
1316 msg = "Not exactly one tile"
1317 raise OSError(msg)
1318
1319 # (self._compression, (extents tuple),
1320 # 0, (rawmode, self._compression, fp))
1321 extents = self.tile[0][1]
1322 args = self.tile[0][3]
1323
1324 # To be nice on memory footprint, if there's a
1325 # file descriptor, use that instead of reading
1326 # into a string in python.
1327 try:
1328 fp = hasattr(self.fp, "fileno") and self.fp.fileno()
1329 # flush the file descriptor, prevents error on pypy 2.4+
1330 # should also eliminate the need for fp.tell
1331 # in _seek
1332 if hasattr(self.fp, "flush"):
1333 self.fp.flush()
1334 except OSError:
1335 # io.BytesIO have a fileno, but returns an OSError if
1336 # it doesn't use a file descriptor.
1337 fp = False
1338
1339 if fp:
1340 assert isinstance(args, tuple)
1341 args_list = list(args)
1342 args_list[2] = fp
1343 args = tuple(args_list)
1344
1345 decoder = Image._getdecoder(self.mode, "libtiff", args, self.decoderconfig)
1346 try:
1347 decoder.setimage(self.im, extents)
1348 except ValueError as e:
1349 msg = "Couldn't set the image"
1350 raise OSError(msg) from e
1351
1352 close_self_fp = self._exclusive_fp and not self.is_animated
1353 if hasattr(self.fp, "getvalue"):
1354 # We've got a stringio like thing passed in. Yay for all in memory.
1355 # The decoder needs the entire file in one shot, so there's not
1356 # a lot we can do here other than give it the entire file.
1357 # unless we could do something like get the address of the
1358 # underlying string for stringio.
1359 #
1360 # Rearranging for supporting byteio items, since they have a fileno
1361 # that returns an OSError if there's no underlying fp. Easier to
1362 # deal with here by reordering.
1363 logger.debug("have getvalue. just sending in a string from getvalue")
1364 n, err = decoder.decode(self.fp.getvalue())
1365 elif fp:
1366 # we've got a actual file on disk, pass in the fp.
1367 logger.debug("have fileno, calling fileno version of the decoder.")
1368 if not close_self_fp:
1369 self.fp.seek(0)
1370 # 4 bytes, otherwise the trace might error out
1371 n, err = decoder.decode(b"fpfp")
1372 else:
1373 # we have something else.
1374 logger.debug("don't have fileno or getvalue. just reading")
1375 self.fp.seek(0)
1376 # UNDONE -- so much for that buffer size thing.
1377 n, err = decoder.decode(self.fp.read())
1378
1379 self.tile = []
1380 self.readonly = 0
1381
1382 self.load_end()
1383
1384 if close_self_fp:
1385 self.fp.close()
1386 self.fp = None # might be shared
1387
1388 if err < 0:
1389 raise OSError(err)
1390
1391 return Image.Image.load(self)
1392
1393 def _setup(self) -> None:
1394 """Setup this image object based on current tags"""
1395
1396 if 0xBC01 in self.tag_v2:
1397 msg = "Windows Media Photo files not yet supported"
1398 raise OSError(msg)
1399
1400 # extract relevant tags
1401 self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]
1402 self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)
1403
1404 # photometric is a required tag, but not everyone is reading
1405 # the specification
1406 photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)
1407
1408 # old style jpeg compression images most certainly are YCbCr
1409 if self._compression == "tiff_jpeg":
1410 photo = 6
1411
1412 fillorder = self.tag_v2.get(FILLORDER, 1)
1413
1414 logger.debug("*** Summary ***")
1415 logger.debug("- compression: %s", self._compression)
1416 logger.debug("- photometric_interpretation: %s", photo)
1417 logger.debug("- planar_configuration: %s", self._planar_configuration)
1418 logger.debug("- fill_order: %s", fillorder)
1419 logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
1420
1421 # size
1422 xsize = self.tag_v2.get(IMAGEWIDTH)
1423 ysize = self.tag_v2.get(IMAGELENGTH)
1424 if not isinstance(xsize, int) or not isinstance(ysize, int):
1425 msg = "Invalid dimensions"
1426 raise ValueError(msg)
1427 self._tile_size = xsize, ysize
1428 orientation = self.tag_v2.get(ExifTags.Base.Orientation)
1429 if orientation in (5, 6, 7, 8):
1430 self._size = ysize, xsize
1431 else:
1432 self._size = xsize, ysize
1433
1434 logger.debug("- size: %s", self.size)
1435
1436 sample_format = self.tag_v2.get(SAMPLEFORMAT, (1,))
1437 if len(sample_format) > 1 and max(sample_format) == min(sample_format) == 1:
1438 # SAMPLEFORMAT is properly per band, so an RGB image will
1439 # be (1,1,1). But, we don't support per band pixel types,
1440 # and anything more than one band is a uint8. So, just
1441 # take the first element. Revisit this if adding support
1442 # for more exotic images.
1443 sample_format = (1,)
1444
1445 bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))
1446 extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())
1447 if photo in (2, 6, 8): # RGB, YCbCr, LAB
1448 bps_count = 3
1449 elif photo == 5: # CMYK
1450 bps_count = 4
1451 else:
1452 bps_count = 1
1453 bps_count += len(extra_tuple)
1454 bps_actual_count = len(bps_tuple)
1455 samples_per_pixel = self.tag_v2.get(
1456 SAMPLESPERPIXEL,
1457 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
1458 )
1459
1460 if samples_per_pixel > MAX_SAMPLESPERPIXEL:
1461 # DOS check, samples_per_pixel can be a Long, and we extend the tuple below
1462 logger.error(
1463 "More samples per pixel than can be decoded: %s", samples_per_pixel
1464 )
1465 msg = "Invalid value for samples per pixel"
1466 raise SyntaxError(msg)
1467
1468 if samples_per_pixel < bps_actual_count:
1469 # If a file has more values in bps_tuple than expected,
1470 # remove the excess.
1471 bps_tuple = bps_tuple[:samples_per_pixel]
1472 elif samples_per_pixel > bps_actual_count and bps_actual_count == 1:
1473 # If a file has only one value in bps_tuple, when it should have more,
1474 # presume it is the same number of bits for all of the samples.
1475 bps_tuple = bps_tuple * samples_per_pixel
1476
1477 if len(bps_tuple) != samples_per_pixel:
1478 msg = "unknown data organization"
1479 raise SyntaxError(msg)
1480
1481 # mode: check photometric interpretation and bits per pixel
1482 key = (
1483 self.tag_v2.prefix,
1484 photo,
1485 sample_format,
1486 fillorder,
1487 bps_tuple,
1488 extra_tuple,
1489 )
1490 logger.debug("format key: %s", key)
1491 try:
1492 self._mode, rawmode = OPEN_INFO[key]
1493 except KeyError as e:
1494 logger.debug("- unsupported format")
1495 msg = "unknown pixel mode"
1496 raise SyntaxError(msg) from e
1497
1498 logger.debug("- raw mode: %s", rawmode)
1499 logger.debug("- pil mode: %s", self.mode)
1500
1501 self.info["compression"] = self._compression
1502
1503 xres = self.tag_v2.get(X_RESOLUTION, 1)
1504 yres = self.tag_v2.get(Y_RESOLUTION, 1)
1505
1506 if xres and yres:
1507 resunit = self.tag_v2.get(RESOLUTION_UNIT)
1508 if resunit == 2: # dots per inch
1509 self.info["dpi"] = (xres, yres)
1510 elif resunit == 3: # dots per centimeter. convert to dpi
1511 self.info["dpi"] = (xres * 2.54, yres * 2.54)
1512 elif resunit is None: # used to default to 1, but now 2)
1513 self.info["dpi"] = (xres, yres)
1514 # For backward compatibility,
1515 # we also preserve the old behavior
1516 self.info["resolution"] = xres, yres
1517 else: # No absolute unit of measurement
1518 self.info["resolution"] = xres, yres
1519
1520 # build tile descriptors
1521 x = y = layer = 0
1522 self.tile = []
1523 self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw"
1524 if self.use_load_libtiff:
1525 # Decoder expects entire file as one tile.
1526 # There's a buffer size limit in load (64k)
1527 # so large g4 images will fail if we use that
1528 # function.
1529 #
1530 # Setup the one tile for the whole image, then
1531 # use the _load_libtiff function.
1532
1533 # libtiff handles the fillmode for us, so 1;IR should
1534 # actually be 1;I. Including the R double reverses the
1535 # bits, so stripes of the image are reversed. See
1536 # https://github.com/python-pillow/Pillow/issues/279
1537 if fillorder == 2:
1538 # Replace fillorder with fillorder=1
1539 key = key[:3] + (1,) + key[4:]
1540 logger.debug("format key: %s", key)
1541 # this should always work, since all the
1542 # fillorder==2 modes have a corresponding
1543 # fillorder=1 mode
1544 self._mode, rawmode = OPEN_INFO[key]
1545 # libtiff always returns the bytes in native order.
1546 # we're expecting image byte order. So, if the rawmode
1547 # contains I;16, we need to convert from native to image
1548 # byte order.
1549 if rawmode == "I;16":
1550 rawmode = "I;16N"
1551 if ";16B" in rawmode:
1552 rawmode = rawmode.replace(";16B", ";16N")
1553 if ";16L" in rawmode:
1554 rawmode = rawmode.replace(";16L", ";16N")
1555
1556 # YCbCr images with new jpeg compression with pixels in one plane
1557 # unpacked straight into RGB values
1558 if (
1559 photo == 6
1560 and self._compression == "jpeg"
1561 and self._planar_configuration == 1
1562 ):
1563 rawmode = "RGB"
1564
1565 # Offset in the tile tuple is 0, we go from 0,0 to
1566 # w,h, and we only do this once -- eds
1567 a = (rawmode, self._compression, False, self.tag_v2.offset)
1568 self.tile.append(ImageFile._Tile("libtiff", (0, 0, xsize, ysize), 0, a))
1569
1570 elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
1571 # striped image
1572 if STRIPOFFSETS in self.tag_v2:
1573 offsets = self.tag_v2[STRIPOFFSETS]
1574 h = self.tag_v2.get(ROWSPERSTRIP, ysize)
1575 w = xsize
1576 else:
1577 # tiled image
1578 offsets = self.tag_v2[TILEOFFSETS]
1579 tilewidth = self.tag_v2.get(TILEWIDTH)
1580 h = self.tag_v2.get(TILELENGTH)
1581 if not isinstance(tilewidth, int) or not isinstance(h, int):
1582 msg = "Invalid tile dimensions"
1583 raise ValueError(msg)
1584 w = tilewidth
1585
1586 for offset in offsets:
1587 if x + w > xsize:
1588 stride = w * sum(bps_tuple) / 8 # bytes per line
1589 else:
1590 stride = 0
1591
1592 tile_rawmode = rawmode
1593 if self._planar_configuration == 2:
1594 # each band on it's own layer
1595 tile_rawmode = rawmode[layer]
1596 # adjust stride width accordingly
1597 stride /= bps_count
1598
1599 args = (tile_rawmode, int(stride), 1)
1600 self.tile.append(
1601 ImageFile._Tile(
1602 self._compression,
1603 (x, y, min(x + w, xsize), min(y + h, ysize)),
1604 offset,
1605 args,
1606 )
1607 )
1608 x = x + w
1609 if x >= xsize:
1610 x, y = 0, y + h
1611 if y >= ysize:
1612 x = y = 0
1613 layer += 1
1614 else:
1615 logger.debug("- unsupported data organization")
1616 msg = "unknown data organization"
1617 raise SyntaxError(msg)
1618
1619 # Fix up info.
1620 if ICCPROFILE in self.tag_v2:
1621 self.info["icc_profile"] = self.tag_v2[ICCPROFILE]
1622
1623 # fixup palette descriptor
1624
1625 if self.mode in ["P", "PA"]:
1626 palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
1627 self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
1628
1629
1630#
1631# --------------------------------------------------------------------
1632# Write TIFF files
1633
1634# little endian is default except for image modes with
1635# explicit big endian byte-order
1636
1637SAVE_INFO = {
1638 # mode => rawmode, byteorder, photometrics,
1639 # sampleformat, bitspersample, extra
1640 "1": ("1", II, 1, 1, (1,), None),
1641 "L": ("L", II, 1, 1, (8,), None),
1642 "LA": ("LA", II, 1, 1, (8, 8), 2),
1643 "P": ("P", II, 3, 1, (8,), None),
1644 "PA": ("PA", II, 3, 1, (8, 8), 2),
1645 "I": ("I;32S", II, 1, 2, (32,), None),
1646 "I;16": ("I;16", II, 1, 1, (16,), None),
1647 "I;16S": ("I;16S", II, 1, 2, (16,), None),
1648 "F": ("F;32F", II, 1, 3, (32,), None),
1649 "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
1650 "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
1651 "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
1652 "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
1653 "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
1654 "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
1655 "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
1656 "I;16B": ("I;16B", MM, 1, 1, (16,), None),
1657 "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
1658 "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
1659}
1660
1661
1662def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
1663 try:
1664 rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
1665 except KeyError as e:
1666 msg = f"cannot write mode {im.mode} as TIFF"
1667 raise OSError(msg) from e
1668
1669 ifd = ImageFileDirectory_v2(prefix=prefix)
1670
1671 encoderinfo = im.encoderinfo
1672 encoderconfig = im.encoderconfig
1673 try:
1674 compression = encoderinfo["compression"]
1675 except KeyError:
1676 compression = im.info.get("compression")
1677 if isinstance(compression, int):
1678 # compression value may be from BMP. Ignore it
1679 compression = None
1680 if compression is None:
1681 compression = "raw"
1682 elif compression == "tiff_jpeg":
1683 # OJPEG is obsolete, so use new-style JPEG compression instead
1684 compression = "jpeg"
1685 elif compression == "tiff_deflate":
1686 compression = "tiff_adobe_deflate"
1687
1688 libtiff = WRITE_LIBTIFF or compression != "raw"
1689
1690 # required for color libtiff images
1691 ifd[PLANAR_CONFIGURATION] = 1
1692
1693 ifd[IMAGEWIDTH] = im.size[0]
1694 ifd[IMAGELENGTH] = im.size[1]
1695
1696 # write any arbitrary tags passed in as an ImageFileDirectory
1697 if "tiffinfo" in encoderinfo:
1698 info = encoderinfo["tiffinfo"]
1699 elif "exif" in encoderinfo:
1700 info = encoderinfo["exif"]
1701 if isinstance(info, bytes):
1702 exif = Image.Exif()
1703 exif.load(info)
1704 info = exif
1705 else:
1706 info = {}
1707 logger.debug("Tiffinfo Keys: %s", list(info))
1708 if isinstance(info, ImageFileDirectory_v1):
1709 info = info.to_v2()
1710 for key in info:
1711 if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS:
1712 ifd[key] = info.get_ifd(key)
1713 else:
1714 ifd[key] = info.get(key)
1715 try:
1716 ifd.tagtype[key] = info.tagtype[key]
1717 except Exception:
1718 pass # might not be an IFD. Might not have populated type
1719
1720 legacy_ifd = {}
1721 if hasattr(im, "tag"):
1722 legacy_ifd = im.tag.to_v2()
1723
1724 supplied_tags = {**legacy_ifd, **getattr(im, "tag_v2", {})}
1725 for tag in (
1726 # IFD offset that may not be correct in the saved image
1727 EXIFIFD,
1728 # Determined by the image format and should not be copied from legacy_ifd.
1729 SAMPLEFORMAT,
1730 ):
1731 if tag in supplied_tags:
1732 del supplied_tags[tag]
1733
1734 # additions written by Greg Couch, gregc@cgl.ucsf.edu
1735 # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
1736 if hasattr(im, "tag_v2"):
1737 # preserve tags from original TIFF image file
1738 for key in (
1739 RESOLUTION_UNIT,
1740 X_RESOLUTION,
1741 Y_RESOLUTION,
1742 IPTC_NAA_CHUNK,
1743 PHOTOSHOP_CHUNK,
1744 XMP,
1745 ):
1746 if key in im.tag_v2:
1747 if key == IPTC_NAA_CHUNK and im.tag_v2.tagtype[key] not in (
1748 TiffTags.BYTE,
1749 TiffTags.UNDEFINED,
1750 ):
1751 del supplied_tags[key]
1752 else:
1753 ifd[key] = im.tag_v2[key]
1754 ifd.tagtype[key] = im.tag_v2.tagtype[key]
1755
1756 # preserve ICC profile (should also work when saving other formats
1757 # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
1758 icc = encoderinfo.get("icc_profile", im.info.get("icc_profile"))
1759 if icc:
1760 ifd[ICCPROFILE] = icc
1761
1762 for key, name in [
1763 (IMAGEDESCRIPTION, "description"),
1764 (X_RESOLUTION, "resolution"),
1765 (Y_RESOLUTION, "resolution"),
1766 (X_RESOLUTION, "x_resolution"),
1767 (Y_RESOLUTION, "y_resolution"),
1768 (RESOLUTION_UNIT, "resolution_unit"),
1769 (SOFTWARE, "software"),
1770 (DATE_TIME, "date_time"),
1771 (ARTIST, "artist"),
1772 (COPYRIGHT, "copyright"),
1773 ]:
1774 if name in encoderinfo:
1775 ifd[key] = encoderinfo[name]
1776
1777 dpi = encoderinfo.get("dpi")
1778 if dpi:
1779 ifd[RESOLUTION_UNIT] = 2
1780 ifd[X_RESOLUTION] = dpi[0]
1781 ifd[Y_RESOLUTION] = dpi[1]
1782
1783 if bits != (1,):
1784 ifd[BITSPERSAMPLE] = bits
1785 if len(bits) != 1:
1786 ifd[SAMPLESPERPIXEL] = len(bits)
1787 if extra is not None:
1788 ifd[EXTRASAMPLES] = extra
1789 if format != 1:
1790 ifd[SAMPLEFORMAT] = format
1791
1792 if PHOTOMETRIC_INTERPRETATION not in ifd:
1793 ifd[PHOTOMETRIC_INTERPRETATION] = photo
1794 elif im.mode in ("1", "L") and ifd[PHOTOMETRIC_INTERPRETATION] == 0:
1795 if im.mode == "1":
1796 inverted_im = im.copy()
1797 px = inverted_im.load()
1798 if px is not None:
1799 for y in range(inverted_im.height):
1800 for x in range(inverted_im.width):
1801 px[x, y] = 0 if px[x, y] == 255 else 255
1802 im = inverted_im
1803 else:
1804 im = ImageOps.invert(im)
1805
1806 if im.mode in ["P", "PA"]:
1807 lut = im.im.getpalette("RGB", "RGB;L")
1808 colormap = []
1809 colors = len(lut) // 3
1810 for i in range(3):
1811 colormap += [v * 256 for v in lut[colors * i : colors * (i + 1)]]
1812 colormap += [0] * (256 - colors)
1813 ifd[COLORMAP] = colormap
1814 # data orientation
1815 w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH]
1816 stride = len(bits) * ((w * bits[0] + 7) // 8)
1817 if ROWSPERSTRIP not in ifd:
1818 # aim for given strip size (64 KB by default) when using libtiff writer
1819 if libtiff:
1820 im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
1821 rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h)
1822 # JPEG encoder expects multiple of 8 rows
1823 if compression == "jpeg":
1824 rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h)
1825 else:
1826 rows_per_strip = h
1827 if rows_per_strip == 0:
1828 rows_per_strip = 1
1829 ifd[ROWSPERSTRIP] = rows_per_strip
1830 strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP]
1831 strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
1832 if strip_byte_counts >= 2**16:
1833 ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
1834 ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
1835 stride * h - strip_byte_counts * (strips_per_image - 1),
1836 )
1837 ifd[STRIPOFFSETS] = tuple(
1838 range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
1839 ) # this is adjusted by IFD writer
1840 # no compression by default:
1841 ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
1842
1843 if im.mode == "YCbCr":
1844 for tag, default_value in {
1845 YCBCRSUBSAMPLING: (1, 1),
1846 REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255),
1847 }.items():
1848 ifd.setdefault(tag, default_value)
1849
1850 blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS]
1851 if libtiff:
1852 if "quality" in encoderinfo:
1853 quality = encoderinfo["quality"]
1854 if not isinstance(quality, int) or quality < 0 or quality > 100:
1855 msg = "Invalid quality setting"
1856 raise ValueError(msg)
1857 if compression != "jpeg":
1858 msg = "quality setting only supported for 'jpeg' compression"
1859 raise ValueError(msg)
1860 ifd[JPEGQUALITY] = quality
1861
1862 logger.debug("Saving using libtiff encoder")
1863 logger.debug("Items: %s", sorted(ifd.items()))
1864 _fp = 0
1865 if hasattr(fp, "fileno"):
1866 try:
1867 fp.seek(0)
1868 _fp = fp.fileno()
1869 except io.UnsupportedOperation:
1870 pass
1871
1872 # optional types for non core tags
1873 types = {}
1874 # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library
1875 # based on the data in the strip.
1876 # OSUBFILETYPE is deprecated.
1877 # The other tags expect arrays with a certain length (fixed or depending on
1878 # BITSPERSAMPLE, etc), passing arrays with a different length will result in
1879 # segfaults. Block these tags until we add extra validation.
1880 # SUBIFD may also cause a segfault.
1881 blocklist += [
1882 OSUBFILETYPE,
1883 REFERENCEBLACKWHITE,
1884 STRIPBYTECOUNTS,
1885 STRIPOFFSETS,
1886 TRANSFERFUNCTION,
1887 SUBIFD,
1888 ]
1889
1890 # bits per sample is a single short in the tiff directory, not a list.
1891 atts: dict[int, Any] = {BITSPERSAMPLE: bits[0]}
1892 # Merge the ones that we have with (optional) more bits from
1893 # the original file, e.g x,y resolution so that we can
1894 # save(load('')) == original file.
1895 for tag, value in itertools.chain(ifd.items(), supplied_tags.items()):
1896 # Libtiff can only process certain core items without adding
1897 # them to the custom dictionary.
1898 # Custom items are supported for int, float, unicode, string and byte
1899 # values. Other types and tuples require a tagtype.
1900 if tag not in TiffTags.LIBTIFF_CORE:
1901 if not getattr(Image.core, "libtiff_support_custom_tags", False):
1902 continue
1903
1904 if tag in ifd.tagtype:
1905 types[tag] = ifd.tagtype[tag]
1906 elif not (isinstance(value, (int, float, str, bytes))):
1907 continue
1908 else:
1909 type = TiffTags.lookup(tag).type
1910 if type:
1911 types[tag] = type
1912 if tag not in atts and tag not in blocklist:
1913 if isinstance(value, str):
1914 atts[tag] = value.encode("ascii", "replace") + b"\0"
1915 elif isinstance(value, IFDRational):
1916 atts[tag] = float(value)
1917 else:
1918 atts[tag] = value
1919
1920 if SAMPLEFORMAT in atts and len(atts[SAMPLEFORMAT]) == 1:
1921 atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0]
1922
1923 logger.debug("Converted items: %s", sorted(atts.items()))
1924
1925 # libtiff always expects the bytes in native order.
1926 # we're storing image byte order. So, if the rawmode
1927 # contains I;16, we need to convert from native to image
1928 # byte order.
1929 if im.mode in ("I;16B", "I;16"):
1930 rawmode = "I;16N"
1931
1932 # Pass tags as sorted list so that the tags are set in a fixed order.
1933 # This is required by libtiff for some tags. For example, the JPEGQUALITY
1934 # pseudo tag requires that the COMPRESS tag was already set.
1935 tags = list(atts.items())
1936 tags.sort()
1937 a = (rawmode, compression, _fp, filename, tags, types)
1938 encoder = Image._getencoder(im.mode, "libtiff", a, encoderconfig)
1939 encoder.setimage(im.im, (0, 0) + im.size)
1940 while True:
1941 errcode, data = encoder.encode(ImageFile.MAXBLOCK)[1:]
1942 if not _fp:
1943 fp.write(data)
1944 if errcode:
1945 break
1946 if errcode < 0:
1947 msg = f"encoder error {errcode} when writing image file"
1948 raise OSError(msg)
1949
1950 else:
1951 for tag in blocklist:
1952 del ifd[tag]
1953 offset = ifd.save(fp)
1954
1955 ImageFile._save(
1956 im,
1957 fp,
1958 [ImageFile._Tile("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))],
1959 )
1960
1961 # -- helper for multi-page save --
1962 if "_debug_multipage" in encoderinfo:
1963 # just to access o32 and o16 (using correct byte order)
1964 setattr(im, "_debug_multipage", ifd)
1965
1966
1967class AppendingTiffWriter(io.BytesIO):
1968 fieldSizes = [
1969 0, # None
1970 1, # byte
1971 1, # ascii
1972 2, # short
1973 4, # long
1974 8, # rational
1975 1, # sbyte
1976 1, # undefined
1977 2, # sshort
1978 4, # slong
1979 8, # srational
1980 4, # float
1981 8, # double
1982 4, # ifd
1983 2, # unicode
1984 4, # complex
1985 8, # long8
1986 ]
1987
1988 Tags = {
1989 273, # StripOffsets
1990 288, # FreeOffsets
1991 324, # TileOffsets
1992 519, # JPEGQTables
1993 520, # JPEGDCTables
1994 521, # JPEGACTables
1995 }
1996
1997 def __init__(self, fn: StrOrBytesPath | IO[bytes], new: bool = False) -> None:
1998 self.f: IO[bytes]
1999 if is_path(fn):
2000 self.name = fn
2001 self.close_fp = True
2002 try:
2003 self.f = open(fn, "w+b" if new else "r+b")
2004 except OSError:
2005 self.f = open(fn, "w+b")
2006 else:
2007 self.f = cast(IO[bytes], fn)
2008 self.close_fp = False
2009 self.beginning = self.f.tell()
2010 self.setup()
2011
2012 def setup(self) -> None:
2013 # Reset everything.
2014 self.f.seek(self.beginning, os.SEEK_SET)
2015
2016 self.whereToWriteNewIFDOffset: int | None = None
2017 self.offsetOfNewPage = 0
2018
2019 self.IIMM = iimm = self.f.read(4)
2020 if not iimm:
2021 # empty file - first page
2022 self.isFirst = True
2023 return
2024
2025 self.isFirst = False
2026 if iimm == b"II\x2a\x00":
2027 self.setEndian("<")
2028 elif iimm == b"MM\x00\x2a":
2029 self.setEndian(">")
2030 else:
2031 msg = "Invalid TIFF file header"
2032 raise RuntimeError(msg)
2033
2034 self.skipIFDs()
2035 self.goToEnd()
2036
2037 def finalize(self) -> None:
2038 if self.isFirst:
2039 return
2040
2041 # fix offsets
2042 self.f.seek(self.offsetOfNewPage)
2043
2044 iimm = self.f.read(4)
2045 if not iimm:
2046 # Make it easy to finish a frame without committing to a new one.
2047 return
2048
2049 if iimm != self.IIMM:
2050 msg = "IIMM of new page doesn't match IIMM of first page"
2051 raise RuntimeError(msg)
2052
2053 ifd_offset = self.readLong()
2054 ifd_offset += self.offsetOfNewPage
2055 assert self.whereToWriteNewIFDOffset is not None
2056 self.f.seek(self.whereToWriteNewIFDOffset)
2057 self.writeLong(ifd_offset)
2058 self.f.seek(ifd_offset)
2059 self.fixIFD()
2060
2061 def newFrame(self) -> None:
2062 # Call this to finish a frame.
2063 self.finalize()
2064 self.setup()
2065
2066 def __enter__(self) -> AppendingTiffWriter:
2067 return self
2068
2069 def __exit__(self, *args: object) -> None:
2070 if self.close_fp:
2071 self.close()
2072
2073 def tell(self) -> int:
2074 return self.f.tell() - self.offsetOfNewPage
2075
2076 def seek(self, offset: int, whence: int = io.SEEK_SET) -> int:
2077 """
2078 :param offset: Distance to seek.
2079 :param whence: Whether the distance is relative to the start,
2080 end or current position.
2081 :returns: The resulting position, relative to the start.
2082 """
2083 if whence == os.SEEK_SET:
2084 offset += self.offsetOfNewPage
2085
2086 self.f.seek(offset, whence)
2087 return self.tell()
2088
2089 def goToEnd(self) -> None:
2090 self.f.seek(0, os.SEEK_END)
2091 pos = self.f.tell()
2092
2093 # pad to 16 byte boundary
2094 pad_bytes = 16 - pos % 16
2095 if 0 < pad_bytes < 16:
2096 self.f.write(bytes(pad_bytes))
2097 self.offsetOfNewPage = self.f.tell()
2098
2099 def setEndian(self, endian: str) -> None:
2100 self.endian = endian
2101 self.longFmt = f"{self.endian}L"
2102 self.shortFmt = f"{self.endian}H"
2103 self.tagFormat = f"{self.endian}HHL"
2104
2105 def skipIFDs(self) -> None:
2106 while True:
2107 ifd_offset = self.readLong()
2108 if ifd_offset == 0:
2109 self.whereToWriteNewIFDOffset = self.f.tell() - 4
2110 break
2111
2112 self.f.seek(ifd_offset)
2113 num_tags = self.readShort()
2114 self.f.seek(num_tags * 12, os.SEEK_CUR)
2115
2116 def write(self, data: Buffer, /) -> int:
2117 return self.f.write(data)
2118
2119 def _fmt(self, field_size: int) -> str:
2120 try:
2121 return {2: "H", 4: "L", 8: "Q"}[field_size]
2122 except KeyError:
2123 msg = "offset is not supported"
2124 raise RuntimeError(msg)
2125
2126 def _read(self, field_size: int) -> int:
2127 (value,) = struct.unpack(
2128 self.endian + self._fmt(field_size), self.f.read(field_size)
2129 )
2130 return value
2131
2132 def readShort(self) -> int:
2133 return self._read(2)
2134
2135 def readLong(self) -> int:
2136 return self._read(4)
2137
2138 @staticmethod
2139 def _verify_bytes_written(bytes_written: int | None, expected: int) -> None:
2140 if bytes_written is not None and bytes_written != expected:
2141 msg = f"wrote only {bytes_written} bytes but wanted {expected}"
2142 raise RuntimeError(msg)
2143
2144 def rewriteLastShortToLong(self, value: int) -> None:
2145 self.f.seek(-2, os.SEEK_CUR)
2146 bytes_written = self.f.write(struct.pack(self.longFmt, value))
2147 self._verify_bytes_written(bytes_written, 4)
2148
2149 def _rewriteLast(self, value: int, field_size: int) -> None:
2150 self.f.seek(-field_size, os.SEEK_CUR)
2151 bytes_written = self.f.write(
2152 struct.pack(self.endian + self._fmt(field_size), value)
2153 )
2154 self._verify_bytes_written(bytes_written, field_size)
2155
2156 def rewriteLastShort(self, value: int) -> None:
2157 return self._rewriteLast(value, 2)
2158
2159 def rewriteLastLong(self, value: int) -> None:
2160 return self._rewriteLast(value, 4)
2161
2162 def writeShort(self, value: int) -> None:
2163 bytes_written = self.f.write(struct.pack(self.shortFmt, value))
2164 self._verify_bytes_written(bytes_written, 2)
2165
2166 def writeLong(self, value: int) -> None:
2167 bytes_written = self.f.write(struct.pack(self.longFmt, value))
2168 self._verify_bytes_written(bytes_written, 4)
2169
2170 def close(self) -> None:
2171 self.finalize()
2172 if self.close_fp:
2173 self.f.close()
2174
2175 def fixIFD(self) -> None:
2176 num_tags = self.readShort()
2177
2178 for i in range(num_tags):
2179 tag, field_type, count = struct.unpack(self.tagFormat, self.f.read(8))
2180
2181 field_size = self.fieldSizes[field_type]
2182 total_size = field_size * count
2183 is_local = total_size <= 4
2184 if not is_local:
2185 offset = self.readLong() + self.offsetOfNewPage
2186 self.rewriteLastLong(offset)
2187
2188 if tag in self.Tags:
2189 cur_pos = self.f.tell()
2190
2191 if is_local:
2192 self._fixOffsets(count, field_size)
2193 self.f.seek(cur_pos + 4)
2194 else:
2195 self.f.seek(offset)
2196 self._fixOffsets(count, field_size)
2197 self.f.seek(cur_pos)
2198
2199 elif is_local:
2200 # skip the locally stored value that is not an offset
2201 self.f.seek(4, os.SEEK_CUR)
2202
2203 def _fixOffsets(self, count: int, field_size: int) -> None:
2204 for i in range(count):
2205 offset = self._read(field_size)
2206 offset += self.offsetOfNewPage
2207 if field_size == 2 and offset >= 65536:
2208 # offset is now too large - we must convert shorts to longs
2209 if count != 1:
2210 msg = "not implemented"
2211 raise RuntimeError(msg) # XXX TODO
2212
2213 # simple case - the offset is just one and therefore it is
2214 # local (not referenced with another offset)
2215 self.rewriteLastShortToLong(offset)
2216 self.f.seek(-10, os.SEEK_CUR)
2217 self.writeShort(TiffTags.LONG) # rewrite the type to LONG
2218 self.f.seek(8, os.SEEK_CUR)
2219 else:
2220 self._rewriteLast(offset, field_size)
2221
2222 def fixOffsets(
2223 self, count: int, isShort: bool = False, isLong: bool = False
2224 ) -> None:
2225 if isShort:
2226 field_size = 2
2227 elif isLong:
2228 field_size = 4
2229 else:
2230 field_size = 0
2231 return self._fixOffsets(count, field_size)
2232
2233
2234def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
2235 encoderinfo = im.encoderinfo.copy()
2236 encoderconfig = im.encoderconfig
2237 append_images = list(encoderinfo.get("append_images", []))
2238 if not hasattr(im, "n_frames") and not append_images:
2239 return _save(im, fp, filename)
2240
2241 cur_idx = im.tell()
2242 try:
2243 with AppendingTiffWriter(fp) as tf:
2244 for ims in [im] + append_images:
2245 ims.encoderinfo = encoderinfo
2246 ims.encoderconfig = encoderconfig
2247 if not hasattr(ims, "n_frames"):
2248 nfr = 1
2249 else:
2250 nfr = ims.n_frames
2251
2252 for idx in range(nfr):
2253 ims.seek(idx)
2254 ims.load()
2255 _save(ims, tf, filename)
2256 tf.newFrame()
2257 finally:
2258 im.seek(cur_idx)
2259
2260
2261#
2262# --------------------------------------------------------------------
2263# Register
2264
2265Image.register_open(TiffImageFile.format, TiffImageFile, _accept)
2266Image.register_save(TiffImageFile.format, _save)
2267Image.register_save_all(TiffImageFile.format, _save_all)
2268
2269Image.register_extensions(TiffImageFile.format, [".tif", ".tiff"])
2270
2271Image.register_mime(TiffImageFile.format, "image/tiff")