|
| 1 | +#include <png.h> |
| 2 | + |
| 3 | +// To remove a gcc warning |
| 4 | +#ifdef _POSIX_C_SOURCE |
| 5 | +#undef _POSIX_C_SOURCE |
| 6 | +#endif |
| 7 | + |
| 8 | +// TODO: Un CXX-ify this module |
| 9 | +#include "CXX/Extensions.hxx" |
| 10 | +#include "numpy/arrayobject.h" |
| 11 | +#include "mplutils.h" |
| 12 | + |
| 13 | +// the extension module |
| 14 | +class _png_module : public Py::ExtensionModule<_png_module> |
| 15 | +{ |
| 16 | +public: |
| 17 | + _png_module() |
| 18 | + : Py::ExtensionModule<_png_module>( "_png" ) |
| 19 | + { |
| 20 | + add_varargs_method("write_png", &_png_module::write_png, |
| 21 | + "write_png(buffer, width, height, fileobj, dpi=None)"); |
| 22 | + add_varargs_method("read_png", &_png_module::write_png, |
| 23 | + "read_png(fileobj)"); |
| 24 | + initialize("Module to write PNG files"); |
| 25 | + } |
| 26 | + |
| 27 | + virtual ~_png_module() {} |
| 28 | + |
| 29 | +private: |
| 30 | + Py::Object write_png(const Py::Tuple& args); |
| 31 | + Py::Object read_png(const Py::Tuple& args); |
| 32 | +}; |
| 33 | + |
| 34 | +static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t length) { |
| 35 | + printf("%x %x %d %x\n", png_ptr, data, *data, length); |
| 36 | + PyObject* py_file_obj = (PyObject*)png_get_io_ptr(png_ptr); |
| 37 | + PyObject* write_method = PyObject_GetAttrString(py_file_obj, "write"); |
| 38 | + PyObject* result = NULL; |
| 39 | + if (write_method) |
| 40 | + result = PyObject_CallFunction(write_method, (char *)"s#", data, length); |
| 41 | + Py_XDECREF(write_method); |
| 42 | + Py_XDECREF(result); |
| 43 | +} |
| 44 | + |
| 45 | +static void flush_png_data(png_structp png_ptr) { |
| 46 | + PyObject* py_file_obj = (PyObject*)png_get_io_ptr(png_ptr); |
| 47 | + PyObject* flush_method = PyObject_GetAttrString(py_file_obj, "flush"); |
| 48 | + PyObject* result = NULL; |
| 49 | + if (flush_method) |
| 50 | + result = PyObject_CallFunction(flush_method, (char *)""); |
| 51 | + Py_XDECREF(flush_method); |
| 52 | + Py_XDECREF(result); |
| 53 | +} |
| 54 | + |
| 55 | +// this code is heavily adapted from the paint license, which is in |
| 56 | +// the file paint.license (BSD compatible) included in this |
| 57 | +// distribution. TODO, add license file to MANIFEST.in and CVS |
| 58 | +Py::Object _png_module::write_png(const Py::Tuple& args) |
| 59 | +{ |
| 60 | + args.verify_length(4, 5); |
| 61 | + |
| 62 | + FILE *fp = NULL; |
| 63 | + bool close_file = false; |
| 64 | + Py::Object buffer_obj = Py::Object(args[0]); |
| 65 | + PyObject* buffer = buffer_obj.ptr(); |
| 66 | + if (!PyObject_CheckReadBuffer(buffer)) { |
| 67 | + throw Py::TypeError("First argument must be an rgba buffer."); |
| 68 | + } |
| 69 | + |
| 70 | + const void* pixBufferPtr = NULL; |
| 71 | + Py_ssize_t pixBufferLength = 0; |
| 72 | + if (PyObject_AsReadBuffer(buffer, &pixBufferPtr, &pixBufferLength)) { |
| 73 | + throw Py::ValueError("Couldn't get data from read buffer."); |
| 74 | + } |
| 75 | + |
| 76 | + png_byte* pixBuffer = (png_byte*)pixBufferPtr; |
| 77 | + int width = (int)Py::Int(args[1]); |
| 78 | + int height = (int)Py::Int(args[2]); |
| 79 | + |
| 80 | + if (pixBufferLength < width * height * 4) { |
| 81 | + throw Py::ValueError("Buffer and width, height don't seem to match."); |
| 82 | + } |
| 83 | + |
| 84 | + Py::Object py_fileobj = Py::Object(args[3]); |
| 85 | + if (py_fileobj.isString()) { |
| 86 | + std::string fileName = Py::String(py_fileobj); |
| 87 | + const char *file_name = fileName.c_str(); |
| 88 | + if ((fp = fopen(file_name, "wb")) == NULL) |
| 89 | + throw Py::RuntimeError( Printf("Could not open file %s", file_name).str() ); |
| 90 | + close_file = true; |
| 91 | + } else if (PyFile_CheckExact(py_fileobj.ptr())) { |
| 92 | + fp = PyFile_AsFile(py_fileobj.ptr()); |
| 93 | + } |
| 94 | + else { |
| 95 | + PyObject* write_method = PyObject_GetAttrString(py_fileobj.ptr(), "write"); |
| 96 | + if (!(write_method && PyCallable_Check(write_method))) { |
| 97 | + Py_XDECREF(write_method); |
| 98 | + throw Py::TypeError("Object does not appear to be a 8-bit string path or a Python file-like object"); |
| 99 | + } |
| 100 | + Py_XDECREF(write_method); |
| 101 | + } |
| 102 | + |
| 103 | + png_bytep *row_pointers = NULL; |
| 104 | + png_structp png_ptr = NULL; |
| 105 | + png_infop info_ptr = NULL; |
| 106 | + |
| 107 | + try { |
| 108 | + struct png_color_8_struct sig_bit; |
| 109 | + png_uint_32 row; |
| 110 | + |
| 111 | + row_pointers = new png_bytep[height]; |
| 112 | + for (row = 0; row < (png_uint_32)height; ++row) { |
| 113 | + row_pointers[row] = pixBuffer + row * width * 4; |
| 114 | + } |
| 115 | + |
| 116 | + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| 117 | + if (png_ptr == NULL) { |
| 118 | + throw Py::RuntimeError("Could not create write struct"); |
| 119 | + } |
| 120 | + |
| 121 | + info_ptr = png_create_info_struct(png_ptr); |
| 122 | + if (info_ptr == NULL) { |
| 123 | + throw Py::RuntimeError("Could not create info struct"); |
| 124 | + } |
| 125 | + |
| 126 | + if (setjmp(png_ptr->jmpbuf)) { |
| 127 | + throw Py::RuntimeError("Error building image"); |
| 128 | + } |
| 129 | + |
| 130 | + if (fp) { |
| 131 | + png_init_io(png_ptr, fp); |
| 132 | + } else { |
| 133 | + png_set_write_fn(png_ptr, (void*)py_fileobj.ptr(), |
| 134 | + &write_png_data, &flush_png_data); |
| 135 | + } |
| 136 | + png_set_IHDR(png_ptr, info_ptr, |
| 137 | + width, height, 8, |
| 138 | + PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, |
| 139 | + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); |
| 140 | + |
| 141 | + // Save the dpi of the image in the file |
| 142 | + if (args.size() == 5) { |
| 143 | + double dpi = Py::Float(args[4]); |
| 144 | + size_t dots_per_meter = (size_t)(dpi / (2.54 / 100.0)); |
| 145 | + png_set_pHYs(png_ptr, info_ptr, dots_per_meter, dots_per_meter, PNG_RESOLUTION_METER); |
| 146 | + } |
| 147 | + |
| 148 | + // this a a color image! |
| 149 | + sig_bit.gray = 0; |
| 150 | + sig_bit.red = 8; |
| 151 | + sig_bit.green = 8; |
| 152 | + sig_bit.blue = 8; |
| 153 | + /* if the image has an alpha channel then */ |
| 154 | + sig_bit.alpha = 8; |
| 155 | + png_set_sBIT(png_ptr, info_ptr, &sig_bit); |
| 156 | + |
| 157 | + png_write_info(png_ptr, info_ptr); |
| 158 | + png_write_image(png_ptr, row_pointers); |
| 159 | + png_write_end(png_ptr, info_ptr); |
| 160 | + } catch (...) { |
| 161 | + if (fp && close_file) fclose(fp); |
| 162 | + delete [] row_pointers; |
| 163 | + /* Changed calls to png_destroy_write_struct to follow |
| 164 | + http://www.libpng.org/pub/png/libpng-manual.txt. |
| 165 | + This ensures the info_ptr memory is released. |
| 166 | + */ |
| 167 | + if (png_ptr && info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); |
| 168 | + throw; |
| 169 | + } |
| 170 | + |
| 171 | + png_destroy_write_struct(&png_ptr, &info_ptr); |
| 172 | + delete [] row_pointers; |
| 173 | + if (fp && close_file) fclose(fp); |
| 174 | + |
| 175 | + return Py::Object(); |
| 176 | +} |
| 177 | + |
| 178 | + |
| 179 | +Py::Object |
| 180 | +_png_module::read_png(const Py::Tuple& args) { |
| 181 | + |
| 182 | + args.verify_length(1); |
| 183 | + std::string fname = Py::String(args[0]); |
| 184 | + |
| 185 | + png_byte header[8]; // 8 is the maximum size that can be checked |
| 186 | + |
| 187 | + FILE *fp = fopen(fname.c_str(), "rb"); |
| 188 | + if (!fp) |
| 189 | + throw Py::RuntimeError(Printf("_image_module::readpng could not open PNG file %s for reading", fname.c_str()).str()); |
| 190 | + |
| 191 | + if (fread(header, 1, 8, fp) != 8) |
| 192 | + throw Py::RuntimeError("_image_module::readpng: error reading PNG header"); |
| 193 | + if (png_sig_cmp(header, 0, 8)) |
| 194 | + throw Py::RuntimeError("_image_module::readpng: file not recognized as a PNG file"); |
| 195 | + |
| 196 | + |
| 197 | + /* initialize stuff */ |
| 198 | + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| 199 | + |
| 200 | + if (!png_ptr) |
| 201 | + throw Py::RuntimeError("_image_module::readpng: png_create_read_struct failed"); |
| 202 | + |
| 203 | + png_infop info_ptr = png_create_info_struct(png_ptr); |
| 204 | + if (!info_ptr) |
| 205 | + throw Py::RuntimeError("_image_module::readpng: png_create_info_struct failed"); |
| 206 | + |
| 207 | + if (setjmp(png_jmpbuf(png_ptr))) |
| 208 | + throw Py::RuntimeError("_image_module::readpng: error during init_io"); |
| 209 | + |
| 210 | + png_init_io(png_ptr, fp); |
| 211 | + png_set_sig_bytes(png_ptr, 8); |
| 212 | + |
| 213 | + png_read_info(png_ptr, info_ptr); |
| 214 | + |
| 215 | + png_uint_32 width = info_ptr->width; |
| 216 | + png_uint_32 height = info_ptr->height; |
| 217 | + |
| 218 | + // convert misc color types to rgb for simplicity |
| 219 | + if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY || |
| 220 | + info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) |
| 221 | + png_set_gray_to_rgb(png_ptr); |
| 222 | + else if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) |
| 223 | + png_set_palette_to_rgb(png_ptr); |
| 224 | + |
| 225 | + |
| 226 | + int bit_depth = info_ptr->bit_depth; |
| 227 | + if (bit_depth == 16) png_set_strip_16(png_ptr); |
| 228 | + |
| 229 | + |
| 230 | + png_set_interlace_handling(png_ptr); |
| 231 | + png_read_update_info(png_ptr, info_ptr); |
| 232 | + |
| 233 | + bool rgba = info_ptr->color_type == PNG_COLOR_TYPE_RGBA; |
| 234 | + if ( (info_ptr->color_type != PNG_COLOR_TYPE_RGB) && !rgba) { |
| 235 | + std::cerr << "Found color type " << (int)info_ptr->color_type << std::endl; |
| 236 | + throw Py::RuntimeError("_image_module::readpng: cannot handle color_type"); |
| 237 | + } |
| 238 | + |
| 239 | + /* read file */ |
| 240 | + if (setjmp(png_jmpbuf(png_ptr))) |
| 241 | + throw Py::RuntimeError("_image_module::readpng: error during read_image"); |
| 242 | + |
| 243 | + png_bytep *row_pointers = new png_bytep[height]; |
| 244 | + png_uint_32 row; |
| 245 | + |
| 246 | + for (row = 0; row < height; row++) |
| 247 | + row_pointers[row] = new png_byte[png_get_rowbytes(png_ptr,info_ptr)]; |
| 248 | + |
| 249 | + png_read_image(png_ptr, row_pointers); |
| 250 | + |
| 251 | + |
| 252 | + |
| 253 | + int dimensions[3]; |
| 254 | + dimensions[0] = height; //numrows |
| 255 | + dimensions[1] = width; //numcols |
| 256 | + dimensions[2] = 4; |
| 257 | + |
| 258 | + PyArrayObject *A = (PyArrayObject *) PyArray_FromDims(3, dimensions, PyArray_FLOAT); |
| 259 | + |
| 260 | + |
| 261 | + for (png_uint_32 y = 0; y < height; y++) { |
| 262 | + png_byte* row = row_pointers[y]; |
| 263 | + for (png_uint_32 x = 0; x < width; x++) { |
| 264 | + |
| 265 | + png_byte* ptr = (rgba) ? &(row[x*4]) : &(row[x*3]); |
| 266 | + size_t offset = y*A->strides[0] + x*A->strides[1]; |
| 267 | + //if ((y<10)&&(x==10)) std::cout << "r = " << ptr[0] << " " << ptr[0]/255.0 << std::endl; |
| 268 | + *(float*)(A->data + offset + 0*A->strides[2]) = (float)(ptr[0]/255.0f); |
| 269 | + *(float*)(A->data + offset + 1*A->strides[2]) = (float)(ptr[1]/255.0f); |
| 270 | + *(float*)(A->data + offset + 2*A->strides[2]) = (float)(ptr[2]/255.0f); |
| 271 | + *(float*)(A->data + offset + 3*A->strides[2]) = rgba ? (float)(ptr[3]/255.0f) : 1.0f; |
| 272 | + } |
| 273 | + } |
| 274 | + |
| 275 | + //free the png memory |
| 276 | + png_read_end(png_ptr, info_ptr); |
| 277 | + png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); |
| 278 | + fclose(fp); |
| 279 | + for (row = 0; row < height; row++) |
| 280 | + delete [] row_pointers[row]; |
| 281 | + delete [] row_pointers; |
| 282 | + return Py::asObject((PyObject*)A); |
| 283 | +} |
| 284 | + |
| 285 | +extern "C" |
| 286 | + DL_EXPORT(void) |
| 287 | + init_png(void) |
| 288 | +{ |
| 289 | + import_array(); |
| 290 | + |
| 291 | + static _png_module* _png = NULL; |
| 292 | + _png = new _png_module; |
| 293 | +} |
0 commit comments