123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 |
- /*
- * The Python Imaging Library.
- *
- * 2D path utilities
- *
- * history:
- * 1996-11-04 fl Added to PIL (incomplete)
- * 1996-11-05 fl Added sequence semantics
- * 1997-02-28 fl Fixed getbbox
- * 1997-06-12 fl Added id attribute
- * 1997-06-14 fl Added slicing and setitem
- * 1998-12-29 fl Improved sequence handling (from Richard Jones)
- * 1999-01-10 fl Fixed IndexError test for 1.5 (from Fred Drake)
- * 2000-10-12 fl Added special cases for tuples and lists
- * 2002-10-27 fl Added clipping boilerplate
- * 2004-09-19 fl Added tolist(flat) variant
- * 2005-05-06 fl Added buffer interface support to path constructor
- *
- * notes:
- * FIXME: fill in remaining slots in the sequence api
- *
- * Copyright (c) 1997-2005 by Secret Labs AB
- * Copyright (c) 1997-2005 by Fredrik Lundh
- *
- * See the README file for information on usage and redistribution.
- */
- #include "Python.h"
- #include "Imaging.h"
- #include <math.h>
- #include "py3.h"
- /* compatibility wrappers (defined in _imaging.c) */
- extern int PyImaging_CheckBuffer(PyObject* buffer);
- extern int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view);
- /* -------------------------------------------------------------------- */
- /* Class */
- /* -------------------------------------------------------------------- */
- typedef struct {
- PyObject_HEAD
- Py_ssize_t count;
- double *xy;
- int index; /* temporary use, e.g. in decimate */
- } PyPathObject;
- static PyTypeObject PyPathType;
- static double*
- alloc_array(Py_ssize_t count)
- {
- double* xy;
- if (count < 0) {
- PyErr_NoMemory();
- return NULL;
- }
- if (count > (SIZE_MAX / (2 * sizeof(double))) - 1 ) {
- PyErr_NoMemory();
- return NULL;
- }
- xy = malloc(2 * count * sizeof(double) + 1);
- if (!xy)
- PyErr_NoMemory();
- return xy;
- }
- static PyPathObject*
- path_new(Py_ssize_t count, double* xy, int duplicate)
- {
- PyPathObject *path;
- if (duplicate) {
- /* duplicate path */
- double* p = alloc_array(count);
- if (!p)
- return NULL;
- memcpy(p, xy, count * 2 * sizeof(double));
- xy = p;
- }
- if (PyType_Ready(&PyPathType) < 0) {
- free(xy);
- return NULL;
- }
- path = PyObject_New(PyPathObject, &PyPathType);
- if (path == NULL) {
- free(xy);
- return NULL;
- }
- path->count = count;
- path->xy = xy;
- return path;
- }
- static void
- path_dealloc(PyPathObject* path)
- {
- free(path->xy);
- PyObject_Del(path);
- }
- /* -------------------------------------------------------------------- */
- /* Helpers */
- /* -------------------------------------------------------------------- */
- #define PyPath_Check(op) (Py_TYPE(op) == &PyPathType)
- Py_ssize_t
- PyPath_Flatten(PyObject* data, double **pxy)
- {
- Py_ssize_t i, j, n;
- double *xy;
- if (PyPath_Check(data)) {
- /* This was another path object. */
- PyPathObject *path = (PyPathObject*) data;
- xy = alloc_array(path->count);
- if (!xy)
- return -1;
- memcpy(xy, path->xy, 2 * path->count * sizeof(double));
- *pxy = xy;
- return path->count;
- }
- if (PyImaging_CheckBuffer(data)) {
- /* Assume the buffer contains floats */
- Py_buffer buffer;
- if (PyImaging_GetBuffer(data, &buffer) == 0) {
- float *ptr = (float*) buffer.buf;
- n = buffer.len / (2 * sizeof(float));
- xy = alloc_array(n);
- if (!xy)
- return -1;
- for (i = 0; i < n+n; i++)
- xy[i] = ptr[i];
- *pxy = xy;
- PyBuffer_Release(&buffer);
- return n;
- }
- PyErr_Clear();
- }
- if (!PySequence_Check(data)) {
- PyErr_SetString(PyExc_TypeError, "argument must be sequence");
- return -1;
- }
- j = 0;
- n = PyObject_Length(data);
- /* Just in case __len__ breaks (or doesn't exist) */
- if (PyErr_Occurred())
- return -1;
- /* Allocate for worst case */
- xy = alloc_array(n);
- if (!xy)
- return -1;
- /* Copy table to path array */
- if (PyList_Check(data)) {
- for (i = 0; i < n; i++) {
- double x, y;
- PyObject *op = PyList_GET_ITEM(data, i);
- if (PyFloat_Check(op))
- xy[j++] = PyFloat_AS_DOUBLE(op);
- else if (PyInt_Check(op))
- xy[j++] = (float) PyInt_AS_LONG(op);
- else if (PyNumber_Check(op))
- xy[j++] = PyFloat_AsDouble(op);
- else if (PyArg_ParseTuple(op, "dd", &x, &y)) {
- xy[j++] = x;
- xy[j++] = y;
- } else {
- free(xy);
- return -1;
- }
- }
- } else if (PyTuple_Check(data)) {
- for (i = 0; i < n; i++) {
- double x, y;
- PyObject *op = PyTuple_GET_ITEM(data, i);
- if (PyFloat_Check(op))
- xy[j++] = PyFloat_AS_DOUBLE(op);
- else if (PyInt_Check(op))
- xy[j++] = (float) PyInt_AS_LONG(op);
- else if (PyNumber_Check(op))
- xy[j++] = PyFloat_AsDouble(op);
- else if (PyArg_ParseTuple(op, "dd", &x, &y)) {
- xy[j++] = x;
- xy[j++] = y;
- } else {
- free(xy);
- return -1;
- }
- }
- } else {
- for (i = 0; i < n; i++) {
- double x, y;
- PyObject *op = PySequence_GetItem(data, i);
- if (!op) {
- /* treat IndexError as end of sequence */
- if (PyErr_Occurred() &&
- PyErr_ExceptionMatches(PyExc_IndexError)) {
- PyErr_Clear();
- break;
- } else {
- free(xy);
- return -1;
- }
- }
- if (PyFloat_Check(op))
- xy[j++] = PyFloat_AS_DOUBLE(op);
- else if (PyInt_Check(op))
- xy[j++] = (float) PyInt_AS_LONG(op);
- else if (PyNumber_Check(op))
- xy[j++] = PyFloat_AsDouble(op);
- else if (PyArg_ParseTuple(op, "dd", &x, &y)) {
- xy[j++] = x;
- xy[j++] = y;
- } else {
- Py_DECREF(op);
- free(xy);
- return -1;
- }
- Py_DECREF(op);
- }
- }
- if (j & 1) {
- PyErr_SetString(PyExc_ValueError, "wrong number of coordinates");
- free(xy);
- return -1;
- }
- *pxy = xy;
- return j/2;
- }
- /* -------------------------------------------------------------------- */
- /* Factories */
- /* -------------------------------------------------------------------- */
- PyObject*
- PyPath_Create(PyObject* self, PyObject* args)
- {
- PyObject* data;
- Py_ssize_t count;
- double *xy;
- if (PyArg_ParseTuple(args, "n:Path", &count)) {
- /* number of vertices */
- xy = alloc_array(count);
- if (!xy)
- return NULL;
- } else {
- /* sequence or other path */
- PyErr_Clear();
- if (!PyArg_ParseTuple(args, "O", &data))
- return NULL;
- count = PyPath_Flatten(data, &xy);
- if (count < 0)
- return NULL;
- }
- return (PyObject*) path_new(count, xy, 0);
- }
- /* -------------------------------------------------------------------- */
- /* Methods */
- /* -------------------------------------------------------------------- */
- static PyObject*
- path_compact(PyPathObject* self, PyObject* args)
- {
- /* Simple-minded method to shorten path. A point is removed if
- the city block distance to the previous point is less than the
- given distance */
- Py_ssize_t i, j;
- double *xy;
- double cityblock = 2.0;
- if (!PyArg_ParseTuple(args, "|d:compact", &cityblock))
- return NULL;
- xy = self->xy;
- /* remove bogus vertices */
- for (i = j = 1; i < self->count; i++) {
- if (fabs(xy[j+j-2]-xy[i+i]) + fabs(xy[j+j-1]-xy[i+i+1]) >= cityblock) {
- xy[j+j] = xy[i+i];
- xy[j+j+1] = xy[i+i+1];
- j++;
- }
- }
- i = self->count - j;
- self->count = j;
- /* shrink coordinate array */
- /* malloc check ok, self->count is smaller than it was before */
- self->xy = realloc(self->xy, 2 * self->count * sizeof(double));
- return Py_BuildValue("i", i); /* number of removed vertices */
- }
- static PyObject*
- path_getbbox(PyPathObject* self, PyObject* args)
- {
- /* Find bounding box */
- Py_ssize_t i;
- double *xy;
- double x0, y0, x1, y1;
- if (!PyArg_ParseTuple(args, ":getbbox"))
- return NULL;
- xy = self->xy;
- x0 = x1 = xy[0];
- y0 = y1 = xy[1];
- for (i = 1; i < self->count; i++) {
- if (xy[i+i] < x0)
- x0 = xy[i+i];
- if (xy[i+i] > x1)
- x1 = xy[i+i];
- if (xy[i+i+1] < y0)
- y0 = xy[i+i+1];
- if (xy[i+i+1] > y1)
- y1 = xy[i+i+1];
- }
- return Py_BuildValue("dddd", x0, y0, x1, y1);
- }
- static PyObject*
- path_getitem(PyPathObject* self, Py_ssize_t i)
- {
- if (i < 0)
- i = self->count + i;
- if (i < 0 || i >= self->count) {
- PyErr_SetString(PyExc_IndexError, "path index out of range");
- return NULL;
- }
- return Py_BuildValue("dd", self->xy[i+i], self->xy[i+i+1]);
- }
- static PyObject*
- path_getslice(PyPathObject* self, Py_ssize_t ilow, Py_ssize_t ihigh)
- {
- /* adjust arguments */
- if (ilow < 0)
- ilow = 0;
- else if (ilow >= self->count)
- ilow = self->count;
- if (ihigh < 0)
- ihigh = 0;
- if (ihigh < ilow)
- ihigh = ilow;
- else if (ihigh > self->count)
- ihigh = self->count;
- return (PyObject*) path_new(ihigh - ilow, self->xy + ilow * 2, 1);
- }
- static Py_ssize_t
- path_len(PyPathObject* self)
- {
- return self->count;
- }
- static PyObject*
- path_map(PyPathObject* self, PyObject* args)
- {
- /* Map coordinate set through function */
- Py_ssize_t i;
- double *xy;
- PyObject* function;
- if (!PyArg_ParseTuple(args, "O:map", &function))
- return NULL;
- xy = self->xy;
- /* apply function to coordinate set */
- for (i = 0; i < self->count; i++) {
- double x = xy[i+i];
- double y = xy[i+i+1];
- PyObject* item = PyObject_CallFunction(function, "dd", x, y);
- if (!item || !PyArg_ParseTuple(item, "dd", &x, &y)) {
- Py_XDECREF(item);
- return NULL;
- }
- xy[i+i] = x;
- xy[i+i+1] = y;
- Py_DECREF(item);
- }
- Py_INCREF(Py_None);
- return Py_None;
- }
- static int
- path_setitem(PyPathObject* self, Py_ssize_t i, PyObject* op)
- {
- double* xy;
- if (i < 0 || i >= self->count) {
- PyErr_SetString(PyExc_IndexError,
- "path assignment index out of range");
- return -1;
- }
- if (op == NULL) {
- PyErr_SetString(PyExc_TypeError,
- "cannot delete from path");
- return -1;
- }
- xy = &self->xy[i+i];
- if (!PyArg_ParseTuple(op, "dd", &xy[0], &xy[1]))
- return -1;
- return 0;
- }
- static PyObject*
- path_tolist(PyPathObject* self, PyObject* args)
- {
- PyObject *list;
- Py_ssize_t i;
- int flat = 0;
- if (!PyArg_ParseTuple(args, "|i:tolist", &flat))
- return NULL;
- if (flat) {
- list = PyList_New(self->count*2);
- for (i = 0; i < self->count*2; i++) {
- PyObject* item;
- item = PyFloat_FromDouble(self->xy[i]);
- if (!item)
- goto error;
- PyList_SetItem(list, i, item);
- }
- } else {
- list = PyList_New(self->count);
- for (i = 0; i < self->count; i++) {
- PyObject* item;
- item = Py_BuildValue("dd", self->xy[i+i], self->xy[i+i+1]);
- if (!item)
- goto error;
- PyList_SetItem(list, i, item);
- }
- }
- return list;
- error:
- Py_DECREF(list);
- return NULL;
- }
- static PyObject*
- path_transform(PyPathObject* self, PyObject* args)
- {
- /* Apply affine transform to coordinate set */
- Py_ssize_t i;
- double *xy;
- double a, b, c, d, e, f;
- double wrap = 0.0;
- if (!PyArg_ParseTuple(args, "(dddddd)|d:transform",
- &a, &b, &c, &d, &e, &f,
- &wrap))
- return NULL;
- xy = self->xy;
- /* transform the coordinate set */
- if (b == 0.0 && d == 0.0)
- /* scaling */
- for (i = 0; i < self->count; i++) {
- xy[i+i] = a*xy[i+i]+c;
- xy[i+i+1] = e*xy[i+i+1]+f;
- }
- else
- /* affine transform */
- for (i = 0; i < self->count; i++) {
- double x = xy[i+i];
- double y = xy[i+i+1];
- xy[i+i] = a*x+b*y+c;
- xy[i+i+1] = d*x+e*y+f;
- }
- /* special treatment of geographical map data */
- if (wrap != 0.0)
- for (i = 0; i < self->count; i++)
- xy[i+i] = fmod(xy[i+i], wrap);
- Py_INCREF(Py_None);
- return Py_None;
- }
- static struct PyMethodDef methods[] = {
- {"getbbox", (PyCFunction)path_getbbox, 1},
- {"tolist", (PyCFunction)path_tolist, 1},
- {"compact", (PyCFunction)path_compact, 1},
- {"map", (PyCFunction)path_map, 1},
- {"transform", (PyCFunction)path_transform, 1},
- {NULL, NULL} /* sentinel */
- };
- static PyObject*
- path_getattr_id(PyPathObject* self, void* closure)
- {
- return Py_BuildValue("n", (Py_ssize_t) self->xy);
- }
- static struct PyGetSetDef getsetters[] = {
- { "id", (getter) path_getattr_id },
- { NULL }
- };
- static PyObject*
- path_subscript(PyPathObject* self, PyObject* item) {
- if (PyIndex_Check(item)) {
- Py_ssize_t i;
- i = PyNumber_AsSsize_t(item, PyExc_IndexError);
- if (i == -1 && PyErr_Occurred())
- return NULL;
- return path_getitem(self, i);
- }
- if (PySlice_Check(item)) {
- int len = 4;
- Py_ssize_t start, stop, step, slicelength;
- #if PY_VERSION_HEX >= 0x03020000
- if (PySlice_GetIndicesEx(item, len, &start, &stop, &step, &slicelength) < 0)
- return NULL;
- #else
- if (PySlice_GetIndicesEx((PySliceObject*)item, len, &start, &stop, &step, &slicelength) < 0)
- return NULL;
- #endif
- if (slicelength <= 0) {
- double *xy = alloc_array(0);
- return (PyObject*) path_new(0, xy, 0);
- }
- else if (step == 1) {
- return path_getslice(self, start, stop);
- }
- else {
- PyErr_SetString(PyExc_TypeError, "slice steps not supported");
- return NULL;
- }
- }
- else {
- PyErr_Format(PyExc_TypeError,
- "Path indices must be integers, not %.200s",
- Py_TYPE(item)->tp_name);
- return NULL;
- }
- }
- static PySequenceMethods path_as_sequence = {
- (lenfunc)path_len, /*sq_length*/
- (binaryfunc)0, /*sq_concat*/
- (ssizeargfunc)0, /*sq_repeat*/
- (ssizeargfunc)path_getitem, /*sq_item*/
- (ssizessizeargfunc)path_getslice, /*sq_slice*/
- (ssizeobjargproc)path_setitem, /*sq_ass_item*/
- (ssizessizeobjargproc)0, /*sq_ass_slice*/
- };
- static PyMappingMethods path_as_mapping = {
- (lenfunc)path_len,
- (binaryfunc)path_subscript,
- NULL
- };
- static PyTypeObject PyPathType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- "Path", /*tp_name*/
- sizeof(PyPathObject), /*tp_size*/
- 0, /*tp_itemsize*/
- /* methods */
- (destructor)path_dealloc, /*tp_dealloc*/
- 0, /*tp_print*/
- 0, /*tp_getattr*/
- 0, /*tp_setattr*/
- 0, /*tp_compare*/
- 0, /*tp_repr*/
- 0, /*tp_as_number */
- &path_as_sequence, /*tp_as_sequence */
- &path_as_mapping, /*tp_as_mapping */
- 0, /*tp_hash*/
- 0, /*tp_call*/
- 0, /*tp_str*/
- 0, /*tp_getattro*/
- 0, /*tp_setattro*/
- 0, /*tp_as_buffer*/
- Py_TPFLAGS_DEFAULT, /*tp_flags*/
- 0, /*tp_doc*/
- 0, /*tp_traverse*/
- 0, /*tp_clear*/
- 0, /*tp_richcompare*/
- 0, /*tp_weaklistoffset*/
- 0, /*tp_iter*/
- 0, /*tp_iternext*/
- methods, /*tp_methods*/
- 0, /*tp_members*/
- getsetters, /*tp_getset*/
- };
|