Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/imageio-2.35.1-py3.8.egg/imageio/plugins/_freeimage.py: 39%

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

604 statements  

1# -*- coding: utf-8 -*- 

2# imageio is distributed under the terms of the (new) BSD License. 

3 

4# styletest: ignore E261 

5 

6""" Module imageio/freeimage.py 

7 

8This module contains the wrapper code for the freeimage library. 

9The functions defined in this module are relatively thin; just thin 

10enough so that arguments and results are native Python/numpy data 

11types. 

12 

13""" 

14 

15import os 

16import sys 

17import ctypes 

18import threading 

19import logging 

20import numpy 

21 

22from ..core import ( 

23 get_remote_file, 

24 load_lib, 

25 Dict, 

26 resource_dirs, 

27 IS_PYPY, 

28 get_platform, 

29 InternetNotAllowedError, 

30 NeedDownloadError, 

31) 

32 

33logger = logging.getLogger(__name__) 

34 

35TEST_NUMPY_NO_STRIDES = False # To test pypy fallback 

36 

37FNAME_PER_PLATFORM = { 

38 "osx32": "libfreeimage-3.16.0-osx10.6.dylib", # universal library 

39 "osx64": "libfreeimage-3.16.0-osx10.6.dylib", 

40 "win32": "FreeImage-3.18.0-win32.dll", 

41 "win64": "FreeImage-3.18.0-win64.dll", 

42 "linux32": "libfreeimage-3.16.0-linux32.so", 

43 "linux64": "libfreeimage-3.16.0-linux64.so", 

44} 

45 

46 

47def download(directory=None, force_download=False): 

48 """Download the FreeImage library to your computer. 

49 

50 Parameters 

51 ---------- 

52 directory : str | None 

53 The directory where the file will be cached if a download was 

54 required to obtain the file. By default, the appdata directory 

55 is used. This is also the first directory that is checked for 

56 a local version of the file. 

57 force_download : bool | str 

58 If True, the file will be downloaded even if a local copy exists 

59 (and this copy will be overwritten). Can also be a YYYY-MM-DD date 

60 to ensure a file is up-to-date (modified date of a file on disk, 

61 if present, is checked). 

62 """ 

63 plat = get_platform() 

64 if plat and plat in FNAME_PER_PLATFORM: 

65 fname = "freeimage/" + FNAME_PER_PLATFORM[plat] 

66 get_remote_file(fname=fname, directory=directory, force_download=force_download) 

67 fi._lib = None # allow trying again (needed to make tests work) 

68 

69 

70def get_freeimage_lib(): 

71 """Ensure we have our version of the binary freeimage lib.""" 

72 

73 lib = os.getenv("IMAGEIO_FREEIMAGE_LIB", None) 

74 if lib: # pragma: no cover 

75 return lib 

76 

77 # Get filename to load 

78 # If we do not provide a binary, the system may still do ... 

79 plat = get_platform() 

80 if plat and plat in FNAME_PER_PLATFORM: 

81 try: 

82 return get_remote_file("freeimage/" + FNAME_PER_PLATFORM[plat], auto=False) 

83 except InternetNotAllowedError: 

84 pass 

85 except NeedDownloadError: 

86 raise NeedDownloadError( 

87 "Need FreeImage library. " 

88 "You can obtain it with either:\n" 

89 " - download using the command: " 

90 "imageio_download_bin freeimage\n" 

91 " - download by calling (in Python): " 

92 "imageio.plugins.freeimage.download()\n" 

93 ) 

94 except RuntimeError as e: # pragma: no cover 

95 logger.warning(str(e)) 

96 

97 

98# Define function to encode a filename to bytes (for the current system) 

99def efn(x): 

100 return x.encode(sys.getfilesystemencoding()) 

101 

102 

103# 4-byte quads of 0,v,v,v from 0,0,0,0 to 0,255,255,255 

104GREY_PALETTE = numpy.arange(0, 0x01000000, 0x00010101, dtype=numpy.uint32) 

105 

106 

107class FI_TYPES(object): 

108 FIT_UNKNOWN = 0 

109 FIT_BITMAP = 1 

110 FIT_UINT16 = 2 

111 FIT_INT16 = 3 

112 FIT_UINT32 = 4 

113 FIT_INT32 = 5 

114 FIT_FLOAT = 6 

115 FIT_DOUBLE = 7 

116 FIT_COMPLEX = 8 

117 FIT_RGB16 = 9 

118 FIT_RGBA16 = 10 

119 FIT_RGBF = 11 

120 FIT_RGBAF = 12 

121 

122 dtypes = { 

123 FIT_BITMAP: numpy.uint8, 

124 FIT_UINT16: numpy.uint16, 

125 FIT_INT16: numpy.int16, 

126 FIT_UINT32: numpy.uint32, 

127 FIT_INT32: numpy.int32, 

128 FIT_FLOAT: numpy.float32, 

129 FIT_DOUBLE: numpy.float64, 

130 FIT_COMPLEX: numpy.complex128, 

131 FIT_RGB16: numpy.uint16, 

132 FIT_RGBA16: numpy.uint16, 

133 FIT_RGBF: numpy.float32, 

134 FIT_RGBAF: numpy.float32, 

135 } 

136 

137 fi_types = { 

138 (numpy.uint8, 1): FIT_BITMAP, 

139 (numpy.uint8, 3): FIT_BITMAP, 

140 (numpy.uint8, 4): FIT_BITMAP, 

141 (numpy.uint16, 1): FIT_UINT16, 

142 (numpy.int16, 1): FIT_INT16, 

143 (numpy.uint32, 1): FIT_UINT32, 

144 (numpy.int32, 1): FIT_INT32, 

145 (numpy.float32, 1): FIT_FLOAT, 

146 (numpy.float64, 1): FIT_DOUBLE, 

147 (numpy.complex128, 1): FIT_COMPLEX, 

148 (numpy.uint16, 3): FIT_RGB16, 

149 (numpy.uint16, 4): FIT_RGBA16, 

150 (numpy.float32, 3): FIT_RGBF, 

151 (numpy.float32, 4): FIT_RGBAF, 

152 } 

153 

154 extra_dims = { 

155 FIT_UINT16: [], 

156 FIT_INT16: [], 

157 FIT_UINT32: [], 

158 FIT_INT32: [], 

159 FIT_FLOAT: [], 

160 FIT_DOUBLE: [], 

161 FIT_COMPLEX: [], 

162 FIT_RGB16: [3], 

163 FIT_RGBA16: [4], 

164 FIT_RGBF: [3], 

165 FIT_RGBAF: [4], 

166 } 

167 

168 

169class IO_FLAGS(object): 

170 FIF_LOAD_NOPIXELS = 0x8000 # loading: load the image header only 

171 # # (not supported by all plugins) 

172 BMP_DEFAULT = 0 

173 BMP_SAVE_RLE = 1 

174 CUT_DEFAULT = 0 

175 DDS_DEFAULT = 0 

176 EXR_DEFAULT = 0 # save data as half with piz-based wavelet compression 

177 EXR_FLOAT = 0x0001 # save data as float instead of half (not recommended) 

178 EXR_NONE = 0x0002 # save with no compression 

179 EXR_ZIP = 0x0004 # save with zlib compression, in blocks of 16 scan lines 

180 EXR_PIZ = 0x0008 # save with piz-based wavelet compression 

181 EXR_PXR24 = 0x0010 # save with lossy 24-bit float compression 

182 EXR_B44 = 0x0020 # save with lossy 44% float compression 

183 # # - goes to 22% when combined with EXR_LC 

184 EXR_LC = 0x0040 # save images with one luminance and two chroma channels, 

185 # # rather than as RGB (lossy compression) 

186 FAXG3_DEFAULT = 0 

187 GIF_DEFAULT = 0 

188 GIF_LOAD256 = 1 # Load the image as a 256 color image with ununsed 

189 # # palette entries, if it's 16 or 2 color 

190 GIF_PLAYBACK = 2 # 'Play' the GIF to generate each frame (as 32bpp) 

191 # # instead of returning raw frame data when loading 

192 HDR_DEFAULT = 0 

193 ICO_DEFAULT = 0 

194 ICO_MAKEALPHA = 1 # convert to 32bpp and create an alpha channel from the 

195 # # AND-mask when loading 

196 IFF_DEFAULT = 0 

197 J2K_DEFAULT = 0 # save with a 16:1 rate 

198 JP2_DEFAULT = 0 # save with a 16:1 rate 

199 JPEG_DEFAULT = 0 # loading (see JPEG_FAST); 

200 # # saving (see JPEG_QUALITYGOOD|JPEG_SUBSAMPLING_420) 

201 JPEG_FAST = 0x0001 # load the file as fast as possible, 

202 # # sacrificing some quality 

203 JPEG_ACCURATE = 0x0002 # load the file with the best quality, 

204 # # sacrificing some speed 

205 JPEG_CMYK = 0x0004 # load separated CMYK "as is" 

206 # # (use | to combine with other load flags) 

207 JPEG_EXIFROTATE = 0x0008 # load and rotate according to 

208 # # Exif 'Orientation' tag if available 

209 JPEG_QUALITYSUPERB = 0x80 # save with superb quality (100:1) 

210 JPEG_QUALITYGOOD = 0x0100 # save with good quality (75:1) 

211 JPEG_QUALITYNORMAL = 0x0200 # save with normal quality (50:1) 

212 JPEG_QUALITYAVERAGE = 0x0400 # save with average quality (25:1) 

213 JPEG_QUALITYBAD = 0x0800 # save with bad quality (10:1) 

214 JPEG_PROGRESSIVE = 0x2000 # save as a progressive-JPEG 

215 # # (use | to combine with other save flags) 

216 JPEG_SUBSAMPLING_411 = 0x1000 # save with high 4x1 chroma 

217 # # subsampling (4:1:1) 

218 JPEG_SUBSAMPLING_420 = 0x4000 # save with medium 2x2 medium chroma 

219 # # subsampling (4:2:0) - default value 

220 JPEG_SUBSAMPLING_422 = 0x8000 # save /w low 2x1 chroma subsampling (4:2:2) 

221 JPEG_SUBSAMPLING_444 = 0x10000 # save with no chroma subsampling (4:4:4) 

222 JPEG_OPTIMIZE = 0x20000 # on saving, compute optimal Huffman coding tables 

223 # # (can reduce a few percent of file size) 

224 JPEG_BASELINE = 0x40000 # save basic JPEG, without metadata or any markers 

225 KOALA_DEFAULT = 0 

226 LBM_DEFAULT = 0 

227 MNG_DEFAULT = 0 

228 PCD_DEFAULT = 0 

229 PCD_BASE = 1 # load the bitmap sized 768 x 512 

230 PCD_BASEDIV4 = 2 # load the bitmap sized 384 x 256 

231 PCD_BASEDIV16 = 3 # load the bitmap sized 192 x 128 

232 PCX_DEFAULT = 0 

233 PFM_DEFAULT = 0 

234 PICT_DEFAULT = 0 

235 PNG_DEFAULT = 0 

236 PNG_IGNOREGAMMA = 1 # loading: avoid gamma correction 

237 PNG_Z_BEST_SPEED = 0x0001 # save using ZLib level 1 compression flag 

238 # # (default value is 6) 

239 PNG_Z_DEFAULT_COMPRESSION = 0x0006 # save using ZLib level 6 compression 

240 # # flag (default recommended value) 

241 PNG_Z_BEST_COMPRESSION = 0x0009 # save using ZLib level 9 compression flag 

242 # # (default value is 6) 

243 PNG_Z_NO_COMPRESSION = 0x0100 # save without ZLib compression 

244 PNG_INTERLACED = 0x0200 # save using Adam7 interlacing (use | to combine 

245 # # with other save flags) 

246 PNM_DEFAULT = 0 

247 PNM_SAVE_RAW = 0 # Writer saves in RAW format (i.e. P4, P5 or P6) 

248 PNM_SAVE_ASCII = 1 # Writer saves in ASCII format (i.e. P1, P2 or P3) 

249 PSD_DEFAULT = 0 

250 PSD_CMYK = 1 # reads tags for separated CMYK (default is conversion to RGB) 

251 PSD_LAB = 2 # reads tags for CIELab (default is conversion to RGB) 

252 RAS_DEFAULT = 0 

253 RAW_DEFAULT = 0 # load the file as linear RGB 48-bit 

254 RAW_PREVIEW = 1 # try to load the embedded JPEG preview with included 

255 # # Exif Data or default to RGB 24-bit 

256 RAW_DISPLAY = 2 # load the file as RGB 24-bit 

257 SGI_DEFAULT = 0 

258 TARGA_DEFAULT = 0 

259 TARGA_LOAD_RGB888 = 1 # Convert RGB555 and ARGB8888 -> RGB888. 

260 TARGA_SAVE_RLE = 2 # Save with RLE compression 

261 TIFF_DEFAULT = 0 

262 TIFF_CMYK = 0x0001 # reads/stores tags for separated CMYK 

263 # # (use | to combine with compression flags) 

264 TIFF_PACKBITS = 0x0100 # save using PACKBITS compression 

265 TIFF_DEFLATE = 0x0200 # save using DEFLATE (a.k.a. ZLIB) compression 

266 TIFF_ADOBE_DEFLATE = 0x0400 # save using ADOBE DEFLATE compression 

267 TIFF_NONE = 0x0800 # save without any compression 

268 TIFF_CCITTFAX3 = 0x1000 # save using CCITT Group 3 fax encoding 

269 TIFF_CCITTFAX4 = 0x2000 # save using CCITT Group 4 fax encoding 

270 TIFF_LZW = 0x4000 # save using LZW compression 

271 TIFF_JPEG = 0x8000 # save using JPEG compression 

272 TIFF_LOGLUV = 0x10000 # save using LogLuv compression 

273 WBMP_DEFAULT = 0 

274 XBM_DEFAULT = 0 

275 XPM_DEFAULT = 0 

276 

277 

278class METADATA_MODELS(object): 

279 FIMD_COMMENTS = 0 

280 FIMD_EXIF_MAIN = 1 

281 FIMD_EXIF_EXIF = 2 

282 FIMD_EXIF_GPS = 3 

283 FIMD_EXIF_MAKERNOTE = 4 

284 FIMD_EXIF_INTEROP = 5 

285 FIMD_IPTC = 6 

286 FIMD_XMP = 7 

287 FIMD_GEOTIFF = 8 

288 FIMD_ANIMATION = 9 

289 

290 

291class METADATA_DATATYPE(object): 

292 FIDT_BYTE = 1 # 8-bit unsigned integer 

293 FIDT_ASCII = 2 # 8-bit bytes w/ last byte null 

294 FIDT_SHORT = 3 # 16-bit unsigned integer 

295 FIDT_LONG = 4 # 32-bit unsigned integer 

296 FIDT_RATIONAL = 5 # 64-bit unsigned fraction 

297 FIDT_SBYTE = 6 # 8-bit signed integer 

298 FIDT_UNDEFINED = 7 # 8-bit untyped data 

299 FIDT_SSHORT = 8 # 16-bit signed integer 

300 FIDT_SLONG = 9 # 32-bit signed integer 

301 FIDT_SRATIONAL = 10 # 64-bit signed fraction 

302 FIDT_FLOAT = 11 # 32-bit IEEE floating point 

303 FIDT_DOUBLE = 12 # 64-bit IEEE floating point 

304 FIDT_IFD = 13 # 32-bit unsigned integer (offset) 

305 FIDT_PALETTE = 14 # 32-bit RGBQUAD 

306 FIDT_LONG8 = 16 # 64-bit unsigned integer 

307 FIDT_SLONG8 = 17 # 64-bit signed integer 

308 FIDT_IFD8 = 18 # 64-bit unsigned integer (offset) 

309 

310 dtypes = { 

311 FIDT_BYTE: numpy.uint8, 

312 FIDT_SHORT: numpy.uint16, 

313 FIDT_LONG: numpy.uint32, 

314 FIDT_RATIONAL: [("numerator", numpy.uint32), ("denominator", numpy.uint32)], 

315 FIDT_LONG8: numpy.uint64, 

316 FIDT_SLONG8: numpy.int64, 

317 FIDT_IFD8: numpy.uint64, 

318 FIDT_SBYTE: numpy.int8, 

319 FIDT_UNDEFINED: numpy.uint8, 

320 FIDT_SSHORT: numpy.int16, 

321 FIDT_SLONG: numpy.int32, 

322 FIDT_SRATIONAL: [("numerator", numpy.int32), ("denominator", numpy.int32)], 

323 FIDT_FLOAT: numpy.float32, 

324 FIDT_DOUBLE: numpy.float64, 

325 FIDT_IFD: numpy.uint32, 

326 FIDT_PALETTE: [ 

327 ("R", numpy.uint8), 

328 ("G", numpy.uint8), 

329 ("B", numpy.uint8), 

330 ("A", numpy.uint8), 

331 ], 

332 } 

333 

334 

335class Freeimage(object): 

336 """Class to represent an interface to the FreeImage library. 

337 This class is relatively thin. It provides a Pythonic API that converts 

338 Freeimage objects to Python objects, but that's about it. 

339 The actual implementation should be provided by the plugins. 

340 

341 The recommended way to call into the Freeimage library (so that 

342 errors and warnings show up in the right moment) is to use this 

343 object as a context manager: 

344 with imageio.fi as lib: 

345 lib.FreeImage_GetPalette() 

346 

347 """ 

348 

349 _API = { 

350 # All we're doing here is telling ctypes that some of the 

351 # FreeImage functions return pointers instead of integers. (On 

352 # 64-bit systems, without this information the pointers get 

353 # truncated and crashes result). There's no need to list 

354 # functions that return ints, or the types of the parameters 

355 # to these or other functions -- that's fine to do implicitly. 

356 # Note that the ctypes immediately converts the returned void_p 

357 # back to a python int again! This is really not helpful, 

358 # because then passing it back to another library call will 

359 # cause truncation-to-32-bits on 64-bit systems. Thanks, ctypes! 

360 # So after these calls one must immediately re-wrap the int as 

361 # a c_void_p if it is to be passed back into FreeImage. 

362 "FreeImage_AllocateT": (ctypes.c_void_p, None), 

363 "FreeImage_FindFirstMetadata": (ctypes.c_void_p, None), 

364 "FreeImage_GetBits": (ctypes.c_void_p, None), 

365 "FreeImage_GetPalette": (ctypes.c_void_p, None), 

366 "FreeImage_GetTagKey": (ctypes.c_char_p, None), 

367 "FreeImage_GetTagValue": (ctypes.c_void_p, None), 

368 "FreeImage_CreateTag": (ctypes.c_void_p, None), 

369 "FreeImage_Save": (ctypes.c_void_p, None), 

370 "FreeImage_Load": (ctypes.c_void_p, None), 

371 "FreeImage_LoadFromMemory": (ctypes.c_void_p, None), 

372 "FreeImage_OpenMultiBitmap": (ctypes.c_void_p, None), 

373 "FreeImage_LoadMultiBitmapFromMemory": (ctypes.c_void_p, None), 

374 "FreeImage_LockPage": (ctypes.c_void_p, None), 

375 "FreeImage_OpenMemory": (ctypes.c_void_p, None), 

376 # 'FreeImage_ReadMemory': (ctypes.c_void_p, None), 

377 # 'FreeImage_CloseMemory': (ctypes.c_void_p, None), 

378 "FreeImage_GetVersion": (ctypes.c_char_p, None), 

379 "FreeImage_GetFIFExtensionList": (ctypes.c_char_p, None), 

380 "FreeImage_GetFormatFromFIF": (ctypes.c_char_p, None), 

381 "FreeImage_GetFIFDescription": (ctypes.c_char_p, None), 

382 "FreeImage_ColorQuantizeEx": (ctypes.c_void_p, None), 

383 # Pypy wants some extra definitions, so here we go ... 

384 "FreeImage_IsLittleEndian": (ctypes.c_int, None), 

385 "FreeImage_SetOutputMessage": (ctypes.c_void_p, None), 

386 "FreeImage_GetFIFCount": (ctypes.c_int, None), 

387 "FreeImage_IsPluginEnabled": (ctypes.c_int, None), 

388 "FreeImage_GetFileType": (ctypes.c_int, None), 

389 # 

390 "FreeImage_GetTagType": (ctypes.c_int, None), 

391 "FreeImage_GetTagLength": (ctypes.c_int, None), 

392 "FreeImage_FindNextMetadata": (ctypes.c_int, None), 

393 "FreeImage_FindCloseMetadata": (ctypes.c_void_p, None), 

394 # 

395 "FreeImage_GetFIFFromFilename": (ctypes.c_int, None), 

396 "FreeImage_FIFSupportsReading": (ctypes.c_int, None), 

397 "FreeImage_FIFSupportsWriting": (ctypes.c_int, None), 

398 "FreeImage_FIFSupportsExportType": (ctypes.c_int, None), 

399 "FreeImage_FIFSupportsExportBPP": (ctypes.c_int, None), 

400 "FreeImage_GetHeight": (ctypes.c_int, None), 

401 "FreeImage_GetWidth": (ctypes.c_int, None), 

402 "FreeImage_GetImageType": (ctypes.c_int, None), 

403 "FreeImage_GetBPP": (ctypes.c_int, None), 

404 "FreeImage_GetColorsUsed": (ctypes.c_int, None), 

405 "FreeImage_ConvertTo32Bits": (ctypes.c_void_p, None), 

406 "FreeImage_GetPitch": (ctypes.c_int, None), 

407 "FreeImage_Unload": (ctypes.c_void_p, None), 

408 } 

409 

410 def __init__(self): 

411 # Initialize freeimage lib as None 

412 self._lib = None 

413 

414 # A lock to create thread-safety 

415 self._lock = threading.RLock() 

416 

417 # Init log messages lists 

418 self._messages = [] 

419 

420 # Select functype for error handler 

421 if sys.platform.startswith("win"): 

422 functype = ctypes.WINFUNCTYPE 

423 else: 

424 functype = ctypes.CFUNCTYPE 

425 

426 # Create output message handler 

427 @functype(None, ctypes.c_int, ctypes.c_char_p) 

428 def error_handler(fif, message): 

429 message = message.decode("utf-8") 

430 self._messages.append(message) 

431 while (len(self._messages)) > 256: 

432 self._messages.pop(0) 

433 

434 # Make sure to keep a ref to function 

435 self._error_handler = error_handler 

436 

437 @property 

438 def lib(self): 

439 if self._lib is None: 

440 try: 

441 self.load_freeimage() 

442 except OSError as err: 

443 self._lib = "The freeimage library could not be loaded: " 

444 self._lib += str(err) 

445 if isinstance(self._lib, str): 

446 raise RuntimeError(self._lib) 

447 return self._lib 

448 

449 def has_lib(self): 

450 try: 

451 self.lib 

452 except Exception: 

453 return False 

454 return True 

455 

456 def load_freeimage(self): 

457 """Try to load the freeimage lib from the system. If not successful, 

458 try to download the imageio version and try again. 

459 """ 

460 # Load library and register API 

461 success = False 

462 try: 

463 # Try without forcing a download, but giving preference 

464 # to the imageio-provided lib (if previously downloaded) 

465 self._load_freeimage() 

466 self._register_api() 

467 if self.lib.FreeImage_GetVersion().decode("utf-8") >= "3.15": 

468 success = True 

469 except OSError: 

470 pass 

471 

472 if not success: 

473 # Ensure we have our own lib, try again 

474 get_freeimage_lib() 

475 self._load_freeimage() 

476 self._register_api() 

477 

478 # Wrap up 

479 self.lib.FreeImage_SetOutputMessage(self._error_handler) 

480 self.lib_version = self.lib.FreeImage_GetVersion().decode("utf-8") 

481 

482 def _load_freeimage(self): 

483 # Define names 

484 lib_names = ["freeimage", "libfreeimage"] 

485 exact_lib_names = [ 

486 "FreeImage", 

487 "libfreeimage.dylib", 

488 "libfreeimage.so", 

489 "libfreeimage.so.3", 

490 ] 

491 # Add names of libraries that we provide (that file may not exist) 

492 res_dirs = resource_dirs() 

493 plat = get_platform() 

494 if plat: # Can be None on e.g. FreeBSD 

495 fname = FNAME_PER_PLATFORM[plat] 

496 for dir in res_dirs: 

497 exact_lib_names.insert(0, os.path.join(dir, "freeimage", fname)) 

498 

499 # Add the path specified with IMAGEIO_FREEIMAGE_LIB: 

500 lib = os.getenv("IMAGEIO_FREEIMAGE_LIB", None) 

501 if lib is not None: 

502 exact_lib_names.insert(0, lib) 

503 

504 # Load 

505 try: 

506 lib, fname = load_lib(exact_lib_names, lib_names, res_dirs) 

507 except OSError as err: # pragma: no cover 

508 err_msg = str(err) + "\nPlease install the FreeImage library." 

509 raise OSError(err_msg) 

510 

511 # Store 

512 self._lib = lib 

513 self.lib_fname = fname 

514 

515 def _register_api(self): 

516 # Albert's ctypes pattern 

517 for f, (restype, argtypes) in self._API.items(): 

518 func = getattr(self.lib, f) 

519 func.restype = restype 

520 func.argtypes = argtypes 

521 

522 # Handling of output messages 

523 

524 def __enter__(self): 

525 self._lock.acquire() 

526 return self.lib 

527 

528 def __exit__(self, *args): 

529 self._show_any_warnings() 

530 self._lock.release() 

531 

532 def _reset_log(self): 

533 """Reset the list of output messages. Call this before 

534 loading or saving an image with the FreeImage API. 

535 """ 

536 self._messages = [] 

537 

538 def _get_error_message(self): 

539 """Get the output messages produced since the last reset as 

540 one string. Returns 'No known reason.' if there are no messages. 

541 Also resets the log. 

542 """ 

543 if self._messages: 

544 res = " ".join(self._messages) 

545 self._reset_log() 

546 return res 

547 else: 

548 return "No known reason." 

549 

550 def _show_any_warnings(self): 

551 """If there were any messages since the last reset, show them 

552 as a warning. Otherwise do nothing. Also resets the messages. 

553 """ 

554 if self._messages: 

555 logger.warning("imageio.freeimage warning: " + self._get_error_message()) 

556 self._reset_log() 

557 

558 def get_output_log(self): 

559 """Return a list of the last 256 output messages 

560 (warnings and errors) produced by the FreeImage library. 

561 """ 

562 # This message log is not cleared/reset, but kept to 256 elements. 

563 return [m for m in self._messages] 

564 

565 def getFIF(self, filename, mode, bb=None): 

566 """Get the freeimage Format (FIF) from a given filename. 

567 If mode is 'r', will try to determine the format by reading 

568 the file, otherwise only the filename is used. 

569 

570 This function also tests whether the format supports reading/writing. 

571 """ 

572 with self as lib: 

573 # Init 

574 ftype = -1 

575 if mode not in "rw": 

576 raise ValueError('Invalid mode (must be "r" or "w").') 

577 

578 # Try getting format from the content. Note that some files 

579 # do not have a header that allows reading the format from 

580 # the file. 

581 if mode == "r": 

582 if bb is not None: 

583 fimemory = lib.FreeImage_OpenMemory(ctypes.c_char_p(bb), len(bb)) 

584 ftype = lib.FreeImage_GetFileTypeFromMemory( 

585 ctypes.c_void_p(fimemory), len(bb) 

586 ) 

587 lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory)) 

588 if (ftype == -1) and os.path.isfile(filename): 

589 ftype = lib.FreeImage_GetFileType(efn(filename), 0) 

590 # Try getting the format from the extension 

591 if ftype == -1: 

592 ftype = lib.FreeImage_GetFIFFromFilename(efn(filename)) 

593 

594 # Test if ok 

595 if ftype == -1: 

596 raise ValueError('Cannot determine format of file "%s"' % filename) 

597 elif mode == "w" and not lib.FreeImage_FIFSupportsWriting(ftype): 

598 raise ValueError('Cannot write the format of file "%s"' % filename) 

599 elif mode == "r" and not lib.FreeImage_FIFSupportsReading(ftype): 

600 raise ValueError('Cannot read the format of file "%s"' % filename) 

601 return ftype 

602 

603 def create_bitmap(self, filename, ftype, flags=0): 

604 """create_bitmap(filename, ftype, flags=0) 

605 Create a wrapped bitmap object. 

606 """ 

607 return FIBitmap(self, filename, ftype, flags) 

608 

609 def create_multipage_bitmap(self, filename, ftype, flags=0): 

610 """create_multipage_bitmap(filename, ftype, flags=0) 

611 Create a wrapped multipage bitmap object. 

612 """ 

613 return FIMultipageBitmap(self, filename, ftype, flags) 

614 

615 

616class FIBaseBitmap(object): 

617 def __init__(self, fi, filename, ftype, flags): 

618 self._fi = fi 

619 self._filename = filename 

620 self._ftype = ftype 

621 self._flags = flags 

622 self._bitmap = None 

623 self._close_funcs = [] 

624 

625 def __del__(self): 

626 self.close() 

627 

628 def close(self): 

629 if (self._bitmap is not None) and self._close_funcs: 

630 for close_func in self._close_funcs: 

631 try: 

632 with self._fi: 

633 fun = close_func[0] 

634 fun(*close_func[1:]) 

635 except Exception: # pragma: no cover 

636 pass 

637 self._close_funcs = [] 

638 self._bitmap = None 

639 

640 def _set_bitmap(self, bitmap, close_func=None): 

641 """Function to set the bitmap and specify the function to unload it.""" 

642 if self._bitmap is not None: 

643 pass # bitmap is converted 

644 if close_func is None: 

645 close_func = self._fi.lib.FreeImage_Unload, bitmap 

646 

647 self._bitmap = bitmap 

648 if close_func: 

649 self._close_funcs.append(close_func) 

650 

651 def get_meta_data(self): 

652 # todo: there is also FreeImage_TagToString, is that useful? 

653 # and would that work well when reading and then saving? 

654 

655 # Create a list of (model_name, number) tuples 

656 models = [ 

657 (name[5:], number) 

658 for name, number in METADATA_MODELS.__dict__.items() 

659 if name.startswith("FIMD_") 

660 ] 

661 

662 # Prepare 

663 metadata = Dict() 

664 tag = ctypes.c_void_p() 

665 

666 with self._fi as lib: 

667 # Iterate over all FreeImage meta models 

668 for model_name, number in models: 

669 # Find beginning, get search handle 

670 mdhandle = lib.FreeImage_FindFirstMetadata( 

671 number, self._bitmap, ctypes.byref(tag) 

672 ) 

673 mdhandle = ctypes.c_void_p(mdhandle) 

674 if mdhandle: 

675 # Iterate over all tags in this model 

676 more = True 

677 while more: 

678 # Get info about tag 

679 tag_name = lib.FreeImage_GetTagKey(tag).decode("utf-8") 

680 tag_type = lib.FreeImage_GetTagType(tag) 

681 byte_size = lib.FreeImage_GetTagLength(tag) 

682 char_ptr = ctypes.c_char * byte_size 

683 data = char_ptr.from_address(lib.FreeImage_GetTagValue(tag)) 

684 # Convert in a way compatible with Pypy 

685 tag_bytes = bytes(bytearray(data)) 

686 # The default value is the raw bytes 

687 tag_val = tag_bytes 

688 # Convert to a Python value in the metadata dict 

689 if tag_type == METADATA_DATATYPE.FIDT_ASCII: 

690 tag_val = tag_bytes.decode("utf-8", "replace") 

691 elif tag_type in METADATA_DATATYPE.dtypes: 

692 dtype = METADATA_DATATYPE.dtypes[tag_type] 

693 if IS_PYPY and isinstance(dtype, (list, tuple)): 

694 pass # pragma: no cover - or we get a segfault 

695 else: 

696 try: 

697 tag_val = numpy.frombuffer( 

698 tag_bytes, dtype=dtype 

699 ).copy() 

700 if len(tag_val) == 1: 

701 tag_val = tag_val[0] 

702 except Exception: # pragma: no cover 

703 pass 

704 # Store data in dict 

705 subdict = metadata.setdefault(model_name, Dict()) 

706 subdict[tag_name] = tag_val 

707 # Next 

708 more = lib.FreeImage_FindNextMetadata( 

709 mdhandle, ctypes.byref(tag) 

710 ) 

711 

712 # Close search handle for current meta model 

713 lib.FreeImage_FindCloseMetadata(mdhandle) 

714 

715 # Done 

716 return metadata 

717 

718 def set_meta_data(self, metadata): 

719 # Create a dict mapping model_name to number 

720 models = {} 

721 for name, number in METADATA_MODELS.__dict__.items(): 

722 if name.startswith("FIMD_"): 

723 models[name[5:]] = number 

724 

725 # Create a mapping from numpy.dtype to METADATA_DATATYPE 

726 def get_tag_type_number(dtype): 

727 for number, numpy_dtype in METADATA_DATATYPE.dtypes.items(): 

728 if dtype == numpy_dtype: 

729 return number 

730 else: 

731 return None 

732 

733 with self._fi as lib: 

734 for model_name, subdict in metadata.items(): 

735 # Get model number 

736 number = models.get(model_name, None) 

737 if number is None: 

738 continue # Unknown model, silent ignore 

739 

740 for tag_name, tag_val in subdict.items(): 

741 # Create new tag 

742 tag = lib.FreeImage_CreateTag() 

743 tag = ctypes.c_void_p(tag) 

744 

745 try: 

746 # Convert Python value to FI type, val 

747 is_ascii = False 

748 if isinstance(tag_val, str): 

749 try: 

750 tag_bytes = tag_val.encode("ascii") 

751 is_ascii = True 

752 except UnicodeError: 

753 pass 

754 if is_ascii: 

755 tag_type = METADATA_DATATYPE.FIDT_ASCII 

756 tag_count = len(tag_bytes) 

757 else: 

758 if not hasattr(tag_val, "dtype"): 

759 tag_val = numpy.array([tag_val]) 

760 tag_type = get_tag_type_number(tag_val.dtype) 

761 if tag_type is None: 

762 logger.warning( 

763 "imageio.freeimage warning: Could not " 

764 "determine tag type of %r." % tag_name 

765 ) 

766 continue 

767 tag_bytes = tag_val.tobytes() 

768 tag_count = tag_val.size 

769 # Set properties 

770 lib.FreeImage_SetTagKey(tag, tag_name.encode("utf-8")) 

771 lib.FreeImage_SetTagType(tag, tag_type) 

772 lib.FreeImage_SetTagCount(tag, tag_count) 

773 lib.FreeImage_SetTagLength(tag, len(tag_bytes)) 

774 lib.FreeImage_SetTagValue(tag, tag_bytes) 

775 # Store tag 

776 tag_key = lib.FreeImage_GetTagKey(tag) 

777 lib.FreeImage_SetMetadata(number, self._bitmap, tag_key, tag) 

778 

779 except Exception as err: # pragma: no cover 

780 logger.warning( 

781 "imagio.freeimage warning: Could not set tag " 

782 "%r: %s, %s" 

783 % (tag_name, self._fi._get_error_message(), str(err)) 

784 ) 

785 finally: 

786 lib.FreeImage_DeleteTag(tag) 

787 

788 

789class FIBitmap(FIBaseBitmap): 

790 """Wrapper for the FI bitmap object.""" 

791 

792 def allocate(self, array): 

793 # Prepare array 

794 assert isinstance(array, numpy.ndarray) 

795 shape = array.shape 

796 dtype = array.dtype 

797 

798 # Get shape and channel info 

799 r, c = shape[:2] 

800 if len(shape) == 2: 

801 n_channels = 1 

802 elif len(shape) == 3: 

803 n_channels = shape[2] 

804 else: 

805 n_channels = shape[0] 

806 

807 # Get fi_type 

808 try: 

809 fi_type = FI_TYPES.fi_types[(dtype.type, n_channels)] 

810 self._fi_type = fi_type 

811 except KeyError: 

812 raise ValueError("Cannot write arrays of given type and shape.") 

813 

814 # Allocate bitmap 

815 with self._fi as lib: 

816 bpp = 8 * dtype.itemsize * n_channels 

817 bitmap = lib.FreeImage_AllocateT(fi_type, c, r, bpp, 0, 0, 0) 

818 bitmap = ctypes.c_void_p(bitmap) 

819 

820 # Check and store 

821 if not bitmap: # pragma: no cover 

822 raise RuntimeError( 

823 "Could not allocate bitmap for storage: %s" 

824 % self._fi._get_error_message() 

825 ) 

826 self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap)) 

827 

828 def load_from_filename(self, filename=None): 

829 if filename is None: 

830 filename = self._filename 

831 

832 with self._fi as lib: 

833 # Create bitmap 

834 bitmap = lib.FreeImage_Load(self._ftype, efn(filename), self._flags) 

835 bitmap = ctypes.c_void_p(bitmap) 

836 

837 # Check and store 

838 if not bitmap: # pragma: no cover 

839 raise ValueError( 

840 'Could not load bitmap "%s": %s' 

841 % (self._filename, self._fi._get_error_message()) 

842 ) 

843 self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap)) 

844 

845 # def load_from_bytes(self, bb): 

846 # with self._fi as lib: 

847 # # Create bitmap 

848 # fimemory = lib.FreeImage_OpenMemory( 

849 # ctypes.c_char_p(bb), len(bb)) 

850 # bitmap = lib.FreeImage_LoadFromMemory( 

851 # self._ftype, ctypes.c_void_p(fimemory), self._flags) 

852 # bitmap = ctypes.c_void_p(bitmap) 

853 # lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory)) 

854 # 

855 # # Check 

856 # if not bitmap: 

857 # raise ValueError('Could not load bitmap "%s": %s' 

858 # % (self._filename, self._fi._get_error_message())) 

859 # else: 

860 # self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap)) 

861 

862 def save_to_filename(self, filename=None): 

863 if filename is None: 

864 filename = self._filename 

865 

866 ftype = self._ftype 

867 bitmap = self._bitmap 

868 fi_type = self._fi_type # element type 

869 

870 with self._fi as lib: 

871 # Check if can write 

872 if fi_type == FI_TYPES.FIT_BITMAP: 

873 can_write = lib.FreeImage_FIFSupportsExportBPP( 

874 ftype, lib.FreeImage_GetBPP(bitmap) 

875 ) 

876 else: 

877 can_write = lib.FreeImage_FIFSupportsExportType(ftype, fi_type) 

878 if not can_write: 

879 raise TypeError("Cannot save image of this format to this file type") 

880 

881 # Save to file 

882 res = lib.FreeImage_Save(ftype, bitmap, efn(filename), self._flags) 

883 # Check 

884 if res is None: # pragma: no cover, we do so many checks, this is rare 

885 raise RuntimeError( 

886 f"Could not save file `{self._filename}`: {self._fi._get_error_message()}" 

887 ) 

888 

889 # def save_to_bytes(self): 

890 # ftype = self._ftype 

891 # bitmap = self._bitmap 

892 # fi_type = self._fi_type # element type 

893 # 

894 # with self._fi as lib: 

895 # # Check if can write 

896 # if fi_type == FI_TYPES.FIT_BITMAP: 

897 # can_write = lib.FreeImage_FIFSupportsExportBPP(ftype, 

898 # lib.FreeImage_GetBPP(bitmap)) 

899 # else: 

900 # can_write = lib.FreeImage_FIFSupportsExportType(ftype, fi_type) 

901 # if not can_write: 

902 # raise TypeError('Cannot save image of this format ' 

903 # 'to this file type') 

904 # 

905 # # Extract the bytes 

906 # fimemory = lib.FreeImage_OpenMemory(0, 0) 

907 # res = lib.FreeImage_SaveToMemory(ftype, bitmap, 

908 # ctypes.c_void_p(fimemory), 

909 # self._flags) 

910 # if res: 

911 # N = lib.FreeImage_TellMemory(ctypes.c_void_p(fimemory)) 

912 # result = ctypes.create_string_buffer(N) 

913 # lib.FreeImage_SeekMemory(ctypes.c_void_p(fimemory), 0) 

914 # lib.FreeImage_ReadMemory(result, 1, N, ctypes.c_void_p(fimemory)) 

915 # result = result.raw 

916 # lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory)) 

917 # 

918 # # Check 

919 # if not res: 

920 # raise RuntimeError('Could not save file "%s": %s' 

921 # % (self._filename, self._fi._get_error_message())) 

922 # 

923 # # Done 

924 # return result 

925 

926 def get_image_data(self): 

927 dtype, shape, bpp = self._get_type_and_shape() 

928 array = self._wrap_bitmap_bits_in_array(shape, dtype, False) 

929 with self._fi as lib: 

930 isle = lib.FreeImage_IsLittleEndian() 

931 

932 # swizzle the color components and flip the scanlines to go from 

933 # FreeImage's BGR[A] and upside-down internal memory format to 

934 # something more normal 

935 def n(arr): 

936 # return arr[..., ::-1].T # Does not work on numpypy yet 

937 if arr.ndim == 1: # pragma: no cover 

938 return arr[::-1].T 

939 elif arr.ndim == 2: # Always the case here ... 

940 return arr[:, ::-1].T 

941 elif arr.ndim == 3: # pragma: no cover 

942 return arr[:, :, ::-1].T 

943 elif arr.ndim == 4: # pragma: no cover 

944 return arr[:, :, :, ::-1].T 

945 

946 if len(shape) == 3 and isle and dtype.type == numpy.uint8: 

947 b = n(array[0]) 

948 g = n(array[1]) 

949 r = n(array[2]) 

950 if shape[0] == 3: 

951 return numpy.dstack((r, g, b)) 

952 elif shape[0] == 4: 

953 a = n(array[3]) 

954 return numpy.dstack((r, g, b, a)) 

955 else: # pragma: no cover - we check this earlier 

956 raise ValueError("Cannot handle images of shape %s" % shape) 

957 

958 # We need to copy because array does *not* own its memory 

959 # after bitmap is freed. 

960 a = n(array).copy() 

961 return a 

962 

963 def set_image_data(self, array): 

964 # Prepare array 

965 assert isinstance(array, numpy.ndarray) 

966 shape = array.shape 

967 dtype = array.dtype 

968 with self._fi as lib: 

969 isle = lib.FreeImage_IsLittleEndian() 

970 

971 # Calculate shape and channels 

972 r, c = shape[:2] 

973 if len(shape) == 2: 

974 n_channels = 1 

975 w_shape = (c, r) 

976 elif len(shape) == 3: 

977 n_channels = shape[2] 

978 w_shape = (n_channels, c, r) 

979 else: 

980 n_channels = shape[0] 

981 

982 def n(arr): # normalise to freeimage's in-memory format 

983 return arr[::-1].T 

984 

985 wrapped_array = self._wrap_bitmap_bits_in_array(w_shape, dtype, True) 

986 # swizzle the color components and flip the scanlines to go to 

987 # FreeImage's BGR[A] and upside-down internal memory format 

988 # The BGR[A] order is only used for 8bits per channel images 

989 # on little endian machines. For everything else RGB[A] is 

990 # used. 

991 if len(shape) == 3 and isle and dtype.type == numpy.uint8: 

992 R = array[:, :, 0] 

993 G = array[:, :, 1] 

994 B = array[:, :, 2] 

995 wrapped_array[0] = n(B) 

996 wrapped_array[1] = n(G) 

997 wrapped_array[2] = n(R) 

998 if shape[2] == 4: 

999 A = array[:, :, 3] 

1000 wrapped_array[3] = n(A) 

1001 else: 

1002 wrapped_array[:] = n(array) 

1003 if self._need_finish: 

1004 self._finish_wrapped_array(wrapped_array) 

1005 

1006 if len(shape) == 2 and dtype.type == numpy.uint8: 

1007 with self._fi as lib: 

1008 palette = lib.FreeImage_GetPalette(self._bitmap) 

1009 palette = ctypes.c_void_p(palette) 

1010 if not palette: 

1011 raise RuntimeError("Could not get image palette") 

1012 try: 

1013 palette_data = GREY_PALETTE.ctypes.data 

1014 except Exception: # pragma: no cover - IS_PYPY 

1015 palette_data = GREY_PALETTE.__array_interface__["data"][0] 

1016 ctypes.memmove(palette, palette_data, 1024) 

1017 

1018 def _wrap_bitmap_bits_in_array(self, shape, dtype, save): 

1019 """Return an ndarray view on the data in a FreeImage bitmap. Only 

1020 valid for as long as the bitmap is loaded (if single page) / locked 

1021 in memory (if multipage). This is used in loading data, but 

1022 also during saving, to prepare a strided numpy array buffer. 

1023 

1024 """ 

1025 # Get bitmap info 

1026 with self._fi as lib: 

1027 pitch = lib.FreeImage_GetPitch(self._bitmap) 

1028 bits = lib.FreeImage_GetBits(self._bitmap) 

1029 

1030 # Get more info 

1031 height = shape[-1] 

1032 byte_size = height * pitch 

1033 itemsize = dtype.itemsize 

1034 

1035 # Get strides 

1036 if len(shape) == 3: 

1037 strides = (itemsize, shape[0] * itemsize, pitch) 

1038 else: 

1039 strides = (itemsize, pitch) 

1040 

1041 # Create numpy array and return 

1042 data = (ctypes.c_char * byte_size).from_address(bits) 

1043 try: 

1044 self._need_finish = False 

1045 if TEST_NUMPY_NO_STRIDES: 

1046 raise NotImplementedError() 

1047 return numpy.ndarray(shape, dtype=dtype, buffer=data, strides=strides) 

1048 except NotImplementedError: 

1049 # IS_PYPY - not very efficient. We create a C-contiguous 

1050 # numpy array (because pypy does not support Fortran-order) 

1051 # and shape it such that the rest of the code can remain. 

1052 if save: 

1053 self._need_finish = True # Flag to use _finish_wrapped_array 

1054 return numpy.zeros(shape, dtype=dtype) 

1055 else: 

1056 bb = bytes(bytearray(data)) 

1057 array = numpy.frombuffer(bb, dtype=dtype).copy() 

1058 # Deal with strides 

1059 if len(shape) == 3: 

1060 array.shape = shape[2], strides[-1] // shape[0], shape[0] 

1061 array2 = array[: shape[2], : shape[1], : shape[0]] 

1062 array = numpy.zeros(shape, dtype=array.dtype) 

1063 for i in range(shape[0]): 

1064 array[i] = array2[:, :, i].T 

1065 else: 

1066 array.shape = shape[1], strides[-1] 

1067 array = array[: shape[1], : shape[0]].T 

1068 return array 

1069 

1070 def _finish_wrapped_array(self, array): # IS_PYPY 

1071 """Hardcore way to inject numpy array in bitmap.""" 

1072 # Get bitmap info 

1073 with self._fi as lib: 

1074 pitch = lib.FreeImage_GetPitch(self._bitmap) 

1075 bits = lib.FreeImage_GetBits(self._bitmap) 

1076 bpp = lib.FreeImage_GetBPP(self._bitmap) 

1077 # Get channels and realwidth 

1078 nchannels = bpp // 8 // array.itemsize 

1079 realwidth = pitch // nchannels 

1080 # Apply padding for pitch if necessary 

1081 extra = realwidth - array.shape[-2] 

1082 assert 0 <= extra < 10 

1083 # Make sort of Fortran, also take padding (i.e. pitch) into account 

1084 newshape = array.shape[-1], realwidth, nchannels 

1085 array2 = numpy.zeros(newshape, array.dtype) 

1086 if nchannels == 1: 

1087 array2[:, : array.shape[-2], 0] = array.T 

1088 else: 

1089 for i in range(nchannels): 

1090 array2[:, : array.shape[-2], i] = array[i, :, :].T 

1091 # copy data 

1092 data_ptr = array2.__array_interface__["data"][0] 

1093 ctypes.memmove(bits, data_ptr, array2.nbytes) 

1094 del array2 

1095 

1096 def _get_type_and_shape(self): 

1097 bitmap = self._bitmap 

1098 

1099 # Get info on bitmap 

1100 with self._fi as lib: 

1101 w = lib.FreeImage_GetWidth(bitmap) 

1102 h = lib.FreeImage_GetHeight(bitmap) 

1103 self._fi_type = fi_type = lib.FreeImage_GetImageType(bitmap) 

1104 if not fi_type: 

1105 raise ValueError("Unknown image pixel type") 

1106 

1107 # Determine required props for numpy array 

1108 bpp = None 

1109 dtype = FI_TYPES.dtypes[fi_type] 

1110 

1111 if fi_type == FI_TYPES.FIT_BITMAP: 

1112 with self._fi as lib: 

1113 bpp = lib.FreeImage_GetBPP(bitmap) 

1114 has_pallette = lib.FreeImage_GetColorsUsed(bitmap) 

1115 if has_pallette: 

1116 # Examine the palette. If it is grayscale, we return as such 

1117 if has_pallette == 256: 

1118 palette = lib.FreeImage_GetPalette(bitmap) 

1119 palette = ctypes.c_void_p(palette) 

1120 p = (ctypes.c_uint8 * (256 * 4)).from_address(palette.value) 

1121 p = numpy.frombuffer(p, numpy.uint32).copy() 

1122 if (GREY_PALETTE == p).all(): 

1123 extra_dims = [] 

1124 return numpy.dtype(dtype), extra_dims + [w, h], bpp 

1125 # Convert bitmap and call this method again 

1126 newbitmap = lib.FreeImage_ConvertTo32Bits(bitmap) 

1127 newbitmap = ctypes.c_void_p(newbitmap) 

1128 self._set_bitmap(newbitmap) 

1129 return self._get_type_and_shape() 

1130 elif bpp == 8: 

1131 extra_dims = [] 

1132 elif bpp == 24: 

1133 extra_dims = [3] 

1134 elif bpp == 32: 

1135 extra_dims = [4] 

1136 else: # pragma: no cover 

1137 # raise ValueError('Cannot convert %d BPP bitmap' % bpp) 

1138 # Convert bitmap and call this method again 

1139 newbitmap = lib.FreeImage_ConvertTo32Bits(bitmap) 

1140 newbitmap = ctypes.c_void_p(newbitmap) 

1141 self._set_bitmap(newbitmap) 

1142 return self._get_type_and_shape() 

1143 else: 

1144 extra_dims = FI_TYPES.extra_dims[fi_type] 

1145 

1146 # Return dtype and shape 

1147 return numpy.dtype(dtype), extra_dims + [w, h], bpp 

1148 

1149 def quantize(self, quantizer=0, palettesize=256): 

1150 """Quantize the bitmap to make it 8-bit (paletted). Returns a new 

1151 FIBitmap object. 

1152 Only for 24 bit images. 

1153 """ 

1154 with self._fi as lib: 

1155 # New bitmap 

1156 bitmap = lib.FreeImage_ColorQuantizeEx( 

1157 self._bitmap, quantizer, palettesize, 0, None 

1158 ) 

1159 bitmap = ctypes.c_void_p(bitmap) 

1160 

1161 # Check and return 

1162 if not bitmap: 

1163 raise ValueError( 

1164 'Could not quantize bitmap "%s": %s' 

1165 % (self._filename, self._fi._get_error_message()) 

1166 ) 

1167 

1168 new = FIBitmap(self._fi, self._filename, self._ftype, self._flags) 

1169 new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap)) 

1170 new._fi_type = self._fi_type 

1171 return new 

1172 

1173 

1174# def convert_to_32bit(self): 

1175# """ Convert to 32bit image. 

1176# """ 

1177# with self._fi as lib: 

1178# # New bitmap 

1179# bitmap = lib.FreeImage_ConvertTo32Bits(self._bitmap) 

1180# bitmap = ctypes.c_void_p(bitmap) 

1181# 

1182# # Check and return 

1183# if not bitmap: 

1184# raise ValueError('Could not convert bitmap to 32bit "%s": %s' % 

1185# (self._filename, 

1186# self._fi._get_error_message())) 

1187# else: 

1188# new = FIBitmap(self._fi, self._filename, self._ftype, 

1189# self._flags) 

1190# new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap)) 

1191# new._fi_type = self._fi_type 

1192# return new 

1193 

1194 

1195class FIMultipageBitmap(FIBaseBitmap): 

1196 """Wrapper for the multipage FI bitmap object.""" 

1197 

1198 def load_from_filename(self, filename=None): 

1199 if filename is None: # pragma: no cover 

1200 filename = self._filename 

1201 

1202 # Prepare 

1203 create_new = False 

1204 read_only = True 

1205 keep_cache_in_memory = False 

1206 

1207 # Try opening 

1208 with self._fi as lib: 

1209 # Create bitmap 

1210 multibitmap = lib.FreeImage_OpenMultiBitmap( 

1211 self._ftype, 

1212 efn(filename), 

1213 create_new, 

1214 read_only, 

1215 keep_cache_in_memory, 

1216 self._flags, 

1217 ) 

1218 multibitmap = ctypes.c_void_p(multibitmap) 

1219 

1220 # Check 

1221 if not multibitmap: # pragma: no cover 

1222 err = self._fi._get_error_message() 

1223 raise ValueError( 

1224 'Could not open file "%s" as multi-image: %s' 

1225 % (self._filename, err) 

1226 ) 

1227 self._set_bitmap(multibitmap, (lib.FreeImage_CloseMultiBitmap, multibitmap)) 

1228 

1229 # def load_from_bytes(self, bb): 

1230 # with self._fi as lib: 

1231 # # Create bitmap 

1232 # fimemory = lib.FreeImage_OpenMemory( 

1233 # ctypes.c_char_p(bb), len(bb)) 

1234 # multibitmap = lib.FreeImage_LoadMultiBitmapFromMemory( 

1235 # self._ftype, ctypes.c_void_p(fimemory), self._flags) 

1236 # multibitmap = ctypes.c_void_p(multibitmap) 

1237 # #lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory)) 

1238 # self._mem = fimemory 

1239 # self._bytes = bb 

1240 # # Check 

1241 # if not multibitmap: 

1242 # raise ValueError('Could not load multibitmap "%s": %s' 

1243 # % (self._filename, self._fi._get_error_message())) 

1244 # else: 

1245 # self._set_bitmap(multibitmap, 

1246 # (lib.FreeImage_CloseMultiBitmap, multibitmap)) 

1247 

1248 def save_to_filename(self, filename=None): 

1249 if filename is None: # pragma: no cover 

1250 filename = self._filename 

1251 

1252 # Prepare 

1253 create_new = True 

1254 read_only = False 

1255 keep_cache_in_memory = False 

1256 

1257 # Open the file 

1258 # todo: Set flags at close func 

1259 with self._fi as lib: 

1260 multibitmap = lib.FreeImage_OpenMultiBitmap( 

1261 self._ftype, 

1262 efn(filename), 

1263 create_new, 

1264 read_only, 

1265 keep_cache_in_memory, 

1266 0, 

1267 ) 

1268 multibitmap = ctypes.c_void_p(multibitmap) 

1269 

1270 # Check 

1271 if not multibitmap: # pragma: no cover 

1272 msg = 'Could not open file "%s" for writing multi-image: %s' % ( 

1273 self._filename, 

1274 self._fi._get_error_message(), 

1275 ) 

1276 raise ValueError(msg) 

1277 self._set_bitmap(multibitmap, (lib.FreeImage_CloseMultiBitmap, multibitmap)) 

1278 

1279 def __len__(self): 

1280 with self._fi as lib: 

1281 return lib.FreeImage_GetPageCount(self._bitmap) 

1282 

1283 def get_page(self, index): 

1284 """Return the sub-bitmap for the given page index. 

1285 Please close the returned bitmap when done. 

1286 """ 

1287 with self._fi as lib: 

1288 # Create low-level bitmap in freeimage 

1289 bitmap = lib.FreeImage_LockPage(self._bitmap, index) 

1290 bitmap = ctypes.c_void_p(bitmap) 

1291 if not bitmap: # pragma: no cover 

1292 raise ValueError( 

1293 "Could not open sub-image %i in %r: %s" 

1294 % (index, self._filename, self._fi._get_error_message()) 

1295 ) 

1296 

1297 # Get bitmap object to wrap this bitmap 

1298 bm = FIBitmap(self._fi, self._filename, self._ftype, self._flags) 

1299 bm._set_bitmap( 

1300 bitmap, (lib.FreeImage_UnlockPage, self._bitmap, bitmap, False) 

1301 ) 

1302 return bm 

1303 

1304 def append_bitmap(self, bitmap): 

1305 """Add a sub-bitmap to the multi-page bitmap.""" 

1306 with self._fi as lib: 

1307 # no return value 

1308 lib.FreeImage_AppendPage(self._bitmap, bitmap._bitmap) 

1309 

1310 

1311# Create instance 

1312fi = Freeimage()