@@ -34,6 +34,27 @@ extern "C" {
34
34
#undef jmpbuf
35
35
#endif
36
36
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
+
37
58
static void write_png_data (png_structp png_ptr, png_bytep data, png_size_t length)
38
59
{
39
60
PyObject *py_file_obj = (PyObject *)png_get_io_ptr (png_ptr);
@@ -62,27 +83,66 @@ static void flush_png_data(png_structp png_ptr)
62
83
Py_XDECREF (result);
63
84
}
64
85
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 " ;
66
122
67
123
static PyObject *Py_write_png (PyObject *self, PyObject *args, PyObject *kwds)
68
124
{
69
125
numpy::array_view<unsigned char , 3 > buffer;
70
126
PyObject *filein;
71
127
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 };
73
131
74
132
// We don't need strict contiguity, just for each row to be
75
133
// contiguous, and libpng has special handling for getting RGB out
76
134
// of RGBA, ARGB or BGR. But the simplest thing to do is to
77
135
// enforce contiguity using array_view::converter_contiguous.
78
136
if (!PyArg_ParseTupleAndKeywords (args,
79
137
kwds,
80
- " O&O|d :write_png" ,
138
+ " O&O|dii :write_png" ,
81
139
(char **)names,
82
140
&buffer.converter_contiguous ,
83
141
&buffer,
84
142
&filein,
85
- &dpi)) {
143
+ &dpi,
144
+ &compression,
145
+ &filter)) {
86
146
return NULL ;
87
147
}
88
148
@@ -104,6 +164,8 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
104
164
png_infop info_ptr = NULL ;
105
165
struct png_color_8_struct sig_bit;
106
166
int png_color_type;
167
+ buffer_t buff;
168
+ buff.str = NULL ;
107
169
108
170
switch (channels) {
109
171
case 1 :
@@ -122,6 +184,12 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
122
184
goto exit ;
123
185
}
124
186
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
+
125
193
if (PyBytes_Check (filein) || PyUnicode_Check (filein)) {
126
194
if ((py_file = mpl_PyFile_OpenFile (filein, (char *)" wb" )) == NULL ) {
127
195
goto exit ;
@@ -131,7 +199,14 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
131
199
py_file = filein;
132
200
}
133
201
134
- if ((fp = mpl_PyFile_Dup (py_file, (char *)" wb" , &offset))) {
202
+ if (filein == Py_None) {
203
+ buff.size = width * height * 4 + 1024 ;
204
+ buff.str = PyBytes_FromStringAndSize (NULL , buff.size );
205
+ if (buff.str == NULL ) {
206
+ goto exit ;
207
+ }
208
+ buff.cursor = 0 ;
209
+ } else if ((fp = mpl_PyFile_Dup (py_file, (char *)" wb" , &offset))) {
135
210
close_dup_file = true ;
136
211
} else {
137
212
PyErr_Clear ();
@@ -152,6 +227,11 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
152
227
goto exit ;
153
228
}
154
229
230
+ png_set_compression_level (png_ptr, compression);
231
+ if (filter >= 0 ) {
232
+ png_set_filter (png_ptr, 0 , filter);
233
+ }
234
+
155
235
info_ptr = png_create_info_struct (png_ptr);
156
236
if (info_ptr == NULL ) {
157
237
PyErr_SetString (PyExc_RuntimeError, " Could not create info struct" );
@@ -163,7 +243,9 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
163
243
goto exit ;
164
244
}
165
245
166
- if (fp) {
246
+ if (buff.str ) {
247
+ png_set_write_fn (png_ptr, (void *)&buff, &write_png_data_buffer, &flush_png_data_buffer);
248
+ } else if (fp) {
167
249
png_init_io (png_ptr, fp);
168
250
} else {
169
251
png_set_write_fn (png_ptr, (void *)py_file, &write_png_data, &flush_png_data);
@@ -227,8 +309,13 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
227
309
}
228
310
229
311
if (PyErr_Occurred ()) {
312
+ Py_XDECREF (buff.str );
230
313
return NULL ;
231
314
} else {
315
+ if (buff.str ) {
316
+ _PyBytes_Resize (&buff.str , buff.cursor );
317
+ return buff.str ;
318
+ }
232
319
Py_RETURN_NONE;
233
320
}
234
321
}
@@ -494,21 +581,46 @@ static PyObject *_read_png(PyObject *filein, bool float_result)
494
581
}
495
582
}
496
583
497
- const char *Py_read_png_float__doc__ = " read_png_float(file)" ;
584
+ const char *Py_read_png_float__doc__ =
585
+ " read_png_float(file)\n "
586
+ " \n "
587
+ " Read in a PNG file, converting values to floating-point doubles\n "
588
+ " in the range (0, 1)\n "
589
+ " \n "
590
+ " Parameters\n "
591
+ " ----------\n "
592
+ " file : str path or file-like object\n " ;
498
593
499
594
static PyObject *Py_read_png_float (PyObject *self, PyObject *args, PyObject *kwds)
500
595
{
501
596
return _read_png (args, true );
502
597
}
503
598
504
- const char *Py_read_png_int__doc__ = " read_png_int(file)" ;
599
+ const char *Py_read_png_int__doc__ =
600
+ " read_png_int(file)\n "
601
+ " \n "
602
+ " Read in a PNG file with original integer values.\n "
603
+ " \n "
604
+ " Parameters\n "
605
+ " ----------\n "
606
+ " file : str path or file-like object\n " ;
505
607
506
608
static PyObject *Py_read_png_int (PyObject *self, PyObject *args, PyObject *kwds)
507
609
{
508
610
return _read_png (args, false );
509
611
}
510
612
511
- const char *Py_read_png__doc__ = " read_png(file)" ;
613
+ const char *Py_read_png__doc__ =
614
+ " read_png(file)\n "
615
+ " \n "
616
+ " Read in a PNG file, converting values to floating-point doubles\n "
617
+ " in the range (0, 1)\n "
618
+ " \n "
619
+ " Alias for read_png_float()\n "
620
+ " \n "
621
+ " Parameters\n "
622
+ " ----------\n "
623
+ " file : str path or file-like object\n " ;
512
624
513
625
static PyMethodDef module_methods[] = {
514
626
{" write_png" , (PyCFunction)Py_write_png, METH_VARARGS|METH_KEYWORDS, Py_write_png__doc__},
@@ -558,6 +670,15 @@ extern "C" {
558
670
559
671
import_array ();
560
672
673
+ if (PyModule_AddIntConstant (m, " PNG_FILTER_NONE" , PNG_FILTER_NONE) ||
674
+ PyModule_AddIntConstant (m, " PNG_FILTER_SUB" , PNG_FILTER_SUB) ||
675
+ PyModule_AddIntConstant (m, " PNG_FILTER_UP" , PNG_FILTER_UP) ||
676
+ PyModule_AddIntConstant (m, " PNG_FILTER_AVG" , PNG_FILTER_AVG) ||
677
+ PyModule_AddIntConstant (m, " PNG_FILTER_PAETH" , PNG_FILTER_PAETH)) {
678
+ INITERROR;
679
+ }
680
+
681
+
561
682
#if PY3K
562
683
return m;
563
684
#endif
0 commit comments