diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp
index 980051144..7ffa47d1a 100644
--- a/src/platform/qt/InputController.cpp
+++ b/src/platform/qt/InputController.cpp
@@ -81,7 +81,9 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren
 
 	m_image.startRequestImage = [](mImageSource* context) {
 		InputControllerImage* image = static_cast<InputControllerImage*>(context);
-		image->image.load(":/res/no-cam.png");
+		if (image->image.isNull()) {
+			image->image.load(":/res/no-cam.png");
+		}
 	};
 	m_image.stopRequestImage = nullptr;
 	m_image.requestImage = [](mImageSource* context, unsigned w, unsigned h, const uint32_t** buffer, size_t* stride) {
@@ -91,10 +93,10 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren
 		const uint32_t* bits = reinterpret_cast<const uint32_t*>(image->resizedImage.constBits());
 		QSize size = image->resizedImage.size();
 		if (size.width() > w) {
-			bits += size.width() / 2;
+			bits += (size.width() - w) / 2;
 		}
 		if (size.height() > h) {
-			bits += (size.height() / 2) * size.width();
+			bits += ((size.height() - h) / 2) * size.width();
 		}
 		*buffer = bits;
 		*stride = size.width();
@@ -643,6 +645,11 @@ void InputController::releaseFocus(QWidget* focus) {
 	}
 }
 
+void InputController::loadCamImage(const QString& path) {
+	m_image.image.load(path);
+	m_image.resizedImage = QImage();
+}
+
 void InputController::increaseLuminanceLevel() {
 	setLuminanceLevel(m_luxLevel + 1);
 }
diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h
index fbad1810e..b5efea9f5 100644
--- a/src/platform/qt/InputController.h
+++ b/src/platform/qt/InputController.h
@@ -81,6 +81,8 @@ public:
 	void stealFocus(QWidget* focus);
 	void releaseFocus(QWidget* focus);
 
+	void loadCamImage(const QString& path);
+
 	mRumble* rumble();
 	mRotationSource* rotationSource();
 	mImageSource* imageSource() { return &m_image; }
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp
index effa21623..a6a992f18 100644
--- a/src/platform/qt/Window.cpp
+++ b/src/platform/qt/Window.cpp
@@ -372,6 +372,13 @@ void Window::openView(QWidget* widget) {
 	widget->show();
 }
 
+void Window::loadCamImage() {
+	QString filename = GBAApp::app()->getOpenFileName(this, tr("Select image"), tr("Image file (*.png *.gif *.jpg *.jpeg);;All files (*)"));
+	if (!filename.isEmpty()) {
+		m_inputController.loadCamImage(filename);
+	}
+}
+
 void Window::importSharkport() {
 	QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), tr("GameShark saves (*.sps *.xps)"));
 	if (!filename.isEmpty()) {
@@ -1064,6 +1071,11 @@ void Window::setupMenu(QMenuBar* menubar) {
 		addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i));
 	}
 
+	fileMenu->addSeparator();
+	QAction* camImage = new QAction(tr("Load camera image..."), fileMenu);
+	connect(camImage, &QAction::triggered, this, &Window::loadCamImage);
+	addControlledAction(fileMenu, camImage, "loadCamImage");
+
 #ifdef M_CORE_GBA
 	fileMenu->addSeparator();
 	QAction* importShark = new QAction(tr("Import GameShark Save"), fileMenu);
diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h
index 0517d7080..4298296d1 100644
--- a/src/platform/qt/Window.h
+++ b/src/platform/qt/Window.h
@@ -76,6 +76,8 @@ public slots:
 	void reloadConfig();
 	void saveConfig();
 
+	void loadCamImage();
+
 	void replaceROM();
 
 	void multiplayerChanged();