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

1296 statements  

1# 

2# The Python Imaging Library. 

3# $Id$ 

4# 

5# TIFF file handling 

6# 

7# TIFF is a flexible, if somewhat aged, image file format originally 

8# defined by Aldus. Although TIFF supports a wide variety of pixel 

9# layouts and compression methods, the name doesn't really stand for 

10# "thousands of incompatible file formats," it just feels that way. 

11# 

12# To read TIFF data from a stream, the stream must be seekable. For 

13# progressive decoding, make sure to use TIFF files where the tag 

14# directory is placed first in the file. 

15# 

16# History: 

17# 1995-09-01 fl Created 

18# 1996-05-04 fl Handle JPEGTABLES tag 

19# 1996-05-18 fl Fixed COLORMAP support 

20# 1997-01-05 fl Fixed PREDICTOR support 

21# 1997-08-27 fl Added support for rational tags (from Perry Stoll) 

22# 1998-01-10 fl Fixed seek/tell (from Jan Blom) 

23# 1998-07-15 fl Use private names for internal variables 

24# 1999-06-13 fl Rewritten for PIL 1.0 (1.0) 

25# 2000-10-11 fl Additional fixes for Python 2.0 (1.1) 

26# 2001-04-17 fl Fixed rewind support (seek to frame 0) (1.2) 

27# 2001-05-12 fl Added write support for more tags (from Greg Couch) (1.3) 

28# 2001-12-18 fl Added workaround for broken Matrox library 

29# 2002-01-18 fl Don't mess up if photometric tag is missing (D. Alan Stewart) 

30# 2003-05-19 fl Check FILLORDER tag 

31# 2003-09-26 fl Added RGBa support 

32# 2004-02-24 fl Added DPI support; fixed rational write support 

33# 2005-02-07 fl Added workaround for broken Corel Draw 10 files 

34# 2006-01-09 fl Added support for float/double tags (from Russell Nelson) 

35# 

36# Copyright (c) 1997-2006 by Secret Labs AB. All rights reserved. 

37# Copyright (c) 1995-1997 by Fredrik Lundh 

38# 

39# See the README file for information on usage and redistribution. 

40# 

41from __future__ import annotations 

42 

43__lazy_modules__ = { 

44 "PIL._binary", 

45 "PIL._util", 

46 "fractions", 

47 "itertools", 

48 "math", 

49 "struct", 

50 "warnings", 

51} 

52 

53import io 

54import itertools 

55import logging 

56import math 

57import os 

58import struct 

59import warnings 

60from collections.abc import Callable, MutableMapping 

61from fractions import Fraction 

62from numbers import Number, Rational 

63from typing import IO, Any, cast 

64 

65from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags 

66from ._binary import i16be as i16 

67from ._binary import i32be as i32 

68from ._util import DeferredError, is_path 

69from .TiffTags import TYPES 

70 

71TYPE_CHECKING = False 

72if TYPE_CHECKING: 

73 from collections.abc import Iterator 

74 from typing import NoReturn, Self 

75 

76 from ._typing import Buffer, IntegralLike, StrOrBytesPath 

77 

78logger = logging.getLogger(__name__) 

79 

80# Set these to true to force use of libtiff for reading or writing. 

81READ_LIBTIFF = False 

82WRITE_LIBTIFF = False 

83STRIP_SIZE = 65536 

84 

85II = b"II" # little-endian (Intel style) 

86MM = b"MM" # big-endian (Motorola style) 

87 

88# 

89# -------------------------------------------------------------------- 

90# Read TIFF files 

91 

92# a few tag names, just to make the code below a bit more readable 

93OSUBFILETYPE = 255 

94IMAGEWIDTH = 256 

95IMAGELENGTH = 257 

96BITSPERSAMPLE = 258 

97COMPRESSION = 259 

98PHOTOMETRIC_INTERPRETATION = 262 

99FILLORDER = 266 

100IMAGEDESCRIPTION = 270 

101STRIPOFFSETS = 273 

102SAMPLESPERPIXEL = 277 

103ROWSPERSTRIP = 278 

104STRIPBYTECOUNTS = 279 

105X_RESOLUTION = 282 

106Y_RESOLUTION = 283 

107PLANAR_CONFIGURATION = 284 

108RESOLUTION_UNIT = 296 

109TRANSFERFUNCTION = 301 

110SOFTWARE = 305 

111DATE_TIME = 306 

112ARTIST = 315 

113PREDICTOR = 317 

114COLORMAP = 320 

115TILEWIDTH = 322 

116TILELENGTH = 323 

117TILEOFFSETS = 324 

118TILEBYTECOUNTS = 325 

119SUBIFD = 330 

120EXTRASAMPLES = 338 

121SAMPLEFORMAT = 339 

122JPEGTABLES = 347 

123YCBCRSUBSAMPLING = 530 

124REFERENCEBLACKWHITE = 532 

125COPYRIGHT = 33432 

126IPTC_NAA_CHUNK = 33723 # newsphoto properties 

127PHOTOSHOP_CHUNK = 34377 # photoshop properties 

128ICCPROFILE = 34675 

129EXIFIFD = 34665 

130XMP = 700 

131JPEGQUALITY = 65537 # pseudo-tag by libtiff 

132 

133# https://github.com/imagej/ImageJ/blob/master/ij/io/TiffDecoder.java 

134IMAGEJ_META_DATA_BYTE_COUNTS = 50838 

135IMAGEJ_META_DATA = 50839 

136 

137COMPRESSION_INFO = { 

138 # Compression => pil compression name 

139 1: "raw", 

140 2: "tiff_ccitt", 

141 3: "group3", 

142 4: "group4", 

143 5: "tiff_lzw", 

144 6: "tiff_jpeg", # obsolete 

145 7: "jpeg", 

146 8: "tiff_adobe_deflate", 

147 32771: "tiff_raw_16", # 16-bit padding 

148 32773: "packbits", 

149 32809: "tiff_thunderscan", 

150 32946: "tiff_deflate", 

151 34676: "tiff_sgilog", 

152 34677: "tiff_sgilog24", 

153 34925: "lzma", 

154 50000: "zstd", 

155 50001: "webp", 

156} 

157 

158COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()} 

159 

160OPEN_INFO = { 

161 # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample, 

162 # ExtraSamples) => mode, rawmode 

163 (II, 0, (1,), 1, (1,), ()): ("1", "1;I"), 

164 (MM, 0, (1,), 1, (1,), ()): ("1", "1;I"), 

165 (II, 0, (1,), 2, (1,), ()): ("1", "1;IR"), 

166 (MM, 0, (1,), 2, (1,), ()): ("1", "1;IR"), 

167 (II, 1, (1,), 1, (1,), ()): ("1", "1"), 

168 (MM, 1, (1,), 1, (1,), ()): ("1", "1"), 

169 (II, 1, (1,), 2, (1,), ()): ("1", "1;R"), 

170 (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"), 

171 (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"), 

172 (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"), 

173 (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), 

174 (MM, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), 

175 (II, 1, (1,), 1, (2,), ()): ("L", "L;2"), 

176 (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"), 

177 (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"), 

178 (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"), 

179 (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"), 

180 (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"), 

181 (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), 

182 (MM, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), 

183 (II, 1, (1,), 1, (4,), ()): ("L", "L;4"), 

184 (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"), 

185 (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"), 

186 (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"), 

187 (II, 0, (1,), 1, (8,), ()): ("L", "L;I"), 

188 (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"), 

189 (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"), 

190 (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"), 

191 (II, 1, (1,), 1, (8,), ()): ("L", "L"), 

192 (MM, 1, (1,), 1, (8,), ()): ("L", "L"), 

193 (II, 1, (2,), 1, (8,), ()): ("L", "L"), 

194 (MM, 1, (2,), 1, (8,), ()): ("L", "L"), 

195 (II, 1, (1,), 2, (8,), ()): ("L", "L;R"), 

196 (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), 

197 (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), 

198 (II, 0, (1,), 1, (16,), ()): ("I;16", "I;16"), 

199 (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), 

200 (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), 

201 (II, 1, (1,), 2, (16,), ()): ("I;16", "I;16R"), 

202 (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"), 

203 (MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"), 

204 (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"), 

205 (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"), 

206 (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"), 

207 (II, 1, (2,), 1, (32,), ()): ("I", "I;32S"), 

208 (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"), 

209 (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"), 

210 (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"), 

211 (II, 1, (1,), 1, (8, 8), (0,)): ("L", "LX"), 

212 (MM, 1, (1,), 1, (8, 8), (0,)): ("L", "LX"), 

213 (II, 1, (1,), 1, (8, 8), (1,)): ("La", "La"), 

214 (MM, 1, (1,), 1, (8, 8), (1,)): ("La", "La"), 

215 (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), 

216 (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), 

217 (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), 

218 (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), 

219 (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"), 

220 (MM, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"), 

221 (II, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples 

222 (MM, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"), # missing ExtraSamples 

223 (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"), 

224 (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGB", "RGBX"), 

225 (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"), 

226 (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGB", "RGBXX"), 

227 (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"), 

228 (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGB", "RGBXXX"), 

229 (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), 

230 (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), 

231 (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"), 

232 (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"), 

233 (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"), 

234 (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"), 

235 (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), 

236 (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), 

237 (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"), 

238 (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"), 

239 (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), 

240 (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), 

241 (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 

242 (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 

243 (II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"), 

244 (MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"), 

245 (II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"), 

246 (MM, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16B"), 

247 (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16L"), 

248 (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGB", "RGBX;16B"), 

249 (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16L"), 

250 (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"), 

251 (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"), 

252 (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"), 

253 (II, 3, (1,), 1, (1,), ()): ("P", "P;1"), 

254 (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"), 

255 (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"), 

256 (MM, 3, (1,), 2, (1,), ()): ("P", "P;1R"), 

257 (II, 3, (1,), 1, (2,), ()): ("P", "P;2"), 

258 (MM, 3, (1,), 1, (2,), ()): ("P", "P;2"), 

259 (II, 3, (1,), 2, (2,), ()): ("P", "P;2R"), 

260 (MM, 3, (1,), 2, (2,), ()): ("P", "P;2R"), 

261 (II, 3, (1,), 1, (4,), ()): ("P", "P;4"), 

262 (MM, 3, (1,), 1, (4,), ()): ("P", "P;4"), 

263 (II, 3, (1,), 2, (4,), ()): ("P", "P;4R"), 

264 (MM, 3, (1,), 2, (4,), ()): ("P", "P;4R"), 

265 (II, 3, (1,), 1, (8,), ()): ("P", "P"), 

266 (MM, 3, (1,), 1, (8,), ()): ("P", "P"), 

267 (II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"), 

268 (MM, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"), 

269 (II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), 

270 (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), 

271 (II, 3, (1,), 2, (8,), ()): ("P", "P;R"), 

272 (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"), 

273 (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), 

274 (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), 

275 (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), 

276 (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"), 

277 (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), 

278 (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"), 

279 (II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"), 

280 (MM, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16B"), 

281 (II, 6, (1,), 1, (8,), ()): ("L", "L"), 

282 (MM, 6, (1,), 1, (8,), ()): ("L", "L"), 

283 (II, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"), 

284 (MM, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"), 

285 (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), 

286 (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), 

287} 

288 

289MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO) 

290 

291PREFIXES = [ 

292 b"MM\x00\x2a", # Valid TIFF header with big-endian byte order 

293 b"II\x2a\x00", # Valid TIFF header with little-endian byte order 

294 b"MM\x2a\x00", # Invalid TIFF header, assume big-endian 

295 b"II\x00\x2a", # Invalid TIFF header, assume little-endian 

296 b"MM\x00\x2b", # BigTIFF with big-endian byte order 

297 b"II\x2b\x00", # BigTIFF with little-endian byte order 

298] 

299 

300 

301def _accept(prefix: bytes) -> bool: 

302 return prefix.startswith(tuple(PREFIXES)) 

303 

304 

305def _limit_rational( 

306 val: float | Fraction | IFDRational, max_val: int 

307) -> tuple[IntegralLike, IntegralLike]: 

308 inv = abs(val) > 1 

309 n_d = IFDRational(1 / val if inv else val).limit_rational(max_val) 

310 return n_d[::-1] if inv else n_d 

311 

312 

313def _limit_signed_rational( 

314 val: IFDRational, max_val: int, min_val: int 

315) -> tuple[IntegralLike, IntegralLike]: 

316 frac = Fraction(val) 

317 n_d: tuple[IntegralLike, IntegralLike] = frac.numerator, frac.denominator 

318 

319 if min(float(i) for i in n_d) < min_val: 

320 n_d = _limit_rational(val, abs(min_val)) 

321 

322 n_d_float = tuple(float(i) for i in n_d) 

323 if max(n_d_float) > max_val: 

324 n_d = _limit_rational(n_d_float[0] / n_d_float[1], max_val) 

325 

326 return n_d 

327 

328 

329## 

330# Wrapper for TIFF IFDs. 

331 

332_load_dispatch: dict[int, tuple[int, _LoaderFunc]] = {} 

333_write_dispatch: dict[int, Callable[..., Any]] = {} 

334 

335 

336def _delegate(op: str) -> Any: 

337 def delegate( 

338 self: IFDRational, *args: tuple[float, ...] 

339 ) -> bool | float | Fraction: 

340 func = getattr(self._val, op) 

341 if len(args) == 1 and isinstance(args[0], IFDRational): 

342 return func(args[0]._val) 

343 return func(*args) 

344 

345 return delegate 

346 

347 

348class IFDRational(Rational): 

349 """Implements a rational class where 0/0 is a legal value to match 

350 the in the wild use of exif rationals. 

351 

352 e.g., DigitalZoomRatio - 0.00/0.00 indicates that no digital zoom was used 

353 """ 

354 

355 """ If the denominator is 0, store this as a float('nan'), otherwise store 

356 as a fractions.Fraction(). Delegate as appropriate 

357 

358 """ 

359 

360 __slots__ = ("_numerator", "_denominator", "_val") 

361 

362 def __init__( 

363 self, value: float | Fraction | IFDRational, denominator: int = 1 

364 ) -> None: 

365 """ 

366 :param value: either an integer numerator, a 

367 float/rational/other number, or an IFDRational 

368 :param denominator: Optional integer denominator 

369 """ 

370 self._val: Fraction | float 

371 if isinstance(value, IFDRational): 

372 self._numerator = value.numerator 

373 self._denominator = value.denominator 

374 self._val = value._val 

375 return 

376 

377 if isinstance(value, Fraction): 

378 self._numerator = value.numerator 

379 self._denominator = value.denominator 

380 else: 

381 if TYPE_CHECKING: 

382 self._numerator = cast("IntegralLike", value) 

383 else: 

384 self._numerator = value 

385 self._denominator = denominator 

386 

387 if denominator == 0: 

388 self._val = float("nan") 

389 elif denominator == 1: 

390 self._val = Fraction(value) 

391 elif int(value) == value: 

392 self._val = Fraction(int(value), denominator) 

393 else: 

394 self._val = Fraction(value / denominator) 

395 

396 @property 

397 def numerator(self) -> IntegralLike: 

398 return self._numerator 

399 

400 @property 

401 def denominator(self) -> int: 

402 return self._denominator 

403 

404 def limit_rational(self, max_denominator: int) -> tuple[IntegralLike, int]: 

405 """ 

406 

407 :param max_denominator: Integer, the maximum denominator value 

408 :returns: Tuple of (numerator, denominator) 

409 """ 

410 

411 if self.denominator == 0: 

412 return self.numerator, self.denominator 

413 

414 assert isinstance(self._val, Fraction) 

415 f = self._val.limit_denominator(max_denominator) 

416 return f.numerator, f.denominator 

417 

418 def __repr__(self) -> str: 

419 return str(float(self._val)) 

420 

421 def __hash__(self) -> int: # type: ignore[override] 

422 return self._val.__hash__() 

423 

424 def __eq__(self, other: object) -> bool: 

425 val = self._val 

426 if isinstance(other, IFDRational): 

427 other = other._val 

428 if isinstance(other, float): 

429 val = float(val) 

430 return val == other 

431 

432 def __getstate__(self) -> list[float | Fraction | IntegralLike]: 

433 return [self._val, self._numerator, self._denominator] 

434 

435 def __setstate__(self, state: list[float | Fraction | IntegralLike]) -> None: 

436 IFDRational.__init__(self, 0) 

437 _val, _numerator, _denominator = state 

438 assert isinstance(_val, (float, Fraction)) 

439 self._val = _val 

440 if TYPE_CHECKING: 

441 self._numerator = cast("IntegralLike", _numerator) 

442 else: 

443 self._numerator = _numerator 

444 assert isinstance(_denominator, int) 

445 self._denominator = _denominator 

446 

447 """ a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul', 

448 'truediv', 'rtruediv', 'floordiv', 'rfloordiv', 

449 'mod','rmod', 'pow','rpow', 'pos', 'neg', 

450 'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool', 

451 'ceil', 'floor', 'round'] 

452 print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a)) 

453 """ 

454 

455 __add__ = _delegate("__add__") 

456 __radd__ = _delegate("__radd__") 

457 __sub__ = _delegate("__sub__") 

458 __rsub__ = _delegate("__rsub__") 

459 __mul__ = _delegate("__mul__") 

460 __rmul__ = _delegate("__rmul__") 

461 __truediv__ = _delegate("__truediv__") 

462 __rtruediv__ = _delegate("__rtruediv__") 

463 __floordiv__ = _delegate("__floordiv__") 

464 __rfloordiv__ = _delegate("__rfloordiv__") 

465 __mod__ = _delegate("__mod__") 

466 __rmod__ = _delegate("__rmod__") 

467 __pow__ = _delegate("__pow__") 

468 __rpow__ = _delegate("__rpow__") 

469 __pos__ = _delegate("__pos__") 

470 __neg__ = _delegate("__neg__") 

471 __abs__ = _delegate("__abs__") 

472 __trunc__ = _delegate("__trunc__") 

473 __lt__ = _delegate("__lt__") 

474 __gt__ = _delegate("__gt__") 

475 __le__ = _delegate("__le__") 

476 __ge__ = _delegate("__ge__") 

477 __bool__ = _delegate("__bool__") 

478 __ceil__ = _delegate("__ceil__") 

479 __floor__ = _delegate("__floor__") 

480 __round__ = _delegate("__round__") 

481 __float__ = _delegate("__float__") 

482 __int__ = _delegate("__int__") 

483 

484 

485_LoaderFunc = Callable[["ImageFileDirectory_v2", bytes, bool], Any] 

486 

487 

488def _register_loader(idx: int, size: int) -> Callable[[_LoaderFunc], _LoaderFunc]: 

489 def decorator(func: _LoaderFunc) -> _LoaderFunc: 

490 from .TiffTags import TYPES 

491 

492 if func.__name__.startswith("load_"): 

493 TYPES[idx] = func.__name__[5:].replace("_", " ") 

494 _load_dispatch[idx] = size, func # noqa: F821 

495 return func 

496 

497 return decorator 

498 

499 

500def _register_writer(idx: int) -> Callable[[Callable[..., Any]], Callable[..., Any]]: 

501 def decorator(func: Callable[..., Any]) -> Callable[..., Any]: 

502 _write_dispatch[idx] = func # noqa: F821 

503 return func 

504 

505 return decorator 

506 

507 

508def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None: 

509 from .TiffTags import TYPES 

510 

511 idx, fmt, name = idx_fmt_name 

512 TYPES[idx] = name 

513 size = struct.calcsize(f"={fmt}") 

514 

515 def basic_handler( 

516 self: ImageFileDirectory_v2, data: bytes, legacy_api: bool = True 

517 ) -> tuple[Any, ...]: 

518 return self._unpack(f"{len(data) // size}{fmt}", data) 

519 

520 _load_dispatch[idx] = size, basic_handler # noqa: F821 

521 _write_dispatch[idx] = lambda self, *values: ( # noqa: F821 

522 b"".join(self._pack(fmt, value) for value in values) 

523 ) 

524 

525 

526if TYPE_CHECKING: 

527 _IFDv2Base = MutableMapping[int, Any] 

528else: 

529 _IFDv2Base = MutableMapping 

530 

531 

532class ImageFileDirectory_v2(_IFDv2Base): 

533 """This class represents a TIFF tag directory. To speed things up, we 

534 don't decode tags unless they're asked for. 

535 

536 Exposes a dictionary interface of the tags in the directory:: 

537 

538 ifd = ImageFileDirectory_v2() 

539 ifd[key] = 'Some Data' 

540 ifd.tagtype[key] = TiffTags.ASCII 

541 print(ifd[key]) 

542 'Some Data' 

543 

544 Individual values are returned as the strings or numbers, sequences are 

545 returned as tuples of the values. 

546 

547 The tiff metadata type of each item is stored in a dictionary of 

548 tag types in 

549 :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types 

550 are read from a tiff file, guessed from the type added, or added 

551 manually. 

552 

553 Data Structures: 

554 

555 * ``self.tagtype = {}`` 

556 

557 * Key: numerical TIFF tag number 

558 * Value: integer corresponding to the data type from 

559 :py:data:`.TiffTags.TYPES` 

560 

561 .. versionadded:: 3.0.0 

562 

563 'Internal' data structures: 

564 

565 * ``self._tags_v2 = {}`` 

566 

567 * Key: numerical TIFF tag number 

568 * Value: decoded data, as tuple for multiple values 

569 

570 * ``self._tagdata = {}`` 

571 

572 * Key: numerical TIFF tag number 

573 * Value: undecoded byte string from file 

574 

575 * ``self._tags_v1 = {}`` 

576 

577 * Key: numerical TIFF tag number 

578 * Value: decoded data in the v1 format 

579 

580 Tags will be found in the private attributes ``self._tagdata``, and in 

581 ``self._tags_v2`` once decoded. 

582 

583 ``self.legacy_api`` is a value for internal use, and shouldn't be changed 

584 from outside code. In cooperation with 

585 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`, if ``legacy_api`` 

586 is true, then decoded tags will be populated into both ``_tags_v1`` and 

587 ``_tags_v2``. ``_tags_v2`` will be used if this IFD is used in the TIFF 

588 save routine. Tags should be read from ``_tags_v1`` if 

589 ``legacy_api == true``. 

590 

591 """ 

592 

593 _load_dispatch: dict[int, tuple[int, _LoaderFunc]] = {} 

594 _write_dispatch: dict[int, Callable[..., Any]] = {} 

595 

596 def __init__( 

597 self, 

598 ifh: bytes = b"II\x2a\x00\x00\x00\x00\x00", 

599 prefix: bytes | None = None, 

600 group: int | None = None, 

601 ) -> None: 

602 """Initialize an ImageFileDirectory. 

603 

604 To construct an ImageFileDirectory from a real file, pass the 8-byte 

605 magic header to the constructor. To only set the endianness, pass it 

606 as the 'prefix' keyword argument. 

607 

608 :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets 

609 endianness. 

610 :param prefix: Override the endianness of the file. 

611 """ 

612 if not _accept(ifh): 

613 msg = f"not a TIFF file (header {repr(ifh)} not valid)" 

614 raise SyntaxError(msg) 

615 self._prefix = prefix if prefix is not None else ifh[:2] 

616 if self._prefix == MM: 

617 self._endian = ">" 

618 elif self._prefix == II: 

619 self._endian = "<" 

620 else: 

621 msg = "not a TIFF IFD" 

622 raise SyntaxError(msg) 

623 self._bigtiff = 43 in ifh[2:4] 

624 self.group = group 

625 self.tagtype: dict[int, int] = {} 

626 """ Dictionary of tag types """ 

627 self.reset() 

628 # Maximum total out-of-line tag data that load() may read, as well 

629 # as the size of the stream. Set by TiffImageFile so that 

630 # all frames of a file share one budget. 

631 self._tag_data_limit: int | None = None 

632 self.next = ( 

633 self._unpack("Q", ifh[8:])[0] 

634 if self._bigtiff 

635 else self._unpack("L", ifh[4:])[0] 

636 ) 

637 self._legacy_api = False 

638 

639 prefix = property(lambda self: self._prefix) 

640 offset = property(lambda self: self._offset) 

641 

642 @property 

643 def legacy_api(self) -> bool: 

644 return self._legacy_api 

645 

646 @legacy_api.setter 

647 def legacy_api(self, value: bool) -> NoReturn: 

648 msg = "Not allowing setting of legacy api" 

649 raise Exception(msg) 

650 

651 def reset(self) -> None: 

652 self._tags_v1: dict[int, Any] = {} # will remain empty if legacy_api is false 

653 self._tags_v2: dict[int, Any] = {} # main tag storage 

654 self._tagdata: dict[int, bytes] = {} 

655 self.tagtype = {} # added 2008-06-05 by Florian Hoech 

656 self._next = None 

657 self._offset: int | None = None 

658 

659 def __str__(self) -> str: 

660 return str(dict(self)) 

661 

662 def named(self) -> dict[str, Any]: 

663 """ 

664 :returns: dict of name|key: value 

665 

666 Returns the complete tag dictionary, with named tags where possible. 

667 """ 

668 return { 

669 TiffTags.lookup(code, self.group).name: value 

670 for code, value in self.items() 

671 } 

672 

673 def __len__(self) -> int: 

674 return len(set(self._tagdata) | set(self._tags_v2)) 

675 

676 def __getitem__(self, tag: int) -> Any: 

677 if tag not in self._tags_v2: # unpack on the fly 

678 data = self._tagdata[tag] 

679 typ = self.tagtype[tag] 

680 size, handler = self._load_dispatch[typ] 

681 self[tag] = handler(self, data, self.legacy_api) # check type 

682 val = self._tags_v2[tag] 

683 if self.legacy_api and not isinstance(val, (tuple, bytes)): 

684 val = (val,) 

685 return val 

686 

687 def __contains__(self, tag: object) -> bool: 

688 return tag in self._tags_v2 or tag in self._tagdata 

689 

690 def __setitem__(self, tag: int, value: Any) -> None: 

691 self._setitem(tag, value, self.legacy_api) 

692 

693 def _setitem(self, tag: int, value: Any, legacy_api: bool) -> None: 

694 basetypes = (Number, bytes, str) 

695 

696 info = TiffTags.lookup(tag, self.group) 

697 values = [value] if isinstance(value, basetypes) else value 

698 

699 if tag not in self.tagtype: 

700 if info.type: 

701 self.tagtype[tag] = info.type 

702 else: 

703 self.tagtype[tag] = TiffTags.UNDEFINED 

704 if all(isinstance(v, IFDRational) for v in values): 

705 for v in values: 

706 value = ( 

707 v.numerator 

708 if isinstance(v, IFDRational) and v.denominator == 0 

709 else v 

710 ) 

711 if value < IFDRational(0): 

712 self.tagtype[tag] = TiffTags.SIGNED_RATIONAL 

713 break 

714 else: 

715 self.tagtype[tag] = TiffTags.RATIONAL 

716 elif all(isinstance(v, int) for v in values): 

717 short = True 

718 signed_short = True 

719 long = True 

720 for v in values: 

721 assert isinstance(v, int) 

722 if short and not (0 <= v < 2**16): 

723 short = False 

724 if signed_short and not (-(2**15) < v < 2**15): 

725 signed_short = False 

726 if long and v < 0: 

727 long = False 

728 if short: 

729 self.tagtype[tag] = TiffTags.SHORT 

730 elif signed_short: 

731 self.tagtype[tag] = TiffTags.SIGNED_SHORT 

732 elif long: 

733 self.tagtype[tag] = TiffTags.LONG 

734 else: 

735 self.tagtype[tag] = TiffTags.SIGNED_LONG 

736 elif all(isinstance(v, float) for v in values): 

737 self.tagtype[tag] = TiffTags.DOUBLE 

738 elif all(isinstance(v, str) for v in values): 

739 self.tagtype[tag] = TiffTags.ASCII 

740 elif all(isinstance(v, bytes) for v in values): 

741 self.tagtype[tag] = TiffTags.BYTE 

742 

743 if self.tagtype[tag] == TiffTags.UNDEFINED: 

744 values = [ 

745 v.encode("ascii", "replace") if isinstance(v, str) else v 

746 for v in values 

747 ] 

748 elif self.tagtype[tag] == TiffTags.RATIONAL: 

749 values = [float(v) if isinstance(v, int) else v for v in values] 

750 

751 is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict) 

752 if not is_ifd: 

753 values = tuple( 

754 info.cvt_enum(value) if isinstance(value, str) else value 

755 for value in values 

756 ) 

757 

758 dest = self._tags_v1 if legacy_api else self._tags_v2 

759 

760 # Three branches: 

761 # Spec'd length == 1, Actual length 1, store as element 

762 # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. 

763 # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. 

764 # Don't mess with the legacy api, since it's frozen. 

765 if not is_ifd and ( 

766 (info.length == 1) 

767 or self.tagtype[tag] == TiffTags.BYTE 

768 or (info.length is None and len(values) == 1 and not legacy_api) 

769 ): 

770 # Don't mess with the legacy api, since it's frozen. 

771 if legacy_api and self.tagtype[tag] in [ 

772 TiffTags.RATIONAL, 

773 TiffTags.SIGNED_RATIONAL, 

774 ]: # rationals 

775 values = (values,) 

776 try: 

777 (dest[tag],) = values 

778 except ValueError: 

779 # We've got a builtin tag with 1 expected entry 

780 if tag != PHOTOMETRIC_INTERPRETATION and self.group is None: 

781 warnings.warn( 

782 f"Metadata Warning, tag {tag} had too many entries: " 

783 f"{len(values)}, expected 1" 

784 ) 

785 dest[tag] = values[0] 

786 

787 else: 

788 # Spec'd length > 1 or undefined 

789 # Unspec'd, and length > 1 

790 dest[tag] = values 

791 

792 def __delitem__(self, tag: int) -> None: 

793 self._tags_v2.pop(tag, None) 

794 self._tags_v1.pop(tag, None) 

795 self._tagdata.pop(tag, None) 

796 

797 def __iter__(self) -> Iterator[int]: 

798 return iter(set(self._tagdata) | set(self._tags_v2)) 

799 

800 def _unpack(self, fmt: str, data: bytes) -> tuple[Any, ...]: 

801 return struct.unpack(self._endian + fmt, data) 

802 

803 def _pack(self, fmt: str, *values: Any) -> bytes: 

804 return struct.pack(self._endian + fmt, *values) 

805 

806 list( 

807 map( 

808 _register_basic, 

809 [ 

810 (TiffTags.SHORT, "H", "short"), 

811 (TiffTags.LONG, "L", "long"), 

812 (TiffTags.SIGNED_BYTE, "b", "signed byte"), 

813 (TiffTags.SIGNED_SHORT, "h", "signed short"), 

814 (TiffTags.SIGNED_LONG, "l", "signed long"), 

815 (TiffTags.FLOAT, "f", "float"), 

816 (TiffTags.DOUBLE, "d", "double"), 

817 (TiffTags.IFD, "L", "long"), 

818 (TiffTags.LONG8, "Q", "long8"), 

819 ], 

820 ) 

821 ) 

822 

823 @_register_loader(1, 1) # Basic type, except for the legacy API. 

824 def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes: 

825 return data 

826 

827 @_register_writer(1) # Basic type, except for the legacy API. 

828 def write_byte(self, data: bytes | int | IFDRational) -> bytes: 

829 if isinstance(data, IFDRational): 

830 data = int(data) 

831 if isinstance(data, int): 

832 data = bytes((data,)) 

833 return data 

834 

835 @_register_loader(2, 1) 

836 def load_string(self, data: bytes, legacy_api: bool = True) -> str: 

837 if data.endswith(b"\0"): 

838 data = data[:-1] 

839 return data.decode("latin-1", "replace") 

840 

841 @_register_writer(2) 

842 def write_string(self, value: str | bytes | int) -> bytes: 

843 # remerge of https://github.com/python-pillow/Pillow/pull/1416 

844 if isinstance(value, int): 

845 value = str(value) 

846 if not isinstance(value, bytes): 

847 value = value.encode("ascii", "replace") 

848 return value + b"\0" 

849 

850 @_register_loader(5, 8) 

851 def load_rational( 

852 self, data: bytes, legacy_api: bool = True 

853 ) -> tuple[tuple[int, int] | IFDRational, ...]: 

854 vals = self._unpack(f"{len(data) // 4}L", data) 

855 

856 def combine(a: int, b: int) -> tuple[int, int] | IFDRational: 

857 return (a, b) if legacy_api else IFDRational(a, b) 

858 

859 return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) 

860 

861 @_register_writer(5) 

862 def write_rational(self, *values: IFDRational) -> bytes: 

863 return b"".join( 

864 self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values 

865 ) 

866 

867 @_register_loader(7, 1) 

868 def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes: 

869 return data 

870 

871 @_register_writer(7) 

872 def write_undefined(self, value: bytes | int | IFDRational) -> bytes: 

873 if isinstance(value, IFDRational): 

874 value = int(value) 

875 if isinstance(value, int): 

876 value = str(value).encode("ascii", "replace") 

877 return value 

878 

879 @_register_loader(10, 8) 

880 def load_signed_rational( 

881 self, data: bytes, legacy_api: bool = True 

882 ) -> tuple[tuple[int, int] | IFDRational, ...]: 

883 vals = self._unpack(f"{len(data) // 4}l", data) 

884 

885 def combine(a: int, b: int) -> tuple[int, int] | IFDRational: 

886 return (a, b) if legacy_api else IFDRational(a, b) 

887 

888 return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) 

889 

890 @_register_writer(10) 

891 def write_signed_rational(self, *values: IFDRational) -> bytes: 

892 return b"".join( 

893 self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31))) 

894 for frac in values 

895 ) 

896 

897 def _ensure_read(self, fp: IO[bytes], size: int) -> bytes: 

898 ret = fp.read(size) 

899 if len(ret) != size: 

900 msg = ( 

901 "Corrupt EXIF data. " 

902 f"Expecting to read {size} bytes but only got {len(ret)}. " 

903 ) 

904 raise OSError(msg) 

905 return ret 

906 

907 def load(self, fp: IO[bytes]) -> None: 

908 self.reset() 

909 self._offset = fp.tell() 

910 limit = ImageFile._get_fp_length(fp) 

911 if self._tag_data_limit is not None: 

912 limit = min(limit, self._tag_data_limit) 

913 self._tag_data_size = 0 

914 

915 try: 

916 tag_count = ( 

917 self._unpack("Q", self._ensure_read(fp, 8)) 

918 if self._bigtiff 

919 else self._unpack("H", self._ensure_read(fp, 2)) 

920 )[0] 

921 total_size = 0 

922 seen_tags = set() 

923 for i in range(tag_count): 

924 tag, typ, count, data = ( 

925 self._unpack("HHQ8s", self._ensure_read(fp, 20)) 

926 if self._bigtiff 

927 else self._unpack("HHL4s", self._ensure_read(fp, 12)) 

928 ) 

929 if tag in seen_tags: 

930 # Ignore duplicate tags, like libtiff 

931 # even if the first one was skipped 

932 continue 

933 seen_tags.add(tag) 

934 if ( 

935 tag == PHOTOMETRIC_INTERPRETATION 

936 and count != 1 

937 and self.group is None 

938 ): 

939 # Ignore a malformed PhotometricInterpretation tag, like libtiff 

940 continue 

941 

942 # tagname = TiffTags.lookup(tag, self.group).name 

943 # typname = TYPES.get(typ, "unknown") 

944 # msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})" 

945 

946 try: 

947 unit_size, handler = self._load_dispatch[typ] 

948 except KeyError: 

949 # logger.debug("%s - unsupported type %s", msg, typ) 

950 continue # ignore unsupported type 

951 size = count * unit_size 

952 if size > (8 if self._bigtiff else 4): 

953 here = fp.tell() 

954 (offset,) = self._unpack("Q" if self._bigtiff else "L", data) 

955 # msg += f" Tag Location: {here} - Data Location: {offset}" 

956 if offset >= 2**63: 

957 warnings.warn("Tag offset too large") 

958 continue 

959 # Well-formed IFD values should fit within the stream they 

960 # are read from, even before accounting for overlaps. 

961 total_size += size 

962 if total_size > limit: 

963 warnings.warn(f"Tag data larger than file. Skipping tag {tag}") 

964 continue 

965 self._tag_data_size += size 

966 fp.seek(offset) 

967 data = ImageFile._safe_read(fp, size) 

968 fp.seek(here) 

969 else: 

970 data = data[:size] 

971 

972 if len(data) != size: 

973 warnings.warn( 

974 "Possibly corrupt EXIF data. " 

975 f"Expecting to read {size} bytes but only got {len(data)}." 

976 f" Skipping tag {tag}" 

977 ) 

978 # logger.debug(msg) 

979 continue 

980 

981 if not data: 

982 # logger.debug(msg) 

983 continue 

984 

985 self._tagdata[tag] = data 

986 self.tagtype[tag] = typ 

987 

988 # msg += " - value: " 

989 # msg += f"<table: {size} bytes>" if size > 32 else repr(data) 

990 

991 # logger.debug(msg) 

992 

993 (self.next,) = ( 

994 self._unpack("Q", self._ensure_read(fp, 8)) 

995 if self._bigtiff 

996 else self._unpack("L", self._ensure_read(fp, 4)) 

997 ) 

998 except OSError as msg: 

999 warnings.warn(str(msg)) 

1000 return 

1001 

1002 def _get_ifh(self) -> bytes: 

1003 ifh = self._prefix + self._pack("H", 43 if self._bigtiff else 42) 

1004 if self._bigtiff: 

1005 ifh += self._pack("HH", 8, 0) 

1006 ifh += self._pack("Q", 16) if self._bigtiff else self._pack("L", 8) 

1007 

1008 return ifh 

1009 

1010 def tobytes(self, offset: int = 0) -> bytes: 

1011 # FIXME What about tagdata? 

1012 result = self._pack("Q" if self._bigtiff else "H", len(self._tags_v2)) 

1013 

1014 entries: list[tuple[int, int, int, bytes, bytes]] = [] 

1015 

1016 fmt = "Q" if self._bigtiff else "L" 

1017 fmt_size = 8 if self._bigtiff else 4 

1018 offset += ( 

1019 len(result) + len(self._tags_v2) * (20 if self._bigtiff else 12) + fmt_size 

1020 ) 

1021 stripoffsets = None 

1022 

1023 # pass 1: convert tags to binary format 

1024 # always write tags in ascending order 

1025 for tag, value in sorted(self._tags_v2.items()): 

1026 if tag == STRIPOFFSETS: 

1027 stripoffsets = len(entries) 

1028 typ = self.tagtype[tag] 

1029 logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value)) 

1030 is_ifd = typ == TiffTags.LONG and isinstance(value, dict) 

1031 if is_ifd: 

1032 if self._bigtiff and offset >= 2**32: 

1033 # Offset is too large for a LONG 

1034 # BigTIFF allows an 8-byte offset to the sub-IFD 

1035 typ = TiffTags.LONG8 

1036 ifd = ImageFileDirectory_v2(self._get_ifh(), group=tag) 

1037 values = self._tags_v2[tag] 

1038 for ifd_tag, ifd_value in values.items(): 

1039 ifd[ifd_tag] = ifd_value 

1040 data = ifd.tobytes(offset) 

1041 else: 

1042 values = value if isinstance(value, tuple) else (value,) 

1043 data = self._write_dispatch[typ](self, *values) 

1044 

1045 tagname = TiffTags.lookup(tag, self.group).name 

1046 typname = "ifd" if is_ifd else TYPES.get(typ, "unknown") 

1047 msg = f"save: {tagname} ({tag}) - type: {typname} ({typ}) - value: " 

1048 msg += f"<table: {len(data)} bytes>" if len(data) >= 16 else str(values) 

1049 logger.debug(msg) 

1050 

1051 # count is sum of lengths for string and arbitrary data 

1052 if is_ifd: 

1053 count = 1 

1054 elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]: 

1055 count = len(data) 

1056 else: 

1057 count = len(values) 

1058 # figure out if data fits into the entry 

1059 if len(data) <= fmt_size: 

1060 value = data.ljust(fmt_size, b"\0") 

1061 data = b"" 

1062 else: 

1063 if is_ifd and typ == TiffTags.LONG and self._bigtiff: 

1064 # The offset needs to be left-justified within a BigTIFF entry, 

1065 # because only the first 4 bytes will be read from this LONG, 

1066 # regardless of endianness 

1067 value = self._pack("L", offset).ljust(fmt_size, b"\0") 

1068 else: 

1069 value = self._pack(fmt, offset) 

1070 offset += (len(data) + 1) // 2 * 2 # pad to word 

1071 entries.append((tag, typ, count, value, data)) 

1072 

1073 # update strip offset data to point beyond auxiliary data 

1074 if stripoffsets is not None: 

1075 tag, typ, count, value, data = entries[stripoffsets] 

1076 size, handler = self._load_dispatch[typ] 

1077 values = [ 

1078 val + offset for val in handler(self, data or value, self.legacy_api) 

1079 ] 

1080 if data: 

1081 data = self._write_dispatch[typ](self, *values) 

1082 else: 

1083 # The offsets fit within the entry 

1084 # May be one offset, or two LONG offsets in BigTIFF 

1085 if ( 

1086 self._bigtiff 

1087 and typ == TiffTags.LONG 

1088 and count == 1 

1089 and values[0] >= 2**32 

1090 ): 

1091 # If a single offset is too large for a LONG, 

1092 # in BigTIFF there is room to change the entry to LONG8 

1093 typ = TiffTags.LONG8 

1094 value = self._write_dispatch[typ](self, *values).ljust(fmt_size, b"\0") 

1095 entries[stripoffsets] = tag, typ, count, value, data 

1096 

1097 # pass 2: write entries to file 

1098 for tag, typ, count, value, data in entries: 

1099 logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data)) 

1100 result += self._pack( 

1101 "HHQ8s" if self._bigtiff else "HHL4s", tag, typ, count, value 

1102 ) 

1103 

1104 # -- overwrite here for multi-page -- 

1105 result += self._pack(fmt, 0) # end of entries 

1106 

1107 # pass 3: write auxiliary data to file 

1108 for tag, typ, count, value, data in entries: 

1109 result += data 

1110 if len(data) & 1: 

1111 result += b"\0" 

1112 

1113 return result 

1114 

1115 def save(self, fp: IO[bytes]) -> int: 

1116 if fp.tell() == 0: # skip TIFF header on subsequent pages 

1117 fp.write(self._get_ifh()) 

1118 

1119 offset = fp.tell() 

1120 result = self.tobytes(offset) 

1121 fp.write(result) 

1122 return offset + len(result) 

1123 

1124 

1125ImageFileDirectory_v2._load_dispatch = _load_dispatch 

1126ImageFileDirectory_v2._write_dispatch = _write_dispatch 

1127for idx, name in TYPES.items(): 

1128 name = name.replace(" ", "_") 

1129 setattr(ImageFileDirectory_v2, f"load_{name}", _load_dispatch[idx][1]) 

1130 setattr(ImageFileDirectory_v2, f"write_{name}", _write_dispatch[idx]) 

1131del _load_dispatch, _write_dispatch, idx, name 

1132 

1133 

1134# Legacy ImageFileDirectory support. 

1135class ImageFileDirectory_v1(ImageFileDirectory_v2): 

1136 """This class represents the **legacy** interface to a TIFF tag directory. 

1137 

1138 Exposes a dictionary interface of the tags in the directory:: 

1139 

1140 ifd = ImageFileDirectory_v1() 

1141 ifd[key] = 'Some Data' 

1142 ifd.tagtype[key] = TiffTags.ASCII 

1143 print(ifd[key]) 

1144 ('Some Data',) 

1145 

1146 Also contains a dictionary of tag types as read from the tiff image file, 

1147 :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. 

1148 

1149 Values are returned as a tuple. 

1150 

1151 .. deprecated:: 3.0.0 

1152 """ 

1153 

1154 def __init__(self, *args: Any, **kwargs: Any) -> None: 

1155 super().__init__(*args, **kwargs) 

1156 self._legacy_api = True 

1157 

1158 tags = property(lambda self: self._tags_v1) 

1159 tagdata = property(lambda self: self._tagdata) 

1160 

1161 # defined in ImageFileDirectory_v2 

1162 tagtype: dict[int, int] 

1163 """Dictionary of tag types""" 

1164 

1165 @classmethod 

1166 def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1: 

1167 """Returns an 

1168 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` 

1169 instance with the same data as is contained in the original 

1170 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` 

1171 instance. 

1172 

1173 :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` 

1174 

1175 """ 

1176 

1177 ifd = cls(prefix=original.prefix) 

1178 ifd._tagdata = original._tagdata 

1179 ifd.tagtype = original.tagtype 

1180 ifd.next = original.next # an indicator for multipage tiffs 

1181 return ifd 

1182 

1183 def to_v2(self) -> ImageFileDirectory_v2: 

1184 """Returns an 

1185 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` 

1186 instance with the same data as is contained in the original 

1187 :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` 

1188 instance. 

1189 

1190 :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` 

1191 

1192 """ 

1193 

1194 ifd = ImageFileDirectory_v2(prefix=self.prefix) 

1195 ifd._tagdata = dict(self._tagdata) 

1196 ifd.tagtype = dict(self.tagtype) 

1197 ifd._tags_v2 = dict(self._tags_v2) 

1198 return ifd 

1199 

1200 def __contains__(self, tag: object) -> bool: 

1201 return tag in self._tags_v1 or tag in self._tagdata 

1202 

1203 def __len__(self) -> int: 

1204 return len(set(self._tagdata) | set(self._tags_v1)) 

1205 

1206 def __iter__(self) -> Iterator[int]: 

1207 return iter(set(self._tagdata) | set(self._tags_v1)) 

1208 

1209 def __setitem__(self, tag: int, value: Any) -> None: 

1210 for legacy_api in (False, True): 

1211 self._setitem(tag, value, legacy_api) 

1212 

1213 def __getitem__(self, tag: int) -> Any: 

1214 if tag not in self._tags_v1: # unpack on the fly 

1215 data = self._tagdata[tag] 

1216 typ = self.tagtype[tag] 

1217 size, handler = self._load_dispatch[typ] 

1218 for legacy in (False, True): 

1219 self._setitem(tag, handler(self, data, legacy), legacy) 

1220 val = self._tags_v1[tag] 

1221 if not isinstance(val, (tuple, bytes)): 

1222 val = (val,) 

1223 return val 

1224 

1225 

1226# undone -- switch this pointer 

1227ImageFileDirectory = ImageFileDirectory_v1 

1228 

1229 

1230## 

1231# Image plugin for TIFF files. 

1232 

1233 

1234class TiffImageFile(ImageFile.ImageFile): 

1235 format = "TIFF" 

1236 format_description = "Adobe TIFF" 

1237 _close_exclusive_fp_after_loading = False 

1238 

1239 def __init__( 

1240 self, 

1241 fp: StrOrBytesPath | IO[bytes], 

1242 filename: str | bytes | None = None, 

1243 ) -> None: 

1244 self.tag_v2: ImageFileDirectory_v2 

1245 """ Image file directory (tag dictionary) """ 

1246 

1247 self.tag: ImageFileDirectory_v1 

1248 """ Legacy tag entries """ 

1249 

1250 self._n_frames: int | None = None 

1251 super().__init__(fp, filename) 

1252 

1253 def _open(self) -> None: 

1254 """Open the first image in a TIFF file""" 

1255 

1256 # Header 

1257 assert self.fp is not None 

1258 ifh = self.fp.read(8) 

1259 if 43 in ifh[2:4]: 

1260 ifh += self.fp.read(8) 

1261 

1262 self.tag_v2 = ImageFileDirectory_v2(ifh) 

1263 

1264 # setup frame pointers 

1265 self.__first = self.__next = self.tag_v2.next 

1266 self.__frame = -1 

1267 self._fp = self.fp 

1268 self._frame_pos: list[int] = [] 

1269 

1270 # Out-of-line tag data is budgeted across all frames, so that many 

1271 # frames cannot each read the same data. Each frame's limit is fixed 

1272 # when it is first loaded, so that reloading it gives the same tags. 

1273 self._tag_data_remaining = self._get_fp_length() 

1274 self._tag_data_limits: list[int] = [] 

1275 

1276 logger.debug("*** TiffImageFile._open ***") 

1277 logger.debug("- __first: %s", self.__first) 

1278 logger.debug("- ifh: %s", repr(ifh)) # Use repr to avoid str(bytes) 

1279 

1280 # and load the first frame 

1281 self._seek(0) 

1282 

1283 @property 

1284 def n_frames(self) -> int: 

1285 current_n_frames = self._n_frames 

1286 if current_n_frames is None: 

1287 current = self.tell() 

1288 self._seek(len(self._frame_pos)) 

1289 while self._n_frames is None: 

1290 self._seek(self.tell() + 1) 

1291 self.seek(current) 

1292 assert self._n_frames is not None 

1293 return self._n_frames 

1294 

1295 def seek(self, frame: int) -> None: 

1296 """Select a given frame as current image""" 

1297 if not self._seek_check(frame): 

1298 return 

1299 self._seek(frame) 

1300 if self._im is not None and ( 

1301 self.im.size != self._tile_size 

1302 or self.im.mode != self.mode 

1303 or self.readonly 

1304 ): 

1305 self._im = None 

1306 

1307 def _seek(self, frame: int) -> None: 

1308 if isinstance(self._fp, DeferredError): 

1309 raise self._fp.ex 

1310 self.fp = self._fp 

1311 

1312 while len(self._frame_pos) <= frame: 

1313 if not self.__next: 

1314 msg = "no more images in TIFF file" 

1315 raise EOFError(msg) 

1316 logger.debug( 

1317 "Seeking to frame %s, on frame %s, __next %s, location: %s", 

1318 frame, 

1319 self.__frame, 

1320 self.__next, 

1321 self.fp.tell(), 

1322 ) 

1323 if self.__next >= 2**63: 

1324 msg = "Unable to seek to frame" 

1325 raise ValueError(msg) 

1326 self.fp.seek(self.__next) 

1327 self._frame_pos.append(self.__next) 

1328 self._tag_data_limits.append(self._tag_data_remaining) 

1329 logger.debug("Loading tags, location: %s", self.fp.tell()) 

1330 self.tag_v2._tag_data_limit = self._tag_data_remaining 

1331 self.tag_v2.load(self.fp) 

1332 self._tag_data_remaining -= self.tag_v2._tag_data_size 

1333 if self.tag_v2.next in self._frame_pos: 

1334 # This IFD has already been processed 

1335 # Declare this to be the end of the image 

1336 self.__next = 0 

1337 else: 

1338 self.__next = self.tag_v2.next 

1339 if self.__next == 0 and self._n_frames is None: 

1340 self._n_frames = frame + 1 

1341 if not hasattr(self, "is_animated"): 

1342 self.is_animated = self.__next != 0 

1343 self.__frame += 1 

1344 self.fp.seek(self._frame_pos[frame]) 

1345 self.tag_v2._tag_data_limit = self._tag_data_limits[frame] 

1346 self.tag_v2.load(self.fp) 

1347 if XMP in self.tag_v2: 

1348 xmp = self.tag_v2[XMP] 

1349 if isinstance(xmp, tuple) and len(xmp) == 1: 

1350 xmp = xmp[0] 

1351 self.info["xmp"] = xmp 

1352 elif "xmp" in self.info: 

1353 del self.info["xmp"] 

1354 self._reload_exif() 

1355 # fill the legacy tag/ifd entries 

1356 self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2) 

1357 self.__frame = frame 

1358 self._setup() 

1359 

1360 def tell(self) -> int: 

1361 """Return the current frame number""" 

1362 return self.__frame 

1363 

1364 def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]: 

1365 """ 

1366 Returns a dictionary of Photoshop "Image Resource Blocks". 

1367 The keys are the image resource ID. For more information, see 

1368 https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037727 

1369 

1370 :returns: Photoshop "Image Resource Blocks" in a dictionary. 

1371 """ 

1372 blocks = {} 

1373 val = self.tag_v2.get(ExifTags.Base.ImageResources) 

1374 if val: 

1375 while val.startswith(b"8BIM") and len(val) >= 12: 

1376 id = i16(val[4:6]) 

1377 n = math.ceil((val[6] + 1) / 2) * 2 

1378 try: 

1379 size = i32(val[6 + n : 10 + n]) 

1380 except struct.error: 

1381 break 

1382 data = val[10 + n : 10 + n + size] 

1383 blocks[id] = {"data": data} 

1384 

1385 val = val[math.ceil((10 + n + size) / 2) * 2 :] 

1386 return blocks 

1387 

1388 def load(self) -> Image.core.PixelAccess | None: 

1389 if not self.tile: 

1390 return super().load() 

1391 

1392 original_size = self._size 

1393 self._size = self._tile_size 

1394 

1395 try: 

1396 if self.use_load_libtiff: 

1397 self._load_libtiff() 

1398 px = Image.Image.load(self) 

1399 else: 

1400 px = super().load() 

1401 finally: 

1402 self._size = original_size 

1403 return px 

1404 

1405 def load_end(self) -> None: 

1406 # allow closing if we're on the first frame, there's no next 

1407 # This is the ImageFile.load path only, libtiff specific below. 

1408 if not self.is_animated: 

1409 self._close_exclusive_fp_after_loading = True 

1410 

1411 # load IFD data from fp before it is closed 

1412 exif = self.getexif() 

1413 for key in TiffTags.TAGS_V2_GROUPS: 

1414 if key not in exif: 

1415 continue 

1416 exif.get_ifd(key) 

1417 

1418 ImageOps.exif_transpose(self, in_place=True) 

1419 if ExifTags.Base.Orientation in self.tag_v2: 

1420 del self.tag_v2[ExifTags.Base.Orientation] 

1421 

1422 def _load_libtiff(self) -> None: 

1423 """Overload method triggered when we detect a compressed tiff 

1424 Calls out to libtiff""" 

1425 

1426 if not len(self.tile) == 1: 

1427 msg = "Not exactly one tile" 

1428 raise OSError(msg) 

1429 

1430 self.load_prepare() 

1431 

1432 extents = self.tile[0].extents 

1433 args = self.tile[0].args 

1434 

1435 # To be nice on memory footprint, if there's a 

1436 # file descriptor, use that instead of reading 

1437 # into a string in python. 

1438 assert self.fp is not None 

1439 try: 

1440 fp = hasattr(self.fp, "fileno") and self.fp.fileno() 

1441 # flush the file descriptor, prevents error on pypy 2.4+ 

1442 # should also eliminate the need for fp.tell 

1443 # in _seek 

1444 if hasattr(self.fp, "flush"): 

1445 self.fp.flush() 

1446 except OSError: 

1447 # io.BytesIO have a fileno, but returns an OSError if 

1448 # it doesn't use a file descriptor. 

1449 fp = False 

1450 

1451 if fp: 

1452 assert isinstance(args, tuple) 

1453 args_list = list(args) 

1454 args_list[1] = fp 

1455 args = tuple(args_list) 

1456 

1457 decoder = Image._getdecoder(self.mode, "libtiff", args, self.decoderconfig) 

1458 try: 

1459 decoder.setimage(self.im, extents) 

1460 except ValueError as e: 

1461 msg = "Couldn't set the image" 

1462 raise OSError(msg) from e 

1463 

1464 close_self_fp = self._exclusive_fp and not self.is_animated 

1465 if hasattr(self.fp, "getvalue"): 

1466 # We've got a stringio like thing passed in. Yay for all in memory. 

1467 # The decoder needs the entire file in one shot, so there's not 

1468 # a lot we can do here other than give it the entire file. 

1469 # unless we could do something like get the address of the 

1470 # underlying string for stringio. 

1471 # 

1472 # Rearranging for supporting byteio items, since they have a fileno 

1473 # that returns an OSError if there's no underlying fp. Easier to 

1474 # deal with here by reordering. 

1475 logger.debug("have getvalue. just sending in a string from getvalue") 

1476 n, err = decoder.decode(self.fp.getvalue()) 

1477 elif fp: 

1478 # we've got a actual file on disk, pass in the fp. 

1479 logger.debug("have fileno, calling fileno version of the decoder.") 

1480 if not close_self_fp: 

1481 self.fp.seek(0) 

1482 # Save and restore the file position, because libtiff will move it 

1483 # outside of the Python runtime, and that will confuse 

1484 # io.BufferedReader and possible others. 

1485 # NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(), 

1486 # because the buffer read head already may not equal the actual 

1487 # file position, and fp.seek() may just adjust it's internal 

1488 # pointer and not actually seek the OS file handle. 

1489 pos = os.lseek(fp, 0, os.SEEK_CUR) 

1490 # 4 bytes, otherwise the trace might error out 

1491 n, err = decoder.decode(b"fpfp") 

1492 os.lseek(fp, pos, os.SEEK_SET) 

1493 else: 

1494 # we have something else. 

1495 logger.debug("don't have fileno or getvalue. just reading") 

1496 self.fp.seek(0) 

1497 # UNDONE -- so much for that buffer size thing. 

1498 n, err = decoder.decode(self.fp.read()) 

1499 

1500 self.tile = [] 

1501 self.readonly = 0 

1502 

1503 self.load_end() 

1504 

1505 if close_self_fp: 

1506 self.fp.close() 

1507 self.fp = None # might be shared 

1508 

1509 if err < 0: 

1510 msg = f"decoder error {err}" 

1511 raise OSError(msg) 

1512 

1513 def _setup(self) -> None: 

1514 """Setup this image object based on current tags""" 

1515 

1516 if 0xBC01 in self.tag_v2: 

1517 msg = "Windows Media Photo files not yet supported" 

1518 raise OSError(msg) 

1519 

1520 # extract relevant tags 

1521 self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)] 

1522 self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1) 

1523 

1524 # photometric is a required tag, but not everyone is reading 

1525 # the specification 

1526 photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0) 

1527 

1528 # old style jpeg compression images most certainly are YCbCr 

1529 if self._compression == "tiff_jpeg": 

1530 photo = 6 

1531 

1532 fillorder = self.tag_v2.get(FILLORDER, 1) 

1533 

1534 logger.debug("*** Summary ***") 

1535 logger.debug("- compression: %s", self._compression) 

1536 logger.debug("- photometric_interpretation: %s", photo) 

1537 logger.debug("- planar_configuration: %s", self._planar_configuration) 

1538 logger.debug("- fill_order: %s", fillorder) 

1539 logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING)) 

1540 

1541 # size 

1542 try: 

1543 xsize = self.tag_v2[IMAGEWIDTH] 

1544 ysize = self.tag_v2[IMAGELENGTH] 

1545 except KeyError as e: 

1546 msg = "Missing dimensions" 

1547 raise TypeError(msg) from e 

1548 if not isinstance(xsize, int) or not isinstance(ysize, int): 

1549 msg = "Invalid dimensions" 

1550 raise ValueError(msg) 

1551 self._tile_size = xsize, ysize 

1552 Image._decompression_bomb_check(self._tile_size) 

1553 orientation = self.tag_v2.get(ExifTags.Base.Orientation) 

1554 if orientation in (5, 6, 7, 8): 

1555 self._size = ysize, xsize 

1556 else: 

1557 self._size = xsize, ysize 

1558 

1559 logger.debug("- size: %s", self.size) 

1560 

1561 sample_format = self.tag_v2.get(SAMPLEFORMAT, (1,)) 

1562 if len(sample_format) > 1 and max(sample_format) == min(sample_format): 

1563 # SAMPLEFORMAT is properly per band, so an RGB image will 

1564 # be (1,1,1). But, we don't support per band pixel types, 

1565 # and anything more than one band is a uint8. So, just 

1566 # take the first element. Revisit this if adding support 

1567 # for more exotic images. 

1568 sample_format = (sample_format[0],) 

1569 

1570 bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,)) 

1571 extra_tuple = self.tag_v2.get(EXTRASAMPLES, ()) 

1572 samples_per_pixel = self.tag_v2.get( 

1573 SAMPLESPERPIXEL, 

1574 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1, 

1575 ) 

1576 if photo in (2, 6, 8): # RGB, YCbCr, LAB 

1577 bps_count = 3 

1578 elif photo == 5: # CMYK 

1579 bps_count = 4 

1580 else: 

1581 bps_count = 1 

1582 if self._planar_configuration == 2 and extra_tuple and max(extra_tuple) == 0: 

1583 # If components are stored separately, 

1584 # then unspecified extra components at the end can be ignored 

1585 bps_tuple = bps_tuple[: -len(extra_tuple)] 

1586 samples_per_pixel -= len(extra_tuple) 

1587 extra_tuple = () 

1588 bps_count += len(extra_tuple) 

1589 bps_actual_count = len(bps_tuple) 

1590 

1591 if samples_per_pixel > MAX_SAMPLESPERPIXEL: 

1592 # DOS check, samples_per_pixel can be a Long, and we extend the tuple below 

1593 logger.error( 

1594 "More samples per pixel than can be decoded: %s", samples_per_pixel 

1595 ) 

1596 msg = "Invalid value for samples per pixel" 

1597 raise SyntaxError(msg) 

1598 

1599 if samples_per_pixel < bps_actual_count: 

1600 # If a file has more values in bps_tuple than expected, 

1601 # remove the excess. 

1602 bps_tuple = bps_tuple[:samples_per_pixel] 

1603 elif samples_per_pixel > bps_actual_count and bps_actual_count == 1: 

1604 # If a file has only one value in bps_tuple, when it should have more, 

1605 # presume it is the same number of bits for all of the samples. 

1606 bps_tuple = bps_tuple * samples_per_pixel 

1607 

1608 if len(bps_tuple) != samples_per_pixel: 

1609 msg = "unknown data organization" 

1610 raise SyntaxError(msg) 

1611 

1612 # mode: check photometric interpretation and bits per pixel 

1613 key = ( 

1614 self.tag_v2.prefix, 

1615 photo, 

1616 sample_format, 

1617 fillorder, 

1618 bps_tuple, 

1619 extra_tuple, 

1620 ) 

1621 logger.debug("format key: %s", key) 

1622 try: 

1623 self._mode, rawmode = OPEN_INFO[key] 

1624 except KeyError as e: 

1625 logger.debug("- unsupported format") 

1626 msg = "unknown pixel mode" 

1627 raise SyntaxError(msg) from e 

1628 

1629 logger.debug("- raw mode: %s", rawmode) 

1630 logger.debug("- pil mode: %s", self.mode) 

1631 

1632 self.info["compression"] = self._compression 

1633 

1634 # Clear info from previous frame 

1635 self.info.pop("dpi", None) 

1636 self.info.pop("resolution", None) 

1637 

1638 xres = self.tag_v2.get(X_RESOLUTION) 

1639 yres = self.tag_v2.get(Y_RESOLUTION) 

1640 

1641 if xres and yres: 

1642 resunit = self.tag_v2.get(RESOLUTION_UNIT) 

1643 if resunit == 2: # dots per inch 

1644 self.info["dpi"] = (xres, yres) 

1645 elif resunit == 3: # dots per centimeter. convert to dpi 

1646 self.info["dpi"] = (xres * 2.54, yres * 2.54) 

1647 elif resunit is None: # used to default to 1, but now 2) 

1648 self.info["dpi"] = (xres, yres) 

1649 # For backward compatibility, 

1650 # we also preserve the old behavior 

1651 self.info["resolution"] = xres, yres 

1652 else: # No absolute unit of measurement 

1653 self.info["resolution"] = xres, yres 

1654 

1655 # build tile descriptors 

1656 x = y = layer = 0 

1657 self.tile = [] 

1658 self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw" 

1659 if self.use_load_libtiff: 

1660 # Decoder expects entire file as one tile. 

1661 # There's a buffer size limit in load (64k) 

1662 # so large g4 images will fail if we use that 

1663 # function. 

1664 # 

1665 # Setup the one tile for the whole image, then 

1666 # use the _load_libtiff function. 

1667 

1668 # libtiff handles the fillmode for us, so 1;IR should 

1669 # actually be 1;I. Including the R double reverses the 

1670 # bits, so stripes of the image are reversed. See 

1671 # https://github.com/python-pillow/Pillow/issues/279 

1672 if fillorder == 2: 

1673 # Replace fillorder with fillorder=1 

1674 key = key[:3] + (1,) + key[4:] 

1675 logger.debug("format key: %s", key) 

1676 # this should always work, since all the 

1677 # fillorder==2 modes have a corresponding 

1678 # fillorder=1 mode 

1679 self._mode, rawmode = OPEN_INFO[key] 

1680 if photo == 6: 

1681 self._mode = "RGB" 

1682 if self._compression in "jpeg" and self._planar_configuration == 1: 

1683 # YCbCr images with new jpeg compression with pixels in one plane 

1684 # unpacked straight into RGB values 

1685 rawmode = "RGB" 

1686 else: 

1687 rawmode = "RGBX" 

1688 # libtiff always returns the bytes in native order. 

1689 # we're expecting image byte order. So, if the rawmode 

1690 # contains I;16, we need to convert from native to image 

1691 # byte order. 

1692 elif rawmode == "I;16": 

1693 rawmode = "I;16N" 

1694 elif rawmode.endswith((";16B", ";16L")): 

1695 rawmode = rawmode[:-1] + "N" 

1696 

1697 # Offset in the tile tuple is 0, we go from 0,0 to 

1698 # w,h, and we only do this once -- eds 

1699 a = (rawmode, False, self.tag_v2.offset) 

1700 self.tile.append(ImageFile._Tile("libtiff", (0, 0, xsize, ysize), 0, a)) 

1701 

1702 elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2: 

1703 # striped image 

1704 if STRIPOFFSETS in self.tag_v2: 

1705 offsets = self.tag_v2[STRIPOFFSETS] 

1706 h = self.tag_v2.get(ROWSPERSTRIP, ysize) 

1707 w = xsize 

1708 else: 

1709 # tiled image 

1710 offsets = self.tag_v2[TILEOFFSETS] 

1711 tilewidth = self.tag_v2.get(TILEWIDTH) 

1712 h = self.tag_v2.get(TILELENGTH) 

1713 if not isinstance(tilewidth, int) or not isinstance(h, int): 

1714 msg = "Invalid tile dimensions" 

1715 raise ValueError(msg) 

1716 w = tilewidth 

1717 

1718 if w == xsize and h == ysize and self._planar_configuration != 2: 

1719 # Every tile covers the image. Only use the last offset 

1720 offsets = offsets[-1:] 

1721 

1722 for offset in offsets: 

1723 if x + w > xsize: 

1724 stride = w * sum(bps_tuple) / 8 # bytes per line 

1725 else: 

1726 stride = 0 

1727 

1728 tile_rawmode = rawmode 

1729 if self._planar_configuration == 2: 

1730 # each band on it's own layer 

1731 tile_rawmode = rawmode[layer] 

1732 # adjust stride width accordingly 

1733 stride /= bps_count 

1734 

1735 args = (tile_rawmode, int(stride), 1) 

1736 self.tile.append( 

1737 ImageFile._Tile( 

1738 self._compression, 

1739 (x, y, min(x + w, xsize), min(y + h, ysize)), 

1740 offset, 

1741 args, 

1742 ) 

1743 ) 

1744 x += w 

1745 if x >= xsize: 

1746 x, y = 0, y + h 

1747 if y >= ysize: 

1748 y = 0 

1749 layer += 1 

1750 else: 

1751 logger.debug("- unsupported data organization") 

1752 msg = "unknown data organization" 

1753 raise SyntaxError(msg) 

1754 

1755 # Fix up info. 

1756 if ICCPROFILE in self.tag_v2: 

1757 self.info["icc_profile"] = self.tag_v2[ICCPROFILE] 

1758 

1759 # fixup palette descriptor 

1760 

1761 if self.mode in ["P", "PA"]: 

1762 palette = tuple(b // 256 for b in self.tag_v2[COLORMAP]) 

1763 self.palette = ImagePalette.raw("RGB;L", palette) 

1764 else: 

1765 self.palette = None 

1766 

1767 

1768# 

1769# -------------------------------------------------------------------- 

1770# Write TIFF files 

1771 

1772# little endian is default except for image modes with 

1773# explicit big endian byte-order 

1774 

1775SAVE_INFO = { 

1776 # mode => rawmode, byteorder, photometrics, 

1777 # sampleformat, bitspersample, extra 

1778 "1": ("1", II, 1, 1, (1,), None), 

1779 "L": ("L", II, 1, 1, (8,), None), 

1780 "LA": ("LA", II, 1, 1, (8, 8), 2), 

1781 "P": ("P", II, 3, 1, (8,), None), 

1782 "PA": ("PA", II, 3, 1, (8, 8), 2), 

1783 "I": ("I;32S", II, 1, 2, (32,), None), 

1784 "I;16": ("I;16", II, 1, 1, (16,), None), 

1785 "I;16L": ("I;16L", II, 1, 1, (16,), None), 

1786 "F": ("F;32F", II, 1, 3, (32,), None), 

1787 "RGB": ("RGB", II, 2, 1, (8, 8, 8), None), 

1788 "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0), 

1789 "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2), 

1790 "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None), 

1791 "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None), 

1792 "LAB": ("LAB", II, 8, 1, (8, 8, 8), None), 

1793 "I;16B": ("I;16B", MM, 1, 1, (16,), None), 

1794} 

1795 

1796 

1797def _validate_tags(tags: dict[int, Any]) -> None: 

1798 if COLORMAP in tags: 

1799 bps = tags.get(BITSPERSAMPLE, 1) 

1800 if isinstance(bps, tuple): 

1801 bps = bps[0] 

1802 expected = (1 << bps) * 3 

1803 if len(tags[COLORMAP]) != expected: 

1804 msg = f"Requiring {expected} items for Colormap" 

1805 raise ValueError(msg) 

1806 

1807 

1808def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

1809 try: 

1810 rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] 

1811 except KeyError as e: 

1812 msg = f"cannot write mode {im.mode} as TIFF" 

1813 raise OSError(msg) from e 

1814 

1815 encoderinfo = im.encoderinfo 

1816 encoderconfig = im.encoderconfig 

1817 

1818 ifd = ImageFileDirectory_v2(prefix=prefix) 

1819 if encoderinfo.get("big_tiff"): 

1820 ifd._bigtiff = True 

1821 

1822 try: 

1823 compression = encoderinfo["compression"] 

1824 except KeyError: 

1825 compression = im.info.get("compression") 

1826 if isinstance(compression, int): 

1827 # compression value may be from BMP. Ignore it 

1828 compression = None 

1829 if compression is None: 

1830 compression = "raw" 

1831 elif compression == "tiff_jpeg": 

1832 # OJPEG is obsolete, so use new-style JPEG compression instead 

1833 compression = "jpeg" 

1834 elif compression == "tiff_deflate": 

1835 compression = "tiff_adobe_deflate" 

1836 

1837 libtiff = WRITE_LIBTIFF or compression != "raw" 

1838 

1839 ifd[IMAGEWIDTH] = im.size[0] 

1840 ifd[IMAGELENGTH] = im.size[1] 

1841 

1842 # write any arbitrary tags passed in as an ImageFileDirectory 

1843 subifds: list[int] = [] 

1844 if "tiffinfo" in encoderinfo: 

1845 info = encoderinfo["tiffinfo"] 

1846 elif "exif" in encoderinfo: 

1847 info = encoderinfo["exif"] 

1848 if isinstance(info, bytes): 

1849 exif = Image.Exif() 

1850 exif.load(info) 

1851 info = exif 

1852 elif isinstance(info, Image.Exif) and not libtiff: 

1853 subifds.extend( 

1854 key for key in TiffTags.TAGS_V2_GROUPS if info._ifds.get(key, {}) != {} 

1855 ) 

1856 else: 

1857 info = {} 

1858 logger.debug("Tiffinfo Keys: %s", list(info)) 

1859 if isinstance(info, ImageFileDirectory_v1): 

1860 info = info.to_v2() 

1861 for key in {*info, *subifds}: 

1862 if libtiff and key in {IMAGEWIDTH, IMAGELENGTH}: 

1863 continue 

1864 if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS: 

1865 ifd[key] = info.get_ifd(key) 

1866 else: 

1867 ifd[key] = info.get(key) 

1868 try: 

1869 ifd.tagtype[key] = info.tagtype[key] 

1870 except Exception: 

1871 pass # might not be an IFD. Might not have populated type 

1872 

1873 # required for color libtiff images 

1874 ifd[PLANAR_CONFIGURATION] = 1 

1875 

1876 legacy_ifd = {} 

1877 if hasattr(im, "tag"): 

1878 legacy_ifd = im.tag.to_v2() 

1879 

1880 supplied_tags = {**legacy_ifd, **getattr(im, "tag_v2", {})} 

1881 if supplied_tags.get(PLANAR_CONFIGURATION) == 2 and EXTRASAMPLES in supplied_tags: 

1882 # If the image used separate component planes, 

1883 # then EXTRASAMPLES should be ignored when saving contiguously 

1884 if SAMPLESPERPIXEL in supplied_tags: 

1885 supplied_tags[SAMPLESPERPIXEL] -= len(supplied_tags[EXTRASAMPLES]) 

1886 del supplied_tags[EXTRASAMPLES] 

1887 for tag in ( 

1888 # IFD offset that may not be correct in the saved image 

1889 EXIFIFD, 

1890 # Determined by the image format and should not be copied from legacy_ifd. 

1891 SAMPLEFORMAT, 

1892 ): 

1893 if tag in supplied_tags: 

1894 del supplied_tags[tag] 

1895 

1896 # additions written by Greg Couch, gregc@cgl.ucsf.edu 

1897 # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com 

1898 if hasattr(im, "tag_v2"): 

1899 # preserve tags from original TIFF image file 

1900 for key in ( 

1901 RESOLUTION_UNIT, 

1902 X_RESOLUTION, 

1903 Y_RESOLUTION, 

1904 IPTC_NAA_CHUNK, 

1905 PHOTOSHOP_CHUNK, 

1906 XMP, 

1907 ): 

1908 if key in im.tag_v2: 

1909 if key == IPTC_NAA_CHUNK and im.tag_v2.tagtype[key] not in ( 

1910 TiffTags.BYTE, 

1911 TiffTags.UNDEFINED, 

1912 ): 

1913 del supplied_tags[key] 

1914 else: 

1915 ifd[key] = im.tag_v2[key] 

1916 ifd.tagtype[key] = im.tag_v2.tagtype[key] 

1917 

1918 # preserve ICC profile (should also work when saving other formats 

1919 # which support profiles as TIFF) -- 2008-06-06 Florian Hoech 

1920 icc = encoderinfo.get("icc_profile", im.info.get("icc_profile")) 

1921 if icc: 

1922 ifd[ICCPROFILE] = icc 

1923 

1924 for key, name in [ 

1925 (IMAGEDESCRIPTION, "description"), 

1926 (X_RESOLUTION, "resolution"), 

1927 (Y_RESOLUTION, "resolution"), 

1928 (X_RESOLUTION, "x_resolution"), 

1929 (Y_RESOLUTION, "y_resolution"), 

1930 (RESOLUTION_UNIT, "resolution_unit"), 

1931 (SOFTWARE, "software"), 

1932 (DATE_TIME, "date_time"), 

1933 (ARTIST, "artist"), 

1934 (COPYRIGHT, "copyright"), 

1935 ]: 

1936 if name in encoderinfo: 

1937 ifd[key] = encoderinfo[name] 

1938 

1939 dpi = encoderinfo.get("dpi") 

1940 if dpi: 

1941 ifd[RESOLUTION_UNIT] = 2 

1942 ifd[X_RESOLUTION] = dpi[0] 

1943 ifd[Y_RESOLUTION] = dpi[1] 

1944 

1945 if bits != (1,): 

1946 ifd[BITSPERSAMPLE] = bits 

1947 ifd[SAMPLESPERPIXEL] = len(bits) 

1948 if extra is not None: 

1949 ifd[EXTRASAMPLES] = extra 

1950 if format != 1: 

1951 ifd[SAMPLEFORMAT] = format 

1952 

1953 if PHOTOMETRIC_INTERPRETATION not in ifd: 

1954 ifd[PHOTOMETRIC_INTERPRETATION] = photo 

1955 elif im.mode in ("1", "L") and ifd[PHOTOMETRIC_INTERPRETATION] == 0: 

1956 if im.mode == "1": 

1957 inverted_im = im.copy() 

1958 px = inverted_im.load() 

1959 if px is not None: 

1960 for y in range(inverted_im.height): 

1961 for x in range(inverted_im.width): 

1962 px[x, y] = 0 if px[x, y] == 255 else 255 

1963 im = inverted_im 

1964 else: 

1965 im = ImageOps.invert(im) 

1966 

1967 if im.mode in ["P", "PA"]: 

1968 lut = im.im.getpalette("RGB;L") 

1969 colormap = [] 

1970 colors = len(lut) // 3 

1971 for i in range(3): 

1972 # Multiplying by 257 scales 0-255 to 0-65535 

1973 colormap += [v * 257 for v in lut[colors * i : colors * (i + 1)]] 

1974 colormap += [0] * (256 - colors) 

1975 ifd[COLORMAP] = colormap 

1976 # data orientation 

1977 w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH] 

1978 stride = len(bits) * ((w * bits[0] + 7) // 8) 

1979 if ROWSPERSTRIP not in ifd: 

1980 # aim for given strip size (64 KB by default) when using libtiff writer 

1981 if libtiff: 

1982 im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE) 

1983 rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h) 

1984 # JPEG encoder expects multiple of 8 rows 

1985 if compression == "jpeg": 

1986 rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h) 

1987 else: 

1988 rows_per_strip = h 

1989 if rows_per_strip == 0: 

1990 rows_per_strip = 1 

1991 ifd[ROWSPERSTRIP] = rows_per_strip 

1992 strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP] 

1993 strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP] 

1994 if strip_byte_counts >= 2**16: 

1995 ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG 

1996 ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + ( 

1997 stride * h - strip_byte_counts * (strips_per_image - 1), 

1998 ) 

1999 ifd[STRIPOFFSETS] = tuple( 

2000 range(0, strip_byte_counts * strips_per_image, strip_byte_counts) 

2001 ) # this is adjusted by IFD writer 

2002 # no compression by default: 

2003 ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) 

2004 

2005 if im.mode == "YCbCr": 

2006 for tag, default_value in { 

2007 YCBCRSUBSAMPLING: (1, 1), 

2008 REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255), 

2009 }.items(): 

2010 ifd.setdefault(tag, default_value) 

2011 

2012 blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS] 

2013 if libtiff: 

2014 if "quality" in encoderinfo: 

2015 quality = encoderinfo["quality"] 

2016 if not isinstance(quality, int) or quality < 0 or quality > 100: 

2017 msg = "Invalid quality setting" 

2018 raise ValueError(msg) 

2019 if compression != "jpeg": 

2020 msg = "quality setting only supported for 'jpeg' compression" 

2021 raise ValueError(msg) 

2022 ifd[JPEGQUALITY] = quality 

2023 

2024 logger.debug("Saving using libtiff encoder") 

2025 logger.debug("Items: %s", sorted(ifd.items())) 

2026 _fp = 0 

2027 if hasattr(fp, "fileno"): 

2028 try: 

2029 fp.seek(0) 

2030 _fp = fp.fileno() 

2031 except io.UnsupportedOperation: 

2032 pass 

2033 

2034 # optional types for non core tags 

2035 types = {} 

2036 # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library 

2037 # based on the data in the strip. 

2038 # OSUBFILETYPE is deprecated. 

2039 # TRANSFERFUNCTION is not supported by the libtiff encoder. 

2040 # SUBIFD may also cause a segfault. 

2041 blocklist += [ 

2042 OSUBFILETYPE, 

2043 STRIPBYTECOUNTS, 

2044 STRIPOFFSETS, 

2045 TRANSFERFUNCTION, 

2046 SUBIFD, 

2047 ] 

2048 

2049 # bits per sample is a single short in the tiff directory, not a list. 

2050 atts: dict[int, Any] = {BITSPERSAMPLE: bits[0]} 

2051 # Merge the ones that we have with (optional) more bits from 

2052 # the original file, e.g x,y resolution so that we can 

2053 # save(load('')) == original file. 

2054 for tag, value in itertools.chain(ifd.items(), supplied_tags.items()): 

2055 # Libtiff can only process certain core items without adding 

2056 # them to the custom dictionary. 

2057 # Custom items are supported for int, float, unicode, string and byte 

2058 # values. Other types and tuples require a tagtype. 

2059 if tag not in TiffTags.LIBTIFF_CORE: 

2060 if tag in TiffTags.TAGS_V2_GROUPS: 

2061 types[tag] = TiffTags.LONG8 

2062 elif tag in ifd.tagtype: 

2063 types[tag] = ifd.tagtype[tag] 

2064 elif isinstance(value, (int, float, str, bytes)) or ( 

2065 isinstance(value, tuple) 

2066 and all(isinstance(v, (int, float, IFDRational)) for v in value) 

2067 ): 

2068 type = TiffTags.lookup(tag).type 

2069 if type: 

2070 types[tag] = type 

2071 if tag not in atts and tag not in blocklist: 

2072 if isinstance(value, str): 

2073 atts[tag] = value.encode("ascii", "replace") + b"\0" 

2074 elif isinstance(value, IFDRational): 

2075 atts[tag] = float(value) 

2076 else: 

2077 atts[tag] = value 

2078 

2079 if SAMPLEFORMAT in atts and len(atts[SAMPLEFORMAT]) == 1: 

2080 atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0] 

2081 _validate_tags(atts) 

2082 

2083 logger.debug("Converted items: %s", sorted(atts.items())) 

2084 

2085 # libtiff always expects the bytes in native order. 

2086 # we're storing image byte order. So, if the rawmode 

2087 # contains I;16, we need to convert from native to image 

2088 # byte order. 

2089 if im.mode in ("I;16", "I;16B", "I;16L"): 

2090 rawmode = "I;16N" 

2091 

2092 # Pass tags as sorted list so that the tags are set in a fixed order. 

2093 # This is required by libtiff for some tags. For example, the JPEGQUALITY 

2094 # pseudo tag requires that the COMPRESS tag was already set. 

2095 tags = list(atts.items()) 

2096 tags.sort() 

2097 a = (rawmode, _fp, filename, tags, types) 

2098 encoder = Image._getencoder(im.mode, "libtiff", a, encoderconfig) 

2099 try: 

2100 encoder.setimage(im.im) 

2101 while True: 

2102 errcode, data = encoder.encode(ImageFile.MAXBLOCK)[1:] 

2103 if not _fp: 

2104 fp.write(data) 

2105 if errcode: 

2106 break 

2107 if errcode < 0: 

2108 msg = f"encoder error {errcode} when writing image file" 

2109 raise OSError(msg) 

2110 finally: 

2111 encoder.cleanup() 

2112 

2113 else: 

2114 for tag in blocklist: 

2115 del ifd[tag] 

2116 _validate_tags(ifd._tags_v2) 

2117 offset = ifd.save(fp) 

2118 

2119 ImageFile._save( 

2120 im, 

2121 fp, 

2122 [ImageFile._Tile("raw", None, offset, (rawmode, stride, 1))], 

2123 ) 

2124 

2125 # -- helper for multi-page save -- 

2126 if "_debug_multipage" in encoderinfo: 

2127 # just to access o32 and o16 (using correct byte order) 

2128 setattr(im, "_debug_multipage", ifd) 

2129 

2130 

2131class AppendingTiffWriter(io.BytesIO): 

2132 fieldSizes = [ 

2133 0, # None 

2134 1, # byte 

2135 1, # ascii 

2136 2, # short 

2137 4, # long 

2138 8, # rational 

2139 1, # sbyte 

2140 1, # undefined 

2141 2, # sshort 

2142 4, # slong 

2143 8, # srational 

2144 4, # float 

2145 8, # double 

2146 4, # ifd 

2147 2, # unicode 

2148 4, # complex 

2149 8, # long8 

2150 ] 

2151 

2152 Tags = { 

2153 273, # StripOffsets 

2154 288, # FreeOffsets 

2155 324, # TileOffsets 

2156 519, # JPEGQTables 

2157 520, # JPEGDCTables 

2158 521, # JPEGACTables 

2159 } 

2160 

2161 def __init__(self, fn: StrOrBytesPath | IO[bytes], new: bool = False) -> None: 

2162 self.f: IO[bytes] 

2163 if is_path(fn): 

2164 self.name = fn 

2165 self.close_fp = True 

2166 try: 

2167 self.f = open(fn, "w+b" if new else "r+b") 

2168 except OSError: 

2169 self.f = open(fn, "w+b") 

2170 else: 

2171 self.f = cast("IO[bytes]", fn) 

2172 self.close_fp = False 

2173 self.beginning = self.f.tell() 

2174 self.setup() 

2175 

2176 def setup(self) -> None: 

2177 # Reset everything. 

2178 self.f.seek(self.beginning, os.SEEK_SET) 

2179 

2180 self.whereToWriteNewIFDOffset: int | None = None 

2181 self.offsetOfNewPage = 0 

2182 

2183 self.IIMM = iimm = self.f.read(4) 

2184 self._bigtiff = b"\x2b" in iimm 

2185 if not iimm: 

2186 # empty file - first page 

2187 self.isFirst = True 

2188 return 

2189 

2190 self.isFirst = False 

2191 if iimm not in PREFIXES: 

2192 msg = "Invalid TIFF file header" 

2193 raise RuntimeError(msg) 

2194 

2195 self.setEndian("<" if iimm.startswith(II) else ">") 

2196 

2197 if self._bigtiff: 

2198 self.f.seek(4, os.SEEK_CUR) 

2199 self.skipIFDs() 

2200 self.goToEnd() 

2201 

2202 def finalize(self) -> None: 

2203 if self.isFirst: 

2204 return 

2205 

2206 # fix offsets 

2207 self.f.seek(self.offsetOfNewPage) 

2208 

2209 iimm = self.f.read(4) 

2210 if not iimm: 

2211 # Make it easy to finish a frame without committing to a new one. 

2212 return 

2213 

2214 if iimm != self.IIMM: 

2215 msg = "IIMM of new page doesn't match IIMM of first page" 

2216 raise RuntimeError(msg) 

2217 

2218 if self._bigtiff: 

2219 self.f.seek(4, os.SEEK_CUR) 

2220 ifd_offset = self._read(8 if self._bigtiff else 4) 

2221 ifd_offset += self.offsetOfNewPage 

2222 assert self.whereToWriteNewIFDOffset is not None 

2223 self.f.seek(self.whereToWriteNewIFDOffset) 

2224 self._write(ifd_offset, 8 if self._bigtiff else 4) 

2225 self.f.seek(ifd_offset) 

2226 self.fixIFD() 

2227 

2228 def newFrame(self) -> None: 

2229 # Call this to finish a frame. 

2230 self.finalize() 

2231 self.setup() 

2232 

2233 def __enter__(self) -> Self: 

2234 return self 

2235 

2236 def __exit__(self, *args: object) -> None: 

2237 if self.close_fp: 

2238 self.close() 

2239 

2240 def tell(self) -> int: 

2241 return self.f.tell() - self.offsetOfNewPage 

2242 

2243 def seek(self, offset: int, whence: int = io.SEEK_SET) -> int: 

2244 """ 

2245 :param offset: Distance to seek. 

2246 :param whence: Whether the distance is relative to the start, 

2247 end or current position. 

2248 :returns: The resulting position, relative to the start. 

2249 """ 

2250 if whence == os.SEEK_SET: 

2251 offset += self.offsetOfNewPage 

2252 

2253 self.f.seek(offset, whence) 

2254 return self.tell() 

2255 

2256 def goToEnd(self) -> None: 

2257 self.f.seek(0, os.SEEK_END) 

2258 pos = self.f.tell() 

2259 

2260 # pad to 16 byte boundary 

2261 pad_bytes = 16 - pos % 16 

2262 if 0 < pad_bytes < 16: 

2263 self.f.write(bytes(pad_bytes)) 

2264 self.offsetOfNewPage = self.f.tell() 

2265 

2266 def setEndian(self, endian: str) -> None: 

2267 self.endian = endian 

2268 self.longFmt = f"{self.endian}L" 

2269 self.shortFmt = f"{self.endian}H" 

2270 self.tagFormat = f"{self.endian}HH" + ("Q" if self._bigtiff else "L") 

2271 

2272 def skipIFDs(self) -> None: 

2273 while True: 

2274 ifd_offset = self._read(8 if self._bigtiff else 4) 

2275 if ifd_offset == 0: 

2276 self.whereToWriteNewIFDOffset = self.f.tell() - ( 

2277 8 if self._bigtiff else 4 

2278 ) 

2279 break 

2280 

2281 self.f.seek(ifd_offset) 

2282 num_tags = self._read(8 if self._bigtiff else 2) 

2283 self.f.seek(num_tags * (20 if self._bigtiff else 12), os.SEEK_CUR) 

2284 

2285 def write(self, data: Buffer, /) -> int: 

2286 return self.f.write(data) 

2287 

2288 def _fmt(self, field_size: int) -> str: 

2289 try: 

2290 return {2: "H", 4: "L", 8: "Q"}[field_size] 

2291 except KeyError: 

2292 msg = "offset is not supported" 

2293 raise RuntimeError(msg) 

2294 

2295 def _read(self, field_size: int) -> int: 

2296 (value,) = struct.unpack( 

2297 self.endian + self._fmt(field_size), self.f.read(field_size) 

2298 ) 

2299 return value 

2300 

2301 def readShort(self) -> int: 

2302 return self._read(2) 

2303 

2304 def readLong(self) -> int: 

2305 return self._read(4) 

2306 

2307 @staticmethod 

2308 def _verify_bytes_written(bytes_written: int | None, expected: int) -> None: 

2309 if bytes_written is not None and bytes_written != expected: 

2310 msg = f"wrote only {bytes_written} bytes but wanted {expected}" 

2311 raise RuntimeError(msg) 

2312 

2313 def _rewriteLast( 

2314 self, value: int, field_size: int, new_field_size: int = 0 

2315 ) -> None: 

2316 self.f.seek(-field_size, os.SEEK_CUR) 

2317 if not new_field_size: 

2318 new_field_size = field_size 

2319 bytes_written = self.f.write( 

2320 struct.pack(self.endian + self._fmt(new_field_size), value) 

2321 ) 

2322 self._verify_bytes_written(bytes_written, new_field_size) 

2323 

2324 def rewriteLastShortToLong(self, value: int) -> None: 

2325 self._rewriteLast(value, 2, 4) 

2326 

2327 def rewriteLastShort(self, value: int) -> None: 

2328 return self._rewriteLast(value, 2) 

2329 

2330 def rewriteLastLong(self, value: int) -> None: 

2331 return self._rewriteLast(value, 4) 

2332 

2333 def _write(self, value: int, field_size: int) -> None: 

2334 bytes_written = self.f.write( 

2335 struct.pack(self.endian + self._fmt(field_size), value) 

2336 ) 

2337 self._verify_bytes_written(bytes_written, field_size) 

2338 

2339 def writeShort(self, value: int) -> None: 

2340 self._write(value, 2) 

2341 

2342 def writeLong(self, value: int) -> None: 

2343 self._write(value, 4) 

2344 

2345 def close(self) -> None: 

2346 self.finalize() 

2347 if self.close_fp: 

2348 self.f.close() 

2349 

2350 def fixIFD(self) -> None: 

2351 num_tags = self._read(8 if self._bigtiff else 2) 

2352 

2353 for i in range(num_tags): 

2354 tag, field_type, count = struct.unpack( 

2355 self.tagFormat, self.f.read(12 if self._bigtiff else 8) 

2356 ) 

2357 

2358 field_size = self.fieldSizes[field_type] 

2359 total_size = field_size * count 

2360 fmt_size = 8 if self._bigtiff else 4 

2361 is_local = total_size <= fmt_size 

2362 if not is_local: 

2363 offset = self._read(fmt_size) + self.offsetOfNewPage 

2364 self._rewriteLast(offset, fmt_size) 

2365 

2366 if tag in self.Tags: 

2367 cur_pos = self.f.tell() 

2368 

2369 logger.debug( 

2370 "fixIFD: %s (%d) - type: %s (%d) - type size: %d - count: %d", 

2371 TiffTags.lookup(tag).name, 

2372 tag, 

2373 TYPES.get(field_type, "unknown"), 

2374 field_type, 

2375 field_size, 

2376 count, 

2377 ) 

2378 

2379 if is_local: 

2380 self._fixOffsets(count, field_size) 

2381 self.f.seek(cur_pos + fmt_size) 

2382 else: 

2383 self.f.seek(offset) 

2384 self._fixOffsets(count, field_size) 

2385 self.f.seek(cur_pos) 

2386 

2387 elif is_local: 

2388 # skip the locally stored value that is not an offset 

2389 self.f.seek(fmt_size, os.SEEK_CUR) 

2390 

2391 def _fixOffsets(self, count: int, field_size: int) -> None: 

2392 for i in range(count): 

2393 offset = self._read(field_size) 

2394 offset += self.offsetOfNewPage 

2395 

2396 new_field_size = 0 

2397 if self._bigtiff and field_size in (2, 4) and offset >= 2**32: 

2398 # offset is now too large - we must convert long to long8 

2399 new_field_size = 8 

2400 elif field_size == 2 and offset >= 2**16: 

2401 # offset is now too large - we must convert short to long 

2402 new_field_size = 4 

2403 if new_field_size: 

2404 if count != 1: 

2405 msg = "not implemented" 

2406 raise RuntimeError(msg) # XXX TODO 

2407 

2408 # simple case - the offset is just one and therefore it is 

2409 # local (not referenced with another offset) 

2410 self._rewriteLast(offset, field_size, new_field_size) 

2411 # Move back past the new offset, past 'count', and before 'field_type' 

2412 rewind = -new_field_size - 4 - 2 

2413 self.f.seek(rewind, os.SEEK_CUR) 

2414 self.writeShort(new_field_size) # rewrite the type 

2415 self.f.seek(2 - rewind, os.SEEK_CUR) 

2416 else: 

2417 self._rewriteLast(offset, field_size) 

2418 

2419 def fixOffsets( 

2420 self, count: int, isShort: bool = False, isLong: bool = False 

2421 ) -> None: 

2422 if isShort: 

2423 field_size = 2 

2424 elif isLong: 

2425 field_size = 4 

2426 else: 

2427 field_size = 0 

2428 return self._fixOffsets(count, field_size) 

2429 

2430 

2431def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

2432 append_images = list(im.encoderinfo.get("append_images", [])) 

2433 if not hasattr(im, "n_frames") and not append_images: 

2434 return _save(im, fp, filename) 

2435 

2436 cur_idx = im.tell() 

2437 try: 

2438 with AppendingTiffWriter(fp) as tf: 

2439 for ims in [im] + append_images: 

2440 encoderinfo = ims._attach_default_encoderinfo(im) 

2441 if not hasattr(ims, "encoderconfig"): 

2442 ims.encoderconfig = () 

2443 nfr = getattr(ims, "n_frames", 1) 

2444 

2445 for idx in range(nfr): 

2446 ims.seek(idx) 

2447 ims.load() 

2448 _save(ims, tf, filename) 

2449 tf.newFrame() 

2450 ims.encoderinfo = encoderinfo 

2451 finally: 

2452 im.seek(cur_idx) 

2453 

2454 

2455# 

2456# -------------------------------------------------------------------- 

2457# Register 

2458 

2459Image.register_open(TiffImageFile.format, TiffImageFile, _accept) 

2460Image.register_save(TiffImageFile.format, _save) 

2461Image.register_save_all(TiffImageFile.format, _save_all) 

2462 

2463Image.register_extensions(TiffImageFile.format, [".tif", ".tiff"]) 

2464 

2465Image.register_mime(TiffImageFile.format, "image/tiff")