DearPyGui handles images a little differently than most Python GUI frameworks. Instead of simply pointing to a file and placing it on screen, you first load image data into a texture, and then you display that texture using a widget. This two-step process can be a bit confusing at first, but it gives you a lot of flexibility — including the ability to update images on the fly.
In this tutorial, we'll walk through every way to add images to a DearPyGui application, starting with the simplest approach and building up to more advanced techniques.
- Understanding the Texture Registry
- Loading Images with dpg.load_image()
- Method 1: Static Textures
- Method 2: Dynamic Textures
- Method 3: Raw Textures
- Method 4: Image Buttons
- Method 5: Images on the Drawing API
- Method 6: Images on Node Editors and Plots
- Using Pillow (PIL) to Load Images
- Showing the Texture Registry (Debugging)
- Quick Reference: Choosing the Right Approach
- Summary
Understanding the Texture Registry
Before we display any image, we need to understand a core concept: the texture registry. In DearPyGui, all image data lives inside a texture registry. Think of it as a central storage area where your app keeps all its image data. Widgets then reference textures from this registry by their tag.
Here's the basic flow:
- Load image data (from a file or from code).
- Register that data as a texture in the texture registry.
- Display the texture using an image widget (or other display method).
Let's see this in practice.
Loading Images with dpg.load_image()
DearPyGui provides a built-in helper function, dpg.load_image(), that loads an image file and returns the data in exactly the format the texture registry expects.
width, height, channels, data = dpg.load_image("my_image.png")
This function returns four values:
- width — the image width in pixels
- height — the image height in pixels
- channels — the number of color channels (3 for RGB, 4 for RGBA)
- data — a flat list of pixel values, normalized to floats between 0.0 and 1.0
The load_image() function supports common formats like PNG, JPG, and BMP. If the file can't be found or loaded, it returns None — so it's good practice to check for that.
result = dpg.load_image("my_image.png")
if result is None:
print("Failed to load image!")
else:
width, height, channels, data = result
Now let's put this data to use.
Method 1: Static Textures
A static texture is the simplest and most common way to display an image. Once created, its pixel data cannot be changed. This makes it perfect for icons, logos, backgrounds, or any image that doesn't need to update.
import dearpygui.dearpygui as dpg
dpg.create_context()
width, height, channels, data = dpg.load_image("my_image.png")
with dpg.texture_registry():
dpg.add_static_texture(
width=width,
height=height,
default_value=data,
tag="my_texture"
)
with dpg.window(label="Image Example", width=600, height=400):
dpg.add_image("my_texture")
dpg.create_viewport(title="Static Image", width=700, height=500)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
Let's break down what's happening:
- We load the image file with
dpg.load_image(). - Inside a
texture_registryblock, we create a static texture and give it the tag"my_texture". - In our window, we use
dpg.add_image()and pass the texture's tag.
That's it! The image appears in your window.
Sizing the Image
By default, dpg.add_image() displays the image at its original pixel dimensions. You can override this with the width and height parameters:
dpg.add_image("my_texture", width=200, height=150)
This scales the image to 200×150 pixels in the UI, regardless of the original image dimensions.
Displaying a Portion of the Image with UV Coordinates
You can also display just a portion of the texture using UV coordinates. UV coordinates range from (0, 0) at the top-left corner to (1, 1) at the bottom-right corner.
# Display only the top-left quarter of the image
dpg.add_image("my_texture", uv_min=(0, 0), uv_max=(0.5, 0.5))
This is useful for sprite sheets or when you want to crop an image without modifying the source file.
Method 2: Dynamic Textures
What if you need to update an image after it's been created? That's where dynamic textures come in. A dynamic texture works exactly like a static texture, except you can modify its pixel data at any time.
import dearpygui.dearpygui as dpg
dpg.create_context()
# Create a 200x200 red image (RGBA)
tex_width, tex_height = 200, 200
texture_data = []
for y in range(tex_height):
for x in range(tex_width):
texture_data.extend([1.0, 0.0, 0.0, 1.0]) # Red, fully opaque
with dpg.texture_registry():
dpg.add_dynamic_texture(
width=tex_width,
height=tex_height,
default_value=texture_data,
tag="dynamic_texture"
)
def make_blue(sender, app_data):
new_data = []
for y in range(tex_height):
for x in range(tex_width):
new_data.extend([0.0, 0.0, 1.0, 1.0]) # Blue
dpg.set_value("dynamic_texture", new_data)
with dpg.window(label="Dynamic Texture", width=400, height=400):
dpg.add_image("dynamic_texture")
dpg.add_button(label="Change to Blue", callback=make_blue)
dpg.create_viewport(title="Dynamic Image", width=500, height=500)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
The key line is dpg.set_value("dynamic_texture", new_data). This replaces all the pixel data in the texture, and the displayed image updates immediately.
Dynamic textures are ideal for:
- Live previews (e.g., a color picker preview)
- Procedurally generated graphics
- Video frames or camera feeds
- Any image that changes in response to user interaction
Performance Tip
Generating large texture data in pure Python (as we did above with nested loops) can be slow. For real applications, consider using NumPy to create and manipulate pixel data, then convert it to a flat list:
import numpy as np
# Create a 200x200 green image
pixels = np.zeros((200, 200, 4), dtype=np.float32)
pixels[:, :, 1] = 1.0 # Green channel
pixels[:, :, 3] = 1.0 # Alpha channel
texture_data = pixels.flatten().tolist()
This is dramatically faster for larger images.
Method 3: Raw Textures
Raw textures are a more advanced option designed for maximum performance. Unlike static and dynamic textures, raw textures accept data as a flat array and give you direct control over the pixel format. They support formats like dpg.mvFormat_Float_rgba, dpg.mvFormat_Float_rgb, and integer-based formats.
import dearpygui.dearpygui as dpg
import array
dpg.create_context()
tex_width, tex_height = 100, 100
# Using the array module for more efficient storage
raw_data = array.array('f') # 'f' = float
for y in range(tex_height):
for x in range(tex_width):
raw_data.extend([0.0, 1.0, 0.0, 1.0]) # Green, RGBA
with dpg.texture_registry():
dpg.add_raw_texture(
width=tex_width,
height=tex_height,
default_value=raw_data,
format=dpg.mvFormat_Float_rgba,
tag="raw_texture"
)
with dpg.window(label="Raw Texture", width=300, height=300):
dpg.add_image("raw_texture")
dpg.create_viewport(title="Raw Texture Example", width=400, height=400)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
The big advantage of raw textures is that when you modify the underlying array.array (or NumPy array) data, the texture updates automatically — you don't need to call dpg.set_value(). The texture reads directly from the buffer.
def update_raw():
"""Modify the buffer in place — the texture updates automatically."""
for i in range(0, len(raw_data), 4):
raw_data[i] = 1.0 # Red
raw_data[i + 1] = 0.0 # Green off
This makes raw textures the best choice for high-performance scenarios where you're updating image data every frame, such as real-time visualizations or video playback.
Available Formats
| Format Constant | Description |
|---|---|
dpg.mvFormat_Float_rgba |
4 floats per pixel (RGBA) |
dpg.mvFormat_Float_rgb |
3 floats per pixel (RGB) |
Method 4: Image Buttons
Sometimes you want an image that the user can click — like an icon button or a clickable thumbnail. DearPyGui provides dpg.add_image_button() for exactly this purpose.
import dearpygui.dearpygui as dpg
dpg.create_context()
width, height, channels, data = dpg.load_image("button_icon.png")
with dpg.texture_registry():
dpg.add_static_texture(
width=width,
height=height,
default_value=data,
tag="button_texture"
)
def on_click(sender, app_data):
print("Image button clicked!")
with dpg.window(label="Image Button", width=400, height=300):
dpg.add_image_button(
"button_texture",
width=64,
height=64,
callback=on_click
)
dpg.create_viewport(title="Image Button Example", width=500, height=400)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
The add_image_button() widget behaves just like a regular button — it supports callbacks, can be enabled/disabled, and responds to clicks — but it renders your texture instead of text.
You can also use UV coordinates with image buttons to display a cropped portion of a texture, which is great for toolbar icons stored in a single sprite sheet:
dpg.add_image_button(
"sprite_sheet",
width=32,
height=32,
uv_min=(0.0, 0.0),
uv_max=(0.25, 0.25),
callback=on_click
)
Method 5: Images on the Drawing API
DearPyGui includes a powerful drawing API that lets you draw shapes, lines, text, and images onto a canvas. This is useful when you need precise control over image positioning, or when you're building something like a game, diagram editor, or custom visualization.
import dearpygui.dearpygui as dpg
dpg.create_context()
width, height, channels, data = dpg.load_image("my_image.png")
with dpg.texture_registry():
dpg.add_static_texture(
width=width,
height=height,
default_value=data,
tag="draw_texture"
)
with dpg.window(label="Drawing Canvas", width=600, height=500):
with dpg.drawlist(width=500, height=400):
dpg.draw_image(
"draw_texture",
pmin=(50, 50),
pmax=(250, 250)
)
dpg.create_viewport(title="Drawing API Image", width=700, height=600)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
With dpg.draw_image(), you specify:
- pmin — the top-left corner position on the canvas
- pmax — the bottom-right corner position on the canvas
The image is stretched (or shrunk) to fill the rectangle defined by these two points. You can also use uv_min and uv_max to display a portion of the texture.
Layering Images
One powerful feature of the drawing API is layering. You can draw multiple images on top of each other, combine them with shapes and text, and control the draw order:
with dpg.drawlist(width=500, height=400):
# Background image
dpg.draw_image("background_texture", pmin=(0, 0), pmax=(500, 400))
# Foreground character
dpg.draw_image("character_texture", pmin=(100, 100), pmax=(200, 200))
# Label on top
dpg.draw_text((100, 210), "Player 1", size=20, color=(255, 255, 255))
Items are drawn in order, so later items appear on top of earlier ones.
Method 6: Images on Node Editors and Plots
You can also place images onto plots and inside node editors, using the same texture-based approach.
Images on Plots
Placing an image on a plot is useful for heatmaps, annotated charts, or background reference images:
with dpg.plot(label="Plot with Image", width=500, height=400):
dpg.add_plot_axis(dpg.mvXAxis, label="X")
with dpg.plot_axis(dpg.mvYAxis, label="Y"):
dpg.add_image_series(
"my_texture",
bounds_min=(0, 0),
bounds_max=(10, 10)
)
The bounds_min and bounds_max parameters define the position in plot coordinates (not pixel coordinates), so the image scales and pans along with the plot axes.
Using Pillow (PIL) to Load Images
While dpg.load_image() works well for basic image loading, you might want to use Pillow for more advanced image manipulation — resizing, filtering, compositing, or loading formats that load_image() doesn't support.
Here's how to bridge Pillow and DearPyGui:
import dearpygui.dearpygui as dpg
from PIL import Image
import numpy as np
dpg.create_context()
# Load and process with Pillow
img = Image.open("my_image.png").convert("RGBA")
img = img.resize((200, 200)) # Resize as needed
# Convert to DearPyGui-compatible format
img_array = np.array(img, dtype=np.float32) / 255.0 # Normalize to 0.0–1.0
texture_data = img_array.flatten().tolist()
with dpg.texture_registry():
dpg.add_static_texture(
width=200,
height=200,
default_value=texture_data,
tag="pil_texture"
)
with dpg.window(label="Pillow Image", width=400, height=400):
dpg.add_image("pil_texture")
dpg.create_viewport(title="Pillow + DearPyGui", width=500, height=500)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
The critical step is normalizing the pixel values. Pillow stores pixels as integers (0–255), but DearPyGui textures expect floats (0.0–1.0). Dividing by 255.0 handles this conversion.
Showing the Texture Registry (Debugging)
When you're working with multiple textures, it can be hard to keep track of what's loaded. DearPyGui provides a built-in debug tool that shows you everything in the texture registry:
dpg.show_item_registry()
You can also make the texture registry visible with a simple call:
with dpg.texture_registry(show=True):
dpg.add_static_texture(width=w, height=h, default_value=data, tag="tex1")
dpg.add_static_texture(width=w2, height=h2, default_value=data2, tag="tex2")
Setting show=True on the texture registry opens a window that displays thumbnails of all registered textures. This is extremely helpful during development.
Quick Reference: Choosing the Right Approach
Here's a summary to help you decide which method to use:
| Method | Best For | Can Update? |
|---|---|---|
Static Texture + add_image() |
Icons, logos, static images | No |
Dynamic Texture + add_image() |
Live previews, procedural images | Yes (via set_value()) |
Raw Texture + add_image() |
High-performance updates, video | Yes (automatic from buffer) |
| Image Button | Clickable icons, toolbars | No (use dynamic texture if needed) |
| Drawing API | Precise positioning, layering, games | No (redraw to update) |
| Plot Image Series | Data visualization overlays | No (use dynamic texture if needed) |
Summary
Adding images to DearPyGui follows a consistent pattern: load your pixel data, register it as a texture, then display it with a widget. The flexibility of this approach means you can use the same texture data in multiple places — an image widget, a button, a drawing canvas, or a plot — without duplicating the data.
Start with static textures and dpg.add_image() for most use cases. When you need images that change, reach for dynamic textures. And when performance is critical, raw textures give you the fastest path to getting pixels on screen.
The most important thing is to experiment. Try loading different images, changing their sizes, combining the drawing API with image widgets, and updating textures in response to user actions. The texture system in DearPyGui is powerful, and once you're comfortable with the pattern, you'll find images easy to work with across your entire application.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick
(PySide6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!