mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 18:06:18 +03:00
641 lines
16 KiB
C
641 lines
16 KiB
C
/*
|
|
* 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 <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;
|
|
}
|
|
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)
|
|
return NULL;
|
|
|
|
path = PyObject_New(PyPathObject, &PyPathType);
|
|
if (path == NULL)
|
|
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)
|
|
|
|
int
|
|
PyPath_Flatten(PyObject* data, double **pxy)
|
|
{
|
|
int 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) {
|
|
int n = buffer.len / (2 * sizeof(float));
|
|
float *ptr = (float*) buffer.buf;
|
|
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 */
|
|
int 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 */
|
|
self->xy = realloc(self->xy, 2 * self->count * sizeof(double));
|
|
|
|
return Py_BuildValue("i", i); /* number of removed vertices */
|
|
}
|
|
|
|
static PyObject*
|
|
path_clip_polygon(PyPathObject* self, PyObject* args)
|
|
{
|
|
/* Clip path representing a single polygon */
|
|
PyErr_SetString(PyExc_RuntimeError, "not yet implemented");
|
|
return NULL;
|
|
}
|
|
|
|
static PyObject*
|
|
path_clip_polyline(PyPathObject* self, PyObject* args)
|
|
{
|
|
/* Clip path representing a single polyline (outline) */
|
|
PyErr_SetString(PyExc_RuntimeError, "not yet implemented");
|
|
return NULL;
|
|
}
|
|
|
|
static PyObject*
|
|
path_getbbox(PyPathObject* self, PyObject* args)
|
|
{
|
|
/* Find bounding box */
|
|
int 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, int 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 */
|
|
int 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, int 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;
|
|
int 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 */
|
|
int 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},
|
|
{"clip_polygon", (PyCFunction)path_clip_polygon, 1},
|
|
{"clip_polyline", (PyCFunction)path_clip_polyline, 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*/
|
|
};
|
|
|