2011-12-08 19:31:03 +04:00
|
|
|
|
# coding: utf8
|
2012-03-22 02:19:27 +04:00
|
|
|
|
"""
|
|
|
|
|
weasyprint.images
|
|
|
|
|
-----------------
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
|
Fetch and decode images in various formats.
|
|
|
|
|
|
|
|
|
|
:copyright: Copyright 2011-2012 Simon Sapin and contributors, see AUTHORS.
|
|
|
|
|
:license: BSD, see LICENSE for details.
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2012-12-29 14:50:11 +04:00
|
|
|
|
from __future__ import division
|
|
|
|
|
# XXX No unicode_literals, cffi likes native strings
|
2012-02-17 21:49:58 +04:00
|
|
|
|
|
2012-12-29 14:50:11 +04:00
|
|
|
|
import sys
|
2012-02-17 21:49:58 +04:00
|
|
|
|
from io import BytesIO
|
2012-12-29 14:50:11 +04:00
|
|
|
|
from functools import partial
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
2012-12-29 14:50:11 +04:00
|
|
|
|
import cffi
|
2012-12-29 04:00:30 +04:00
|
|
|
|
import cairocffi as cairo
|
2013-02-25 19:26:39 +04:00
|
|
|
|
cairo.install_as_pycairo() # for CairoSVG
|
|
|
|
|
|
|
|
|
|
import cairosvg.parser
|
|
|
|
|
import cairosvg.surface
|
|
|
|
|
assert cairosvg.surface.cairo is cairo, (
|
|
|
|
|
'CairoSVG is using pycairo instead of cairocffi. '
|
|
|
|
|
'Make sure it is not imported before WeasyPrint.')
|
|
|
|
|
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
2012-02-22 20:12:40 +04:00
|
|
|
|
from .logger import LOGGER
|
2012-12-29 14:50:11 +04:00
|
|
|
|
from .compat import xrange
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ffi = cffi.FFI()
|
|
|
|
|
ffi.cdef('''
|
|
|
|
|
|
|
|
|
|
typedef unsigned long gsize;
|
|
|
|
|
typedef unsigned int guint32;
|
|
|
|
|
typedef unsigned int guint;
|
|
|
|
|
typedef unsigned char guchar;
|
|
|
|
|
typedef char gchar;
|
|
|
|
|
typedef int gint;
|
|
|
|
|
typedef gint gboolean;
|
|
|
|
|
typedef guint32 GQuark;
|
|
|
|
|
typedef void* gpointer;
|
|
|
|
|
typedef struct {
|
|
|
|
|
GQuark domain;
|
|
|
|
|
gint code;
|
|
|
|
|
gchar *message;
|
|
|
|
|
} GError;
|
|
|
|
|
typedef struct {
|
|
|
|
|
gchar *name;
|
|
|
|
|
/* ... */
|
|
|
|
|
} GdkPixbufFormat;
|
|
|
|
|
typedef enum {
|
|
|
|
|
GDK_COLORSPACE_RGB
|
|
|
|
|
} GdkColorspace;
|
|
|
|
|
typedef ... GdkPixbufLoader;
|
|
|
|
|
typedef ... GdkPixbuf;
|
2012-12-30 01:59:13 +04:00
|
|
|
|
typedef ... cairo_t;
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GdkPixbufLoader * gdk_pixbuf_loader_new (void);
|
|
|
|
|
GdkPixbufFormat * gdk_pixbuf_loader_get_format (GdkPixbufLoader *loader);
|
|
|
|
|
GdkPixbuf * gdk_pixbuf_loader_get_pixbuf (GdkPixbufLoader *loader);
|
|
|
|
|
gboolean gdk_pixbuf_loader_write (
|
|
|
|
|
GdkPixbufLoader *loader, const guchar *buf, gsize count,
|
|
|
|
|
GError **error);
|
|
|
|
|
gboolean gdk_pixbuf_loader_close (
|
|
|
|
|
GdkPixbufLoader *loader, GError **error);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GdkColorspace gdk_pixbuf_get_colorspace (const GdkPixbuf *pixbuf);
|
|
|
|
|
int gdk_pixbuf_get_n_channels (const GdkPixbuf *pixbuf);
|
|
|
|
|
gboolean gdk_pixbuf_get_has_alpha (const GdkPixbuf *pixbuf);
|
|
|
|
|
int gdk_pixbuf_get_bits_per_sample (const GdkPixbuf *pixbuf);
|
|
|
|
|
int gdk_pixbuf_get_width (const GdkPixbuf *pixbuf);
|
|
|
|
|
int gdk_pixbuf_get_height (const GdkPixbuf *pixbuf);
|
|
|
|
|
int gdk_pixbuf_get_rowstride (const GdkPixbuf *pixbuf);
|
|
|
|
|
guchar * gdk_pixbuf_get_pixels (const GdkPixbuf *pixbuf);
|
|
|
|
|
gsize gdk_pixbuf_get_byte_length (const GdkPixbuf *pixbuf);
|
2013-02-25 19:27:03 +04:00
|
|
|
|
gboolean gdk_pixbuf_save_to_buffer (
|
|
|
|
|
GdkPixbuf *pixbuf, gchar **buffer, gsize *buffer_size,
|
|
|
|
|
const char *type, GError **error, ...);
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
2012-12-30 01:59:13 +04:00
|
|
|
|
void gdk_cairo_set_source_pixbuf (
|
|
|
|
|
cairo_t *cr, const GdkPixbuf *pixbuf, double pixbuf_x, double pixbuf_y);
|
|
|
|
|
|
|
|
|
|
|
2012-12-29 14:50:11 +04:00
|
|
|
|
void g_object_ref (gpointer object);
|
|
|
|
|
void g_object_unref (gpointer object);
|
|
|
|
|
void g_error_free (GError *error);
|
2012-12-29 17:20:38 +04:00
|
|
|
|
void g_type_init (void);
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
|
|
|
|
''')
|
|
|
|
|
|
2012-12-30 01:59:13 +04:00
|
|
|
|
try:
|
2013-02-25 19:34:42 +04:00
|
|
|
|
gobject = ffi.dlopen('gobject-2.0')
|
|
|
|
|
gdk_pixbuf = ffi.dlopen('gdk_pixbuf-2.0')
|
2012-12-30 01:59:13 +04:00
|
|
|
|
except OSError:
|
2013-02-25 19:34:42 +04:00
|
|
|
|
gdk_pixbuf = gobject = gdk = None
|
|
|
|
|
else:
|
2012-12-30 01:59:13 +04:00
|
|
|
|
try:
|
2013-02-25 19:34:42 +04:00
|
|
|
|
gdk = ffi.dlopen('gdk-3')
|
2012-12-30 01:59:13 +04:00
|
|
|
|
except OSError:
|
2013-02-25 19:34:42 +04:00
|
|
|
|
try:
|
|
|
|
|
gdk = ffi.dlopen('gdk-x11-2.0')
|
|
|
|
|
except OSError:
|
|
|
|
|
gdk = None
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
2013-02-25 19:34:42 +04:00
|
|
|
|
gobject.g_type_init()
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
2011-12-09 13:52:49 +04:00
|
|
|
|
# TODO: currently CairoSVG only support images with an explicit
|
|
|
|
|
# width and height. When it supports images with only an intrinsic ratio
|
|
|
|
|
# this API will need to change.
|
|
|
|
|
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
2013-02-25 19:27:03 +04:00
|
|
|
|
def handle_g_error(error, raise_=False):
|
2012-12-29 14:50:11 +04:00
|
|
|
|
if error != ffi.NULL:
|
2012-12-30 01:59:13 +04:00
|
|
|
|
error_message = ffi.string(error.message).decode('utf8', 'replace')
|
2012-12-29 14:50:11 +04:00
|
|
|
|
gobject.g_error_free(error)
|
2013-02-25 19:27:03 +04:00
|
|
|
|
exception = ValueError('Pixbuf error: ' + error_message)
|
|
|
|
|
if raise_:
|
|
|
|
|
raise exception
|
|
|
|
|
else:
|
|
|
|
|
return exception
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Pixbuf(object):
|
|
|
|
|
def __init__(self, handle):
|
2012-12-30 14:08:55 +04:00
|
|
|
|
self._pointer = ffi.gc(handle, gobject.g_object_unref)
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
2012-12-30 14:08:55 +04:00
|
|
|
|
return partial(getattr(gdk_pixbuf, 'gdk_pixbuf_' + name), self._pointer)
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_pixbuf(file_obj=None, string=None):
|
2012-07-29 20:21:33 +04:00
|
|
|
|
"""Create a Pixbuf object."""
|
2012-12-20 23:10:48 +04:00
|
|
|
|
if file_obj:
|
|
|
|
|
string = file_obj.read()
|
|
|
|
|
if not string:
|
|
|
|
|
raise ValueError('Could not load image: empty content')
|
2012-12-29 14:50:11 +04:00
|
|
|
|
loader = ffi.gc(
|
|
|
|
|
gdk_pixbuf.gdk_pixbuf_loader_new(), gobject.g_object_unref)
|
|
|
|
|
error = ffi.new('GError **')
|
|
|
|
|
|
|
|
|
|
gdk_pixbuf.gdk_pixbuf_loader_write(
|
|
|
|
|
loader, ffi.new('guchar[]', string), len(string), error)
|
|
|
|
|
write_exception = handle_g_error(error[0])
|
|
|
|
|
gdk_pixbuf.gdk_pixbuf_loader_close(loader, error)
|
|
|
|
|
close_exception = handle_g_error(error[0])
|
|
|
|
|
if write_exception is not None:
|
|
|
|
|
raise write_exception # Only after closing
|
|
|
|
|
if close_exception is not None:
|
|
|
|
|
raise close_exception
|
|
|
|
|
|
|
|
|
|
format_ = gdk_pixbuf.gdk_pixbuf_loader_get_format(loader)
|
2012-12-29 17:20:38 +04:00
|
|
|
|
is_jpeg = format_ != ffi.NULL and ffi.string(format_.name) == b'jpeg'
|
2012-12-29 14:50:11 +04:00
|
|
|
|
jpeg_data = string if is_jpeg else None
|
2012-12-29 17:20:38 +04:00
|
|
|
|
|
|
|
|
|
pixbuf = gdk_pixbuf.gdk_pixbuf_loader_get_pixbuf(loader)
|
|
|
|
|
assert pixbuf != ffi.NULL
|
|
|
|
|
gobject.g_object_ref(pixbuf)
|
|
|
|
|
return Pixbuf(pixbuf), jpeg_data
|
2012-12-29 14:50:11 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gdkpixbuf_loader(file_obj, string):
|
|
|
|
|
"""Load raster images with gdk-pixbuf through introspection
|
|
|
|
|
and Gdk.
|
|
|
|
|
|
|
|
|
|
"""
|
2013-02-25 19:34:42 +04:00
|
|
|
|
if gdk_pixbuf is None:
|
|
|
|
|
raise OSError(
|
|
|
|
|
'Could not load GDK-Pixbuf. '
|
|
|
|
|
'PNG and SVG are the only image formats available.')
|
2012-12-29 14:50:11 +04:00
|
|
|
|
pixbuf, jpeg_data = get_pixbuf(file_obj, string)
|
2013-02-25 19:27:03 +04:00
|
|
|
|
surface = (
|
|
|
|
|
pixbuf_to_cairo_gdk(pixbuf) if gdk is not None
|
|
|
|
|
else pixbuf_to_cairo_slices(pixbuf) if not pixbuf.get_has_alpha()
|
|
|
|
|
else pixbuf_to_cairo_png(pixbuf))
|
2013-01-01 19:55:48 +04:00
|
|
|
|
if jpeg_data:
|
|
|
|
|
surface.set_mime_data('image/jpeg', jpeg_data)
|
2012-12-30 01:59:13 +04:00
|
|
|
|
get_pattern = lambda: cairo.SurfacePattern(surface)
|
|
|
|
|
return get_pattern, surface.get_width(), surface.get_height()
|
|
|
|
|
|
|
|
|
|
|
2013-02-25 19:27:03 +04:00
|
|
|
|
def pixbuf_to_cairo_gdk(pixbuf):
|
|
|
|
|
"""Convert with GDK.
|
|
|
|
|
|
|
|
|
|
This method is fastest but GDK is not always available.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
dummy_context = cairo.Context(cairo.PDFSurface(None, 1, 1))
|
|
|
|
|
gdk.gdk_cairo_set_source_pixbuf(
|
|
|
|
|
ffi.cast('cairo_t *', dummy_context._pointer), pixbuf._pointer, 0, 0)
|
|
|
|
|
return dummy_context.get_source().get_surface()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def pixbuf_to_cairo_slices(pixbuf):
|
|
|
|
|
"""Slice-based byte swapping.
|
|
|
|
|
|
|
|
|
|
This method is 2~5x slower than GDK but does not support an alpha channel.
|
|
|
|
|
(cairo uses pre-multiplied alpha, but not Pixbuf.)
|
|
|
|
|
|
|
|
|
|
"""
|
2012-12-29 14:50:11 +04:00
|
|
|
|
assert pixbuf.get_colorspace() == 'GDK_COLORSPACE_RGB'
|
2013-02-25 19:27:03 +04:00
|
|
|
|
assert pixbuf.get_n_channels() == 3
|
|
|
|
|
assert pixbuf.get_bits_per_sample() == 8
|
2012-12-29 14:50:11 +04:00
|
|
|
|
width = pixbuf.get_width()
|
|
|
|
|
height = pixbuf.get_height()
|
2012-12-29 17:56:10 +04:00
|
|
|
|
rowstride = pixbuf.get_rowstride()
|
2012-12-29 14:50:11 +04:00
|
|
|
|
pixels = ffi.buffer(pixbuf.get_pixels(), pixbuf.get_byte_length())
|
2012-12-29 17:56:10 +04:00
|
|
|
|
# TODO: remove this when cffi buffers support slicing with a stride.
|
2012-12-29 14:50:11 +04:00
|
|
|
|
pixels = pixels[:]
|
|
|
|
|
|
|
|
|
|
# Convert GdkPixbuf’s big-endian RGBA to cairo’s native-endian ARGB
|
2013-02-25 19:27:03 +04:00
|
|
|
|
cairo_stride = cairo.ImageSurface.format_stride_for_width('RGB24', width)
|
2012-12-29 14:50:11 +04:00
|
|
|
|
data = bytearray(cairo_stride * height)
|
|
|
|
|
big_endian = sys.byteorder == 'big'
|
2013-02-25 19:27:03 +04:00
|
|
|
|
pixbuf_row_length = width * 3 # stride == row_length + padding
|
|
|
|
|
cairo_row_length = width * 4 # stride == row_length + padding
|
2012-12-29 14:50:11 +04:00
|
|
|
|
for y in xrange(height):
|
|
|
|
|
offset = rowstride * y
|
2013-02-25 19:27:03 +04:00
|
|
|
|
end = offset + pixbuf_row_length
|
|
|
|
|
red = pixels[offset:end:3]
|
|
|
|
|
green = pixels[offset + 1:end:3]
|
|
|
|
|
blue = pixels[offset + 2:end:3]
|
|
|
|
|
|
2012-12-29 14:50:11 +04:00
|
|
|
|
offset = cairo_stride * y
|
2013-02-25 19:27:03 +04:00
|
|
|
|
end = offset + cairo_row_length
|
2012-12-29 14:50:11 +04:00
|
|
|
|
if big_endian:
|
2013-02-25 19:27:03 +04:00
|
|
|
|
# data[offset:end:4] is left un-initialized
|
2012-12-29 14:50:11 +04:00
|
|
|
|
data[offset + 1:end:4] = red
|
|
|
|
|
data[offset + 2:end:4] = green
|
|
|
|
|
data[offset + 3:end:4] = blue
|
|
|
|
|
else:
|
2013-02-25 19:27:03 +04:00
|
|
|
|
# data[offset + 3:end:4] is left un-initialized
|
2012-12-29 14:50:11 +04:00
|
|
|
|
data[offset + 2:end:4] = red
|
|
|
|
|
data[offset + 1:end:4] = green
|
|
|
|
|
data[offset:end:4] = blue
|
|
|
|
|
|
2012-12-30 01:59:13 +04:00
|
|
|
|
return cairo.ImageSurface('ARGB32', width, height, data, cairo_stride)
|
2012-07-29 20:21:33 +04:00
|
|
|
|
|
|
|
|
|
|
2013-02-25 19:27:03 +04:00
|
|
|
|
def pixbuf_to_cairo_png(pixbuf):
|
|
|
|
|
"""Going through PNG.
|
|
|
|
|
|
|
|
|
|
This method is 20~30x slower than GDK but always works.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
buffer_pointer = ffi.new('gchar **')
|
|
|
|
|
buffer_size = ffi.new('gsize *')
|
|
|
|
|
error = ffi.new('GError **')
|
|
|
|
|
pixbuf.save_to_buffer(
|
|
|
|
|
buffer_pointer, buffer_size, ffi.new('char[]', b'png'), error,
|
|
|
|
|
ffi.new('char[]', b'compression'), ffi.new('char[]', b'0'),
|
|
|
|
|
ffi.NULL)
|
|
|
|
|
handle_g_error(error[0], raise_=True)
|
|
|
|
|
png_bytes = ffi.buffer(buffer_pointer[0], buffer_size[0])
|
|
|
|
|
return cairo.ImageSurface.create_from_png(BytesIO(png_bytes))
|
|
|
|
|
|
|
|
|
|
|
2013-01-01 19:55:48 +04:00
|
|
|
|
def cairo_png_loader(file_obj, string):
|
2011-12-08 19:31:03 +04:00
|
|
|
|
"""Return a cairo Surface from a PNG byte stream."""
|
2012-07-13 14:24:55 +04:00
|
|
|
|
surface = cairo.ImageSurface.create_from_png(file_obj or BytesIO(string))
|
2012-09-26 18:59:40 +04:00
|
|
|
|
get_pattern = lambda: cairo.SurfacePattern(surface)
|
|
|
|
|
return get_pattern, surface.get_width(), surface.get_height()
|
2011-12-08 19:31:03 +04:00
|
|
|
|
|
|
|
|
|
|
2013-02-25 19:26:39 +04:00
|
|
|
|
class ScaledSVGSurface(cairosvg.surface.SVGSurface):
|
|
|
|
|
"""
|
|
|
|
|
Have the cairo Surface object have intrinsic dimension
|
|
|
|
|
in pixels instead of points.
|
|
|
|
|
"""
|
|
|
|
|
@property
|
|
|
|
|
def device_units_per_user_units(self):
|
|
|
|
|
scale = super(ScaledSVGSurface, self).device_units_per_user_units
|
|
|
|
|
return scale / 0.75
|
|
|
|
|
|
|
|
|
|
|
2012-07-29 00:11:28 +04:00
|
|
|
|
def cairosvg_loader(file_obj, string, uri):
|
2011-12-08 21:25:19 +04:00
|
|
|
|
"""Return a cairo Surface from a SVG byte stream.
|
|
|
|
|
|
2012-07-29 00:11:28 +04:00
|
|
|
|
This loader uses CairoSVG: http://cairosvg.org/
|
2011-12-08 21:25:19 +04:00
|
|
|
|
"""
|
2012-02-21 15:59:06 +04:00
|
|
|
|
if uri.startswith('data:'):
|
|
|
|
|
# Don’t pass data URIs to CairoSVG.
|
|
|
|
|
# They are useless for relative URIs anyway.
|
|
|
|
|
uri = None
|
2012-07-13 14:24:55 +04:00
|
|
|
|
if file_obj:
|
|
|
|
|
string = file_obj.read()
|
2012-05-29 18:24:14 +04:00
|
|
|
|
|
2012-09-26 18:59:40 +04:00
|
|
|
|
def get_surface():
|
2013-02-25 19:26:39 +04:00
|
|
|
|
tree = cairosvg.parser.Tree(bytestring=string, url=uri)
|
2012-09-26 18:59:40 +04:00
|
|
|
|
# Draw to a cairo surface but do not write to a file
|
2012-05-29 18:24:14 +04:00
|
|
|
|
surface = ScaledSVGSurface(tree, output=None, dpi=96)
|
2012-09-26 18:59:40 +04:00
|
|
|
|
return surface.cairo, surface.width, surface.height
|
2012-05-29 18:24:14 +04:00
|
|
|
|
|
2012-09-26 18:59:40 +04:00
|
|
|
|
def get_pattern():
|
|
|
|
|
# Do not re-use the Surface object, but regenerate it as needed.
|
|
|
|
|
# If a surface for a SVG image is still alive by the time we call
|
|
|
|
|
# show_page(), cairo will rasterize the image instead writing vectors.
|
|
|
|
|
surface, _, _ = get_surface()
|
|
|
|
|
return cairo.SurfacePattern(surface)
|
|
|
|
|
|
|
|
|
|
# Render once to get the size and trigger any exception.
|
|
|
|
|
# If this does not raise, future calls to get_pattern() will hopefully
|
|
|
|
|
# not raise either.
|
|
|
|
|
_, width, height = get_surface()
|
|
|
|
|
if not (width > 0 and height > 0):
|
|
|
|
|
raise ValueError('Images without an intrinsic size are not supported.')
|
|
|
|
|
return get_pattern, width, height
|
2012-01-12 22:26:27 +04:00
|
|
|
|
|
|
|
|
|
|
2012-07-13 14:24:55 +04:00
|
|
|
|
def get_image_from_uri(cache, url_fetcher, uri, type_=None):
|
2011-12-08 19:31:03 +04:00
|
|
|
|
"""Get a :class:`cairo.Surface`` from an image URI."""
|
|
|
|
|
try:
|
2012-05-29 18:24:14 +04:00
|
|
|
|
missing = object()
|
2012-09-26 18:59:40 +04:00
|
|
|
|
image = cache.get(uri, missing)
|
|
|
|
|
if image is not missing:
|
|
|
|
|
return image
|
2012-07-13 14:24:55 +04:00
|
|
|
|
result = url_fetcher(uri)
|
2012-05-29 18:24:14 +04:00
|
|
|
|
try:
|
|
|
|
|
if not type_:
|
2012-07-13 14:24:55 +04:00
|
|
|
|
type_ = result['mime_type'] # Use eg. the HTTP header
|
2012-05-29 18:24:14 +04:00
|
|
|
|
#else: the type was forced by eg. a 'type' attribute on <embed>
|
2012-09-26 18:59:40 +04:00
|
|
|
|
|
2012-07-29 00:11:28 +04:00
|
|
|
|
if type_ == 'image/svg+xml':
|
2012-09-26 18:59:40 +04:00
|
|
|
|
image = cairosvg_loader(
|
2012-07-29 00:11:28 +04:00
|
|
|
|
result.get('file_obj'), result.get('string'), uri)
|
|
|
|
|
elif type_ == 'image/png':
|
2012-09-26 18:59:40 +04:00
|
|
|
|
image = cairo_png_loader(
|
2012-07-29 00:11:28 +04:00
|
|
|
|
result.get('file_obj'), result.get('string'))
|
|
|
|
|
else:
|
2012-09-26 18:59:40 +04:00
|
|
|
|
image = gdkpixbuf_loader(
|
2012-07-29 00:11:28 +04:00
|
|
|
|
result.get('file_obj'), result.get('string'))
|
2012-05-29 18:24:14 +04:00
|
|
|
|
finally:
|
2012-08-03 15:32:42 +04:00
|
|
|
|
if 'file_obj' in result:
|
|
|
|
|
try:
|
|
|
|
|
result['file_obj'].close()
|
|
|
|
|
except Exception: # pragma: no cover
|
|
|
|
|
# May already be closed or something.
|
|
|
|
|
# This is just cleanup anyway.
|
|
|
|
|
pass
|
2012-03-21 17:34:27 +04:00
|
|
|
|
except Exception as exc:
|
|
|
|
|
LOGGER.warn('Error for image at %s : %r', uri, exc)
|
2012-09-26 18:59:40 +04:00
|
|
|
|
image = None
|
|
|
|
|
cache[uri] = image
|
|
|
|
|
return image
|