Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c4fa469

Browse filesBrowse files
authored
Merge pull request #6667 from tacaswell/backport_png_updates
Merge pull request #5389 from mdboom/webagg-speed
2 parents fe720e2 + 33e674e commit c4fa469
Copy full SHA for c4fa469

File tree

Expand file treeCollapse file tree

2 files changed

+126
-22
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+126
-22
lines changed

‎lib/matplotlib/backends/backend_webagg_core.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_webagg_core.py
+4-14Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,6 @@ class FigureCanvasWebAggCore(backend_agg.FigureCanvasAgg):
145145
def __init__(self, *args, **kwargs):
146146
backend_agg.FigureCanvasAgg.__init__(self, *args, **kwargs)
147147

148-
# A buffer to hold the PNG data for the last frame. This is
149-
# retained so it can be resent to each client without
150-
# regenerating it.
151-
self._png_buffer = io.BytesIO()
152-
153148
# Set to True when the renderer contains data that is newer
154149
# than the PNG buffer.
155150
self._png_is_old = True
@@ -227,24 +222,19 @@ def get_diff_image(self):
227222
diff = buff != last_buffer
228223
output = np.where(diff, buff, 0)
229224

230-
# Clear out the PNG data buffer rather than recreating it
231-
# each time. This reduces the number of memory
232-
# (de)allocations.
233-
self._png_buffer.truncate()
234-
self._png_buffer.seek(0)
235-
236225
# TODO: We should write a new version of write_png that
237226
# handles the differencing inline
238-
_png.write_png(
227+
buff = _png.write_png(
239228
output.view(dtype=np.uint8).reshape(output.shape + (4,)),
240-
self._png_buffer)
229+
None, compression=6, filter=_png.PNG_FILTER_NONE)
241230

242231
# Swap the renderer frames
243232
self._renderer, self._last_renderer = (
244233
self._last_renderer, renderer)
245234
self._force_full = False
246235
self._png_is_old = False
247-
return self._png_buffer.getvalue()
236+
237+
return buff
248238

249239
def get_renderer(self, cleared=None):
250240
# Mirrors super.get_renderer, but caches the old one

‎src/_png.cpp

Copy file name to clipboardExpand all lines: src/_png.cpp
+122-8Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,27 @@ extern "C" {
3434
#undef jmpbuf
3535
#endif
3636

37+
struct buffer_t {
38+
PyObject *str;
39+
size_t cursor;
40+
size_t size;
41+
};
42+
43+
44+
static void write_png_data_buffer(png_structp png_ptr, png_bytep data, png_size_t length)
45+
{
46+
buffer_t *buff = (buffer_t *)png_get_io_ptr(png_ptr);
47+
if (buff->cursor + length < buff->size) {
48+
memcpy(PyBytes_AS_STRING(buff->str) + buff->cursor, data, length);
49+
buff->cursor += length;
50+
}
51+
}
52+
53+
static void flush_png_data_buffer(png_structp png_ptr)
54+
{
55+
56+
}
57+
3758
static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t length)
3859
{
3960
PyObject *py_file_obj = (PyObject *)png_get_io_ptr(png_ptr);
@@ -62,27 +83,66 @@ static void flush_png_data(png_structp png_ptr)
6283
Py_XDECREF(result);
6384
}
6485

65-
const char *Py_write_png__doc__ = "write_png(buffer, file, dpi=0)";
86+
const char *Py_write_png__doc__ =
87+
"write_png(buffer, file, dpi=0, compression=6, filter=auto)\n"
88+
"\n"
89+
"Parameters\n"
90+
"----------\n"
91+
"buffer : numpy array of image data\n"
92+
" Must be an MxNxD array of dtype uint8.\n"
93+
" - if D is 1, the image is greyscale\n"
94+
" - if D is 3, the image is RGB\n"
95+
" - if D is 4, the image is RGBA\n"
96+
"\n"
97+
"file : str path, file-like object or None\n"
98+
" - If a str, must be a file path\n"
99+
" - If a file-like object, must write bytes\n"
100+
" - If None, a byte string containing the PNG data will be returned\n"
101+
"\n"
102+
"dpi : float\n"
103+
" The dpi to store in the file metadata.\n"
104+
"\n"
105+
"compression : int\n"
106+
" The level of lossless zlib compression to apply. 0 indicates no\n"
107+
" compression. Values 1-9 indicate low/fast through high/slow\n"
108+
" compression. Default is 6.\n"
109+
"\n"
110+
"filter : int\n"
111+
" Filter to apply. Must be one of the constants: PNG_FILTER_NONE,\n"
112+
" PNG_FILTER_SUB, PNG_FILTER_UP, PNG_FILTER_AVG, PNG_FILTER_PAETH.\n"
113+
" See the PNG standard for more information.\n"
114+
" If not provided, libpng will try to automatically determine the\n"
115+
" best filter on a line-by-line basis.\n"
116+
"\n"
117+
"Returns\n"
118+
"-------\n"
119+
"buffer : bytes or None\n"
120+
" Byte string containing the PNG content if None was passed in for\n"
121+
" file, otherwise None is returned.\n";
66122

67123
static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
68124
{
69125
numpy::array_view<unsigned char, 3> buffer;
70126
PyObject *filein;
71127
double dpi = 0;
72-
const char *names[] = { "buffer", "file", "dpi", NULL };
128+
int compression = 6;
129+
int filter = -1;
130+
const char *names[] = { "buffer", "file", "dpi", "compression", "filter", NULL };
73131

74132
// We don't need strict contiguity, just for each row to be
75133
// contiguous, and libpng has special handling for getting RGB out
76134
// of RGBA, ARGB or BGR. But the simplest thing to do is to
77135
// enforce contiguity using array_view::converter_contiguous.
78136
if (!PyArg_ParseTupleAndKeywords(args,
79137
kwds,
80-
"O&O|d:write_png",
138+
"O&O|dii:write_png",
81139
(char **)names,
82140
&buffer.converter_contiguous,
83141
&buffer,
84142
&filein,
85-
&dpi)) {
143+
&dpi,
144+
&compression,
145+
&filter)) {
86146
return NULL;
87147
}
88148

@@ -104,6 +164,8 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
104164
png_infop info_ptr = NULL;
105165
struct png_color_8_struct sig_bit;
106166
int png_color_type;
167+
buffer_t buff;
168+
buff.str = NULL;
107169

108170
switch (channels) {
109171
case 1:
@@ -122,6 +184,12 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
122184
goto exit;
123185
}
124186

187+
if (compression < 0 || compression > 9) {
188+
PyErr_Format(PyExc_ValueError,
189+
"compression must be in range 0-9, got %d", compression);
190+
goto exit;
191+
}
192+
125193
if (PyBytes_Check(filein) || PyUnicode_Check(filein)) {
126194
if ((py_file = mpl_PyFile_OpenFile(filein, (char *)"wb")) == NULL) {
127195
goto exit;
@@ -160,6 +228,11 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
160228
goto exit;
161229
}
162230

231+
png_set_compression_level(png_ptr, compression);
232+
if (filter >= 0) {
233+
png_set_filter(png_ptr, 0, filter);
234+
}
235+
163236
info_ptr = png_create_info_struct(png_ptr);
164237
if (info_ptr == NULL) {
165238
PyErr_SetString(PyExc_RuntimeError, "Could not create info struct");
@@ -171,7 +244,9 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
171244
goto exit;
172245
}
173246

174-
if (fp) {
247+
if (buff.str) {
248+
png_set_write_fn(png_ptr, (void *)&buff, &write_png_data_buffer, &flush_png_data_buffer);
249+
} else if (fp) {
175250
png_init_io(png_ptr, fp);
176251
} else {
177252
png_set_write_fn(png_ptr, (void *)py_file, &write_png_data, &flush_png_data);
@@ -235,8 +310,13 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
235310
}
236311

237312
if (PyErr_Occurred()) {
313+
Py_XDECREF(buff.str);
238314
return NULL;
239315
} else {
316+
if (buff.str) {
317+
_PyBytes_Resize(&buff.str, buff.cursor);
318+
return buff.str;
319+
}
240320
Py_RETURN_NONE;
241321
}
242322
}
@@ -509,21 +589,46 @@ static PyObject *_read_png(PyObject *filein, bool float_result)
509589
}
510590
}
511591

512-
const char *Py_read_png_float__doc__ = "read_png_float(file)";
592+
const char *Py_read_png_float__doc__ =
593+
"read_png_float(file)\n"
594+
"\n"
595+
"Read in a PNG file, converting values to floating-point doubles\n"
596+
"in the range (0, 1)\n"
597+
"\n"
598+
"Parameters\n"
599+
"----------\n"
600+
"file : str path or file-like object\n";
513601

514602
static PyObject *Py_read_png_float(PyObject *self, PyObject *args, PyObject *kwds)
515603
{
516604
return _read_png(args, true);
517605
}
518606

519-
const char *Py_read_png_int__doc__ = "read_png_int(file)";
607+
const char *Py_read_png_int__doc__ =
608+
"read_png_int(file)\n"
609+
"\n"
610+
"Read in a PNG file with original integer values.\n"
611+
"\n"
612+
"Parameters\n"
613+
"----------\n"
614+
"file : str path or file-like object\n";
520615

521616
static PyObject *Py_read_png_int(PyObject *self, PyObject *args, PyObject *kwds)
522617
{
523618
return _read_png(args, false);
524619
}
525620

526-
const char *Py_read_png__doc__ = "read_png(file)";
621+
const char *Py_read_png__doc__ =
622+
"read_png(file)\n"
623+
"\n"
624+
"Read in a PNG file, converting values to floating-point doubles\n"
625+
"in the range (0, 1)\n"
626+
"\n"
627+
"Alias for read_png_float()\n"
628+
"\n"
629+
"Parameters\n"
630+
"----------\n"
631+
"file : str path or file-like object\n";
527632

528633
static PyMethodDef module_methods[] = {
529634
{"write_png", (PyCFunction)Py_write_png, METH_VARARGS|METH_KEYWORDS, Py_write_png__doc__},
@@ -573,6 +678,15 @@ extern "C" {
573678

574679
import_array();
575680

681+
if (PyModule_AddIntConstant(m, "PNG_FILTER_NONE", PNG_FILTER_NONE) ||
682+
PyModule_AddIntConstant(m, "PNG_FILTER_SUB", PNG_FILTER_SUB) ||
683+
PyModule_AddIntConstant(m, "PNG_FILTER_UP", PNG_FILTER_UP) ||
684+
PyModule_AddIntConstant(m, "PNG_FILTER_AVG", PNG_FILTER_AVG) ||
685+
PyModule_AddIntConstant(m, "PNG_FILTER_PAETH", PNG_FILTER_PAETH)) {
686+
INITERROR;
687+
}
688+
689+
576690
#if PY3K
577691
return m;
578692
#endif

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.