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
17 changes: 15 additions & 2 deletions kiva/agg/src/graphics_context.i
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ namespace kiva {
_swig_setattr(self, GraphicsContextArray, 'thisown2', 1)

self.bmp_array = ary
self.base_scale = base_pixel_scale

def __del__(self, destroy=_agg.destroy_graphics_context):
try:
Expand Down Expand Up @@ -839,15 +840,27 @@ namespace kiva {
"""
from PIL import Image

FmtsWithDpi = ('jpg', 'png', 'tiff', 'jpeg')
FmtsWithoutAlpha = ('jpg', 'bmp', 'eps', "jpeg")
size = (self.width(), self.height())
fmt = self.format()

if pil_options is None:
pil_options = {}

file_ext = filename.rpartition(".")[-1].lower() if isinstance(filename, str) else ""
if (file_ext in FmtsWithDpi or
(file_format is not None and
file_format.lower() in FmtsWithDpi)):
# Assume 72dpi is 1x
dpi = int(72 * self.base_scale)
Comment thread
jwiggins marked this conversation as resolved.
pil_options["dpi"] = (dpi, dpi)

# determine the output pixel format and PIL format
if fmt.endswith("32"):
pilformat = "RGBA"
pixelformat = "rgba32"
if (isinstance(filename, str) and filename[-3:].lower() in FmtsWithoutAlpha) or \
if file_ext in FmtsWithoutAlpha or \
(file_format is not None and file_format.lower() in FmtsWithoutAlpha):
pilformat = "RGB"
pixelformat = "rgb24"
Expand All @@ -865,7 +878,7 @@ namespace kiva {
bmp = self.bmp_array

img = Image.fromarray(bmp, pilformat)
img.save(filename, format=file_format, options=pil_options)
img.save(filename, format=file_format, **pil_options)


#----------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion kiva/cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1250,7 +1250,7 @@ def render_component(self, component, container_coords=False):
self.translate_ctm(x, y)
component.draw(self, view_bounds=(0, 0, w, h))

def save(self, filename, file_format=None):
def save(self, filename, file_format=None, pil_options=None):
""" Save the GraphicsContext to a (PNG) file.
file_format is ignored.
"""
Expand Down
25 changes: 18 additions & 7 deletions kiva/celiagg.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ def __init__(self, size, *args, **kwargs):
self.__state_stack = []

# For HiDPI support
base_scale = kwargs.pop('base_pixel_scale', 1)
self.transform.scale(base_scale, base_scale)
self.base_scale = kwargs.pop('base_pixel_scale', 1)
self.transform.scale(self.base_scale, self.base_scale)

# ----------------------------------------------------------------
# Size info
Expand Down Expand Up @@ -814,7 +814,7 @@ def draw_path_at_points(self, points, path, mode=constants.FILL_STROKE):
fill=self.fill_paint,
)

def save(self, filename, file_format=None):
def save(self, filename, file_format=None, pil_options=None):
""" Save the contents of the context to a file
"""
try:
Expand All @@ -824,6 +824,8 @@ def save(self, filename, file_format=None):

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

pixels = self.gc.array
if self.pix_format.startswith('bgra'):
Expand All @@ -837,14 +839,23 @@ def save(self, filename, file_format=None):
data = pixels
img = Image.fromarray(data, 'RGBA')

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 ((isinstance(filename, str) and
os.path.splitext(filename)[1][1:] in no_alpha_formats) or
(file_format.lower() in no_alpha_formats)):
if ext in no_alpha_formats or file_format.lower() in no_alpha_formats:
img = img.convert('RGB')

img.save(filename, format=file_format)
# Check the output format to see if it can handle DPI
dpi_formats = ('jpg', 'png', 'tiff', 'jpeg')
if ext in dpi_formats or file_format.lower() in dpi_formats:
# Assume 72dpi is 1x
dpi = int(72 * self.base_scale)
pil_options['dpi'] = (dpi, dpi)

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


class CompiledPath(object):
Expand Down
2 changes: 1 addition & 1 deletion kiva/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,5 +783,5 @@ def draw_path(self, mode=constants.FILL_STROKE):
# erase the current path.
self.current_pdf_path = None

def save(self):
def save(self, filename='', file_format=None, pil_options=None):
self.gc.save()
2 changes: 1 addition & 1 deletion kiva/ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def width(self):
def height(self):
return self.size[1]

def save(self, filename):
def save(self, filename, file_format=None, pil_options=None):
with open(filename, "w") as f:
ext = os.path.splitext(filename)[1]
if ext in (".eps", ".epsf"):
Expand Down
2 changes: 1 addition & 1 deletion kiva/qpainter.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ def _flip_y(self, y):
"Converts between a Kiva and a Qt y coordinate"
return self._height - y - 1

def save(self, filename, file_format=None):
def save(self, filename, file_format=None, pil_options=None):
""" Save the contents of the context to a file
"""
if isinstance(self.qt_dc, QtGui.QPixmap):
Expand Down
31 changes: 24 additions & 7 deletions kiva/quartz/ABCGI.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ cdef class ShadingFunction

cdef class CGContext:
cdef CGContextRef context
cdef float base_scale
cdef long can_release
cdef object current_font
cdef object current_style
Expand All @@ -201,9 +202,10 @@ cdef class CGContext:
self.can_release = 0
self.text_matrix = CGAffineTransformMake(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)

def __init__(self, size_t context, long can_release=0):
def __init__(self, size_t context, long can_release=0, base_pixel_scale=1.0):
self.context = <CGContextRef>context

self.base_scale = base_pixel_scale
self.can_release = can_release
self.fill_color = (0.0, 0.0, 0.0, 1.0)
self.stroke_color = (0.0, 0.0, 0.0, 1.0)
Expand Down Expand Up @@ -1340,7 +1342,7 @@ cdef class CGLayerContext(CGContextInABox):
# Create a CGBitmapContext from this layer, draw to it, then let it save
# itself out.
rect = (0, 0) + self.size
bmp = CGBitmapContext(self.size)
bmp = CGBitmapContext(self.size, base_pixel_scale=self.base_scale)
CGContextDrawLayerInRect(bmp.context, CGRectMakeFromPython(rect), self.layer)
bmp.save(filename, file_format=file_format, pil_options=pil_options)

Expand Down Expand Up @@ -1411,12 +1413,14 @@ cdef class CGBitmapContext(CGContext):

def __init__(self, object size_or_array, bool grey_scale=0,
int bits_per_component=8, int bytes_per_row=-1,
alpha_info=kCGImageAlphaPremultipliedLast):
alpha_info=kCGImageAlphaPremultipliedLast, base_pixel_scale=1.0):

cdef int bits_per_pixel
cdef CGColorSpaceRef colorspace
cdef void* dataptr

self.base_scale = base_pixel_scale

if hasattr(size_or_array, '__array_interface__'):
# It's an array.
arr = numpy.asarray(size_or_array, order='C')
Expand Down Expand Up @@ -1609,18 +1613,31 @@ cdef class CGBitmapContext(CGContext):

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

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

# Check te output format to see if DPI can be passed
dpi_formats = ('jpg', 'png', 'tiff', 'jpeg')
if file_ext in dpi_formats or file_format.lower() in dpi_formats:
# Assume 72dpi is 1x
dpi = int(72 * self.base_scale)
Comment thread
jwiggins marked this conversation as resolved.
pil_options['dpi'] = (dpi, dpi)

img = PilImage.frombuffer(mode, (self.width(), self.height()), self,
'raw', mode, 0, 1)
if 'A' in mode:
# Check the output format to see if it can handle an alpha channel.
no_alpha_formats = ('jpg', 'bmp', 'eps', 'jpeg')
if ((isinstance(filename, basestring) and
os.path.splitext(filename)[1][1:] in no_alpha_formats) or
(file_format.lower() in no_alpha_formats)):
if (file_ext in no_alpha_formats or
file_format.lower() in no_alpha_formats):
img = img.convert('RGB')

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

cdef class CGImage:
cdef CGImageRef image
Expand Down
2 changes: 1 addition & 1 deletion kiva/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def width(self):
def height(self):
return self.size[1]

def save(self, filename):
def save(self, filename, file_format=None, pil_options=None):
with open(filename, "w") as f:
ext = os.path.splitext(filename)[1]
if ext == ".svg":
Expand Down
13 changes: 13 additions & 0 deletions kiva/tests/drawing_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,19 @@ def create_graphics_context(self, width, length, pixel_scale):
"""
raise NotImplementedError()

def save_and_return_dpi(self):
""" Draw an image and save it. Then read it back and return the DPI
"""
self.gc.begin_path()
self.gc.arc(150, 150, 100, 0.0, 2 * numpy.pi)
self.gc.fill_path()

filename = "{0}.png".format(self.filename)
self.gc.save(filename)
image = Image.open(filename)
dpi = image.info['dpi']
return dpi[0]

@contextlib.contextmanager
def draw_and_check(self):
yield
Expand Down
4 changes: 4 additions & 0 deletions kiva/tests/test_agg_drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class TestAggDrawing(DrawingImageTester, unittest.TestCase):
def create_graphics_context(self, width, height, pixel_scale):
return GraphicsContext((width, height), base_pixel_scale=pixel_scale)

def test_save_dpi(self):
# Base DPI is 72, but our default pixel scale is 2x.
self.assertEqual(self.save_and_return_dpi(), 144)
Comment thread
jwiggins marked this conversation as resolved.

def test_unicode_gradient_args(self):
color_nodes = [(0.0, 1.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0)]
with self.draw_and_check():
Expand Down
4 changes: 4 additions & 0 deletions kiva/tests/test_celiagg_drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class TestCeliaggDrawing(DrawingImageTester, unittest.TestCase):
def create_graphics_context(self, width, height, pixel_scale):
return GraphicsContext((width, height), base_pixel_scale=pixel_scale)

def test_save_dpi(self):
# Base DPI is 72, but our default pixel scale is 2x.
self.assertEqual(self.save_and_return_dpi(), 144)

def test_clip_rect_transform(self):
with self.draw_and_check():
self.gc.clip_to_rect(0, 0, 100, 100)
Expand Down
6 changes: 5 additions & 1 deletion kiva/tests/test_quartz_drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ class TestQuartzDrawing(DrawingImageTester, unittest.TestCase):
def create_graphics_context(self, width, height, pixel_scale):
from kiva.quartz import ABCGI

return ABCGI.CGBitmapContext((width, height))
return ABCGI.CGBitmapContext((width, height), base_pixel_scale=pixel_scale)

def test_save_dpi(self):
# Base DPI is 72, but our default pixel scale is 2x.
self.assertEqual(self.save_and_return_dpi(), 144)