Starting to abstract renderer for image assets.
diff --git a/README.md b/README.md
index 12fa22a..182848f 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,9 @@
 
 There's a VSCode command provided to ```run tests``` from the Tasks: Run Task command palette. 
 
+## Code Formatting
+rive-cpp uses clang-format, you can install it with brew on MacOS: ```brew install clang-format```.
+
 ## Memory Checks
 Note that if you're on MacOS you'll want to install valgrind, which is somewhat complicated these days. This is the easiest solution (please PR a better one when it becomes available).
 ```
diff --git a/include/rive/assets/file_asset.hpp b/include/rive/assets/file_asset.hpp
index 50a84b5..5826e3a 100644
--- a/include/rive/assets/file_asset.hpp
+++ b/include/rive/assets/file_asset.hpp
@@ -7,6 +7,8 @@
 	class FileAsset : public FileAssetBase
 	{
 	public:
+		virtual void decode(const uint8_t* bytes) = 0;
+		StatusCode import(ImportStack& importStack) override;
 	};
 } // namespace rive
 
diff --git a/include/rive/assets/file_asset_referencer.hpp b/include/rive/assets/file_asset_referencer.hpp
new file mode 100644
index 0000000..7ba5976
--- /dev/null
+++ b/include/rive/assets/file_asset_referencer.hpp
@@ -0,0 +1,14 @@
+#ifndef _RIVE_FILE_ASSET_REFERENCER_HPP_
+#define _RIVE_FILE_ASSET_REFERENCER_HPP_
+
+namespace rive
+{
+	class FileAsset;
+	class FileAssetReferencer
+	{
+	public:
+		virtual void assets(const std::vector<FileAsset*>& assets) = 0;
+	};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/assets/image_asset.hpp b/include/rive/assets/image_asset.hpp
index c3fed2d..152ff58 100644
--- a/include/rive/assets/image_asset.hpp
+++ b/include/rive/assets/image_asset.hpp
@@ -4,9 +4,18 @@
 #include <stdio.h>
 namespace rive
 {
+	class RenderImage;
 	class ImageAsset : public ImageAssetBase
 	{
+	private:
+		RenderImage* m_RenderImage;
+
 	public:
+		ImageAsset();
+		~ImageAsset();
+
+		void decode(const uint8_t* bytes) override;
+		RenderImage* renderImage() const { return m_RenderImage; }
 	};
 } // namespace rive
 
diff --git a/include/rive/drawable.hpp b/include/rive/drawable.hpp
index 4bd3568..fc1727f 100644
--- a/include/rive/drawable.hpp
+++ b/include/rive/drawable.hpp
@@ -23,6 +23,7 @@
 		Drawable* next = nullptr;
 
 	public:
+		BlendMode blendMode() const { return (BlendMode)blendModeValue(); }
 		bool clip(Renderer* renderer) const;
 		virtual void draw(Renderer* renderer) = 0;
 		void addClippingShape(ClippingShape* shape);
diff --git a/include/rive/importers/backboard_importer.hpp b/include/rive/importers/backboard_importer.hpp
index 6d7ba8d..be97d34 100644
--- a/include/rive/importers/backboard_importer.hpp
+++ b/include/rive/importers/backboard_importer.hpp
@@ -10,12 +10,16 @@
 	class Artboard;
 	class NestedArtboard;
 	class Backboard;
+	class FileAsset;
+	class FileAssetReferencer;
 	class BackboardImporter : public ImportStackObject
 	{
 	private:
 		Backboard* m_Backboard;
 		std::unordered_map<int, Artboard*> m_ArtboardLookup;
 		std::vector<NestedArtboard*> m_NestedArtboards;
+		std::vector<FileAsset*> m_FileAssets;
+		std::vector<FileAssetReferencer*> m_FileAssetReferencers;
 		int m_NextArtboardId;
 
 	public:
@@ -23,6 +27,8 @@
 		void addArtboard(Artboard* artboard);
 		void addMissingArtboard();
 		void addNestedArtboard(NestedArtboard* artboard);
+		void addFileAsset(FileAsset* asset);
+		void addFileAssetReferencer(FileAssetReferencer* referencer);
 
 		StatusCode resolve() override;
 		const Backboard* backboard() const { return m_Backboard; }
diff --git a/include/rive/renderer.hpp b/include/rive/renderer.hpp
index edb8810..f48d5ec 100644
--- a/include/rive/renderer.hpp
+++ b/include/rive/renderer.hpp
@@ -38,6 +38,19 @@
 		virtual ~RenderPaint() {}
 	};
 
+	class RenderImage
+	{
+	protected:
+		int m_Width = 0;
+		int m_Height = 0;
+
+	public:
+		virtual ~RenderImage() {}
+		virtual bool decode(const uint8_t* bytes) = 0;
+		int width() const { return m_Width; }
+		int height() const { return m_Height; }
+	};
+
 	class RenderPath : public CommandPath
 	{
 	public:
@@ -60,6 +73,8 @@
 		virtual void transform(const Mat2D& transform) = 0;
 		virtual void drawPath(RenderPath* path, RenderPaint* paint) = 0;
 		virtual void clipPath(RenderPath* path) = 0;
+		virtual void
+		drawImage(RenderImage* image, BlendMode value, float opacity) = 0;
 
 		void computeAlignment(Mat2D& result,
 		                      Fit fit,
@@ -154,5 +169,6 @@
 
 	extern RenderPath* makeRenderPath();
 	extern RenderPaint* makeRenderPaint();
+	extern RenderImage* makeRenderImage();
 } // namespace rive
 #endif
\ No newline at end of file
diff --git a/include/rive/shapes/image.hpp b/include/rive/shapes/image.hpp
index c74c802..d36fcbd 100644
--- a/include/rive/shapes/image.hpp
+++ b/include/rive/shapes/image.hpp
@@ -1,13 +1,20 @@
 #ifndef _RIVE_IMAGE_HPP_
 #define _RIVE_IMAGE_HPP_
 #include "rive/generated/shapes/image_base.hpp"
-#include <stdio.h>
+#include "rive/assets/file_asset_referencer.hpp"
 namespace rive
 {
-	class Image : public ImageBase
+	class ImageAsset;
+	class Image : public ImageBase, public FileAssetReferencer
 	{
+	private:
+		ImageAsset* m_ImageAsset = nullptr;
+
 	public:
+		ImageAsset* imageAsset() const { return m_ImageAsset; }
 		void draw(Renderer* renderer) override;
+		StatusCode import(ImportStack& importStack) override;
+		void assets(const std::vector<FileAsset*>& assets) override;
 	};
 } // namespace rive
 
diff --git a/src/assets/file_asset.cpp b/src/assets/file_asset.cpp
new file mode 100644
index 0000000..4779d15
--- /dev/null
+++ b/src/assets/file_asset.cpp
@@ -0,0 +1,18 @@
+#include "rive/assets/file_asset.hpp"
+#include "rive/backboard.hpp"
+#include "rive/importers/backboard_importer.hpp"
+
+using namespace rive;
+
+StatusCode FileAsset::import(ImportStack& importStack)
+{
+	auto backboardImporter =
+	    importStack.latest<BackboardImporter>(Backboard::typeKey);
+	if (backboardImporter == nullptr)
+	{
+		return StatusCode::MissingObject;
+	}
+	backboardImporter->addFileAsset(this);
+
+	return Super::import(importStack);
+}
\ No newline at end of file
diff --git a/src/assets/image_asset.cpp b/src/assets/image_asset.cpp
new file mode 100644
index 0000000..22ae470
--- /dev/null
+++ b/src/assets/image_asset.cpp
@@ -0,0 +1,9 @@
+#include "rive/assets/image_asset.hpp"
+#include "rive/renderer.hpp"
+
+using namespace rive;
+
+ImageAsset::ImageAsset() : m_RenderImage(makeRenderImage()) {}
+
+ImageAsset::~ImageAsset() { delete m_RenderImage; }
+void ImageAsset::decode(const uint8_t* bytes) { m_RenderImage->decode(bytes); }
\ No newline at end of file
diff --git a/src/importers/backboard_importer.cpp b/src/importers/backboard_importer.cpp
index b85adfc..0cf0f1b 100644
--- a/src/importers/backboard_importer.cpp
+++ b/src/importers/backboard_importer.cpp
@@ -1,6 +1,7 @@
 
 #include "rive/importers/backboard_importer.hpp"
 #include "rive/nested_artboard.hpp"
+#include "rive/assets/file_asset_referencer.hpp"
 
 using namespace rive;
 
@@ -13,6 +14,16 @@
 	m_NestedArtboards.push_back(artboard);
 }
 
+void BackboardImporter::addFileAsset(FileAsset* asset)
+{
+	m_FileAssets.push_back(asset);
+}
+
+void BackboardImporter::addFileAssetReferencer(FileAssetReferencer* referencer)
+{
+	m_FileAssetReferencers.push_back(referencer);
+}
+
 void BackboardImporter::addArtboard(Artboard* artboard)
 {
 	m_ArtboardLookup[m_NextArtboardId++] = artboard;
@@ -34,5 +45,10 @@
 			}
 		}
 	}
+
+	for (auto referencer : m_FileAssetReferencers)
+	{
+		referencer->assets(m_FileAssets);
+	}
 	return StatusCode::Ok;
 }
diff --git a/src/shapes/image.cpp b/src/shapes/image.cpp
index dee6734..2256a1a 100644
--- a/src/shapes/image.cpp
+++ b/src/shapes/image.cpp
@@ -1,4 +1,8 @@
 #include "rive/shapes/image.hpp"
+#include "rive/backboard.hpp"
+#include "rive/importers/backboard_importer.hpp"
+#include "rive/assets/file_asset.hpp"
+#include "rive/assets/image_asset.hpp"
 
 using namespace rive;
 
@@ -8,4 +12,32 @@
 	{
 		return;
 	}
+	renderer->drawImage(
+	    m_ImageAsset->renderImage(), blendMode(), renderOpacity());
+}
+
+StatusCode Image::import(ImportStack& importStack)
+{
+	auto backboardImporter =
+	    importStack.latest<BackboardImporter>(Backboard::typeKey);
+	if (backboardImporter == nullptr)
+	{
+		return StatusCode::MissingObject;
+	}
+	backboardImporter->addFileAssetReferencer(this);
+
+	return Super::import(importStack);
+}
+
+void Image::assets(const std::vector<FileAsset*>& assets)
+{
+	if (assetId() < 0 || assetId() >= assets.size())
+	{
+		return;
+	}
+	auto asset = assets[assetId()];
+	if (asset->is<ImageAsset>())
+	{
+		m_ImageAsset = asset->as<ImageAsset>();
+	}
 }
\ No newline at end of file
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp
index 8e8767c..e9c1186 100644
--- a/src/shapes/shape.cpp
+++ b/src/shapes/shape.cpp
@@ -82,7 +82,7 @@
 	// blend mode changes.
 	for (auto paint : m_ShapePaints)
 	{
-		paint->blendMode((BlendMode)blendModeValue());
+		paint->blendMode(blendMode());
 	}
 }
 
diff --git a/test/assets/walle.riv b/test/assets/walle.riv
new file mode 100644
index 0000000..248193a
--- /dev/null
+++ b/test/assets/walle.riv
Binary files differ
diff --git a/test/image_asset_test.cpp b/test/image_asset_test.cpp
new file mode 100644
index 0000000..61bc219
--- /dev/null
+++ b/test/image_asset_test.cpp
@@ -0,0 +1,55 @@
+#include <rive/core/binary_reader.hpp>
+#include <rive/file.hpp>
+#include <rive/node.hpp>
+#include <rive/shapes/clipping_shape.hpp>
+#include <rive/shapes/rectangle.hpp>
+#include <rive/shapes/image.hpp>
+#include "no_op_renderer.hpp"
+#include <catch.hpp>
+#include <cstdio>
+
+TEST_CASE("image assets loads correctly", "[assets]")
+{
+	FILE* fp = fopen("../../test/assets/walle.riv", "r");
+	REQUIRE(fp != nullptr);
+
+	fseek(fp, 0, SEEK_END);
+	auto length = ftell(fp);
+	fseek(fp, 0, SEEK_SET);
+	uint8_t* bytes = new uint8_t[length];
+	REQUIRE(fread(bytes, 1, length, fp) == length);
+	auto reader = rive::BinaryReader(bytes, length);
+	rive::File* file = nullptr;
+	auto result = rive::File::import(reader, &file);
+
+	REQUIRE(result == rive::ImportResult::success);
+	REQUIRE(file != nullptr);
+	REQUIRE(file->artboard() != nullptr);
+
+	auto node = file->artboard()->find("walle");
+	REQUIRE(node != nullptr);
+	REQUIRE(node->is<rive::Image>());
+	auto walle = node->as<rive::Image>();
+	REQUIRE(walle->imageAsset() != nullptr);
+
+	auto eve_left = file->artboard()->find("eve_left");
+	REQUIRE(eve_left != nullptr);
+	REQUIRE(eve_left->is<rive::Image>());
+	REQUIRE(eve_left->as<rive::Image>()->imageAsset() != nullptr);
+
+	auto eve_right = file->artboard()->find("eve_right");
+	REQUIRE(eve_right != nullptr);
+	REQUIRE(eve_right->is<rive::Image>());
+	REQUIRE(eve_right->as<rive::Image>()->imageAsset() != nullptr);
+	REQUIRE(eve_right->as<rive::Image>()->imageAsset() != walle->imageAsset());
+	REQUIRE(eve_right->as<rive::Image>()->imageAsset() ==
+	        eve_left->as<rive::Image>()->imageAsset());
+
+	file->artboard()->updateComponents();
+
+	rive::NoOpRenderer renderer;
+	file->artboard()->draw(&renderer);
+
+	delete file;
+	delete[] bytes;
+}
\ No newline at end of file
diff --git a/test/no_op_renderer.cpp b/test/no_op_renderer.cpp
index 51d91c2..5ef624b 100644
--- a/test/no_op_renderer.cpp
+++ b/test/no_op_renderer.cpp
@@ -5,4 +5,5 @@
 {
 	RenderPaint* makeRenderPaint() { return new NoOpRenderPaint(); }
 	RenderPath* makeRenderPath() { return new NoOpRenderPath(); }
+	RenderImage* makeRenderImage() { return new NoOpRenderImage(); }
 } // namespace rive
\ No newline at end of file
diff --git a/test/no_op_renderer.hpp b/test/no_op_renderer.hpp
index f1716ac..b7ad368 100644
--- a/test/no_op_renderer.hpp
+++ b/test/no_op_renderer.hpp
@@ -5,6 +5,12 @@
 
 namespace rive
 {
+	class NoOpRenderImage : public RenderImage
+	{
+	public:
+		bool decode(const uint8_t* bytes) override { return true; }
+	};
+
 	class NoOpRenderPaint : public RenderPaint
 	{
 	public:
@@ -90,12 +96,15 @@
 
 	class NoOpRenderer : public Renderer
 	{
-		void save() {}
-		void restore() {}
-		void transform(const Mat2D& transform) {}
-		void translate(float x, float y) {}
-		void drawPath(RenderPath* path, RenderPaint* paint) {}
-		void clipPath(RenderPath* path) {}
+		void save() override {}
+		void restore() override {}
+		void transform(const Mat2D& transform) override {}
+		void drawPath(RenderPath* path, RenderPaint* paint) override {}
+		void clipPath(RenderPath* path) override {}
+		void
+		drawImage(RenderImage* image, BlendMode value, float opacity) override
+		{
+		}
 	};
 
 } // namespace rive