@@ -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+ 
3758static  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
67123static  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" 
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;
@@ -131,7 +199,14 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
131199        py_file = filein;
132200    }
133201
134-     if  ((fp = mpl_PyFile_Dup (py_file, (char  *)" wb" 
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" 
135210        close_dup_file = true ;
136211    } else  {
137212        PyErr_Clear ();
@@ -152,6 +227,11 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
152227        goto  exit;
153228    }
154229
230+     png_set_compression_level (png_ptr, compression);
231+     if  (filter >= 0 ) {
232+         png_set_filter (png_ptr, 0 , filter);
233+     }
234+ 
155235    info_ptr = png_create_info_struct (png_ptr);
156236    if  (info_ptr == NULL ) {
157237        PyErr_SetString (PyExc_RuntimeError, " Could not create info struct" 
@@ -163,7 +243,9 @@ static PyObject *Py_write_png(PyObject *self, PyObject *args, PyObject *kwds)
163243        goto  exit;
164244    }
165245
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) {
167249        png_init_io (png_ptr, fp);
168250    } else  {
169251        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)
227309    }
228310
229311    if  (PyErr_Occurred ()) {
312+         Py_XDECREF (buff.str );
230313        return  NULL ;
231314    } else  {
315+         if  (buff.str ) {
316+             _PyBytes_Resize (&buff.str , buff.cursor );
317+             return  buff.str ;
318+         }
232319        Py_RETURN_NONE;
233320    }
234321}
@@ -494,21 +581,46 @@ static PyObject *_read_png(PyObject *filein, bool float_result)
494581    }
495582}
496583
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 " 
498593
499594static  PyObject *Py_read_png_float (PyObject *self, PyObject *args, PyObject *kwds)
500595{
501596    return  _read_png (args, true );
502597}
503598
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 " 
505607
506608static  PyObject *Py_read_png_int (PyObject *self, PyObject *args, PyObject *kwds)
507609{
508610    return  _read_png (args, false );
509611}
510612
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 " 
512624
513625static  PyMethodDef module_methods[] = {
514626    {" write_png" 
@@ -558,6 +670,15 @@ extern "C" {
558670
559671        import_array ();
560672
673+         if  (PyModule_AddIntConstant (m, " PNG_FILTER_NONE" 
674+             PyModule_AddIntConstant (m, " PNG_FILTER_SUB" 
675+             PyModule_AddIntConstant (m, " PNG_FILTER_UP" 
676+             PyModule_AddIntConstant (m, " PNG_FILTER_AVG" 
677+             PyModule_AddIntConstant (m, " PNG_FILTER_PAETH" 
678+             INITERROR;
679+         }
680+ 
681+ 
561682#if  PY3K
562683        return  m;
563684#endif 
0 commit comments