diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index 6116dd7eeb66..fb4a61f8a564 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -39,6 +39,7 @@ #include "numpy/arrayobject.h" #include "agg_py_transforms.h" +#include "file_compat.h" #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -2028,44 +2029,42 @@ RendererAgg::write_rgba(const Py::Tuple& args) FILE *fp = NULL; Py::Object py_fileobj = Py::Object(args[0]); - - #if PY3K - int fd = PyObject_AsFileDescriptor(py_fileobj.ptr()); - PyErr_Clear(); - #endif + PyObject* py_file = NULL; + bool close_file = false; if (py_fileobj.isString()) { - std::string fileName = Py::String(py_fileobj); - const char *file_name = fileName.c_str(); - if ((fp = fopen(file_name, "wb")) == NULL) - throw Py::RuntimeError( - Printf("Could not open file %s", file_name).str()); - if (fwrite(pixBuffer, 1, NUMBYTES, fp) != NUMBYTES) - { - fclose(fp); - throw Py::RuntimeError( - Printf("Error writing to file %s", file_name).str()); + if ((py_file = npy_PyFile_OpenFile(py_fileobj.ptr(), (char *)"wb")) == NULL) { + throw Py::Exception(); } + close_file = true; } - #if PY3K - else if (fd != -1) + else { - if (write(fd, pixBuffer, NUMBYTES) != (ssize_t)NUMBYTES) - { - throw Py::RuntimeError("Error writing to file"); - } + py_file = py_fileobj.ptr(); } - #else - else if (PyFile_CheckExact(py_fileobj.ptr())) + + if ((fp = npy_PyFile_Dup(py_file, (char *)"wb"))) { - fp = PyFile_AsFile(py_fileobj.ptr()); if (fwrite(pixBuffer, 1, NUMBYTES, fp) != NUMBYTES) { + npy_PyFile_DupClose(py_file, fp); + + if (close_file) { + npy_PyFile_CloseFile(py_file); + Py_DECREF(py_file); + } + throw Py::RuntimeError("Error writing to file"); } + + npy_PyFile_DupClose(py_file, fp); + + if (close_file) { + npy_PyFile_CloseFile(py_file); + Py_DECREF(py_file); + } } - #endif else { PyObject* write_method = PyObject_GetAttrString(py_fileobj.ptr(), diff --git a/src/_png.cpp b/src/_png.cpp index 23ee5980b79a..9f23b214f7e7 100644 --- a/src/_png.cpp +++ b/src/_png.cpp @@ -27,6 +27,7 @@ #include "CXX/Extensions.hxx" #include "numpy/arrayobject.h" #include "mplutils.h" +#include "file_compat.h" // As reported in [3082058] build _png.so on aix #ifdef _AIX @@ -104,6 +105,7 @@ Py::Object _png_module::write_png(const Py::Tuple& args) FILE *fp = NULL; bool close_file = false; + bool close_dup_file = false; Py::Object buffer_obj = Py::Object(args[0]); PyObject* buffer = buffer_obj.ptr(); if (!PyObject_CheckReadBuffer(buffer)) @@ -128,41 +130,34 @@ Py::Object _png_module::write_png(const Py::Tuple& args) } Py::Object py_fileobj = Py::Object(args[3]); -#if PY3K - int fd = PyObject_AsFileDescriptor(py_fileobj.ptr()); - PyErr_Clear(); -#endif + PyObject* py_file = NULL; if (py_fileobj.isString()) { - std::string fileName = Py::String(py_fileobj); - const char *file_name = fileName.c_str(); - if ((fp = fopen(file_name, "wb")) == NULL) - { - throw Py::RuntimeError( - Printf("Could not open file %s", file_name).str()); + if ((py_file = npy_PyFile_OpenFile(py_fileobj.ptr(), (char *)"wb")) == NULL) { + throw Py::Exception(); } close_file = true; } -#if PY3K - else if (fd != -1) + else { - fp = fdopen(fd, "w"); + py_file = py_fileobj.ptr(); } -#else - else if (PyFile_CheckExact(py_fileobj.ptr())) + + if ((fp = npy_PyFile_Dup(py_file, (char *)"wb"))) { - fp = PyFile_AsFile(py_fileobj.ptr()); + close_dup_file = true; } -#endif else { + PyErr_Clear(); PyObject* write_method = PyObject_GetAttrString( - py_fileobj.ptr(), "write"); + py_file, "write"); if (!(write_method && PyCallable_Check(write_method))) { Py_XDECREF(write_method); throw Py::TypeError( - "Object does not appear to be a 8-bit string path or a Python file-like object"); + "Object does not appear to be a 8-bit string path or " + "a Python file-like object"); } Py_XDECREF(write_method); } @@ -205,7 +200,7 @@ Py::Object _png_module::write_png(const Py::Tuple& args) } else { - png_set_write_fn(png_ptr, (void*)py_fileobj.ptr(), + png_set_write_fn(png_ptr, (void*)py_file, &write_png_data, &flush_png_data); } png_set_IHDR(png_ptr, info_ptr, @@ -241,9 +236,16 @@ Py::Object _png_module::write_png(const Py::Tuple& args) png_destroy_write_struct(&png_ptr, &info_ptr); } delete [] row_pointers; - if (fp && close_file) + + if (close_dup_file) + { + npy_PyFile_DupClose(py_file, fp); + } + + if (close_file) { - fclose(fp); + npy_PyFile_CloseFile(py_file); + Py_DECREF(py_file); } /* Changed calls to png_destroy_write_struct to follow http://www.libpng.org/pub/png/libpng-manual.txt. @@ -254,15 +256,15 @@ Py::Object _png_module::write_png(const Py::Tuple& args) png_destroy_write_struct(&png_ptr, &info_ptr); delete [] row_pointers; -#if PY3K - if (fp) + if (close_dup_file) { - fflush(fp); + npy_PyFile_DupClose(py_file, fp); } -#endif - if (fp && close_file) + + if (close_file) { - fclose(fp); + npy_PyFile_CloseFile(py_file); + Py_DECREF(py_file); } if (PyErr_Occurred()) { @@ -306,40 +308,33 @@ _png_module::_read_png(const Py::Object& py_fileobj, const bool float_result, png_byte header[8]; // 8 is the maximum size that can be checked FILE* fp = NULL; bool close_file = false; - -#if PY3K - int fd = PyObject_AsFileDescriptor(py_fileobj.ptr()); - PyErr_Clear(); -#endif + bool close_dup_file = false; + PyObject *py_file = NULL; if (py_fileobj.isString()) { - std::string fileName = Py::String(py_fileobj); - const char *file_name = fileName.c_str(); - if ((fp = fopen(file_name, "rb")) == NULL) - { - throw Py::RuntimeError( - Printf("Could not open file %s for reading", file_name).str()); + if ((py_file = npy_PyFile_OpenFile(py_fileobj.ptr(), (char *)"rb")) == NULL) { + throw Py::Exception(); } close_file = true; + } else { + py_file = py_fileobj.ptr(); } -#if PY3K - else if (fd != -1) { - fp = fdopen(fd, "r"); - } -#else - else if (PyFile_CheckExact(py_fileobj.ptr())) + + if ((fp = npy_PyFile_Dup(py_file, "rb"))) { - fp = PyFile_AsFile(py_fileobj.ptr()); + close_dup_file = true; } -#endif else { - PyObject* read_method = PyObject_GetAttrString(py_fileobj.ptr(), "read"); + PyErr_Clear(); + PyObject* read_method = PyObject_GetAttrString(py_file, "read"); if (!(read_method && PyCallable_Check(read_method))) { Py_XDECREF(read_method); - throw Py::TypeError("Object does not appear to be a 8-bit string path or a Python file-like object"); + throw Py::TypeError( + "Object does not appear to be a 8-bit string path or a Python " + "file-like object"); } Py_XDECREF(read_method); } @@ -354,7 +349,7 @@ _png_module::_read_png(const Py::Object& py_fileobj, const bool float_result, } else { - _read_png_data(py_fileobj.ptr(), header, 8); + _read_png_data(py_file, header, 8); } if (png_sig_cmp(header, 0, 8)) { @@ -390,7 +385,7 @@ _png_module::_read_png(const Py::Object& py_fileobj, const bool float_result, } else { - png_set_read_fn(png_ptr, (void*)py_fileobj.ptr(), &read_png_data); + png_set_read_fn(png_ptr, (void*)py_file, &read_png_data); } png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); @@ -572,10 +567,17 @@ _png_module::_read_png(const Py::Object& py_fileobj, const bool float_result, #else png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); #endif + if (close_dup_file) + { + npy_PyFile_DupClose(py_file, fp); + } + if (close_file) { - fclose(fp); + npy_PyFile_CloseFile(py_file); + Py_DECREF(py_file); } + for (row = 0; row < height; row++) { delete [] row_pointers[row]; diff --git a/src/file_compat.h b/src/file_compat.h new file mode 100644 index 000000000000..fbb545d5665e --- /dev/null +++ b/src/file_compat.h @@ -0,0 +1,135 @@ +#ifndef __FILE_COMPAT_H__ +#define __FILE_COMPAT_H__ + +#include "numpy/npy_3kcompat.h" + +#if NPY_API_VERSION < 0x4 /* corresponds to Numpy 1.5 */ +/* + * PyFile_* compatibility + */ +#if defined(NPY_PY3K) + +/* + * Get a FILE* handle to the file represented by the Python object + */ +static NPY_INLINE FILE* +npy_PyFile_Dup(PyObject *file, char *mode) +{ + int fd, fd2; + PyObject *ret, *os; + Py_ssize_t pos; + FILE *handle; + /* Flush first to ensure things end up in the file in the correct order */ + ret = PyObject_CallMethod(file, "flush", ""); + if (ret == NULL) { + return NULL; + } + Py_DECREF(ret); + fd = PyObject_AsFileDescriptor(file); + if (fd == -1) { + return NULL; + } + os = PyImport_ImportModule("os"); + if (os == NULL) { + return NULL; + } + ret = PyObject_CallMethod(os, "dup", "i", fd); + Py_DECREF(os); + if (ret == NULL) { + return NULL; + } + fd2 = PyNumber_AsSsize_t(ret, NULL); + Py_DECREF(ret); +#ifdef _WIN32 + handle = _fdopen(fd2, mode); +#else + handle = fdopen(fd2, mode); +#endif + if (handle == NULL) { + PyErr_SetString(PyExc_IOError, + "Getting a FILE* from a Python file object failed"); + } + ret = PyObject_CallMethod(file, "tell", ""); + if (ret == NULL) { + fclose(handle); + return NULL; + } + pos = PyNumber_AsSsize_t(ret, PyExc_OverflowError); + Py_DECREF(ret); + if (PyErr_Occurred()) { + fclose(handle); + return NULL; + } + npy_fseek(handle, pos, SEEK_SET); + return handle; +} + +/* + * Close the dup-ed file handle, and seek the Python one to the current position + */ +static NPY_INLINE int +npy_PyFile_DupClose(PyObject *file, FILE* handle) +{ + PyObject *ret; + Py_ssize_t position; + position = npy_ftell(handle); + fclose(handle); + + ret = PyObject_CallMethod(file, "seek", NPY_SSIZE_T_PYFMT "i", position, 0); + if (ret == NULL) { + return -1; + } + Py_DECREF(ret); + return 0; +} + +static NPY_INLINE int +npy_PyFile_Check(PyObject *file) +{ + int fd; + fd = PyObject_AsFileDescriptor(file); + if (fd == -1) { + PyErr_Clear(); + return 0; + } + return 1; +} + +#else + +#define npy_PyFile_Dup(file, mode) PyFile_AsFile(file) +#define npy_PyFile_DupClose(file, handle) (0) + +#endif + +static NPY_INLINE PyObject* +npy_PyFile_OpenFile(PyObject *filename, const char *mode) +{ + PyObject *open; + open = PyDict_GetItemString(PyEval_GetBuiltins(), "open"); + if (open == NULL) { + return NULL; + } + return PyObject_CallFunction(open, "Os", filename, mode); +} + +#endif /* NPY_API_VERSION < 0x4 */ + +#if NPY_API_VERSION < 0x7 /* corresponds to Numpy 1.7 */ + +static NPY_INLINE int +npy_PyFile_CloseFile(PyObject *file) +{ + PyObject *ret; + + ret = PyObject_CallMethod(file, "close", NULL); + if (ret == NULL) { + return -1; + } + Py_DECREF(ret); + return 0; +} + +#endif /* NPY_API_VERSION < 0x7 */ + +#endif /* ifndef __FILE_COMPAT_H__ */