Compare commits
5 Commits
b71d5dff57
...
098895bfd9
Author | SHA1 | Date | |
---|---|---|---|
098895bfd9 | |||
235b14dc11 | |||
6ce5c7c2b6 | |||
07e7d39ea2 | |||
562e140a1e |
190
fiv-io.c
190
fiv-io.c
@ -1596,6 +1596,65 @@ open_libraw(const gchar *data, gsize len, GError **error)
|
||||
#endif // HAVE_LIBRAW ---------------------------------------------------------
|
||||
#ifdef HAVE_RESVG // ----------------------------------------------------------
|
||||
|
||||
typedef struct {
|
||||
FivIoRenderClosure parent;
|
||||
resvg_render_tree *tree; ///< Loaded resvg tree
|
||||
double width; ///< Normal width
|
||||
double height; ///< Normal height
|
||||
} FivIoRenderClosureResvg;
|
||||
|
||||
static void
|
||||
load_resvg_destroy(void *closure)
|
||||
{
|
||||
FivIoRenderClosureResvg *self = closure;
|
||||
resvg_tree_destroy(self->tree);
|
||||
g_free(self);
|
||||
}
|
||||
|
||||
static cairo_surface_t *
|
||||
load_resvg_render_internal(
|
||||
FivIoRenderClosureResvg *self, double scale, GError **error)
|
||||
{
|
||||
double w = ceil(self->width * scale), h = ceil(self->height * scale);
|
||||
if (w > SHRT_MAX || h > SHRT_MAX) {
|
||||
set_error(error, "image dimensions overflow");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cairo_surface_t *surface =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
|
||||
cairo_status_t surface_status = cairo_surface_status(surface);
|
||||
if (surface_status != CAIRO_STATUS_SUCCESS) {
|
||||
set_error(error, cairo_status_to_string(surface_status));
|
||||
cairo_surface_destroy(surface);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t *pixels = (uint32_t *) cairo_image_surface_get_data(surface);
|
||||
resvg_fit_to fit_to = {
|
||||
scale == 1 ? RESVG_FIT_TO_TYPE_ORIGINAL : RESVG_FIT_TO_TYPE_ZOOM,
|
||||
scale};
|
||||
resvg_render(self->tree, fit_to, resvg_transform_identity(),
|
||||
cairo_image_surface_get_width(surface),
|
||||
cairo_image_surface_get_height(surface), (char *) pixels);
|
||||
|
||||
// TODO(p): Also apply colour management, we'll need to un-premultiply.
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
uint32_t rgba = g_ntohl(pixels[i]);
|
||||
pixels[i] = rgba << 24 | rgba >> 8;
|
||||
}
|
||||
|
||||
cairo_surface_mark_dirty(surface);
|
||||
return surface;
|
||||
}
|
||||
|
||||
static cairo_surface_t *
|
||||
load_resvg_render(FivIoRenderClosure *closure, double scale)
|
||||
{
|
||||
FivIoRenderClosureResvg *self = (FivIoRenderClosureResvg *) closure;
|
||||
return load_resvg_render_internal(self, scale, NULL);
|
||||
}
|
||||
|
||||
static const char *
|
||||
load_resvg_error(int err)
|
||||
{
|
||||
@ -1627,6 +1686,7 @@ open_resvg(const gchar *data, gsize len, const gchar *uri, GError **error)
|
||||
resvg_options *opt = resvg_options_create();
|
||||
resvg_options_load_system_fonts(opt);
|
||||
resvg_options_set_resources_dir(opt, g_file_peek_path(base_file));
|
||||
// TODO(p): Acquire and set the right DPI for use.
|
||||
resvg_render_tree *tree = NULL;
|
||||
int err = resvg_parse_tree_from_data(data, len, opt, &tree);
|
||||
resvg_options_destroy(opt);
|
||||
@ -1636,52 +1696,73 @@ open_resvg(const gchar *data, gsize len, const gchar *uri, GError **error)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO(p): Support retrieving a scaled-up/down version.
|
||||
// TODO(p): See if there is a situation for resvg_get_image_viewbox().
|
||||
resvg_size size = resvg_get_image_size(tree);
|
||||
double w = ceil(size.width), h = ceil(size.height);
|
||||
if (w > SHRT_MAX || h > SHRT_MAX) {
|
||||
set_error(error, "image dimensions overflow");
|
||||
resvg_tree_destroy(tree);
|
||||
|
||||
FivIoRenderClosureResvg *closure = g_malloc0(sizeof *closure);
|
||||
closure->parent.render = load_resvg_render;
|
||||
closure->tree = tree;
|
||||
closure->width = size.width;
|
||||
closure->height = size.height;
|
||||
|
||||
cairo_surface_t *surface = load_resvg_render_internal(closure, 1., error);
|
||||
if (!surface) {
|
||||
load_resvg_destroy(closure);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cairo_surface_t *surface =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
|
||||
cairo_status_t surface_status = cairo_surface_status(surface);
|
||||
if (surface_status != CAIRO_STATUS_SUCCESS) {
|
||||
set_error(error, cairo_status_to_string(surface_status));
|
||||
cairo_surface_destroy(surface);
|
||||
resvg_tree_destroy(tree);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t *pixels = (uint32_t *) cairo_image_surface_get_data(surface);
|
||||
resvg_fit_to fit_to = { RESVG_FIT_TO_TYPE_ORIGINAL, 1. };
|
||||
resvg_render(tree, fit_to, resvg_transform_identity(),
|
||||
cairo_image_surface_get_width(surface),
|
||||
cairo_image_surface_get_height(surface), (char *) pixels);
|
||||
resvg_tree_destroy(tree);
|
||||
|
||||
// TODO(p): Also apply colour management, we'll need to un-premultiply.
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
uint32_t rgba = g_ntohl(pixels[i]);
|
||||
pixels[i] = rgba << 24 | rgba >> 8;
|
||||
}
|
||||
|
||||
cairo_surface_mark_dirty(surface);
|
||||
cairo_surface_set_user_data(
|
||||
surface, &fiv_io_key_render, closure, load_resvg_destroy);
|
||||
return surface;
|
||||
}
|
||||
|
||||
#endif // HAVE_RESVG ----------------------------------------------------------
|
||||
#ifdef HAVE_LIBRSVG // --------------------------------------------------------
|
||||
|
||||
#ifdef FIV_RSVG_DEBUG
|
||||
#include <cairo/cairo-script.h>
|
||||
#include <cairo/cairo-svg.h>
|
||||
#endif
|
||||
typedef struct {
|
||||
FivIoRenderClosure parent;
|
||||
RsvgHandle *handle; ///< Loaded rsvg handle
|
||||
double width; ///< Normal width
|
||||
double height; ///< Normal height
|
||||
} FivIoRenderClosureLibrsvg;
|
||||
|
||||
static void
|
||||
load_librsvg_destroy(void *closure)
|
||||
{
|
||||
FivIoRenderClosureLibrsvg *self = closure;
|
||||
g_object_unref(self->handle);
|
||||
g_free(self);
|
||||
}
|
||||
|
||||
static cairo_surface_t *
|
||||
load_librsvg_render(FivIoRenderClosure *closure, double scale)
|
||||
{
|
||||
FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure;
|
||||
RsvgRectangle viewport = {.x = 0, .y = 0,
|
||||
.width = self->width * scale, .height = self->height * scale};
|
||||
cairo_surface_t *surface = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_ARGB32, ceil(viewport.width), ceil(viewport.height));
|
||||
|
||||
GError *error = NULL;
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
(void) rsvg_handle_render_document(self->handle, cr, &viewport, &error);
|
||||
cairo_destroy(cr);
|
||||
if (error) {
|
||||
g_debug("%s", error->message);
|
||||
g_error_free(error);
|
||||
cairo_surface_destroy(surface);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cairo_status_t surface_status = cairo_surface_status(surface);
|
||||
if (surface_status != CAIRO_STATUS_SUCCESS) {
|
||||
g_debug("%s", cairo_status_to_string(surface_status));
|
||||
cairo_surface_destroy(surface);
|
||||
return NULL;
|
||||
}
|
||||
return surface;
|
||||
}
|
||||
|
||||
// FIXME: librsvg rasterizes filters, so this method isn't fully appropriate.
|
||||
static cairo_surface_t *
|
||||
open_librsvg(const gchar *data, gsize len, const gchar *uri, GError **error)
|
||||
{
|
||||
@ -1719,22 +1800,14 @@ open_librsvg(const gchar *data, gsize len, const gchar *uri, GError **error)
|
||||
h = viewbox.height;
|
||||
}
|
||||
|
||||
// librsvg rasterizes filters, so this method isn't fully appropriate.
|
||||
// It might be worth removing altogether.
|
||||
cairo_rectangle_t extents = {
|
||||
.x = 0, .y = 0, .width = ceil(w), .height = ceil(h)};
|
||||
cairo_surface_t *surface =
|
||||
cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, &extents);
|
||||
|
||||
#ifdef FIV_RSVG_DEBUG
|
||||
cairo_device_t *script = cairo_script_create("cairo.script");
|
||||
cairo_surface_t *tee =
|
||||
cairo_script_surface_create_for_target(script, surface);
|
||||
cairo_t *cr = cairo_create(tee);
|
||||
cairo_device_destroy(script);
|
||||
cairo_surface_destroy(tee);
|
||||
#else
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
#endif
|
||||
|
||||
RsvgRectangle viewport = {.x = 0, .y = 0, .width = w, .height = h};
|
||||
if (!rsvg_handle_render_document(handle, cr, &viewport, error)) {
|
||||
cairo_surface_destroy(surface);
|
||||
@ -1744,26 +1817,14 @@ open_librsvg(const gchar *data, gsize len, const gchar *uri, GError **error)
|
||||
}
|
||||
|
||||
cairo_destroy(cr);
|
||||
g_object_unref(handle);
|
||||
|
||||
#ifdef FIV_RSVG_DEBUG
|
||||
cairo_surface_t *svg = cairo_svg_surface_create("cairo.svg", w, h);
|
||||
cr = cairo_create(svg);
|
||||
cairo_set_source_surface(cr, surface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_destroy(svg);
|
||||
|
||||
cairo_surface_t *png =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w * 10, h * 10);
|
||||
cr = cairo_create(png);
|
||||
cairo_scale(cr, 10, 10);
|
||||
cairo_set_source_surface(cr, surface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_write_to_png(png, "cairo.png");
|
||||
cairo_surface_destroy(png);
|
||||
#endif
|
||||
FivIoRenderClosureLibrsvg *closure = g_malloc0(sizeof *closure);
|
||||
closure->parent.render = load_librsvg_render;
|
||||
closure->handle = handle;
|
||||
closure->width = w;
|
||||
closure->height = h;
|
||||
cairo_surface_set_user_data(
|
||||
surface, &fiv_io_key_render, closure, load_librsvg_destroy);
|
||||
return surface;
|
||||
}
|
||||
|
||||
@ -2438,6 +2499,7 @@ open_gdkpixbuf(
|
||||
|
||||
#endif // HAVE_GDKPIXBUF ------------------------------------------------------
|
||||
|
||||
// TODO(p): Check that all cairo_surface_set_user_data() calls succeed.
|
||||
cairo_user_data_key_t fiv_io_key_exif;
|
||||
cairo_user_data_key_t fiv_io_key_orientation;
|
||||
cairo_user_data_key_t fiv_io_key_icc;
|
||||
@ -2452,6 +2514,8 @@ cairo_user_data_key_t fiv_io_key_loops;
|
||||
cairo_user_data_key_t fiv_io_key_page_next;
|
||||
cairo_user_data_key_t fiv_io_key_page_previous;
|
||||
|
||||
cairo_user_data_key_t fiv_io_key_render;
|
||||
|
||||
cairo_surface_t *
|
||||
fiv_io_open(
|
||||
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error)
|
||||
|
10
fiv-io.h
10
fiv-io.h
@ -73,6 +73,16 @@ extern cairo_user_data_key_t fiv_io_key_page_next;
|
||||
/// There is no wrap-around. This is a weak pointer.
|
||||
extern cairo_user_data_key_t fiv_io_key_page_previous;
|
||||
|
||||
typedef struct _FivIoRenderClosure {
|
||||
/// The rendering is allowed to fail.
|
||||
cairo_surface_t *(*render)(struct _FivIoRenderClosure *, double scale);
|
||||
} FivIoRenderClosure;
|
||||
|
||||
/// A FivIoRenderClosure for parametrized re-rendering of vector formats.
|
||||
/// This is attached at the page level.
|
||||
/// The rendered image will not have this key.
|
||||
extern cairo_user_data_key_t fiv_io_key_render;
|
||||
|
||||
cairo_surface_t *fiv_io_open(
|
||||
const gchar *uri, FivIoProfile profile, gboolean enhance, GError **error);
|
||||
cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len,
|
||||
|
@ -126,6 +126,16 @@ adjust_thumbnail(cairo_surface_t *thumbnail, double row_height)
|
||||
scale_x = round(scale_y * w) / w;
|
||||
}
|
||||
|
||||
// Vector images should not have orientation, this should handle them all.
|
||||
FivIoRenderClosure *closure =
|
||||
cairo_surface_get_user_data(thumbnail, &fiv_io_key_render);
|
||||
if (closure && orientation <= FivIoOrientation0) {
|
||||
// This API doesn't accept non-uniform scaling; prefer a vertical fit.
|
||||
cairo_surface_t *scaled = closure->render(closure, scale_y);
|
||||
if (scaled)
|
||||
return scaled;
|
||||
}
|
||||
|
||||
// This will be CAIRO_FORMAT_INVALID with non-image surfaces, which is fine.
|
||||
cairo_format_t format = cairo_image_surface_get_format(thumbnail);
|
||||
if (format != CAIRO_FORMAT_INVALID &&
|
||||
|
65
fiv-view.c
65
fiv-view.c
@ -53,6 +53,7 @@ struct _FivView {
|
||||
gchar *uri; ///< Path to the current image (if any)
|
||||
cairo_surface_t *image; ///< The loaded image (sequence)
|
||||
cairo_surface_t *page; ///< Current page within image, weak
|
||||
cairo_surface_t *page_scaled; ///< Current page within image, scaled
|
||||
cairo_surface_t *frame; ///< Current frame within page, weak
|
||||
FivIoOrientation orientation; ///< Current page orientation
|
||||
bool enable_cms : 1; ///< Smooth scaling toggle
|
||||
@ -95,7 +96,7 @@ static FivIoOrientation view_mirror[9] = {
|
||||
[FivIoOrientation180] = FivIoOrientationMirror180,
|
||||
[FivIoOrientationMirror180] = FivIoOrientation180,
|
||||
[FivIoOrientationMirror270] = FivIoOrientation270,
|
||||
[FivIoOrientation90] = FivIoOrientationMirror270,
|
||||
[FivIoOrientation90] = FivIoOrientationMirror90,
|
||||
[FivIoOrientationMirror90] = FivIoOrientation90,
|
||||
[FivIoOrientation270] = FivIoOrientationMirror270,
|
||||
};
|
||||
@ -144,6 +145,7 @@ fiv_view_finalize(GObject *gobject)
|
||||
g_clear_pointer(&self->screen_cms_profile, fiv_io_profile_free);
|
||||
g_clear_pointer(&self->enhance_swap, cairo_surface_destroy);
|
||||
g_clear_pointer(&self->image, cairo_surface_destroy);
|
||||
g_clear_pointer(&self->page_scaled, cairo_surface_destroy);
|
||||
g_free(self->uri);
|
||||
|
||||
G_OBJECT_CLASS(fiv_view_parent_class)->finalize(gobject);
|
||||
@ -274,6 +276,24 @@ fiv_view_get_preferred_width(GtkWidget *widget, gint *minimum, gint *natural)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
prescale_page(FivView *self)
|
||||
{
|
||||
FivIoRenderClosure *closure =
|
||||
cairo_surface_get_user_data(self->page, &fiv_io_key_render);
|
||||
if (!closure)
|
||||
return;
|
||||
|
||||
// TODO(p): Restart the animation. No vector formats currently animate.
|
||||
g_return_if_fail(!self->frame_update_connection);
|
||||
|
||||
// If it fails, the previous frame pointer may become invalid.
|
||||
g_clear_pointer(&self->page_scaled, cairo_surface_destroy);
|
||||
self->frame = self->page_scaled = closure->render(closure, self->scale);
|
||||
if (!self->page_scaled)
|
||||
self->frame = self->page;
|
||||
}
|
||||
|
||||
static void
|
||||
fiv_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
|
||||
{
|
||||
@ -284,13 +304,17 @@ fiv_view_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
|
||||
return;
|
||||
|
||||
Dimensions surface_dimensions = get_surface_dimensions(self);
|
||||
self->scale = 1;
|
||||
double scale = 1;
|
||||
if (ceil(surface_dimensions.width * scale) > allocation->width)
|
||||
scale = allocation->width / surface_dimensions.width;
|
||||
if (ceil(surface_dimensions.height * scale) > allocation->height)
|
||||
scale = allocation->height / surface_dimensions.height;
|
||||
|
||||
if (ceil(surface_dimensions.width * self->scale) > allocation->width)
|
||||
self->scale = allocation->width / surface_dimensions.width;
|
||||
if (ceil(surface_dimensions.height * self->scale) > allocation->height)
|
||||
self->scale = allocation->height / surface_dimensions.height;
|
||||
g_object_notify_by_pspec(G_OBJECT(widget), view_properties[PROP_SCALE]);
|
||||
if (self->scale != scale) {
|
||||
self->scale = scale;
|
||||
g_object_notify_by_pspec(G_OBJECT(widget), view_properties[PROP_SCALE]);
|
||||
prescale_page(self);
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.freedesktop.org/wiki/OpenIcc/ICC_Profiles_in_X_Specification_0.4
|
||||
@ -409,9 +433,9 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
|
||||
y = round((allocation.height - h) / 2.);
|
||||
|
||||
Dimensions surface_dimensions = {};
|
||||
cairo_matrix_t matrix =
|
||||
fiv_io_orientation_apply(self->page, self->orientation,
|
||||
&surface_dimensions.width, &surface_dimensions.height);
|
||||
cairo_matrix_t matrix = fiv_io_orientation_apply(
|
||||
self->page_scaled ? self->page_scaled : self->page, self->orientation,
|
||||
&surface_dimensions.width, &surface_dimensions.height);
|
||||
|
||||
cairo_translate(cr, x, y);
|
||||
if (self->checkerboard) {
|
||||
@ -421,6 +445,14 @@ fiv_view_draw(GtkWidget *widget, cairo_t *cr)
|
||||
gtk_style_context_restore(style);
|
||||
}
|
||||
|
||||
// Then all frames are pre-scaled.
|
||||
if (self->page_scaled) {
|
||||
cairo_set_source_surface(cr, self->frame, 0, 0);
|
||||
cairo_pattern_set_matrix(cairo_get_source(cr), &matrix);
|
||||
cairo_paint(cr);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// FIXME: Recording surfaces do not work well with CAIRO_SURFACE_TYPE_XLIB,
|
||||
// we always get a shitty pixmap, where transparency contains junk.
|
||||
if (cairo_surface_get_type(self->frame) == CAIRO_SURFACE_TYPE_RECORDING) {
|
||||
@ -497,9 +529,13 @@ set_scale_to_fit(FivView *self, bool scale_to_fit)
|
||||
static gboolean
|
||||
set_scale(FivView *self, double scale)
|
||||
{
|
||||
self->scale = scale;
|
||||
g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_SCALE]);
|
||||
gtk_widget_queue_resize(GTK_WIDGET(self));
|
||||
if (self->scale != scale) {
|
||||
self->scale = scale;
|
||||
g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_SCALE]);
|
||||
prescale_page(self);
|
||||
|
||||
gtk_widget_queue_resize(GTK_WIDGET(self));
|
||||
}
|
||||
return set_scale_to_fit(self, false);
|
||||
}
|
||||
|
||||
@ -638,7 +674,10 @@ start_animating(FivView *self)
|
||||
static void
|
||||
switch_page(FivView *self, cairo_surface_t *page)
|
||||
{
|
||||
g_clear_pointer(&self->page_scaled, cairo_surface_destroy);
|
||||
self->frame = self->page = page;
|
||||
prescale_page(self);
|
||||
|
||||
if ((self->orientation = (uintptr_t) cairo_surface_get_user_data(
|
||||
self->page, &fiv_io_key_orientation)) == FivIoOrientationUnknown)
|
||||
self->orientation = FivIoOrientation0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user