Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4f28fc1

Browse files
committed
[ 1448342 ] FigureCanvasAgg.print_figure fails with StringIO 'file'
Added support to write png to an arbitrary Python file-like object in the agg backend. Will continue to use faster C-only calls if the file-like object is in fact a file. Also, clean up exception handling in write_png. svn path=/trunk/matplotlib/; revision=4237
1 parent f7d2a83 commit 4f28fc1

2 files changed

Lines changed: 91 additions & 77 deletions

File tree

lib/matplotlib/backends/backend_agg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,5 +415,5 @@ def print_raw(self, filename, *args, **kwargs):
415415

416416
def print_png(self, filename, *args, **kwargs):
417417
self.draw()
418-
self.get_renderer()._renderer.write_png(str(filename), self.figure.dpi.get())
418+
self.get_renderer()._renderer.write_png(filename, self.figure.dpi.get())
419419

src/_backend_agg.cpp

Lines changed: 90 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2256,6 +2256,21 @@ RendererAgg::write_rgba(const Py::Tuple& args) {
22562256

22572257
}
22582258

2259+
static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t length) {
2260+
PyObject* py_file_obj = (PyObject*)png_get_io_ptr(png_ptr);
2261+
PyObject* write_method = PyObject_GetAttrString(py_file_obj, "write");
2262+
PyObject_CallFunction(write_method, "s#", data, length);
2263+
2264+
// MGDTODO: Check NULL on failure
2265+
}
2266+
2267+
static void flush_png_data(png_structp png_ptr) {
2268+
PyObject* py_file_obj = (PyObject*)png_get_io_ptr(png_ptr);
2269+
PyObject* flush_method = PyObject_GetAttrString(py_file_obj, "write");
2270+
if (flush_method) {
2271+
PyObject_CallFunction(flush_method, "");
2272+
}
2273+
}
22592274

22602275
// this code is heavily adapted from the paint license, which is in
22612276
// the file paint.license (BSD compatible) included in this
@@ -2267,97 +2282,96 @@ RendererAgg::write_png(const Py::Tuple& args)
22672282

22682283
args.verify_length(1, 2);
22692284

2270-
FILE *fp;
2271-
Py::Object o = Py::Object(args[0]);
2272-
bool fpclose = true;
2273-
if (o.isString()) {
2274-
std::string fileName = Py::String(o);
2285+
FILE *fp = NULL;
2286+
Py::Object py_fileobj = Py::Object(args[0]);
2287+
if (py_fileobj.isString()) {
2288+
std::string fileName = Py::String(py_fileobj);
22752289
const char *file_name = fileName.c_str();
22762290
if ((fp = fopen(file_name, "wb")) == NULL)
22772291
throw Py::RuntimeError( Printf("Could not open file %s", file_name).str() );
22782292
}
22792293
else {
2280-
if ((fp = PyFile_AsFile(o.ptr())) == NULL)
2281-
throw Py::TypeError("Could not convert object to file pointer");
2282-
fpclose = false;
2283-
}
2284-
2285-
png_structp png_ptr;
2286-
png_infop info_ptr;
2287-
struct png_color_8_struct sig_bit;
2288-
png_uint_32 row;
2289-
2290-
png_bytep *row_pointers = new png_bytep[height];
2291-
for (row = 0; row < height; ++row) {
2292-
row_pointers[row] = pixBuffer + row * width * 4;
2293-
}
2294-
2295-
2296-
if (fp == NULL) {
2297-
delete [] row_pointers;
2298-
throw Py::RuntimeError("Could not open file");
2294+
if ((fp = PyFile_AsFile(py_fileobj.ptr())) == NULL) {
2295+
PyObject* write_method = PyObject_GetAttrString(py_fileobj.ptr(), "write");
2296+
if (!(write_method && PyCallable_Check(write_method)))
2297+
throw Py::TypeError("Object does not appear to be a Python file-like object");
2298+
}
22992299
}
2300+
2301+
png_bytep *row_pointers = NULL;
2302+
png_structp png_ptr = NULL;
2303+
png_infop info_ptr = NULL;
23002304

2305+
try {
2306+
struct png_color_8_struct sig_bit;
2307+
png_uint_32 row;
2308+
2309+
row_pointers = new png_bytep[height];
2310+
for (row = 0; row < height; ++row) {
2311+
row_pointers[row] = pixBuffer + row * width * 4;
2312+
}
23012313

2302-
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
2303-
if (png_ptr == NULL) {
2304-
if (fpclose) fclose(fp);
2305-
delete [] row_pointers;
2306-
throw Py::RuntimeError("Could not create write struct");
2307-
}
2314+
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
2315+
if (png_ptr == NULL) {
2316+
throw Py::RuntimeError("Could not create write struct");
2317+
}
23082318

2309-
info_ptr = png_create_info_struct(png_ptr);
2310-
if (info_ptr == NULL) {
2311-
if (fpclose) fclose(fp);
2312-
png_destroy_write_struct(&png_ptr, &info_ptr);
2313-
delete [] row_pointers;
2314-
throw Py::RuntimeError("Could not create info struct");
2315-
}
2319+
info_ptr = png_create_info_struct(png_ptr);
2320+
if (info_ptr == NULL) {
2321+
throw Py::RuntimeError("Could not create info struct");
2322+
}
23162323

2317-
if (setjmp(png_ptr->jmpbuf)) {
2318-
if (fpclose) fclose(fp);
2319-
png_destroy_write_struct(&png_ptr, &info_ptr);
2320-
delete [] row_pointers;
2321-
throw Py::RuntimeError("Error building image");
2322-
}
2324+
if (setjmp(png_ptr->jmpbuf)) {
2325+
throw Py::RuntimeError("Error building image");
2326+
}
23232327

2324-
png_init_io(png_ptr, fp);
2325-
png_set_IHDR(png_ptr, info_ptr,
2326-
width, height, 8,
2327-
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
2328-
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
2329-
2330-
// Save the dpi of the image in the file
2331-
if (args.size() == 2) {
2332-
double dpi = Py::Float(args[1]);
2333-
size_t dots_per_meter = (size_t)(dpi / (2.54 / 100.0));
2334-
png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER);
2328+
if (fp) {
2329+
png_init_io(png_ptr, fp);
2330+
} else {
2331+
png_set_write_fn(png_ptr, (void*)py_fileobj.ptr(),
2332+
&write_png_data, &flush_png_data);
2333+
}
2334+
png_set_IHDR(png_ptr, info_ptr,
2335+
width, height, 8,
2336+
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
2337+
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
2338+
2339+
// Save the dpi of the image in the file
2340+
if (args.size() == 2) {
2341+
double dpi = Py::Float(args[1]);
2342+
size_t dots_per_meter = (size_t)(dpi / (2.54 / 100.0));
2343+
png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER);
2344+
}
2345+
2346+
// this a a color image!
2347+
sig_bit.gray = 0;
2348+
sig_bit.red = 8;
2349+
sig_bit.green = 8;
2350+
sig_bit.blue = 8;
2351+
/* if the image has an alpha channel then */
2352+
sig_bit.alpha = 8;
2353+
png_set_sBIT(png_ptr, info_ptr, &sig_bit);
2354+
2355+
png_write_info(png_ptr, info_ptr);
2356+
png_write_image(png_ptr, row_pointers);
2357+
png_write_end(png_ptr, info_ptr);
2358+
2359+
/* Changed calls to png_destroy_write_struct to follow
2360+
http://www.libpng.org/pub/png/libpng-manual.txt.
2361+
This ensures the info_ptr memory is released.
2362+
*/
2363+
2364+
} catch (...) {
2365+
if (fp) fclose(fp);
2366+
delete [] row_pointers;
2367+
if (png_ptr && info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr);
2368+
throw;
23352369
}
23362370

2337-
// this a a color image!
2338-
sig_bit.gray = 0;
2339-
sig_bit.red = 8;
2340-
sig_bit.green = 8;
2341-
sig_bit.blue = 8;
2342-
/* if the image has an alpha channel then */
2343-
sig_bit.alpha = 8;
2344-
png_set_sBIT(png_ptr, info_ptr, &sig_bit);
2345-
2346-
png_write_info(png_ptr, info_ptr);
2347-
png_write_image(png_ptr, row_pointers);
2348-
png_write_end(png_ptr, info_ptr);
2349-
2350-
/* Changed calls to png_destroy_write_struct to follow
2351-
http://www.libpng.org/pub/png/libpng-manual.txt.
2352-
This ensures the info_ptr memory is released.
2353-
*/
2354-
23552371
png_destroy_write_struct(&png_ptr, &info_ptr);
2356-
23572372
delete [] row_pointers;
2358-
2359-
if (fpclose) fclose(fp);
2360-
2373+
if (fp) fclose(fp);
2374+
23612375
return Py::Object();
23622376
}
23632377

0 commit comments

Comments
 (0)