<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - resize</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/resize.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2020-05-10T00:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Resizing the QPainter canvas — How to make a QLabel drawing canvas resize with the window</title><link href="https://www.pythonguis.com/faq/resizing-the-qpainter-canvas/" rel="alternate"/><published>2020-05-10T00:00:00+00:00</published><updated>2020-05-10T00:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-05-10:/faq/resizing-the-qpainter-canvas/</id><summary type="html">When I resize the window, my QLabel canvas is not resized, and the drawing coordinates are still coming from the window, not the canvas, so everything is shifted. How do I make a QPainter canvas on a QLabel resize properly with the window?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;When I resize the window, my QLabel canvas is not resized, and the drawing coordinates are still coming from the window, not the canvas, so everything is shifted. How do I make a QPainter canvas on a QLabel resize properly with the window?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you've built a drawing canvas using a &lt;code&gt;QLabel&lt;/code&gt; and a &lt;code&gt;QPixmap&lt;/code&gt;, you've probably noticed that resizing the window doesn't resize the canvas. The pixmap stays at its original size, and worse, the mouse coordinates can end up misaligned &amp;mdash; so your drawing appears offset from where you're actually clicking. This is a common issue, and the fix involves a few small but important changes to how the canvas is set up and how it responds to resize events.&lt;/p&gt;
&lt;p&gt;Let's walk through the problem and build a working solution.&lt;/p&gt;
&lt;h2 id="why-the-canvas-doesnt-resize"&gt;Why the canvas doesn't resize&lt;/h2&gt;
&lt;p&gt;When you create a &lt;code&gt;QPixmap&lt;/code&gt; with a fixed size and set it on a &lt;code&gt;QLabel&lt;/code&gt;, the pixmap stays at that size no matter what happens to the window. The &lt;code&gt;QLabel&lt;/code&gt; might grow or shrink, but the pixmap inside it doesn't follow along. On top of that, mouse events report coordinates relative to the &lt;code&gt;QLabel&lt;/code&gt; widget, not the pixmap &amp;mdash; so if the label is bigger than the pixmap, your drawing ends up in the wrong place.&lt;/p&gt;
&lt;p&gt;To fix this, you need to do a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set a &lt;strong&gt;size policy&lt;/strong&gt; so the label participates properly in layouts.&lt;/li&gt;
&lt;li&gt;Set a &lt;strong&gt;minimum size&lt;/strong&gt; so the label can shrink as well as grow.&lt;/li&gt;
&lt;li&gt;Handle the &lt;strong&gt;resize event&lt;/strong&gt; to update the pixmap when the widget changes size.&lt;/li&gt;
&lt;li&gt;Optionally, &lt;strong&gt;keep the aspect ratio&lt;/strong&gt; and center the pixmap.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="setting-up-the-canvas-class"&gt;Setting up the canvas class&lt;/h2&gt;
&lt;p&gt;Here's a basic &lt;code&gt;Canvas&lt;/code&gt; class that handles resizing. We'll start with the setup:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6.QtCore import Qt
from PyQt6.QtGui import QColor, QGuiApplication, QPainter, QPen, QPixmap
from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QSizePolicy,
    QVBoxLayout,
    QWidget,
)
import random
import sys

SPRAY_PARTICLES = 20
SPRAY_DIAMETER = 10


class Canvas(QLabel):
    def __init__(self):
        super().__init__()
        self.pen_color = QColor("#000000")

        self.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
        )
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setMinimumSize(10, 10)

        self._create_pixmap(600, 300)

    def _create_pixmap(self, width, height):
        pixmap = QPixmap(width, height)
        pixmap.fill(QColor("#ffffff"))
        self.setPixmap(pixmap)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;A few things to note here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setSizePolicy&lt;/code&gt; with &lt;code&gt;Expanding&lt;/code&gt; tells the layout system that this widget &lt;em&gt;wants&lt;/em&gt; to grow to fill available space. Without this, the label stays at the pixmap's natural size. For more on how layouts work in PyQt6, see the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-layouts/"&gt;layouts tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setAlignment(Qt.AlignmentFlag.AlignCenter)&lt;/code&gt; centers the pixmap inside the label if they're different sizes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setMinimumSize(10, 10)&lt;/code&gt; allows the widget to shrink below the pixmap's original size. Without this, Qt prevents the widget from getting smaller than the pixmap.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="handling-the-resize-event"&gt;Handling the resize event&lt;/h2&gt;
&lt;p&gt;When the window is resized, we need to create a new pixmap that matches the widget's new size. We do this by overriding &lt;code&gt;resizeEvent&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def resizeEvent(self, event):
    self._create_pixmap(self.width(), self.height())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This replaces the pixmap with a fresh one every time the widget changes size. The downside is that any existing drawing is lost. If you want to preserve the drawing, you can scale the old pixmap onto the new one instead:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def resizeEvent(self, event):
    old_pixmap = self.pixmap()
    new_pixmap = QPixmap(self.width(), self.height())
    new_pixmap.fill(QColor("#ffffff"))
    if old_pixmap:
        scaled = old_pixmap.scaled(
            self.width(),
            self.height(),
            Qt.AspectRatioMode.KeepAspectRatio,
            Qt.TransformationMode.SmoothTransformation,
        )
        painter = QPainter(new_pixmap)
        # Center the scaled pixmap on the new canvas.
        x = (new_pixmap.width() - scaled.width()) // 2
        y = (new_pixmap.height() - scaled.height()) // 2
        painter.drawPixmap(x, y, scaled)
        painter.end()
    self.setPixmap(new_pixmap)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This approach keeps the aspect ratio of the original drawing and centers it on the new canvas. The &lt;code&gt;SmoothTransformation&lt;/code&gt; flag ensures the scaled image looks decent rather than blocky.&lt;/p&gt;
&lt;p&gt;Be aware that repeatedly scaling a pixmap will gradually degrade its quality, since each scale is a lossy operation. For a simple drawing app this is usually fine, but for something more precise you might want to store the drawing operations themselves (as a list of points, for example) and replay them onto a fresh pixmap at the new size. For more advanced drawing with QPainter, take a look at the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-bitmap-graphics/"&gt;bitmap graphics tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="fixing-the-mouse-coordinates"&gt;Fixing the mouse coordinates&lt;/h2&gt;
&lt;p&gt;With the pixmap now matching the widget size, the mouse coordinates from &lt;code&gt;mouseMoveEvent&lt;/code&gt; will line up correctly. Here's the drawing method:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def set_pen_color(self, c):
    self.pen_color = QColor(c)

def mouseMoveEvent(self, event):
    painter = QPainter(self.pixmap())
    p = QPen()
    p.setWidth(1)
    p.setColor(self.pen_color)
    painter.setPen(p)
    pos = event.position()
    for n in range(SPRAY_PARTICLES):
        xo = random.gauss(0, SPRAY_DIAMETER)
        yo = random.gauss(0, SPRAY_DIAMETER)
        painter.drawPoint(int(pos.x() + xo), int(pos.y() + yo))
    painter.end()
    self.update()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In PyQt6, mouse events use &lt;code&gt;event.position()&lt;/code&gt; which returns a &lt;code&gt;QPointF&lt;/code&gt; with floating-point coordinates. We convert to &lt;code&gt;int&lt;/code&gt; when passing to &lt;code&gt;drawPoint&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="a-note-about-setscaledcontents"&gt;A note about &lt;code&gt;setScaledContents&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;You might think &lt;code&gt;self.setScaledContents(True)&lt;/code&gt; would solve the problem &amp;mdash; it makes the pixmap visually fill the label. But it causes issues with drawing, because the pixmap itself is still its original size. The coordinates you draw at won't match what you see on screen, since Qt is scaling the display but not the underlying pixmap. It's better to handle the resizing yourself as shown above.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's a full, runnable example that puts everything together. You can spray-paint on the canvas, and it resizes with the window. If you're new to building PyQt6 applications, you might want to start with the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-first-window/"&gt;creating your first window tutorial&lt;/a&gt; and the &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-widgets/"&gt;widgets overview&lt;/a&gt; first.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import random
import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QColor, QPainter, QPen, QPixmap
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QSizePolicy,
    QVBoxLayout,
    QWidget,
)

SPRAY_PARTICLES = 20
SPRAY_DIAMETER = 10


class Canvas(QLabel):
    def __init__(self):
        super().__init__()
        self.pen_color = QColor("#000000")

        self.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
        )
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setMinimumSize(10, 10)

        self._create_pixmap(600, 300)

    def _create_pixmap(self, width, height):
        pixmap = QPixmap(width, height)
        pixmap.fill(QColor("#ffffff"))
        self.setPixmap(pixmap)

    def set_pen_color(self, c):
        self.pen_color = QColor(c)

    def mouseMoveEvent(self, event):
        painter = QPainter(self.pixmap())
        pen = QPen()
        pen.setWidth(1)
        pen.setColor(self.pen_color)
        painter.setPen(pen)
        pos = event.position()
        for n in range(SPRAY_PARTICLES):
            xo = random.gauss(0, SPRAY_DIAMETER)
            yo = random.gauss(0, SPRAY_DIAMETER)
            painter.drawPoint(int(pos.x() + xo), int(pos.y() + yo))
        painter.end()
        self.update()

    def resizeEvent(self, event):
        old_pixmap = self.pixmap()
        new_pixmap = QPixmap(self.width(), self.height())
        new_pixmap.fill(QColor("#ffffff"))
        if old_pixmap and not old_pixmap.isNull():
            scaled = old_pixmap.scaled(
                self.width(),
                self.height(),
                Qt.AspectRatioMode.KeepAspectRatio,
                Qt.TransformationMode.SmoothTransformation,
            )
            painter = QPainter(new_pixmap)
            x = (new_pixmap.width() - scaled.width()) // 2
            y = (new_pixmap.height() - scaled.height()) // 2
            painter.drawPixmap(x, y, scaled)
            painter.end()
        self.setPixmap(new_pixmap)


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Resizable Canvas")

        layout = QVBoxLayout(self)

        self.canvas = Canvas()
        layout.addWidget(self.canvas)

        # Color buttons.
        button_layout = QHBoxLayout()
        colors = [
            ("#000000", "Black"),
            ("#ff0000", "Red"),
            ("#00aa00", "Green"),
            ("#0000ff", "Blue"),
        ]
        for color, name in colors:
            btn = QPushButton(name)
            btn.setStyleSheet(f"color: {color};")
            btn.clicked.connect(
                lambda checked, c=color: self.canvas.set_pen_color(c)
            )
            button_layout.addWidget(btn)

        layout.addLayout(button_layout)
        self.resize(700, 400)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this, try resizing the window, and you'll see the canvas expands and contracts with it. The drawing stays aligned with your mouse, and any existing spray paint is preserved (scaled) when you resize.&lt;/p&gt;
&lt;p&gt;If you'd prefer the canvas to simply clear on resize &amp;mdash; which avoids the gradual quality loss from repeated scaling &amp;mdash; replace the &lt;code&gt;resizeEvent&lt;/code&gt; with the simpler version:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def resizeEvent(self, event):
    self._create_pixmap(self.width(), self.height())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Choose whichever approach fits your app best. If you want to build a more fully-featured drawing tool, you might also be interested in &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-creating-your-own-custom-widgets/"&gt;creating your own custom widgets&lt;/a&gt; where you have complete control over the paint event.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="qpainter"/><category term="qlabel"/><category term="resize"/><category term="python"/><category term="qt"/><category term="qt6"/></entry></feed>