Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pillow-10.4.0-py3.8-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

1138 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 MutableMapping 

51from fractions import Fraction 

52from numbers import Number, Rational 

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

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 .TiffTags import TYPES 

61 

62logger = logging.getLogger(__name__) 

63 

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

65READ_LIBTIFF = False 

66WRITE_LIBTIFF = False 

67IFD_LEGACY_API = True 

68STRIP_SIZE = 65536 

69 

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

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

72 

73# 

74# -------------------------------------------------------------------- 

75# Read TIFF files 

76 

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

78OSUBFILETYPE = 255 

79IMAGEWIDTH = 256 

80IMAGELENGTH = 257 

81BITSPERSAMPLE = 258 

82COMPRESSION = 259 

83PHOTOMETRIC_INTERPRETATION = 262 

84FILLORDER = 266 

85IMAGEDESCRIPTION = 270 

86STRIPOFFSETS = 273 

87SAMPLESPERPIXEL = 277 

88ROWSPERSTRIP = 278 

89STRIPBYTECOUNTS = 279 

90X_RESOLUTION = 282 

91Y_RESOLUTION = 283 

92PLANAR_CONFIGURATION = 284 

93RESOLUTION_UNIT = 296 

94TRANSFERFUNCTION = 301 

95SOFTWARE = 305 

96DATE_TIME = 306 

97ARTIST = 315 

98PREDICTOR = 317 

99COLORMAP = 320 

100TILEWIDTH = 322 

101TILELENGTH = 323 

102TILEOFFSETS = 324 

103TILEBYTECOUNTS = 325 

104SUBIFD = 330 

105EXTRASAMPLES = 338 

106SAMPLEFORMAT = 339 

107JPEGTABLES = 347 

108YCBCRSUBSAMPLING = 530 

109REFERENCEBLACKWHITE = 532 

110COPYRIGHT = 33432 

111IPTC_NAA_CHUNK = 33723 # newsphoto properties 

112PHOTOSHOP_CHUNK = 34377 # photoshop properties 

113ICCPROFILE = 34675 

114EXIFIFD = 34665 

115XMP = 700 

116JPEGQUALITY = 65537 # pseudo-tag by libtiff 

117 

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

119IMAGEJ_META_DATA_BYTE_COUNTS = 50838 

120IMAGEJ_META_DATA = 50839 

121 

122COMPRESSION_INFO = { 

123 # Compression => pil compression name 

124 1: "raw", 

125 2: "tiff_ccitt", 

126 3: "group3", 

127 4: "group4", 

128 5: "tiff_lzw", 

129 6: "tiff_jpeg", # obsolete 

130 7: "jpeg", 

131 8: "tiff_adobe_deflate", 

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

133 32773: "packbits", 

134 32809: "tiff_thunderscan", 

135 32946: "tiff_deflate", 

136 34676: "tiff_sgilog", 

137 34677: "tiff_sgilog24", 

138 34925: "lzma", 

139 50000: "zstd", 

140 50001: "webp", 

141} 

142 

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

144 

145OPEN_INFO = { 

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

147 # ExtraSamples) => mode, rawmode 

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

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

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

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

152 (II, 1, (1,), 1, (1,), ()): ("1", "1"), 

153 (MM, 1, (1,), 1, (1,), ()): ("1", "1"), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

268} 

269 

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

271 

272PREFIXES = [ 

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

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

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

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

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

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

279] 

280 

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

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

283 

284 

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

286 return prefix[:4] in PREFIXES 

287 

288 

289def _limit_rational(val, max_val): 

290 inv = abs(val) > 1 

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

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

293 

294 

295def _limit_signed_rational(val, max_val, min_val): 

296 frac = Fraction(val) 

297 n_d = frac.numerator, frac.denominator 

298 

299 if min(n_d) < min_val: 

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

301 

302 if max(n_d) > max_val: 

303 val = Fraction(*n_d) 

304 n_d = _limit_rational(val, max_val) 

305 

306 return n_d 

307 

308 

309## 

310# Wrapper for TIFF IFDs. 

311 

312_load_dispatch = {} 

313_write_dispatch = {} 

314 

315 

316def _delegate(op): 

317 def delegate(self, *args): 

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

319 

320 return delegate 

321 

322 

323class IFDRational(Rational): 

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

325 the in the wild use of exif rationals. 

326 

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

328 """ 

329 

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

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

332 

333 """ 

334 

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

336 

337 def __init__(self, value, denominator=1): 

338 """ 

339 :param value: either an integer numerator, a 

340 float/rational/other number, or an IFDRational 

341 :param denominator: Optional integer denominator 

342 """ 

343 if isinstance(value, IFDRational): 

344 self._numerator = value.numerator 

345 self._denominator = value.denominator 

346 self._val = value._val 

347 return 

348 

349 if isinstance(value, Fraction): 

350 self._numerator = value.numerator 

351 self._denominator = value.denominator 

352 else: 

353 self._numerator = value 

354 self._denominator = denominator 

355 

356 if denominator == 0: 

357 self._val = float("nan") 

358 elif denominator == 1: 

359 self._val = Fraction(value) 

360 else: 

361 self._val = Fraction(value, denominator) 

362 

363 @property 

364 def numerator(self): 

365 return self._numerator 

366 

367 @property 

368 def denominator(self): 

369 return self._denominator 

370 

371 def limit_rational(self, max_denominator): 

372 """ 

373 

374 :param max_denominator: Integer, the maximum denominator value 

375 :returns: Tuple of (numerator, denominator) 

376 """ 

377 

378 if self.denominator == 0: 

379 return self.numerator, self.denominator 

380 

381 f = self._val.limit_denominator(max_denominator) 

382 return f.numerator, f.denominator 

383 

384 def __repr__(self) -> str: 

385 return str(float(self._val)) 

386 

387 def __hash__(self) -> int: 

388 return self._val.__hash__() 

389 

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

391 val = self._val 

392 if isinstance(other, IFDRational): 

393 other = other._val 

394 if isinstance(other, float): 

395 val = float(val) 

396 return val == other 

397 

398 def __getstate__(self): 

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

400 

401 def __setstate__(self, state): 

402 IFDRational.__init__(self, 0) 

403 _val, _numerator, _denominator = state 

404 self._val = _val 

405 self._numerator = _numerator 

406 self._denominator = _denominator 

407 

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

409 'truediv', 'rtruediv', 'floordiv', 'rfloordiv', 

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

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

412 'ceil', 'floor', 'round'] 

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

414 """ 

415 

416 __add__ = _delegate("__add__") 

417 __radd__ = _delegate("__radd__") 

418 __sub__ = _delegate("__sub__") 

419 __rsub__ = _delegate("__rsub__") 

420 __mul__ = _delegate("__mul__") 

421 __rmul__ = _delegate("__rmul__") 

422 __truediv__ = _delegate("__truediv__") 

423 __rtruediv__ = _delegate("__rtruediv__") 

424 __floordiv__ = _delegate("__floordiv__") 

425 __rfloordiv__ = _delegate("__rfloordiv__") 

426 __mod__ = _delegate("__mod__") 

427 __rmod__ = _delegate("__rmod__") 

428 __pow__ = _delegate("__pow__") 

429 __rpow__ = _delegate("__rpow__") 

430 __pos__ = _delegate("__pos__") 

431 __neg__ = _delegate("__neg__") 

432 __abs__ = _delegate("__abs__") 

433 __trunc__ = _delegate("__trunc__") 

434 __lt__ = _delegate("__lt__") 

435 __gt__ = _delegate("__gt__") 

436 __le__ = _delegate("__le__") 

437 __ge__ = _delegate("__ge__") 

438 __bool__ = _delegate("__bool__") 

439 __ceil__ = _delegate("__ceil__") 

440 __floor__ = _delegate("__floor__") 

441 __round__ = _delegate("__round__") 

442 # Python >= 3.11 

443 if hasattr(Fraction, "__int__"): 

444 __int__ = _delegate("__int__") 

445 

446 

447def _register_loader(idx, size): 

448 def decorator(func): 

449 from .TiffTags import TYPES 

450 

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

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

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

454 return func 

455 

456 return decorator 

457 

458 

459def _register_writer(idx): 

460 def decorator(func): 

461 _write_dispatch[idx] = func # noqa: F821 

462 return func 

463 

464 return decorator 

465 

466 

467def _register_basic(idx_fmt_name): 

468 from .TiffTags import TYPES 

469 

470 idx, fmt, name = idx_fmt_name 

471 TYPES[idx] = name 

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

473 _load_dispatch[idx] = ( # noqa: F821 

474 size, 

475 lambda self, data, legacy_api=True: ( 

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

477 ), 

478 ) 

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

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

481 ) 

482 

483 

484if TYPE_CHECKING: 

485 _IFDv2Base = MutableMapping[int, Any] 

486else: 

487 _IFDv2Base = MutableMapping 

488 

489 

490class ImageFileDirectory_v2(_IFDv2Base): 

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

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

493 

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

495 

496 ifd = ImageFileDirectory_v2() 

497 ifd[key] = 'Some Data' 

498 ifd.tagtype[key] = TiffTags.ASCII 

499 print(ifd[key]) 

500 'Some Data' 

501 

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

503 returned as tuples of the values. 

504 

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

506 tag types in 

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

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

509 manually. 

510 

511 Data Structures: 

512 

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

514 

515 * Key: numerical TIFF tag number 

516 * Value: integer corresponding to the data type from 

517 :py:data:`.TiffTags.TYPES` 

518 

519 .. versionadded:: 3.0.0 

520 

521 'Internal' data structures: 

522 

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

524 

525 * Key: numerical TIFF tag number 

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

527 

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

529 

530 * Key: numerical TIFF tag number 

531 * Value: undecoded byte string from file 

532 

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

534 

535 * Key: numerical TIFF tag number 

536 * Value: decoded data in the v1 format 

537 

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

539 ``self._tags_v2`` once decoded. 

540 

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

542 from outside code. In cooperation with 

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

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

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

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

547 ``legacy_api == true``. 

548 

549 """ 

550 

551 _load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {} 

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

553 

554 def __init__( 

555 self, 

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

557 prefix: bytes | None = None, 

558 group: int | None = None, 

559 ) -> None: 

560 """Initialize an ImageFileDirectory. 

561 

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

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

564 as the 'prefix' keyword argument. 

565 

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

567 endianness. 

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

569 """ 

570 if not _accept(ifh): 

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

572 raise SyntaxError(msg) 

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

574 if self._prefix == MM: 

575 self._endian = ">" 

576 elif self._prefix == II: 

577 self._endian = "<" 

578 else: 

579 msg = "not a TIFF IFD" 

580 raise SyntaxError(msg) 

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

582 self.group = group 

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

584 """ Dictionary of tag types """ 

585 self.reset() 

586 (self.next,) = ( 

587 self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:]) 

588 ) 

589 self._legacy_api = False 

590 

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

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

593 

594 @property 

595 def legacy_api(self) -> bool: 

596 return self._legacy_api 

597 

598 @legacy_api.setter 

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

600 msg = "Not allowing setting of legacy api" 

601 raise Exception(msg) 

602 

603 def reset(self) -> None: 

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

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

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

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

608 self._next = None 

609 self._offset = None 

610 

611 def __str__(self) -> str: 

612 return str(dict(self)) 

613 

614 def named(self): 

615 """ 

616 :returns: dict of name|key: value 

617 

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

619 """ 

620 return { 

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

622 for code, value in self.items() 

623 } 

624 

625 def __len__(self) -> int: 

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

627 

628 def __getitem__(self, tag): 

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

630 data = self._tagdata[tag] 

631 typ = self.tagtype[tag] 

632 size, handler = self._load_dispatch[typ] 

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

634 val = self._tags_v2[tag] 

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

636 val = (val,) 

637 return val 

638 

639 def __contains__(self, tag): 

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

641 

642 def __setitem__(self, tag, value): 

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

644 

645 def _setitem(self, tag, value, legacy_api): 

646 basetypes = (Number, bytes, str) 

647 

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

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

650 

651 if tag not in self.tagtype: 

652 if info.type: 

653 self.tagtype[tag] = info.type 

654 else: 

655 self.tagtype[tag] = TiffTags.UNDEFINED 

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

657 self.tagtype[tag] = ( 

658 TiffTags.RATIONAL 

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

660 else TiffTags.SIGNED_RATIONAL 

661 ) 

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

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

664 self.tagtype[tag] = TiffTags.SHORT 

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

666 self.tagtype[tag] = TiffTags.SIGNED_SHORT 

667 else: 

668 self.tagtype[tag] = ( 

669 TiffTags.LONG 

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

671 else TiffTags.SIGNED_LONG 

672 ) 

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

674 self.tagtype[tag] = TiffTags.DOUBLE 

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

676 self.tagtype[tag] = TiffTags.ASCII 

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

678 self.tagtype[tag] = TiffTags.BYTE 

679 

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

681 values = [ 

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

683 for v in values 

684 ] 

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

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

687 

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

689 if not is_ifd: 

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

691 

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

693 

694 # Three branches: 

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

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

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

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

699 if not is_ifd and ( 

700 (info.length == 1) 

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

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

703 ): 

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

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

706 TiffTags.RATIONAL, 

707 TiffTags.SIGNED_RATIONAL, 

708 ]: # rationals 

709 values = (values,) 

710 try: 

711 (dest[tag],) = values 

712 except ValueError: 

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

714 warnings.warn( 

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

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

717 ) 

718 dest[tag] = values[0] 

719 

720 else: 

721 # Spec'd length > 1 or undefined 

722 # Unspec'd, and length > 1 

723 dest[tag] = values 

724 

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

726 self._tags_v2.pop(tag, None) 

727 self._tags_v1.pop(tag, None) 

728 self._tagdata.pop(tag, None) 

729 

730 def __iter__(self): 

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

732 

733 def _unpack(self, fmt, data): 

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

735 

736 def _pack(self, fmt, *values): 

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

738 

739 list( 

740 map( 

741 _register_basic, 

742 [ 

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

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

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

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

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

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

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

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

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

752 ], 

753 ) 

754 ) 

755 

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

757 def load_byte(self, data, legacy_api=True): 

758 return data 

759 

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

761 def write_byte(self, data): 

762 if isinstance(data, IFDRational): 

763 data = int(data) 

764 if isinstance(data, int): 

765 data = bytes((data,)) 

766 return data 

767 

768 @_register_loader(2, 1) 

769 def load_string(self, data, legacy_api=True): 

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

771 data = data[:-1] 

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

773 

774 @_register_writer(2) 

775 def write_string(self, value): 

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

777 if isinstance(value, int): 

778 value = str(value) 

779 if not isinstance(value, bytes): 

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

781 return value + b"\0" 

782 

783 @_register_loader(5, 8) 

784 def load_rational(self, data, legacy_api=True): 

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

786 

787 def combine(a, b): 

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

789 

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

791 

792 @_register_writer(5) 

793 def write_rational(self, *values): 

794 return b"".join( 

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

796 ) 

797 

798 @_register_loader(7, 1) 

799 def load_undefined(self, data, legacy_api=True): 

800 return data 

801 

802 @_register_writer(7) 

803 def write_undefined(self, value): 

804 if isinstance(value, IFDRational): 

805 value = int(value) 

806 if isinstance(value, int): 

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

808 return value 

809 

810 @_register_loader(10, 8) 

811 def load_signed_rational(self, data, legacy_api=True): 

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

813 

814 def combine(a, b): 

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

816 

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

818 

819 @_register_writer(10) 

820 def write_signed_rational(self, *values): 

821 return b"".join( 

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

823 for frac in values 

824 ) 

825 

826 def _ensure_read(self, fp, size): 

827 ret = fp.read(size) 

828 if len(ret) != size: 

829 msg = ( 

830 "Corrupt EXIF data. " 

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

832 ) 

833 raise OSError(msg) 

834 return ret 

835 

836 def load(self, fp): 

837 self.reset() 

838 self._offset = fp.tell() 

839 

840 try: 

841 tag_count = ( 

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

843 if self._bigtiff 

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

845 )[0] 

846 for i in range(tag_count): 

847 tag, typ, count, data = ( 

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

849 if self._bigtiff 

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

851 ) 

852 

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

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

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

856 

857 try: 

858 unit_size, handler = self._load_dispatch[typ] 

859 except KeyError: 

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

861 continue # ignore unsupported type 

862 size = count * unit_size 

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

864 here = fp.tell() 

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

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

867 fp.seek(offset) 

868 data = ImageFile._safe_read(fp, size) 

869 fp.seek(here) 

870 else: 

871 data = data[:size] 

872 

873 if len(data) != size: 

874 warnings.warn( 

875 "Possibly corrupt EXIF data. " 

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

877 f" Skipping tag {tag}" 

878 ) 

879 logger.debug(msg) 

880 continue 

881 

882 if not data: 

883 logger.debug(msg) 

884 continue 

885 

886 self._tagdata[tag] = data 

887 self.tagtype[tag] = typ 

888 

889 msg += " - value: " + ( 

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

891 ) 

892 logger.debug(msg) 

893 

894 (self.next,) = ( 

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

896 if self._bigtiff 

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

898 ) 

899 except OSError as msg: 

900 warnings.warn(str(msg)) 

901 return 

902 

903 def tobytes(self, offset=0): 

904 # FIXME What about tagdata? 

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

906 

907 entries = [] 

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

909 stripoffsets = None 

910 

911 # pass 1: convert tags to binary format 

912 # always write tags in ascending order 

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

914 if tag == STRIPOFFSETS: 

915 stripoffsets = len(entries) 

916 typ = self.tagtype.get(tag) 

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

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

919 if is_ifd: 

920 if self._endian == "<": 

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

922 else: 

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

924 ifd = ImageFileDirectory_v2(ifh, group=tag) 

925 values = self._tags_v2[tag] 

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

927 ifd[ifd_tag] = ifd_value 

928 data = ifd.tobytes(offset) 

929 else: 

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

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

932 

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

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

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

936 msg += " - value: " + ( 

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

938 ) 

939 logger.debug(msg) 

940 

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

942 if is_ifd: 

943 count = 1 

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

945 count = len(data) 

946 else: 

947 count = len(values) 

948 # figure out if data fits into the entry 

949 if len(data) <= 4: 

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

951 else: 

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

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

954 

955 # update strip offset data to point beyond auxiliary data 

956 if stripoffsets is not None: 

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

958 if data: 

959 msg = "multistrip support not yet implemented" 

960 raise NotImplementedError(msg) 

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

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

963 

964 # pass 2: write entries to file 

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

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

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

968 

969 # -- overwrite here for multi-page -- 

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

971 

972 # pass 3: write auxiliary data to file 

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

974 result += data 

975 if len(data) & 1: 

976 result += b"\0" 

977 

978 return result 

979 

980 def save(self, fp): 

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

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

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

984 

985 offset = fp.tell() 

986 result = self.tobytes(offset) 

987 fp.write(result) 

988 return offset + len(result) 

989 

990 

991ImageFileDirectory_v2._load_dispatch = _load_dispatch 

992ImageFileDirectory_v2._write_dispatch = _write_dispatch 

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

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

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

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

997del _load_dispatch, _write_dispatch, idx, name 

998 

999 

1000# Legacy ImageFileDirectory support. 

1001class ImageFileDirectory_v1(ImageFileDirectory_v2): 

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

1003 

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

1005 

1006 ifd = ImageFileDirectory_v1() 

1007 ifd[key] = 'Some Data' 

1008 ifd.tagtype[key] = TiffTags.ASCII 

1009 print(ifd[key]) 

1010 ('Some Data',) 

1011 

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

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

1014 

1015 Values are returned as a tuple. 

1016 

1017 .. deprecated:: 3.0.0 

1018 """ 

1019 

1020 def __init__(self, *args, **kwargs): 

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

1022 self._legacy_api = True 

1023 

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

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

1026 

1027 # defined in ImageFileDirectory_v2 

1028 tagtype: dict[int, int] 

1029 """Dictionary of tag types""" 

1030 

1031 @classmethod 

1032 def from_v2(cls, original): 

1033 """Returns an 

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

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

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

1037 instance. 

1038 

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

1040 

1041 """ 

1042 

1043 ifd = cls(prefix=original.prefix) 

1044 ifd._tagdata = original._tagdata 

1045 ifd.tagtype = original.tagtype 

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

1047 return ifd 

1048 

1049 def to_v2(self) -> ImageFileDirectory_v2: 

1050 """Returns an 

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

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

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

1054 instance. 

1055 

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

1057 

1058 """ 

1059 

1060 ifd = ImageFileDirectory_v2(prefix=self.prefix) 

1061 ifd._tagdata = dict(self._tagdata) 

1062 ifd.tagtype = dict(self.tagtype) 

1063 ifd._tags_v2 = dict(self._tags_v2) 

1064 return ifd 

1065 

1066 def __contains__(self, tag): 

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

1068 

1069 def __len__(self) -> int: 

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

1071 

1072 def __iter__(self): 

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

1074 

1075 def __setitem__(self, tag, value): 

1076 for legacy_api in (False, True): 

1077 self._setitem(tag, value, legacy_api) 

1078 

1079 def __getitem__(self, tag): 

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

1081 data = self._tagdata[tag] 

1082 typ = self.tagtype[tag] 

1083 size, handler = self._load_dispatch[typ] 

1084 for legacy in (False, True): 

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

1086 val = self._tags_v1[tag] 

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

1088 val = (val,) 

1089 return val 

1090 

1091 

1092# undone -- switch this pointer when IFD_LEGACY_API == False 

1093ImageFileDirectory = ImageFileDirectory_v1 

1094 

1095 

1096## 

1097# Image plugin for TIFF files. 

1098 

1099 

1100class TiffImageFile(ImageFile.ImageFile): 

1101 format = "TIFF" 

1102 format_description = "Adobe TIFF" 

1103 _close_exclusive_fp_after_loading = False 

1104 

1105 def __init__(self, fp=None, filename=None): 

1106 self.tag_v2 = None 

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

1108 

1109 self.tag = None 

1110 """ Legacy tag entries """ 

1111 

1112 super().__init__(fp, filename) 

1113 

1114 def _open(self) -> None: 

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

1116 

1117 # Header 

1118 ifh = self.fp.read(8) 

1119 if ifh[2] == 43: 

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

1121 

1122 self.tag_v2 = ImageFileDirectory_v2(ifh) 

1123 

1124 # legacy IFD entries will be filled in later 

1125 self.ifd = None 

1126 

1127 # setup frame pointers 

1128 self.__first = self.__next = self.tag_v2.next 

1129 self.__frame = -1 

1130 self._fp = self.fp 

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

1132 self._n_frames: int | None = None 

1133 

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

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

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

1137 

1138 # and load the first frame 

1139 self._seek(0) 

1140 

1141 @property 

1142 def n_frames(self): 

1143 if self._n_frames is None: 

1144 current = self.tell() 

1145 self._seek(len(self._frame_pos)) 

1146 while self._n_frames is None: 

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

1148 self.seek(current) 

1149 return self._n_frames 

1150 

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

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

1153 if not self._seek_check(frame): 

1154 return 

1155 self._seek(frame) 

1156 # Create a new core image object on second and 

1157 # subsequent frames in the image. Image may be 

1158 # different size/mode. 

1159 Image._decompression_bomb_check(self.size) 

1160 self.im = Image.core.new(self.mode, self.size) 

1161 

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

1163 self.fp = self._fp 

1164 

1165 # reset buffered io handle in case fp 

1166 # was passed to libtiff, invalidating the buffer 

1167 self.fp.tell() 

1168 

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

1170 if not self.__next: 

1171 msg = "no more images in TIFF file" 

1172 raise EOFError(msg) 

1173 logger.debug( 

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

1175 frame, 

1176 self.__frame, 

1177 self.__next, 

1178 self.fp.tell(), 

1179 ) 

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

1181 msg = "Unable to seek to frame" 

1182 raise ValueError(msg) 

1183 self.fp.seek(self.__next) 

1184 self._frame_pos.append(self.__next) 

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

1186 self.tag_v2.load(self.fp) 

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

1188 # This IFD has already been processed 

1189 # Declare this to be the end of the image 

1190 self.__next = 0 

1191 else: 

1192 self.__next = self.tag_v2.next 

1193 if self.__next == 0: 

1194 self._n_frames = frame + 1 

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

1196 self.is_animated = self.__next != 0 

1197 self.__frame += 1 

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

1199 self.tag_v2.load(self.fp) 

1200 if XMP in self.tag_v2: 

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

1202 elif "xmp" in self.info: 

1203 del self.info["xmp"] 

1204 self._reload_exif() 

1205 # fill the legacy tag/ifd entries 

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

1207 self.__frame = frame 

1208 self._setup() 

1209 

1210 def tell(self) -> int: 

1211 """Return the current frame number""" 

1212 return self.__frame 

1213 

1214 def get_photoshop_blocks(self): 

1215 """ 

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

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

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

1219 

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

1221 """ 

1222 blocks = {} 

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

1224 if val: 

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

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

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

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

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

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

1231 

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

1233 return blocks 

1234 

1235 def load(self): 

1236 if self.tile and self.use_load_libtiff: 

1237 return self._load_libtiff() 

1238 return super().load() 

1239 

1240 def load_end(self) -> None: 

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

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

1243 if not self.is_animated: 

1244 self._close_exclusive_fp_after_loading = True 

1245 

1246 # reset buffered io handle in case fp 

1247 # was passed to libtiff, invalidating the buffer 

1248 self.fp.tell() 

1249 

1250 # load IFD data from fp before it is closed 

1251 exif = self.getexif() 

1252 for key in TiffTags.TAGS_V2_GROUPS: 

1253 if key not in exif: 

1254 continue 

1255 exif.get_ifd(key) 

1256 

1257 ImageOps.exif_transpose(self, in_place=True) 

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

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

1260 

1261 def _load_libtiff(self): 

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

1263 Calls out to libtiff""" 

1264 

1265 Image.Image.load(self) 

1266 

1267 self.load_prepare() 

1268 

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

1270 msg = "Not exactly one tile" 

1271 raise OSError(msg) 

1272 

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

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

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

1276 args = list(self.tile[0][3]) 

1277 

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

1279 # file descriptor, use that instead of reading 

1280 # into a string in python. 

1281 try: 

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

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

1284 # should also eliminate the need for fp.tell 

1285 # in _seek 

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

1287 self.fp.flush() 

1288 except OSError: 

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

1290 # it doesn't use a file descriptor. 

1291 fp = False 

1292 

1293 if fp: 

1294 args[2] = fp 

1295 

1296 decoder = Image._getdecoder( 

1297 self.mode, "libtiff", tuple(args), self.decoderconfig 

1298 ) 

1299 try: 

1300 decoder.setimage(self.im, extents) 

1301 except ValueError as e: 

1302 msg = "Couldn't set the image" 

1303 raise OSError(msg) from e 

1304 

1305 close_self_fp = self._exclusive_fp and not self.is_animated 

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

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

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

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

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

1311 # underlying string for stringio. 

1312 # 

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

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

1315 # deal with here by reordering. 

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

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

1318 elif fp: 

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

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

1321 if not close_self_fp: 

1322 self.fp.seek(0) 

1323 # 4 bytes, otherwise the trace might error out 

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

1325 else: 

1326 # we have something else. 

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

1328 self.fp.seek(0) 

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

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

1331 

1332 self.tile = [] 

1333 self.readonly = 0 

1334 

1335 self.load_end() 

1336 

1337 if close_self_fp: 

1338 self.fp.close() 

1339 self.fp = None # might be shared 

1340 

1341 if err < 0: 

1342 raise OSError(err) 

1343 

1344 return Image.Image.load(self) 

1345 

1346 def _setup(self): 

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

1348 

1349 if 0xBC01 in self.tag_v2: 

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

1351 raise OSError(msg) 

1352 

1353 # extract relevant tags 

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

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

1356 

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

1358 # the specification 

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

1360 

1361 # old style jpeg compression images most certainly are YCbCr 

1362 if self._compression == "tiff_jpeg": 

1363 photo = 6 

1364 

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

1366 

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

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

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

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

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

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

1373 

1374 # size 

1375 xsize = int(self.tag_v2.get(IMAGEWIDTH)) 

1376 ysize = int(self.tag_v2.get(IMAGELENGTH)) 

1377 self._size = xsize, ysize 

1378 

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

1380 

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

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

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

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

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

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

1387 # for more exotic images. 

1388 sample_format = (1,) 

1389 

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

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

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

1393 bps_count = 3 

1394 elif photo == 5: # CMYK 

1395 bps_count = 4 

1396 else: 

1397 bps_count = 1 

1398 bps_count += len(extra_tuple) 

1399 bps_actual_count = len(bps_tuple) 

1400 samples_per_pixel = self.tag_v2.get( 

1401 SAMPLESPERPIXEL, 

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

1403 ) 

1404 

1405 if samples_per_pixel > MAX_SAMPLESPERPIXEL: 

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

1407 logger.error( 

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

1409 ) 

1410 msg = "Invalid value for samples per pixel" 

1411 raise SyntaxError(msg) 

1412 

1413 if samples_per_pixel < bps_actual_count: 

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

1415 # remove the excess. 

1416 bps_tuple = bps_tuple[:samples_per_pixel] 

1417 elif samples_per_pixel > bps_actual_count and bps_actual_count == 1: 

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

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

1420 bps_tuple = bps_tuple * samples_per_pixel 

1421 

1422 if len(bps_tuple) != samples_per_pixel: 

1423 msg = "unknown data organization" 

1424 raise SyntaxError(msg) 

1425 

1426 # mode: check photometric interpretation and bits per pixel 

1427 key = ( 

1428 self.tag_v2.prefix, 

1429 photo, 

1430 sample_format, 

1431 fillorder, 

1432 bps_tuple, 

1433 extra_tuple, 

1434 ) 

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

1436 try: 

1437 self._mode, rawmode = OPEN_INFO[key] 

1438 except KeyError as e: 

1439 logger.debug("- unsupported format") 

1440 msg = "unknown pixel mode" 

1441 raise SyntaxError(msg) from e 

1442 

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

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

1445 

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

1447 

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

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

1450 

1451 if xres and yres: 

1452 resunit = self.tag_v2.get(RESOLUTION_UNIT) 

1453 if resunit == 2: # dots per inch 

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

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

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

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

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

1459 # For backward compatibility, 

1460 # we also preserve the old behavior 

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

1462 else: # No absolute unit of measurement 

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

1464 

1465 # build tile descriptors 

1466 x = y = layer = 0 

1467 self.tile = [] 

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

1469 if self.use_load_libtiff: 

1470 # Decoder expects entire file as one tile. 

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

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

1473 # function. 

1474 # 

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

1476 # use the _load_libtiff function. 

1477 

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

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

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

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

1482 if fillorder == 2: 

1483 # Replace fillorder with fillorder=1 

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

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

1486 # this should always work, since all the 

1487 # fillorder==2 modes have a corresponding 

1488 # fillorder=1 mode 

1489 self._mode, rawmode = OPEN_INFO[key] 

1490 # libtiff always returns the bytes in native order. 

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

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

1493 # byte order. 

1494 if rawmode == "I;16": 

1495 rawmode = "I;16N" 

1496 if ";16B" in rawmode: 

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

1498 if ";16L" in rawmode: 

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

1500 

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

1502 # unpacked straight into RGB values 

1503 if ( 

1504 photo == 6 

1505 and self._compression == "jpeg" 

1506 and self._planar_configuration == 1 

1507 ): 

1508 rawmode = "RGB" 

1509 

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

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

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

1513 self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a)) 

1514 

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

1516 # striped image 

1517 if STRIPOFFSETS in self.tag_v2: 

1518 offsets = self.tag_v2[STRIPOFFSETS] 

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

1520 w = self.size[0] 

1521 else: 

1522 # tiled image 

1523 offsets = self.tag_v2[TILEOFFSETS] 

1524 w = self.tag_v2.get(TILEWIDTH) 

1525 h = self.tag_v2.get(TILELENGTH) 

1526 

1527 for offset in offsets: 

1528 if x + w > xsize: 

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

1530 else: 

1531 stride = 0 

1532 

1533 tile_rawmode = rawmode 

1534 if self._planar_configuration == 2: 

1535 # each band on it's own layer 

1536 tile_rawmode = rawmode[layer] 

1537 # adjust stride width accordingly 

1538 stride /= bps_count 

1539 

1540 a = (tile_rawmode, int(stride), 1) 

1541 self.tile.append( 

1542 ( 

1543 self._compression, 

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

1545 offset, 

1546 a, 

1547 ) 

1548 ) 

1549 x = x + w 

1550 if x >= self.size[0]: 

1551 x, y = 0, y + h 

1552 if y >= self.size[1]: 

1553 x = y = 0 

1554 layer += 1 

1555 else: 

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

1557 msg = "unknown data organization" 

1558 raise SyntaxError(msg) 

1559 

1560 # Fix up info. 

1561 if ICCPROFILE in self.tag_v2: 

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

1563 

1564 # fixup palette descriptor 

1565 

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

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

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

1569 

1570 

1571# 

1572# -------------------------------------------------------------------- 

1573# Write TIFF files 

1574 

1575# little endian is default except for image modes with 

1576# explicit big endian byte-order 

1577 

1578SAVE_INFO = { 

1579 # mode => rawmode, byteorder, photometrics, 

1580 # sampleformat, bitspersample, extra 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1600} 

1601 

1602 

1603def _save(im, fp, filename): 

1604 try: 

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

1606 except KeyError as e: 

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

1608 raise OSError(msg) from e 

1609 

1610 ifd = ImageFileDirectory_v2(prefix=prefix) 

1611 

1612 encoderinfo = im.encoderinfo 

1613 encoderconfig = im.encoderconfig 

1614 try: 

1615 compression = encoderinfo["compression"] 

1616 except KeyError: 

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

1618 if isinstance(compression, int): 

1619 # compression value may be from BMP. Ignore it 

1620 compression = None 

1621 if compression is None: 

1622 compression = "raw" 

1623 elif compression == "tiff_jpeg": 

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

1625 compression = "jpeg" 

1626 elif compression == "tiff_deflate": 

1627 compression = "tiff_adobe_deflate" 

1628 

1629 libtiff = WRITE_LIBTIFF or compression != "raw" 

1630 

1631 # required for color libtiff images 

1632 ifd[PLANAR_CONFIGURATION] = 1 

1633 

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

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

1636 

1637 # write any arbitrary tags passed in as an ImageFileDirectory 

1638 if "tiffinfo" in encoderinfo: 

1639 info = encoderinfo["tiffinfo"] 

1640 elif "exif" in encoderinfo: 

1641 info = encoderinfo["exif"] 

1642 if isinstance(info, bytes): 

1643 exif = Image.Exif() 

1644 exif.load(info) 

1645 info = exif 

1646 else: 

1647 info = {} 

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

1649 if isinstance(info, ImageFileDirectory_v1): 

1650 info = info.to_v2() 

1651 for key in info: 

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

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

1654 else: 

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

1656 try: 

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

1658 except Exception: 

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

1660 

1661 legacy_ifd = {} 

1662 if hasattr(im, "tag"): 

1663 legacy_ifd = im.tag.to_v2() 

1664 

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

1666 for tag in ( 

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

1668 EXIFIFD, 

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

1670 SAMPLEFORMAT, 

1671 ): 

1672 if tag in supplied_tags: 

1673 del supplied_tags[tag] 

1674 

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

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

1677 if hasattr(im, "tag_v2"): 

1678 # preserve tags from original TIFF image file 

1679 for key in ( 

1680 RESOLUTION_UNIT, 

1681 X_RESOLUTION, 

1682 Y_RESOLUTION, 

1683 IPTC_NAA_CHUNK, 

1684 PHOTOSHOP_CHUNK, 

1685 XMP, 

1686 ): 

1687 if key in im.tag_v2: 

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

1689 TiffTags.BYTE, 

1690 TiffTags.UNDEFINED, 

1691 ): 

1692 del supplied_tags[key] 

1693 else: 

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

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

1696 

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

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

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

1700 if icc: 

1701 ifd[ICCPROFILE] = icc 

1702 

1703 for key, name in [ 

1704 (IMAGEDESCRIPTION, "description"), 

1705 (X_RESOLUTION, "resolution"), 

1706 (Y_RESOLUTION, "resolution"), 

1707 (X_RESOLUTION, "x_resolution"), 

1708 (Y_RESOLUTION, "y_resolution"), 

1709 (RESOLUTION_UNIT, "resolution_unit"), 

1710 (SOFTWARE, "software"), 

1711 (DATE_TIME, "date_time"), 

1712 (ARTIST, "artist"), 

1713 (COPYRIGHT, "copyright"), 

1714 ]: 

1715 if name in encoderinfo: 

1716 ifd[key] = encoderinfo[name] 

1717 

1718 dpi = encoderinfo.get("dpi") 

1719 if dpi: 

1720 ifd[RESOLUTION_UNIT] = 2 

1721 ifd[X_RESOLUTION] = dpi[0] 

1722 ifd[Y_RESOLUTION] = dpi[1] 

1723 

1724 if bits != (1,): 

1725 ifd[BITSPERSAMPLE] = bits 

1726 if len(bits) != 1: 

1727 ifd[SAMPLESPERPIXEL] = len(bits) 

1728 if extra is not None: 

1729 ifd[EXTRASAMPLES] = extra 

1730 if format != 1: 

1731 ifd[SAMPLEFORMAT] = format 

1732 

1733 if PHOTOMETRIC_INTERPRETATION not in ifd: 

1734 ifd[PHOTOMETRIC_INTERPRETATION] = photo 

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

1736 if im.mode == "1": 

1737 inverted_im = im.copy() 

1738 px = inverted_im.load() 

1739 for y in range(inverted_im.height): 

1740 for x in range(inverted_im.width): 

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

1742 im = inverted_im 

1743 else: 

1744 im = ImageOps.invert(im) 

1745 

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

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

1748 colormap = [] 

1749 colors = len(lut) // 3 

1750 for i in range(3): 

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

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

1753 ifd[COLORMAP] = colormap 

1754 # data orientation 

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

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

1757 if ROWSPERSTRIP not in ifd: 

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

1759 if libtiff: 

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

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

1762 # JPEG encoder expects multiple of 8 rows 

1763 if compression == "jpeg": 

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

1765 else: 

1766 rows_per_strip = h 

1767 if rows_per_strip == 0: 

1768 rows_per_strip = 1 

1769 ifd[ROWSPERSTRIP] = rows_per_strip 

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

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

1772 if strip_byte_counts >= 2**16: 

1773 ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG 

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

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

1776 ) 

1777 ifd[STRIPOFFSETS] = tuple( 

1778 range(0, strip_byte_counts * strips_per_image, strip_byte_counts) 

1779 ) # this is adjusted by IFD writer 

1780 # no compression by default: 

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

1782 

1783 if im.mode == "YCbCr": 

1784 for tag, value in { 

1785 YCBCRSUBSAMPLING: (1, 1), 

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

1787 }.items(): 

1788 ifd.setdefault(tag, value) 

1789 

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

1791 if libtiff: 

1792 if "quality" in encoderinfo: 

1793 quality = encoderinfo["quality"] 

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

1795 msg = "Invalid quality setting" 

1796 raise ValueError(msg) 

1797 if compression != "jpeg": 

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

1799 raise ValueError(msg) 

1800 ifd[JPEGQUALITY] = quality 

1801 

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

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

1804 _fp = 0 

1805 if hasattr(fp, "fileno"): 

1806 try: 

1807 fp.seek(0) 

1808 _fp = os.dup(fp.fileno()) 

1809 except io.UnsupportedOperation: 

1810 pass 

1811 

1812 # optional types for non core tags 

1813 types = {} 

1814 # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library 

1815 # based on the data in the strip. 

1816 # OSUBFILETYPE is deprecated. 

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

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

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

1820 # SUBIFD may also cause a segfault. 

1821 blocklist += [ 

1822 OSUBFILETYPE, 

1823 REFERENCEBLACKWHITE, 

1824 STRIPBYTECOUNTS, 

1825 STRIPOFFSETS, 

1826 TRANSFERFUNCTION, 

1827 SUBIFD, 

1828 ] 

1829 

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

1831 atts = {BITSPERSAMPLE: bits[0]} 

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

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

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

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

1836 # Libtiff can only process certain core items without adding 

1837 # them to the custom dictionary. 

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

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

1840 if tag not in TiffTags.LIBTIFF_CORE: 

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

1842 continue 

1843 

1844 if tag in ifd.tagtype: 

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

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

1847 continue 

1848 else: 

1849 type = TiffTags.lookup(tag).type 

1850 if type: 

1851 types[tag] = type 

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

1853 if isinstance(value, str): 

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

1855 elif isinstance(value, IFDRational): 

1856 atts[tag] = float(value) 

1857 else: 

1858 atts[tag] = value 

1859 

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

1861 atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0] 

1862 

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

1864 

1865 # libtiff always expects the bytes in native order. 

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

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

1868 # byte order. 

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

1870 rawmode = "I;16N" 

1871 

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

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

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

1875 tags = list(atts.items()) 

1876 tags.sort() 

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

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

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

1880 while True: 

1881 # undone, change to self.decodermaxblock: 

1882 errcode, data = encoder.encode(16 * 1024)[1:] 

1883 if not _fp: 

1884 fp.write(data) 

1885 if errcode: 

1886 break 

1887 if _fp: 

1888 try: 

1889 os.close(_fp) 

1890 except OSError: 

1891 pass 

1892 if errcode < 0: 

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

1894 raise OSError(msg) 

1895 

1896 else: 

1897 for tag in blocklist: 

1898 del ifd[tag] 

1899 offset = ifd.save(fp) 

1900 

1901 ImageFile._save( 

1902 im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))] 

1903 ) 

1904 

1905 # -- helper for multi-page save -- 

1906 if "_debug_multipage" in encoderinfo: 

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

1908 im._debug_multipage = ifd 

1909 

1910 

1911class AppendingTiffWriter: 

1912 fieldSizes = [ 

1913 0, # None 

1914 1, # byte 

1915 1, # ascii 

1916 2, # short 

1917 4, # long 

1918 8, # rational 

1919 1, # sbyte 

1920 1, # undefined 

1921 2, # sshort 

1922 4, # slong 

1923 8, # srational 

1924 4, # float 

1925 8, # double 

1926 4, # ifd 

1927 2, # unicode 

1928 4, # complex 

1929 8, # long8 

1930 ] 

1931 

1932 Tags = { 

1933 273, # StripOffsets 

1934 288, # FreeOffsets 

1935 324, # TileOffsets 

1936 519, # JPEGQTables 

1937 520, # JPEGDCTables 

1938 521, # JPEGACTables 

1939 } 

1940 

1941 def __init__(self, fn, new=False): 

1942 if hasattr(fn, "read"): 

1943 self.f = fn 

1944 self.close_fp = False 

1945 else: 

1946 self.name = fn 

1947 self.close_fp = True 

1948 try: 

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

1950 except OSError: 

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

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

1953 self.setup() 

1954 

1955 def setup(self) -> None: 

1956 # Reset everything. 

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

1958 

1959 self.whereToWriteNewIFDOffset = None 

1960 self.offsetOfNewPage = 0 

1961 

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

1963 if not iimm: 

1964 # empty file - first page 

1965 self.isFirst = True 

1966 return 

1967 

1968 self.isFirst = False 

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

1970 self.setEndian("<") 

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

1972 self.setEndian(">") 

1973 else: 

1974 msg = "Invalid TIFF file header" 

1975 raise RuntimeError(msg) 

1976 

1977 self.skipIFDs() 

1978 self.goToEnd() 

1979 

1980 def finalize(self) -> None: 

1981 if self.isFirst: 

1982 return 

1983 

1984 # fix offsets 

1985 self.f.seek(self.offsetOfNewPage) 

1986 

1987 iimm = self.f.read(4) 

1988 if not iimm: 

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

1990 return 

1991 

1992 if iimm != self.IIMM: 

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

1994 raise RuntimeError(msg) 

1995 

1996 ifd_offset = self.readLong() 

1997 ifd_offset += self.offsetOfNewPage 

1998 self.f.seek(self.whereToWriteNewIFDOffset) 

1999 self.writeLong(ifd_offset) 

2000 self.f.seek(ifd_offset) 

2001 self.fixIFD() 

2002 

2003 def newFrame(self) -> None: 

2004 # Call this to finish a frame. 

2005 self.finalize() 

2006 self.setup() 

2007 

2008 def __enter__(self) -> AppendingTiffWriter: 

2009 return self 

2010 

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

2012 if self.close_fp: 

2013 self.close() 

2014 

2015 def tell(self) -> int: 

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

2017 

2018 def seek(self, offset, whence=io.SEEK_SET): 

2019 if whence == os.SEEK_SET: 

2020 offset += self.offsetOfNewPage 

2021 

2022 self.f.seek(offset, whence) 

2023 return self.tell() 

2024 

2025 def goToEnd(self) -> None: 

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

2027 pos = self.f.tell() 

2028 

2029 # pad to 16 byte boundary 

2030 pad_bytes = 16 - pos % 16 

2031 if 0 < pad_bytes < 16: 

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

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

2034 

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

2036 self.endian = endian 

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

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

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

2040 

2041 def skipIFDs(self) -> None: 

2042 while True: 

2043 ifd_offset = self.readLong() 

2044 if ifd_offset == 0: 

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

2046 break 

2047 

2048 self.f.seek(ifd_offset) 

2049 num_tags = self.readShort() 

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

2051 

2052 def write(self, data: bytes) -> int | None: 

2053 return self.f.write(data) 

2054 

2055 def readShort(self) -> int: 

2056 (value,) = struct.unpack(self.shortFmt, self.f.read(2)) 

2057 return value 

2058 

2059 def readLong(self) -> int: 

2060 (value,) = struct.unpack(self.longFmt, self.f.read(4)) 

2061 return value 

2062 

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

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

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

2066 if bytes_written is not None and bytes_written != 4: 

2067 msg = f"wrote only {bytes_written} bytes but wanted 4" 

2068 raise RuntimeError(msg) 

2069 

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

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

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

2073 if bytes_written is not None and bytes_written != 2: 

2074 msg = f"wrote only {bytes_written} bytes but wanted 2" 

2075 raise RuntimeError(msg) 

2076 

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

2078 self.f.seek(-4, os.SEEK_CUR) 

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

2080 if bytes_written is not None and bytes_written != 4: 

2081 msg = f"wrote only {bytes_written} bytes but wanted 4" 

2082 raise RuntimeError(msg) 

2083 

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

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

2086 if bytes_written is not None and bytes_written != 2: 

2087 msg = f"wrote only {bytes_written} bytes but wanted 2" 

2088 raise RuntimeError(msg) 

2089 

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

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

2092 if bytes_written is not None and bytes_written != 4: 

2093 msg = f"wrote only {bytes_written} bytes but wanted 4" 

2094 raise RuntimeError(msg) 

2095 

2096 def close(self) -> None: 

2097 self.finalize() 

2098 self.f.close() 

2099 

2100 def fixIFD(self) -> None: 

2101 num_tags = self.readShort() 

2102 

2103 for i in range(num_tags): 

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

2105 

2106 field_size = self.fieldSizes[field_type] 

2107 total_size = field_size * count 

2108 is_local = total_size <= 4 

2109 offset: int | None 

2110 if not is_local: 

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

2112 self.rewriteLastLong(offset) 

2113 

2114 if tag in self.Tags: 

2115 cur_pos = self.f.tell() 

2116 

2117 if is_local: 

2118 self.fixOffsets( 

2119 count, isShort=(field_size == 2), isLong=(field_size == 4) 

2120 ) 

2121 self.f.seek(cur_pos + 4) 

2122 else: 

2123 self.f.seek(offset) 

2124 self.fixOffsets( 

2125 count, isShort=(field_size == 2), isLong=(field_size == 4) 

2126 ) 

2127 self.f.seek(cur_pos) 

2128 

2129 offset = cur_pos = None 

2130 

2131 elif is_local: 

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

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

2134 

2135 def fixOffsets( 

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

2137 ) -> None: 

2138 if not isShort and not isLong: 

2139 msg = "offset is neither short nor long" 

2140 raise RuntimeError(msg) 

2141 

2142 for i in range(count): 

2143 offset = self.readShort() if isShort else self.readLong() 

2144 offset += self.offsetOfNewPage 

2145 if isShort and offset >= 65536: 

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

2147 if count != 1: 

2148 msg = "not implemented" 

2149 raise RuntimeError(msg) # XXX TODO 

2150 

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

2152 # local (not referenced with another offset) 

2153 self.rewriteLastShortToLong(offset) 

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

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

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

2157 elif isShort: 

2158 self.rewriteLastShort(offset) 

2159 else: 

2160 self.rewriteLastLong(offset) 

2161 

2162 

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

2164 encoderinfo = im.encoderinfo.copy() 

2165 encoderconfig = im.encoderconfig 

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

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

2168 return _save(im, fp, filename) 

2169 

2170 cur_idx = im.tell() 

2171 try: 

2172 with AppendingTiffWriter(fp) as tf: 

2173 for ims in [im] + append_images: 

2174 ims.encoderinfo = encoderinfo 

2175 ims.encoderconfig = encoderconfig 

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

2177 nfr = 1 

2178 else: 

2179 nfr = ims.n_frames 

2180 

2181 for idx in range(nfr): 

2182 ims.seek(idx) 

2183 ims.load() 

2184 _save(ims, tf, filename) 

2185 tf.newFrame() 

2186 finally: 

2187 im.seek(cur_idx) 

2188 

2189 

2190# 

2191# -------------------------------------------------------------------- 

2192# Register 

2193 

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

2195Image.register_save(TiffImageFile.format, _save) 

2196Image.register_save_all(TiffImageFile.format, _save_all) 

2197 

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

2199 

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