diff --git a/scikits/image/io/_plugins/freeimage_plugin.py b/scikits/image/io/_plugins/freeimage_plugin.py index 3bde8ae9..d9492f83 100644 --- a/scikits/image/io/_plugins/freeimage_plugin.py +++ b/scikits/image/io/_plugins/freeimage_plugin.py @@ -1,531 +1,531 @@ -import ctypes -import numpy -import sys -import os -import os.path -from numpy.compat import asbytes - -def _load_library(libname, loader_path): - """ A small fork of numpy.ctypeslib.load_library - to support windll. - """ - if ctypes.__version__ < '1.0.1': - import warnings - warnings.warn("All features of ctypes interface may not work " \ - "with ctypes < 1.0.1") - - ext = os.path.splitext(libname)[1] - if not ext: - # Try to load library with platform-specific name, otherwise - # default to libname.[so|pyd]. Sometimes, these files are built - # erroneously on non-linux platforms. - libname_ext = ['%s.so' % libname, '%s.pyd' % libname] - if sys.platform == 'win32': - libname_ext.insert(0, '%s.dll' % libname) - elif sys.platform == 'darwin': - libname_ext.insert(0, '%s.dylib' % libname) - else: - libname_ext = [libname] - - loader_path = os.path.abspath(loader_path) - if not os.path.isdir(loader_path): - libdir = os.path.dirname(loader_path) - else: - libdir = loader_path - for ln in libname_ext: - try: - libpath = os.path.join(libdir, ln) - if sys.platform == 'win32': - return ctypes.windll[libpath] - else: - return ctypes.cdll[libpath] - except OSError, e: - pass - - raise e - -lib_dirs = [os.path.dirname(__file__), - '/lib', - '/usr/lib', - '/usr/local/lib', - '/opt/local/lib', - ] - -if 'HOME' in os.environ: - lib_dirs.append(os.path.join(os.environ['HOME'], 'lib')) - -API = { - 'FreeImage_Load': (ctypes.c_void_p, - [ctypes.c_int, ctypes.c_char_p, ctypes.c_int]), - 'FreeImage_GetWidth': (ctypes.c_uint, - [ctypes.c_void_p]), - 'FreeImage_GetHeight': (ctypes.c_uint, - [ctypes.c_void_p]), - 'FreeImage_GetImageType': (ctypes.c_uint, - [ctypes.c_void_p]), - 'FreeImage_GetBPP': (ctypes.c_uint, - [ctypes.c_void_p]), - 'FreeImage_GetPitch': (ctypes.c_uint, - [ctypes.c_void_p]), - 'FreeImage_GetBits': (ctypes.c_void_p, - [ctypes.c_void_p]), - } - -# Albert's ctypes pattern -def register_api(lib,api): - for f, (restype, argtypes) in api.items(): - func = getattr(lib, f) - func.restype = restype - func.argtypes = argtypes - -_FI = None -for d in lib_dirs: - for libname in ('freeimage', 'FreeImage', - 'libfreeimage', 'libFreeImage'): - try: - _FI = _load_library(libname, d) - except OSError: - pass - else: - break - - if _FI is not None: - break - -if not _FI: - raise OSError('Could not find libFreeImage in any of the following ' - 'directories: \'%s\'' % '\', \''.join(lib_dirs)) - -register_api(_FI, API) - -if sys.platform == 'win32': - _functype = ctypes.WINFUNCTYPE -else: - _functype = ctypes.CFUNCTYPE - -@_functype(None, ctypes.c_int, ctypes.c_char_p) -def _error_handler(fif, message): - raise RuntimeError('FreeImage error: %s' % message) - -_FI.FreeImage_SetOutputMessage(_error_handler) - -class FI_TYPES(object): - FIT_UNKNOWN = 0 - FIT_BITMAP = 1 - FIT_UINT16 = 2 - FIT_INT16 = 3 - FIT_UINT32 = 4 - FIT_INT32 = 5 - FIT_FLOAT = 6 - FIT_DOUBLE = 7 - FIT_COMPLEX = 8 - FIT_RGB16 = 9 - FIT_RGBA16 = 10 - FIT_RGBF = 11 - FIT_RGBAF = 12 - - dtypes = { - FIT_BITMAP: numpy.uint8, - FIT_UINT16: numpy.uint16, - FIT_INT16: numpy.int16, - FIT_UINT32: numpy.uint32, - FIT_INT32: numpy.int32, - FIT_FLOAT: numpy.float32, - FIT_DOUBLE: numpy.float64, - FIT_COMPLEX: numpy.complex128, - FIT_RGB16: numpy.uint16, - FIT_RGBA16: numpy.uint16, - FIT_RGBF: numpy.float32, - FIT_RGBAF: numpy.float32 - } - - fi_types = { - (numpy.uint8, 1): FIT_BITMAP, - (numpy.uint8, 3): FIT_BITMAP, - (numpy.uint8, 4): FIT_BITMAP, - (numpy.uint16, 1): FIT_UINT16, - (numpy.int16, 1): FIT_INT16, - (numpy.uint32, 1): FIT_UINT32, - (numpy.int32, 1): FIT_INT32, - (numpy.float32, 1): FIT_FLOAT, - (numpy.float64, 1): FIT_DOUBLE, - (numpy.complex128, 1): FIT_COMPLEX, - (numpy.uint16, 3): FIT_RGB16, - (numpy.uint16, 4): FIT_RGBA16, - (numpy.float32, 3): FIT_RGBF, - (numpy.float32, 4): FIT_RGBAF - } - - extra_dims = { - FIT_UINT16: [], - FIT_INT16: [], - FIT_UINT32: [], - FIT_INT32: [], - FIT_FLOAT: [], - FIT_DOUBLE: [], - FIT_COMPLEX: [], - FIT_RGB16: [3], - FIT_RGBA16: [4], - FIT_RGBF: [3], - FIT_RGBAF: [4] - } - - @classmethod - def get_type_and_shape(cls, bitmap): - w = _FI.FreeImage_GetWidth(bitmap) - h = _FI.FreeImage_GetHeight(bitmap) - fi_type = _FI.FreeImage_GetImageType(bitmap) - if not fi_type: - raise ValueError('Unknown image pixel type') - dtype = cls.dtypes[fi_type] - if fi_type == cls.FIT_BITMAP: - bpp = _FI.FreeImage_GetBPP(bitmap) - if bpp == 8: - extra_dims = [] - elif bpp == 24: - extra_dims = [3] - elif bpp == 32: - extra_dims = [4] - else: - raise ValueError('Cannot convert %d BPP bitmap' % bpp) - else: - extra_dims = cls.extra_dims[fi_type] - return numpy.dtype(dtype), extra_dims + [w, h] - -class IO_FLAGS(object): - #Bmp - BMP_DEFAULT = 0 - BMP_SAVE_RLE = 1 - - #Png - PNG_DEFAULT = 0 - PNG_IGNOREGAMMA = 1 - - #Gif - GIF_DEFAULT = 0 - GIF_LOAD256 = 1 - GIF_PLAYBACK = 2 - - #Ico - ICO_DEFAULT = 0 - ICO_MAKEALPHA = 1 - - #Tiff - TIFF_DEFAULT = 0 - TIFF_CMYK = 0x0001 - TIFF_NONE = 0x0800 - TIFF_PACKBITS = 0x0100 - TIFF_DEFLATE = 0x0200 - TIFF_ADOBE_DEFLATE = 0x0400 - TIFF_CCITTFAX3 = 0x1000 - TIFF_CCITTFAX4 = 0x2000 - TIFF_LZW = 0x4000 - TIFF_JPEG = 0x8000 - - #Jpeg - JPEG_DEFAULT = 0 - JPEG_FAST = 1 - JPEG_ACCURATE = 2 - JPEG_QUALITYSUPERB = 0x80 - JPEG_QUALITYGOOD = 0x100 - JPEG_QUALITYNORMAL = 0x200 - JPEG_QUALITYAVERAGE = 0x400 - JPEG_QUALITYBAD = 0x800 - JPEG_CMYK = 0x1000 - JPEG_PROGRESSIVE = 0x2000 - - #Others... - CUT_DEFAULT = 0 - DDS_DEFAULT = 0 - HDR_DEFAULT = 0 - IFF_DEFAULT = 0 - KOALA_DEFAULT = 0 - LBM_DEFAULT = 0 - MNG_DEFAULT = 0 - PCD_DEFAULT = 0 - PCD_BASE = 1 - PCD_BASEDIV4 = 2 - PCD_BASEDIV16 = 3 - PCX_DEFAULT = 0 - PNM_DEFAULT = 0 - PNM_SAVE_RAW = 0 - PNM_SAVE_ASCII = 1 - PSD_DEFAULT = 0 - RAS_DEFAULT = 0 - TARGA_DEFAULT = 0 - TARGA_LOAD_RGB888 = 1 - WBMP_DEFAULT = 0 - XBM_DEFAULT = 0 - -class METADATA_MODELS(object): - FIMD_NODATA = -1 - FIMD_COMMENTS = 0 - FIMD_EXIF_MAIN = 1 - FIMD_EXIF_EXIF = 2 - FIMD_EXIF_GPS = 3 - FIMD_EXIF_MAKERNOTE = 4 - FIMD_EXIF_INTEROP = 5 - FIMD_IPTC = 6 - FIMD_XMP = 7 - FIMD_GEOTIFF = 8 - FIMD_ANIMATION = 9 - FIMD_CUSTOM = 10 - -def read(filename, flags=0): - """Read an image to a numpy array of shape (width, height) for - greyscale images, or shape (width, height, nchannels) for RGB or - RGBA images. - - """ - bitmap = _read_bitmap(filename, flags) - try: - return _array_from_bitmap(bitmap) - finally: - _FI.FreeImage_Unload(bitmap) - -def read_multipage(filename, flags=0): - """Read a multipage image to a list of numpy arrays, where each - array is of shape (width, height) for greyscale images, or shape - (nchannels, width, height) for RGB or RGBA images. - - """ - filename = asbytes(filename) - ftype = _FI.FreeImage_GetFileType(filename, 0) - if ftype == -1: - raise ValueError('Cannot determine type of file %s' % filename) - create_new = False - read_only = True - keep_cache_in_memory = True - multibitmap = _FI.FreeImage_OpenMultiBitmap(ftype, filename, create_new, - read_only, keep_cache_in_memory, - flags) - if not multibitmap: - raise ValueError('Could not open %s as multi-page image.' % filename) - try: - multibitmap = ctypes.c_void_p(multibitmap) - pages = _FI.FreeImage_GetPageCount(multibitmap) - arrays = [] - for i in range(pages): - bitmap = _FI.FreeImage_LockPage(multibitmap, i) - bitmap = ctypes.c_void_p(bitmap) - try: - arrays.append(_array_from_bitmap(bitmap)) - finally: - _FI.FreeImage_UnlockPage(multibitmap, bitmap, False) - return arrays - finally: - _FI.FreeImage_CloseMultiBitmap(multibitmap, 0) - -def _read_bitmap(filename, flags): - """Load a file to a FreeImage bitmap pointer""" - filename = asbytes(filename) - ftype = _FI.FreeImage_GetFileType(filename, 0) - if ftype == -1: - raise ValueError('Cannot determine type of file %s' % filename) - bitmap = _FI.FreeImage_Load(ftype, filename, flags) - if not bitmap: - raise ValueError('Could not load file %s' % filename) - return ctypes.c_void_p(bitmap) - -def _wrap_bitmap_bits_in_array(bitmap, shape, dtype): - """Return an ndarray view on the data in a FreeImage bitmap. Only - valid for as long as the bitmap is loaded (if single page) / locked - in memory (if multipage). - - """ - pitch = _FI.FreeImage_GetPitch(bitmap) - height = shape[-1] - byte_size = height * pitch - itemsize = dtype.itemsize - - if len(shape) == 3: - strides = (itemsize, shape[0]*itemsize, pitch) - else: - strides = (itemsize, pitch) - bits = _FI.FreeImage_GetBits(bitmap) - array = numpy.ndarray(shape, dtype=dtype, - buffer=(ctypes.c_char*byte_size).from_address(bits), - strides=strides) - return array - -def _array_from_bitmap(bitmap): - """Convert a FreeImage bitmap pointer to a numpy array - - """ - dtype, shape = FI_TYPES.get_type_and_shape(bitmap) - array = _wrap_bitmap_bits_in_array(bitmap, shape, dtype) - # swizzle the color components and flip the scanlines to go from - # FreeImage's BGR[A] and upside-down internal memory format to something - # more normal - def n(arr): - return arr[..., ::-1].T - if len(shape) == 3 and _FI.FreeImage_IsLittleEndian() and \ - dtype.type == numpy.uint8: - b = n(array[0]) - g = n(array[1]) - r = n(array[2]) - if shape[0] == 3: - return numpy.dstack( (r, g, b) ) - elif shape[0] == 4: - a = n(array[3]) - return numpy.dstack( (r, g, b, a) ) - else: - raise ValueError('Cannot handle images of shape %s' % shape) - - # We need to copy because array does *not* own its memory - # after bitmap is freed. - return n(array).copy() - -def string_tag(bitmap, key, model=METADATA_MODELS.FIMD_EXIF_MAIN): - """Retrieve the value of a metadata tag with the given string key as a - string.""" - tag = ctypes.c_int() - if not _FI.FreeImage_GetMetadata(model, bitmap, str(key), - ctypes.byref(tag)): - return - char_ptr = ctypes.c_char * _FI.FreeImage_GetTagLength(tag) - return char_ptr.from_address(_FI.FreeImage_GetTagValue(tag)).raw() - -def write(array, filename, flags=0): - """Write a (width, height) or (width, height, nchannels) array to - a greyscale, RGB, or RGBA image, with file type deduced from the - filename. - - """ - filename = asbytes(filename) - ftype = _FI.FreeImage_GetFIFFromFilename(filename) - if ftype == -1: - raise ValueError('Cannot determine type for %s' % filename) - bitmap, fi_type = _array_to_bitmap(array) - try: - if fi_type == FI_TYPES.FIT_BITMAP: - can_write = _FI.FreeImage_FIFSupportsExportBPP(ftype, - _FI.FreeImage_GetBPP(bitmap)) - else: - can_write = _FI.FreeImage_FIFSupportsExportType(ftype, fi_type) - if not can_write: - raise TypeError('Cannot save image of this format ' - 'to this file type') - res = _FI.FreeImage_Save(ftype, bitmap, filename, flags) - if not res: - raise RuntimeError('Could not save image properly.') - finally: - _FI.FreeImage_Unload(bitmap) - -def write_multipage(arrays, filename, flags=0): - """Write a list of (width, height) or (nchannels, width, height) - arrays to a multipage greyscale, RGB, or RGBA image, with file type - deduced from the filename. - - """ - filename = asbytes(filename) - ftype = _FI.FreeImage_GetFIFFromFilename(filename) - if ftype == -1: - raise ValueError('Cannot determine type of file %s' % filename) - create_new = True - read_only = False - keep_cache_in_memory = True - multibitmap = _FI.FreeImage_OpenMultiBitmap(ftype, filename, - create_new, read_only, - keep_cache_in_memory, 0) - if not multibitmap: - raise ValueError('Could not open %s for writing multi-page image.' % - filename) - try: - multibitmap = ctypes.c_void_p(multibitmap) - for array in arrays: - bitmap, fi_type = _array_to_bitmap(array) - _FI.FreeImage_AppendPage(multibitmap, bitmap) - finally: - _FI.FreeImage_CloseMultiBitmap(multibitmap, flags) - -# 4-byte quads of 0,v,v,v from 0,0,0,0 to 0,255,255,255 -_GREY_PALETTE = numpy.arange(0, 0x01000000, 0x00010101, dtype=numpy.uint32) - -def _array_to_bitmap(array): - """Allocate a FreeImage bitmap and copy a numpy array into it. - - """ - shape = array.shape - dtype = array.dtype - r,c = shape[:2] - if len(shape) == 2: - n_channels = 1 - w_shape = (c,r) - elif len(shape) == 3: - n_channels = shape[2] - w_shape = (n_channels,c,r) - else: - n_channels = shape[0] - try: - fi_type = FI_TYPES.fi_types[(dtype.type, n_channels)] - except KeyError: - raise ValueError('Cannot write arrays of given type and shape.') - - itemsize = array.dtype.itemsize - bpp = 8 * itemsize * n_channels - bitmap = _FI.FreeImage_AllocateT(fi_type, c, r, bpp, 0, 0, 0) - if not bitmap: - raise RuntimeError('Could not allocate image for storage') - try: - def n(arr): # normalise to freeimage's in-memory format - return arr.T[:,::-1] - bitmap = ctypes.c_void_p(bitmap) - wrapped_array = _wrap_bitmap_bits_in_array(bitmap, w_shape, dtype) - # swizzle the color components and flip the scanlines to go to - # FreeImage's BGR[A] and upside-down internal memory format - if len(shape) == 3 and _FI.FreeImage_IsLittleEndian() and \ - dtype.type == numpy.uint8: - wrapped_array[0] = n(array[:,:,2]) - wrapped_array[1] = n(array[:,:,1]) - wrapped_array[2] = n(array[:,:,0]) - if shape[2] == 4: - wrapped_array[3] = n(array[:,:,3]) - else: - wrapped_array[:] = n(array) - if len(shape) == 2 and dtype.type == numpy.uint8: - palette = _FI.FreeImage_GetPalette(bitmap) - if not palette: - raise RuntimeError('Could not get image palette') - ctypes.memmove(palette, _GREY_PALETTE.ctypes.data, 1024) - return bitmap, fi_type - except: - _FI.FreeImage_Unload(bitmap) - raise - - -def imread(filename, as_grey=False): - """ - img = imread(filename, as_grey=False) - - Reads an image from file `filename` - - Parameters - ---------- - filename : file name - as_grey : Whether to convert to grey scale image (default: no) - Returns - ------- - img : ndarray - """ - img = read(filename) - if as_grey and len(img) == 3: - # these are the values that wikipedia says are typical - transform = numpy.array([ 0.30, 0.59, 0.11]) - return numpy.dot(img, transform) - return img - -def imsave(filename, img): - ''' - imsave(filename, img) - - Save image to disk - - Image type is inferred from filename - - Parameters - ---------- - filename : file name - img : image to be saved as nd array - ''' - write(img, filename) +import ctypes +import numpy +import sys +import os +import os.path +from numpy.compat import asbytes + +def _load_library(libname, loader_path): + """ A small fork of numpy.ctypeslib.load_library + to support windll. + """ + if ctypes.__version__ < '1.0.1': + import warnings + warnings.warn("All features of ctypes interface may not work " \ + "with ctypes < 1.0.1") + + ext = os.path.splitext(libname)[1] + if not ext: + # Try to load library with platform-specific name, otherwise + # default to libname.[so|pyd]. Sometimes, these files are built + # erroneously on non-linux platforms. + libname_ext = ['%s.so' % libname, '%s.pyd' % libname] + if sys.platform == 'win32': + libname_ext.insert(0, '%s.dll' % libname) + elif sys.platform == 'darwin': + libname_ext.insert(0, '%s.dylib' % libname) + else: + libname_ext = [libname] + + loader_path = os.path.abspath(loader_path) + if not os.path.isdir(loader_path): + libdir = os.path.dirname(loader_path) + else: + libdir = loader_path + for ln in libname_ext: + try: + libpath = os.path.join(libdir, ln) + if sys.platform == 'win32': + return ctypes.windll[libpath] + else: + return ctypes.cdll[libpath] + except OSError, e: + pass + + raise e + +lib_dirs = [os.path.dirname(__file__), + '/lib', + '/usr/lib', + '/usr/local/lib', + '/opt/local/lib', + ] + +if 'HOME' in os.environ: + lib_dirs.append(os.path.join(os.environ['HOME'], 'lib')) + +API = { + 'FreeImage_Load': (ctypes.c_void_p, + [ctypes.c_int, ctypes.c_char_p, ctypes.c_int]), + 'FreeImage_GetWidth': (ctypes.c_uint, + [ctypes.c_void_p]), + 'FreeImage_GetHeight': (ctypes.c_uint, + [ctypes.c_void_p]), + 'FreeImage_GetImageType': (ctypes.c_uint, + [ctypes.c_void_p]), + 'FreeImage_GetBPP': (ctypes.c_uint, + [ctypes.c_void_p]), + 'FreeImage_GetPitch': (ctypes.c_uint, + [ctypes.c_void_p]), + 'FreeImage_GetBits': (ctypes.c_void_p, + [ctypes.c_void_p]), + } + +# Albert's ctypes pattern +def register_api(lib,api): + for f, (restype, argtypes) in api.items(): + func = getattr(lib, f) + func.restype = restype + func.argtypes = argtypes + +_FI = None +for d in lib_dirs: + for libname in ('freeimage', 'FreeImage', + 'libfreeimage', 'libFreeImage'): + try: + _FI = _load_library(libname, d) + except OSError: + pass + else: + break + + if _FI is not None: + break + +if not _FI: + raise OSError('Could not find libFreeImage in any of the following ' + 'directories: \'%s\'' % '\', \''.join(lib_dirs)) + +register_api(_FI, API) + +if sys.platform == 'win32': + _functype = ctypes.WINFUNCTYPE +else: + _functype = ctypes.CFUNCTYPE + +@_functype(None, ctypes.c_int, ctypes.c_char_p) +def _error_handler(fif, message): + raise RuntimeError('FreeImage error: %s' % message) + +_FI.FreeImage_SetOutputMessage(_error_handler) + +class FI_TYPES(object): + FIT_UNKNOWN = 0 + FIT_BITMAP = 1 + FIT_UINT16 = 2 + FIT_INT16 = 3 + FIT_UINT32 = 4 + FIT_INT32 = 5 + FIT_FLOAT = 6 + FIT_DOUBLE = 7 + FIT_COMPLEX = 8 + FIT_RGB16 = 9 + FIT_RGBA16 = 10 + FIT_RGBF = 11 + FIT_RGBAF = 12 + + dtypes = { + FIT_BITMAP: numpy.uint8, + FIT_UINT16: numpy.uint16, + FIT_INT16: numpy.int16, + FIT_UINT32: numpy.uint32, + FIT_INT32: numpy.int32, + FIT_FLOAT: numpy.float32, + FIT_DOUBLE: numpy.float64, + FIT_COMPLEX: numpy.complex128, + FIT_RGB16: numpy.uint16, + FIT_RGBA16: numpy.uint16, + FIT_RGBF: numpy.float32, + FIT_RGBAF: numpy.float32 + } + + fi_types = { + (numpy.uint8, 1): FIT_BITMAP, + (numpy.uint8, 3): FIT_BITMAP, + (numpy.uint8, 4): FIT_BITMAP, + (numpy.uint16, 1): FIT_UINT16, + (numpy.int16, 1): FIT_INT16, + (numpy.uint32, 1): FIT_UINT32, + (numpy.int32, 1): FIT_INT32, + (numpy.float32, 1): FIT_FLOAT, + (numpy.float64, 1): FIT_DOUBLE, + (numpy.complex128, 1): FIT_COMPLEX, + (numpy.uint16, 3): FIT_RGB16, + (numpy.uint16, 4): FIT_RGBA16, + (numpy.float32, 3): FIT_RGBF, + (numpy.float32, 4): FIT_RGBAF + } + + extra_dims = { + FIT_UINT16: [], + FIT_INT16: [], + FIT_UINT32: [], + FIT_INT32: [], + FIT_FLOAT: [], + FIT_DOUBLE: [], + FIT_COMPLEX: [], + FIT_RGB16: [3], + FIT_RGBA16: [4], + FIT_RGBF: [3], + FIT_RGBAF: [4] + } + + @classmethod + def get_type_and_shape(cls, bitmap): + w = _FI.FreeImage_GetWidth(bitmap) + h = _FI.FreeImage_GetHeight(bitmap) + fi_type = _FI.FreeImage_GetImageType(bitmap) + if not fi_type: + raise ValueError('Unknown image pixel type') + dtype = cls.dtypes[fi_type] + if fi_type == cls.FIT_BITMAP: + bpp = _FI.FreeImage_GetBPP(bitmap) + if bpp == 8: + extra_dims = [] + elif bpp == 24: + extra_dims = [3] + elif bpp == 32: + extra_dims = [4] + else: + raise ValueError('Cannot convert %d BPP bitmap' % bpp) + else: + extra_dims = cls.extra_dims[fi_type] + return numpy.dtype(dtype), extra_dims + [w, h] + +class IO_FLAGS(object): + #Bmp + BMP_DEFAULT = 0 + BMP_SAVE_RLE = 1 + + #Png + PNG_DEFAULT = 0 + PNG_IGNOREGAMMA = 1 + + #Gif + GIF_DEFAULT = 0 + GIF_LOAD256 = 1 + GIF_PLAYBACK = 2 + + #Ico + ICO_DEFAULT = 0 + ICO_MAKEALPHA = 1 + + #Tiff + TIFF_DEFAULT = 0 + TIFF_CMYK = 0x0001 + TIFF_NONE = 0x0800 + TIFF_PACKBITS = 0x0100 + TIFF_DEFLATE = 0x0200 + TIFF_ADOBE_DEFLATE = 0x0400 + TIFF_CCITTFAX3 = 0x1000 + TIFF_CCITTFAX4 = 0x2000 + TIFF_LZW = 0x4000 + TIFF_JPEG = 0x8000 + + #Jpeg + JPEG_DEFAULT = 0 + JPEG_FAST = 1 + JPEG_ACCURATE = 2 + JPEG_QUALITYSUPERB = 0x80 + JPEG_QUALITYGOOD = 0x100 + JPEG_QUALITYNORMAL = 0x200 + JPEG_QUALITYAVERAGE = 0x400 + JPEG_QUALITYBAD = 0x800 + JPEG_CMYK = 0x1000 + JPEG_PROGRESSIVE = 0x2000 + + #Others... + CUT_DEFAULT = 0 + DDS_DEFAULT = 0 + HDR_DEFAULT = 0 + IFF_DEFAULT = 0 + KOALA_DEFAULT = 0 + LBM_DEFAULT = 0 + MNG_DEFAULT = 0 + PCD_DEFAULT = 0 + PCD_BASE = 1 + PCD_BASEDIV4 = 2 + PCD_BASEDIV16 = 3 + PCX_DEFAULT = 0 + PNM_DEFAULT = 0 + PNM_SAVE_RAW = 0 + PNM_SAVE_ASCII = 1 + PSD_DEFAULT = 0 + RAS_DEFAULT = 0 + TARGA_DEFAULT = 0 + TARGA_LOAD_RGB888 = 1 + WBMP_DEFAULT = 0 + XBM_DEFAULT = 0 + +class METADATA_MODELS(object): + FIMD_NODATA = -1 + FIMD_COMMENTS = 0 + FIMD_EXIF_MAIN = 1 + FIMD_EXIF_EXIF = 2 + FIMD_EXIF_GPS = 3 + FIMD_EXIF_MAKERNOTE = 4 + FIMD_EXIF_INTEROP = 5 + FIMD_IPTC = 6 + FIMD_XMP = 7 + FIMD_GEOTIFF = 8 + FIMD_ANIMATION = 9 + FIMD_CUSTOM = 10 + +def read(filename, flags=0): + """Read an image to a numpy array of shape (width, height) for + greyscale images, or shape (width, height, nchannels) for RGB or + RGBA images. + + """ + bitmap = _read_bitmap(filename, flags) + try: + return _array_from_bitmap(bitmap) + finally: + _FI.FreeImage_Unload(bitmap) + +def read_multipage(filename, flags=0): + """Read a multipage image to a list of numpy arrays, where each + array is of shape (width, height) for greyscale images, or shape + (nchannels, width, height) for RGB or RGBA images. + + """ + filename = asbytes(filename) + ftype = _FI.FreeImage_GetFileType(filename, 0) + if ftype == -1: + raise ValueError('Cannot determine type of file %s' % filename) + create_new = False + read_only = True + keep_cache_in_memory = True + multibitmap = _FI.FreeImage_OpenMultiBitmap(ftype, filename, create_new, + read_only, keep_cache_in_memory, + flags) + if not multibitmap: + raise ValueError('Could not open %s as multi-page image.' % filename) + try: + multibitmap = ctypes.c_void_p(multibitmap) + pages = _FI.FreeImage_GetPageCount(multibitmap) + arrays = [] + for i in range(pages): + bitmap = _FI.FreeImage_LockPage(multibitmap, i) + bitmap = ctypes.c_void_p(bitmap) + try: + arrays.append(_array_from_bitmap(bitmap)) + finally: + _FI.FreeImage_UnlockPage(multibitmap, bitmap, False) + return arrays + finally: + _FI.FreeImage_CloseMultiBitmap(multibitmap, 0) + +def _read_bitmap(filename, flags): + """Load a file to a FreeImage bitmap pointer""" + filename = asbytes(filename) + ftype = _FI.FreeImage_GetFileType(filename, 0) + if ftype == -1: + raise ValueError('Cannot determine type of file %s' % filename) + bitmap = _FI.FreeImage_Load(ftype, filename, flags) + if not bitmap: + raise ValueError('Could not load file %s' % filename) + return ctypes.c_void_p(bitmap) + +def _wrap_bitmap_bits_in_array(bitmap, shape, dtype): + """Return an ndarray view on the data in a FreeImage bitmap. Only + valid for as long as the bitmap is loaded (if single page) / locked + in memory (if multipage). + + """ + pitch = _FI.FreeImage_GetPitch(bitmap) + height = shape[-1] + byte_size = height * pitch + itemsize = dtype.itemsize + + if len(shape) == 3: + strides = (itemsize, shape[0]*itemsize, pitch) + else: + strides = (itemsize, pitch) + bits = _FI.FreeImage_GetBits(bitmap) + array = numpy.ndarray(shape, dtype=dtype, + buffer=(ctypes.c_char*byte_size).from_address(bits), + strides=strides) + return array + +def _array_from_bitmap(bitmap): + """Convert a FreeImage bitmap pointer to a numpy array + + """ + dtype, shape = FI_TYPES.get_type_and_shape(bitmap) + array = _wrap_bitmap_bits_in_array(bitmap, shape, dtype) + # swizzle the color components and flip the scanlines to go from + # FreeImage's BGR[A] and upside-down internal memory format to something + # more normal + def n(arr): + return arr[..., ::-1].T + if len(shape) == 3 and _FI.FreeImage_IsLittleEndian() and \ + dtype.type == numpy.uint8: + b = n(array[0]) + g = n(array[1]) + r = n(array[2]) + if shape[0] == 3: + return numpy.dstack( (r, g, b) ) + elif shape[0] == 4: + a = n(array[3]) + return numpy.dstack( (r, g, b, a) ) + else: + raise ValueError('Cannot handle images of shape %s' % shape) + + # We need to copy because array does *not* own its memory + # after bitmap is freed. + return n(array).copy() + +def string_tag(bitmap, key, model=METADATA_MODELS.FIMD_EXIF_MAIN): + """Retrieve the value of a metadata tag with the given string key as a + string.""" + tag = ctypes.c_int() + if not _FI.FreeImage_GetMetadata(model, bitmap, str(key), + ctypes.byref(tag)): + return + char_ptr = ctypes.c_char * _FI.FreeImage_GetTagLength(tag) + return char_ptr.from_address(_FI.FreeImage_GetTagValue(tag)).raw() + +def write(array, filename, flags=0): + """Write a (width, height) or (width, height, nchannels) array to + a greyscale, RGB, or RGBA image, with file type deduced from the + filename. + + """ + filename = asbytes(filename) + ftype = _FI.FreeImage_GetFIFFromFilename(filename) + if ftype == -1: + raise ValueError('Cannot determine type for %s' % filename) + bitmap, fi_type = _array_to_bitmap(array) + try: + if fi_type == FI_TYPES.FIT_BITMAP: + can_write = _FI.FreeImage_FIFSupportsExportBPP(ftype, + _FI.FreeImage_GetBPP(bitmap)) + else: + can_write = _FI.FreeImage_FIFSupportsExportType(ftype, fi_type) + if not can_write: + raise TypeError('Cannot save image of this format ' + 'to this file type') + res = _FI.FreeImage_Save(ftype, bitmap, filename, flags) + if not res: + raise RuntimeError('Could not save image properly.') + finally: + _FI.FreeImage_Unload(bitmap) + +def write_multipage(arrays, filename, flags=0): + """Write a list of (width, height) or (nchannels, width, height) + arrays to a multipage greyscale, RGB, or RGBA image, with file type + deduced from the filename. + + """ + filename = asbytes(filename) + ftype = _FI.FreeImage_GetFIFFromFilename(filename) + if ftype == -1: + raise ValueError('Cannot determine type of file %s' % filename) + create_new = True + read_only = False + keep_cache_in_memory = True + multibitmap = _FI.FreeImage_OpenMultiBitmap(ftype, filename, + create_new, read_only, + keep_cache_in_memory, 0) + if not multibitmap: + raise ValueError('Could not open %s for writing multi-page image.' % + filename) + try: + multibitmap = ctypes.c_void_p(multibitmap) + for array in arrays: + bitmap, fi_type = _array_to_bitmap(array) + _FI.FreeImage_AppendPage(multibitmap, bitmap) + finally: + _FI.FreeImage_CloseMultiBitmap(multibitmap, flags) + +# 4-byte quads of 0,v,v,v from 0,0,0,0 to 0,255,255,255 +_GREY_PALETTE = numpy.arange(0, 0x01000000, 0x00010101, dtype=numpy.uint32) + +def _array_to_bitmap(array): + """Allocate a FreeImage bitmap and copy a numpy array into it. + + """ + shape = array.shape + dtype = array.dtype + r,c = shape[:2] + if len(shape) == 2: + n_channels = 1 + w_shape = (c,r) + elif len(shape) == 3: + n_channels = shape[2] + w_shape = (n_channels,c,r) + else: + n_channels = shape[0] + try: + fi_type = FI_TYPES.fi_types[(dtype.type, n_channels)] + except KeyError: + raise ValueError('Cannot write arrays of given type and shape.') + + itemsize = array.dtype.itemsize + bpp = 8 * itemsize * n_channels + bitmap = _FI.FreeImage_AllocateT(fi_type, c, r, bpp, 0, 0, 0) + if not bitmap: + raise RuntimeError('Could not allocate image for storage') + try: + def n(arr): # normalise to freeimage's in-memory format + return arr.T[:,::-1] + bitmap = ctypes.c_void_p(bitmap) + wrapped_array = _wrap_bitmap_bits_in_array(bitmap, w_shape, dtype) + # swizzle the color components and flip the scanlines to go to + # FreeImage's BGR[A] and upside-down internal memory format + if len(shape) == 3 and _FI.FreeImage_IsLittleEndian() and \ + dtype.type == numpy.uint8: + wrapped_array[0] = n(array[:,:,2]) + wrapped_array[1] = n(array[:,:,1]) + wrapped_array[2] = n(array[:,:,0]) + if shape[2] == 4: + wrapped_array[3] = n(array[:,:,3]) + else: + wrapped_array[:] = n(array) + if len(shape) == 2 and dtype.type == numpy.uint8: + palette = _FI.FreeImage_GetPalette(bitmap) + if not palette: + raise RuntimeError('Could not get image palette') + ctypes.memmove(palette, _GREY_PALETTE.ctypes.data, 1024) + return bitmap, fi_type + except: + _FI.FreeImage_Unload(bitmap) + raise + + +def imread(filename, as_grey=False): + """ + img = imread(filename, as_grey=False) + + Reads an image from file `filename` + + Parameters + ---------- + filename : file name + as_grey : Whether to convert to grey scale image (default: no) + Returns + ------- + img : ndarray + """ + img = read(filename) + if as_grey and len(img) == 3: + # these are the values that wikipedia says are typical + transform = numpy.array([ 0.30, 0.59, 0.11]) + return numpy.dot(img, transform) + return img + +def imsave(filename, img): + ''' + imsave(filename, img) + + Save image to disk + + Image type is inferred from filename + + Parameters + ---------- + filename : file name + img : image to be saved as nd array + ''' + write(img, filename)