diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 600a52320..ee5a062c6 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -305,9 +305,9 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): class CoreResamplePassesTest(PillowTestCase): @contextmanager def count(self, diff): - count = Image.core.getcount() + count = Image.core.get_stats()['new_count'] yield - self.assertEqual(Image.core.getcount() - count, diff) + self.assertEqual(Image.core.get_stats()['new_count'] - count, diff) def test_horizontal(self): im = hopper('L') diff --git a/_imaging.c b/_imaging.c index 8bcc6afd2..b6a5e8d00 100644 --- a/_imaging.c +++ b/_imaging.c @@ -641,15 +641,6 @@ _new_block(PyObject* self, PyObject* args) return PyImagingNew(ImagingNewBlock(mode, xsize, ysize)); } -static PyObject* -_getcount(PyObject* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, ":getcount")) - return NULL; - - return PyInt_FromLong(ImagingDefaultArena.stats_new_count); -} - static PyObject* _linear_gradient(PyObject* self, PyObject* args) { @@ -3337,6 +3328,169 @@ static PyTypeObject PixelAccess_Type = { /* -------------------------------------------------------------------- */ +static PyObject* +_get_stats(PyObject* self, PyObject* args) +{ + PyObject* d; + ImagingMemoryArena arena = &ImagingDefaultArena; + + if (!PyArg_ParseTuple(args, ":get_stats")) + return NULL; + + d = PyDict_New(); + if ( ! d) + return NULL; + PyDict_SetItemString(d, "new_count", + PyInt_FromLong(arena->stats_new_count)); + PyDict_SetItemString(d, "allocated_blocks", + PyInt_FromLong(arena->stats_allocated_blocks)); + PyDict_SetItemString(d, "reused_blocks", + PyInt_FromLong(arena->stats_reused_blocks)); + PyDict_SetItemString(d, "reallocated_blocks", + PyInt_FromLong(arena->stats_reallocated_blocks)); + PyDict_SetItemString(d, "freed_blocks", + PyInt_FromLong(arena->stats_freed_blocks)); + return d; +} + +static PyObject* +_reset_stats(PyObject* self, PyObject* args) +{ + ImagingMemoryArena arena = &ImagingDefaultArena; + + if (!PyArg_ParseTuple(args, ":reset_stats")) + return NULL; + + arena->stats_new_count = 0; + arena->stats_allocated_blocks = 0; + arena->stats_reused_blocks = 0; + arena->stats_reallocated_blocks = 0; + arena->stats_freed_blocks = 0; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +_get_alignment(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, ":get_alignment")) + return NULL; + + return PyInt_FromLong(ImagingDefaultArena.alignment); +} + +static PyObject* +_get_block_size(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, ":get_block_size")) + return NULL; + + return PyInt_FromLong(ImagingDefaultArena.block_size); +} + +static PyObject* +_get_blocks_max(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, ":get_blocks_max")) + return NULL; + + return PyInt_FromLong(ImagingDefaultArena.blocks_max); +} + +static PyObject* +_get_blocks_free(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, ":get_blocks_free")) + return NULL; + + return PyInt_FromLong(ImagingDefaultArena.blocks_free); +} + +static PyObject* +_set_alignment(PyObject* self, PyObject* args) +{ + int alignment; + if (!PyArg_ParseTuple(args, "i:set_alignment", &alignment)) + return NULL; + + if (alignment < 1 || alignment > 128) { + PyErr_SetString(PyExc_ValueError, "alignment should be from 1 to 128"); + return NULL; + } + /* Is power of two */ + if (alignment & (alignment - 1)) { + PyErr_SetString(PyExc_ValueError, "alignment should be power of two"); + return NULL; + } + + ImagingDefaultArena.alignment = alignment; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +_set_block_size(PyObject* self, PyObject* args) +{ + int block_size; + if (!PyArg_ParseTuple(args, "i:set_block_size", &block_size)) + return NULL; + + if (block_size < 0) { + PyErr_SetString(PyExc_ValueError, + "block_size should be greater than 0"); + return NULL; + } + + if (block_size & 0xfff) { + PyErr_SetString(PyExc_ValueError, + "block_size should be multiple of 4096"); + return NULL; + } + + ImagingDefaultArena.block_size = block_size; + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +_set_blocks_max(PyObject* self, PyObject* args) +{ + int blocks_max; + if (!PyArg_ParseTuple(args, "i:set_blocks_max", &blocks_max)) + return NULL; + + if (blocks_max < 0) { + PyErr_SetString(PyExc_ValueError, + "blocks_max should be greater than 0"); + return NULL; + } + + if ( ! ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) { + ImagingError_MemoryError(); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* +_clear_cache(PyObject* self, PyObject* args) +{ + if (!PyArg_ParseTuple(args, ":_clear_cache")) + return NULL; + + ImagingMemoryClearCache(&ImagingDefaultArena); + + Py_INCREF(Py_None); + return Py_None; +} + +/* -------------------------------------------------------------------- */ + /* FIXME: this is something of a mess. Should replace this with pluggable codecs, but not before PIL 1.2 */ @@ -3400,8 +3554,6 @@ static PyMethodDef functions[] = { {"new", (PyCFunction)_new, 1}, {"merge", (PyCFunction)_merge, 1}, - {"getcount", (PyCFunction)_getcount, 1}, - /* Functions */ {"convert", (PyCFunction)_convert2, 1}, @@ -3491,6 +3643,18 @@ static PyMethodDef functions[] = { {"outline", (PyCFunction)PyOutline_Create, 1}, #endif + /* Resource management */ + {"get_stats", (PyCFunction)_get_stats, 1}, + {"reset_stats", (PyCFunction)_reset_stats, 1}, + {"get_alignment", (PyCFunction)_get_alignment, 1}, + {"get_block_size", (PyCFunction)_get_block_size, 1}, + {"get_blocks_max", (PyCFunction)_get_blocks_max, 1}, + {"get_blocks_free", (PyCFunction)_get_blocks_free, 1}, + {"set_alignment", (PyCFunction)_set_alignment, 1}, + {"set_block_size", (PyCFunction)_set_block_size, 1}, + {"set_blocks_max", (PyCFunction)_set_blocks_max, 1}, + {"clear_cache", (PyCFunction)_clear_cache, 1}, + {NULL, NULL} /* sentinel */ }; diff --git a/docs/releasenotes/4.3.0.rst b/docs/releasenotes/4.3.0.rst index 4708eeb29..b893a623a 100644 --- a/docs/releasenotes/4.3.0.rst +++ b/docs/releasenotes/4.3.0.rst @@ -52,3 +52,6 @@ identified the format of the clipboard data. The ``PIL.Image.core.copy`` and ``PIL.Image.Image.im.copy2`` methods have been removed. + +The ``PIL.Image.core.getcount`` methods have been removed, use +``PIL.Image.core.get_stats()['new_count']`` property instead. diff --git a/libImaging/Imaging.h b/libImaging/Imaging.h index 4f202b2a7..991808ca4 100644 --- a/libImaging/Imaging.h +++ b/libImaging/Imaging.h @@ -160,10 +160,10 @@ typedef struct ImagingMemoryArena { int blocks_free; void **blocks; int stats_new_count; - int stats_allocated_block; - int stats_reused_block; - int stats_reallocated_block; - int stats_freed_block; + int stats_allocated_blocks; + int stats_reused_blocks; + int stats_reallocated_blocks; + int stats_freed_blocks; } *ImagingMemoryArena; @@ -171,6 +171,8 @@ typedef struct ImagingMemoryArena { /* ------- */ extern struct ImagingMemoryArena ImagingDefaultArena; +extern int ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max); +extern void ImagingMemoryClearCache(ImagingMemoryArena arena); extern Imaging ImagingNew(const char* mode, int xsize, int ysize); extern Imaging ImagingNewDirty(const char* mode, int xsize, int ysize); diff --git a/libImaging/Storage.c b/libImaging/Storage.c index b8704edf5..565adc8a1 100644 --- a/libImaging/Storage.c +++ b/libImaging/Storage.c @@ -273,24 +273,47 @@ struct ImagingMemoryArena ImagingDefaultArena = { 0, 0, 0, 0, 0 // Stats }; -void -memory_set_blocks_max(ImagingMemoryArena arena, int blocks_max) +int +ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max) { + void *p; /* Free already cached blocks */ while (arena->blocks_free > blocks_max) { arena->blocks_free -= 1; free(arena->blocks[arena->blocks_free]); - arena->stats_freed_block += 1; + arena->stats_freed_blocks += 1; } - arena->blocks_max = blocks_max; if (blocks_max == 0 && arena->blocks != NULL) { free(arena->blocks); arena->blocks = NULL; } else if (arena->blocks != NULL) { - arena->blocks = realloc(arena->blocks, sizeof(void*) * blocks_max); + p = realloc(arena->blocks, sizeof(void*) * blocks_max); + if ( ! p) { + // Leave previous blocks_max value + return 0; + } + arena->blocks = p; } else { arena->blocks = calloc(sizeof(void*), blocks_max); + if ( ! arena->blocks) { + // Fallback to 0 + arena->blocks_max = 0; + return 0; + } + } + arena->blocks_max = blocks_max; + + return 1; +} + +void +ImagingMemoryClearCache(ImagingMemoryArena arena) +{ + while (arena->blocks_free > 0) { + arena->blocks_free -= 1; + free(arena->blocks[arena->blocks_free]); + arena->stats_freed_blocks += 1; } } @@ -303,12 +326,12 @@ memory_get_block(ImagingMemoryArena arena, int requested_size, int dirty) block = realloc(arena->blocks[arena->blocks_free], requested_size); if ( ! block) { free(arena->blocks[arena->blocks_free]); - arena->stats_freed_block += 1; + arena->stats_freed_blocks += 1; return NULL; } - arena->stats_reused_block += 1; + arena->stats_reused_blocks += 1; if (block != arena->blocks[arena->blocks_free]) { - arena->stats_reallocated_block += 1; + arena->stats_reallocated_blocks += 1; } if ( ! dirty) { memset(block, 0, requested_size); @@ -319,7 +342,7 @@ memory_get_block(ImagingMemoryArena arena, int requested_size, int dirty) } else { block = calloc(1, requested_size); } - arena->stats_allocated_block += 1; + arena->stats_allocated_blocks += 1; } return block; } @@ -332,7 +355,7 @@ memory_return_block(ImagingMemoryArena arena, void *block) arena->blocks_free += 1; } else { free(block); - arena->stats_freed_block += 1; + arena->stats_freed_blocks += 1; } }