diff --git a/bootloader/bootloader-display.c b/bootloader/bootloader-display.c
index 8c13116d78e937292ce8499c7014dce19b536f07..d04db551d6833aca9391cd5aad80139792ecfdf4 100644
--- a/bootloader/bootloader-display.c
+++ b/bootloader/bootloader-display.c
@@ -6,31 +6,18 @@
 #include "gfx.h"
 #include "display.h"
 
-/*
- * "Decompress" splash-screen image.  The algorithm works as follows:
- *
- * Each byte encodes up to 127 pixels in either white or black.  The most
- * significant bit determines the color, the remaining 7 bits determine the
- * amount.
- */
 static void bootloader_display_splash(void)
 {
-	int idx = 0;
-
-	Color white = gfx_color(&display_screen, WHITE);
-	Color black = gfx_color(&display_screen, BLACK);
-	for (int i = 0; i < sizeof(splash); i++) {
-		Color color    = (splash[i] & 0x80) ? white : black;
-		uint8_t length = splash[i] & 0x7f;
-
-		for (int j = 0; j < length; j++) {
-			uint16_t x = idx % 160;
-			uint16_t y = idx / 160;
-			gfx_setpixel(&display_screen, x, y, color);
-			idx++;
-		}
-	}
-
+	gfx_copy_region(
+		&display_screen,
+		0,
+		0,
+		160,
+		80,
+		GFX_RLE_MONO,
+		sizeof(splash),
+		(const void *)(splash)
+	);
 	gfx_update(&display_screen);
 }
 
diff --git a/epicardium/main.c b/epicardium/main.c
index 3d40ceb8ecbcbf09418b74a0c54adc8b3e7b1780..95319203b53b33b0c099d0b7ecaa50f3ea3e50d8 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -34,8 +34,15 @@ int main(void)
 	mxc_delay(500000);
 	epic_disp_clear(0x0000);
 	if (strcmp(CARD10_VERSION, "v1.11") == 0) {
-		gfx_copy_region_raw(
-			&display_screen, 0, 0, 160, 80, 2, version_splash
+		gfx_copy_region(
+			&display_screen,
+			0,
+			0,
+			160,
+			80,
+			GFX_RAW,
+			sizeof(version_splash),
+			version_splash
 		);
 	} else {
 		const int off = (160 - (int)strlen(version_buf) * 14) / 2;
diff --git a/hw-tests/dual-core/main.c b/hw-tests/dual-core/main.c
index 60636833753d48b3812833266f01876297037a1d..30026b8051f1e64f775dbcee8df46fd7b1d8615b 100644
--- a/hw-tests/dual-core/main.c
+++ b/hw-tests/dual-core/main.c
@@ -3,27 +3,34 @@
  ******************************************************************************/
 
 /***** Includes *****/
-#include "pmic.h"
-#include "leds.h"
 #include "card10.h"
+#include "leds.h"
+#include "pmic.h"
 
-#include "gfx.h"
 #include "display.h"
+#include "gfx.h"
 
 #include "tmr_utils.h"
 
-#include <stdio.h>
+#include <Heart.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <string.h>
-#include <Heart.h>
 
 int main(void)
 {
 	card10_init();
 	card10_diag();
 
-	gfx_copy_region_raw(
-		&display_screen, 0, 0, 160, 80, 2, (const void *)(Heart)
+	gfx_copy_region(
+		&display_screen,
+		0,
+		0,
+		160,
+		80,
+		GFX_RAW,
+		sizeof(Heart),
+		(const void *)(Heart)
 	);
 	gfx_update(&display_screen);
 
diff --git a/hw-tests/hello-world/main.c b/hw-tests/hello-world/main.c
index 87fdab3ba7d8bf33eea462ce5b71d0bf1eacbd5e..5f2a82aad5b14a52cfa94ee3e3f6acf891a3dfa3 100644
--- a/hw-tests/hello-world/main.c
+++ b/hw-tests/hello-world/main.c
@@ -30,8 +30,15 @@ int main(void)
 	card10_init();
 	card10_diag();
 
-	gfx_copy_region_raw(
-		&display_screen, 0, 0, 160, 80, 2, (const void *)(Heart)
+	gfx_copy_region(
+		&display_screen,
+		0,
+		0,
+		160,
+		80,
+		GFX_RAW,
+		sizeof(Heart),
+		(const void *)(Heart)
 	);
 	gfx_update(&display_screen);
 
diff --git a/hw-tests/ips/main.c b/hw-tests/ips/main.c
index 6aa51eae2c518e69219f7e16af89e39b33a4cb24..b638a7832310f0d681c57f979001b595fe1e140c 100644
--- a/hw-tests/ips/main.c
+++ b/hw-tests/ips/main.c
@@ -37,22 +37,24 @@ int main(void)
 	gfx_line(&display_screen, 100, 10, 70, 40, 2, yellow);
 	gfx_circle(&display_screen, 85, 25, 22, 2, green);
 
-	gfx_copy_region_raw(
+	gfx_copy_region(
 		&display_screen,
 		120,
 		0,
 		40,
 		40,
-		2,
+		GFX_RAW,
+		40 * 40 * 2,
 		(const void *)(gImage_40X40)
 	);
-	gfx_copy_region_raw(
+	gfx_copy_region(
 		&display_screen,
 		0,
 		0,
 		160,
 		80,
-		2,
+		GFX_RAW,
+		160 * 80 * 2,
 		(const void *)(gImage_160X80)
 	);
 	gfx_update(&display_screen);
diff --git a/lib/gfx/gfx.c b/lib/gfx/gfx.c
index 49b8d3a8d618bf49c1ed849f49c97316b9210496..8e6c6115ce28b399f85fe3d8f33737553a2d4de4 100644
--- a/lib/gfx/gfx.c
+++ b/lib/gfx/gfx.c
@@ -273,15 +273,17 @@ Color gfx_color(struct gfx_region *reg, enum gfx_color color)
 	return gfx_color_rgb(reg, c->r, c->g, c->b);
 }
 
-void gfx_copy_region_raw(
+static void gfx_copy_region_raw(
 	struct gfx_region *reg,
 	int x,
 	int y,
 	int w,
 	int h,
-	size_t bpp,
+	size_t size,
 	const void *p
 ) {
+	size_t bpp = size / (w * h);
+
 	for (int y_ = 0; y_ < h; y_++) {
 		for (int x_ = 0; x_ < w; x_++) {
 			Color c;
@@ -299,6 +301,96 @@ void gfx_copy_region_raw(
 	}
 }
 
+static void gfx_copy_region_mono(
+	struct gfx_region *reg,
+	int x,
+	int y,
+	int w,
+	int h,
+	size_t size,
+	const void *p
+) {
+	const char *bp = p;
+	int bit        = 0;
+	Color white    = gfx_color(reg, WHITE);
+	Color black    = gfx_color(reg, BLACK);
+
+	for (int y_ = 0; y_ < h; y_++) {
+		for (int x_ = 0; x_ < w; x_++) {
+			int value = *bp & (1 << bit);
+			if (++bit >= 8) {
+				bp++;
+				bit %= 8;
+
+				if ((const void *)(bp) >= (p + size))
+					return;
+			}
+
+			Color c = value ? white : black;
+			gfx_setpixel(reg, x + x_, y + y_, c);
+		}
+	}
+}
+
+/*
+ * "Decompress" the image.  The algorithm works as follows:
+ *
+ * Each byte encodes up to 127 pixels in either white or black.  The most
+ * significant bit determines the color, the remaining 7 bits determine the
+ * amount.
+ */
+static void gfx_copy_region_rle_mono(
+	struct gfx_region *reg,
+	int x,
+	int y,
+	int w,
+	int h,
+	size_t size,
+	const void *p
+) {
+	const char *data = p;
+	int idx          = 0;
+	Color white      = gfx_color(reg, WHITE);
+	Color black      = gfx_color(reg, BLACK);
+
+	for (int i = 0; i < size; i++) {
+		Color color    = (data[i] & 0x80) ? white : black;
+		uint8_t length = data[i] & 0x7f;
+
+		for (int j = 0; j < length; j++) {
+			uint16_t x = idx % w;
+			uint16_t y = idx / w;
+			gfx_setpixel(reg, x, y, color);
+			idx++;
+		}
+	}
+}
+
+void gfx_copy_region(
+	struct gfx_region *reg,
+	int x,
+	int y,
+	int w,
+	int h,
+	enum gfx_encoding encoding,
+	size_t size,
+	const void *p
+) {
+	switch (encoding) {
+	case GFX_RAW:
+		gfx_copy_region_raw(reg, x, y, w, h, size, p);
+		break;
+	case GFX_MONO:
+		gfx_copy_region_mono(reg, x, y, w, h, size, p);
+		break;
+	case GFX_RLE_MONO:
+		gfx_copy_region_rle_mono(reg, x, y, w, h, size, p);
+		break;
+	default:
+		break;
+	}
+}
+
 void gfx_copy_raw(struct gfx_region *reg, const void *p, size_t size)
 {
 	fb_copy_raw(reg->fb, p, size);
diff --git a/lib/gfx/gfx.h b/lib/gfx/gfx.h
index 0f64a429b59170182077b7d01e2d1cd027743974..4c9a16e508fef1ac4d7346d7adeeee62d71d5c1f 100644
--- a/lib/gfx/gfx.h
+++ b/lib/gfx/gfx.h
@@ -43,6 +43,12 @@ enum gfx_color {
 	COLORS
 };
 
+enum gfx_encoding {
+	GFX_RAW,
+	GFX_MONO,
+	GFX_RLE_MONO
+};
+
 struct gfx_color_rgb {
 	uint8_t r;
 	uint8_t g;
@@ -51,8 +57,9 @@ struct gfx_color_rgb {
 
 Color gfx_color(struct gfx_region *reg, enum gfx_color color);
 
-void gfx_copy_region_raw(struct gfx_region *reg, int x, int y, int w, int h,
-					         size_t bpp, const void *p);
+void gfx_copy_region(struct gfx_region *reg, int x, int y, int w, int h,
+				enum gfx_encoding encoding, size_t size,
+							 const void *p);
 void gfx_copy_raw(struct gfx_region *reg, const void *p, size_t size);
 
 #endif