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