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