_freeimage.py 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312
  1. # -*- coding: utf-8 -*-
  2. # imageio is distributed under the terms of the (new) BSD License.
  3. # styletest: ignore E261
  4. """Module imageio/freeimage.py
  5. This module contains the wrapper code for the freeimage library.
  6. The functions defined in this module are relatively thin; just thin
  7. enough so that arguments and results are native Python/numpy data
  8. types.
  9. """
  10. import os
  11. import sys
  12. import ctypes
  13. import threading
  14. import logging
  15. import numpy
  16. from ..core import (
  17. get_remote_file,
  18. load_lib,
  19. Dict,
  20. resource_dirs,
  21. IS_PYPY,
  22. get_platform,
  23. InternetNotAllowedError,
  24. NeedDownloadError,
  25. )
  26. logger = logging.getLogger(__name__)
  27. TEST_NUMPY_NO_STRIDES = False # To test pypy fallback
  28. FNAME_PER_PLATFORM = {
  29. "osx32": "libfreeimage-3.16.0-osx10.6.dylib", # universal library
  30. "osx64": "libfreeimage-3.16.0-osx10.6.dylib",
  31. "win32": "FreeImage-3.18.0-win32.dll",
  32. "win64": "FreeImage-3.18.0-win64.dll",
  33. "linux32": "libfreeimage-3.16.0-linux32.so",
  34. "linux64": "libfreeimage-3.16.0-linux64.so",
  35. }
  36. def download(directory=None, force_download=False):
  37. """Download the FreeImage library to your computer.
  38. Parameters
  39. ----------
  40. directory : str | None
  41. The directory where the file will be cached if a download was
  42. required to obtain the file. By default, the appdata directory
  43. is used. This is also the first directory that is checked for
  44. a local version of the file.
  45. force_download : bool | str
  46. If True, the file will be downloaded even if a local copy exists
  47. (and this copy will be overwritten). Can also be a YYYY-MM-DD date
  48. to ensure a file is up-to-date (modified date of a file on disk,
  49. if present, is checked).
  50. """
  51. plat = get_platform()
  52. if plat and plat in FNAME_PER_PLATFORM:
  53. fname = "freeimage/" + FNAME_PER_PLATFORM[plat]
  54. get_remote_file(fname=fname, directory=directory, force_download=force_download)
  55. fi._lib = None # allow trying again (needed to make tests work)
  56. def get_freeimage_lib():
  57. """Ensure we have our version of the binary freeimage lib."""
  58. lib = os.getenv("IMAGEIO_FREEIMAGE_LIB", None)
  59. if lib: # pragma: no cover
  60. return lib
  61. # Get filename to load
  62. # If we do not provide a binary, the system may still do ...
  63. plat = get_platform()
  64. if plat and plat in FNAME_PER_PLATFORM:
  65. try:
  66. return get_remote_file("freeimage/" + FNAME_PER_PLATFORM[plat], auto=False)
  67. except InternetNotAllowedError:
  68. pass
  69. except NeedDownloadError:
  70. raise NeedDownloadError(
  71. "Need FreeImage library. "
  72. "You can obtain it with either:\n"
  73. " - download using the command: "
  74. "imageio_download_bin freeimage\n"
  75. " - download by calling (in Python): "
  76. "imageio.plugins.freeimage.download()\n"
  77. )
  78. except RuntimeError as e: # pragma: no cover
  79. logger.warning(str(e))
  80. # Define function to encode a filename to bytes (for the current system)
  81. def efn(x):
  82. return x.encode(sys.getfilesystemencoding())
  83. # 4-byte quads of 0,v,v,v from 0,0,0,0 to 0,255,255,255
  84. GREY_PALETTE = numpy.arange(0, 0x01000000, 0x00010101, dtype=numpy.uint32)
  85. class FI_TYPES(object):
  86. FIT_UNKNOWN = 0
  87. FIT_BITMAP = 1
  88. FIT_UINT16 = 2
  89. FIT_INT16 = 3
  90. FIT_UINT32 = 4
  91. FIT_INT32 = 5
  92. FIT_FLOAT = 6
  93. FIT_DOUBLE = 7
  94. FIT_COMPLEX = 8
  95. FIT_RGB16 = 9
  96. FIT_RGBA16 = 10
  97. FIT_RGBF = 11
  98. FIT_RGBAF = 12
  99. dtypes = {
  100. FIT_BITMAP: numpy.uint8,
  101. FIT_UINT16: numpy.uint16,
  102. FIT_INT16: numpy.int16,
  103. FIT_UINT32: numpy.uint32,
  104. FIT_INT32: numpy.int32,
  105. FIT_FLOAT: numpy.float32,
  106. FIT_DOUBLE: numpy.float64,
  107. FIT_COMPLEX: numpy.complex128,
  108. FIT_RGB16: numpy.uint16,
  109. FIT_RGBA16: numpy.uint16,
  110. FIT_RGBF: numpy.float32,
  111. FIT_RGBAF: numpy.float32,
  112. }
  113. fi_types = {
  114. (numpy.uint8, 1): FIT_BITMAP,
  115. (numpy.uint8, 3): FIT_BITMAP,
  116. (numpy.uint8, 4): FIT_BITMAP,
  117. (numpy.uint16, 1): FIT_UINT16,
  118. (numpy.int16, 1): FIT_INT16,
  119. (numpy.uint32, 1): FIT_UINT32,
  120. (numpy.int32, 1): FIT_INT32,
  121. (numpy.float32, 1): FIT_FLOAT,
  122. (numpy.float64, 1): FIT_DOUBLE,
  123. (numpy.complex128, 1): FIT_COMPLEX,
  124. (numpy.uint16, 3): FIT_RGB16,
  125. (numpy.uint16, 4): FIT_RGBA16,
  126. (numpy.float32, 3): FIT_RGBF,
  127. (numpy.float32, 4): FIT_RGBAF,
  128. }
  129. extra_dims = {
  130. FIT_UINT16: [],
  131. FIT_INT16: [],
  132. FIT_UINT32: [],
  133. FIT_INT32: [],
  134. FIT_FLOAT: [],
  135. FIT_DOUBLE: [],
  136. FIT_COMPLEX: [],
  137. FIT_RGB16: [3],
  138. FIT_RGBA16: [4],
  139. FIT_RGBF: [3],
  140. FIT_RGBAF: [4],
  141. }
  142. class IO_FLAGS(object):
  143. FIF_LOAD_NOPIXELS = 0x8000 # loading: load the image header only
  144. # # (not supported by all plugins)
  145. BMP_DEFAULT = 0
  146. BMP_SAVE_RLE = 1
  147. CUT_DEFAULT = 0
  148. DDS_DEFAULT = 0
  149. EXR_DEFAULT = 0 # save data as half with piz-based wavelet compression
  150. EXR_FLOAT = 0x0001 # save data as float instead of half (not recommended)
  151. EXR_NONE = 0x0002 # save with no compression
  152. EXR_ZIP = 0x0004 # save with zlib compression, in blocks of 16 scan lines
  153. EXR_PIZ = 0x0008 # save with piz-based wavelet compression
  154. EXR_PXR24 = 0x0010 # save with lossy 24-bit float compression
  155. EXR_B44 = 0x0020 # save with lossy 44% float compression
  156. # # - goes to 22% when combined with EXR_LC
  157. EXR_LC = 0x0040 # save images with one luminance and two chroma channels,
  158. # # rather than as RGB (lossy compression)
  159. FAXG3_DEFAULT = 0
  160. GIF_DEFAULT = 0
  161. GIF_LOAD256 = 1 # Load the image as a 256 color image with ununsed
  162. # # palette entries, if it's 16 or 2 color
  163. GIF_PLAYBACK = 2 # 'Play' the GIF to generate each frame (as 32bpp)
  164. # # instead of returning raw frame data when loading
  165. HDR_DEFAULT = 0
  166. ICO_DEFAULT = 0
  167. ICO_MAKEALPHA = 1 # convert to 32bpp and create an alpha channel from the
  168. # # AND-mask when loading
  169. IFF_DEFAULT = 0
  170. J2K_DEFAULT = 0 # save with a 16:1 rate
  171. JP2_DEFAULT = 0 # save with a 16:1 rate
  172. JPEG_DEFAULT = 0 # loading (see JPEG_FAST);
  173. # # saving (see JPEG_QUALITYGOOD|JPEG_SUBSAMPLING_420)
  174. JPEG_FAST = 0x0001 # load the file as fast as possible,
  175. # # sacrificing some quality
  176. JPEG_ACCURATE = 0x0002 # load the file with the best quality,
  177. # # sacrificing some speed
  178. JPEG_CMYK = 0x0004 # load separated CMYK "as is"
  179. # # (use | to combine with other load flags)
  180. JPEG_EXIFROTATE = 0x0008 # load and rotate according to
  181. # # Exif 'Orientation' tag if available
  182. JPEG_QUALITYSUPERB = 0x80 # save with superb quality (100:1)
  183. JPEG_QUALITYGOOD = 0x0100 # save with good quality (75:1)
  184. JPEG_QUALITYNORMAL = 0x0200 # save with normal quality (50:1)
  185. JPEG_QUALITYAVERAGE = 0x0400 # save with average quality (25:1)
  186. JPEG_QUALITYBAD = 0x0800 # save with bad quality (10:1)
  187. JPEG_PROGRESSIVE = 0x2000 # save as a progressive-JPEG
  188. # # (use | to combine with other save flags)
  189. JPEG_SUBSAMPLING_411 = 0x1000 # save with high 4x1 chroma
  190. # # subsampling (4:1:1)
  191. JPEG_SUBSAMPLING_420 = 0x4000 # save with medium 2x2 medium chroma
  192. # # subsampling (4:2:0) - default value
  193. JPEG_SUBSAMPLING_422 = 0x8000 # save /w low 2x1 chroma subsampling (4:2:2)
  194. JPEG_SUBSAMPLING_444 = 0x10000 # save with no chroma subsampling (4:4:4)
  195. JPEG_OPTIMIZE = 0x20000 # on saving, compute optimal Huffman coding tables
  196. # # (can reduce a few percent of file size)
  197. JPEG_BASELINE = 0x40000 # save basic JPEG, without metadata or any markers
  198. KOALA_DEFAULT = 0
  199. LBM_DEFAULT = 0
  200. MNG_DEFAULT = 0
  201. PCD_DEFAULT = 0
  202. PCD_BASE = 1 # load the bitmap sized 768 x 512
  203. PCD_BASEDIV4 = 2 # load the bitmap sized 384 x 256
  204. PCD_BASEDIV16 = 3 # load the bitmap sized 192 x 128
  205. PCX_DEFAULT = 0
  206. PFM_DEFAULT = 0
  207. PICT_DEFAULT = 0
  208. PNG_DEFAULT = 0
  209. PNG_IGNOREGAMMA = 1 # loading: avoid gamma correction
  210. PNG_Z_BEST_SPEED = 0x0001 # save using ZLib level 1 compression flag
  211. # # (default value is 6)
  212. PNG_Z_DEFAULT_COMPRESSION = 0x0006 # save using ZLib level 6 compression
  213. # # flag (default recommended value)
  214. PNG_Z_BEST_COMPRESSION = 0x0009 # save using ZLib level 9 compression flag
  215. # # (default value is 6)
  216. PNG_Z_NO_COMPRESSION = 0x0100 # save without ZLib compression
  217. PNG_INTERLACED = 0x0200 # save using Adam7 interlacing (use | to combine
  218. # # with other save flags)
  219. PNM_DEFAULT = 0
  220. PNM_SAVE_RAW = 0 # Writer saves in RAW format (i.e. P4, P5 or P6)
  221. PNM_SAVE_ASCII = 1 # Writer saves in ASCII format (i.e. P1, P2 or P3)
  222. PSD_DEFAULT = 0
  223. PSD_CMYK = 1 # reads tags for separated CMYK (default is conversion to RGB)
  224. PSD_LAB = 2 # reads tags for CIELab (default is conversion to RGB)
  225. RAS_DEFAULT = 0
  226. RAW_DEFAULT = 0 # load the file as linear RGB 48-bit
  227. RAW_PREVIEW = 1 # try to load the embedded JPEG preview with included
  228. # # Exif Data or default to RGB 24-bit
  229. RAW_DISPLAY = 2 # load the file as RGB 24-bit
  230. SGI_DEFAULT = 0
  231. TARGA_DEFAULT = 0
  232. TARGA_LOAD_RGB888 = 1 # Convert RGB555 and ARGB8888 -> RGB888.
  233. TARGA_SAVE_RLE = 2 # Save with RLE compression
  234. TIFF_DEFAULT = 0
  235. TIFF_CMYK = 0x0001 # reads/stores tags for separated CMYK
  236. # # (use | to combine with compression flags)
  237. TIFF_PACKBITS = 0x0100 # save using PACKBITS compression
  238. TIFF_DEFLATE = 0x0200 # save using DEFLATE (a.k.a. ZLIB) compression
  239. TIFF_ADOBE_DEFLATE = 0x0400 # save using ADOBE DEFLATE compression
  240. TIFF_NONE = 0x0800 # save without any compression
  241. TIFF_CCITTFAX3 = 0x1000 # save using CCITT Group 3 fax encoding
  242. TIFF_CCITTFAX4 = 0x2000 # save using CCITT Group 4 fax encoding
  243. TIFF_LZW = 0x4000 # save using LZW compression
  244. TIFF_JPEG = 0x8000 # save using JPEG compression
  245. TIFF_LOGLUV = 0x10000 # save using LogLuv compression
  246. WBMP_DEFAULT = 0
  247. XBM_DEFAULT = 0
  248. XPM_DEFAULT = 0
  249. class METADATA_MODELS(object):
  250. FIMD_COMMENTS = 0
  251. FIMD_EXIF_MAIN = 1
  252. FIMD_EXIF_EXIF = 2
  253. FIMD_EXIF_GPS = 3
  254. FIMD_EXIF_MAKERNOTE = 4
  255. FIMD_EXIF_INTEROP = 5
  256. FIMD_IPTC = 6
  257. FIMD_XMP = 7
  258. FIMD_GEOTIFF = 8
  259. FIMD_ANIMATION = 9
  260. class METADATA_DATATYPE(object):
  261. FIDT_BYTE = 1 # 8-bit unsigned integer
  262. FIDT_ASCII = 2 # 8-bit bytes w/ last byte null
  263. FIDT_SHORT = 3 # 16-bit unsigned integer
  264. FIDT_LONG = 4 # 32-bit unsigned integer
  265. FIDT_RATIONAL = 5 # 64-bit unsigned fraction
  266. FIDT_SBYTE = 6 # 8-bit signed integer
  267. FIDT_UNDEFINED = 7 # 8-bit untyped data
  268. FIDT_SSHORT = 8 # 16-bit signed integer
  269. FIDT_SLONG = 9 # 32-bit signed integer
  270. FIDT_SRATIONAL = 10 # 64-bit signed fraction
  271. FIDT_FLOAT = 11 # 32-bit IEEE floating point
  272. FIDT_DOUBLE = 12 # 64-bit IEEE floating point
  273. FIDT_IFD = 13 # 32-bit unsigned integer (offset)
  274. FIDT_PALETTE = 14 # 32-bit RGBQUAD
  275. FIDT_LONG8 = 16 # 64-bit unsigned integer
  276. FIDT_SLONG8 = 17 # 64-bit signed integer
  277. FIDT_IFD8 = 18 # 64-bit unsigned integer (offset)
  278. dtypes = {
  279. FIDT_BYTE: numpy.uint8,
  280. FIDT_SHORT: numpy.uint16,
  281. FIDT_LONG: numpy.uint32,
  282. FIDT_RATIONAL: [("numerator", numpy.uint32), ("denominator", numpy.uint32)],
  283. FIDT_LONG8: numpy.uint64,
  284. FIDT_SLONG8: numpy.int64,
  285. FIDT_IFD8: numpy.uint64,
  286. FIDT_SBYTE: numpy.int8,
  287. FIDT_UNDEFINED: numpy.uint8,
  288. FIDT_SSHORT: numpy.int16,
  289. FIDT_SLONG: numpy.int32,
  290. FIDT_SRATIONAL: [("numerator", numpy.int32), ("denominator", numpy.int32)],
  291. FIDT_FLOAT: numpy.float32,
  292. FIDT_DOUBLE: numpy.float64,
  293. FIDT_IFD: numpy.uint32,
  294. FIDT_PALETTE: [
  295. ("R", numpy.uint8),
  296. ("G", numpy.uint8),
  297. ("B", numpy.uint8),
  298. ("A", numpy.uint8),
  299. ],
  300. }
  301. class Freeimage(object):
  302. """Class to represent an interface to the FreeImage library.
  303. This class is relatively thin. It provides a Pythonic API that converts
  304. Freeimage objects to Python objects, but that's about it.
  305. The actual implementation should be provided by the plugins.
  306. The recommended way to call into the Freeimage library (so that
  307. errors and warnings show up in the right moment) is to use this
  308. object as a context manager:
  309. with imageio.fi as lib:
  310. lib.FreeImage_GetPalette()
  311. """
  312. _API = {
  313. # All we're doing here is telling ctypes that some of the
  314. # FreeImage functions return pointers instead of integers. (On
  315. # 64-bit systems, without this information the pointers get
  316. # truncated and crashes result). There's no need to list
  317. # functions that return ints, or the types of the parameters
  318. # to these or other functions -- that's fine to do implicitly.
  319. # Note that the ctypes immediately converts the returned void_p
  320. # back to a python int again! This is really not helpful,
  321. # because then passing it back to another library call will
  322. # cause truncation-to-32-bits on 64-bit systems. Thanks, ctypes!
  323. # So after these calls one must immediately re-wrap the int as
  324. # a c_void_p if it is to be passed back into FreeImage.
  325. "FreeImage_AllocateT": (ctypes.c_void_p, None),
  326. "FreeImage_FindFirstMetadata": (ctypes.c_void_p, None),
  327. "FreeImage_GetBits": (ctypes.c_void_p, None),
  328. "FreeImage_GetPalette": (ctypes.c_void_p, None),
  329. "FreeImage_GetTagKey": (ctypes.c_char_p, None),
  330. "FreeImage_GetTagValue": (ctypes.c_void_p, None),
  331. "FreeImage_CreateTag": (ctypes.c_void_p, None),
  332. "FreeImage_Save": (ctypes.c_void_p, None),
  333. "FreeImage_Load": (ctypes.c_void_p, None),
  334. "FreeImage_LoadFromMemory": (ctypes.c_void_p, None),
  335. "FreeImage_OpenMultiBitmap": (ctypes.c_void_p, None),
  336. "FreeImage_LoadMultiBitmapFromMemory": (ctypes.c_void_p, None),
  337. "FreeImage_LockPage": (ctypes.c_void_p, None),
  338. "FreeImage_OpenMemory": (ctypes.c_void_p, None),
  339. # 'FreeImage_ReadMemory': (ctypes.c_void_p, None),
  340. # 'FreeImage_CloseMemory': (ctypes.c_void_p, None),
  341. "FreeImage_GetVersion": (ctypes.c_char_p, None),
  342. "FreeImage_GetFIFExtensionList": (ctypes.c_char_p, None),
  343. "FreeImage_GetFormatFromFIF": (ctypes.c_char_p, None),
  344. "FreeImage_GetFIFDescription": (ctypes.c_char_p, None),
  345. "FreeImage_ColorQuantizeEx": (ctypes.c_void_p, None),
  346. # Pypy wants some extra definitions, so here we go ...
  347. "FreeImage_IsLittleEndian": (ctypes.c_int, None),
  348. "FreeImage_SetOutputMessage": (ctypes.c_void_p, None),
  349. "FreeImage_GetFIFCount": (ctypes.c_int, None),
  350. "FreeImage_IsPluginEnabled": (ctypes.c_int, None),
  351. "FreeImage_GetFileType": (ctypes.c_int, None),
  352. #
  353. "FreeImage_GetTagType": (ctypes.c_int, None),
  354. "FreeImage_GetTagLength": (ctypes.c_int, None),
  355. "FreeImage_FindNextMetadata": (ctypes.c_int, None),
  356. "FreeImage_FindCloseMetadata": (ctypes.c_void_p, None),
  357. #
  358. "FreeImage_GetFIFFromFilename": (ctypes.c_int, None),
  359. "FreeImage_FIFSupportsReading": (ctypes.c_int, None),
  360. "FreeImage_FIFSupportsWriting": (ctypes.c_int, None),
  361. "FreeImage_FIFSupportsExportType": (ctypes.c_int, None),
  362. "FreeImage_FIFSupportsExportBPP": (ctypes.c_int, None),
  363. "FreeImage_GetHeight": (ctypes.c_int, None),
  364. "FreeImage_GetWidth": (ctypes.c_int, None),
  365. "FreeImage_GetImageType": (ctypes.c_int, None),
  366. "FreeImage_GetBPP": (ctypes.c_int, None),
  367. "FreeImage_GetColorsUsed": (ctypes.c_int, None),
  368. "FreeImage_ConvertTo32Bits": (ctypes.c_void_p, None),
  369. "FreeImage_GetPitch": (ctypes.c_int, None),
  370. "FreeImage_Unload": (ctypes.c_void_p, None),
  371. }
  372. def __init__(self):
  373. # Initialize freeimage lib as None
  374. self._lib = None
  375. # A lock to create thread-safety
  376. self._lock = threading.RLock()
  377. # Init log messages lists
  378. self._messages = []
  379. # Select functype for error handler
  380. if sys.platform.startswith("win"):
  381. functype = ctypes.WINFUNCTYPE
  382. else:
  383. functype = ctypes.CFUNCTYPE
  384. # Create output message handler
  385. @functype(None, ctypes.c_int, ctypes.c_char_p)
  386. def error_handler(fif, message):
  387. message = message.decode("utf-8")
  388. self._messages.append(message)
  389. while (len(self._messages)) > 256:
  390. self._messages.pop(0)
  391. # Make sure to keep a ref to function
  392. self._error_handler = error_handler
  393. @property
  394. def lib(self):
  395. if self._lib is None:
  396. try:
  397. self.load_freeimage()
  398. except OSError as err:
  399. self._lib = "The freeimage library could not be loaded: "
  400. self._lib += str(err)
  401. if isinstance(self._lib, str):
  402. raise RuntimeError(self._lib)
  403. return self._lib
  404. def has_lib(self):
  405. try:
  406. self.lib
  407. except Exception:
  408. return False
  409. return True
  410. def load_freeimage(self):
  411. """Try to load the freeimage lib from the system. If not successful,
  412. try to download the imageio version and try again.
  413. """
  414. # Load library and register API
  415. success = False
  416. try:
  417. # Try without forcing a download, but giving preference
  418. # to the imageio-provided lib (if previously downloaded)
  419. self._load_freeimage()
  420. self._register_api()
  421. if self.lib.FreeImage_GetVersion().decode("utf-8") >= "3.15":
  422. success = True
  423. except OSError:
  424. pass
  425. if not success:
  426. # Ensure we have our own lib, try again
  427. get_freeimage_lib()
  428. self._load_freeimage()
  429. self._register_api()
  430. # Wrap up
  431. self.lib.FreeImage_SetOutputMessage(self._error_handler)
  432. self.lib_version = self.lib.FreeImage_GetVersion().decode("utf-8")
  433. def _load_freeimage(self):
  434. # Define names
  435. lib_names = ["freeimage", "libfreeimage"]
  436. exact_lib_names = [
  437. "FreeImage",
  438. "libfreeimage.dylib",
  439. "libfreeimage.so",
  440. "libfreeimage.so.3",
  441. ]
  442. # Add names of libraries that we provide (that file may not exist)
  443. res_dirs = resource_dirs()
  444. plat = get_platform()
  445. if plat: # Can be None on e.g. FreeBSD
  446. fname = FNAME_PER_PLATFORM[plat]
  447. for dir in res_dirs:
  448. exact_lib_names.insert(0, os.path.join(dir, "freeimage", fname))
  449. # Add the path specified with IMAGEIO_FREEIMAGE_LIB:
  450. lib = os.getenv("IMAGEIO_FREEIMAGE_LIB", None)
  451. if lib is not None:
  452. exact_lib_names.insert(0, lib)
  453. # Load
  454. try:
  455. lib, fname = load_lib(exact_lib_names, lib_names, res_dirs)
  456. except OSError as err: # pragma: no cover
  457. err_msg = str(err) + "\nPlease install the FreeImage library."
  458. raise OSError(err_msg)
  459. # Store
  460. self._lib = lib
  461. self.lib_fname = fname
  462. def _register_api(self):
  463. # Albert's ctypes pattern
  464. for f, (restype, argtypes) in self._API.items():
  465. func = getattr(self.lib, f)
  466. func.restype = restype
  467. func.argtypes = argtypes
  468. # Handling of output messages
  469. def __enter__(self):
  470. self._lock.acquire()
  471. return self.lib
  472. def __exit__(self, *args):
  473. self._show_any_warnings()
  474. self._lock.release()
  475. def _reset_log(self):
  476. """Reset the list of output messages. Call this before
  477. loading or saving an image with the FreeImage API.
  478. """
  479. self._messages = []
  480. def _get_error_message(self):
  481. """Get the output messages produced since the last reset as
  482. one string. Returns 'No known reason.' if there are no messages.
  483. Also resets the log.
  484. """
  485. if self._messages:
  486. res = " ".join(self._messages)
  487. self._reset_log()
  488. return res
  489. else:
  490. return "No known reason."
  491. def _show_any_warnings(self):
  492. """If there were any messages since the last reset, show them
  493. as a warning. Otherwise do nothing. Also resets the messages.
  494. """
  495. if self._messages:
  496. logger.warning("imageio.freeimage warning: " + self._get_error_message())
  497. self._reset_log()
  498. def get_output_log(self):
  499. """Return a list of the last 256 output messages
  500. (warnings and errors) produced by the FreeImage library.
  501. """
  502. # This message log is not cleared/reset, but kept to 256 elements.
  503. return [m for m in self._messages]
  504. def getFIF(self, filename, mode, bb=None):
  505. """Get the freeimage Format (FIF) from a given filename.
  506. If mode is 'r', will try to determine the format by reading
  507. the file, otherwise only the filename is used.
  508. This function also tests whether the format supports reading/writing.
  509. """
  510. with self as lib:
  511. # Init
  512. ftype = -1
  513. if mode not in "rw":
  514. raise ValueError('Invalid mode (must be "r" or "w").')
  515. # Try getting format from the content. Note that some files
  516. # do not have a header that allows reading the format from
  517. # the file.
  518. if mode == "r":
  519. if bb is not None:
  520. fimemory = lib.FreeImage_OpenMemory(ctypes.c_char_p(bb), len(bb))
  521. ftype = lib.FreeImage_GetFileTypeFromMemory(
  522. ctypes.c_void_p(fimemory), len(bb)
  523. )
  524. lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
  525. if (ftype == -1) and os.path.isfile(filename):
  526. ftype = lib.FreeImage_GetFileType(efn(filename), 0)
  527. # Try getting the format from the extension
  528. if ftype == -1:
  529. ftype = lib.FreeImage_GetFIFFromFilename(efn(filename))
  530. # Test if ok
  531. if ftype == -1:
  532. raise ValueError('Cannot determine format of file "%s"' % filename)
  533. elif mode == "w" and not lib.FreeImage_FIFSupportsWriting(ftype):
  534. raise ValueError('Cannot write the format of file "%s"' % filename)
  535. elif mode == "r" and not lib.FreeImage_FIFSupportsReading(ftype):
  536. raise ValueError('Cannot read the format of file "%s"' % filename)
  537. return ftype
  538. def create_bitmap(self, filename, ftype, flags=0):
  539. """create_bitmap(filename, ftype, flags=0)
  540. Create a wrapped bitmap object.
  541. """
  542. return FIBitmap(self, filename, ftype, flags)
  543. def create_multipage_bitmap(self, filename, ftype, flags=0):
  544. """create_multipage_bitmap(filename, ftype, flags=0)
  545. Create a wrapped multipage bitmap object.
  546. """
  547. return FIMultipageBitmap(self, filename, ftype, flags)
  548. class FIBaseBitmap(object):
  549. def __init__(self, fi, filename, ftype, flags):
  550. self._fi = fi
  551. self._filename = filename
  552. self._ftype = ftype
  553. self._flags = flags
  554. self._bitmap = None
  555. self._close_funcs = []
  556. def __del__(self):
  557. self.close()
  558. def close(self):
  559. if (self._bitmap is not None) and self._close_funcs:
  560. for close_func in self._close_funcs:
  561. try:
  562. with self._fi:
  563. fun = close_func[0]
  564. fun(*close_func[1:])
  565. except Exception: # pragma: no cover
  566. pass
  567. self._close_funcs = []
  568. self._bitmap = None
  569. def _set_bitmap(self, bitmap, close_func=None):
  570. """Function to set the bitmap and specify the function to unload it."""
  571. if self._bitmap is not None:
  572. pass # bitmap is converted
  573. if close_func is None:
  574. close_func = self._fi.lib.FreeImage_Unload, bitmap
  575. self._bitmap = bitmap
  576. if close_func:
  577. self._close_funcs.append(close_func)
  578. def get_meta_data(self):
  579. # todo: there is also FreeImage_TagToString, is that useful?
  580. # and would that work well when reading and then saving?
  581. # Create a list of (model_name, number) tuples
  582. models = [
  583. (name[5:], number)
  584. for name, number in METADATA_MODELS.__dict__.items()
  585. if name.startswith("FIMD_")
  586. ]
  587. # Prepare
  588. metadata = Dict()
  589. tag = ctypes.c_void_p()
  590. with self._fi as lib:
  591. # Iterate over all FreeImage meta models
  592. for model_name, number in models:
  593. # Find beginning, get search handle
  594. mdhandle = lib.FreeImage_FindFirstMetadata(
  595. number, self._bitmap, ctypes.byref(tag)
  596. )
  597. mdhandle = ctypes.c_void_p(mdhandle)
  598. if mdhandle:
  599. # Iterate over all tags in this model
  600. more = True
  601. while more:
  602. # Get info about tag
  603. tag_name = lib.FreeImage_GetTagKey(tag).decode("utf-8")
  604. tag_type = lib.FreeImage_GetTagType(tag)
  605. byte_size = lib.FreeImage_GetTagLength(tag)
  606. char_ptr = ctypes.c_char * byte_size
  607. data = char_ptr.from_address(lib.FreeImage_GetTagValue(tag))
  608. # Convert in a way compatible with Pypy
  609. tag_bytes = bytes(bytearray(data))
  610. # The default value is the raw bytes
  611. tag_val = tag_bytes
  612. # Convert to a Python value in the metadata dict
  613. if tag_type == METADATA_DATATYPE.FIDT_ASCII:
  614. tag_val = tag_bytes.decode("utf-8", "replace")
  615. elif tag_type in METADATA_DATATYPE.dtypes:
  616. dtype = METADATA_DATATYPE.dtypes[tag_type]
  617. if IS_PYPY and isinstance(dtype, (list, tuple)):
  618. pass # pragma: no cover - or we get a segfault
  619. else:
  620. try:
  621. tag_val = numpy.frombuffer(
  622. tag_bytes, dtype=dtype
  623. ).copy()
  624. if len(tag_val) == 1:
  625. tag_val = tag_val[0]
  626. except Exception: # pragma: no cover
  627. pass
  628. # Store data in dict
  629. subdict = metadata.setdefault(model_name, Dict())
  630. subdict[tag_name] = tag_val
  631. # Next
  632. more = lib.FreeImage_FindNextMetadata(
  633. mdhandle, ctypes.byref(tag)
  634. )
  635. # Close search handle for current meta model
  636. lib.FreeImage_FindCloseMetadata(mdhandle)
  637. # Done
  638. return metadata
  639. def set_meta_data(self, metadata):
  640. # Create a dict mapping model_name to number
  641. models = {}
  642. for name, number in METADATA_MODELS.__dict__.items():
  643. if name.startswith("FIMD_"):
  644. models[name[5:]] = number
  645. # Create a mapping from numpy.dtype to METADATA_DATATYPE
  646. def get_tag_type_number(dtype):
  647. for number, numpy_dtype in METADATA_DATATYPE.dtypes.items():
  648. if dtype == numpy_dtype:
  649. return number
  650. else:
  651. return None
  652. with self._fi as lib:
  653. for model_name, subdict in metadata.items():
  654. # Get model number
  655. number = models.get(model_name, None)
  656. if number is None:
  657. continue # Unknown model, silent ignore
  658. for tag_name, tag_val in subdict.items():
  659. # Create new tag
  660. tag = lib.FreeImage_CreateTag()
  661. tag = ctypes.c_void_p(tag)
  662. try:
  663. # Convert Python value to FI type, val
  664. is_ascii = False
  665. if isinstance(tag_val, str):
  666. try:
  667. tag_bytes = tag_val.encode("ascii")
  668. is_ascii = True
  669. except UnicodeError:
  670. pass
  671. if is_ascii:
  672. tag_type = METADATA_DATATYPE.FIDT_ASCII
  673. tag_count = len(tag_bytes)
  674. else:
  675. if not hasattr(tag_val, "dtype"):
  676. tag_val = numpy.array([tag_val])
  677. tag_type = get_tag_type_number(tag_val.dtype)
  678. if tag_type is None:
  679. logger.warning(
  680. "imageio.freeimage warning: Could not "
  681. "determine tag type of %r." % tag_name
  682. )
  683. continue
  684. tag_bytes = tag_val.tobytes()
  685. tag_count = tag_val.size
  686. # Set properties
  687. lib.FreeImage_SetTagKey(tag, tag_name.encode("utf-8"))
  688. lib.FreeImage_SetTagType(tag, tag_type)
  689. lib.FreeImage_SetTagCount(tag, tag_count)
  690. lib.FreeImage_SetTagLength(tag, len(tag_bytes))
  691. lib.FreeImage_SetTagValue(tag, tag_bytes)
  692. # Store tag
  693. tag_key = lib.FreeImage_GetTagKey(tag)
  694. lib.FreeImage_SetMetadata(number, self._bitmap, tag_key, tag)
  695. except Exception as err: # pragma: no cover
  696. logger.warning(
  697. "imagio.freeimage warning: Could not set tag "
  698. "%r: %s, %s"
  699. % (tag_name, self._fi._get_error_message(), str(err))
  700. )
  701. finally:
  702. lib.FreeImage_DeleteTag(tag)
  703. class FIBitmap(FIBaseBitmap):
  704. """Wrapper for the FI bitmap object."""
  705. def allocate(self, array):
  706. # Prepare array
  707. assert isinstance(array, numpy.ndarray)
  708. shape = array.shape
  709. dtype = array.dtype
  710. # Get shape and channel info
  711. r, c = shape[:2]
  712. if len(shape) == 2:
  713. n_channels = 1
  714. elif len(shape) == 3:
  715. n_channels = shape[2]
  716. else:
  717. n_channels = shape[0]
  718. # Get fi_type
  719. try:
  720. fi_type = FI_TYPES.fi_types[(dtype.type, n_channels)]
  721. self._fi_type = fi_type
  722. except KeyError:
  723. raise ValueError("Cannot write arrays of given type and shape.")
  724. # Allocate bitmap
  725. with self._fi as lib:
  726. bpp = 8 * dtype.itemsize * n_channels
  727. bitmap = lib.FreeImage_AllocateT(fi_type, c, r, bpp, 0, 0, 0)
  728. bitmap = ctypes.c_void_p(bitmap)
  729. # Check and store
  730. if not bitmap: # pragma: no cover
  731. raise RuntimeError(
  732. "Could not allocate bitmap for storage: %s"
  733. % self._fi._get_error_message()
  734. )
  735. self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
  736. def load_from_filename(self, filename=None):
  737. if filename is None:
  738. filename = self._filename
  739. with self._fi as lib:
  740. # Create bitmap
  741. bitmap = lib.FreeImage_Load(self._ftype, efn(filename), self._flags)
  742. bitmap = ctypes.c_void_p(bitmap)
  743. # Check and store
  744. if not bitmap: # pragma: no cover
  745. raise ValueError(
  746. 'Could not load bitmap "%s": %s'
  747. % (self._filename, self._fi._get_error_message())
  748. )
  749. self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
  750. # def load_from_bytes(self, bb):
  751. # with self._fi as lib:
  752. # # Create bitmap
  753. # fimemory = lib.FreeImage_OpenMemory(
  754. # ctypes.c_char_p(bb), len(bb))
  755. # bitmap = lib.FreeImage_LoadFromMemory(
  756. # self._ftype, ctypes.c_void_p(fimemory), self._flags)
  757. # bitmap = ctypes.c_void_p(bitmap)
  758. # lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
  759. #
  760. # # Check
  761. # if not bitmap:
  762. # raise ValueError('Could not load bitmap "%s": %s'
  763. # % (self._filename, self._fi._get_error_message()))
  764. # else:
  765. # self._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
  766. def save_to_filename(self, filename=None):
  767. if filename is None:
  768. filename = self._filename
  769. ftype = self._ftype
  770. bitmap = self._bitmap
  771. fi_type = self._fi_type # element type
  772. with self._fi as lib:
  773. # Check if can write
  774. if fi_type == FI_TYPES.FIT_BITMAP:
  775. can_write = lib.FreeImage_FIFSupportsExportBPP(
  776. ftype, lib.FreeImage_GetBPP(bitmap)
  777. )
  778. else:
  779. can_write = lib.FreeImage_FIFSupportsExportType(ftype, fi_type)
  780. if not can_write:
  781. raise TypeError("Cannot save image of this format to this file type")
  782. # Save to file
  783. res = lib.FreeImage_Save(ftype, bitmap, efn(filename), self._flags)
  784. # Check
  785. if res is None: # pragma: no cover, we do so many checks, this is rare
  786. raise RuntimeError(
  787. f"Could not save file `{self._filename}`: {self._fi._get_error_message()}"
  788. )
  789. # def save_to_bytes(self):
  790. # ftype = self._ftype
  791. # bitmap = self._bitmap
  792. # fi_type = self._fi_type # element type
  793. #
  794. # with self._fi as lib:
  795. # # Check if can write
  796. # if fi_type == FI_TYPES.FIT_BITMAP:
  797. # can_write = lib.FreeImage_FIFSupportsExportBPP(ftype,
  798. # lib.FreeImage_GetBPP(bitmap))
  799. # else:
  800. # can_write = lib.FreeImage_FIFSupportsExportType(ftype, fi_type)
  801. # if not can_write:
  802. # raise TypeError('Cannot save image of this format '
  803. # 'to this file type')
  804. #
  805. # # Extract the bytes
  806. # fimemory = lib.FreeImage_OpenMemory(0, 0)
  807. # res = lib.FreeImage_SaveToMemory(ftype, bitmap,
  808. # ctypes.c_void_p(fimemory),
  809. # self._flags)
  810. # if res:
  811. # N = lib.FreeImage_TellMemory(ctypes.c_void_p(fimemory))
  812. # result = ctypes.create_string_buffer(N)
  813. # lib.FreeImage_SeekMemory(ctypes.c_void_p(fimemory), 0)
  814. # lib.FreeImage_ReadMemory(result, 1, N, ctypes.c_void_p(fimemory))
  815. # result = result.raw
  816. # lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
  817. #
  818. # # Check
  819. # if not res:
  820. # raise RuntimeError('Could not save file "%s": %s'
  821. # % (self._filename, self._fi._get_error_message()))
  822. #
  823. # # Done
  824. # return result
  825. def get_image_data(self):
  826. dtype, shape, bpp = self._get_type_and_shape()
  827. array = self._wrap_bitmap_bits_in_array(shape, dtype, False)
  828. with self._fi as lib:
  829. isle = lib.FreeImage_IsLittleEndian()
  830. # swizzle the color components and flip the scanlines to go from
  831. # FreeImage's BGR[A] and upside-down internal memory format to
  832. # something more normal
  833. def n(arr):
  834. # return arr[..., ::-1].T # Does not work on numpypy yet
  835. if arr.ndim == 1: # pragma: no cover
  836. return arr[::-1].T
  837. elif arr.ndim == 2: # Always the case here ...
  838. return arr[:, ::-1].T
  839. elif arr.ndim == 3: # pragma: no cover
  840. return arr[:, :, ::-1].T
  841. elif arr.ndim == 4: # pragma: no cover
  842. return arr[:, :, :, ::-1].T
  843. if len(shape) == 3 and isle and dtype.type == numpy.uint8:
  844. b = n(array[0])
  845. g = n(array[1])
  846. r = n(array[2])
  847. if shape[0] == 3:
  848. return numpy.dstack((r, g, b))
  849. elif shape[0] == 4:
  850. a = n(array[3])
  851. return numpy.dstack((r, g, b, a))
  852. else: # pragma: no cover - we check this earlier
  853. raise ValueError("Cannot handle images of shape %s" % shape)
  854. # We need to copy because array does *not* own its memory
  855. # after bitmap is freed.
  856. a = n(array).copy()
  857. return a
  858. def set_image_data(self, array):
  859. # Prepare array
  860. assert isinstance(array, numpy.ndarray)
  861. shape = array.shape
  862. dtype = array.dtype
  863. with self._fi as lib:
  864. isle = lib.FreeImage_IsLittleEndian()
  865. # Calculate shape and channels
  866. r, c = shape[:2]
  867. if len(shape) == 2:
  868. n_channels = 1
  869. w_shape = (c, r)
  870. elif len(shape) == 3:
  871. n_channels = shape[2]
  872. w_shape = (n_channels, c, r)
  873. else:
  874. n_channels = shape[0]
  875. def n(arr): # normalise to freeimage's in-memory format
  876. return arr[::-1].T
  877. wrapped_array = self._wrap_bitmap_bits_in_array(w_shape, dtype, True)
  878. # swizzle the color components and flip the scanlines to go to
  879. # FreeImage's BGR[A] and upside-down internal memory format
  880. # The BGR[A] order is only used for 8bits per channel images
  881. # on little endian machines. For everything else RGB[A] is
  882. # used.
  883. if len(shape) == 3 and isle and dtype.type == numpy.uint8:
  884. R = array[:, :, 0]
  885. G = array[:, :, 1]
  886. B = array[:, :, 2]
  887. wrapped_array[0] = n(B)
  888. wrapped_array[1] = n(G)
  889. wrapped_array[2] = n(R)
  890. if shape[2] == 4:
  891. A = array[:, :, 3]
  892. wrapped_array[3] = n(A)
  893. else:
  894. wrapped_array[:] = n(array)
  895. if self._need_finish:
  896. self._finish_wrapped_array(wrapped_array)
  897. if len(shape) == 2 and dtype.type == numpy.uint8:
  898. with self._fi as lib:
  899. palette = lib.FreeImage_GetPalette(self._bitmap)
  900. palette = ctypes.c_void_p(palette)
  901. if not palette:
  902. raise RuntimeError("Could not get image palette")
  903. try:
  904. palette_data = GREY_PALETTE.ctypes.data
  905. except Exception: # pragma: no cover - IS_PYPY
  906. palette_data = GREY_PALETTE.__array_interface__["data"][0]
  907. ctypes.memmove(palette, palette_data, 1024)
  908. def _wrap_bitmap_bits_in_array(self, shape, dtype, save):
  909. """Return an ndarray view on the data in a FreeImage bitmap. Only
  910. valid for as long as the bitmap is loaded (if single page) / locked
  911. in memory (if multipage). This is used in loading data, but
  912. also during saving, to prepare a strided numpy array buffer.
  913. """
  914. # Get bitmap info
  915. with self._fi as lib:
  916. pitch = lib.FreeImage_GetPitch(self._bitmap)
  917. bits = lib.FreeImage_GetBits(self._bitmap)
  918. # Get more info
  919. height = shape[-1]
  920. byte_size = height * pitch
  921. itemsize = dtype.itemsize
  922. # Get strides
  923. if len(shape) == 3:
  924. strides = (itemsize, shape[0] * itemsize, pitch)
  925. else:
  926. strides = (itemsize, pitch)
  927. # Create numpy array and return
  928. data = (ctypes.c_char * byte_size).from_address(bits)
  929. try:
  930. self._need_finish = False
  931. if TEST_NUMPY_NO_STRIDES:
  932. raise NotImplementedError()
  933. return numpy.ndarray(shape, dtype=dtype, buffer=data, strides=strides)
  934. except NotImplementedError:
  935. # IS_PYPY - not very efficient. We create a C-contiguous
  936. # numpy array (because pypy does not support Fortran-order)
  937. # and shape it such that the rest of the code can remain.
  938. if save:
  939. self._need_finish = True # Flag to use _finish_wrapped_array
  940. return numpy.zeros(shape, dtype=dtype)
  941. else:
  942. bb = bytes(bytearray(data))
  943. array = numpy.frombuffer(bb, dtype=dtype).copy()
  944. # Deal with strides
  945. if len(shape) == 3:
  946. array.shape = shape[2], strides[-1] // shape[0], shape[0]
  947. array2 = array[: shape[2], : shape[1], : shape[0]]
  948. array = numpy.zeros(shape, dtype=array.dtype)
  949. for i in range(shape[0]):
  950. array[i] = array2[:, :, i].T
  951. else:
  952. array.shape = shape[1], strides[-1]
  953. array = array[: shape[1], : shape[0]].T
  954. return array
  955. def _finish_wrapped_array(self, array): # IS_PYPY
  956. """Hardcore way to inject numpy array in bitmap."""
  957. # Get bitmap info
  958. with self._fi as lib:
  959. pitch = lib.FreeImage_GetPitch(self._bitmap)
  960. bits = lib.FreeImage_GetBits(self._bitmap)
  961. bpp = lib.FreeImage_GetBPP(self._bitmap)
  962. # Get channels and realwidth
  963. nchannels = bpp // 8 // array.itemsize
  964. realwidth = pitch // nchannels
  965. # Apply padding for pitch if necessary
  966. extra = realwidth - array.shape[-2]
  967. assert 0 <= extra < 10
  968. # Make sort of Fortran, also take padding (i.e. pitch) into account
  969. newshape = array.shape[-1], realwidth, nchannels
  970. array2 = numpy.zeros(newshape, array.dtype)
  971. if nchannels == 1:
  972. array2[:, : array.shape[-2], 0] = array.T
  973. else:
  974. for i in range(nchannels):
  975. array2[:, : array.shape[-2], i] = array[i, :, :].T
  976. # copy data
  977. data_ptr = array2.__array_interface__["data"][0]
  978. ctypes.memmove(bits, data_ptr, array2.nbytes)
  979. del array2
  980. def _get_type_and_shape(self):
  981. bitmap = self._bitmap
  982. # Get info on bitmap
  983. with self._fi as lib:
  984. w = lib.FreeImage_GetWidth(bitmap)
  985. h = lib.FreeImage_GetHeight(bitmap)
  986. self._fi_type = fi_type = lib.FreeImage_GetImageType(bitmap)
  987. if not fi_type:
  988. raise ValueError("Unknown image pixel type")
  989. # Determine required props for numpy array
  990. bpp = None
  991. dtype = FI_TYPES.dtypes[fi_type]
  992. if fi_type == FI_TYPES.FIT_BITMAP:
  993. with self._fi as lib:
  994. bpp = lib.FreeImage_GetBPP(bitmap)
  995. has_pallette = lib.FreeImage_GetColorsUsed(bitmap)
  996. if has_pallette:
  997. # Examine the palette. If it is grayscale, we return as such
  998. if has_pallette == 256:
  999. palette = lib.FreeImage_GetPalette(bitmap)
  1000. palette = ctypes.c_void_p(palette)
  1001. p = (ctypes.c_uint8 * (256 * 4)).from_address(palette.value)
  1002. p = numpy.frombuffer(p, numpy.uint32).copy()
  1003. if (GREY_PALETTE == p).all():
  1004. extra_dims = []
  1005. return numpy.dtype(dtype), extra_dims + [w, h], bpp
  1006. # Convert bitmap and call this method again
  1007. newbitmap = lib.FreeImage_ConvertTo32Bits(bitmap)
  1008. newbitmap = ctypes.c_void_p(newbitmap)
  1009. self._set_bitmap(newbitmap)
  1010. return self._get_type_and_shape()
  1011. elif bpp == 8:
  1012. extra_dims = []
  1013. elif bpp == 24:
  1014. extra_dims = [3]
  1015. elif bpp == 32:
  1016. extra_dims = [4]
  1017. else: # pragma: no cover
  1018. # raise ValueError('Cannot convert %d BPP bitmap' % bpp)
  1019. # Convert bitmap and call this method again
  1020. newbitmap = lib.FreeImage_ConvertTo32Bits(bitmap)
  1021. newbitmap = ctypes.c_void_p(newbitmap)
  1022. self._set_bitmap(newbitmap)
  1023. return self._get_type_and_shape()
  1024. else:
  1025. extra_dims = FI_TYPES.extra_dims[fi_type]
  1026. # Return dtype and shape
  1027. return numpy.dtype(dtype), extra_dims + [w, h], bpp
  1028. def quantize(self, quantizer=0, palettesize=256):
  1029. """Quantize the bitmap to make it 8-bit (paletted). Returns a new
  1030. FIBitmap object.
  1031. Only for 24 bit images.
  1032. """
  1033. with self._fi as lib:
  1034. # New bitmap
  1035. bitmap = lib.FreeImage_ColorQuantizeEx(
  1036. self._bitmap, quantizer, palettesize, 0, None
  1037. )
  1038. bitmap = ctypes.c_void_p(bitmap)
  1039. # Check and return
  1040. if not bitmap:
  1041. raise ValueError(
  1042. 'Could not quantize bitmap "%s": %s'
  1043. % (self._filename, self._fi._get_error_message())
  1044. )
  1045. new = FIBitmap(self._fi, self._filename, self._ftype, self._flags)
  1046. new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
  1047. new._fi_type = self._fi_type
  1048. return new
  1049. # def convert_to_32bit(self):
  1050. # """ Convert to 32bit image.
  1051. # """
  1052. # with self._fi as lib:
  1053. # # New bitmap
  1054. # bitmap = lib.FreeImage_ConvertTo32Bits(self._bitmap)
  1055. # bitmap = ctypes.c_void_p(bitmap)
  1056. #
  1057. # # Check and return
  1058. # if not bitmap:
  1059. # raise ValueError('Could not convert bitmap to 32bit "%s": %s' %
  1060. # (self._filename,
  1061. # self._fi._get_error_message()))
  1062. # else:
  1063. # new = FIBitmap(self._fi, self._filename, self._ftype,
  1064. # self._flags)
  1065. # new._set_bitmap(bitmap, (lib.FreeImage_Unload, bitmap))
  1066. # new._fi_type = self._fi_type
  1067. # return new
  1068. class FIMultipageBitmap(FIBaseBitmap):
  1069. """Wrapper for the multipage FI bitmap object."""
  1070. def load_from_filename(self, filename=None):
  1071. if filename is None: # pragma: no cover
  1072. filename = self._filename
  1073. # Prepare
  1074. create_new = False
  1075. read_only = True
  1076. keep_cache_in_memory = False
  1077. # Try opening
  1078. with self._fi as lib:
  1079. # Create bitmap
  1080. multibitmap = lib.FreeImage_OpenMultiBitmap(
  1081. self._ftype,
  1082. efn(filename),
  1083. create_new,
  1084. read_only,
  1085. keep_cache_in_memory,
  1086. self._flags,
  1087. )
  1088. multibitmap = ctypes.c_void_p(multibitmap)
  1089. # Check
  1090. if not multibitmap: # pragma: no cover
  1091. err = self._fi._get_error_message()
  1092. raise ValueError(
  1093. 'Could not open file "%s" as multi-image: %s'
  1094. % (self._filename, err)
  1095. )
  1096. self._set_bitmap(multibitmap, (lib.FreeImage_CloseMultiBitmap, multibitmap))
  1097. # def load_from_bytes(self, bb):
  1098. # with self._fi as lib:
  1099. # # Create bitmap
  1100. # fimemory = lib.FreeImage_OpenMemory(
  1101. # ctypes.c_char_p(bb), len(bb))
  1102. # multibitmap = lib.FreeImage_LoadMultiBitmapFromMemory(
  1103. # self._ftype, ctypes.c_void_p(fimemory), self._flags)
  1104. # multibitmap = ctypes.c_void_p(multibitmap)
  1105. # #lib.FreeImage_CloseMemory(ctypes.c_void_p(fimemory))
  1106. # self._mem = fimemory
  1107. # self._bytes = bb
  1108. # # Check
  1109. # if not multibitmap:
  1110. # raise ValueError('Could not load multibitmap "%s": %s'
  1111. # % (self._filename, self._fi._get_error_message()))
  1112. # else:
  1113. # self._set_bitmap(multibitmap,
  1114. # (lib.FreeImage_CloseMultiBitmap, multibitmap))
  1115. def save_to_filename(self, filename=None):
  1116. if filename is None: # pragma: no cover
  1117. filename = self._filename
  1118. # Prepare
  1119. create_new = True
  1120. read_only = False
  1121. keep_cache_in_memory = False
  1122. # Open the file
  1123. # todo: Set flags at close func
  1124. with self._fi as lib:
  1125. multibitmap = lib.FreeImage_OpenMultiBitmap(
  1126. self._ftype,
  1127. efn(filename),
  1128. create_new,
  1129. read_only,
  1130. keep_cache_in_memory,
  1131. 0,
  1132. )
  1133. multibitmap = ctypes.c_void_p(multibitmap)
  1134. # Check
  1135. if not multibitmap: # pragma: no cover
  1136. msg = 'Could not open file "%s" for writing multi-image: %s' % (
  1137. self._filename,
  1138. self._fi._get_error_message(),
  1139. )
  1140. raise ValueError(msg)
  1141. self._set_bitmap(multibitmap, (lib.FreeImage_CloseMultiBitmap, multibitmap))
  1142. def __len__(self):
  1143. with self._fi as lib:
  1144. return lib.FreeImage_GetPageCount(self._bitmap)
  1145. def get_page(self, index):
  1146. """Return the sub-bitmap for the given page index.
  1147. Please close the returned bitmap when done.
  1148. """
  1149. with self._fi as lib:
  1150. # Create low-level bitmap in freeimage
  1151. bitmap = lib.FreeImage_LockPage(self._bitmap, index)
  1152. bitmap = ctypes.c_void_p(bitmap)
  1153. if not bitmap: # pragma: no cover
  1154. raise ValueError(
  1155. "Could not open sub-image %i in %r: %s"
  1156. % (index, self._filename, self._fi._get_error_message())
  1157. )
  1158. # Get bitmap object to wrap this bitmap
  1159. bm = FIBitmap(self._fi, self._filename, self._ftype, self._flags)
  1160. bm._set_bitmap(
  1161. bitmap, (lib.FreeImage_UnlockPage, self._bitmap, bitmap, False)
  1162. )
  1163. return bm
  1164. def append_bitmap(self, bitmap):
  1165. """Add a sub-bitmap to the multi-page bitmap."""
  1166. with self._fi as lib:
  1167. # no return value
  1168. lib.FreeImage_AppendPage(self._bitmap, bitmap._bitmap)
  1169. # Create instance
  1170. fi = Freeimage()