diff --git a/README.md b/README.md index 72e446d..194b744 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,58 @@ +![popsicgl](./docs/assets/popsicgl.WEBP) + # pysicgl pysicgl is a Python C extension interface for the [sicgl](https://github.com/oclyke/sicgl) graphics library. both projects are young and would benefit from community involvement. + +# getting started as a developer + +**get submodules** + +```bash +git submodule update --init --recursive +``` + +**set up the python environment** + +* remove any existing virtual environment +* create a new virtual environment +* activate the virtual environment +* install development dependencies + +```bash +rm -rf venv +python3 -m venv venv # use your Python 3 interpreter +source venv/bin/activate +pip install -r requirements.dev.txt +``` + +**build and develop pysicl** + +```bash +python setup.py build +python setup.py develop +``` + +**run tests and install** + +```bash +python -m pytest +python setup.py test +python setup.py install +``` + +# formatting + +``` +source venv/bin/activate +./scripts/third-party/run-clang-format/run-clang-format.py -r include src +black . +``` + +# design choices + +## color sequences + +color sequences are immutable. side effects are not allowed. diff --git a/docs/assets/popsicgl.WEBP b/docs/assets/popsicgl.WEBP new file mode 100644 index 0000000..71b4146 Binary files /dev/null and b/docs/assets/popsicgl.WEBP differ diff --git a/docs/assets/popsicle-bit-art.png b/docs/assets/popsicle-bit-art.png new file mode 100644 index 0000000..0437b12 Binary files /dev/null and b/docs/assets/popsicle-bit-art.png differ diff --git a/examples/simple.py b/examples/simple.py index bdb76ad..f7918b8 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -15,7 +15,7 @@ display = pysicgl.Interface(display_screen, display_memory) # create a orange-red color using a 4-tuple of RGBA components -color = pysicgl.Color.from_rgba((255, 128, 3, 0)) +color = pysicgl.color.from_rgba((255, 128, 3, 0)) # draw a pixel directly to the interface origin # the coordinates are given in the interface-relative system diff --git a/include/pysicgl.h b/include/pysicgl.h deleted file mode 100644 index bc45e63..0000000 --- a/include/pysicgl.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "pysicgl/color_sequence.h" -#include "pysicgl/drawing/blit.h" -#include "pysicgl/drawing/compose.h" -#include "pysicgl/drawing/field.h" -#include "pysicgl/drawing/global.h" -#include "pysicgl/drawing/interface.h" -#include "pysicgl/drawing/screen.h" -#include "pysicgl/field.h" -#include "pysicgl/interface.h" -#include "pysicgl/screen.h" -#include "pysicgl/utilities.h" diff --git a/include/pysicgl/color.h b/include/pysicgl/color.h deleted file mode 100644 index b2de505..0000000 --- a/include/pysicgl/color.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include "sicgl/color.h" - -// declare the type -extern PyTypeObject ColorType; - -typedef struct { - PyObject_HEAD -} ColorObject; diff --git a/include/pysicgl/color_sequence.h b/include/pysicgl/color_sequence.h deleted file mode 100644 index 737a7d0..0000000 --- a/include/pysicgl/color_sequence.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include "sicgl/color_sequence.h" - -// declare the type -extern PyTypeObject ColorSequenceType; - -typedef struct { - PyObject_HEAD PyObject* _colors; - int _type; - sequence_map_fn _interpolation_map_fn; -} ColorSequenceObject; - -int ColorSequence_post_ready_init(); - -int ColorSequence_get( - ColorSequenceObject* self, size_t* len, color_t* colors_out, - size_t colors_out_len); -int ColorSequence_get_interp_map_fn( - size_t interp_type, sequence_map_fn* fn_out); diff --git a/include/pysicgl/drawing/blit.h b/include/pysicgl/drawing/blit.h deleted file mode 100644 index 03a91c7..0000000 --- a/include/pysicgl/drawing/blit.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -PyObject* blit(PyObject* self, PyObject* args); diff --git a/include/pysicgl/drawing/compose.h b/include/pysicgl/drawing/compose.h deleted file mode 100644 index dcf22df..0000000 --- a/include/pysicgl/drawing/compose.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -PyObject* compose(PyObject* self_in, PyObject* args); diff --git a/include/pysicgl/drawing/field.h b/include/pysicgl/drawing/field.h deleted file mode 100644 index bfd469d..0000000 --- a/include/pysicgl/drawing/field.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -PyObject* scalar_field(PyObject* self_in, PyObject* args); diff --git a/include/pysicgl/submodules/color.h b/include/pysicgl/submodules/color.h new file mode 100644 index 0000000..cfd5caf --- /dev/null +++ b/include/pysicgl/submodules/color.h @@ -0,0 +1,7 @@ +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +PyMODINIT_FUNC PyInit_color(void); diff --git a/include/pysicgl/submodules/composition.h b/include/pysicgl/submodules/composition.h new file mode 100644 index 0000000..d39c908 --- /dev/null +++ b/include/pysicgl/submodules/composition.h @@ -0,0 +1,7 @@ +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +PyMODINIT_FUNC PyInit_composition(void); diff --git a/include/pysicgl/submodules/functional.h b/include/pysicgl/submodules/functional.h new file mode 100644 index 0000000..2827734 --- /dev/null +++ b/include/pysicgl/submodules/functional.h @@ -0,0 +1,7 @@ +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +PyMODINIT_FUNC PyInit_functional(void); diff --git a/include/pysicgl/submodules/functional/color.h b/include/pysicgl/submodules/functional/color.h new file mode 100644 index 0000000..bb91214 --- /dev/null +++ b/include/pysicgl/submodules/functional/color.h @@ -0,0 +1,8 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +PyObject* color_from_rgba(PyObject* self, PyObject* args); +PyObject* color_to_rgba(PyObject* self, PyObject* args); +PyObject* interpolate_color_sequence( + PyObject* self_in, PyObject* args, PyObject* kwds); diff --git a/include/pysicgl/submodules/functional/color_correction.h b/include/pysicgl/submodules/functional/color_correction.h new file mode 100644 index 0000000..318c15c --- /dev/null +++ b/include/pysicgl/submodules/functional/color_correction.h @@ -0,0 +1,5 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +PyObject* gamma_correct(PyObject* self, PyObject* args); diff --git a/include/pysicgl/drawing/global.h b/include/pysicgl/submodules/functional/drawing/global.h similarity index 91% rename from include/pysicgl/drawing/global.h rename to include/pysicgl/submodules/functional/drawing/global.h index 36a3bcf..b6d2285 100644 --- a/include/pysicgl/drawing/global.h +++ b/include/pysicgl/submodules/functional/drawing/global.h @@ -2,7 +2,7 @@ #define PY_SSIZE_T_CLEAN #include -// python includes must come first +// python includes first (clang-format) PyObject* global_pixel(PyObject* self_in, PyObject* args); PyObject* global_line(PyObject* self_in, PyObject* args); diff --git a/include/pysicgl/drawing/interface.h b/include/pysicgl/submodules/functional/drawing/interface.h similarity index 75% rename from include/pysicgl/drawing/interface.h rename to include/pysicgl/submodules/functional/drawing/interface.h index 24683ec..9168531 100644 --- a/include/pysicgl/drawing/interface.h +++ b/include/pysicgl/submodules/functional/drawing/interface.h @@ -2,7 +2,10 @@ #define PY_SSIZE_T_CLEAN #include -// python includes must come first +// python includes first (clang-format) + +PyObject* interface_compose(PyObject* self_in, PyObject* args); +PyObject* interface_blit(PyObject* self_in, PyObject* args); PyObject* interface_fill(PyObject* self_in, PyObject* args); PyObject* interface_pixel(PyObject* self_in, PyObject* args); diff --git a/include/pysicgl/drawing/screen.h b/include/pysicgl/submodules/functional/drawing/screen.h similarity index 92% rename from include/pysicgl/drawing/screen.h rename to include/pysicgl/submodules/functional/drawing/screen.h index c7c6223..478b205 100644 --- a/include/pysicgl/drawing/screen.h +++ b/include/pysicgl/submodules/functional/drawing/screen.h @@ -2,7 +2,7 @@ #define PY_SSIZE_T_CLEAN #include -// python includes must come first +// python includes first (clang-format) PyObject* screen_fill(PyObject* self_in, PyObject* args); PyObject* screen_pixel(PyObject* self_in, PyObject* args); diff --git a/include/pysicgl/submodules/functional/operations.h b/include/pysicgl/submodules/functional/operations.h new file mode 100644 index 0000000..e3dbd08 --- /dev/null +++ b/include/pysicgl/submodules/functional/operations.h @@ -0,0 +1,8 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +PyObject* scalar_field(PyObject* self_in, PyObject* args, PyObject* kwds); +PyObject* compose(PyObject* self_in, PyObject* args); +PyObject* blit(PyObject* self_in, PyObject* args); +PyObject* scale(PyObject* self_in, PyObject* args); diff --git a/include/pysicgl/submodules/interpolation.h b/include/pysicgl/submodules/interpolation.h new file mode 100644 index 0000000..330a1f7 --- /dev/null +++ b/include/pysicgl/submodules/interpolation.h @@ -0,0 +1,7 @@ +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +PyMODINIT_FUNC PyInit_interpolation(void); diff --git a/include/pysicgl/types/color_sequence.h b/include/pysicgl/types/color_sequence.h new file mode 100644 index 0000000..b7adfad --- /dev/null +++ b/include/pysicgl/types/color_sequence.h @@ -0,0 +1,20 @@ +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/color_sequence_interpolator.h" +#include "sicgl/color_sequence.h" + +// declare the type +extern PyTypeObject ColorSequenceType; + +typedef struct { + PyObject_HEAD color_sequence_t sequence; + ColorSequenceInterpolatorObject* interpolator; + + // iterator state + // protected by the GIL + size_t iterator_index; +} ColorSequenceObject; diff --git a/include/pysicgl/types/color_sequence_interpolator.h b/include/pysicgl/types/color_sequence_interpolator.h new file mode 100644 index 0000000..9f578a4 --- /dev/null +++ b/include/pysicgl/types/color_sequence_interpolator.h @@ -0,0 +1,18 @@ +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "sicgl/color_sequence.h" + +// declare the type +extern PyTypeObject ColorSequenceInterpolatorType; + +typedef struct { + PyObject_HEAD sequence_map_fn fn; + void* args; +} ColorSequenceInterpolatorObject; + +ColorSequenceInterpolatorObject* new_color_sequence_interpolator_object( + sequence_map_fn fn, void* args); diff --git a/include/pysicgl/types/compositor.h b/include/pysicgl/types/compositor.h new file mode 100644 index 0000000..37e0a31 --- /dev/null +++ b/include/pysicgl/types/compositor.h @@ -0,0 +1,23 @@ +#pragma once + +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include + +#include "sicgl/compose.h" +#include "sicgl/compositors.h" + +// declare the type +extern PyTypeObject CompositorType; + +typedef struct { + PyObject_HEAD + /* Type-specific fields go here. */ + compositor_fn fn; + void* args; +} CompositorObject; + +// public constructors +CompositorObject* new_compositor_object(compositor_fn fn, void* args); diff --git a/include/pysicgl/interface.h b/include/pysicgl/types/interface.h similarity index 76% rename from include/pysicgl/interface.h rename to include/pysicgl/types/interface.h index b6f07b3..f08abb7 100644 --- a/include/pysicgl/interface.h +++ b/include/pysicgl/types/interface.h @@ -2,9 +2,9 @@ #define PY_SSIZE_T_CLEAN #include -// python includes must come first +// python includes first (clang-format) -#include "pysicgl/screen.h" +#include "pysicgl/types/screen.h" #include "sicgl/interface.h" #include "sicgl/screen.h" @@ -18,8 +18,8 @@ typedef struct { // a ScreenObject which is linked to // the interface screen by reference - ScreenObject* _screen; + ScreenObject* screen; // a buffer backs up the interface memory - Py_buffer _memory_buffer; + Py_buffer memory_buffer; } InterfaceObject; diff --git a/include/pysicgl/field.h b/include/pysicgl/types/scalar_field.h similarity index 51% rename from include/pysicgl/field.h rename to include/pysicgl/types/scalar_field.h index 22ceb51..b8467da 100644 --- a/include/pysicgl/field.h +++ b/include/pysicgl/types/scalar_field.h @@ -2,7 +2,7 @@ #define PY_SSIZE_T_CLEAN #include -// python includes must come first +// python includes first (clang-format) #include "sicgl/field.h" @@ -10,8 +10,6 @@ extern PyTypeObject ScalarFieldType; typedef struct { - PyObject_HEAD Py_buffer _scalars_buffer; + PyObject_HEAD double* scalars; + size_t length; } ScalarFieldObject; - -int scalar_field_get_scalars( - ScalarFieldObject* self, size_t* len, double** scalars); diff --git a/include/pysicgl/screen.h b/include/pysicgl/types/screen.h similarity index 72% rename from include/pysicgl/screen.h rename to include/pysicgl/types/screen.h index 9baee32..af02cc4 100644 --- a/include/pysicgl/screen.h +++ b/include/pysicgl/types/screen.h @@ -2,7 +2,7 @@ #define PY_SSIZE_T_CLEAN #include -// python includes must come first +// python includes first (clang-format) #include @@ -18,8 +18,5 @@ typedef struct { screen_t _screen; // a flag to explicitly indicate whether this is an object or reference - bool _is_reference; + bool is_reference; } ScreenObject; - -// publicly accessible constructors -ScreenObject* new_screen_object(screen_t* ref); diff --git a/include/pysicgl/utilities.h b/include/pysicgl/utilities.h deleted file mode 100644 index 07a9da3..0000000 --- a/include/pysicgl/utilities.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include "sicgl/extent.h" - -// int unpack_ext_t_tuple2(PyObject* obj, ext_t* u, ext_t* v); diff --git a/packages/pysicgl/__init__.py b/packages/pysicgl/__init__.py new file mode 100644 index 0000000..6eb8646 --- /dev/null +++ b/packages/pysicgl/__init__.py @@ -0,0 +1 @@ +from ._core import * diff --git a/requirements.dev.txt b/requirements.dev.txt index 5ffbba5..f21434a 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -1,3 +1,4 @@ black >= 22.12.0, < 23.0.0 pytest >= 7.2.1, < 8.0.0 +setuptools >= 67.0.0, < 68.0.0 sphinx >= 6.1.3, < 7.0.0 diff --git a/setup.py b/setup.py index 0af6f79..751c63c 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from distutils.core import setup, Extension +from setuptools import setup, Extension, find_packages from pathlib import Path, PurePath # source files for sicgl @@ -12,19 +12,27 @@ sicgl_sources = list( str(PurePath(sicgl_root_dir, "src", source)) for source in [ + "compositors/alpha.c", + "compositors/bitwise.c", + "compositors/channelwise.c", + "compositors/direct.c", + "domain/global.c", + "domain/interface.c", + "domain/screen.c", + "private/direct.c", + "private/interpolation.c", + "blend.c", + "blenders.c", "blit.c", "color_sequence.c", "compose.c", "field.c", "gamma.c", + "interface.c", "iter.c", "screen.c", "translate.c", - "domain/global.c", - "domain/interface.c", - "domain/screen.c", - "private/direct.c", - "private/interpolation.c", + "unity_color.c", ] ) @@ -38,29 +46,39 @@ pysicgl_sources = list( str(PurePath(pysicgl_root_dir, "src", source)) for source in [ + "submodules/composition/module.c", + "submodules/functional/drawing/global.c", + "submodules/functional/drawing/interface.c", + "submodules/functional/drawing/screen.c", + "submodules/functional/color.c", + "submodules/functional/color_correction.c", + "submodules/functional/module.c", + "submodules/functional/operations.c", + "submodules/interpolation/module.c", + "types/color_sequence/type.c", + "types/color_sequence_interpolator/type.c", + "types/compositor/type.c", + "types/scalar_field/type.c", + "types/interface/type.c", + "types/screen/type.c", "module.c", - "color.c", - "color_sequence.c", - "field.c", - "interface.c", - "screen.c", - "utilities.c", - "drawing/blit.c", - "drawing/compose.c", - "drawing/field.c", - "drawing/interface.c", - "drawing/screen.c", - "drawing/global.c", ] ) -pysicgl = Extension( - "pysicgl", +sicgl_core = Extension( + "pysicgl._core", include_dirs=[*pysicgl_include_dirs, *sicgl_include_dirs], sources=[*pysicgl_sources, *sicgl_sources], + extra_compile_args=[ + "-Werror", + "-Wall", "-Wextra", "-pedantic", + "-Wno-missing-field-initializers", "-Wno-sign-compare", "-Wno-sometimes-uninitialized", + ], ) setup( - ext_modules=[pysicgl], + ext_modules=[sicgl_core], + packages=find_packages(where="packages"), + package_dir={'': 'packages'}, setup_requires=["setuptools_scm"], ) diff --git a/src/color.c b/src/color.c deleted file mode 100644 index e8ecf69..0000000 --- a/src/color.c +++ /dev/null @@ -1,45 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include -#include - -#include "pysicgl/color.h" -#include "sicgl/color.h" - -// methods -static PyObject* get_rgba(PyObject* self, PyObject* input) { - color_t color = PyLong_AsLong(input); - return PyTuple_Pack( - 4, PyLong_FromLong(color_channel_red(color)), - PyLong_FromLong(color_channel_green(color)), - PyLong_FromLong(color_channel_blue(color)), - PyLong_FromLong(color_channel_alpha(color))); -} - -static PyObject* from_rgba(PyObject* self, PyObject* input) { - return PyLong_FromLong(color_from_channels( - PyLong_AsLong(PyTuple_GetItem(input, 0)), - PyLong_AsLong(PyTuple_GetItem(input, 1)), - PyLong_AsLong(PyTuple_GetItem(input, 2)), - PyLong_AsLong(PyTuple_GetItem(input, 3)))); -} - -static PyMethodDef tp_methods[] = { - {"get_rgba", (PyCFunction)get_rgba, METH_O | METH_STATIC, - "return the individual RGBA components of the input color as a 4-tuple"}, - {"from_rgba", (PyCFunction)from_rgba, METH_O | METH_STATIC, - "return the color comprised of the RGBA input 4-tuple"}, - {NULL}, -}; - -PyTypeObject ColorType = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pysicgl.Color", - .tp_doc = PyDoc_STR("sicgl color"), - .tp_basicsize = sizeof(ColorObject), - .tp_itemsize = 0, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = PyType_GenericNew, - .tp_methods = tp_methods, -}; diff --git a/src/color_sequence.c b/src/color_sequence.c deleted file mode 100644 index 8bc935a..0000000 --- a/src/color_sequence.c +++ /dev/null @@ -1,321 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include - -#include "pysicgl/color_sequence.h" - -// fwd declarations -static Py_ssize_t mp_length(PyObject* self_in); - -// data -/** - * @brief interpolation type names. - * These names each correspond to a particular interpolation style that can - * be used by the color sequence. - */ -typedef struct _interp_type_entry { - char* name; - sequence_map_fn map; -} interp_type_enty_t; -static const interp_type_enty_t interp_types[] = { - {.name = "CONTINUOUS_CIRCULAR", - .map = color_sequence_interpolate_color_continuous_circular}, - {.name = "CONTINUOUS_LINEAR", - .map = color_sequence_interpolate_color_continuous_linear}, - {.name = "DISCRETE_CIRCULAR", - .map = color_sequence_interpolate_color_discrete_circular}, - {.name = "DISCRETE_LINEAR", - .map = color_sequence_interpolate_color_discrete_linear}, -}; -static const size_t num_interp_types = - sizeof(interp_types) / sizeof(interp_type_enty_t); - -// utilities for C consumers -int ColorSequence_get( - ColorSequenceObject* self, size_t* len, color_t* colors_out, - size_t colors_out_len) { - int ret = 0; - if (NULL == self) { - ret = -1; - goto out; - } - - // provide the length of the sequence - size_t length = mp_length((PyObject*)self); - if (NULL != len) { - *len = length; - } - - // fill the color output buffer - if (NULL != colors_out) { - size_t elements_out = colors_out_len; - if (elements_out > length) { - elements_out = length; - } - for (size_t idx = 0; idx < elements_out; idx++) { - colors_out[idx] = PyLong_AsLong(PyList_GetItem(self->_colors, idx)); - } - } - -out: - return ret; -} - -int ColorSequence_get_interp_map_fn( - size_t interp_type, sequence_map_fn* fn_out) { - int ret = 0; - // determine the interpolation function - if (interp_type > num_interp_types) { - ret = -EINVAL; - goto out; - } - if (NULL == fn_out) { - ret = -ENOMEM; - goto out; - } - - // provide the corresponding map function - *fn_out = interp_types[interp_type].map; - -out: - return ret; -} - -// getset -static PyObject* get_colors(PyObject* self_in, void* closure) { - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - Py_INCREF(self->_colors); - return self->_colors; -} - -static int set_colors(PyObject* self_in, PyObject* value, void* closure) { - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - if (!PyObject_IsInstance((PyObject*)value, (PyObject*)&PyList_Type)) { - PyErr_SetNone(PyExc_TypeError); - return -1; - } - - if (NULL != self->_colors) { - Py_DECREF(self->_colors); - } - - self->_colors = value; - Py_INCREF(self->_colors); - - return 0; -} - -// methods -static PyObject* interpolate( - PyObject* self_in, PyObject* args, PyObject* kwds) { - int ret = 0; - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - PyObject* samples_obj; - unsigned int interp_type = 0; - char* keywords[] = { - "samples", - "interp_type", - NULL, - }; - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O|I", keywords, &samples_obj, &interp_type)) { - return NULL; - } - - if (interp_type > num_interp_types) { - PyErr_SetNone(PyExc_ValueError); - return NULL; - } - - // determine color sequence length - size_t len; - ret = ColorSequence_get(self, &len, NULL, 0); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - // allocate space on the stack for the colors - color_t colors[len]; - ret = ColorSequence_get(self, NULL, colors, len); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - // create the color_sequence_t for sicgl - color_sequence_t sequence = { - .colors = colors, - .length = len, - }; - - // determine the interpolation function - sequence_map_fn interp_fn; - ret = ColorSequence_get_interp_map_fn(interp_type, &interp_fn); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - // use this sequences' interpolation method to handle the input - PyObject* result = NULL; - if (PyLong_Check(samples_obj)) { - // input is a single sample, return the interpolated color directly - color_t color; - ret = interp_fn(&sequence, (double)PyLong_AsLong(samples_obj), &color); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - result = PyLong_FromLong(color); - - } else if (PyFloat_Check(samples_obj)) { - // input is a single sample, return the interpolated color directly - color_t color; - ret = interp_fn(&sequence, PyFloat_AsDouble(samples_obj), &color); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - result = PyLong_FromLong(color); - } else if (PyList_Check(samples_obj)) { - // input is a list of samples, return a tuple of interpolated colors - size_t num_samples = PyList_Size(samples_obj); - result = PyTuple_New(num_samples); - for (size_t idx = 0; idx < num_samples; idx++) { - color_t color; - ret = interp_fn( - &sequence, PyFloat_AsDouble(PyList_GetItem(samples_obj, idx)), - &color); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - ret = PyTuple_SetItem(result, idx, PyLong_FromLong(color)); - if (0 != ret) { - return NULL; - } - } - } else if (PyTuple_Check(samples_obj)) { - // input is a tuple of samples, return a tuple of interpolated colors - size_t num_samples = PyTuple_Size(samples_obj); - result = PyTuple_New(num_samples); - for (size_t idx = 0; idx < num_samples; idx++) { - color_t color; - ret = interp_fn( - &sequence, PyFloat_AsDouble(PyTuple_GetItem(samples_obj, idx)), - &color); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - ret = PyTuple_SetItem(result, idx, PyLong_FromLong(color)); - if (0 != ret) { - return NULL; - } - } - } else { - PyErr_SetNone(PyExc_TypeError); - return NULL; - } - - return result; -} - -static Py_ssize_t mp_length(PyObject* self_in) { - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - return PyList_Size(self->_colors); -} - -static PyObject* mp_subscript(PyObject* self_in, PyObject* key) { - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - return PyList_GetItem(self->_colors, PyLong_AsLong(key)); -} - -static int mp_ass_subscript(PyObject* self_in, PyObject* key, PyObject* v) { - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - if (!PyLong_Check(v)) { - PyErr_SetNone(PyExc_TypeError); - return -1; - } - return PyList_SetItem(self->_colors, PyLong_AsLong(key), v); -} - -static void tp_dealloc(PyObject* self_in) { - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - Py_XDECREF(self->_colors); - Py_TYPE(self)->tp_free(self); -} - -static int tp_init(PyObject* self_in, PyObject* args, PyObject* kwds) { - ColorSequenceObject* self = (ColorSequenceObject*)self_in; - char* keywords[] = { - "colors", - NULL, - }; - PyObject* colors_obj = PyList_New(0); - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O", keywords, &colors_obj)) { - return -1; - } - - int ret = set_colors((PyObject*)self, colors_obj, NULL); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return -1; - } - - return 0; -} - -static PyGetSetDef tp_getset[] = { - {"colors", get_colors, set_colors, "list of colors in this sequence", NULL}, - {NULL}, -}; - -static PyMethodDef tp_methods[] = { - {"interpolate", (PyCFunctionWithKeywords)interpolate, - METH_VARARGS | METH_KEYWORDS, - "interpolate the color sequence at one or more points using the given " - "interpolation type"}, - {NULL}, -}; - -static PyMappingMethods tp_as_mapping = { - .mp_length = mp_length, - .mp_subscript = mp_subscript, - .mp_ass_subscript = mp_ass_subscript, -}; - -PyTypeObject ColorSequenceType = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pysicgl.ColorSequence", - .tp_doc = PyDoc_STR("sicgl color"), - .tp_basicsize = sizeof(ColorSequenceObject), - .tp_itemsize = 0, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = PyType_GenericNew, - .tp_dealloc = tp_dealloc, - .tp_init = tp_init, - .tp_getset = tp_getset, - .tp_methods = tp_methods, - .tp_as_mapping = &tp_as_mapping, -}; - -// additional initialization for this type -int ColorSequence_post_ready_init() { - int ret = 0; - - // add INTERP constants to class dict - for (size_t idx = 0; idx < num_interp_types; idx++) { - ret = PyDict_SetItem( - ColorSequenceType.tp_dict, - PyUnicode_FromFormat("INTERP_%s", interp_types[idx].name), - PyLong_FromLong(idx)); - if (0 != ret) { - return ret; - } - } - - return ret; -} diff --git a/src/drawing/blit.c b/src/drawing/blit.c deleted file mode 100644 index f2d8b68..0000000 --- a/src/drawing/blit.c +++ /dev/null @@ -1,28 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include "pysicgl/drawing/blit.h" -#include "pysicgl/interface.h" -#include "pysicgl/screen.h" -#include "sicgl/blit.h" - -PyObject* blit(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen; - Py_buffer sprite; - if (!PyArg_ParseTuple(args, "O!y*", &ScreenType, &screen, &sprite)) { - return NULL; - } - - int ret = sicgl_blit(&self->interface, screen->screen, sprite.buf); - - PyBuffer_Release(&sprite); - - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - return Py_None; -} diff --git a/src/drawing/compose.c b/src/drawing/compose.c deleted file mode 100644 index 205ce4d..0000000 --- a/src/drawing/compose.c +++ /dev/null @@ -1,151 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include - -#include "pysicgl/drawing/blit.h" -#include "pysicgl/drawing/compose.h" -#include "pysicgl/interface.h" -#include "pysicgl/screen.h" -#include "sicgl/compose.h" - -static inline color_t clamp_u8(color_t channel) { - if (channel > 255) { - return 255; - } else if (channel < 0) { - return 0; - } else { - return channel; - } -} - -static void compositor_set(color_t* source, color_t* dest, size_t width) { - memcpy(dest, source, width * bytes_per_pixel()); -} - -static void compositor_add_clamped( - color_t* source, color_t* dest, size_t width) { - for (size_t idx = 0; idx < width; idx++) { - dest[idx] = color_from_channels( - clamp_u8(color_channel_red(dest[idx]) + color_channel_red(source[idx])), - clamp_u8( - color_channel_green(dest[idx]) + color_channel_green(source[idx])), - clamp_u8( - color_channel_blue(dest[idx]) + color_channel_blue(source[idx])), - clamp_u8( - color_channel_alpha(dest[idx]) + color_channel_alpha(source[idx]))); - } -} - -static void compositor_subtract_clamped( - color_t* source, color_t* dest, size_t width) { - for (size_t idx = 0; idx < width; idx++) { - dest[idx] = color_from_channels( - clamp_u8(color_channel_red(dest[idx]) - color_channel_red(source[idx])), - clamp_u8( - color_channel_green(dest[idx]) - color_channel_green(source[idx])), - clamp_u8( - color_channel_blue(dest[idx]) - color_channel_blue(source[idx])), - clamp_u8( - color_channel_alpha(dest[idx]) - color_channel_alpha(source[idx]))); - } -} - -static void compositor_multiply_clamped( - color_t* source, color_t* dest, size_t width) { - for (size_t idx = 0; idx < width; idx++) { - dest[idx] = color_from_channels( - clamp_u8(color_channel_red(dest[idx]) * color_channel_red(source[idx])), - clamp_u8( - color_channel_green(dest[idx]) * color_channel_green(source[idx])), - clamp_u8( - color_channel_blue(dest[idx]) * color_channel_blue(source[idx])), - clamp_u8( - color_channel_alpha(dest[idx]) * color_channel_alpha(source[idx]))); - } -} - -static void compositor_AND(color_t* source, color_t* dest, size_t width) { - for (size_t idx = 0; idx < width; idx++) { - dest[idx] = color_from_channels( - color_channel_red(dest[idx]) & color_channel_red(source[idx]), - color_channel_green(dest[idx]) & color_channel_green(source[idx]), - color_channel_blue(dest[idx]) & color_channel_blue(source[idx]), - color_channel_alpha(dest[idx]) & color_channel_alpha(source[idx])); - } -} - -static void compositor_OR(color_t* source, color_t* dest, size_t width) { - for (size_t idx = 0; idx < width; idx++) { - dest[idx] = color_from_channels( - color_channel_red(dest[idx]) | color_channel_red(source[idx]), - color_channel_green(dest[idx]) | color_channel_green(source[idx]), - color_channel_blue(dest[idx]) | color_channel_blue(source[idx]), - color_channel_alpha(dest[idx]) | color_channel_alpha(source[idx])); - } -} - -static void compositor_XOR(color_t* source, color_t* dest, size_t width) { - for (size_t idx = 0; idx < width; idx++) { - dest[idx] = color_from_channels( - color_channel_red(dest[idx]) ^ color_channel_red(source[idx]), - color_channel_green(dest[idx]) ^ color_channel_green(source[idx]), - color_channel_blue(dest[idx]) ^ color_channel_blue(source[idx]), - color_channel_alpha(dest[idx]) ^ color_channel_alpha(source[idx])); - } -} - -PyObject* compose(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen; - Py_buffer sprite; - int mode; - if (!PyArg_ParseTuple(args, "O!y*i", &ScreenType, &screen, &sprite, &mode)) { - return NULL; - } - - compositor_fn compositor = NULL; - switch (mode) { - case 0: - compositor = compositor_set; - break; - - case 1: - compositor = compositor_add_clamped; - break; - - case 2: - compositor = compositor_subtract_clamped; - break; - - case 3: - compositor = compositor_multiply_clamped; - break; - - case 4: - compositor = compositor_AND; - break; - - case 5: - compositor = compositor_OR; - break; - - case 6: - compositor = compositor_XOR; - break; - - default: - PyErr_SetNone(PyExc_ValueError); - return NULL; - break; - } - - int ret = - sicgl_compose(&self->interface, screen->screen, sprite.buf, compositor); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} diff --git a/src/drawing/field.c b/src/drawing/field.c deleted file mode 100644 index 5d66956..0000000 --- a/src/drawing/field.c +++ /dev/null @@ -1,78 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include "pysicgl/color_sequence.h" -#include "pysicgl/drawing/blit.h" -#include "pysicgl/field.h" -#include "pysicgl/interface.h" -#include "pysicgl/screen.h" -#include "sicgl/blit.h" - -PyObject* scalar_field(PyObject* self_in, PyObject* args, PyObject* kwds) { - int ret = 0; - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* field_obj; - ScalarFieldObject* scalar_field_obj; - ColorSequenceObject* color_sequence_obj; - unsigned int interp_type = 0; - double offset = 0.0; - char* keywords[] = { - "field", "scalars", "color_sequence", "interp_type", "offset", NULL, - }; - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O!O!O!|Id", keywords, &ScreenType, &field_obj, - &ScalarFieldType, &scalar_field_obj, &ColorSequenceType, - &color_sequence_obj, &interp_type, &offset)) { - return NULL; - } - - // determine color sequence length - size_t len; - ret = ColorSequence_get(color_sequence_obj, &len, NULL, 0); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - // allocate space on the stack for the colors - color_t colors[len]; - ret = ColorSequence_get(color_sequence_obj, NULL, colors, len); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - // create the color_sequence_t for sicgl - color_sequence_t sequence = { - .colors = colors, - .length = len, - }; - - // get the scalars - double* scalars; - size_t scalars_length; - ret = scalar_field_get_scalars(scalar_field_obj, &scalars_length, &scalars); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - sequence_map_fn interp_map_fn; - ret = ColorSequence_get_interp_map_fn(interp_type, &interp_map_fn); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - // apply the field - ret = sicgl_scalar_field( - &self->interface, field_obj->screen, scalars, offset, &sequence, - interp_map_fn); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - return Py_None; -} diff --git a/src/drawing/global.c b/src/drawing/global.c deleted file mode 100644 index 6784140..0000000 --- a/src/drawing/global.c +++ /dev/null @@ -1,105 +0,0 @@ -#include "pysicgl/drawing/global.h" - -#include - -#include "pysicgl/interface.h" -#include "sicgl/domain/global.h" - -PyObject* global_pixel(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u, v; - if (!PyArg_ParseTuple(args, "i(ii)", &color, &u, &v)) { - return NULL; - } - - int ret = sicgl_global_pixel(&self->interface, color, u, v); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* global_line(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple(args, "O!i(ii)(ii)", &color, &u0, &v0, &u1, &v1)) { - return NULL; - } - - int ret = sicgl_global_line(&self->interface, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* global_rectangle(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple(args, "O!i(ii)(ii)", &color, &u0, &v0, &u1, &v1)) { - return NULL; - } - - int ret = sicgl_global_rectangle(&self->interface, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* global_rectangle_filled(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple(args, "O!i(ii)(ii)", &color, &u0, &v0, &u1, &v1)) { - return NULL; - } - - int ret = - sicgl_global_rectangle_filled(&self->interface, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* global_circle(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, diameter; - if (!PyArg_ParseTuple(args, "O!i(ii)(ii)", &color, &u0, &v0, &diameter)) { - return NULL; - } - - int ret = - sicgl_global_circle_ellipse(&self->interface, color, u0, v0, diameter); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* global_ellipse(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, semiu, semiv; - if (!PyArg_ParseTuple( - args, "O!i(ii)(ii)", &color, &u0, &v0, &semiu, &semiv)) { - return NULL; - } - - int ret = sicgl_global_ellipse(&self->interface, color, u0, v0, semiu, semiv); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} diff --git a/src/drawing/interface.c b/src/drawing/interface.c deleted file mode 100644 index 8985c3c..0000000 --- a/src/drawing/interface.c +++ /dev/null @@ -1,118 +0,0 @@ -#include "pysicgl/interface.h" - -#include "pysicgl/utilities.h" -#include "sicgl/domain/interface.h" - -PyObject* interface_fill(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - if (!PyArg_ParseTuple(args, "i", &color)) { - return NULL; - } - - int ret = sicgl_interface_fill(&self->interface, color); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* interface_pixel(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u, v; - if (!PyArg_ParseTuple(args, "i(ii)", &color, &u, &v)) { - return NULL; - } - - int ret = sicgl_interface_pixel(&self->interface, color, u, v); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* interface_line(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple(args, "i(ii)(ii)", &color, &u0, &v0, &u1, &v1)) { - return NULL; - } - - int ret = sicgl_interface_line(&self->interface, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* interface_rectangle(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple(args, "i(ii)(ii)", &color, &u0, &v0, &u1, &v1)) { - return NULL; - } - - int ret = sicgl_interface_rectangle(&self->interface, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* interface_rectangle_filled(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple(args, "i(ii)(ii)", &color, &u0, &v0, &u1, &v1)) { - return NULL; - } - - int ret = - sicgl_interface_rectangle_filled(&self->interface, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* interface_circle(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, diameter; - if (!PyArg_ParseTuple(args, "i(ii)i", &color, &u0, &v0, &diameter)) { - return NULL; - } - - int ret = - sicgl_interface_circle_ellipse(&self->interface, color, u0, v0, diameter); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* interface_ellipse(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - int color; - ext_t u0, v0, semiu, semiv; - if (!PyArg_ParseTuple(args, "i(ii)(ii)", &color, &u0, &v0, &semiu, &semiv)) { - return NULL; - } - - int ret = - sicgl_interface_ellipse(&self->interface, color, u0, v0, semiu, semiv); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} diff --git a/src/drawing/screen.c b/src/drawing/screen.c deleted file mode 100644 index e17a25e..0000000 --- a/src/drawing/screen.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "pysicgl/drawing/screen.h" - -#include "pysicgl/interface.h" -#include "pysicgl/screen.h" -#include "pysicgl/utilities.h" -#include "sicgl/domain/screen.h" - -PyObject* screen_fill(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen_obj; - int color; - if (!PyArg_ParseTuple(args, "O!i", &ScreenType, &screen_obj, &color)) { - return NULL; - } - - int ret = sicgl_screen_fill(&self->interface, screen_obj->screen, color); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* screen_pixel(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen_obj; - int color; - ext_t u, v; - if (!PyArg_ParseTuple( - args, "O!i(ii)", &ScreenType, &screen_obj, &color, &u, &v)) { - return NULL; - } - - int ret = - sicgl_screen_pixel(&self->interface, screen_obj->screen, color, u, v); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* screen_line(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen_obj; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple( - args, "O!i(ii)(ii)", &ScreenType, &screen_obj, &color, &u0, &v0, &u1, - &v1)) { - return NULL; - } - - int ret = sicgl_screen_line( - &self->interface, screen_obj->screen, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* screen_rectangle(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen_obj; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple( - args, "O!i(ii)(ii)", &ScreenType, &screen_obj, &color, &u0, &v0, &u1, - &v1)) { - return NULL; - } - - int ret = sicgl_screen_rectangle( - &self->interface, screen_obj->screen, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* screen_rectangle_filled(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen_obj; - int color; - ext_t u0, v0, u1, v1; - if (!PyArg_ParseTuple( - args, "O!i(ii)(ii)", &ScreenType, &screen_obj, &color, &u0, &v0, &u1, - &v1)) { - return NULL; - } - - int ret = sicgl_screen_rectangle_filled( - &self->interface, screen_obj->screen, color, u0, v0, u1, v1); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* screen_circle(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen_obj; - int color; - ext_t u0, v0, diameter; - if (!PyArg_ParseTuple( - args, "O!i(ii)i", &ScreenType, &screen_obj, &color, &u0, &v0, - &diameter)) { - return NULL; - } - - int ret = sicgl_screen_circle_ellipse( - &self->interface, screen_obj->screen, color, u0, v0, diameter); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} - -PyObject* screen_ellipse(PyObject* self_in, PyObject* args) { - InterfaceObject* self = (InterfaceObject*)self_in; - ScreenObject* screen_obj; - int color; - ext_t u0, v0, semiu, semiv; - if (!PyArg_ParseTuple( - args, "O!i(ii)(ii)", &ScreenType, &screen_obj, &color, &u0, &v0, - &semiu, &semiv)) { - return NULL; - } - int ret = sicgl_screen_ellipse( - &self->interface, screen_obj->screen, color, u0, v0, semiu, semiv); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - return Py_None; -} diff --git a/src/field.c b/src/field.c deleted file mode 100644 index 5f3e0eb..0000000 --- a/src/field.c +++ /dev/null @@ -1,197 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -#include -// python includes must come first - -#include -#include - -#include "pysicgl/field.h" - -// utilities for C consumers -int scalar_field_get_scalars( - ScalarFieldObject* self, size_t* len, double** scalars) { - int ret = 0; - - // provide the length of the scalars - if (NULL != len) { - *len = self->_scalars_buffer.len; - } - - // provide the pointer to the scalars - if (NULL != scalars) { - *scalars = (double*)self->_scalars_buffer.buf; - } - -out: - return ret; -} - -// getset -static PyObject* get_pixels(PyObject* self_in, void* closure) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - size_t pixels = 0; - if (NULL == self->_scalars_buffer.obj) { - goto out; - } - - pixels = self->_scalars_buffer.len / sizeof(double); - -out: - return PyLong_FromLong(pixels); -} - -static PyObject* get_memory(PyObject* self_in, void* closure) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - return PyMemoryView_FromBuffer(&self->_scalars_buffer); -} - -static int set_memory(PyObject* self_in, PyObject* value, void* closure) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - if (!PyObject_IsInstance((PyObject*)value, (PyObject*)&PyByteArray_Type)) { - PyErr_SetNone(PyExc_TypeError); - return -1; - } - PyByteArrayObject* bytearray_obj = (PyByteArrayObject*)value; - - // clean up old buffer - if (NULL != self->_scalars_buffer.obj) { - PyBuffer_Release(&self->_scalars_buffer); - } - - // reassign the buffer - int ret = PyObject_GetBuffer( - (PyObject*)bytearray_obj, &self->_scalars_buffer, PyBUF_WRITABLE); - if (0 != ret) { - return -1; - } - - return 0; -} - -// methods -static PyObject* allocate_scalar_memory(PyObject* self, PyObject* scalars_in) { - size_t scalars; - if (PyLong_Check(scalars_in)) { - scalars = PyLong_AsSize_t(scalars_in); - } else { - PyErr_SetNone(PyExc_TypeError); - return NULL; - } - - return PyByteArray_FromObject(PyLong_FromSize_t(scalars * sizeof(double))); -} - -static Py_ssize_t mp_length(PyObject* self_in) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - - size_t len; - int ret = scalar_field_get_scalars(self, &len, NULL); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - return PyLong_FromLong(len); -} - -static PyObject* mp_subscript(PyObject* self_in, PyObject* key) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - size_t len; - double* scalars; - int ret = scalar_field_get_scalars(self, &len, &scalars); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - size_t idx = PyLong_AsSize_t(key); - - if (idx > (len - 1)) { - PyErr_SetNone(PyExc_IndexError); - return NULL; - } - - return PyFloat_FromDouble(scalars[idx]); -} - -static int mp_ass_subscript(PyObject* self_in, PyObject* key, PyObject* v) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - size_t len; - double* scalars; - int ret = scalar_field_get_scalars(self, &len, &scalars); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return -1; - } - size_t idx = PyLong_AsSize_t(key); - - if (idx > (len - 1)) { - PyErr_SetNone(PyExc_IndexError); - return -1; - } - - // set the double here - scalars[idx] = PyFloat_AsDouble(v); - - return 0; -} - -static void tp_dealloc(PyObject* self_in) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - PyBuffer_Release(&self->_scalars_buffer); - Py_TYPE(self)->tp_free(self); -} - -static int tp_init(PyObject* self_in, PyObject* args, PyObject* kwds) { - ScalarFieldObject* self = (ScalarFieldObject*)self_in; - char* keywords[] = { - "memory", - NULL, - }; - PyByteArrayObject* memory_bytearray_obj; - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "Y", keywords, &memory_bytearray_obj)) { - return -1; - } - - // set memory - int ret = set_memory((PyObject*)self, (PyObject*)memory_bytearray_obj, NULL); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return -1; - } - - return 0; -} - -static PyMethodDef tp_methods[] = { - {"allocate_scalar_memory", (PyCFunction)allocate_scalar_memory, - METH_O | METH_STATIC, "allocate memory for the given number of scalars"}, - {NULL}, -}; - -static PyGetSetDef tp_getset[] = { - {"pixels", get_pixels, NULL, "number of scalar values in memory", NULL}, - {"memory", get_memory, set_memory, "scalar memory", NULL}, - {NULL}, -}; - -static PyMappingMethods tp_as_mapping = { - .mp_length = mp_length, - .mp_subscript = mp_subscript, - .mp_ass_subscript = mp_ass_subscript, -}; - -PyTypeObject ScalarFieldType = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pysicgl.ScalarField", - .tp_doc = PyDoc_STR("sicgl ScalarField"), - .tp_basicsize = sizeof(ScalarFieldObject), - .tp_itemsize = 0, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = PyType_GenericNew, - .tp_dealloc = tp_dealloc, - .tp_init = tp_init, - .tp_methods = tp_methods, - .tp_getset = tp_getset, - .tp_as_mapping = &tp_as_mapping, -}; diff --git a/src/interface.c b/src/interface.c deleted file mode 100644 index 4f3189e..0000000 --- a/src/interface.c +++ /dev/null @@ -1,181 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include -#include - -#include "pysicgl/drawing/blit.h" -#include "pysicgl/drawing/compose.h" -#include "pysicgl/drawing/field.h" -#include "pysicgl/drawing/global.h" -#include "pysicgl/drawing/interface.h" -#include "pysicgl/drawing/screen.h" -#include "pysicgl/interface.h" -#include "pysicgl/screen.h" - -// getset -static PyObject* get_screen(PyObject* self_in, void* closure) { - InterfaceObject* self = (InterfaceObject*)self_in; - // it is important to return a NEW REFERENCE to the object, - // otherwise its reference count will be deleted by the caller - // who is passed the reference and later decrements the refcount - Py_INCREF((PyObject*)self->_screen); - return (PyObject*)self->_screen; -} -static PyObject* get_memory(PyObject* self_in, void* closure) { - InterfaceObject* self = (InterfaceObject*)self_in; - return PyMemoryView_FromBuffer(&self->_memory_buffer); -} - -static int set_screen(PyObject* self_in, PyObject* value, void* closure) { - InterfaceObject* self = (InterfaceObject*)self_in; - if (!PyObject_IsInstance((PyObject*)value, (PyObject*)&ScreenType)) { - PyErr_SetNone(PyExc_TypeError); - return -1; - } - ScreenObject* screen_obj = (ScreenObject*)value; - - if (NULL != self->_screen) { - Py_DECREF((PyObject*)self->_screen); - self->interface.screen = NULL; - } - - self->_screen = screen_obj; - Py_INCREF((PyObject*)self->_screen); - self->interface.screen = self->_screen->screen; - - return 0; -} - -static int set_memory(PyObject* self_in, PyObject* value, void* closure) { - InterfaceObject* self = (InterfaceObject*)self_in; - if (!PyObject_IsInstance((PyObject*)value, (PyObject*)&PyByteArray_Type)) { - PyErr_SetNone(PyExc_TypeError); - return -1; - } - PyByteArrayObject* bytearray_obj = (PyByteArrayObject*)value; - - if (NULL != self->_memory_buffer.obj) { - // clean up the old memory - PyBuffer_Release(&self->_memory_buffer); - self->interface.memory = NULL; - } - - int ret = PyObject_GetBuffer( - (PyObject*)bytearray_obj, &self->_memory_buffer, PyBUF_WRITABLE); - if (0 != ret) { - return -1; - } - self->interface.memory = self->_memory_buffer.buf; - - return 0; -} - -static void tp_dealloc(InterfaceObject* self) { - Py_XDECREF(self->_screen); - PyBuffer_Release(&self->_memory_buffer); - Py_TYPE(self)->tp_free(self); -} - -static int tp_init(PyObject* self_in, PyObject* args, PyObject* kwds) { - InterfaceObject* self = (InterfaceObject*)self_in; - char* keywords[] = { - "screen", - "memory", - NULL, - }; - PyObject* screen_obj; - PyByteArrayObject* memory_bytearray_obj; - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "O!Y", keywords, &ScreenType, &screen_obj, - &memory_bytearray_obj)) { - return -1; - } - - // set screen and memory - int ret = set_screen((PyObject*)self, screen_obj, NULL); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return -1; - } - ret = set_memory((PyObject*)self, (PyObject*)memory_bytearray_obj, NULL); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return -1; - } - - return 0; -} - -static PyMethodDef tp_methods[] = { - {"blit", (PyCFunction)blit, METH_VARARGS, - "blit a sprite onto the interface memory directly"}, - {"compose", (PyCFunction)compose, METH_VARARGS, - "compose a sprite onto the interface memory using a composition method"}, - {"scalar_field", (PyCFunction)scalar_field, METH_VARARGS | METH_KEYWORDS, - "map a scalar field onto the interface through a color sequence"}, - - {"interface_fill", (PyCFunction)interface_fill, METH_VARARGS, - "fill color into interface"}, - {"interface_pixel", (PyCFunction)interface_pixel, METH_VARARGS, - "draw pixel to interface"}, - {"interface_line", (PyCFunction)interface_line, METH_VARARGS, - "draw line to interface"}, - {"interface_rectangle", (PyCFunction)interface_rectangle, METH_VARARGS, - "draw rectangle to interface"}, - {"interface_rectangle_filled", (PyCFunction)interface_rectangle_filled, - METH_VARARGS, "draw filled rectangle to interface"}, - {"interface_circle", (PyCFunction)interface_circle, METH_VARARGS, - "draw circle to interface"}, - {"interface_ellipse", (PyCFunction)interface_ellipse, METH_VARARGS, - "draw ellipse to interface"}, - - {"screen_fill", (PyCFunction)screen_fill, METH_VARARGS, - "fill color into screen"}, - {"screen_pixel", (PyCFunction)screen_pixel, METH_VARARGS, - "draw pixel to screen"}, - {"screen_line", (PyCFunction)screen_line, METH_VARARGS, - "draw line to screen"}, - {"screen_rectangle", (PyCFunction)screen_rectangle, METH_VARARGS, - "draw rectangle to screen"}, - {"screen_rectangle_filled", (PyCFunction)screen_rectangle_filled, - METH_VARARGS, "draw filled rectangle to screen"}, - {"screen_circle", (PyCFunction)screen_circle, METH_VARARGS, - "draw circle to screen"}, - {"screen_ellipse", (PyCFunction)screen_ellipse, METH_VARARGS, - "draw ellipse to screen"}, - - {"global_pixel", (PyCFunction)global_pixel, METH_VARARGS, - "draw pixel to global"}, - {"global_line", (PyCFunction)global_line, METH_VARARGS, - "draw line to global"}, - {"global_rectangle", (PyCFunction)global_rectangle, METH_VARARGS, - "draw rectangle to global"}, - {"global_rectangle_filled", (PyCFunction)global_rectangle_filled, - METH_VARARGS, "draw filled rectangle to global"}, - {"global_circle", (PyCFunction)global_circle, METH_VARARGS, - "draw circle to global"}, - {"global_ellipse", (PyCFunction)global_ellipse, METH_VARARGS, - "draw ellipse to global"}, - {NULL}, -}; - -static PyGetSetDef tp_getset[] = { - {"screen", get_screen, set_screen, "screen definition", NULL}, - {"memory", get_memory, set_memory, "pixel memory", NULL}, - {NULL}, -}; - -PyTypeObject InterfaceType = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pysicgl.Interface", - .tp_doc = PyDoc_STR("sicgl interface"), - .tp_basicsize = sizeof(InterfaceObject), - .tp_itemsize = 0, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = PyType_GenericNew, - .tp_dealloc = tp_dealloc, - .tp_init = tp_init, - .tp_getset = tp_getset, - .tp_methods = tp_methods, -}; diff --git a/src/module.c b/src/module.c index 0f45f6a..5d274b6 100644 --- a/src/module.c +++ b/src/module.c @@ -1,15 +1,42 @@ #define PY_SSIZE_T_CLEAN #include -// python includes must come first +// python includes first (clang-format) -#include "pysicgl/color.h" -#include "pysicgl/color_sequence.h" -#include "pysicgl/field.h" -#include "pysicgl/interface.h" -#include "pysicgl/screen.h" +#include "pysicgl/submodules/color.h" +#include "pysicgl/submodules/composition.h" +#include "pysicgl/submodules/functional.h" +#include "pysicgl/submodules/interpolation.h" +#include "pysicgl/types/color_sequence.h" +#include "pysicgl/types/color_sequence_interpolator.h" +#include "pysicgl/types/compositor.h" +#include "pysicgl/types/interface.h" +#include "pysicgl/types/scalar_field.h" +#include "pysicgl/types/screen.h" #include "sicgl.h" +/** + * @brief Get the number of bytes per pixel. + * + * @param self + * @param args + * @return PyObject* Number of bytes per pixel. + */ +static PyObject* get_bytes_per_pixel(PyObject* self, PyObject* args) { + (void)self; + (void)args; + return PyLong_FromSize_t(bytes_per_pixel()); +} + +/** + * @brief Allocate memory for a specified number of pixels. + * + * @param self + * @param pixels_in Number of pixels for which to allocate + * memory. + * @return PyObject* Allocated memory as a bytearray. + */ static PyObject* allocate_pixel_memory(PyObject* self, PyObject* pixels_in) { + (void)self; size_t pixels; if (PyLong_Check(pixels_in)) { pixels = PyLong_AsSize_t(pixels_in); @@ -22,32 +49,11 @@ static PyObject* allocate_pixel_memory(PyObject* self, PyObject* pixels_in) { return PyByteArray_FromObject(PyLong_FromSize_t(pixels * bpp)); } -static PyObject* gamma_correct(PyObject* self, PyObject* args) { - PyObject* input_obj; - PyObject* output_obj; - if (!PyArg_ParseTuple( - args, "O!O!", &InterfaceType, &input_obj, &InterfaceType, - &output_obj)) { - return NULL; - } - - InterfaceObject* input = (InterfaceObject*)input_obj; - InterfaceObject* output = (InterfaceObject*)output_obj; - - int ret = sicgl_gamma_correct(&input->interface, &output->interface); - if (0 != ret) { - PyErr_SetNone(PyExc_OSError); - return NULL; - } - - return Py_None; -} - -PyMethodDef pysicgl_funcs[] = { +static PyMethodDef funcs[] = { + {"get_bytes_per_pixel", (PyCFunction)get_bytes_per_pixel, METH_NOARGS, + "Get the number of bytes per pixel."}, {"allocate_pixel_memory", (PyCFunction)allocate_pixel_memory, METH_O, - "Allocate memory for the specified number of pixels"}, - {"gamma_correct", (PyCFunction)gamma_correct, METH_VARARGS, - "perform gamma correction on interface memory"}, + "Allocate memory for the specified number of pixels."}, {NULL}, }; @@ -56,7 +62,7 @@ static PyModuleDef module = { "pysicgl", "sicgl in Python", -1, - pysicgl_funcs, + funcs, NULL, NULL, NULL, @@ -68,14 +74,30 @@ typedef struct _type_entry_t { const char* name; PyTypeObject* type; } type_entry_t; -type_entry_t pysicgl_types[] = { - {"Interface", &InterfaceType}, {"Color", &ColorType}, - {"ColorSequence", &ColorSequenceType}, {"Screen", &ScreenType}, +static type_entry_t pysicgl_types[] = { + {"Interface", &InterfaceType}, + {"ColorSequence", &ColorSequenceType}, + {"ColorSequenceInterpolator", &ColorSequenceInterpolatorType}, + {"Screen", &ScreenType}, {"ScalarField", &ScalarFieldType}, + {"Compositor", &CompositorType}, }; -size_t num_types = sizeof(pysicgl_types) / sizeof(type_entry_t); +static size_t num_types = sizeof(pysicgl_types) / sizeof(type_entry_t); -PyMODINIT_FUNC PyInit_pysicgl(void) { +// collect submodule definitions for the module +typedef struct _submodule_entry_t { + const char* name; + PyObject* (*init)(void); +} submodule_entry_t; +static submodule_entry_t pysicgl_submodules[] = { + {"composition", PyInit_composition}, + {"functional", PyInit_functional}, + {"interpolation", PyInit_interpolation}, +}; +static size_t num_submodules = + sizeof(pysicgl_submodules) / sizeof(submodule_entry_t); + +PyMODINIT_FUNC PyInit__core(void) { // ensure that types are ready for (size_t idx = 0; idx < num_types; idx++) { type_entry_t entry = pysicgl_types[idx]; @@ -84,13 +106,6 @@ PyMODINIT_FUNC PyInit_pysicgl(void) { } } - // run additional initialization for types - int ret = ColorSequence_post_ready_init(); - if (0 != ret) { - PyErr_SetString(PyExc_OSError, "failed ColorSequence post-ready init"); - return NULL; - } - // create the module PyObject* m = PyModule_Create(&module); @@ -105,6 +120,21 @@ PyMODINIT_FUNC PyInit_pysicgl(void) { } } + // create and register submodules + for (size_t idx = 0; idx < num_submodules; idx++) { + submodule_entry_t entry = pysicgl_submodules[idx]; + PyObject* submodule = entry.init(); + if (submodule == NULL) { + Py_DECREF(m); + return NULL; + } + if (PyModule_AddObject(m, entry.name, submodule) < 0) { + Py_DECREF(submodule); + Py_DECREF(m); + return NULL; + } + } + // return the module return m; } diff --git a/src/submodules/composition/module.c b/src/submodules/composition/module.c new file mode 100644 index 0000000..5975422 --- /dev/null +++ b/src/submodules/composition/module.c @@ -0,0 +1,120 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/compositor.h" +#include "sicgl/compositors.h" + +// collect type definitions for the module +typedef struct _type_entry_t { + const char* name; + PyTypeObject* type; +} type_entry_t; +static type_entry_t pysicgl_types[] = { + {"Compositor", &CompositorType}, +}; +static size_t num_types = sizeof(pysicgl_types) / sizeof(type_entry_t); + +// collect compositors for the module +typedef struct _compositor_entry_t { + const char* name; + compositor_fn fn; +} compositor_entry_t; +static compositor_entry_t compositors[] = { + // direct compositors + {"DIRECT_SET", compositor_direct_set}, + {"DIRECT_CLEAR", compositor_direct_clear}, + {"DIRECT_NONE", compositor_direct_none}, + + // bitwise compositors + {"BIT_AND", compositor_bitwise_and}, + {"BIT_OR", compositor_bitwise_or}, + {"BIT_XOR", compositor_bitwise_xor}, + {"BIT_NAND", compositor_bitwise_nand}, + {"BIT_NOR", compositor_bitwise_nor}, + {"BIT_XNOR", compositor_bitwise_xnor}, + // // These bitwise compositors are not implemented yet in sicgl. + // {"BIT_NOT_SOURCE", compositor_bitwise_not_source}, + // {"BIT_NOT_DESTINATION", compositor_bitwise_not_destination}, + + // channelwise compositors + {"CHANNEL_MIN", compositor_channelwise_min}, + {"CHANNEL_MAX", compositor_channelwise_max}, + + {"CHANNEL_SUM", compositor_channelwise_sum}, + {"CHANNEL_DIFF", compositor_channelwise_diff}, + {"CHANNEL_DIFF_REVERSE", compositor_channelwise_diff_reverse}, + {"CHANNEL_MULTIPLY", compositor_channelwise_multiply}, + {"CHANNEL_DIVIDE", compositor_channelwise_divide}, + {"CHANNEL_DIVIDE_REVERSE", compositor_channelwise_divide_reverse}, + + {"CHANNEL_SUM_CLAMPED", compositor_channelwise_sum_clamped}, + {"CHANNEL_DIFF_CLAMPED", compositor_channelwise_diff_clamped}, + {"CHANNEL_DIFF_REVERSE_CLAMPED", + compositor_channelwise_diff_reverse_clamped}, + {"CHANNEL_MULTIPLY_CLAMPED", compositor_channelwise_multiply_clamped}, + {"CHANNEL_DIVIDE_CLAMPED", compositor_channelwise_divide_clamped}, + {"CHANNEL_DIVIDE_REVERSE_CLAMPED", + compositor_channelwise_divide_reverse_clamped}, + + // porter-duff alpha compositing + {"ALPHA_CLEAR", compositor_alpha_clear}, + {"ALPHA_COPY", compositor_alpha_copy}, + {"ALPHA_DESTINATION", compositor_alpha_destination}, + {"ALPHA_SOURCE_OVER", compositor_alpha_source_over}, + {"ALPHA_DESTINATION_OVER", compositor_alpha_destination_over}, + {"ALPHA_SOURCE_IN", compositor_alpha_source_in}, + {"ALPHA_DESTINATION_IN", compositor_alpha_destination_in}, + {"ALPHA_SOURCE_OUT", compositor_alpha_source_out}, + {"ALPHA_DESTINATION_OUT", compositor_alpha_destination_out}, + {"ALPHA_SOURCE_ATOP", compositor_alpha_source_atop}, + {"ALPHA_DESTINATION_ATOP", compositor_alpha_destination_atop}, + {"ALPHA_XOR", compositor_alpha_xor}, + {"ALPHA_LIGHTER", compositor_alpha_lighter}, +}; +static size_t num_compositors = + sizeof(compositors) / sizeof(compositor_entry_t); + +static PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "compositors", + "sicgl compositors", + -1, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +PyMODINIT_FUNC PyInit_composition(void) { + // ensure that types are ready + for (size_t idx = 0; idx < num_types; idx++) { + type_entry_t entry = pysicgl_types[idx]; + if (PyType_Ready(entry.type) < 0) { + return NULL; + } + } + + // create the module + PyObject* m = PyModule_Create(&module); + + // create and register compositors + for (size_t idx = 0; idx < num_compositors; idx++) { + compositor_entry_t entry = compositors[idx]; + CompositorObject* obj = new_compositor_object(entry.fn, NULL); + if (NULL == obj) { + PyErr_SetString(PyExc_OSError, "failed to create compositor object"); + return NULL; + } + if (PyModule_AddObject(m, entry.name, (PyObject*)obj) < 0) { + Py_DECREF(obj); + Py_DECREF(m); + PyErr_SetString( + PyExc_OSError, "failed to add compositor object to module"); + return NULL; + } + } + + return m; +} diff --git a/src/submodules/functional/color.c b/src/submodules/functional/color.c new file mode 100644 index 0000000..d61183a --- /dev/null +++ b/src/submodules/functional/color.c @@ -0,0 +1,128 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/color_sequence.h" +#include "sicgl/color.h" + +PyObject* color_to_rgba(PyObject* self, PyObject* args) { + (void)self; + PyObject* obj; + if (!PyArg_ParseTuple(args, "O", &obj)) { + return NULL; + } + + color_t color = PyLong_AsLong(obj); + return PyTuple_Pack( + 4, PyLong_FromLong(color_channel_red(color)), + PyLong_FromLong(color_channel_green(color)), + PyLong_FromLong(color_channel_blue(color)), + PyLong_FromLong(color_channel_alpha(color))); +} + +PyObject* color_from_rgba(PyObject* self, PyObject* args) { + (void)self; + PyObject* obj; + if (!PyArg_ParseTuple(args, "O", &obj)) { + return NULL; + } + + return PyLong_FromLong(color_from_channels( + PyLong_AsLong(PyTuple_GetItem(obj, 0)), + PyLong_AsLong(PyTuple_GetItem(obj, 1)), + PyLong_AsLong(PyTuple_GetItem(obj, 2)), + PyLong_AsLong(PyTuple_GetItem(obj, 3)))); +} + +PyObject* interpolate_color_sequence( + PyObject* self_in, PyObject* args, PyObject* kwds) { + (void)self_in; + int ret = 0; + ColorSequenceObject* color_sequence_obj; + PyObject* samples_obj; + char* keywords[] = { + "color_sequence", + "samples", + NULL, + }; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O!O", keywords, &ColorSequenceType, &color_sequence_obj, + &samples_obj)) { + return NULL; + } + + // determine the interpolation function + sequence_map_fn interp_fn = color_sequence_obj->interpolator->fn; + + // use this sequences' interpolation method to handle the input + if (PyLong_Check(samples_obj)) { + // input is a single sample, return the interpolated color directly + color_t color; + ret = interp_fn( + &color_sequence_obj->sequence, (double)PyLong_AsLong(samples_obj), + &color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + return PyLong_FromLong(color); + + } else if (PyFloat_Check(samples_obj)) { + // input is a single sample, return the interpolated color directly + color_t color; + ret = interp_fn( + &color_sequence_obj->sequence, PyFloat_AsDouble(samples_obj), &color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + return PyLong_FromLong(color); + + } else if (PyList_Check(samples_obj)) { + // input is a list of samples, return a tuple of interpolated colors + size_t num_samples = PyList_Size(samples_obj); + PyObject* result = PyTuple_New(num_samples); + for (size_t idx = 0; idx < num_samples; idx++) { + color_t color; + ret = interp_fn( + &color_sequence_obj->sequence, + PyFloat_AsDouble(PyList_GetItem(samples_obj, idx)), &color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + ret = PyTuple_SetItem(result, idx, PyLong_FromLong(color)); + if (0 != ret) { + return NULL; + } + } + return result; + + } else if (PyTuple_Check(samples_obj)) { + // input is a tuple of samples, return a tuple of interpolated colors + size_t num_samples = PyTuple_Size(samples_obj); + PyObject* result = PyTuple_New(num_samples); + for (size_t idx = 0; idx < num_samples; idx++) { + color_t color; + ret = interp_fn( + &color_sequence_obj->sequence, + PyFloat_AsDouble(PyTuple_GetItem(samples_obj, idx)), &color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + ret = PyTuple_SetItem(result, idx, PyLong_FromLong(color)); + if (0 != ret) { + return NULL; + } + } + + } else { + PyErr_SetNone(PyExc_TypeError); + return NULL; + } + + // should never get here + PyErr_SetNone(PyExc_NotImplementedError); + return NULL; +} diff --git a/src/submodules/functional/color_correction.c b/src/submodules/functional/color_correction.c new file mode 100644 index 0000000..4c487b7 --- /dev/null +++ b/src/submodules/functional/color_correction.c @@ -0,0 +1,32 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/interface.h" +#include "sicgl/gamma.h" + +/** + * @brief Perform gamma correction on interface memory. + * + * @param self + * @param args + * @return PyObject* None. + */ +PyObject* gamma_correct(PyObject* self, PyObject* args) { + (void)self; + InterfaceObject* input; + InterfaceObject* output; + if (!PyArg_ParseTuple( + args, "O!O!", &InterfaceType, &input, &InterfaceType, &output)) { + return NULL; + } + + int ret = sicgl_gamma_correct(&input->interface, &output->interface); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/submodules/functional/drawing/global.c b/src/submodules/functional/drawing/global.c new file mode 100644 index 0000000..9ed7b82 --- /dev/null +++ b/src/submodules/functional/drawing/global.c @@ -0,0 +1,136 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/interface.h" +#include "sicgl/blit.h" +#include "sicgl/domain/global.h" + +PyObject* global_pixel(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u, v; + if (!PyArg_ParseTuple( + args, "O!i(ii)", &InterfaceType, &interface_obj, &color, &u, &v)) { + return NULL; + } + + int ret = sicgl_global_pixel(&interface_obj->interface, color, u, v); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* global_line(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &u1, &v1)) { + return NULL; + } + + int ret = sicgl_global_line(&interface_obj->interface, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* global_rectangle(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &u1, &v1)) { + return NULL; + } + + int ret = + sicgl_global_rectangle(&interface_obj->interface, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* global_rectangle_filled(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &u1, &v1)) { + return NULL; + } + + int ret = sicgl_global_rectangle_filled( + &interface_obj->interface, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* global_circle(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, diameter; + if (!PyArg_ParseTuple( + args, "O!i(ii)i", &InterfaceType, &interface_obj, &color, &u0, &v0, + &diameter)) { + return NULL; + } + + int ret = sicgl_global_circle_ellipse( + &interface_obj->interface, color, u0, v0, diameter); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* global_ellipse(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, semiu, semiv; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &semiu, &semiv)) { + return NULL; + } + + int ret = sicgl_global_ellipse( + &interface_obj->interface, color, u0, v0, semiu, semiv); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/submodules/functional/drawing/interface.c b/src/submodules/functional/drawing/interface.c new file mode 100644 index 0000000..aa0c334 --- /dev/null +++ b/src/submodules/functional/drawing/interface.c @@ -0,0 +1,155 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/interface.h" +#include "sicgl/blit.h" +#include "sicgl/domain/interface.h" + +PyObject* interface_fill(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + if (!PyArg_ParseTuple(args, "O!i", &InterfaceType, &interface_obj, &color)) { + return NULL; + } + + int ret = sicgl_interface_fill(&interface_obj->interface, color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* interface_pixel(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u, v; + if (!PyArg_ParseTuple( + args, "O!i(ii)", &InterfaceType, &interface_obj, &color, &u, &v)) { + return NULL; + } + + int ret = sicgl_interface_pixel(&interface_obj->interface, color, u, v); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* interface_line(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &u1, &v1)) { + return NULL; + } + + int ret = + sicgl_interface_line(&interface_obj->interface, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* interface_rectangle(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &u1, &v1)) { + return NULL; + } + + int ret = sicgl_interface_rectangle( + &interface_obj->interface, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* interface_rectangle_filled(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &u1, &v1)) { + return NULL; + } + + int ret = sicgl_interface_rectangle_filled( + &interface_obj->interface, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* interface_circle(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, diameter; + if (!PyArg_ParseTuple( + args, "O!i(ii)i", &InterfaceType, &interface_obj, &color, &u0, &v0, + &diameter)) { + return NULL; + } + + int ret = sicgl_interface_circle_ellipse( + &interface_obj->interface, color, u0, v0, diameter); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* interface_ellipse(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + int color; + ext_t u0, v0, semiu, semiv; + if (!PyArg_ParseTuple( + args, "O!i(ii)(ii)", &InterfaceType, &interface_obj, &color, &u0, &v0, + &semiu, &semiv)) { + return NULL; + } + + int ret = sicgl_interface_ellipse( + &interface_obj->interface, color, u0, v0, semiu, semiv); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/submodules/functional/drawing/screen.c b/src/submodules/functional/drawing/screen.c new file mode 100644 index 0000000..0ca8351 --- /dev/null +++ b/src/submodules/functional/drawing/screen.c @@ -0,0 +1,166 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/interface.h" +#include "sicgl/domain/screen.h" + +PyObject* screen_fill(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen_obj; + int color; + if (!PyArg_ParseTuple( + args, "O!O!i", &InterfaceType, &interface_obj, &ScreenType, + &screen_obj, &color)) { + return NULL; + } + + int ret = + sicgl_screen_fill(&interface_obj->interface, screen_obj->screen, color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* screen_pixel(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen_obj; + int color; + ext_t u, v; + if (!PyArg_ParseTuple( + args, "O!O!i(ii)", &InterfaceType, &interface_obj, &ScreenType, + &screen_obj, &color, &u, &v)) { + return NULL; + } + + int ret = sicgl_screen_pixel( + &interface_obj->interface, screen_obj->screen, color, u, v); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* screen_line(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!O!i(ii)(ii)", &InterfaceType, &interface_obj, &ScreenType, + &screen_obj, &color, &u0, &v0, &u1, &v1)) { + return NULL; + } + + int ret = sicgl_screen_line( + &interface_obj->interface, screen_obj->screen, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* screen_rectangle(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!O!i(ii)(ii)", &InterfaceType, &interface_obj, &ScreenType, + &screen_obj, &color, &u0, &v0, &u1, &v1)) { + return NULL; + } + + int ret = sicgl_screen_rectangle( + &interface_obj->interface, screen_obj->screen, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* screen_rectangle_filled(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen_obj; + int color; + ext_t u0, v0, u1, v1; + if (!PyArg_ParseTuple( + args, "O!O!i(ii)(ii)", &InterfaceType, &interface_obj, &ScreenType, + &screen_obj, &color, &u0, &v0, &u1, &v1)) { + return NULL; + } + + int ret = sicgl_screen_rectangle_filled( + &interface_obj->interface, screen_obj->screen, color, u0, v0, u1, v1); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* screen_circle(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen_obj; + int color; + ext_t u0, v0, diameter; + if (!PyArg_ParseTuple( + args, "O!O!i(ii)i", &InterfaceType, &interface_obj, &ScreenType, + &screen_obj, &color, &u0, &v0, &diameter)) { + return NULL; + } + + int ret = sicgl_screen_circle_ellipse( + &interface_obj->interface, screen_obj->screen, color, u0, v0, diameter); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* screen_ellipse(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen_obj; + int color; + ext_t u0, v0, semiu, semiv; + if (!PyArg_ParseTuple( + args, "O!O!i(ii)(ii)", &InterfaceType, &interface_obj, &ScreenType, + &screen_obj, &color, &u0, &v0, &semiu, &semiv)) { + return NULL; + } + int ret = sicgl_screen_ellipse( + &interface_obj->interface, screen_obj->screen, color, u0, v0, semiu, + semiv); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/submodules/functional/module.c b/src/submodules/functional/module.c new file mode 100644 index 0000000..675a626 --- /dev/null +++ b/src/submodules/functional/module.c @@ -0,0 +1,170 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/submodules/functional/color.h" +#include "pysicgl/submodules/functional/color_correction.h" +#include "pysicgl/submodules/functional/drawing/global.h" +#include "pysicgl/submodules/functional/drawing/interface.h" +#include "pysicgl/submodules/functional/drawing/screen.h" +#include "pysicgl/submodules/functional/operations.h" +#include "pysicgl/types/interface.h" +#include "sicgl/gamma.h" + +/** + * @brief Get the pixel color at the specified offset. + * + * @param self + * @param args + * - memorv_obj: The memory buffer bytearray. + * - offset_obj: The pixel offset into the buffer. + * @return PyObject* the pixel color as an integer. + */ +static PyObject* get_pixel_at_offset(PyObject* self, PyObject* args) { + (void)self; + InterfaceObject* interface_obj; + int offset; + if (!PyArg_ParseTuple(args, "O!i", &InterfaceType, &interface_obj, &offset)) { + return NULL; + } + + color_t color; + int ret = sicgl_interface_get_pixel_offset( + &interface_obj->interface, offset, &color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + return PyLong_FromLong(color); +} + +/** + * @brief Get the pixel color at specified coordinates. + * + * @param self + * @param args + * - interface_obj: The interface. + * - coordinates_obj: The coordinates tuple (u, v). + */ +static PyObject* get_pixel_at_coordinates(PyObject* self, PyObject* args) { + (void)self; + InterfaceObject* interface_obj; + ext_t u; + ext_t v; + if (!PyArg_ParseTuple( + args, "O!(ii)", &InterfaceType, &interface_obj, &u, &v)) { + return NULL; + } + + color_t color; + int ret = sicgl_interface_get_pixel(&interface_obj->interface, u, v, &color); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + return PyLong_FromLong(color); +} + +static PyMethodDef funcs[] = { + + // utilities + {"get_pixel_at_offset", (PyCFunction)get_pixel_at_offset, METH_VARARGS, + "Get the pixel color at the specified offset."}, + {"get_pixel_at_coordinates", (PyCFunction)get_pixel_at_coordinates, + METH_VARARGS, "Get the pixel color at the specified coordinates."}, + + // color utilities + {"color_from_rgba", (PyCFunction)color_from_rgba, METH_VARARGS, + "Return the color comprised of the RGBA input 4-tuple."}, + {"color_to_rgba", (PyCFunction)color_to_rgba, METH_VARARGS, + "Return the individual RGBA components of the input color as a 4-tuple."}, + {"interpolate_color_sequence", (PyCFunction)interpolate_color_sequence, + METH_VARARGS | METH_KEYWORDS, + "Interpolate the color sequence at one or more points using the given " + "interpolation type."}, + + // color correction + {"gamma_correct", (PyCFunction)gamma_correct, METH_VARARGS, + "Perform gamma correction on interface memory."}, + + // advanced operations + {"blit", (PyCFunction)blit, METH_VARARGS, + "blit a sprite onto the interface memory directly"}, + {"compose", (PyCFunction)compose, METH_VARARGS, + "compose a sprite onto the interface memory using a composition method"}, + {"scalar_field", (PyCFunction)scalar_field, METH_VARARGS | METH_KEYWORDS, + "map a scalar field onto the interface through a color sequence"}, + {"scale", (PyCFunction)scale, METH_VARARGS, + "scale the interface memory by a scalar factor"}, + + // interface relative drawing + + {"interface_fill", (PyCFunction)interface_fill, METH_VARARGS, + "fill color into interface"}, + {"interface_pixel", (PyCFunction)interface_pixel, METH_VARARGS, + "draw pixel to interface"}, + {"interface_line", (PyCFunction)interface_line, METH_VARARGS, + "draw line to interface"}, + {"interface_rectangle", (PyCFunction)interface_rectangle, METH_VARARGS, + "draw rectangle to interface"}, + {"interface_rectangle_filled", (PyCFunction)interface_rectangle_filled, + METH_VARARGS, "draw filled rectangle to interface"}, + {"interface_circle", (PyCFunction)interface_circle, METH_VARARGS, + "draw circle to interface"}, + {"interface_ellipse", (PyCFunction)interface_ellipse, METH_VARARGS, + "draw ellipse to interface"}, + + // screen relative drawing + {"screen_fill", (PyCFunction)screen_fill, METH_VARARGS, + "fill color into screen"}, + {"screen_pixel", (PyCFunction)screen_pixel, METH_VARARGS, + "draw pixel to screen"}, + {"screen_line", (PyCFunction)screen_line, METH_VARARGS, + "draw line to screen"}, + {"screen_rectangle", (PyCFunction)screen_rectangle, METH_VARARGS, + "draw rectangle to screen"}, + {"screen_rectangle_filled", (PyCFunction)screen_rectangle_filled, + METH_VARARGS, "draw filled rectangle to screen"}, + {"screen_circle", (PyCFunction)screen_circle, METH_VARARGS, + "draw circle to screen"}, + {"screen_ellipse", (PyCFunction)screen_ellipse, METH_VARARGS, + "draw ellipse to screen"}, + + // global drawing + {"global_pixel", (PyCFunction)global_pixel, METH_VARARGS, + "Draw a pixel in global coordinates. Output clipped to interface."}, + {"global_line", (PyCFunction)global_line, METH_VARARGS, + "Draw a line in global coordinates. Output clipped to interface."}, + {"global_rectangle", (PyCFunction)global_rectangle, METH_VARARGS, + "Draw a rectangle in global coordinates. Output clipped to interface."}, + {"global_rectangle_filled", (PyCFunction)global_rectangle_filled, + METH_VARARGS, + "Draw a filled rectangle in global coordinates. Output clipped to " + "interface."}, + {"global_circle", (PyCFunction)global_circle, METH_VARARGS, + "Draw a circle in global coordinates. Output clipped to interface."}, + {"global_ellipse", (PyCFunction)global_ellipse, METH_VARARGS, + "Draw an ellipse in global coordinates. Output clipped to interface."}, + + {NULL}, +}; + +static PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "functional", + "sicgl functional interface", + -1, + funcs, + NULL, + NULL, + NULL, + NULL, +}; + +PyMODINIT_FUNC PyInit_functional(void) { + PyObject* m = PyModule_Create(&module); + + return m; +} diff --git a/src/submodules/functional/operations.c b/src/submodules/functional/operations.c new file mode 100644 index 0000000..272c60c --- /dev/null +++ b/src/submodules/functional/operations.c @@ -0,0 +1,148 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/color_sequence.h" +#include "pysicgl/types/color_sequence_interpolator.h" +#include "pysicgl/types/compositor.h" +#include "pysicgl/types/interface.h" +#include "pysicgl/types/scalar_field.h" +#include "sicgl/blit.h" +#include "sicgl/compose.h" +#include "sicgl/gamma.h" + +static inline color_t clamp_u8(color_t channel) { + if (channel > 255) { + return 255; + } else if (channel < 0) { + return 0; + } else { + return channel; + } +} + +static inline color_t color_scale(color_t color, double scale) { + // scales only the color components, alpha channel is untouched + return color_from_channels( + clamp_u8((color_t)(color_channel_red(color) * scale)), + clamp_u8((color_t)(color_channel_green(color) * scale)), + clamp_u8((color_t)(color_channel_blue(color) * scale)), + color_channel_alpha(color)); +} + +PyObject* scalar_field(PyObject* self_in, PyObject* args, PyObject* kwds) { + (void)self_in; + int ret = 0; + InterfaceObject* interface_obj; + ScreenObject* field_obj; + ScalarFieldObject* scalar_field_obj; + ColorSequenceObject* color_sequence_obj; + double offset = 0.0; + char* keywords[] = { + "interface", "screen", "scalar_field", "color_sequence", "offset", NULL, + }; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O!O!O!O!|d", keywords, &InterfaceType, &interface_obj, + &ScreenType, &field_obj, &ScalarFieldType, &scalar_field_obj, + &ColorSequenceType, &color_sequence_obj, &offset)) { + return NULL; + } + + Py_INCREF(color_sequence_obj); + Py_INCREF(scalar_field_obj); + + // check length of scalars is sufficient for the field + uint32_t pixels; + ret = screen_get_num_pixels(field_obj->screen, &pixels); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + size_t scalars = scalar_field_obj->length; + if (pixels > scalars) { + PyErr_SetString(PyExc_ValueError, "scalars buffer is too small"); + return NULL; + } + + ColorSequenceInterpolatorObject* interpolator_obj = + color_sequence_obj->interpolator; + ret = sicgl_scalar_field( + &interface_obj->interface, field_obj->screen, scalar_field_obj->scalars, + offset, &color_sequence_obj->sequence, interpolator_obj->fn); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_DECREF(scalar_field_obj); + Py_DECREF(color_sequence_obj); + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* compose(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen; + Py_buffer sprite; + CompositorObject* compositor; + if (!PyArg_ParseTuple( + args, "O!O!y*O!", &InterfaceType, &interface_obj, &ScreenType, + &screen, &sprite, &CompositorType, &compositor)) { + return NULL; + } + + int ret = sicgl_compose( + &interface_obj->interface, screen->screen, sprite.buf, compositor->fn, + compositor->args); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* blit(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + ScreenObject* screen; + Py_buffer sprite; + if (!PyArg_ParseTuple( + args, "O!O!y*", &InterfaceType, &interface_obj, &ScreenType, &screen, + &sprite)) { + return NULL; + } + + int ret = sicgl_blit(&interface_obj->interface, screen->screen, sprite.buf); + + PyBuffer_Release(&sprite); + + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* scale(PyObject* self_in, PyObject* args) { + (void)self_in; + InterfaceObject* interface_obj; + double fraction; + if (!PyArg_ParseTuple( + args, "O!d", &InterfaceType, &interface_obj, &fraction)) { + return NULL; + } + color_t* memory = interface_obj->interface.memory; + for (ext_t idx = 0; idx < interface_obj->interface.length; idx++) { + memory[idx] = color_scale(memory[idx], fraction); + } + + Py_INCREF(Py_None); + return Py_None; +} diff --git a/src/submodules/interpolation/module.c b/src/submodules/interpolation/module.c new file mode 100644 index 0000000..8d10f00 --- /dev/null +++ b/src/submodules/interpolation/module.c @@ -0,0 +1,61 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/color_sequence_interpolator.h" +#include "sicgl/color_sequence.h" + +static PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "interpolation", + "sicgl interpolation module", + -1, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +// collect interpolators for the module +typedef struct _interpolator_entry_t { + char* name; + sequence_map_fn fn; +} interpolator_entry_t; +static const interpolator_entry_t interpolators[] = { + {.name = "CONTINUOUS_CIRCULAR", + .fn = color_sequence_interpolate_color_continuous_circular}, + {.name = "CONTINUOUS_LINEAR", + .fn = color_sequence_interpolate_color_continuous_linear}, + {.name = "DISCRETE_CIRCULAR", + .fn = color_sequence_interpolate_color_discrete_circular}, + {.name = "DISCRETE_LINEAR", + .fn = color_sequence_interpolate_color_discrete_linear}, +}; +static const size_t num_interpolators = + sizeof(interpolators) / sizeof(interpolator_entry_t); + +PyMODINIT_FUNC PyInit_interpolation(void) { + PyObject* m = PyModule_Create(&module); + + // create and register interpolators + PyType_Ready(&ColorSequenceInterpolatorType); + for (size_t idx = 0; idx < num_interpolators; idx++) { + interpolator_entry_t entry = interpolators[idx]; + ColorSequenceInterpolatorObject* obj = + new_color_sequence_interpolator_object(entry.fn, NULL); + if (NULL == obj) { + PyErr_SetString(PyExc_OSError, "failed to create interpolator object"); + return NULL; + } + if (PyModule_AddObject(m, entry.name, (PyObject*)obj) < 0) { + Py_DECREF(obj); + Py_DECREF(m); + PyErr_SetString( + PyExc_OSError, "failed to add interpolator object to module"); + return NULL; + } + } + + return m; +} diff --git a/src/types/color_sequence/type.c b/src/types/color_sequence/type.c new file mode 100644 index 0000000..b979811 --- /dev/null +++ b/src/types/color_sequence/type.c @@ -0,0 +1,194 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include + +#include "pysicgl/submodules/color.h" +#include "pysicgl/types/color_sequence.h" +#include "pysicgl/types/color_sequence_interpolator.h" + +// fwd declarations +static Py_ssize_t mp_length(PyObject* self_in); + +// utilities for C consumers +//////////////////////////// + +/** + * @brief Deallocate the color sequence. + * + * @param self + * @return int + */ +static int deallocate_sequence(ColorSequenceObject* self) { + int ret = 0; + if (NULL == self) { + ret = -1; + goto out; + } + + PyMem_Free(self->sequence.colors); + self->sequence.colors = NULL; + self->sequence.length = 0; + +out: + return ret; +} + +/** + * @brief Allocate memory for the color sequence. + * + * @param self + * @param len + * @return int + */ +static int allocate_sequence(ColorSequenceObject* self, size_t len) { + int ret = 0; + if (NULL == self) { + ret = -1; + goto out; + } + + self->sequence.colors = PyMem_Malloc(len * sizeof(color_t)); + if (NULL == self->sequence.colors) { + ret = -ENOMEM; + goto out; + } + self->sequence.length = len; + +out: + return ret; +} + +// methods +////////// + +static PyObject* get_colors(PyObject* self_in, void* closure) { + (void)closure; + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + PyObject* colors = PyList_New(self->sequence.length); + for (size_t idx = 0; idx < self->sequence.length; idx++) { + PyList_SetItem(colors, idx, PyLong_FromLong(self->sequence.colors[idx])); + } + return colors; +} + +static PyObject* get_interpolator(PyObject* self_in, void* closure) { + (void)closure; + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + Py_INCREF((PyObject*)self->interpolator); + return (PyObject*)self->interpolator; +} + +static Py_ssize_t mp_length(PyObject* self_in) { + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + return self->sequence.length; +} + +static PyObject* mp_subscript(PyObject* self_in, PyObject* key) { + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + return PyLong_FromLong(self->sequence.colors[PyLong_AsLong(key)]); +} + +static PyObject* tp_iter(PyObject* self_in) { + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + self->iterator_index = 0; + Py_INCREF(self); + return self_in; +} + +static PyObject* tp_iternext(PyObject* self_in) { + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + if (self->iterator_index < self->sequence.length) { + PyObject* item = + PyLong_FromLong(self->sequence.colors[self->iterator_index]); + self->iterator_index++; + return item; + } else { + // No more items. Raise StopIteration + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } +} + +static void tp_dealloc(PyObject* self_in) { + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + Py_XDECREF(self->interpolator); + deallocate_sequence(self); + Py_TYPE(self)->tp_free(self); +} + +static int tp_init(PyObject* self_in, PyObject* args, PyObject* kwds) { + int ret = 0; + ColorSequenceObject* self = (ColorSequenceObject*)self_in; + PyObject* colors_obj; + ColorSequenceInterpolatorObject* interpolator_obj; + char* keywords[] = { + "colors", + "interpolator", + NULL, + }; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "OO!", keywords, &colors_obj, + &ColorSequenceInterpolatorType, &interpolator_obj)) { + return -1; + } + + // set the interpolator + self->interpolator = interpolator_obj; + Py_INCREF(self->interpolator); + + // ensure that the colors object is a list + if (!PyList_Check(colors_obj)) { + PyErr_SetNone(PyExc_TypeError); + return -1; + } + + // size of the sequence + size_t len = PyList_Size(colors_obj); + + // allocate memory for the sequence + ret = allocate_sequence(self, len); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return -1; + } + + // copy the colors into the sequence + for (size_t idx = 0; idx < len; idx++) { + self->sequence.colors[idx] = PyLong_AsLong(PyList_GetItem(colors_obj, idx)); + } + + return ret; +} + +static PyMethodDef tp_methods[] = { + {NULL}, +}; + +static PyMappingMethods tp_as_mapping = { + .mp_length = mp_length, + .mp_subscript = mp_subscript, +}; + +static PyGetSetDef tp_getset[] = { + {"colors", get_colors, NULL, "colors", NULL}, + {"interpolator", get_interpolator, NULL, "interpolator", NULL}, + {NULL}, +}; + +PyTypeObject ColorSequenceType = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_sicgl_core.ColorSequence", + .tp_doc = PyDoc_STR("sicgl color"), + .tp_basicsize = sizeof(ColorSequenceObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_dealloc = tp_dealloc, + .tp_init = tp_init, + .tp_getset = tp_getset, + .tp_methods = tp_methods, + .tp_as_mapping = &tp_as_mapping, + .tp_iter = tp_iter, + .tp_iternext = tp_iternext, +}; diff --git a/src/types/color_sequence_interpolator/type.c b/src/types/color_sequence_interpolator/type.c new file mode 100644 index 0000000..c0b3740 --- /dev/null +++ b/src/types/color_sequence_interpolator/type.c @@ -0,0 +1,27 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include "pysicgl/types/color_sequence_interpolator.h" + +ColorSequenceInterpolatorObject* new_color_sequence_interpolator_object( + sequence_map_fn fn, void* args) { + ColorSequenceInterpolatorObject* self = + (ColorSequenceInterpolatorObject*)(ColorSequenceInterpolatorType.tp_alloc( + &ColorSequenceInterpolatorType, 0)); + if (self != NULL) { + self->fn = fn; + self->args = args; + } + + return self; +} + +PyTypeObject ColorSequenceInterpolatorType = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = + "_sicgl_core.ColorSequenceInterpolator", + .tp_doc = PyDoc_STR("sicgl color sequence interpolator"), + .tp_basicsize = sizeof(ColorSequenceInterpolatorObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; diff --git a/src/types/compositor/type.c b/src/types/compositor/type.c new file mode 100644 index 0000000..dd25b24 --- /dev/null +++ b/src/types/compositor/type.c @@ -0,0 +1,33 @@ +#define PY_SSIZE_T_CLEAN +#include +#include +// python includes first (clang-format) + +#include +#include + +#include "pysicgl/types/compositor.h" + +/** + * @brief Creates a new compositor object. + * + * @return CompositorObject* pointer to the new compositor object. + */ +CompositorObject* new_compositor_object(compositor_fn fn, void* args) { + CompositorObject* self = + (CompositorObject*)(CompositorType.tp_alloc(&CompositorType, 0)); + if (self != NULL) { + self->fn = fn; + self->args = args; + } + + return self; +} + +PyTypeObject CompositorType = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_sicgl_core.Compositor", + .tp_doc = PyDoc_STR("sicgl compositor"), + .tp_basicsize = sizeof(CompositorObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, +}; diff --git a/src/types/interface/type.c b/src/types/interface/type.c new file mode 100644 index 0000000..da1e21b --- /dev/null +++ b/src/types/interface/type.c @@ -0,0 +1,279 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include +#include + +#include "pysicgl/types/color_sequence.h" +#include "pysicgl/types/color_sequence_interpolator.h" +#include "pysicgl/types/compositor.h" +#include "pysicgl/types/interface.h" +#include "pysicgl/types/scalar_field.h" +#include "sicgl/blit.h" +#include "sicgl/domain/interface.h" +#include "sicgl/gamma.h" + +// utilities for C consumers +//////////////////////////// + +/** + * @brief Removes the screen object from the interface. + * + * @param self + * @return int + */ +int Interface_remove_screen(InterfaceObject* self) { + int ret = 0; + if (NULL == self) { + ret = -ENOMEM; + goto out; + } + + if (NULL != self->screen) { + Py_DECREF((PyObject*)self->screen); + self->interface.screen = NULL; + } + +out: + return ret; +} + +/** + * @brief Sets the screen object. + * + * @param self + * @param screen_obj + * @return int + */ +int Interface_set_screen(InterfaceObject* self, ScreenObject* screen_obj) { + int ret = 0; + if (NULL == self) { + ret = -ENOMEM; + goto out; + } + + self->screen = screen_obj; + Py_INCREF((PyObject*)self->screen); + self->interface.screen = self->screen->screen; + +out: + return ret; +} + +/** + * @brief Removes the memory object from the interface. + * + * @param self + * @return int + */ +int Interface_remove_memory(InterfaceObject* self) { + int ret = 0; + if (NULL == self) { + ret = -ENOMEM; + goto out; + } + + if (NULL != self->memory_buffer.obj) { + PyBuffer_Release(&self->memory_buffer); + self->interface.memory = NULL; + self->interface.length = 0; + } + +out: + return ret; +} + +/** + * @brief Sets the memory object. + * + * @param self + * @param bytearray_obj + * @return int + */ +int Interface_set_memory( + InterfaceObject* self, PyByteArrayObject* bytearray_obj) { + int ret = 0; + if (NULL == self) { + ret = -ENOMEM; + goto out; + } + + size_t bpp = bytes_per_pixel(); + + ret = PyObject_GetBuffer( + (PyObject*)bytearray_obj, &self->memory_buffer, PyBUF_WRITABLE); + if (0 != ret) { + goto out; + } + self->interface.memory = self->memory_buffer.buf; + self->interface.length = self->memory_buffer.len / bpp; + +out: + return ret; +} + +// getset +///////// + +/** + * @brief Get a new reference to the screen object. + * + * @param self_in + * @param closure + * @return PyObject* + * + * @note This function returns a new reference to the + * screen object. + */ +static PyObject* get_screen(PyObject* self_in, void* closure) { + (void)closure; + InterfaceObject* self = (InterfaceObject*)self_in; + // it is important to return a NEW REFERENCE to the object, + // otherwise its reference count will be deleted by the caller + // who is passed the reference and later decrements the refcount + Py_INCREF((PyObject*)self->screen); + return (PyObject*)self->screen; +} + +/** + * @brief Get a memoryview of the memory buffer. + * + * @param self_in + * @param closure + * @return PyObject* + * + * @note This function returns a new reference to the + * memoryview of the memory buffer. + */ +static PyObject* get_memory(PyObject* self_in, void* closure) { + (void)closure; + InterfaceObject* self = (InterfaceObject*)self_in; + return PyMemoryView_FromBuffer(&self->memory_buffer); +} + +/** + * @brief Set the screen object. + * + * @param self_in + * @param value + * @param closure + * @return int + * + * @note This function steals a reference to the screen + * object and releases any existing screen object. + */ +static int set_screen(PyObject* self_in, PyObject* value, void* closure) { + (void)closure; + int ret = 0; + InterfaceObject* self = (InterfaceObject*)self_in; + if (!PyObject_IsInstance((PyObject*)value, (PyObject*)&ScreenType)) { + PyErr_SetNone(PyExc_TypeError); + return -1; + } + + ret = Interface_remove_screen(self); + if (0 != ret) { + ret = -1; + goto out; + } + ret = Interface_set_screen(self, (ScreenObject*)value); + if (0 != ret) { + ret = -1; + goto out; + } + +out: + return ret; +} + +/** + * @brief Set the memory object. + * + * @param self_in + * @param value + * @param closure + * @return int + * + * @note This function relies on PyObject_GetBuffer and + * PyBuffer_Release to handle the memory buffer reference + * count. + */ +static int set_memory(PyObject* self_in, PyObject* value, void* closure) { + (void)closure; + int ret = 0; + InterfaceObject* self = (InterfaceObject*)self_in; + if (!PyObject_IsInstance((PyObject*)value, (PyObject*)&PyByteArray_Type)) { + PyErr_SetNone(PyExc_TypeError); + return -1; + } + + ret = Interface_remove_memory(self); + if (0 != ret) { + ret = -1; + goto out; + } + ret = Interface_set_memory(self, (PyByteArrayObject*)value); + if (0 != ret) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static void tp_dealloc(PyObject* self_in) { + InterfaceObject* self = (InterfaceObject*)self_in; + Interface_remove_memory(self); + Interface_remove_screen(self); + Py_TYPE(self)->tp_free(self); +} + +static int tp_init(PyObject* self_in, PyObject* args, PyObject* kwds) { + InterfaceObject* self = (InterfaceObject*)self_in; + char* keywords[] = { + "screen", + "memory", + NULL, + }; + PyObject* screen_obj; + PyByteArrayObject* memory_bytearray_obj; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O!Y", keywords, &ScreenType, &screen_obj, + &memory_bytearray_obj)) { + return -1; + } + + // set screen and memory + int ret = set_screen((PyObject*)self, screen_obj, NULL); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return -1; + } + ret = set_memory((PyObject*)self, (PyObject*)memory_bytearray_obj, NULL); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return -1; + } + + return 0; +} + +static PyGetSetDef tp_getset[] = { + {"screen", get_screen, set_screen, "screen definition", NULL}, + {"memory", get_memory, set_memory, "pixel memory", NULL}, + {NULL}, +}; + +PyTypeObject InterfaceType = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_sicgl_core.Interface", + .tp_doc = PyDoc_STR("sicgl interface"), + .tp_basicsize = sizeof(InterfaceObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_dealloc = tp_dealloc, + .tp_init = tp_init, + .tp_getset = tp_getset, +}; diff --git a/src/types/scalar_field/type.c b/src/types/scalar_field/type.c new file mode 100644 index 0000000..334dd75 --- /dev/null +++ b/src/types/scalar_field/type.c @@ -0,0 +1,159 @@ +#define PY_SSIZE_T_CLEAN +#include +// python includes first (clang-format) + +#include +#include +#include + +#include "pysicgl/types/scalar_field.h" + +/** + * @brief Deallocate the scalars memory. + * + * @param self + * @return int + */ +static int deallocate_scalars(ScalarFieldObject* self) { + int ret = 0; + if (NULL == self) { + ret = -1; + goto out; + } + + PyMem_Free(self->scalars); + self->scalars = NULL; + self->length = 0; + +out: + return ret; +} + +/** + * @brief Allocate memory for the scalars. + * + * @param self + * @param len + * @return int + */ +static int allocate_scalars(ScalarFieldObject* self, size_t len) { + int ret = 0; + if (NULL == self) { + ret = -1; + goto out; + } + + self->scalars = PyMem_Malloc(len * sizeof(double)); + if (NULL == self->scalars) { + ret = -ENOMEM; + goto out; + } + self->length = len; + +out: + return ret; +} + +// methods +////////// + +static Py_ssize_t mp_length(PyObject* self_in) { + ScalarFieldObject* self = (ScalarFieldObject*)self_in; + return self->length; +} + +static PyObject* mp_subscript(PyObject* self_in, PyObject* key) { + ScalarFieldObject* self = (ScalarFieldObject*)self_in; + size_t idx = PyLong_AsSize_t(key); + if (idx >= self->length) { + PyErr_SetNone(PyExc_IndexError); + return NULL; + } + return PyFloat_FromDouble(self->scalars[idx]); +} + +static void tp_dealloc(PyObject* self_in) { + ScalarFieldObject* self = (ScalarFieldObject*)self_in; + int ret = deallocate_scalars(self); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return; + } + Py_TYPE(self)->tp_free(self); +} + +static int tp_init(PyObject* self_in, PyObject* args, PyObject* kwds) { + ScalarFieldObject* self = (ScalarFieldObject*)self_in; + char* keywords[] = { + "scalars", + NULL, + }; + PyObject* scalars_obj; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", keywords, &scalars_obj)) { + return -1; + } + + if (PyList_Check(scalars_obj)) { + size_t len = PyList_Size(scalars_obj); + + // allocate memory for the sequence + int ret = allocate_scalars(self, len); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return -1; + } + + // copy the colors into the sequence + for (size_t idx = 0; idx < len; idx++) { + PyObject* item = PyList_GetItem(scalars_obj, idx); + if (!PyFloat_Check(item)) { + PyErr_SetNone(PyExc_TypeError); + return -1; + } + self->scalars[idx] = PyFloat_AsDouble(item); + } + + } else if (PyTuple_Check(scalars_obj)) { + size_t len = PyTuple_Size(scalars_obj); + + // allocate memory for the sequence + int ret = allocate_scalars(self, len); + if (0 != ret) { + PyErr_SetNone(PyExc_OSError); + return -1; + } + + // copy the colors into the sequence + for (size_t idx = 0; idx < len; idx++) { + PyObject* item = PyTuple_GetItem(scalars_obj, idx); + if (!PyFloat_Check(item)) { + PyErr_SetNone(PyExc_TypeError); + return -1; + } + self->scalars[idx] = PyFloat_AsDouble(item); + } + + } else { + PyErr_SetNone(PyExc_TypeError); + return -1; + } + + return 0; +} + +static PyMappingMethods tp_as_mapping = { + .mp_length = mp_length, + .mp_subscript = mp_subscript, +}; + +PyTypeObject ScalarFieldType = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_sicgl_core.ScalarField", + .tp_doc = PyDoc_STR("sicgl ScalarField"), + .tp_basicsize = sizeof(ScalarFieldObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_dealloc = tp_dealloc, + .tp_init = tp_init, + .tp_as_mapping = &tp_as_mapping, +}; diff --git a/src/screen.c b/src/types/screen/type.c similarity index 92% rename from src/screen.c rename to src/types/screen/type.c index 873caa7..56c2833 100644 --- a/src/screen.c +++ b/src/types/screen/type.c @@ -1,50 +1,57 @@ #define PY_SSIZE_T_CLEAN #include #include -// python includes must come first +// python includes first (clang-format) #include #include -#include "pysicgl/screen.h" -#include "pysicgl/utilities.h" +#include "pysicgl/types/screen.h" // fwd declarations static PyObject* tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds); // getset static PyObject* get_width(PyObject* self_in, void* closure) { + (void)closure; return PyLong_FromLong(((ScreenObject*)self_in)->screen->width); } static PyObject* get_height(PyObject* self_in, void* closure) { + (void)closure; return PyLong_FromLong(((ScreenObject*)self_in)->screen->height); } static PyObject* get_pixels(PyObject* self_in, void* closure) { + (void)closure; ScreenObject* self = (ScreenObject*)self_in; return PyLong_FromLong(self->screen->width * self->screen->height); } static PyObject* get_corners(PyObject* self_in, void* closure) { + (void)closure; ScreenObject* self = (ScreenObject*)self_in; return Py_BuildValue( "(ii)(ii)", self->screen->u0, self->screen->v0, self->screen->u1, self->screen->v1); } static PyObject* get_global_corners(PyObject* self_in, void* closure) { + (void)closure; ScreenObject* self = (ScreenObject*)self_in; return Py_BuildValue( "(ii)(ii)", self->screen->_gu0, self->screen->_gv0, self->screen->_gu1, self->screen->_gv1); } static PyObject* get_extent(PyObject* self_in, void* closure) { + (void)closure; ScreenObject* self = (ScreenObject*)self_in; return Py_BuildValue("(ii)", self->screen->width, self->screen->height); } static PyObject* get_location(PyObject* self_in, void* closure) { + (void)closure; ScreenObject* self = (ScreenObject*)self_in; return Py_BuildValue("(ii)", self->screen->lu, self->screen->lv); } static int set_extent(PyObject* self_in, PyObject* val, void* closure) { + (void)closure; ScreenObject* self = (ScreenObject*)self_in; ext_t width, height; if (!PyArg_ParseTuple(val, "(ii)", &width, &height)) { @@ -61,6 +68,7 @@ static int set_extent(PyObject* self_in, PyObject* val, void* closure) { } static int set_location(PyObject* self_in, PyObject* val, void* closure) { + (void)closure; ScreenObject* self = (ScreenObject*)self_in; ext_t lu, lv; @@ -79,6 +87,7 @@ static int set_location(PyObject* self_in, PyObject* val, void* closure) { // methods static PyObject* intersect(PyObject* self, PyObject* args) { + (void)self; PyObject* _s0; PyObject* _s1; if (!PyArg_ParseTuple(args, "O!O!", &ScreenType, &_s0, &ScreenType, &_s1)) { @@ -101,12 +110,15 @@ static PyObject* intersect(PyObject* self, PyObject* args) { } static PyObject* normalize(PyObject* self_in, PyObject* args) { + (void)args; ScreenObject* self = (ScreenObject*)self_in; int ret = screen_normalize(self->screen); if (0 != ret) { PyErr_SetNone(PyExc_OSError); return NULL; } + + Py_INCREF(Py_None); return Py_None; } @@ -128,6 +140,7 @@ static PyObject* set_corners(PyObject* self_in, PyObject* args) { return NULL; } + Py_INCREF(Py_None); return Py_None; } @@ -139,15 +152,15 @@ static PyObject* set_corners(PyObject* self_in, PyObject* args) { * @param ref * @return PyObject* */ -ScreenObject* new_screen_object(screen_t* ref) { +static ScreenObject* new_screen_object(screen_t* ref) { ScreenObject* self = (ScreenObject*)(ScreenType.tp_alloc(&ScreenType, 0)); if (self != NULL) { if (NULL == ref) { self->screen = &self->_screen; - self->_is_reference = false; + self->is_reference = false; } else { self->screen = ref; - self->_is_reference = true; + self->is_reference = true; } int ret = screen_normalize(self->screen); if (0 != ret) { @@ -170,6 +183,9 @@ ScreenObject* new_screen_object(screen_t* ref) { * @return PyObject* */ static PyObject* tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { + (void)type; + (void)args; + (void)kwds; ScreenObject* self = new_screen_object(NULL); return (PyObject*)self; } @@ -237,7 +253,7 @@ static PyGetSetDef tp_getset[] = { }; PyTypeObject ScreenType = { - PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pysicgl.Screen", + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_sicgl_core.Screen", .tp_doc = PyDoc_STR("sicgl screen"), .tp_basicsize = sizeof(ScreenObject), .tp_itemsize = 0, diff --git a/src/utilities.c b/src/utilities.c deleted file mode 100644 index 772cef1..0000000 --- a/src/utilities.c +++ /dev/null @@ -1,39 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#include -// python includes must come first - -#include - -#include "pysicgl/utilities.h" - -/** - * @brief Unpack a Python Tuple object with two elements into ext_t outputs. - * - * @param obj - * @param u - * @param v - * @return int - */ -int unpack_ext_t_tuple2(PyObject* obj, ext_t* u, ext_t* v) { - int ret = 0; - - if (NULL == u) { - ret = -ENOMEM; - goto out; - } - - if (!PyTuple_Check(obj)) { - ret = -EINVAL; - goto out; - } - if (2 != PyTuple_Size(obj)) { - ret = -EINVAL; - goto out; - } - - *u = PyLong_AsLong(PyTuple_GetItem(obj, 0)); - *v = PyLong_AsLong(PyTuple_GetItem(obj, 1)); - -out: - return ret; -} diff --git a/tests/test_color_sequence.py b/tests/test_color_sequence.py new file mode 100644 index 0000000..c922922 --- /dev/null +++ b/tests/test_color_sequence.py @@ -0,0 +1,61 @@ +import pytest +import pysicgl + + +DEFAULT_COLORS = [2] +DEFAULT_INTERPOLATOR = pysicgl.interpolation.CONTINUOUS_CIRCULAR + + +def test_initialization(): + sequence = pysicgl.ColorSequence( + colors=DEFAULT_COLORS, interpolator=DEFAULT_INTERPOLATOR + ) + + +@pytest.mark.skip(reason="Not implemented") +def test_has_len(): + sequence = pysicgl.ColorSequence( + colors=DEFAULT_COLORS, interpolator=DEFAULT_INTERPOLATOR + ) + assert hasattr(sequence, "__len__") + + +def test_len(): + sequence = pysicgl.ColorSequence( + colors=DEFAULT_COLORS, interpolator=DEFAULT_INTERPOLATOR + ) + assert len(sequence) == len(DEFAULT_COLORS) + + +def test_subscr(): + sequence = pysicgl.ColorSequence( + colors=DEFAULT_COLORS, interpolator=DEFAULT_INTERPOLATOR + ) + for idx in range(len(DEFAULT_COLORS)): + assert sequence[idx] == DEFAULT_COLORS[idx] + + +@pytest.mark.skip(reason="Not implemented") +def test_length(): + sequence = pysicgl.ColorSequence( + colors=DEFAULT_COLORS, interpolator=DEFAULT_INTERPOLATOR + ) + assert len(sequence) == 0 + + sequence.colors = ((0, 0, 0, 0),) + assert len(sequence) == 1 + + sequence.colors = ((0, 0, 0, 0), (0, 0, 0, 0)) + assert len(sequence) == 2 + + +def test_iterator(): + sequence = pysicgl.ColorSequence( + colors=DEFAULT_COLORS, interpolator=DEFAULT_INTERPOLATOR + ) + assert hasattr(sequence, "__iter__") + assert iter(sequence) is sequence + assert len(list(sequence)) == len(DEFAULT_COLORS) + + for color in sequence: + assert color == DEFAULT_COLORS[0] diff --git a/tests/test_compositor.py b/tests/test_compositor.py new file mode 100644 index 0000000..20b2eb5 --- /dev/null +++ b/tests/test_compositor.py @@ -0,0 +1,50 @@ +import pysicgl + + +def test_builtin_compositors(): + expected_compositors = ( + "DIRECT_SET", + "DIRECT_CLEAR", + "DIRECT_NONE", + "BIT_AND", + "BIT_OR", + "BIT_XOR", + "BIT_NAND", + "BIT_NOR", + "BIT_XNOR", + # # These bitwise compositors are not implemented yet in sicgl. + # "BIT_NOT_SOURCE", + # "BIT_NOT_DESTINATION", + "CHANNEL_MIN", + "CHANNEL_MAX", + "CHANNEL_SUM", + "CHANNEL_DIFF", + "CHANNEL_DIFF_REVERSE", + "CHANNEL_MULTIPLY", + "CHANNEL_DIVIDE", + "CHANNEL_DIVIDE_REVERSE", + "CHANNEL_SUM_CLAMPED", + "CHANNEL_DIFF_CLAMPED", + "CHANNEL_DIFF_REVERSE_CLAMPED", + "CHANNEL_MULTIPLY_CLAMPED", + "CHANNEL_DIVIDE_CLAMPED", + "CHANNEL_DIVIDE_REVERSE_CLAMPED", + "ALPHA_CLEAR", + "ALPHA_COPY", + "ALPHA_DESTINATION", + "ALPHA_SOURCE_OVER", + "ALPHA_DESTINATION_OVER", + "ALPHA_SOURCE_IN", + "ALPHA_DESTINATION_IN", + "ALPHA_SOURCE_OUT", + "ALPHA_DESTINATION_OUT", + "ALPHA_SOURCE_ATOP", + "ALPHA_DESTINATION_ATOP", + "ALPHA_XOR", + "ALPHA_LIGHTER", + ) + + for compositor_name in expected_compositors: + assert hasattr(pysicgl.composition, compositor_name) + compositor = getattr(pysicgl.composition, compositor_name) + assert isinstance(compositor, pysicgl.Compositor) diff --git a/tests/test_functional.py b/tests/test_functional.py new file mode 100644 index 0000000..aaf8137 --- /dev/null +++ b/tests/test_functional.py @@ -0,0 +1,18 @@ +import pytest +import pysicgl + + +def test_module_exists(): + assert hasattr(pysicgl, "functional") + + +def test_has_gamma_correct(): + assert hasattr(pysicgl.functional, "gamma_correct") + + +def test_has_get_pixel_at_offset(): + assert hasattr(pysicgl.functional, "get_pixel_at_offset") + + +def test_has_get_pixel_at_coordinates(): + assert hasattr(pysicgl.functional, "get_pixel_at_coordinates") diff --git a/tests/test_interface.py b/tests/test_interface.py new file mode 100644 index 0000000..530191e --- /dev/null +++ b/tests/test_interface.py @@ -0,0 +1,12 @@ +import pytest +import pysicgl + + +@pytest.fixture +def interface(): + WIDTH = 1 + HEIGHT = 1 + display_screen = pysicgl.Screen((WIDTH, HEIGHT)) + display_memory = pysicgl.allocate_pixel_memory(display_screen.pixels) + _interface = pysicgl.Interface(display_screen, display_memory) + return _interface diff --git a/tests/test_module.py b/tests/test_module.py new file mode 100644 index 0000000..79ad063 --- /dev/null +++ b/tests/test_module.py @@ -0,0 +1,296 @@ +import pytest +import pysicgl + + +def test_allocate_pixel_memory(): + NUM_PIXELS = 1 + bpp = pysicgl.get_bytes_per_pixel() + memory = pysicgl.allocate_pixel_memory(NUM_PIXELS) + assert len(memory) == NUM_PIXELS * 4 + + +def test_gamma_correction(): + # create a screen definition with extent (WIDTH, HEIGHT) + WIDTH = 1 + HEIGHT = 1 + display_screen = pysicgl.Screen((WIDTH, HEIGHT)) + display_memory = pysicgl.allocate_pixel_memory(display_screen.pixels) + display = pysicgl.Interface(display_screen, display_memory) + + output_memory = pysicgl.allocate_pixel_memory(display_screen.pixels) + output = pysicgl.Interface(display_screen, display_memory) + + # expected gamma table + GAMMA_TABLE = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 6, + 7, + 7, + 7, + 7, + 8, + 8, + 8, + 9, + 9, + 9, + 10, + 10, + 10, + 11, + 11, + 11, + 12, + 12, + 13, + 13, + 13, + 14, + 14, + 15, + 15, + 16, + 16, + 17, + 17, + 18, + 18, + 19, + 19, + 20, + 20, + 21, + 21, + 22, + 22, + 23, + 24, + 24, + 25, + 25, + 26, + 27, + 27, + 28, + 29, + 29, + 30, + 31, + 32, + 32, + 33, + 34, + 35, + 35, + 36, + 37, + 38, + 39, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 50, + 51, + 52, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 66, + 67, + 68, + 69, + 70, + 72, + 73, + 74, + 75, + 77, + 78, + 79, + 81, + 82, + 83, + 85, + 86, + 87, + 89, + 90, + 92, + 93, + 95, + 96, + 98, + 99, + 101, + 102, + 104, + 105, + 107, + 109, + 110, + 112, + 114, + 115, + 117, + 119, + 120, + 122, + 124, + 126, + 127, + 129, + 131, + 133, + 135, + 137, + 138, + 140, + 142, + 144, + 146, + 148, + 150, + 152, + 154, + 156, + 158, + 160, + 162, + 164, + 167, + 169, + 171, + 173, + 175, + 177, + 180, + 182, + 184, + 186, + 189, + 191, + 193, + 196, + 198, + 200, + 203, + 205, + 208, + 210, + 213, + 215, + 218, + 220, + 223, + 225, + 228, + 231, + 233, + 236, + 239, + 241, + 244, + 247, + 249, + 252, + 255, + ] + + # test gamma correction for all values + for idx in range(len(GAMMA_TABLE)): + color = pysicgl.functional.color_from_rgba((idx, idx, idx, idx)) + pysicgl.functional.interface_pixel(display, color, (0, 0)) + + pysicgl.functional.gamma_correct(display, output) + + # check that the gamma correction was applied + pixel = pysicgl.functional.get_pixel_at_coordinates(output, (0, 0)) + r, g, b, a = pysicgl.functional.color_to_rgba(pixel) + assert r == GAMMA_TABLE[idx] + assert g == GAMMA_TABLE[idx] + assert b == GAMMA_TABLE[idx] + assert a == idx diff --git a/tests/test_screen.py b/tests/test_screen.py index 0f5a789..8118756 100644 --- a/tests/test_screen.py +++ b/tests/test_screen.py @@ -1,5 +1,5 @@ -import pytest import pysicgl +from tests.testutils import vec_add def test_init_extent(): @@ -12,8 +12,6 @@ def test_init_extent(): def test_init_normalization(): - from testutils import vec_add - extent = (20, 20) location = (1, 1) diff --git a/third-party/sicgl b/third-party/sicgl index 8530898..9960b49 160000 --- a/third-party/sicgl +++ b/third-party/sicgl @@ -1 +1 @@ -Subproject commit 853089889f8145690b13c700e1fe3d60c5021d8c +Subproject commit 9960b494d0dd9ce38dd47ac8d35199999551f178