Writing

Optimising Images for Small Memory Devices with Limited Resources

4 min read

Maximising Efficiency: Image Optimisation for Small Memory Devices (ESP32 S3)

In today’s world of IoT and embedded systems, resource constraints are a challenge every developer faces, especially when dealing with small memory devices like microcontrollers. These devices often have limited storage, processing power, and memory, making it crucial to optimize every asset — and one of the most important assets in UI-heavy applications is images.

As I’ve been working on optimizing resources for my ESP32-based project with a 240x240 circular display, I realized how critical it is to streamline the images used in these systems. Whether you're working with Squareline Studio, LVGL, or similar UI libraries, image optimization becomes an essential step in ensuring the smooth performance and usability of your embedded project. In this blog post, I’ll walk you through an automated solution I created to handle image optimization effectively for small memory devices.

Why Image Optimization Matters for Small Devices

When building applications on devices with limited memory, each kilobyte matters. Large, unoptimized images can lead to performance bottlenecks, memory overflow, or increased power consumption. This is especially important for devices with:

  • Limited storage: Devices may have as little as 512KB of flash memory, which needs to accommodate both firmware and assets (e.g., images, and fonts).

  • Low processing power: Large images can be slow to load and render, especially in resource-constrained environments.

  • Power constraints: Large images result in longer data transfers and increased power consumption — a big concern for battery-powered devices.

Automating Image Optimization: The Script

To address these challenges, I developed a batch script that automates the process of optimizing PNG images, specifically tailored for projects targeting small displays like the ESP32’s 240x240 circular display. This script ensures that the images are resized, compressed, and optimized, all while maintaining image quality and minimizing file sizes.

Here’s what the script does:

  1. Trims excess transparency: It removes unnecessary transparent pixels from the edges of images, reducing the overall file size without compromising visible content.

  2. Resizes images proportionally: The script can resize images to a maximum dimension (e.g., 48x48 pixels), prioritizing the larger dimension while maintaining the aspect ratio. This step is optional and can be controlled via an argument when running the script.

  3. Compresses images: Using PNGQuant, the script compresses the PNG files, striking a balance between image quality and file size. This ensures that your assets remain crisp on screen but lightweight enough to be handled by resource-limited devices.

  4. Tracks file size reduction: The script provides feedback on how much each image was reduced, displaying the percentage and size difference before and after optimization.

#!/bin/bash
# By default, the current directory is used.
current_dir=$(pwd)
# Check if the resize argument was provided and if a max size is specified
if [ "$1" == "resize" ] && [ ! -z "$2" ]; then
    resize_images=true
    max_size=$2
else
    resize_images=false
fi
# Function to calculate the size in human-readable format
human_readable_size() {
    echo $(numfmt --to=iec-i --suffix=B --padding=7 "$1")
}
# Loop through each PNG file in the current directory
for img in "$current_dir"/*.png; do
    # Check if there are no PNG files
    if [[ ! -f "$img" ]]; then
        echo "No PNG files found in the current directory."
        exit 1
    fi
    # Get the original file size before processing
    original_size=$(stat -f%z "$img")
    # Trim excess transparency around the image
    echo "Trimming transparency from $img"
    convert "$img" -trim "$img"
    # Get the image dimensions using ImageMagick's identify command
    dimensions=$(identify -format "%wx%h" "$img")
    width=$(echo $dimensions | cut -d'x' -f1)
    height=$(echo $dimensions | cut -d'x' -f2)
    # Optionally resize the image if the resize_images argument is passed
    if [ "$resize_images" = true ]; then
        # Determine the larger dimension and resize proportionally
        if [ $width -gt $height ]; then
            larger_dimension=$width
        else
            larger_dimension=$height
        fi
        if [ $larger_dimension -gt $max_size ]; then
            echo "Resizing $img (original size: ${width}x${height}) to fit within $max_size pixels"
            # Resize the image proportionally to fit within max_size while maintaining aspect ratio
            convert "$img" -resize "${max_size}x${max_size}" "$img"
        else
            echo "$img is already smaller or equal to the maximum size ($max_size pixels), skipping resizing."
        fi
    else
        echo "Skipping resizing for $img."
    fi
    # Compress the image using PNGQuant
    pngquant --quality=65-80 --ext .png --force "$img"
    # Get the file size after processing
    final_size=$(stat -f%z "$img")
    # Calculate the reduction percentage
    reduction=$(( original_size - final_size ))
    if [ $original_size -gt 0 ]; then
        reduction_percentage=$(( 100 * reduction / original_size ))
    else
        reduction_percentage=0
    fi
    # Display file size reduction
    echo "$img: Reduced by $(human_readable_size $reduction) (${reduction_percentage}% reduction)"
    # Optional: Check if the file is larger than 50 KB after compression
    if [ $final_size -gt 51200 ]; then
        echo "WARNING: $img is still larger than 50 KB!"
    fi
done
echo "All images processed!"

Example Script Breakdown

Here’s a high-level overview of the script:

  • Trimming Transparency: Transparent regions, often found in UI icons or assets, may not be necessary, and removing them helps save valuable memory.

  • Aspect Ratio Resizing: In embedded systems, you’ll often need to resize images to fit within small screens, like 48x48 pixels for a button icon. The script ensures the larger dimension is reduced first while maintaining the aspect ratio.

  • Compression: Efficient compression reduces image sizes without sacrificing too much quality, perfect for embedded devices where every kilobyte counts.

Practical Example

Let’s say you’re working with a set of 64x64 PNG images that need to be reduced to 48x48 while keeping their aspect ratio intact. Additionally, you want to remove any unnecessary transparent borders and compress the files to minimize their size. This script will do that in one go, saving you hours of manual optimization.

optimize_images resize 48

Output might look like this:

Trimming transparency from /path/to/images/icon.png
Resizing /path/to/images/icon.png (original size: 64x64) to fit within 48 pixels
/path/to/images/icon.png: Reduced by 10.4K (22% reduction)

Benefits for Embedded Projects

By optimizing your images, you’ll:

  • Save memory: Keep your total memory footprint as small as possible, leaving more room for important code and other assets.

  • Boost performance: Smaller images load faster, making your UI more responsive.

  • Reduce power consumption: Smaller images lead to less data transfer, which can extend your device's battery life.

Final Thoughts

Image optimization isn’t just a nice-to-have when working with small memory devices — it’s necessary. As we continue to push the boundaries of what can be done with minimal hardware, smart asset management becomes one of the most important steps in ensuring smooth, power-efficient performance.

If you’re working with embedded devices, optimize your images using scripts like this. This will improve your application’s performance and ensure your project scales more effectively as it grows.

I’d love to hear about your experiences with image optimization for embedded projects. Let’s connect!

First published on Substack.