From 2e02174fa4fd085d4ce4bff180420560ddf79e5e Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 12 Dec 2020 03:38:10 +0100 Subject: [PATCH] Ottie: Add --- gtk/meson.build | 2 +- meson.build | 1 + ottie/meson.build | 67 ++++ ottie/ottie.h | 31 ++ ottie/ottiecolorvalue.c | 149 +++++++ ottie/ottiecolorvalueprivate.h | 54 +++ ottie/ottiecreation.c | 659 +++++++++++++++++++++++++++++++ ottie/ottiecreation.h | 80 ++++ ottie/ottiecreationprivate.h | 36 ++ ottie/ottiedoublevalue.c | 121 ++++++ ottie/ottiedoublevalueprivate.h | 51 +++ ottie/ottiefillshape.c | 135 +++++++ ottie/ottiefillshapeprivate.h | 45 +++ ottie/ottiegroupshape.c | 235 +++++++++++ ottie/ottiegroupshapeprivate.h | 50 +++ ottie/ottiekeyframesimpl.c | 382 ++++++++++++++++++ ottie/ottielayer.c | 79 ++++ ottie/ottielayerprivate.h | 88 +++++ ottie/ottiepaintable.c | 381 ++++++++++++++++++ ottie/ottiepaintable.h | 54 +++ ottie/ottieparser.c | 490 +++++++++++++++++++++++ ottie/ottieparserprivate.h | 97 +++++ ottie/ottiepathshape.c | 115 ++++++ ottie/ottiepathshapeprivate.h | 45 +++ ottie/ottiepathvalue.c | 402 +++++++++++++++++++ ottie/ottiepathvalueprivate.h | 53 +++ ottie/ottieplayer.c | 490 +++++++++++++++++++++++ ottie/ottieplayer.h | 59 +++ ottie/ottiepoint3dvalue.c | 154 ++++++++ ottie/ottiepoint3dvalueprivate.h | 54 +++ ottie/ottiepointvalue.c | 140 +++++++ ottie/ottiepointvalueprivate.h | 53 +++ ottie/ottieprecomp.c | 177 +++++++++ ottie/ottieprecomplayer.c | 119 ++++++ ottie/ottieprecomplayerprivate.h | 45 +++ ottie/ottieprecompprivate.h | 47 +++ ottie/ottierectshape.c | 137 +++++++ ottie/ottierectshapeprivate.h | 45 +++ ottie/ottieshape.c | 123 ++++++ ottie/ottieshapelayer.c | 132 +++++++ ottie/ottieshapelayerprivate.h | 45 +++ ottie/ottieshapeprivate.h | 87 ++++ ottie/ottiestrokeshape.c | 155 ++++++++ ottie/ottiestrokeshapeprivate.h | 45 +++ ottie/ottietransform.c | 182 +++++++++ ottie/ottietransformprivate.h | 48 +++ ottie/ottietrimshape.c | 198 ++++++++++ ottie/ottietrimshapeprivate.h | 45 +++ ottie/ottievalueimpl.c | 133 +++++++ tests/meson.build | 11 +- tests/ottie.c | 98 +++++ 51 files changed, 6718 insertions(+), 6 deletions(-) create mode 100644 ottie/meson.build create mode 100644 ottie/ottie.h create mode 100644 ottie/ottiecolorvalue.c create mode 100644 ottie/ottiecolorvalueprivate.h create mode 100644 ottie/ottiecreation.c create mode 100644 ottie/ottiecreation.h create mode 100644 ottie/ottiecreationprivate.h create mode 100644 ottie/ottiedoublevalue.c create mode 100644 ottie/ottiedoublevalueprivate.h create mode 100644 ottie/ottiefillshape.c create mode 100644 ottie/ottiefillshapeprivate.h create mode 100644 ottie/ottiegroupshape.c create mode 100644 ottie/ottiegroupshapeprivate.h create mode 100644 ottie/ottiekeyframesimpl.c create mode 100644 ottie/ottielayer.c create mode 100644 ottie/ottielayerprivate.h create mode 100644 ottie/ottiepaintable.c create mode 100644 ottie/ottiepaintable.h create mode 100644 ottie/ottieparser.c create mode 100644 ottie/ottieparserprivate.h create mode 100644 ottie/ottiepathshape.c create mode 100644 ottie/ottiepathshapeprivate.h create mode 100644 ottie/ottiepathvalue.c create mode 100644 ottie/ottiepathvalueprivate.h create mode 100644 ottie/ottieplayer.c create mode 100644 ottie/ottieplayer.h create mode 100644 ottie/ottiepoint3dvalue.c create mode 100644 ottie/ottiepoint3dvalueprivate.h create mode 100644 ottie/ottiepointvalue.c create mode 100644 ottie/ottiepointvalueprivate.h create mode 100644 ottie/ottieprecomp.c create mode 100644 ottie/ottieprecomplayer.c create mode 100644 ottie/ottieprecomplayerprivate.h create mode 100644 ottie/ottieprecompprivate.h create mode 100644 ottie/ottierectshape.c create mode 100644 ottie/ottierectshapeprivate.h create mode 100644 ottie/ottieshape.c create mode 100644 ottie/ottieshapelayer.c create mode 100644 ottie/ottieshapelayerprivate.h create mode 100644 ottie/ottieshapeprivate.h create mode 100644 ottie/ottiestrokeshape.c create mode 100644 ottie/ottiestrokeshapeprivate.h create mode 100644 ottie/ottietransform.c create mode 100644 ottie/ottietransformprivate.h create mode 100644 ottie/ottietrimshape.c create mode 100644 ottie/ottietrimshapeprivate.h create mode 100644 ottie/ottievalueimpl.c create mode 100644 tests/ottie.c diff --git a/gtk/meson.build b/gtk/meson.build index 9f07d3d5f0..2d816a0295 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -1109,7 +1109,7 @@ libgtk = library('gtk-4', c_args: gtk_cargs + common_cflags, include_directories: [confinc, gdkinc, gskinc, gtkinc], dependencies: gtk_deps + [libgtk_css_dep, libgdk_dep, libgsk_dep], - link_whole: [libgtk_css, libgdk, libgsk, ], + link_whole: [libgtk_css, libgdk, libgsk, libottie], link_args: common_ldflags, darwin_versions: darwin_versions, install: true, diff --git a/meson.build b/meson.build index c86d5ce5c2..12687fd605 100644 --- a/meson.build +++ b/meson.build @@ -677,6 +677,7 @@ endif subdir('gtk/css') subdir('gdk') subdir('gsk') +subdir('ottie') subdir('gtk') subdir('modules') if get_option('demos') diff --git a/ottie/meson.build b/ottie/meson.build new file mode 100644 index 0000000000..1bb154c946 --- /dev/null +++ b/ottie/meson.build @@ -0,0 +1,67 @@ +ottie_public_sources = files([ + 'ottiecreation.c', + 'ottiepaintable.c', + 'ottieplayer.c', +]) + +ottie_private_sources = files([ + 'ottiecolorvalue.c', + 'ottiedoublevalue.c', + 'ottiefillshape.c', + 'ottiegroupshape.c', + 'ottielayer.c', + 'ottieparser.c', + 'ottiepathshape.c', + 'ottiepathvalue.c', + 'ottiepointvalue.c', + 'ottiepoint3dvalue.c', + 'ottieprecomp.c', + 'ottieprecomplayer.c', + 'ottierectshape.c', + 'ottieshape.c', + 'ottieshapelayer.c', + 'ottiestrokeshape.c', + 'ottietransform.c', + 'ottietrimshape.c', +]) + +ottie_public_headers = files([ + 'ottie.h', + 'ottiecreation.h', + 'ottiepaintable.h', + 'ottieplayer.h', +]) + +install_headers(ottie_public_headers, 'ottie.h', subdir: 'gtk-4.0/ottie') + +json_glib_dep = dependency('json-glib-1.0', required: true) +ottie_deps = [ + libm, + glib_dep, + gobject_dep, + platform_gio_dep, + libgdk_dep, + libgsk_dep, + json_glib_dep +] + +libottie = static_library('ottie', + sources: [ + ottie_public_sources, + ottie_private_sources, + ], + dependencies: ottie_deps, + include_directories: [ confinc, ], + c_args: [ + '-DGTK_COMPILATION', + '-DG_LOG_DOMAIN="Gtk"', + ] + common_cflags, + link_with: [libgdk, libgsk ], + link_args: common_ldflags) + +# We don't have link_with: to internal static libs here on purpose, just +# list the dependencies and generated headers and such, for use in the +# "public" libgtk_dep used by internal executables. +libottie_dep = declare_dependency(include_directories: [ confinc, ], + dependencies: ottie_deps) + diff --git a/ottie/ottie.h b/ottie/ottie.h new file mode 100644 index 0000000000..c2da341e41 --- /dev/null +++ b/ottie/ottie.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_H__ +#define __OTTIE_H__ + +#define __OTTIE_H_INSIDE__ + +#include +#include +#include + +#undef __OTTIE_H_INSIDE__ + +#endif /* __OTTIE_H__ */ diff --git a/ottie/ottiecolorvalue.c b/ottie/ottiecolorvalue.c new file mode 100644 index 0000000000..7b81afd1ee --- /dev/null +++ b/ottie/ottiecolorvalue.c @@ -0,0 +1,149 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiecolorvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_color_value_parse_one (JsonReader *reader, + gsize offset, + gpointer data) +{ + GdkRGBA *rgba = (GdkRGBA *) ((guint8 *) data + offset); + double d[3]; + + if (!ottie_parser_parse_array (reader, "color value", + 3, 3, NULL, + 0, sizeof (double), + ottie_parser_option_double, + d)) + { + d[0] = d[1] = d[2] = 0; + } + + rgba->red = d[0]; + rgba->green = d[1]; + rgba->blue = d[2]; + rgba->alpha = 1; + + return TRUE; +} + +static void +ottie_color_value_interpolate (const GdkRGBA *start, + const GdkRGBA *end, + double progress, + GdkRGBA *result) +{ + result->red = start->red + progress * (end->red - start->red); + result->green = start->green + progress * (end->green - start->green); + result->blue = start->blue + progress * (end->blue - start->blue); + result->alpha = start->alpha + progress * (end->alpha - start->alpha); +} + +#define OTTIE_KEYFRAMES_NAME ottie_color_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttieColorKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE GdkRGBA +#define OTTIE_KEYFRAMES_BY_VALUE 1 +#define OTTIE_KEYFRAMES_DIMENSIONS 4 +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_color_value_parse_one +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_color_value_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_color_value_init (OttieColorValue *self, + const GdkRGBA *value) +{ + self->is_static = TRUE; + self->static_value = *value; +} + +void +ottie_color_value_clear (OttieColorValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_color_keyframes_free); +} + +void +ottie_color_value_get (OttieColorValue *self, + double timestamp, + GdkRGBA *rgba) +{ + if (self->is_static) + { + *rgba = self->static_value; + return; + } + + ottie_color_keyframes_get (self->keyframes, timestamp, rgba); +} + +gboolean +ottie_color_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieColorValue *self = (OttieColorValue *) ((guint8 *) data + offset); + + if (json_reader_read_member (reader, "k")) + { + gboolean is_static; + + if (!json_reader_is_array (reader)) + is_static = TRUE; + else + { + if (json_reader_read_element (reader, 0)) + is_static = !json_reader_is_object (reader); + else + is_static = TRUE; + json_reader_end_element (reader); + } + + if (is_static) + { + self->is_static = TRUE; + ottie_color_value_parse_one (reader, 0, &self->static_value); + } + else + { + self->is_static = FALSE; + self->keyframes = ottie_color_keyframes_parse (reader); + if (self->keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + } + } + else + { + ottie_parser_error_syntax (reader, "Property is not a color value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiecolorvalueprivate.h b/ottie/ottiecolorvalueprivate.h new file mode 100644 index 0000000000..48998d807a --- /dev/null +++ b/ottie/ottiecolorvalueprivate.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_COLOR_VALUE_PRIVATE_H__ +#define __OTTIE_COLOR_VALUE_PRIVATE_H__ + +#include + +#include + +G_BEGIN_DECLS + +typedef struct _OttieColorValue OttieColorValue; + +struct _OttieColorValue +{ + gboolean is_static; + union { + GdkRGBA static_value; + gpointer keyframes; + }; +}; + +void ottie_color_value_init (OttieColorValue *self, + const GdkRGBA *rgba); +void ottie_color_value_clear (OttieColorValue *self); + +void ottie_color_value_get (OttieColorValue *self, + double timestamp, + GdkRGBA *rgba); + +gboolean ottie_color_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_COLOR_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottiecreation.c b/ottie/ottiecreation.c new file mode 100644 index 0000000000..16927164f4 --- /dev/null +++ b/ottie/ottiecreation.c @@ -0,0 +1,659 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiecreationprivate.h" + +#include "ottielayerprivate.h" +#include "ottieparserprivate.h" +#include "ottieprecompprivate.h" + +#include +#include + +struct _OttieCreation +{ + GObject parent; + + char *name; + double frame_rate; + double start_frame; + double end_frame; + double width; + double height; + + OttiePrecomp *layers; + GHashTable *precomp_assets; + + GCancellable *cancellable; +}; + +struct _OttieCreationClass +{ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_END_FRAME, + PROP_FRAME_RATE, + PROP_HEIGHT, + PROP_LOADING, + PROP_NAME, + PROP_PREPARED, + PROP_START_FRAME, + PROP_WIDTH, + + N_PROPS +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +G_DEFINE_TYPE (OttieCreation, ottie_creation, G_TYPE_OBJECT) + +static void +ottie_creation_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + //OttieCreation *self = OTTIE_CREATION (object); + + switch (prop_id) + { + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_creation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OttieCreation *self = OTTIE_CREATION (object); + + switch (prop_id) + { + case PROP_END_FRAME: + g_value_set_double (value, self->end_frame); + break; + + case PROP_FRAME_RATE: + g_value_set_double (value, self->frame_rate); + break; + + case PROP_HEIGHT: + g_value_set_double (value, self->height); + break; + + case PROP_PREPARED: + g_value_set_boolean (value, self->cancellable != NULL); + break; + + case PROP_NAME: + g_value_set_string (value, self->name); + break; + + case PROP_START_FRAME: + g_value_set_double (value, self->start_frame); + break; + + case PROP_WIDTH: + g_value_set_double (value, self->width); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_creation_stop_loading (OttieCreation *self, + gboolean emit) +{ + if (self->cancellable == NULL) + return; + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + if (emit) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); +} + +static void +ottie_creation_reset (OttieCreation *self) +{ + g_clear_object (&self->layers); + g_hash_table_remove_all (self->precomp_assets); + + g_clear_pointer (&self->name, g_free); + self->frame_rate = 0; + self->start_frame = 0; + self->end_frame = 0; + self->width = 0; + self->height = 0; +} + +static void +ottie_creation_dispose (GObject *object) +{ + OttieCreation *self = OTTIE_CREATION (object); + + ottie_creation_stop_loading (self, FALSE); + ottie_creation_reset (self); + + G_OBJECT_CLASS (ottie_creation_parent_class)->dispose (object); +} + +static void +ottie_creation_finalize (GObject *object) +{ + OttieCreation *self = OTTIE_CREATION (object); + + g_hash_table_unref (self->precomp_assets); + + G_OBJECT_CLASS (ottie_creation_parent_class)->finalize (object); +} + +static void +ottie_creation_class_init (OttieCreationClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = ottie_creation_set_property; + gobject_class->get_property = ottie_creation_get_property; + gobject_class->finalize = ottie_creation_finalize; + gobject_class->dispose = ottie_creation_dispose; + + /** + * OttieCreation:end-frame: + * + * End frame of the creation + */ + properties[PROP_END_FRAME] = + g_param_spec_double ("end-frame", + "End frame", + "End frame of the creation", + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttieCreation:loading: + * + * Whether the creation is currently loading. + */ + properties[PROP_LOADING] = + g_param_spec_boolean ("loading", + "Loading", + "Whether the creation is currently loading", + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttieCreation:frame-rate: + * + * Frame rate of this creation + */ + properties[PROP_FRAME_RATE] = + g_param_spec_double ("frame-rate", + "Frame rate", + "Frame rate of this creation", + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttieCreation:height: + * + * Height of this creation + */ + properties[PROP_HEIGHT] = + g_param_spec_double ("height", + "Height", + "Height of this creation", + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttieCreation:name: + * + * The name of the creation. + */ + properties[PROP_NAME] = + g_param_spec_string ("name", + "Name", + "The name of the creation", + NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttieCreation:prepared: + * + * Whether the creation is prepared to render + */ + properties[PROP_PREPARED] = + g_param_spec_boolean ("prepared", + "Prepared", + "Whether the creation is prepared to render", + FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttieCreation:start-frame: + * + * Start frame of the creation + */ + properties[PROP_START_FRAME] = + g_param_spec_double ("start-frame", + "Start frame", + "Start frame of the creation", + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttieCreation:width: + * + * Width of this creation + */ + properties[PROP_WIDTH] = + g_param_spec_double ("width", + "Width", + "Width of this creation", + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +ottie_creation_init (OttieCreation *self) +{ + self->precomp_assets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); +} + +/** + * ottie_creation_is_loading: + * @self: a #OttieCreation + * + * Returns whether @self is still in the process of loading. This may not just involve + * the creation itself, but also any assets that are a part of the creation. + * + * Returns: %TRUE if the creation is loading + */ +gboolean +ottie_creation_is_loading (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), FALSE); + + return self->cancellable != NULL; +} + +/** + * ottie_creation_is_prepared: + * @self: a #OttieCreation + * + * Returns whether @self has successfully loaded a document that it can display. + * + * Returns: %TRUE if the creation can be used + */ +gboolean +ottie_creation_is_prepared (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), FALSE); + + return self->frame_rate > 0; +} + +/** + * ottie_creation_get_name: + * @self: a #OttieCreation + * + * Returns the name of the current creation or %NULL if the creation is unnamed. + * + * Returns: (allow-none): The name of the creation + */ +const char * +ottie_creation_get_name (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), FALSE); + + return self->name; +} + +static void +ottie_creation_emit_error (OttieCreation *self, + const GError *error) +{ + g_print ("Ottie is sad: %s\n", error->message); +} + +typedef struct { + char *id; + OttiePrecomp *precomp; +} OttieParserAsset; + +static gboolean +ottie_creation_parse_asset (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieParserOption options[] = { + { "id", ottie_parser_option_string, G_STRUCT_OFFSET (OttieParserAsset, id) }, + { "layers", ottie_precomp_parse_layers, G_STRUCT_OFFSET (OttieParserAsset, precomp) }, + }; + OttieCreation *self = data; + OttieParserAsset asset = { }; + gboolean result; + + result = ottie_parser_parse_object (reader, "asset", options, G_N_ELEMENTS (options), &asset); + + if (result) + { + if (asset.id == NULL) + ottie_parser_error_syntax (reader, "No name given to asset"); + else if (asset.precomp == NULL) + ottie_parser_error_syntax (reader, "No precomp layer or image asset defined for name %s", asset.id); + else + g_hash_table_insert (self->precomp_assets, g_strdup (asset.id), g_object_ref (asset.precomp)); + } + + g_clear_pointer (&asset.id, g_free); + g_clear_object (&asset.precomp); + + return result; +} + +static gboolean +ottie_creation_parse_assets (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_parser_parse_array (reader, "assets", + 0, G_MAXUINT, NULL, + offset, 0, + ottie_creation_parse_asset, + data); +} + +static gboolean +ottie_creation_parse_marker (JsonReader *reader, + gsize offset, + gpointer data) +{ + ottie_parser_error_unsupported (reader, "Markers are not implemented yet."); + + return TRUE; +} + +static gboolean +ottie_creation_parse_markers (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_parser_parse_array (reader, "markers", + 0, G_MAXUINT, NULL, + offset, 0, + ottie_creation_parse_marker, + data); +} + +static gboolean +ottie_creation_load_from_reader (OttieCreation *self, + JsonReader *reader) +{ + OttieParserOption options[] = { + { "fr", ottie_parser_option_double, G_STRUCT_OFFSET (OttieCreation, frame_rate) }, + { "w", ottie_parser_option_double, G_STRUCT_OFFSET (OttieCreation, width) }, + { "h", ottie_parser_option_double, G_STRUCT_OFFSET (OttieCreation, height) }, + { "nm", ottie_parser_option_string, G_STRUCT_OFFSET (OttieCreation, name) }, + { "ip", ottie_parser_option_double, G_STRUCT_OFFSET (OttieCreation, start_frame) }, + { "op", ottie_parser_option_double, G_STRUCT_OFFSET (OttieCreation, end_frame) }, + { "ddd", ottie_parser_option_3d, 0 }, + { "v", ottie_parser_option_skip, 0 }, + { "layers", ottie_precomp_parse_layers, G_STRUCT_OFFSET (OttieCreation, layers) }, + { "assets", ottie_creation_parse_assets, 0 }, + { "markers", ottie_creation_parse_markers, 0 }, + }; + + return ottie_parser_parse_object (reader, "toplevel", options, G_N_ELEMENTS (options), self); +} + +static void +ottie_creation_notify_prepared (OttieCreation *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PREPARED]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FRAME_RATE]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WIDTH]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEIGHT]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_START_FRAME]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_END_FRAME]); +} + +static void +ottie_creation_load_from_node (OttieCreation *self, + JsonNode *root) +{ + JsonReader *reader = json_reader_new (root); + + ottie_creation_load_from_reader (self, reader); + + g_object_unref (reader); +} + +static void +ottie_creation_load_file_parsed (GObject *parser, + GAsyncResult *res, + gpointer data) +{ + OttieCreation *self = data; + GError *error = NULL; + + if (!json_parser_load_from_stream_finish (JSON_PARSER (parser), res, &error)) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + ottie_creation_emit_error (self, error); + g_error_free (error); + ottie_creation_stop_loading (self, TRUE); + return; + } + + g_object_freeze_notify (G_OBJECT (self)); + + ottie_creation_load_from_node (self, json_parser_get_root (JSON_PARSER (parser))); + ottie_creation_stop_loading (self, TRUE); + ottie_creation_notify_prepared (self); + + g_object_thaw_notify (G_OBJECT (self)); +} + +static void +ottie_creation_load_file_open (GObject *file, + GAsyncResult *res, + gpointer data) +{ + OttieCreation *self = data; + GFileInputStream *stream; + GError *error = NULL; + JsonParser *parser; + + stream = g_file_read_finish (G_FILE (file), res, &error); + if (stream == NULL) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + ottie_creation_emit_error (self, error); + g_error_free (error); + ottie_creation_stop_loading (self, TRUE); + return; + } + + parser = json_parser_new (); + json_parser_load_from_stream_async (parser, + G_INPUT_STREAM (stream), + self->cancellable, + ottie_creation_load_file_parsed, + self); + g_object_unref (parser); +} + +void +ottie_creation_load_file (OttieCreation *self, + GFile *file) +{ + g_return_if_fail (OTTIE_IS_CREATION (self)); + g_return_if_fail (G_IS_FILE (file)); + + g_object_freeze_notify (G_OBJECT (self)); + + ottie_creation_stop_loading (self, FALSE); + if (self->frame_rate) + { + ottie_creation_reset (self); + ottie_creation_notify_prepared (self); + } + + self->cancellable = g_cancellable_new (); + + g_file_read_async (file, + G_PRIORITY_DEFAULT, + self->cancellable, + ottie_creation_load_file_open, + self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +void +ottie_creation_load_filename (OttieCreation *self, + const char *filename) +{ + GFile *file; + + g_return_if_fail (OTTIE_IS_CREATION (self)); + g_return_if_fail (filename != NULL); + + file = g_file_new_for_path (filename); + + ottie_creation_load_file (self, file); + + g_clear_object (&file); +} + +OttieCreation * +ottie_creation_new (void) +{ + return g_object_new (OTTIE_TYPE_CREATION, NULL); +} + +OttieCreation * +ottie_creation_new_for_file (GFile *file) +{ + OttieCreation *self; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + + self = g_object_new (OTTIE_TYPE_CREATION, NULL); + + ottie_creation_load_file (self, file); + + return self; +} + +OttieCreation * +ottie_creation_new_for_filename (const char *filename) +{ + OttieCreation *self; + GFile *file; + + g_return_val_if_fail (filename != NULL, NULL); + + file = g_file_new_for_path (filename); + + self = ottie_creation_new_for_file (file); + + g_clear_object (&file); + + return self; +} + +double +ottie_creation_get_frame_rate (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), 0); + + return self->frame_rate; +} + +double +ottie_creation_get_start_frame (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), 0); + + return self->start_frame; +} + +double +ottie_creation_get_end_frame (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), 0); + + return self->end_frame; +} + +double +ottie_creation_get_width (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), 0); + + return self->width; +} + +double +ottie_creation_get_height (OttieCreation *self) +{ + g_return_val_if_fail (OTTIE_IS_CREATION (self), 0); + + return self->height; +} + +void +ottie_creation_snapshot (OttieCreation *self, + GtkSnapshot *snapshot, + double timestamp) +{ + if (self->layers == NULL) + return; + + timestamp = timestamp * self->frame_rate; + ottie_layer_snapshot (OTTIE_LAYER (self->layers), snapshot, timestamp); +} + + diff --git a/ottie/ottiecreation.h b/ottie/ottiecreation.h new file mode 100644 index 0000000000..9ba0f59820 --- /dev/null +++ b/ottie/ottiecreation.h @@ -0,0 +1,80 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_CREATION_H__ +#define __OTTIE_CREATION_H__ + +#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_CREATION (ottie_creation_get_type ()) +#define OTTIE_CREATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_CREATION, OttieCreation)) +#define OTTIE_CREATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_CREATION, OttieCreationClass)) +#define OTTIE_IS_CREATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_CREATION)) +#define OTTIE_IS_CREATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_CREATION)) +#define OTTIE_CREATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_CREATION, OttieCreationClass)) + +typedef struct _OttieCreation OttieCreation; +typedef struct _OttieCreationClass OttieCreationClass; + +GDK_AVAILABLE_IN_ALL +GType ottie_creation_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_creation_new (void); +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_creation_new_for_file (GFile *file); +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_creation_new_for_filename (const char *filename); + +GDK_AVAILABLE_IN_ALL +void ottie_creation_load_file (OttieCreation *self, + GFile *file); +GDK_AVAILABLE_IN_ALL +void ottie_creation_load_filename (OttieCreation *self, + const char *filename); + +GDK_AVAILABLE_IN_ALL +gboolean ottie_creation_is_loading (OttieCreation *self); +GDK_AVAILABLE_IN_ALL +gboolean ottie_creation_is_prepared (OttieCreation *self); + +GDK_AVAILABLE_IN_ALL +const char * ottie_creation_get_name (OttieCreation *self); +GDK_AVAILABLE_IN_ALL +double ottie_creation_get_frame_rate (OttieCreation *self); +GDK_AVAILABLE_IN_ALL +double ottie_creation_get_start_frame (OttieCreation *self); +GDK_AVAILABLE_IN_ALL +double ottie_creation_get_end_frame (OttieCreation *self); +GDK_AVAILABLE_IN_ALL +double ottie_creation_get_width (OttieCreation *self); +GDK_AVAILABLE_IN_ALL +double ottie_creation_get_height (OttieCreation *self); + + + +G_END_DECLS + +#endif /* __OTTIE_CREATION_H__ */ diff --git a/ottie/ottiecreationprivate.h b/ottie/ottiecreationprivate.h new file mode 100644 index 0000000000..0b1e491e67 --- /dev/null +++ b/ottie/ottiecreationprivate.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_CREATION_PRIVATE_H__ +#define __OTTIE_CREATION_PRIVATE_H__ + +#include "ottiecreation.h" + +#include + +G_BEGIN_DECLS + + +void ottie_creation_snapshot (OttieCreation *self, + GtkSnapshot *snapshot, + double timestamp); + +G_END_DECLS + +#endif /* __OTTIE_CREATION_PRIVATE_H__ */ diff --git a/ottie/ottiedoublevalue.c b/ottie/ottiedoublevalue.c new file mode 100644 index 0000000000..e3897ea033 --- /dev/null +++ b/ottie/ottiedoublevalue.c @@ -0,0 +1,121 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiedoublevalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_double_value_parse_value (JsonReader *reader, + gsize offset, + gpointer data) +{ + gboolean result, array; + + /* Lottie being Lottie, single values may get dumped into arrays. */ + array = json_reader_is_array (reader); + if (array) + json_reader_read_element (reader, 0); + + result = ottie_parser_option_double (reader, offset, data); + + if (array) + json_reader_end_element (reader); + + return result; +} + +static double +ottie_double_value_interpolate (double start, + double end, + double progress) +{ + return start + (end - start) * progress; +} + +#define OTTIE_KEYFRAMES_NAME ottie_double_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttieDoubleKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE double +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_double_value_parse_value +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_double_value_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_double_value_init (OttieDoubleValue *self, + double value) +{ + self->is_static = TRUE; + self->static_value = value; +} + +void +ottie_double_value_clear (OttieDoubleValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_double_keyframes_free); +} + +double +ottie_double_value_get (OttieDoubleValue *self, + double timestamp) +{ + if (self->is_static) + return self->static_value; + + return ottie_double_keyframes_get (self->keyframes, timestamp); +} + +gboolean +ottie_double_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieDoubleValue *self = (OttieDoubleValue *) ((guint8 *) data + GPOINTER_TO_SIZE (offset)); + + if (json_reader_read_member (reader, "k")) + { + if (!json_reader_is_array (reader)) + { + self->is_static = TRUE; + self->static_value = json_reader_get_double_value (reader); + } + else + { + self->is_static = FALSE; + self->keyframes = ottie_double_keyframes_parse (reader); + if (self->keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + } + } + else + { + ottie_parser_error_syntax (reader, "Property is not a number"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiedoublevalueprivate.h b/ottie/ottiedoublevalueprivate.h new file mode 100644 index 0000000000..d1eaa27aac --- /dev/null +++ b/ottie/ottiedoublevalueprivate.h @@ -0,0 +1,51 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_DOUBLE_VALUE_PRIVATE_H__ +#define __OTTIE_DOUBLE_VALUE_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _OttieDoubleValue OttieDoubleValue; + +struct _OttieDoubleValue +{ + gboolean is_static; + union { + double static_value; + gpointer keyframes; + }; +}; + +void ottie_double_value_init (OttieDoubleValue *self, + double value); +void ottie_double_value_clear (OttieDoubleValue *self); + +double ottie_double_value_get (OttieDoubleValue *self, + double timestamp); + +gboolean ottie_double_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_DOUBLE_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottiefillshape.c b/ottie/ottiefillshape.c new file mode 100644 index 0000000000..1b8d3a25e0 --- /dev/null +++ b/ottie/ottiefillshape.c @@ -0,0 +1,135 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiefillshapeprivate.h" + +#include "ottiecolorvalueprivate.h" +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +struct _OttieFillShape +{ + OttieShape parent; + + OttieDoubleValue opacity; + OttieColorValue color; + GskBlendMode blend_mode; +}; + +struct _OttieFillShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieFillShape, ottie_fill_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_fill_shape_snapshot (OttieShape *shape, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ + OttieFillShape *self = OTTIE_FILL_SHAPE (shape); + GskPath *path; + graphene_rect_t bounds; + GdkRGBA color; + double opacity; + + opacity = ottie_double_value_get (&self->opacity, timestamp); + opacity = CLAMP (opacity, 0, 100); + ottie_color_value_get (&self->color, timestamp, &color); + color.alpha = color.alpha * opacity / 100.f; + if (gdk_rgba_is_clear (&color)) + return; + + path = ottie_shape_snapshot_get_path (snapshot_data); + gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING); + + gsk_path_get_bounds (path, &bounds); + gtk_snapshot_append_color (snapshot, &color, &bounds); + + gtk_snapshot_pop (snapshot); +} + +static void +ottie_fill_shape_dispose (GObject *object) +{ + OttieFillShape *self = OTTIE_FILL_SHAPE (object); + + ottie_double_value_clear (&self->opacity); + ottie_color_value_clear (&self->color); + + G_OBJECT_CLASS (ottie_fill_shape_parent_class)->dispose (object); +} + +static void +ottie_fill_shape_finalize (GObject *object) +{ + //OttieFillShape *self = OTTIE_FILL_SHAPE (object); + + G_OBJECT_CLASS (ottie_fill_shape_parent_class)->finalize (object); +} + +static void +ottie_fill_shape_class_init (OttieFillShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->snapshot = ottie_fill_shape_snapshot; + + gobject_class->finalize = ottie_fill_shape_finalize; + gobject_class->dispose = ottie_fill_shape_dispose; +} + +static void +ottie_fill_shape_init (OttieFillShape *self) +{ + ottie_double_value_init (&self->opacity, 100); + ottie_color_value_init (&self->color, &(GdkRGBA) { 0, 0, 0, 1 }); +} + +OttieShape * +ottie_fill_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE + { "o", ottie_double_value_parse, G_STRUCT_OFFSET (OttieFillShape, opacity) }, + { "c", ottie_color_value_parse, G_STRUCT_OFFSET (OttieFillShape, color) }, + { "bm", ottie_parser_option_blend_mode, G_STRUCT_OFFSET (OttieFillShape, blend_mode) }, + }; + OttieFillShape *self; + + self = g_object_new (OTTIE_TYPE_FILL_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "fill shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottiefillshapeprivate.h b/ottie/ottiefillshapeprivate.h new file mode 100644 index 0000000000..ea8889f2eb --- /dev/null +++ b/ottie/ottiefillshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_FILL_SHAPE_PRIVATE_H__ +#define __OTTIE_FILL_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_FILL_SHAPE (ottie_fill_shape_get_type ()) +#define OTTIE_FILL_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_FILL_SHAPE, OttieFillShape)) +#define OTTIE_FILL_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_FILL_SHAPE, OttieFillShapeClass)) +#define OTTIE_IS_FILL_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_FILL_SHAPE)) +#define OTTIE_IS_FILL_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_FILL_SHAPE)) +#define OTTIE_FILL_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_FILL_SHAPE, OttieFillShapeClass)) + +typedef struct _OttieFillShape OttieFillShape; +typedef struct _OttieFillShapeClass OttieFillShapeClass; + +GType ottie_fill_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_fill_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_FILL_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottiegroupshape.c b/ottie/ottiegroupshape.c new file mode 100644 index 0000000000..ed520935ec --- /dev/null +++ b/ottie/ottiegroupshape.c @@ -0,0 +1,235 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiegroupshapeprivate.h" + +#include "ottiefillshapeprivate.h" +#include "ottieparserprivate.h" +#include "ottiepathshapeprivate.h" +#include "ottierectshapeprivate.h" +#include "ottieshapeprivate.h" +#include "ottiestrokeshapeprivate.h" +#include "ottietransformprivate.h" +#include "ottietrimshapeprivate.h" + +#include +#include + +#define GDK_ARRAY_ELEMENT_TYPE OttieShape * +#define GDK_ARRAY_FREE_FUNC g_object_unref +#define GDK_ARRAY_TYPE_NAME OttieShapeList +#define GDK_ARRAY_NAME ottie_shape_list +#define GDK_ARRAY_PREALLOC 4 +#include "gdk/gdkarrayimpl.c" + +struct _OttieGroupShape +{ + OttieShape parent; + + OttieShapeList shapes; + GskBlendMode blend_mode; +}; + +struct _OttieGroupShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieGroupShape, ottie_group_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_group_shape_snapshot (OttieShape *shape, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ + OttieGroupShape *self = OTTIE_GROUP_SHAPE (shape); + OttieShapeSnapshot group_snapshot; + + gtk_snapshot_save (snapshot); + + ottie_shape_snapshot_init (&group_snapshot, snapshot_data); + + for (gsize i = 0; i < ottie_shape_list_get_size (&self->shapes); i++) + { + OttieShape *tr_shape = ottie_shape_list_get (&self->shapes, i); + + if (OTTIE_IS_TRANSFORM (tr_shape)) + { + GskTransform *transform = ottie_transform_get_transform (OTTIE_TRANSFORM (tr_shape), timestamp); + gtk_snapshot_transform (snapshot, transform); + gsk_transform_unref (transform); + break; + } + } + + for (gsize i = 0; i < ottie_shape_list_get_size (&self->shapes); i++) + { + ottie_shape_snapshot (ottie_shape_list_get (&self->shapes, i), + snapshot, + &group_snapshot, + timestamp); + } + + ottie_shape_snapshot_clear (&group_snapshot); + + gtk_snapshot_restore (snapshot); +} + +static void +ottie_group_shape_dispose (GObject *object) +{ + OttieGroupShape *self = OTTIE_GROUP_SHAPE (object); + + ottie_shape_list_clear (&self->shapes); + + G_OBJECT_CLASS (ottie_group_shape_parent_class)->dispose (object); +} + +static void +ottie_group_shape_finalize (GObject *object) +{ + //OttieGroupShape *self = OTTIE_GROUP_SHAPE (object); + + G_OBJECT_CLASS (ottie_group_shape_parent_class)->finalize (object); +} + +static void +ottie_group_shape_class_init (OttieGroupShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->snapshot = ottie_group_shape_snapshot; + + gobject_class->finalize = ottie_group_shape_finalize; + gobject_class->dispose = ottie_group_shape_dispose; +} + +static void +ottie_group_shape_init (OttieGroupShape *self) +{ +} + +gboolean +ottie_group_shape_parse_shapes (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieGroupShape *self = data; + + if (!json_reader_is_array (reader)) + { + ottie_parser_error_syntax (reader, "Shapes are not an array."); + return FALSE; + } + + for (int i = 0; ; i++) + { + OttieShape *shape; + const char *type; + + if (!json_reader_read_element (reader, i)) + break; + + if (!json_reader_is_object (reader)) + { + ottie_parser_error_syntax (reader, "Shape %d is not an object", i); + continue; + } + + if (!json_reader_read_member (reader, "ty")) + { + ottie_parser_error_syntax (reader, "Shape %d has no type", i); + json_reader_end_member (reader); + json_reader_end_element (reader); + continue; + } + + type = json_reader_get_string_value (reader); + if (type == NULL || json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + json_reader_end_member (reader); + json_reader_end_element (reader); + continue; + } + json_reader_end_member (reader); + + if (g_str_equal (type, "fl")) + shape = ottie_fill_shape_parse (reader); + else if (g_str_equal (type, "gr")) + shape = ottie_group_shape_parse (reader); + else if (g_str_equal (type, "rc")) + shape = ottie_rect_shape_parse (reader); + else if (g_str_equal (type, "sh")) + shape = ottie_path_shape_parse (reader); + else if (g_str_equal (type, "st")) + shape = ottie_stroke_shape_parse (reader); + else if (g_str_equal (type, "tm")) + shape = ottie_trim_shape_parse (reader); + else if (g_str_equal (type, "tr")) + shape = ottie_transform_parse (reader); + else + { + ottie_parser_error_value (reader, "Shape %d has unknown type \"%s\"", i, type); + shape = NULL; + } + + if (shape) + ottie_shape_list_append (&self->shapes, shape); + json_reader_end_element (reader); + } + + json_reader_end_element (reader); + + return TRUE; +} + +OttieShape * +ottie_group_shape_new (void) +{ + return g_object_new (OTTIE_TYPE_GROUP_SHAPE, NULL); +} + +OttieShape * +ottie_group_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE + { "bm", ottie_parser_option_blend_mode, G_STRUCT_OFFSET (OttieGroupShape, blend_mode) }, + { "np", ottie_parser_option_skip_expression, 0 }, + { "cix", ottie_parser_option_skip_index, 0 }, + { "it", ottie_group_shape_parse_shapes, 0 }, + }; + OttieShape *self; + + self = ottie_group_shape_new (); + + if (!ottie_parser_parse_object (reader, "group shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return self; +} + diff --git a/ottie/ottiegroupshapeprivate.h b/ottie/ottiegroupshapeprivate.h new file mode 100644 index 0000000000..a436aeb35d --- /dev/null +++ b/ottie/ottiegroupshapeprivate.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_GROUP_SHAPE_PRIVATE_H__ +#define __OTTIE_GROUP_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_GROUP_SHAPE (ottie_group_shape_get_type ()) +#define OTTIE_GROUP_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShape)) +#define OTTIE_GROUP_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShapeClass)) +#define OTTIE_IS_GROUP_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_GROUP_SHAPE)) +#define OTTIE_IS_GROUP_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_GROUP_SHAPE)) +#define OTTIE_GROUP_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShapeClass)) + +typedef struct _OttieGroupShape OttieGroupShape; +typedef struct _OttieGroupShapeClass OttieGroupShapeClass; + +GType ottie_group_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_group_shape_new (void); +OttieShape * ottie_group_shape_parse (JsonReader *reader); + +gboolean ottie_group_shape_parse_shapes (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_GROUP_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottiekeyframesimpl.c b/ottie/ottiekeyframesimpl.c new file mode 100644 index 0000000000..255d17e3a4 --- /dev/null +++ b/ottie/ottiekeyframesimpl.c @@ -0,0 +1,382 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include + +G_BEGIN_DECLS + +#ifndef OTTIE_KEYFRAMES_TYPE_NAME +#define OTTIE_KEYFRAMES_TYPE_NAME OttieKeyframes +#endif + +#ifndef OTTIE_KEYFRAMES_NAME +#define OTTIE_KEYFRAMES_NAME ottie_keyframes +#endif + +#ifndef OTTIE_KEYFRAMES_ELEMENT_TYPE +#define OTTIE_KEYFRAMES_ELEMENT_TYPE gpointer +#endif + +#ifndef OTTIE_KEYFRAMES_DIMENSIONS +#define OTTIE_KEYFRAMES_DIMENSIONS 1 +#endif + +/* make this readable */ +#define _T_ OTTIE_KEYFRAMES_ELEMENT_TYPE +#define OttieKeyframes OTTIE_KEYFRAMES_TYPE_NAME +#define OttieKeyframe OTTIE_KEYFRAMES_TYPE_NAME ## Keyframe +#define OttieControlPoint OTTIE_KEYFRAMES_TYPE_NAME ## ControlPoint +#define ottie_keyframes_paste_more(OTTIE_KEYFRAMES_NAME, func_name) OTTIE_KEYFRAMES_NAME ## _ ## func_name +#define ottie_keyframes_paste(OTTIE_KEYFRAMES_NAME, func_name) ottie_keyframes_paste_more (OTTIE_KEYFRAMES_NAME, func_name) +#define ottie_keyframes(func_name) ottie_keyframes_paste (OTTIE_KEYFRAMES_NAME, func_name) + +typedef struct OttieControlPoint OttieControlPoint; +typedef struct OttieKeyframe OttieKeyframe; +typedef struct OttieKeyframes OttieKeyframes; + +struct OttieControlPoint +{ + double x[OTTIE_KEYFRAMES_DIMENSIONS]; + double y[OTTIE_KEYFRAMES_DIMENSIONS]; +}; + +struct OttieKeyframe +{ + /* Cubic control points, but Lottie names them in and out points */ + OttieControlPoint in; + OttieControlPoint out; + double start_time; + _T_ start_value; + _T_ end_value; +}; + +struct OttieKeyframes +{ + gsize n_items; + OttieKeyframe items[]; +}; + +static inline OttieKeyframes * +ottie_keyframes(new) (gsize n_items) +{ + OttieKeyframes *self; + + self = g_malloc0 (sizeof (OttieKeyframes) + n_items * sizeof (OttieKeyframe)); + self->n_items = n_items; + + return self; +} + +static inline void +ottie_keyframes(free_value) (_T_ *item) +{ +#ifdef OTTIE_KEYFRAMES_FREE_FUNC +#ifdef OTTIE_KEYFRAMES_BY_VALUE + OTTIE_KEYFRAMES_FREE_FUNC (item); +#else + OTTIE_KEYFRAMES_FREE_FUNC (*item); +#endif +#endif +} + +static inline void +ottie_keyframes(copy_value) (_T_ *dest, + _T_ *src) +{ +#ifdef OTTIE_KEYFRAMES_COPY_FUNC +# ifdef OTTIE_KEYFRAMES_BY_VALUE + OTTIE_KEYFRAMES_COPY_FUNC (dest, src); +# else + *dest = OTTIE_KEYFRAMES_COPY_FUNC (*src); +# endif +#else + *dest = *src; +#endif +} + +/* no G_GNUC_UNUSED here */ +static inline void +ottie_keyframes(free) (OttieKeyframes *self) +{ +#ifdef OTTIE_KEYFRAMES_FREE_FUNC + gsize i; + + for (i = 0; i < self->n_items; i++) + { + ottie_keyframes(free_value) (&self->items[i].start_value); + ottie_keyframes(free_value) (&self->items[i].end_value); + } +#endif +} + +static +#ifdef OTTIE_KEYFRAMES_BY_VALUE +void +#else +_T_ +#endif +ottie_keyframes(get) (const OttieKeyframes *self, + double timestamp +#ifdef OTTIE_KEYFRAMES_BY_VALUE + , _T_ *out_result +#endif + ) +{ + const OttieKeyframe *keyframe; + gsize i; + + for (i = 0; i < self->n_items; i++) + { + if (self->items[i].start_time > timestamp) + break; + } + + if (i == 0 || i >= self->n_items) + { + keyframe = &self->items[i == 0 ? 0 : self->n_items - 1]; +#ifdef OTTIE_KEYFRAMES_BY_VALUE + *out_result = keyframe->start_value; + return; +#else + return keyframe->start_value; +#endif + } + + keyframe = &self->items[i - 1]; + + double progress = (timestamp - keyframe->start_time) / (self->items[i].start_time - keyframe->start_time); +#ifdef OTTIE_KEYFRAMES_BY_VALUE + OTTIE_KEYFRAMES_INTERPOLATE_FUNC (&keyframe->start_value, &keyframe->end_value, progress, out_result); +#else + return OTTIE_KEYFRAMES_INTERPOLATE_FUNC (keyframe->start_value, keyframe->end_value, progress); +#endif +} + +static gboolean +ottie_keyframes(parse_control_point_dimension) (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d[OTTIE_KEYFRAMES_DIMENSIONS]; + + if (json_reader_is_array (reader)) + { + if (json_reader_count_elements (reader) != OTTIE_KEYFRAMES_DIMENSIONS) + ottie_parser_error_value (reader, "control point has %d dimension, not %u", json_reader_count_elements (reader), OTTIE_KEYFRAMES_DIMENSIONS); + + for (int i = 0; i < OTTIE_KEYFRAMES_DIMENSIONS; i++) + { + if (!json_reader_read_element (reader, i)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + } + else + { + if (!ottie_parser_option_double (reader, 0, &d[i])) + d[i] = 0; + } + json_reader_end_element (reader); + } + } + else + { + if (!ottie_parser_option_double (reader, 0, &d[0])) + return FALSE; + + for (gsize i = 1; i < OTTIE_KEYFRAMES_DIMENSIONS; i++) + d[i] = d[0]; + } + + memcpy ((guint8 *) data + GPOINTER_TO_SIZE (offset), d, sizeof (double) * OTTIE_KEYFRAMES_DIMENSIONS); + return TRUE; +} + +static gboolean +ottie_keyframes(parse_control_point) (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieParserOption options[] = { + { "x", ottie_keyframes(parse_control_point_dimension), G_STRUCT_OFFSET (OttieControlPoint, x) }, + { "y", ottie_keyframes(parse_control_point_dimension), G_STRUCT_OFFSET (OttieControlPoint, y) }, + }; + OttieControlPoint cp; + OttieControlPoint *target = (OttieControlPoint *) ((guint8 *) data + offset); + + if (!ottie_parser_parse_object (reader, "control point", options, G_N_ELEMENTS (options), &cp)) + return FALSE; + + *target = cp; + return TRUE; +} + +typedef struct +{ + OttieKeyframe keyframe; + gboolean has_start_value; + gboolean has_end_value; +} OttieKeyframeParse; + +static gboolean +ottie_keyframes(parse_start_value) (JsonReader *reader, + gsize pos, + gpointer data) +{ + OttieKeyframeParse *parse = data; + + if (parse->has_start_value) + ottie_keyframes(free_value) (&parse->keyframe.start_value); + + parse->has_start_value = OTTIE_KEYFRAMES_PARSE_FUNC (reader, 0, &parse->keyframe.start_value); + return parse->has_start_value; +} + +static gboolean +ottie_keyframes(parse_end_value) (JsonReader *reader, + gsize pos, + gpointer data) +{ + OttieKeyframeParse *parse = data; + + if (parse->has_end_value) + ottie_keyframes(free_value) (&parse->keyframe.end_value); + + parse->has_end_value = OTTIE_KEYFRAMES_PARSE_FUNC (reader, 0, &parse->keyframe.end_value); + return parse->has_end_value; +} + +typedef struct +{ + OttieKeyframes *keyframes; + gboolean has_end_value; +} OttieKeyframesParse; + +static gboolean +ottie_keyframes(parse_keyframe) (JsonReader *reader, + gsize pos, + gpointer data) +{ + OttieParserOption options[] = { + { "s", ottie_keyframes(parse_start_value), 0, }, + { "e", ottie_keyframes(parse_end_value), 0, }, + { "t", ottie_parser_option_double, G_STRUCT_OFFSET (OttieKeyframe, start_time) }, + { "i", ottie_keyframes(parse_control_point), G_STRUCT_OFFSET (OttieKeyframe, in) }, + { "o", ottie_keyframes(parse_control_point), G_STRUCT_OFFSET (OttieKeyframe, out) }, + { "ix", ottie_parser_option_skip_index, 0 }, + }; + OttieKeyframesParse *self = data; + OttieKeyframeParse parse = { 0, }; + + if (!ottie_parser_parse_object (reader, "keyframe", options, G_N_ELEMENTS (options), &parse)) + goto fail; + + if (pos == 0) + { + if (!parse.has_start_value) + { + ottie_parser_error_syntax (reader, "First keyframe must have a start value"); + return FALSE; + } + } + else + { + if (parse.keyframe.start_time <= self->keyframes->items[pos - 1].start_time) + goto fail; + + if (!parse.has_start_value) + { + if (self->has_end_value) + { + ottie_keyframes(copy_value) (&parse.keyframe.start_value, &self->keyframes->items[pos - 1].end_value); + } + else + { + ottie_parser_error_syntax (reader, "Keyframe %zu has no end value and %zu has no start value.", pos - 1, pos); + goto fail; + } + } + + if (!self->has_end_value) + ottie_keyframes(copy_value) (&self->keyframes->items[pos - 1].end_value, &parse.keyframe.start_value); + } + + self->has_end_value = parse.has_end_value; + self->keyframes->items[pos] = parse.keyframe; + + return TRUE; + +fail: + self->keyframes->n_items = pos; + if (parse.has_start_value) + ottie_keyframes(free_value) (&parse.keyframe.start_value); + if (parse.has_end_value) + ottie_keyframes(free_value) (&parse.keyframe.end_value); + return FALSE; +} + +/* no G_GNUC_UNUSED here, if you don't use a type, remove it. */ +static inline OttieKeyframes * +ottie_keyframes(parse) (JsonReader *reader) +{ + OttieKeyframesParse parse; + OttieKeyframes *self; + + self = ottie_keyframes(new) (json_reader_count_elements (reader)); + + parse.keyframes = self; + parse.has_end_value = FALSE; + + if (!ottie_parser_parse_array (reader, "keyframes", + self->n_items, self->n_items, + NULL, + 0, 1, + ottie_keyframes(parse_keyframe), + &parse)) + { + /* do a dumb copy so the free has something to free */ + if (!parse.has_end_value && self->n_items > 0) + ottie_keyframes(copy_value) (&self->items[self->n_items - 1].end_value, + &self->items[self->n_items - 1].start_value); + ottie_keyframes(free) (self); + return NULL; + } + + if (!parse.has_end_value) + ottie_keyframes(copy_value) (&self->items[self->n_items - 1].end_value, + &self->items[self->n_items - 1].start_value); + + return parse.keyframes; +} + +#ifndef OTTIE_KEYFRAMES_NO_UNDEF + +#undef _T_ +#undef OttieKeyframes +#undef ottie_keyframes_paste_more +#undef ottie_keyframes_paste +#undef ottie_keyframes + +#undef OTTIE_KEYFRAMES_COPY_FUNC +#undef OTTIE_KEYFRAMES_PARSE_FUNC +#undef OTTIE_KEYFRAMES_BY_VALUE +#undef OTTIE_KEYFRAMES_ELEMENT_TYPE +#undef OTTIE_KEYFRAMES_FREE_FUNC +#undef OTTIE_KEYFRAMES_NAME +#undef OTTIE_KEYFRAMES_TYPE_NAME +#endif diff --git a/ottie/ottielayer.c b/ottie/ottielayer.c new file mode 100644 index 0000000000..e043534215 --- /dev/null +++ b/ottie/ottielayer.c @@ -0,0 +1,79 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottielayerprivate.h" + +#include +#include + +G_DEFINE_TYPE (OttieLayer, ottie_layer, G_TYPE_OBJECT) + +static void +ottie_layer_dispose (GObject *object) +{ + OttieLayer *self = OTTIE_LAYER (object); + + g_clear_object (&self->transform); + + G_OBJECT_CLASS (ottie_layer_parent_class)->dispose (object); +} + +static void +ottie_layer_finalize (GObject *object) +{ + //OttieLayer *self = OTTIE_LAYER (object); + + G_OBJECT_CLASS (ottie_layer_parent_class)->finalize (object); +} + +static void +ottie_layer_class_init (OttieLayerClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = ottie_layer_finalize; + gobject_class->dispose = ottie_layer_dispose; +} + +static void +ottie_layer_init (OttieLayer *self) +{ + self->stretch = 1; + self->blend_mode = GSK_BLEND_MODE_DEFAULT; +} + +void +ottie_layer_snapshot (OttieLayer *self, + GtkSnapshot *snapshot, + double timestamp) +{ + if (self->transform) + { + GskTransform *transform; + + transform = ottie_transform_get_transform (self->transform, timestamp); + gtk_snapshot_transform (snapshot, transform); + gsk_transform_unref (transform); + } + + OTTIE_LAYER_GET_CLASS (self)->snapshot (self, snapshot, timestamp); +} + diff --git a/ottie/ottielayerprivate.h b/ottie/ottielayerprivate.h new file mode 100644 index 0000000000..c4dacccde3 --- /dev/null +++ b/ottie/ottielayerprivate.h @@ -0,0 +1,88 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_LAYER_PRIVATE_H__ +#define __OTTIE_LAYER_PRIVATE_H__ + +#include + +#include "ottie/ottietransformprivate.h" +#include "ottie/ottieparserprivate.h" + +G_BEGIN_DECLS + +#define OTTIE_TYPE_LAYER (ottie_layer_get_type ()) +#define OTTIE_LAYER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_LAYER, OttieLayer)) +#define OTTIE_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_LAYER, OttieLayerClass)) +#define OTTIE_IS_LAYER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_LAYER)) +#define OTTIE_IS_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_LAYER)) +#define OTTIE_LAYER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_LAYER, OttieLayerClass)) + +typedef struct _OttieLayer OttieLayer; +typedef struct _OttieLayerClass OttieLayerClass; + +struct _OttieLayer +{ + GObject parent; + + OttieTransform *transform; + gboolean auto_orient; + GskBlendMode blend_mode; + double index; + char *layer_name; + char *name; + double start_frame; + double end_frame; + double start_time; + double stretch; +}; + +struct _OttieLayerClass +{ + GObjectClass parent_class; + + void (* snapshot) (OttieLayer *layer, + GtkSnapshot *snapshot, + double timestamp); +}; + +GType ottie_layer_get_type (void) G_GNUC_CONST; + +void ottie_layer_snapshot (OttieLayer *self, + GtkSnapshot *snapshot, + double timestamp); + +#define OTTIE_PARSE_OPTIONS_LAYER \ + { "ao", ottie_parser_option_boolean, G_STRUCT_OFFSET (OttieLayer, auto_orient) }, \ + { "bm", ottie_parser_option_blend_mode, G_STRUCT_OFFSET (OttieLayer, blend_mode) }, \ + { "nm", ottie_parser_option_string, G_STRUCT_OFFSET (OttieLayer, name) }, \ + { "ln", ottie_parser_option_string, G_STRUCT_OFFSET (OttieLayer, layer_name) }, \ + { "ks", ottie_parser_option_transform, G_STRUCT_OFFSET (OttieLayer, transform) }, \ + { "ip", ottie_parser_option_double, G_STRUCT_OFFSET (OttieLayer, start_frame) }, \ + { "ind", ottie_parser_option_double, G_STRUCT_OFFSET (OttieLayer, index) }, \ + { "op", ottie_parser_option_double, G_STRUCT_OFFSET (OttieLayer, end_frame) }, \ + { "st", ottie_parser_option_double, G_STRUCT_OFFSET (OttieLayer, start_time) }, \ + { "sr", ottie_parser_option_double, G_STRUCT_OFFSET (OttieLayer, stretch) }, \ + { "ddd", ottie_parser_option_3d, 0 }, \ + { "ix", ottie_parser_option_skip_index, 0 }, \ + { "ty", ottie_parser_option_skip, 0 }, + +G_END_DECLS + +#endif /* __OTTIE_LAYER_PRIVATE_H__ */ diff --git a/ottie/ottiepaintable.c b/ottie/ottiepaintable.c new file mode 100644 index 0000000000..27b273300b --- /dev/null +++ b/ottie/ottiepaintable.c @@ -0,0 +1,381 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepaintable.h" + +#include "ottiecreationprivate.h" + +#include +#include + +struct _OttiePaintable +{ + GObject parent_instance; + + OttieCreation *creation; + gint64 timestamp; +}; + +struct _OttiePaintableClass +{ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_CREATION, + PROP_DURATION, + PROP_TIMESTAMP, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +ottie_paintable_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + OttiePaintable *self = OTTIE_PAINTABLE (paintable); + double w, h, timestamp; + + if (!self->creation) + return; + + w = ottie_creation_get_width (self->creation); + h = ottie_creation_get_height (self->creation); + timestamp = (double) self->timestamp / G_USEC_PER_SEC; + + if (w != width || h != height) + { + gtk_snapshot_save (snapshot); + gtk_snapshot_scale (snapshot, width / w, height / h); + } + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, w, h)); + ottie_creation_snapshot (self->creation, snapshot, timestamp); + gtk_snapshot_pop (snapshot); + + if (w != width || h != height) + gtk_snapshot_restore (snapshot); +} + +static int +ottie_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + OttiePaintable *self = OTTIE_PAINTABLE (paintable); + + if (!self->creation) + return 0; + + return ceil (ottie_creation_get_width (self->creation)); +} + +static int +ottie_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + OttiePaintable *self = OTTIE_PAINTABLE (paintable); + + if (!self->creation) + return 0; + + return ceil (ottie_creation_get_height (self->creation)); + +} + +static void +ottie_paintable_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = ottie_paintable_paintable_snapshot; + iface->get_intrinsic_width = ottie_paintable_paintable_get_intrinsic_width; + iface->get_intrinsic_height = ottie_paintable_paintable_get_intrinsic_height; +} + +G_DEFINE_TYPE_EXTENDED (OttiePaintable, ottie_paintable, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + ottie_paintable_paintable_init)) + +static void +ottie_paintable_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + OttiePaintable *self = OTTIE_PAINTABLE (object); + + switch (prop_id) + { + case PROP_CREATION: + ottie_paintable_set_creation (self, g_value_get_object (value)); + break; + + case PROP_TIMESTAMP: + ottie_paintable_set_timestamp (self, g_value_get_int64 (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_paintable_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OttiePaintable *self = OTTIE_PAINTABLE (object); + + switch (prop_id) + { + case PROP_CREATION: + g_value_set_object (value, self->creation); + break; + + case PROP_DURATION: + g_value_set_int64 (value, ottie_paintable_get_duration (self)); + break; + + case PROP_TIMESTAMP: + g_value_set_int64 (value, self->timestamp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_paintable_prepared_cb (OttieCreation *creation, + GParamSpec *pspec, + OttiePaintable *self) +{ + gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); +} + +static void +ottie_paintable_unset_creation (OttiePaintable *self) +{ + if (self->creation == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->creation, ottie_paintable_prepared_cb, self); + g_clear_object (&self->creation); +} + +static void +ottie_paintable_dispose (GObject *object) +{ + OttiePaintable *self = OTTIE_PAINTABLE (object); + + ottie_paintable_unset_creation (self); + + G_OBJECT_CLASS (ottie_paintable_parent_class)->dispose (object); +} + +static void +ottie_paintable_class_init (OttiePaintableClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = ottie_paintable_get_property; + gobject_class->set_property = ottie_paintable_set_property; + gobject_class->dispose = ottie_paintable_dispose; + + /** + * OttiePaintable:creation + * + * The displayed creation or %NULL. + */ + properties[PROP_CREATION] = + g_param_spec_object ("creation", + _("Creation"), + _("The displayed creation"), + OTTIE_TYPE_CREATION, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttiePaintable:duration + * + * Duration of the displayed creation + */ + properties[PROP_DURATION] = + g_param_spec_int64 ("duration", + _("Duration"), + _("Duration of the displayed creation"), + 0, G_MAXINT64, 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttiePaintable:timestamp + * + * At what timestamp to display the creation. + */ + properties[PROP_TIMESTAMP] = + g_param_spec_int64 ("timestmp", + _("Timestamp"), + _("At what timestamp to display the creation"), + 0, G_MAXINT64, 0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +ottie_paintable_init (OttiePaintable *self) +{ +} + +/** + * ottie_paintable_new: + * @creation: (allow-none) (transfer full): an #OttiePaintable or %NULL + * + * Creates a new Ottie paintable for the given @creation + * + * Returns: (transfer full) (type OttiePaintable): a new #OttiePaintable + **/ +OttiePaintable * +ottie_paintable_new (OttieCreation *creation) +{ + OttiePaintable *self; + + g_return_val_if_fail (creation == creation || OTTIE_IS_CREATION (creation), NULL); + + self = g_object_new (OTTIE_TYPE_PAINTABLE, + "creation", creation, + NULL); + + g_clear_object (&creation); + + return self; +} + +/** + * ottie_paintable_get_creation: + * @self: an #OttiePaintable + * + * Returns the creation that shown or %NULL + * if none. + * + * Returns: (transfer none) (nullable): the observed creation. + **/ +OttieCreation * +ottie_paintable_get_creation (OttiePaintable *self) +{ + g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), NULL); + + return self->creation; +} + +/** + * ottie_paintable_set_creation: + * @self: an #OttiePaintable + * @creation: (allow-none): the creation to show or %NULL + * + * Sets the creation that should be shown. + **/ +void +ottie_paintable_set_creation (OttiePaintable *self, + OttieCreation *creation) +{ + g_return_if_fail (OTTIE_IS_PAINTABLE (self)); + g_return_if_fail (creation == NULL || OTTIE_IS_CREATION (creation)); + + if (self->creation == creation) + return; + + ottie_paintable_unset_creation (self); + + self->creation = g_object_ref (creation); + g_signal_connect (creation, "notify::prepared", G_CALLBACK (ottie_paintable_prepared_cb), self); + + gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CREATION]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); +} + +/** + * ottie_paintable_get_timestamp: + * @self: an #OttiePaintable + * + * Gets the timestamp for the currently displayed image. + * + * Returns: the timestamp + **/ +gint64 +ottie_paintable_get_timestamp (OttiePaintable *self) +{ + g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), 0); + + return self->timestamp; +} + +/** + * ottie_paintable_set_timestamp: + * @self: an #OttiePaintable + * @timestamp: the timestamp to display + * + * Sets the timestamp to display the creation at. + **/ +void +ottie_paintable_set_timestamp (OttiePaintable *self, + gint64 timestamp) +{ + g_return_if_fail (OTTIE_IS_PAINTABLE (self)); + g_return_if_fail (timestamp >= 0); + + if (self->timestamp == timestamp) + return; + + self->timestamp = timestamp; + + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP]); +} + +/** + * ottie_paintable_get_duration: + * @self: an #OttiePaintable + * + * Gets the duration of the currently playing creation. + * + * Returns: The duration in usec. + **/ +gint64 +ottie_paintable_get_duration (OttiePaintable *self) +{ + g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), 0); + + if (self->creation == NULL) + return 0; + + return ceil (G_USEC_PER_SEC * ottie_creation_get_end_frame (self->creation) + / ottie_creation_get_frame_rate (self->creation)); +} + diff --git a/ottie/ottiepaintable.h b/ottie/ottiepaintable.h new file mode 100644 index 0000000000..6c818194a1 --- /dev/null +++ b/ottie/ottiepaintable.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PAINTABLE_H__ +#define __OTTIE_PAINTABLE_H__ + +#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PAINTABLE (ottie_paintable_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (OttiePaintable, ottie_paintable, OTTIE, PAINTABLE, GObject) + +GDK_AVAILABLE_IN_ALL +OttiePaintable * ottie_paintable_new (OttieCreation *creation); + +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_paintable_get_creation (OttiePaintable *self); +GDK_AVAILABLE_IN_ALL +void ottie_paintable_set_creation (OttiePaintable *self, + OttieCreation *creation); +GDK_AVAILABLE_IN_ALL +gint64 ottie_paintable_get_timestamp (OttiePaintable *self); +GDK_AVAILABLE_IN_ALL +void ottie_paintable_set_timestamp (OttiePaintable *self, + gint64 timestamp); +GDK_AVAILABLE_IN_ALL +gint64 ottie_paintable_get_duration (OttiePaintable *self); + +G_END_DECLS + +#endif /* __OTTIE_PAINTABLE_H__ */ diff --git a/ottie/ottieparser.c b/ottie/ottieparser.c new file mode 100644 index 0000000000..86b158a251 --- /dev/null +++ b/ottie/ottieparser.c @@ -0,0 +1,490 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieparserprivate.h" + +#include "ottietransformprivate.h" + +#include + +void +ottie_parser_emit_error (JsonReader *reader, + const GError *error) +{ + g_printerr ("Ottie is sad: %s\n", error->message); +} + +void +ottie_parser_error_syntax (JsonReader *reader, + const char *format, + ...) +{ + va_list args; + GError *error; + + va_start (args, format); + error = g_error_new_valist (JSON_PARSER_ERROR, + JSON_PARSER_ERROR_INVALID_DATA, + format, args); + va_end (args); + + ottie_parser_emit_error (reader, error); + + g_error_free (error); +} + +void +ottie_parser_error_value (JsonReader *reader, + const char *format, + ...) +{ + va_list args; + GError *error; + + va_start (args, format); + error = g_error_new_valist (JSON_PARSER_ERROR, + JSON_PARSER_ERROR_INVALID_DATA, + format, args); + va_end (args); + + ottie_parser_emit_error (reader, error); + + g_error_free (error); +} + +void +ottie_parser_error_unsupported (JsonReader *reader, + const char *format, + ...) +{ + va_list args; + GError *error; + + va_start (args, format); + error = g_error_new_valist (JSON_PARSER_ERROR, + JSON_PARSER_ERROR_INVALID_DATA, + format, args); + va_end (args); + + ottie_parser_emit_error (reader, error); + + g_error_free (error); +} + +gboolean +ottie_parser_parse_array (JsonReader *reader, + const char *debug_name, + guint min_items, + guint max_items, + guint *out_n_items, + gsize start_offset, + gsize offset_multiplier, + OttieParseFunc func, + gpointer data) +{ + guint i; + + if (!json_reader_is_array (reader)) + { + if (min_items > 1) + { + ottie_parser_error_syntax (reader, "Expected an array when parsing %s", debug_name); + if (out_n_items) + *out_n_items = 0; + return FALSE; + } + else + { + if (!func (reader, start_offset, data)) + { + if (out_n_items) + *out_n_items = 0; + return FALSE; + } + if (out_n_items) + *out_n_items = 1; + return TRUE; + } + } + + if (json_reader_count_elements (reader) < min_items) + { + ottie_parser_error_syntax (reader, "%s needs %u items, but only %u given", + debug_name, min_items, json_reader_count_elements (reader)); + return FALSE; + } + max_items = MIN (max_items, json_reader_count_elements (reader)); + + for (i = 0; i < max_items; i++) + { + if (!json_reader_read_element (reader, i) || + !func (reader, start_offset + offset_multiplier * i, data)) + { + json_reader_end_element (reader); + if (out_n_items) + *out_n_items = i; + return FALSE; + } + + json_reader_end_element (reader); + } + + if (out_n_items) + *out_n_items = i; + return TRUE; +} + +gboolean +ottie_parser_parse_object (JsonReader *reader, + const char *debug_name, + const OttieParserOption *options, + gsize n_options, + gpointer data) +{ + if (!json_reader_is_object (reader)) + { + ottie_parser_error_syntax (reader, "Expected an object when parsing %s", debug_name); + return FALSE; + } + + for (int i = 0; ; i++) + { + const OttieParserOption *o = NULL; + const char *name; + + if (!json_reader_read_element (reader, i)) + break; + + name = json_reader_get_member_name (reader); + + for (gsize j = 0; j < n_options; j++) + { + o = &options[j]; + if (g_str_equal (o->name, name)) + break; + o = NULL; + } + + if (o) + { + if (!o->parse_func (reader, o->option_data, data)) + { + json_reader_end_element (reader); + return FALSE; + } + } + else + { + ottie_parser_error_unsupported (reader, "Unsupported %s property \"%s\"", debug_name, json_reader_get_member_name (reader)); + } + + json_reader_end_element (reader); + } + + json_reader_end_element (reader); + + return TRUE; +} + +gboolean +ottie_parser_option_skip (JsonReader *reader, + gsize offset, + gpointer data) +{ + return TRUE; +} + +gboolean +ottie_parser_option_boolean (JsonReader *reader, + gsize offset, + gpointer data) +{ + gboolean b; + + b = json_reader_get_boolean_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + json_reader_end_element (reader); + return FALSE; + } + + *(gboolean *) ((guint8 *) data + offset) = b; + + return TRUE; +} + +gboolean +ottie_parser_option_double (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d; + + d = json_reader_get_double_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + *(double *) ((guint8 *) data + offset) = d; + + return TRUE; +} + +gboolean +ottie_parser_option_string (JsonReader *reader, + gsize offset, + gpointer data) +{ + char **target; + const char *s; + + s = json_reader_get_string_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + target = (char **) ((guint8 *) data + offset); + + g_clear_pointer (target, g_free); + *target = g_strdup (s); + + return TRUE; +} + +gboolean +ottie_parser_option_blend_mode (JsonReader *reader, + gsize offset, + gpointer data) +{ + GskBlendMode blend_mode; + gint64 i; + + i = json_reader_get_int_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + switch (i) + { + case 0: + blend_mode = GSK_BLEND_MODE_DEFAULT; + break; + + case 1: + blend_mode = GSK_BLEND_MODE_MULTIPLY; + break; + + case 2: + blend_mode = GSK_BLEND_MODE_SCREEN; + break; + + case 3: + blend_mode = GSK_BLEND_MODE_OVERLAY; + break; + + case 4: + blend_mode = GSK_BLEND_MODE_DARKEN; + break; + + case 5: + blend_mode = GSK_BLEND_MODE_LIGHTEN; + break; + + case 6: + blend_mode = GSK_BLEND_MODE_COLOR_DODGE; + break; + + case 7: + blend_mode = GSK_BLEND_MODE_COLOR_BURN; + break; + + case 8: + blend_mode = GSK_BLEND_MODE_HARD_LIGHT; + break; + + case 9: + blend_mode = GSK_BLEND_MODE_SOFT_LIGHT; + break; + + case 10: + blend_mode = GSK_BLEND_MODE_DIFFERENCE; + break; + + case 11: + blend_mode = GSK_BLEND_MODE_EXCLUSION; + break; + + case 12: + blend_mode = GSK_BLEND_MODE_HUE; + break; + + case 13: + blend_mode = GSK_BLEND_MODE_SATURATION; + break; + + case 14: + blend_mode = GSK_BLEND_MODE_COLOR; + break; + + case 15: + blend_mode = GSK_BLEND_MODE_LUMINOSITY; + break; + + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known blend mode", i); + return FALSE; + } + + if (blend_mode != GSK_BLEND_MODE_DEFAULT) + ottie_parser_error_value (reader, "Blend modes are not implemented yet."); + + *(GskBlendMode *) ((guint8 *) data + offset) = blend_mode; + + return TRUE; +} + +gboolean +ottie_parser_option_3d (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d; + + d = json_reader_get_double_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + if (d != 0) + { + ottie_parser_error_value (reader, "3D is not supported.\n"); + } + + return TRUE; +} + +gboolean +ottie_parser_option_line_cap (JsonReader *reader, + gsize offset, + gpointer data) +{ + GskLineCap line_cap; + gint64 i; + + i = json_reader_get_int_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + switch (i) + { + case 1: + line_cap = GSK_LINE_CAP_BUTT; + break; + + case 2: + line_cap = GSK_LINE_CAP_ROUND; + break; + + case 3: + line_cap = GSK_LINE_CAP_SQUARE; + break; + + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known line cap", i); + return FALSE; + } + + *(GskLineCap *) ((guint8 *) data + offset) = line_cap; + + return TRUE; +} + +gboolean +ottie_parser_option_line_join (JsonReader *reader, + gsize offset, + gpointer data) +{ + GskLineJoin line_join; + gint64 i; + + i = json_reader_get_int_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + switch (i) + { + case 1: + line_join = GSK_LINE_JOIN_MITER; + break; + + case 2: + line_join = GSK_LINE_JOIN_ROUND; + break; + + case 3: + line_join = GSK_LINE_JOIN_BEVEL; + break; + + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known line join", i); + return FALSE; + } + + *(GskLineJoin *) ((guint8 *) data + offset) = line_join; + + return TRUE; +} + +gboolean +ottie_parser_option_transform (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieShape **target; + OttieShape *t; + + t = ottie_transform_parse (reader); + if (t == NULL) + return FALSE; + + target = (OttieShape **) ((guint8 *) data + offset); + + g_clear_object (target); + *target = t; + + return TRUE; +} + diff --git a/ottie/ottieparserprivate.h b/ottie/ottieparserprivate.h new file mode 100644 index 0000000000..d0662fa045 --- /dev/null +++ b/ottie/ottieparserprivate.h @@ -0,0 +1,97 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PARSER_PRIVATE_H__ +#define __OTTIE_PARSER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _OttieParserOption OttieParserOption; + +typedef gboolean (* OttieParseFunc) (JsonReader *reader, gsize offset, gpointer data); + +struct _OttieParserOption +{ + const char *name; + OttieParseFunc parse_func; + gsize option_data; +}; + +void ottie_parser_emit_error (JsonReader *reader, + const GError *error); +void ottie_parser_error_syntax (JsonReader *reader, + const char *format, + ...) G_GNUC_PRINTF (2, 3); +void ottie_parser_error_value (JsonReader *reader, + const char *format, + ...) G_GNUC_PRINTF (2, 3); +void ottie_parser_error_unsupported (JsonReader *reader, + const char *format, + ...) G_GNUC_PRINTF (2, 3); + +gboolean ottie_parser_parse_array (JsonReader *reader, + const char *debug_name, + guint min_items, + guint max_items, + guint *out_n_items, + gsize start_offset, + gsize offset_multiplier, + OttieParseFunc func, + gpointer data); + +gboolean ottie_parser_parse_object (JsonReader *reader, + const char *debug_name, + const OttieParserOption *options, + gsize n_options, + gpointer data); +gboolean ottie_parser_option_skip (JsonReader *reader, + gsize offset, + gpointer data); +#define ottie_parser_option_skip_index ottie_parser_option_skip +#define ottie_parser_option_skip_expression ottie_parser_option_skip +gboolean ottie_parser_option_boolean (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_double (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_string (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_3d (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_blend_mode (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_line_cap (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_line_join (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_transform (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_PARSER_PRIVATE_H__ */ diff --git a/ottie/ottiepathshape.c b/ottie/ottiepathshape.c new file mode 100644 index 0000000000..12eebbd7c4 --- /dev/null +++ b/ottie/ottiepathshape.c @@ -0,0 +1,115 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepathshapeprivate.h" + +#include "ottiepathvalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include + +struct _OttiePathShape +{ + OttieShape parent; + + double direction; + OttiePathValue path; +}; + +struct _OttiePathShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttiePathShape, ottie_path_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_path_shape_snapshot (OttieShape *shape, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ + OttiePathShape *self = OTTIE_PATH_SHAPE (shape); + + ottie_shape_snapshot_add_path (snapshot_data, + ottie_path_value_get (&self->path, + timestamp, + self->direction)); +} + +static void +ottie_path_shape_dispose (GObject *object) +{ + OttiePathShape *self = OTTIE_PATH_SHAPE (object); + + ottie_path_value_clear (&self->path); + + G_OBJECT_CLASS (ottie_path_shape_parent_class)->dispose (object); +} + +static void +ottie_path_shape_finalize (GObject *object) +{ + //OttiePathShape *self = OTTIE_PATH_SHAPE (object); + + G_OBJECT_CLASS (ottie_path_shape_parent_class)->finalize (object); +} + +static void +ottie_path_shape_class_init (OttiePathShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->snapshot = ottie_path_shape_snapshot; + + gobject_class->finalize = ottie_path_shape_finalize; + gobject_class->dispose = ottie_path_shape_dispose; +} + +static void +ottie_path_shape_init (OttiePathShape *self) +{ + ottie_path_value_init (&self->path); +} + +OttieShape * +ottie_path_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE + { "d", ottie_parser_option_double, G_STRUCT_OFFSET (OttiePathShape, direction) }, + { "ks", ottie_path_value_parse, G_STRUCT_OFFSET (OttiePathShape, path) }, + }; + OttiePathShape *self; + + self = g_object_new (OTTIE_TYPE_PATH_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "path shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottiepathshapeprivate.h b/ottie/ottiepathshapeprivate.h new file mode 100644 index 0000000000..827e3cf3b1 --- /dev/null +++ b/ottie/ottiepathshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PATH_SHAPE_PRIVATE_H__ +#define __OTTIE_PATH_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PATH_SHAPE (ottie_path_shape_get_type ()) +#define OTTIE_PATH_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_PATH_SHAPE, OttiePathShape)) +#define OTTIE_PATH_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_PATH_SHAPE, OttiePathShapeClass)) +#define OTTIE_IS_PATH_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_PATH_SHAPE)) +#define OTTIE_IS_PATH_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_PATH_SHAPE)) +#define OTTIE_PATH_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_PATH_SHAPE, OttiePathShapeClass)) + +typedef struct _OttiePathShape OttiePathShape; +typedef struct _OttiePathShapeClass OttiePathShapeClass; + +GType ottie_path_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_path_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_PATH_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottiepathvalue.c b/ottie/ottiepathvalue.c new file mode 100644 index 0000000000..7099ea26c7 --- /dev/null +++ b/ottie/ottiepathvalue.c @@ -0,0 +1,402 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepathvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +typedef struct _OttieContour OttieContour; +typedef struct _OttieCurve OttieCurve; +typedef struct _OttiePath OttiePath; + +struct _OttieCurve { + double point[2]; + double in[2]; + double out[2]; +}; + +struct _OttieContour { + gboolean closed; + guint n_curves; + OttieCurve curves[0]; +}; + +struct _OttiePath { + guint ref_count; + guint n_contours; + OttieContour *contours[]; +}; + +static OttieContour * +ottie_contour_renew (OttieContour *path, + guint n_curves) +{ + OttieContour *self; + + self = g_realloc (path, sizeof (OttieContour) + n_curves * sizeof (OttieCurve)); + self->n_curves = n_curves; + + return self; +} + +static OttieContour * +ottie_contour_new (gboolean closed, + guint n_curves) +{ + OttieContour *self; + + self = g_malloc0 (sizeof (OttieContour) + n_curves * sizeof (OttieCurve)); + self->closed = closed; + self->n_curves = n_curves; + + return self; +} + +static void +ottie_contour_free (OttieContour *path) +{ + g_free (path); +} + +static gboolean +ottie_parse_value_parse_numbers (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_parser_parse_array (reader, "number", + 2, 2, NULL, + offset, sizeof (double), + ottie_parser_option_double, data); +} + +#define MAKE_OPEN_CONTOUR (NULL) +#define MAKE_CLOSED_CONTOUR GSIZE_TO_POINTER(1) +static gboolean +ottie_parse_value_parse_curve_array (JsonReader *reader, + gsize offset, + gpointer data) +{ + /* Attention: The offset value here is to the point in the curve, not + * to the target */ + OttieContour **target = (OttieContour **) data; + OttieContour *path = *target; + guint n_curves; + + n_curves = json_reader_count_elements (reader); + if (path == MAKE_OPEN_CONTOUR) + path = ottie_contour_new (FALSE, n_curves); + else if (path == MAKE_CLOSED_CONTOUR) + path = ottie_contour_new (TRUE, n_curves); + else if (n_curves < path->n_curves) + path = ottie_contour_renew (path, n_curves); + else if (n_curves > path->n_curves) + n_curves = path->n_curves; + + *target = path; + + return ottie_parser_parse_array (reader, "path array", + 0, path->n_curves, NULL, + offset, sizeof (OttieCurve), + ottie_parse_value_parse_numbers, &path->curves[0]); +} + +static gboolean +ottie_parser_value_parse_closed (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieContour **target = (OttieContour **) data; + gboolean b; + + b = json_reader_get_boolean_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + if (*target == MAKE_OPEN_CONTOUR || *target == MAKE_CLOSED_CONTOUR) + { + if (b) + *target = MAKE_CLOSED_CONTOUR; + else + *target = MAKE_OPEN_CONTOUR; + } + else + (*target)->closed = b; + + return TRUE; +} + +static gboolean +ottie_path_value_parse_contour (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieContour **target = (OttieContour **) ((guint8 *) data + offset); + OttieParserOption options[] = { + { "c", ottie_parser_value_parse_closed, 0 }, + { "i", ottie_parse_value_parse_curve_array, G_STRUCT_OFFSET (OttieCurve, in) }, + { "o", ottie_parse_value_parse_curve_array, G_STRUCT_OFFSET (OttieCurve, out) }, + { "v", ottie_parse_value_parse_curve_array, G_STRUCT_OFFSET (OttieCurve, point) }, + }; + + g_assert (*target == NULL); + *target = MAKE_CLOSED_CONTOUR; + + if (!ottie_parser_parse_object (reader, "contour", options, G_N_ELEMENTS (options), target)) + { + if (*target != MAKE_OPEN_CONTOUR && *target != MAKE_CLOSED_CONTOUR) + g_clear_pointer (target, ottie_contour_free); + *target = NULL; + return FALSE; + } + + if (*target == MAKE_OPEN_CONTOUR) + *target = ottie_contour_new (FALSE, 0); + else if (*target == MAKE_CLOSED_CONTOUR) + *target = ottie_contour_new (TRUE, 0); + + return TRUE; +} + +static OttiePath * +ottie_path_new (gsize n_contours) +{ + OttiePath *self; + + self = g_malloc0 (sizeof (OttiePath) + sizeof (OttieContour *) * n_contours); + self->ref_count = 1; + self->n_contours = n_contours; + + return self; +} + +static OttiePath * +ottie_path_ref (OttiePath *self) +{ + self->ref_count++; + + return self; +} + +static void +ottie_path_unref (OttiePath *self) +{ + self->ref_count--; + if (self->ref_count > 0) + return; + + for (guint i = 0; i < self->n_contours; i++) + { + g_clear_pointer (&self->contours[i], ottie_contour_free); + } + + g_free (self); +} + +static gboolean +ottie_path_value_parse_one (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePath **target = (OttiePath **) ((guint8 *) data + offset); + OttiePath *path; + + if (json_reader_is_array (reader)) + path = ottie_path_new (json_reader_count_elements (reader)); + else + path = ottie_path_new (1); + + if (!ottie_parser_parse_array (reader, "path", + path->n_contours, path->n_contours, NULL, + G_STRUCT_OFFSET (OttiePath, contours), + sizeof (OttieContour *), + ottie_path_value_parse_contour, path)) + { + ottie_path_unref (path); + return FALSE; + } + + g_clear_pointer (target, ottie_path_unref); + *target = path; + + return TRUE; +} + +static OttieContour * +ottie_contour_interpolate (const OttieContour *start, + const OttieContour *end, + double progress) +{ + OttieContour *self = ottie_contour_new (start->closed || end->closed, + MIN (start->n_curves, end->n_curves)); + + for (gsize i = 0; i < self->n_curves; i++) + { + self->curves[i].point[0] = start->curves[i].point[0] + progress * (end->curves[i].point[0] - start->curves[i].point[0]); + self->curves[i].point[1] = start->curves[i].point[1] + progress * (end->curves[i].point[1] - start->curves[i].point[1]); + self->curves[i].in[0] = start->curves[i].in[0] + progress * (end->curves[i].in[0] - start->curves[i].in[0]); + self->curves[i].in[1] = start->curves[i].in[1] + progress * (end->curves[i].in[1] - start->curves[i].in[1]); + self->curves[i].out[0] = start->curves[i].out[0] + progress * (end->curves[i].out[0] - start->curves[i].out[0]); + self->curves[i].out[1] = start->curves[i].out[1] + progress * (end->curves[i].out[1] - start->curves[i].out[1]); + } + + return self; +} + +static OttiePath * +ottie_path_interpolate (const OttiePath *start, + const OttiePath *end, + double progress) +{ + OttiePath *self = ottie_path_new (MIN (start->n_contours, end->n_contours)); + + for (gsize i = 0; i < self->n_contours; i++) + { + self->contours[i] = ottie_contour_interpolate (start->contours[i], + end->contours[i], + progress); + } + + return self; +} + +#define OTTIE_KEYFRAMES_NAME ottie_path_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttieContourKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE OttiePath * +#define OTTIE_KEYFRAMES_COPY_FUNC ottie_path_ref +#define OTTIE_KEYFRAMES_FREE_FUNC ottie_path_unref +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_path_value_parse_one +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_path_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_path_value_init (OttiePathValue *self) +{ + self->is_static = TRUE; + self->static_value = NULL; +} + +void +ottie_path_value_clear (OttiePathValue *self) +{ + if (self->is_static) + g_clear_pointer (&self->static_value, ottie_path_unref); + else + g_clear_pointer (&self->keyframes, ottie_path_keyframes_free); +} + +static GskPath * +ottie_path_build (OttiePath *self, + gboolean reverse) +{ + GskPathBuilder *builder; + + if (reverse) + g_warning ("FIXME: Make paths reversible"); + + builder = gsk_path_builder_new (); + for (gsize i = 0; i < self->n_contours; i++) + { + OttieContour *contour = self->contours[i]; + if (contour->n_curves == 0) + continue; + + gsk_path_builder_move_to (builder, + contour->curves[0].point[0], contour->curves[0].point[1]); + for (guint j = 1; j < contour->n_curves; j++) + { + gsk_path_builder_curve_to (builder, + contour->curves[j-1].point[0] + contour->curves[j-1].out[0], + contour->curves[j-1].point[1] + contour->curves[j-1].out[1], + contour->curves[j].point[0] + contour->curves[j].in[0], + contour->curves[j].point[1] + contour->curves[j].in[1], + contour->curves[j].point[0], + contour->curves[j].point[1]); + } + if (contour->closed) + { + gsk_path_builder_curve_to (builder, + contour->curves[contour->n_curves-1].point[0] + contour->curves[contour->n_curves-1].out[0], + contour->curves[contour->n_curves-1].point[1] + contour->curves[contour->n_curves-1].out[1], + contour->curves[0].point[0] + contour->curves[0].in[0], + contour->curves[0].point[1] + contour->curves[0].in[1], + contour->curves[0].point[0], + contour->curves[0].point[1]); + gsk_path_builder_close (builder); + } + } + + return gsk_path_builder_free_to_path (builder); +} + +GskPath * +ottie_path_value_get (OttiePathValue *self, + double timestamp, + gboolean reverse) +{ + if (self->is_static) + return ottie_path_build (self->static_value, reverse); + + return ottie_path_build (ottie_path_keyframes_get (self->keyframes, timestamp), reverse); +} + +gboolean +ottie_path_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePathValue *self = (OttiePathValue *) ((guint8 *) data + GPOINTER_TO_SIZE (offset)); + + if (json_reader_read_member (reader, "k")) + { + if (!json_reader_is_array (reader)) + { + if (!ottie_path_value_parse_one (reader, 0, &self->static_value)) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = TRUE; + } + else + { + self->keyframes = ottie_path_keyframes_parse (reader); + if (self->keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = FALSE; + } + } + else + { + ottie_parser_error_syntax (reader, "Property is not a path value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiepathvalueprivate.h b/ottie/ottiepathvalueprivate.h new file mode 100644 index 0000000000..a7035cc783 --- /dev/null +++ b/ottie/ottiepathvalueprivate.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PATH_VALUE_PRIVATE_H__ +#define __OTTIE_PATH_VALUE_PRIVATE_H__ + +#include + +#include + +G_BEGIN_DECLS + +typedef struct _OttiePathValue OttiePathValue; + +struct _OttiePathValue +{ + gboolean is_static; + union { + gpointer static_value; + gpointer keyframes; + }; +}; + +void ottie_path_value_init (OttiePathValue *self); +void ottie_path_value_clear (OttiePathValue *self); + +GskPath * ottie_path_value_get (OttiePathValue *self, + double timestamp, + gboolean reverse); + +gboolean ottie_path_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_PATH_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottieplayer.c b/ottie/ottieplayer.c new file mode 100644 index 0000000000..d41aa5e2a3 --- /dev/null +++ b/ottie/ottieplayer.c @@ -0,0 +1,490 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieplayer.h" + +#include "ottiecreation.h" +#include "ottiepaintable.h" + +#include + +struct _OttiePlayer +{ + GObject parent_instance; + + GFile *file; + + OttieCreation *creation; + OttiePaintable *paintable; + gint64 time_offset; + guint timer_cb; +}; + +struct _OttiePlayerClass +{ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_FILE, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +ottie_player_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + OttiePlayer *self = OTTIE_PLAYER (paintable); + + gdk_paintable_snapshot (GDK_PAINTABLE (self->paintable), snapshot, width, height); +} + +static int +ottie_player_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + OttiePlayer *self = OTTIE_PLAYER (paintable); + + return gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (self->paintable)); +} + +static int +ottie_player_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + OttiePlayer *self = OTTIE_PLAYER (paintable); + + return gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (self->paintable)); +} + +static void +ottie_player_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = ottie_player_paintable_snapshot; + iface->get_intrinsic_width = ottie_player_paintable_get_intrinsic_width; + iface->get_intrinsic_height = ottie_player_paintable_get_intrinsic_height; +} + +G_DEFINE_TYPE_EXTENDED (OttiePlayer, ottie_player, GTK_TYPE_MEDIA_STREAM, 0, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + ottie_player_paintable_init)) + +static gboolean +ottie_player_timer_cb (gpointer data) +{ + OttiePlayer *self = OTTIE_PLAYER (data); + gint64 timestamp; + + timestamp = g_get_monotonic_time () - self->time_offset; + if (timestamp > ottie_paintable_get_duration (self->paintable)) + { + if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (self))) + { + timestamp %= ottie_paintable_get_duration (self->paintable); + } + else + { + timestamp = ottie_paintable_get_duration (self->paintable); + gtk_media_stream_ended (GTK_MEDIA_STREAM (self)); + } + } + ottie_paintable_set_timestamp (self->paintable, timestamp); + gtk_media_stream_update (GTK_MEDIA_STREAM (self), timestamp); + + return G_SOURCE_CONTINUE; +} + +static gboolean +ottie_player_play (GtkMediaStream *stream) +{ + OttiePlayer *self = OTTIE_PLAYER (stream); + double frame_rate; + + frame_rate = ottie_creation_get_frame_rate (self->creation); + if (frame_rate <= 0) + return FALSE; + + self->time_offset = g_get_monotonic_time () - ottie_paintable_get_timestamp (self->paintable); + self->timer_cb = g_timeout_add (1000 / frame_rate, ottie_player_timer_cb, self); + + return TRUE; +} + +static void +ottie_player_pause (GtkMediaStream *stream) +{ + OttiePlayer *self = OTTIE_PLAYER (stream); + + g_clear_handle_id (&self->timer_cb, g_source_remove); +} + +static void +ottie_player_seek (GtkMediaStream *stream, + gint64 timestamp) +{ + OttiePlayer *self = OTTIE_PLAYER (stream); + + if (!ottie_creation_is_prepared (self->creation)) + gtk_media_stream_seek_failed (stream); + + ottie_paintable_set_timestamp (self->paintable, timestamp); + self->time_offset = g_get_monotonic_time () - timestamp; + + gtk_media_stream_seek_success (stream); + gtk_media_stream_update (stream, timestamp); +} + +static void +ottie_player_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + OttiePlayer *self = OTTIE_PLAYER (object); + + switch (prop_id) + { + case PROP_FILE: + ottie_player_set_file (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_player_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OttiePlayer *self = OTTIE_PLAYER (object); + + switch (prop_id) + { + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_player_dispose (GObject *object) +{ + OttiePlayer *self = OTTIE_PLAYER (object); + + if (self->paintable) + { + g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_contents, self); + g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_size, self); + g_clear_object (&self->paintable); + } + g_clear_object (&self->creation); + + G_OBJECT_CLASS (ottie_player_parent_class)->dispose (object); +} + +static void +ottie_player_class_init (OttiePlayerClass *klass) +{ + GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + stream_class->play = ottie_player_play; + stream_class->pause = ottie_player_pause; + stream_class->seek = ottie_player_seek; + + gobject_class->get_property = ottie_player_get_property; + gobject_class->set_property = ottie_player_set_property; + gobject_class->dispose = ottie_player_dispose; + + /** + * OttiePlayer:file + * + * The played file or %NULL. + */ + properties[PROP_FILE] = + g_param_spec_object ("file", + _("File"), + _("The played file"), + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +ottie_player_prepared_cb (OttieCreation *creation, + GParamSpec *pspec, + OttiePlayer *self) +{ + if (ottie_creation_is_prepared (creation)) + gtk_media_stream_prepared (GTK_MEDIA_STREAM (self), + FALSE, + TRUE, + TRUE, + ottie_paintable_get_duration (self->paintable)); + else + gtk_media_stream_unprepared (GTK_MEDIA_STREAM (self)); + + ottie_paintable_set_timestamp (self->paintable, 0); +} + +static void +ottie_player_init (OttiePlayer *self) +{ + self->creation = ottie_creation_new (); + g_signal_connect (self->creation, "notify::prepared", G_CALLBACK (ottie_player_prepared_cb), self); + self->paintable = ottie_paintable_new (self->creation); + g_signal_connect_swapped (self->paintable, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self); + g_signal_connect_swapped (self->paintable, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self); +} + +/** + * ottie_player_new: + * + * Creates a new Ottie player. + * + * Returns: (transfer full): a new #OttiePlayer + **/ +OttiePlayer * +ottie_player_new (void) +{ + return g_object_new (OTTIE_TYPE_PLAYER, NULL); +} + +/** + * ottie_player_new_for_file: + * @file: (nullable): a #GFile + * + * Creates a new #OttiePlayer playing the given @file. If the file + * isn’t found or can’t be loaded, the resulting #OttiePlayer be empty. + * + * Returns: a new #OttiePlayer + **/ +OttiePlayer* +ottie_player_new_for_file (GFile *file) +{ + g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); + + return g_object_new (OTTIE_TYPE_PLAYER, + "file", file, + NULL); +} + +/** + * ottie_player_new_for_filename: + * @filename: (type filename) (nullable): a filename + * + * Creates a new #OttiePlayer displaying the file @filename. + * + * This is a utility function that calls ottie_player_new_for_file(). + * See that function for details. + * + * Returns: a new #OttiePlayer + **/ +OttiePlayer* +ottie_player_new_for_filename (const char *filename) +{ + OttiePlayer *result; + GFile *file; + + if (filename) + file = g_file_new_for_path (filename); + else + file = NULL; + + result = ottie_player_new_for_file (file); + + if (file) + g_object_unref (file); + + return result; +} + +/** + * ottie_player_new_for_resource: + * @resource_path: (nullable): resource path to play back + * + * Creates a new #OttiePlayer displaying the file @resource_path. + * + * This is a utility function that calls ottie_player_new_for_file(). + * See that function for details. + * + * Returns: a new #OttiePlayer + **/ +OttiePlayer * +ottie_player_new_for_resource (const char *resource_path) +{ + OttiePlayer *result; + GFile *file; + + if (resource_path) + { + char *uri, *escaped; + + escaped = g_uri_escape_string (resource_path, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + uri = g_strconcat ("resource://", escaped, NULL); + g_free (escaped); + + file = g_file_new_for_uri (uri); + g_free (uri); + } + else + { + file = NULL; + } + + result = ottie_player_new_for_file (file); + + if (file) + g_object_unref (file); + + return result; +} + +/** + * ottie_player_set_file: + * @self: a #OttiePlayer + * @file: (nullable): a %GFile or %NULL + * + * Makes @self load and display @file. + * + * See ottie_player_new_for_file() for details. + **/ +void +ottie_player_set_file (OttiePlayer *self, + GFile *file) +{ + g_return_if_fail (OTTIE_IS_PLAYER (self)); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + if (self->file == file) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + g_set_object (&self->file, file); + ottie_creation_load_file (self->creation, file); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * ottie_player_get_file: + * @self: a #OttiePlayer + * + * Gets the #GFile currently displayed if @self is displaying a file. + * If @self is not displaying a file, then %NULL is returned. + * + * Returns: (nullable) (transfer none): The #GFile displayed by @self. + **/ +GFile * +ottie_player_get_file (OttiePlayer *self) +{ + g_return_val_if_fail (OTTIE_IS_PLAYER (self), FALSE); + + return self->file; +} + +/** + * ottie_player_set_filename: + * @self: a #OttiePlayer + * @filename: (nullable): the filename to play + * + * Makes @self load and display the given @filename. + * + * This is a utility function that calls ottie_player_set_file(). + **/ +void +ottie_player_set_filename (OttiePlayer *self, + const char *filename) +{ + GFile *file; + + g_return_if_fail (OTTIE_IS_PLAYER (self)); + + if (filename) + file = g_file_new_for_path (filename); + else + file = NULL; + + ottie_player_set_file (self, file); + + if (file) + g_object_unref (file); +} + +/** + * ottie_player_set_resource: + * @self: a #OttiePlayer + * @resource_path: (nullable): the resource to set + * + * Makes @self load and display the resource at the given + * @resource_path. + * + * This is a utility function that calls ottie_player_set_file(), + **/ +void +ottie_player_set_resource (OttiePlayer *self, + const char *resource_path) +{ + GFile *file; + + g_return_if_fail (OTTIE_IS_PLAYER (self)); + + if (resource_path) + { + char *uri, *escaped; + + escaped = g_uri_escape_string (resource_path, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + uri = g_strconcat ("resource://", escaped, NULL); + g_free (escaped); + + file = g_file_new_for_uri (uri); + g_free (uri); + } + else + { + file = NULL; + } + + ottie_player_set_file (self, file); + + if (file) + g_object_unref (file); +} + diff --git a/ottie/ottieplayer.h b/ottie/ottieplayer.h new file mode 100644 index 0000000000..10891523d6 --- /dev/null +++ b/ottie/ottieplayer.h @@ -0,0 +1,59 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PLAYER_H__ +#define __OTTIE_PLAYER_H__ + +#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PLAYER (ottie_player_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (OttiePlayer, ottie_player, OTTIE, PLAYER, GtkMediaStream) + +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new (void); +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new_for_file (GFile *file); +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new_for_filename (const char *filename); +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new_for_resource (const char *resource_path); + +GDK_AVAILABLE_IN_ALL +void ottie_player_set_file (OttiePlayer *self, + GFile *file); +GDK_AVAILABLE_IN_ALL +GFile * ottie_player_get_file (OttiePlayer *self); +GDK_AVAILABLE_IN_ALL +void ottie_player_set_filename (OttiePlayer *self, + const char *filename); +GDK_AVAILABLE_IN_ALL +void ottie_player_set_resource (OttiePlayer *self, + const char *resource_path); + +G_END_DECLS + +#endif /* __OTTIE_PLAYER_H__ */ diff --git a/ottie/ottiepoint3dvalue.c b/ottie/ottiepoint3dvalue.c new file mode 100644 index 0000000000..37ee149e90 --- /dev/null +++ b/ottie/ottiepoint3dvalue.c @@ -0,0 +1,154 @@ +/** + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepoint3dvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_point3d_value_parse_value (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d[3]; + guint n_items; + + if (!ottie_parser_parse_array (reader, "point", + 2, 3, &n_items, + 0, sizeof (double), + ottie_parser_option_double, + &d)) + return FALSE; + + if (n_items == 2) + d[2] = NAN; /* We do fixup below */ + + *(graphene_point3d_t *) ((guint8 *) data + offset) = GRAPHENE_POINT3D_INIT (d[0], d[1], d[2]); + return TRUE; +} + +#define OTTIE_KEYFRAMES_NAME ottie_point_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttiePointKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE graphene_point3d_t +#define OTTIE_KEYFRAMES_BY_VALUE 1 +#define OTTIE_KEYFRAMES_DIMENSIONS 3 +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_point3d_value_parse_value +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC graphene_point3d_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_point3d_value_init (OttiePoint3DValue *self, + const graphene_point3d_t *value) +{ + self->is_static = TRUE; + self->static_value = *value; +} + +void +ottie_point3d_value_clear (OttiePoint3DValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_point_keyframes_free); +} + +void +ottie_point3d_value_get (OttiePoint3DValue *self, + double timestamp, + graphene_point3d_t *value) +{ + if (self->is_static) + { + *value = self->static_value; + return; + } + + ottie_point_keyframes_get (self->keyframes, timestamp, value); +} + +gboolean +ottie_point3d_value_parse (JsonReader *reader, + float default_value, + gsize offset, + gpointer data) +{ + OttiePoint3DValue *self = (OttiePoint3DValue *) ((guint8 *) data + offset); + + if (json_reader_read_member (reader, "k")) + { + gboolean is_static; + + if (!json_reader_is_array (reader)) + { + ottie_parser_error_syntax (reader, "Point value needs an array for its value"); + return FALSE; + } + + if (!json_reader_read_element (reader, 0)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + json_reader_end_element (reader); + return FALSE; + } + + is_static = !json_reader_is_object (reader); + json_reader_end_element (reader); + + if (is_static) + { + if (!ottie_point3d_value_parse_value (reader, 0, &self->static_value)) + { + json_reader_end_member (reader); + return FALSE; + } + if (isnan (self->static_value.z)) + self->static_value.z = default_value; + self->is_static = TRUE; + } + else + { + OttiePointKeyframes *keyframes = ottie_point_keyframes_parse (reader); + if (keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + for (int i = 0; i < keyframes->n_items; i++) + { + if (isnan (keyframes->items[i].start_value.z)) + keyframes->items[i].start_value.z = default_value; + if (isnan (keyframes->items[i].end_value.z)) + keyframes->items[i].end_value.z = default_value; + } + self->is_static = FALSE; + self->keyframes = keyframes; + } + } + else + { + ottie_parser_error_syntax (reader, "Point value has no value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiepoint3dvalueprivate.h b/ottie/ottiepoint3dvalueprivate.h new file mode 100644 index 0000000000..f735f06b95 --- /dev/null +++ b/ottie/ottiepoint3dvalueprivate.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_POINT3D_VALUE_PRIVATE_H__ +#define __OTTIE_POINT3D_VALUE_PRIVATE_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _OttiePoint3DValue OttiePoint3DValue; + +struct _OttiePoint3DValue +{ + gboolean is_static; + union { + graphene_point3d_t static_value; + gpointer keyframes; + }; +}; + +void ottie_point3d_value_init (OttiePoint3DValue *self, + const graphene_point3d_t *value); +void ottie_point3d_value_clear (OttiePoint3DValue *self); + +void ottie_point3d_value_get (OttiePoint3DValue *self, + double timestamp, + graphene_point3d_t *value); + +gboolean ottie_point3d_value_parse (JsonReader *reader, + float default_value, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_POINT3D_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottiepointvalue.c b/ottie/ottiepointvalue.c new file mode 100644 index 0000000000..875c4c1a24 --- /dev/null +++ b/ottie/ottiepointvalue.c @@ -0,0 +1,140 @@ +/** + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepointvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_point_value_parse_value (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d[2]; + + if (!ottie_parser_parse_array (reader, "point", + 2, 2, NULL, + 0, sizeof (double), + ottie_parser_option_double, + &d)) + return FALSE; + + *(graphene_point_t *) ((guint8 *) data + offset) = GRAPHENE_POINT_INIT (d[0], d[1]); + return TRUE; +} + +#define OTTIE_KEYFRAMES_NAME ottie_point_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttiePointKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE graphene_point_t +#define OTTIE_KEYFRAMES_BY_VALUE 1 +#define OTTIE_KEYFRAMES_DIMENSIONS 2 +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_point_value_parse_value +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC graphene_point_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_point_value_init (OttiePointValue *self, + const graphene_point_t *value) +{ + self->is_static = TRUE; + self->static_value = *value; +} + +void +ottie_point_value_clear (OttiePointValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_point_keyframes_free); +} + +void +ottie_point_value_get (OttiePointValue *self, + double timestamp, + graphene_point_t *value) +{ + if (self->is_static) + { + *value = self->static_value; + return; + } + + ottie_point_keyframes_get (self->keyframes, timestamp, value); +} + +gboolean +ottie_point_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePointValue *self = (OttiePointValue *) ((guint8 *) data + offset); + + if (json_reader_read_member (reader, "k")) + { + gboolean is_static; + + if (!json_reader_is_array (reader)) + { + ottie_parser_error_syntax (reader, "Point value needs an array for its value"); + return FALSE; + } + + if (!json_reader_read_element (reader, 0)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + json_reader_end_element (reader); + return FALSE; + } + + is_static = !json_reader_is_object (reader); + json_reader_end_element (reader); + + if (is_static) + { + if (!ottie_point_value_parse_value (reader, 0, &self->static_value)) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = TRUE; + } + else + { + OttiePointKeyframes *keyframes = ottie_point_keyframes_parse (reader); + if (keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = FALSE; + self->keyframes = keyframes; + } + } + else + { + ottie_parser_error_syntax (reader, "Point value has no value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiepointvalueprivate.h b/ottie/ottiepointvalueprivate.h new file mode 100644 index 0000000000..635fa78d6d --- /dev/null +++ b/ottie/ottiepointvalueprivate.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_POINT_VALUE_PRIVATE_H__ +#define __OTTIE_POINT_VALUE_PRIVATE_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _OttiePointValue OttiePointValue; + +struct _OttiePointValue +{ + gboolean is_static; + union { + graphene_point_t static_value; + gpointer keyframes; + }; +}; + +void ottie_point_value_init (OttiePointValue *self, + const graphene_point_t *value); +void ottie_point_value_clear (OttiePointValue *self); + +void ottie_point_value_get (OttiePointValue *self, + double timestamp, + graphene_point_t *value); + +gboolean ottie_point_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_POINT_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottieprecomp.c b/ottie/ottieprecomp.c new file mode 100644 index 0000000000..8906fd1791 --- /dev/null +++ b/ottie/ottieprecomp.c @@ -0,0 +1,177 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieprecompprivate.h" + +#include "ottieparserprivate.h" +#include "ottieprecomplayerprivate.h" +#include "ottieshapelayerprivate.h" + +#include +#include + +#define GDK_ARRAY_ELEMENT_TYPE OttieLayer * +#define GDK_ARRAY_FREE_FUNC g_object_unref +#define GDK_ARRAY_TYPE_NAME OttieLayerList +#define GDK_ARRAY_NAME ottie_layer_list +#define GDK_ARRAY_PREALLOC 4 +#include "gdk/gdkarrayimpl.c" + +struct _OttiePrecomp +{ + OttieLayer parent; + + OttieLayerList layers; +}; + +struct _OttiePrecompClass +{ + OttieLayerClass parent_class; +}; + +G_DEFINE_TYPE (OttiePrecomp, ottie_precomp, OTTIE_TYPE_LAYER) + +static void +ottie_precomp_snapshot (OttieLayer *layer, + GtkSnapshot *snapshot, + double timestamp) +{ + OttiePrecomp *self = OTTIE_PRECOMP (layer); + + for (gsize i = ottie_layer_list_get_size (&self->layers); i-- > 0; ) + { + ottie_layer_snapshot (ottie_layer_list_get (&self->layers, i), snapshot, timestamp); + } +} + +static void +ottie_precomp_dispose (GObject *object) +{ + OttiePrecomp *self = OTTIE_PRECOMP (object); + + ottie_layer_list_clear (&self->layers); + + G_OBJECT_CLASS (ottie_precomp_parent_class)->dispose (object); +} + +static void +ottie_precomp_finalize (GObject *object) +{ + //OttiePrecomp *self = OTTIE_PRECOMP (object); + + G_OBJECT_CLASS (ottie_precomp_parent_class)->finalize (object); +} + +static void +ottie_precomp_class_init (OttiePrecompClass *klass) +{ + OttieLayerClass *layer_class = OTTIE_LAYER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + layer_class->snapshot = ottie_precomp_snapshot; + + gobject_class->finalize = ottie_precomp_finalize; + gobject_class->dispose = ottie_precomp_dispose; +} + +static void +ottie_precomp_init (OttiePrecomp *self) +{ + ottie_layer_list_init (&self->layers); +} + +static gboolean +ottie_precomp_parse_layer (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePrecomp *self = data; + OttieLayer *layer; + int type; + + if (!json_reader_is_object (reader)) + { + ottie_parser_error_syntax (reader, "Layer %zu is not an object", + ottie_layer_list_get_size (&self->layers)); + return FALSE; + } + + if (!json_reader_read_member (reader, "ty")) + { + ottie_parser_error_syntax (reader, "Layer %zu has no type", + ottie_layer_list_get_size (&self->layers)); + json_reader_end_member (reader); + return FALSE; + } + + type = json_reader_get_int_value (reader); + json_reader_end_member (reader); + + switch (type) + { + case 0: + layer = ottie_precomp_layer_parse (reader); + break; + + case 4: + layer = ottie_shape_layer_parse (reader); + break; + + default: + ottie_parser_error_value (reader, "Layer %zu has unknown type %d", + ottie_layer_list_get_size (&self->layers), + type); + layer = NULL; + break; + } + + if (layer) + ottie_layer_list_append (&self->layers, layer); + + return TRUE; +} + +gboolean +ottie_precomp_parse_layers (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePrecomp **target = (OttiePrecomp **) ((guint8 *) data + offset); + OttiePrecomp *self; + + self = g_object_new (OTTIE_TYPE_PRECOMP, NULL); + + if (!ottie_parser_parse_array (reader, "layers", + 0, G_MAXUINT, NULL, + 0, 0, + ottie_precomp_parse_layer, + self)) + { + g_object_unref (self); + return FALSE; + } + + g_clear_object (target); + *target = self; + + return TRUE; +} + diff --git a/ottie/ottieprecomplayer.c b/ottie/ottieprecomplayer.c new file mode 100644 index 0000000000..7c04de01ea --- /dev/null +++ b/ottie/ottieprecomplayer.c @@ -0,0 +1,119 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieprecomplayerprivate.h" + +#include "ottiedoublevalueprivate.h" + +#include +#include + +struct _OttiePrecompLayer +{ + OttieLayer parent; + + OttieDoubleValue time_map; + + char *ref_id; + OttieLayer *ref; +}; + +struct _OttiePrecompLayerClass +{ + OttieLayerClass parent_class; +}; + +G_DEFINE_TYPE (OttiePrecompLayer, ottie_precomp_layer, OTTIE_TYPE_LAYER) + +static void +ottie_precomp_layer_snapshot (OttieLayer *layer, + GtkSnapshot *snapshot, + double timestamp) +{ + OttiePrecompLayer *self = OTTIE_PRECOMP_LAYER (layer); + + if (self->ref == NULL) + return; + + ottie_layer_snapshot (self->ref, + snapshot, + ottie_double_value_get (&self->time_map, timestamp)); +} + +static void +ottie_precomp_layer_dispose (GObject *object) +{ + OttiePrecompLayer *self = OTTIE_PRECOMP_LAYER (object); + + g_clear_object (&self->ref); + g_clear_pointer (&self->ref_id, g_free); + ottie_double_value_clear (&self->time_map); + + G_OBJECT_CLASS (ottie_precomp_layer_parent_class)->dispose (object); +} + +static void +ottie_precomp_layer_finalize (GObject *object) +{ + //OttiePrecompLayer *self = OTTIE_PRECOMP_LAYER (object); + + G_OBJECT_CLASS (ottie_precomp_layer_parent_class)->finalize (object); +} + +static void +ottie_precomp_layer_class_init (OttiePrecompLayerClass *klass) +{ + OttieLayerClass *layer_class = OTTIE_LAYER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + layer_class->snapshot = ottie_precomp_layer_snapshot; + + gobject_class->finalize = ottie_precomp_layer_finalize; + gobject_class->dispose = ottie_precomp_layer_dispose; +} + +static void +ottie_precomp_layer_init (OttiePrecompLayer *self) +{ + ottie_double_value_init (&self->time_map, 0); +} + +OttieLayer * +ottie_precomp_layer_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_LAYER + { "refId", ottie_parser_option_string, G_STRUCT_OFFSET (OttiePrecompLayer, ref_id) }, + { "tm", ottie_double_value_parse, 0 }, + }; + OttiePrecompLayer *self; + + self = g_object_new (OTTIE_TYPE_PRECOMP_LAYER, NULL); + + if (!ottie_parser_parse_object (reader, "precomp layer", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_LAYER (self); +} + diff --git a/ottie/ottieprecomplayerprivate.h b/ottie/ottieprecomplayerprivate.h new file mode 100644 index 0000000000..cf084c109f --- /dev/null +++ b/ottie/ottieprecomplayerprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PRECOMP_LAYER_PRIVATE_H__ +#define __OTTIE_PRECOMP_LAYER_PRIVATE_H__ + +#include "ottielayerprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PRECOMP_LAYER (ottie_precomp_layer_get_type ()) +#define OTTIE_PRECOMP_LAYER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_PRECOMP_LAYER, OttiePrecompLayer)) +#define OTTIE_PRECOMP_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_PRECOMP_LAYER, OttiePrecompLayerClass)) +#define OTTIE_IS_PRECOMP_LAYER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_PRECOMP_LAYER)) +#define OTTIE_IS_PRECOMP_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_PRECOMP_LAYER)) +#define OTTIE_PRECOMP_LAYER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_PRECOMP_LAYER, OttiePrecompLayerClass)) + +typedef struct _OttiePrecompLayer OttiePrecompLayer; +typedef struct _OttiePrecompLayerClass OttiePrecompLayerClass; + +GType ottie_precomp_layer_get_type (void) G_GNUC_CONST; + +OttieLayer * ottie_precomp_layer_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_PRECOMP_LAYER_PRIVATE_H__ */ diff --git a/ottie/ottieprecompprivate.h b/ottie/ottieprecompprivate.h new file mode 100644 index 0000000000..08678ff8a2 --- /dev/null +++ b/ottie/ottieprecompprivate.h @@ -0,0 +1,47 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PRECOMP_PRIVATE_H__ +#define __OTTIE_PRECOMP_PRIVATE_H__ + +#include "ottielayerprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PRECOMP (ottie_precomp_get_type ()) +#define OTTIE_PRECOMP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_PRECOMP, OttiePrecomp)) +#define OTTIE_PRECOMP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_PRECOMP, OttiePrecompClass)) +#define OTTIE_IS_PRECOMP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_PRECOMP)) +#define OTTIE_IS_PRECOMP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_PRECOMP)) +#define OTTIE_PRECOMP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_PRECOMP, OttiePrecompClass)) + +typedef struct _OttiePrecomp OttiePrecomp; +typedef struct _OttiePrecompClass OttiePrecompClass; + +GType ottie_precomp_get_type (void) G_GNUC_CONST; + +gboolean ottie_precomp_parse_layers (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_PRECOMP_PRIVATE_H__ */ diff --git a/ottie/ottierectshape.c b/ottie/ottierectshape.c new file mode 100644 index 0000000000..1a58d59199 --- /dev/null +++ b/ottie/ottierectshape.c @@ -0,0 +1,137 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottierectshapeprivate.h" + +#include "ottiedoublevalueprivate.h" +#include "ottiepointvalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include + +struct _OttieRectShape +{ + OttieShape parent; + + double direction; + OttiePointValue position; + OttiePointValue size; + OttieDoubleValue rounded; +}; + +struct _OttieRectShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieRectShape, ottie_rect_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_rect_shape_snapshot (OttieShape *shape, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ + OttieRectShape *self = OTTIE_RECT_SHAPE (shape); + graphene_point_t p, s; + double r; + graphene_rect_t rect; + GskPathBuilder *builder; + + ottie_point_value_get (&self->position, timestamp, &p); + ottie_point_value_get (&self->size, timestamp, &s); + r = ottie_double_value_get (&self->rounded, timestamp); + + builder = gsk_path_builder_new (); + rect = GRAPHENE_RECT_INIT (p.x - s.x / 2, + p.y - s.y / 2, + s.x, s.y); + if (r <= 0) + { + gsk_path_builder_add_rect (builder, &rect); + } + else + { + GskRoundedRect rounded; + + gsk_rounded_rect_init_from_rect (&rounded, &rect, r); + gsk_path_builder_add_rounded_rect (builder, &rounded); + } + + ottie_shape_snapshot_add_path (snapshot_data, + gsk_path_builder_free_to_path (builder)); +} + +static void +ottie_rect_shape_dispose (GObject *object) +{ + OttieRectShape *self = OTTIE_RECT_SHAPE (object); + + ottie_point_value_clear (&self->position); + ottie_point_value_clear (&self->size); + ottie_double_value_clear (&self->rounded); + + G_OBJECT_CLASS (ottie_rect_shape_parent_class)->dispose (object); +} + +static void +ottie_rect_shape_class_init (OttieRectShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->snapshot = ottie_rect_shape_snapshot; + + gobject_class->dispose = ottie_rect_shape_dispose; +} + +static void +ottie_rect_shape_init (OttieRectShape *self) +{ + ottie_point_value_init (&self->position, &GRAPHENE_POINT_INIT (0, 0)); + ottie_point_value_init (&self->size, &GRAPHENE_POINT_INIT (0, 0)); + ottie_double_value_init (&self->rounded, 0); +} + +OttieShape * +ottie_rect_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE + { "d", ottie_parser_option_double, G_STRUCT_OFFSET (OttieRectShape, direction) }, + { "p", ottie_point_value_parse, G_STRUCT_OFFSET (OttieRectShape, position) }, + { "s", ottie_point_value_parse, G_STRUCT_OFFSET (OttieRectShape, size) }, + { "r", ottie_double_value_parse, G_STRUCT_OFFSET (OttieRectShape, rounded) }, + }; + OttieRectShape *self; + + self = g_object_new (OTTIE_TYPE_RECT_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "rect shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottierectshapeprivate.h b/ottie/ottierectshapeprivate.h new file mode 100644 index 0000000000..75875ccf6d --- /dev/null +++ b/ottie/ottierectshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_RECT_SHAPE_PRIVATE_H__ +#define __OTTIE_RECT_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_RECT_SHAPE (ottie_rect_shape_get_type ()) +#define OTTIE_RECT_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_RECT_SHAPE, OttieRectShape)) +#define OTTIE_RECT_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_RECT_SHAPE, OttieRectShapeClass)) +#define OTTIE_IS_RECT_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_RECT_SHAPE)) +#define OTTIE_IS_RECT_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_RECT_SHAPE)) +#define OTTIE_RECT_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_RECT_SHAPE, OttieRectShapeClass)) + +typedef struct _OttieRectShape OttieRectShape; +typedef struct _OttieRectShapeClass OttieRectShapeClass; + +GType ottie_rect_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_rect_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_RECT_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottieshape.c b/ottie/ottieshape.c new file mode 100644 index 0000000000..c730669d28 --- /dev/null +++ b/ottie/ottieshape.c @@ -0,0 +1,123 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieshapeprivate.h" + +#include + +G_DEFINE_TYPE (OttieShape, ottie_shape, G_TYPE_OBJECT) + +static void +ottie_shape_dispose (GObject *object) +{ + OttieShape *self = OTTIE_SHAPE (object); + + g_clear_pointer (&self->name, g_free); + g_clear_pointer (&self->match_name, g_free); + + G_OBJECT_CLASS (ottie_shape_parent_class)->dispose (object); +} + +static void +ottie_shape_finalize (GObject *object) +{ + //OttieShape *self = OTTIE_SHAPE (object); + + G_OBJECT_CLASS (ottie_shape_parent_class)->finalize (object); +} + +static void +ottie_shape_class_init (OttieShapeClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = ottie_shape_finalize; + gobject_class->dispose = ottie_shape_dispose; +} + +static void +ottie_shape_init (OttieShape *self) +{ +} + +void +ottie_shape_snapshot (OttieShape *self, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ + OTTIE_SHAPE_GET_CLASS (self)->snapshot (self, snapshot, snapshot_data, timestamp); +} + +void +ottie_shape_snapshot_init (OttieShapeSnapshot *data, + OttieShapeSnapshot *copy_from) +{ + if (copy_from) + { + data->paths = g_slist_copy_deep (copy_from->paths, (GCopyFunc) gsk_path_ref, NULL); + if (copy_from->cached_path) + data->cached_path = gsk_path_ref (copy_from->cached_path); + else + data->cached_path = NULL; + } + else + { + memset (data, 0, sizeof (OttieShapeSnapshot)); + } +} + +void +ottie_shape_snapshot_clear (OttieShapeSnapshot *data) +{ + g_slist_free_full (data->paths, (GDestroyNotify) gsk_path_unref); + data->paths = NULL; + + g_clear_pointer (&data->cached_path, gsk_path_unref); +} + +void +ottie_shape_snapshot_add_path (OttieShapeSnapshot *data, + GskPath *path) +{ + g_clear_pointer (&data->cached_path, gsk_path_unref); + data->paths = g_slist_prepend (data->paths, path); +} + +GskPath * +ottie_shape_snapshot_get_path (OttieShapeSnapshot *data) +{ + GskPathBuilder *builder; + GSList *l; + + if (data->cached_path) + return data->cached_path; + + builder = gsk_path_builder_new (); + for (l = data->paths; l; l = l->next) + { + gsk_path_builder_add_path (builder, l->data); + } + data->cached_path = gsk_path_builder_free_to_path (builder); + + return data->cached_path; +} + diff --git a/ottie/ottieshapelayer.c b/ottie/ottieshapelayer.c new file mode 100644 index 0000000000..b0376cf055 --- /dev/null +++ b/ottie/ottieshapelayer.c @@ -0,0 +1,132 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieshapelayerprivate.h" + +#include "ottiefillshapeprivate.h" +#include "ottiegroupshapeprivate.h" +#include "ottieparserprivate.h" +#include "ottiepathshapeprivate.h" +#include "ottieshapeprivate.h" +#include "ottiestrokeshapeprivate.h" +#include "ottietransformprivate.h" + +#include +#include + +struct _OttieShapeLayer +{ + OttieLayer parent; + + OttieShape *shapes; +}; + +struct _OttieShapeLayerClass +{ + OttieLayerClass parent_class; +}; + +G_DEFINE_TYPE (OttieShapeLayer, ottie_shape_layer, OTTIE_TYPE_LAYER) + +static void +ottie_shape_layer_snapshot (OttieLayer *layer, + GtkSnapshot *snapshot, + double timestamp) +{ + OttieShapeLayer *self = OTTIE_SHAPE_LAYER (layer); + OttieShapeSnapshot snapshot_data; + + ottie_shape_snapshot_init (&snapshot_data, NULL); + + ottie_shape_snapshot (self->shapes, + snapshot, + &snapshot_data, + timestamp); + + ottie_shape_snapshot_clear (&snapshot_data); +} + +static void +ottie_shape_layer_dispose (GObject *object) +{ + OttieShapeLayer *self = OTTIE_SHAPE_LAYER (object); + + g_clear_object (&self->shapes); + + G_OBJECT_CLASS (ottie_shape_layer_parent_class)->dispose (object); +} + +static void +ottie_shape_layer_finalize (GObject *object) +{ + //OttieShapeLayer *self = OTTIE_SHAPE_LAYER (object); + + G_OBJECT_CLASS (ottie_shape_layer_parent_class)->finalize (object); +} + +static void +ottie_shape_layer_class_init (OttieShapeLayerClass *klass) +{ + OttieLayerClass *layer_class = OTTIE_LAYER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + layer_class->snapshot = ottie_shape_layer_snapshot; + + gobject_class->finalize = ottie_shape_layer_finalize; + gobject_class->dispose = ottie_shape_layer_dispose; +} + +static void +ottie_shape_layer_init (OttieShapeLayer *self) +{ + self->shapes = ottie_group_shape_new (); +} + +static gboolean +ottie_shape_layer_parse_shapes (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieShapeLayer *self = data; + + return ottie_group_shape_parse_shapes (reader, 0, self->shapes); +} + +OttieLayer * +ottie_shape_layer_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_LAYER + { "shapes", ottie_shape_layer_parse_shapes, 0 }, + }; + OttieShapeLayer *self; + + self = g_object_new (OTTIE_TYPE_SHAPE_LAYER, NULL); + + if (!ottie_parser_parse_object (reader, "shape layer", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_LAYER (self); +} + diff --git a/ottie/ottieshapelayerprivate.h b/ottie/ottieshapelayerprivate.h new file mode 100644 index 0000000000..4487f5fc1f --- /dev/null +++ b/ottie/ottieshapelayerprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_SHAPE_LAYER_PRIVATE_H__ +#define __OTTIE_SHAPE_LAYER_PRIVATE_H__ + +#include "ottielayerprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_SHAPE_LAYER (ottie_shape_layer_get_type ()) +#define OTTIE_SHAPE_LAYER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_SHAPE_LAYER, OttieShapeLayer)) +#define OTTIE_SHAPE_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_SHAPE_LAYER, OttieShapeLayerClass)) +#define OTTIE_IS_SHAPE_LAYER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_SHAPE_LAYER)) +#define OTTIE_IS_SHAPE_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_SHAPE_LAYER)) +#define OTTIE_SHAPE_LAYER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_SHAPE_LAYER, OttieShapeLayerClass)) + +typedef struct _OttieShapeLayer OttieShapeLayer; +typedef struct _OttieShapeLayerClass OttieShapeLayerClass; + +GType ottie_shape_layer_get_type (void) G_GNUC_CONST; + +OttieLayer * ottie_shape_layer_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_SHAPE_LAYER_PRIVATE_H__ */ diff --git a/ottie/ottieshapeprivate.h b/ottie/ottieshapeprivate.h new file mode 100644 index 0000000000..dc522834c5 --- /dev/null +++ b/ottie/ottieshapeprivate.h @@ -0,0 +1,87 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_SHAPE_PRIVATE_H__ +#define __OTTIE_SHAPE_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_SHAPE (ottie_shape_get_type ()) +#define OTTIE_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_SHAPE, OttieShape)) +#define OTTIE_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_SHAPE, OttieShapeClass)) +#define OTTIE_IS_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_SHAPE)) +#define OTTIE_IS_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_SHAPE)) +#define OTTIE_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_SHAPE, OttieShapeClass)) + +typedef struct _OttieShapeSnapshot OttieShapeSnapshot; +typedef struct _OttieShape OttieShape; +typedef struct _OttieShapeClass OttieShapeClass; + +struct _OttieShapeSnapshot +{ + GSList *paths; + GskPath *cached_path; +}; + +struct _OttieShape +{ + GObject parent; + + char *name; + char *match_name; + gboolean hidden; +}; + +struct _OttieShapeClass +{ + GObjectClass parent_class; + + void (* snapshot) (OttieShape *self, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp); +}; + +GType ottie_shape_get_type (void) G_GNUC_CONST; + +void ottie_shape_snapshot (OttieShape *self, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp); + + +void ottie_shape_snapshot_init (OttieShapeSnapshot *data, + OttieShapeSnapshot *copy_from); +void ottie_shape_snapshot_clear (OttieShapeSnapshot *data); +void ottie_shape_snapshot_add_path (OttieShapeSnapshot *data, + GskPath *path); +GskPath * ottie_shape_snapshot_get_path (OttieShapeSnapshot *data); + +#define OTTIE_PARSE_OPTIONS_SHAPE \ + { "nm", ottie_parser_option_string, G_STRUCT_OFFSET (OttieShape, name) }, \ + { "mn", ottie_parser_option_string, G_STRUCT_OFFSET (OttieShape, match_name) }, \ + { "hd", ottie_parser_option_boolean, G_STRUCT_OFFSET (OttieShape, hidden) }, \ + { "ix", ottie_parser_option_skip_index, 0 }, \ + { "ty", ottie_parser_option_skip, 0 }, + +G_END_DECLS + +#endif /* __OTTIE_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottiestrokeshape.c b/ottie/ottiestrokeshape.c new file mode 100644 index 0000000000..ce18dd8930 --- /dev/null +++ b/ottie/ottiestrokeshape.c @@ -0,0 +1,155 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiestrokeshapeprivate.h" + +#include "ottiecolorvalueprivate.h" +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +struct _OttieStrokeShape +{ + OttieShape parent; + + OttieDoubleValue opacity; + OttieColorValue color; + OttieDoubleValue line_width; + GskLineCap line_cap; + GskLineJoin line_join; + double miter_limit; + GskBlendMode blend_mode; +}; + +struct _OttieStrokeShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieStrokeShape, ottie_stroke_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_stroke_shape_snapshot (OttieShape *shape, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ + OttieStrokeShape *self = OTTIE_STROKE_SHAPE (shape); + GskPath *path; + graphene_rect_t bounds; + GdkRGBA color; + GskStroke *stroke; + double opacity, line_width; + + line_width = ottie_double_value_get (&self->line_width, timestamp); + if (line_width <= 0) + return; + + opacity = ottie_double_value_get (&self->opacity, timestamp); + opacity = CLAMP (opacity, 0, 100); + ottie_color_value_get (&self->color, timestamp, &color); + color.alpha = color.alpha * opacity / 100.f; + if (gdk_rgba_is_clear (&color)) + return; + + path = ottie_shape_snapshot_get_path (snapshot_data); + stroke = gsk_stroke_new (line_width); + gsk_stroke_set_line_cap (stroke, self->line_cap); + gsk_stroke_set_line_join (stroke, self->line_join); + gsk_stroke_set_miter_limit (stroke, self->miter_limit); + gtk_snapshot_push_stroke (snapshot, path, stroke); + + gsk_path_get_stroke_bounds (path, stroke, &bounds); + gtk_snapshot_append_color (snapshot, &color, &bounds); + + gsk_stroke_free (stroke); + gtk_snapshot_pop (snapshot); +} + +static void +ottie_stroke_shape_dispose (GObject *object) +{ + OttieStrokeShape *self = OTTIE_STROKE_SHAPE (object); + + ottie_double_value_clear (&self->opacity); + ottie_color_value_clear (&self->color); + ottie_double_value_clear (&self->line_width); + + G_OBJECT_CLASS (ottie_stroke_shape_parent_class)->dispose (object); +} + +static void +ottie_stroke_shape_finalize (GObject *object) +{ + //OttieStrokeShape *self = OTTIE_STROKE_SHAPE (object); + + G_OBJECT_CLASS (ottie_stroke_shape_parent_class)->finalize (object); +} + +static void +ottie_stroke_shape_class_init (OttieStrokeShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->snapshot = ottie_stroke_shape_snapshot; + + gobject_class->finalize = ottie_stroke_shape_finalize; + gobject_class->dispose = ottie_stroke_shape_dispose; +} + +static void +ottie_stroke_shape_init (OttieStrokeShape *self) +{ + ottie_double_value_init (&self->opacity, 100); + ottie_color_value_init (&self->color, &(GdkRGBA) { 0, 0, 0, 1 }); + ottie_double_value_init (&self->line_width, 1); +} + +OttieShape * +ottie_stroke_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE + { "w", ottie_double_value_parse, G_STRUCT_OFFSET (OttieStrokeShape, line_width) }, + { "o", ottie_double_value_parse, G_STRUCT_OFFSET (OttieStrokeShape, opacity) }, + { "c", ottie_color_value_parse, G_STRUCT_OFFSET (OttieStrokeShape, color) }, + { "lc", ottie_parser_option_line_cap, G_STRUCT_OFFSET (OttieStrokeShape, line_cap) }, + { "lj", ottie_parser_option_line_join, G_STRUCT_OFFSET (OttieStrokeShape, line_join) }, + { "ml", ottie_parser_option_double, G_STRUCT_OFFSET (OttieStrokeShape, miter_limit) }, + { "bm", ottie_parser_option_blend_mode, G_STRUCT_OFFSET (OttieStrokeShape, blend_mode) }, + }; + OttieStrokeShape *self; + + self = g_object_new (OTTIE_TYPE_STROKE_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "stroke shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottiestrokeshapeprivate.h b/ottie/ottiestrokeshapeprivate.h new file mode 100644 index 0000000000..2160ec77cd --- /dev/null +++ b/ottie/ottiestrokeshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_STROKE_SHAPE_PRIVATE_H__ +#define __OTTIE_STROKE_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_STROKE_SHAPE (ottie_stroke_shape_get_type ()) +#define OTTIE_STROKE_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_STROKE_SHAPE, OttieStrokeShape)) +#define OTTIE_STROKE_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_STROKE_SHAPE, OttieStrokeShapeClass)) +#define OTTIE_IS_STROKE_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_STROKE_SHAPE)) +#define OTTIE_IS_STROKE_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_STROKE_SHAPE)) +#define OTTIE_STROKE_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_STROKE_SHAPE, OttieStrokeShapeClass)) + +typedef struct _OttieStrokeShape OttieStrokeShape; +typedef struct _OttieStrokeShapeClass OttieStrokeShapeClass; + +GType ottie_stroke_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_stroke_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_STROKE_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottietransform.c b/ottie/ottietransform.c new file mode 100644 index 0000000000..9a47ff8c69 --- /dev/null +++ b/ottie/ottietransform.c @@ -0,0 +1,182 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottietransformprivate.h" + +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottiepoint3dvalueprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +struct _OttieTransform +{ + OttieShape parent; + + OttieDoubleValue opacity; + OttieDoubleValue rotation; + OttieDoubleValue skew; + OttieDoubleValue skew_angle; + OttiePoint3DValue anchor; + OttiePoint3DValue position; + OttiePoint3DValue scale; +}; + +struct _OttieTransformClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieTransform, ottie_transform, OTTIE_TYPE_SHAPE) + +static void +ottie_transform_snapshot (OttieShape *shape, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ +} + +static void +ottie_transform_dispose (GObject *object) +{ + OttieTransform *self = OTTIE_TRANSFORM (object); + + ottie_double_value_clear (&self->opacity); + ottie_double_value_clear (&self->rotation); + ottie_double_value_clear (&self->skew); + ottie_double_value_clear (&self->skew_angle); + ottie_point3d_value_clear (&self->anchor); + ottie_point3d_value_clear (&self->position); + ottie_point3d_value_clear (&self->scale); + + G_OBJECT_CLASS (ottie_transform_parent_class)->dispose (object); +} + +static void +ottie_transform_finalize (GObject *object) +{ + //OttieTransform *self = OTTIE_TRANSFORM (object); + + G_OBJECT_CLASS (ottie_transform_parent_class)->finalize (object); +} + +static void +ottie_transform_class_init (OttieTransformClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->snapshot = ottie_transform_snapshot; + + gobject_class->finalize = ottie_transform_finalize; + gobject_class->dispose = ottie_transform_dispose; +} + +static void +ottie_transform_init (OttieTransform *self) +{ + ottie_double_value_init (&self->opacity, 100); + ottie_double_value_init (&self->rotation, 0); + ottie_double_value_init (&self->skew, 0); + ottie_double_value_init (&self->skew_angle, 0); + ottie_point3d_value_init (&self->anchor, &GRAPHENE_POINT3D_INIT (0, 0, 0)); + ottie_point3d_value_init (&self->position, &GRAPHENE_POINT3D_INIT (0, 0, 0)); + ottie_point3d_value_init (&self->scale, &GRAPHENE_POINT3D_INIT (100, 100, 100)); +} + +static gboolean +ottie_transform_value_parse_point (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_point3d_value_parse (reader, 0, offset, data); +} + +static gboolean +ottie_transform_value_parse_scale (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_point3d_value_parse (reader, 100, offset, data); +} + + +OttieShape * +ottie_transform_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE + { "o", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, opacity) }, + { "r", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, rotation) }, + { "a", ottie_transform_value_parse_point, G_STRUCT_OFFSET (OttieTransform, anchor) }, + { "p", ottie_transform_value_parse_point, G_STRUCT_OFFSET (OttieTransform, position) }, + { "s", ottie_transform_value_parse_scale, G_STRUCT_OFFSET (OttieTransform, scale) }, + { "sk", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, skew) }, + { "sa", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, skew_angle) }, + }; + OttieTransform *self; + + self = g_object_new (OTTIE_TYPE_TRANSFORM, NULL); + + if (!ottie_parser_parse_object (reader, "transform", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + +GskTransform * +ottie_transform_get_transform (OttieTransform *self, + double timestamp) +{ + graphene_point3d_t anchor, position, scale; + GskTransform *transform; + double skew, skew_angle; + + ottie_point3d_value_get (&self->anchor, timestamp, &anchor); + ottie_point3d_value_get (&self->position, timestamp, &position); + ottie_point3d_value_get (&self->scale, timestamp, &scale); + + transform = NULL; + transform = gsk_transform_translate_3d (transform, &position); + transform = gsk_transform_rotate (transform, ottie_double_value_get (&self->rotation, timestamp)); + skew = ottie_double_value_get (&self->skew, timestamp); + if (skew) + { + graphene_matrix_t matrix; + skew_angle = ottie_double_value_get (&self->skew_angle, timestamp); + transform = gsk_transform_rotate (transform, -skew_angle); + graphene_matrix_init_skew (&matrix, -skew / 180.0 * G_PI, 0); + transform = gsk_transform_matrix (transform, &matrix); + transform = gsk_transform_rotate (transform, skew_angle); + } + transform = gsk_transform_scale_3d (transform, scale.x / 100, scale.y / 100, scale.z / 100); + graphene_point3d_scale (&anchor, -1, &anchor); + transform = gsk_transform_translate_3d (transform, &anchor); + + return transform; +} + diff --git a/ottie/ottietransformprivate.h b/ottie/ottietransformprivate.h new file mode 100644 index 0000000000..0cd80a3054 --- /dev/null +++ b/ottie/ottietransformprivate.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_TRANSFORM_PRIVATE_H__ +#define __OTTIE_TRANSFORM_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_TRANSFORM (ottie_transform_get_type ()) +#define OTTIE_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_TRANSFORM, OttieTransform)) +#define OTTIE_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_TRANSFORM, OttieTransformClass)) +#define OTTIE_IS_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_TRANSFORM)) +#define OTTIE_IS_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_TRANSFORM)) +#define OTTIE_TRANSFORM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_TRANSFORM, OttieTransformClass)) + +typedef struct _OttieTransform OttieTransform; +typedef struct _OttieTransformClass OttieTransformClass; + +GType ottie_transform_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_transform_parse (JsonReader *reader); + +GskTransform * ottie_transform_get_transform (OttieTransform *self, + double timestamp); + +G_END_DECLS + +#endif /* __OTTIE_TRANSFORM_PRIVATE_H__ */ diff --git a/ottie/ottietrimshape.c b/ottie/ottietrimshape.c new file mode 100644 index 0000000000..8fd5579964 --- /dev/null +++ b/ottie/ottietrimshape.c @@ -0,0 +1,198 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottietrimshapeprivate.h" + +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +typedef enum +{ + OTTIE_TRIM_TOGETHER, + OTTIE_TRIM_SEPARATELY +} OttieTrimMode; + +struct _OttieTrimShape +{ + OttieShape parent; + + OttieTrimMode mode; + OttieDoubleValue start; + OttieDoubleValue end; + OttieDoubleValue offset; +}; + +struct _OttieTrimShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieTrimShape, ottie_trim_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_trim_shape_snapshot (OttieShape *shape, + GtkSnapshot *snapshot, + OttieShapeSnapshot *snapshot_data, + double timestamp) +{ + OttieTrimShape *self = OTTIE_TRIM_SHAPE (shape); + GskPathMeasure *measure; + GskPath *path; + GskPathBuilder *builder; + double start, end, offset; + + path = ottie_shape_snapshot_get_path (snapshot_data); + measure = gsk_path_measure_new (path); + start = ottie_double_value_get (&self->start, timestamp); + start = CLAMP (start, 0, 100) / 100.f; + end = ottie_double_value_get (&self->end, timestamp); + end = CLAMP (end, 0, 100) / 100.f; + + builder = gsk_path_builder_new (); + if (start != end) + { + if (start > end) + { + double swap = end; + end = start; + start = swap; + } + offset = ottie_double_value_get (&self->offset, timestamp) / 360.f; + start += offset; + start = start - floor (start); + start *= gsk_path_measure_get_length (measure); + end += offset; + end = end - floor (end); + end *= gsk_path_measure_get_length (measure); + + gsk_path_builder_add_segment (builder, measure, start, end); + } + path = gsk_path_builder_free_to_path (builder); + + ottie_shape_snapshot_clear (snapshot_data); + ottie_shape_snapshot_add_path (snapshot_data, path); + + gsk_path_measure_unref (measure); +} + +static void +ottie_trim_shape_dispose (GObject *object) +{ + OttieTrimShape *self = OTTIE_TRIM_SHAPE (object); + + ottie_double_value_clear (&self->start); + ottie_double_value_clear (&self->end); + ottie_double_value_clear (&self->offset); + + G_OBJECT_CLASS (ottie_trim_shape_parent_class)->dispose (object); +} + +static void +ottie_trim_shape_finalize (GObject *object) +{ + //OttieTrimShape *self = OTTIE_TRIM_SHAPE (object); + + G_OBJECT_CLASS (ottie_trim_shape_parent_class)->finalize (object); +} + +static void +ottie_trim_shape_class_init (OttieTrimShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->snapshot = ottie_trim_shape_snapshot; + + gobject_class->finalize = ottie_trim_shape_finalize; + gobject_class->dispose = ottie_trim_shape_dispose; +} + +static void +ottie_trim_shape_init (OttieTrimShape *self) +{ + ottie_double_value_init (&self->start, 0); + ottie_double_value_init (&self->end, 100); + ottie_double_value_init (&self->offset, 0); +} + +static gboolean +ottie_trim_mode_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieTrimMode trim_mode; + gint64 i; + + i = json_reader_get_int_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + switch (i) + { + case 1: + trim_mode = OTTIE_TRIM_TOGETHER; + break; + + case 2: + ottie_parser_error_unsupported (reader, "Figure out separate trim mode"); + trim_mode = OTTIE_TRIM_SEPARATELY; + break; + + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known trim mode", i); + return FALSE; + } + + *(OttieTrimMode *) ((guint8 *) data + offset) = trim_mode; + + return TRUE; +} + +OttieShape * +ottie_trim_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE + { "s", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTrimShape, start) }, + { "e", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTrimShape, end) }, + { "o", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTrimShape, offset) }, + { "m", ottie_trim_mode_parse, G_STRUCT_OFFSET (OttieTrimShape, mode) }, + }; + OttieTrimShape *self; + + self = g_object_new (OTTIE_TYPE_TRIM_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "trim shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottietrimshapeprivate.h b/ottie/ottietrimshapeprivate.h new file mode 100644 index 0000000000..cd41479bbc --- /dev/null +++ b/ottie/ottietrimshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_TRIM_SHAPE_PRIVATE_H__ +#define __OTTIE_TRIM_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_TRIM_SHAPE (ottie_trim_shape_get_type ()) +#define OTTIE_TRIM_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_TRIM_SHAPE, OttieTrimShape)) +#define OTTIE_TRIM_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_TRIM_SHAPE, OttieTrimShapeClass)) +#define OTTIE_IS_TRIM_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_TRIM_SHAPE)) +#define OTTIE_IS_TRIM_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_TRIM_SHAPE)) +#define OTTIE_TRIM_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_TRIM_SHAPE, OttieTrimShapeClass)) + +typedef struct _OttieTrimShape OttieTrimShape; +typedef struct _OttieTrimShapeClass OttieTrimShapeClass; + +GType ottie_trim_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_trim_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_TRIM_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottievalueimpl.c b/ottie/ottievalueimpl.c new file mode 100644 index 0000000000..12bcede664 --- /dev/null +++ b/ottie/ottievalueimpl.c @@ -0,0 +1,133 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include + +G_BEGIN_DECLS + +#ifndef OTTIE_VALUE_TYPE_NAME +#define OTTIE_VALUE_TYPE_NAME OttieValue +#endif + +#ifndef OTTIE_VALUE_NAME +#define OTTIE_VALUE_NAME ottie_value +#endif + +#ifndef OTTIE_VALUE_ELEMENT_TYPE +#define OTTIE_VALUE_ELEMENT_TYPE gpointer +#endif + +/* make this readable */ +#define _T_ OTTIE_VALUE_ELEMENT_TYPE +#define OttieValue OTTIE_VALUE_TYPE_NAME +#define ottie_value_paste_more(OTTIE_VALUE_NAME, func_name) OTTIE_VALUE_NAME ## _ ## func_name +#define ottie_value_paste(OTTIE_VALUE_NAME, func_name) ottie_value_paste_more (OTTIE_VALUE_NAME, func_name) +#define ottie_value(func_name) ottie_value_paste (OTTIE_VALUE_NAME, func_name) + +typedef struct OttieValue OttieValue; + +struct OttieValue +{ + enum { + STATIC, + KEYFRAMES + } type; + union { + _T_ static_value; + struct { + _T_ *values; + gsize n_frames; + } keyframes; +}; + +void +ottie_value(init) (OttieValue *self) +{ + memset (self, 0, sizeof (OttieValue)); +} + +static inline void +ottie_value(free_item) (_T_ *item) +{ +#ifdef OTTIE_VALUE_FREE_FUNC +#ifdef OTTIE_VALUE_BY_VALUE + OTTIE_VALUE_FREE_FUNC (item); +#else + OTTIE_VALUE_FREE_FUNC (*item); +#endif +#endif +} + +void +ottie_value(clear) (OttieValue *self) +{ +#ifdef OTTIE_VALUE_FREE_FUNC + gsize i; + + if (self->type == STATIC) + { + ottie_value(free_item) (&self->static_value); + } + else + { + for (i = 0; i < self->n_values, i++) + ottie_value(free_item) (&self->values[i]); + } +} + +#ifdef OTTIE_VALUE_BY_VALUE +_T_ * +#else +_T_ +#endif +ottie_value(get) (const OttieValue *self, + double progress) +{ + _T_ * result; + + if (self->type == STATIC) + { + result = &self->static_value; + } + else + { + result = &self->values[progress * self->n_values]; + } + +#ifdef OTTIE_VALUE_BY_VALUE + return result; +#else + return *result; +#endif +} + +#ifndef OTTIE_VALUE_NO_UNDEF + +#undef _T_ +#undef OttieValue +#undef ottie_value_paste_more +#undef ottie_value_paste +#undef ottie_value + +#undef OTTIE_VALUE_BY_VALUE +#undef OTTIE_VALUE_ELEMENT_TYPE +#undef OTTIE_VALUE_FREE_FUNC +#undef OTTIE_VALUE_NAME +#undef OTTIE_VALUE_TYPE_NAME +#endif diff --git a/tests/meson.build b/tests/meson.build index 783dd61914..3e1a19dd4c 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,17 +1,18 @@ gtk_tests = [ # testname, optional extra sources + ['animated-resizing', ['frame-stats.c', 'variable.c']], + ['animated-revealing', ['frame-stats.c', 'variable.c']], + ['blur-performance', ['../gsk/gskcairoblur.c']], + ['motion-compression'], + ['ottie'], + ['overlayscroll'], ['testupload'], ['testtransform'], ['testdropdown'], ['rendernode'], ['rendernode-create-tests'], - ['overlayscroll'], ['syncscroll'], - ['animated-resizing', ['frame-stats.c', 'variable.c']], - ['animated-revealing', ['frame-stats.c', 'variable.c']], - ['motion-compression'], ['scrolling-performance', ['frame-stats.c', 'variable.c']], - ['blur-performance', ['../gsk/gskcairoblur.c']], ['simple'], ['video-timer', ['variable.c']], ['testaccel'], diff --git a/tests/ottie.c b/tests/ottie.c new file mode 100644 index 0000000000..8a2cbc231b --- /dev/null +++ b/tests/ottie.c @@ -0,0 +1,98 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include +#include + +G_GNUC_UNUSED static gboolean +save_paintable (GdkPaintable *paintable, + const char *filename) +{ + GtkSnapshot *snapshot; + GskRenderNode *node; + int width, height; + cairo_t *cr; + cairo_surface_t *surface; + gboolean result; + + width = gdk_paintable_get_intrinsic_width (paintable); + height = gdk_paintable_get_intrinsic_height (paintable); + + snapshot = gtk_snapshot_new (); + gdk_paintable_snapshot (paintable, snapshot, width, height); + node = gtk_snapshot_free_to_node (snapshot); + if (!gsk_render_node_write_to_file (node, "foo.node", NULL)) + return FALSE; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + cr = cairo_create (surface); + gsk_render_node_draw (node, cr); + cairo_destroy (cr); + gsk_render_node_unref (node); + + result = cairo_surface_write_to_png (surface, filename) == CAIRO_STATUS_SUCCESS; + + cairo_surface_destroy (surface); + + return result; +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window, *video; + OttiePlayer *player; + + gtk_init (); + + if (argc > 1) + player = ottie_player_new_for_filename (argv[1]); + else + player = ottie_player_new (); + + window = gtk_window_new (); + gtk_window_set_title (GTK_WINDOW (window), "Ottie"); + gtk_window_set_default_size (GTK_WINDOW (window), 400, 300); + g_signal_connect (window, "destroy", G_CALLBACK (gtk_window_destroy), NULL); + + video = gtk_video_new (); + gtk_video_set_loop (GTK_VIDEO (video), TRUE); + gtk_video_set_autoplay (GTK_VIDEO (video), TRUE); + gtk_video_set_media_stream (GTK_VIDEO (video), GTK_MEDIA_STREAM (player)); + gtk_window_set_child (GTK_WINDOW (window), video); + + gtk_widget_show (window); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + g_main_context_iteration (NULL, TRUE); + +#if 0 + for (int i = 0; i < 62; i++) + { + ottie_paintable_set_timestamp (paintable, i * G_USEC_PER_SEC / 30); + save_paintable (GDK_PAINTABLE (paintable), g_strdup_printf ("foo%u.png", i)); + } +#else + //save_paintable (GDK_PAINTABLE (paintable), "foo.png"); +#endif + + return 0; +}