diff --git a/TODO b/TODO
index 6252fdc..bf076ae 100644
--- a/TODO
+++ b/TODO
@@ -18,6 +18,8 @@ to maintain focus until mouse release (fix scroll bars)
 [ ] Animations, somehow
 [ ] Maybe cache codepoint converted strings
 [x] Fix scroll wheel when div is scrolled
+[ ] Be consistent with the initialization methods some are foo.new() and some are foo.init()
+[ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg
 
 ## Layout
 
@@ -63,4 +65,3 @@ _ border radius
 [ ] Text Input box
 [ ] Icon Buttons
 [ ] Switch
-
diff --git a/lib/libmqoi.c3l/Makefile b/lib/libmqoi.c3l/Makefile
new file mode 100644
index 0000000..53230c5
--- /dev/null
+++ b/lib/libmqoi.c3l/Makefile
@@ -0,0 +1,6 @@
+all: thirdparty/mini-qoi/mqoi.a
+	cp thirdparty/mini-qoi/mqoi.a linux-x64/libmqoi.a
+
+thirdparty/mini-qoi/mqoi.a:
+	gcc -O2 -c -o thirdparty/mini-qoi/mqoi.o thirdparty/mini-qoi/src/mini_qoi.c
+	ar -rc thirdparty/mini-qoi/mqoi.a thirdparty/mini-qoi/mqoi.o
diff --git a/lib/libmqoi.c3l/libmqoi.c3i b/lib/libmqoi.c3l/libmqoi.c3i
new file mode 100644
index 0000000..330145d
--- /dev/null
+++ b/lib/libmqoi.c3l/libmqoi.c3i
@@ -0,0 +1,122 @@
+module mqoi;
+
+macro rgb_hash(Rgb px) => ((px.r * 3 + px.g * 5 + px.b * 7) & 0b00111111);
+macro mqoi_rgba_hash(Rgba px) => ((px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11) & 0b00111111);
+
+const uint MQOI_HEADER_SIZE = 14;
+
+const char MQOI_MASK_OP_2B = 0b11000000;
+const char MQOI_MASK_OP_8B = 0b11111111;
+
+const char MQOI_MASK_OP_LUMA_DG = 0b00111111;
+const char MQOI_MASK_OP_RUN = 0b00111111;
+
+// basic types
+
+enum DescErr : uint (uint value) {
+	MQOI_DESC_OK = 0, // The descriptor is valid
+	MQOI_DESC_INVALID_MAGIC = 1, // The magic value isn't correct
+	MQOI_DESC_INVALID_CHANNELS = 2, // The channel number isn't valid
+	MQOI_DESC_INVALID_COLORSPACE = 3, // The colorspace isn't valid
+}
+
+enum Op : char (char value) {
+	MQOI_OP2_INDEX   = (0b00 << 6),
+	MQOI_OP2_DIFF    = (0b01 << 6),
+	MQOI_OP2_LUMA    = (0b10 << 6),
+	MQOI_OP2_RUN     = (0b11 << 6),
+	MQOI_OP8_RUN_RGB  = (0b11111110),
+	MQOI_OP8_RUN_RGBA = (0b11111111),
+}
+
+enum Channels : char (char value) {
+	MQOI_CHANNELS_RGB = 3,
+	MQOI_CHANNELS_RGBA = 4,
+}
+
+enum Colorspace : char (char value) {
+	MQOI_COLORSPACE_SRGB = 0,
+	MQOI_COLORSPACE_LINEAR = 1,
+}
+
+union Rgb {
+	struct { char r, g, b; }
+	char[3] value;
+}
+
+union Rgba{
+	struct { char r, g, b, a; }
+	char[4] value;
+}
+
+struct Desc {
+	char head;
+
+	char[4] magic;
+	char[4] width; // big-endian width
+	char[4] height; // big-endian height
+	char channels;
+	char colorspace;
+}
+
+// ==== chunks ====
+
+union Chunk {
+	struct {
+		char head;
+		union {
+			Rgb rgb;
+			Rgba rgba;
+			char drdb;
+		}
+	}
+	char[5] value;
+}
+
+// ==== codecs ====
+
+struct Encoding {
+	Rgba[64] hashtable;
+	Rgba prev_px;
+	Chunk working_chunk;
+	char working_chunk_size;
+}
+
+struct Dec {
+	Rgba[64] hashtable;
+	Rgba prev_px;
+	Chunk curr_chunk;
+	bitstruct : char {
+		char curr_chunk_head : 0..3;
+		char curr_chunk_size : 4..7;
+	}
+	uint pix_left;
+}
+
+// ==== utilities ====
+
+fn void u32_write(uint * n, char * dest) @extern("mqoi_u32_write");
+fn void u32_read(char * src, uint * n) @extern("mqoi_u32_read");
+
+// ==== Desc ====
+
+fn void  desc_init(Desc* desc) @extern("mqoi_desc_init");
+fn void  desc_push(Desc* desc, char byte) @extern("mqoi_desc_push");
+fn char* desc_pop(Desc* desc) @extern("mqoi_desc_pop");
+fn char  desc_verify(Desc* desc, uint* w, uint* h) @extern("mqoi_desc_verify");
+fn bool  desc_done(Desc* desc) @extern("mqoi_desc_done");
+
+/* the encoder is still WIP
+void mqoi_enc_init(mqoi_enc_t * enc);
+void mqoi_enc_push(mqoi_enc_t * enc, Rgba * pix)
+Chunk * mqoi_enc_pop(mqoi_enc_t * enc, char * size);
+*/
+
+// ==== Dec ====
+
+fn void  dec_init(Dec* dec, uint n_pix) @extern("mqoi_dec_init");
+fn void  dec_push(Dec* dec, char byte) @extern("mqoi_dec_push");
+fn char  dec_take(Dec* dec, char* bytes) @extern("mqoi_dec_take");
+fn Rgba* dec_pop(Dec* dec) @extern("mqoi_dec_pop");
+fn bool  dec_done(Dec* dec) @extern("mqoi_dec_done"); 
+
diff --git a/lib/libmqoi.c3l/manifest.json b/lib/libmqoi.c3l/manifest.json
new file mode 100644
index 0000000..4043957
--- /dev/null
+++ b/lib/libmqoi.c3l/manifest.json
@@ -0,0 +1,9 @@
+{
+  "provides" : "mqoi",
+  "targets" : {
+    "linux-x64" : {
+      "dependencies" : [],
+      "linked-libraries" : ["mqoi", "c"]
+    }
+  }
+}
diff --git a/lib/libmqoi.c3l/project.json b/lib/libmqoi.c3l/project.json
new file mode 100644
index 0000000..78ab828
--- /dev/null
+++ b/lib/libmqoi.c3l/project.json
@@ -0,0 +1,31 @@
+{
+  // Language version of C3.
+  "langrev": "1",
+  // Warnings used for all targets.
+  "warnings": [ "no-unused" ],
+  // Directories where C3 library files may be found.
+  "dependency-search-paths": [ ".." ],
+  // Libraries to use for all targets.
+  "dependencies": [ "mqoi" ],
+  // Authors, optionally with email.
+  "authors": [ "Alessandro Mauri <alemauri001@gmail.com" ],
+  // Version using semantic versioning.
+  "version": "0.1.0",
+  // Sources compiled for all targets.
+  "sources": [ ],
+  // C sources if the project also compiles C sources
+  // relative to the project file.
+  // "c-sources": [ "csource/**" ],
+  // Output location, relative to project file.
+  "output": "build",
+  // Architecture and OS target.
+  // You can use 'c3c --list-targets' to list all valid targets.
+  // "target": "windows-x64",
+  "features": [],
+  // Global settings.
+  // CPU name, used for optimizations in the LLVM backend.
+  "cpu": "generic",
+  // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz".
+  "opt": "O0",
+  // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties.
+}
diff --git a/lib/libmqoi.c3l/thirdparty/mini-qoi b/lib/libmqoi.c3l/thirdparty/mini-qoi
new file mode 160000
index 0000000..f8b5a8a
--- /dev/null
+++ b/lib/libmqoi.c3l/thirdparty/mini-qoi
@@ -0,0 +1 @@
+Subproject commit f8b5a8a4d2eeee68b52e143060b7412ba78b5077
diff --git a/project.json b/project.json
index 8922226..0101175 100644
--- a/project.json
+++ b/project.json
@@ -2,11 +2,11 @@
   // Language version of C3.
   "langrev": "1",
   // Warnings used for all targets.
-  "warnings": [ "no-unused" ],
+  "warnings": ["no-unused"],
   // Directories where C3 library files may be found.
-  "dependency-search-paths": [ "lib" ],
+  "dependency-search-paths": ["lib"],
   // Libraries to use for all targets.
-  "dependencies": [ "raylib", "schrift", "grapheme" ],
+  "dependencies": ["raylib", "schrift", "grapheme", "mqoi"],
   "features": [
     // See rcore.c3
     //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT",
@@ -22,11 +22,11 @@
     //"RAYGUI_CUSTOM_ICONS",
   ],
   // Authors, optionally with email.
-  "authors": [ "Alessandro Mauri <ale@shitposting.expert>" ],
+  "authors": ["Alessandro Mauri <ale@shitposting.expert>"],
   // Version using semantic versioning.
   "version": "0.1.0",
   // Sources compiled for all targets.
-  "sources": [ "src/**" ],
+  "sources": ["src/**"],
   // C sources if the project also compiles C sources
   // relative to the project file.
   // "c-sources": [ "csource/**" ],
@@ -41,15 +41,15 @@
   "targets": {
     "ugui": {
       // Executable or library.
-      "type": "executable",
+      "type": "executable"
       // Additional libraries, sources
       // and overrides of global settings here.
-    },
+    }
   },
   // Global settings.
   // CPU name, used for optimizations in the LLVM backend.
   "cpu": "generic",
   // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz".
-  "opt": "O0",
+  "opt": "O0"
   // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties.
 }
diff --git a/resources/tux.qoi b/resources/tux.qoi
new file mode 100644
index 0000000..57d6333
Binary files /dev/null and b/resources/tux.qoi differ
diff --git a/src/main.c3 b/src/main.c3
index 7a88495..a5cad6f 100644
--- a/src/main.c3
+++ b/src/main.c3
@@ -81,6 +81,8 @@ fn int main(String[] args)
 	ugui::Ctx ui;
 	ui.init()!!;
 	ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!;
+	ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!;
+	ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!;
 
 	short width = 800;
 	short height = 450;
@@ -99,8 +101,11 @@ fn int main(String[] args)
 	// font stuff
 	rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS);
 	rl::Image font_atlas;
+	rl::Image sprite_atlas;
 	rl::Texture2D font_texture;
+	rl::Texture2D sprite_texture;
 	ugui::Id font_id = ui.get_font_id("font1");
+	ugui::Id sprite_id = ui.get_sprite_atlas_id("icons");
 
 	// Main loop
 	while (!rl::window_should_close()) {
@@ -164,7 +169,7 @@ fn int main(String[] args)
 			if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) {
 				io::printn("release button2");
 			}
-			
+
 			ui.layout_set_row()!!;
 			ui.layout_next_row()!!;
 			static float rf, gf, bf, af;
@@ -179,6 +184,7 @@ fn int main(String[] args)
 			ui.layout_next_column()!!;
 			ui.button_label("Continua!")!!;
 		|};
+		ui.draw_sprite("tux")!!;
 		ui.div_end()!!;
 
 		ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
@@ -205,7 +211,7 @@ fn int main(String[] args)
 			ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!;
 		|};
 		ui.div_end()!!;
-		
+
 		// Timings counter
 		TimeStats dts = draw_times.get_stats();
 		TimeStats uts = ui_times.get_stats();
@@ -237,27 +243,48 @@ fn int main(String[] args)
 				float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w;
 				rl::draw_rectangle_rounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv());
 			case ugui::CmdType.CMD_UPDATE_ATLAS:
-				if (cmd.update_atlas.id != font_id) { break; }
-				//rl::unload_image(font_atlas);
-				font_atlas.data = cmd.update_atlas.raw_buffer;
-				font_atlas.width = cmd.update_atlas.width;
-				font_atlas.height = cmd.update_atlas.height;
-				font_atlas.mipmaps = 1;
-				//font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
-				font_atlas.format = 1;
-				if (rl::is_texture_ready(font_texture)) {
-					rl::unload_texture(font_texture);
+				if (cmd.update_atlas.id == font_id) {
+					//rl::unload_image(font_atlas);
+					font_atlas.data = cmd.update_atlas.raw_buffer;
+					font_atlas.width = cmd.update_atlas.width;
+					font_atlas.height = cmd.update_atlas.height;
+					font_atlas.mipmaps = 1;
+					//font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
+					font_atlas.format = 1;
+					if (rl::is_texture_ready(font_texture)) {
+						rl::unload_texture(font_texture);
+					}
+					font_texture = rl::load_texture_from_image(font_atlas);
+				} else if (cmd.update_atlas.id == sprite_id) {
+					sprite_atlas.data = cmd.update_atlas.raw_buffer;
+					sprite_atlas.width = cmd.update_atlas.width;
+					sprite_atlas.height = cmd.update_atlas.height;
+					sprite_atlas.mipmaps = 1;
+					//sprite_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
+					sprite_atlas.format = 7;
+					if (rl::is_texture_ready(sprite_texture)) {
+						rl::unload_texture(sprite_texture);
+					}
+					sprite_texture = rl::load_texture_from_image(sprite_atlas);
 				}
-				font_texture = rl::load_texture_from_image(font_atlas);
 			case ugui::CmdType.CMD_SPRITE:
-				if (cmd.sprite.texture_id != font_id) { break; }
-				rl::Vector2 position = {
-					.x = cmd.sprite.rect.x,
-					.y = cmd.sprite.rect.y,
-				};
-				rl::begin_shader_mode(font_shader);
-				rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
-				rl::end_shader_mode();
+				if (cmd.sprite.texture_id == font_id) {
+					rl::Vector2 position = {
+						.x = cmd.sprite.rect.x,
+						.y = cmd.sprite.rect.y,
+					};
+					rl::begin_shader_mode(font_shader);
+					rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
+					rl::end_shader_mode();
+				} else if (cmd.sprite.texture_id == sprite_id) {
+					rl::Vector2 position = {
+						.x = cmd.sprite.rect.x,
+						.y = cmd.sprite.rect.y,
+					};
+					rl::draw_texture_rec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
+				} else {
+					io::printfn("unknown texture id: %d", cmd.sprite.texture_id);
+				}
 			case ugui::CmdType.CMD_SCISSOR:
 				if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) {
 					rl::end_scissor_mode();
@@ -272,7 +299,6 @@ fn int main(String[] args)
 		//draw_times.print_stats();
 		rl::end_drawing();
 		/* End Drawing */
-
 	}
 
 	rl::close_window();
diff --git a/src/ugui_atlas.c3 b/src/ugui_atlas.c3
index 0f04c08..1976ec4 100644
--- a/src/ugui_atlas.c3
+++ b/src/ugui_atlas.c3
@@ -9,6 +9,7 @@ fault UgAtlasError {
 
 enum AtlasType {
 	ATLAS_GRAYSCALE,
+	ATLAS_RGBA32,
 }
 
 // black and white atlas
@@ -28,6 +29,7 @@ macro usz AtlasType.bpp(type)
 {
 	switch (type) {
 	case ATLAS_GRAYSCALE: return 1;
+	case ATLAS_RGBA32: return 4;
 	}
 }
 
@@ -46,10 +48,25 @@ fn void Atlas.free(&atlas)
 	free(atlas.buffer);
 }
 
+
+/*
+ * pixels -> +--------------+-----+
+ *           |              |     | h
+ *           |              |     | e
+ *           |              |     | i
+ *           |              |     | g
+ *           |              |     | h
+ *           |              |     | t
+ *           +--------------+-----+
+ *           |<--- width -->|
+ *           |<----- stride ----->|
+ * bytes per pixels are inferred and have to be the same
+ * as the atlas type
+ */
 // place a rect inside the atlas
 // uses a row first algorithm
 // TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/
-fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h)
+fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride)
 {
 	Point p;
 
@@ -66,11 +83,11 @@ fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h)
 		}
 	}
 
+	usz bpp = atlas.type.bpp();
 	for (usz y = 0; y < h; y++) {
 		for (usz x = 0; x < w; x++) {
-			char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x) ..];
-			char[] pix = pixels[y*w + x ..];
-			usz bpp = atlas.type.bpp();
+			char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width*bpp + (p.x+x)*bpp ..];
+			char[] pix = pixels[(usz)y*stride*bpp + x*bpp ..];
 
 			buf[0..bpp-1] = pix[0..bpp-1];
 		}
diff --git a/src/ugui_core.c3 b/src/ugui_core.c3
index 52f9fb4..e2b9a0e 100644
--- a/src/ugui_core.c3
+++ b/src/ugui_core.c3
@@ -95,6 +95,7 @@ struct Ctx {
 	ushort width, height;
 	Style style;
 	Font font;
+	SpriteAtlas sprite_atlas;
 
 	bool has_focus;
 	struct input {
@@ -220,6 +221,7 @@ fn void Ctx.free(&ctx)
 	(void)ctx.cache.free();
 	(void)ctx.cmd_queue.free();
 	(void)ctx.font.free();
+	(void)ctx.sprite_atlas.free();
 }
 
 fn void! Ctx.frame_begin(&ctx)
@@ -281,6 +283,10 @@ fn void! Ctx.frame_end(&ctx)
 		ctx.push_update_atlas(&ctx.font.atlas)!;
 		ctx.font.should_update = false;
 	}
+	if (ctx.sprite_atlas.should_update) {
+		ctx.push_update_atlas(&ctx.sprite_atlas.atlas)!;
+		ctx.sprite_atlas.should_update = false;
+	}
 
 $if 1:
 	// draw mouse position
diff --git a/src/ugui_font.c3 b/src/ugui_font.c3
index 189fdef..546c7b9 100644
--- a/src/ugui_font.c3
+++ b/src/ugui_font.c3
@@ -144,7 +144,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code)
 	//io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d",
 	//    glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv);
 
-	Point uv = font.atlas.place(pixels, glyph.w, glyph.h)!;
+	Point uv = font.atlas.place(pixels, glyph.w, glyph.h, (ushort)img.width)!;
 	glyph.u = uv.x;
 	glyph.v = uv.y;
 
@@ -159,6 +159,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code)
 fn void Font.free(&font)
 {
 	font.atlas.free();
+	font.table.free();
 	schrift::freefont(font.sft.font);
 }
 
diff --git a/src/ugui_sprite.c3 b/src/ugui_sprite.c3
new file mode 100644
index 0000000..aff0649
--- /dev/null
+++ b/src/ugui_sprite.c3
@@ -0,0 +1,130 @@
+module ugui;
+
+import std::collections::map;
+import std::io;
+import mqoi;
+
+const usz SRITES_PER_ATLAS = 64;
+
+struct Sprite {
+	Id id;
+	ushort u, v;
+	ushort w, h;
+}
+
+def SpriteMap = map::HashMap(<Id, Sprite>);
+
+struct SpriteAtlas {
+	Id id;
+	Atlas atlas;
+	SpriteMap sprites;
+	bool should_update;
+}
+
+
+// name: some examples are "icons" or "images"
+fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height)
+{
+	// FIXME: for now only RGBA32 format is supported
+	if (type != ATLAS_RGBA32) {
+		return UgAtlasError.INVALID_TYPE?;
+	}
+
+	this.id = name.hash();
+	this.atlas.new(this.id, AtlasType.ATLAS_RGBA32, width, height)!;
+	this.sprites.new_init(capacity: SRITES_PER_ATLAS);
+	this.should_update = false;
+}
+
+fn void! SpriteAtlas.free(&this)
+{
+	this.atlas.free();
+	this.sprites.free();
+}
+
+// FIXME: this should throw an error when a different pixel format than the atlas' is used
+fn Sprite*! SpriteAtlas.insert(&this, String name, char[] pixels, ushort w, ushort h, ushort stride)
+{
+	Sprite s;
+	s.id = name.hash();
+	Point uv = this.atlas.place(pixels, w, h, stride)!;
+	s.w = w;
+	s.h = h;
+	s.u = uv.x;
+	s.v = uv.y;
+	this.sprites.set(s.id, s);
+	this.should_update = true;
+	return this.sprites.get_ref(s.id);
+}
+
+fn Sprite*! SpriteAtlas.get(&this, String name)
+{
+	Id id = name.hash();
+	return this.sprites.get_ref(id);
+}
+
+fn Sprite*! SpriteAtlas.get_by_id(&this, Id id)
+{
+	return this.sprites.get_ref(id);
+}
+
+fn void! Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h)
+{
+	ctx.sprite_atlas.init(name, type, w, h)!;
+}
+
+fn Id Ctx.get_sprite_atlas_id(&ctx, String name)
+{
+	return name.hash();
+}
+
+fn void! Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride)
+{
+	ctx.sprite_atlas.insert(name, pixels, w, h, stride)!;
+}
+
+fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path)
+{
+	mqoi::Desc image_desc;
+	uint w, h;
+
+	File file = file::open(path, "rb")!;
+	defer (void) file.close();
+
+	while (!mqoi::desc_done(&image_desc)) {
+		mqoi::desc_push(&image_desc, file.read_byte()!);
+	}
+	if (mqoi::desc_verify(&image_desc, &w, &h) != 0) {
+		return IoError.FILE_NOT_VALID?;
+	}
+
+	mqoi::Dec dec;
+	mqoi::Rgba* px;
+
+	usz idx;
+	char[] pixels = mem::new_array(char, (usz)w*h*4);
+	defer mem::free(pixels);
+
+	mqoi::dec_init(&dec, w*h);
+	while (!mqoi::dec_done(&dec)) {
+		mqoi::dec_push(&dec, file.read_byte()!);
+
+		while ((px = mqoi::dec_pop(&dec)) != null) {
+			pixels[idx..idx+3] = px.value;
+			idx += 4;
+		}
+	}
+
+	ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!;
+}
+
+// FIXME: test function, very different from every other function here
+fn void! Ctx.draw_sprite(&ctx, String name)
+{
+	Sprite* sprite = ctx.sprite_atlas.get(name)!;
+	Rect bounds = { 100, 100, sprite.w, sprite.h };
+	Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h };
+	Id tex_id = ctx.sprite_atlas.id;
+
+	return ctx.push_sprite(bounds, uv, tex_id)!;
+}