Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/pillow-11.0.0-py3.10-linux-x86_64.egg/PIL/TiffImagePlugin.py: 23%

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

1185 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 

43import io 

44import itertools 

45import logging 

46import math 

47import os 

48import struct 

49import warnings 

50from collections.abc import Iterator, MutableMapping 

51from fractions import Fraction 

52from numbers import Number, Rational 

53from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn, cast 

54 

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

56from ._binary import i16be as i16 

57from ._binary import i32be as i32 

58from ._binary import o8 

59from ._deprecate import deprecate 

60from ._typing import StrOrBytesPath 

61from ._util import is_path 

62from .TiffTags import TYPES 

63 

64if TYPE_CHECKING: 

65 from ._typing import Buffer, IntegralLike 

66 

67logger = logging.getLogger(__name__) 

68 

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

70READ_LIBTIFF = False 

71WRITE_LIBTIFF = False 

72STRIP_SIZE = 65536 

73 

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

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

76 

77# 

78# -------------------------------------------------------------------- 

79# Read TIFF files 

80 

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

82OSUBFILETYPE = 255 

83IMAGEWIDTH = 256 

84IMAGELENGTH = 257 

85BITSPERSAMPLE = 258 

86COMPRESSION = 259 

87PHOTOMETRIC_INTERPRETATION = 262 

88FILLORDER = 266 

89IMAGEDESCRIPTION = 270 

90STRIPOFFSETS = 273 

91SAMPLESPERPIXEL = 277 

92ROWSPERSTRIP = 278 

93STRIPBYTECOUNTS = 279 

94X_RESOLUTION = 282 

95Y_RESOLUTION = 283 

96PLANAR_CONFIGURATION = 284 

97RESOLUTION_UNIT = 296 

98TRANSFERFUNCTION = 301 

99SOFTWARE = 305 

100DATE_TIME = 306 

101ARTIST = 315 

102PREDICTOR = 317 

103COLORMAP = 320 

104TILEWIDTH = 322 

105TILELENGTH = 323 

106TILEOFFSETS = 324 

107TILEBYTECOUNTS = 325 

108SUBIFD = 330 

109EXTRASAMPLES = 338 

110SAMPLEFORMAT = 339 

111JPEGTABLES = 347 

112YCBCRSUBSAMPLING = 530 

113REFERENCEBLACKWHITE = 532 

114COPYRIGHT = 33432 

115IPTC_NAA_CHUNK = 33723 # newsphoto properties 

116PHOTOSHOP_CHUNK = 34377 # photoshop properties 

117ICCPROFILE = 34675 

118EXIFIFD = 34665 

119XMP = 700 

120JPEGQUALITY = 65537 # pseudo-tag by libtiff 

121 

122# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java 

123IMAGEJ_META_DATA_BYTE_COUNTS = 50838 

124IMAGEJ_META_DATA = 50839 

125 

126COMPRESSION_INFO = { 

127 # Compression => pil compression name 

128 1: "raw", 

129 2: "tiff_ccitt", 

130 3: "group3", 

131 4: "group4", 

132 5: "tiff_lzw", 

133 6: "tiff_jpeg", # obsolete 

134 7: "jpeg", 

135 8: "tiff_adobe_deflate", 

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

137 32773: "packbits", 

138 32809: "tiff_thunderscan", 

139 32946: "tiff_deflate", 

140 34676: "tiff_sgilog", 

141 34677: "tiff_sgilog24", 

142 34925: "lzma", 

143 50000: "zstd", 

144 50001: "webp", 

145} 

146 

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

148 

149OPEN_INFO = { 

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

151 # ExtraSamples) => mode, rawmode 

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

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

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

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

156 (II, 1, (1,), 1, (1,), ()): ("1", "1"), 

157 (MM, 1, (1,), 1, (1,), ()): ("1", "1"), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

267 # JPEG compressed images handled by LibTiff and auto-converted to RGBX 

268 # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel 

269 (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), 

270 (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"), 

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

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

273} 

274 

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

276 

277PREFIXES = [ 

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

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

280 b"MM\x2A\x00", # Invalid TIFF header, assume big-endian 

281 b"II\x00\x2A", # Invalid TIFF header, assume little-endian 

282 b"MM\x00\x2B", # BigTIFF with big-endian byte order 

283 b"II\x2B\x00", # BigTIFF with little-endian byte order 

284] 

285 

286if not getattr(Image.core, "libtiff_support_custom_tags", True): 

287 deprecate("Support for LibTIFF earlier than version 4", 12) 

288 

289 

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

291 return prefix[:4] in PREFIXES 

292 

293 

294def _limit_rational( 

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

296) -> tuple[IntegralLike, IntegralLike]: 

297 inv = abs(float(val)) > 1 

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

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

300 

301 

302def _limit_signed_rational( 

303 val: IFDRational, max_val: int, min_val: int 

304) -> tuple[IntegralLike, IntegralLike]: 

305 frac = Fraction(val) 

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

307 

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

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

310 

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

312 if max(n_d_float) > max_val: 

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

314 

315 return n_d 

316 

317 

318## 

319# Wrapper for TIFF IFDs. 

320 

321_load_dispatch = {} 

322_write_dispatch = {} 

323 

324 

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

326 def delegate( 

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

328 ) -> bool | float | Fraction: 

329 return getattr(self._val, op)(*args) 

330 

331 return delegate 

332 

333 

334class IFDRational(Rational): 

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

336 the in the wild use of exif rationals. 

337 

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

339 """ 

340 

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

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

343 

344 """ 

345 

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

347 

348 def __init__( 

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

350 ) -> None: 

351 """ 

352 :param value: either an integer numerator, a 

353 float/rational/other number, or an IFDRational 

354 :param denominator: Optional integer denominator 

355 """ 

356 self._val: Fraction | float 

357 if isinstance(value, IFDRational): 

358 self._numerator = value.numerator 

359 self._denominator = value.denominator 

360 self._val = value._val 

361 return 

362 

363 if isinstance(value, Fraction): 

364 self._numerator = value.numerator 

365 self._denominator = value.denominator 

366 else: 

367 if TYPE_CHECKING: 

368 self._numerator = cast(IntegralLike, value) 

369 else: 

370 self._numerator = value 

371 self._denominator = denominator 

372 

373 if denominator == 0: 

374 self._val = float("nan") 

375 elif denominator == 1: 

376 self._val = Fraction(value) 

377 elif int(value) == value: 

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

379 else: 

380 self._val = Fraction(value / denominator) 

381 

382 @property 

383 def numerator(self) -> IntegralLike: 

384 return self._numerator 

385 

386 @property 

387 def denominator(self) -> int: 

388 return self._denominator 

389 

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

391 """ 

392 

393 :param max_denominator: Integer, the maximum denominator value 

394 :returns: Tuple of (numerator, denominator) 

395 """ 

396 

397 if self.denominator == 0: 

398 return self.numerator, self.denominator 

399 

400 assert isinstance(self._val, Fraction) 

401 f = self._val.limit_denominator(max_denominator) 

402 return f.numerator, f.denominator 

403 

404 def __repr__(self) -> str: 

405 return str(float(self._val)) 

406 

407 def __hash__(self) -> int: 

408 return self._val.__hash__() 

409 

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

411 val = self._val 

412 if isinstance(other, IFDRational): 

413 other = other._val 

414 if isinstance(other, float): 

415 val = float(val) 

416 return val == other 

417 

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

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

420 

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

422 IFDRational.__init__(self, 0) 

423 _val, _numerator, _denominator = state 

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

425 self._val = _val 

426 if TYPE_CHECKING: 

427 self._numerator = cast(IntegralLike, _numerator) 

428 else: 

429 self._numerator = _numerator 

430 assert isinstance(_denominator, int) 

431 self._denominator = _denominator 

432 

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

434 'truediv', 'rtruediv', 'floordiv', 'rfloordiv', 

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

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

437 'ceil', 'floor', 'round'] 

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

439 """ 

440 

441 __add__ = _delegate("__add__") 

442 __radd__ = _delegate("__radd__") 

443 __sub__ = _delegate("__sub__") 

444 __rsub__ = _delegate("__rsub__") 

445 __mul__ = _delegate("__mul__") 

446 __rmul__ = _delegate("__rmul__") 

447 __truediv__ = _delegate("__truediv__") 

448 __rtruediv__ = _delegate("__rtruediv__") 

449 __floordiv__ = _delegate("__floordiv__") 

450 __rfloordiv__ = _delegate("__rfloordiv__") 

451 __mod__ = _delegate("__mod__") 

452 __rmod__ = _delegate("__rmod__") 

453 __pow__ = _delegate("__pow__") 

454 __rpow__ = _delegate("__rpow__") 

455 __pos__ = _delegate("__pos__") 

456 __neg__ = _delegate("__neg__") 

457 __abs__ = _delegate("__abs__") 

458 __trunc__ = _delegate("__trunc__") 

459 __lt__ = _delegate("__lt__") 

460 __gt__ = _delegate("__gt__") 

461 __le__ = _delegate("__le__") 

462 __ge__ = _delegate("__ge__") 

463 __bool__ = _delegate("__bool__") 

464 __ceil__ = _delegate("__ceil__") 

465 __floor__ = _delegate("__floor__") 

466 __round__ = _delegate("__round__") 

467 # Python >= 3.11 

468 if hasattr(Fraction, "__int__"): 

469 __int__ = _delegate("__int__") 

470 

471 

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

473 

474 

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

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

477 from .TiffTags import TYPES 

478 

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

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

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

482 return func 

483 

484 return decorator 

485 

486 

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

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

489 _write_dispatch[idx] = func # noqa: F821 

490 return func 

491 

492 return decorator 

493 

494 

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

496 from .TiffTags import TYPES 

497 

498 idx, fmt, name = idx_fmt_name 

499 TYPES[idx] = name 

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

501 

502 def basic_handler( 

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

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

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

506 

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

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

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

510 ) 

511 

512 

513if TYPE_CHECKING: 

514 _IFDv2Base = MutableMapping[int, Any] 

515else: 

516 _IFDv2Base = MutableMapping 

517 

518 

519class ImageFileDirectory_v2(_IFDv2Base): 

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

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

522 

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

524 

525 ifd = ImageFileDirectory_v2() 

526 ifd[key] = 'Some Data' 

527 ifd.tagtype[key] = TiffTags.ASCII 

528 print(ifd[key]) 

529 'Some Data' 

530 

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

532 returned as tuples of the values. 

533 

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

535 tag types in 

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

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

538 manually. 

539 

540 Data Structures: 

541 

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

543 

544 * Key: numerical TIFF tag number 

545 * Value: integer corresponding to the data type from 

546 :py:data:`.TiffTags.TYPES` 

547 

548 .. versionadded:: 3.0.0 

549 

550 'Internal' data structures: 

551 

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

553 

554 * Key: numerical TIFF tag number 

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

556 

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

558 

559 * Key: numerical TIFF tag number 

560 * Value: undecoded byte string from file 

561 

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

563 

564 * Key: numerical TIFF tag number 

565 * Value: decoded data in the v1 format 

566 

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

568 ``self._tags_v2`` once decoded. 

569 

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

571 from outside code. In cooperation with 

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

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

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

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

576 ``legacy_api == true``. 

577 

578 """ 

579 

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

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

582 

583 def __init__( 

584 self, 

585 ifh: bytes = b"II\052\0\0\0\0\0", 

586 prefix: bytes | None = None, 

587 group: int | None = None, 

588 ) -> None: 

589 """Initialize an ImageFileDirectory. 

590 

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

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

593 as the 'prefix' keyword argument. 

594 

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

596 endianness. 

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

598 """ 

599 if not _accept(ifh): 

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

601 raise SyntaxError(msg) 

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

603 if self._prefix == MM: 

604 self._endian = ">" 

605 elif self._prefix == II: 

606 self._endian = "<" 

607 else: 

608 msg = "not a TIFF IFD" 

609 raise SyntaxError(msg) 

610 self._bigtiff = ifh[2] == 43 

611 self.group = group 

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

613 """ Dictionary of tag types """ 

614 self.reset() 

615 self.next = ( 

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

617 if self._bigtiff 

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

619 ) 

620 self._legacy_api = False 

621 

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

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

624 

625 @property 

626 def legacy_api(self) -> bool: 

627 return self._legacy_api 

628 

629 @legacy_api.setter 

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

631 msg = "Not allowing setting of legacy api" 

632 raise Exception(msg) 

633 

634 def reset(self) -> None: 

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

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

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

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

639 self._next = None 

640 self._offset: int | None = None 

641 

642 def __str__(self) -> str: 

643 return str(dict(self)) 

644 

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

646 """ 

647 :returns: dict of name|key: value 

648 

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

650 """ 

651 return { 

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

653 for code, value in self.items() 

654 } 

655 

656 def __len__(self) -> int: 

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

658 

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

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

661 data = self._tagdata[tag] 

662 typ = self.tagtype[tag] 

663 size, handler = self._load_dispatch[typ] 

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

665 val = self._tags_v2[tag] 

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

667 val = (val,) 

668 return val 

669 

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

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

672 

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

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

675 

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

677 basetypes = (Number, bytes, str) 

678 

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

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

681 

682 if tag not in self.tagtype: 

683 if info.type: 

684 self.tagtype[tag] = info.type 

685 else: 

686 self.tagtype[tag] = TiffTags.UNDEFINED 

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

688 self.tagtype[tag] = ( 

689 TiffTags.RATIONAL 

690 if all(v >= 0 for v in values) 

691 else TiffTags.SIGNED_RATIONAL 

692 ) 

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

694 if all(0 <= v < 2**16 for v in values): 

695 self.tagtype[tag] = TiffTags.SHORT 

696 elif all(-(2**15) < v < 2**15 for v in values): 

697 self.tagtype[tag] = TiffTags.SIGNED_SHORT 

698 else: 

699 self.tagtype[tag] = ( 

700 TiffTags.LONG 

701 if all(v >= 0 for v in values) 

702 else TiffTags.SIGNED_LONG 

703 ) 

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

705 self.tagtype[tag] = TiffTags.DOUBLE 

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

707 self.tagtype[tag] = TiffTags.ASCII 

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

709 self.tagtype[tag] = TiffTags.BYTE 

710 

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

712 values = [ 

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

714 for v in values 

715 ] 

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

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

718 

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

720 if not is_ifd: 

721 values = tuple(info.cvt_enum(value) for value in values) 

722 

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

724 

725 # Three branches: 

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

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

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

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

730 if not is_ifd and ( 

731 (info.length == 1) 

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

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

734 ): 

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

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

737 TiffTags.RATIONAL, 

738 TiffTags.SIGNED_RATIONAL, 

739 ]: # rationals 

740 values = (values,) 

741 try: 

742 (dest[tag],) = values 

743 except ValueError: 

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

745 warnings.warn( 

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

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

748 ) 

749 dest[tag] = values[0] 

750 

751 else: 

752 # Spec'd length > 1 or undefined 

753 # Unspec'd, and length > 1 

754 dest[tag] = values 

755 

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

757 self._tags_v2.pop(tag, None) 

758 self._tags_v1.pop(tag, None) 

759 self._tagdata.pop(tag, None) 

760 

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

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

763 

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

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

766 

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

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

769 

770 list( 

771 map( 

772 _register_basic, 

773 [ 

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

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

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

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

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

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

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

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

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

783 ], 

784 ) 

785 ) 

786 

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

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

789 return data 

790 

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

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

793 if isinstance(data, IFDRational): 

794 data = int(data) 

795 if isinstance(data, int): 

796 data = bytes((data,)) 

797 return data 

798 

799 @_register_loader(2, 1) 

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

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

802 data = data[:-1] 

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

804 

805 @_register_writer(2) 

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

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

808 if isinstance(value, int): 

809 value = str(value) 

810 if not isinstance(value, bytes): 

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

812 return value + b"\0" 

813 

814 @_register_loader(5, 8) 

815 def load_rational( 

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

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

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

819 

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

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

822 

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

824 

825 @_register_writer(5) 

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

827 return b"".join( 

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

829 ) 

830 

831 @_register_loader(7, 1) 

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

833 return data 

834 

835 @_register_writer(7) 

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

837 if isinstance(value, IFDRational): 

838 value = int(value) 

839 if isinstance(value, int): 

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

841 return value 

842 

843 @_register_loader(10, 8) 

844 def load_signed_rational( 

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

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

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

848 

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

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

851 

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

853 

854 @_register_writer(10) 

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

856 return b"".join( 

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

858 for frac in values 

859 ) 

860 

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

862 ret = fp.read(size) 

863 if len(ret) != size: 

864 msg = ( 

865 "Corrupt EXIF data. " 

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

867 ) 

868 raise OSError(msg) 

869 return ret 

870 

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

872 self.reset() 

873 self._offset = fp.tell() 

874 

875 try: 

876 tag_count = ( 

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

878 if self._bigtiff 

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

880 )[0] 

881 for i in range(tag_count): 

882 tag, typ, count, data = ( 

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

884 if self._bigtiff 

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

886 ) 

887 

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

889 typname = TYPES.get(typ, "unknown") 

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

891 

892 try: 

893 unit_size, handler = self._load_dispatch[typ] 

894 except KeyError: 

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

896 continue # ignore unsupported type 

897 size = count * unit_size 

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

899 here = fp.tell() 

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

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

902 fp.seek(offset) 

903 data = ImageFile._safe_read(fp, size) 

904 fp.seek(here) 

905 else: 

906 data = data[:size] 

907 

908 if len(data) != size: 

909 warnings.warn( 

910 "Possibly corrupt EXIF data. " 

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

912 f" Skipping tag {tag}" 

913 ) 

914 logger.debug(msg) 

915 continue 

916 

917 if not data: 

918 logger.debug(msg) 

919 continue 

920 

921 self._tagdata[tag] = data 

922 self.tagtype[tag] = typ 

923 

924 msg += " - value: " + ( 

925 "<table: %d bytes>" % size if size > 32 else repr(data) 

926 ) 

927 logger.debug(msg) 

928 

929 (self.next,) = ( 

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

931 if self._bigtiff 

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

933 ) 

934 except OSError as msg: 

935 warnings.warn(str(msg)) 

936 return 

937 

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

939 # FIXME What about tagdata? 

940 result = self._pack("H", len(self._tags_v2)) 

941 

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

943 offset = offset + len(result) + len(self._tags_v2) * 12 + 4 

944 stripoffsets = None 

945 

946 # pass 1: convert tags to binary format 

947 # always write tags in ascending order 

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

949 if tag == STRIPOFFSETS: 

950 stripoffsets = len(entries) 

951 typ = self.tagtype[tag] 

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

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

954 if is_ifd: 

955 if self._endian == "<": 

956 ifh = b"II\x2A\x00\x08\x00\x00\x00" 

957 else: 

958 ifh = b"MM\x00\x2A\x00\x00\x00\x08" 

959 ifd = ImageFileDirectory_v2(ifh, group=tag) 

960 values = self._tags_v2[tag] 

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

962 ifd[ifd_tag] = ifd_value 

963 data = ifd.tobytes(offset) 

964 else: 

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

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

967 

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

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

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

971 msg += " - value: " + ( 

972 "<table: %d bytes>" % len(data) if len(data) >= 16 else str(values) 

973 ) 

974 logger.debug(msg) 

975 

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

977 if is_ifd: 

978 count = 1 

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

980 count = len(data) 

981 else: 

982 count = len(values) 

983 # figure out if data fits into the entry 

984 if len(data) <= 4: 

985 entries.append((tag, typ, count, data.ljust(4, b"\0"), b"")) 

986 else: 

987 entries.append((tag, typ, count, self._pack("L", offset), data)) 

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

989 

990 # update strip offset data to point beyond auxiliary data 

991 if stripoffsets is not None: 

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

993 if data: 

994 size, handler = self._load_dispatch[typ] 

995 values = [val + offset for val in handler(self, data, self.legacy_api)] 

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

997 else: 

998 value = self._pack("L", self._unpack("L", value)[0] + offset) 

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

1000 

1001 # pass 2: write entries to file 

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

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

1004 result += self._pack("HHL4s", tag, typ, count, value) 

1005 

1006 # -- overwrite here for multi-page -- 

1007 result += b"\0\0\0\0" # end of entries 

1008 

1009 # pass 3: write auxiliary data to file 

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

1011 result += data 

1012 if len(data) & 1: 

1013 result += b"\0" 

1014 

1015 return result 

1016 

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

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

1019 # tiff header -- PIL always starts the first IFD at offset 8 

1020 fp.write(self._prefix + self._pack("HL", 42, 8)) 

1021 

1022 offset = fp.tell() 

1023 result = self.tobytes(offset) 

1024 fp.write(result) 

1025 return offset + len(result) 

1026 

1027 

1028ImageFileDirectory_v2._load_dispatch = _load_dispatch 

1029ImageFileDirectory_v2._write_dispatch = _write_dispatch 

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

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

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

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

1034del _load_dispatch, _write_dispatch, idx, name 

1035 

1036 

1037# Legacy ImageFileDirectory support. 

1038class ImageFileDirectory_v1(ImageFileDirectory_v2): 

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

1040 

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

1042 

1043 ifd = ImageFileDirectory_v1() 

1044 ifd[key] = 'Some Data' 

1045 ifd.tagtype[key] = TiffTags.ASCII 

1046 print(ifd[key]) 

1047 ('Some Data',) 

1048 

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

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

1051 

1052 Values are returned as a tuple. 

1053 

1054 .. deprecated:: 3.0.0 

1055 """ 

1056 

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

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

1059 self._legacy_api = True 

1060 

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

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

1063 

1064 # defined in ImageFileDirectory_v2 

1065 tagtype: dict[int, int] 

1066 """Dictionary of tag types""" 

1067 

1068 @classmethod 

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

1070 """Returns an 

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

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

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

1074 instance. 

1075 

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

1077 

1078 """ 

1079 

1080 ifd = cls(prefix=original.prefix) 

1081 ifd._tagdata = original._tagdata 

1082 ifd.tagtype = original.tagtype 

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

1084 return ifd 

1085 

1086 def to_v2(self) -> ImageFileDirectory_v2: 

1087 """Returns an 

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

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

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

1091 instance. 

1092 

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

1094 

1095 """ 

1096 

1097 ifd = ImageFileDirectory_v2(prefix=self.prefix) 

1098 ifd._tagdata = dict(self._tagdata) 

1099 ifd.tagtype = dict(self.tagtype) 

1100 ifd._tags_v2 = dict(self._tags_v2) 

1101 return ifd 

1102 

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

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

1105 

1106 def __len__(self) -> int: 

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

1108 

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

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

1111 

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

1113 for legacy_api in (False, True): 

1114 self._setitem(tag, value, legacy_api) 

1115 

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

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

1118 data = self._tagdata[tag] 

1119 typ = self.tagtype[tag] 

1120 size, handler = self._load_dispatch[typ] 

1121 for legacy in (False, True): 

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

1123 val = self._tags_v1[tag] 

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

1125 val = (val,) 

1126 return val 

1127 

1128 

1129# undone -- switch this pointer 

1130ImageFileDirectory = ImageFileDirectory_v1 

1131 

1132 

1133## 

1134# Image plugin for TIFF files. 

1135 

1136 

1137class TiffImageFile(ImageFile.ImageFile): 

1138 format = "TIFF" 

1139 format_description = "Adobe TIFF" 

1140 _close_exclusive_fp_after_loading = False 

1141 

1142 def __init__( 

1143 self, 

1144 fp: StrOrBytesPath | IO[bytes], 

1145 filename: str | bytes | None = None, 

1146 ) -> None: 

1147 self.tag_v2: ImageFileDirectory_v2 

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

1149 

1150 self.tag: ImageFileDirectory_v1 

1151 """ Legacy tag entries """ 

1152 

1153 super().__init__(fp, filename) 

1154 

1155 def _open(self) -> None: 

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

1157 

1158 # Header 

1159 ifh = self.fp.read(8) 

1160 if ifh[2] == 43: 

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

1162 

1163 self.tag_v2 = ImageFileDirectory_v2(ifh) 

1164 

1165 # setup frame pointers 

1166 self.__first = self.__next = self.tag_v2.next 

1167 self.__frame = -1 

1168 self._fp = self.fp 

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

1170 self._n_frames: int | None = None 

1171 

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

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

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

1175 

1176 # and load the first frame 

1177 self._seek(0) 

1178 

1179 @property 

1180 def n_frames(self) -> int: 

1181 current_n_frames = self._n_frames 

1182 if current_n_frames is None: 

1183 current = self.tell() 

1184 self._seek(len(self._frame_pos)) 

1185 while self._n_frames is None: 

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

1187 self.seek(current) 

1188 assert self._n_frames is not None 

1189 return self._n_frames 

1190 

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

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

1193 if not self._seek_check(frame): 

1194 return 

1195 self._seek(frame) 

1196 if self._im is not None and ( 

1197 self.im.size != self._tile_size or self.im.mode != self.mode 

1198 ): 

1199 # The core image will no longer be used 

1200 self._im = None 

1201 

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

1203 self.fp = self._fp 

1204 

1205 # reset buffered io handle in case fp 

1206 # was passed to libtiff, invalidating the buffer 

1207 self.fp.tell() 

1208 

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

1210 if not self.__next: 

1211 msg = "no more images in TIFF file" 

1212 raise EOFError(msg) 

1213 logger.debug( 

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

1215 frame, 

1216 self.__frame, 

1217 self.__next, 

1218 self.fp.tell(), 

1219 ) 

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

1221 msg = "Unable to seek to frame" 

1222 raise ValueError(msg) 

1223 self.fp.seek(self.__next) 

1224 self._frame_pos.append(self.__next) 

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

1226 self.tag_v2.load(self.fp) 

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

1228 # This IFD has already been processed 

1229 # Declare this to be the end of the image 

1230 self.__next = 0 

1231 else: 

1232 self.__next = self.tag_v2.next 

1233 if self.__next == 0: 

1234 self._n_frames = frame + 1 

1235 if len(self._frame_pos) == 1: 

1236 self.is_animated = self.__next != 0 

1237 self.__frame += 1 

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

1239 self.tag_v2.load(self.fp) 

1240 if XMP in self.tag_v2: 

1241 self.info["xmp"] = self.tag_v2[XMP] 

1242 elif "xmp" in self.info: 

1243 del self.info["xmp"] 

1244 self._reload_exif() 

1245 # fill the legacy tag/ifd entries 

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

1247 self.__frame = frame 

1248 self._setup() 

1249 

1250 def tell(self) -> int: 

1251 """Return the current frame number""" 

1252 return self.__frame 

1253 

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

1255 """ 

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

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

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

1259 

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

1261 """ 

1262 blocks = {} 

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

1264 if val: 

1265 while val[:4] == b"8BIM": 

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

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

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

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

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

1271 

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

1273 return blocks 

1274 

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

1276 if self.tile and self.use_load_libtiff: 

1277 return self._load_libtiff() 

1278 return super().load() 

1279 

1280 def load_prepare(self) -> None: 

1281 if self._im is None: 

1282 Image._decompression_bomb_check(self._tile_size) 

1283 self.im = Image.core.new(self.mode, self._tile_size) 

1284 ImageFile.ImageFile.load_prepare(self) 

1285 

1286 def load_end(self) -> None: 

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

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

1289 if not self.is_animated: 

1290 self._close_exclusive_fp_after_loading = True 

1291 

1292 # reset buffered io handle in case fp 

1293 # was passed to libtiff, invalidating the buffer 

1294 self.fp.tell() 

1295 

1296 # load IFD data from fp before it is closed 

1297 exif = self.getexif() 

1298 for key in TiffTags.TAGS_V2_GROUPS: 

1299 if key not in exif: 

1300 continue 

1301 exif.get_ifd(key) 

1302 

1303 ImageOps.exif_transpose(self, in_place=True) 

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

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

1306 

1307 def _load_libtiff(self) -> Image.core.PixelAccess | None: 

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

1309 Calls out to libtiff""" 

1310 

1311 Image.Image.load(self) 

1312 

1313 self.load_prepare() 

1314 

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

1316 msg = "Not exactly one tile" 

1317 raise OSError(msg) 

1318 

1319 # (self._compression, (extents tuple), 

1320 # 0, (rawmode, self._compression, fp)) 

1321 extents = self.tile[0][1] 

1322 args = self.tile[0][3] 

1323 

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

1325 # file descriptor, use that instead of reading 

1326 # into a string in python. 

1327 try: 

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

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

1330 # should also eliminate the need for fp.tell 

1331 # in _seek 

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

1333 self.fp.flush() 

1334 except OSError: 

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

1336 # it doesn't use a file descriptor. 

1337 fp = False 

1338 

1339 if fp: 

1340 assert isinstance(args, tuple) 

1341 args_list = list(args) 

1342 args_list[2] = fp 

1343 args = tuple(args_list) 

1344 

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

1346 try: 

1347 decoder.setimage(self.im, extents) 

1348 except ValueError as e: 

1349 msg = "Couldn't set the image" 

1350 raise OSError(msg) from e 

1351 

1352 close_self_fp = self._exclusive_fp and not self.is_animated 

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

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

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

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

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

1358 # underlying string for stringio. 

1359 # 

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

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

1362 # deal with here by reordering. 

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

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

1365 elif fp: 

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

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

1368 if not close_self_fp: 

1369 self.fp.seek(0) 

1370 # 4 bytes, otherwise the trace might error out 

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

1372 else: 

1373 # we have something else. 

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

1375 self.fp.seek(0) 

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

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

1378 

1379 self.tile = [] 

1380 self.readonly = 0 

1381 

1382 self.load_end() 

1383 

1384 if close_self_fp: 

1385 self.fp.close() 

1386 self.fp = None # might be shared 

1387 

1388 if err < 0: 

1389 raise OSError(err) 

1390 

1391 return Image.Image.load(self) 

1392 

1393 def _setup(self) -> None: 

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

1395 

1396 if 0xBC01 in self.tag_v2: 

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

1398 raise OSError(msg) 

1399 

1400 # extract relevant tags 

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

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

1403 

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

1405 # the specification 

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

1407 

1408 # old style jpeg compression images most certainly are YCbCr 

1409 if self._compression == "tiff_jpeg": 

1410 photo = 6 

1411 

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

1413 

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

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

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

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

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

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

1420 

1421 # size 

1422 xsize = self.tag_v2.get(IMAGEWIDTH) 

1423 ysize = self.tag_v2.get(IMAGELENGTH) 

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

1425 msg = "Invalid dimensions" 

1426 raise ValueError(msg) 

1427 self._tile_size = xsize, ysize 

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

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

1430 self._size = ysize, xsize 

1431 else: 

1432 self._size = xsize, ysize 

1433 

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

1435 

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

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

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

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

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

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

1442 # for more exotic images. 

1443 sample_format = (1,) 

1444 

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

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

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

1448 bps_count = 3 

1449 elif photo == 5: # CMYK 

1450 bps_count = 4 

1451 else: 

1452 bps_count = 1 

1453 bps_count += len(extra_tuple) 

1454 bps_actual_count = len(bps_tuple) 

1455 samples_per_pixel = self.tag_v2.get( 

1456 SAMPLESPERPIXEL, 

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

1458 ) 

1459 

1460 if samples_per_pixel > MAX_SAMPLESPERPIXEL: 

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

1462 logger.error( 

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

1464 ) 

1465 msg = "Invalid value for samples per pixel" 

1466 raise SyntaxError(msg) 

1467 

1468 if samples_per_pixel < bps_actual_count: 

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

1470 # remove the excess. 

1471 bps_tuple = bps_tuple[:samples_per_pixel] 

1472 elif samples_per_pixel > bps_actual_count and bps_actual_count == 1: 

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

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

1475 bps_tuple = bps_tuple * samples_per_pixel 

1476 

1477 if len(bps_tuple) != samples_per_pixel: 

1478 msg = "unknown data organization" 

1479 raise SyntaxError(msg) 

1480 

1481 # mode: check photometric interpretation and bits per pixel 

1482 key = ( 

1483 self.tag_v2.prefix, 

1484 photo, 

1485 sample_format, 

1486 fillorder, 

1487 bps_tuple, 

1488 extra_tuple, 

1489 ) 

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

1491 try: 

1492 self._mode, rawmode = OPEN_INFO[key] 

1493 except KeyError as e: 

1494 logger.debug("- unsupported format") 

1495 msg = "unknown pixel mode" 

1496 raise SyntaxError(msg) from e 

1497 

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

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

1500 

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

1502 

1503 xres = self.tag_v2.get(X_RESOLUTION, 1) 

1504 yres = self.tag_v2.get(Y_RESOLUTION, 1) 

1505 

1506 if xres and yres: 

1507 resunit = self.tag_v2.get(RESOLUTION_UNIT) 

1508 if resunit == 2: # dots per inch 

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

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

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

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

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

1514 # For backward compatibility, 

1515 # we also preserve the old behavior 

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

1517 else: # No absolute unit of measurement 

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

1519 

1520 # build tile descriptors 

1521 x = y = layer = 0 

1522 self.tile = [] 

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

1524 if self.use_load_libtiff: 

1525 # Decoder expects entire file as one tile. 

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

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

1528 # function. 

1529 # 

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

1531 # use the _load_libtiff function. 

1532 

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

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

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

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

1537 if fillorder == 2: 

1538 # Replace fillorder with fillorder=1 

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

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

1541 # this should always work, since all the 

1542 # fillorder==2 modes have a corresponding 

1543 # fillorder=1 mode 

1544 self._mode, rawmode = OPEN_INFO[key] 

1545 # libtiff always returns the bytes in native order. 

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

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

1548 # byte order. 

1549 if rawmode == "I;16": 

1550 rawmode = "I;16N" 

1551 if ";16B" in rawmode: 

1552 rawmode = rawmode.replace(";16B", ";16N") 

1553 if ";16L" in rawmode: 

1554 rawmode = rawmode.replace(";16L", ";16N") 

1555 

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

1557 # unpacked straight into RGB values 

1558 if ( 

1559 photo == 6 

1560 and self._compression == "jpeg" 

1561 and self._planar_configuration == 1 

1562 ): 

1563 rawmode = "RGB" 

1564 

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

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

1567 a = (rawmode, self._compression, False, self.tag_v2.offset) 

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

1569 

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

1571 # striped image 

1572 if STRIPOFFSETS in self.tag_v2: 

1573 offsets = self.tag_v2[STRIPOFFSETS] 

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

1575 w = xsize 

1576 else: 

1577 # tiled image 

1578 offsets = self.tag_v2[TILEOFFSETS] 

1579 tilewidth = self.tag_v2.get(TILEWIDTH) 

1580 h = self.tag_v2.get(TILELENGTH) 

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

1582 msg = "Invalid tile dimensions" 

1583 raise ValueError(msg) 

1584 w = tilewidth 

1585 

1586 for offset in offsets: 

1587 if x + w > xsize: 

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

1589 else: 

1590 stride = 0 

1591 

1592 tile_rawmode = rawmode 

1593 if self._planar_configuration == 2: 

1594 # each band on it's own layer 

1595 tile_rawmode = rawmode[layer] 

1596 # adjust stride width accordingly 

1597 stride /= bps_count 

1598 

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

1600 self.tile.append( 

1601 ImageFile._Tile( 

1602 self._compression, 

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

1604 offset, 

1605 args, 

1606 ) 

1607 ) 

1608 x = x + w 

1609 if x >= xsize: 

1610 x, y = 0, y + h 

1611 if y >= ysize: 

1612 x = y = 0 

1613 layer += 1 

1614 else: 

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

1616 msg = "unknown data organization" 

1617 raise SyntaxError(msg) 

1618 

1619 # Fix up info. 

1620 if ICCPROFILE in self.tag_v2: 

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

1622 

1623 # fixup palette descriptor 

1624 

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

1626 palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]] 

1627 self.palette = ImagePalette.raw("RGB;L", b"".join(palette)) 

1628 

1629 

1630# 

1631# -------------------------------------------------------------------- 

1632# Write TIFF files 

1633 

1634# little endian is default except for image modes with 

1635# explicit big endian byte-order 

1636 

1637SAVE_INFO = { 

1638 # mode => rawmode, byteorder, photometrics, 

1639 # sampleformat, bitspersample, extra 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1659} 

1660 

1661 

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

1663 try: 

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

1665 except KeyError as e: 

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

1667 raise OSError(msg) from e 

1668 

1669 ifd = ImageFileDirectory_v2(prefix=prefix) 

1670 

1671 encoderinfo = im.encoderinfo 

1672 encoderconfig = im.encoderconfig 

1673 try: 

1674 compression = encoderinfo["compression"] 

1675 except KeyError: 

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

1677 if isinstance(compression, int): 

1678 # compression value may be from BMP. Ignore it 

1679 compression = None 

1680 if compression is None: 

1681 compression = "raw" 

1682 elif compression == "tiff_jpeg": 

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

1684 compression = "jpeg" 

1685 elif compression == "tiff_deflate": 

1686 compression = "tiff_adobe_deflate" 

1687 

1688 libtiff = WRITE_LIBTIFF or compression != "raw" 

1689 

1690 # required for color libtiff images 

1691 ifd[PLANAR_CONFIGURATION] = 1 

1692 

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

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

1695 

1696 # write any arbitrary tags passed in as an ImageFileDirectory 

1697 if "tiffinfo" in encoderinfo: 

1698 info = encoderinfo["tiffinfo"] 

1699 elif "exif" in encoderinfo: 

1700 info = encoderinfo["exif"] 

1701 if isinstance(info, bytes): 

1702 exif = Image.Exif() 

1703 exif.load(info) 

1704 info = exif 

1705 else: 

1706 info = {} 

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

1708 if isinstance(info, ImageFileDirectory_v1): 

1709 info = info.to_v2() 

1710 for key in info: 

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

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

1713 else: 

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

1715 try: 

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

1717 except Exception: 

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

1719 

1720 legacy_ifd = {} 

1721 if hasattr(im, "tag"): 

1722 legacy_ifd = im.tag.to_v2() 

1723 

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

1725 for tag in ( 

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

1727 EXIFIFD, 

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

1729 SAMPLEFORMAT, 

1730 ): 

1731 if tag in supplied_tags: 

1732 del supplied_tags[tag] 

1733 

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

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

1736 if hasattr(im, "tag_v2"): 

1737 # preserve tags from original TIFF image file 

1738 for key in ( 

1739 RESOLUTION_UNIT, 

1740 X_RESOLUTION, 

1741 Y_RESOLUTION, 

1742 IPTC_NAA_CHUNK, 

1743 PHOTOSHOP_CHUNK, 

1744 XMP, 

1745 ): 

1746 if key in im.tag_v2: 

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

1748 TiffTags.BYTE, 

1749 TiffTags.UNDEFINED, 

1750 ): 

1751 del supplied_tags[key] 

1752 else: 

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

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

1755 

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

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

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

1759 if icc: 

1760 ifd[ICCPROFILE] = icc 

1761 

1762 for key, name in [ 

1763 (IMAGEDESCRIPTION, "description"), 

1764 (X_RESOLUTION, "resolution"), 

1765 (Y_RESOLUTION, "resolution"), 

1766 (X_RESOLUTION, "x_resolution"), 

1767 (Y_RESOLUTION, "y_resolution"), 

1768 (RESOLUTION_UNIT, "resolution_unit"), 

1769 (SOFTWARE, "software"), 

1770 (DATE_TIME, "date_time"), 

1771 (ARTIST, "artist"), 

1772 (COPYRIGHT, "copyright"), 

1773 ]: 

1774 if name in encoderinfo: 

1775 ifd[key] = encoderinfo[name] 

1776 

1777 dpi = encoderinfo.get("dpi") 

1778 if dpi: 

1779 ifd[RESOLUTION_UNIT] = 2 

1780 ifd[X_RESOLUTION] = dpi[0] 

1781 ifd[Y_RESOLUTION] = dpi[1] 

1782 

1783 if bits != (1,): 

1784 ifd[BITSPERSAMPLE] = bits 

1785 if len(bits) != 1: 

1786 ifd[SAMPLESPERPIXEL] = len(bits) 

1787 if extra is not None: 

1788 ifd[EXTRASAMPLES] = extra 

1789 if format != 1: 

1790 ifd[SAMPLEFORMAT] = format 

1791 

1792 if PHOTOMETRIC_INTERPRETATION not in ifd: 

1793 ifd[PHOTOMETRIC_INTERPRETATION] = photo 

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

1795 if im.mode == "1": 

1796 inverted_im = im.copy() 

1797 px = inverted_im.load() 

1798 if px is not None: 

1799 for y in range(inverted_im.height): 

1800 for x in range(inverted_im.width): 

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

1802 im = inverted_im 

1803 else: 

1804 im = ImageOps.invert(im) 

1805 

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

1807 lut = im.im.getpalette("RGB", "RGB;L") 

1808 colormap = [] 

1809 colors = len(lut) // 3 

1810 for i in range(3): 

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

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

1813 ifd[COLORMAP] = colormap 

1814 # data orientation 

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

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

1817 if ROWSPERSTRIP not in ifd: 

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

1819 if libtiff: 

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

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

1822 # JPEG encoder expects multiple of 8 rows 

1823 if compression == "jpeg": 

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

1825 else: 

1826 rows_per_strip = h 

1827 if rows_per_strip == 0: 

1828 rows_per_strip = 1 

1829 ifd[ROWSPERSTRIP] = rows_per_strip 

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

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

1832 if strip_byte_counts >= 2**16: 

1833 ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG 

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

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

1836 ) 

1837 ifd[STRIPOFFSETS] = tuple( 

1838 range(0, strip_byte_counts * strips_per_image, strip_byte_counts) 

1839 ) # this is adjusted by IFD writer 

1840 # no compression by default: 

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

1842 

1843 if im.mode == "YCbCr": 

1844 for tag, default_value in { 

1845 YCBCRSUBSAMPLING: (1, 1), 

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

1847 }.items(): 

1848 ifd.setdefault(tag, default_value) 

1849 

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

1851 if libtiff: 

1852 if "quality" in encoderinfo: 

1853 quality = encoderinfo["quality"] 

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

1855 msg = "Invalid quality setting" 

1856 raise ValueError(msg) 

1857 if compression != "jpeg": 

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

1859 raise ValueError(msg) 

1860 ifd[JPEGQUALITY] = quality 

1861 

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

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

1864 _fp = 0 

1865 if hasattr(fp, "fileno"): 

1866 try: 

1867 fp.seek(0) 

1868 _fp = fp.fileno() 

1869 except io.UnsupportedOperation: 

1870 pass 

1871 

1872 # optional types for non core tags 

1873 types = {} 

1874 # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library 

1875 # based on the data in the strip. 

1876 # OSUBFILETYPE is deprecated. 

1877 # The other tags expect arrays with a certain length (fixed or depending on 

1878 # BITSPERSAMPLE, etc), passing arrays with a different length will result in 

1879 # segfaults. Block these tags until we add extra validation. 

1880 # SUBIFD may also cause a segfault. 

1881 blocklist += [ 

1882 OSUBFILETYPE, 

1883 REFERENCEBLACKWHITE, 

1884 STRIPBYTECOUNTS, 

1885 STRIPOFFSETS, 

1886 TRANSFERFUNCTION, 

1887 SUBIFD, 

1888 ] 

1889 

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

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

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

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

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

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

1896 # Libtiff can only process certain core items without adding 

1897 # them to the custom dictionary. 

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

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

1900 if tag not in TiffTags.LIBTIFF_CORE: 

1901 if not getattr(Image.core, "libtiff_support_custom_tags", False): 

1902 continue 

1903 

1904 if tag in ifd.tagtype: 

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

1906 elif not (isinstance(value, (int, float, str, bytes))): 

1907 continue 

1908 else: 

1909 type = TiffTags.lookup(tag).type 

1910 if type: 

1911 types[tag] = type 

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

1913 if isinstance(value, str): 

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

1915 elif isinstance(value, IFDRational): 

1916 atts[tag] = float(value) 

1917 else: 

1918 atts[tag] = value 

1919 

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

1921 atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0] 

1922 

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

1924 

1925 # libtiff always expects the bytes in native order. 

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

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

1928 # byte order. 

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

1930 rawmode = "I;16N" 

1931 

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

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

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

1935 tags = list(atts.items()) 

1936 tags.sort() 

1937 a = (rawmode, compression, _fp, filename, tags, types) 

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

1939 encoder.setimage(im.im, (0, 0) + im.size) 

1940 while True: 

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

1942 if not _fp: 

1943 fp.write(data) 

1944 if errcode: 

1945 break 

1946 if errcode < 0: 

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

1948 raise OSError(msg) 

1949 

1950 else: 

1951 for tag in blocklist: 

1952 del ifd[tag] 

1953 offset = ifd.save(fp) 

1954 

1955 ImageFile._save( 

1956 im, 

1957 fp, 

1958 [ImageFile._Tile("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))], 

1959 ) 

1960 

1961 # -- helper for multi-page save -- 

1962 if "_debug_multipage" in encoderinfo: 

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

1964 setattr(im, "_debug_multipage", ifd) 

1965 

1966 

1967class AppendingTiffWriter(io.BytesIO): 

1968 fieldSizes = [ 

1969 0, # None 

1970 1, # byte 

1971 1, # ascii 

1972 2, # short 

1973 4, # long 

1974 8, # rational 

1975 1, # sbyte 

1976 1, # undefined 

1977 2, # sshort 

1978 4, # slong 

1979 8, # srational 

1980 4, # float 

1981 8, # double 

1982 4, # ifd 

1983 2, # unicode 

1984 4, # complex 

1985 8, # long8 

1986 ] 

1987 

1988 Tags = { 

1989 273, # StripOffsets 

1990 288, # FreeOffsets 

1991 324, # TileOffsets 

1992 519, # JPEGQTables 

1993 520, # JPEGDCTables 

1994 521, # JPEGACTables 

1995 } 

1996 

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

1998 self.f: IO[bytes] 

1999 if is_path(fn): 

2000 self.name = fn 

2001 self.close_fp = True 

2002 try: 

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

2004 except OSError: 

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

2006 else: 

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

2008 self.close_fp = False 

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

2010 self.setup() 

2011 

2012 def setup(self) -> None: 

2013 # Reset everything. 

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

2015 

2016 self.whereToWriteNewIFDOffset: int | None = None 

2017 self.offsetOfNewPage = 0 

2018 

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

2020 if not iimm: 

2021 # empty file - first page 

2022 self.isFirst = True 

2023 return 

2024 

2025 self.isFirst = False 

2026 if iimm == b"II\x2a\x00": 

2027 self.setEndian("<") 

2028 elif iimm == b"MM\x00\x2a": 

2029 self.setEndian(">") 

2030 else: 

2031 msg = "Invalid TIFF file header" 

2032 raise RuntimeError(msg) 

2033 

2034 self.skipIFDs() 

2035 self.goToEnd() 

2036 

2037 def finalize(self) -> None: 

2038 if self.isFirst: 

2039 return 

2040 

2041 # fix offsets 

2042 self.f.seek(self.offsetOfNewPage) 

2043 

2044 iimm = self.f.read(4) 

2045 if not iimm: 

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

2047 return 

2048 

2049 if iimm != self.IIMM: 

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

2051 raise RuntimeError(msg) 

2052 

2053 ifd_offset = self.readLong() 

2054 ifd_offset += self.offsetOfNewPage 

2055 assert self.whereToWriteNewIFDOffset is not None 

2056 self.f.seek(self.whereToWriteNewIFDOffset) 

2057 self.writeLong(ifd_offset) 

2058 self.f.seek(ifd_offset) 

2059 self.fixIFD() 

2060 

2061 def newFrame(self) -> None: 

2062 # Call this to finish a frame. 

2063 self.finalize() 

2064 self.setup() 

2065 

2066 def __enter__(self) -> AppendingTiffWriter: 

2067 return self 

2068 

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

2070 if self.close_fp: 

2071 self.close() 

2072 

2073 def tell(self) -> int: 

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

2075 

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

2077 """ 

2078 :param offset: Distance to seek. 

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

2080 end or current position. 

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

2082 """ 

2083 if whence == os.SEEK_SET: 

2084 offset += self.offsetOfNewPage 

2085 

2086 self.f.seek(offset, whence) 

2087 return self.tell() 

2088 

2089 def goToEnd(self) -> None: 

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

2091 pos = self.f.tell() 

2092 

2093 # pad to 16 byte boundary 

2094 pad_bytes = 16 - pos % 16 

2095 if 0 < pad_bytes < 16: 

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

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

2098 

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

2100 self.endian = endian 

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

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

2103 self.tagFormat = f"{self.endian}HHL" 

2104 

2105 def skipIFDs(self) -> None: 

2106 while True: 

2107 ifd_offset = self.readLong() 

2108 if ifd_offset == 0: 

2109 self.whereToWriteNewIFDOffset = self.f.tell() - 4 

2110 break 

2111 

2112 self.f.seek(ifd_offset) 

2113 num_tags = self.readShort() 

2114 self.f.seek(num_tags * 12, os.SEEK_CUR) 

2115 

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

2117 return self.f.write(data) 

2118 

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

2120 try: 

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

2122 except KeyError: 

2123 msg = "offset is not supported" 

2124 raise RuntimeError(msg) 

2125 

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

2127 (value,) = struct.unpack( 

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

2129 ) 

2130 return value 

2131 

2132 def readShort(self) -> int: 

2133 return self._read(2) 

2134 

2135 def readLong(self) -> int: 

2136 return self._read(4) 

2137 

2138 @staticmethod 

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

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

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

2142 raise RuntimeError(msg) 

2143 

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

2145 self.f.seek(-2, os.SEEK_CUR) 

2146 bytes_written = self.f.write(struct.pack(self.longFmt, value)) 

2147 self._verify_bytes_written(bytes_written, 4) 

2148 

2149 def _rewriteLast(self, value: int, field_size: int) -> None: 

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

2151 bytes_written = self.f.write( 

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

2153 ) 

2154 self._verify_bytes_written(bytes_written, field_size) 

2155 

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

2157 return self._rewriteLast(value, 2) 

2158 

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

2160 return self._rewriteLast(value, 4) 

2161 

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

2163 bytes_written = self.f.write(struct.pack(self.shortFmt, value)) 

2164 self._verify_bytes_written(bytes_written, 2) 

2165 

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

2167 bytes_written = self.f.write(struct.pack(self.longFmt, value)) 

2168 self._verify_bytes_written(bytes_written, 4) 

2169 

2170 def close(self) -> None: 

2171 self.finalize() 

2172 if self.close_fp: 

2173 self.f.close() 

2174 

2175 def fixIFD(self) -> None: 

2176 num_tags = self.readShort() 

2177 

2178 for i in range(num_tags): 

2179 tag, field_type, count = struct.unpack(self.tagFormat, self.f.read(8)) 

2180 

2181 field_size = self.fieldSizes[field_type] 

2182 total_size = field_size * count 

2183 is_local = total_size <= 4 

2184 if not is_local: 

2185 offset = self.readLong() + self.offsetOfNewPage 

2186 self.rewriteLastLong(offset) 

2187 

2188 if tag in self.Tags: 

2189 cur_pos = self.f.tell() 

2190 

2191 if is_local: 

2192 self._fixOffsets(count, field_size) 

2193 self.f.seek(cur_pos + 4) 

2194 else: 

2195 self.f.seek(offset) 

2196 self._fixOffsets(count, field_size) 

2197 self.f.seek(cur_pos) 

2198 

2199 elif is_local: 

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

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

2202 

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

2204 for i in range(count): 

2205 offset = self._read(field_size) 

2206 offset += self.offsetOfNewPage 

2207 if field_size == 2 and offset >= 65536: 

2208 # offset is now too large - we must convert shorts to longs 

2209 if count != 1: 

2210 msg = "not implemented" 

2211 raise RuntimeError(msg) # XXX TODO 

2212 

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

2214 # local (not referenced with another offset) 

2215 self.rewriteLastShortToLong(offset) 

2216 self.f.seek(-10, os.SEEK_CUR) 

2217 self.writeShort(TiffTags.LONG) # rewrite the type to LONG 

2218 self.f.seek(8, os.SEEK_CUR) 

2219 else: 

2220 self._rewriteLast(offset, field_size) 

2221 

2222 def fixOffsets( 

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

2224 ) -> None: 

2225 if isShort: 

2226 field_size = 2 

2227 elif isLong: 

2228 field_size = 4 

2229 else: 

2230 field_size = 0 

2231 return self._fixOffsets(count, field_size) 

2232 

2233 

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

2235 encoderinfo = im.encoderinfo.copy() 

2236 encoderconfig = im.encoderconfig 

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

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

2239 return _save(im, fp, filename) 

2240 

2241 cur_idx = im.tell() 

2242 try: 

2243 with AppendingTiffWriter(fp) as tf: 

2244 for ims in [im] + append_images: 

2245 ims.encoderinfo = encoderinfo 

2246 ims.encoderconfig = encoderconfig 

2247 if not hasattr(ims, "n_frames"): 

2248 nfr = 1 

2249 else: 

2250 nfr = ims.n_frames 

2251 

2252 for idx in range(nfr): 

2253 ims.seek(idx) 

2254 ims.load() 

2255 _save(ims, tf, filename) 

2256 tf.newFrame() 

2257 finally: 

2258 im.seek(cur_idx) 

2259 

2260 

2261# 

2262# -------------------------------------------------------------------- 

2263# Register 

2264 

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

2266Image.register_save(TiffImageFile.format, _save) 

2267Image.register_save_all(TiffImageFile.format, _save_all) 

2268 

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

2270 

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