Compare commits

...

85 Commits

Author SHA1 Message Date
Matthias Clasen
f088650181 gtk-demo: Rewrite the text mask demo
Use GtkSnapshot and GskPath instead of cairo for this.
2020-12-27 02:01:38 -05:00
Matthias Clasen
aadcc60583 path fill demo: Use gsk_path_builder_add_layout
We have an api now to hide the cairo use.
2020-12-27 01:10:02 -05:00
Matthias Clasen
2a70b590f1 gsk: Add gsk_path_builder_add_layout
This api makes it easy to turn text into a path that
can be further manipulated. The implementation currently
goes via cairo.
2020-12-27 01:10:02 -05:00
Benjamin Otte
e9d01c1a63 Ottie: Add ottie-editor 2020-12-27 01:12:11 +01:00
Benjamin Otte
e235392894 ottie: Add a snapshot testsuite test
The test takes a lottie file and a timestamp in seconds and produces a
rendernode at that timestamp.

It then serializes that node and compares it via diff(1) with a file
containing the expected output.

This is a lot stricter than it needs to be (because different node files
can generate the same output and updates to the rendering pipeline can
break everything) but I chose this method on purpose because it does a
good job at guarding against accidental changes in other parts of the
code.

It's also better than comparing image output because it avoids
antialiasing artifacts when using curves and things like that.
2020-12-27 01:12:11 +01:00
Benjamin Otte
73904f3034 ottie: Add a command-line tool
Supports:

 * Taking a screenie:
   ottie image file.lottie image.png

 * Recording a rendernode:
   ottie node file.lottie render.node

 * Encoding an image:
   ottie video file.lottie video.webm
2020-12-27 01:12:11 +01:00
Benjamin Otte
c2ed71af7a Ottie: Add 2020-12-27 01:12:11 +01:00
Benjamin Otte
aa2f6345c8 path: Add gsk_path_builder_add_ellipse() 2020-12-27 00:31:18 +01:00
Benjamin Otte
cef1bc9098 path: Change semantics of gtk_path_builder_add_segment()
Allow start >= end to mean that the path continues at the beginning
after reaching the end until it reaches the point at @end.
2020-12-27 00:31:18 +01:00
Benjamin Otte
828ccc51d9 path: Add gsk_path_measure_is_closed () 2020-12-27 00:31:18 +01:00
Benjamin Otte
8397d10c20 path: Add gsk_path_measure_restrict_to_contour() 2020-12-27 00:31:18 +01:00
Matthias Clasen
78d06e58d0 Add gsk_path_measure_get_{path,tolerance}
These are just nice apis to have and avoid having to carry
these around as extra arguments in many places.

This was showing up as inconvenience in writing tests
for the measure apis.
2020-12-27 00:31:18 +01:00
Benjamin Otte
a5e13cc96b xxx path)_fill 2020-12-27 00:31:18 +01:00
Matthias Clasen
776dc54c8a Add gsk_path_get_stroke_bounds
A relatively cheap way to get bounds for the area
that would be affected by stroking a path.
2020-12-27 00:31:18 +01:00
Benjamin Otte
ee6879fba8 testsuite: Add tests for the dasher 2020-12-27 00:31:18 +01:00
Benjamin Otte
14f9395476 path: Add a foreach function that dashes a path 2020-12-27 00:31:18 +01:00
Benjamin Otte
1349cf2454 path: Deal with non-uniformness of progress parameter
The progress is non-uniform, so simple translation of progress doesn't work.
So check if larger and smaller values inch closer towards minimal distance.
2020-12-27 00:31:18 +01:00
Benjamin Otte
d47ebd388c path: Always decompose conics into at least 2 segments
Conics are evil in that their parameter skews towards the center, and if
it's a very flat conic (weight almost equal to 0), then we'd approximate
it with a single segment and not subdivide, which would cause the
parameter to be wildly off around 0.25 or 0.75.

And that would cause offset calculations to fail.
2020-12-27 00:31:18 +01:00
Matthias Clasen
61214221b3 testsuite Add curve tangent tests 2020-12-27 00:31:18 +01:00
Benjamin Otte
7eb4ed8f86 testsuite: Add a test for the conic that got us segment() 2020-12-27 00:31:18 +01:00
Benjamin Otte
92b472fec1 path: Add gsk_curve_segment()
Using split() twice with scaled t values does not work with conics.
2020-12-27 00:31:18 +01:00
Benjamin Otte
7a6b008479 testsuite: Add a test for gsk_curve_decompose() 2020-12-27 00:31:18 +01:00
Benjamin Otte
d8c26172a5 testuite: Add tests for gsk_curve_get_tangent() 2020-12-27 00:31:18 +01:00
Matthias Clasen
da835321a5 testuite: Add tests for gsk_curve_get_point()
Add a few tests for gsk_curve_get_point().

Since GskCurve is not public api, we add gskcurve.c
as source to the test binary.
2020-12-27 00:31:18 +01:00
Benjamin Otte
30635d1cac curve: Split eval() into get_point() and get_tangent()
That's more in line with the get_start/end_point/tangent() functions.

Plus, those calls are independent and we usually want one or the other.
2020-12-27 00:31:18 +01:00
Matthias Clasen
a60b72ba58 Add gsk_curve_get_{start,end}_tangent
Add a way to get the tangents at the start and end of the curve.
This will be used in stroking.
2020-12-27 00:31:18 +01:00
Benjamin Otte
36347892d8 testsuite: Add conics to the random paths 2020-12-27 00:31:18 +01:00
Benjamin Otte
924f0bc2c5 path: Add GskCurve
GskCurve is an abstraction for path operations. It's essentially a
collection of vfuncs per GskPathOperation.

GskStandardContour has been ported to use it where appropriate.
2020-12-27 00:31:18 +01:00
Benjamin Otte
23c5318de1 path: Introduce gskpathop
A gskpathop is a pointer to a graphene_point_t* with the low bits used
to encode the GskPathOperation. It's an easy way to introduce API for
operations.

So far it's just used to replace GskStandardOperation.
2020-12-27 00:31:18 +01:00
Benjamin Otte
678405bd22 WIP: css: Replace border rendering code with GskPath
The weight is wrong still, I need to compute the correct one to get real
45deg circle corners and not just roughly correct ones.
2020-12-27 00:31:18 +01:00
Benjamin Otte
946b76881b WIP: pathbuilder: Add gsk_path_builder_add_rounded_rect()
It works, but does not use a custom contour yet.
2020-12-27 00:31:18 +01:00
Benjamin Otte
061683344f path: Add conic curves
So far this just adds the API, if you use it, you'll get lots of
g_warnings().

This will be fixed in future commits.
2020-12-27 00:31:18 +01:00
Benjamin Otte
af9a85616d path: Rename to gtk_path_builder_add_segment()
It's about bulding paths, not about measuring them.
2020-12-27 00:31:18 +01:00
Benjamin Otte
0f332de3d3 path: Split contours into their own file
I'm not sure I want to keep all contours in one file, but for now that's
how it is.
2020-12-27 00:31:18 +01:00
Benjamin Otte
15e8f2bacc path: Make all private contour APIs take a GskContour
... instead of a path, index tuple.
2020-12-27 00:31:18 +01:00
Benjamin Otte
25f9eadb0d stroke: Add support for dashes
... and hook it up in the node parser and for Cairo rendering.
2020-12-27 00:31:18 +01:00
Matthias Clasen
ced2959cdd gsk: Implement parsing fill and stroke nodes
Make serialization and deserialization work for stroke and
fill nodes.
2020-12-27 00:31:18 +01:00
Benjamin Otte
3c363f00d5 path: Add flags to gsk_path_foreach()
This way we can default to the siplest possible foreach() output - like
cairo_copy_path_flat() decomposing everything into lines - and add flags
to get more and more fancy.

This will be useful to have conics automatically decomposed for Cairo
drawing or if we want to add more line types in the future.
2020-12-27 00:31:18 +01:00
Benjamin Otte
a9ad37ed89 testsuite: Add an in_fill() test 2020-12-27 00:31:18 +01:00
Matthias Clasen
acdf04287d Implement gsk_path_measure_in_fill
Implement this in the obvious way, using the decomposed form
of standard contours. Since the decomposed form is part of the
measure object, this api moves from gsk_path_in_fill to
gsk_path_measure_in_fill.
2020-12-27 00:31:18 +01:00
Benjamin Otte
34725c1207 testsuite: Add a parsing test
This test includes an implementation of a gsk_path_equal() func with
a tolerance that is necessary because parsing does not always work
100% exactly due to floating point rounding, so we can't just
compare the to_string() output.
2020-12-27 00:31:18 +01:00
Matthias Clasen
cb10cf07e6 path: Special-case rects and circles
Write out the commands for rects and circles in a special
way, and add code in the parser to recognize this, so we
can successfully round-trip these through the SVG path format.

The special way - for people who want to use it for debugging -
for now is that we use uppercase "Z" to close standard paths, but
lowercase "z" to close our special paths.

A test is included, but the random path serializations should take care
of it, too.
2020-12-27 00:31:18 +01:00
Matthias Clasen
a7ba2bde86 path: Fix serialization for circles
The svg A can not do a full circle, since it is a two point
parametrization - if the start and end point are the same,
it draws nothing. So, use two arcs.
2020-12-27 00:31:18 +01:00
Benjamin Otte
87b3cb1df8 testsuite: Add librsvg path tests 2020-12-27 00:31:18 +01:00
Matthias Clasen
2478963cbb path: Implement gsk_path_parse
Implement the SVG path syntax to read back the strings
that we generate when serializing paths. The tests for
this code are taken from librsvg.

This includes an elliptical arc implementation according
to the SVG spec. The code is mostly taken from librsvg,
but pretty directly follows the SVG spec implementation
notes. We don't export this, since the parametrization
is inconvenient. We do want an arc_to API, but
these are not the arcs we are looking for.
2020-12-27 00:31:18 +01:00
Matthias Clasen
abbbaef7e8 path: Implement SVG arcs
This is elliptical arc implementation according to the SVG spec.
The code is mostly taken from librsvg, but pretty directly
follows the SVG spec implementation notes.

We don't export this, since the parametrization is inconvenient.
We do want an arc_to API, but these are not the arcs we are
looking for.

It will be used in parsing SVG path syntax.
2020-12-27 00:31:18 +01:00
Matthias Clasen
69177416a0 stroke: Add miter limit
Add a miter limit to GskStroke. This will be needed to
fully implement line joins.

Also introduce the GSK_LINE_JOIN_MITER_CLIP value,
following SVG 2.0. cairo does not have it, so translate
it to plain miter when using cairo.
2020-12-27 00:31:18 +01:00
Matthias Clasen
ff47bfdbcf Documentation typo fixes 2020-12-27 00:31:18 +01:00
Benjamin Otte
120e449bdd testsuite: Add relative path functions
They're making the paths slightly weirder, but they test public API, so
woohoo!
2020-12-27 00:31:18 +01:00
Benjamin Otte
237c37f6c2 pathbuilder: Add relative path commands
And gsk_path_builder_get_current_point().

They will be needed by the string parser.
2020-12-27 00:31:18 +01:00
Benjamin Otte
683729c388 path: Add GSK_CIRCLE_POINT_INIT() to initialize points on the circle
This is just splitting out a commonly done operation into a macro.
2020-12-27 00:31:18 +01:00
Benjamin Otte
9fe71e7dae pathbuilder: Redo semantics for starting curves
We now always have a "current point" which is either the last point an
operation was made to, or (0, 0) if no drawing operation has
been made yet.

Adding a contour of any kind to the builder will always update the
current point to that contour's end point.
2020-12-27 00:31:18 +01:00
Benjamin Otte
c74970e6c1 xxx: demo 2020-12-27 00:31:18 +01:00
Benjamin Otte
5225d01504 path: Split GskPathBuilder into its own file
... and add missing API docs.
2020-12-27 00:31:18 +01:00
Benjamin Otte
916d950069 testsuite: Add a test using get_point() and get_closest_point() 2020-12-27 00:31:18 +01:00
Benjamin Otte
76d6fed248 testsuite: Add a test for get_point() 2020-12-27 00:31:18 +01:00
Benjamin Otte
7ff6235ae6 testsuite: Update create_random_path()
1. Allow specifying the max number of contours
2. Be smarter about creating the paths:
   With 10% chance, create a "weird" path like the empty one or only
   points or things like that.
   Otherwise create a bunch of contours, with 2/3 a standard contour,
   with 1/3 a predetermined one.
2020-12-27 00:31:18 +01:00
Benjamin Otte
9b957c26cb gtk-demo: Add cute maze demo 2020-12-27 00:31:18 +01:00
Benjamin Otte
445b2382e6 testsuite: Add tests for gsk_path_measure_get_closest_point() 2020-12-27 00:31:18 +01:00
Benjamin Otte
790a4201c3 path: Add gsk_path_measure_get_closest_point()
... and gsk_path_measure_get_closest_point_full().

Those 2 functions allow finding the closest point on a path to a given
point.
2020-12-27 00:31:18 +01:00
Benjamin Otte
00b9af65b5 spline: Use Skia's tolerance checks
This avoids measuring being too far off (it's still off, but it's less
than a percent now.
2020-12-27 00:31:18 +01:00
Benjamin Otte
f7c367338c testsuite: Add tests for gsk_path_measure_add_segment() 2020-12-27 00:31:18 +01:00
Benjamin Otte
5d69887618 gtk-demo: Add a text-on-path demo 2020-12-27 00:31:18 +01:00
Benjamin Otte
b658c26a3e xxx: path_fill demo 2020-12-27 00:31:18 +01:00
Benjamin Otte
e4c147cf2f path: Add gsk_path_measure_get_point()
Allows querying the coordinates and direction of any specific point on a
path.
2020-12-27 00:31:18 +01:00
Matthias Clasen
fc2c6f1566 path: Add gsk_path_add_circle()
Adds a circle contour, too.
2020-12-27 00:31:18 +01:00
Benjamin Otte
b0d6130905 pathmeasure: Implement support for beziers
Instead of treating bezier curves as lines, we properly decompose them
into line segments now so that we can treat those as lines.
2020-12-27 00:31:18 +01:00
Benjamin Otte
a0b9238a15 path: Implement gsk_path_to_cairo() using foreach() 2020-12-27 00:31:18 +01:00
Benjamin Otte
801c63494c path: Add gsk_path_foreach() 2020-12-27 00:31:17 +01:00
Benjamin Otte
d2cfe74140 path: Collect flags
We don't need them yet, but maybe later.
2020-12-27 00:31:17 +01:00
Benjamin Otte
a3ac003546 testsuite: Add path tests 2020-12-27 00:31:17 +01:00
Benjamin Otte
483a4773cd pathmeasure: Add gsk_path_measure_add_segment()
This allows chunking paths, weeee.
2020-12-27 00:31:17 +01:00
Benjamin Otte
c82ad0214e path: Add gsk_path_builder_add_path() 2020-12-27 00:31:17 +01:00
Benjamin Otte
207feeb7a9 gsk: Add GskPathMeasure
An object to do measuring operations on paths - determining their
length, cutting off subpaths, things like that.
2020-12-27 00:31:17 +01:00
Benjamin Otte
9a2e4ac9a9 path: Change data structure for standard path
Instead of the Cairo method and imitating cairo_path_data_t, use the
Skia method and keep points and operations separate.

That way we get a points array that includes the starting point -
because it's always the end point of the previous operation.
2020-12-27 00:31:17 +01:00
Benjamin Otte
2bd3aa3cbe popover: Use fill and stroke nodes instead of Cairo
... to render the arrow.

The arrow should really be turned into a real thing - maybe an icon?
2020-12-27 00:31:17 +01:00
Benjamin Otte
118e41f432 snapshot: Add gtk_snapshot_push_stroke() 2020-12-27 00:31:17 +01:00
Benjamin Otte
2d7897a769 gsk: Add GskStrokeNode 2020-12-27 00:31:17 +01:00
Benjamin Otte
c279e1aa1e gsk: Add GskStroke
It's unused in this commit. This just prepares the new object.
2020-12-27 00:31:17 +01:00
Benjamin Otte
3c91d0be91 demos: Add a simple demo filling a path 2020-12-27 00:31:17 +01:00
Benjamin Otte
e7bbed54b5 snapshot: Add gtk_snapshot_push_fill() 2020-12-27 00:31:17 +01:00
Benjamin Otte
4eb7d68970 gsk: Add GskFillNode
Take a rendernode as source and a GskPath and fill the region in the
path just like cairo_fill() would.
2020-12-27 00:31:17 +01:00
Benjamin Otte
98337f7115 gsk: Add GskPath 2020-12-27 00:31:17 +01:00
Benjamin Otte
08ee6c7acf mediafile: Load extension at startup with GTK_MEDIA
When the GTK_MEDIA env var is set, check at startup that it works, not
only when the first MeidaFile is instantiated.

This has the fortunate side effect that it prints help output for
GTK_MEDIA=help at startup, too.
2020-12-27 00:31:17 +01:00
Benjamin Otte
c815496fe3 gtk: Build as static library first
This allows linking against the static libgtk from the testsuite.

We build the dynamic library by linking all the static libraries into
the final product.
2020-12-27 00:31:16 +01:00
160 changed files with 26660 additions and 149 deletions

View File

@@ -319,6 +319,9 @@
<file>paintable_svg.c</file>
<file>panes.c</file>
<file>password_entry.c</file>
<file>path_fill.c</file>
<file>path_maze.c</file>
<file>path_text.c</file>
<file>peg_solitaire.c</file>
<file>pickers.c</file>
<file>printing.c</file>
@@ -403,6 +406,9 @@
<gresource prefix="/fontrendering">
<file>fontrendering.ui</file>
</gresource>
<gresource prefix="/path_text">
<file>path_text.ui</file>
</gresource>
<gresource prefix="/org/gtk/Demo4">
<file>icons/16x16/actions/application-exit.png</file>
<file>icons/16x16/actions/document-new.png</file>

View File

@@ -68,6 +68,9 @@ demos = files([
'paintable_mediastream.c',
'panes.c',
'password_entry.c',
'path_fill.c',
'path_maze.c',
'path_text.c',
'peg_solitaire.c',
'pickers.c',
'printing.c',

348
demos/gtk-demo/path_fill.c Normal file
View File

@@ -0,0 +1,348 @@
/* Path/Fill
*
* This demo shows how to use PangoCairo to draw text with more than
* just a single color.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#include "gsk/gskpathdashprivate.h"
#define GTK_TYPE_PATH_PAINTABLE (gtk_path_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathPaintable, gtk_path_paintable, GTK, PATH_PAINTABLE, GObject)
struct _GtkPathPaintable
{
GObject parent_instance;
int width;
int height;
GskPath *path;
GdkPaintable *background;
};
struct _GtkPathPaintableClass
{
GObjectClass parent_class;
};
static int
gtk_path_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_width (self->background), self->width);
else
return self->width;
}
static int
gtk_path_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_height (self->background), self->height);
else
return self->height;
}
static void
gtk_path_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
#if 0
gtk_snapshot_push_fill (snapshot, self->path, GSK_FILL_RULE_WINDING);
#else
GskStroke *stroke = gsk_stroke_new (2.0);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
#endif
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static GdkPaintableFlags
gtk_path_paintable_get_flags (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return gdk_paintable_get_flags (self->background);
else
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_path_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->get_intrinsic_width = gtk_path_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_path_paintable_get_intrinsic_height;
iface->snapshot = gtk_path_paintable_snapshot;
iface->get_flags = gtk_path_paintable_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkPathPaintable, gtk_path_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_path_paintable_paintable_init))
/* Here's the boilerplate for the GObject declaration.
* We don't need to do anything special here, because we keep no
* data of our own.
*/
static void
gtk_path_paintable_class_init (GtkPathPaintableClass *klass)
{
}
static void
gtk_path_paintable_init (GtkPathPaintable *self)
{
}
/* And finally, we add a simple constructor.
* It is declared in the header so that the other examples
* can use it.
*/
GdkPaintable *
gtk_path_paintable_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkPathPaintable *self;
self = g_object_new (GTK_TYPE_PATH_PAINTABLE, NULL);
self->path = path;
self->background = background;
if (self->background)
{
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
}
self->width = width;
self->height = height;
return GDK_PAINTABLE (self);
}
void
gtk_path_paintable_set_path (GtkPathPaintable *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
self->path = gsk_path_ref (path);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
}
static GskPath *
create_hexagon (GtkWidget *widget)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 120, 0);
gsk_path_builder_line_to (builder, 360, 0);
gsk_path_builder_line_to (builder, 480, 208);
gsk_path_builder_line_to (builder, 360, 416);
gsk_path_builder_line_to (builder, 120, 416);
gsk_path_builder_line_to (builder, 0, 208);
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskPath *
create_path_from_text (GtkWidget *widget)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
layout = gtk_widget_create_pango_layout (widget, "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
return gsk_path_builder_free_to_path (builder);
}
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static gboolean
update_path (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer measure)
{
float progress = gdk_frame_clock_get_frame_time (frame_clock) % (60 * G_USEC_PER_SEC) / (float) (30 * G_USEC_PER_SEC);
GskPathBuilder *builder;
GskPath *path;
graphene_point_t pos;
graphene_vec2_t tangent;
GskStroke *stroke;
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder,
measure,
#if 1
0.0, gsk_path_measure_get_length (measure));
#else
progress > 1 ? (progress - 1) * gsk_path_measure_get_length (measure) : 0.0,
(progress < 1 ? progress : 1.0) * gsk_path_measure_get_length (measure));
#endif
path = gsk_path_builder_free_to_path (builder);
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, (float[2]) { 10, 5 }, 2);
gsk_stroke_set_dash_offset (stroke, - (gdk_frame_clock_get_frame_time (frame_clock) % G_USEC_PER_SEC) * 15. / G_USEC_PER_SEC);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.2, build_path, builder);
gsk_path_unref (path);
gsk_path_measure_get_point (measure,
(progress > 1 ? (progress - 1) : progress) * gsk_path_measure_get_length (measure),
&pos,
&tangent);
gsk_path_builder_move_to (builder, pos.x + 5 * graphene_vec2_get_x (&tangent), pos.y + 5 * graphene_vec2_get_y (&tangent));
gsk_path_builder_line_to (builder, pos.x + 3 * graphene_vec2_get_y (&tangent), pos.y + 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_line_to (builder, pos.x - 3 * graphene_vec2_get_y (&tangent), pos.y - 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_close (builder);
path = gsk_path_builder_free_to_path (builder);
gtk_path_paintable_set_path (GTK_PATH_PAINTABLE (gtk_picture_get_paintable (GTK_PICTURE (widget))),
path);
gsk_path_unref (path);
return G_SOURCE_CONTINUE;
}
GtkWidget *
do_path_fill (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *picture;
GdkPaintable *paintable;
GtkMediaStream *stream;
GskPath *path;
graphene_rect_t bounds;
GskPathMeasure *measure;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_hexagon (window);
path = create_path_from_text (window);
gsk_path_get_bounds (path, &bounds);
paintable = gtk_path_paintable_new (path,
GDK_PAINTABLE (stream),
bounds.origin.x + bounds.size.width,
bounds.origin.y + bounds.size.height);
picture = gtk_picture_new_for_paintable (paintable);
measure = gsk_path_measure_new (path);
gtk_widget_add_tick_callback (picture, update_path, measure, (GDestroyNotify) gsk_path_measure_unref);
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (picture), FALSE);
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
g_object_unref (paintable);
gtk_window_set_child (GTK_WINDOW (window), picture);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

338
demos/gtk-demo/path_maze.c Normal file
View File

@@ -0,0 +1,338 @@
/* Path/Maze
*
* This demo shows how to use a GskPath to create a maze and use
* gsk_path_measure_get_closest_point() to check the mouse stays
* on the path.
*
* It also shows off the performance of GskPath (or not) as this
* is a rather complex path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#define MAZE_GRID_SIZE 20
#define MAZE_STROKE_SIZE_ACTIVE (MAZE_GRID_SIZE - 4)
#define MAZE_STROKE_SIZE_INACTIVE (MAZE_GRID_SIZE - 12)
#define MAZE_WIDTH 31
#define MAZE_HEIGHT 21
#define GTK_TYPE_MAZE (gtk_maze_get_type ())
G_DECLARE_FINAL_TYPE (GtkMaze, gtk_maze, GTK, MAZE, GtkWidget)
struct _GtkMaze
{
GtkWidget parent_instance;
int width;
int height;
GskPath *path;
GskPathMeasure *measure;
GdkPaintable *background;
gboolean active;
};
struct _GtkMazeClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (GtkMaze, gtk_maze, GTK_TYPE_WIDGET)
static void
gtk_maze_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkMaze *self = GTK_MAZE (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = self->width;
else
*minimum = *natural = self->height;
}
static void
gtk_maze_snapshot (GtkWidget *widget,
GdkSnapshot *snapshot)
{
GtkMaze *self = GTK_MAZE (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskStroke *stroke;
stroke = gsk_stroke_new (MAZE_STROKE_SIZE_INACTIVE);
if (self->active)
gsk_stroke_set_line_width (stroke, MAZE_STROKE_SIZE_ACTIVE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static void
gtk_maze_dispose (GObject *object)
{
GtkMaze *self = GTK_MAZE (object);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
if (self->background)
{
g_signal_handlers_disconnect_matched (self->background, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
g_clear_object (&self->background);
}
G_OBJECT_CLASS (gtk_maze_parent_class)->dispose (object);
}
static void
gtk_maze_class_init (GtkMazeClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_maze_dispose;
widget_class->measure = gtk_maze_measure;
widget_class->snapshot = gtk_maze_snapshot;
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkMaze *self)
{
if (!self->active)
return;
if (gsk_path_measure_get_closest_point (self->measure, &GRAPHENE_POINT_INIT (x, y), NULL) <= MAZE_STROKE_SIZE_ACTIVE / 2.0f)
return;
self->active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkMaze *self)
{
if (!self->active)
{
self->active = TRUE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
gtk_maze_init (GtkMaze *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->active = TRUE;
}
static void
gtk_maze_set_path (GtkMaze *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
self->path = gsk_path_ref (path);
self->measure = gsk_path_measure_new (path);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
GtkWidget *
gtk_maze_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkMaze *self;
self = g_object_new (GTK_TYPE_MAZE, NULL);
gtk_maze_set_path (self, path);
gsk_path_unref (path);
self->background = background;
if (self->background)
{
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gtk_widget_queue_draw), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gtk_widget_queue_resize), self);
}
self->width = width;
self->height = height;
return GTK_WIDGET (self);
}
static void
add_point_to_maze (GtkBitset *maze,
GskPathBuilder *builder,
guint x,
guint y)
{
gboolean set[4] = { };
guint dir;
gtk_bitset_add (maze, y * MAZE_WIDTH + x);
while (TRUE)
{
set[0] = set[0] || x == 0 || gtk_bitset_contains (maze, y * MAZE_WIDTH + x - 1);
set[1] = set[1] || y == 0 || gtk_bitset_contains (maze, (y - 1) * MAZE_WIDTH + x);
set[2] = set[2] || x + 1 == MAZE_WIDTH || gtk_bitset_contains (maze, y * MAZE_WIDTH + x + 1);
set[3] = set[3] || y + 1 == MAZE_HEIGHT || gtk_bitset_contains (maze, (y + 1) * MAZE_WIDTH + x);
if (set[0] && set[1] && set[2] && set[3])
return;
do
{
dir = g_random_int_range (0, 4);
}
while (set[dir]);
switch (dir)
{
case 0:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x - 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x - 1, y);
break;
case 1:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y - 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y - 1);
break;
case 2:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 1.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x + 1, y);
break;
case 3:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 1.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y + 1);
break;
default:
g_assert_not_reached ();
break;
}
}
}
static GskPath *
create_path_for_maze (GtkWidget *widget)
{
GskPathBuilder *builder;
GtkBitset *maze;
builder = gsk_path_builder_new ();
maze = gtk_bitset_new_empty ();
/* make sure the outer lines are unreachable:
* Set the full range, then remove the center again. */
gtk_bitset_add_range (maze, 0, MAZE_WIDTH * MAZE_HEIGHT);
gtk_bitset_remove_rectangle (maze, MAZE_WIDTH + 1, MAZE_WIDTH - 2, MAZE_HEIGHT - 2, MAZE_WIDTH);
/* Fill the maze */
add_point_to_maze (maze, builder, MAZE_WIDTH / 2, MAZE_HEIGHT / 2);
/* Add start and stop lines */
gsk_path_builder_move_to (builder, 1.5 * MAZE_GRID_SIZE, -0.5 * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, 1.5 * MAZE_GRID_SIZE, 1.5 * MAZE_GRID_SIZE);
gsk_path_builder_move_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT - 1.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT + 0.5) * MAZE_GRID_SIZE);
gtk_bitset_unref (maze);
return gsk_path_builder_free_to_path (builder);
}
GtkWidget *
do_path_maze (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *maze;
GtkMediaStream *stream;
GskPath *path;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Follow the maze with the mouse");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_path_for_maze (window);
maze = gtk_maze_new (path,
GDK_PAINTABLE (stream),
MAZE_WIDTH * MAZE_GRID_SIZE,
MAZE_HEIGHT * MAZE_GRID_SIZE);
gtk_window_set_child (GTK_WINDOW (window), maze);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

590
demos/gtk-demo/path_text.c Normal file
View File

@@ -0,0 +1,590 @@
/* Path/Text
*
* This demo shows how to use GskPath to animate a path along another path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#define GTK_TYPE_PATH_WIDGET (gtk_path_widget_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathWidget, gtk_path_widget, GTK, PATH_WIDGET, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_TEXT,
PROP_EDITABLE,
N_PROPS
};
struct _GtkPathWidget
{
GtkWidget parent_instance;
char *text;
gboolean editable;
graphene_point_t points[4];
guint active_point;
float line_closest;
GskPath *line_path;
GskPathMeasure *line_measure;
GskPath *text_path;
GdkPaintable *background;
};
struct _GtkPathWidgetClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathWidget, gtk_path_widget, GTK_TYPE_WIDGET)
static GskPath *
create_path_from_text (GtkWidget *widget,
const char *text)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_path_t *path;
PangoLayout *layout;
PangoFontDescription *desc;
GskPath *result;
surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
cr = cairo_create (surface);
layout = gtk_widget_create_pango_layout (widget, text);
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
cairo_move_to (cr, 0, - pango_layout_get_baseline (layout) / (double) PANGO_SCALE);
pango_cairo_layout_path (cr, layout);
path = cairo_copy_path_flat (cr);
result = gsk_path_new_from_cairo (path);
cairo_path_destroy (path);
g_object_unref (layout);
cairo_destroy (cr);
cairo_surface_destroy (surface);
return result;
}
typedef struct
{
GskPathMeasure *measure;
GskPathBuilder *builder;
double scale;
} GtkPathTransform;
static void
gtk_path_transform_point (GskPathMeasure *measure,
const graphene_point_t *pt,
float scale,
graphene_point_t *res)
{
graphene_vec2_t tangent;
gsk_path_measure_get_point (measure, pt->x * scale, res, &tangent);
res->x -= pt->y * scale * graphene_vec2_get_y (&tangent);
res->y += pt->y * scale * graphene_vec2_get_x (&tangent);
}
static gboolean
gtk_path_transform_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GtkPathTransform *transform = data;
switch (op)
{
case GSK_PATH_MOVE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[0], transform->scale, &res);
gsk_path_builder_move_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_LINE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res);
gsk_path_builder_line_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_CURVE:
{
graphene_point_t res[3];
gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], transform->scale, &res[1]);
gtk_path_transform_point (transform->measure, &pts[3], transform->scale, &res[2]);
gsk_path_builder_curve_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, res[2].x, res[2].y);
}
break;
case GSK_PATH_CONIC:
{
graphene_point_t res[2];
gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], transform->scale, &res[1]);
gsk_path_builder_conic_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, weight);
}
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (transform->builder);
break;
default:
g_assert_not_reached();
return FALSE;
}
return TRUE;
}
static GskPath *
gtk_path_transform (GskPathMeasure *measure,
GskPath *path)
{
GtkPathTransform transform = { measure, gsk_path_builder_new () };
graphene_rect_t bounds;
gsk_path_get_bounds (path, &bounds);
if (bounds.origin.x + bounds.size.width > 0)
transform.scale = gsk_path_measure_get_length (measure) / (bounds.origin.x + bounds.size.width);
else
transform.scale = 1.0f;
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVE, gtk_path_transform_op, &transform);
return gsk_path_builder_free_to_path (transform.builder);
}
static void
gtk_path_widget_clear_text_path (GtkPathWidget *self)
{
g_clear_pointer (&self->text_path, gsk_path_unref);
}
static void
gtk_path_widget_clear_paths (GtkPathWidget *self)
{
gtk_path_widget_clear_text_path (self);
g_clear_pointer (&self->line_path, gsk_path_unref);
g_clear_pointer (&self->line_measure, gsk_path_measure_unref);
}
static void
gtk_path_widget_create_text_path (GtkPathWidget *self)
{
GskPath *path;
gtk_path_widget_clear_text_path (self);
if (self->line_measure == NULL)
return;
path = create_path_from_text (GTK_WIDGET (self), self->text);
self->text_path = gtk_path_transform (self->line_measure, path);
gsk_path_unref (path);
}
static void
gtk_path_widget_create_paths (GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
GskPathBuilder *builder;
gtk_path_widget_clear_paths (self);
if (width <= 0 || height <= 0)
return;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder,
self->points[0].x * width, self->points[0].y * height);
gsk_path_builder_curve_to (builder,
self->points[1].x * width, self->points[1].y * height,
self->points[2].x * width, self->points[2].y * height,
self->points[3].x * width, self->points[3].y * height);
self->line_path = gsk_path_builder_free_to_path (builder);
self->line_measure = gsk_path_measure_new (self->line_path);
gtk_path_widget_create_text_path (self);
}
static void
gtk_path_widget_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
GTK_WIDGET_CLASS (gtk_path_widget_parent_class)->size_allocate (widget, width, height, baseline);
gtk_path_widget_create_paths (self);
}
static void
gtk_path_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskPath *path;
GskStroke *stroke;
gsize i;
/* frosted glass the background */
gtk_snapshot_push_blur (snapshot, 100);
gdk_paintable_snapshot (self->background, snapshot, width, height);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 0.6 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the text */
if (self->text_path)
{
gtk_snapshot_push_fill (snapshot, self->text_path, GSK_FILL_RULE_WINDING);
gdk_paintable_snapshot (self->background, snapshot, width, height);
/* ... with an emboss effect */
stroke = gsk_stroke_new (2.0);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT(1, 1));
gtk_snapshot_push_stroke (snapshot, self->text_path, stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 0.2 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gsk_stroke_free (stroke);
gtk_snapshot_pop (snapshot);
gtk_snapshot_pop (snapshot);
}
if (self->editable && self->line_path)
{
GskPathBuilder *builder;
/* draw the control line */
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, self->line_path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the points */
builder = gsk_path_builder_new ();
for (i = 0; i < 4; i++)
{
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), POINT_SIZE);
}
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
if (self->line_closest >= 0)
{
GskPathBuilder *builder;
graphene_point_t closest;
builder = gsk_path_builder_new ();
gsk_path_measure_get_point (self->line_measure, self->line_closest, &closest, NULL);
gsk_path_builder_add_circle (builder, &closest, POINT_SIZE);
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
}
static void
gtk_path_widget_set_text (GtkPathWidget *self,
const char *text)
{
if (g_strcmp0 (self->text, text) == 0)
return;
g_free (self->text);
self->text = g_strdup (text);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT]);
}
static void
gtk_path_widget_set_editable (GtkPathWidget *self,
gboolean editable)
{
if (self->editable == editable)
return;
self->editable = editable;
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITABLE]);
}
static void
gtk_path_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
gtk_path_widget_set_text (self, g_value_get_string (value));
break;
case PROP_EDITABLE:
gtk_path_widget_set_editable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
g_value_set_string (value, self->text);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, self->editable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_dispose (GObject *object)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
gtk_path_widget_clear_paths (self);
G_OBJECT_CLASS (gtk_path_widget_parent_class)->dispose (object);
}
static void
gtk_path_widget_class_init (GtkPathWidgetClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_widget_dispose;
object_class->set_property = gtk_path_widget_set_property;
object_class->get_property = gtk_path_widget_get_property;
widget_class->size_allocate = gtk_path_widget_allocate;
widget_class->snapshot = gtk_path_widget_snapshot;
properties[PROP_TEXT] =
g_param_spec_string ("text",
"text",
"Text transformed along a path",
NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_EDITABLE] =
g_param_spec_boolean ("editable",
"editable",
"If the path can be edited by the user",
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
drag_begin (GtkGestureDrag *gesture,
double x,
double y,
GtkPathWidget *self)
{
graphene_point_t mouse = GRAPHENE_POINT_INIT (x, y);
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
gsize i;
for (i = 0; i < 4; i++)
{
if (graphene_point_distance (&GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), &mouse, NULL, NULL) <= POINT_SIZE)
{
self->active_point = i;
break;
}
}
if (i == 4)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
drag_update (GtkGestureDrag *drag,
double offset_x,
double offset_y,
GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
double start_x, start_y;
gtk_gesture_drag_get_start_point (drag, &start_x, &start_y);
self->points[self->active_point] = GRAPHENE_POINT_INIT ((start_x + offset_x) / width,
(start_y + offset_y) / height);
self->points[self->active_point].x = CLAMP (self->points[self->active_point].x, 0, 1);
self->points[self->active_point].y = CLAMP (self->points[self->active_point].y, 0, 1);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkPathWidget *self)
{
gsk_path_measure_get_closest_point_full (self->line_measure,
&GRAPHENE_POINT_INIT (x, y),
INFINITY,
&self->line_closest,
NULL, NULL, NULL);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkPathWidget *self)
{
self->line_closest = -1;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_path_widget_init (GtkPathWidget *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
g_signal_connect (controller, "drag-end", G_CALLBACK (drag_update), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "enter", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->line_closest = -1;
self->points[0] = GRAPHENE_POINT_INIT (0.1, 0.9);
self->points[1] = GRAPHENE_POINT_INIT (0.3, 0.1);
self->points[2] = GRAPHENE_POINT_INIT (0.7, 0.1);
self->points[3] = GRAPHENE_POINT_INIT (0.9, 0.9);
self->background = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
gtk_path_widget_set_text (self, "It's almost working");
}
GtkWidget *
gtk_path_widget_new (void)
{
GtkPathWidget *self;
self = g_object_new (GTK_TYPE_PATH_WIDGET, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_text (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_WIDGET);
builder = gtk_builder_new_from_resource ("/path_text/path_text.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_widget_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Text along a Path</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type="end">
<object class="GtkToggleButton" id="edit-toggle">
<property name="icon-name">document-edit-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkRevealer">
<property name="reveal-child" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<child>
<object class="GtkEntry" id="text">
<property name="text">Through the looking glass</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkPathWidget" id="view">
<property name="editable" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<property name="text" bind-source="text" bind-property="text" bind-flags="sync-create"></property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -7,68 +7,186 @@
#include <glib/gi18n.h>
#include <gtk/gtk.h>
static void
draw_text (GtkDrawingArea *da,
cairo_t *cr,
int width,
int height,
gpointer data)
#define TEXT_TYPE_MASK_DEMO (text_mask_demo_get_type ())
G_DECLARE_FINAL_TYPE (TextMaskDemo, text_mask_demo, TEXT, MASK_DEMO, GtkWidget)
struct _TextMaskDemo
{
cairo_pattern_t *pattern;
PangoLayout *layout;
GtkWidget parent_instance;
char *text;
PangoFontDescription *desc;
GskPath *path;
};
cairo_save (cr);
struct _TextMaskDemoClass
{
GtkWidgetClass parent_class;
};
layout = gtk_widget_create_pango_layout (GTK_WIDGET (da), "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("sans bold 34");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
G_DEFINE_TYPE (TextMaskDemo, text_mask_demo, GTK_TYPE_WIDGET)
cairo_move_to (cr, 30, 20);
pango_cairo_layout_path (cr, layout);
g_object_unref (layout);
static void
update_path (TextMaskDemo *demo)
{
PangoLayout *layout;
GskPathBuilder *builder;
pattern = cairo_pattern_create_linear (0.0, 0.0, width, height);
cairo_pattern_add_color_stop_rgb (pattern, 0.0, 1.0, 0.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.2, 1.0, 0.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.3, 1.0, 1.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.4, 0.0, 1.0, 0.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.6, 0.0, 1.0, 1.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.7, 0.0, 0.0, 1.0);
cairo_pattern_add_color_stop_rgb (pattern, 0.8, 1.0, 0.0, 1.0);
cairo_pattern_add_color_stop_rgb (pattern, 1.0, 1.0, 0.0, 1.0);
g_clear_pointer (&demo->path, gsk_path_unref);
cairo_set_source (cr, pattern);
cairo_fill_preserve (cr);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (demo), demo->text);
pango_layout_set_font_description (layout, demo->desc);
cairo_pattern_destroy (pattern);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
demo->path = gsk_path_builder_free_to_path (builder);
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
cairo_set_line_width (cr, 0.5);
cairo_stroke (cr);
gtk_widget_queue_draw (GTK_WIDGET (demo));
}
cairo_restore (cr);
static void
text_mask_demo_init (TextMaskDemo *demo)
{
demo->text = g_strdup ("No text. No fun");
demo->desc = pango_font_description_from_string ("Cantarell 20");
update_path (demo);
}
static void
text_mask_demo_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
TextMaskDemo *demo = TEXT_MASK_DEMO (widget);
float width, height;
GskColorStop stops[4];
GskStroke *stroke;
gdk_rgba_parse (&stops[0].color, "red");
gdk_rgba_parse (&stops[1].color, "green");
gdk_rgba_parse (&stops[2].color, "yellow");
gdk_rgba_parse (&stops[3].color, "blue");
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
stops[0].offset = 0;
stops[1].offset = 0.25;
stops[2].offset = 0.50;
stops[3].offset = 0.75;
gtk_snapshot_push_fill (snapshot, demo->path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
stops, 4);
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, demo->path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot,
&(GdkRGBA){ 0, 0, 0, 1 },
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
}
void
text_mask_demo_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum_size,
int *natural_size,
int *minimum_baseline,
int *natural_baseline)
{
TextMaskDemo *demo = TEXT_MASK_DEMO (widget);
graphene_rect_t rect;
gsk_path_get_bounds (demo->path, &rect);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum_size = *natural_size = rect.size.width;
else
*minimum_size = *natural_size = rect.size.height;
}
static void
text_mask_demo_finalize (GObject *object)
{
TextMaskDemo *demo = TEXT_MASK_DEMO (object);
g_free (demo->text);
pango_font_description_free (demo->desc);
gsk_path_unref (demo->path);
G_OBJECT_CLASS (text_mask_demo_parent_class)->finalize (object);
}
static void
text_mask_demo_class_init (TextMaskDemoClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->finalize = text_mask_demo_finalize;
widget_class->snapshot = text_mask_demo_snapshot;
widget_class->measure = text_mask_demo_measure;
}
static GtkWidget *
text_mask_demo_new (void)
{
return g_object_new (text_mask_demo_get_type (), NULL);
}
static void
text_mask_demo_set_text (TextMaskDemo *demo,
const char *text)
{
g_free (demo->text);
demo->text = g_strdup (text);
update_path (demo);
}
static void
text_mask_demo_set_font (TextMaskDemo *demo,
PangoFontDescription *desc)
{
pango_font_description_free (demo->desc);
demo->desc = pango_font_description_copy (desc);
update_path (demo);
}
GtkWidget *
do_textmask (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
static GtkWidget *da;
static GtkWidget *demo;
if (!window)
{
PangoFontDescription *desc;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_widget_set_size_request (window, 400, 240);
gtk_window_set_title (GTK_WINDOW (window), "Text Mask");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
da = gtk_drawing_area_new ();
demo = text_mask_demo_new ();
text_mask_demo_set_text (TEXT_MASK_DEMO (demo), "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("Sans Bold 34");
text_mask_demo_set_font (TEXT_MASK_DEMO (demo), desc);
pango_font_description_free (desc);
gtk_window_set_child (GTK_WINDOW (window), da);
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_text, NULL, NULL);
gtk_window_set_child (GTK_WINDOW (window), demo);
}
if (!gtk_widget_get_visible (window))

View File

@@ -23,6 +23,14 @@
<xi:include href="xml/GskGLShader.xml" />
</reference>
<part id="paths">
<title>Paths</title>
<xi:include href="xml/GskPath.xml" />
<xi:include href="xml/GskPathBuilder.xml" />
<xi:include href="xml/GskPathMeasure.xml" />
<xi:include href="xml/GskStroke.xml" />
</part>
<index id="api-index-full">
<title>Index of all symbols</title>
<xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>

View File

@@ -182,6 +182,12 @@ gsk_rounded_clip_node_new
gsk_rounded_clip_node_get_child
gsk_rounded_clip_node_get_clip
<SUBSECTION>
GskFillRule
gsk_fill_node_new
gsk_fill_node_get_child
gsk_fill_node_get_path
gsk_fill_node_get_fill_rule
<SUBSECTION>
GskShadow
gsk_shadow_node_new
gsk_shadow_node_get_shadow
@@ -235,6 +241,7 @@ GSK_TYPE_CONTAINER_NODE
GSK_TYPE_CONIC_GRADIENT_NODE
GSK_TYPE_CROSS_FADE_NODE
GSK_TYPE_DEBUG_NODE
GSK_TYPE_FILL_NODE
GSK_TYPE_GL_SHADER_NODE
GSK_TYPE_INSET_SHADOW_NODE
GSK_TYPE_LINEAR_GRADIENT_NODE
@@ -265,6 +272,7 @@ gsk_conic_gradient_node_get_type
gsk_container_node_get_type
gsk_cross_fade_node_get_type
gsk_debug_node_get_type
gsk_fill_node_get_type
gsk_gl_shader_node_get_type
gsk_inset_shadow_node_get_type
gsk_linear_gradient_node_get_type
@@ -306,6 +314,115 @@ gsk_rounded_rect_intersects_rect
GSK_TYPE_CORNER
</SECTION>
<SECTION>
<FILE>GskPath</FILE>
<SUBSECTION>
GskPath
gsk_path_ref
gsk_path_unref
gsk_path_new_rect
gsk_path_new_from_cairo
gsk_path_parse
<SUBSECTION>
gsk_path_print
gsk_path_to_string
gsk_path_to_cairo
<SUBSECTION>
gsk_path_is_empty
gsk_path_get_bounds
gsk_path_get_stroke_bounds
<SUBSECTION>
GskPathOperation
GskPathForeachFlags
GskPathForeachFunc
gsk_path_foreach
<SUBSECTION Private>
GSK_TYPE_PATH
gsk_path_get_type
</SECTION>
<SECTION>
<FILE>GskPathBuilder</FILE>
GskPathBuilder
gsk_path_builder_new
gsk_path_builder_ref
gsk_path_builder_unref
gsk_path_builder_to_path
gsk_path_builder_free_to_path
<SUBSECTION>
gsk_path_builder_get_current_point
<SUBSECTION>
gsk_path_builder_add_rect
gsk_path_builder_add_rounded_rect
gsk_path_builder_add_circle
gsk_path_builder_add_ellipse
gsk_path_builder_add_path
gtk_path_builder_add_segment
gtk_path_builder_add_layout
<SUBSECTION>
gsk_path_builder_move_to
gsk_path_builder_rel_move_to
gsk_path_builder_line_to
gsk_path_builder_rel_line_to
gsk_path_builder_curve_to
gsk_path_builder_rel_curve_to
gsk_path_builder_conic_to
gsk_path_builder_rel_conic_to
gsk_path_builder_close
<SUBSECTION Private>
GSK_TYPE_PATH_BUILDER
gsk_path_builder_get_type
</SECTION>
<SECTION>
<FILE>GskPathMeasure</FILE>
GskPathMeasure
gsk_path_measure_new
gsk_path_measure_new_with_tolerance
gsk_path_measure_ref
gsk_path_measure_unref
<SUBSECTION>
gsk_path_measure_get_path
gsk_path_measure_get_tolerance
gsk_path_measure_get_n_contours
gsk_path_measure_restrict_to_contour
<SUBSECTION>
gsk_path_measure_get_length
gsk_path_measure_is_closed
gsk_path_measure_get_point
gsk_path_measure_get_closest_point
gsk_path_measure_get_closest_point_full
gsk_path_measure_in_fill
<SUBSECTION Private>
GSK_TYPE_PATH_MEASURE
gsk_path_measure_get_type
</SECTION>
<SECTION>
<FILE>GskStroke</FILE>
GskLineCap
GskLineJoin
gsk_stroke_new
gsk_stroke_copy
gsk_stroke_free
gsk_stroke_equal
gsk_stroke_set_line_width
gsk_stroke_get_line_width
gsk_stroke_set_line_join
gsk_stroke_get_line_join
gsk_stroke_set_line_cap
gsk_stroke_get_line_cap
gsk_stroke_set_miter_limit
gsk_stroke_get_miter_limit
gsk_stroke_set_dash
gsk_stroke_get_dash
gsk_stroke_set_dash_offset
gsk_stroke_get_dash_offset
<SUBSECTION Private>
GSK_TYPE_STROKE
gsk_stroke_get_type
</SECTION>
<SECTION>
<FILE>GskTransform</FILE>
GskTransform

View File

@@ -4295,6 +4295,8 @@ gtk_snapshot_push_color_matrix
gtk_snapshot_push_repeat
gtk_snapshot_push_clip
gtk_snapshot_push_rounded_clip
gtk_snapshot_push_fill
gtk_snapshot_push_stroke
gtk_snapshot_push_cross_fade
gtk_snapshot_push_blend
gtk_snapshot_push_blur

View File

@@ -269,6 +269,8 @@ collect_reused_child_nodes (GskRenderer *renderer,
case GSK_BLEND_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
default:
@@ -855,6 +857,8 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer,
case GSK_CROSS_FADE_NODE:
case GSK_BLUR_NODE:
case GSK_GL_SHADER_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
default:
break; /* Fallback */
}

View File

@@ -3813,6 +3813,8 @@ gsk_gl_renderer_add_render_ops (GskGLRenderer *self,
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
case GSK_CAIRO_NODE:
default:
{

View File

@@ -22,6 +22,8 @@
#ifndef __GI_SCANNER__
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskRenderer, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskRenderNode, gsk_render_node_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskTransform, gsk_transform_unref)

View File

@@ -21,11 +21,15 @@
#define __GSK_H_INSIDE__
#include <gsk/gskenums.h>
#include <gsk/gskglshader.h>
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
#include <gsk/gskpathmeasure.h>
#include <gsk/gskrenderer.h>
#include <gsk/gskrendernode.h>
#include <gsk/gskroundedrect.h>
#include <gsk/gskstroke.h>
#include <gsk/gsktransform.h>
#include <gsk/gskglshader.h>
#include <gsk/gskcairorenderer.h>

1771
gsk/gskcontour.c Normal file

File diff suppressed because it is too large Load Diff

103
gsk/gskcontourprivate.h Normal file
View File

@@ -0,0 +1,103 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_CONTOUR_PRIVATE_H__
#define __GSK_CONTOUR_PRIVATE_H__
#include <gskpath.h>
#include "gskpathopprivate.h"
G_BEGIN_DECLS
typedef enum
{
GSK_PATH_FLAT,
GSK_PATH_CLOSED
} GskPathFlags;
typedef struct _GskContour GskContour;
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius,
float start_angle,
float end_angle);
GskContour * gsk_standard_contour_new (GskPathFlags flags,
const graphene_point_t *points,
gsize n_points,
const gskpathop *ops,
gsize n_ops,
gssize offset);
void gsk_contour_copy (GskContour * dest,
const GskContour *src);
GskContour * gsk_contour_dup (const GskContour *src);
gsize gsk_contour_get_size (const GskContour *self);
GskPathFlags gsk_contour_get_flags (const GskContour *self);
void gsk_contour_print (const GskContour *self,
GString *string);
gboolean gsk_contour_get_bounds (const GskContour *self,
graphene_rect_t *bounds);
gpointer gsk_contour_init_measure (const GskContour *self,
float tolerance,
float *out_length);
void gsk_contour_free_measure (const GskContour *self,
gpointer data);
gboolean gsk_contour_foreach (const GskContour *self,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
void gsk_contour_get_start_end (const GskContour *self,
graphene_point_t *start,
graphene_point_t *end);
void gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
gboolean gsk_contour_get_closest_point (const GskContour *self,
gpointer measure_data,
float tolerance,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent);
int gsk_contour_get_winding (const GskContour *self,
gpointer measure_data,
const graphene_point_t *point,
gboolean *on_edge);
void gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
gpointer measure_data,
gboolean emit_move_to,
float start,
float end);
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
G_END_DECLS
#endif /* __GSK_CONTOUR_PRIVATE_H__ */

978
gsk/gskcurve.c Normal file
View File

@@ -0,0 +1,978 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskcurveprivate.h"
#define MIN_PROGRESS (1/1024.f)
typedef struct _GskCurveClass GskCurveClass;
struct _GskCurveClass
{
void (* init) (GskCurve *curve,
gskpathop op);
void (* init_foreach) (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight);
void (* get_point) (const GskCurve *curve,
float t,
graphene_point_t *pos);
void (* get_tangent) (const GskCurve *curve,
float t,
graphene_vec2_t *tangent);
void (* split) (const GskCurve *curve,
float progress,
GskCurve *result1,
GskCurve *result2);
void (* segment) (const GskCurve *curve,
float start,
float end,
GskCurve *segment);
gboolean (* decompose) (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data);
gskpathop (* pathop) (const GskCurve *curve);
const graphene_point_t * (* get_start_point) (const GskCurve *curve);
const graphene_point_t * (* get_end_point) (const GskCurve *curve);
void (* get_start_tangent) (const GskCurve *curve,
graphene_vec2_t *tangent);
void (* get_end_tangent) (const GskCurve *curve,
graphene_vec2_t *tangent);
};
static void
get_tangent (const graphene_point_t *p0,
const graphene_point_t *p1,
graphene_vec2_t *t)
{
graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y);
graphene_vec2_normalize (t, t);
}
/** LINE **/
static void
gsk_line_curve_init_from_points (GskLineCurve *self,
GskPathOperation op,
const graphene_point_t *start,
const graphene_point_t *end)
{
self->op = op;
self->points[0] = *start;
self->points[1] = *end;
}
static void
gsk_line_curve_init (GskCurve *curve,
gskpathop op)
{
GskLineCurve *self = &curve->line;
const graphene_point_t *pts = gsk_pathop_points (op);
gsk_line_curve_init_from_points (self, gsk_pathop_op (op), &pts[0], &pts[1]);
}
static void
gsk_line_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskLineCurve *self = &curve->line;
g_assert (n_pts == 2);
gsk_line_curve_init_from_points (self, op, &pts[0], &pts[1]);
}
static void
gsk_line_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskLineCurve *self = &curve->line;
graphene_point_interpolate (&self->points[0], &self->points[1], t, pos);
}
static void
gsk_line_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskLineCurve *self = &curve->line;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_line_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskLineCurve *self = &curve->line;
graphene_point_t point;
graphene_point_interpolate (&self->points[0], &self->points[1], progress, &point);
if (start)
gsk_line_curve_init_from_points (&start->line, GSK_PATH_LINE, &self->points[0], &point);
if (end)
gsk_line_curve_init_from_points (&end->line, GSK_PATH_LINE, &point, &self->points[1]);
}
static void
gsk_line_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
const GskLineCurve *self = &curve->line;
graphene_point_t start_point, end_point;
graphene_point_interpolate (&self->points[0], &self->points[1], start, &start_point);
graphene_point_interpolate (&self->points[0], &self->points[1], end, &end_point);
gsk_line_curve_init_from_points (&segment->line, GSK_PATH_LINE, &start_point, &end_point);
}
static gboolean
gsk_line_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskLineCurve *self = &curve->line;
return add_line_func (&self->points[0], &self->points[1], 0.0f, 1.0f, user_data);
}
static gskpathop
gsk_line_curve_pathop (const GskCurve *curve)
{
const GskLineCurve *self = &curve->line;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_line_curve_get_start_point (const GskCurve *curve)
{
const GskLineCurve *self = &curve->line;
return &self->points[0];
}
static const graphene_point_t *
gsk_line_curve_get_end_point (const GskCurve *curve)
{
const GskLineCurve *self = &curve->line;
return &self->points[1];
}
static void
gsk_line_curve_get_start_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskLineCurve *self = &curve->line;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static const GskCurveClass GSK_LINE_CURVE_CLASS = {
gsk_line_curve_init,
gsk_line_curve_init_foreach,
gsk_line_curve_get_point,
gsk_line_curve_get_tangent,
gsk_line_curve_split,
gsk_line_curve_segment,
gsk_line_curve_decompose,
gsk_line_curve_pathop,
gsk_line_curve_get_start_point,
gsk_line_curve_get_end_point,
gsk_line_curve_get_start_end_tangent,
gsk_line_curve_get_start_end_tangent
};
/** CURVE **/
static void
gsk_curve_curve_init_from_points (GskCurveCurve *self,
const graphene_point_t pts[4])
{
self->op = GSK_PATH_CURVE;
self->has_coefficients = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 4);
}
static void
gsk_curve_curve_init (GskCurve *curve,
gskpathop op)
{
GskCurveCurve *self = &curve->curve;
gsk_curve_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_curve_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskCurveCurve *self = &curve->curve;
g_assert (n_pts == 4);
gsk_curve_curve_init_from_points (self, pts);
}
static void
gsk_curve_curve_ensure_coefficients (const GskCurveCurve *curve)
{
GskCurveCurve *self = (GskCurveCurve *) curve;
const graphene_point_t *pts = &self->points[0];
if (self->has_coefficients)
return;
self->coeffs[0] = GRAPHENE_POINT_INIT (pts[3].x - 3.0f * pts[2].x + 3.0f * pts[1].x - pts[0].x,
pts[3].y - 3.0f * pts[2].y + 3.0f * pts[1].y - pts[0].y);
self->coeffs[1] = GRAPHENE_POINT_INIT (3.0f * pts[2].x - 6.0f * pts[1].x + 3.0f * pts[0].x,
3.0f * pts[2].y - 6.0f * pts[1].y + 3.0f * pts[0].y);
self->coeffs[2] = GRAPHENE_POINT_INIT (3.0f * pts[1].x - 3.0f * pts[0].x,
3.0f * pts[1].y - 3.0f * pts[0].y);
self->coeffs[3] = pts[0];
self->has_coefficients = TRUE;
}
static void
gsk_curve_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskCurveCurve *self = &curve->curve;
const graphene_point_t *c = self->coeffs;
gsk_curve_curve_ensure_coefficients (self);
*pos = GRAPHENE_POINT_INIT (((c[0].x * t + c[1].x) * t +c[2].x) * t + c[3].x,
((c[0].y * t + c[1].y) * t +c[2].y) * t + c[3].y);
}
static void
gsk_curve_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskCurveCurve *self = &curve->curve;
const graphene_point_t *c = self->coeffs;
gsk_curve_curve_ensure_coefficients (self);
graphene_vec2_init (tangent,
(3.0f * c[0].x * t + 2.0f * c[1].x) * t + c[2].x,
(3.0f * c[0].y * t + 2.0f * c[1].y) * t + c[2].y);
graphene_vec2_normalize (tangent, tangent);
}
static void
gsk_curve_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskCurveCurve *self = &curve->curve;
const graphene_point_t *pts = self->points;
graphene_point_t ab, bc, cd;
graphene_point_t abbc, bccd;
graphene_point_t final;
graphene_point_interpolate (&pts[0], &pts[1], progress, &ab);
graphene_point_interpolate (&pts[1], &pts[2], progress, &bc);
graphene_point_interpolate (&pts[2], &pts[3], progress, &cd);
graphene_point_interpolate (&ab, &bc, progress, &abbc);
graphene_point_interpolate (&bc, &cd, progress, &bccd);
graphene_point_interpolate (&abbc, &bccd, progress, &final);
if (start)
gsk_curve_curve_init_from_points (&start->curve, (graphene_point_t[4]) { pts[0], ab, abbc, final });
if (end)
gsk_curve_curve_init_from_points (&end->curve, (graphene_point_t[4]) { final, bccd, cd, pts[3] });
}
static void
gsk_curve_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
GskCurve tmp;
gsk_curve_curve_split (curve, start, NULL, &tmp);
gsk_curve_curve_split (&tmp, (end - start) / (1.0f - start), segment, NULL);
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_curve_curve_too_curvy (const GskCurveCurve *self,
float tolerance)
{
const graphene_point_t *pts = self->points;
graphene_point_t p;
graphene_point_interpolate (&pts[0], &pts[3], 1.0f / 3, &p);
if (ABS (p.x - pts[1].x) + ABS (p.y - pts[1].y) > tolerance)
return TRUE;
graphene_point_interpolate (&pts[0], &pts[3], 2.0f / 3, &p);
if (ABS (p.x - pts[2].x) + ABS (p.y - pts[2].y) > tolerance)
return TRUE;
return FALSE;
}
static gboolean
gsk_curce_curve_decompose_step (const GskCurve *curve,
float start_progress,
float end_progress,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskCurveCurve *self = &curve->curve;
GskCurve left, right;
float mid_progress;
if (!gsk_curve_curve_too_curvy (self, tolerance) || end_progress - start_progress <= MIN_PROGRESS)
return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, user_data);
gsk_curve_curve_split ((const GskCurve *) self, 0.5, &left, &right);
mid_progress = (start_progress + end_progress) / 2;
return gsk_curce_curve_decompose_step (&left, start_progress, mid_progress, tolerance, add_line_func, user_data)
&& gsk_curce_curve_decompose_step (&right, mid_progress, end_progress, tolerance, add_line_func, user_data);
}
static gboolean
gsk_curve_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
return gsk_curce_curve_decompose_step (curve, 0.0, 1.0, tolerance, add_line_func, user_data);
}
static gskpathop
gsk_curve_curve_pathop (const GskCurve *curve)
{
const GskCurveCurve *self = &curve->curve;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_curve_curve_get_start_point (const GskCurve *curve)
{
const GskCurveCurve *self = &curve->curve;
return &self->points[0];
}
static const graphene_point_t *
gsk_curve_curve_get_end_point (const GskCurve *curve)
{
const GskCurveCurve *self = &curve->curve;
return &self->points[3];
}
static void
gsk_curve_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskCurveCurve *self = &curve->curve;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_curve_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskCurveCurve *self = &curve->curve;
get_tangent (&self->points[2], &self->points[3], tangent);
}
static const GskCurveClass GSK_CURVE_CURVE_CLASS = {
gsk_curve_curve_init,
gsk_curve_curve_init_foreach,
gsk_curve_curve_get_point,
gsk_curve_curve_get_tangent,
gsk_curve_curve_split,
gsk_curve_curve_segment,
gsk_curve_curve_decompose,
gsk_curve_curve_pathop,
gsk_curve_curve_get_start_point,
gsk_curve_curve_get_end_point,
gsk_curve_curve_get_start_tangent,
gsk_curve_curve_get_end_tangent
};
/** CONIC **/
static void
gsk_conic_curve_init_from_points (GskConicCurve *self,
const graphene_point_t pts[4])
{
self->op = GSK_PATH_CONIC;
self->has_coefficients = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 4);
}
static void
gsk_conic_curve_init (GskCurve *curve,
gskpathop op)
{
GskConicCurve *self = &curve->conic;
gsk_conic_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_conic_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskConicCurve *self = &curve->conic;
g_assert (n_pts == 3);
gsk_conic_curve_init_from_points (self,
(graphene_point_t[4]) {
pts[0],
pts[1],
GRAPHENE_POINT_INIT (weight, 0),
pts[2]
});
}
static inline float
gsk_conic_curve_get_weight (const GskConicCurve *self)
{
return self->points[2].x;
}
static void
gsk_conic_curve_ensure_coefficents (const GskConicCurve *curve)
{
GskConicCurve *self = (GskConicCurve *) curve;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
graphene_point_t pw = GRAPHENE_POINT_INIT (w * pts[1].x, w * pts[1].y);
if (self->has_coefficients)
return;
self->num[2] = pts[0];
self->num[1] = GRAPHENE_POINT_INIT (2 * (pw.x - pts[0].x),
2 * (pw.y - pts[0].y));
self->num[0] = GRAPHENE_POINT_INIT (pts[3].x - 2 * pw.x + pts[0].x,
pts[3].y - 2 * pw.y + pts[0].y);
self->denom[2] = GRAPHENE_POINT_INIT (1, 1);
self->denom[1] = GRAPHENE_POINT_INIT (2 * (w - 1), 2 * (w - 1));
self->denom[0] = GRAPHENE_POINT_INIT (-self->denom[1].x, -self->denom[1].y);
self->has_coefficients = TRUE;
}
static inline void
gsk_curve_eval_quad (const graphene_point_t quad[3],
float progress,
graphene_point_t *result)
{
*result = GRAPHENE_POINT_INIT ((quad[0].x * progress + quad[1].x) * progress + quad[2].x,
(quad[0].y * progress + quad[1].y) * progress + quad[2].y);
}
static inline void
gsk_conic_curve_eval_point (const GskConicCurve *self,
float progress,
graphene_point_t *point)
{
graphene_point_t num, denom;
gsk_curve_eval_quad (self->num, progress, &num);
gsk_curve_eval_quad (self->denom, progress, &denom);
*point = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y);
}
static void
gsk_conic_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskConicCurve *self = &curve->conic;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, t, pos);
}
static void
gsk_conic_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t tmp;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
/* The tangent will be 0 in these corner cases, just
* treat it like a line here. */
if ((t <= 0.f && graphene_point_equal (&pts[0], &pts[1])) ||
(t >= 1.f && graphene_point_equal (&pts[1], &pts[3])))
{
graphene_vec2_init (tangent, pts[3].x - pts[0].x, pts[3].y - pts[0].y);
return;
}
gsk_curve_eval_quad ((graphene_point_t[3]) {
GRAPHENE_POINT_INIT ((w - 1) * (pts[3].x - pts[0].x),
(w - 1) * (pts[3].y - pts[0].y)),
GRAPHENE_POINT_INIT (pts[3].x - pts[0].x - 2 * w * (pts[1].x - pts[0].x),
pts[3].y - pts[0].y - 2 * w * (pts[1].y - pts[0].y)),
GRAPHENE_POINT_INIT (w * (pts[1].x - pts[0].x),
w * (pts[1].y - pts[0].y))
},
t,
&tmp);
graphene_vec2_init (tangent, tmp.x, tmp.y);
graphene_vec2_normalize (tangent, tangent);
}
static void
split_bezier3d_recurse (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right,
int *lpos,
int *rpos)
{
if (l == 1)
{
left[*lpos] = p[0];
right[*rpos] = p[0];
}
else
{
graphene_point3d_t *np;
int i;
np = g_alloca (sizeof (graphene_point3d_t) * (l - 1));
for (i = 0; i < l - 1; i++)
{
if (i == 0)
{
left[*lpos] = p[i];
(*lpos)++;
}
if (i + 1 == l - 1)
{
right[*rpos] = p[i + 1];
(*rpos)--;
}
graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]);
}
split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos);
}
}
static void
split_bezier3d (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right)
{
int lpos = 0;
int rpos = l - 1;
split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos);
}
static void
gsk_conic_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskConicCurve *self = &curve->conic;
graphene_point3d_t p[3];
graphene_point3d_t l[3], r[3];
graphene_point_t left[4], right[4];
float w;
/* do de Casteljau in homogeneous coordinates... */
w = self->points[2].x;
p[0] = GRAPHENE_POINT3D_INIT (self->points[0].x, self->points[0].y, 1);
p[1] = GRAPHENE_POINT3D_INIT (self->points[1].x * w, self->points[1].y * w, w);
p[2] = GRAPHENE_POINT3D_INIT (self->points[3].x, self->points[3].y, 1);
split_bezier3d (p, 3, progress, l, r);
/* then project the control points down */
left[0] = GRAPHENE_POINT_INIT (l[0].x / l[0].z, l[0].y / l[0].z);
left[1] = GRAPHENE_POINT_INIT (l[1].x / l[1].z, l[1].y / l[1].z);
left[3] = GRAPHENE_POINT_INIT (l[2].x / l[2].z, l[2].y / l[2].z);
right[0] = GRAPHENE_POINT_INIT (r[0].x / r[0].z, r[0].y / r[0].z);
right[1] = GRAPHENE_POINT_INIT (r[1].x / r[1].z, r[1].y / r[1].z);
right[3] = GRAPHENE_POINT_INIT (r[2].x / r[2].z, r[2].y / r[2].z);
/* normalize the outer weights to be 1 by using
* the fact that weights w_i and c*w_i are equivalent
* for any nonzero constant c
*/
for (int i = 0; i < 3; i++)
{
l[i].z /= l[0].z;
r[i].z /= r[2].z;
}
/* normalize the inner weight to be 1 by using
* the fact that w_0*w_2/w_1^2 is a constant for
* all equivalent weights.
*/
left[2] = GRAPHENE_POINT_INIT (l[1].z / sqrt (l[2].z), 0);
right[2] = GRAPHENE_POINT_INIT (r[1].z / sqrt (r[0].z), 0);
if (start)
gsk_curve_init (start, gsk_pathop_encode (GSK_PATH_CONIC, left));
if (end)
gsk_curve_init (end, gsk_pathop_encode (GSK_PATH_CONIC, right));
}
static void
gsk_conic_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t start_num, start_denom;
graphene_point_t mid_num, mid_denom;
graphene_point_t end_num, end_denom;
graphene_point_t ctrl_num, ctrl_denom;
float mid;
if (start <= 0.0f)
return gsk_conic_curve_split (curve, end, segment, NULL);
else if (end >= 1.0f)
return gsk_conic_curve_split (curve, start, NULL, segment);
gsk_conic_curve_ensure_coefficents (self);
gsk_curve_eval_quad (self->num, start, &start_num);
gsk_curve_eval_quad (self->denom, start, &start_denom);
mid = (start + end) / 2;
gsk_curve_eval_quad (self->num, mid, &mid_num);
gsk_curve_eval_quad (self->denom, mid, &mid_denom);
gsk_curve_eval_quad (self->num, end, &end_num);
gsk_curve_eval_quad (self->denom, end, &end_denom);
ctrl_num = GRAPHENE_POINT_INIT (2 * mid_num.x - (start_num.x + end_num.x) / 2,
2 * mid_num.y - (start_num.y + end_num.y) / 2);
ctrl_denom = GRAPHENE_POINT_INIT (2 * mid_denom.x - (start_denom.x + end_denom.x) / 2,
2 * mid_denom.y - (start_denom.y + end_denom.y) / 2);
gsk_conic_curve_init_from_points (&segment->conic,
(graphene_point_t[4]) {
GRAPHENE_POINT_INIT (start_num.x / start_denom.x,
start_num.y / start_denom.y),
GRAPHENE_POINT_INIT (ctrl_num.x / ctrl_denom.x,
ctrl_num.y / ctrl_denom.y),
GRAPHENE_POINT_INIT (ctrl_denom.x / sqrtf (start_denom.x * end_denom.x),
0),
GRAPHENE_POINT_INIT (end_num.x / end_denom.x,
end_num.y / end_denom.y)
});
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_conic_curve_too_curvy (const graphene_point_t *start,
const graphene_point_t *mid,
const graphene_point_t *end,
float tolerance)
{
return fabs ((start->x + end->x) * 0.5 - mid->x) > tolerance
|| fabs ((start->y + end->y) * 0.5 - mid->y) > tolerance;
}
static gboolean
gsk_conic_curve_decompose_subdivide (const GskConicCurve *self,
float tolerance,
const graphene_point_t *start,
float start_progress,
const graphene_point_t *end,
float end_progress,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
graphene_point_t mid;
float mid_progress;
mid_progress = (start_progress + end_progress) / 2;
gsk_conic_curve_eval_point (self, mid_progress, &mid);
if (end_progress - start_progress <= MIN_PROGRESS ||
!gsk_conic_curve_too_curvy (start, &mid, end, tolerance))
{
return add_line_func (start, end, start_progress, end_progress, user_data);
}
return gsk_conic_curve_decompose_subdivide (self, tolerance,
start, start_progress, &mid, mid_progress,
add_line_func, user_data)
&& gsk_conic_curve_decompose_subdivide (self, tolerance,
&mid, mid_progress, end, end_progress,
add_line_func, user_data);
}
static gboolean
gsk_conic_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t mid;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, 0.5, &mid);
return gsk_conic_curve_decompose_subdivide (self,
tolerance,
&self->points[0],
0.0f,
&mid,
0.5f,
add_line_func,
user_data)
&& gsk_conic_curve_decompose_subdivide (self,
tolerance,
&mid,
0.5f,
&self->points[3],
1.0f,
add_line_func,
user_data);
}
static gskpathop
gsk_conic_curve_pathop (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_conic_curve_get_start_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[0];
}
static const graphene_point_t *
gsk_conic_curve_get_end_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[3];
}
static void
gsk_conic_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_conic_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[1], &self->points[3], tangent);
}
static const GskCurveClass GSK_CONIC_CURVE_CLASS = {
gsk_conic_curve_init,
gsk_conic_curve_init_foreach,
gsk_conic_curve_get_point,
gsk_conic_curve_get_tangent,
gsk_conic_curve_split,
gsk_conic_curve_segment,
gsk_conic_curve_decompose,
gsk_conic_curve_pathop,
gsk_conic_curve_get_start_point,
gsk_conic_curve_get_end_point,
gsk_conic_curve_get_start_tangent,
gsk_conic_curve_get_end_tangent
};
/** API **/
static const GskCurveClass *
get_class (GskPathOperation op)
{
const GskCurveClass *klasses[] = {
[GSK_PATH_CLOSE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_LINE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_CURVE] = &GSK_CURVE_CURVE_CLASS,
[GSK_PATH_CONIC] = &GSK_CONIC_CURVE_CLASS,
};
g_assert (op < G_N_ELEMENTS (klasses) && klasses[op] != NULL);
return klasses[op];
}
void
gsk_curve_init (GskCurve *curve,
gskpathop op)
{
get_class (gsk_pathop_op (op))->init (curve, op);
}
void
gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
get_class (op)->init_foreach (curve, op, pts, n_pts, weight);
}
void
gsk_curve_get_point (const GskCurve *curve,
float progress,
graphene_point_t *pos)
{
get_class (curve->op)->get_point (curve, progress, pos);
}
void
gsk_curve_get_tangent (const GskCurve *curve,
float progress,
graphene_vec2_t *tangent)
{
get_class (curve->op)->get_tangent (curve, progress, tangent);
}
void
gsk_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
get_class (curve->op)->split (curve, progress, start, end);
}
void
gsk_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
if (start <= 0 && end >= 1)
{
*segment = *curve;
return;
}
get_class (curve->op)->segment (curve, start, end, segment);
}
gboolean
gsk_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
return get_class (curve->op)->decompose (curve, tolerance, add_line_func, user_data);
}
gskpathop
gsk_curve_pathop (const GskCurve *curve)
{
return get_class (curve->op)->pathop (curve);
}
const graphene_point_t *
gsk_curve_get_start_point (const GskCurve *curve)
{
return get_class (curve->op)->get_start_point (curve);
}
const graphene_point_t *
gsk_curve_get_end_point (const GskCurve *curve)
{
return get_class (curve->op)->get_end_point (curve);
}
void
gsk_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
get_class (curve->op)->get_start_tangent (curve, tangent);
}
void
gsk_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
get_class (curve->op)->get_end_tangent (curve, tangent);
}

123
gsk/gskcurveprivate.h Normal file
View File

@@ -0,0 +1,123 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_CURVE_PRIVATE_H__
#define __GSK_CURVE_PRIVATE_H__
#include "gskpathopprivate.h"
G_BEGIN_DECLS
typedef gpointer gskpathop;
typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskCurveCurve GskCurveCurve;
typedef struct _GskConicCurve GskConicCurve;
struct _GskLineCurve
{
GskPathOperation op;
gboolean padding;
graphene_point_t points[2];
};
struct _GskCurveCurve
{
GskPathOperation op;
gboolean has_coefficients;
graphene_point_t points[4];
graphene_point_t coeffs[4];
};
struct _GskConicCurve
{
GskPathOperation op;
gboolean has_coefficients;
graphene_point_t points[4];
graphene_point_t num[3];
graphene_point_t denom[3];
};
union _GskCurve
{
GskPathOperation op;
GskLineCurve line;
GskCurveCurve curve;
GskConicCurve conic;
};
typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
const graphene_point_t *to,
float from_progress,
float to_progress,
gpointer user_data);
void gsk_curve_init (GskCurve *curve,
gskpathop op);
void gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight);
void gsk_curve_get_point (const GskCurve *curve,
float progress,
graphene_point_t *pos);
void gsk_curve_get_tangent (const GskCurve *curve,
float progress,
graphene_vec2_t *tangent);
void gsk_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end);
void gsk_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment);
gboolean gsk_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data);
gskpathop gsk_curve_pathop (const GskCurve *curve);
#define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve))
const graphene_point_t *gsk_curve_get_start_point (const GskCurve *curve);
const graphene_point_t *gsk_curve_get_end_point (const GskCurve *curve);
void gsk_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent);
void gsk_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent);
G_END_DECLS
#endif /* __GSK_CURVE_PRIVATE_H__ */

View File

@@ -73,6 +73,8 @@ typedef enum {
GSK_REPEAT_NODE,
GSK_CLIP_NODE,
GSK_ROUNDED_CLIP_NODE,
GSK_FILL_NODE,
GSK_STROKE_NODE,
GSK_SHADOW_NODE,
GSK_BLEND_NODE,
GSK_CROSS_FADE_NODE,
@@ -167,6 +169,104 @@ typedef enum {
GSK_CORNER_BOTTOM_LEFT
} GskCorner;
/**
* GskFillRule:
* @GSK_FILL_RULE_WINDING: If the path crosses the ray from
* left-to-right, counts +1. If the path crosses the ray
* from right to left, counts -1. (Left and right are determined
* from the perspective of looking along the ray from the starting
* point.) If the total count is non-zero, the point will be filled.
* @GSK_FILL_RULE_EVEN_ODD: Counts the total number of
* intersections, without regard to the orientation of the contour. If
* the total number of intersections is odd, the point will be
* filled.
*
* #GskFillRule is used to select how paths are filled, for example in
* gsk_fill_node_new(). Whether or not a point is included in the fill is
* determined by taking a ray from that point to infinity and looking
* at intersections with the path. The ray can be in any direction,
* as long as it doesn't pass through the end point of a segment
* or have a tricky intersection such as intersecting tangent to the path.
* (Note that filling is not actually implemented in this way. This
* is just a description of the rule that is applied.)
*
* New entries may be added in future versions.
**/
typedef enum {
GSK_FILL_RULE_WINDING,
GSK_FILL_RULE_EVEN_ODD
} GskFillRule;
/**
* @GSK_LINE_CAP_BUTT: Start and stop the line exactly at the start
* and end point
* @GSK_LINE_CAP_ROUND: Use a round ending, the center of the circle
* is the start or end point.
* @GSK_LINE_CAP_SQUARE: use squared ending, the center of the square
* is the start or end point.
*
* Specifies how to render the start and end points of contours or
* dashes when stroking.
*
* The default line cap style is %GSK_LINE_CAP_BUTT.
*/
typedef enum {
GSK_LINE_CAP_BUTT,
GSK_LINE_CAP_ROUND,
GSK_LINE_CAP_SQUARE
} GskLineCap;
/**
* GskLineJoin:
* @GSK_LINE_JOIN_MITER: Use a sharp, angled corner
* @GSK_LINE_JOIN_MITER_CLIP: Use a sharp, angled corner, at a distance
* @GSK_LINE_JOIN_ROUND: Use a round join, the center of the circle is
* the joint point
* @GSK_LINE_JOIN_BEVEL: Use a cut-off join, the join is cut off at half
* the line width from the joint point
*
* Specifies how to render the junction of two lines when stroking.
*
* See gsk_stroke_set_miter_limit() for details on the difference between
* @GSK_LINE_JOIN_MITER and @GSK_LINE_JOIN_MITER_CLIP.
*
* The default line join style is %GSK_LINE_JOIN_MITER.
**/
typedef enum {
GSK_LINE_JOIN_MITER,
GSK_LINE_JOIN_MITER_CLIP,
GSK_LINE_JOIN_ROUND,
GSK_LINE_JOIN_BEVEL
} GskLineJoin;
/**
* GskPathOperation:
* @GSK_PATH_MOVE: A move-to operation, with 1 point describing the
* target point.
* @GSK_PATH_LINE: A line-to operation, with 2 points describing the
* start and end point of a straight line.
* @GSK_PATH_CLOSE: A close operation ending the current contour with
* a line back to the starting point. Two points describe the start
* and end of the line.
* @GSK_PATH_CURVE: A curve-to operation describing a cubic Bézier curve
* with 4 points describing the start point, the two control points
* and the end point of the curve.
* @GSK_PATH_CONIC: A weighted quadratic bezier curve with 3 points
* describing the start point, control point and end point of the
* curve. A weight for the curve will be passed, too.
*
* Path operations can be used to approximate a #GskPath.
*
* More values may be added in the future.
**/
typedef enum {
GSK_PATH_MOVE,
GSK_PATH_CLOSE,
GSK_PATH_LINE,
GSK_PATH_CURVE,
GSK_PATH_CONIC,
} GskPathOperation;
/**
* GskSerializationError:
* @GSK_SERIALIZATION_UNSUPPORTED_FORMAT: The format can not be
@@ -249,5 +349,4 @@ typedef enum
GSK_GL_UNIFORM_TYPE_VEC4,
} GskGLUniformType;
#endif /* __GSK_TYPES_H__ */

1220
gsk/gskpath.c Normal file

File diff suppressed because it is too large Load Diff

115
gsk/gskpath.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_H__
#define __GSK_PATH_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
/**
* GskPathForeachFlags:
* @GSK_PATH_FOREACH_ALLOW_CURVE: Allow emission of %GSK_PATH_CURVE
* operations.
* @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of %GSK_PATH_CONIC
* operations.
*
* Flags that can be passed to gsk_path_foreach() to enable additional
* features.
*
* By default, gsk_path_foreach() will only emit a path with all operations
* flattened to straight lines to allow for maximum compatibility. The only
* operations emitted will be %GSK_PATH_MOVE, %GSK_PATH_LINE and %GSK_PATH_CLOSE.
*/
typedef enum
{
GSK_PATH_FOREACH_ALLOW_CURVE = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 1)
} GskPathForeachFlags;
/**
* GskPathForeachFunc:
* @op: The operation to perform
* @pts: The points of the operation
* @n_pts: The number of points
* @weight: The weight for conic curves, or unused if not a conic curve.
* @user_data: The user data provided with the function
*
* Prototype of the callback to iterate throught the operations of
* a path.
*
* Returns: %TRUE to continue evaluating the path, %FALSE to
* immediately abort and not call the function again.
*/
typedef gboolean (* GskPathForeachFunc) (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data);
#define GSK_TYPE_PATH (gsk_path_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_new_from_cairo (const cairo_path_t *path);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_ref (GskPath *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_unref (GskPath *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_print (GskPath *self,
GString *string);
GDK_AVAILABLE_IN_ALL
char * gsk_path_to_string (GskPath *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_parse (const char *string);
GDK_AVAILABLE_IN_ALL
void gsk_path_to_cairo (GskPath *self,
cairo_t *cr);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_is_empty (GskPath *path);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_get_bounds (GskPath *path,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_get_stroke_bounds (GskPath *path,
const GskStroke *stroke,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_foreach (GskPath *path,
GskPathForeachFlags flags,
GskPathForeachFunc func,
gpointer user_data);
G_END_DECLS
#endif /* __GSK_PATH_H__ */

976
gsk/gskpathbuilder.c Normal file
View File

@@ -0,0 +1,976 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathbuilder.h"
#include "gskpathprivate.h"
/**
* SECTION:gskpathbuilder
* @Title: Building paths
* @Short_description: Building paths of lines and curves
* @See_also: #GskPath, #GskPathMeasure
*
* This section describes how to construct #GskPath structures.
*
* A path is constructed like this:
*
* |[<!-- language="C" -->
* GskPath *
* construct_path (void)
* {
* GskPathBuilder *builder;
*
* builder = gsk_path_builder_new ();
*
* // add contours to the path here
*
* return gsk_path_builder_free_to_path (builder);
* ]|
*
* Adding contours to the path can be done in two ways.
* The easiest option is to use the `gsk_path_builder_add_*` group
* of functions that add predefined contours to the current path,
* either common shapes like gsk_path_builder_add_circle()
* or by adding from other paths like gsk_path_builder_add_path().
*
* The other option is to define each line and curve manually with
* the `gsk_path_builder_*_to` group of functions. You start with
* a call to gsk_path_builder_move_to() to set the starting point
* and then use multiple calls to any of the drawing functions to
* move the pen along the plane. Once you are done, you can call
* gsk_path_builder_close() to close the path by connecting it
* back with a line to the starting point.
* This is similar for how paths are drawn in Cairo.
*/
/**
* GskPathBuilder:
*
* A #GskPathBuilder struct is an opaque struct. It is meant to
* not be kept around and only be used to create new #GskPath
* objects.
*/
struct _GskPathBuilder
{
int ref_count;
GSList *contours; /* (reverse) list of already recorded contours */
GskPathFlags flags; /* flags for the current path */
graphene_point_t current_point; /* the point all drawing ops start from */
GArray *ops; /* operations for current contour - size == 0 means no current contour */
GArray *points; /* points for the operations */
};
G_DEFINE_BOXED_TYPE (GskPathBuilder,
gsk_path_builder,
gsk_path_builder_ref,
gsk_path_builder_unref)
/**
* gsk_path_builder_new:
*
* Create a new #GskPathBuilder object. The resulting builder
* would create an empty #GskPath. Use addition functions to add
* types to it.
*
* Returns: a new #GskPathBuilder
**/
GskPathBuilder *
gsk_path_builder_new (void)
{
GskPathBuilder *builder;
builder = g_slice_new0 (GskPathBuilder);
builder->ref_count = 1;
builder->ops = g_array_new (FALSE, FALSE, sizeof (gskpathop));
builder->points = g_array_new (FALSE, FALSE, sizeof (graphene_point_t));
/* Be explicit here */
builder->current_point = GRAPHENE_POINT_INIT (0, 0);
return builder;
}
/**
* gsk_path_builder_ref:
* @builder: a #GskPathBuilder
*
* Acquires a reference on the given @builder.
*
* This function is intended primarily for bindings. #GskPathBuilder objects
* should not be kept around.
*
* Returns: (transfer none): the given #GskPathBuilder with
* its reference count increased
*/
GskPathBuilder *
gsk_path_builder_ref (GskPathBuilder *builder)
{
g_return_val_if_fail (builder != NULL, NULL);
g_return_val_if_fail (builder->ref_count > 0, NULL);
builder->ref_count += 1;
return builder;
}
/* We're cheating here. Out pathops are relative to the NULL pointer,
* so that we can not care about the points GArray reallocating itself
* until we create the contour.
* This does however mean that we need to not use gsk_pathop_get_points()
* without offsetting the returned pointer.
*/
static inline gskpathop
gsk_pathop_encode_index (GskPathOperation op,
gsize index)
{
return gsk_pathop_encode (op, ((graphene_point_t *) NULL) + index);
}
static void
gsk_path_builder_ensure_current (GskPathBuilder *builder)
{
if (builder->ops->len != 0)
return;
builder->flags = GSK_PATH_FLAT;
g_array_append_vals (builder->ops, (gskpathop[1]) { gsk_pathop_encode_index (GSK_PATH_MOVE, 0) }, 1);
g_array_append_val (builder->points, builder->current_point);
}
static void
gsk_path_builder_append_current (GskPathBuilder *builder,
GskPathOperation op,
gsize n_points,
const graphene_point_t *points)
{
gsk_path_builder_ensure_current (builder);
g_array_append_vals (builder->ops, (gskpathop[1]) { gsk_pathop_encode_index (op, builder->points->len - 1) }, 1);
g_array_append_vals (builder->points, points, n_points);
builder->current_point = points[n_points - 1];
}
static void
gsk_path_builder_end_current (GskPathBuilder *builder)
{
GskContour *contour;
if (builder->ops->len == 0)
return;
contour = gsk_standard_contour_new (builder->flags,
(graphene_point_t *) builder->points->data,
builder->points->len,
(gskpathop *) builder->ops->data,
builder->ops->len,
(graphene_point_t *) builder->points->data - (graphene_point_t *) NULL);
g_array_set_size (builder->ops, 0);
g_array_set_size (builder->points, 0);
/* do this at the end to avoid inflooping when add_contour calls back here */
gsk_path_builder_add_contour (builder, contour);
}
static void
gsk_path_builder_clear (GskPathBuilder *builder)
{
gsk_path_builder_end_current (builder);
g_slist_free_full (builder->contours, g_free);
builder->contours = NULL;
}
/**
* gsk_path_builder_unref:
* @builder: a #GskPathBuilder
*
* Releases a reference on the given @builder.
*/
void
gsk_path_builder_unref (GskPathBuilder *builder)
{
g_return_if_fail (builder != NULL);
g_return_if_fail (builder->ref_count > 0);
builder->ref_count -= 1;
if (builder->ref_count > 0)
return;
gsk_path_builder_clear (builder);
g_array_unref (builder->ops);
g_array_unref (builder->points);
g_slice_free (GskPathBuilder, builder);
}
/**
* gsk_path_builder_free_to_path: (skip)
* @builder: a #GskPathBuilder
*
* Creates a new #GskPath from the current state of the
* given @builder, and frees the @builder instance.
*
* Returns: (transfer full): the newly created #GskPath
* with all the contours added to @builder
*/
GskPath *
gsk_path_builder_free_to_path (GskPathBuilder *builder)
{
GskPath *res;
g_return_val_if_fail (builder != NULL, NULL);
res = gsk_path_builder_to_path (builder);
gsk_path_builder_unref (builder);
return res;
}
/**
* gsk_path_builder_to_path:
* @builder: a #GskPathBuilder
*
* Creates a new #GskPath from the given @builder.
*
* The given #GskPathBuilder is reset once this function returns;
* you cannot call this function multiple times on the same @builder instance.
*
* This function is intended primarily for bindings. C code should use
* gsk_path_builder_free_to_path().
*
* Returns: (transfer full): the newly created #GskPath
* with all the contours added to @builder
*/
GskPath *
gsk_path_builder_to_path (GskPathBuilder *builder)
{
GskPath *path;
g_return_val_if_fail (builder != NULL, NULL);
gsk_path_builder_end_current (builder);
builder->contours = g_slist_reverse (builder->contours);
path = gsk_path_new_from_contours (builder->contours);
gsk_path_builder_clear (builder);
return path;
}
void
gsk_path_builder_add_contour (GskPathBuilder *builder,
GskContour *contour)
{
gsk_path_builder_end_current (builder);
builder->contours = g_slist_prepend (builder->contours, contour);
}
/**
* gsk_path_builder_get_current_point:
* @builder: a #GskPathBuilder
*
* Gets the current point. The current point is used for relative
* drawing commands and updated after every operation.
*
* When @builder is created, the default current point is set to (0, 0).
*
* Returns: (transfer none) The current point
**/
const graphene_point_t *
gsk_path_builder_get_current_point (GskPathBuilder *builder)
{
g_return_val_if_fail (builder != NULL, NULL);
return &builder->current_point;
}
/**
* gsk_path_builder_add_path:
* @builder: a #GskPathBuilder
* @path: (transfer none): the path to append
*
* Appends all of @path to @builder.
**/
void
gsk_path_builder_add_path (GskPathBuilder *builder,
GskPath *path)
{
gsize i;
g_return_if_fail (builder != NULL);
g_return_if_fail (path != NULL);
for (i = 0; i < gsk_path_get_n_contours (path); i++)
{
const GskContour *contour = gsk_path_get_contour (path, i);
gsk_path_builder_add_contour (builder, gsk_contour_dup (contour));
}
}
/**
* gsk_path_builder_add_rect:
* @builder: A #GskPathBuilder
* @rect: The rectangle to create a path for
*
* Creates a path representing the given rectangle.
*
* If the width or height of the rectangle is negative, the start
* point will be on the right or bottom, respectively.
*
* If the the width or height are 0, the path will be a closed
* horizontal or vertical line. If both are 0, it'll be a closed dot.
*
* Returns: a new #GskPath representing a rectangle
**/
void
gsk_path_builder_add_rect (GskPathBuilder *builder,
const graphene_rect_t *rect)
{
GskContour *contour;
g_return_if_fail (builder != NULL);
contour = gsk_rect_contour_new (rect);
gsk_path_builder_add_contour (builder, contour);
gsk_contour_get_start_end (contour, NULL, &builder->current_point);
}
/**
* gsk_path_builder_add_rounded_rect:
* @self: a #GskPathBuilder
* @rect: the rounded rect
*
* Adds @rect as a new contour to the path built in @self.
**/
void
gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
const GskRoundedRect *rect)
{
const float weight = sqrt(0.5f);
g_return_if_fail (self != NULL);
g_return_if_fail (rect != NULL);
gsk_path_builder_move_to (self,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
/* top */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->bounds.origin.y);
/* topright corner */
gsk_path_builder_conic_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height,
weight);
/* right */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
/* bottomright corner */
gsk_path_builder_conic_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->bounds.origin.y + rect->bounds.size.height,
weight);
/* bottom */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottomleft corner */
gsk_path_builder_conic_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
weight);
/* left */
gsk_path_builder_line_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height);
/* topleft corner */
gsk_path_builder_conic_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y,
weight);
/* done */
gsk_path_builder_close (self);
}
/**
* gsk_path_builder_add_circle:
* @builder: a #GskPathBuilder
* @center: the center of the circle
* @radius: the radius of the circle
*
* Adds a circle with the @center and @radius.
**/
void
gsk_path_builder_add_circle (GskPathBuilder *builder,
const graphene_point_t *center,
float radius)
{
GskContour *contour;
g_return_if_fail (builder != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius > 0);
contour = gsk_circle_contour_new (center, radius, 0, 360);
gsk_path_builder_add_contour (builder, contour);
}
/**
* gsk_path_builder_add_ellipse:
* @builder: a #GskPathBuilder
* @center: the center point of the ellipse
* @radius: the radius of the ellipse in x/y direction
*
* Adds an ellipse with the given @center and the @radius in
* x/y direction.
**/
void
gsk_path_builder_add_ellipse (GskPathBuilder *self,
const graphene_point_t *center,
const graphene_size_t *radius)
{
const float weight = sqrt(0.5f);
graphene_point_t pts[8];
g_return_if_fail (self != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius != NULL);
pts[0] = GRAPHENE_POINT_INIT (center->x + radius->width / 2,
center->y);
pts[1] = GRAPHENE_POINT_INIT (center->x + radius->width / 2,
center->y + radius->height / 2);
pts[2] = GRAPHENE_POINT_INIT (center->x,
center->y + radius->height / 2);
pts[3] = GRAPHENE_POINT_INIT (center->x - radius->width / 2,
center->y + radius->height / 2);
pts[4] = GRAPHENE_POINT_INIT (center->x - radius->width / 2,
center->y);
pts[5] = GRAPHENE_POINT_INIT (center->x - radius->width / 2,
center->y - radius->height / 2);
pts[6] = GRAPHENE_POINT_INIT (center->x,
center->y - radius->height / 2);
pts[7] = GRAPHENE_POINT_INIT (center->x + radius->width / 2,
center->y - radius->height / 2);
gsk_path_builder_move_to (self, pts[0].x, pts[0].y);
gsk_path_builder_conic_to (self, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
gsk_path_builder_conic_to (self, pts[3].x, pts[3].y, pts[4].x, pts[4].y, weight);
gsk_path_builder_conic_to (self, pts[5].x, pts[5].y, pts[6].x, pts[6].y, weight);
gsk_path_builder_conic_to (self, pts[7].x, pts[7].y, pts[0].x, pts[0].y, weight);
gsk_path_builder_close (self);
}
/**
* gsk_path_builder_move_to:
* @builder: a #GskPathBuilder
* @x: x coordinate
* @y: y coordinate
*
* Starts a new contour by placing the pen at @x, @y.
*
* If gsk_path_builder_move_to() is called twice in succession, the first
* call will result in a contour made up of a single point. The second call
* will start a new contour.
**/
void
gsk_path_builder_move_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_end_current (builder);
builder->current_point = GRAPHENE_POINT_INIT(x, y);
gsk_path_builder_ensure_current (builder);
}
/**
* gsk_path_builder_rel_move_to:
* @builder: a #GskPathBuilder
* @x: x offset
* @y: y offset
*
* Starts a new contour by placing the pen at @x, @y relative to the current
* point.
*
* This is the relative version of gsk_path_builder_move_to().
**/
void
gsk_path_builder_rel_move_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_move_to (builder,
builder->current_point.x + x,
builder->current_point.y + y);
}
/**
* gsk_path_builder_line_to:
* @builder: a #GskPathBuilder
* @x: x coordinate
* @y: y coordinate
*
* Draws a line from the current point to @x, @y and makes it the new current
* point.
**/
void
gsk_path_builder_line_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
/* skip the line if it goes to the same point */
if (graphene_point_equal (&builder->current_point,
&GRAPHENE_POINT_INIT (x, y)))
return;
gsk_path_builder_append_current (builder,
GSK_PATH_LINE,
1, (graphene_point_t[1]) {
GRAPHENE_POINT_INIT (x, y)
});
}
/**
* gsk_path_builder_line_to:
* @builder: a #GskPathBuilder
* @x: x offset
* @y: y offset
*
* Draws a line from the current point to a point offset to it by @x, @y
* and makes it the new current point.
*
* This is the relative version of gsk_path_builder_line_to().
**/
void
gsk_path_builder_rel_line_to (GskPathBuilder *builder,
float x,
float y)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_line_to (builder,
builder->current_point.x + x,
builder->current_point.y + y);
}
/**
* gsk_path_builder_curve_to:
* @builder: a #GskPathBuilder
* @x1: x coordinate of first control point
* @y1: y coordinate of first control point
* @x2: x coordinate of second control point
* @y2: y coordinate of second control point
* @x3: x coordinate of the end of the curve
* @y3: y coordinate of the end of the curve
*
* Adds a [cubic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
* from the current point to @x3, @y3 with @x1, @y1 and @x2, @y2 as the control
* points.
*
* After this, @x3, @y3 will be the new current point.
**/
void
gsk_path_builder_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3)
{
g_return_if_fail (builder != NULL);
builder->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (builder,
GSK_PATH_CURVE,
3, (graphene_point_t[3]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (x2, y2),
GRAPHENE_POINT_INIT (x3, y3)
});
}
/**
* gsk_path_builder_rel_curve_to:
* @builder: a #GskPathBuilder
* @x1: x offset of first control point
* @y1: y offset of first control point
* @x2: x offset of second control point
* @y2: y offset of second control point
* @x3: x offset of the end of the curve
* @y3: y offset of the end of the curve
*
* Adds a [cubic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
* from the current point to @x3, @y3 with @x1, @y1 and @x2, @y2 as the control
* points. All coordinates are given relative to the current point.
*
* This is the relative version of gsk_path_builder_curve_to().
**/
void
gsk_path_builder_rel_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3)
{
g_return_if_fail (builder != NULL);
gsk_path_builder_curve_to (builder,
builder->current_point.x + x1,
builder->current_point.y + y1,
builder->current_point.x + x2,
builder->current_point.y + y2,
builder->current_point.x + x3,
builder->current_point.y + y3);
}
/**
* gsk_path_builder_conic_to:
* @builder: a #GskPathBuilder
* @x1: x coordinate of control point
* @y1: y coordinate of control point
* @x2: x coordinate of the end of the curve
* @y2: y coordinate of the end of the curve
* @weight: weight of the curve
*
* Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
* from the current point to @x2, @y2 with the given
* @weight and @x1, @y1 as the single control point.
*
* Conic curves can be used to draw ellipses and circles.
*
* After this, @x2, @y2 will be the new current point.
**/
void
gsk_path_builder_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (builder != NULL);
g_return_if_fail (weight >= 0);
builder->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (builder,
GSK_PATH_CONIC,
3, (graphene_point_t[3]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (weight, 0),
GRAPHENE_POINT_INIT (x2, y2)
});
}
/**
* gsk_path_builder_rel_conic_to:
* @builder: a #GskPathBuilder
* @x1: x offset of control point
* @y1: y offset of control point
* @x2: x offset of the end of the curve
* @y2: y offset of the end of the curve
* @weight: weight of the curve
*
* Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
* from the current point to @x2, @y2 with the given
* @weight and @x1, @y1 as the single control point.
*
* This is the relative version of gsk_path_builder_conic_to().
**/
void
gsk_path_builder_rel_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (builder != NULL);
g_return_if_fail (weight >= 0);
gsk_path_builder_conic_to (builder,
builder->current_point.x + x1,
builder->current_point.y + y1,
builder->current_point.x + x2,
builder->current_point.y + y2,
weight);
}
/**
* gsk_path_builder_close:
* @builder: a #GskPathBuilder
*
* Ends the current contour with a line back to the start point.
*
* Note that this is different from calling gsk_path_builder_line_to()
* with the start point in that the contour will be closed. A closed
* contour behaves different from an open one when stroking its start
* and end point are considered connected, so they will be joined
* via the line join, and not ended with line caps.
**/
void
gsk_path_builder_close (GskPathBuilder *builder)
{
g_return_if_fail (builder != NULL);
if (builder->ops->len == 0)
return;
builder->flags |= GSK_PATH_CLOSED;
gsk_path_builder_append_current (builder,
GSK_PATH_CLOSE,
1, (graphene_point_t[1]) {
g_array_index (builder->points, graphene_point_t, 0)
});
gsk_path_builder_end_current (builder);
}
static void
arc_segment (GskPathBuilder *builder,
double cx,
double cy,
double rx,
double ry,
double sin_phi,
double cos_phi,
double sin_th0,
double cos_th0,
double sin_th1,
double cos_th1,
double t)
{
double x1, y1, x2, y2, x3, y3;
x1 = rx * (cos_th0 - t * sin_th0);
y1 = ry * (sin_th0 + t * cos_th0);
x3 = rx * cos_th1;
y3 = ry * sin_th1;
x2 = x3 + rx * (t * sin_th1);
y2 = y3 + ry * (-t * cos_th1);
gsk_path_builder_curve_to (builder,
cx + cos_phi * x1 - sin_phi * y1,
cy + sin_phi * x1 + cos_phi * y1,
cx + cos_phi * x2 - sin_phi * y2,
cy + sin_phi * x2 + cos_phi * y2,
cx + cos_phi * x3 - sin_phi * y3,
cy + sin_phi * x3 + cos_phi * y3);
}
void
gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
float rx,
float ry,
float x_axis_rotation,
gboolean large_arc,
gboolean positive_sweep,
float x,
float y)
{
graphene_point_t *current;
double x1, y1, x2, y2;
double phi, sin_phi, cos_phi;
double mid_x, mid_y;
double lambda;
double d;
double k;
double x1_, y1_;
double cx_, cy_;
double cx, cy;
double ux, uy, u_len;
double cos_theta1, theta1;
double vx, vy, v_len;
double dp_uv;
double cos_delta_theta, delta_theta;
int i, n_segs;
double d_theta, theta;
double sin_th0, cos_th0;
double sin_th1, cos_th1;
double th_half;
double t;
if (builder->points->len > 0)
{
current = &g_array_index (builder->points, graphene_point_t, builder->points->len - 1);
x1 = current->x;
y1 = current->y;
}
else
{
x1 = 0;
y1 = 0;
}
x2 = x;
y2 = y;
phi = x_axis_rotation * M_PI / 180.0;
sincos (phi, &sin_phi, &cos_phi);
rx = fabs (rx);
ry = fabs (ry);
mid_x = (x1 - x2) / 2;
mid_y = (y1 - y2) / 2;
x1_ = cos_phi * mid_x + sin_phi * mid_y;
y1_ = - sin_phi * mid_x + cos_phi * mid_y;
lambda = (x1_ / rx) * (x1_ / rx) + (y1_ / ry) * (y1_ / ry);
if (lambda > 1)
{
lambda = sqrt (lambda);
rx *= lambda;
ry *= lambda;
}
d = (rx * y1_) * (rx * y1_) + (ry * x1_) * (ry * x1_);
if (d == 0)
return;
k = sqrt (fabs ((rx * ry) * (rx * ry) / d - 1.0));
if (positive_sweep == large_arc)
k = -k;
cx_ = k * rx * y1_ / ry;
cy_ = -k * ry * x1_ / rx;
cx = cos_phi * cx_ - sin_phi * cy_ + (x1 + x2) / 2;
cy = sin_phi * cx_ + cos_phi * cy_ + (y1 + y2) / 2;
ux = (x1_ - cx_) / rx;
uy = (y1_ - cy_) / ry;
u_len = sqrt (ux * ux + uy * uy);
if (u_len == 0)
return;
cos_theta1 = CLAMP (ux / u_len, -1, 1);
theta1 = acos (cos_theta1);
if (uy < 0)
theta1 = - theta1;
vx = (- x1_ - cx_) / rx;
vy = (- y1_ - cy_) / ry;
v_len = sqrt (vx * vx + vy * vy);
if (v_len == 0)
return;
dp_uv = ux * vx + uy * vy;
cos_delta_theta = CLAMP (dp_uv / (u_len * v_len), -1, 1);
delta_theta = acos (cos_delta_theta);
if (ux * vy - uy * vx < 0)
delta_theta = - delta_theta;
if (positive_sweep && delta_theta < 0)
delta_theta += 2 * M_PI;
else if (!positive_sweep && delta_theta > 0)
delta_theta -= 2 * M_PI;
n_segs = ceil (fabs (delta_theta / (M_PI_2 + 0.001)));
d_theta = delta_theta / n_segs;
theta = theta1;
sincos (theta1, &sin_th1, &cos_th1);
th_half = d_theta / 2;
t = (8.0 / 3.0) * sin (th_half / 2) * sin (th_half / 2) / sin (th_half);
for (i = 0; i < n_segs; i++)
{
theta = theta1;
theta1 = theta + d_theta;
sin_th0 = sin_th1;
cos_th0 = cos_th1;
sincos (theta1, &sin_th1, &cos_th1);
arc_segment (builder,
cx, cy, rx, ry,
sin_phi, cos_phi,
sin_th0, cos_th0,
sin_th1, cos_th1,
t);
}
}
/**
* gsk_path_builder_add_layout:
* @builder: a #GskPathBuilder
* @layout: the pango layout to add
*
* Adds the outlines for the glyphs in @layout to
* @builder.
*/
void
gsk_path_builder_add_layout (GskPathBuilder *builder,
PangoLayout *layout)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_path_t *cairo_path;
GskPath *path;
surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
cr = cairo_create (surface);
pango_cairo_layout_path (cr, layout);
cairo_path = cairo_copy_path (cr);
path = gsk_path_new_from_cairo (cairo_path);
gsk_path_builder_add_path (builder, path);
gsk_path_unref (path);
cairo_path_destroy (cairo_path);
cairo_destroy (cr);
cairo_surface_destroy (surface);
}

132
gsk/gskpathbuilder.h Normal file
View File

@@ -0,0 +1,132 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_BUILDER_H__
#define __GSK_PATH_BUILDER_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gskroundedrect.h>
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_builder_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPathBuilder * gsk_path_builder_new (void);
GDK_AVAILABLE_IN_ALL
GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_unref (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_builder_free_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT;
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_builder_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT;
GDK_AVAILABLE_IN_ALL
const graphene_point_t *gsk_path_builder_get_current_point (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_path (GskPathBuilder *builder,
GskPath *path);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_rect (GskPathBuilder *builder,
const graphene_rect_t *rect);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_rounded_rect (GskPathBuilder *builder,
const GskRoundedRect *rect);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_circle (GskPathBuilder *builder,
const graphene_point_t *center,
float radius);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_ellipse (GskPathBuilder *self,
const graphene_point_t *center,
const graphene_size_t *radius);
/* next function implemented in gskpathmeasure.c */
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_segment (GskPathBuilder *builder,
GskPathMeasure *self,
float start,
float end);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_move_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_move_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_line_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_line_to (GskPathBuilder *builder,
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_curve_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_conic_to (GskPathBuilder *builder,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_close (GskPathBuilder *builder);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_add_layout (GskPathBuilder *builder,
PangoLayout *layout);
G_END_DECLS
#endif /* __GSK_PATH_BUILDER_H__ */

291
gsk/gskpathdash.c Normal file
View File

@@ -0,0 +1,291 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathdashprivate.h"
#include "gskcontourprivate.h"
#include "gskcurveprivate.h"
#include "gskpathprivate.h"
#include "gskstrokeprivate.h"
typedef struct
{
float offset; /* how much of the current dash we've spent */
gsize dash_index; /* goes from 0 to n_dash * 2, so we don't have to care about on/off
for uneven dashes */
gboolean on; /* If we're currently dashing or not */
gboolean may_close; /* TRUE if we haven't turned the dash off in this contour */
gboolean needs_move_to; /* If we have emitted the initial move_to() yet */
enum {
NORMAL, /* no special behavior required */
SKIP, /* skip the next dash */
ONLY, /* only do the first dash */
DONE /* done with the first dash */
} first_dash_behavior; /* How to handle the first dash in the loop. We loop closed contours
twice to make sure the first dash and the last dash can get joined */
GskCurve curve; /* Curve we are currently processing */
float collect_start; /* We're collecting multiple line segments when decomposing. */
float collect_length; /* No need to emit a curve for every line segment when the dash is long enough. */
/* from the stroke */
float *dash;
gsize n_dash;
float dash_length;
float dash_offset;
float tolerance;
GskPathForeachFunc func;
gpointer user_data;
} GskPathDash;
static void
gsk_path_dash_setup (GskPathDash *self)
{
self->offset = fmodf (self->dash_offset, 2 * self->dash_length);
self->dash_index = 0;
self->on = TRUE;
self->may_close = TRUE;
while (self->offset > self->dash[self->dash_index % self->n_dash])
{
self->offset -= self->dash[self->dash_index % self->n_dash];
self->dash_index++;
self->on = !self->on;
}
if (self->first_dash_behavior != ONLY)
self->needs_move_to = TRUE;
}
static gboolean
gsk_path_dash_ensure_move_to (GskPathDash *self,
const graphene_point_t *pt)
{
if (!self->needs_move_to)
return TRUE;
if (!self->func (GSK_PATH_MOVE, pt, 1, 0, self->user_data))
return FALSE;
self->needs_move_to = FALSE;
return TRUE;
}
static gboolean
gsk_path_dash_add_line_segment (const graphene_point_t *start,
const graphene_point_t *end,
float t_start,
float t_end,
gpointer user_data)
{
GskPathDash *self = user_data;
float remaining, length, t_step;
length = graphene_point_distance (start, end, NULL, NULL);
if (self->collect_length)
{
t_start = self->collect_start;
length += self->collect_length;
self->collect_length = 0;
}
t_step = t_end - t_start;
remaining = length;
while (remaining)
{
float piece;
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
/* try collecting multiple line segments */
if (t_end < 1.0)
{
self->collect_start = t_start + t_step * (length - remaining) / length;
self->collect_length = remaining;
return TRUE;
}
piece = remaining;
}
else
piece = self->dash[self->dash_index % self->n_dash] - self->offset;
if (self->on)
{
if (self->first_dash_behavior != SKIP)
{
GskCurve segment;
if (piece)
{
gsk_curve_segment (&self->curve,
t_start + t_step * (length - remaining) / length,
t_start + t_step * (length - (remaining - piece)) / length,
&segment);
if (!gsk_path_dash_ensure_move_to (self, gsk_curve_get_start_point (&segment)))
return FALSE;
if (!gsk_pathop_foreach (gsk_curve_pathop (&segment), self->func, self->user_data))
return FALSE;
}
else
{
graphene_point_t p;
gsk_curve_get_point (&self->curve, t_start + t_step * (length - remaining) / length, &p);
if (!gsk_path_dash_ensure_move_to (self, &p))
return FALSE;
}
}
}
else
{
self->may_close = FALSE;
if (self->first_dash_behavior == ONLY)
{
self->first_dash_behavior = DONE;
return FALSE;
}
self->first_dash_behavior = NORMAL;
}
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
self->offset += remaining;
remaining = 0;
}
else
{
remaining -= piece;
self->offset = 0;
self->dash_index++;
self->dash_index %= 2 * self->n_dash;
self->on = !self->on;
self->needs_move_to = TRUE;
}
}
return TRUE;
}
static gboolean
gsk_path_dash_foreach (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathDash *self = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_dash_setup (self);
break;
case GSK_PATH_CLOSE:
if (self->may_close)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return self->func (GSK_PATH_CLOSE, pts, 2, 0, self->user_data);
}
else
op = GSK_PATH_LINE;
G_GNUC_FALLTHROUGH;
case GSK_PATH_LINE:
case GSK_PATH_CURVE:
case GSK_PATH_CONIC:
gsk_curve_init_foreach (&self->curve, op, pts, n_pts, weight);
if (!gsk_curve_decompose (&self->curve, self->tolerance, gsk_path_dash_add_line_segment, self))
return FALSE;
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
gboolean
gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
GskPathDash self = {
.offset = 0,
.dash = stroke->dash,
.n_dash = stroke->n_dash,
.dash_length = stroke->dash_length,
.dash_offset = stroke->dash_offset,
.tolerance = tolerance,
.func = func,
.user_data = user_data
};
gboolean is_closed = gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
self.first_dash_behavior = is_closed ? SKIP : NORMAL;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self))
return FALSE;
if (is_closed)
{
if (self.first_dash_behavior == NORMAL)
self.first_dash_behavior = ONLY;
else
self.first_dash_behavior = NORMAL;
self.needs_move_to = !self.on;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self) &&
self.first_dash_behavior != DONE)
return FALSE;
}
return TRUE;
}
gboolean
gsk_path_dash (GskPath *path,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
gsize i;
/* Dashing disabled, no need to do any work */
if (stroke->dash_length <= 0)
return gsk_path_foreach (path, -1, func, user_data);
for (i = 0; i < gsk_path_get_n_contours (path); i++)
{
if (!gsk_contour_dash (gsk_path_get_contour (path, i), stroke, tolerance, func, user_data))
return FALSE;
}
return TRUE;
}

50
gsk/gskpathdashprivate.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_DASH_PRIVATE_H__
#define __GSK_PATH_DASH_PRIVATE_H__
#include <gsk/gskpath.h>
G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_dash (GskPath *path,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
#ifdef GTK_COMPILATION
#include "gskcontourprivate.h"
gboolean gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
#endif /* GTK_COMPILATION */
G_END_DECLS
#endif /* __GSK_PATH_DASH_PRIVATE_H__ */

628
gsk/gskpathmeasure.c Normal file
View File

@@ -0,0 +1,628 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskpathmeasure.h"
#include "gskpathbuilder.h"
#include "gskpathprivate.h"
/**
* SECTION:gskpathmeasure
* @Title: PathMeasure
* @Short_description: Measuring operations on paths
* @See_also: #GskPath
*
* #GskPathMeasure is an object that allows measuring operations on #GskPaths.
* These operations are useful when implementing animations.
*/
typedef struct _GskContourMeasure GskContourMeasure;
struct _GskContourMeasure
{
float length;
gpointer contour_data;
};
struct _GskPathMeasure
{
/*< private >*/
guint ref_count;
GskPath *path;
float tolerance;
gsize first;
gsize last;
float length;
gsize n_contours;
GskContourMeasure measures[];
};
/**
* GskPathMeasure:
*
* A #GskPathMeasure struct is a reference counted struct
* and should be treated as opaque.
*/
G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure,
gsk_path_measure_ref,
gsk_path_measure_unref)
/**
* gsk_path_measure_new:
* @path: the path to measure
*
* Creates a measure object for the given @path.
*
* Returns: a new #GskPathMeasure representing @path
**/
GskPathMeasure *
gsk_path_measure_new (GskPath *path)
{
return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT);
}
/**
* gsk_path_measure_new_with_tolerance:
* @path: the path to measure
* @tolerance: the tolerance for measuring operations
*
* Creates a measure object for the given @path and @tolerance.
*
* Returns: a new #GskPathMeasure representing @path
**/
GskPathMeasure *
gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance)
{
GskPathMeasure *self;
gsize i, n_contours;
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (tolerance > 0, NULL);
n_contours = gsk_path_get_n_contours (path);
self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure));
self->ref_count = 1;
self->path = gsk_path_ref (path);
self->tolerance = tolerance;
self->n_contours = n_contours;
self->first = 0;
self->last = n_contours;
for (i = 0; i < n_contours; i++)
{
self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i),
self->tolerance,
&self->measures[i].length);
self->length += self->measures[i].length;
}
return self;
}
/**
* gsk_path_measure_ref:
* @self: a #GskPathMeasure
*
* Increases the reference count of a #GskPathMeasure by one.
*
* Returns: the passed in #GskPathMeasure.
**/
GskPathMeasure *
gsk_path_measure_ref (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count++;
return self;
}
/**
* gsk_path_measure_unref:
* @self: a #GskPathMeasure
*
* Decreases the reference count of a #GskPathMeasure by one.
* If the resulting reference count is zero, frees the path_measure.
**/
void
gsk_path_measure_unref (GskPathMeasure *self)
{
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
self->ref_count--;
if (self->ref_count > 0)
return;
for (i = 0; i < self->n_contours; i++)
{
gsk_contour_free_measure (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data);
}
gsk_path_unref (self->path);
g_free (self);
}
/**
* gsk_path_measure_get_path:
* @self: a #GskPathMeasure
*
* Returns the path that the measure was created for.
*
* Returns: (transfer none): the path of @self
*/
GskPath *
gsk_path_measure_get_path (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
return self->path;
}
/**
* gsk_path_measure_get_tolerance:
* @self: a #GskPathMeasure
*
* Returns the tolerance that the measure was created with.
*
* Returns: the tolerance of @self
*/
float
gsk_path_measure_get_tolerance (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0.f);
return self->tolerance;
}
/**
* gsk_path_measure_get_n_contours:
* @self: a #GskPathMeasure
*
* Returns the number of contours in the path being measured.
*
* The returned value is independent of whether @self if restricted
* or not.
*
* Returns: The number of contours
**/
gsize
gsk_path_measure_get_n_contours (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->n_contours;
}
/**
* gsk_path_measure_restrict_to_contour:
* @self: a #GskPathMeasure
* @contour: contour to restrict to or (gsize) -1 for using the
* whole path
*
* Restricts all functions on the path to just the given @contour.
*
* If @contour >= gsk_path_measure_get_n_contours() - so in
* particular when it is set to -1 - the whole path will be used.
**/
void
gsk_path_measure_restrict_to_contour (GskPathMeasure *self,
gsize contour)
{
if (contour >= self->n_contours)
{
/* use the whole path */
self->first = 0;
self->last = self->n_contours;
}
else
{
/* use just one contour */
self->first = contour;
self->last = contour + 1;
}
self->length = 0;
for (gsize i = self->first; i < self->last; i++)
{
self->length += self->measures[i].length;
}
}
/**
* gsk_path_measure_get_length:
* @self: a #GskPathMeasure
*
* Gets the length of the path being measured.
*
* The length is cached, so this function does not do any work.
*
* Returns: The length of the path measured by @self
**/
float
gsk_path_measure_get_length (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->length;
}
/**
* gsk_path_measure_is_closed:
* @self: a #GskPathMeasure
*
* Returns if the path being measured represents a single closed
* contour.
*
* Returns: %TRUE if the current path is closed
**/
gboolean
gsk_path_measure_is_closed (GskPathMeasure *self)
{
const GskContour *contour;
g_return_val_if_fail (self != NULL, FALSE);
/* XXX: is the empty path closed? Currently it's not */
if (self->last - self->first != 1)
return FALSE;
contour = gsk_path_get_contour (self->path, self->first);
return gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
}
static float
gsk_path_measure_clamp_distance (GskPathMeasure *self,
float distance)
{
if (isnan (distance))
return 0;
return CLAMP (distance, 0, self->length);
}
/**
* gsk_path_measure_get_point:
* @self: a #GskPathMeasure
* @distance: distance into the path
* @pos: (out) (optional) (caller-allocates): The coordinates
* of the position at @distance
* @tangent: (out) (optional) (caller-allocates): The tangent
* to the position at @distance
*
* Calculates the coordinates and tangent of the point @distance
* units into the path. The value will be clamped to the length
* of the path.
*
* If the point is a discontinuous edge in the path, the returned
* point and tangent will describe the line starting at that point
* going forward.
*
* If @self describes an empty path, the returned point will be
* set to `(0, 0)` and the tangent will be the x axis or `(1, 0)`.
**/
void
gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent)
{
gsize i;
g_return_if_fail (self != NULL);
if (pos == NULL && tangent == NULL)
return;
distance = gsk_path_measure_clamp_distance (self, distance);
for (i = self->first; i < self->last; i++)
{
if (distance < self->measures[i].length)
break;
distance -= self->measures[i].length;
}
/* weird corner cases */
if (i == self->last)
{
/* the empty path goes here */
if (self->first == self->last)
{
if (pos)
graphene_point_init (pos, 0.f, 0.f);
if (tangent)
graphene_vec2_init (tangent, 1.f, 0.f);
return;
}
/* rounding errors can make this happen */
i = self->last - 1;
distance = self->measures[i].length;
}
gsk_contour_get_point (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
distance,
pos,
tangent);
}
/**
* gsk_path_measure_get_closest_point:
* @self: a #GskPathMeasure
* @point: the point to fond the closest point to
* @out_pos: (out) (optional) (caller-allocates): return location
* for the closest point
*
* Gets the point on the path that is closest to @point.
*
* If the path being measured is empty, return 0 and set
* @out_pos to (0, 0).
*
* This is a simpler and slower version of
* gsk_path_measure_get_closest_point_full(). Use that one if you
* need more control.
*
* Returns: The offset into the path of the closest point
**/
float
gsk_path_measure_get_closest_point (GskPathMeasure *self,
const graphene_point_t *point,
graphene_point_t *out_pos)
{
float result;
g_return_val_if_fail (self != NULL, 0.0f);
if (gsk_path_measure_get_closest_point_full (self,
point,
INFINITY,
&result,
out_pos,
NULL,
NULL))
return result;
if (out_pos)
*out_pos = GRAPHENE_POINT_INIT (0, 0);
return 0;
}
/**
* gsk_path_measure_get_closest_point_full:
* @self: a #GskPathMeasure
* @point: the point to fond the closest point to
* @threshold: The maximum allowed distance between the path and @point.
* Use INFINITY to look for any point.
* @out_distance: (out) (optional) (caller-allocates): The
* distance between the found closest point on the path and the given
* @point.
* @out_pos: (out) (optional) (caller-allocates): return location
* for the closest point
* @out_offset: (out) (optional) (caller-allocates): The offset into
* the path of the found point
* @out_tangent: (out) (optional) (caller-allocates): return location for
* the tangent at the closest point
*
* Gets the point on the path that is closest to @point. If no point on
* path is closer to @point than @threshold, return %FALSE.
*
* Returns: %TRUE if a point was found, %FALSE otherwise.
**/
gboolean
gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent)
{
gboolean result;
gsize i;
float distance, length;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (point != NULL, FALSE);
result = FALSE;
length = 0;
for (i = self->first; i < self->last; i++)
{
if (gsk_contour_get_closest_point (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
self->tolerance,
point,
threshold,
&distance,
out_pos,
out_offset,
out_tangent))
{
result = TRUE;
if (out_offset)
*out_offset += length;
if (distance < self->tolerance)
break;
threshold = distance - self->tolerance;
}
length += self->measures[i].length;
}
if (result && out_distance)
*out_distance = distance;
return result;
}
/**
* gsk_path_measure_in_fill:
* @self: a #GskPathMeasure
* @point: the point to test
* @fill_rule: the fill rule to follow
*
* Returns whether the given point is inside the area that would be
* affected if the path of @self was filled according to @fill_rule.
*
* Returns: %TRUE if @point is inside
*/
gboolean
gsk_path_measure_in_fill (GskPathMeasure *self,
graphene_point_t *point,
GskFillRule fill_rule)
{
int winding = 0;
gboolean on_edge = FALSE;
int i;
for (i = self->first; i < self->last; i++)
{
winding += gsk_contour_get_winding (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data,
point,
&on_edge);
if (on_edge)
return TRUE;
}
switch (fill_rule)
{
case GSK_FILL_RULE_EVEN_ODD:
return winding & 1;
case GSK_FILL_RULE_WINDING:
return winding != 0;
default:
g_assert_not_reached ();
}
}
static void
gsk_path_builder_add_segment_chunk (GskPathBuilder *self,
GskPathMeasure *measure,
gboolean emit_move_to,
float start,
float end)
{
g_assert (start < end);
for (gsize i = measure->first; i < measure->last; i++)
{
if (measure->measures[i].length < start)
{
start -= measure->measures[i].length;
end -= measure->measures[i].length;
}
else if (start > 0 || end < measure->measures[i].length)
{
float len = MIN (end, measure->measures[i].length);
gsk_contour_add_segment (gsk_path_get_contour (measure->path, i),
self,
measure->measures[i].contour_data,
emit_move_to,
start,
len);
end -= len;
start = 0;
if (end <= 0)
break;
}
else
{
end -= measure->measures[i].length;
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (measure->path, i)));
}
emit_move_to = TRUE;
}
}
/**
* gsk_path_builder_add_segment:
* @self: a #GskPathBuilder
* @measure: the #GskPathMeasure to take the segment to
* @start: start distance into the path
* @end: end distance into the path
*
* Adds to @self the segment of @measure from @start to @end.
*
* The distances are given relative to the length of @measure's path,
* from 0 for the beginning of the path to
* gsk_path_measure_get_length() for the end of the path. The values
* will be clamped to that range.
*
* If @start >= @end after clamping, the path will first add the segment
* from @start to the end of the path, and then add the segment from
* the beginning to @end. If the path is closed, these segments will
* be connected.
**/
void
gsk_path_builder_add_segment (GskPathBuilder *self,
GskPathMeasure *measure,
float start,
float end)
{
g_return_if_fail (self != NULL);
g_return_if_fail (measure != NULL);
start = gsk_path_measure_clamp_distance (measure, start);
end = gsk_path_measure_clamp_distance (measure, end);
if (start < end)
{
gsk_path_builder_add_segment_chunk (self, measure, TRUE, start, end);
}
else
{
/* If the path is closed, we can connect the 2 subpaths. */
gboolean closed = gsk_path_measure_is_closed (measure);
gboolean need_move_to = !closed;
if (start < measure->length)
gsk_path_builder_add_segment_chunk (self, measure,
TRUE,
start, measure->length);
else
need_move_to = TRUE;
if (end > 0)
gsk_path_builder_add_segment_chunk (self, measure,
need_move_to,
0, end);
if (start == end && closed)
gsk_path_builder_close (self);
}
}

87
gsk/gskpathmeasure.h Normal file
View File

@@ -0,0 +1,87 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_MEASURE_H__
#define __GSK_PATH_MEASURE_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gskpath.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_new (GskPath *path);
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance);
GDK_AVAILABLE_IN_ALL
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_unref (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
gsize gsk_path_measure_get_n_contours (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_restrict_to_contour (GskPathMeasure *self,
gsize contour);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_length (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_is_closed (GskPathMeasure *self);
GDK_AVAILABLE_IN_ALL
void gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
graphene_point_t *pos,
graphene_vec2_t *tangent);
GDK_AVAILABLE_IN_ALL
float gsk_path_measure_get_closest_point (GskPathMeasure *self,
const graphene_point_t *point,
graphene_point_t *out_pos);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
const graphene_point_t *point,
float threshold,
float *out_distance,
graphene_point_t *out_pos,
float *out_offset,
graphene_vec2_t *out_tangent);
GDK_AVAILABLE_IN_ALL
gboolean gsk_path_measure_in_fill (GskPathMeasure *self,
graphene_point_t *point,
GskFillRule fill_rule);
G_END_DECLS
#endif /* __GSK_PATH_MEASURE_H__ */

179
gsk/gskpathopprivate.h Normal file
View File

@@ -0,0 +1,179 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATHOP_PRIVATE_H__
#define __GSK_PATHOP_PRIVATE_H__
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
G_BEGIN_DECLS
typedef gpointer gskpathop;
static inline
gskpathop gsk_pathop_encode (GskPathOperation op,
const graphene_point_t *pts);
static inline
const graphene_point_t *gsk_pathop_points (gskpathop pop);
static inline
GskPathOperation gsk_pathop_op (gskpathop pop);
static inline
gboolean gsk_pathop_foreach (gskpathop pop,
GskPathForeachFunc func,
gpointer user_data);
/* included inline so tests can use them */
static inline
void gsk_path_builder_pathop_to (GskPathBuilder *builder,
gskpathop op);
static inline
void gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gskpathop op);
/* IMPLEMENTATION */
#define GSK_PATHOP_OPERATION_MASK (0x7)
static inline gskpathop
gsk_pathop_encode (GskPathOperation op,
const graphene_point_t *pts)
{
/* g_assert (op & GSK_PATHOP_OPERATION_MASK == op); */
g_assert ((GPOINTER_TO_SIZE (pts) & GSK_PATHOP_OPERATION_MASK) == 0);
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pts) | op);
}
static inline const graphene_point_t *
gsk_pathop_points (gskpathop pop)
{
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pop) & ~GSK_PATHOP_OPERATION_MASK);
}
static inline
GskPathOperation gsk_pathop_op (gskpathop pop)
{
return GPOINTER_TO_SIZE (pop) & GSK_PATHOP_OPERATION_MASK;
}
static inline gboolean
gsk_pathop_foreach (gskpathop pop,
GskPathForeachFunc func,
gpointer user_data)
{
switch (gsk_pathop_op (pop))
{
case GSK_PATH_MOVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, 0, user_data);
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
case GSK_PATH_CURVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data);
case GSK_PATH_CONIC:
{
const graphene_point_t *pts = gsk_pathop_points (pop);
return func (gsk_pathop_op (pop), (graphene_point_t[3]) { pts[0], pts[1], pts[3] }, 3, pts[2].x, user_data);
}
default:
g_assert_not_reached ();
return TRUE;
}
}
static inline void
gsk_path_builder_pathop_to (GskPathBuilder *builder,
gskpathop op)
{
const graphene_point_t *pts = gsk_pathop_points (op);
switch (gsk_pathop_op (op))
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[3].x, pts[3].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
}
}
static inline void
gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gskpathop op)
{
const graphene_point_t *pts = gsk_pathop_points (op);
switch (gsk_pathop_op (op))
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_line_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_CURVE:
gsk_path_builder_curve_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
}
}
G_END_DECLS
#endif /* __GSK_PATHOP_PRIVATE_H__ */

61
gsk/gskpathprivate.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_PATH_PRIVATE_H__
#define __GSK_PATH_PRIVATE_H__
#include "gskpath.h"
#include "gskcontourprivate.h"
#include "gskpathopprivate.h"
G_BEGIN_DECLS
/* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */
#define GSK_PATH_TOLERANCE_DEFAULT (0.5)
GskPath * gsk_path_new_from_contours (const GSList *contours);
gsize gsk_path_get_n_contours (GskPath *path);
const GskContour * gsk_path_get_contour (GskPath *path,
gsize i);
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
GskPathForeachFlags flags,
double tolerance,
GskPathForeachFunc func,
gpointer user_data);
void gsk_path_builder_add_contour (GskPathBuilder *builder,
GskContour *contour);
void gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
float rx,
float ry,
float x_axis_rotation,
gboolean large_arc,
gboolean positive_sweep,
float x,
float y);
G_END_DECLS
#endif /* __GSK_PATH_PRIVATE_H__ */

View File

@@ -158,6 +158,8 @@ GskRenderNode * gsk_render_node_deserialize (GBytes
#define GSK_TYPE_REPEAT_NODE (gsk_repeat_node_get_type())
#define GSK_TYPE_CLIP_NODE (gsk_clip_node_get_type())
#define GSK_TYPE_ROUNDED_CLIP_NODE (gsk_rounded_clip_node_get_type())
#define GSK_TYPE_FILL_NODE (gsk_fill_node_get_type())
#define GSK_TYPE_STROKE_NODE (gsk_stroke_node_get_type())
#define GSK_TYPE_SHADOW_NODE (gsk_shadow_node_get_type())
#define GSK_TYPE_BLEND_NODE (gsk_blend_node_get_type())
#define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type())
@@ -184,6 +186,8 @@ typedef struct _GskColorMatrixNode GskColorMatrixNode;
typedef struct _GskRepeatNode GskRepeatNode;
typedef struct _GskClipNode GskClipNode;
typedef struct _GskRoundedClipNode GskRoundedClipNode;
typedef struct _GskFillNode GskFillNode;
typedef struct _GskStrokeNode GskStrokeNode;
typedef struct _GskShadowNode GskShadowNode;
typedef struct _GskBlendNode GskBlendNode;
typedef struct _GskCrossFadeNode GskCrossFadeNode;
@@ -443,6 +447,32 @@ GskRenderNode * gsk_rounded_clip_node_get_child (GskRenderNode
GDK_AVAILABLE_IN_ALL
const GskRoundedRect * gsk_rounded_clip_node_get_clip (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_fill_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_fill_node_new (GskRenderNode *child,
GskPath *path,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_fill_node_get_child (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_fill_node_get_path (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskFillRule gsk_fill_node_get_fill_rule (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_stroke_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_stroke_node_new (GskRenderNode *child,
GskPath *path,
const GskStroke *stroke);
GDK_AVAILABLE_IN_ALL
GskRenderNode * gsk_stroke_node_get_child (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_stroke_node_get_path (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
const GskStroke * gsk_stroke_node_get_stroke (GskRenderNode *node);
GDK_AVAILABLE_IN_ALL
GType gsk_shadow_node_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL

View File

@@ -23,8 +23,10 @@
#include "gskcairoblurprivate.h"
#include "gskdebugprivate.h"
#include "gskdiffprivate.h"
#include "gskpath.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gskstrokeprivate.h"
#include "gsktransformprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -3682,6 +3684,340 @@ gsk_rounded_clip_node_get_clip (GskRenderNode *node)
return &self->clip;
}
/*** GSK_FILL_NODE ***/
struct _GskFillNode
{
GskRenderNode render_node;
GskRenderNode *child;
GskPath *path;
GskFillRule fill_rule;
};
static void
gsk_fill_node_finalize (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_FILL_NODE));
gsk_render_node_unref (self->child);
gsk_path_unref (self->path);
parent_class->finalize (node);
}
static void
gsk_fill_node_draw (GskRenderNode *node,
cairo_t *cr)
{
GskFillNode *self = (GskFillNode *) node;
cairo_save (cr);
switch (self->fill_rule)
{
case GSK_FILL_RULE_WINDING:
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
break;
case GSK_FILL_RULE_EVEN_ODD:
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
break;
default:
g_assert_not_reached ();
break;
}
gsk_path_to_cairo (self->path, cr);
cairo_clip (cr);
gsk_render_node_draw (self->child, cr);
cairo_restore (cr);
}
static void
gsk_fill_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
GskFillNode *self1 = (GskFillNode *) node1;
GskFillNode *self2 = (GskFillNode *) node2;
if (self1->path == self2->path)
{
cairo_region_t *sub;
cairo_rectangle_int_t clip_rect;
graphene_rect_t rect;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
graphene_rect_union (&node1->bounds, &node2->bounds, &rect);
rectangle_init_from_graphene (&clip_rect, &rect);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}
else
{
gsk_render_node_diff_impossible (node1, node2, region);
}
}
/**
* gsk_fill_node_new:
* @child: The node to fill the area with
* @path: The path describing the area to fill
* @fill_rule: The fill rule to use
*
* Creates a #GskRenderNode that will fill the @child in the area
* given by @path and @fill_rule.
*
* Returns: (transfer none) (type GskFillNode): A new #GskRenderNode
*/
GskRenderNode *
gsk_fill_node_new (GskRenderNode *child,
GskPath *path,
GskFillRule fill_rule)
{
GskFillNode *self;
GskRenderNode *node;
graphene_rect_t path_bounds;
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
g_return_val_if_fail (path != NULL, NULL);
self = gsk_render_node_alloc (GSK_FILL_NODE);
node = (GskRenderNode *) self;
self->child = gsk_render_node_ref (child);
self->path = gsk_path_ref (path);
self->fill_rule = fill_rule;
if (gsk_path_get_bounds (path, &path_bounds))
graphene_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
else
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
return node;
}
/**
* gsk_fill_node_get_child:
* @node: (type GskFillNode): a fill #GskRenderNode
*
* Gets the child node that is getting drawn by the given @node.
*
* Returns: (transfer none): The child that is getting drawn
**/
GskRenderNode *
gsk_fill_node_get_child (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
return self->child;
}
/**
* gsk_fill_node_get_path:
* @node: (type GskFillNode): a fill #GskRenderNode
*
* Retrievs the path used to describe the area filled with the contents of
* the @node.
*
* Returns: (transfer none): a #GskPath
*/
GskPath *
gsk_fill_node_get_path (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
return self->path;
}
/**
* gsk_fill_node_get_fill_rule:
* @node: (type GskFillNode): a fill #GskRenderNode
*
* Retrievs the fill rule used to determine how the path is filled.
*
* Returns: a #GskFillRule
*/
GskFillRule
gsk_fill_node_get_fill_rule (GskRenderNode *node)
{
GskFillNode *self = (GskFillNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), GSK_FILL_RULE_WINDING);
return self->fill_rule;
}
/*** GSK_STROKE_NODE ***/
struct _GskStrokeNode
{
GskRenderNode render_node;
GskRenderNode *child;
GskPath *path;
GskStroke stroke;
};
static void
gsk_stroke_node_finalize (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_STROKE_NODE));
gsk_render_node_unref (self->child);
gsk_path_unref (self->path);
gsk_stroke_clear (&self->stroke);
parent_class->finalize (node);
}
static void
gsk_stroke_node_draw (GskRenderNode *node,
cairo_t *cr)
{
GskStrokeNode *self = (GskStrokeNode *) node;
cairo_save (cr);
gsk_cairo_rectangle (cr, &self->child->bounds);
cairo_clip (cr);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
cairo_pop_group_to_source (cr);
gsk_stroke_to_cairo (&self->stroke, cr);
gsk_path_to_cairo (self->path, cr);
cairo_stroke (cr);
cairo_restore (cr);
}
static void
gsk_stroke_node_diff (GskRenderNode *node1,
GskRenderNode *node2,
cairo_region_t *region)
{
GskStrokeNode *self1 = (GskStrokeNode *) node1;
GskStrokeNode *self2 = (GskStrokeNode *) node2;
if (self1->path == self2->path &&
gsk_stroke_equal (&self1->stroke, &self2->stroke))
{
cairo_region_t *sub;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}
else
{
gsk_render_node_diff_impossible (node1, node2, region);
}
}
/**
* gsk_stroke_node_new:
* @child: The node to stroke the area with
* @path: (transfer none): The path describing the area to stroke
* @stroke: (transfer none): The stroke attributes to use
*
* Creates a #GskRenderNode that will stroke the @child along the given
* @path using the attributes defined in @stroke.
*
* Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode
*/
GskRenderNode *
gsk_stroke_node_new (GskRenderNode *child,
GskPath *path,
const GskStroke *stroke)
{
GskStrokeNode *self;
GskRenderNode *node;
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (stroke != NULL, NULL);
self = gsk_render_node_alloc (GSK_STROKE_NODE);
node = (GskRenderNode *) self;
self->child = gsk_render_node_ref (child);
self->path = gsk_path_ref (path);
gsk_stroke_init_copy (&self->stroke, stroke);
/* XXX: Figure out a way to compute bounds from the path */
graphene_rect_init_from_rect (&node->bounds, &child->bounds);
return node;
}
/**
* gsk_stroke_node_get_child:
* @node: (type GskStrokeNode): a stroke #GskRenderNode
*
* Gets the child node that is getting drawn by the given @node.
*
* Returns: (transfer none): The child that is getting drawn
**/
GskRenderNode *
gsk_stroke_node_get_child (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return self->child;
}
/**
* gsk_stroke_node_get_path:
* @node: (type GskStrokeNode): a stroke #GskRenderNode
*
* Retrievs the path that will be stroked with the contents of
* the @node.
*
* Returns: (transfer none): a #GskPath
*/
GskPath *
gsk_stroke_node_get_path (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return self->path;
}
/**
* gsk_stroke_node_get_stroke:
* @node: (type GskStrokeNode): a stroke #GskRenderNode
*
* Retrievs the stroke attributes used in this @node.
*
* Returns: a #GskStroke
*/
const GskStroke *
gsk_stroke_node_get_stroke (GskRenderNode *node)
{
GskStrokeNode *self = (GskStrokeNode *) node;
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
return &self->stroke;
}
/*** GSK_SHADOW_NODE ***/
/**
@@ -5230,6 +5566,8 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_matrix_node, GSK_COLOR_MATRIX_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_fill_node, GSK_FILL_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_stroke_node, GSK_STROKE_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
@@ -5529,6 +5867,38 @@ gsk_render_node_init_types_once (void)
gsk_render_node_types[GSK_ROUNDED_CLIP_NODE] = node_type;
}
{
const GskRenderNodeTypeInfo node_info =
{
GSK_FILL_NODE,
sizeof (GskFillNode),
NULL,
gsk_fill_node_finalize,
gsk_fill_node_draw,
NULL,
gsk_fill_node_diff,
};
GType node_type = gsk_render_node_type_register_static (I_("GskFillNode"), &node_info);
gsk_render_node_types[GSK_FILL_NODE] = node_type;
}
{
const GskRenderNodeTypeInfo node_info =
{
GSK_STROKE_NODE,
sizeof (GskStrokeNode),
NULL,
gsk_stroke_node_finalize,
gsk_stroke_node_draw,
NULL,
gsk_stroke_node_diff,
};
GType node_type = gsk_render_node_type_register_static (I_("GskStrokeNode"), &node_info);
gsk_render_node_types[GSK_STROKE_NODE] = node_type;
}
{
const GskRenderNodeTypeInfo node_info =
{

View File

@@ -23,9 +23,13 @@
#include "gskrendernodeparserprivate.h"
#include "gskpath.h"
#include "gskpathbuilder.h"
#include "gskroundedrectprivate.h"
#include "gskrendernodeprivate.h"
#include "gskstroke.h"
#include "gsktransformprivate.h"
#include "gskenumtypes.h"
#include "gdk/gdkrgbaprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -406,7 +410,10 @@ parse_string (GtkCssParser *parser,
token = gtk_css_parser_get_token (parser);
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
return FALSE;
{
gtk_css_parser_error_syntax (parser, "Expected a string");
return FALSE;
}
s = g_strdup (token->string.string);
gtk_css_parser_consume_token (parser);
@@ -955,6 +962,26 @@ create_default_render_node (void)
return gsk_color_node_new (&GDK_RGBA("FF00CC"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
}
static GskPath *
create_default_path (void)
{
GskPathBuilder *builder;
guint i;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 25, 0);
for (i = 1; i < 5; i++)
{
gsk_path_builder_line_to (builder,
sin (i * G_PI * 0.8) * 25 + 25,
-cos (i * G_PI * 0.8) * 25 + 25);
}
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskRenderNode *
parse_color_node (GtkCssParser *parser)
{
@@ -1765,6 +1792,208 @@ parse_rounded_clip_node (GtkCssParser *parser)
return result;
}
static gboolean
parse_path (GtkCssParser *parser,
gpointer out_path)
{
GskPath *path;
char *str = NULL;
if (!parse_string (parser, &str))
return FALSE;
path = gsk_path_parse (str);
g_free (str);
if (path == NULL)
{
gtk_css_parser_error_value (parser, "Invalid path");
return FALSE;
}
*((GskPath **) out_path) = path;
return TRUE;
}
static void
clear_path (gpointer inout_path)
{
g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
}
static gboolean
parse_dash (GtkCssParser *parser,
gpointer out_dash)
{
GArray *dash;
double d;
/* because CSS does this, too */
if (gtk_css_parser_try_ident (parser, "none"))
{
*((GArray **) out_dash) = NULL;
return TRUE;
}
dash = g_array_new (FALSE, FALSE, sizeof (float));
do {
if (!gtk_css_parser_consume_number (parser, &d))
{
g_array_free (dash, TRUE);
return FALSE;
}
g_array_append_vals (dash, (float[1]) { d }, 1);
} while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER));
*((GArray **) out_dash) = dash;
return TRUE;
}
static void
clear_dash (gpointer inout_array)
{
g_clear_pointer ((GArray **) inout_array, g_array_unref);
}
static gboolean
parse_enum (GtkCssParser *parser,
GType type,
gpointer out_value)
{
GEnumClass *class;
GEnumValue *v;
const GtkCssToken *token;
token = gtk_css_parser_get_token (parser);
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
{
gtk_css_parser_error_syntax (parser, "Expected a valid identifier");
return FALSE;
}
class = g_type_class_ref (type);
v = g_enum_get_value_by_nick (class, token->string.string);
if (v == NULL)
{
gtk_css_parser_error_value (parser, "\"%s\" is not a valid identifier here", token->string.string);
g_type_class_unref (class);
return FALSE;
}
*(int*)out_value = v->value;
g_type_class_unref (class);
gtk_css_parser_consume_token (parser);
return TRUE;
}
static gboolean
parse_fill_rule (GtkCssParser *parser,
gpointer out_rule)
{
return parse_enum (parser, GSK_TYPE_FILL_RULE, out_rule);
}
static GskRenderNode *
parse_fill_node (GtkCssParser *parser)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
int rule = GSK_FILL_RULE_WINDING;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "fill-rule", parse_fill_rule, NULL, &rule },
};
GskRenderNode *result;
parse_declarations (parser, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
result = gsk_fill_node_new (child, path, rule);
gsk_path_unref (path);
gsk_render_node_unref (child);
return result;
}
static gboolean
parse_line_cap (GtkCssParser *parser,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_CAP, out);
}
static gboolean
parse_line_join (GtkCssParser *parser,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_JOIN, out);
}
static GskRenderNode *
parse_stroke_node (GtkCssParser *parser)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
double line_width = 1.0;
int line_cap = GSK_LINE_CAP_BUTT;
int line_join = GSK_LINE_JOIN_MITER;
double miter_limit = 4.0;
GArray *dash = NULL;
double dash_offset = 0.0;
GskStroke *stroke;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "line-width", parse_double, NULL, &line_width },
{ "line-cap", parse_line_cap, NULL, &line_cap },
{ "line-join", parse_line_join, NULL, &line_join },
{ "miter-limit", parse_double, NULL, &miter_limit },
{ "dash", parse_dash, clear_dash, &dash },
{ "dash-offset", parse_double, NULL, &dash_offset},
};
GskRenderNode *result;
parse_declarations (parser, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
stroke = gsk_stroke_new (line_width);
gsk_stroke_set_line_cap (stroke, line_cap);
gsk_stroke_set_line_join (stroke, line_join);
gsk_stroke_set_miter_limit (stroke, miter_limit);
if (dash)
{
gsk_stroke_set_dash (stroke, (float *) dash->data, dash->len);
g_array_free (dash, TRUE);
}
gsk_stroke_set_dash_offset (stroke, dash_offset);
result = gsk_stroke_node_new (child, path, stroke);
gsk_path_unref (path);
gsk_stroke_free (stroke);
gsk_render_node_unref (child);
return result;
}
static GskRenderNode *
parse_shadow_node (GtkCssParser *parser)
{
@@ -1844,6 +2073,8 @@ parse_node (GtkCssParser *parser,
{ "repeating-linear-gradient", parse_repeating_linear_gradient_node },
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
{ "rounded-clip", parse_rounded_clip_node },
{ "fill", parse_fill_node },
{ "stroke", parse_stroke_node },
{ "shadow", parse_shadow_node },
{ "text", parse_text_node },
{ "texture", parse_texture_node },
@@ -2100,7 +2331,7 @@ append_float_param (Printer *p,
float value,
float default_value)
{
/* Don't approximate-compare here, better be topo verbose */
/* Don't approximate-compare here, better be too verbose */
if (value == default_value)
return;
@@ -2275,8 +2506,11 @@ append_escaping_newlines (GString *str,
len = strcspn (string, "\n");
g_string_append_len (str, string, len);
string += len;
g_string_append (str, "\\\n");
string++;
if (*string)
{
g_string_append (str, "\\\n");
string++;
}
} while (*string);
}
@@ -2388,6 +2622,83 @@ gsk_text_node_serialize_glyphs (GskRenderNode *node,
pango_glyph_string_free (ascii);
}
static const char *
enum_to_nick (GType type,
int value)
{
GEnumClass *class;
GEnumValue *v;
class = g_type_class_ref (type);
v = g_enum_get_value (class, value);
g_type_class_unref (class);
return v->value_nick;
}
static void
append_enum_param (Printer *p,
const char *param_name,
GType type,
int value)
{
_indent (p);
g_string_append_printf (p->str, "%s: ", param_name);
g_string_append (p->str, enum_to_nick (type, value));
g_string_append_c (p->str, ';');
g_string_append_c (p->str, '\n');
}
static void
append_path_param (Printer *p,
const char *param_name,
GskPath *path)
{
char *str, *s;
_indent (p);
g_string_append (p->str, "path: \"\\\n");
str = gsk_path_to_string (path);
/* Put each command on a new line */
for (s = str; *s; s++)
{
if (*s == ' ' &&
(s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L' || s[1] == 'O'))
*s = '\n';
}
append_escaping_newlines (p->str, str);
g_string_append (p->str, "\";\n");
g_free (str);
}
static void
append_dash_param (Printer *p,
const char *param_name,
const float *dash,
gsize n_dash)
{
_indent (p);
g_string_append (p->str, "dash: ");
if (n_dash == 0)
{
g_string_append (p->str, "none");
}
else
{
gsize i;
string_append_double (p->str, dash[0]);
for (i = 1; i < n_dash; i++)
{
g_string_append_c (p->str, ' ');
string_append_double (p->str, dash[i]);
}
}
g_string_append (p->str, ";\n");
}
static void
render_node_print (Printer *p,
GskRenderNode *node)
@@ -2536,6 +2847,42 @@ render_node_print (Printer *p,
append_node_param (p, "child", gsk_rounded_clip_node_get_child (node));
append_rounded_rect_param (p, "clip", gsk_rounded_clip_node_get_clip (node));
end_node (p);
}
break;
case GSK_FILL_NODE:
{
start_node (p, "fill");
append_node_param (p, "child", gsk_fill_node_get_child (node));
append_path_param (p, "path", gsk_fill_node_get_path (node));
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
end_node (p);
}
break;
case GSK_STROKE_NODE:
{
const GskStroke *stroke;
const float *dash;
gsize n_dash;
start_node (p, "stroke");
append_node_param (p, "child", gsk_stroke_node_get_child (node));
append_path_param (p, "path", gsk_stroke_node_get_path (node));
stroke = gsk_stroke_node_get_stroke (node);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0f);
append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke));
append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke));
append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f);
dash = gsk_stroke_get_dash (stroke, &n_dash);
if (dash)
append_dash_param (p, "dash", dash, n_dash);
append_float_param (p, "dash-offset", gsk_stroke_get_dash_offset (stroke), 0.0f);
end_node (p);
}

208
gsk/gskspline.c Normal file
View File

@@ -0,0 +1,208 @@
/*
* Copyright © 2002 University of Southern California
* 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>
* Carl D. Worth <cworth@cworth.org>
*/
#include "config.h"
#include "gsksplineprivate.h"
#include <math.h>
/* Spline deviation from the circle in radius would be given by:
error = sqrt (x**2 + y**2) - 1
A simpler error function to work with is:
e = x**2 + y**2 - 1
From "Good approximation of circles by curvature-continuous Bezier
curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
Design 8 (1990) 22-41, we learn:
abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
and
abs (error) =~ 1/2 * e
Of course, this error value applies only for the particular spline
approximation that is used in _cairo_gstate_arc_segment.
*/
static float
arc_error_normalized (float angle)
{
return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
}
static float
arc_max_angle_for_tolerance_normalized (float tolerance)
{
float angle, error;
guint i;
/* Use table lookup to reduce search time in most cases. */
struct {
float angle;
float error;
} table[] = {
{ G_PI / 1.0, 0.0185185185185185036127 },
{ G_PI / 2.0, 0.000272567143730179811158 },
{ G_PI / 3.0, 2.38647043651461047433e-05 },
{ G_PI / 4.0, 4.2455377443222443279e-06 },
{ G_PI / 5.0, 1.11281001494389081528e-06 },
{ G_PI / 6.0, 3.72662000942734705475e-07 },
{ G_PI / 7.0, 1.47783685574284411325e-07 },
{ G_PI / 8.0, 6.63240432022601149057e-08 },
{ G_PI / 9.0, 3.2715520137536980553e-08 },
{ G_PI / 10.0, 1.73863223499021216974e-08 },
{ G_PI / 11.0, 9.81410988043554039085e-09 },
};
for (i = 0; i < G_N_ELEMENTS (table); i++)
{
if (table[i].error < tolerance)
return table[i].angle;
}
i++;
do {
angle = G_PI / i++;
error = arc_error_normalized (angle);
} while (error > tolerance);
return angle;
}
static guint
arc_segments_needed (float angle,
float radius,
float tolerance)
{
float max_angle;
/* the error is amplified by at most the length of the
* major axis of the circle; see cairo-pen.c for a more detailed analysis
* of this. */
max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
return ceil (fabs (angle) / max_angle);
}
/* We want to draw a single spline approximating a circular arc radius
R from angle A to angle B. Since we want a symmetric spline that
matches the endpoints of the arc in position and slope, we know
that the spline control points must be:
(R * cos(A), R * sin(A))
(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
(R * cos(B), R * sin(B))
for some value of h.
"Approximation of circular arcs by cubic polynomials", Michael
Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
various values of h along with error analysis for each.
From that paper, a very practical value of h is:
h = 4/3 * R * tan(angle/4)
This value does not give the spline with minimal error, but it does
provide a very good approximation, (6th-order convergence), and the
error expression is quite simple, (see the comment for
_arc_error_normalized).
*/
static gboolean
gsk_spline_decompose_arc_segment (const graphene_point_t *center,
float radius,
float angle_A,
float angle_B,
GskSplineAddCurveFunc curve_func,
gpointer user_data)
{
float r_sin_A, r_cos_A;
float r_sin_B, r_cos_B;
float h;
r_sin_A = radius * sin (angle_A);
r_cos_A = radius * cos (angle_A);
r_sin_B = radius * sin (angle_B);
r_cos_B = radius * cos (angle_B);
h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
return curve_func ((graphene_point_t[4]) {
GRAPHENE_POINT_INIT (
center->x + r_cos_A,
center->y + r_sin_A
),
GRAPHENE_POINT_INIT (
center->x + r_cos_A - h * r_sin_A,
center->y + r_sin_A + h * r_cos_A
),
GRAPHENE_POINT_INIT (
center->x + r_cos_B + h * r_sin_B,
center->y + r_sin_B - h * r_cos_B
),
GRAPHENE_POINT_INIT (
center->x + r_cos_B,
center->y + r_sin_B
)
},
user_data);
}
gboolean
gsk_spline_decompose_arc (const graphene_point_t *center,
float radius,
float tolerance,
float start_angle,
float end_angle,
GskSplineAddCurveFunc curve_func,
gpointer user_data)
{
float step = start_angle - end_angle;
guint i, n_segments;
/* Recurse if drawing arc larger than pi */
if (ABS (step) > G_PI)
{
float mid_angle = (start_angle + end_angle) / 2.0;
return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data)
&& gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data);
}
else if (ABS (step) < tolerance)
{
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
}
n_segments = arc_segments_needed (ABS (step), radius, tolerance);
step = (end_angle - start_angle) / n_segments;
for (i = 0; i < n_segments - 1; i++, start_angle += step)
{
if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data))
return FALSE;
}
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
}

41
gsk/gsksplineprivate.h Normal file
View File

@@ -0,0 +1,41 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_SPLINE_PRIVATE_H__
#define __GSK_SPLINE_PRIVATE_H__
#include "gskpath.h"
G_BEGIN_DECLS
typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4],
gpointer user_data);
gboolean gsk_spline_decompose_arc (const graphene_point_t *center,
float radius,
float tolerance,
float start_angle,
float end_angle,
GskSplineAddCurveFunc curve_func,
gpointer user_data);
G_END_DECLS
#endif /* __GSK_SPLINE_PRIVATE_H__ */

441
gsk/gskstroke.c Normal file
View File

@@ -0,0 +1,441 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskstrokeprivate.h"
/**
* SECTION:gskstroke
* @Title: Stroke
* @Short_description: Properties of a stroke operation
* @See_also: #GskPath, gsk_stroke_node_new()
*
* This section describes the #GskStroke structure that is used to
* describe lines and curves that are more complex than simple rectangles.
*
* #GskStroke is an immutable struct. After creation, you cannot change
* the types it represents. Instead, new #GskStroke have to be created.
* The #GskStrokeBuilder structure is meant to help in this endeavor.
*/
/**
* GskStroke:
*
* A #GskStroke struct is an opaque struct that should be copied
* on use.
*/
G_DEFINE_BOXED_TYPE (GskStroke, gsk_stroke,
gsk_stroke_copy,
gsk_stroke_free)
/**
* gsk_stroke_new:
* @line_width: line width of the stroke. Must be > 0
*
* Creates a new #GskStroke with the given @line_width.
*
* Returns: a new #GskStroke
**/
GskStroke *
gsk_stroke_new (float line_width)
{
GskStroke *self;
g_return_val_if_fail (line_width > 0, NULL);
self = g_new0 (GskStroke, 1);
self->line_width = line_width;
self->line_cap = GSK_LINE_CAP_BUTT;
self->line_join = GSK_LINE_JOIN_MITER;
self->miter_limit = 4.f; /* following svg */
return self;
}
/**
* gsk_stroke_copy:
* @other: #GskStroke to copy
*
* Creates a copy of the given @other stroke.
*
* Returns: a new #GskStroke. Use gsk_stroke_free() to free it.
**/
GskStroke *
gsk_stroke_copy (const GskStroke *other)
{
GskStroke *self;
g_return_val_if_fail (other != NULL, NULL);
self = g_new (GskStroke, 1);
gsk_stroke_init_copy (self, other);
return self;
}
/**
* gsk_stroke_free:
* @self: a #GskStroke
*
* Frees a #GskStroke.
**/
void
gsk_stroke_free (GskStroke *self)
{
if (self == NULL)
return;
gsk_stroke_clear (self);
g_free (self);
}
void
gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr)
{
cairo_set_line_width (cr, self->line_width);
/* gcc can optimize that to a direct case. This catches later additions to the enum */
switch (self->line_cap)
{
case GSK_LINE_CAP_BUTT:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
break;
case GSK_LINE_CAP_ROUND:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
break;
case GSK_LINE_CAP_SQUARE:
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
break;
default:
g_assert_not_reached ();
break;
}
/* gcc can optimize that to a direct case. This catches later additions to the enum */
switch (self->line_join)
{
case GSK_LINE_JOIN_MITER:
case GSK_LINE_JOIN_MITER_CLIP:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
break;
case GSK_LINE_JOIN_ROUND:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
break;
case GSK_LINE_JOIN_BEVEL:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
break;
default:
g_assert_not_reached ();
break;
}
cairo_set_miter_limit (cr, self->miter_limit);
if (self->dash_length)
{
gsize i;
double *dash = g_newa (double, self->n_dash);
for (i = 0; i < self->n_dash; i++)
{
dash[i] = self->dash[i];
}
cairo_set_dash (cr, dash, self->n_dash, self->dash_offset);
}
else
cairo_set_dash (cr, NULL, 0, 0.0);
}
/**
* gsk_stroke_equal:
* @stroke1: the first #GskStroke
* @stroke2: the second #GskStroke
*
* Checks if 2 strokes are identical.
*
* Returns: %TRUE if the 2 strokes are equal, %FALSE otherwise
**/
gboolean
gsk_stroke_equal (gconstpointer stroke1,
gconstpointer stroke2)
{
const GskStroke *self1 = stroke1;
const GskStroke *self2 = stroke2;
return self1->line_width == self2->line_width;
}
/**
* gsk_stroke_set_line_width:
* @self: a #GskStroke
* @line_width: width of the line in pixels
*
* Sets the line width to be used when stroking. The line width
* must be > 0.
**/
void
gsk_stroke_set_line_width (GskStroke *self,
float line_width)
{
g_return_if_fail (self != NULL);
g_return_if_fail (line_width > 0);
self->line_width = line_width;
}
/**
* gsk_stroke_get_line_width:
* @self: a #GskStroke
*
* Gets the line width used.
*
* Returns: The line width
**/
float
gsk_stroke_get_line_width (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_width;
}
/**
* gsk_stroke_set_line_cap:
* @self: a #GskStroke
* @line_cap: the #GskLineCap
*
* Sets the line cap to be used when stroking.
* See #GskLineCap for details.
**/
void
gsk_stroke_set_line_cap (GskStroke *self,
GskLineCap line_cap)
{
g_return_if_fail (self != NULL);
self->line_cap = line_cap;
}
/**
* gsk_stroke_get_line_cap:
* @self: a #GskStroke
*
* Gets the line cap used. See #GskLineCap for details.
*
* Returns: The line cap
**/
GskLineCap
gsk_stroke_get_line_cap (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_cap;
}
/**
* gsk_stroke_set_line_join:
* @self: a #GskStroke
* @line_join: The line join to use
*
* Sets the line join to be used when stroking.
* See #GskLineJoin for details.
**/
void
gsk_stroke_set_line_join (GskStroke *self,
GskLineJoin line_join)
{
g_return_if_fail (self != NULL);
self->line_join = line_join;
}
/**
* gsk_stroke_get_line_join:
* @self: a #GskStroke
*
* Gets the line join used. See #GskLineJoin for details.
*
* Returns: The line join
**/
GskLineJoin
gsk_stroke_get_line_join (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 0.0);
return self->line_join;
}
/**
* gsk_stroke_set_miter_limit:
* @self: a #GskStroke
* @limit: the miter limit, must be non-negative
*
* Sets the limit for the distance from the corner where sharp
* turns of joins get cut off. The miter limit is in units of
* line width.
*
* For joins of type %GSK_LINE_JOIN_MITER that exceed the miter
* limit, the join gets rendered as if it was of type
* %GSK_LINE_JOIN_BEVEL. For joins of type %GSK_LINE_JOIN_MITER_CLIP,
* the miter is clipped at a distance of half the miter limit.
*/
void
gsk_stroke_set_miter_limit (GskStroke *self,
float limit)
{
g_return_if_fail (self != NULL);
g_return_if_fail (limit >= 0);
self->miter_limit = limit;
}
/**
* gsk_stroke_get_miter_limit:
* @self: a #GskStroke
*
* Returns the miter limit of a #GskStroke.
*/
float
gsk_stroke_get_miter_limit (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 4.f);
return self->miter_limit;
}
/**
* gsk_stroke_set_dash:
* @self: a #GskStroke
* @dash: (array length=n_dash) (transfer none) (allow none):
* the array of dashes
* @n_dash: number of elements in @dash
*
* Sets the dash pattern to use by this stroke. A dash pattern is specified by
* an array of alternating non-negative values. Each value provides the length
* of alternate "on" and "off" portions of the stroke.
*
* Each "on" segment will have caps applied as if the segment were a separate
* contour. In particular, it is valid to use an "on" length of 0 with
* @GSK_LINE_CAP_ROUND or @GSK_LINE_CAP_SQUARE to draw dots or squares along
* a path.
*
* If @n_dash is 0, if all elements in @dash are 0, or if there are negative
* values in @dash, then dashing is disabled.
*
* If @n_dash is 1, an alternating "on" and "off" pattern with the single
* dash length provided is assumed.
*
* If @n_dash is uneven, the dash array will be used with the first element
* in @dash defining an "on" or "off" in alternating passes through the array.
*
* You can specify a starting offset into the dash with
* @gsk_stroke_set_dash_offset().
**/
void
gsk_stroke_set_dash (GskStroke *self,
const float *dash,
gsize n_dash)
{
float dash_length;
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (dash != NULL || n_dash == 0);
dash_length = 0;
for (i = 0; i < n_dash; i++)
{
if (!(dash[i] >= 0)) /* should catch NaN */
{
g_critical ("invalid value in dash array at position %zu", i);
return;
}
dash_length += dash[i];
}
self->dash_length = dash_length;
g_free (self->dash);
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
self->n_dash = n_dash;
}
/**
* gsk_stroke_get_dash:
* @self: a #GskStroke
* @n_dash: (out) (caller allocates): number of elements
* in the array returned
*
* Gets the dash array in use or %NULL if dashing is disabled.
*
* Returns: (array length=n_dash) (transfer none) (allow none):
* The dash array or %NULL if the dash array is empty.
**/
const float *
gsk_stroke_get_dash (const GskStroke *self,
gsize *n_dash)
{
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (n_dash != NULL, NULL);
*n_dash = self->n_dash;
return self->dash;
}
/**
* gsk_stroke_set_dash_offset:
* @self: a #GskStroke
* @offset: offset into the dash pattern
*
* Sets the offset into the dash pattern set via gsk_stroke_set_dash() where
* dashing should begin.
*
* This is an offset into the length of the path, not an index into the array values of
* the dash array.
**/
void
gsk_stroke_set_dash_offset (GskStroke *self,
float offset)
{
g_return_if_fail (self != NULL);
self->dash_offset = offset;
}
/**
* gsk_stroke_get_dash_offset:
* @self: a #GskStroke
*
* Returns the dash_offset of a #GskStroke.
*/
float
gsk_stroke_get_dash_offset (const GskStroke *self)
{
g_return_val_if_fail (self != NULL, 4.f);
return self->dash_offset;
}

87
gsk/gskstroke.h Normal file
View File

@@ -0,0 +1,87 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_STROKE_H__
#define __GSK_STROKE_H__
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gsktypes.h>
G_BEGIN_DECLS
#define GSK_TYPE_STROKE (gsk_stroke_get_type ())
GDK_AVAILABLE_IN_ALL
GType gsk_stroke_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskStroke * gsk_stroke_new (float line_width);
GDK_AVAILABLE_IN_ALL
GskStroke * gsk_stroke_copy (const GskStroke *other);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_free (GskStroke *self);
GDK_AVAILABLE_IN_ALL
gboolean gsk_stroke_equal (gconstpointer stroke1,
gconstpointer stroke2);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_width (GskStroke *self,
float line_width);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_line_width (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_cap (GskStroke *self,
GskLineCap line_cap);
GDK_AVAILABLE_IN_ALL
GskLineCap gsk_stroke_get_line_cap (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_line_join (GskStroke *self,
GskLineJoin line_join);
GDK_AVAILABLE_IN_ALL
GskLineJoin gsk_stroke_get_line_join (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_miter_limit (GskStroke *self,
float limit);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_miter_limit (const GskStroke *self);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_dash (GskStroke *self,
const float *dash,
gsize n_dash);
GDK_AVAILABLE_IN_ALL
const float * gsk_stroke_get_dash (const GskStroke *self,
gsize *n_dash);
GDK_AVAILABLE_IN_ALL
void gsk_stroke_set_dash_offset (GskStroke *self,
float offset);
GDK_AVAILABLE_IN_ALL
float gsk_stroke_get_dash_offset (const GskStroke *self);
G_END_DECLS
#endif /* __GSK_STROKE_H__ */

63
gsk/gskstrokeprivate.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GSK_STROKE_PRIVATE_H__
#define __GSK_STROKE_PRIVATE_H__
#include "gskstroke.h"
G_BEGIN_DECLS
struct _GskStroke
{
float line_width;
GskLineCap line_cap;
GskLineJoin line_join;
float miter_limit;
float *dash;
gsize n_dash;
float dash_length; /* sum of all dashes in the array */
float dash_offset;
};
static inline void
gsk_stroke_init_copy (GskStroke *stroke,
const GskStroke *other)
{
*stroke = *other;
stroke->dash = g_memdup (other->dash, stroke->n_dash * sizeof (float));
}
static inline void
gsk_stroke_clear (GskStroke *stroke)
{
g_clear_pointer (&stroke->dash, g_free);
stroke->n_dash = 0; /* better safe than sorry */
}
void gsk_stroke_to_cairo (const GskStroke *self,
cairo_t *cr);
G_END_DECLS
#endif /* __GSK_STROKE_PRIVATE_H__ */

View File

@@ -26,7 +26,11 @@
#include <gdk/gdk.h>
#include <gsk/gskenums.h>
typedef struct _GskPath GskPath;
typedef struct _GskPathBuilder GskPathBuilder;
typedef struct _GskPathMeasure GskPathMeasure;
typedef struct _GskRenderer GskRenderer;
typedef struct _GskStroke GskStroke;
typedef struct _GskTransform GskTransform;
#endif /* __GSK_TYPES_H__ */

View File

@@ -21,23 +21,31 @@ gsk_private_gl_shaders = [
]
gsk_public_sources = files([
'gskdiff.c',
'gskcairorenderer.c',
'gskdiff.c',
'gskglshader.c',
'gskpath.c',
'gskpathbuilder.c',
'gskpathdash.c',
'gskpathmeasure.c',
'gskrenderer.c',
'gskrendernode.c',
'gskrendernodeimpl.c',
'gskrendernodeparser.c',
'gskroundedrect.c',
'gskstroke.c',
'gsktransform.c',
'gl/gskglrenderer.c',
])
gsk_private_sources = files([
'gskcairoblur.c',
'gskcontour.c',
'gskcurve.c',
'gskdebug.c',
'gskprivate.c',
'gskprofiler.c',
'gskspline.c',
'gl/gskglshaderbuilder.c',
'gl/gskglprofiler.c',
'gl/gskglglyphcache.c',
@@ -54,9 +62,13 @@ gsk_public_headers = files([
'gskcairorenderer.h',
'gskenums.h',
'gskglshader.h',
'gskpath.h',
'gskpathbuilder.h',
'gskpathmeasure.h',
'gskrenderer.h',
'gskrendernode.h',
'gskroundedrect.h',
'gskstroke.h',
'gsktransform.h',
'gsktypes.h',
'gsk-autocleanup.h',

View File

@@ -260,6 +260,8 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self,
case GSK_RADIAL_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
case GSK_CONIC_GRADIENT_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
default:
FALLBACK ("Unsupported node '%s'", g_type_name_from_instance ((GTypeInstance *) node));

View File

@@ -639,4 +639,8 @@ gtk_media_file_extension_init (void)
g_type_name (g_io_extension_get_type (ext)));
}
}
/* IF the env var is given, check at startup that things actually work */
if (g_getenv ("GTK_MEDIA"))
gtk_media_file_get_extension ();
}

View File

@@ -1204,6 +1204,28 @@ get_border (GtkCssNode *node,
border->left = _gtk_css_number_value_get (style->border->border_left_width, 100);
}
static GskPath *
gtk_popover_get_tail_path (GtkPopover *popover)
{
GskPathBuilder *builder;
int initial_x, initial_y;
int tip_x, tip_y;
int final_x, final_y;
builder = gsk_path_builder_new ();
gtk_popover_get_gap_coords (popover,
&initial_x, &initial_y,
&tip_x, &tip_y,
&final_x, &final_y);
gsk_path_builder_move_to (builder, initial_x, initial_y);
gsk_path_builder_line_to (builder, tip_x, tip_y);
gsk_path_builder_line_to (builder, final_x, final_y);
return gsk_path_builder_free_to_path (builder);
}
static void
gtk_popover_apply_tail_path (GtkPopover *popover,
cairo_t *cr)
@@ -1397,22 +1419,14 @@ create_arrow_render_node (GtkPopover *popover)
GtkWidget *widget = GTK_WIDGET (popover);
GtkStyleContext *context;
GtkBorder border;
cairo_t *cr;
GtkSnapshot *snapshot;
GskPath *path;
snapshot = gtk_snapshot_new ();
cr = gtk_snapshot_append_cairo (snapshot,
&GRAPHENE_RECT_INIT (
0, 0,
gtk_widget_get_width (widget),
gtk_widget_get_height (widget)
));
/* Clip to the arrow shape */
cairo_save (cr);
gtk_popover_apply_tail_path (popover, cr);
cairo_clip (cr);
path = gtk_popover_get_tail_path (popover);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
get_border (priv->arrow_node, &border);
@@ -1420,29 +1434,34 @@ create_arrow_render_node (GtkPopover *popover)
gtk_style_context_save_to_node (context, priv->arrow_node);
/* Render the arrow background */
gtk_render_background (context, cr,
0, 0,
gtk_widget_get_width (widget),
gtk_widget_get_height (widget));
gtk_snapshot_render_background (snapshot, context,
0, 0,
gtk_widget_get_width (widget),
gtk_widget_get_height (widget));
/* Render the border of the arrow tip */
if (border.bottom > 0)
{
GtkCssStyle *style;
const GdkRGBA *border_color;
GskStroke *stroke;
graphene_rect_t bounds;
style = gtk_css_node_get_style (priv->arrow_node);
border_color = gtk_css_color_value_get_rgba (style->border->border_left_color ? style->border->border_left_color : style->core->color);
gtk_popover_apply_tail_path (popover, cr);
gdk_cairo_set_source_rgba (cr, border_color);
stroke = gsk_stroke_new (border.bottom + 1);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
cairo_set_line_width (cr, border.bottom + 1);
cairo_stroke (cr);
gsk_path_get_bounds (path, &bounds);
gtk_snapshot_append_color (snapshot, border_color, &bounds);
gtk_snapshot_pop (snapshot);
}
cairo_restore (cr);
cairo_destroy (cr);
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
gtk_style_context_restore (context);

View File

@@ -357,16 +357,16 @@ snapshot_frame_fill (GtkSnapshot *snapshot,
gtk_snapshot_append_border (snapshot, outline, border_width, colors);
}
static void
set_stroke_style (cairo_t *cr,
double line_width,
GtkBorderStyle style,
double length)
static GskStroke *
create_stroke_style (double line_width,
GtkBorderStyle style,
double length)
{
double segments[2];
GskStroke *stroke;
float segments[2];
double n;
cairo_set_line_width (cr, line_width);
stroke = gsk_stroke_new (line_width);
if (style == GTK_BORDER_STYLE_DOTTED)
{
@@ -374,12 +374,12 @@ set_stroke_style (cairo_t *cr,
segments[0] = 0;
segments[1] = n ? length / n : 2;
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
}
else
else if (style == GTK_BORDER_STYLE_DASHED)
{
n = length / line_width;
/* Optimize the common case of an integer-sized rectangle
@@ -397,32 +397,33 @@ set_stroke_style (cairo_t *cr,
segments[0] = n ? (1. / 3) * length / n : 1;
segments[1] = 2 * segments[0];
}
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_SQUARE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_MITER);
}
else
{
g_assert_not_reached ();
}
return stroke;
}
static void
render_frame_stroke (cairo_t *cr,
const GskRoundedRect *border_box,
const double border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *border_box,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
gboolean different_colors, different_borders;
GskRoundedRect stroke_box;
GskPathBuilder *builder;
GskPath *path;
GskStroke *stroke;
guint i;
different_colors = !gdk_rgba_equal (&colors[0], &colors[1]) ||
!gdk_rgba_equal (&colors[0], &colors[2]) ||
!gdk_rgba_equal (&colors[0], &colors[3]);
different_borders = border_width[0] != border_width[1] ||
border_width[0] != border_width[2] ||
border_width[0] != border_width[3] ;
stroke_box = *border_box;
gsk_rounded_rect_shrink (&stroke_box,
border_width[GTK_CSS_TOP] / 2.0,
@@ -430,32 +431,36 @@ render_frame_stroke (cairo_t *cr,
border_width[GTK_CSS_BOTTOM] / 2.0,
border_width[GTK_CSS_LEFT] / 2.0);
if (!different_colors && !different_borders && hidden_side == 0)
if (border_width[0] == border_width[1] &&
border_width[0] == border_width[2] &&
border_width[0] == border_width[3] &&
hidden_side == 0)
{
double length = 0;
/* FAST PATH:
* Mostly expected to trigger for focus rectangles */
for (i = 0; i < 4; i++)
for (i = 0; i < 4; i++)
{
length += _gtk_rounded_box_guess_length (&stroke_box, i);
}
gsk_rounded_rect_path (&stroke_box, cr);
gdk_cairo_set_source_rgba (cr, &colors[0]);
set_stroke_style (cr, border_width[0], stroke_style, length);
cairo_stroke (cr);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rounded_rect (builder, &stroke_box);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[0],
stroke_style, length);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
gtk_snapshot_pop (snapshot);
}
else
{
GskRoundedRect padding_box;
padding_box = *border_box;
gsk_rounded_rect_shrink (&padding_box,
border_width[GTK_CSS_TOP],
border_width[GTK_CSS_RIGHT],
border_width[GTK_CSS_BOTTOM],
border_width[GTK_CSS_LEFT]);
const float weight = sqrtf(2)/2.0;
for (i = 0; i < 4; i++)
{
@@ -465,49 +470,111 @@ render_frame_stroke (cairo_t *cr,
if (border_width[i] == 0)
continue;
cairo_save (cr);
builder = gsk_path_builder_new ();
if (i == 0)
_gtk_rounded_box_path_top (border_box, &padding_box, cr);
{
/* top */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width,
stroke_box.bounds.origin.y);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2,
weight);
}
else if (i == 1)
_gtk_rounded_box_path_right (border_box, &padding_box, cr);
{
/* right */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2,
weight);
}
else if (i == 2)
_gtk_rounded_box_path_bottom (border_box, &padding_box, cr);
{
/* bottom */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2,
weight);
}
else if (i == 3)
_gtk_rounded_box_path_left (border_box, &padding_box, cr);
cairo_clip (cr);
{
/* left */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
}
_gtk_rounded_box_path_side (&stroke_box, cr, i);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gdk_cairo_set_source_rgba (cr, &colors[i]);
set_stroke_style (cr,
border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
cairo_stroke (cr);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
cairo_restore (cr);
gtk_snapshot_pop (snapshot);
}
}
}
static void
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
cairo_t *cr;
cr = gtk_snapshot_append_cairo (snapshot,
&outline->bounds);
render_frame_stroke (cr, outline, double_width, colors, hidden_side, stroke_style);
cairo_destroy (cr);
}
static void
color_shade (const GdkRGBA *color,
double factor,

View File

@@ -31,6 +31,7 @@
#include "gsktransformprivate.h"
#include "gsk/gskrendernodeprivate.h"
#include "gsk/gskstrokeprivate.h"
#include "gtk/gskpango.h"
@@ -108,6 +109,14 @@ struct _GtkSnapshotState {
struct {
GskRoundedRect bounds;
} rounded_clip;
struct {
GskPath *path;
GskFillRule fill_rule;
} fill;
struct {
GskPath *path;
GskStroke stroke;
} stroke;
struct {
gsize n_shadows;
GskShadow *shadows;
@@ -1102,6 +1111,135 @@ gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
gtk_rounded_rect_scale_affine (&state->data.rounded_clip.bounds, bounds, scale_x, scale_y, dx, dy);
}
static GskRenderNode *
gtk_snapshot_collect_fill (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *fill_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
fill_node = gsk_fill_node_new (node,
state->data.fill.path,
state->data.fill.fill_rule);
if (fill_node->bounds.size.width == 0 ||
fill_node->bounds.size.height == 0)
{
gsk_render_node_unref (node);
gsk_render_node_unref (fill_node);
return NULL;
}
gsk_render_node_unref (node);
return fill_node;
}
static void
gtk_snapshot_clear_fill (GtkSnapshotState *state)
{
gsk_path_unref (state->data.fill.path);
}
/**
* gtk_snapshot_push_fill:
* @snapshot: a #GtkSnapshot
* @path: The path describing the area to fill
* @fill_rule: The fill rule to use
*
* Fills the area given by @path and @fill_rule with an image and discards everything
* outside of it.
*
* The image is recorded until the next call to gtk_snapshot_pop().
*/
void
gtk_snapshot_push_fill (GtkSnapshot *snapshot,
GskPath *path,
GskFillRule fill_rule)
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_fill,
gtk_snapshot_clear_fill);
state->data.fill.path = gsk_path_ref (path);
state->data.fill.fill_rule = fill_rule;
}
static GskRenderNode *
gtk_snapshot_collect_stroke (GtkSnapshot *snapshot,
GtkSnapshotState *state,
GskRenderNode **nodes,
guint n_nodes)
{
GskRenderNode *node, *stroke_node;
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
if (node == NULL)
return NULL;
stroke_node = gsk_stroke_node_new (node,
state->data.stroke.path,
&state->data.stroke.stroke);
if (stroke_node->bounds.size.width == 0 ||
stroke_node->bounds.size.height == 0)
{
gsk_render_node_unref (node);
gsk_render_node_unref (stroke_node);
return NULL;
}
gsk_render_node_unref (node);
return stroke_node;
}
static void
gtk_snapshot_clear_stroke (GtkSnapshotState *state)
{
gsk_path_unref (state->data.stroke.path);
gsk_stroke_clear (&state->data.stroke.stroke);
}
/**
* gtk_snapshot_push_stroke:
* @snapshot: a #GtkSnapshot
* @path: The path to stroke
* @stroke: The stroke attributes
*
* Strokes the given @path with the attributes given by @stroke and the
* image being recorded until the next call to gtk_snapshot_pop().
*/
void
gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
GskPath *path,
const GskStroke *stroke)
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,
gtk_snapshot_get_current_state (snapshot)->transform,
gtk_snapshot_collect_stroke,
gtk_snapshot_clear_stroke);
state->data.stroke.path = gsk_path_ref (path);
gsk_stroke_init_copy (&state->data.stroke.stroke, stroke);
}
static GskRenderNode *
gtk_snapshot_collect_shadow (GtkSnapshot *snapshot,
GtkSnapshotState *state,

View File

@@ -89,6 +89,14 @@ GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
const GskRoundedRect *bounds);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_fill (GtkSnapshot *snapshot,
GskPath *path,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
GskPath *path,
const GskStroke *stroke);
GDK_AVAILABLE_IN_ALL
void gtk_snapshot_push_shadow (GtkSnapshot *snapshot,
const GskShadow *shadow,
gsize n_shadows);

View File

@@ -161,6 +161,12 @@ create_list_model_for_render_node (GskRenderNode *node)
case GSK_ROUNDED_CLIP_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_rounded_clip_node_get_child (node) }, 1);
case GSK_FILL_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_fill_node_get_child (node) }, 1);
case GSK_STROKE_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_stroke_node_get_child (node) }, 1);
case GSK_SHADOW_NODE:
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_shadow_node_get_child (node) }, 1);
@@ -282,6 +288,10 @@ node_type_name (GskRenderNodeType type)
return "Clip";
case GSK_ROUNDED_CLIP_NODE:
return "Rounded Clip";
case GSK_FILL_NODE:
return "Fill";
case GSK_STROKE_NODE:
return "Stroke";
case GSK_SHADOW_NODE:
return "Shadow";
case GSK_BLEND_NODE:
@@ -321,6 +331,8 @@ node_name (GskRenderNode *node)
case GSK_REPEAT_NODE:
case GSK_CLIP_NODE:
case GSK_ROUNDED_CLIP_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
case GSK_SHADOW_NODE:
case GSK_BLEND_NODE:
case GSK_CROSS_FADE_NODE:
@@ -575,6 +587,20 @@ add_float_row (GtkListStore *store,
g_free (text);
}
static const char *
enum_to_nick (GType type,
int value)
{
GEnumClass *class;
GEnumValue *v;
class = g_type_class_ref (type);
v = g_enum_get_value (class, value);
g_type_class_unref (class);
return v->value_nick;
}
static void
populate_render_node_properties (GtkListStore *store,
GskRenderNode *node)
@@ -837,9 +863,7 @@ populate_render_node_properties (GtkListStore *store,
case GSK_BLEND_NODE:
{
GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
tmp = g_enum_to_string (GSK_TYPE_BLEND_MODE, mode);
add_text_row (store, "Blendmode", tmp);
g_free (tmp);
add_text_row (store, "Blendmode", enum_to_nick (GSK_TYPE_BLEND_MODE, mode));
}
break;
@@ -1072,6 +1096,39 @@ populate_render_node_properties (GtkListStore *store,
}
break;
case GSK_FILL_NODE:
{
GskPath *path = gsk_fill_node_get_path (node);
GskFillRule fill_rule = gsk_fill_node_get_fill_rule (node);
tmp = gsk_path_to_string (path);
add_text_row (store, "Path", tmp);
g_free (tmp);
add_text_row (store, "Fill rule", enum_to_nick (GSK_TYPE_FILL_RULE, fill_rule));
}
break;
case GSK_STROKE_NODE:
{
GskPath *path = gsk_stroke_node_get_path (node);
const GskStroke *stroke = gsk_stroke_node_get_stroke (node);
GskLineCap line_cap = gsk_stroke_get_line_cap (stroke);
GskLineJoin line_join = gsk_stroke_get_line_join (stroke);
tmp = gsk_path_to_string (path);
add_text_row (store, "Path", tmp);
g_free (tmp);
tmp = g_strdup_printf ("%.2f", gsk_stroke_get_line_width (stroke));
add_text_row (store, "Line width", tmp);
g_free (tmp);
add_text_row (store, "Line cap", enum_to_nick (GSK_TYPE_LINE_CAP, line_cap));
add_text_row (store, "Line join", enum_to_nick (GSK_TYPE_LINE_JOIN, line_join));
}
break;
case GSK_CONTAINER_NODE:
tmp = g_strdup_printf ("%d", gsk_container_node_get_n_children (node));
add_text_row (store, "Children", tmp);

View File

@@ -1102,14 +1102,24 @@ darwin_versions = [
]
# Library
libgtk_static = static_library('gtk',
sources: [typefuncs, gtk_sources, gtkmarshal_h, gtkprivatetypebuiltins_h],
dependencies: gtk_deps,
include_directories: [confinc, gdkinc, gskinc, gtkinc],
c_args: gtk_cargs + common_cflags,
link_with: [libgtk_css, libgdk, libgsk, libottie],
)
libgtk_static_dep = declare_dependency(include_directories: [ confinc, ],
dependencies: gtk_deps)
libgtk = library('gtk-4',
soversion: gtk_soversion,
version: gtk_library_version,
sources: [typefuncs, gtk_sources, gtkmarshal_h, gtkprivatetypebuiltins_h],
c_args: gtk_cargs + common_cflags,
include_directories: [confinc, gdkinc, gskinc, gtkinc],
dependencies: gtk_deps + [libgtk_css_dep, libgdk_dep, libgsk_dep],
link_whole: [libgtk_css, libgdk, libgsk, ],
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,

View File

@@ -679,7 +679,10 @@ endif
subdir('gtk/css')
subdir('gdk')
subdir('gsk')
subdir('ottie')
subdir('gtk')
subdir('ottie/tools')
subdir('ottie/editor')
subdir('modules')
if get_option('demos')
subdir('demos')

28
ottie/editor/main.c Normal file
View File

@@ -0,0 +1,28 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include <ottie-editor-application.h>
int
main (int argc, char *argv[])
{
return g_application_run (G_APPLICATION (ottie_editor_application_new ()), argc, argv);
}

31
ottie/editor/meson.build Normal file
View File

@@ -0,0 +1,31 @@
ottie_editor_sources = [
'main.c',
'ottie-editor-application.c',
'ottie-editor-window.c',
]
ottie_editor_resources = gnome.compile_resources('ottie_editor_resources',
'ottie-editor.gresource.xml',
source_dir: '.',
)
executable('ottie-editor',
sources: [ottie_editor_sources, ottie_editor_resources],
dependencies: [libgtk_css_dep, libgdk_dep, libgsk_dep, libgtk_static_dep, libottie_dep],
include_directories: confinc,
c_args: [
'-DGTK_COMPILATION',
'-DG_LOG_DOMAIN="OttieEditor"',
] + common_cflags,
gui_app: true,
link_with: [libgtk_static, libgtk_css, libgdk, libgsk, libottie],
link_args: common_ldflags,
install: false,
)
# icons, don't install them until we decide to install ottie-editor
#icontheme_dir = join_paths(gtk_datadir, 'icons/hicolor')
#foreach size: ['scalable', 'symbolic']
# install_subdir('data/' + size, install_dir: icontheme_dir)
#endforeach

View File

@@ -0,0 +1,215 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottie-editor-application.h"
#include "ottie-editor-window.h"
struct _OttieEditorApplication
{
GtkApplication parent;
};
struct _OttieEditorApplicationClass
{
GtkApplicationClass parent_class;
};
G_DEFINE_TYPE(OttieEditorApplication, ottie_editor_application, GTK_TYPE_APPLICATION);
static void
ottie_editor_application_init (OttieEditorApplication *app)
{
}
static void
activate_about (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
GtkApplication *app = user_data;
char *version;
GString *s;
char *os_name;
char *os_version;
GtkWidget *dialog;
os_name = g_get_os_info (G_OS_INFO_KEY_NAME);
os_version = g_get_os_info (G_OS_INFO_KEY_VERSION_ID);
s = g_string_new ("");
if (os_name && os_version)
g_string_append_printf (s, "OS\t%s %s\n\n", os_name, os_version);
g_string_append (s, "System libraries\n");
g_string_append_printf (s, "\tGLib\t%d.%d.%d\n",
glib_major_version,
glib_minor_version,
glib_micro_version);
g_string_append_printf (s, "\tPango\t%s\n",
pango_version_string ());
g_string_append_printf (s, "\tGTK\t%d.%d.%d\n",
gtk_get_major_version (),
gtk_get_minor_version (),
gtk_get_micro_version ());
version = g_strdup_printf ("%s\nRunning against GTK %d.%d.%d",
PACKAGE_VERSION,
gtk_get_major_version (),
gtk_get_minor_version (),
gtk_get_micro_version ());
dialog = g_object_new (GTK_TYPE_ABOUT_DIALOG,
"transient-for", gtk_application_get_active_window (app),
"program-name", "GTK Node Editor",
"version", version,
"copyright", "©2019—2020 The GTK Team",
"license-type", GTK_LICENSE_LGPL_2_1,
"website", "http://www.gtk.org",
"comments", "Program to test GTK rendering",
"authors", (const char *[]){ "Benjamin Otte", "Timm Bäder", NULL},
"logo-icon-name", "org.gtk.gtk4.OttieEditor.Devel",
"title", "About GTK Node Editor",
"system-information", s->str,
NULL);
gtk_about_dialog_add_credit_section (GTK_ABOUT_DIALOG (dialog),
"Artwork by", (const char *[]) { "Jakub Steiner", NULL });
gtk_window_present (GTK_WINDOW (dialog));
g_string_free (s, TRUE);
g_free (version);
g_free (os_name);
g_free (os_version);
}
static void
activate_quit (GSimpleAction *action,
GVariant *parameter,
gpointer data)
{
g_application_quit (G_APPLICATION (data));
}
static void
activate_inspector (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
gtk_window_set_interactive_debugging (TRUE);
}
static void
activate_help (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
GtkBuilder *builder;
GtkWidget *window;
GtkTextBuffer *buffer;
GBytes *bytes;
const char *text;
gsize len;
builder = gtk_builder_new ();
gtk_builder_add_from_resource (builder, "/org/gtk/gtk4/node-editor/help-window.ui", NULL);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
buffer = GTK_TEXT_BUFFER (gtk_builder_get_object (builder, "buffer"));
bytes = g_resources_lookup_data ("/org/gtk/gtk4/node-editor/node-format.md",
G_RESOURCE_LOOKUP_FLAGS_NONE,
NULL);
text = g_bytes_get_data (bytes, &len);
gtk_text_buffer_set_text (buffer, text, len);
g_bytes_unref (bytes);
gtk_window_present (GTK_WINDOW (window));
g_object_unref (builder);
}
static GActionEntry app_entries[] =
{
{ "about", activate_about, NULL, NULL, NULL },
{ "quit", activate_quit, NULL, NULL, NULL },
{ "inspector", activate_inspector, NULL, NULL, NULL },
{ "help", activate_help, NULL, NULL, NULL },
};
static void
ottie_editor_application_startup (GApplication *app)
{
const char *help_accels[2] = { "F1", NULL };
const char *quit_accels[2] = { "<Ctrl>Q", NULL };
const char *open_accels[2] = { "<Ctrl>O", NULL };
G_APPLICATION_CLASS (ottie_editor_application_parent_class)->startup (app);
g_action_map_add_action_entries (G_ACTION_MAP (app),
app_entries, G_N_ELEMENTS (app_entries),
app);
gtk_application_set_accels_for_action (GTK_APPLICATION (app), "app.help", help_accels);
gtk_application_set_accels_for_action (GTK_APPLICATION (app), "app.quit", quit_accels);
gtk_application_set_accels_for_action (GTK_APPLICATION (app), "win.open", open_accels);
}
static void
ottie_editor_application_activate (GApplication *app)
{
OttieEditorWindow *win;
win = ottie_editor_window_new (OTTIE_EDITOR_APPLICATION (app));
gtk_window_present (GTK_WINDOW (win));
}
static void
ottie_editor_application_open (GApplication *app,
GFile **files,
int n_files,
const char *hint)
{
OttieEditorWindow *win;
int i;
for (i = 0; i < n_files; i++)
{
win = ottie_editor_window_new (OTTIE_EDITOR_APPLICATION (app));
ottie_editor_window_load (win, files[i]);
gtk_window_present (GTK_WINDOW (win));
}
}
static void
ottie_editor_application_class_init (OttieEditorApplicationClass *class)
{
GApplicationClass *application_class = G_APPLICATION_CLASS (class);
application_class->startup = ottie_editor_application_startup;
application_class->activate = ottie_editor_application_activate;
application_class->open = ottie_editor_application_open;
}
OttieEditorApplication *
ottie_editor_application_new (void)
{
return g_object_new (OTTIE_EDITOR_APPLICATION_TYPE,
"application-id", "org.gtk.gtk4.OttieEditor",
"flags", G_APPLICATION_HANDLES_OPEN,
NULL);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_EDITOR_APPLICATION_H__
#define __OTTIE_EDITOR_APPLICATION_H__
#include <gtk/gtk.h>
#define OTTIE_EDITOR_APPLICATION_TYPE (ottie_editor_application_get_type ())
#define OTTIE_EDITOR_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), OTTIE_EDITOR_APPLICATION_TYPE, OttieEditorApplication))
typedef struct _OttieEditorApplication OttieEditorApplication;
typedef struct _OttieEditorApplicationClass OttieEditorApplicationClass;
GType ottie_editor_application_get_type (void);
OttieEditorApplication *ottie_editor_application_new (void);
#endif /* __OTTIE_EDITOR_APPLICATION_H__ */

View File

@@ -0,0 +1,431 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottie-editor-window.h"
#include "ottie/ottiecreationprivate.h"
#include "ottie/ottiecompositionlayerprivate.h"
#include "ottie/ottiegroupshapeprivate.h"
#include "ottie/ottieshapelayerprivate.h"
#include "ottie/ottiepaintable.h"
struct _OttieEditorWindow
{
GtkApplicationWindow parent;
GFileMonitor *file_monitor;
OttieCreation *creation;
OttiePaintable *paintable;
GtkWidget *picture;
GtkWidget *listview;
};
struct _OttieEditorWindowClass
{
GtkApplicationWindowClass parent_class;
};
G_DEFINE_TYPE(OttieEditorWindow, ottie_editor_window, GTK_TYPE_APPLICATION_WINDOW);
static gboolean
load_file_contents (OttieEditorWindow *self,
GFile *file)
{
GBytes *bytes;
bytes = g_file_load_bytes (file, NULL, NULL, NULL);
if (bytes == NULL)
return FALSE;
if (!g_utf8_validate (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL))
{
g_bytes_unref (bytes);
return FALSE;
}
ottie_creation_load_bytes (self->creation, bytes);
#if 0
gtk_text_buffer_set_text (self->text_buffer,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes));
#endif
g_bytes_unref (bytes);
return TRUE;
}
static void
file_changed_cb (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
gpointer user_data)
{
OttieEditorWindow *self = user_data;
if (event_type == G_FILE_MONITOR_EVENT_CHANGED)
load_file_contents (self, file);
}
void
ottie_editor_window_load (OttieEditorWindow *self,
GFile *file)
{
GError *error = NULL;
if (!load_file_contents (self, file))
return;
g_clear_object (&self->file_monitor);
self->file_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
if (error)
{
g_warning ("couldn't monitor file: %s", error->message);
g_error_free (error);
}
else
{
g_signal_connect (self->file_monitor, "changed", G_CALLBACK (file_changed_cb), self);
}
}
static void
open_response_cb (GtkWidget *dialog,
int response,
OttieEditorWindow *self)
{
gtk_widget_hide (dialog);
if (response == GTK_RESPONSE_ACCEPT)
{
GFile *file;
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
ottie_editor_window_load (self, file);
g_object_unref (file);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
static void
show_open_filechooser (OttieEditorWindow *self)
{
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Open lottie file",
GTK_WINDOW (self),
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Load", GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
GFile *cwd = g_file_new_for_path (".");
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd, NULL);
g_object_unref (cwd);
g_signal_connect (dialog, "response", G_CALLBACK (open_response_cb), self);
gtk_widget_show (dialog);
}
static void
open_cb (GtkWidget *button,
OttieEditorWindow *self)
{
show_open_filechooser (self);
}
static void
save_response_cb (GtkWidget *dialog,
int response,
OttieEditorWindow *self)
{
gtk_widget_hide (dialog);
if (response == GTK_RESPONSE_ACCEPT)
{
#if 0
GFile *file;
char *text;
#endif
GError *error = NULL;
#if 0
text = get_current_text (self->text_buffer);
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
g_file_replace_contents (file, text, strlen (text),
NULL, FALSE,
G_FILE_CREATE_NONE,
NULL,
NULL,
&error);
#endif
if (error != NULL)
{
GtkWidget *message_dialog;
message_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_INFO,
GTK_BUTTONS_OK,
"Saving failed");
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message_dialog),
"%s", error->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (error);
}
#if 0
g_free (text);
g_object_unref (file);
#endif
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
static void
save_cb (GtkWidget *button,
OttieEditorWindow *self)
{
GtkWidget *dialog;
dialog = gtk_file_chooser_dialog_new ("Save file",
GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))),
GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
GFile *cwd = g_file_new_for_path (".");
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), cwd, NULL);
g_object_unref (cwd);
g_signal_connect (dialog, "response", G_CALLBACK (save_response_cb), self);
gtk_widget_show (dialog);
}
static GdkTexture *
create_texture (OttieEditorWindow *self)
{
GtkSnapshot *snapshot;
GskRenderer *renderer;
GskRenderNode *node;
GdkTexture *texture;
int width, height;
width = gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (self->paintable));
height = gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (self->paintable));
if (width <= 0 || height <= 0)
return NULL;
snapshot = gtk_snapshot_new ();
gdk_paintable_snapshot (GDK_PAINTABLE (self->paintable), snapshot, width, height);
node = gtk_snapshot_free_to_node (snapshot);
if (node == NULL)
return NULL;
renderer = gtk_native_get_renderer (gtk_widget_get_native (GTK_WIDGET (self)));
texture = gsk_renderer_render_texture (renderer, node, NULL);
gsk_render_node_unref (node);
return texture;
}
static void
export_image_response_cb (GtkWidget *dialog,
int response,
GdkTexture *texture)
{
gtk_widget_hide (dialog);
if (response == GTK_RESPONSE_ACCEPT)
{
GFile *file;
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
if (!gdk_texture_save_to_png (texture, g_file_peek_path (file)))
{
GtkWidget *message_dialog;
message_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_window_get_transient_for (GTK_WINDOW (dialog))),
GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_INFO,
GTK_BUTTONS_OK,
"Exporting to image failed");
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
}
g_object_unref (file);
}
gtk_window_destroy (GTK_WINDOW (dialog));
g_object_unref (texture);
}
static void
export_image_cb (GtkWidget *button,
OttieEditorWindow *self)
{
GdkTexture *texture;
GtkWidget *dialog;
texture = create_texture (self);
if (texture == NULL)
return;
dialog = gtk_file_chooser_dialog_new ("",
GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))),
GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Save", GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
g_signal_connect (dialog, "response", G_CALLBACK (export_image_response_cb), texture);
gtk_widget_show (dialog);
}
static GListModel *
create_object_children (gpointer item,
gpointer user_data)
{
if (OTTIE_IS_COMPOSITION_LAYER (item))
{
return G_LIST_MODEL (g_object_ref (ottie_composition_layer_get_composition (item)));
}
else if (OTTIE_IS_SHAPE_LAYER (item))
{
return G_LIST_MODEL (g_object_ref (ottie_shape_layer_get_shape (item)));
}
else if (OTTIE_IS_GROUP_SHAPE (item))
{
return g_object_ref (item);
}
else
{
return NULL;
}
}
static void
notify_prepared_cb (OttieCreation *creation,
GParamSpec *pspec,
OttieEditorWindow *self)
{
GtkTreeListModel *treemodel;
GtkSingleSelection *selection;
if (ottie_creation_is_prepared (creation))
{
treemodel = gtk_tree_list_model_new (G_LIST_MODEL (ottie_creation_get_composition (self->creation)),
FALSE,
TRUE,
create_object_children,
NULL,
NULL);
selection = gtk_single_selection_new (G_LIST_MODEL (treemodel));
gtk_list_view_set_model (GTK_LIST_VIEW (self->listview), GTK_SELECTION_MODEL (selection));
g_object_unref (selection);
}
else
{
gtk_list_view_set_model (GTK_LIST_VIEW (self->listview), NULL);
}
}
static void
ottie_editor_window_finalize (GObject *object)
{
//OttieEditorWindow *self = OTTIE_EDITOR_WINDOW (object);
G_OBJECT_CLASS (ottie_editor_window_parent_class)->finalize (object);
}
static void
ottie_editor_window_class_init (OttieEditorWindowClass *class)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = ottie_editor_window_finalize;
g_type_ensure (OTTIE_TYPE_CREATION);
g_type_ensure (OTTIE_TYPE_PAINTABLE);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gtk/gtk4/ottie-editor/ottie-editor-window.ui");
gtk_widget_class_bind_template_child (widget_class, OttieEditorWindow, creation);
gtk_widget_class_bind_template_child (widget_class, OttieEditorWindow, paintable);
gtk_widget_class_bind_template_child (widget_class, OttieEditorWindow, picture);
gtk_widget_class_bind_template_child (widget_class, OttieEditorWindow, listview);
gtk_widget_class_bind_template_callback (widget_class, open_cb);
gtk_widget_class_bind_template_callback (widget_class, save_cb);
gtk_widget_class_bind_template_callback (widget_class, export_image_cb);
gtk_widget_class_bind_template_callback (widget_class, notify_prepared_cb);
}
static void
window_open (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
OttieEditorWindow *self = user_data;
show_open_filechooser (self);
}
static GActionEntry win_entries[] = {
{ "open", window_open, NULL, NULL, NULL },
};
static void
ottie_editor_window_init (OttieEditorWindow *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
g_action_map_add_action_entries (G_ACTION_MAP (self), win_entries, G_N_ELEMENTS (win_entries), self);
}
OttieEditorWindow *
ottie_editor_window_new (OttieEditorApplication *application)
{
return g_object_new (OTTIE_EDITOR_WINDOW_TYPE,
"application", application,
NULL);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_EDITOR_WINDOW_H__
#define __OTTIE_EDITOR_WINDOW_H__
#include <gtk/gtk.h>
#include "ottie-editor-application.h"
#define OTTIE_EDITOR_WINDOW_TYPE (ottie_editor_window_get_type ())
#define OTTIE_EDITOR_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), OTTIE_EDITOR_WINDOW_TYPE, OttieEditorWindow))
typedef struct _OttieEditorWindow OttieEditorWindow;
typedef struct _OttieEditorWindowClass OttieEditorWindowClass;
GType ottie_editor_window_get_type (void);
OttieEditorWindow * ottie_editor_window_new (OttieEditorApplication *application);
void ottie_editor_window_load (OttieEditorWindow *self,
GFile *file);
#endif /* __OTTIE_EDITOR_WINDOW_H__ */

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="gear_menu">
<section>
<item>
<attribute name="label" translatable="yes">_Help</attribute>
<attribute name="action">app.help</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Inspector</attribute>
<attribute name="action">app.inspector</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_About Ottie Editor</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
<object class="OttieCreation" id="creation">
<signal name="notify::prepared" handler="notify_prepared_cb"/>
</object>
<object class="OttiePaintable" id="paintable">
<property name="creation">creation</property>
</object>
<template class="OttieEditorWindow" parent="GtkApplicationWindow">
<property name="title" translatable="yes">Ottie Editor</property>
<property name="default-width">1024</property>
<property name="default-height">768</property>
<child type="titlebar">
<object class="GtkHeaderBar" id="header">
<child type="start">
<object class="GtkButton">
<property name="icon-name">document-open-symbolic</property>
<property name="tooltip-text">Open file</property>
<signal name="clicked" handler="open_cb"/>
</object>
</child>
<child type="start">
<object class="GtkButton">
<property name="icon-name">document-save-symbolic</property>
<property name="tooltip-text">Save</property>
<signal name="clicked" handler="save_cb"/>
</object>
</child>
<child type="start">
<object class="GtkButton">
<property name="icon-name">insert-image-symbolic</property>
<property name="tooltip-text">Export to image</property>
<signal name="clicked" handler="export_image_cb"/>
</object>
</child>
<child type="end">
<object class="GtkMenuButton" id="gear_menu_button">
<property name="valign">center</property>
<property name="menu-model">gear_menu</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkPaned">
<property name="shrink-end-child">false</property>
<property name="position">400</property>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkListView" id="listview">
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkTreeExpander" id="expander">
<binding name="list-row">
<lookup name="item">GtkListItem</lookup>
</binding>
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="OttieObject">
<lookup name="item">expander</lookup>
</lookup>
</binding>
</object>
</property>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="min-content-height">100</property>
<property name="min-content-width">100</property>
<child>
<object class="GtkViewport">
<child>
<object class="GtkPicture" id="picture">
<property name="can-shrink">0</property>
<property name="paintable">paintable</property>
<property name="halign">center</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gtk/gtk4/ottie-editor">
<file preprocess="xml-stripblanks">ottie-editor-window.ui</file>
</gresource>
</gresources>

71
ottie/meson.build Normal file
View File

@@ -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)

31
ottie/ottie.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_H__
#define __OTTIE_H__
#define __OTTIE_H_INSIDE__
#include <ottie/ottiecreation.h>
#include <ottie/ottiepaintable.h>
#include <ottie/ottieplayer.h>
#undef __OTTIE_H_INSIDE__
#endif /* __OTTIE_H__ */

149
ottie/ottiecolorvalue.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiecolorvalueprivate.h"
#include "ottieparserprivate.h"
#include <glib/gi18n-lib.h>
static gboolean
ottie_color_value_parse_one (JsonReader *reader,
gsize offset,
gpointer data)
{
GdkRGBA *rgba = (GdkRGBA *) ((guint8 *) data + offset);
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;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_COLOR_VALUE_PRIVATE_H__
#define __OTTIE_COLOR_VALUE_PRIVATE_H__
#include <json-glib/json-glib.h>
#include <gdk/gdk.h>
G_BEGIN_DECLS
typedef struct _OttieColorValue OttieColorValue;
struct _OttieColorValue
{
gboolean is_static;
union {
GdkRGBA static_value;
gpointer keyframes;
};
};
void ottie_color_value_init (OttieColorValue *self,
const GdkRGBA *rgba);
void ottie_color_value_clear (OttieColorValue *self);
void ottie_color_value_get (OttieColorValue *self,
double timestamp,
GdkRGBA *rgba);
gboolean ottie_color_value_parse (JsonReader *reader,
gsize offset,
gpointer data);
G_END_DECLS
#endif /* __OTTIE_COLOR_VALUE_PRIVATE_H__ */

268
ottie/ottiecomposition.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiecompositionprivate.h"
#include "ottieparserprivate.h"
#include "ottiecompositionlayerprivate.h"
#include "ottienulllayerprivate.h"
#include "ottieshapelayerprivate.h"
#include <glib/gi18n-lib.h>
#include <gsk/gsk.h>
#define GDK_ARRAY_ELEMENT_TYPE OttieLayer *
#define GDK_ARRAY_FREE_FUNC g_object_unref
#define GDK_ARRAY_TYPE_NAME OttieLayerList
#define GDK_ARRAY_NAME ottie_layer_list
#define GDK_ARRAY_PREALLOC 4
#include "gdk/gdkarrayimpl.c"
struct _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;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiecompositionlayerprivate.h"
#include "ottiedoublevalueprivate.h"
#include <glib/gi18n-lib.h>
#include <gsk/gsk.h>
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;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_COMPOSITION_LAYER_PRIVATE_H__
#define __OTTIE_COMPOSITION_LAYER_PRIVATE_H__
#include "ottielayerprivate.h"
#include "ottiecompositionprivate.h"
#include <json-glib/json-glib.h>
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__ */

View File

@@ -0,0 +1,47 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_COMPOSITION_PRIVATE_H__
#define __OTTIE_COMPOSITION_PRIVATE_H__
#include "ottielayerprivate.h"
#include <json-glib/json-glib.h>
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__ */

746
ottie/ottiecreation.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiecreationprivate.h"
#include "ottielayerprivate.h"
#include "ottieparserprivate.h"
#include "ottiecompositionprivate.h"
#include <glib/gi18n-lib.h>
#include <json-glib/json-glib.h>
struct _OttieCreation
{
GObject parent;
char *name;
double frame_rate;
double start_frame;
double end_frame;
double width;
double height;
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;
}

83
ottie/ottiecreation.h Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_CREATION_H__
#define __OTTIE_CREATION_H__
#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <ottie/ottie.h> can be included directly."
#endif
#include <gdk/gdk.h>
G_BEGIN_DECLS
#define OTTIE_TYPE_CREATION (ottie_creation_get_type ())
#define OTTIE_CREATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_CREATION, OttieCreation))
#define OTTIE_CREATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_CREATION, OttieCreationClass))
#define OTTIE_IS_CREATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_CREATION))
#define OTTIE_IS_CREATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_CREATION))
#define OTTIE_CREATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_CREATION, OttieCreationClass))
typedef struct _OttieCreation OttieCreation;
typedef struct _OttieCreationClass OttieCreationClass;
GDK_AVAILABLE_IN_ALL
GType ottie_creation_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
OttieCreation * ottie_creation_new (void);
GDK_AVAILABLE_IN_ALL
OttieCreation * ottie_creation_new_for_file (GFile *file);
GDK_AVAILABLE_IN_ALL
OttieCreation * ottie_creation_new_for_filename (const char *filename);
GDK_AVAILABLE_IN_ALL
void ottie_creation_load_file (OttieCreation *self,
GFile *file);
GDK_AVAILABLE_IN_ALL
void ottie_creation_load_filename (OttieCreation *self,
const char *filename);
GDK_AVAILABLE_IN_ALL
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__ */

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_CREATION_PRIVATE_H__
#define __OTTIE_CREATION_PRIVATE_H__
#include "ottiecreation.h"
#include "ottiecompositionprivate.h"
#include <gtk/gtk.h>
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__ */

121
ottie/ottiedoublevalue.c Normal file
View File

@@ -0,0 +1,121 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiedoublevalueprivate.h"
#include "ottieparserprivate.h"
#include <glib/gi18n-lib.h>
static gboolean
ottie_double_value_parse_value (JsonReader *reader,
gsize offset,
gpointer data)
{
gboolean result, array;
/* Lottie being Lottie, single values may get dumped into arrays. */
array = json_reader_is_array (reader);
if (array)
json_reader_read_element (reader, 0);
result = ottie_parser_option_double (reader, offset, data);
if (array)
json_reader_end_element (reader);
return result;
}
static double
ottie_double_value_interpolate (double start,
double end,
double progress)
{
return start + (end - start) * progress;
}
#define OTTIE_KEYFRAMES_NAME ottie_double_keyframes
#define OTTIE_KEYFRAMES_TYPE_NAME OttieDoubleKeyframes
#define OTTIE_KEYFRAMES_ELEMENT_TYPE double
#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_double_value_parse_value
#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_double_value_interpolate
#include "ottiekeyframesimpl.c"
void
ottie_double_value_init (OttieDoubleValue *self,
double value)
{
self->is_static = TRUE;
self->static_value = value;
}
void
ottie_double_value_clear (OttieDoubleValue *self)
{
if (!self->is_static)
g_clear_pointer (&self->keyframes, ottie_double_keyframes_free);
}
double
ottie_double_value_get (OttieDoubleValue *self,
double timestamp)
{
if (self->is_static)
return self->static_value;
return ottie_double_keyframes_get (self->keyframes, timestamp);
}
gboolean
ottie_double_value_parse (JsonReader *reader,
gsize offset,
gpointer data)
{
OttieDoubleValue *self = (OttieDoubleValue *) ((guint8 *) data + GPOINTER_TO_SIZE (offset));
if (json_reader_read_member (reader, "k"))
{
if (!json_reader_is_array (reader))
{
self->is_static = TRUE;
self->static_value = json_reader_get_double_value (reader);
}
else
{
self->is_static = FALSE;
self->keyframes = ottie_double_keyframes_parse (reader);
if (self->keyframes == NULL)
{
json_reader_end_member (reader);
return FALSE;
}
}
}
else
{
ottie_parser_error_syntax (reader, "Property is not a number");
}
json_reader_end_member (reader);
return TRUE;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_DOUBLE_VALUE_PRIVATE_H__
#define __OTTIE_DOUBLE_VALUE_PRIVATE_H__
#include <json-glib/json-glib.h>
G_BEGIN_DECLS
typedef struct _OttieDoubleValue OttieDoubleValue;
struct _OttieDoubleValue
{
gboolean is_static;
union {
double static_value;
gpointer keyframes;
};
};
void ottie_double_value_init (OttieDoubleValue *self,
double value);
void ottie_double_value_clear (OttieDoubleValue *self);
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__ */

138
ottie/ottieellipseshape.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottieellipseshapeprivate.h"
#include "ottiedoublevalueprivate.h"
#include "ottiepointvalueprivate.h"
#include "ottieparserprivate.h"
#include "ottieshapeprivate.h"
#include <glib/gi18n-lib.h>
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);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_ELLIPSE_SHAPE_PRIVATE_H__
#define __OTTIE_ELLIPSE_SHAPE_PRIVATE_H__
#include "ottieshapeprivate.h"
#include <json-glib/json-glib.h>
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__ */

132
ottie/ottiefillshape.c Normal file
View File

@@ -0,0 +1,132 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiefillshapeprivate.h"
#include "ottiecolorvalueprivate.h"
#include "ottiedoublevalueprivate.h"
#include "ottieparserprivate.h"
#include "ottieshapeprivate.h"
#include <glib/gi18n-lib.h>
#include <gsk/gsk.h>
struct _OttieFillShape
{
OttieShape parent;
OttieDoubleValue opacity;
OttieColorValue color;
GskBlendMode blend_mode;
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);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_FILL_SHAPE_PRIVATE_H__
#define __OTTIE_FILL_SHAPE_PRIVATE_H__
#include "ottieshapeprivate.h"
#include <json-glib/json-glib.h>
G_BEGIN_DECLS
#define OTTIE_TYPE_FILL_SHAPE (ottie_fill_shape_get_type ())
#define OTTIE_FILL_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_FILL_SHAPE, OttieFillShape))
#define OTTIE_FILL_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_FILL_SHAPE, OttieFillShapeClass))
#define OTTIE_IS_FILL_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_FILL_SHAPE))
#define OTTIE_IS_FILL_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_FILL_SHAPE))
#define OTTIE_FILL_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_FILL_SHAPE, OttieFillShapeClass))
typedef struct _OttieFillShape OttieFillShape;
typedef struct _OttieFillShapeClass OttieFillShapeClass;
GType ottie_fill_shape_get_type (void) G_GNUC_CONST;
OttieShape * ottie_fill_shape_parse (JsonReader *reader);
G_END_DECLS
#endif /* __OTTIE_FILL_SHAPE_PRIVATE_H__ */

247
ottie/ottiegroupshape.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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 <glib/gi18n-lib.h>
#include <gsk/gsk.h>
#define GDK_ARRAY_ELEMENT_TYPE OttieShape *
#define GDK_ARRAY_FREE_FUNC g_object_unref
#define GDK_ARRAY_TYPE_NAME OttieShapeList
#define GDK_ARRAY_NAME ottie_shape_list
#define GDK_ARRAY_PREALLOC 4
#include "gdk/gdkarrayimpl.c"
struct _OttieGroupShape
{
OttieShape parent;
OttieShapeList shapes;
GskBlendMode blend_mode;
};
struct _OttieGroupShapeClass
{
OttieShapeClass parent_class;
};
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;
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_GROUP_SHAPE_PRIVATE_H__
#define __OTTIE_GROUP_SHAPE_PRIVATE_H__
#include "ottieshapeprivate.h"
#include <json-glib/json-glib.h>
G_BEGIN_DECLS
#define OTTIE_TYPE_GROUP_SHAPE (ottie_group_shape_get_type ())
#define OTTIE_GROUP_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShape))
#define OTTIE_GROUP_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShapeClass))
#define OTTIE_IS_GROUP_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_GROUP_SHAPE))
#define OTTIE_IS_GROUP_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_GROUP_SHAPE))
#define OTTIE_GROUP_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_GROUP_SHAPE, OttieGroupShapeClass))
typedef struct _OttieGroupShape OttieGroupShape;
typedef struct _OttieGroupShapeClass OttieGroupShapeClass;
GType ottie_group_shape_get_type (void) G_GNUC_CONST;
OttieShape * ottie_group_shape_new (void);
OttieShape * ottie_group_shape_parse (JsonReader *reader);
gboolean ottie_group_shape_parse_shapes (JsonReader *reader,
gsize offset,
gpointer data);
G_END_DECLS
#endif /* __OTTIE_GROUP_SHAPE_PRIVATE_H__ */

15
ottie/ottieintl.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef __OTTIE_INTL_H__
#define __OTTIE_INTL_H__
#include <glib/gi18n-lib.h>
#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

382
ottie/ottiekeyframesimpl.c Normal file
View File

@@ -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 <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

96
ottie/ottielayer.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottielayerprivate.h"
#include <glib/gi18n-lib.h>
#include <json-glib/json-glib.h>
G_DEFINE_TYPE (OttieLayer, ottie_layer, 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);
}

93
ottie/ottielayerprivate.h Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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__ */

66
ottie/ottienulllayer.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottienulllayerprivate.h"
#include <glib/gi18n-lib.h>
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);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_NULL_LAYER_PRIVATE_H__
#define __OTTIE_NULL_LAYER_PRIVATE_H__
#include "ottielayerprivate.h"
#include <json-glib/json-glib.h>
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__ */

175
ottie/ottieobject.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_OBJECT_PRIVATE_H__
#define __OTTIE_OBJECT_PRIVATE_H__
#include <glib-object.h>
#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__ */

381
ottie/ottiepaintable.c Normal file
View File

@@ -0,0 +1,381 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiepaintable.h"
#include "ottiecreationprivate.h"
#include <math.h>
#include <glib/gi18n.h>
struct _OttiePaintable
{
GObject parent_instance;
OttieCreation *creation;
gint64 timestamp;
};
struct _OttiePaintableClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
PROP_CREATION,
PROP_DURATION,
PROP_TIMESTAMP,
N_PROPS,
};
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
ottie_paintable_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
OttiePaintable *self = OTTIE_PAINTABLE (paintable);
double w, h, timestamp;
if (!self->creation)
return;
w = ottie_creation_get_width (self->creation);
h = ottie_creation_get_height (self->creation);
timestamp = (double) self->timestamp / G_USEC_PER_SEC;
if (w != width || h != height)
{
gtk_snapshot_save (snapshot);
gtk_snapshot_scale (snapshot, width / w, height / h);
}
gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, w, h));
ottie_creation_snapshot (self->creation, snapshot, timestamp);
gtk_snapshot_pop (snapshot);
if (w != width || h != height)
gtk_snapshot_restore (snapshot);
}
static int
ottie_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
OttiePaintable *self = OTTIE_PAINTABLE (paintable);
if (!self->creation)
return 0;
return ceil (ottie_creation_get_width (self->creation));
}
static int
ottie_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
OttiePaintable *self = OTTIE_PAINTABLE (paintable);
if (!self->creation)
return 0;
return ceil (ottie_creation_get_height (self->creation));
}
static void
ottie_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = ottie_paintable_paintable_snapshot;
iface->get_intrinsic_width = ottie_paintable_paintable_get_intrinsic_width;
iface->get_intrinsic_height = ottie_paintable_paintable_get_intrinsic_height;
}
G_DEFINE_TYPE_EXTENDED (OttiePaintable, ottie_paintable, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
ottie_paintable_paintable_init))
static void
ottie_paintable_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
OttiePaintable *self = OTTIE_PAINTABLE (object);
switch (prop_id)
{
case PROP_CREATION:
ottie_paintable_set_creation (self, g_value_get_object (value));
break;
case PROP_TIMESTAMP:
ottie_paintable_set_timestamp (self, g_value_get_int64 (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ottie_paintable_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
OttiePaintable *self = OTTIE_PAINTABLE (object);
switch (prop_id)
{
case PROP_CREATION:
g_value_set_object (value, self->creation);
break;
case PROP_DURATION:
g_value_set_int64 (value, ottie_paintable_get_duration (self));
break;
case PROP_TIMESTAMP:
g_value_set_int64 (value, self->timestamp);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ottie_paintable_prepared_cb (OttieCreation *creation,
GParamSpec *pspec,
OttiePaintable *self)
{
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
}
static void
ottie_paintable_unset_creation (OttiePaintable *self)
{
if (self->creation == NULL)
return;
g_signal_handlers_disconnect_by_func (self->creation, ottie_paintable_prepared_cb, self);
g_clear_object (&self->creation);
}
static void
ottie_paintable_dispose (GObject *object)
{
OttiePaintable *self = OTTIE_PAINTABLE (object);
ottie_paintable_unset_creation (self);
G_OBJECT_CLASS (ottie_paintable_parent_class)->dispose (object);
}
static void
ottie_paintable_class_init (OttiePaintableClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = ottie_paintable_get_property;
gobject_class->set_property = ottie_paintable_set_property;
gobject_class->dispose = ottie_paintable_dispose;
/**
* OttiePaintable:creation
*
* The displayed creation or %NULL.
*/
properties[PROP_CREATION] =
g_param_spec_object ("creation",
_("Creation"),
_("The displayed creation"),
OTTIE_TYPE_CREATION,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* OttiePaintable:duration
*
* Duration of the displayed creation
*/
properties[PROP_DURATION] =
g_param_spec_int64 ("duration",
_("Duration"),
_("Duration of the displayed creation"),
0, G_MAXINT64, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* OttiePaintable:timestamp
*
* At what timestamp to display the creation.
*/
properties[PROP_TIMESTAMP] =
g_param_spec_int64 ("timestmp",
_("Timestamp"),
_("At what timestamp to display the creation"),
0, G_MAXINT64, 0,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
ottie_paintable_init (OttiePaintable *self)
{
}
/**
* ottie_paintable_new:
* @creation: (allow-none) (transfer full): an #OttiePaintable or %NULL
*
* Creates a new Ottie paintable for the given @creation
*
* Returns: (transfer full) (type OttiePaintable): a new #OttiePaintable
**/
OttiePaintable *
ottie_paintable_new (OttieCreation *creation)
{
OttiePaintable *self;
g_return_val_if_fail (creation == creation || OTTIE_IS_CREATION (creation), NULL);
self = g_object_new (OTTIE_TYPE_PAINTABLE,
"creation", creation,
NULL);
g_clear_object (&creation);
return self;
}
/**
* ottie_paintable_get_creation:
* @self: an #OttiePaintable
*
* Returns the creation that shown or %NULL
* if none.
*
* Returns: (transfer none) (nullable): the observed creation.
**/
OttieCreation *
ottie_paintable_get_creation (OttiePaintable *self)
{
g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), NULL);
return self->creation;
}
/**
* ottie_paintable_set_creation:
* @self: an #OttiePaintable
* @creation: (allow-none): the creation to show or %NULL
*
* Sets the creation that should be shown.
**/
void
ottie_paintable_set_creation (OttiePaintable *self,
OttieCreation *creation)
{
g_return_if_fail (OTTIE_IS_PAINTABLE (self));
g_return_if_fail (creation == NULL || OTTIE_IS_CREATION (creation));
if (self->creation == creation)
return;
ottie_paintable_unset_creation (self);
self->creation = g_object_ref (creation);
g_signal_connect (creation, "notify::prepared", G_CALLBACK (ottie_paintable_prepared_cb), self);
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CREATION]);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]);
}
/**
* ottie_paintable_get_timestamp:
* @self: an #OttiePaintable
*
* Gets the timestamp for the currently displayed image.
*
* Returns: the timestamp
**/
gint64
ottie_paintable_get_timestamp (OttiePaintable *self)
{
g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), 0);
return self->timestamp;
}
/**
* ottie_paintable_set_timestamp:
* @self: an #OttiePaintable
* @timestamp: the timestamp to display
*
* Sets the timestamp to display the creation at.
**/
void
ottie_paintable_set_timestamp (OttiePaintable *self,
gint64 timestamp)
{
g_return_if_fail (OTTIE_IS_PAINTABLE (self));
g_return_if_fail (timestamp >= 0);
if (self->timestamp == timestamp)
return;
self->timestamp = timestamp;
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIMESTAMP]);
}
/**
* ottie_paintable_get_duration:
* @self: an #OttiePaintable
*
* Gets the duration of the currently playing creation.
*
* Returns: The duration in usec.
**/
gint64
ottie_paintable_get_duration (OttiePaintable *self)
{
g_return_val_if_fail (OTTIE_IS_PAINTABLE (self), 0);
if (self->creation == NULL)
return 0;
return ceil (G_USEC_PER_SEC * ottie_creation_get_end_frame (self->creation)
/ ottie_creation_get_frame_rate (self->creation));
}

54
ottie/ottiepaintable.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_PAINTABLE_H__
#define __OTTIE_PAINTABLE_H__
#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <ottie/ottie.h> can be included directly."
#endif
#include <ottie/ottiecreation.h>
G_BEGIN_DECLS
#define OTTIE_TYPE_PAINTABLE (ottie_paintable_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (OttiePaintable, ottie_paintable, OTTIE, PAINTABLE, GObject)
GDK_AVAILABLE_IN_ALL
OttiePaintable * ottie_paintable_new (OttieCreation *creation);
GDK_AVAILABLE_IN_ALL
OttieCreation * ottie_paintable_get_creation (OttiePaintable *self);
GDK_AVAILABLE_IN_ALL
void ottie_paintable_set_creation (OttiePaintable *self,
OttieCreation *creation);
GDK_AVAILABLE_IN_ALL
gint64 ottie_paintable_get_timestamp (OttiePaintable *self);
GDK_AVAILABLE_IN_ALL
void ottie_paintable_set_timestamp (OttiePaintable *self,
gint64 timestamp);
GDK_AVAILABLE_IN_ALL
gint64 ottie_paintable_get_duration (OttiePaintable *self);
G_END_DECLS
#endif /* __OTTIE_PAINTABLE_H__ */

592
ottie/ottieparser.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottieparserprivate.h"
#include "ottietransformprivate.h"
#include <gsk/gsk.h>
void
ottie_parser_emit_error (JsonReader *reader,
const GError *error)
{
g_printerr ("Ottie is sad: %s\n", error->message);
}
void
ottie_parser_error_syntax (JsonReader *reader,
const char *format,
...)
{
va_list args;
GError *error;
va_start (args, format);
error = g_error_new_valist (JSON_PARSER_ERROR,
JSON_PARSER_ERROR_INVALID_DATA,
format, args);
va_end (args);
ottie_parser_emit_error (reader, error);
g_error_free (error);
}
void
ottie_parser_error_value (JsonReader *reader,
const char *format,
...)
{
va_list args;
GError *error;
va_start (args, format);
error = g_error_new_valist (JSON_PARSER_ERROR,
JSON_PARSER_ERROR_INVALID_DATA,
format, args);
va_end (args);
ottie_parser_emit_error (reader, error);
g_error_free (error);
}
void
ottie_parser_error_unsupported (JsonReader *reader,
const char *format,
...)
{
va_list args;
GError *error;
va_start (args, format);
error = g_error_new_valist (JSON_PARSER_ERROR,
JSON_PARSER_ERROR_INVALID_DATA,
format, args);
va_end (args);
ottie_parser_emit_error (reader, error);
g_error_free (error);
}
gboolean
ottie_parser_parse_array (JsonReader *reader,
const char *debug_name,
guint min_items,
guint max_items,
guint *out_n_items,
gsize start_offset,
gsize offset_multiplier,
OttieParseFunc func,
gpointer data)
{
guint i;
if (!json_reader_is_array (reader))
{
if (min_items > 1)
{
ottie_parser_error_syntax (reader, "Expected an array when parsing %s", debug_name);
if (out_n_items)
*out_n_items = 0;
return FALSE;
}
else
{
if (!func (reader, start_offset, data))
{
if (out_n_items)
*out_n_items = 0;
return FALSE;
}
if (out_n_items)
*out_n_items = 1;
return TRUE;
}
}
if (json_reader_count_elements (reader) < min_items)
{
ottie_parser_error_syntax (reader, "%s needs %u items, but only %u given",
debug_name, min_items, json_reader_count_elements (reader));
return FALSE;
}
max_items = MIN (max_items, json_reader_count_elements (reader));
for (i = 0; i < max_items; i++)
{
if (!json_reader_read_element (reader, i) ||
!func (reader, start_offset + offset_multiplier * i, data))
{
json_reader_end_element (reader);
if (out_n_items)
*out_n_items = i;
return FALSE;
}
json_reader_end_element (reader);
}
if (out_n_items)
*out_n_items = i;
return TRUE;
}
gboolean
ottie_parser_parse_object (JsonReader *reader,
const char *debug_name,
const OttieParserOption *options,
gsize n_options,
gpointer data)
{
if (!json_reader_is_object (reader))
{
ottie_parser_error_syntax (reader, "Expected an object when parsing %s", debug_name);
return FALSE;
}
for (int i = 0; ; i++)
{
const OttieParserOption *o = NULL;
const char *name;
if (!json_reader_read_element (reader, i))
break;
name = json_reader_get_member_name (reader);
for (gsize j = 0; j < n_options; j++)
{
o = &options[j];
if (g_str_equal (o->name, name))
break;
o = NULL;
}
if (o)
{
if (!o->parse_func (reader, o->option_data, data))
{
json_reader_end_element (reader);
return FALSE;
}
}
else
{
ottie_parser_error_unsupported (reader, "Unsupported %s property \"%s\"", debug_name, json_reader_get_member_name (reader));
}
json_reader_end_element (reader);
}
json_reader_end_element (reader);
return TRUE;
}
gboolean
ottie_parser_option_skip (JsonReader *reader,
gsize offset,
gpointer data)
{
return TRUE;
}
gboolean
ottie_parser_option_boolean (JsonReader *reader,
gsize offset,
gpointer data)
{
gboolean b;
b = json_reader_get_boolean_value (reader);
if (json_reader_get_error (reader))
{
ottie_parser_emit_error (reader, json_reader_get_error (reader));
json_reader_end_element (reader);
return FALSE;
}
*(gboolean *) ((guint8 *) data + offset) = b;
return TRUE;
}
gboolean
ottie_parser_option_double (JsonReader *reader,
gsize offset,
gpointer data)
{
double d;
d = json_reader_get_double_value (reader);
if (json_reader_get_error (reader))
{
ottie_parser_emit_error (reader, json_reader_get_error (reader));
return FALSE;
}
*(double *) ((guint8 *) data + offset) = d;
return TRUE;
}
gboolean
ottie_parser_option_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;
}

115
ottie/ottieparserprivate.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_PARSER_PRIVATE_H__
#define __OTTIE_PARSER_PRIVATE_H__
#include <json-glib/json-glib.h>
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__ */

105
ottie/ottiepathshape.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiepathshapeprivate.h"
#include "ottiepathvalueprivate.h"
#include "ottieparserprivate.h"
#include "ottieshapeprivate.h"
#include <glib/gi18n-lib.h>
struct _OttiePathShape
{
OttieShape parent;
double direction;
OttiePathValue path;
};
struct _OttiePathShapeClass
{
OttieShapeClass parent_class;
};
G_DEFINE_TYPE (OttiePathShape, ottie_path_shape, OTTIE_TYPE_SHAPE)
static void
ottie_path_shape_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);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_PATH_SHAPE_PRIVATE_H__
#define __OTTIE_PATH_SHAPE_PRIVATE_H__
#include "ottieshapeprivate.h"
#include <json-glib/json-glib.h>
G_BEGIN_DECLS
#define OTTIE_TYPE_PATH_SHAPE (ottie_path_shape_get_type ())
#define OTTIE_PATH_SHAPE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), OTTIE_TYPE_PATH_SHAPE, OttiePathShape))
#define OTTIE_PATH_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), OTTIE_TYPE_PATH_SHAPE, OttiePathShapeClass))
#define OTTIE_IS_PATH_SHAPE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), OTTIE_TYPE_PATH_SHAPE))
#define OTTIE_IS_PATH_SHAPE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), OTTIE_TYPE_PATH_SHAPE))
#define OTTIE_PATH_SHAPE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OTTIE_TYPE_PATH_SHAPE, OttiePathShapeClass))
typedef struct _OttiePathShape OttiePathShape;
typedef struct _OttiePathShapeClass OttiePathShapeClass;
GType ottie_path_shape_get_type (void) G_GNUC_CONST;
OttieShape * ottie_path_shape_parse (JsonReader *reader);
G_END_DECLS
#endif /* __OTTIE_PATH_SHAPE_PRIVATE_H__ */

402
ottie/ottiepathvalue.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiepathvalueprivate.h"
#include "ottieparserprivate.h"
#include <glib/gi18n-lib.h>
typedef struct _OttieContour OttieContour;
typedef struct _OttieCurve OttieCurve;
typedef struct _OttiePath OttiePath;
struct _OttieCurve {
double point[2];
double in[2];
double out[2];
};
struct _OttieContour {
gboolean closed;
guint n_curves;
OttieCurve curves[0];
};
struct _OttiePath {
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;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_PATH_VALUE_PRIVATE_H__
#define __OTTIE_PATH_VALUE_PRIVATE_H__
#include <json-glib/json-glib.h>
#include <gsk/gsk.h>
G_BEGIN_DECLS
typedef struct _OttiePathValue OttiePathValue;
struct _OttiePathValue
{
gboolean is_static;
union {
gpointer static_value;
gpointer keyframes;
};
};
void ottie_path_value_init (OttiePathValue *self);
void ottie_path_value_clear (OttiePathValue *self);
GskPath * ottie_path_value_get (OttiePathValue *self,
double timestamp,
gboolean reverse);
gboolean ottie_path_value_parse (JsonReader *reader,
gsize offset,
gpointer data);
G_END_DECLS
#endif /* __OTTIE_PATH_VALUE_PRIVATE_H__ */

490
ottie/ottieplayer.c Normal file
View File

@@ -0,0 +1,490 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottieplayer.h"
#include "ottiecreation.h"
#include "ottiepaintable.h"
#include <glib/gi18n.h>
struct _OttiePlayer
{
GObject parent_instance;
GFile *file;
OttieCreation *creation;
OttiePaintable *paintable;
gint64 time_offset;
guint timer_cb;
};
struct _OttiePlayerClass
{
GObjectClass parent_class;
};
enum {
PROP_0,
PROP_FILE,
N_PROPS,
};
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
ottie_player_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
OttiePlayer *self = OTTIE_PLAYER (paintable);
gdk_paintable_snapshot (GDK_PAINTABLE (self->paintable), snapshot, width, height);
}
static int
ottie_player_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
OttiePlayer *self = OTTIE_PLAYER (paintable);
return gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (self->paintable));
}
static int
ottie_player_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
OttiePlayer *self = OTTIE_PLAYER (paintable);
return gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (self->paintable));
}
static void
ottie_player_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = ottie_player_paintable_snapshot;
iface->get_intrinsic_width = ottie_player_paintable_get_intrinsic_width;
iface->get_intrinsic_height = ottie_player_paintable_get_intrinsic_height;
}
G_DEFINE_TYPE_EXTENDED (OttiePlayer, ottie_player, GTK_TYPE_MEDIA_STREAM, 0,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
ottie_player_paintable_init))
static gboolean
ottie_player_timer_cb (gpointer data)
{
OttiePlayer *self = OTTIE_PLAYER (data);
gint64 timestamp;
timestamp = g_get_monotonic_time () - self->time_offset;
if (timestamp > ottie_paintable_get_duration (self->paintable))
{
if (gtk_media_stream_get_loop (GTK_MEDIA_STREAM (self)))
{
timestamp %= ottie_paintable_get_duration (self->paintable);
}
else
{
timestamp = ottie_paintable_get_duration (self->paintable);
gtk_media_stream_ended (GTK_MEDIA_STREAM (self));
}
}
ottie_paintable_set_timestamp (self->paintable, timestamp);
gtk_media_stream_update (GTK_MEDIA_STREAM (self), timestamp);
return G_SOURCE_CONTINUE;
}
static gboolean
ottie_player_play (GtkMediaStream *stream)
{
OttiePlayer *self = OTTIE_PLAYER (stream);
double frame_rate;
frame_rate = ottie_creation_get_frame_rate (self->creation);
if (frame_rate <= 0)
return FALSE;
self->time_offset = g_get_monotonic_time () - ottie_paintable_get_timestamp (self->paintable);
self->timer_cb = g_timeout_add (1000 / frame_rate, ottie_player_timer_cb, self);
return TRUE;
}
static void
ottie_player_pause (GtkMediaStream *stream)
{
OttiePlayer *self = OTTIE_PLAYER (stream);
g_clear_handle_id (&self->timer_cb, g_source_remove);
}
static void
ottie_player_seek (GtkMediaStream *stream,
gint64 timestamp)
{
OttiePlayer *self = OTTIE_PLAYER (stream);
if (!ottie_creation_is_prepared (self->creation))
gtk_media_stream_seek_failed (stream);
ottie_paintable_set_timestamp (self->paintable, timestamp);
self->time_offset = g_get_monotonic_time () - timestamp;
gtk_media_stream_seek_success (stream);
gtk_media_stream_update (stream, timestamp);
}
static void
ottie_player_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
OttiePlayer *self = OTTIE_PLAYER (object);
switch (prop_id)
{
case PROP_FILE:
ottie_player_set_file (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ottie_player_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
OttiePlayer *self = OTTIE_PLAYER (object);
switch (prop_id)
{
case PROP_FILE:
g_value_set_object (value, self->file);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
ottie_player_dispose (GObject *object)
{
OttiePlayer *self = OTTIE_PLAYER (object);
if (self->paintable)
{
g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_contents, self);
g_signal_handlers_disconnect_by_func (self->paintable, gdk_paintable_invalidate_size, self);
g_clear_object (&self->paintable);
}
g_clear_object (&self->creation);
G_OBJECT_CLASS (ottie_player_parent_class)->dispose (object);
}
static void
ottie_player_class_init (OttiePlayerClass *klass)
{
GtkMediaStreamClass *stream_class = GTK_MEDIA_STREAM_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
stream_class->play = ottie_player_play;
stream_class->pause = ottie_player_pause;
stream_class->seek = ottie_player_seek;
gobject_class->get_property = ottie_player_get_property;
gobject_class->set_property = ottie_player_set_property;
gobject_class->dispose = ottie_player_dispose;
/**
* OttiePlayer:file
*
* The played file or %NULL.
*/
properties[PROP_FILE] =
g_param_spec_object ("file",
_("File"),
_("The played file"),
G_TYPE_FILE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
ottie_player_prepared_cb (OttieCreation *creation,
GParamSpec *pspec,
OttiePlayer *self)
{
if (ottie_creation_is_prepared (creation))
gtk_media_stream_prepared (GTK_MEDIA_STREAM (self),
FALSE,
TRUE,
TRUE,
ottie_paintable_get_duration (self->paintable));
else
gtk_media_stream_unprepared (GTK_MEDIA_STREAM (self));
ottie_paintable_set_timestamp (self->paintable, 0);
}
static void
ottie_player_init (OttiePlayer *self)
{
self->creation = ottie_creation_new ();
g_signal_connect (self->creation, "notify::prepared", G_CALLBACK (ottie_player_prepared_cb), self);
self->paintable = ottie_paintable_new (self->creation);
g_signal_connect_swapped (self->paintable, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
g_signal_connect_swapped (self->paintable, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
}
/**
* ottie_player_new:
*
* Creates a new Ottie player.
*
* Returns: (transfer full): a new #OttiePlayer
**/
OttiePlayer *
ottie_player_new (void)
{
return g_object_new (OTTIE_TYPE_PLAYER, NULL);
}
/**
* ottie_player_new_for_file:
* @file: (nullable): a #GFile
*
* Creates a new #OttiePlayer playing the given @file. If the file
* isnt found or cant 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);
}

59
ottie/ottieplayer.h Normal file
View File

@@ -0,0 +1,59 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_PLAYER_H__
#define __OTTIE_PLAYER_H__
#if !defined (__OTTIE_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <ottie/ottie.h> can be included directly."
#endif
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define OTTIE_TYPE_PLAYER (ottie_player_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (OttiePlayer, ottie_player, OTTIE, PLAYER, GtkMediaStream)
GDK_AVAILABLE_IN_ALL
OttiePlayer * ottie_player_new (void);
GDK_AVAILABLE_IN_ALL
OttiePlayer * ottie_player_new_for_file (GFile *file);
GDK_AVAILABLE_IN_ALL
OttiePlayer * ottie_player_new_for_filename (const char *filename);
GDK_AVAILABLE_IN_ALL
OttiePlayer * ottie_player_new_for_resource (const char *resource_path);
GDK_AVAILABLE_IN_ALL
void ottie_player_set_file (OttiePlayer *self,
GFile *file);
GDK_AVAILABLE_IN_ALL
GFile * ottie_player_get_file (OttiePlayer *self);
GDK_AVAILABLE_IN_ALL
void ottie_player_set_filename (OttiePlayer *self,
const char *filename);
GDK_AVAILABLE_IN_ALL
void ottie_player_set_resource (OttiePlayer *self,
const char *resource_path);
G_END_DECLS
#endif /* __OTTIE_PLAYER_H__ */

154
ottie/ottiepoint3dvalue.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiepoint3dvalueprivate.h"
#include "ottieparserprivate.h"
#include <glib/gi18n-lib.h>
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;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_POINT3D_VALUE_PRIVATE_H__
#define __OTTIE_POINT3D_VALUE_PRIVATE_H__
#include <json-glib/json-glib.h>
#include <graphene.h>
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__ */

140
ottie/ottiepointvalue.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottiepointvalueprivate.h"
#include "ottieparserprivate.h"
#include <glib/gi18n-lib.h>
static gboolean
ottie_point_value_parse_value (JsonReader *reader,
gsize offset,
gpointer data)
{
double d[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;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_POINT_VALUE_PRIVATE_H__
#define __OTTIE_POINT_VALUE_PRIVATE_H__
#include <json-glib/json-glib.h>
#include <graphene.h>
G_BEGIN_DECLS
typedef struct _OttiePointValue OttiePointValue;
struct _OttiePointValue
{
gboolean is_static;
union {
graphene_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__ */

219
ottie/ottierectshape.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottierectshapeprivate.h"
#include "ottiedoublevalueprivate.h"
#include "ottiepointvalueprivate.h"
#include "ottieparserprivate.h"
#include "ottieshapeprivate.h"
#include <glib/gi18n-lib.h>
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);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __OTTIE_RECT_SHAPE_PRIVATE_H__
#define __OTTIE_RECT_SHAPE_PRIVATE_H__
#include "ottieshapeprivate.h"
#include <json-glib/json-glib.h>
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__ */

276
ottie/ottierender.c Normal file
View File

@@ -0,0 +1,276 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "ottierenderprivate.h"
#include <glib/gi18n-lib.h>
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);
}
}

Some files were not shown because too many files have changed in this diff Show More