Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions kiva/agg/src/graphics_context.i
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,40 @@ namespace kiva {
def get_empty_path(self):
return CompiledPath()

def to_image(self):
""" Return the contents of the GraphicsContext as a PIL Image.

Images are in RGB or RGBA format; if this GC is not in one of
these formats, it is automatically converted.

Returns
-------
img : Image
The contents of the context as a PIL/Pillow Image.
"""
from PIL import Image
size = (self.width(), self.height())
fmt = self.format()

# determine the output pixel format and PIL format
if fmt.endswith("32"):
pilformat = "RGBA"
pixelformat = "rgba32"
elif fmt.endswith("24"):
pilformat = "RGB"
pixelformat = "rgb24"

# perform a conversion if necessary
if fmt != pixelformat:
newimg = GraphicsContextArray(size, fmt)
newimg.draw_image(self)
newimg.convert_pixel_format(pixelformat, 1)
bmp = newimg.bmp_array
else:
bmp = self.bmp_array

return Image.fromarray(bmp, pilformat)

def save(self, filename, file_format=None, pil_options=None):
""" Save the GraphicsContext to a file. Output files are always
saved in RGB or RGBA format; if this GC is not in one of
Expand Down Expand Up @@ -907,6 +941,29 @@ namespace kiva {
def __exit__(self, type, value, traceback):
self.restore_state()

#----------------------------------------------------------------
# IPython/Jupyter support
#----------------------------------------------------------------

def _repr_png_(self):
""" Return a the current contents of the context as PNG image.

This provides Jupyter and IPython compatibility, so that the graphics
context can be displayed in the Jupyter Notebook or the IPython Qt
console.

Returns
-------
data : bytes
The contents of the context as PNG-format bytes.
"""
from io import BytesIO

img = self.to_image()
data = BytesIO()
img.save(data, format='png')
return data.getvalue()

%}

//---------------------------------------------------------------------
Expand Down
64 changes: 49 additions & 15 deletions kiva/celiagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#
# Thanks for using Enthought open source!
from collections import namedtuple
from io import BytesIO
from math import fabs
import os
import sys
Expand Down Expand Up @@ -828,32 +829,19 @@ def draw_path_at_points(self, points, path, mode=constants.FILL_STROKE):
def save(self, filename, file_format=None, pil_options=None):
""" Save the contents of the context to a file
"""
try:
from PIL import Image
except ImportError:
raise ImportError("need Pillow to save images")

if file_format is None:
file_format = ''
if pil_options is None:
pil_options = {}

pixels = self.gc.array
if self.pix_format.startswith('bgra'):
# Data is BGRA; Convert to RGBA
data = np.empty(pixels.shape, dtype=np.uint8)
data[..., 0] = pixels[..., 2]
data[..., 1] = pixels[..., 1]
data[..., 2] = pixels[..., 0]
data[..., 3] = pixels[..., 3]
else:
data = pixels
img = Image.fromarray(data, 'RGBA')
img = self.to_image()

ext = (
os.path.splitext(filename)[1][1:] if isinstance(filename, str)
else ''
)

# Check the output format to see if it can handle an alpha channel.
no_alpha_formats = ('jpg', 'bmp', 'eps', 'jpeg')
if ext in no_alpha_formats or file_format.lower() in no_alpha_formats:
Expand All @@ -868,6 +856,52 @@ def save(self, filename, file_format=None, pil_options=None):

img.save(filename, format=file_format, **pil_options)

def to_image(self):
""" Return the contents of the context as a PIL Image.

If the graphics context is in BGRA format, it will convert it to
RGBA for the image.

Returns
-------
img : Image
A PIL/Pillow Image object with the data in RGBA format.
"""
try:
from PIL import Image
except ImportError:
raise ImportError("need Pillow to save images")

pixels = self.gc.array
if self.pix_format.startswith('bgra'):
# Data is BGRA; Convert to RGBA
data = np.empty(pixels.shape, dtype=np.uint8)
data[..., 0] = pixels[..., 2]
data[..., 1] = pixels[..., 1]
data[..., 2] = pixels[..., 0]
data[..., 3] = pixels[..., 3]
else:
data = pixels

return Image.fromarray(data, 'RGBA')
Comment on lines +875 to +886
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It hasn't been a problem yet, but we might want to add support for formats without alpha at some point.


def _repr_png_(self):
""" Return a the current contents of the context as PNG image.

This provides Jupyter and IPython compatibility, so that the graphics
context can be displayed in the Jupyter Notebook or the IPython Qt
console.

Returns
-------
data : bytes
The contents of the context as PNG-format bytes.
"""
img = self.to_image()
data = BytesIO()
img.save(data, format='png')
return data.getvalue()


class CompiledPath(object):
def __init__(self):
Expand Down
85 changes: 85 additions & 0 deletions kiva/examples/kiva/Kiva Explorer.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Kiva API Explorer\n",
"\n",
"This notebook allows you to experiment with the K"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from math import pi\n",
"from kiva import constants\n",
"from kiva.fonttools import Font"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from kiva.celiagg import GraphicsContext\n",
"\n",
"gc = GraphicsContext((500, 500), pix_format='rgba32')\n",
"\n",
"with gc:\n",
" gc.set_fill_color((1.0, 1.0, 0.0, 1.0))\n",
" gc.arc(200, 200, 100, 0, 2*pi)\n",
" gc.fill_path()\n",
"\n",
" with gc:\n",
" gc.set_font(Font('Times New Roman', size=24))\n",
" gc.translate_ctm(200, 200)\n",
" for i in range(0, 12):\n",
" gc.set_fill_color((i/12.0, 0.0, 1.0-(i/12.0), 0.75))\n",
" gc.rotate_ctm(2*pi/12.0)\n",
" gc.show_text_at_point(\"Hello World\", 20, 0)\n",
"\n",
" gc.set_stroke_color((0.0, 0.0, 1.0, 1.0))\n",
" gc.set_line_width(7)\n",
" gc.set_line_join(constants.JOIN_ROUND)\n",
" gc.set_line_cap(constants.CAP_ROUND)\n",
" gc.rect(100, 400, 50, 50)\n",
" gc.stroke_path()\n",
"\n",
"gc"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
14 changes: 14 additions & 0 deletions kiva/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,20 @@ def device_update_line_state(self):
def device_update_fill_state(self):
pass

def _repr_svg_(self):
""" Return a the current contents of the context as SVG text.

This provides Jupyter and IPython compatibility, so that the graphics
context can be displayed in the Jupyter Notebook or the IPython Qt
console.

Returns
-------
svg : str
The contents of the context as an SVG string.
"""
return self.render('svg')


def font_metrics_provider():
return GraphicsContext((1, 1))
Expand Down
10 changes: 10 additions & 0 deletions kiva/tests/test_agg_drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,13 @@ def test_unicode_gradient_args(self):
0, 0, w, 0, grad_stops, "pad", b"userSpaceOnUse"
)
self.gc.fill_path()

def test_ipython_repr_png(self):
self.gc.begin_path()
self.gc.rect(75, 75, 25, 25)
self.gc.fill_path()
stream = self.gc._repr_png_()
filename = "{0}.png".format(self.filename)
with open(filename, 'wb') as fp:
fp.write(stream)
self.assertImageSavedWithContent(filename)
10 changes: 10 additions & 0 deletions kiva/tests/test_celiagg_drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,13 @@ def test_clip_rect_transform(self):
self.gc.begin_path()
self.gc.rect(75, 75, 25, 25)
self.gc.fill_path()

def test_ipython_repr_png(self):
self.gc.begin_path()
self.gc.rect(75, 75, 25, 25)
self.gc.fill_path()
stream = self.gc._repr_png_()
filename = "{0}.png".format(self.filename)
with open(filename, 'wb') as fp:
fp.write(stream)
self.assertImageSavedWithContent(filename)
13 changes: 13 additions & 0 deletions kiva/tests/test_svg_drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ def draw_and_check(self):
elements = [element for element in tree.iter()]
if not len(elements) in [4, 7]:
self.fail("The expected number of elements was not found")

def test_ipython_repr_svg(self):
self.gc.begin_path()
self.gc.rect(75, 75, 25, 25)
self.gc.fill_path()
stream = self.gc._repr_svg_()
filename = "{0}.svg".format(self.filename)
with open(filename, 'w', encoding='utf8') as fp:
fp.write(stream)
tree = ElementTree.parse(filename)
elements = [element for element in tree.iter()]
if not len(elements) in [4, 7]:
self.fail("The expected number of elements was not found")