diff --git a/Python/CMakeLists.txt b/Python/CMakeLists.txt index 101245a17..b45e94f6e 100644 --- a/Python/CMakeLists.txt +++ b/Python/CMakeLists.txt @@ -684,10 +684,14 @@ set(SYS_HEADERS set(UTIL_SOURCES Util/pyBitVector.cpp Util/pyDDSurface.cpp + Util/pyJPEG.cpp + Util/pyPNG.cpp ) set(UTIL_HEADERS Util/pyBitVector.h Util/pyDDSurface.h + Util/pyJPEG.h + Util/pyPNG.h ) set(VAULT_SOURCES diff --git a/Python/Module.cpp b/Python/Module.cpp index fb0c09152..c7bd3c259 100644 --- a/Python/Module.cpp +++ b/Python/Module.cpp @@ -29,6 +29,8 @@ #include "Sys/pyUnifiedTime.h" #include "Util/pyBitVector.h" #include "Util/pyDDSurface.h" +#include "Util/pyJPEG.h" +#include "Util/pyPNG.h" #include "Vault/pyServerGuid.h" #include "Vault/pyVaultNode.h" #include "Vault/pyVaultStore.h" @@ -487,6 +489,8 @@ PyMODINIT_FUNC PyInit_PyHSPlasma() /* Util */ PyModule_AddObject(module, "hsBitVector", Init_pyBitVector_Type()); PyModule_AddObject(module, "plDDSurface", Init_pyDDSurface_Type()); + PyModule_AddObject(module, "plJPEG", Init_pyJPEG_Type()); + PyModule_AddObject(module, "plPNG", Init_pyPNG_Type()); /* Vault */ PyModule_AddObject(module, "plServerGuid", Init_pyServerGuid_Type()); diff --git a/Python/PyHSPlasma.pyi b/Python/PyHSPlasma.pyi index cf72abc7f..0cbb4da26 100644 --- a/Python/PyHSPlasma.pyi +++ b/Python/PyHSPlasma.pyi @@ -3757,6 +3757,11 @@ class plInterfaceInfoModifier(plSingleModifier): def clearIntfKeys(self) -> None: ... def delIntfKey(self, idx: int) -> None: ... +class plJPEG: + @staticmethod + def DecompressJPEG(stream: hsStream) -> plMipmap: + ... + class plKey(Generic[KeyedT]): id: int = ... location: plLocation = ... @@ -4482,6 +4487,11 @@ class plPhysicalSndGroup(hsKeyedObject): class plPickingDetector(plDetectorModifier): ... +class plPNG: + @staticmethod + def DecompressPNG(stream: hsStream) -> plMipmap: + ... + class plPoint3Controller(plLeafController): ... diff --git a/Python/Util/pyJPEG.cpp b/Python/Util/pyJPEG.cpp new file mode 100644 index 000000000..8f8e946fa --- /dev/null +++ b/Python/Util/pyJPEG.cpp @@ -0,0 +1,73 @@ +/* This file is part of HSPlasma. + * + * HSPlasma is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HSPlasma is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HSPlasma. If not, see . + */ + +#include "pyJPEG.h" + +#include "PRP/Surface/pyBitmap.h" +#include "Stream/pyStream.h" + +#include + +PY_PLASMA_EMPTY_INIT(JPEG) +PY_PLASMA_NEW_MSG(JPEG, "plJPEG cannot be constructed") + +PY_METHOD_STATIC_VA(JPEG, DecompressJPEG, + "Params: stream\n" + "Read JPEG file from stream directly into a plMipmap") +{ + PyObject* streamObj; + if (!PyArg_ParseTuple(args, "O", &streamObj)) { + PyErr_SetString(PyExc_TypeError, "DecompressJPEG expects an hsStream"); + return nullptr; + } + if (!pyStream_Check(streamObj)) { + PyErr_SetString(PyExc_TypeError, "DecompressJPEG expects an hsStream"); + return nullptr; + } + + plMipmap* mm = plJPEG::DecompressJPEG(((pyStream*)streamObj)->fThis); + + // We're doing this manually because the new Mipmap object is being + // released to Python code. + pyMipmap* mmObj = nullptr; + try { + mmObj = PyObject_New(pyMipmap, &pyMipmap_Type); + } catch (const hsJPEGException& ex) { + PyErr_SetString(PyExc_RuntimeError, ex.what()); + return nullptr; + } + mmObj->fPyOwned = true; + mmObj->fThis = mm; + return (PyObject*)mmObj; +} + +static PyMethodDef pyJPEG_Methods[] = { + pyJPEG_DecompressJPEG_method, + PY_METHOD_TERMINATOR, +}; + +PY_PLASMA_TYPE(JPEG, plJPEG, "plJPEG wrapper") + +PY_PLASMA_TYPE_INIT(JPEG) +{ + pyJPEG_Type.tp_new = pyJPEG_new; + pyJPEG_Type.tp_methods = pyJPEG_Methods; + if (PyType_CheckAndReady(&pyJPEG_Type) < 0) + return nullptr; + + Py_INCREF(&pyJPEG_Type); + return (PyObject*)&pyJPEG_Type; +} diff --git a/Python/Util/pyJPEG.h b/Python/Util/pyJPEG.h new file mode 100644 index 000000000..041570c98 --- /dev/null +++ b/Python/Util/pyJPEG.h @@ -0,0 +1,24 @@ +/* This file is part of HSPlasma. + * + * HSPlasma is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HSPlasma is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HSPlasma. If not, see . + */ + +#ifndef _PYJPEG_H +#define _PYJPEG_H + +#include "PyPlasma.h" + +PY_WRAP_PLASMA(JPEG, class plJPEG); + +#endif diff --git a/Python/Util/pyPNG.cpp b/Python/Util/pyPNG.cpp new file mode 100644 index 000000000..5f9b53868 --- /dev/null +++ b/Python/Util/pyPNG.cpp @@ -0,0 +1,73 @@ +/* This file is part of HSPlasma. + * + * HSPlasma is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HSPlasma is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HSPlasma. If not, see . + */ + +#include "pyPNG.h" + +#include "PRP/Surface/pyBitmap.h" +#include "Stream/pyStream.h" + +#include + +PY_PLASMA_EMPTY_INIT(PNG) +PY_PLASMA_NEW_MSG(PNG, "plPNG cannot be constructed") + +PY_METHOD_STATIC_VA(PNG, DecompressPNG, + "Params: stream\n" + "Read PNG file from stream directly into a plMipmap") +{ + PyObject* streamObj; + if (!PyArg_ParseTuple(args, "O", &streamObj)) { + PyErr_SetString(PyExc_TypeError, "DecompressPNG expects an hsStream"); + return nullptr; + } + if (!pyStream_Check(streamObj)) { + PyErr_SetString(PyExc_TypeError, "DecompressPNG expects an hsStream"); + return nullptr; + } + + plMipmap* mm = nullptr; + try { + mm = plPNG::DecompressPNG(((pyStream*)streamObj)->fThis); + } catch (const hsPNGException& ex) { + PyErr_SetString(PyExc_RuntimeError, ex.what()); + return nullptr; + } + + // We're doing this manually because the new Mipmap object is being + // released to Python code. + pyMipmap* mmObj = PyObject_New(pyMipmap, &pyMipmap_Type); + mmObj->fPyOwned = true; + mmObj->fThis = mm; + return (PyObject*)mmObj; +} + +static PyMethodDef pyPNG_Methods[] = { + pyPNG_DecompressPNG_method, + PY_METHOD_TERMINATOR, +}; + +PY_PLASMA_TYPE(PNG, plPNG, "plPNG wrapper") + +PY_PLASMA_TYPE_INIT(PNG) +{ + pyPNG_Type.tp_new = pyPNG_new; + pyPNG_Type.tp_methods = pyPNG_Methods; + if (PyType_CheckAndReady(&pyPNG_Type) < 0) + return nullptr; + + Py_INCREF(&pyPNG_Type); + return (PyObject*)&pyPNG_Type; +} diff --git a/Python/Util/pyPNG.h b/Python/Util/pyPNG.h new file mode 100644 index 000000000..3d478ecf6 --- /dev/null +++ b/Python/Util/pyPNG.h @@ -0,0 +1,24 @@ +/* This file is part of HSPlasma. + * + * HSPlasma is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * HSPlasma is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with HSPlasma. If not, see . + */ + +#ifndef _PYPNG_H +#define _PYPNG_H + +#include "PyPlasma.h" + +PY_WRAP_PLASMA(PNG, class plPNG); + +#endif diff --git a/core/Util/plJPEG.cpp b/core/Util/plJPEG.cpp index ca7f47037..bfaedb97a 100644 --- a/core/Util/plJPEG.cpp +++ b/core/Util/plJPEG.cpp @@ -22,6 +22,7 @@ #include extern "C" { +#include #include } @@ -192,34 +193,58 @@ METHODDEF(void) plasma_output_message(j_common_ptr cinfo) METHODDEF(void) plasma_error_exit(j_common_ptr cinfo) { (*cinfo->err->output_message)(cinfo); - jpeg_destroy(cinfo); throw hsJPEGException(__FILE__, __LINE__, jpeg_error_buf); } - /* plJPEG */ -plJPEG& plJPEG::Instance() +class plJPEG_CInfo { - static plJPEG s_instance; - return s_instance; -} + jpeg_compress_struct fStruct; + jpeg_error_mgr fErrorMgr; -plJPEG::plJPEG() -{ - cinfo.err = jpeg_std_error(&jerr); - dinfo.err = cinfo.err; - cinfo.err->error_exit = plasma_error_exit; - cinfo.err->output_message = plasma_output_message; +public: + plJPEG_CInfo() + { + fStruct.err = jpeg_std_error(&fErrorMgr); + fErrorMgr.error_exit = plasma_error_exit; + fErrorMgr.output_message = plasma_output_message; - jpeg_create_compress(&cinfo); - jpeg_create_decompress(&dinfo); -} + jpeg_create_compress(&fStruct); + } -plJPEG::~plJPEG() + ~plJPEG_CInfo() + { + jpeg_destroy_compress(&fStruct); + } + + j_compress_ptr operator &() { return &fStruct; } + j_compress_ptr operator ->() { return &fStruct; } +}; + +class plJPEG_DInfo { - jpeg_destroy_compress(&cinfo); - jpeg_destroy_decompress(&dinfo); -} + jpeg_decompress_struct fStruct; + jpeg_error_mgr fErrorMgr; + +public: + plJPEG_DInfo() + { + fStruct.err = jpeg_std_error(&fErrorMgr); + fErrorMgr.error_exit = plasma_error_exit; + fErrorMgr.output_message = plasma_output_message; + + jpeg_create_decompress(&fStruct); + } + + ~plJPEG_DInfo() + { + jpeg_destroy_decompress(&fStruct); + } + + j_decompress_ptr operator &() { return &fStruct; } + j_decompress_ptr operator ->() { return &fStruct; } +}; + template struct RAII_JSAMPROW @@ -241,65 +266,65 @@ struct RAII_JSAMPROW void plJPEG::DecompressJPEG(hsStream* S, void* buf, size_t size) { - plJPEG& ji = Instance(); + plJPEG_DInfo dinfo; - jpeg_hsStream_src(&ji.dinfo, S); - jpeg_read_header(&ji.dinfo, TRUE); - ji.dinfo.out_color_space = JCS_EXT_BGRA; // Data stored as RGB on disk but Plasma uses BGR - jpeg_start_decompress(&ji.dinfo); + jpeg_hsStream_src(&dinfo, S); + jpeg_read_header(&dinfo, TRUE); + dinfo->out_color_space = JCS_EXT_BGRA; // Data stored as RGB on disk but Plasma uses BGR + jpeg_start_decompress(&dinfo); - int row_stride = ji.dinfo.output_width * ji.dinfo.out_color_components; + int row_stride = dinfo->output_width * dinfo->out_color_components; int out_stride = row_stride; RAII_JSAMPROW<1> jbuffer(row_stride); size_t offs = 0; - while (ji.dinfo.output_scanline < ji.dinfo.output_height) { + while (dinfo->output_scanline < dinfo->output_height) { if (offs + out_stride > size) throw hsJPEGException(__FILE__, __LINE__, "buffer overflow"); - jpeg_read_scanlines(&ji.dinfo, jbuffer.data, 1); - for (size_t x = 0; xoutput_width; x++) { memcpy(((unsigned char*)buf) + offs + (x * 4), - jbuffer.data[0] + (x * ji.dinfo.out_color_components), - ji.dinfo.out_color_components); + jbuffer.data[0] + (x * dinfo->out_color_components), + dinfo->out_color_components); } offs += out_stride; } - jpeg_finish_decompress(&ji.dinfo); + jpeg_finish_decompress(&dinfo); } plMipmap* plJPEG::DecompressJPEG(hsStream* S) { - plJPEG& ji = Instance(); + plJPEG_DInfo dinfo; - jpeg_hsStream_src(&ji.dinfo, S); - jpeg_read_header(&ji.dinfo, TRUE); - ji.dinfo.out_color_space = JCS_EXT_BGRA; // Data stored as RGB on disk but Plasma uses BGR - jpeg_start_decompress(&ji.dinfo); + jpeg_hsStream_src(&dinfo, S); + jpeg_read_header(&dinfo, TRUE); + dinfo->out_color_space = JCS_EXT_BGRA; // Data stored as RGB on disk but Plasma uses BGR + jpeg_start_decompress(&dinfo); - int row_stride = ji.dinfo.output_width * ji.dinfo.out_color_components; + int row_stride = dinfo->output_width * dinfo->out_color_components; int out_stride = row_stride; RAII_JSAMPROW<1> jbuffer(row_stride); // Start with a reasonable size for the buffer - auto buffer_size = ji.dinfo.output_width * ji.dinfo.output_height * ji.dinfo.out_color_components; + auto buffer_size = dinfo->output_width * dinfo->output_height * dinfo->out_color_components; auto buffer = std::make_unique(buffer_size); size_t offs = 0; - while (ji.dinfo.output_scanline < ji.dinfo.output_height) { - jpeg_read_scanlines(&ji.dinfo, jbuffer.data, 1); - for (size_t x = 0; x < ji.dinfo.output_width; x++) { + while (dinfo->output_scanline < dinfo->output_height) { + jpeg_read_scanlines(&dinfo, jbuffer.data, 1); + for (size_t x = 0; x < dinfo->output_width; x++) { memcpy(buffer.get() + offs + (x * 4), - jbuffer.data[0] + (x * ji.dinfo.out_color_components), - ji.dinfo.out_color_components); + jbuffer.data[0] + (x * dinfo->out_color_components), + dinfo->out_color_components); } offs += out_stride; } - jpeg_finish_decompress(&ji.dinfo); + jpeg_finish_decompress(&dinfo); - plMipmap* newMipmap = new plMipmap(ji.dinfo.output_width, ji.dinfo.output_height, 1, plMipmap::kUncompressed, plMipmap::kRGB8888); + plMipmap* newMipmap = new plMipmap(dinfo->output_width, dinfo->output_height, 1, plMipmap::kUncompressed, plMipmap::kRGB8888); newMipmap->setImageData(buffer.get(), buffer_size); return newMipmap; @@ -307,42 +332,40 @@ plMipmap* plJPEG::DecompressJPEG(hsStream* S) void plJPEG::CompressJPEG(hsStream* S, void* buf, size_t size, uint32_t width, uint32_t height, uint32_t bpp) { - plJPEG& ji = Instance(); + plJPEG_CInfo cinfo; JSAMPLE* image_buffer = reinterpret_cast(buf); - jpeg_create_compress(&ji.cinfo); - jpeg_hsStream_dest(&ji.cinfo, S); + jpeg_hsStream_dest(&cinfo, S); - ji.cinfo.image_width = width; - ji.cinfo.image_height = height; - ji.cinfo.input_components = bpp / 8; - if (ji.cinfo.input_components == 4) - ji.cinfo.in_color_space = JCS_EXT_RGBX; + cinfo->image_width = width; + cinfo->image_height = height; + cinfo->input_components = bpp / 8; + if (cinfo->input_components == 4) + cinfo->in_color_space = JCS_EXT_RGBX; else - ji.cinfo.in_color_space = JCS_RGB; + cinfo->in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, 100, TRUE); + jpeg_start_compress(&cinfo, TRUE); - jpeg_set_defaults(&ji.cinfo); - jpeg_set_quality(&ji.cinfo, 100, TRUE); - jpeg_start_compress(&ji.cinfo, TRUE); - - uint32_t row_stride = ji.cinfo.image_width * ji.cinfo.input_components; + uint32_t row_stride = cinfo->image_width * cinfo->input_components; RAII_JSAMPROW<1> jbuffer(row_stride); size_t offs = 0; - while (ji.cinfo.next_scanline < ji.cinfo.image_height) { + while (cinfo->next_scanline < cinfo->image_height) { if (offs + row_stride > size) throw hsJPEGException(__FILE__, __LINE__, "buffer overread"); - for (size_t x = 0; x < ji.cinfo.image_width; x++) { - memcpy(jbuffer.data[0] + (x * ji.cinfo.input_components), - ((unsigned char*)image_buffer) + offs + (x * ji.cinfo.input_components), - ji.cinfo.input_components); + for (size_t x = 0; x < cinfo->image_width; x++) { + memcpy(jbuffer.data[0] + (x * cinfo->input_components), + ((unsigned char*)image_buffer) + offs + (x * cinfo->input_components), + cinfo->input_components); } - (void)jpeg_write_scanlines(&ji.cinfo, jbuffer.data, 1); + (void)jpeg_write_scanlines(&cinfo, jbuffer.data, 1); offs += row_stride; } - jpeg_finish_compress(&ji.cinfo); - jpeg_destroy_compress(&ji.cinfo); + jpeg_finish_compress(&cinfo); } diff --git a/core/Util/plJPEG.h b/core/Util/plJPEG.h index 06966579f..a80c6fd86 100644 --- a/core/Util/plJPEG.h +++ b/core/Util/plJPEG.h @@ -20,10 +20,6 @@ #include "PlasmaDefs.h" #include "Debug/hsExceptions.hpp" -extern "C" { -#include -} - class hsJPEGException : public hsException { public: @@ -42,11 +38,6 @@ class plMipmap; class HSPLASMA_EXPORT plJPEG { -private: - jpeg_compress_struct cinfo; - jpeg_decompress_struct dinfo; - jpeg_error_mgr jerr; - public: /* Read JPEG file from stream into buffer as bitmap data. */ static void DecompressJPEG(hsStream* S, void* buf, size_t size); @@ -59,10 +50,7 @@ class HSPLASMA_EXPORT plJPEG uint32_t width, uint32_t height, uint32_t bpp); private: - plJPEG(); - ~plJPEG(); - - static plJPEG& Instance(); + plJPEG() = delete; }; #endif diff --git a/core/Util/plPNG.cpp b/core/Util/plPNG.cpp index ee1c8b3ce..7cb076d7b 100644 --- a/core/Util/plPNG.cpp +++ b/core/Util/plPNG.cpp @@ -20,6 +20,8 @@ #include +#include + /* Don't know why this isn't provided by libpng itself... */ #define PNG_SIG_LENGTH (8) diff --git a/core/Util/plPNG.h b/core/Util/plPNG.h index 7daae6372..6ce40b8c2 100644 --- a/core/Util/plPNG.h +++ b/core/Util/plPNG.h @@ -20,8 +20,6 @@ #include "PlasmaDefs.h" #include "Debug/hsExceptions.hpp" -#include - class hsPNGException : public hsException { public: