Skip to content

Commit 97eb7c0

Browse files
committed
WIP -- First light of round trip of image -> arrow -> image
* basic safety included * only respecting the types we emit
1 parent e1ef083 commit 97eb7c0

File tree

5 files changed

+178
-5
lines changed

5 files changed

+178
-5
lines changed

Tests/test_arrow.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from PIL import Image
88

9-
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
9+
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature, assert_image_equal
1010

1111
from typing import Any # undone
1212

@@ -64,6 +64,11 @@ def test_to_array(mode: str, dtype: Any, mask: Any ) -> None:
6464
_test_img_equals_pyarray(img, arr, mask)
6565
assert arr.type == dtype
6666

67+
reloaded = Image.fromarrow(arr, mode, img.size)
68+
69+
assert reloaded
70+
71+
assert_image_equal(img, reloaded)
6772

6873
def test_lifetime():
6974
# valgrind shouldn't error out here.

src/PIL/Image.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3258,6 +3258,14 @@ class SupportsArrayInterface(Protocol):
32583258
def __array_interface__(self) -> dict[str, Any]:
32593259
raise NotImplementedError()
32603260

3261+
class SupportsArrowArrayInterface(Protocol):
3262+
"""
3263+
An object that has an ``__arrow_c_array__`` method corresponding to the arrow c data interface.
3264+
"""
3265+
3266+
def __arrow_c_array__(self, requested_schema:"PyCapsule"=None) -> tuple["PyCapsule", "PyCapsule"]:
3267+
raise NotImplementedError()
3268+
32613269

32623270
def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
32633271
"""
@@ -3347,6 +3355,18 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
33473355
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
33483356

33493357

3358+
def fromarrow(obj: SupportsArrowArrayIngerface, mode, size) -> ImageFile.ImageFile:
3359+
if not hasattr(obj, '__arrow_c_array__'):
3360+
raise ValueError("arrow_c_array interface not found")
3361+
3362+
(schema_capsule, array_capsule) = obj.__arrow_c_array__()
3363+
_im = core.new_arrow(mode, size, schema_capsule, array_capsule)
3364+
if (_im):
3365+
return Image()._new(_im)
3366+
3367+
return None
3368+
3369+
33503370
def fromqimage(im: ImageQt.QImage) -> ImageFile.ImageFile:
33513371
"""Creates an image instance from a QImage image"""
33523372
from . import ImageQt

src/_imaging.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@
8989
#endif
9090

9191
#include "libImaging/Imaging.h"
92-
#include "libImaging/Arrow.h"
9392

9493
#define _USE_MATH_DEFINES
9594
#include <math.h>
@@ -261,6 +260,38 @@ PyObject* ExportArrowArrayPyCapsule(ImagingObject *self) {
261260
}
262261

263262

263+
static PyObject *
264+
_new_arrow(PyObject *self, PyObject *args) {
265+
char *mode;
266+
int xsize, ysize;
267+
PyObject *schema_capsule, *array_capsule;
268+
PyObject *ret;
269+
270+
if (!PyArg_ParseTuple(args, "s(ii)OO", &mode, &xsize, &ysize,
271+
&schema_capsule, &array_capsule)) {
272+
return NULL;
273+
}
274+
275+
struct ArrowSchema* schema =
276+
(struct ArrowSchema*)PyCapsule_GetPointer(schema_capsule, "arrow_schema");
277+
278+
struct ArrowArray* array =
279+
(struct ArrowArray*)PyCapsule_GetPointer(array_capsule, "arrow_array");
280+
281+
ret = PyImagingNew(ImagingNewArrow(mode, xsize, ysize, schema, array));
282+
if (schema->release){
283+
schema->release(schema);
284+
schema->release = NULL;
285+
}
286+
if (!ret && array->release) {
287+
array->release(array);
288+
array->release = NULL;
289+
}
290+
return ret;
291+
}
292+
293+
294+
264295
/* -------------------------------------------------------------------- */
265296
/* EXCEPTION REROUTING */
266297
/* -------------------------------------------------------------------- */
@@ -4258,6 +4289,7 @@ static PyMethodDef functions[] = {
42584289
{"fill", (PyCFunction)_fill, METH_VARARGS},
42594290
{"new", (PyCFunction)_new, METH_VARARGS},
42604291
{"new_block", (PyCFunction)_new_block, METH_VARARGS},
4292+
{"new_arrow", (PyCFunction)_new_arrow, METH_VARARGS},
42614293
{"merge", (PyCFunction)_merge, METH_VARARGS},
42624294

42634295
/* Functions */

src/libImaging/Imaging.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ extern "C" {
2020
#define M_PI 3.1415926535897932384626433832795
2121
#endif
2222

23+
#include "Arrow.h"
24+
2325
/* -------------------------------------------------------------------- */
2426

2527
/*
@@ -107,11 +109,17 @@ struct ImagingMemoryInstance {
107109

108110
/* arrow */
109111
int arrow_borrow; /* Number of arrow arrays that have been allocated */
112+
char band_names[4][3]; /* names of bands, max 2 char + null terminator */
113+
char arrow_band_format[2]; /* single character + null terminator */
114+
115+
int read_only; /* flag for read-only. set for arrow borrowed arrays */
116+
struct ArrowArray *arrow_array_capsule; /* upstream arrow array source */
117+
110118
int blocks_count; /* Number of blocks that have been allocated */
111119
int lines_per_block; /* Number of lines in a block have been allocated */
112120

113-
char band_names[4][3]; /* names of bands, max 2 char + null terminator */
114-
char arrow_band_format[2]; /* single character + null terminator */
121+
122+
115123
};
116124

117125
#define IMAGING_PIXEL_1(im, x, y) ((im)->image8[(y)][(x)])
@@ -195,6 +203,11 @@ ImagingDelete(Imaging im);
195203
extern Imaging
196204
ImagingNewBlock(const char *mode, int xsize, int ysize);
197205

206+
extern Imaging
207+
ImagingNewArrow(const char *mode, int xsize, int ysize,
208+
struct ArrowSchema *schema,
209+
struct ArrowArray *external_array);
210+
198211
extern Imaging
199212
ImagingNewPrologue(const char *mode, int xsize, int ysize);
200213
extern Imaging
@@ -712,7 +725,6 @@ _imaging_tell_pyFd(PyObject *fd);
712725

713726
/* Arrow */
714727

715-
#include "Arrow.h"
716728
extern int export_imaging_array(Imaging im, struct ArrowArray* array);
717729
extern int export_imaging_schema(Imaging im, struct ArrowSchema* schema);
718730
extern void export_uint32_type(struct ArrowSchema* schema);

src/libImaging/Storage.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
278278
break;
279279
}
280280

281+
// UNDONE -- not accurate for arrow
281282
MUTEX_LOCK(&ImagingDefaultArena.mutex);
282283
ImagingDefaultArena.stats_new_count += 1;
283284
MUTEX_UNLOCK(&ImagingDefaultArena.mutex);
@@ -556,6 +557,62 @@ ImagingAllocateBlock(Imaging im) {
556557
return im;
557558
}
558559

560+
/* Borrowed Arrow Storage Type */
561+
/* --------------------------- */
562+
/* Don't allocate the image. */
563+
564+
565+
static void
566+
ImagingDestroyArrow(Imaging im) {
567+
if (im->arrow_array_capsule && im->arrow_array_capsule->release) {
568+
im->arrow_array_capsule->release(im->arrow_array_capsule);
569+
im->arrow_array_capsule->release = NULL;
570+
im->arrow_array_capsule = NULL;
571+
}
572+
}
573+
574+
Imaging
575+
ImagingAllocateArrow(Imaging im, struct ArrowArray *external_array) {
576+
/* don't really allocate, but naming patterns */
577+
Py_ssize_t y, i;
578+
579+
char* borrowed_buffer = NULL;
580+
struct ArrowArray* arr = external_array;
581+
582+
/* overflow check for malloc */
583+
if (im->linesize && im->ysize > INT_MAX / im->linesize) {
584+
return (Imaging)ImagingError_MemoryError();
585+
}
586+
587+
if (arr->n_children == 1) {
588+
arr = arr->children[0];
589+
}
590+
if (arr->n_buffers == 2) {
591+
// undone offset. offset here is # of elements,
592+
// so depends on the size of the element
593+
// undone image is char*, arrow is void *
594+
// buffer 0 is the null list
595+
// buffer 1 is the data
596+
borrowed_buffer = (char *)arr->buffers[1];
597+
}
598+
599+
if (! borrowed_buffer) {
600+
// UNDONE better error here.
601+
// currently, only wanting one where
602+
return (Imaging)ImagingError_MemoryError();
603+
}
604+
605+
for (y = i = 0; y < im->ysize; y++) {
606+
im->image[y] = borrowed_buffer + i;
607+
i += im->linesize;
608+
}
609+
im->read_only = 1;
610+
im->arrow_array_capsule = external_array;
611+
im->destroy = ImagingDestroyArrow;
612+
613+
return im;
614+
}
615+
559616
/* --------------------------------------------------------------------
560617
* Create a new, internally allocated, image.
561618
*/
@@ -627,6 +684,53 @@ ImagingNewBlock(const char *mode, int xsize, int ysize) {
627684
return NULL;
628685
}
629686

687+
Imaging
688+
ImagingNewArrow(const char *mode, int xsize, int ysize,
689+
struct ArrowSchema *schema,
690+
struct ArrowArray *external_array) {
691+
/* A borrowed arrow array */
692+
Imaging im;
693+
694+
if (xsize < 0 || ysize < 0) {
695+
return (Imaging)ImagingError_ValueError("bad image size");
696+
}
697+
698+
im = ImagingNewPrologue(mode, xsize, ysize);
699+
if (!im) {
700+
return NULL;
701+
}
702+
703+
int64_t pixels = (int64_t)xsize * (int64_t)ysize;
704+
705+
if (((strcmp(schema->format, "i") == 0 &&
706+
im->pixelsize == 4) ||
707+
(strcmp(schema->format, im->arrow_band_format) == 0 &&
708+
im->bands == 1))
709+
&& pixels == external_array->length) {
710+
// one arrow element per, and it matches a pixelsize*char
711+
if (ImagingAllocateArrow(im, external_array)) {
712+
return im;
713+
}
714+
}
715+
if (strcmp(schema->format, "+w:4") == 0
716+
&& im->pixelsize == 4
717+
&& schema->n_children > 0
718+
&& schema->children
719+
&& strcmp(schema->children[0]->format, "C") == 0
720+
&& pixels == external_array->length
721+
&& external_array->n_children == 1
722+
&& external_array->children
723+
&& 4 * pixels == external_array->children[0]->length) {
724+
// 4 up element of char into pixelsize == 4
725+
if (ImagingAllocateArrow(im, external_array)) {
726+
return im;
727+
}
728+
}
729+
730+
ImagingDelete(im);
731+
return NULL;
732+
}
733+
630734
Imaging
631735
ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn) {
632736
/* allocate or validate output image */

0 commit comments

Comments
 (0)