Skip to content

Commit

Permalink
Add numpy() exporter to Surface, Canvas, and Image
Browse files Browse the repository at this point in the history
  • Loading branch information
kyamagu committed Jun 16, 2020
1 parent 3123523 commit 76c9323
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 32 deletions.
9 changes: 8 additions & 1 deletion docs/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ A few differences are:
- All bindings reside in ``skia`` module.
- Removes class name prefix ``Sk``; e.g., ``SkCanvas`` is ``skia.Canvas``.
- Some method signatures adapt to Python style; e.g.,
- :py:meth:`skia.Surface.__init__` accepts additional args

- :py:meth:`skia.Surface.__init__` accepts additional args.
- :py:class:`skia.Paint` is implicitly convertible from ``dict``.
- Args taking `void* ptr` use buffer protocol.
- Methods returning a raw memory address wrap in ``memoryview``.

- NumPy export method is added to ``Surface``, ``Canvas``, ``Image``.
- Buffer protocol support in ``Bitmap``, ``Pixmap``.


Contributing
------------
Expand Down
1 change: 0 additions & 1 deletion docs/tutorial/canvas.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ into numpy array.
plot.imshow(array)
plot.show()
GPU
---

Expand Down
18 changes: 17 additions & 1 deletion docs/tutorial/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ More drawings

All of the other draw APIs are similar, each one ending with a paint parameter.
The following example demonstrates drawing of shape primitives,
:py:class:`Path`, external :py:class:`image`, or
:py:class:`Path`, external :py:class:`Image`, or
:py:class:`TextBlob` objects onto the canvas::

import skia
Expand Down Expand Up @@ -152,6 +152,22 @@ The following example demonstrates drawing of shape primitives,
:width: 256
:alt: Various drawings

Python interoperability
-----------------------

`skia-python` supports numpy array in several methods. The following directly
draws into numpy array::

import numpy as np

array = np.zeros((320, 240, 4), np.uint8)
canvas = skia.Canvas(array)
paint = skia.Paint(AntiAlias=True, Color=skia.ColorCYAN)
canvas.drawCircle(180, 50, 25, paint)

Alternatively, :py:class:`Canvas` content can be exported to numpy array.

array = canvas.numpy()

APIs at a glance
----------------
Expand Down
49 changes: 26 additions & 23 deletions notebooks/Python-Image-IO.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@
],
"source": [
"buffer = np.zeros((256, 256, 4), dtype=np.uint8)\n",
"surface = skia.Surface(buffer, colorType=skia.kRGBA_8888_ColorType)\n",
"\n",
"surface = skia.Surface(buffer)\n",
"canvas = surface.getCanvas()\n",
"paint = skia.Paint(AntiAlias=True, Color=skia.ColorCYAN)\n",
"canvas.drawCircle((128, 128), 64, paint)\n",
Expand All @@ -151,7 +152,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"NumPy arrays can be also used as `Image`, `Pixmap`, or `Bitmap` instead of `Surface`."
"NumPy arrays can be also used as `Image`, `Pixmap`, or `Bitmap`."
]
},
{
Expand All @@ -160,19 +161,32 @@
"metadata": {
"scrolled": true
},
"outputs": [],
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAGJJREFUeJzt0IENgCAQBEG0/56xCIUNcaYA2L8xAAAAAACAv7g2/DEXv//qhvurilMZoA6oGaAOqBmgDqgZoA6oGaAOqBmgDqgZoA6oGaAOqBmgDqgZoA6oGaAOAAAAANjtATuzATANtKc0AAAAAElFTkSuQmCC\n",
"text/plain": [
"<IPython.core.display.Image object>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"array = np.zeros((64, 64, 4), dtype=np.uint8)\n",
"array[24:48, 24:48, 3] = 255\n",
"\n",
"image = skia.Image(array)\n",
"display.Image(data=image.encodeToData())\n",
"\n",
"pixmap = skia.Pixmap(array)\n",
"\n",
"bitmap = skia.Bitmap()\n",
"bitmap.setInfo(skia.ImageInfo.Make(64, 64, skia.kRGBA_8888_ColorType, skia.kUnpremul_AlphaType))\n",
"bitmap.setPixels(array)"
"bitmap.setPixels(array)\n",
"\n",
"display.Image(data=image.encodeToData())"
]
},
{
Expand All @@ -186,7 +200,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"It is also possible to copy pixels from an image to numpy array:"
"Exporting to a numpy array is easy:"
]
},
{
Expand All @@ -211,19 +225,16 @@
"with open('../skia/resources/images/rainbow-gradient.png', 'rb') as f:\n",
" image = skia.Image.DecodeToRaster(f.read())\n",
" \n",
"info = image.imageInfo().makeAlphaType(skia.kUnpremul_AlphaType)\n",
"buffer = np.zeros((info.height(), info.width(), info.bytesPerPixel()), dtype=np.uint8)\n",
"assert image.readPixels(info, buffer), 'Failed to read'\n",
"\n",
"plt.imshow(buffer)\n",
"array = image.numpy()\n",
"plt.imshow(array)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Canvas or surface can copy to numpy array, too."
"Canvas or surface can also export to numpy array."
]
},
{
Expand Down Expand Up @@ -262,20 +273,12 @@
"paint = skia.Paint(AntiAlias=True, Color=skia.ColorCYAN)\n",
"canvas.drawCircle((64, 64), 32, paint)\n",
"\n",
"info = canvas.imageInfo().makeAlphaType(skia.kUnpremul_AlphaType)\n",
"buffer = np.zeros((info.height(), info.width(), info.bytesPerPixel()), dtype=np.uint8)\n",
"assert canvas.readPixels(info, buffer), 'Failed to read'\n",
"\n",
"plt.figure(figsize=(2, 2))\n",
"plt.imshow(buffer)\n",
"plt.imshow(canvas.numpy())\n",
"plt.show()\n",
"\n",
"info = surface.imageInfo().makeAlphaType(skia.kUnpremul_AlphaType)\n",
"buffer = np.zeros((info.height(), info.width(), info.bytesPerPixel()), dtype=np.uint8)\n",
"assert surface.readPixels(info, buffer), 'Failed to read'\n",
"\n",
"plt.figure(figsize=(2, 2))\n",
"plt.imshow(buffer)\n",
"plt.imshow(surface.numpy())\n",
"plt.show()"
]
},
Expand Down Expand Up @@ -352,7 +355,7 @@
"data": {
"image/png": "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\n",
"text/plain": [
"<PIL.Image.Image image mode=RGBA size=100x100 at 0x118F11850>"
"<PIL.Image.Image image mode=RGBA size=100x100 at 0x1215B4150>"
]
},
"execution_count": 11,
Expand Down
4 changes: 2 additions & 2 deletions src/skia/Bitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ py::enum_<SkBitmap::AllocFlags>(bitmap, "AllocFlags", py::arithmetic())
bitmap
.def_buffer(
[] (const SkBitmap& bitmap) {
CHECK_NOTNULL(bitmap.getPixels());
return ImageInfoToBufferInfo(
bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), false);
})
Expand Down Expand Up @@ -334,8 +335,7 @@ bitmap
py::arg("alphaType"))
.def("getPixels",
[] (const SkBitmap& bitmap) -> py::object {
if (!bitmap.getPixels())
return py::none();
CHECK_NOTNULL(bitmap.getPixels());
return py::memoryview(ImageInfoToBufferInfo(
bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), false));
},
Expand Down
9 changes: 7 additions & 2 deletions src/skia/Canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,11 @@ canvas
independent fonts
)docstring",
py::arg("bitmap"), py::arg("props"))
.def("numpy", &ReadToNumpy<SkCanvas>,
py::arg("srcX") = 0, py::arg("srcY") = 0,
py::arg("colorType") = kUnknown_SkColorType,
py::arg("alphaType") = kUnpremul_SkAlphaType,
py::arg("colorSpace") = nullptr)
.def("imageInfo", &SkCanvas::imageInfo,
R"docstring(
Returns :py:class:`ImageInfo` for :py:class:`Canvas`.
Expand Down Expand Up @@ -534,8 +539,8 @@ canvas
py::arg("pixmap"))
.def("readPixels", &ReadPixels<SkCanvas>,
R"docstring(
Copies :py:class:`Rect` of pixels from :py:class:`Canvas` into numpy
array.
Copies :py:class:`Rect` of pixels from :py:class:`Canvas` into
dstPixels.
:py:class:`Matrix` and clip are ignored.
Expand Down
5 changes: 5 additions & 0 deletions src/skia/Image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ image
py::arg("array"), py::arg("colorType") = kRGBA_8888_SkColorType,
py::arg("alphaType") = kUnpremul_SkAlphaType,
py::arg("colorSpace") = nullptr, py::arg("copy") = false)
.def("numpy", &ReadToNumpy<SkImage>,
py::arg("srcX") = 0, py::arg("srcY") = 0,
py::arg("colorType") = kUnknown_SkColorType,
py::arg("alphaType") = kUnpremul_SkAlphaType,
py::arg("colorSpace") = nullptr)
.def("__repr__",
[] (const SkImage& image) {
return py::str("Image({}, {}, {}, {})").format(
Expand Down
2 changes: 2 additions & 0 deletions src/skia/Pixmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ py::memoryview AddrN(const SkPixmap& pixmap) {

template <bool readonly = true>
py::memoryview Addr(const SkPixmap& pixmap) {
CHECK_NOTNULL(pixmap.addr());
return py::memoryview(ImageInfoToBufferInfo(
pixmap.info(), pixmap.writable_addr(), pixmap.rowBytes(), readonly));
}
Expand Down Expand Up @@ -59,6 +60,7 @@ py::class_<SkPixmap>(m, "Pixmap",
)docstring",
py::buffer_protocol())
.def_buffer([] (SkPixmap& pixmap) {
CHECK_NOTNULL(pixmap.addr());
return ImageInfoToBufferInfo(
pixmap.info(), pixmap.writable_addr(), pixmap.rowBytes(), false);
})
Expand Down
5 changes: 5 additions & 0 deletions src/skia/Surface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ surface
return py::str("Surface({}, {})").format(
surface.width(), surface.height());
})
.def("numpy", &ReadToNumpy<SkSurface>,
py::arg("srcX") = 0, py::arg("srcY") = 0,
py::arg("colorType") = kUnknown_SkColorType,
py::arg("alphaType") = kUnpremul_SkAlphaType,
py::arg("colorSpace") = nullptr)
.def(py::init(&SkSurface::MakeRasterN32Premul),
R"docstring(
See :py:meth:`~MakeRasterN32Premul`
Expand Down
17 changes: 17 additions & 0 deletions src/skia/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace pybind11 { class array; } // namespace pybind11

namespace py = pybind11;

#define CHECK_NOTNULL(ptr) \
if (!ptr) throw std::runtime_error("Null pointer exception.")

PYBIND11_DECLARE_HOLDER_TYPE(T, sk_sp<T>);

Expand All @@ -35,6 +37,21 @@ bool ReadPixels(T& readable, const SkImageInfo& imageInfo, py::buffer dstPixels,
return readable.readPixels(imageInfo, info.ptr, rowBytes, srcX, srcY);
}

template <typename T, typename R = py::array>
R ReadToNumpy(T& readable, int srcX, int srcY, SkColorType colorType,
SkAlphaType alphaType, const SkColorSpace* colorSpace) {
if (colorType == kUnknown_SkColorType)
colorType = readable.imageInfo().colorType();
auto imageInfo = SkImageInfo::Make(
readable.imageInfo().dimensions(), colorType, alphaType,
CloneColorSpace(colorSpace));
R array(ImageInfoToBufferInfo(imageInfo, nullptr));
if (!readable.readPixels(
imageInfo, array.mutable_data(), array.strides(0), srcX, srcY))
throw std::runtime_error("Failed to convert to numpy array.");
return array;
}

SkImageInfo NumPyToImageInfo(
py::array array, SkColorType ct, SkAlphaType at, const SkColorSpace* cs);

Expand Down
2 changes: 0 additions & 2 deletions src/skia/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ SkImageInfo NumPyToImageInfo(py::array array, SkColorType ct, SkAlphaType at,

py::buffer_info ImageInfoToBufferInfo(
const SkImageInfo& imageInfo, void* data, ssize_t rowBytes, bool readonly) {
if (!data)
throw std::runtime_error("Null pointer exception.");
ssize_t width = imageInfo.width();
ssize_t height = imageInfo.height();
ssize_t bytesPerPixel = imageInfo.bytesPerPixel();
Expand Down
4 changes: 4 additions & 0 deletions tests/test_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def test_Canvas_peekPixels(canvas):
assert isinstance(canvas.peekPixels(skia.Pixmap()), bool)


def test_Canvas_numpy(canvas):
assert isinstance(canvas.numpy(), np.ndarray)


@pytest.mark.parametrize('args', [
(skia.Pixmap(), 0, 0),
(
Expand Down
4 changes: 4 additions & 0 deletions tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def test_Image_init(args, color_type, error):
assert isinstance(skia.Image(np.zeros(*args), color_type), skia.Image)


def test_Image_numpy(image):
assert isinstance(image.numpy(), np.ndarray)


def test_Image_repr(image):
assert isinstance(repr(image), str)

Expand Down
4 changes: 4 additions & 0 deletions tests/test_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def test_Surface_init(args):
check_surface(skia.Surface(*args))


def test_Surface_numpy(surface):
assert isinstance(surface.numpy(), np.ndarray)


def test_Surface_repr(surface):
assert isinstance(repr(surface), str)

Expand Down

0 comments on commit 76c9323

Please sign in to comment.