mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
af5228896a
This commit: * Adds Python 3 module initialization functions. I split out the main init of each module into a static setup_module function. * Adds a py3.h which unifies int/long in Python 3 and unicode/bytes in Python 2. _imagingft.c unfortunately looks a little kludgy after this because it was already using PyUnicode functions, and I had to mix and match there manually. With this commit, the modules all build successfully under Python 3. What this commit does NOT do is patch all of the uses of PyArg_ParseTuple and Py_BuildValue, which all need to be checked for proper use of bytes and unicode codes. It also does not let selftest.py run yet, because there are probably hundreds of issues to fix in the Python code itself.
606 lines
15 KiB
C
606 lines
15 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>
|
|
|
|
#if PY_VERSION_HEX < 0x01060000
|
|
#define PyObject_New PyObject_NEW
|
|
#define PyObject_Del PyMem_DEL
|
|
#endif
|
|
|
|
#if PY_VERSION_HEX < 0x02050000
|
|
#define Py_ssize_t int
|
|
#define lenfunc inquiry
|
|
#define ssizeargfunc intargfunc
|
|
#define ssizessizeargfunc intintargfunc
|
|
#define ssizeobjargproc intobjargproc
|
|
#define ssizessizeobjargproc intintobjargproc
|
|
#endif
|
|
|
|
#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(int 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, "i: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) {
|
|
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("l", (long) self->xy);
|
|
}
|
|
|
|
static struct PyGetSetDef getsetters[] = {
|
|
{ "id", (getter) path_getattr_id },
|
|
{ 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 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 */
|
|
0, /*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*/
|
|
};
|
|
|