aboutsummaryrefslogtreecommitdiff
path: root/canvas.c
diff options
context:
space:
mode:
Diffstat (limited to 'canvas.c')
-rw-r--r--canvas.c719
1 files changed, 719 insertions, 0 deletions
diff --git a/canvas.c b/canvas.c
new file mode 100644
index 0000000..af2df7b
--- /dev/null
+++ b/canvas.c
@@ -0,0 +1,719 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source 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
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdatomic.h>
+#include <inttypes.h>
+
+#include <lv2/lv2plug.in/ns/ext/patch/patch.h>
+#include <lv2/lv2plug.in/ns/ext/log/log.h>
+#include <lv2/lv2plug.in/ns/ext/log/logger.h>
+
+#include <canvas.lv2/forge.h>
+#include <canvas.lv2/render.h>
+
+#include <lv2_extensions.h>
+
+#include <cairo/cairo.h>
+
+//#define DEBUG
+#define MAX_GRAPH_BUF 0x10000
+
+typedef struct _plughandle_t plughandle_t;
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge forge_through;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *control;
+ LV2_Atom_Sequence *notify;
+
+ LV2_URID patch_Get;
+ LV2_URID patch_Set;
+ LV2_URID patch_Put;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+ LV2_URID patch_body;
+
+ atomic_flag lock;
+
+ LV2_Inline_Display *queue_draw;
+ LV2_Inline_Display_Image_Surface image_surface;
+ struct {
+ cairo_surface_t *surface;
+ cairo_t *ctx;
+ } cairo;
+
+ LV2_Canvas canvas;
+ bool dirty;
+
+ float aspect_ratio;
+ union {
+ LV2_Atom_Tuple graph;
+ uint8_t buf [MAX_GRAPH_BUF];
+ };
+};
+
+static inline void
+_spin_lock(atomic_flag *lock)
+{
+ while(atomic_flag_test_and_set_explicit(lock, memory_order_acquire))
+ {
+ // spin
+ }
+}
+
+static inline bool
+_try_lock(atomic_flag *lock)
+{
+ return atomic_flag_test_and_set_explicit(lock, memory_order_acquire);
+}
+
+static inline void
+_unlock(atomic_flag *lock)
+{
+ atomic_flag_clear_explicit(lock, memory_order_release);
+}
+
+static const float lv2_L [] = {
+ 0.05, 0.275,
+ 0.05, 0.73463521816969,
+ 0.39996786383766, 0.73463521816969,
+ 0.35805418792799, 0.61981755929103,
+ 0.16950515672412, 0.61981755929103,
+ 0.16950515672412, 0.275,
+ 0.05, 0.275
+};
+
+static const float lv2_V [] = {
+ 0.44035674587458, 0.73463521816969,
+ 0.27321237521861, 0.275,
+ 0.39612954205777, 0.275,
+ 0.5215250619933, 0.61980400005209,
+ 0.64678627651808, 0.275,
+ 0.76999411666921, 0.275,
+ 0.60269884777111, 0.73463521816969,
+ 0.44035674587458, 0.73463521816969
+};
+
+static inline LV2_Atom_Forge_Ref
+_lv2_logo_forge(LV2_Atom_Forge *forge, LV2_Canvas_URID *urid)
+{
+ const uint32_t fg = 0xbb6600ff;
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_canvas_forge_beginPath(forge, urid))
+ && (ref = lv2_canvas_forge_polyLine(forge, urid, sizeof(lv2_L)/sizeof(float), lv2_L))
+ && (ref = lv2_canvas_forge_closePath(forge, urid))
+ && (ref = lv2_canvas_forge_style(forge, urid, fg))
+ && (ref = lv2_canvas_forge_stroke(forge, urid))
+
+ && (ref = lv2_canvas_forge_beginPath(forge, urid))
+ && (ref = lv2_canvas_forge_polyLine(forge, urid, sizeof(lv2_L)/sizeof(float), lv2_V))
+ && (ref = lv2_canvas_forge_closePath(forge, urid))
+ && (ref = lv2_canvas_forge_style(forge, urid, fg))
+ && (ref = lv2_canvas_forge_stroke(forge, urid))
+
+ && (ref = lv2_canvas_forge_beginPath(forge, urid))
+ && (ref = lv2_canvas_forge_moveTo(forge, urid, 0.92679577564592, 0.33745757758451))
+ && (ref = lv2_canvas_forge_curveTo(forge, urid, 0.95, 0.37544661222032, 0.9486097413556,
+ 0.42890103900541, 0.91866073788306, 0.46581025262318))
+ && (ref = lv2_canvas_forge_curveTo(forge, urid, 0.87662774067075, 0.51761178520021,
+ 0.84865149155459, 0.52351773004551, 0.8188709443895, 0.55088574387747))
+ && (ref = lv2_canvas_forge_lineTo(forge, urid, 0.93798338878322, 0.55088574387747))
+ && (ref = lv2_canvas_forge_lineTo(forge, urid, 0.93798338878322, 0.61972641362727))
+ && (ref = lv2_canvas_forge_lineTo(forge, urid, 0.68857649440815, 0.61972641362727))
+ && (ref = lv2_canvas_forge_curveTo(forge, urid, 0.70410821191941, 0.57897193773781,
+ 0.71568706655441, 0.55649255812279, 0.73505227967577, 0.53436493734023))
+ && (ref = lv2_canvas_forge_curveTo(forge, urid, 0.78431409785481, 0.47807598612821,
+ 0.88073913173375, 0.44149338929647, 0.87483180798279, 0.39074363998918))
+ && (ref = lv2_canvas_forge_curveTo(forge, urid, 0.8731729385169, 0.37649219041461,
+ 0.86900905711197, 0.34385128732334, 0.80655313421425, 0.34385128732334))
+ && (ref = lv2_canvas_forge_lineTo(forge, urid, 0.7834998081023, 0.34385128732334))
+ && (ref = lv2_canvas_forge_lineTo(forge, urid, 0.80849192152801, 0.275))
+ && (ref = lv2_canvas_forge_curveTo(forge, urid, 0.88098903540187, 0.275,
+ 0.90879494370618, 0.30798728419169, 0.92679577564592, 0.33745757758451))
+ && (ref = lv2_canvas_forge_style(forge, urid, fg))
+ && (ref = lv2_canvas_forge_stroke(forge, urid)) )
+ {
+ return ref;
+ }
+
+ return 0;
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw))
+ handle->queue_draw = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(handle->log)
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+
+ handle->patch_Get = handle->map->map(handle->map->handle, LV2_PATCH__Get);
+ handle->patch_Set = handle->map->map(handle->map->handle, LV2_PATCH__Set);
+ handle->patch_Put = handle->map->map(handle->map->handle, LV2_PATCH__Put);
+ handle->patch_property = handle->map->map(handle->map->handle, LV2_PATCH__property);
+ handle->patch_value = handle->map->map(handle->map->handle, LV2_PATCH__value);
+ handle->patch_body = handle->map->map(handle->map->handle, LV2_PATCH__body);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ handle->lock = (atomic_flag)ATOMIC_FLAG_INIT;
+
+ lv2_canvas_init(&handle->canvas, handle->map);
+
+ handle->aspect_ratio = 1.f;
+
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(&handle->forge, handle->buf, MAX_GRAPH_BUF);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_tuple(&handle->forge, &frame);
+ if(ref)
+ ref = _lv2_logo_forge(&handle->forge, &handle->canvas.urid);
+ if(ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ handle->dirty = true;
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control = data;
+ break;
+ case 1:
+ handle->notify = data;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static LV2_Atom_Forge_Ref
+_forge_graph(plughandle_t *handle, int64_t frames)
+{
+ const uint32_t szg = lv2_atom_total_size(&handle->graph.atom);
+
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(&handle->forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(&handle->forge, &obj_frame, 0, handle->patch_Set);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(&handle->forge, handle->canvas.urid.Canvas_graph);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->patch_value);
+ if(ref)
+ ref = lv2_atom_forge_write(&handle->forge, &handle->graph, szg);
+ if(ref)
+ lv2_atom_forge_pop(&handle->forge, &obj_frame);
+
+ return ref;
+}
+
+static LV2_Atom_Forge_Ref
+_forge_aspect(plughandle_t *handle, int64_t frames)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(&handle->forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(&handle->forge, &obj_frame, 0, handle->patch_Set);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(&handle->forge, handle->canvas.urid.Canvas_aspectRatio);
+ if(ref)
+ ref = lv2_atom_forge_key(&handle->forge, handle->patch_value);
+ if(ref)
+ ref = lv2_atom_forge_float(&handle->forge, handle->aspect_ratio);
+ if(ref)
+ lv2_atom_forge_pop(&handle->forge, &obj_frame);
+
+ return ref;
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ const uint32_t capacity = handle->notify->atom.size;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->notify, capacity);
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const uint32_t szo = lv2_atom_total_size(&obj->atom);
+ bool route = false;
+
+ if(lv2_atom_forge_is_object_type(&handle->forge, obj->atom.type))
+ {
+ if(obj->body.otype == handle->patch_Get)
+ {
+ const LV2_Atom_URID *property = NULL;
+
+ lv2_atom_object_get(obj, handle->patch_property, &property,
+ 0);
+
+ if(!property)
+ {
+ if(ref)
+ ref = _forge_aspect(handle, ev->time.frames);
+ if(ref)
+ ref = _forge_graph(handle, ev->time.frames);
+ }
+ if( property
+ && (property->atom.type == handle->forge.URID)
+ && (property->body == handle->canvas.urid.Canvas_graph) )
+ {
+ if(ref)
+ ref = _forge_graph(handle, ev->time.frames);
+ }
+ else if( property
+ && (property->atom.type == handle->forge.URID)
+ && (property->body == handle->canvas.urid.Canvas_aspectRatio) )
+ {
+ if(ref)
+ ref = _forge_aspect(handle, ev->time.frames);
+ }
+ }
+ else if(obj->body.otype == handle->patch_Set)
+ {
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom *value = NULL;
+
+ lv2_atom_object_get(obj, handle->patch_property, &property,
+ handle->patch_value, &value,
+ 0);
+
+ if( property
+ && (property->atom.type == handle->forge.URID)
+ && value)
+ {
+ if( (property->body == handle->canvas.urid.Canvas_graph)
+ && (value->type == handle->forge.Tuple) )
+ {
+ const LV2_Atom_Tuple *graph = (const LV2_Atom_Tuple *)value;
+ const uint32_t szg = lv2_atom_total_size(&graph->atom);
+
+ if( (szg <= MAX_GRAPH_BUF)
+ && _try_lock(&handle->lock) )
+ {
+ memcpy(&handle->graph, graph, szg);
+
+ _unlock(&handle->lock);
+
+ if(handle->queue_draw)
+ handle->queue_draw->queue_draw(handle->queue_draw->handle);
+ }
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_aspectRatio)
+ && (value->type == handle->forge.Float) )
+ {
+ if( _try_lock(&handle->lock) )
+ {
+ handle->aspect_ratio = ((const LV2_Atom_Float *)value)->body;
+
+ _unlock(&handle->lock);
+
+ if(handle->queue_draw)
+ handle->queue_draw->queue_draw(handle->queue_draw->handle);
+ }
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mouseButtonLeft)
+ && (value->type == handle->forge.Bool) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseButtonLeft: %"PRIi32"\n",
+ ((const LV2_Atom_Bool *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mouseButtonMiddle)
+ && (value->type == handle->forge.Bool) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseButtonMiddle: %"PRIi32"\n",
+ ((const LV2_Atom_Bool *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mouseButtonRight)
+ && (value->type == handle->forge.Bool) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseButtonRight: %"PRIi32"\n",
+ ((const LV2_Atom_Bool *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mousePositionX)
+ && (value->type == handle->forge.Double) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mousePositionX: %lf\n",
+ ((const LV2_Atom_Double *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mousePositionY)
+ && (value->type == handle->forge.Double) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mousePositionY: %lf\n",
+ ((const LV2_Atom_Double *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mouseWheelX)
+ && (value->type == handle->forge.Double) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseWheelX: %lf\n",
+ ((const LV2_Atom_Double *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mouseWheelY)
+ && (value->type == handle->forge.Double) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseWheelY: %lf\n",
+ ((const LV2_Atom_Double *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ else if( (property->body == handle->canvas.urid.Canvas_mouseFocus)
+ && (value->type == handle->forge.Bool) )
+ {
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseFocus: %"PRIi32"\n",
+ ((const LV2_Atom_Bool *)value)->body);
+ }
+#endif
+
+ route = true;
+ }
+ }
+ }
+ else if(obj->body.otype == handle->patch_Put)
+ {
+ const LV2_Atom_Object *body = NULL;
+
+ lv2_atom_object_get(obj, handle->patch_body, &body,
+ 0);
+
+ if( body
+ && lv2_atom_forge_is_object_type(&handle->forge, body->atom.type) )
+ {
+ const LV2_Atom_Bool *l = NULL;
+ const LV2_Atom_Bool *m = NULL;
+ const LV2_Atom_Bool *r = NULL;
+ const LV2_Atom_Double *dx = NULL;
+ const LV2_Atom_Double *dy = NULL;
+ const LV2_Atom_Double *x = NULL;
+ const LV2_Atom_Double *y = NULL;
+ const LV2_Atom_Bool *f = NULL;
+
+ lv2_atom_object_get(body,
+ handle->canvas.urid.Canvas_mouseButtonLeft, &l,
+ handle->canvas.urid.Canvas_mouseButtonMiddle, &m,
+ handle->canvas.urid.Canvas_mouseButtonRight, &r,
+ handle->canvas.urid.Canvas_mouseWheelX, &dx,
+ handle->canvas.urid.Canvas_mouseWheelY, &dy,
+ handle->canvas.urid.Canvas_mousePositionX, &x,
+ handle->canvas.urid.Canvas_mousePositionY, &y,
+ handle->canvas.urid.Canvas_mouseFocus, &f,
+ 0);
+
+#ifdef DEBUG
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "{\n");
+ if(l && (l->atom.type == handle->forge.Bool) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mousebuttonLeft: %"PRIi32"\n", l->body);
+ if(m && (m->atom.type == handle->forge.Bool) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mousebuttonMiddle: %"PRIi32"\n", m->body);
+ if(r && (r->atom.type == handle->forge.Bool) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mousebuttonRight: %"PRIi32"\n", r->body);
+ if(x && (x->atom.type == handle->forge.Double) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mousePositionX: %lf\n", x->body);
+ if(y && (y->atom.type == handle->forge.Double) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mousePositionY: %lf\n", y->body);
+ if(dx && (dx->atom.type == handle->forge.Double) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseWheelX: %lf\n", dx->body);
+ if(dy && (dy->atom.type == handle->forge.Double) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseWheelY: %lf\n", dy->body);
+ if(f && (f->atom.type == handle->forge.Bool) )
+ lv2_log_trace(&handle->logger, "\tcanvas:mouseFocus: %"PRIi32"\n", f->body);
+ lv2_log_trace(&handle->logger, "}\n");
+ }
+#endif
+
+ if( x && (x->atom.type == handle->forge.Double)
+ && y && (y->atom.type == handle->forge.Double) )
+ route = true; // a valid input event at least sets x and y
+ }
+ }
+ }
+
+ if(route) // should we route to UI?
+ {
+ if(ref)
+ ref = lv2_atom_forge_frame_time(&handle->forge, ev->time.frames);
+ if(ref)
+ ref = lv2_atom_forge_write(&handle->forge, obj, szo);
+ }
+ }
+
+ if(handle->dirty)
+ {
+ if(handle->queue_draw)
+ handle->queue_draw->queue_draw(handle->queue_draw->handle);
+
+ if(ref)
+ ref = _forge_aspect(handle, nsamples-1);
+ if(ref)
+ ref = _forge_graph(handle, nsamples-1);
+
+ handle->dirty = false;
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+}
+
+static inline LV2_Inline_Display_Image_Surface *
+_cairo_init(plughandle_t *handle, int w, int h)
+{
+ LV2_Inline_Display_Image_Surface *surf = &handle->image_surface;
+
+ surf->width = w;
+ surf->height = w > h ? h : w; // try to use 1:1 ratio
+ surf->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, surf->width);
+ surf->data = realloc(surf->data, surf->stride * surf->height);
+ if(!surf->data)
+ return NULL;
+
+ handle->cairo.surface = cairo_image_surface_create_for_data(
+ surf->data, CAIRO_FORMAT_ARGB32, surf->width, surf->height, surf->stride);
+
+ if(handle->cairo.surface)
+ {
+ cairo_surface_set_device_scale(handle->cairo.surface, surf->width, surf->height);
+
+ handle->cairo.ctx = cairo_create(handle->cairo.surface);
+ if(handle->cairo.ctx)
+ {
+ cairo_select_font_face(handle->cairo.ctx, "cairo:monospace", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ }
+ }
+
+ return surf;
+}
+
+static inline void
+_cairo_deinit(plughandle_t *handle)
+{
+ LV2_Inline_Display_Image_Surface *surf = &handle->image_surface;
+
+ if(handle->cairo.ctx)
+ {
+ cairo_destroy(handle->cairo.ctx);
+ handle->cairo.ctx = NULL;
+ }
+
+ if(handle->cairo.surface)
+ {
+ cairo_surface_finish(handle->cairo.surface);
+ cairo_surface_destroy(handle->cairo.surface);
+ handle->cairo.surface = NULL;
+ }
+
+ if(surf->data)
+ {
+ free(surf->data);
+ surf->data = NULL;
+ }
+}
+
+// non-rt
+static LV2_Inline_Display_Image_Surface *
+_render(LV2_Handle instance, uint32_t w, uint32_t h)
+{
+ plughandle_t *handle = instance;
+ LV2_Inline_Display_Image_Surface *surf = &handle->image_surface;
+
+ int W;
+ int H;
+
+ if(handle->aspect_ratio == 0.f)
+ {
+ W = w;
+ H = h;
+ }
+ else if(handle->aspect_ratio <= 1.f)
+ {
+ W = h * handle->aspect_ratio;
+ H = h;
+ }
+ else if(handle->aspect_ratio > 1.f)
+ {
+ W = w;
+ H = w / handle->aspect_ratio;
+ }
+
+ if( (surf->width != W) || (surf->height != H) || !surf->data)
+ {
+ _cairo_deinit(handle);
+ surf = _cairo_init(handle, W, H);
+ }
+
+ if(!surf)
+ return NULL;
+
+ _spin_lock(&handle->lock);
+
+ lv2_canvas_render(&handle->canvas, handle->cairo.ctx, &handle->graph);
+
+ _unlock(&handle->lock);
+
+ return surf;
+}
+
+static const LV2_Inline_Display_Interface idisp_iface = {
+ .render = _render
+};
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ _cairo_deinit(handle);
+
+ if(handle->image_surface.data)
+ free(handle->image_surface.data);
+
+ free(handle);
+}
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_INLINEDISPLAY__interface))
+ return &idisp_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor canvas_canvas = {
+ .URI = "http://open-music-kontrollers.ch/lv2/canvas_display#canvas",
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+LV2_SYMBOL_EXPORT const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &canvas_canvas;
+
+ default:
+ return NULL;
+ }
+}