diff --git a/src/apps/qcam/assets/shader/bayer_8.vert b/src/apps/qcam/assets/shader/bayer_8.vert
index 3695a5e910c9..fb5109eee3f5 100644
--- a/src/apps/qcam/assets/shader/bayer_8.vert
+++ b/src/apps/qcam/assets/shader/bayer_8.vert
@@ -19,6 +19,8 @@ Copyright (C) 2021, Linaro
 attribute vec4 vertexIn;
 attribute vec2 textureIn;
 
+uniform mat4 proj_matrix;
+
 uniform vec2 tex_size;	/* The texture size in pixels */
 uniform vec2 tex_step;
 
@@ -47,5 +49,5 @@ void main(void) {
     yCoord = center.y + vec4(-2.0 * tex_step.y,
                               -tex_step.y, tex_step.y, 2.0 * tex_step.y);
 
-    gl_Position = vertexIn;
+    gl_Position = proj_matrix * vertexIn;
 }
diff --git a/src/apps/qcam/assets/shader/identity.vert b/src/apps/qcam/assets/shader/identity.vert
index 12c41377cfe7..907e8741fae7 100644
--- a/src/apps/qcam/assets/shader/identity.vert
+++ b/src/apps/qcam/assets/shader/identity.vert
@@ -9,10 +9,11 @@ attribute vec4 vertexIn;
 attribute vec2 textureIn;
 varying vec2 textureOut;
 
+uniform mat4 proj_matrix;
 uniform float stride_factor;
 
 void main(void)
 {
-	gl_Position = vertexIn;
+	gl_Position = proj_matrix * vertexIn;
 	textureOut = vec2(textureIn.x * stride_factor, textureIn.y);
 }
diff --git a/src/apps/qcam/viewfinder_gl.cpp b/src/apps/qcam/viewfinder_gl.cpp
index b2096faf859f..f31956ff0504 100644
--- a/src/apps/qcam/viewfinder_gl.cpp
+++ b/src/apps/qcam/viewfinder_gl.cpp
@@ -12,6 +12,7 @@
 #include <QByteArray>
 #include <QFile>
 #include <QImage>
+#include <QMatrix4x4>
 #include <QStringList>
 
 #include <libcamera/formats.h>
@@ -464,6 +465,7 @@ bool ViewFinderGL::createFragmentShader()
 
 	vertexBuffer_.release();
 
+	projMatrixUniform_ = shaderProgram_.uniformLocation("proj_matrix");
 	textureUniformY_ = shaderProgram_.uniformLocation("tex_y");
 	textureUniformU_ = shaderProgram_.uniformLocation("tex_u");
 	textureUniformV_ = shaderProgram_.uniformLocation("tex_v");
@@ -509,7 +511,7 @@ void ViewFinderGL::initializeGL()
 	glEnable(GL_TEXTURE_2D);
 	glDisable(GL_DEPTH_TEST);
 
-	static const GLfloat coordinates[2][4][2]{
+	const GLfloat coordinates[2][4][2]{
 		{
 			/* Vertex coordinates */
 			{ -1.0f, -1.0f },
@@ -801,6 +803,17 @@ void ViewFinderGL::doRender()
 	shaderProgram_.setUniformValue(textureUniformStrideFactor_,
 				       static_cast<float>(size_.width() - 1) /
 				       (stridePixels - 1));
+
+	/*
+	 * Place the viewfinder in the centre of the widget, preserving the
+	 * aspect ratio of the image.
+	 */
+	QMatrix4x4 projMatrix;
+	QSizeF scaledSize = size_.scaled(size(), Qt::KeepAspectRatio);
+	projMatrix.scale(scaledSize.width() / size().width(),
+			 scaledSize.height() / size().height());
+
+	shaderProgram_.setUniformValue(projMatrixUniform_, projMatrix);
 }
 
 void ViewFinderGL::paintGL()
@@ -818,18 +831,14 @@ void ViewFinderGL::paintGL()
 		close();
 	}
 
-	if (image_) {
-		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+	if (!image_)
+		return;
 
-		doRender();
-		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-	}
-}
+	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
-void ViewFinderGL::resizeGL(int w, int h)
-{
-	glViewport(0, 0, w, h);
+	doRender();
+	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 }
 
 QSize ViewFinderGL::sizeHint() const
diff --git a/src/apps/qcam/viewfinder_gl.h b/src/apps/qcam/viewfinder_gl.h
index 23744b411c86..23c657bcfae2 100644
--- a/src/apps/qcam/viewfinder_gl.h
+++ b/src/apps/qcam/viewfinder_gl.h
@@ -51,7 +51,6 @@ Q_SIGNALS:
 protected:
 	void initializeGL() override;
 	void paintGL() override;
-	void resizeGL(int w, int h) override;
 	QSize sizeHint() const override;
 
 private:
@@ -88,6 +87,7 @@ private:
 
 	/* Common texture parameters */
 	GLuint textureMinMagFilters_;
+	GLuint projMatrixUniform_;
 
 	/* YUV texture parameters */
 	GLuint textureUniformU_;
