Files
gtk/ottie/ottiekeyframesimpl.c
Benjamin Otte c2ed71af7a Ottie: Add
2020-12-27 01:12:11 +01:00

383 lines
11 KiB
C

/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include <glib.h>
G_BEGIN_DECLS
#ifndef OTTIE_KEYFRAMES_TYPE_NAME
#define OTTIE_KEYFRAMES_TYPE_NAME OttieKeyframes
#endif
#ifndef OTTIE_KEYFRAMES_NAME
#define OTTIE_KEYFRAMES_NAME ottie_keyframes
#endif
#ifndef OTTIE_KEYFRAMES_ELEMENT_TYPE
#define OTTIE_KEYFRAMES_ELEMENT_TYPE gpointer
#endif
#ifndef OTTIE_KEYFRAMES_DIMENSIONS
#define OTTIE_KEYFRAMES_DIMENSIONS 1
#endif
/* make this readable */
#define _T_ OTTIE_KEYFRAMES_ELEMENT_TYPE
#define OttieKeyframes OTTIE_KEYFRAMES_TYPE_NAME
#define OttieKeyframe OTTIE_KEYFRAMES_TYPE_NAME ## Keyframe
#define OttieControlPoint OTTIE_KEYFRAMES_TYPE_NAME ## ControlPoint
#define ottie_keyframes_paste_more(OTTIE_KEYFRAMES_NAME, func_name) OTTIE_KEYFRAMES_NAME ## _ ## func_name
#define ottie_keyframes_paste(OTTIE_KEYFRAMES_NAME, func_name) ottie_keyframes_paste_more (OTTIE_KEYFRAMES_NAME, func_name)
#define ottie_keyframes(func_name) ottie_keyframes_paste (OTTIE_KEYFRAMES_NAME, func_name)
typedef struct OttieControlPoint OttieControlPoint;
typedef struct OttieKeyframe OttieKeyframe;
typedef struct OttieKeyframes OttieKeyframes;
struct OttieControlPoint
{
double x[OTTIE_KEYFRAMES_DIMENSIONS];
double y[OTTIE_KEYFRAMES_DIMENSIONS];
};
struct OttieKeyframe
{
/* Cubic control points, but Lottie names them in and out points */
OttieControlPoint in;
OttieControlPoint out;
double start_time;
_T_ 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