diff --git a/gtk/meson.build b/gtk/meson.build index 7cdfe63af5..16d95959cf 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -1118,8 +1118,8 @@ libgtk = library('gtk-4', version: gtk_library_version, c_args: gtk_cargs + common_cflags, include_directories: [confinc, gdkinc, gskinc, gtkinc], - dependencies: [libgtk_static_dep, libgtk_css_dep, libgdk_dep, libgsk_dep], - link_whole: [libgtk_static, libgtk_css, libgdk, libgsk], + dependencies: [libgtk_static_dep, libgtk_css_dep, libgdk_dep, libgsk_dep, libottie_dep], + link_whole: [libgtk_static, libgtk_css, libgdk, libgsk, libottie], link_args: common_ldflags, darwin_versions: darwin_versions, install: true, diff --git a/meson.build b/meson.build index bb58717f6a..06e169d914 100644 --- a/meson.build +++ b/meson.build @@ -679,6 +679,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..fd2a8a7b22 --- /dev/null +++ b/ottie/meson.build @@ -0,0 +1,71 @@ +ottie_public_sources = files([ + 'ottiecreation.c', + 'ottiepaintable.c', + 'ottieplayer.c', +]) + +ottie_private_sources = files([ + 'ottiecolorvalue.c', + 'ottiecomposition.c', + 'ottiecompositionlayer.c', + 'ottiedoublevalue.c', + 'ottieellipseshape.c', + 'ottiefillshape.c', + 'ottiegroupshape.c', + 'ottielayer.c', + 'ottienulllayer.c', + 'ottieobject.c', + 'ottieparser.c', + 'ottiepathshape.c', + 'ottiepathvalue.c', + 'ottiepointvalue.c', + 'ottiepoint3dvalue.c', + 'ottierectshape.c', + 'ottierender.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="Ottie"', + ] + common_cflags, + link_with: [libgdk, libgsk ], + link_args: common_ldflags) + +# We don't have link_with: to internal static libs here on purpose, just +# list the dependencies and generated headers and such, for use in the +# "public" libgtk_dep used by internal executables. +libottie_dep = declare_dependency(include_directories: [ confinc, ], + dependencies: ottie_deps) + diff --git a/ottie/ottie.h b/ottie/ottie.h new file mode 100644 index 0000000000..c2da341e41 --- /dev/null +++ b/ottie/ottie.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_H__ +#define __OTTIE_H__ + +#define __OTTIE_H_INSIDE__ + +#include +#include +#include + +#undef __OTTIE_H_INSIDE__ + +#endif /* __OTTIE_H__ */ diff --git a/ottie/ottiecolorvalue.c b/ottie/ottiecolorvalue.c new file mode 100644 index 0000000000..7b81afd1ee --- /dev/null +++ b/ottie/ottiecolorvalue.c @@ -0,0 +1,149 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiecolorvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_color_value_parse_one (JsonReader *reader, + gsize offset, + gpointer data) +{ + GdkRGBA *rgba = (GdkRGBA *) ((guint8 *) data + offset); + double d[3]; + + if (!ottie_parser_parse_array (reader, "color value", + 3, 3, NULL, + 0, sizeof (double), + ottie_parser_option_double, + d)) + { + d[0] = d[1] = d[2] = 0; + } + + rgba->red = d[0]; + rgba->green = d[1]; + rgba->blue = d[2]; + rgba->alpha = 1; + + return TRUE; +} + +static void +ottie_color_value_interpolate (const GdkRGBA *start, + const GdkRGBA *end, + double progress, + GdkRGBA *result) +{ + result->red = start->red + progress * (end->red - start->red); + result->green = start->green + progress * (end->green - start->green); + result->blue = start->blue + progress * (end->blue - start->blue); + result->alpha = start->alpha + progress * (end->alpha - start->alpha); +} + +#define OTTIE_KEYFRAMES_NAME ottie_color_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttieColorKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE GdkRGBA +#define OTTIE_KEYFRAMES_BY_VALUE 1 +#define OTTIE_KEYFRAMES_DIMENSIONS 4 +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_color_value_parse_one +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_color_value_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_color_value_init (OttieColorValue *self, + const GdkRGBA *value) +{ + self->is_static = TRUE; + self->static_value = *value; +} + +void +ottie_color_value_clear (OttieColorValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_color_keyframes_free); +} + +void +ottie_color_value_get (OttieColorValue *self, + double timestamp, + GdkRGBA *rgba) +{ + if (self->is_static) + { + *rgba = self->static_value; + return; + } + + ottie_color_keyframes_get (self->keyframes, timestamp, rgba); +} + +gboolean +ottie_color_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieColorValue *self = (OttieColorValue *) ((guint8 *) data + offset); + + if (json_reader_read_member (reader, "k")) + { + gboolean is_static; + + if (!json_reader_is_array (reader)) + is_static = TRUE; + else + { + if (json_reader_read_element (reader, 0)) + is_static = !json_reader_is_object (reader); + else + is_static = TRUE; + json_reader_end_element (reader); + } + + if (is_static) + { + self->is_static = TRUE; + ottie_color_value_parse_one (reader, 0, &self->static_value); + } + else + { + self->is_static = FALSE; + self->keyframes = ottie_color_keyframes_parse (reader); + if (self->keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + } + } + else + { + ottie_parser_error_syntax (reader, "Property is not a color value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiecolorvalueprivate.h b/ottie/ottiecolorvalueprivate.h new file mode 100644 index 0000000000..48998d807a --- /dev/null +++ b/ottie/ottiecolorvalueprivate.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_COLOR_VALUE_PRIVATE_H__ +#define __OTTIE_COLOR_VALUE_PRIVATE_H__ + +#include + +#include + +G_BEGIN_DECLS + +typedef struct _OttieColorValue OttieColorValue; + +struct _OttieColorValue +{ + gboolean is_static; + union { + GdkRGBA static_value; + gpointer keyframes; + }; +}; + +void ottie_color_value_init (OttieColorValue *self, + const GdkRGBA *rgba); +void ottie_color_value_clear (OttieColorValue *self); + +void ottie_color_value_get (OttieColorValue *self, + double timestamp, + GdkRGBA *rgba); + +gboolean ottie_color_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_COLOR_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottiecomposition.c b/ottie/ottiecomposition.c new file mode 100644 index 0000000000..623a52d10b --- /dev/null +++ b/ottie/ottiecomposition.c @@ -0,0 +1,268 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiecompositionprivate.h" + +#include "ottieparserprivate.h" +#include "ottiecompositionlayerprivate.h" +#include "ottienulllayerprivate.h" +#include "ottieshapelayerprivate.h" + +#include +#include + +#define GDK_ARRAY_ELEMENT_TYPE OttieLayer * +#define GDK_ARRAY_FREE_FUNC g_object_unref +#define GDK_ARRAY_TYPE_NAME OttieLayerList +#define GDK_ARRAY_NAME ottie_layer_list +#define GDK_ARRAY_PREALLOC 4 +#include "gdk/gdkarrayimpl.c" + +struct _OttieComposition +{ + OttieLayer parent; + + OttieLayerList layers; + GHashTable *layers_by_index; +}; + +struct _OttieCompositionClass +{ + OttieLayerClass parent_class; +}; + +static GType +ottie_composition_get_item_type (GListModel *list) +{ + return OTTIE_TYPE_LAYER; +} + +static guint +ottie_composition_get_n_items (GListModel *list) +{ + OttieComposition *self = OTTIE_COMPOSITION (list); + + return ottie_layer_list_get_size (&self->layers); +} + +static gpointer +ottie_composition_get_item (GListModel *list, + guint position) +{ + OttieComposition *self = OTTIE_COMPOSITION (list); + + if (position >= ottie_layer_list_get_size (&self->layers)) + return NULL; + + return g_object_ref (ottie_layer_list_get (&self->layers, position)); +} + +static void +ottie_composition_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = ottie_composition_get_item_type; + iface->get_n_items = ottie_composition_get_n_items; + iface->get_item = ottie_composition_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (OttieComposition, ottie_composition, OTTIE_TYPE_LAYER, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, ottie_composition_list_model_init)) + +static void +ottie_composition_update (OttieLayer *layer, + GHashTable *compositions) +{ + OttieComposition *self = OTTIE_COMPOSITION (layer); + + for (gsize i = ottie_layer_list_get_size (&self->layers); i-- > 0; ) + { + ottie_layer_update (ottie_layer_list_get (&self->layers, i), compositions); + } +} + +static void +ottie_composition_render (OttieLayer *layer, + OttieRender *render, + double timestamp) +{ + OttieComposition *self = OTTIE_COMPOSITION (layer); + OttieRender child_render; + + ottie_render_init (&child_render); + + for (gsize i = 0; i < ottie_layer_list_get_size (&self->layers); i++) + { + OttieLayer *child = ottie_layer_list_get (&self->layers, i); + + ottie_layer_render (child, &child_render, timestamp); + /* XXX: Should we clear paths here because they're not needed anymore? */ + + /* Use a counter here to avoid inflooping */ + for (gsize j = 0; j < ottie_layer_list_get_size (&self->layers); j++) + { + if (child->transform) + ottie_shape_render (OTTIE_SHAPE (child->transform), &child_render, timestamp); + if (child->parent_index == OTTIE_INT_UNSET) + break; + child = g_hash_table_lookup (self->layers_by_index, GINT_TO_POINTER (child->parent_index)); + if (child == NULL) + break; + } + + ottie_render_merge (render, &child_render); + } + + ottie_render_clear (&child_render); +} + +static void +ottie_composition_dispose (GObject *object) +{ + OttieComposition *self = OTTIE_COMPOSITION (object); + + ottie_layer_list_clear (&self->layers); + g_hash_table_remove_all (self->layers_by_index); + + G_OBJECT_CLASS (ottie_composition_parent_class)->dispose (object); +} + +static void +ottie_composition_finalize (GObject *object) +{ + OttieComposition *self = OTTIE_COMPOSITION (object); + + g_hash_table_unref (self->layers_by_index); + + G_OBJECT_CLASS (ottie_composition_parent_class)->finalize (object); +} + +static void +ottie_composition_class_init (OttieCompositionClass *klass) +{ + OttieLayerClass *layer_class = OTTIE_LAYER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + layer_class->update = ottie_composition_update; + layer_class->render = ottie_composition_render; + + gobject_class->dispose = ottie_composition_dispose; + gobject_class->finalize = ottie_composition_finalize; +} + +static void +ottie_composition_init (OttieComposition *self) +{ + ottie_layer_list_init (&self->layers); + + self->layers_by_index = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void +ottie_composition_append (OttieComposition *self, + OttieLayer *layer) +{ + ottie_layer_list_append (&self->layers, layer); + if (layer->index != OTTIE_INT_UNSET) + g_hash_table_insert (self->layers_by_index, GINT_TO_POINTER (layer->index), layer); + g_list_model_items_changed (G_LIST_MODEL (self), ottie_layer_list_get_size (&self->layers), 0, 1); +} + +static gboolean +ottie_composition_parse_layer (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieComposition *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_composition_layer_parse (reader); + break; + + case 3: + layer = ottie_null_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_composition_append (self, layer); + + return TRUE; +} + +gboolean +ottie_composition_parse_layers (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieComposition **target = (OttieComposition **) ((guint8 *) data + offset); + OttieComposition *self; + + self = g_object_new (OTTIE_TYPE_COMPOSITION, NULL); + + if (!ottie_parser_parse_array (reader, "layers", + 0, G_MAXUINT, NULL, + 0, 0, + ottie_composition_parse_layer, + self)) + { + g_object_unref (self); + return FALSE; + } + + g_clear_object (target); + *target = self; + + return TRUE; +} + diff --git a/ottie/ottiecompositionlayer.c b/ottie/ottiecompositionlayer.c new file mode 100644 index 0000000000..c3efb98a6b --- /dev/null +++ b/ottie/ottiecompositionlayer.c @@ -0,0 +1,153 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiecompositionlayerprivate.h" + +#include "ottiedoublevalueprivate.h" + +#include +#include + +struct _OttieCompositionLayer +{ + OttieLayer parent; + + OttieDoubleValue time_map; + double width; + double height; + char *ref_id; + OttieComposition *composition; +}; + +struct _OttieCompositionLayerClass +{ + OttieLayerClass parent_class; +}; + +G_DEFINE_TYPE (OttieCompositionLayer, ottie_composition_layer, OTTIE_TYPE_LAYER) + +static void +ottie_composition_layer_update (OttieLayer *layer, + GHashTable *compositions) +{ + OttieCompositionLayer *self = OTTIE_COMPOSITION_LAYER (layer); + + g_clear_object (&self->composition); + + if (self->ref_id) + self->composition = g_object_ref (g_hash_table_lookup (compositions, self->ref_id)); +} + +static void +ottie_composition_layer_render (OttieLayer *layer, + OttieRender *render, + double timestamp) +{ + OttieCompositionLayer *self = OTTIE_COMPOSITION_LAYER (layer); + GskRenderNode *node; + double time_map; + + if (self->composition == NULL) + return; + + if (ottie_double_value_is_static (&self->time_map)) + time_map = timestamp; + else + time_map = ottie_double_value_get (&self->time_map, timestamp); + + ottie_layer_render (OTTIE_LAYER (self->composition), + render, + time_map); + + node = ottie_render_get_node (render); + ottie_render_clear_nodes (render); + if (node) + { + ottie_render_add_node (render, + gsk_clip_node_new (node, + &GRAPHENE_RECT_INIT ( + 0, 0, + self->width, self->height + ))); + gsk_render_node_unref (node); + } +} + +static void +ottie_composition_layer_dispose (GObject *object) +{ + OttieCompositionLayer *self = OTTIE_COMPOSITION_LAYER (object); + + g_clear_object (&self->composition); + g_clear_pointer (&self->ref_id, g_free); + ottie_double_value_clear (&self->time_map); + + G_OBJECT_CLASS (ottie_composition_layer_parent_class)->dispose (object); +} + +static void +ottie_composition_layer_class_init (OttieCompositionLayerClass *klass) +{ + OttieLayerClass *layer_class = OTTIE_LAYER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + layer_class->update = ottie_composition_layer_update; + layer_class->render = ottie_composition_layer_render; + + gobject_class->dispose = ottie_composition_layer_dispose; +} + +static void +ottie_composition_layer_init (OttieCompositionLayer *self) +{ + ottie_double_value_init (&self->time_map, 0); +} + +OttieLayer * +ottie_composition_layer_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_LAYER, + { "refId", ottie_parser_option_string, G_STRUCT_OFFSET (OttieCompositionLayer, ref_id) }, + { "tm", ottie_double_value_parse, 0 }, + { "w", ottie_parser_option_double, G_STRUCT_OFFSET (OttieCompositionLayer, width) }, + { "h", ottie_parser_option_double, G_STRUCT_OFFSET (OttieCompositionLayer, height) }, + }; + OttieCompositionLayer *self; + + self = g_object_new (OTTIE_TYPE_COMPOSITION_LAYER, NULL); + + if (!ottie_parser_parse_object (reader, "composition layer", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_LAYER (self); +} + +OttieComposition * +ottie_composition_layer_get_composition (OttieCompositionLayer *self) +{ + g_return_val_if_fail (OTTIE_IS_COMPOSITION_LAYER (self), NULL); + + return self->composition; +} diff --git a/ottie/ottiecompositionlayerprivate.h b/ottie/ottiecompositionlayerprivate.h new file mode 100644 index 0000000000..5d5b44e521 --- /dev/null +++ b/ottie/ottiecompositionlayerprivate.h @@ -0,0 +1,49 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_COMPOSITION_LAYER_PRIVATE_H__ +#define __OTTIE_COMPOSITION_LAYER_PRIVATE_H__ + +#include "ottielayerprivate.h" + +#include "ottiecompositionprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_COMPOSITION_LAYER (ottie_composition_layer_get_type ()) +#define OTTIE_COMPOSITION_LAYER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_COMPOSITION_LAYER, OttieCompositionLayer)) +#define OTTIE_COMPOSITION_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_COMPOSITION_LAYER, OttieCompositionLayerClass)) +#define OTTIE_IS_COMPOSITION_LAYER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_COMPOSITION_LAYER)) +#define OTTIE_IS_COMPOSITION_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_COMPOSITION_LAYER)) +#define OTTIE_COMPOSITION_LAYER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_COMPOSITION_LAYER, OttieCompositionLayerClass)) + +typedef struct _OttieCompositionLayer OttieCompositionLayer; +typedef struct _OttieCompositionLayerClass OttieCompositionLayerClass; + +GType ottie_composition_layer_get_type (void) G_GNUC_CONST; + +OttieComposition * ottie_composition_layer_get_composition (OttieCompositionLayer *self); + +OttieLayer * ottie_composition_layer_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_COMPOSITION_LAYER_PRIVATE_H__ */ diff --git a/ottie/ottiecompositionprivate.h b/ottie/ottiecompositionprivate.h new file mode 100644 index 0000000000..883440f69f --- /dev/null +++ b/ottie/ottiecompositionprivate.h @@ -0,0 +1,47 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_COMPOSITION_PRIVATE_H__ +#define __OTTIE_COMPOSITION_PRIVATE_H__ + +#include "ottielayerprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_COMPOSITION (ottie_composition_get_type ()) +#define OTTIE_COMPOSITION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_COMPOSITION, OttieComposition)) +#define OTTIE_COMPOSITION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_COMPOSITION, OttieCompositionClass)) +#define OTTIE_IS_COMPOSITION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_COMPOSITION)) +#define OTTIE_IS_COMPOSITION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_COMPOSITION)) +#define OTTIE_COMPOSITION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_COMPOSITION, OttieCompositionClass)) + +typedef struct _OttieComposition OttieComposition; +typedef struct _OttieCompositionClass OttieCompositionClass; + +GType ottie_composition_get_type (void) G_GNUC_CONST; + +gboolean ottie_composition_parse_layers (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_COMPOSITION_PRIVATE_H__ */ diff --git a/ottie/ottiecreation.c b/ottie/ottiecreation.c new file mode 100644 index 0000000000..3b7f1564dd --- /dev/null +++ b/ottie/ottiecreation.c @@ -0,0 +1,746 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiecreationprivate.h" + +#include "ottielayerprivate.h" +#include "ottieparserprivate.h" +#include "ottiecompositionprivate.h" + +#include +#include + +struct _OttieCreation +{ + GObject parent; + + char *name; + double frame_rate; + double start_frame; + double end_frame; + double width; + double height; + + OttieComposition *layers; + GHashTable *composition_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->composition_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->composition_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->composition_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; + OttieComposition *composition; +} 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_composition_parse_layers, G_STRUCT_OFFSET (OttieParserAsset, composition) }, + }; + 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.composition == NULL) + ottie_parser_error_syntax (reader, "No composition layer or image asset defined for name %s", asset.id); + else + g_hash_table_insert (self->composition_assets, g_strdup (asset.id), g_object_ref (asset.composition)); + } + + g_clear_pointer (&asset.id, g_free); + g_clear_object (&asset.composition); + + 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_composition_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_update_layers (OttieCreation *self) +{ + GHashTableIter iter; + gpointer layer; + + g_hash_table_iter_init (&iter, self->composition_assets); + + while (g_hash_table_iter_next (&iter, NULL, &layer)) + ottie_layer_update (layer, self->composition_assets); + + ottie_layer_update (OTTIE_LAYER (self->layers), self->composition_assets); +} + +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 gboolean +ottie_creation_load_from_node (OttieCreation *self, + JsonNode *root) +{ + JsonReader *reader = json_reader_new (root); + gboolean result; + + result = ottie_creation_load_from_reader (self, reader); + + g_object_unref (reader); + + return result; +} + +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)); + + if (ottie_creation_load_from_node (self, json_parser_get_root (JSON_PARSER (parser)))) + { + ottie_creation_update_layers (self); + } + else + { + ottie_creation_reset (self); + } + + 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_bytes (OttieCreation *self, + GBytes *bytes) +{ + GError *error = NULL; + JsonParser *parser; + + g_return_if_fail (OTTIE_IS_CREATION (self)); + g_return_if_fail (bytes != NULL); + + g_object_freeze_notify (G_OBJECT (self)); + + ottie_creation_stop_loading (self, FALSE); + ottie_creation_reset (self); + + parser = json_parser_new (); + if (json_parser_load_from_data (parser, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + &error)) + { + if (ottie_creation_load_from_node (self, json_parser_get_root (JSON_PARSER (parser)))) + { + ottie_creation_update_layers (self); + } + else + { + ottie_creation_reset (self); + } + } + else + { + ottie_creation_emit_error (self, error); + g_error_free (error); + } + + g_object_unref (parser); + + ottie_creation_notify_prepared (self); + + g_object_thaw_notify (G_OBJECT (self)); +} + +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) +{ + GskRenderNode *node; + OttieRender render; + + if (self->layers == NULL) + return; + + timestamp = timestamp * self->frame_rate; + + ottie_render_init (&render); + + ottie_layer_render (OTTIE_LAYER (self->layers), &render, timestamp); + node = ottie_render_get_node (&render); + if (node) + { + gtk_snapshot_append_node (snapshot, node); + gsk_render_node_unref (node); + } + + ottie_render_clear (&render); +} + +OttieComposition * +ottie_creation_get_composition (OttieCreation *self) +{ + return self->layers; +} + diff --git a/ottie/ottiecreation.h b/ottie/ottiecreation.h new file mode 100644 index 0000000000..ad795ed8ee --- /dev/null +++ b/ottie/ottiecreation.h @@ -0,0 +1,83 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_CREATION_H__ +#define __OTTIE_CREATION_H__ + +#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_CREATION (ottie_creation_get_type ()) +#define OTTIE_CREATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_CREATION, OttieCreation)) +#define OTTIE_CREATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_CREATION, OttieCreationClass)) +#define OTTIE_IS_CREATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_CREATION)) +#define OTTIE_IS_CREATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_CREATION)) +#define OTTIE_CREATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_CREATION, OttieCreationClass)) + +typedef struct _OttieCreation OttieCreation; +typedef struct _OttieCreationClass OttieCreationClass; + +GDK_AVAILABLE_IN_ALL +GType ottie_creation_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_creation_new (void); +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_creation_new_for_file (GFile *file); +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_creation_new_for_filename (const char *filename); + +GDK_AVAILABLE_IN_ALL +void ottie_creation_load_file (OttieCreation *self, + GFile *file); +GDK_AVAILABLE_IN_ALL +void ottie_creation_load_filename (OttieCreation *self, + const char *filename); +GDK_AVAILABLE_IN_ALL +void ottie_creation_load_bytes (OttieCreation *self, + GBytes *bytes); + +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..c59091b723 --- /dev/null +++ b/ottie/ottiecreationprivate.h @@ -0,0 +1,40 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_CREATION_PRIVATE_H__ +#define __OTTIE_CREATION_PRIVATE_H__ + +#include "ottiecreation.h" + +#include "ottiecompositionprivate.h" + +#include + +G_BEGIN_DECLS + + +void ottie_creation_snapshot (OttieCreation *self, + GtkSnapshot *snapshot, + double timestamp); + +OttieComposition * ottie_creation_get_composition (OttieCreation *self); + +G_END_DECLS + +#endif /* __OTTIE_CREATION_PRIVATE_H__ */ diff --git a/ottie/ottiedoublevalue.c b/ottie/ottiedoublevalue.c new file mode 100644 index 0000000000..e3897ea033 --- /dev/null +++ b/ottie/ottiedoublevalue.c @@ -0,0 +1,121 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiedoublevalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_double_value_parse_value (JsonReader *reader, + gsize offset, + gpointer data) +{ + gboolean result, array; + + /* Lottie being Lottie, single values may get dumped into arrays. */ + array = json_reader_is_array (reader); + if (array) + json_reader_read_element (reader, 0); + + result = ottie_parser_option_double (reader, offset, data); + + if (array) + json_reader_end_element (reader); + + return result; +} + +static double +ottie_double_value_interpolate (double start, + double end, + double progress) +{ + return start + (end - start) * progress; +} + +#define OTTIE_KEYFRAMES_NAME ottie_double_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttieDoubleKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE double +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_double_value_parse_value +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_double_value_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_double_value_init (OttieDoubleValue *self, + double value) +{ + self->is_static = TRUE; + self->static_value = value; +} + +void +ottie_double_value_clear (OttieDoubleValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_double_keyframes_free); +} + +double +ottie_double_value_get (OttieDoubleValue *self, + double timestamp) +{ + if (self->is_static) + return self->static_value; + + return ottie_double_keyframes_get (self->keyframes, timestamp); +} + +gboolean +ottie_double_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieDoubleValue *self = (OttieDoubleValue *) ((guint8 *) data + GPOINTER_TO_SIZE (offset)); + + if (json_reader_read_member (reader, "k")) + { + if (!json_reader_is_array (reader)) + { + self->is_static = TRUE; + self->static_value = json_reader_get_double_value (reader); + } + else + { + self->is_static = FALSE; + self->keyframes = ottie_double_keyframes_parse (reader); + if (self->keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + } + } + else + { + ottie_parser_error_syntax (reader, "Property is not a number"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiedoublevalueprivate.h b/ottie/ottiedoublevalueprivate.h new file mode 100644 index 0000000000..ed33e9227b --- /dev/null +++ b/ottie/ottiedoublevalueprivate.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_DOUBLE_VALUE_PRIVATE_H__ +#define __OTTIE_DOUBLE_VALUE_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _OttieDoubleValue OttieDoubleValue; + +struct _OttieDoubleValue +{ + gboolean is_static; + union { + double static_value; + gpointer keyframes; + }; +}; + +void ottie_double_value_init (OttieDoubleValue *self, + double value); +void ottie_double_value_clear (OttieDoubleValue *self); + +static inline gboolean ottie_double_value_is_static (OttieDoubleValue *self); +double ottie_double_value_get (OttieDoubleValue *self, + double timestamp); + +gboolean ottie_double_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +static inline gboolean +ottie_double_value_is_static (OttieDoubleValue *self) +{ + return self->is_static; +} + +G_END_DECLS + +#endif /* __OTTIE_DOUBLE_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottieellipseshape.c b/ottie/ottieellipseshape.c new file mode 100644 index 0000000000..e26e96b02b --- /dev/null +++ b/ottie/ottieellipseshape.c @@ -0,0 +1,138 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieellipseshapeprivate.h" + +#include "ottiedoublevalueprivate.h" +#include "ottiepointvalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include + +struct _OttieEllipseShape +{ + OttieShape parent; + + double diellipseion; + OttiePointValue position; + OttiePointValue size; +}; + +struct _OttieEllipseShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieEllipseShape, ottie_ellipse_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_ellipse_shape_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttieEllipseShape *self = OTTIE_ELLIPSE_SHAPE (shape); + graphene_point_t p, s; + GskPathBuilder *builder; + const float weight = sqrt(0.5f); + + ottie_point_value_get (&self->position, timestamp, &p); + ottie_point_value_get (&self->size, timestamp, &s); + s.x /= 2; + s.y /= 2; + + builder = gsk_path_builder_new (); + + gsk_path_builder_move_to (builder, + p.x, p.y - s.y); + gsk_path_builder_conic_to (builder, + p.x + s.x, p.y - s.y, + p.x + s.x, p.y, + weight); + gsk_path_builder_conic_to (builder, + p.x + s.x, p.y + s.y, + p.x, p.y + s.y, + weight); + gsk_path_builder_conic_to (builder, + p.x - s.x, p.y + s.y, + p.x - s.x, p.y, + weight); + gsk_path_builder_conic_to (builder, + p.x - s.x, p.y - s.y, + p.x, p.y - s.y, + weight); + gsk_path_builder_close (builder); + + ottie_render_add_path (render, + gsk_path_builder_free_to_path (builder)); +} + +static void +ottie_ellipse_shape_dispose (GObject *object) +{ + OttieEllipseShape *self = OTTIE_ELLIPSE_SHAPE (object); + + ottie_point_value_clear (&self->position); + ottie_point_value_clear (&self->size); + + G_OBJECT_CLASS (ottie_ellipse_shape_parent_class)->dispose (object); +} + +static void +ottie_ellipse_shape_class_init (OttieEllipseShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_ellipse_shape_render; + + gobject_class->dispose = ottie_ellipse_shape_dispose; +} + +static void +ottie_ellipse_shape_init (OttieEllipseShape *self) +{ + ottie_point_value_init (&self->position, &GRAPHENE_POINT_INIT (0, 0)); + ottie_point_value_init (&self->size, &GRAPHENE_POINT_INIT (0, 0)); +} + +OttieShape * +ottie_ellipse_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE, + { "d", ottie_parser_option_double, G_STRUCT_OFFSET (OttieEllipseShape, diellipseion) }, + { "p", ottie_point_value_parse, G_STRUCT_OFFSET (OttieEllipseShape, position) }, + { "s", ottie_point_value_parse, G_STRUCT_OFFSET (OttieEllipseShape, size) }, + }; + OttieEllipseShape *self; + + self = g_object_new (OTTIE_TYPE_ELLIPSE_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "ellipse shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottieellipseshapeprivate.h b/ottie/ottieellipseshapeprivate.h new file mode 100644 index 0000000000..0ec6b8ca28 --- /dev/null +++ b/ottie/ottieellipseshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_ELLIPSE_SHAPE_PRIVATE_H__ +#define __OTTIE_ELLIPSE_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_ELLIPSE_SHAPE (ottie_ellipse_shape_get_type ()) +#define OTTIE_ELLIPSE_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_ELLIPSE_SHAPE, OttieEllipseShape)) +#define OTTIE_ELLIPSE_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_ELLIPSE_SHAPE, OttieEllipseShapeClass)) +#define OTTIE_IS_ELLIPSE_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_ELLIPSE_SHAPE)) +#define OTTIE_IS_ELLIPSE_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_ELLIPSE_SHAPE)) +#define OTTIE_ELLIPSE_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_ELLIPSE_SHAPE, OttieEllipseShapeClass)) + +typedef struct _OttieEllipseShape OttieEllipseShape; +typedef struct _OttieEllipseShapeClass OttieEllipseShapeClass; + +GType ottie_ellipse_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_ellipse_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_ELLIPSE_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottiefillshape.c b/ottie/ottiefillshape.c new file mode 100644 index 0000000000..f26ca9ee53 --- /dev/null +++ b/ottie/ottiefillshape.c @@ -0,0 +1,132 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiefillshapeprivate.h" + +#include "ottiecolorvalueprivate.h" +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +struct _OttieFillShape +{ + OttieShape parent; + + OttieDoubleValue opacity; + OttieColorValue color; + GskBlendMode blend_mode; + GskFillRule fill_rule; +}; + +struct _OttieFillShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieFillShape, ottie_fill_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_fill_shape_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttieFillShape *self = OTTIE_FILL_SHAPE (shape); + GskPath *path; + graphene_rect_t bounds; + GdkRGBA color; + double opacity; + GskRenderNode *color_node; + + 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_render_get_path (render); + if (gsk_path_is_empty (path)) + return; + + gsk_path_get_bounds (path, &bounds); + color_node = gsk_color_node_new (&color, &bounds); + + ottie_render_add_node (render, gsk_fill_node_new (color_node, path, self->fill_rule)); + + gsk_render_node_unref (color_node); +} + +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_class_init (OttieFillShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_fill_shape_render; + + 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 }); + self->fill_rule = GSK_FILL_RULE_WINDING; +} + +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) }, + { "r", ottie_parser_option_fill_rule, G_STRUCT_OFFSET (OttieFillShape, fill_rule) }, + }; + OttieFillShape *self; + + self = g_object_new (OTTIE_TYPE_FILL_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "fill shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottiefillshapeprivate.h b/ottie/ottiefillshapeprivate.h new file mode 100644 index 0000000000..ea8889f2eb --- /dev/null +++ b/ottie/ottiefillshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_FILL_SHAPE_PRIVATE_H__ +#define __OTTIE_FILL_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_FILL_SHAPE (ottie_fill_shape_get_type ()) +#define OTTIE_FILL_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_FILL_SHAPE, OttieFillShape)) +#define OTTIE_FILL_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_FILL_SHAPE, OttieFillShapeClass)) +#define OTTIE_IS_FILL_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_FILL_SHAPE)) +#define OTTIE_IS_FILL_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_FILL_SHAPE)) +#define OTTIE_FILL_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_FILL_SHAPE, OttieFillShapeClass)) + +typedef struct _OttieFillShape OttieFillShape; +typedef struct _OttieFillShapeClass OttieFillShapeClass; + +GType ottie_fill_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_fill_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_FILL_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottiegroupshape.c b/ottie/ottiegroupshape.c new file mode 100644 index 0000000000..22cec48f6c --- /dev/null +++ b/ottie/ottiegroupshape.c @@ -0,0 +1,247 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiegroupshapeprivate.h" + +#include "ottieellipseshapeprivate.h" +#include "ottiefillshapeprivate.h" +#include "ottieparserprivate.h" +#include "ottiepathshapeprivate.h" +#include "ottierectshapeprivate.h" +#include "ottieshapeprivate.h" +#include "ottiestrokeshapeprivate.h" +#include "ottietransformprivate.h" +#include "ottietrimshapeprivate.h" + +#include +#include + +#define GDK_ARRAY_ELEMENT_TYPE OttieShape * +#define GDK_ARRAY_FREE_FUNC g_object_unref +#define GDK_ARRAY_TYPE_NAME OttieShapeList +#define GDK_ARRAY_NAME ottie_shape_list +#define GDK_ARRAY_PREALLOC 4 +#include "gdk/gdkarrayimpl.c" + +struct _OttieGroupShape +{ + OttieShape parent; + + OttieShapeList shapes; + GskBlendMode blend_mode; +}; + +struct _OttieGroupShapeClass +{ + OttieShapeClass parent_class; +}; + +static GType +ottie_group_shape_get_item_type (GListModel *list) +{ + return OTTIE_TYPE_SHAPE; +} + +static guint +ottie_group_shape_get_n_items (GListModel *list) +{ + OttieGroupShape *self = OTTIE_GROUP_SHAPE (list); + + return ottie_shape_list_get_size (&self->shapes); +} + +static gpointer +ottie_group_shape_get_item (GListModel *list, + guint position) +{ + OttieGroupShape *self = OTTIE_GROUP_SHAPE (list); + + if (position >= ottie_shape_list_get_size (&self->shapes)) + return NULL; + + return g_object_ref (ottie_shape_list_get (&self->shapes, position)); +} + +static void +ottie_group_shape_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = ottie_group_shape_get_item_type; + iface->get_n_items = ottie_group_shape_get_n_items; + iface->get_item = ottie_group_shape_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (OttieGroupShape, ottie_group_shape, OTTIE_TYPE_SHAPE, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, ottie_group_shape_list_model_init)) + +static void +ottie_group_shape_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttieGroupShape *self = OTTIE_GROUP_SHAPE (shape); + OttieRender child_render; + + ottie_render_init (&child_render); + + for (gsize i = 0; i < ottie_shape_list_get_size (&self->shapes); i++) + { + ottie_shape_render (ottie_shape_list_get (&self->shapes, i), + &child_render, + timestamp); + } + + ottie_render_merge (render, &child_render); + + ottie_render_clear (&child_render); +} + +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_class_init (OttieGroupShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_group_shape_render; + + 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, "el")) + shape = ottie_ellipse_shape_parse (reader); + else if (g_str_equal (type, "fl")) + shape = ottie_fill_shape_parse (reader); + else if (g_str_equal (type, "gr")) + shape = ottie_group_shape_parse (reader); + else if (g_str_equal (type, "rc")) + shape = ottie_rect_shape_parse (reader); + else if (g_str_equal (type, "sh")) + shape = ottie_path_shape_parse (reader); + else if (g_str_equal (type, "st")) + shape = ottie_stroke_shape_parse (reader); + else if (g_str_equal (type, "tm")) + shape = ottie_trim_shape_parse (reader); + else if (g_str_equal (type, "tr")) + shape = ottie_transform_parse (reader); + else + { + ottie_parser_error_value (reader, "Shape %d has unknown type \"%s\"", i, type); + shape = NULL; + } + + if (shape) + ottie_shape_list_append (&self->shapes, shape); + json_reader_end_element (reader); + } + + json_reader_end_element (reader); + + return TRUE; +} + +OttieShape * +ottie_group_shape_new (void) +{ + return g_object_new (OTTIE_TYPE_GROUP_SHAPE, NULL); +} + +OttieShape * +ottie_group_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE, + { "bm", ottie_parser_option_blend_mode, G_STRUCT_OFFSET (OttieGroupShape, blend_mode) }, + { "np", ottie_parser_option_skip_expression, 0 }, + { "cix", ottie_parser_option_skip_index, 0 }, + { "it", ottie_group_shape_parse_shapes, 0 }, + }; + OttieShape *self; + + self = ottie_group_shape_new (); + + if (!ottie_parser_parse_object (reader, "group shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return self; +} + diff --git a/ottie/ottiegroupshapeprivate.h b/ottie/ottiegroupshapeprivate.h new file mode 100644 index 0000000000..d0f9b02e88 --- /dev/null +++ b/ottie/ottiegroupshapeprivate.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_GROUP_SHAPE_PRIVATE_H__ +#define __OTTIE_GROUP_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_GROUP_SHAPE (ottie_group_shape_get_type ()) +#define OTTIE_GROUP_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShape)) +#define OTTIE_GROUP_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShapeClass)) +#define OTTIE_IS_GROUP_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_GROUP_SHAPE)) +#define OTTIE_IS_GROUP_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_GROUP_SHAPE)) +#define OTTIE_GROUP_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShapeClass)) + +typedef struct _OttieGroupShape OttieGroupShape; +typedef struct _OttieGroupShapeClass OttieGroupShapeClass; + +GType ottie_group_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_group_shape_new (void); + +OttieShape * ottie_group_shape_parse (JsonReader *reader); +gboolean ottie_group_shape_parse_shapes (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_GROUP_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottieintl.h b/ottie/ottieintl.h new file mode 100644 index 0000000000..310b720660 --- /dev/null +++ b/ottie/ottieintl.h @@ -0,0 +1,15 @@ +#ifndef __OTTIE_INTL_H__ +#define __OTTIE_INTL_H__ + +#include + +#ifdef ENABLE_NLS +#define P_(String) g_dgettext(GETTEXT_PACKAGE "-properties",String) +#else +#define P_(String) (String) +#endif + +/* not really I18N-related, but also a string marker macro */ +#define I_(string) g_intern_static_string (string) + +#endif diff --git a/ottie/ottiekeyframesimpl.c b/ottie/ottiekeyframesimpl.c new file mode 100644 index 0000000000..255d17e3a4 --- /dev/null +++ b/ottie/ottiekeyframesimpl.c @@ -0,0 +1,382 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include + +G_BEGIN_DECLS + +#ifndef OTTIE_KEYFRAMES_TYPE_NAME +#define OTTIE_KEYFRAMES_TYPE_NAME OttieKeyframes +#endif + +#ifndef OTTIE_KEYFRAMES_NAME +#define OTTIE_KEYFRAMES_NAME ottie_keyframes +#endif + +#ifndef OTTIE_KEYFRAMES_ELEMENT_TYPE +#define OTTIE_KEYFRAMES_ELEMENT_TYPE gpointer +#endif + +#ifndef OTTIE_KEYFRAMES_DIMENSIONS +#define OTTIE_KEYFRAMES_DIMENSIONS 1 +#endif + +/* make this readable */ +#define _T_ OTTIE_KEYFRAMES_ELEMENT_TYPE +#define OttieKeyframes OTTIE_KEYFRAMES_TYPE_NAME +#define OttieKeyframe OTTIE_KEYFRAMES_TYPE_NAME ## Keyframe +#define OttieControlPoint OTTIE_KEYFRAMES_TYPE_NAME ## ControlPoint +#define ottie_keyframes_paste_more(OTTIE_KEYFRAMES_NAME, func_name) OTTIE_KEYFRAMES_NAME ## _ ## func_name +#define ottie_keyframes_paste(OTTIE_KEYFRAMES_NAME, func_name) ottie_keyframes_paste_more (OTTIE_KEYFRAMES_NAME, func_name) +#define ottie_keyframes(func_name) ottie_keyframes_paste (OTTIE_KEYFRAMES_NAME, func_name) + +typedef struct OttieControlPoint OttieControlPoint; +typedef struct OttieKeyframe OttieKeyframe; +typedef struct OttieKeyframes OttieKeyframes; + +struct OttieControlPoint +{ + double x[OTTIE_KEYFRAMES_DIMENSIONS]; + double y[OTTIE_KEYFRAMES_DIMENSIONS]; +}; + +struct OttieKeyframe +{ + /* Cubic control points, but Lottie names them in and out points */ + OttieControlPoint in; + OttieControlPoint out; + double start_time; + _T_ start_value; + _T_ end_value; +}; + +struct OttieKeyframes +{ + gsize n_items; + OttieKeyframe items[]; +}; + +static inline OttieKeyframes * +ottie_keyframes(new) (gsize n_items) +{ + OttieKeyframes *self; + + self = g_malloc0 (sizeof (OttieKeyframes) + n_items * sizeof (OttieKeyframe)); + self->n_items = n_items; + + return self; +} + +static inline void +ottie_keyframes(free_value) (_T_ *item) +{ +#ifdef OTTIE_KEYFRAMES_FREE_FUNC +#ifdef OTTIE_KEYFRAMES_BY_VALUE + OTTIE_KEYFRAMES_FREE_FUNC (item); +#else + OTTIE_KEYFRAMES_FREE_FUNC (*item); +#endif +#endif +} + +static inline void +ottie_keyframes(copy_value) (_T_ *dest, + _T_ *src) +{ +#ifdef OTTIE_KEYFRAMES_COPY_FUNC +# ifdef OTTIE_KEYFRAMES_BY_VALUE + OTTIE_KEYFRAMES_COPY_FUNC (dest, src); +# else + *dest = OTTIE_KEYFRAMES_COPY_FUNC (*src); +# endif +#else + *dest = *src; +#endif +} + +/* no G_GNUC_UNUSED here */ +static inline void +ottie_keyframes(free) (OttieKeyframes *self) +{ +#ifdef OTTIE_KEYFRAMES_FREE_FUNC + gsize i; + + for (i = 0; i < self->n_items; i++) + { + ottie_keyframes(free_value) (&self->items[i].start_value); + ottie_keyframes(free_value) (&self->items[i].end_value); + } +#endif +} + +static +#ifdef OTTIE_KEYFRAMES_BY_VALUE +void +#else +_T_ +#endif +ottie_keyframes(get) (const OttieKeyframes *self, + double timestamp +#ifdef OTTIE_KEYFRAMES_BY_VALUE + , _T_ *out_result +#endif + ) +{ + const OttieKeyframe *keyframe; + gsize i; + + for (i = 0; i < self->n_items; i++) + { + if (self->items[i].start_time > timestamp) + break; + } + + if (i == 0 || i >= self->n_items) + { + keyframe = &self->items[i == 0 ? 0 : self->n_items - 1]; +#ifdef OTTIE_KEYFRAMES_BY_VALUE + *out_result = keyframe->start_value; + return; +#else + return keyframe->start_value; +#endif + } + + keyframe = &self->items[i - 1]; + + double progress = (timestamp - keyframe->start_time) / (self->items[i].start_time - keyframe->start_time); +#ifdef OTTIE_KEYFRAMES_BY_VALUE + OTTIE_KEYFRAMES_INTERPOLATE_FUNC (&keyframe->start_value, &keyframe->end_value, progress, out_result); +#else + return OTTIE_KEYFRAMES_INTERPOLATE_FUNC (keyframe->start_value, keyframe->end_value, progress); +#endif +} + +static gboolean +ottie_keyframes(parse_control_point_dimension) (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d[OTTIE_KEYFRAMES_DIMENSIONS]; + + if (json_reader_is_array (reader)) + { + if (json_reader_count_elements (reader) != OTTIE_KEYFRAMES_DIMENSIONS) + ottie_parser_error_value (reader, "control point has %d dimension, not %u", json_reader_count_elements (reader), OTTIE_KEYFRAMES_DIMENSIONS); + + for (int i = 0; i < OTTIE_KEYFRAMES_DIMENSIONS; i++) + { + if (!json_reader_read_element (reader, i)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + } + else + { + if (!ottie_parser_option_double (reader, 0, &d[i])) + d[i] = 0; + } + json_reader_end_element (reader); + } + } + else + { + if (!ottie_parser_option_double (reader, 0, &d[0])) + return FALSE; + + for (gsize i = 1; i < OTTIE_KEYFRAMES_DIMENSIONS; i++) + d[i] = d[0]; + } + + memcpy ((guint8 *) data + GPOINTER_TO_SIZE (offset), d, sizeof (double) * OTTIE_KEYFRAMES_DIMENSIONS); + return TRUE; +} + +static gboolean +ottie_keyframes(parse_control_point) (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieParserOption options[] = { + { "x", ottie_keyframes(parse_control_point_dimension), G_STRUCT_OFFSET (OttieControlPoint, x) }, + { "y", ottie_keyframes(parse_control_point_dimension), G_STRUCT_OFFSET (OttieControlPoint, y) }, + }; + OttieControlPoint cp; + OttieControlPoint *target = (OttieControlPoint *) ((guint8 *) data + offset); + + if (!ottie_parser_parse_object (reader, "control point", options, G_N_ELEMENTS (options), &cp)) + return FALSE; + + *target = cp; + return TRUE; +} + +typedef struct +{ + OttieKeyframe keyframe; + gboolean has_start_value; + gboolean has_end_value; +} OttieKeyframeParse; + +static gboolean +ottie_keyframes(parse_start_value) (JsonReader *reader, + gsize pos, + gpointer data) +{ + OttieKeyframeParse *parse = data; + + if (parse->has_start_value) + ottie_keyframes(free_value) (&parse->keyframe.start_value); + + parse->has_start_value = OTTIE_KEYFRAMES_PARSE_FUNC (reader, 0, &parse->keyframe.start_value); + return parse->has_start_value; +} + +static gboolean +ottie_keyframes(parse_end_value) (JsonReader *reader, + gsize pos, + gpointer data) +{ + OttieKeyframeParse *parse = data; + + if (parse->has_end_value) + ottie_keyframes(free_value) (&parse->keyframe.end_value); + + parse->has_end_value = OTTIE_KEYFRAMES_PARSE_FUNC (reader, 0, &parse->keyframe.end_value); + return parse->has_end_value; +} + +typedef struct +{ + OttieKeyframes *keyframes; + gboolean has_end_value; +} OttieKeyframesParse; + +static gboolean +ottie_keyframes(parse_keyframe) (JsonReader *reader, + gsize pos, + gpointer data) +{ + OttieParserOption options[] = { + { "s", ottie_keyframes(parse_start_value), 0, }, + { "e", ottie_keyframes(parse_end_value), 0, }, + { "t", ottie_parser_option_double, G_STRUCT_OFFSET (OttieKeyframe, start_time) }, + { "i", ottie_keyframes(parse_control_point), G_STRUCT_OFFSET (OttieKeyframe, in) }, + { "o", ottie_keyframes(parse_control_point), G_STRUCT_OFFSET (OttieKeyframe, out) }, + { "ix", ottie_parser_option_skip_index, 0 }, + }; + OttieKeyframesParse *self = data; + OttieKeyframeParse parse = { 0, }; + + if (!ottie_parser_parse_object (reader, "keyframe", options, G_N_ELEMENTS (options), &parse)) + goto fail; + + if (pos == 0) + { + if (!parse.has_start_value) + { + ottie_parser_error_syntax (reader, "First keyframe must have a start value"); + return FALSE; + } + } + else + { + if (parse.keyframe.start_time <= self->keyframes->items[pos - 1].start_time) + goto fail; + + if (!parse.has_start_value) + { + if (self->has_end_value) + { + ottie_keyframes(copy_value) (&parse.keyframe.start_value, &self->keyframes->items[pos - 1].end_value); + } + else + { + ottie_parser_error_syntax (reader, "Keyframe %zu has no end value and %zu has no start value.", pos - 1, pos); + goto fail; + } + } + + if (!self->has_end_value) + ottie_keyframes(copy_value) (&self->keyframes->items[pos - 1].end_value, &parse.keyframe.start_value); + } + + self->has_end_value = parse.has_end_value; + self->keyframes->items[pos] = parse.keyframe; + + return TRUE; + +fail: + self->keyframes->n_items = pos; + if (parse.has_start_value) + ottie_keyframes(free_value) (&parse.keyframe.start_value); + if (parse.has_end_value) + ottie_keyframes(free_value) (&parse.keyframe.end_value); + return FALSE; +} + +/* no G_GNUC_UNUSED here, if you don't use a type, remove it. */ +static inline OttieKeyframes * +ottie_keyframes(parse) (JsonReader *reader) +{ + OttieKeyframesParse parse; + OttieKeyframes *self; + + self = ottie_keyframes(new) (json_reader_count_elements (reader)); + + parse.keyframes = self; + parse.has_end_value = FALSE; + + if (!ottie_parser_parse_array (reader, "keyframes", + self->n_items, self->n_items, + NULL, + 0, 1, + ottie_keyframes(parse_keyframe), + &parse)) + { + /* do a dumb copy so the free has something to free */ + if (!parse.has_end_value && self->n_items > 0) + ottie_keyframes(copy_value) (&self->items[self->n_items - 1].end_value, + &self->items[self->n_items - 1].start_value); + ottie_keyframes(free) (self); + return NULL; + } + + if (!parse.has_end_value) + ottie_keyframes(copy_value) (&self->items[self->n_items - 1].end_value, + &self->items[self->n_items - 1].start_value); + + return parse.keyframes; +} + +#ifndef OTTIE_KEYFRAMES_NO_UNDEF + +#undef _T_ +#undef OttieKeyframes +#undef ottie_keyframes_paste_more +#undef ottie_keyframes_paste +#undef ottie_keyframes + +#undef OTTIE_KEYFRAMES_COPY_FUNC +#undef OTTIE_KEYFRAMES_PARSE_FUNC +#undef OTTIE_KEYFRAMES_BY_VALUE +#undef OTTIE_KEYFRAMES_ELEMENT_TYPE +#undef OTTIE_KEYFRAMES_FREE_FUNC +#undef OTTIE_KEYFRAMES_NAME +#undef OTTIE_KEYFRAMES_TYPE_NAME +#endif diff --git a/ottie/ottielayer.c b/ottie/ottielayer.c new file mode 100644 index 0000000000..0d88102150 --- /dev/null +++ b/ottie/ottielayer.c @@ -0,0 +1,96 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottielayerprivate.h" + +#include +#include + +G_DEFINE_TYPE (OttieLayer, ottie_layer, OTTIE_TYPE_OBJECT) + +static void +ottie_layer_default_update (OttieLayer *self, + GHashTable *compositions) +{ +} + +static void +ottie_layer_default_render (OttieLayer *self, + OttieRender *render, + double timestamp) +{ +} + +static void +ottie_layer_dispose (GObject *object) +{ + OttieLayer *self = OTTIE_LAYER (object); + + g_clear_object (&self->transform); + g_clear_pointer (&self->layer_name, g_free); + + G_OBJECT_CLASS (ottie_layer_parent_class)->dispose (object); +} + +static void +ottie_layer_class_init (OttieLayerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + klass->update = ottie_layer_default_update; + klass->render = ottie_layer_default_render; + + gobject_class->dispose = ottie_layer_dispose; +} + +static void +ottie_layer_init (OttieLayer *self) +{ + self->start_frame = -G_MAXDOUBLE; + self->end_frame = G_MAXDOUBLE; + self->stretch = 1; + self->blend_mode = GSK_BLEND_MODE_DEFAULT; + self->parent_index = OTTIE_INT_UNSET; + self->index = OTTIE_INT_UNSET; +} + +void +ottie_layer_update (OttieLayer *self, + GHashTable *compositions) +{ + OTTIE_LAYER_GET_CLASS (self)->update (self, compositions); +} + +void +ottie_layer_render (OttieLayer *self, + OttieRender *render, + double timestamp) +{ + if (timestamp < self->start_frame || + timestamp > self->end_frame) + return; + + timestamp -= self->start_time; + timestamp /= self->stretch; + + OTTIE_LAYER_GET_CLASS (self)->render (self, render, timestamp); +} + diff --git a/ottie/ottielayerprivate.h b/ottie/ottielayerprivate.h new file mode 100644 index 0000000000..6d5007f4ea --- /dev/null +++ b/ottie/ottielayerprivate.h @@ -0,0 +1,93 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_LAYER_PRIVATE_H__ +#define __OTTIE_LAYER_PRIVATE_H__ + +#include "ottie/ottietransformprivate.h" +#include "ottie/ottieobjectprivate.h" +#include "ottie/ottieparserprivate.h" +#include "ottie/ottierenderprivate.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 +{ + OttieObject parent; + + OttieTransform *transform; + gboolean auto_orient; + GskBlendMode blend_mode; + int index; + int parent_index; + char *layer_name; + double start_frame; + double end_frame; + double start_time; + double stretch; +}; + +struct _OttieLayerClass +{ + OttieObjectClass parent_class; + + void (* update) (OttieLayer *layer, + GHashTable *compositions); + void (* render) (OttieLayer *layer, + OttieRender *render, + double timestamp); +}; + +GType ottie_layer_get_type (void) G_GNUC_CONST; + +void ottie_layer_update (OttieLayer *self, + GHashTable *compositions); +void ottie_layer_render (OttieLayer *self, + OttieRender *render, + double timestamp); + +#define OTTIE_PARSE_OPTIONS_LAYER \ + OTTIE_PARSE_OPTIONS_OBJECT, \ + { "ao", ottie_parser_option_boolean, G_STRUCT_OFFSET (OttieLayer, auto_orient) }, \ + { "bm", ottie_parser_option_blend_mode, G_STRUCT_OFFSET (OttieLayer, blend_mode) }, \ + { "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_int, G_STRUCT_OFFSET (OttieLayer, index) }, \ + { "parent", ottie_parser_option_int, G_STRUCT_OFFSET (OttieLayer, parent_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/ottienulllayer.c b/ottie/ottienulllayer.c new file mode 100644 index 0000000000..baf0f599ee --- /dev/null +++ b/ottie/ottienulllayer.c @@ -0,0 +1,66 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottienulllayerprivate.h" + +#include + +struct _OttieNullLayer +{ + OttieLayer parent; +}; + +struct _OttieNullLayerClass +{ + OttieLayerClass parent_class; +}; + +G_DEFINE_TYPE (OttieNullLayer, ottie_null_layer, OTTIE_TYPE_LAYER) + +static void +ottie_null_layer_class_init (OttieNullLayerClass *klass) +{ +} + +static void +ottie_null_layer_init (OttieNullLayer *self) +{ +} + +OttieLayer * +ottie_null_layer_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_LAYER, + }; + OttieNullLayer *self; + + self = g_object_new (OTTIE_TYPE_NULL_LAYER, NULL); + + if (!ottie_parser_parse_object (reader, "null layer", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_LAYER (self); +} + diff --git a/ottie/ottienulllayerprivate.h b/ottie/ottienulllayerprivate.h new file mode 100644 index 0000000000..40347a2741 --- /dev/null +++ b/ottie/ottienulllayerprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_NULL_LAYER_PRIVATE_H__ +#define __OTTIE_NULL_LAYER_PRIVATE_H__ + +#include "ottielayerprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_NULL_LAYER (ottie_null_layer_get_type ()) +#define OTTIE_NULL_LAYER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_NULL_LAYER, OttieNullLayer)) +#define OTTIE_NULL_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_NULL_LAYER, OttieNullLayerClass)) +#define OTTIE_IS_NULL_LAYER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_NULL_LAYER)) +#define OTTIE_IS_NULL_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_NULL_LAYER)) +#define OTTIE_NULL_LAYER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_NULL_LAYER, OttieNullLayerClass)) + +typedef struct _OttieNullLayer OttieNullLayer; +typedef struct _OttieNullLayerClass OttieNullLayerClass; + +GType ottie_null_layer_get_type (void) G_GNUC_CONST; + +OttieLayer * ottie_null_layer_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_NULL_LAYER_PRIVATE_H__ */ diff --git a/ottie/ottieobject.c b/ottie/ottieobject.c new file mode 100644 index 0000000000..5720555b77 --- /dev/null +++ b/ottie/ottieobject.c @@ -0,0 +1,175 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieobjectprivate.h" + +#include "ottieintl.h" + +enum { + PROP_0, + PROP_MATCH_NAME, + PROP_NAME, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +G_DEFINE_TYPE (OttieObject, ottie_object, G_TYPE_OBJECT) + +static void +ottie_object_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + OttieObject *self = OTTIE_OBJECT (object); + + switch (prop_id) + { + case PROP_MATCH_NAME: + ottie_object_set_match_name (self, g_value_get_string (value)); + break; + + case PROP_NAME: + ottie_object_set_name (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_object_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OttieObject *self = OTTIE_OBJECT (object); + + switch (prop_id) + { + case PROP_MATCH_NAME: + g_value_set_string (value, self->match_name); + break; + + case PROP_NAME: + g_value_set_string (value, self->name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_object_dispose (GObject *object) +{ + OttieObject *self = OTTIE_OBJECT (object); + + g_clear_pointer (&self->name, g_free); + g_clear_pointer (&self->match_name, g_free); + + G_OBJECT_CLASS (ottie_object_parent_class)->dispose (object); +} + +static void +ottie_object_class_init (OttieObjectClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = ottie_object_set_property; + gobject_class->get_property = ottie_object_get_property; + gobject_class->dispose = ottie_object_dispose; + + properties[PROP_NAME] = + g_param_spec_string ("name", + P_("Name"), + P_("User-given name"), + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + properties[PROP_MATCH_NAME] = + g_param_spec_string ("match-name", + P_("Match name"), + P_("Name for matching in scripts"), + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +ottie_object_init (OttieObject *self) +{ +} + +void +ottie_object_set_name (OttieObject *self, + const char *name) +{ + g_return_if_fail (OTTIE_IS_OBJECT (self)); + + if (g_strcmp0 (self->name, name) == 0) + return; + + g_free (self->name); + self->name = g_strdup (name); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]); +} + +const char * +ottie_object_get_name (OttieObject *self) +{ + g_return_val_if_fail (OTTIE_IS_OBJECT (self), NULL); + + return self->name; +} + +void +ottie_object_set_match_name (OttieObject *self, + const char *match_name) +{ + g_return_if_fail (OTTIE_IS_OBJECT (self)); + + if (g_strcmp0 (self->match_name, match_name) == 0) + return; + + g_free (self->match_name); + self->match_name = g_strdup (match_name); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MATCH_NAME]); +} + +const char * +ottie_object_get_match_name (OttieObject *self) +{ + g_return_val_if_fail (OTTIE_IS_OBJECT (self), NULL); + + return self->match_name; +} + + diff --git a/ottie/ottieobjectprivate.h b/ottie/ottieobjectprivate.h new file mode 100644 index 0000000000..1ea562afb9 --- /dev/null +++ b/ottie/ottieobjectprivate.h @@ -0,0 +1,68 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_OBJECT_PRIVATE_H__ +#define __OTTIE_OBJECT_PRIVATE_H__ + +#include + +#include "ottie/ottierenderprivate.h" + +G_BEGIN_DECLS + +#define OTTIE_TYPE_OBJECT (ottie_object_get_type ()) +#define OTTIE_OBJECT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_OBJECT, OttieObject)) +#define OTTIE_OBJECT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_OBJECT, OttieObjectClass)) +#define OTTIE_IS_OBJECT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_OBJECT)) +#define OTTIE_IS_OBJECT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_OBJECT)) +#define OTTIE_OBJECT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_OBJECT, OttieObjectClass)) + +typedef struct _OttieObject OttieObject; +typedef struct _OttieObjectClass OttieObjectClass; + +struct _OttieObject +{ + GObject parent; + + char *name; + char *match_name; +}; + +struct _OttieObjectClass +{ + GObjectClass parent_class; +}; + +GType ottie_object_get_type (void) G_GNUC_CONST; + +void ottie_object_set_name (OttieObject *self, + const char *name); +const char * ottie_object_get_name (OttieObject *self); + +void ottie_object_set_match_name (OttieObject *self, + const char *match_name); +const char * ottie_object_get_match_name (OttieObject *self); + +#define OTTIE_PARSE_OPTIONS_OBJECT \ + { "nm", ottie_parser_option_string, G_STRUCT_OFFSET (OttieObject, name) }, \ + { "mn", ottie_parser_option_string, G_STRUCT_OFFSET (OttieObject, match_name) } + +G_END_DECLS + +#endif /* __OTTIE_OBJECT_PRIVATE_H__ */ diff --git a/ottie/ottiepaintable.c b/ottie/ottiepaintable.c new file mode 100644 index 0000000000..27b273300b --- /dev/null +++ b/ottie/ottiepaintable.c @@ -0,0 +1,381 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepaintable.h" + +#include "ottiecreationprivate.h" + +#include +#include + +struct _OttiePaintable +{ + GObject parent_instance; + + OttieCreation *creation; + gint64 timestamp; +}; + +struct _OttiePaintableClass +{ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_CREATION, + PROP_DURATION, + PROP_TIMESTAMP, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +ottie_paintable_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + OttiePaintable *self = OTTIE_PAINTABLE (paintable); + double w, h, timestamp; + + if (!self->creation) + return; + + w = ottie_creation_get_width (self->creation); + h = ottie_creation_get_height (self->creation); + timestamp = (double) self->timestamp / G_USEC_PER_SEC; + + if (w != width || h != height) + { + gtk_snapshot_save (snapshot); + gtk_snapshot_scale (snapshot, width / w, height / h); + } + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, w, h)); + ottie_creation_snapshot (self->creation, snapshot, timestamp); + gtk_snapshot_pop (snapshot); + + if (w != width || h != height) + gtk_snapshot_restore (snapshot); +} + +static int +ottie_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + OttiePaintable *self = OTTIE_PAINTABLE (paintable); + + if (!self->creation) + return 0; + + return ceil (ottie_creation_get_width (self->creation)); +} + +static int +ottie_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + OttiePaintable *self = OTTIE_PAINTABLE (paintable); + + if (!self->creation) + return 0; + + return ceil (ottie_creation_get_height (self->creation)); + +} + +static void +ottie_paintable_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = ottie_paintable_paintable_snapshot; + iface->get_intrinsic_width = ottie_paintable_paintable_get_intrinsic_width; + iface->get_intrinsic_height = ottie_paintable_paintable_get_intrinsic_height; +} + +G_DEFINE_TYPE_EXTENDED (OttiePaintable, ottie_paintable, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + ottie_paintable_paintable_init)) + +static void +ottie_paintable_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + OttiePaintable *self = OTTIE_PAINTABLE (object); + + switch (prop_id) + { + case PROP_CREATION: + ottie_paintable_set_creation (self, g_value_get_object (value)); + break; + + case PROP_TIMESTAMP: + ottie_paintable_set_timestamp (self, g_value_get_int64 (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_paintable_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OttiePaintable *self = OTTIE_PAINTABLE (object); + + switch (prop_id) + { + case PROP_CREATION: + g_value_set_object (value, self->creation); + break; + + case PROP_DURATION: + g_value_set_int64 (value, ottie_paintable_get_duration (self)); + break; + + case PROP_TIMESTAMP: + g_value_set_int64 (value, self->timestamp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_paintable_prepared_cb (OttieCreation *creation, + GParamSpec *pspec, + OttiePaintable *self) +{ + gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); +} + +static void +ottie_paintable_unset_creation (OttiePaintable *self) +{ + if (self->creation == NULL) + return; + + g_signal_handlers_disconnect_by_func (self->creation, ottie_paintable_prepared_cb, self); + g_clear_object (&self->creation); +} + +static void +ottie_paintable_dispose (GObject *object) +{ + OttiePaintable *self = OTTIE_PAINTABLE (object); + + ottie_paintable_unset_creation (self); + + G_OBJECT_CLASS (ottie_paintable_parent_class)->dispose (object); +} + +static void +ottie_paintable_class_init (OttiePaintableClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = ottie_paintable_get_property; + gobject_class->set_property = ottie_paintable_set_property; + gobject_class->dispose = ottie_paintable_dispose; + + /** + * OttiePaintable:creation + * + * The displayed creation or %NULL. + */ + properties[PROP_CREATION] = + g_param_spec_object ("creation", + _("Creation"), + _("The displayed creation"), + OTTIE_TYPE_CREATION, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttiePaintable:duration + * + * Duration of the displayed creation + */ + properties[PROP_DURATION] = + g_param_spec_int64 ("duration", + _("Duration"), + _("Duration of the displayed creation"), + 0, G_MAXINT64, 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * OttiePaintable:timestamp + * + * At what timestamp to display the creation. + */ + properties[PROP_TIMESTAMP] = + g_param_spec_int64 ("timestmp", + _("Timestamp"), + _("At what timestamp to display the creation"), + 0, G_MAXINT64, 0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +ottie_paintable_init (OttiePaintable *self) +{ +} + +/** + * ottie_paintable_new: + * @creation: (allow-none) (transfer full): an #OttiePaintable or %NULL + * + * Creates a new Ottie paintable for the given @creation + * + * Returns: (transfer full) (type OttiePaintable): a new #OttiePaintable + **/ +OttiePaintable * +ottie_paintable_new (OttieCreation *creation) +{ + OttiePaintable *self; + + g_return_val_if_fail (creation == creation || OTTIE_IS_CREATION (creation), NULL); + + self = g_object_new (OTTIE_TYPE_PAINTABLE, + "creation", creation, + NULL); + + g_clear_object (&creation); + + return self; +} + +/** + * ottie_paintable_get_creation: + * @self: an #OttiePaintable + * + * Returns the creation that shown or %NULL + * if none. + * + * Returns: (transfer none) (nullable): the observed creation. + **/ +OttieCreation * +ottie_paintable_get_creation (OttiePaintable *self) +{ + g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), NULL); + + return self->creation; +} + +/** + * ottie_paintable_set_creation: + * @self: an #OttiePaintable + * @creation: (allow-none): the creation to show or %NULL + * + * Sets the creation that should be shown. + **/ +void +ottie_paintable_set_creation (OttiePaintable *self, + OttieCreation *creation) +{ + g_return_if_fail (OTTIE_IS_PAINTABLE (self)); + g_return_if_fail (creation == NULL || OTTIE_IS_CREATION (creation)); + + if (self->creation == creation) + return; + + ottie_paintable_unset_creation (self); + + self->creation = g_object_ref (creation); + g_signal_connect (creation, "notify::prepared", G_CALLBACK (ottie_paintable_prepared_cb), self); + + gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CREATION]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); +} + +/** + * ottie_paintable_get_timestamp: + * @self: an #OttiePaintable + * + * Gets the timestamp for the currently displayed image. + * + * Returns: the timestamp + **/ +gint64 +ottie_paintable_get_timestamp (OttiePaintable *self) +{ + g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), 0); + + return self->timestamp; +} + +/** + * ottie_paintable_set_timestamp: + * @self: an #OttiePaintable + * @timestamp: the timestamp to display + * + * Sets the timestamp to display the creation at. + **/ +void +ottie_paintable_set_timestamp (OttiePaintable *self, + gint64 timestamp) +{ + g_return_if_fail (OTTIE_IS_PAINTABLE (self)); + g_return_if_fail (timestamp >= 0); + + if (self->timestamp == timestamp) + return; + + self->timestamp = timestamp; + + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP]); +} + +/** + * ottie_paintable_get_duration: + * @self: an #OttiePaintable + * + * Gets the duration of the currently playing creation. + * + * Returns: The duration in usec. + **/ +gint64 +ottie_paintable_get_duration (OttiePaintable *self) +{ + g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), 0); + + if (self->creation == NULL) + return 0; + + return ceil (G_USEC_PER_SEC * ottie_creation_get_end_frame (self->creation) + / ottie_creation_get_frame_rate (self->creation)); +} + diff --git a/ottie/ottiepaintable.h b/ottie/ottiepaintable.h new file mode 100644 index 0000000000..6c818194a1 --- /dev/null +++ b/ottie/ottiepaintable.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PAINTABLE_H__ +#define __OTTIE_PAINTABLE_H__ + +#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PAINTABLE (ottie_paintable_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (OttiePaintable, ottie_paintable, OTTIE, PAINTABLE, GObject) + +GDK_AVAILABLE_IN_ALL +OttiePaintable * ottie_paintable_new (OttieCreation *creation); + +GDK_AVAILABLE_IN_ALL +OttieCreation * ottie_paintable_get_creation (OttiePaintable *self); +GDK_AVAILABLE_IN_ALL +void ottie_paintable_set_creation (OttiePaintable *self, + OttieCreation *creation); +GDK_AVAILABLE_IN_ALL +gint64 ottie_paintable_get_timestamp (OttiePaintable *self); +GDK_AVAILABLE_IN_ALL +void ottie_paintable_set_timestamp (OttiePaintable *self, + gint64 timestamp); +GDK_AVAILABLE_IN_ALL +gint64 ottie_paintable_get_duration (OttiePaintable *self); + +G_END_DECLS + +#endif /* __OTTIE_PAINTABLE_H__ */ diff --git a/ottie/ottieparser.c b/ottie/ottieparser.c new file mode 100644 index 0000000000..c0be81c951 --- /dev/null +++ b/ottie/ottieparser.c @@ -0,0 +1,592 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieparserprivate.h" + +#include "ottietransformprivate.h" + +#include + +void +ottie_parser_emit_error (JsonReader *reader, + const GError *error) +{ + g_printerr ("Ottie is sad: %s\n", error->message); +} + +void +ottie_parser_error_syntax (JsonReader *reader, + const char *format, + ...) +{ + va_list args; + GError *error; + + va_start (args, format); + error = g_error_new_valist (JSON_PARSER_ERROR, + JSON_PARSER_ERROR_INVALID_DATA, + format, args); + va_end (args); + + ottie_parser_emit_error (reader, error); + + g_error_free (error); +} + +void +ottie_parser_error_value (JsonReader *reader, + const char *format, + ...) +{ + va_list args; + GError *error; + + va_start (args, format); + error = g_error_new_valist (JSON_PARSER_ERROR, + JSON_PARSER_ERROR_INVALID_DATA, + format, args); + va_end (args); + + ottie_parser_emit_error (reader, error); + + g_error_free (error); +} + +void +ottie_parser_error_unsupported (JsonReader *reader, + const char *format, + ...) +{ + va_list args; + GError *error; + + va_start (args, format); + error = g_error_new_valist (JSON_PARSER_ERROR, + JSON_PARSER_ERROR_INVALID_DATA, + format, args); + va_end (args); + + ottie_parser_emit_error (reader, error); + + g_error_free (error); +} + +gboolean +ottie_parser_parse_array (JsonReader *reader, + const char *debug_name, + guint min_items, + guint max_items, + guint *out_n_items, + gsize start_offset, + gsize offset_multiplier, + OttieParseFunc func, + gpointer data) +{ + guint i; + + if (!json_reader_is_array (reader)) + { + if (min_items > 1) + { + ottie_parser_error_syntax (reader, "Expected an array when parsing %s", debug_name); + if (out_n_items) + *out_n_items = 0; + return FALSE; + } + else + { + if (!func (reader, start_offset, data)) + { + if (out_n_items) + *out_n_items = 0; + return FALSE; + } + if (out_n_items) + *out_n_items = 1; + return TRUE; + } + } + + if (json_reader_count_elements (reader) < min_items) + { + ottie_parser_error_syntax (reader, "%s needs %u items, but only %u given", + debug_name, min_items, json_reader_count_elements (reader)); + return FALSE; + } + max_items = MIN (max_items, json_reader_count_elements (reader)); + + for (i = 0; i < max_items; i++) + { + if (!json_reader_read_element (reader, i) || + !func (reader, start_offset + offset_multiplier * i, data)) + { + json_reader_end_element (reader); + if (out_n_items) + *out_n_items = i; + return FALSE; + } + + json_reader_end_element (reader); + } + + if (out_n_items) + *out_n_items = i; + return TRUE; +} + +gboolean +ottie_parser_parse_object (JsonReader *reader, + const char *debug_name, + const OttieParserOption *options, + gsize n_options, + gpointer data) +{ + if (!json_reader_is_object (reader)) + { + ottie_parser_error_syntax (reader, "Expected an object when parsing %s", debug_name); + return FALSE; + } + + for (int i = 0; ; i++) + { + const OttieParserOption *o = NULL; + const char *name; + + if (!json_reader_read_element (reader, i)) + break; + + name = json_reader_get_member_name (reader); + + for (gsize j = 0; j < n_options; j++) + { + o = &options[j]; + if (g_str_equal (o->name, name)) + break; + o = NULL; + } + + if (o) + { + if (!o->parse_func (reader, o->option_data, data)) + { + json_reader_end_element (reader); + return FALSE; + } + } + else + { + ottie_parser_error_unsupported (reader, "Unsupported %s property \"%s\"", debug_name, json_reader_get_member_name (reader)); + } + + json_reader_end_element (reader); + } + + json_reader_end_element (reader); + + return TRUE; +} + +gboolean +ottie_parser_option_skip (JsonReader *reader, + gsize offset, + gpointer data) +{ + return TRUE; +} + +gboolean +ottie_parser_option_boolean (JsonReader *reader, + gsize offset, + gpointer data) +{ + gboolean b; + + b = json_reader_get_boolean_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + json_reader_end_element (reader); + return FALSE; + } + + *(gboolean *) ((guint8 *) data + offset) = b; + + return TRUE; +} + +gboolean +ottie_parser_option_double (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d; + + d = json_reader_get_double_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + *(double *) ((guint8 *) data + offset) = d; + + return TRUE; +} + +gboolean +ottie_parser_option_int (JsonReader *reader, + gsize offset, + gpointer data) +{ + 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; + } + + if (i > G_MAXINT || i < G_MININT) + { + ottie_parser_error_value (reader, "Integer value %"G_GINT64_FORMAT" out of range", i); + return FALSE; + } + if (i == OTTIE_INT_UNSET) + { + ottie_parser_error_unsupported (reader, "The Integer value %d is a magic internal value of Ottie, file a bug", OTTIE_INT_UNSET); + return FALSE; + } + + *(int *) ((guint8 *) data + offset) = i; + + 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."); + } + + return TRUE; +} + +gboolean +ottie_parser_option_direction (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieDirection direction; + 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) + { + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known direction", i); + G_GNUC_FALLTHROUGH; + case 0: + direction = OTTIE_DIRECTION_FORWARD; + break; + + case 1: + case 2: + direction = OTTIE_DIRECTION_BACKWARD; + break; + } + + *(OttieDirection *) ((guint8 *) data + offset) = direction; + + 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_fill_rule (JsonReader *reader, + gsize offset, + gpointer data) +{ + GskFillRule fill_rule; + 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: + fill_rule = GSK_FILL_RULE_WINDING; + break; + + case 2: + fill_rule = GSK_FILL_RULE_EVEN_ODD; + break; + + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known fill rule", i); + /* XXX: really? */ + fill_rule = GSK_FILL_RULE_EVEN_ODD; + break; + } + + *(GskFillRule *) ((guint8 *) data + offset) = fill_rule; + + 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..27fc41acdc --- /dev/null +++ b/ottie/ottieparserprivate.h @@ -0,0 +1,115 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PARSER_PRIVATE_H__ +#define __OTTIE_PARSER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +/* for integers where we want to track that nobody has assigned a value to them */ +#define OTTIE_INT_UNSET G_MININT + +typedef enum +{ + OTTIE_DIRECTION_FORWARD, + OTTIE_DIRECTION_BACKWARD +} OttieDirection; + +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_int (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_direction (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_fill_rule (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..b8e9d04de2 --- /dev/null +++ b/ottie/ottiepathshape.c @@ -0,0 +1,105 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepathshapeprivate.h" + +#include "ottiepathvalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include + +struct _OttiePathShape +{ + OttieShape parent; + + double direction; + OttiePathValue path; +}; + +struct _OttiePathShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttiePathShape, ottie_path_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_path_shape_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttiePathShape *self = OTTIE_PATH_SHAPE (shape); + + ottie_render_add_path (render, + 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_class_init (OttiePathShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_path_shape_render; + + gobject_class->dispose = ottie_path_shape_dispose; +} + +static void +ottie_path_shape_init (OttiePathShape *self) +{ + ottie_path_value_init (&self->path); +} + +OttieShape * +ottie_path_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE, + { "d", ottie_parser_option_double, G_STRUCT_OFFSET (OttiePathShape, direction) }, + { "ks", ottie_path_value_parse, G_STRUCT_OFFSET (OttiePathShape, path) }, + }; + OttiePathShape *self; + + self = g_object_new (OTTIE_TYPE_PATH_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "path shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottiepathshapeprivate.h b/ottie/ottiepathshapeprivate.h new file mode 100644 index 0000000000..827e3cf3b1 --- /dev/null +++ b/ottie/ottiepathshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PATH_SHAPE_PRIVATE_H__ +#define __OTTIE_PATH_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PATH_SHAPE (ottie_path_shape_get_type ()) +#define OTTIE_PATH_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_PATH_SHAPE, OttiePathShape)) +#define OTTIE_PATH_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_PATH_SHAPE, OttiePathShapeClass)) +#define OTTIE_IS_PATH_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_PATH_SHAPE)) +#define OTTIE_IS_PATH_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_PATH_SHAPE)) +#define OTTIE_PATH_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_PATH_SHAPE, OttiePathShapeClass)) + +typedef struct _OttiePathShape OttiePathShape; +typedef struct _OttiePathShapeClass OttiePathShapeClass; + +GType ottie_path_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_path_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_PATH_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottiepathvalue.c b/ottie/ottiepathvalue.c new file mode 100644 index 0000000000..7099ea26c7 --- /dev/null +++ b/ottie/ottiepathvalue.c @@ -0,0 +1,402 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepathvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +typedef struct _OttieContour OttieContour; +typedef struct _OttieCurve OttieCurve; +typedef struct _OttiePath OttiePath; + +struct _OttieCurve { + double point[2]; + double in[2]; + double out[2]; +}; + +struct _OttieContour { + gboolean closed; + guint n_curves; + OttieCurve curves[0]; +}; + +struct _OttiePath { + guint ref_count; + guint n_contours; + OttieContour *contours[]; +}; + +static OttieContour * +ottie_contour_renew (OttieContour *path, + guint n_curves) +{ + OttieContour *self; + + self = g_realloc (path, sizeof (OttieContour) + n_curves * sizeof (OttieCurve)); + self->n_curves = n_curves; + + return self; +} + +static OttieContour * +ottie_contour_new (gboolean closed, + guint n_curves) +{ + OttieContour *self; + + self = g_malloc0 (sizeof (OttieContour) + n_curves * sizeof (OttieCurve)); + self->closed = closed; + self->n_curves = n_curves; + + return self; +} + +static void +ottie_contour_free (OttieContour *path) +{ + g_free (path); +} + +static gboolean +ottie_parse_value_parse_numbers (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_parser_parse_array (reader, "number", + 2, 2, NULL, + offset, sizeof (double), + ottie_parser_option_double, data); +} + +#define MAKE_OPEN_CONTOUR (NULL) +#define MAKE_CLOSED_CONTOUR GSIZE_TO_POINTER(1) +static gboolean +ottie_parse_value_parse_curve_array (JsonReader *reader, + gsize offset, + gpointer data) +{ + /* Attention: The offset value here is to the point in the curve, not + * to the target */ + OttieContour **target = (OttieContour **) data; + OttieContour *path = *target; + guint n_curves; + + n_curves = json_reader_count_elements (reader); + if (path == MAKE_OPEN_CONTOUR) + path = ottie_contour_new (FALSE, n_curves); + else if (path == MAKE_CLOSED_CONTOUR) + path = ottie_contour_new (TRUE, n_curves); + else if (n_curves < path->n_curves) + path = ottie_contour_renew (path, n_curves); + else if (n_curves > path->n_curves) + n_curves = path->n_curves; + + *target = path; + + return ottie_parser_parse_array (reader, "path array", + 0, path->n_curves, NULL, + offset, sizeof (OttieCurve), + ottie_parse_value_parse_numbers, &path->curves[0]); +} + +static gboolean +ottie_parser_value_parse_closed (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieContour **target = (OttieContour **) data; + gboolean b; + + b = json_reader_get_boolean_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + if (*target == MAKE_OPEN_CONTOUR || *target == MAKE_CLOSED_CONTOUR) + { + if (b) + *target = MAKE_CLOSED_CONTOUR; + else + *target = MAKE_OPEN_CONTOUR; + } + else + (*target)->closed = b; + + return TRUE; +} + +static gboolean +ottie_path_value_parse_contour (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieContour **target = (OttieContour **) ((guint8 *) data + offset); + OttieParserOption options[] = { + { "c", ottie_parser_value_parse_closed, 0 }, + { "i", ottie_parse_value_parse_curve_array, G_STRUCT_OFFSET (OttieCurve, in) }, + { "o", ottie_parse_value_parse_curve_array, G_STRUCT_OFFSET (OttieCurve, out) }, + { "v", ottie_parse_value_parse_curve_array, G_STRUCT_OFFSET (OttieCurve, point) }, + }; + + g_assert (*target == NULL); + *target = MAKE_CLOSED_CONTOUR; + + if (!ottie_parser_parse_object (reader, "contour", options, G_N_ELEMENTS (options), target)) + { + if (*target != MAKE_OPEN_CONTOUR && *target != MAKE_CLOSED_CONTOUR) + g_clear_pointer (target, ottie_contour_free); + *target = NULL; + return FALSE; + } + + if (*target == MAKE_OPEN_CONTOUR) + *target = ottie_contour_new (FALSE, 0); + else if (*target == MAKE_CLOSED_CONTOUR) + *target = ottie_contour_new (TRUE, 0); + + return TRUE; +} + +static OttiePath * +ottie_path_new (gsize n_contours) +{ + OttiePath *self; + + self = g_malloc0 (sizeof (OttiePath) + sizeof (OttieContour *) * n_contours); + self->ref_count = 1; + self->n_contours = n_contours; + + return self; +} + +static OttiePath * +ottie_path_ref (OttiePath *self) +{ + self->ref_count++; + + return self; +} + +static void +ottie_path_unref (OttiePath *self) +{ + self->ref_count--; + if (self->ref_count > 0) + return; + + for (guint i = 0; i < self->n_contours; i++) + { + g_clear_pointer (&self->contours[i], ottie_contour_free); + } + + g_free (self); +} + +static gboolean +ottie_path_value_parse_one (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePath **target = (OttiePath **) ((guint8 *) data + offset); + OttiePath *path; + + if (json_reader_is_array (reader)) + path = ottie_path_new (json_reader_count_elements (reader)); + else + path = ottie_path_new (1); + + if (!ottie_parser_parse_array (reader, "path", + path->n_contours, path->n_contours, NULL, + G_STRUCT_OFFSET (OttiePath, contours), + sizeof (OttieContour *), + ottie_path_value_parse_contour, path)) + { + ottie_path_unref (path); + return FALSE; + } + + g_clear_pointer (target, ottie_path_unref); + *target = path; + + return TRUE; +} + +static OttieContour * +ottie_contour_interpolate (const OttieContour *start, + const OttieContour *end, + double progress) +{ + OttieContour *self = ottie_contour_new (start->closed || end->closed, + MIN (start->n_curves, end->n_curves)); + + for (gsize i = 0; i < self->n_curves; i++) + { + self->curves[i].point[0] = start->curves[i].point[0] + progress * (end->curves[i].point[0] - start->curves[i].point[0]); + self->curves[i].point[1] = start->curves[i].point[1] + progress * (end->curves[i].point[1] - start->curves[i].point[1]); + self->curves[i].in[0] = start->curves[i].in[0] + progress * (end->curves[i].in[0] - start->curves[i].in[0]); + self->curves[i].in[1] = start->curves[i].in[1] + progress * (end->curves[i].in[1] - start->curves[i].in[1]); + self->curves[i].out[0] = start->curves[i].out[0] + progress * (end->curves[i].out[0] - start->curves[i].out[0]); + self->curves[i].out[1] = start->curves[i].out[1] + progress * (end->curves[i].out[1] - start->curves[i].out[1]); + } + + return self; +} + +static OttiePath * +ottie_path_interpolate (const OttiePath *start, + const OttiePath *end, + double progress) +{ + OttiePath *self = ottie_path_new (MIN (start->n_contours, end->n_contours)); + + for (gsize i = 0; i < self->n_contours; i++) + { + self->contours[i] = ottie_contour_interpolate (start->contours[i], + end->contours[i], + progress); + } + + return self; +} + +#define OTTIE_KEYFRAMES_NAME ottie_path_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttieContourKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE OttiePath * +#define OTTIE_KEYFRAMES_COPY_FUNC ottie_path_ref +#define OTTIE_KEYFRAMES_FREE_FUNC ottie_path_unref +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_path_value_parse_one +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_path_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_path_value_init (OttiePathValue *self) +{ + self->is_static = TRUE; + self->static_value = NULL; +} + +void +ottie_path_value_clear (OttiePathValue *self) +{ + if (self->is_static) + g_clear_pointer (&self->static_value, ottie_path_unref); + else + g_clear_pointer (&self->keyframes, ottie_path_keyframes_free); +} + +static GskPath * +ottie_path_build (OttiePath *self, + gboolean reverse) +{ + GskPathBuilder *builder; + + if (reverse) + g_warning ("FIXME: Make paths reversible"); + + builder = gsk_path_builder_new (); + for (gsize i = 0; i < self->n_contours; i++) + { + OttieContour *contour = self->contours[i]; + if (contour->n_curves == 0) + continue; + + gsk_path_builder_move_to (builder, + contour->curves[0].point[0], contour->curves[0].point[1]); + for (guint j = 1; j < contour->n_curves; j++) + { + gsk_path_builder_curve_to (builder, + contour->curves[j-1].point[0] + contour->curves[j-1].out[0], + contour->curves[j-1].point[1] + contour->curves[j-1].out[1], + contour->curves[j].point[0] + contour->curves[j].in[0], + contour->curves[j].point[1] + contour->curves[j].in[1], + contour->curves[j].point[0], + contour->curves[j].point[1]); + } + if (contour->closed) + { + gsk_path_builder_curve_to (builder, + contour->curves[contour->n_curves-1].point[0] + contour->curves[contour->n_curves-1].out[0], + contour->curves[contour->n_curves-1].point[1] + contour->curves[contour->n_curves-1].out[1], + contour->curves[0].point[0] + contour->curves[0].in[0], + contour->curves[0].point[1] + contour->curves[0].in[1], + contour->curves[0].point[0], + contour->curves[0].point[1]); + gsk_path_builder_close (builder); + } + } + + return gsk_path_builder_free_to_path (builder); +} + +GskPath * +ottie_path_value_get (OttiePathValue *self, + double timestamp, + gboolean reverse) +{ + if (self->is_static) + return ottie_path_build (self->static_value, reverse); + + return ottie_path_build (ottie_path_keyframes_get (self->keyframes, timestamp), reverse); +} + +gboolean +ottie_path_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePathValue *self = (OttiePathValue *) ((guint8 *) data + GPOINTER_TO_SIZE (offset)); + + if (json_reader_read_member (reader, "k")) + { + if (!json_reader_is_array (reader)) + { + if (!ottie_path_value_parse_one (reader, 0, &self->static_value)) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = TRUE; + } + else + { + self->keyframes = ottie_path_keyframes_parse (reader); + if (self->keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = FALSE; + } + } + else + { + ottie_parser_error_syntax (reader, "Property is not a path value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiepathvalueprivate.h b/ottie/ottiepathvalueprivate.h new file mode 100644 index 0000000000..a7035cc783 --- /dev/null +++ b/ottie/ottiepathvalueprivate.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PATH_VALUE_PRIVATE_H__ +#define __OTTIE_PATH_VALUE_PRIVATE_H__ + +#include + +#include + +G_BEGIN_DECLS + +typedef struct _OttiePathValue OttiePathValue; + +struct _OttiePathValue +{ + gboolean is_static; + union { + gpointer static_value; + gpointer keyframes; + }; +}; + +void ottie_path_value_init (OttiePathValue *self); +void ottie_path_value_clear (OttiePathValue *self); + +GskPath * ottie_path_value_get (OttiePathValue *self, + double timestamp, + gboolean reverse); + +gboolean ottie_path_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_PATH_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottieplayer.c b/ottie/ottieplayer.c new file mode 100644 index 0000000000..d41aa5e2a3 --- /dev/null +++ b/ottie/ottieplayer.c @@ -0,0 +1,490 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieplayer.h" + +#include "ottiecreation.h" +#include "ottiepaintable.h" + +#include + +struct _OttiePlayer +{ + GObject parent_instance; + + GFile *file; + + OttieCreation *creation; + OttiePaintable *paintable; + gint64 time_offset; + guint timer_cb; +}; + +struct _OttiePlayerClass +{ + GObjectClass parent_class; +}; + +enum { + PROP_0, + PROP_FILE, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +ottie_player_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + OttiePlayer *self = OTTIE_PLAYER (paintable); + + gdk_paintable_snapshot (GDK_PAINTABLE (self->paintable), snapshot, width, height); +} + +static int +ottie_player_paintable_get_intrinsic_width (GdkPaintable *paintable) +{ + OttiePlayer *self = OTTIE_PLAYER (paintable); + + return gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (self->paintable)); +} + +static int +ottie_player_paintable_get_intrinsic_height (GdkPaintable *paintable) +{ + OttiePlayer *self = OTTIE_PLAYER (paintable); + + return gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (self->paintable)); +} + +static void +ottie_player_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = ottie_player_paintable_snapshot; + iface->get_intrinsic_width = ottie_player_paintable_get_intrinsic_width; + iface->get_intrinsic_height = ottie_player_paintable_get_intrinsic_height; +} + +G_DEFINE_TYPE_EXTENDED (OttiePlayer, ottie_player, GTK_TYPE_MEDIA_STREAM, 0, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + ottie_player_paintable_init)) + +static gboolean +ottie_player_timer_cb (gpointer data) +{ + OttiePlayer *self = OTTIE_PLAYER (data); + gint64 timestamp; + + timestamp = g_get_monotonic_time () - self->time_offset; + if (timestamp > ottie_paintable_get_duration (self->paintable)) + { + if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (self))) + { + timestamp %= ottie_paintable_get_duration (self->paintable); + } + else + { + timestamp = ottie_paintable_get_duration (self->paintable); + gtk_media_stream_ended (GTK_MEDIA_STREAM (self)); + } + } + ottie_paintable_set_timestamp (self->paintable, timestamp); + gtk_media_stream_update (GTK_MEDIA_STREAM (self), timestamp); + + return G_SOURCE_CONTINUE; +} + +static gboolean +ottie_player_play (GtkMediaStream *stream) +{ + OttiePlayer *self = OTTIE_PLAYER (stream); + double frame_rate; + + frame_rate = ottie_creation_get_frame_rate (self->creation); + if (frame_rate <= 0) + return FALSE; + + self->time_offset = g_get_monotonic_time () - ottie_paintable_get_timestamp (self->paintable); + self->timer_cb = g_timeout_add (1000 / frame_rate, ottie_player_timer_cb, self); + + return TRUE; +} + +static void +ottie_player_pause (GtkMediaStream *stream) +{ + OttiePlayer *self = OTTIE_PLAYER (stream); + + g_clear_handle_id (&self->timer_cb, g_source_remove); +} + +static void +ottie_player_seek (GtkMediaStream *stream, + gint64 timestamp) +{ + OttiePlayer *self = OTTIE_PLAYER (stream); + + if (!ottie_creation_is_prepared (self->creation)) + gtk_media_stream_seek_failed (stream); + + ottie_paintable_set_timestamp (self->paintable, timestamp); + self->time_offset = g_get_monotonic_time () - timestamp; + + gtk_media_stream_seek_success (stream); + gtk_media_stream_update (stream, timestamp); +} + +static void +ottie_player_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + OttiePlayer *self = OTTIE_PLAYER (object); + + switch (prop_id) + { + case PROP_FILE: + ottie_player_set_file (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_player_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + OttiePlayer *self = OTTIE_PLAYER (object); + + switch (prop_id) + { + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ottie_player_dispose (GObject *object) +{ + OttiePlayer *self = OTTIE_PLAYER (object); + + if (self->paintable) + { + g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_contents, self); + g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_size, self); + g_clear_object (&self->paintable); + } + g_clear_object (&self->creation); + + G_OBJECT_CLASS (ottie_player_parent_class)->dispose (object); +} + +static void +ottie_player_class_init (OttiePlayerClass *klass) +{ + GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + stream_class->play = ottie_player_play; + stream_class->pause = ottie_player_pause; + stream_class->seek = ottie_player_seek; + + gobject_class->get_property = ottie_player_get_property; + gobject_class->set_property = ottie_player_set_property; + gobject_class->dispose = ottie_player_dispose; + + /** + * OttiePlayer:file + * + * The played file or %NULL. + */ + properties[PROP_FILE] = + g_param_spec_object ("file", + _("File"), + _("The played file"), + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +ottie_player_prepared_cb (OttieCreation *creation, + GParamSpec *pspec, + OttiePlayer *self) +{ + if (ottie_creation_is_prepared (creation)) + gtk_media_stream_prepared (GTK_MEDIA_STREAM (self), + FALSE, + TRUE, + TRUE, + ottie_paintable_get_duration (self->paintable)); + else + gtk_media_stream_unprepared (GTK_MEDIA_STREAM (self)); + + ottie_paintable_set_timestamp (self->paintable, 0); +} + +static void +ottie_player_init (OttiePlayer *self) +{ + self->creation = ottie_creation_new (); + g_signal_connect (self->creation, "notify::prepared", G_CALLBACK (ottie_player_prepared_cb), self); + self->paintable = ottie_paintable_new (self->creation); + g_signal_connect_swapped (self->paintable, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self); + g_signal_connect_swapped (self->paintable, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self); +} + +/** + * ottie_player_new: + * + * Creates a new Ottie player. + * + * Returns: (transfer full): a new #OttiePlayer + **/ +OttiePlayer * +ottie_player_new (void) +{ + return g_object_new (OTTIE_TYPE_PLAYER, NULL); +} + +/** + * ottie_player_new_for_file: + * @file: (nullable): a #GFile + * + * Creates a new #OttiePlayer playing the given @file. If the file + * isn’t found or can’t be loaded, the resulting #OttiePlayer be empty. + * + * Returns: a new #OttiePlayer + **/ +OttiePlayer* +ottie_player_new_for_file (GFile *file) +{ + g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); + + return g_object_new (OTTIE_TYPE_PLAYER, + "file", file, + NULL); +} + +/** + * ottie_player_new_for_filename: + * @filename: (type filename) (nullable): a filename + * + * Creates a new #OttiePlayer displaying the file @filename. + * + * This is a utility function that calls ottie_player_new_for_file(). + * See that function for details. + * + * Returns: a new #OttiePlayer + **/ +OttiePlayer* +ottie_player_new_for_filename (const char *filename) +{ + OttiePlayer *result; + GFile *file; + + if (filename) + file = g_file_new_for_path (filename); + else + file = NULL; + + result = ottie_player_new_for_file (file); + + if (file) + g_object_unref (file); + + return result; +} + +/** + * ottie_player_new_for_resource: + * @resource_path: (nullable): resource path to play back + * + * Creates a new #OttiePlayer displaying the file @resource_path. + * + * This is a utility function that calls ottie_player_new_for_file(). + * See that function for details. + * + * Returns: a new #OttiePlayer + **/ +OttiePlayer * +ottie_player_new_for_resource (const char *resource_path) +{ + OttiePlayer *result; + GFile *file; + + if (resource_path) + { + char *uri, *escaped; + + escaped = g_uri_escape_string (resource_path, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + uri = g_strconcat ("resource://", escaped, NULL); + g_free (escaped); + + file = g_file_new_for_uri (uri); + g_free (uri); + } + else + { + file = NULL; + } + + result = ottie_player_new_for_file (file); + + if (file) + g_object_unref (file); + + return result; +} + +/** + * ottie_player_set_file: + * @self: a #OttiePlayer + * @file: (nullable): a %GFile or %NULL + * + * Makes @self load and display @file. + * + * See ottie_player_new_for_file() for details. + **/ +void +ottie_player_set_file (OttiePlayer *self, + GFile *file) +{ + g_return_if_fail (OTTIE_IS_PLAYER (self)); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + if (self->file == file) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + g_set_object (&self->file, file); + ottie_creation_load_file (self->creation, file); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * ottie_player_get_file: + * @self: a #OttiePlayer + * + * Gets the #GFile currently displayed if @self is displaying a file. + * If @self is not displaying a file, then %NULL is returned. + * + * Returns: (nullable) (transfer none): The #GFile displayed by @self. + **/ +GFile * +ottie_player_get_file (OttiePlayer *self) +{ + g_return_val_if_fail (OTTIE_IS_PLAYER (self), FALSE); + + return self->file; +} + +/** + * ottie_player_set_filename: + * @self: a #OttiePlayer + * @filename: (nullable): the filename to play + * + * Makes @self load and display the given @filename. + * + * This is a utility function that calls ottie_player_set_file(). + **/ +void +ottie_player_set_filename (OttiePlayer *self, + const char *filename) +{ + GFile *file; + + g_return_if_fail (OTTIE_IS_PLAYER (self)); + + if (filename) + file = g_file_new_for_path (filename); + else + file = NULL; + + ottie_player_set_file (self, file); + + if (file) + g_object_unref (file); +} + +/** + * ottie_player_set_resource: + * @self: a #OttiePlayer + * @resource_path: (nullable): the resource to set + * + * Makes @self load and display the resource at the given + * @resource_path. + * + * This is a utility function that calls ottie_player_set_file(), + **/ +void +ottie_player_set_resource (OttiePlayer *self, + const char *resource_path) +{ + GFile *file; + + g_return_if_fail (OTTIE_IS_PLAYER (self)); + + if (resource_path) + { + char *uri, *escaped; + + escaped = g_uri_escape_string (resource_path, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + uri = g_strconcat ("resource://", escaped, NULL); + g_free (escaped); + + file = g_file_new_for_uri (uri); + g_free (uri); + } + else + { + file = NULL; + } + + ottie_player_set_file (self, file); + + if (file) + g_object_unref (file); +} + diff --git a/ottie/ottieplayer.h b/ottie/ottieplayer.h new file mode 100644 index 0000000000..10891523d6 --- /dev/null +++ b/ottie/ottieplayer.h @@ -0,0 +1,59 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_PLAYER_H__ +#define __OTTIE_PLAYER_H__ + +#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_PLAYER (ottie_player_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (OttiePlayer, ottie_player, OTTIE, PLAYER, GtkMediaStream) + +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new (void); +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new_for_file (GFile *file); +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new_for_filename (const char *filename); +GDK_AVAILABLE_IN_ALL +OttiePlayer * ottie_player_new_for_resource (const char *resource_path); + +GDK_AVAILABLE_IN_ALL +void ottie_player_set_file (OttiePlayer *self, + GFile *file); +GDK_AVAILABLE_IN_ALL +GFile * ottie_player_get_file (OttiePlayer *self); +GDK_AVAILABLE_IN_ALL +void ottie_player_set_filename (OttiePlayer *self, + const char *filename); +GDK_AVAILABLE_IN_ALL +void ottie_player_set_resource (OttiePlayer *self, + const char *resource_path); + +G_END_DECLS + +#endif /* __OTTIE_PLAYER_H__ */ diff --git a/ottie/ottiepoint3dvalue.c b/ottie/ottiepoint3dvalue.c new file mode 100644 index 0000000000..37ee149e90 --- /dev/null +++ b/ottie/ottiepoint3dvalue.c @@ -0,0 +1,154 @@ +/** + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepoint3dvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_point3d_value_parse_value (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d[3]; + guint n_items; + + if (!ottie_parser_parse_array (reader, "point", + 2, 3, &n_items, + 0, sizeof (double), + ottie_parser_option_double, + &d)) + return FALSE; + + if (n_items == 2) + d[2] = NAN; /* We do fixup below */ + + *(graphene_point3d_t *) ((guint8 *) data + offset) = GRAPHENE_POINT3D_INIT (d[0], d[1], d[2]); + return TRUE; +} + +#define OTTIE_KEYFRAMES_NAME ottie_point_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttiePointKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE graphene_point3d_t +#define OTTIE_KEYFRAMES_BY_VALUE 1 +#define OTTIE_KEYFRAMES_DIMENSIONS 3 +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_point3d_value_parse_value +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC graphene_point3d_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_point3d_value_init (OttiePoint3DValue *self, + const graphene_point3d_t *value) +{ + self->is_static = TRUE; + self->static_value = *value; +} + +void +ottie_point3d_value_clear (OttiePoint3DValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_point_keyframes_free); +} + +void +ottie_point3d_value_get (OttiePoint3DValue *self, + double timestamp, + graphene_point3d_t *value) +{ + if (self->is_static) + { + *value = self->static_value; + return; + } + + ottie_point_keyframes_get (self->keyframes, timestamp, value); +} + +gboolean +ottie_point3d_value_parse (JsonReader *reader, + float default_value, + gsize offset, + gpointer data) +{ + OttiePoint3DValue *self = (OttiePoint3DValue *) ((guint8 *) data + offset); + + if (json_reader_read_member (reader, "k")) + { + gboolean is_static; + + if (!json_reader_is_array (reader)) + { + ottie_parser_error_syntax (reader, "Point value needs an array for its value"); + return FALSE; + } + + if (!json_reader_read_element (reader, 0)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + json_reader_end_element (reader); + return FALSE; + } + + is_static = !json_reader_is_object (reader); + json_reader_end_element (reader); + + if (is_static) + { + if (!ottie_point3d_value_parse_value (reader, 0, &self->static_value)) + { + json_reader_end_member (reader); + return FALSE; + } + if (isnan (self->static_value.z)) + self->static_value.z = default_value; + self->is_static = TRUE; + } + else + { + OttiePointKeyframes *keyframes = ottie_point_keyframes_parse (reader); + if (keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + for (int i = 0; i < keyframes->n_items; i++) + { + if (isnan (keyframes->items[i].start_value.z)) + keyframes->items[i].start_value.z = default_value; + if (isnan (keyframes->items[i].end_value.z)) + keyframes->items[i].end_value.z = default_value; + } + self->is_static = FALSE; + self->keyframes = keyframes; + } + } + else + { + ottie_parser_error_syntax (reader, "Point value has no value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiepoint3dvalueprivate.h b/ottie/ottiepoint3dvalueprivate.h new file mode 100644 index 0000000000..f735f06b95 --- /dev/null +++ b/ottie/ottiepoint3dvalueprivate.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_POINT3D_VALUE_PRIVATE_H__ +#define __OTTIE_POINT3D_VALUE_PRIVATE_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _OttiePoint3DValue OttiePoint3DValue; + +struct _OttiePoint3DValue +{ + gboolean is_static; + union { + graphene_point3d_t static_value; + gpointer keyframes; + }; +}; + +void ottie_point3d_value_init (OttiePoint3DValue *self, + const graphene_point3d_t *value); +void ottie_point3d_value_clear (OttiePoint3DValue *self); + +void ottie_point3d_value_get (OttiePoint3DValue *self, + double timestamp, + graphene_point3d_t *value); + +gboolean ottie_point3d_value_parse (JsonReader *reader, + float default_value, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_POINT3D_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottiepointvalue.c b/ottie/ottiepointvalue.c new file mode 100644 index 0000000000..875c4c1a24 --- /dev/null +++ b/ottie/ottiepointvalue.c @@ -0,0 +1,140 @@ +/** + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiepointvalueprivate.h" + +#include "ottieparserprivate.h" + +#include + +static gboolean +ottie_point_value_parse_value (JsonReader *reader, + gsize offset, + gpointer data) +{ + double d[2]; + + if (!ottie_parser_parse_array (reader, "point", + 2, 2, NULL, + 0, sizeof (double), + ottie_parser_option_double, + &d)) + return FALSE; + + *(graphene_point_t *) ((guint8 *) data + offset) = GRAPHENE_POINT_INIT (d[0], d[1]); + return TRUE; +} + +#define OTTIE_KEYFRAMES_NAME ottie_point_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttiePointKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE graphene_point_t +#define OTTIE_KEYFRAMES_BY_VALUE 1 +#define OTTIE_KEYFRAMES_DIMENSIONS 2 +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_point_value_parse_value +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC graphene_point_interpolate +#include "ottiekeyframesimpl.c" + +void +ottie_point_value_init (OttiePointValue *self, + const graphene_point_t *value) +{ + self->is_static = TRUE; + self->static_value = *value; +} + +void +ottie_point_value_clear (OttiePointValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_point_keyframes_free); +} + +void +ottie_point_value_get (OttiePointValue *self, + double timestamp, + graphene_point_t *value) +{ + if (self->is_static) + { + *value = self->static_value; + return; + } + + ottie_point_keyframes_get (self->keyframes, timestamp, value); +} + +gboolean +ottie_point_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttiePointValue *self = (OttiePointValue *) ((guint8 *) data + offset); + + if (json_reader_read_member (reader, "k")) + { + gboolean is_static; + + if (!json_reader_is_array (reader)) + { + ottie_parser_error_syntax (reader, "Point value needs an array for its value"); + return FALSE; + } + + if (!json_reader_read_element (reader, 0)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + json_reader_end_element (reader); + return FALSE; + } + + is_static = !json_reader_is_object (reader); + json_reader_end_element (reader); + + if (is_static) + { + if (!ottie_point_value_parse_value (reader, 0, &self->static_value)) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = TRUE; + } + else + { + OttiePointKeyframes *keyframes = ottie_point_keyframes_parse (reader); + if (keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + self->is_static = FALSE; + self->keyframes = keyframes; + } + } + else + { + ottie_parser_error_syntax (reader, "Point value has no value"); + } + json_reader_end_member (reader); + + return TRUE; +} + diff --git a/ottie/ottiepointvalueprivate.h b/ottie/ottiepointvalueprivate.h new file mode 100644 index 0000000000..635fa78d6d --- /dev/null +++ b/ottie/ottiepointvalueprivate.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_POINT_VALUE_PRIVATE_H__ +#define __OTTIE_POINT_VALUE_PRIVATE_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _OttiePointValue OttiePointValue; + +struct _OttiePointValue +{ + gboolean is_static; + union { + graphene_point_t static_value; + gpointer keyframes; + }; +}; + +void ottie_point_value_init (OttiePointValue *self, + const graphene_point_t *value); +void ottie_point_value_clear (OttiePointValue *self); + +void ottie_point_value_get (OttiePointValue *self, + double timestamp, + graphene_point_t *value); + +gboolean ottie_point_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +G_END_DECLS + +#endif /* __OTTIE_POINT_VALUE_PRIVATE_H__ */ diff --git a/ottie/ottierectshape.c b/ottie/ottierectshape.c new file mode 100644 index 0000000000..8301ea775c --- /dev/null +++ b/ottie/ottierectshape.c @@ -0,0 +1,219 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottierectshapeprivate.h" + +#include "ottiedoublevalueprivate.h" +#include "ottiepointvalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include + +struct _OttieRectShape +{ + OttieShape parent; + + OttieDirection direction; + OttiePointValue position; + OttiePointValue size; + OttieDoubleValue rounded; +}; + +struct _OttieRectShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieRectShape, ottie_rect_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_rect_shape_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttieRectShape *self = OTTIE_RECT_SHAPE (shape); + graphene_point_t p, s; + double r; + GskPathBuilder *builder; + + ottie_point_value_get (&self->position, timestamp, &p); + ottie_point_value_get (&self->size, timestamp, &s); + r = ottie_double_value_get (&self->rounded, timestamp); + s.x /= 2; + s.y /= 2; + r = MIN (r, MIN (s.x, s.y)); + + builder = gsk_path_builder_new (); + + switch (self->direction) + { + case OTTIE_DIRECTION_FORWARD: + if (r <= 0) + { + gsk_path_builder_move_to (builder, p.x + s.x, p.y - s.y); + gsk_path_builder_line_to (builder, p.x - s.x, p.y - s.y); + gsk_path_builder_line_to (builder, p.x - s.x, p.y + s.y); + gsk_path_builder_line_to (builder, p.x + s.x, p.y + s.y); + gsk_path_builder_line_to (builder, p.x + s.x, p.y - s.y); + gsk_path_builder_close (builder); + } + else + { + const float weight = sqrt(0.5f); + + gsk_path_builder_move_to (builder, + p.x + s.x, p.y - s.y + r); + gsk_path_builder_conic_to (builder, + p.x + s.x, p.y - s.y, + p.x + s.x - r, p.y - s.y, + weight); + gsk_path_builder_line_to (builder, + p.x - s.x + r, p.y - s.y); + gsk_path_builder_conic_to (builder, + p.x - s.x, p.y - s.y, + p.x - s.x, p.y - s.y + r, + weight); + gsk_path_builder_line_to (builder, + p.x - s.x, p.y + s.y - r); + gsk_path_builder_conic_to (builder, + p.x - s.x, p.y + s.y, + p.x - s.x + r, p.y + s.y, + weight); + gsk_path_builder_line_to (builder, + p.x + s.x - r, p.y + s.y); + gsk_path_builder_conic_to (builder, + p.x + s.x, p.y + s.y, + p.x + s.x, p.y + s.y - r, + weight); + gsk_path_builder_line_to (builder, + p.x + s.x, p.y - s.y + r); + gsk_path_builder_close (builder); + } + break; + + case OTTIE_DIRECTION_BACKWARD: + if (r <= 0) + { + gsk_path_builder_move_to (builder, p.x + s.x, p.y - s.y); + gsk_path_builder_line_to (builder, p.x + s.x, p.y + s.y); + gsk_path_builder_line_to (builder, p.x - s.x, p.y + s.y); + gsk_path_builder_line_to (builder, p.x - s.x, p.y - s.y); + gsk_path_builder_line_to (builder, p.x + s.x, p.y - s.y); + gsk_path_builder_close (builder); + } + else + { + const float weight = sqrt(0.5f); + + gsk_path_builder_move_to (builder, + p.x + s.x, p.y - s.y + r); + gsk_path_builder_line_to (builder, + p.x + s.x, p.y + s.y - r); + gsk_path_builder_conic_to (builder, + p.x + s.x, p.y + s.y, + p.x + s.x - r, p.y + s.y, + weight); + gsk_path_builder_line_to (builder, + p.x - s.x + r, p.y + s.y); + gsk_path_builder_conic_to (builder, + p.x - s.x, p.y + s.y, + p.x - s.x, p.y + s.y - r, + weight); + gsk_path_builder_line_to (builder, + p.x - s.x, p.y - s.y + r); + gsk_path_builder_conic_to (builder, + p.x - s.x, p.y - s.y, + p.x - s.x + r, p.y - s.y, + weight); + gsk_path_builder_line_to (builder, + p.x + s.x - r, p.y - s.y); + gsk_path_builder_conic_to (builder, + p.x + s.x, p.y - s.y, + p.x + s.x, p.y - s.y + r, + weight); + gsk_path_builder_close (builder); + } + break; + + default: + g_assert_not_reached(); + break; + } + + ottie_render_add_path (render, + gsk_path_builder_free_to_path (builder)); +} + +static void +ottie_rect_shape_dispose (GObject *object) +{ + OttieRectShape *self = OTTIE_RECT_SHAPE (object); + + ottie_point_value_clear (&self->position); + ottie_point_value_clear (&self->size); + ottie_double_value_clear (&self->rounded); + + G_OBJECT_CLASS (ottie_rect_shape_parent_class)->dispose (object); +} + +static void +ottie_rect_shape_class_init (OttieRectShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_rect_shape_render; + + gobject_class->dispose = ottie_rect_shape_dispose; +} + +static void +ottie_rect_shape_init (OttieRectShape *self) +{ + ottie_point_value_init (&self->position, &GRAPHENE_POINT_INIT (0, 0)); + ottie_point_value_init (&self->size, &GRAPHENE_POINT_INIT (0, 0)); + ottie_double_value_init (&self->rounded, 0); +} + +OttieShape * +ottie_rect_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE, + { "d", ottie_parser_option_direction, G_STRUCT_OFFSET (OttieRectShape, direction) }, + { "p", ottie_point_value_parse, G_STRUCT_OFFSET (OttieRectShape, position) }, + { "s", ottie_point_value_parse, G_STRUCT_OFFSET (OttieRectShape, size) }, + { "r", ottie_double_value_parse, G_STRUCT_OFFSET (OttieRectShape, rounded) }, + }; + OttieRectShape *self; + + self = g_object_new (OTTIE_TYPE_RECT_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "rect shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottierectshapeprivate.h b/ottie/ottierectshapeprivate.h new file mode 100644 index 0000000000..75875ccf6d --- /dev/null +++ b/ottie/ottierectshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_RECT_SHAPE_PRIVATE_H__ +#define __OTTIE_RECT_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_RECT_SHAPE (ottie_rect_shape_get_type ()) +#define OTTIE_RECT_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_RECT_SHAPE, OttieRectShape)) +#define OTTIE_RECT_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_RECT_SHAPE, OttieRectShapeClass)) +#define OTTIE_IS_RECT_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_RECT_SHAPE)) +#define OTTIE_IS_RECT_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_RECT_SHAPE)) +#define OTTIE_RECT_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_RECT_SHAPE, OttieRectShapeClass)) + +typedef struct _OttieRectShape OttieRectShape; +typedef struct _OttieRectShapeClass OttieRectShapeClass; + +GType ottie_rect_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_rect_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_RECT_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottierender.c b/ottie/ottierender.c new file mode 100644 index 0000000000..0fca70301b --- /dev/null +++ b/ottie/ottierender.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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottierenderprivate.h" + +#include + +void +ottie_render_init (OttieRender *self) +{ + memset (self, 0, sizeof (OttieRender)); + + ottie_render_paths_init (&self->paths); + ottie_render_nodes_init (&self->nodes); +} + +void +ottie_render_clear_path (OttieRender *self) +{ + ottie_render_paths_set_size (&self->paths, 0); + g_clear_pointer (&self->cached_path, gsk_path_unref); +} + +void +ottie_render_clear_nodes (OttieRender *self) +{ + ottie_render_nodes_set_size (&self->nodes, 0); +} + +void +ottie_render_clear (OttieRender *self) +{ + ottie_render_nodes_clear (&self->nodes); + + ottie_render_paths_clear (&self->paths); + g_clear_pointer (&self->cached_path, gsk_path_unref); +} + +void +ottie_render_merge (OttieRender *self, + OttieRender *source) +{ + /* prepend all the nodes from source */ + ottie_render_nodes_splice (&self->nodes, + 0, + 0, FALSE, + ottie_render_nodes_index (&source->nodes, 0), + ottie_render_nodes_get_size (&source->nodes)); + /* steal all the nodes from source because refcounting */ + ottie_render_nodes_splice (&source->nodes, + 0, + ottie_render_nodes_get_size (&source->nodes), TRUE, + NULL, 0); + + /* append all the paths from source */ + ottie_render_paths_splice (&self->paths, + ottie_render_paths_get_size (&self->paths), + 0, FALSE, + ottie_render_paths_index (&source->paths, 0), + ottie_render_paths_get_size (&source->paths)); + /* steal all the paths from source because refcounting */ + ottie_render_paths_splice (&source->paths, + 0, + ottie_render_paths_get_size (&source->paths), TRUE, + NULL, 0); + + g_clear_pointer (&self->cached_path, gsk_path_unref); + g_clear_pointer (&source->cached_path, gsk_path_unref); +} + +void +ottie_render_add_path (OttieRender *self, + GskPath *path) +{ + g_clear_pointer (&self->cached_path, gsk_path_unref); + + if (gsk_path_is_empty (path)) + { + gsk_path_unref (path); + return; + } + + ottie_render_paths_append (&self->paths, &(OttieRenderPath) { path, NULL }); +} + +typedef struct +{ + GskPathBuilder *builder; + GskTransform *transform; +} TransformForeach; + +static gboolean +ottie_render_path_transform_foreach (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer data) +{ + TransformForeach *tf = data; + graphene_point_t p[3]; + + switch (op) + { + case GSK_PATH_MOVE: + gsk_transform_transform_point (tf->transform, &pts[0], &p[0]); + gsk_path_builder_move_to (tf->builder, p[0].x, p[0].y); + break; + + case GSK_PATH_CLOSE: + gsk_path_builder_close (tf->builder); + break; + + case GSK_PATH_LINE: + gsk_transform_transform_point (tf->transform, &pts[1], &p[0]); + gsk_path_builder_line_to (tf->builder, p[0].x, p[0].y); + break; + + case GSK_PATH_CURVE: + gsk_transform_transform_point (tf->transform, &pts[1], &p[0]); + gsk_transform_transform_point (tf->transform, &pts[2], &p[1]); + gsk_transform_transform_point (tf->transform, &pts[3], &p[2]); + gsk_path_builder_curve_to (tf->builder, p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y); + break; + + case GSK_PATH_CONIC: + gsk_transform_transform_point (tf->transform, &pts[1], &p[0]); + gsk_transform_transform_point (tf->transform, &pts[2], &p[1]); + gsk_path_builder_conic_to (tf->builder, p[0].x, p[0].y, p[1].x, p[1].y, weight); + break; + + default: + g_assert_not_reached (); + break; + } + + return TRUE; +} + +GskPath * +ottie_render_get_path (OttieRender *self) +{ + GskPathBuilder *builder; + + if (self->cached_path) + return self->cached_path; + + builder = gsk_path_builder_new (); + for (gsize i = 0; i < ottie_render_paths_get_size (&self->paths); i++) + { + OttieRenderPath *path = ottie_render_paths_get (&self->paths, i); + + switch (gsk_transform_get_category (path->transform)) + { + case GSK_TRANSFORM_CATEGORY_IDENTITY: + gsk_path_builder_add_path (builder, path->path); + break; + + case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: + case GSK_TRANSFORM_CATEGORY_2D_AFFINE: + case GSK_TRANSFORM_CATEGORY_2D: + { + TransformForeach tf = { builder, path->transform }; + gsk_path_foreach (path->path, -1, ottie_render_path_transform_foreach, &tf); + } + break; + + case GSK_TRANSFORM_CATEGORY_3D: + case GSK_TRANSFORM_CATEGORY_ANY: + case GSK_TRANSFORM_CATEGORY_UNKNOWN: + g_critical ("How did we get a 3D matrix?!"); + gsk_path_builder_add_path (builder, path->path); + break; + + default: + g_assert_not_reached(); + break; + } + } + self->cached_path = gsk_path_builder_free_to_path (builder); + + return self->cached_path; +} + +gsize +ottie_render_get_n_subpaths (OttieRender *self) +{ + return ottie_render_paths_get_size (&self->paths); +} + +GskPath * +ottie_render_get_subpath (OttieRender *self, + gsize i) +{ + OttieRenderPath *path = ottie_render_paths_get (&self->paths, i); + + return path->path; +} + +void +ottie_render_replace_subpath (OttieRender *self, + gsize i, + GskPath *path) +{ + OttieRenderPath *rpath = ottie_render_paths_get (&self->paths, i); + + gsk_path_unref (rpath->path); + rpath->path = path; + + g_clear_pointer (&self->cached_path, gsk_path_unref); +} + +void +ottie_render_add_node (OttieRender *self, + GskRenderNode *node) +{ + ottie_render_nodes_splice (&self->nodes, 0, 0, FALSE, &node, 1); +} + +GskRenderNode * +ottie_render_get_node (OttieRender *self) +{ + if (ottie_render_nodes_get_size (&self->nodes) == 0) + return NULL; + + if (ottie_render_nodes_get_size (&self->nodes) == 1) + return gsk_render_node_ref (ottie_render_nodes_get (&self->nodes, 0)); + + return gsk_container_node_new (ottie_render_nodes_index (&self->nodes, 0), + ottie_render_nodes_get_size (&self->nodes)); +} + +void +ottie_render_transform (OttieRender *self, + GskTransform *transform) +{ + GskRenderNode *node; + + if (gsk_transform_get_category (transform) == GSK_TRANSFORM_CATEGORY_IDENTITY) + return; + + for (gsize i = 0; i < ottie_render_paths_get_size (&self->paths); i++) + { + OttieRenderPath *path = ottie_render_paths_get (&self->paths, i); + + path->transform = gsk_transform_transform (path->transform, transform); + } + + node = ottie_render_get_node (self); + if (node) + { + GskRenderNode *transform_node = gsk_transform_node_new (node, transform); + + ottie_render_clear_nodes (self); + ottie_render_add_node (self, transform_node); + + gsk_render_node_unref (node); + } +} diff --git a/ottie/ottierenderprivate.h b/ottie/ottierenderprivate.h new file mode 100644 index 0000000000..8ee089c6f5 --- /dev/null +++ b/ottie/ottierenderprivate.h @@ -0,0 +1,91 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_RENDER_PRIVATE_H__ +#define __OTTIE_RENDER_PRIVATE_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _OttieRender OttieRender; +typedef struct _OttieRenderPath OttieRenderPath; + +struct _OttieRenderPath +{ + GskPath *path; + GskTransform *transform; +}; + +static inline void +ottie_render_path_clear (OttieRenderPath *path) +{ + gsk_path_unref (path->path); + gsk_transform_unref (path->transform); +} + +#define GDK_ARRAY_ELEMENT_TYPE OttieRenderPath +#define GDK_ARRAY_TYPE_NAME OttieRenderPaths +#define GDK_ARRAY_NAME ottie_render_paths +#define GDK_ARRAY_FREE_FUNC ottie_render_path_clear +#define GDK_ARRAY_BY_VALUE 1 +#define GDK_ARRAY_PREALLOC 8 +#include "gdk/gdkarrayimpl.c" + +#define GDK_ARRAY_ELEMENT_TYPE GskRenderNode * +#define GDK_ARRAY_TYPE_NAME OttieRenderNodes +#define GDK_ARRAY_NAME ottie_render_nodes +#define GDK_ARRAY_FREE_FUNC gsk_render_node_unref +#define GDK_ARRAY_PREALLOC 8 +#include "gdk/gdkarrayimpl.c" + +struct _OttieRender +{ + OttieRenderPaths paths; + GskPath *cached_path; + OttieRenderNodes nodes; +}; + +void ottie_render_init (OttieRender *self); +void ottie_render_clear (OttieRender *self); + +void ottie_render_merge (OttieRender *self, + OttieRender *source); + +void ottie_render_add_path (OttieRender *self, + GskPath *path); +GskPath * ottie_render_get_path (OttieRender *self); +void ottie_render_clear_path (OttieRender *self); +gsize ottie_render_get_n_subpaths (OttieRender *self); +GskPath * ottie_render_get_subpath (OttieRender *self, + gsize i); +void ottie_render_replace_subpath (OttieRender *self, + gsize i, + GskPath *path); + +void ottie_render_add_node (OttieRender *self, + GskRenderNode *node); +GskRenderNode * ottie_render_get_node (OttieRender *self); +void ottie_render_clear_nodes (OttieRender *self); + +void ottie_render_transform (OttieRender *self, + GskTransform *transform); +G_END_DECLS + +#endif /* __OTTIE_RENDER_PRIVATE_H__ */ diff --git a/ottie/ottieshape.c b/ottie/ottieshape.c new file mode 100644 index 0000000000..408b307ed2 --- /dev/null +++ b/ottie/ottieshape.c @@ -0,0 +1,56 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieshapeprivate.h" + +#include + +G_DEFINE_TYPE (OttieShape, ottie_shape, OTTIE_TYPE_OBJECT) + +static void +ottie_shape_dispose (GObject *object) +{ + //OttieShape *self = OTTIE_SHAPE (object); + + G_OBJECT_CLASS (ottie_shape_parent_class)->dispose (object); +} + +static void +ottie_shape_class_init (OttieShapeClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->dispose = ottie_shape_dispose; +} + +static void +ottie_shape_init (OttieShape *self) +{ +} + +void +ottie_shape_render (OttieShape *self, + OttieRender *render, + double timestamp) +{ + OTTIE_SHAPE_GET_CLASS (self)->render (self, render, timestamp); +} + diff --git a/ottie/ottieshapelayer.c b/ottie/ottieshapelayer.c new file mode 100644 index 0000000000..2d76fb048d --- /dev/null +++ b/ottie/ottieshapelayer.c @@ -0,0 +1,124 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottieshapelayerprivate.h" + +#include "ottiefillshapeprivate.h" +#include "ottiegroupshapeprivate.h" +#include "ottieparserprivate.h" +#include "ottiepathshapeprivate.h" +#include "ottieshapeprivate.h" +#include "ottiestrokeshapeprivate.h" +#include "ottietransformprivate.h" + +#include +#include + +struct _OttieShapeLayer +{ + OttieLayer parent; + + OttieShape *shapes; +}; + +struct _OttieShapeLayerClass +{ + OttieLayerClass parent_class; +}; + +G_DEFINE_TYPE (OttieShapeLayer, ottie_shape_layer, OTTIE_TYPE_LAYER) + +static void +ottie_shape_layer_render (OttieLayer *layer, + OttieRender *render, + double timestamp) +{ + OttieShapeLayer *self = OTTIE_SHAPE_LAYER (layer); + + ottie_shape_render (self->shapes, + render, + timestamp); +} + +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_class_init (OttieShapeLayerClass *klass) +{ + OttieLayerClass *layer_class = OTTIE_LAYER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + layer_class->render = ottie_shape_layer_render; + + 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); +} + +OttieShape * +ottie_shape_layer_get_shape (OttieShapeLayer *self) +{ + g_return_val_if_fail (OTTIE_IS_SHAPE_LAYER (self), NULL); + + return self->shapes; +} diff --git a/ottie/ottieshapelayerprivate.h b/ottie/ottieshapelayerprivate.h new file mode 100644 index 0000000000..fb4707c849 --- /dev/null +++ b/ottie/ottieshapelayerprivate.h @@ -0,0 +1,47 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_SHAPE_LAYER_PRIVATE_H__ +#define __OTTIE_SHAPE_LAYER_PRIVATE_H__ + +#include "ottielayerprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_SHAPE_LAYER (ottie_shape_layer_get_type ()) +#define OTTIE_SHAPE_LAYER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_SHAPE_LAYER, OttieShapeLayer)) +#define OTTIE_SHAPE_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_SHAPE_LAYER, OttieShapeLayerClass)) +#define OTTIE_IS_SHAPE_LAYER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_SHAPE_LAYER)) +#define OTTIE_IS_SHAPE_LAYER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_SHAPE_LAYER)) +#define OTTIE_SHAPE_LAYER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_SHAPE_LAYER, OttieShapeLayerClass)) + +typedef struct _OttieShapeLayer OttieShapeLayer; +typedef struct _OttieShapeLayerClass OttieShapeLayerClass; + +GType ottie_shape_layer_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_shape_layer_get_shape (OttieShapeLayer *self); + +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..d58a6ee036 --- /dev/null +++ b/ottie/ottieshapeprivate.h @@ -0,0 +1,69 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_SHAPE_PRIVATE_H__ +#define __OTTIE_SHAPE_PRIVATE_H__ + +#include "ottie/ottieobjectprivate.h" +#include "ottie/ottierenderprivate.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 _OttieShape OttieShape; +typedef struct _OttieShapeClass OttieShapeClass; + +struct _OttieShape +{ + OttieObject parent; + + gboolean hidden; +}; + +struct _OttieShapeClass +{ + OttieObjectClass parent_class; + + void (* render) (OttieShape *self, + OttieRender *render, + double timestamp); +}; + +GType ottie_shape_get_type (void) G_GNUC_CONST; + +void ottie_shape_render (OttieShape *self, + OttieRender *render, + double timestamp); + + +#define OTTIE_PARSE_OPTIONS_SHAPE \ + OTTIE_PARSE_OPTIONS_OBJECT, \ + { "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..38de557823 --- /dev/null +++ b/ottie/ottiestrokeshape.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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottiestrokeshapeprivate.h" + +#include "ottiecolorvalueprivate.h" +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +struct _OttieStrokeShape +{ + OttieShape parent; + + OttieDoubleValue opacity; + OttieColorValue color; + OttieDoubleValue line_width; + GskLineCap line_cap; + GskLineJoin line_join; + double miter_limit; + GskBlendMode blend_mode; +}; + +struct _OttieStrokeShapeClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieStrokeShape, ottie_stroke_shape, OTTIE_TYPE_SHAPE) + +static void +ottie_stroke_shape_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttieStrokeShape *self = OTTIE_STROKE_SHAPE (shape); + GskPath *path; + graphene_rect_t bounds; + GdkRGBA color; + GskStroke *stroke; + double opacity, line_width; + GskRenderNode *color_node; + + 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_render_get_path (render); + if (gsk_path_is_empty (path)) + return; + + 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); + + gsk_path_get_stroke_bounds (path, stroke, &bounds); + color_node = gsk_color_node_new (&color, &bounds); + + ottie_render_add_node (render, gsk_stroke_node_new (color_node, path, stroke)); + + gsk_render_node_unref (color_node); + gsk_stroke_free (stroke); +} + +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_class_init (OttieStrokeShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_stroke_shape_render; + + 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); + + self->miter_limit = 10; +} + +OttieShape * +ottie_stroke_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE, + { "w", ottie_double_value_parse, G_STRUCT_OFFSET (OttieStrokeShape, line_width) }, + { "o", ottie_double_value_parse, G_STRUCT_OFFSET (OttieStrokeShape, opacity) }, + { "c", ottie_color_value_parse, G_STRUCT_OFFSET (OttieStrokeShape, color) }, + { "lc", ottie_parser_option_line_cap, G_STRUCT_OFFSET (OttieStrokeShape, line_cap) }, + { "lj", ottie_parser_option_line_join, G_STRUCT_OFFSET (OttieStrokeShape, line_join) }, + { "ml", ottie_parser_option_double, G_STRUCT_OFFSET (OttieStrokeShape, miter_limit) }, + { "bm", ottie_parser_option_blend_mode, G_STRUCT_OFFSET (OttieStrokeShape, blend_mode) }, + }; + OttieStrokeShape *self; + + self = g_object_new (OTTIE_TYPE_STROKE_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "stroke shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottiestrokeshapeprivate.h b/ottie/ottiestrokeshapeprivate.h new file mode 100644 index 0000000000..2160ec77cd --- /dev/null +++ b/ottie/ottiestrokeshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_STROKE_SHAPE_PRIVATE_H__ +#define __OTTIE_STROKE_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_STROKE_SHAPE (ottie_stroke_shape_get_type ()) +#define OTTIE_STROKE_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_STROKE_SHAPE, OttieStrokeShape)) +#define OTTIE_STROKE_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_STROKE_SHAPE, OttieStrokeShapeClass)) +#define OTTIE_IS_STROKE_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_STROKE_SHAPE)) +#define OTTIE_IS_STROKE_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_STROKE_SHAPE)) +#define OTTIE_STROKE_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_STROKE_SHAPE, OttieStrokeShapeClass)) + +typedef struct _OttieStrokeShape OttieStrokeShape; +typedef struct _OttieStrokeShapeClass OttieStrokeShapeClass; + +GType ottie_stroke_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_stroke_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_STROKE_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottietransform.c b/ottie/ottietransform.c new file mode 100644 index 0000000000..cfd8c2aeb2 --- /dev/null +++ b/ottie/ottietransform.c @@ -0,0 +1,187 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottietransformprivate.h" + +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottiepoint3dvalueprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +struct _OttieTransform +{ + OttieShape parent; + + OttieDoubleValue opacity; + OttieDoubleValue rotation; + OttieDoubleValue skew; + OttieDoubleValue skew_angle; + OttiePoint3DValue anchor; + OttiePoint3DValue position; + OttiePoint3DValue scale; +}; + +struct _OttieTransformClass +{ + OttieShapeClass parent_class; +}; + +G_DEFINE_TYPE (OttieTransform, ottie_transform, OTTIE_TYPE_SHAPE) + +static void +ottie_transform_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttieTransform *self = OTTIE_TRANSFORM (shape); + GskTransform *transform; + + transform = ottie_transform_get_transform (self, timestamp); + ottie_render_transform (render, transform); + gsk_transform_unref (transform); +} + +static void +ottie_transform_dispose (GObject *object) +{ + OttieTransform *self = OTTIE_TRANSFORM (object); + + ottie_double_value_clear (&self->opacity); + ottie_double_value_clear (&self->rotation); + ottie_double_value_clear (&self->skew); + ottie_double_value_clear (&self->skew_angle); + ottie_point3d_value_clear (&self->anchor); + ottie_point3d_value_clear (&self->position); + ottie_point3d_value_clear (&self->scale); + + G_OBJECT_CLASS (ottie_transform_parent_class)->dispose (object); +} + +static void +ottie_transform_finalize (GObject *object) +{ + //OttieTransform *self = OTTIE_TRANSFORM (object); + + G_OBJECT_CLASS (ottie_transform_parent_class)->finalize (object); +} + +static void +ottie_transform_class_init (OttieTransformClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_transform_render; + + gobject_class->finalize = ottie_transform_finalize; + gobject_class->dispose = ottie_transform_dispose; +} + +static void +ottie_transform_init (OttieTransform *self) +{ + ottie_double_value_init (&self->opacity, 100); + ottie_double_value_init (&self->rotation, 0); + ottie_double_value_init (&self->skew, 0); + ottie_double_value_init (&self->skew_angle, 0); + ottie_point3d_value_init (&self->anchor, &GRAPHENE_POINT3D_INIT (0, 0, 0)); + ottie_point3d_value_init (&self->position, &GRAPHENE_POINT3D_INIT (0, 0, 0)); + ottie_point3d_value_init (&self->scale, &GRAPHENE_POINT3D_INIT (100, 100, 100)); +} + +static gboolean +ottie_transform_value_parse_point (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_point3d_value_parse (reader, 0, offset, data); +} + +static gboolean +ottie_transform_value_parse_scale (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_point3d_value_parse (reader, 100, offset, data); +} + + +OttieShape * +ottie_transform_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE, + { "o", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, opacity) }, + { "r", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, rotation) }, + { "a", ottie_transform_value_parse_point, G_STRUCT_OFFSET (OttieTransform, anchor) }, + { "p", ottie_transform_value_parse_point, G_STRUCT_OFFSET (OttieTransform, position) }, + { "s", ottie_transform_value_parse_scale, G_STRUCT_OFFSET (OttieTransform, scale) }, + { "sk", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, skew) }, + { "sa", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTransform, skew_angle) }, + }; + OttieTransform *self; + + self = g_object_new (OTTIE_TYPE_TRANSFORM, NULL); + + if (!ottie_parser_parse_object (reader, "transform", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + +GskTransform * +ottie_transform_get_transform (OttieTransform *self, + double timestamp) +{ + graphene_point3d_t anchor, position, scale; + GskTransform *transform; + double skew, skew_angle; + + ottie_point3d_value_get (&self->anchor, timestamp, &anchor); + ottie_point3d_value_get (&self->position, timestamp, &position); + ottie_point3d_value_get (&self->scale, timestamp, &scale); + + transform = NULL; + transform = gsk_transform_translate_3d (transform, &position); + transform = gsk_transform_rotate (transform, ottie_double_value_get (&self->rotation, timestamp)); + skew = ottie_double_value_get (&self->skew, timestamp); + if (skew) + { + graphene_matrix_t matrix; + skew_angle = ottie_double_value_get (&self->skew_angle, timestamp); + transform = gsk_transform_rotate (transform, -skew_angle); + graphene_matrix_init_skew (&matrix, -skew / 180.0 * G_PI, 0); + transform = gsk_transform_matrix (transform, &matrix); + transform = gsk_transform_rotate (transform, skew_angle); + } + transform = gsk_transform_scale_3d (transform, scale.x / 100, scale.y / 100, scale.z / 100); + graphene_point3d_scale (&anchor, -1, &anchor); + transform = gsk_transform_translate_3d (transform, &anchor); + + return transform; +} + diff --git a/ottie/ottietransformprivate.h b/ottie/ottietransformprivate.h new file mode 100644 index 0000000000..0cd80a3054 --- /dev/null +++ b/ottie/ottietransformprivate.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_TRANSFORM_PRIVATE_H__ +#define __OTTIE_TRANSFORM_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_TRANSFORM (ottie_transform_get_type ()) +#define OTTIE_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_TRANSFORM, OttieTransform)) +#define OTTIE_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_TRANSFORM, OttieTransformClass)) +#define OTTIE_IS_TRANSFORM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_TRANSFORM)) +#define OTTIE_IS_TRANSFORM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_TRANSFORM)) +#define OTTIE_TRANSFORM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_TRANSFORM, OttieTransformClass)) + +typedef struct _OttieTransform OttieTransform; +typedef struct _OttieTransformClass OttieTransformClass; + +GType ottie_transform_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_transform_parse (JsonReader *reader); + +GskTransform * ottie_transform_get_transform (OttieTransform *self, + double timestamp); + +G_END_DECLS + +#endif /* __OTTIE_TRANSFORM_PRIVATE_H__ */ diff --git a/ottie/ottietrimshape.c b/ottie/ottietrimshape.c new file mode 100644 index 0000000000..7791e2a121 --- /dev/null +++ b/ottie/ottietrimshape.c @@ -0,0 +1,220 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "ottietrimshapeprivate.h" + +#include "ottiedoublevalueprivate.h" +#include "ottieparserprivate.h" +#include "ottieshapeprivate.h" + +#include +#include + +/* + * OttieTrimMode: + * @OTTIE_TRIM_SIMULTANEOUSLY: Treat each contour as a custom path + * @OTTIE_TRIM_INDIVIDUALLY: Treat the path as one whole path + * + * Names taken from the spec / After Effects. Don't blame me. + */ +typedef enum +{ + OTTIE_TRIM_SIMULTANEOUSLY, + OTTIE_TRIM_INDIVIDUALLY, +} 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_render (OttieShape *shape, + OttieRender *render, + double timestamp) +{ + OttieTrimShape *self = OTTIE_TRIM_SHAPE (shape); + GskPathMeasure *measure; + GskPath *path; + GskPathBuilder *builder; + double start, end, offset; + + 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; + 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); + end += offset; + end = end - floor (end); + + switch (self->mode) + { + case OTTIE_TRIM_SIMULTANEOUSLY: + for (gsize i = 0; i < ottie_render_get_n_subpaths (render); i++) + { + builder = gsk_path_builder_new (); + path = ottie_render_get_subpath (render, i); + measure = gsk_path_measure_new (path); + gsk_path_measure_restrict_to_contour (measure, i); + gsk_path_builder_add_segment (builder, + measure, + start * gsk_path_measure_get_length (measure), + end * gsk_path_measure_get_length (measure)); + gsk_path_measure_unref (measure); + ottie_render_replace_subpath (render, i, gsk_path_builder_free_to_path (builder)); + } + break; + + case OTTIE_TRIM_INDIVIDUALLY: + builder = gsk_path_builder_new (); + path = ottie_render_get_path (render); + measure = gsk_path_measure_new (path); + gsk_path_builder_add_segment (builder, + measure, + start * gsk_path_measure_get_length (measure), + end * gsk_path_measure_get_length (measure)); + ottie_render_clear_path (render); + gsk_path_measure_unref (measure); + ottie_render_add_path (render, gsk_path_builder_free_to_path (builder)); + break; + + default: + g_assert_not_reached (); + break; + } + } + else + { + ottie_render_clear_path (render); + } +} + +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_class_init (OttieTrimShapeClass *klass) +{ + OttieShapeClass *shape_class = OTTIE_SHAPE_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + shape_class->render = ottie_trim_shape_render; + + 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_SIMULTANEOUSLY; + break; + + case 2: + trim_mode = OTTIE_TRIM_INDIVIDUALLY; + break; + + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known trim mode", i); + return FALSE; + } + + *(OttieTrimMode *) ((guint8 *) data + offset) = trim_mode; + + return TRUE; +} + +OttieShape * +ottie_trim_shape_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_SHAPE, + { "s", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTrimShape, start) }, + { "e", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTrimShape, end) }, + { "o", ottie_double_value_parse, G_STRUCT_OFFSET (OttieTrimShape, offset) }, + { "m", ottie_trim_mode_parse, G_STRUCT_OFFSET (OttieTrimShape, mode) }, + }; + OttieTrimShape *self; + + self = g_object_new (OTTIE_TYPE_TRIM_SHAPE, NULL); + + if (!ottie_parser_parse_object (reader, "trim shape", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_SHAPE (self); +} + diff --git a/ottie/ottietrimshapeprivate.h b/ottie/ottietrimshapeprivate.h new file mode 100644 index 0000000000..cd41479bbc --- /dev/null +++ b/ottie/ottietrimshapeprivate.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#ifndef __OTTIE_TRIM_SHAPE_PRIVATE_H__ +#define __OTTIE_TRIM_SHAPE_PRIVATE_H__ + +#include "ottieshapeprivate.h" + +#include + +G_BEGIN_DECLS + +#define OTTIE_TYPE_TRIM_SHAPE (ottie_trim_shape_get_type ()) +#define OTTIE_TRIM_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_TRIM_SHAPE, OttieTrimShape)) +#define OTTIE_TRIM_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_TRIM_SHAPE, OttieTrimShapeClass)) +#define OTTIE_IS_TRIM_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_TRIM_SHAPE)) +#define OTTIE_IS_TRIM_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_TRIM_SHAPE)) +#define OTTIE_TRIM_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_TRIM_SHAPE, OttieTrimShapeClass)) + +typedef struct _OttieTrimShape OttieTrimShape; +typedef struct _OttieTrimShapeClass OttieTrimShapeClass; + +GType ottie_trim_shape_get_type (void) G_GNUC_CONST; + +OttieShape * ottie_trim_shape_parse (JsonReader *reader); + +G_END_DECLS + +#endif /* __OTTIE_TRIM_SHAPE_PRIVATE_H__ */ diff --git a/ottie/ottievalueimpl.c b/ottie/ottievalueimpl.c new file mode 100644 index 0000000000..12bcede664 --- /dev/null +++ b/ottie/ottievalueimpl.c @@ -0,0 +1,133 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include + +G_BEGIN_DECLS + +#ifndef OTTIE_VALUE_TYPE_NAME +#define OTTIE_VALUE_TYPE_NAME OttieValue +#endif + +#ifndef OTTIE_VALUE_NAME +#define OTTIE_VALUE_NAME ottie_value +#endif + +#ifndef OTTIE_VALUE_ELEMENT_TYPE +#define OTTIE_VALUE_ELEMENT_TYPE gpointer +#endif + +/* make this readable */ +#define _T_ OTTIE_VALUE_ELEMENT_TYPE +#define OttieValue OTTIE_VALUE_TYPE_NAME +#define ottie_value_paste_more(OTTIE_VALUE_NAME, func_name) OTTIE_VALUE_NAME ## _ ## func_name +#define ottie_value_paste(OTTIE_VALUE_NAME, func_name) ottie_value_paste_more (OTTIE_VALUE_NAME, func_name) +#define ottie_value(func_name) ottie_value_paste (OTTIE_VALUE_NAME, func_name) + +typedef struct OttieValue OttieValue; + +struct OttieValue +{ + enum { + STATIC, + KEYFRAMES + } type; + union { + _T_ static_value; + struct { + _T_ *values; + gsize n_frames; + } keyframes; +}; + +void +ottie_value(init) (OttieValue *self) +{ + memset (self, 0, sizeof (OttieValue)); +} + +static inline void +ottie_value(free_item) (_T_ *item) +{ +#ifdef OTTIE_VALUE_FREE_FUNC +#ifdef OTTIE_VALUE_BY_VALUE + OTTIE_VALUE_FREE_FUNC (item); +#else + OTTIE_VALUE_FREE_FUNC (*item); +#endif +#endif +} + +void +ottie_value(clear) (OttieValue *self) +{ +#ifdef OTTIE_VALUE_FREE_FUNC + gsize i; + + if (self->type == STATIC) + { + ottie_value(free_item) (&self->static_value); + } + else + { + for (i = 0; i < self->n_values, i++) + ottie_value(free_item) (&self->values[i]); + } +} + +#ifdef OTTIE_VALUE_BY_VALUE +_T_ * +#else +_T_ +#endif +ottie_value(get) (const OttieValue *self, + double progress) +{ + _T_ * result; + + if (self->type == STATIC) + { + result = &self->static_value; + } + else + { + result = &self->values[progress * self->n_values]; + } + +#ifdef OTTIE_VALUE_BY_VALUE + return result; +#else + return *result; +#endif +} + +#ifndef OTTIE_VALUE_NO_UNDEF + +#undef _T_ +#undef OttieValue +#undef ottie_value_paste_more +#undef ottie_value_paste +#undef ottie_value + +#undef OTTIE_VALUE_BY_VALUE +#undef OTTIE_VALUE_ELEMENT_TYPE +#undef OTTIE_VALUE_FREE_FUNC +#undef OTTIE_VALUE_NAME +#undef OTTIE_VALUE_TYPE_NAME +#endif diff --git a/tests/meson.build b/tests/meson.build index 783dd61914..47492ab41c 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,17 +1,17 @@ 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'], + ['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'],