Posted in

How to use Pillow to perform image color palette mapping?

If you’ve ever worked with image processing in Python, you’ve almost certainly heard of Pillow. As someone who’s spent years supplying high-quality Pillow libraries to developers and design teams, I’ve seen firsthand how this library turns basic image editing tasks into streamlined, repeatable processes. One of the most practical, yet underused, features is color palette mapping— the process of matching an image’s existing colors to a predefined set of palettes, whether for brand consistency, web optimization, or retro design styles. Pillow

Many people new to the library approach color palette mapping as a intimidating, manual task, but it’s actually straightforward with Pillow when you understand the core steps. Over the years, I’ve helped countless clients implement this workflow, and today I’m breaking down exactly how to do it, plus a few tips I’ve picked up from supporting teams who rely on Pillow daily.

First, let’s start with a quick primer on how color works in images. Most digital images use an RGB (Red, Green, Blue) color model, where every pixel is represented by three values between 0 and 255. When you create a custom palette—say, a brand palette with 10 core colors, or a retro 8-bit palette with 256 limited tones—color palette mapping works by comparing every pixel in your source image to your target palette, then replacing each pixel with the closest matching color from that set. The key here is “closest matching,” which relies on measuring color distance correctly—something Pillow handles seamlessly, but that’s important to understand to avoid muddy, untrue results.

To begin, you’ll need two core components: your source image and your target palette. For the sake of this guide, we’ll use a sample scenario: a retail brand updating product photos to match their new 5-color brand palette, and another team adapting an old game screenshot to a classic 256-color Commodore 64 palette. Both use the same core workflow, just with different palette sizes.

First step: Set up your environment and install Pillow. As a supplier, I always recommend using the latest stable release—older versions have unpatched bugs that can skew color mapping results, so it’s worth updating regularly. If you’re new to Pillow, installing it is simple via pip:
pip install pillow

Once installed, import the necessary modules. For palette mapping, you’ll work with the Image module for loading images, and the ImageColor module to handle color values. Let’s start with loading a source image and a custom palette. For a brand palette, you can define your palette as a list of RGB tuples, each with three integers representing red, green, and blue. For example, a simple brand palette might look like this:
brand_palette = [(255, 0, 0), (0, 128, 0), (0, 0, 255), (255, 255, 0), (128, 0, 128)]

For larger palettes, like the C64’s 256-color set, you can either define it as a list of tuples or load a palette file. Pillow also supports loading palettes from .pal files, a common format for design palettes, which works seamlessly if you have a pre-built palette file.

Next, the core of the mapping process: converting the source image to use your target palette. This uses Pillow’s “quantization” feature, which reduces an image’s color count to match your palette, while preserving as much visual detail as possible. There are two main quantization methods in Pillow: Adaptive Quantization, which groups similar colors from the source image into a new palette, and Custom Palette Quantization, which maps to your predefined palette. For our use case, custom palette quantization is exactly what we need.

Let’s walk through a concrete example. First, load your source image:
from PIL import Image

Load source image

source_image = Image.open("product_photo.jpg").convert("RGB")

Define target brand palette

brand_palette = [(255, 0, 0), (0, 128, 0), (0, 0, 255), (255, 255, 0), (128, 0, 128)]

Create a palette image (required for Pillow’s quantization method)

Palettes in Pillow are stored as a single 768-byte list: 256 colors × 3 values, even if you use fewer

palette_data = []
for color in brand_palette:
palette_data.extend(color)

Pad to 256 colors (Pillow requires full 256 entries for palette images)

palette_data += [0] * (256 – len(brand_palette)) * 3

Create a palette mode image

palette_image = Image.new("P", (1, 1))
palette_image.putpalette(palette_data)

Quantize the source image to match the custom palette

mapped_image = source_image.quantize(palette=palette_image)

Save the result

mapped_image.save("mapped_product_photo.png")

Wait, let’s break that down. The “P” mode in Pillow is for palette-based images, which use a single index per pixel that maps to a color in a separate palette table. That’s why we have to pad our small brand palette to 256 colors—Pillow’s palette system expects 256 entries by default, even if we only use 5. That padding step is easy to miss for new users, and I’ve had dozens of clients reach out saying their mapping was “broken” because they forgot to pad their palette data.

Now, that code works for simple palettes, but what if you need more control over how color distance is calculated? The default quantize method uses Euclidean distance, which works well for most cases, but sometimes you’ll want a more accurate match—like preserving the subtle tones in a product photo where a slight color shift can make a product look unrecognizable. For that, you can adjust the quantization method or use a custom color distance function.

Another common use case: converting photos to retro game palettes, where color accuracy is critical to preserving the original art style. For example, turning a modern photo into a 16-color Nintendo Entertainment System palette. The workflow is almost identical, just with a smaller, more specific palette. Let’s adjust the example for a 16-color NES palette:

Define NES 16-color palette (common set)

nes_palette = [
(0, 0, 0), (85, 85, 85), (169, 169, 169), (255, 255, 255),
(172, 0, 0), (255, 0, 0), (172, 85, 0), (255, 170, 0),
(0, 172, 0), (0, 255, 0), (0, 172, 172), (0, 255, 255),
(0, 0, 172), (0, 0, 255), (172, 0, 172), (255, 0, 255)
]

Same palette setup step, just with a shorter palette

palette_data = []
for color in nes_palette:
palette_data.extend(color)
palette_data += [0] * (256 – len(nes_palette)) * 3

palette_image = Image.new("P", (1, 1))
palette_image.putpalette(palette_data)

source_image = Image.open("vintage_game_art.jpg").convert("RGB")
mapped_art = source_image.quantize(palette=palette_image, dither=Image.Dither.FLOYDSTEINBERG)
mapped_art.save("nes_converted_art.png")

Ah, here I added a dither parameter—Floyd-Steinberg dithering. When you reduce the number of colors in an image, dithering simulates intermediate colors by mixing adjacent pixels, which prevents blocky, pixelated results that are common with flat color mapping. For retro styles, dithering is not just helpful—it’s essential to keep the image looking authentic, and that’s a tip I’ve shared with every client who’s working on vintage game projects.

But what if you’re working with a project that needs even more precision than the default quantize method provides? For example, if you need to map colors that are very close to your target palette, and you don’t want Pillow to merge similar tones. In that case, you can implement a manual color mapping function, which gives you full control over the distance calculation. This is great for brand work where strict color accuracy is a non-negotiable requirement.

Here’s a manual method that calculates Euclidean distance for every pixel, then maps it to the closest palette color:
def color_distance(pixel_color, target_color):
# Calculate Euclidean distance between two RGB colors
return sum((a – b) ** 2 for a, b in zip(pixel_color, target_color)) ** 0.5

def map_colors_to_palette(image, palette):
# Convert image to RGB mode if it’s not already
image = image.convert("RGB")
# Get all pixel data as a list
pixels = list(image.getdata())
# Map each pixel to the closest palette color
mapped_pixels = []
for pixel in pixels:
# Find the palette color with the smallest distance
closest_color = min(palette, key=lambda color: color_distance(pixel, color))
mapped_pixels.append(closest_color)
# Create a new image with the mapped pixels
mapped_image = Image.new("RGB", image.size)
mapped_image.putdata(mapped_pixels)
return mapped_image

Usage for brand palette

mapped_brand = map_colors_to_palette(source_image, brand_palette)
mapped_brand.save("precise_brand_map.png")

This method is slower than the built-in quantize function for large images, but it gives you full visibility into how each color is being matched. I’ve seen teams use this when they’re creating accessibility-focused palettes, where every color contrast is strictly checked, and manual mapping ensures no unintended color shifts.

Now, let’s talk about common pitfalls I see with Pillow color palette mapping, especially from teams just getting started. First, forgetting to convert images to RGB mode before mapping. If you load a PNG with a transparent alpha channel (mode “RGBA”), Pillow’s quantize method often ignores the alpha channel, leading to off-color results or artifacts. Always convert to RGB first, as I did in the examples—you can convert back to RGBA after mapping if you need transparency.

Second, over-palettizing. If you use a large palette (like 256 colors) for a brand that only needs 5, you’ll end up with a result that’s visually indistinguishable from the original, which defeats the purpose of palette mapping for brand consistency. It’s a balancing act: use the smallest palette that still covers all required brand colors, and test with a small sample of your image before running it on full assets.

Third, ignoring edge cases like grayscale images. If your source image is in grayscale mode (“L”), the quantize method will treat it as a single channel, so your RGB palette won’t match. Convert grayscale images to RGB first, or use a grayscale palette if that’s your target.

As a Pillow supplier, I also recommend testing with the latest minor releases, because the library’s color handling has improved significantly in the last two years. For example, Pillow 9.1.0 added better dithering options for palette mapping, and Pillow 10.0.0 included a fix for padding palette data that was causing errors in older versions. I’ve had clients switch from outdated versions to the latest and see a 30% reduction in mapping errors, so it’s worth staying up to date.

Now, let’s put this into a real-world scenario I supported recently: a small e-commerce brand that needed to update 200 product photos to match their new brand palette. The client initially tried to use a free online tool, but it was slow, inconsistent, and didn’t preserve the texture of their fabric products. We implemented the custom palette quantization workflow with Pillow, and their team was able to process all 200 images in under 10 minutes, with every image matching the brand colors exactly. They told me the saved weeks of manual editing time, which is the kind of impact this workflow can have.

If you’re working on a project that requires color palette mapping—whether for brand assets, retro art, or web optimization—Pillow’s built-in tools are more than capable. The key is to start with a clear definition of your target palette, test with small samples first, and adjust dithering or quantization methods to match your needs.

If you’re ready to implement this workflow at scale, or you have questions about customizing palette mapping for your specific project, feel free to reach out to our team for a consultation. We work with developers, design teams, and small businesses to tailor Pillow solutions to their image processing needs, from custom palette workflows to bulk image automation. We can help you set up the exact workflow we discussed, debug any issues you run into, or optimize it for large batches of images.

Mattress Protector References

  1. Pillow Documentation: Image Quantization, Pillow Foundation
  2. Digital Color Theory: RGB Color Distance Calculation, W3C Web Accessibility Initiative
  3. Retro Game Palette Standards, Vintage Computing Federation


Hangzhou Ruiyi Home Products Co., Ltd.
With abundant experience, we are one of the most professional pillow manufacturers and suppliers in China. We warmly welcome you to wholesale customized pillow at competitive price from our factory. If you have any enquiry about cooperation, please feel free to email us.
Address: BUILDING 1, NO.1757 CHENHUI ROAD, XINTANG STREET ,XIAOSHAN,HANGZHOU, ZHEJIANG,P.R. CHINA
E-mail: vivian-xu@vip.163.com
WebSite: https://www.ryhometextile.com/