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

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

156 statements  

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

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

3 

4"""Read/Write images using FreeImage. 

5 

6Backend Library: `FreeImage <https://freeimage.sourceforge.io/>`_ 

7 

8.. note:: 

9 To use this plugin you have to install its backend:: 

10 

11 imageio_download_bin freeimage 

12 

13 or you can download the backend using the function:: 

14 

15 imageio.plugins.freeimage.download() 

16 

17Each Freeimage format has the ``flags`` keyword argument. See the `Freeimage 

18documentation <https://freeimage.sourceforge.io/>`_ for more information. 

19 

20Parameters 

21---------- 

22flags : int 

23 A freeimage-specific option. In most cases we provide explicit 

24 parameters for influencing image reading. 

25 

26""" 

27 

28import numpy as np 

29 

30from ..core import Format, image_as_uint 

31from ..core.request import RETURN_BYTES 

32from ._freeimage import FNAME_PER_PLATFORM, IO_FLAGS, download, fi # noqa 

33 

34# todo: support files with only meta data 

35 

36 

37class FreeimageFormat(Format): 

38 """See :mod:`imageio.plugins.freeimage`""" 

39 

40 _modes = "i" 

41 

42 def __init__(self, name, description, extensions=None, modes=None, *, fif=None): 

43 super().__init__(name, description, extensions=extensions, modes=modes) 

44 self._fif = fif 

45 

46 @property 

47 def fif(self): 

48 return self._fif # Set when format is created 

49 

50 def _can_read(self, request): 

51 # Ask freeimage if it can read it, maybe ext missing 

52 if fi.has_lib(): 

53 if not hasattr(request, "_fif"): 

54 try: 

55 request._fif = fi.getFIF(request.filename, "r", request.firstbytes) 

56 except Exception: # pragma: no cover 

57 request._fif = -1 

58 if request._fif == self.fif: 

59 return True 

60 elif request._fif == 7 and self.fif == 14: 

61 # PPM gets identified as PBM and PPM can read PBM 

62 # see: https://github.com/imageio/imageio/issues/677 

63 return True 

64 

65 def _can_write(self, request): 

66 # Ask freeimage, because we are not aware of all formats 

67 if fi.has_lib(): 

68 if not hasattr(request, "_fif"): 

69 try: 

70 request._fif = fi.getFIF(request.filename, "w") 

71 except ValueError: # pragma: no cover 

72 if request.raw_uri == RETURN_BYTES: 

73 request._fif = self.fif 

74 else: 

75 request._fif = -1 

76 if request._fif is self.fif: 

77 return True 

78 

79 # -- 

80 

81 class Reader(Format.Reader): 

82 def _get_length(self): 

83 return 1 

84 

85 def _open(self, flags=0): 

86 self._bm = fi.create_bitmap(self.request.filename, self.format.fif, flags) 

87 self._bm.load_from_filename(self.request.get_local_filename()) 

88 

89 def _close(self): 

90 self._bm.close() 

91 

92 def _get_data(self, index): 

93 if index != 0: 

94 raise IndexError("This format only supports singleton images.") 

95 return self._bm.get_image_data(), self._bm.get_meta_data() 

96 

97 def _get_meta_data(self, index): 

98 if not (index is None or index == 0): 

99 raise IndexError() 

100 return self._bm.get_meta_data() 

101 

102 # -- 

103 

104 class Writer(Format.Writer): 

105 def _open(self, flags=0): 

106 self._flags = flags # Store flags for later use 

107 self._bm = None 

108 self._is_set = False # To prevent appending more than one image 

109 self._meta = {} 

110 

111 def _close(self): 

112 # Set global meta data 

113 self._bm.set_meta_data(self._meta) 

114 # Write and close 

115 self._bm.save_to_filename(self.request.get_local_filename()) 

116 self._bm.close() 

117 

118 def _append_data(self, im, meta): 

119 # Check if set 

120 if not self._is_set: 

121 self._is_set = True 

122 else: 

123 raise RuntimeError( 

124 "Singleton image; " "can only append image data once." 

125 ) 

126 # Pop unit dimension for grayscale images 

127 if im.ndim == 3 and im.shape[-1] == 1: 

128 im = im[:, :, 0] 

129 # Lazy instantaion of the bitmap, we need image data 

130 if self._bm is None: 

131 self._bm = fi.create_bitmap( 

132 self.request.filename, self.format.fif, self._flags 

133 ) 

134 self._bm.allocate(im) 

135 # Set data 

136 self._bm.set_image_data(im) 

137 # There is no distinction between global and per-image meta data 

138 # for singleton images 

139 self._meta = meta 

140 

141 def _set_meta_data(self, meta): 

142 self._meta = meta 

143 

144 

145# Special plugins 

146 

147# todo: there is also FIF_LOAD_NOPIXELS, 

148# but perhaps that should be used with get_meta_data. 

149 

150 

151class FreeimageBmpFormat(FreeimageFormat): 

152 """A BMP format based on the Freeimage library. 

153 

154 This format supports grayscale, RGB and RGBA images. 

155 

156 The freeimage plugin requires a `freeimage` binary. If this binary 

157 not available on the system, it can be downloaded manually from 

158 <https://github.com/imageio/imageio-binaries> by either 

159 

160 - the command line script ``imageio_download_bin freeimage`` 

161 - the Python method ``imageio.plugins.freeimage.download()`` 

162 

163 Parameters for saving 

164 --------------------- 

165 compression : bool 

166 Whether to compress the bitmap using RLE when saving. Default False. 

167 It seems this does not always work, but who cares, you should use 

168 PNG anyway. 

169 

170 """ 

171 

172 class Writer(FreeimageFormat.Writer): 

173 def _open(self, flags=0, compression=False): 

174 # Build flags from kwargs 

175 flags = int(flags) 

176 if compression: 

177 flags |= IO_FLAGS.BMP_SAVE_RLE 

178 else: 

179 flags |= IO_FLAGS.BMP_DEFAULT 

180 # Act as usual, but with modified flags 

181 return FreeimageFormat.Writer._open(self, flags) 

182 

183 def _append_data(self, im, meta): 

184 im = image_as_uint(im, bitdepth=8) 

185 return FreeimageFormat.Writer._append_data(self, im, meta) 

186 

187 

188class FreeimagePngFormat(FreeimageFormat): 

189 """A PNG format based on the Freeimage library. 

190 

191 This format supports grayscale, RGB and RGBA images. 

192 

193 The freeimage plugin requires a `freeimage` binary. If this binary 

194 not available on the system, it can be downloaded manually from 

195 <https://github.com/imageio/imageio-binaries> by either 

196 

197 - the command line script ``imageio_download_bin freeimage`` 

198 - the Python method ``imageio.plugins.freeimage.download()`` 

199 

200 Parameters for reading 

201 ---------------------- 

202 ignoregamma : bool 

203 Avoid gamma correction. Default True. 

204 

205 Parameters for saving 

206 --------------------- 

207 compression : {0, 1, 6, 9} 

208 The compression factor. Higher factors result in more 

209 compression at the cost of speed. Note that PNG compression is 

210 always lossless. Default 9. 

211 quantize : int 

212 If specified, turn the given RGB or RGBA image in a paletted image 

213 for more efficient storage. The value should be between 2 and 256. 

214 If the value of 0 the image is not quantized. 

215 interlaced : bool 

216 Save using Adam7 interlacing. Default False. 

217 """ 

218 

219 class Reader(FreeimageFormat.Reader): 

220 def _open(self, flags=0, ignoregamma=True): 

221 # Build flags from kwargs 

222 flags = int(flags) 

223 if ignoregamma: 

224 flags |= IO_FLAGS.PNG_IGNOREGAMMA 

225 # Enter as usual, with modified flags 

226 return FreeimageFormat.Reader._open(self, flags) 

227 

228 # -- 

229 

230 class Writer(FreeimageFormat.Writer): 

231 def _open(self, flags=0, compression=9, quantize=0, interlaced=False): 

232 compression_map = { 

233 0: IO_FLAGS.PNG_Z_NO_COMPRESSION, 

234 1: IO_FLAGS.PNG_Z_BEST_SPEED, 

235 6: IO_FLAGS.PNG_Z_DEFAULT_COMPRESSION, 

236 9: IO_FLAGS.PNG_Z_BEST_COMPRESSION, 

237 } 

238 # Build flags from kwargs 

239 flags = int(flags) 

240 if interlaced: 

241 flags |= IO_FLAGS.PNG_INTERLACED 

242 try: 

243 flags |= compression_map[compression] 

244 except KeyError: 

245 raise ValueError("Png compression must be 0, 1, 6, or 9.") 

246 # Act as usual, but with modified flags 

247 return FreeimageFormat.Writer._open(self, flags) 

248 

249 def _append_data(self, im, meta): 

250 if str(im.dtype) == "uint16": 

251 im = image_as_uint(im, bitdepth=16) 

252 else: 

253 im = image_as_uint(im, bitdepth=8) 

254 FreeimageFormat.Writer._append_data(self, im, meta) 

255 # Quantize? 

256 q = int(self.request.kwargs.get("quantize", False)) 

257 if not q: 

258 pass 

259 elif not (im.ndim == 3 and im.shape[-1] == 3): 

260 raise ValueError("Can only quantize RGB images") 

261 elif q < 2 or q > 256: 

262 raise ValueError("PNG quantize param must be 2..256") 

263 else: 

264 bm = self._bm.quantize(0, q) 

265 self._bm.close() 

266 self._bm = bm 

267 

268 

269class FreeimageJpegFormat(FreeimageFormat): 

270 """A JPEG format based on the Freeimage library. 

271 

272 This format supports grayscale and RGB images. 

273 

274 The freeimage plugin requires a `freeimage` binary. If this binary 

275 not available on the system, it can be downloaded manually from 

276 <https://github.com/imageio/imageio-binaries> by either 

277 

278 - the command line script ``imageio_download_bin freeimage`` 

279 - the Python method ``imageio.plugins.freeimage.download()`` 

280 

281 Parameters for reading 

282 ---------------------- 

283 exifrotate : bool 

284 Automatically rotate the image according to the exif flag. 

285 Default True. If 2 is given, do the rotation in Python instead 

286 of freeimage. 

287 quickread : bool 

288 Read the image more quickly, at the expense of quality. 

289 Default False. 

290 

291 Parameters for saving 

292 --------------------- 

293 quality : scalar 

294 The compression factor of the saved image (1..100), higher 

295 numbers result in higher quality but larger file size. Default 75. 

296 progressive : bool 

297 Save as a progressive JPEG file (e.g. for images on the web). 

298 Default False. 

299 optimize : bool 

300 On saving, compute optimal Huffman coding tables (can reduce a 

301 few percent of file size). Default False. 

302 baseline : bool 

303 Save basic JPEG, without metadata or any markers. Default False. 

304 

305 """ 

306 

307 class Reader(FreeimageFormat.Reader): 

308 def _open(self, flags=0, exifrotate=True, quickread=False): 

309 # Build flags from kwargs 

310 flags = int(flags) 

311 if exifrotate and exifrotate != 2: 

312 flags |= IO_FLAGS.JPEG_EXIFROTATE 

313 if not quickread: 

314 flags |= IO_FLAGS.JPEG_ACCURATE 

315 # Enter as usual, with modified flags 

316 return FreeimageFormat.Reader._open(self, flags) 

317 

318 def _get_data(self, index): 

319 im, meta = FreeimageFormat.Reader._get_data(self, index) 

320 im = self._rotate(im, meta) 

321 return im, meta 

322 

323 def _rotate(self, im, meta): 

324 """Use Orientation information from EXIF meta data to 

325 orient the image correctly. Freeimage is also supposed to 

326 support that, and I am pretty sure it once did, but now it 

327 does not, so let's just do it in Python. 

328 Edit: and now it works again, just leave in place as a fallback. 

329 """ 

330 if self.request.kwargs.get("exifrotate", None) == 2: 

331 try: 

332 ori = meta["EXIF_MAIN"]["Orientation"] 

333 except KeyError: # pragma: no cover 

334 pass # Orientation not available 

335 else: # pragma: no cover - we cannot touch all cases 

336 # www.impulseadventure.com/photo/exif-orientation.html 

337 if ori in [1, 2]: 

338 pass 

339 if ori in [3, 4]: 

340 im = np.rot90(im, 2) 

341 if ori in [5, 6]: 

342 im = np.rot90(im, 3) 

343 if ori in [7, 8]: 

344 im = np.rot90(im) 

345 if ori in [2, 4, 5, 7]: # Flipped cases (rare) 

346 im = np.fliplr(im) 

347 return im 

348 

349 # -- 

350 

351 class Writer(FreeimageFormat.Writer): 

352 def _open( 

353 self, flags=0, quality=75, progressive=False, optimize=False, baseline=False 

354 ): 

355 # Test quality 

356 quality = int(quality) 

357 if quality < 1 or quality > 100: 

358 raise ValueError("JPEG quality should be between 1 and 100.") 

359 # Build flags from kwargs 

360 flags = int(flags) 

361 flags |= quality 

362 if progressive: 

363 flags |= IO_FLAGS.JPEG_PROGRESSIVE 

364 if optimize: 

365 flags |= IO_FLAGS.JPEG_OPTIMIZE 

366 if baseline: 

367 flags |= IO_FLAGS.JPEG_BASELINE 

368 # Act as usual, but with modified flags 

369 return FreeimageFormat.Writer._open(self, flags) 

370 

371 def _append_data(self, im, meta): 

372 if im.ndim == 3 and im.shape[-1] == 4: 

373 raise IOError("JPEG does not support alpha channel.") 

374 im = image_as_uint(im, bitdepth=8) 

375 return FreeimageFormat.Writer._append_data(self, im, meta) 

376 

377 

378class FreeimagePnmFormat(FreeimageFormat): 

379 """A PNM format based on the Freeimage library. 

380 

381 This format supports single bit (PBM), grayscale (PGM) and RGB (PPM) 

382 images, even with ASCII or binary coding. 

383 

384 The freeimage plugin requires a `freeimage` binary. If this binary 

385 not available on the system, it can be downloaded manually from 

386 <https://github.com/imageio/imageio-binaries> by either 

387 

388 - the command line script ``imageio_download_bin freeimage`` 

389 - the Python method ``imageio.plugins.freeimage.download()`` 

390 

391 Parameters for saving 

392 --------------------- 

393 use_ascii : bool 

394 Save with ASCII coding. Default True. 

395 """ 

396 

397 class Writer(FreeimageFormat.Writer): 

398 def _open(self, flags=0, use_ascii=True): 

399 # Build flags from kwargs 

400 flags = int(flags) 

401 if use_ascii: 

402 flags |= IO_FLAGS.PNM_SAVE_ASCII 

403 # Act as usual, but with modified flags 

404 return FreeimageFormat.Writer._open(self, flags)