Index: uspace/lib/guigfx/private/canvas.h
===================================================================
--- uspace/lib/guigfx/private/canvas.h	(revision 5f483bed56525671e8eedbe4fda24dc1bc0b6993)
+++ uspace/lib/guigfx/private/canvas.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -45,8 +45,5 @@
 #include <io/pixel.h>
 
-/** Actual structure of graphics context.
- *
- * This is private to libguigfx. It is not visible to clients nor backends.
- */
+/** Actual structure of canvas GC. */
 struct canvas_gc {
 	/** Base graphic context */
Index: uspace/lib/guigfx/src/canvas.c
===================================================================
--- uspace/lib/guigfx/src/canvas.c	(revision 5f483bed56525671e8eedbe4fda24dc1bc0b6993)
+++ uspace/lib/guigfx/src/canvas.c	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -114,5 +114,5 @@
  * Create graphics context for rendering into a canvas.
  *
- * @param con Canvas object
+ * @param canvas Canvas object
  * @param fout File to which characters are written (canvas)
  * @param rgc Place to store pointer to new GC.
Index: uspace/lib/memgfx/doc/doxygroups.h
===================================================================
--- uspace/lib/memgfx/doc/doxygroups.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/doc/doxygroups.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,3 @@
+/** @addtogroup libmemgfx libmemgfx
+ * @ingroup libs
+ */
Index: uspace/lib/memgfx/include/memgfx/memgc.h
===================================================================
--- uspace/lib/memgfx/include/memgfx/memgc.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/include/memgfx/memgc.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libmemgfx
+ * @{
+ */
+/**
+ * @file GFX memory backend
+ */
+
+#ifndef _MEMGFX_MEMGC_H
+#define _MEMGFX_MEMGC_H
+
+#include <errno.h>
+#include <types/gfx/bitmap.h>
+#include <types/gfx/context.h>
+#include <types/gfx/coord.h>
+#include <types/gfx/ops/context.h>
+#include <types/memgfx/memgc.h>
+
+extern gfx_context_ops_t mem_gc_ops;
+
+extern errno_t mem_gc_create(gfx_rect_t *, gfx_bitmap_alloc_t *, mem_gc_t **);
+extern errno_t mem_gc_delete(mem_gc_t *);
+extern gfx_context_t *mem_gc_get_ctx(mem_gc_t *);
+extern void mem_gc_get_update_rect(mem_gc_t *, gfx_rect_t *);
+extern void mem_gc_clear_update_rect(mem_gc_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/memgfx/include/types/memgfx/memgc.h
===================================================================
--- uspace/lib/memgfx/include/types/memgfx/memgc.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/include/types/memgfx/memgc.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libmemgfx
+ * @{
+ */
+/**
+ * @file GFX memory backend
+ */
+
+#ifndef _MEMGFX_TYPES_MEMGC_H
+#define _MEMGFX_TYPES_MEMGC_H
+
+struct mem_gc;
+typedef struct mem_gc mem_gc_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/memgfx/meson.build
===================================================================
--- uspace/lib/memgfx/meson.build	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/meson.build	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2020 Jiri Svoboda
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+deps = [ 'gfx' ]
+src = files(
+	'src/memgc.c'
+)
+
+test_src = files(
+	'test/main.c',
+	'test/memgfx.c',
+)
Index: uspace/lib/memgfx/private/memgc.h
===================================================================
--- uspace/lib/memgfx/private/memgc.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/private/memgc.h	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libguigfx
+ * @{
+ */
+/**
+ * @file GFX memory backend structure
+ *
+ */
+
+#ifndef _MEMGFX_PRIVATE_MEMGC_H
+#define _MEMGFX_PRIVATE_MEMGC_H
+
+#include <gfx/bitmap.h>
+#include <gfx/context.h>
+#include <gfx/coord.h>
+#include <io/pixel.h>
+
+/** Actual structure of memory GC. */
+struct mem_gc {
+	/** Base graphic context */
+	gfx_context_t *gc;
+	/** Bounding rectangle */
+	gfx_rect_t rect;
+	/** Update rectangle */
+	gfx_rect_t upd_rect;
+	/** Allocation info */
+	gfx_bitmap_alloc_t alloc;
+	/** Current drawing color */
+	pixel_t color;
+};
+
+/** Bitmap in memory GC */
+typedef struct {
+	/** Containing canvas GC */
+	struct mem_gc *mgc;
+	/** Allocation info */
+	gfx_bitmap_alloc_t alloc;
+	/** @c true if we allocated the bitmap, @c false if allocated by caller */
+	bool myalloc;
+	/** Rectangle covered by bitmap */
+	gfx_rect_t rect;
+	/** Bitmap flags */
+	gfx_bitmap_flags_t flags;
+	/** Key color */
+	pixel_t key_color;
+} mem_gc_bitmap_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/memgfx/src/memgc.c
===================================================================
--- uspace/lib/memgfx/src/memgc.c	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/src/memgc.c	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libmemgfx
+ * @{
+ */
+/**
+ * @file GFX memory backend
+ *
+ * This implements a graphics context over a block of memory (i.e. a simple
+ * software renderer).
+ */
+
+#include <assert.h>
+#include <gfx/color.h>
+#include <gfx/context.h>
+#include <gfx/render.h>
+#include <io/pixel.h>
+#include <io/pixelmap.h>
+#include <memgfx/memgc.h>
+#include <stdlib.h>
+#include "../private/memgc.h"
+
+static errno_t mem_gc_set_color(void *, gfx_color_t *);
+static errno_t mem_gc_fill_rect(void *, gfx_rect_t *);
+static errno_t mem_gc_bitmap_create(void *, gfx_bitmap_params_t *,
+    gfx_bitmap_alloc_t *, void **);
+static errno_t mem_gc_bitmap_destroy(void *);
+static errno_t mem_gc_bitmap_render(void *, gfx_rect_t *, gfx_coord2_t *);
+static errno_t mem_gc_bitmap_get_alloc(void *, gfx_bitmap_alloc_t *);
+static void mem_gc_invalidate_rect(mem_gc_t *, gfx_rect_t *);
+
+gfx_context_ops_t mem_gc_ops = {
+	.set_color = mem_gc_set_color,
+	.fill_rect = mem_gc_fill_rect,
+	.bitmap_create = mem_gc_bitmap_create,
+	.bitmap_destroy = mem_gc_bitmap_destroy,
+	.bitmap_render = mem_gc_bitmap_render,
+	.bitmap_get_alloc = mem_gc_bitmap_get_alloc
+};
+
+/** Set color on memory GC.
+ *
+ * Set drawing color on memory GC.
+ *
+ * @param arg Canvas GC
+ * @param color Color
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t mem_gc_set_color(void *arg, gfx_color_t *color)
+{
+	mem_gc_t *mgc = (mem_gc_t *) arg;
+	uint16_t r, g, b;
+
+	gfx_color_get_rgb_i16(color, &r, &g, &b);
+	mgc->color = PIXEL(0, r >> 8, g >> 8, b >> 8);
+	return EOK;
+}
+
+/** Fill rectangle on memory GC.
+ *
+ * @param arg Canvas GC
+ * @param rect Rectangle
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t mem_gc_fill_rect(void *arg, gfx_rect_t *rect)
+{
+	mem_gc_t *mgc = (mem_gc_t *) arg;
+	gfx_rect_t crect;
+	gfx_coord2_t dims;
+	gfx_coord_t x, y;
+	pixelmap_t pixelmap;
+
+	/* Make sure we have a sorted, clipped rectangle */
+	gfx_rect_clip(rect, &mgc->rect, &crect);
+
+	gfx_rect_dims(&mgc->rect, &dims);
+	pixelmap.width = dims.x;
+	pixelmap.height = dims.y;
+	pixelmap.data = mgc->alloc.pixels;
+
+	for (y = crect.p0.y; y < crect.p1.y; y++) {
+		for (x = crect.p0.x; x < crect.p1.x; x++) {
+			pixelmap_put_pixel(&pixelmap, x, y, mgc->color);
+		}
+	}
+
+	mem_gc_invalidate_rect(mgc, &crect);
+	return EOK;
+}
+
+/** Create memory GC.
+ *
+ * Create graphics context for rendering into a block of memory.
+ *
+ * @param rect Bounding rectangle
+ * @param alloc Allocation info
+ * @param rgc Place to store pointer to new memory GC
+ *
+ * @return EOK on success or an error code
+ */
+errno_t mem_gc_create(gfx_rect_t *rect, gfx_bitmap_alloc_t *alloc,
+    mem_gc_t **rgc)
+{
+	mem_gc_t *mgc = NULL;
+	gfx_context_t *gc = NULL;
+	errno_t rc;
+
+	mgc = calloc(1, sizeof(mem_gc_t));
+	if (mgc == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = gfx_context_new(&mem_gc_ops, mgc, &gc);
+	if (rc != EOK)
+		goto error;
+
+	mgc->gc = gc;
+	mgc->rect = *rect;
+	mgc->alloc = *alloc;
+
+	/*
+	 * These are the limitations of pixelmap which we are using.
+	 * Rather than falling back to an ad-hoc method of pixel access
+	 * (which is not searchable), use pixelmap for now and switch
+	 * to a better, more universal method later (e.g. supporting
+	 * different color depths).
+	 */
+	assert(rect->p0.x == 0);
+	assert(rect->p0.y == 0);
+	assert(alloc->pitch == rect->p1.x * (int)sizeof(uint32_t));
+
+	mem_gc_clear_update_rect(mgc);
+
+	*rgc = mgc;
+	return EOK;
+error:
+	if (mgc != NULL)
+		free(mgc);
+	gfx_context_delete(gc);
+	return rc;
+}
+
+/** Delete memory GC.
+ *
+ * @param mgc Canvas GC
+ */
+errno_t mem_gc_delete(mem_gc_t *mgc)
+{
+	errno_t rc;
+
+	rc = gfx_context_delete(mgc->gc);
+	if (rc != EOK)
+		return rc;
+
+	free(mgc);
+	return EOK;
+}
+
+/** Get generic graphic context from memory GC.
+ *
+ * @param mgc Canvas GC
+ * @return Graphic context
+ */
+gfx_context_t *mem_gc_get_ctx(mem_gc_t *mgc)
+{
+	return mgc->gc;
+}
+
+static void mem_gc_invalidate_rect(mem_gc_t *mgc, gfx_rect_t *rect)
+{
+	gfx_rect_t nrect;
+
+	gfx_rect_envelope(&mgc->upd_rect, rect, &nrect);
+	mgc->upd_rect = nrect;
+}
+
+void mem_gc_get_update_rect(mem_gc_t *mgc, gfx_rect_t *rect)
+{
+	*rect = mgc->upd_rect;
+}
+
+void mem_gc_clear_update_rect(mem_gc_t *mgc)
+{
+	mgc->upd_rect.p0.x = 0;
+	mgc->upd_rect.p0.y = 0;
+	mgc->upd_rect.p1.x = 0;
+	mgc->upd_rect.p1.y = 0;
+}
+
+/** Create bitmap in memory GC.
+ *
+ * @param arg Canvas GC
+ * @param params Bitmap params
+ * @param alloc Bitmap allocation info or @c NULL
+ * @param rbm Place to store pointer to new bitmap
+ * @return EOK on success or an error code
+ */
+errno_t mem_gc_bitmap_create(void *arg, gfx_bitmap_params_t *params,
+    gfx_bitmap_alloc_t *alloc, void **rbm)
+{
+	mem_gc_t *mgc = (mem_gc_t *) arg;
+	mem_gc_bitmap_t *mbm = NULL;
+	gfx_coord2_t dim;
+	errno_t rc;
+
+	mbm = calloc(1, sizeof(mem_gc_bitmap_t));
+	if (mbm == NULL)
+		return ENOMEM;
+
+	gfx_coord2_subtract(&params->rect.p1, &params->rect.p0, &dim);
+	mbm->rect = params->rect;
+	mbm->flags = params->flags;
+	mbm->key_color = params->key_color;
+
+	if (alloc == NULL) {
+		mbm->alloc.pitch = dim.x * sizeof(uint32_t);
+		mbm->alloc.off0 = 0;
+		mbm->alloc.pixels = malloc(mbm->alloc.pitch * dim.y);
+		mbm->myalloc = true;
+
+		if (mbm->alloc.pixels == NULL) {
+			rc = ENOMEM;
+			goto error;
+		}
+	} else {
+		mbm->alloc = *alloc;
+	}
+
+	mbm->mgc = mgc;
+	*rbm = (void *)mbm;
+	return EOK;
+error:
+	if (mbm != NULL)
+		free(mbm);
+	return rc;
+}
+
+/** Destroy bitmap in memory GC.
+ *
+ * @param bm Bitmap
+ * @return EOK on success or an error code
+ */
+static errno_t mem_gc_bitmap_destroy(void *bm)
+{
+	mem_gc_bitmap_t *mbm = (mem_gc_bitmap_t *)bm;
+	if (mbm->myalloc)
+		free(mbm->alloc.pixels);
+
+	free(mbm);
+	return EOK;
+}
+
+/** Render bitmap in memory GC.
+ *
+ * @param bm Bitmap
+ * @param srect0 Source rectangle or @c NULL
+ * @param offs0 Offset or @c NULL
+ * @return EOK on success or an error code
+ */
+static errno_t mem_gc_bitmap_render(void *bm, gfx_rect_t *srect0,
+    gfx_coord2_t *offs0)
+{
+	mem_gc_bitmap_t *mbm = (mem_gc_bitmap_t *)bm;
+	gfx_rect_t srect;
+	gfx_rect_t drect;
+	gfx_coord2_t offs;
+	gfx_coord2_t dim;
+	gfx_coord_t x, y;
+	gfx_coord2_t sdims;
+	gfx_coord2_t ddims;
+	pixelmap_t smap;
+	pixelmap_t dmap;
+	pixel_t pixel;
+
+	if (srect0 != NULL)
+		gfx_rect_clip(srect0, &mbm->rect, &srect);
+	else
+		srect = mbm->rect;
+
+	if (offs0 != NULL) {
+		offs = *offs0;
+	} else {
+		offs.x = 0;
+		offs.y = 0;
+	}
+
+	/* Destination rectangle */
+	gfx_rect_translate(&offs, &srect, &drect);
+
+	gfx_coord2_subtract(&drect.p1, &drect.p0, &dim);
+
+	gfx_rect_dims(&mbm->rect, &sdims);
+	smap.width = sdims.x;
+	smap.height = sdims.y;
+	smap.data = mbm->alloc.pixels;
+
+	gfx_rect_dims(&mbm->mgc->rect, &ddims);
+	dmap.width = ddims.x;
+	dmap.height = ddims.y;
+	dmap.data = mbm->mgc->alloc.pixels;
+
+	if ((mbm->flags & bmpf_color_key) == 0) {
+		for (y = drect.p0.y; y < drect.p1.y; y++) {
+			for (x = drect.p0.x; x < drect.p1.x; x++) {
+				pixel = pixelmap_get_pixel(&smap, x - offs.x,
+				    y - offs.y);
+				pixelmap_put_pixel(&dmap, x, y, pixel);
+			}
+		}
+	} else {
+		for (y = drect.p0.y; y < drect.p1.y; y++) {
+			for (x = drect.p0.x; x < drect.p1.x; x++) {
+				pixel = pixelmap_get_pixel(&smap, x - offs.x,
+				    y - offs.y);
+				if (pixel != mbm->key_color)
+					pixelmap_put_pixel(&dmap, x, y, pixel);
+			}
+		}
+	}
+
+	mem_gc_invalidate_rect(mbm->mgc, &drect);
+	return EOK;
+}
+
+/** Get allocation info for bitmap in memory GC.
+ *
+ * @param bm Bitmap
+ * @param alloc Place to store allocation info
+ * @return EOK on success or an error code
+ */
+static errno_t mem_gc_bitmap_get_alloc(void *bm, gfx_bitmap_alloc_t *alloc)
+{
+	mem_gc_bitmap_t *mbm = (mem_gc_bitmap_t *)bm;
+	*alloc = mbm->alloc;
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/lib/memgfx/test/main.c
===================================================================
--- uspace/lib/memgfx/test/main.c	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/test/main.c	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(memgfx);
+
+PCUT_MAIN();
Index: uspace/lib/memgfx/test/memgfx.c
===================================================================
--- uspace/lib/memgfx/test/memgfx.c	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
+++ uspace/lib/memgfx/test/memgfx.c	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <gfx/bitmap.h>
+#include <gfx/color.h>
+#include <gfx/coord.h>
+#include <gfx/context.h>
+#include <gfx/render.h>
+#include <io/pixelmap.h>
+#include <memgfx/memgc.h>
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(memgfx);
+
+/** Test creating and deleting a memory GC */
+PCUT_TEST(create_delete)
+{
+	mem_gc_t *mgc;
+	gfx_rect_t rect;
+	gfx_bitmap_alloc_t alloc;
+	errno_t rc;
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 10;
+
+	alloc.pitch = (rect.p1.x - rect.p0.x) * sizeof(uint32_t);
+	alloc.off0 = 0;
+	alloc.pixels = calloc(1, alloc.pitch * (rect.p1.y - rect.p0.y));
+	PCUT_ASSERT_NOT_NULL(alloc.pixels);
+
+	rc = mem_gc_create(&rect, &alloc, &mgc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	mem_gc_delete(mgc);
+	free(alloc.pixels);
+}
+
+/** Test filling a rectangle in memory GC */
+PCUT_TEST(fill_rect)
+{
+	mem_gc_t *mgc;
+	gfx_rect_t rect;
+	gfx_rect_t frect;
+	gfx_rect_t urect;
+	gfx_bitmap_alloc_t alloc;
+	gfx_context_t *gc;
+	gfx_color_t *color;
+	gfx_coord2_t pos;
+	pixelmap_t pixelmap;
+	pixel_t pixel;
+	pixel_t expected;
+	errno_t rc;
+
+	/* Bounding rectangle for memory GC */
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 10;
+
+	alloc.pitch = (rect.p1.x - rect.p0.x) * sizeof(uint32_t);
+	alloc.off0 = 0;
+	alloc.pixels = calloc(1, alloc.pitch * (rect.p1.y - rect.p0.y));
+	PCUT_ASSERT_NOT_NULL(alloc.pixels);
+
+	rc = mem_gc_create(&rect, &alloc, &mgc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gc = mem_gc_get_ctx(mgc);
+	PCUT_ASSERT_NOT_NULL(gc);
+
+	/* Fill a rectangle */
+
+	rc = gfx_color_new_rgb_i16(0xffff, 0xffff, 0, &color);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_set_color(gc, color);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	frect.p0.x = 2;
+	frect.p0.y = 2;
+	frect.p1.x = 5;
+	frect.p1.y = 5;
+
+	rc = gfx_fill_rect(gc, &frect);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	pixelmap.width = rect.p1.x - rect.p0.x;
+	pixelmap.height = rect.p1.y - rect.p0.y;
+	pixelmap.data = alloc.pixels;
+
+	/* Check that the pixels of the rectangle are set and no others are */
+	for (pos.y = rect.p0.y; pos.y < rect.p1.y; pos.y++) {
+		for (pos.x = rect.p0.x; pos.x < rect.p1.x; pos.x++) {
+			pixel = pixelmap_get_pixel(&pixelmap, pos.x, pos.y);
+			expected = gfx_pix_inside_rect(&pos, &frect) ?
+			    PIXEL(0, 255, 255, 0) : PIXEL(0, 0, 0, 0);
+			PCUT_ASSERT_INT_EQUALS(expected, pixel);
+		}
+	}
+
+	/* Check that the update rect is equal to the filled rect */
+	mem_gc_get_update_rect(mgc, &urect);
+	PCUT_ASSERT_INT_EQUALS(frect.p0.x, urect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(frect.p0.y, urect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(frect.p1.x, urect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(frect.p1.y, urect.p1.y);
+
+	/* Check that mem_gc_clear_update_rect() clears the update rect */
+	mem_gc_clear_update_rect(mgc);
+	mem_gc_get_update_rect(mgc, &urect);
+	PCUT_ASSERT_TRUE(gfx_rect_is_empty(&urect));
+
+	/* TODO: Check clipping once memgc can support pitch != width etc. */
+
+	mem_gc_delete(mgc);
+	free(alloc.pixels);
+}
+
+/** Test rendering a bitmap in memory GC */
+PCUT_TEST(bitmap_render)
+{
+	mem_gc_t *mgc;
+	gfx_rect_t rect;
+	gfx_rect_t urect;
+	gfx_bitmap_alloc_t alloc;
+	gfx_context_t *gc;
+	gfx_coord2_t pos;
+	gfx_bitmap_params_t params;
+	gfx_bitmap_alloc_t balloc;
+	gfx_bitmap_t *bitmap;
+	pixelmap_t bpmap;
+	pixelmap_t dpmap;
+	pixel_t pixel;
+	pixel_t expected;
+	errno_t rc;
+
+	/* Bounding rectangle for memory GC */
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 10;
+
+	alloc.pitch = (rect.p1.x - rect.p0.x) * sizeof(uint32_t);
+	alloc.off0 = 0;
+	alloc.pixels = calloc(1, alloc.pitch * (rect.p1.y - rect.p0.y));
+	PCUT_ASSERT_NOT_NULL(alloc.pixels);
+
+	rc = mem_gc_create(&rect, &alloc, &mgc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gc = mem_gc_get_ctx(mgc);
+	PCUT_ASSERT_NOT_NULL(gc);
+
+	/* Create bitmap */
+
+	params.rect.p0.x = 0;
+	params.rect.p0.y = 0;
+	params.rect.p1.x = 6;
+	params.rect.p1.y = 6;
+
+	/* TODO Test client allocation */
+	rc = gfx_bitmap_create(gc, &params, NULL, &bitmap);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+
+	rc = gfx_bitmap_get_alloc(bitmap, &balloc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	bpmap.width = params.rect.p1.x - params.rect.p0.x;
+	bpmap.height = params.rect.p1.y - params.rect.p0.y;
+	bpmap.data = balloc.pixels;
+
+	/* Fill bitmap pixels with constant color */
+	for (pos.y = params.rect.p0.y; pos.y < params.rect.p1.y; pos.y++) {
+		for (pos.x =  params.rect.p0.x; pos.x <  params.rect.p1.x; pos.x++) {
+			pixelmap_put_pixel(&bpmap, pos.x, pos.y,
+			    PIXEL(0, 255, 255, 0));
+		}
+	}
+
+	dpmap.width = rect.p1.x - rect.p0.x;
+	dpmap.height = rect.p1.y - rect.p0.y;
+	dpmap.data = alloc.pixels;
+
+	/* Render the bitmap */
+	/* TODO Test rendering sub-rectangle */
+	/* TODO Test rendering with offset */
+	rc = gfx_bitmap_render(bitmap, NULL, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Check that the pixels of the rectangle are set and no others are */
+	for (pos.y = rect.p0.y; pos.y < rect.p1.y; pos.y++) {
+		for (pos.x = rect.p0.x; pos.x < rect.p1.x; pos.x++) {
+			pixel = pixelmap_get_pixel(&dpmap, pos.x, pos.y);
+			expected = gfx_pix_inside_rect(&pos, &params.rect) ?
+			    PIXEL(0, 255, 255, 0) : PIXEL(0, 0, 0, 0);
+			PCUT_ASSERT_INT_EQUALS(expected, pixel);
+		}
+	}
+
+	/* Check that the update rect is equal to the filled rect */
+	mem_gc_get_update_rect(mgc, &urect);
+	PCUT_ASSERT_INT_EQUALS(params.rect.p0.x, urect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(params.rect.p0.y, urect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(params.rect.p1.x, urect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(params.rect.p1.y, urect.p1.y);
+
+	/* Check that mem_gc_clear_update_rect() clears the update rect */
+	mem_gc_clear_update_rect(mgc);
+	mem_gc_get_update_rect(mgc, &urect);
+	PCUT_ASSERT_TRUE(gfx_rect_is_empty(&urect));
+
+	/* TODO: Check clipping once memgc can support pitch != width etc. */
+
+	mem_gc_delete(mgc);
+	free(alloc.pixels);
+}
+
+PCUT_EXPORT(memgfx);
Index: uspace/lib/meson.build
===================================================================
--- uspace/lib/meson.build	(revision 5f483bed56525671e8eedbe4fda24dc1bc0b6993)
+++ uspace/lib/meson.build	(revision d8e2485695a8df0a972901d55b5c62f1bafbe173)
@@ -82,4 +82,5 @@
 	'hound',
 	'ipcgfx',
+	'memgfx',
 	'nic',
 	'usb',
