diff --git a/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in b/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in
index db22d7b7b26e..486cdfc5d28c 100644
--- a/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in
+++ b/python/PyQt6/core/auto_generated/qgsmaptopixel.sip.in
@@ -100,6 +100,16 @@ This method modifies the given coordinates in place. It is intended as a fast wa
 transform.
 %End
 
+    QRectF transformBounds( const QRectF &bounds ) const;
+%Docstring
+Transforms a bounding box from map coordinates to device coordinates.
+
+The returns bounding box will always completely enclose the transformed input bounding box (i.e. this
+method will grow the bounds wherever required).
+
+.. versionadded:: 3.40
+%End
+
 
 
     QgsPointXY toMapCoordinates( int x, int y ) const;
diff --git a/python/core/auto_generated/qgsmaptopixel.sip.in b/python/core/auto_generated/qgsmaptopixel.sip.in
index db22d7b7b26e..486cdfc5d28c 100644
--- a/python/core/auto_generated/qgsmaptopixel.sip.in
+++ b/python/core/auto_generated/qgsmaptopixel.sip.in
@@ -100,6 +100,16 @@ This method modifies the given coordinates in place. It is intended as a fast wa
 transform.
 %End
 
+    QRectF transformBounds( const QRectF &bounds ) const;
+%Docstring
+Transforms a bounding box from map coordinates to device coordinates.
+
+The returns bounding box will always completely enclose the transformed input bounding box (i.e. this
+method will grow the bounds wherever required).
+
+.. versionadded:: 3.40
+%End
+
 
 
     QgsPointXY toMapCoordinates( int x, int y ) const;
diff --git a/src/core/qgsmaptopixel.h b/src/core/qgsmaptopixel.h
index 09dae6441d1d..d35b55e52321 100644
--- a/src/core/qgsmaptopixel.h
+++ b/src/core/qgsmaptopixel.h
@@ -20,12 +20,10 @@
 #include "qgis_core.h"
 #include "qgis_sip.h"
 #include <QTransform>
-#include <vector>
 #include "qgis.h"
 #include "qgspointxy.h"
 
 #include <cassert>
-#include <memory>
 
 class QPoint;
 
@@ -131,6 +129,36 @@ class CORE_EXPORT QgsMapToPixel
       y = my;
     }
 
+    /**
+     * Transforms a bounding box from map coordinates to device coordinates.
+     *
+     * The returns bounding box will always completely enclose the transformed input bounding box (i.e. this
+     * method will grow the bounds wherever required).
+     *
+     * \since QGIS 3.40
+     */
+    QRectF transformBounds( const QRectF &bounds ) const
+    {
+      QPointF topLeft = bounds.topLeft();
+      QPointF topRight = bounds.topRight();
+      QPointF bottomLeft = bounds.bottomLeft();
+      QPointF bottomRight = bounds.bottomRight();
+
+      transformInPlace( topLeft.rx(), topLeft.ry() );
+      transformInPlace( topRight.rx(), topRight.ry() );
+      transformInPlace( bottomLeft.rx(), bottomLeft.ry() );
+      transformInPlace( bottomRight.rx(), bottomRight.ry() );
+
+      auto minMaxX = std::minmax( { topLeft.x(), topRight.x(), bottomLeft.x(), bottomRight.x() } );
+      auto minMaxY = std::minmax( { topLeft.y(), topRight.y(), bottomLeft.y(), bottomRight.y() } );
+
+      const double left = minMaxX.first;
+      const double right = minMaxX.second;
+      const double top = minMaxY.first;
+      const double bottom = minMaxY.second;
+      return QRectF( left, top, right - left, bottom - top );
+    }
+
     /**
      * Transforms device coordinates to map coordinates.
      *
diff --git a/tests/src/core/testqgsmaptopixel.cpp b/tests/src/core/testqgsmaptopixel.cpp
index f5ba8dad34eb..d29f82fcc0bb 100644
--- a/tests/src/core/testqgsmaptopixel.cpp
+++ b/tests/src/core/testqgsmaptopixel.cpp
@@ -30,6 +30,7 @@ class TestQgsMapToPixel: public QObject
     void fromScale();
     void equality();
     void toMapCoordinates();
+    void transformBounds();
 };
 
 void TestQgsMapToPixel::isValid()
@@ -184,6 +185,26 @@ void TestQgsMapToPixel::toMapCoordinates()
   QCOMPARE( p, QgsPointXY( 20, 20 ) );
 }
 
+void TestQgsMapToPixel::transformBounds()
+{
+  // no rotation
+  const QgsMapToPixel m2p( 1.5, 5, 15, 20, 10, 0 );
+  QRectF result = m2p.transformBounds( QRectF( 10, 20, 30, 40 ) );
+
+  QGSCOMPARENEAR( result.left(), 13.3333333333, 6 );
+  QGSCOMPARENEAR( result.right(), 33.3333333333, 6 );
+  QCOMPARE( result.top(), -25 );
+  QGSCOMPARENEAR( result.bottom(), 1.66666666667, 6 );
+
+  // with rotation
+  const QgsMapToPixel m2pRotated( 1.5, 5, 15, 20, 10, 45 );
+  result = m2pRotated.transformBounds( QRectF( 10, 20, 30, 40 ) );
+  QGSCOMPARENEAR( result.left(), 14.7140452079, 6 );
+  QGSCOMPARENEAR( result.right(), 47.7123616633, 6 );
+  QGSCOMPARENEAR( result.top(), -13.8561808316, 6 );
+  QGSCOMPARENEAR( result.bottom(), 19.1421356237, 6 );
+}
+
 QGSTEST_MAIN( TestQgsMapToPixel )
 #include "testqgsmaptopixel.moc"