[gtk/wip/otte/lottie: 1/3] Ottie: Add
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/lottie: 1/3] Ottie: Add
- Date: Mon, 21 Dec 2020 02:53:31 +0000 (UTC)
commit b5abd4b7ff4b4f607e82bc6ac4761ec034c06644
Author: Benjamin Otte <otte redhat com>
Date: Sat Dec 12 03:38:10 2020 +0100
Ottie: Add
gtk/meson.build | 2 +-
meson.build | 1 +
ottie/meson.build | 65 ++++
ottie/ottie.h | 31 ++
ottie/ottiecolorvalue.c | 161 ++++++++++
ottie/ottiecolorvalueprivate.h | 54 ++++
ottie/ottiecreation.c | 656 +++++++++++++++++++++++++++++++++++++++
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 | 232 ++++++++++++++
ottie/ottiegroupshapeprivate.h | 50 +++
ottie/ottiekeyframesimpl.c | 276 ++++++++++++++++
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 | 387 +++++++++++++++++++++++
ottie/ottiepathvalueprivate.h | 53 ++++
ottie/ottieplayer.c | 490 +++++++++++++++++++++++++++++
ottie/ottieplayer.h | 59 ++++
ottie/ottiepointvalue.c | 152 +++++++++
ottie/ottiepointvalueprivate.h | 54 ++++
ottie/ottieprecomp.c | 177 +++++++++++
ottie/ottieprecomplayer.c | 119 +++++++
ottie/ottieprecomplayerprivate.h | 45 +++
ottie/ottieprecompprivate.h | 47 +++
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 ++++++
47 files changed, 6224 insertions(+), 6 deletions(-)
---
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..15623ea18f
--- /dev/null
+++ b/ottie/meson.build
@@ -0,0 +1,65 @@
+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',
+ 'ottieprecomp.c',
+ 'ottieprecomplayer.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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_H__
+#define __OTTIE_H__
+
+#define __OTTIE_H_INSIDE__
+
+#include <ottie/ottiecreation.h>
+#include <ottie/ottiepaintable.h>
+#include <ottie/ottieplayer.h>
+
+#undef __OTTIE_H_INSIDE__
+
+#endif /* __OTTIE_H__ */
diff --git a/ottie/ottiecolorvalue.c b/ottie/ottiecolorvalue.c
new file mode 100644
index 0000000000..844864bc9f
--- /dev/null
+++ b/ottie/ottiecolorvalue.c
@@ -0,0 +1,161 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiecolorvalueprivate.h"
+
+#include "ottieparserprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+static gboolean
+ottie_color_value_parse_one (JsonReader *reader,
+ gsize offset,
+ gpointer data)
+{
+ GdkRGBA *rgba = (GdkRGBA *) ((guint8 *) data + offset);
+ int count = json_reader_count_elements (reader);
+
+ json_reader_read_element (reader, 0);
+ rgba->red = json_reader_get_double_value (reader);
+ json_reader_end_element (reader);
+
+ json_reader_read_element (reader, 1);
+ rgba->green = json_reader_get_double_value (reader);
+ json_reader_end_element (reader);
+
+ json_reader_read_element (reader, 2);
+ rgba->blue = json_reader_get_double_value (reader);
+ json_reader_end_element (reader);
+
+ if (count > 3)
+ {
+ json_reader_read_element (reader, 3);
+ rgba->alpha = json_reader_get_double_value (reader);
+ json_reader_end_element (reader);
+ }
+ else
+ 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))
+ {
+ 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)
+ {
+ 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_COLOR_VALUE_PRIVATE_H__
+#define __OTTIE_COLOR_VALUE_PRIVATE_H__
+
+#include <json-glib/json-glib.h>
+
+#include <gdk/gdk.h>
+
+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..ab826e2402
--- /dev/null
+++ b/ottie/ottiecreation.c
@@ -0,0 +1,656 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiecreationprivate.h"
+
+#include "ottielayerprivate.h"
+#include "ottieparserprivate.h"
+#include "ottieprecompprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <json-glib/json-glib.h>
+
+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)
+{
+ 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_CREATION_H__
+#define __OTTIE_CREATION_H__
+
+#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <ottie/ottie.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_CREATION_PRIVATE_H__
+#define __OTTIE_CREATION_PRIVATE_H__
+
+#include "ottiecreation.h"
+
+#include <gtk/gtk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiedoublevalueprivate.h"
+
+#include "ottieparserprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_DOUBLE_VALUE_PRIVATE_H__
+#define __OTTIE_DOUBLE_VALUE_PRIVATE_H__
+
+#include <json-glib/json-glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiefillshapeprivate.h"
+
+#include "ottiecolorvalueprivate.h"
+#include "ottiedoublevalueprivate.h"
+#include "ottieparserprivate.h"
+#include "ottieshapeprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_FILL_SHAPE_PRIVATE_H__
+#define __OTTIE_FILL_SHAPE_PRIVATE_H__
+
+#include "ottieshapeprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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..5222a662b2
--- /dev/null
+++ b/ottie/ottiegroupshape.c
@@ -0,0 +1,232 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiegroupshapeprivate.h"
+
+#include "ottiefillshapeprivate.h"
+#include "ottieparserprivate.h"
+#include "ottiepathshapeprivate.h"
+#include "ottieshapeprivate.h"
+#include "ottiestrokeshapeprivate.h"
+#include "ottietransformprivate.h"
+#include "ottietrimshapeprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+#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, "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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_GROUP_SHAPE_PRIVATE_H__
+#define __OTTIE_GROUP_SHAPE_PRIVATE_H__
+
+#include "ottieshapeprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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..4a2ec7b3aa
--- /dev/null
+++ b/ottie/ottiekeyframesimpl.c
@@ -0,0 +1,276 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include <glib.h>
+
+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_ 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_item) (_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
+}
+
+/* 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_item) (&self->items[i].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 *start, *end;
+
+ start = end = NULL;
+
+ for (gsize i = 0; i < self->n_items; i++)
+ {
+ if (self->items[i].start_time <= timestamp)
+ start = &self->items[i];
+
+ if (self->items[i].start_time >= timestamp)
+ {
+ end = &self->items[i];
+ break;
+ }
+ }
+
+ g_assert (start != NULL || end != NULL);
+
+#ifdef OTTIE_KEYFRAMES_BY_VALUE
+ if (start == NULL || start == end)
+ *out_result = end->value;
+ else if (end == NULL)
+ *out_result = start->value;
+#else
+ if (start == NULL || start == end)
+ return end->value;
+ else if (end == NULL)
+ return start->value;
+#endif
+ else
+ {
+ double progress = (timestamp - start->start_time) / (end->start_time - start->start_time);
+#ifdef OTTIE_KEYFRAMES_BY_VALUE
+ OTTIE_KEYFRAMES_INTERPOLATE_FUNC (&start->value, &end->value, progress, out_result);
+#else
+ return OTTIE_KEYFRAMES_INTERPOLATE_FUNC (start->value, 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;
+}
+
+static gboolean
+ottie_keyframes(parse_keyframe) (JsonReader *reader,
+ gsize offset,
+ gpointer data)
+{
+ OttieParserOption options[] = {
+ { "s", OTTIE_KEYFRAMES_PARSE_FUNC, G_STRUCT_OFFSET (OttieKeyframe, value) },
+ { "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 },
+ };
+ OttieKeyframe *keyframe = (OttieKeyframe *) ((guint8 *) data + offset);
+
+ return ottie_parser_parse_object (reader, "keyframe", options, G_N_ELEMENTS (options), keyframe);
+}
+
+/* no G_GNUC_UNUSED here, if you don't use a type, remove it. */
+static inline OttieKeyframes *
+ottie_keyframes(parse) (JsonReader *reader)
+{
+ OttieKeyframes *self;
+
+ self = ottie_keyframes(new) (json_reader_count_elements (reader));
+
+ if (!ottie_parser_parse_array (reader, "keyframes",
+ self->n_items, self->n_items,
+ NULL,
+ G_STRUCT_OFFSET (OttieKeyframes, items),
+ sizeof (OttieKeyframe),
+ ottie_keyframes(parse_keyframe),
+ self))
+ {
+ ottie_keyframes(free) (self);
+ return NULL;
+ }
+
+ /* XXX: Do we need to order keyframes here? */
+
+ return self;
+}
+
+#ifndef OTTIE_KEYFRAMES_NO_UNDEF
+
+#undef _T_
+#undef OttieKeyframes
+#undef ottie_keyframes_paste_more
+#undef ottie_keyframes_paste
+#undef ottie_keyframes
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottielayerprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <json-glib/json-glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_LAYER_PRIVATE_H__
+#define __OTTIE_LAYER_PRIVATE_H__
+
+#include <gtk/gtk.h>
+
+#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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiepaintable.h"
+
+#include "ottiecreationprivate.h"
+
+#include <math.h>
+#include <glib/gi18n.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_PAINTABLE_H__
+#define __OTTIE_PAINTABLE_H__
+
+#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <ottie/ottie.h> can be included directly."
+#endif
+
+#include <ottie/ottiecreation.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottieparserprivate.h"
+
+#include "ottietransformprivate.h"
+
+#include <gsk/gsk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_PARSER_PRIVATE_H__
+#define __OTTIE_PARSER_PRIVATE_H__
+
+#include <json-glib/json-glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiepathshapeprivate.h"
+
+#include "ottiepathvalueprivate.h"
+#include "ottieparserprivate.h"
+#include "ottieshapeprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_PATH_SHAPE_PRIVATE_H__
+#define __OTTIE_PATH_SHAPE_PRIVATE_H__
+
+#include "ottieshapeprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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..76aafb2b5c
--- /dev/null
+++ b/ottie/ottiepathvalue.c
@@ -0,0 +1,387 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiepathvalueprivate.h"
+
+#include "ottieparserprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+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 {
+ gsize 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->n_contours = n_contours;
+
+ return self;
+}
+
+static void
+ottie_path_free (OttiePath *path)
+{
+ for (gsize i = 0; i < path->n_contours; i++)
+ {
+ g_clear_pointer (&path->contours[i], ottie_contour_free);
+ }
+
+ g_free (path);
+}
+
+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_free (path);
+ return FALSE;
+ }
+
+ g_clear_pointer (target, ottie_path_free);
+ *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_FREE_FUNC ottie_path_free
+#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_free);
+ 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_PATH_VALUE_PRIVATE_H__
+#define __OTTIE_PATH_VALUE_PRIVATE_H__
+
+#include <json-glib/json-glib.h>
+
+#include <gsk/gsk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottieplayer.h"
+
+#include "ottiecreation.h"
+#include "ottiepaintable.h"
+
+#include <glib/gi18n.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_PLAYER_H__
+#define __OTTIE_PLAYER_H__
+
+#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <ottie/ottie.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+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/ottiepointvalue.c b/ottie/ottiepointvalue.c
new file mode 100644
index 0000000000..f0739c5199
--- /dev/null
+++ b/ottie/ottiepointvalue.c
@@ -0,0 +1,152 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiepointvalueprivate.h"
+
+#include "ottieparserprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+static gboolean
+ottie_point_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_point_value_parse_value
+#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC graphene_point3d_interpolate
+#include "ottiekeyframesimpl.c"
+
+void
+ottie_point_value_init (OttiePointValue *self,
+ const graphene_point3d_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_point3d_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,
+ float default_value,
+ 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;
+ }
+ 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].value.z))
+ keyframes->items[i].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/ottiepointvalueprivate.h b/ottie/ottiepointvalueprivate.h
new file mode 100644
index 0000000000..d6b6a784b9
--- /dev/null
+++ b/ottie/ottiepointvalueprivate.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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_POINT_VALUE_PRIVATE_H__
+#define __OTTIE_POINT_VALUE_PRIVATE_H__
+
+#include <json-glib/json-glib.h>
+#include <graphene.h>
+
+G_BEGIN_DECLS
+
+typedef struct _OttiePointValue OttiePointValue;
+
+struct _OttiePointValue
+{
+ gboolean is_static;
+ union {
+ graphene_point3d_t static_value;
+ gpointer keyframes;
+ };
+};
+
+void ottie_point_value_init (OttiePointValue *self,
+ const graphene_point3d_t *value);
+void ottie_point_value_clear (OttiePointValue *self);
+
+void ottie_point_value_get (OttiePointValue *self,
+ double timestamp,
+ graphene_point3d_t *value);
+
+gboolean ottie_point_value_parse (JsonReader *reader,
+ float
default_value,
+ 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..689de2761b
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottieprecompprivate.h"
+
+#include "ottieparserprivate.h"
+#include "ottieprecomplayerprivate.h"
+#include "ottieshapelayerprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+#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 = 0; i < ottie_layer_list_get_size (&self->layers); i++)
+ {
+ 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottieprecomplayerprivate.h"
+
+#include "ottiedoublevalueprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_PRECOMP_LAYER_PRIVATE_H__
+#define __OTTIE_PRECOMP_LAYER_PRIVATE_H__
+
+#include "ottielayerprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_PRECOMP_PRIVATE_H__
+#define __OTTIE_PRECOMP_PRIVATE_H__
+
+#include "ottielayerprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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/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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottieshapeprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#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 <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_SHAPE_LAYER_PRIVATE_H__
+#define __OTTIE_SHAPE_LAYER_PRIVATE_H__
+
+#include "ottielayerprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_SHAPE_PRIVATE_H__
+#define __OTTIE_SHAPE_PRIVATE_H__
+
+#include <gtk/gtk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottiestrokeshapeprivate.h"
+
+#include "ottiecolorvalueprivate.h"
+#include "ottiedoublevalueprivate.h"
+#include "ottieparserprivate.h"
+#include "ottieshapeprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_STROKE_SHAPE_PRIVATE_H__
+#define __OTTIE_STROKE_SHAPE_PRIVATE_H__
+
+#include "ottieshapeprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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..0c9f00c9c1
--- /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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottietransformprivate.h"
+
+#include "ottiedoublevalueprivate.h"
+#include "ottieparserprivate.h"
+#include "ottiepointvalueprivate.h"
+#include "ottieshapeprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+struct _OttieTransform
+{
+ OttieShape parent;
+
+ OttieDoubleValue opacity;
+ OttieDoubleValue rotation;
+ OttieDoubleValue skew;
+ OttieDoubleValue skew_angle;
+ OttiePointValue anchor;
+ OttiePointValue position;
+ OttiePointValue 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_point_value_clear (&self->anchor);
+ ottie_point_value_clear (&self->position);
+ ottie_point_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_point_value_init (&self->anchor, &GRAPHENE_POINT3D_INIT (0, 0, 0));
+ ottie_point_value_init (&self->position, &GRAPHENE_POINT3D_INIT (0, 0, 0));
+ ottie_point_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_point_value_parse (reader, 0, offset, data);
+}
+
+static gboolean
+ottie_transform_value_parse_scale (JsonReader *reader,
+ gsize offset,
+ gpointer data)
+{
+ return ottie_point_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_point_value_get (&self->anchor, timestamp, &anchor);
+ ottie_point_value_get (&self->position, timestamp, &position);
+ ottie_point_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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_TRANSFORM_PRIVATE_H__
+#define __OTTIE_TRANSFORM_PRIVATE_H__
+
+#include "ottieshapeprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "ottietrimshapeprivate.h"
+
+#include "ottiedoublevalueprivate.h"
+#include "ottieparserprivate.h"
+#include "ottieshapeprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <gsk/gsk.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __OTTIE_TRIM_SHAPE_PRIVATE_H__
+#define __OTTIE_TRIM_SHAPE_PRIVATE_H__
+
+#include "ottieshapeprivate.h"
+
+#include <json-glib/json-glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include <glib.h>
+
+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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include <ottie/ottie.h>
+#include <gtk/gtk.h>
+
+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;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]