Skip to content

Commit

Permalink
Add HexTree-based RGBA image quantizer.
Browse files Browse the repository at this point in the history
- Move to numpy 2.0.
- Add HexTree-based image quantizer.
- Push version.
  • Loading branch information
cubicibo authored Oct 15, 2024
2 parents 13ad998 + 40e607f commit 34ae6ed
Show file tree
Hide file tree
Showing 10 changed files with 876 additions and 41 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
Collection of C extension modules for SUPer with pure-python fallbacks
- Brule: Bitmap RUn LEngth coder and decoder.
- LayoutEngine: An optimal two-rects layout finder for a sequence of images.
- HexTree: RGBA image quantizer.

## Brule brief
Brule implements 3 times the same run-length encoder and decoder.
Brule implements 2 or 3 times the same function, and select the fastest implementation available at runtime.
- C (fastest)
- numba (fast)
- numba (fast - implemented only for the RLE codec)
- pure Python (slow)

For the encoder, the C implementation is up to 20 times faster than numba. The pure Python implementation does not compete and is only there for convenience.

## Install
Given `./brule` the (cloned) repository:
```bash
$ python -m pip install brule
$ python -m pip install ./brule
```

## Example
## Example (RLE Codec)

The run-length codec is operated like this:
```python
Expand All @@ -29,5 +31,16 @@ The run-length codec is operated like this:
True
```

## Example (HexTree)
The image quantizer is used like this:
```python
>>> from brule import HexTree
>>> import numpy as np
>>> from PIL import Image
>>> rgba = np.asarray(Image.open(...).convert('RGBA'))
>>> #quantize with 255 colours
>>> bitmap, palette = HexTree.quantize(rgba, 255)
```

## Credits
- https://github.com/pallets/markupsafe markupsafe for the pip install fallback mechanism with compiled extensions.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[build-system]
requires = ["setuptools", "wheel", "Cython>=0.29", "numpy < 2.0"]
[build-system]
requires = ["setuptools", "wheel", "Cython>=0.29", "numpy>=2.0"]
15 changes: 12 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def build_extension(self, ext):
except (CCompilerError, ExecError, PlatformError) as e:
raise BuildFailed() from e
except ValueError as e:
import sys
# this can happen on Windows 64 bit, see Python issue 7511
if "'path'" in str(sys.exc_info()[1]): # works with Python 2 and 3
raise BuildFailed() from e
Expand Down Expand Up @@ -97,7 +98,15 @@ def show_message(*lines):
extra_compile_args=extra_compile_args,
)

modules = [brule_codec, layout_eng]
hextree = setuptools.Extension(
f"{NAME}._hextree",
sources=[f"src/{NAME}/_hextree.cc"],
include_dirs=[np.get_include()],
language="c",
extra_compile_args=extra_compile_args,
)

modules = [brule_codec, layout_eng, hextree]

def run_setup(modules):
setuptools.setup(
Expand All @@ -118,8 +127,8 @@ def run_setup(modules):
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.9',
],
python_requires='>=3.10',
install_requires=["numpy<2.0", "numba"],
python_requires='>=3.11',
install_requires=["numpy>=2.0.1", "numba", "anytree"],
zip_safe=False,
)
####run_setup
Expand Down
3 changes: 3 additions & 0 deletions src/brule/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@
SOFTWARE.
"""

from .__metadata__ import __version__, __author__

from .brule import Brule
from .layouteng import LayoutEngine
from .hextree import HexTree
2 changes: 1 addition & 1 deletion src/brule/__metadata__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__MAJOR, __MINOR, __REVISION = 0, 0, 3
__MAJOR, __MINOR, __REVISION = 0, 0, 4

__version__ = '.'.join(map(str, [__MAJOR, __MINOR, __REVISION]))
__author__ = 'cubicibo'
23 changes: 3 additions & 20 deletions src/brule/_brule.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>

#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/ndarraytypes.h>
#include <numpy/arrayobject.h>

Expand Down Expand Up @@ -112,7 +111,9 @@ PyObject* brule_decode(PyObject* self, PyObject* arg)
if (res.width && res.height) {
npy_intp dims[2] = {res.height, res.width};
PyArray_Descr *desc = PyArray_DescrFromType(NPY_BYTE);
PyObject *arr_obj = PyArray_NewFromDescr(&PyArray_Type, desc, 2, dims, NULL, res.data, NPY_ARRAY_OWNDATA, NULL);
PyObject *arr_obj = PyArray_NewFromDescr(&PyArray_Type, desc, 2, dims, NULL, res.data, 0, NULL);
PyArray_ENABLEFLAGS((PyArrayObject*)arr_obj, NPY_ARRAY_OWNDATA);

res.data = NULL; //do not own anymore, numpy will free it when no longer needed.
return arr_obj;
} else {
Expand All @@ -138,30 +139,12 @@ static PyMethodDef brule_functions[] = {
{ NULL, NULL, 0, NULL } /* marks end of array */
};

/*
* Initialize brule. May be called multiple times, so avoid
* using static state.
*/
int exec_brule(PyObject *module) {
PyModule_AddFunctions(module, brule_functions);

PyModule_AddStringConstant(module, "__author__", LRB_AUTHOR);
PyModule_AddStringConstant(module, "__version__", LRB_VERSION_STR);

return 0;
}

/*
* Documentation for brule.
*/
PyDoc_STRVAR(brule_doc, "Bitmap RUn LEngth module");


/*static PyModuleDef_Slot brule_slots[] = {
{ Py_mod_exec, exec_brule },
{ 0, NULL }
};*/

static PyModuleDef brule_def = {
PyModuleDef_HEAD_INIT,
"brule",
Expand Down
Loading

0 comments on commit 34ae6ed

Please sign in to comment.