Files
random_tools/@sideCards/tools/.venv/lib/python3.9/site-packages/cairocffi/pixbuf.py
T
2026-08-11 09:53:42 -04:00

214 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
cairocffi.pixbuf
~~~~~~~~~~~~~~~~
Loading various image formats with GDK-PixBuf
:copyright: Copyright 2013-2019 by Simon Sapin
:license: BSD, see LICENSE for details.
"""
import sys
from array import array
from functools import partial
from io import BytesIO
from . import Context, ImageSurface, constants, dlopen
from .ffi import ffi_pixbuf as ffi
__all__ = ['decode_to_image_surface']
gdk_pixbuf = dlopen(
ffi, ('gdk_pixbuf-2.0', 'libgdk_pixbuf-2.0-0'),
('libgdk_pixbuf-2.0.so.0', 'libgdk_pixbuf-2.0.0.dylib',
'libgdk_pixbuf-2.0-0.dll'))
gobject = dlopen(
ffi, ('gobject-2.0', 'libgobject-2.0-0'),
('libgobject-2.0.so.0', 'libgobject-2.0.dylib', 'libgobject-2.0-0.dll'))
glib = dlopen(
ffi, ('glib-2.0', 'libglib-2.0-0'),
('libglib-2.0.so.0', 'libglib-2.0.dylib', 'libglib-2.0-0.dll'))
try:
gdk = dlopen(
ffi, ('gdk-3', 'libgdk-3-0'),
('libgdk-3.so.0', 'libgdk-3.0.dylib', 'libgdk-3-0.dll'))
except OSError:
gdk = None
gobject.g_type_init()
class ImageLoadingError(ValueError):
"""PixBuf returned an error when loading an image.
The image data is probably corrupted.
"""
def handle_g_error(error, return_value):
"""Convert a ``GError**`` to a Python :exception:`ImageLoadingError`,
and raise it.
"""
error = error[0]
assert bool(return_value) == (error == ffi.NULL)
if error != ffi.NULL:
if error.message != ffi.NULL:
message = ('Pixbuf error: ' +
ffi.string(error.message).decode('utf8', 'replace'))
else: # pragma: no cover
message = 'Pixbuf error'
glib.g_error_free(error)
raise ImageLoadingError(message)
class Pixbuf(object):
"""Wrap a ``GdkPixbuf`` pointer and simulate methods."""
def __init__(self, pointer):
gobject.g_object_ref(pointer)
self._pointer = ffi.gc(pointer, gobject.g_object_unref)
def __getattr__(self, name):
function = getattr(gdk_pixbuf, 'gdk_pixbuf_' + name)
return partial(function, self._pointer)
def decode_to_pixbuf(image_data, width=None, height=None):
"""Decode an image from memory with GDK-PixBuf.
The file format is detected automatically.
:param image_data: A byte string
:param width: Integer width in pixels or None
:param height: Integer height in pixels or None
:returns:
A tuple of a new :class:`PixBuf` object
and the name of the detected image format.
:raises:
:exc:`ImageLoadingError` if the image data is invalid
or in an unsupported format.
"""
loader = ffi.gc(
gdk_pixbuf.gdk_pixbuf_loader_new(), gobject.g_object_unref)
error = ffi.new('GError **')
if width and height:
gdk_pixbuf.gdk_pixbuf_loader_set_size(loader, width, height)
handle_g_error(error, gdk_pixbuf.gdk_pixbuf_loader_write(
loader, image_data, len(image_data), error))
handle_g_error(error, gdk_pixbuf.gdk_pixbuf_loader_close(loader, error))
format_ = gdk_pixbuf.gdk_pixbuf_loader_get_format(loader)
format_name = (
ffi.string(gdk_pixbuf.gdk_pixbuf_format_get_name(format_))
.decode('ascii')
if format_ != ffi.NULL else None)
pixbuf = gdk_pixbuf.gdk_pixbuf_loader_get_pixbuf(loader)
if pixbuf == ffi.NULL: # pragma: no cover
raise ImageLoadingError('Not enough image data (got a NULL pixbuf.)')
return Pixbuf(pixbuf), format_name
def decode_to_image_surface(image_data, width=None, height=None):
"""Decode an image from memory into a cairo surface.
The file format is detected automatically.
:param image_data: A byte string
:param width: Integer width in pixels or None
:param height: Integer height in pixels or None
:returns:
A tuple of a new :class:`~cairocffi.ImageSurface` object
and the name of the detected image format.
:raises:
:exc:`ImageLoadingError` if the image data is invalid
or in an unsupported format.
"""
pixbuf, format_name = decode_to_pixbuf(image_data, width, height)
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))
return surface, format_name
def pixbuf_to_cairo_gdk(pixbuf):
"""Convert from PixBuf to ImageSurface, using GDK.
This method is fastest but GDK is not always available.
"""
dummy_context = Context(ImageSurface(constants.FORMAT_ARGB32, 1, 1))
gdk.gdk_cairo_set_source_pixbuf(
dummy_context._pointer, pixbuf._pointer, 0, 0)
return dummy_context.get_source().get_surface()
def pixbuf_to_cairo_slices(pixbuf):
"""Convert from PixBuf to ImageSurface, using 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.)
"""
assert pixbuf.get_colorspace() == gdk_pixbuf.GDK_COLORSPACE_RGB
assert pixbuf.get_n_channels() == 3
assert pixbuf.get_bits_per_sample() == 8
width = pixbuf.get_width()
height = pixbuf.get_height()
rowstride = pixbuf.get_rowstride()
pixels = ffi.buffer(pixbuf.get_pixels(), pixbuf.get_byte_length())
# TODO: remove this when cffi buffers support slicing with a stride.
pixels = pixels[:]
# Convert GdkPixbufs big-endian RGBA to cairos native-endian ARGB
cairo_stride = ImageSurface.format_stride_for_width(
constants.FORMAT_RGB24, width)
data = bytearray(cairo_stride * height)
big_endian = sys.byteorder == 'big'
pixbuf_row_length = width * 3 # stride == row_length + padding
cairo_row_length = width * 4 # stride == row_length + padding
alpha = b'\xff' * width # opaque
for y in range(height):
offset = rowstride * y
end = offset + pixbuf_row_length
red = pixels[offset:end:3]
green = pixels[offset + 1:end:3]
blue = pixels[offset + 2:end:3]
offset = cairo_stride * y
end = offset + cairo_row_length
if big_endian: # pragma: no cover
data[offset:end:4] = alpha
data[offset + 1:end:4] = red
data[offset + 2:end:4] = green
data[offset + 3:end:4] = blue
else:
data[offset + 3:end:4] = alpha
data[offset + 2:end:4] = red
data[offset + 1:end:4] = green
data[offset:end:4] = blue
data = array('B', data)
return ImageSurface(constants.FORMAT_RGB24,
width, height, data, cairo_stride)
def pixbuf_to_cairo_png(pixbuf):
"""Convert from PixBuf to ImageSurface, by going through the PNG format.
This method is 10~30x slower than GDK but always works.
"""
buffer_pointer = ffi.new('gchar **')
buffer_size = ffi.new('gsize *')
error = ffi.new('GError **')
handle_g_error(error, 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))
png_bytes = ffi.buffer(buffer_pointer[0], buffer_size[0])
return ImageSurface.create_from_png(BytesIO(png_bytes))