Skip to content

Add blend modes and blend groups for compositing - #31162

Merged
story645 merged 10 commits into
matplotlib:mainfrom
ayshih:agg_compositing
Aug 27, 2026
Merged

Add blend modes and blend groups for compositing#31162
story645 merged 10 commits into
matplotlib:mainfrom
ayshih:agg_compositing

Conversation

@ayshih

@ayshih ayshih commented Feb 15, 2026

Copy link
Copy Markdown
Contributor

PR summary

This PR adds support for blend modes beyond alpha blending (e.g., "screen" or "hard light"), so closes #6210. With this PR, all artists can specify blend_mode, and they are supported by Agg-based and Cairo-based backends, and mostly supported by SVG/PDF/PGF backends.

Of course, mplcairo provides access to these blend modes, but this PR provides blend-mode support without needing cairo.

Update: This PR uses this functionality to fix a long-standing bug (e.g., fixes #27016) with Agg rendering of Gouraud shading, where the edges of triangles would become visible when transparency is involved.

Update: This PR adds support for blend groups, which can be isolated, knockout, or both.


✅ = supported, 🟡 = supported through rasterization, ❌ = not supported

Blend modes Agg Cairo SVG PDF PGF PS
normal
multiply, screen, overlay, darken, lighten,
color dodge, color burn, hard light, soft light,
difference, exclusion
🟡
hue, saturation, color, luminosity ✅* 🟡
knockout, erase, clear, atop, xor, plus 🟡 🟡 🟡 🟡
  • "normal" is the normal alpha blending (also known as "over" or "source over")
  • "multiply" through "exclusion" are separable blend modes (color channels are independent)
  • "hue" through "luminosity" are non-separable blend modes
  • "knockout" through "plus" are Porter Duff compositing operators, where "knockout" is also known as "source" and "erase" is also known as "destination out"
  • * Text artists disappear on some combinations of Cairo version and platform, likely a Cairo bug
Blend groups Agg Cairo SVG PDF PGF PS
neither isolated nor knockout (same as no group)
isolated only 🟡
isolated and knockout 🟡 🟡
knockout only

Agg showcase

  • Gouraud shading can look wrong with some blend modes, for the same reason it can look wrong under normal blend mode when alpha < 1, due to overlapping triangles Now fixed
agg

Cairo showcase

  • With some combinations of Cairo version and platform, text is missing in non-separable blend modes, which is presumably a bug in Cairo
  • Gouraud shading is apparently not supported by the Cairo backend, so I commented out the pcolormesh call I added support for Gouraud shading

Windows

cairo

macOS

blend_modes_cairo_macos

SVG showcase

  • These results may not render as intended depending on the SVG renderer (try non-mobile web browsers)
  • The Porter Duff compositing operators normally use a different mechanism to command the renderer, which is inaccessible through SVG XML, so are currently disabled (and fall back to "normal" with a warning)
Figure_1

PDF showcase

  • I haven't figured out how to implement the Porter Duff compositing operators

Figure_1.pdf

PGF showcase

  • The PGF and PGF->PDF output looks fine, but the PGF->PNG output via pdftocairo (on Windows) appears to screw up some colors for the non-separable blend modes
  • Gouraud shading is apparently not supported by the PGF backend, so I commented out the pcolormesh call
  • No Porter Duff compositing operators yet again

Figure_1.pgf.pdf

Generating code

import matplotlib
#matplotlib.use('TkCairo')

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Rectangle

N = 10
data = np.arange(N**2).reshape((N, N)) % (N-1)

fig, axs = plt.subplots(3, 8, figsize=(10, 5.5), layout="tight")
axs = axs.flatten()
fig.set_facecolor("none")

blend_modes = ["normal", "multiply", "screen", "overlay",
               "darken", "lighten", "color dodge", "color burn",
               "hard light", "soft light", "difference", "exclusion",
               "hue", "saturation", "color", "luminosity",
               "knockout", "erase", "clear", "atop", "xor", "plus"]

for ax in axs:
    ax.set_facecolor("none")
    ax.set_xlim(0, 1)
    ax.set_ylim(0, 1.2)
    ax.set_axis_off()

for i, blend_mode in enumerate(blend_modes):
    axs[i].imshow(data, cmap='Reds', alpha=0.75, extent=(0, 0.8, 0, 0.8))
    axs[i].imshow(data[::-1, :], cmap='Blues', alpha=0.75, extent=(0.2, 1, 0.4, 1.2),
                  blend_mode=blend_mode)
    axs[i].pcolormesh(*np.meshgrid(np.linspace(0.6, 0.9, 5), np.linspace(0.7, 1, 5)),
                      data[:5, :5], cmap='Spectral', alpha=0.75, shading='gouraud',
                      blend_mode=blend_mode)
    axs[i].text(0.05, 0.15, "Horizontal", weight="bold", color="c",
                blend_mode=blend_mode)
    axs[i].text(0.35, 0.10, "Tilted", weight="bold", color="m", rotation=45,
                blend_mode=blend_mode)
    axs[i].plot([0.1, 0.1, 0.1, 0.1, 0.2, 0.2, 0.2, 0.2], [0.7, 0.8, 0.9, 1, 0.7, 0.8, 0.9, 1],
                'p', markersize=15, markeredgecolor="orange", markerfacecolor="purple", alpha=0.75,
                blend_mode=blend_mode)
    axs[i].plot([0, 1], [1.2, 0], color="y",
                blend_mode=blend_mode)
    circ = Circle((.65, 0.5), .3, facecolor='g', alpha=0.5,
                  blend_mode=blend_mode, zorder=2)
    axs[i].add_artist(circ)

    rect = Rectangle((0, 1.2), 1, .3, facecolor='lightgray', clip_on=False)
    axs[i].add_artist(rect)
    axs[i].set_title(blend_mode)

plt.show()

Put off to future work:

  • Change the way pcolormesh.snap behaves when the mesh edges are not horizontal/vertical
  • Change the default antialiasing behavior of contourf()/pcolor()/pcolormesh() to be True
  • Agg: investigate alpha edge around Gouraud shading

PR checklist

@ayshih ayshih changed the title WIP: Add blend modes for compositing, supported by Agg backend WIP: Add blend modes for compositing, supported by Agg-based backends Feb 15, 2026
@github-actions github-actions Bot added topic: mpl_toolkit Documentation: API files in lib/ and doc/api labels Feb 15, 2026
@timhoffm

Copy link
Copy Markdown
Member

This looks interesting. Thanks for working on it.

Since I’m not into the topic I can dare to ask the stupid questions:

  • Is it correct that an Artist and its blend mode define completely how they blend with “the background” I.e. all previously drawn artists? In particular, this does not depend on the blend mode of the other artists.
  • Are all these blend modes parameter-less?

@ayshih

ayshih commented Feb 15, 2026

Copy link
Copy Markdown
Contributor Author
  • Is it correct that an Artist and its blend mode define completely how they blend with “the background” I.e. all previously drawn artists? In particular, this does not depend on the blend mode of the other artists.

Yup, that is correct: the history of how that "background" was constructed has no bearing on how the next Artist is blended in using its specific blend mode.

It's also important to remember that that the "empty" background of an Axes is solid white, and thus not actually empty as far as these blend modes are concerned. For example, using "screen" to blend in an image on a truly empty background will just return the image, but on a white background will return solid white, which can make it look instead like the image call failed. That's why I turn off the face colors in my example above.

What I still need to investigate is how my changes interact with collections of Artists. A user may want "over" blending (the default) within the collection before using a different blend mode for the collection as a whole.

  • Are all these blend modes parameter-less?

Yes. In principle, the transformation functions for the hue/saturation/color/luminosity operators could have more than one possibility, but in practice I think everyone has simply used the same functions for decades (as defined in the PDF specification).

@ayshih
ayshih force-pushed the agg_compositing branch 2 times, most recently from 2221d69 to 6d49bcf Compare February 16, 2026 04:43
Comment thread lib/matplotlib/artist.py Outdated
@anntzer

anntzer commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

This is pretty cool :-)

It's also important to remember that that the "empty" background of an Axes is solid white, and thus not actually empty as far as these blend modes are concerned. For example, using "screen" to blend in an image on a truly empty background will just return the image, but on a white background will return solid white, which can make it look instead like the image call failed. That's why I turn off the face colors in my example above.

What I still need to investigate is how my changes interact with collections of Artists. A user may want "over" blending (the default) within the collection before using a different blend mode for the collection as a whole.

Actually I suspect that another possibility is to want some nonstandard blending between multiple artists, then "over" blending of the result over the background.

In general I suspect this would be related to adding support for temporary, intermediate rendering buffers, which is also something that would be useful for other purposes e.g. contour label overplotting (#26971 (comment)).

@ayshih

ayshih commented Feb 16, 2026

Copy link
Copy Markdown
Contributor Author

By the way, I decided to rename "over" to "normal". That mode of blending is referred to as "normal" often enough, and it makes it readily apparent to users that it is the standard choice (and the default).

@ayshih
ayshih force-pushed the agg_compositing branch 4 times, most recently from f49e8d0 to f7af1e4 Compare February 17, 2026 14:15
@ayshih ayshih changed the title WIP: Add blend modes for compositing, supported by Agg-based backends WIP: Add blend modes for compositing, fully supported by Agg backend and mostly supported by others Feb 17, 2026
@ayshih ayshih changed the title WIP: Add blend modes for compositing, fully supported by Agg backend and mostly supported by others WIP: Add blend modes for compositing, fully supported by Agg backends and mostly supported by other major backends Feb 17, 2026
@ayshih

ayshih commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Given that the output is as desired in nearly all cases, is the concern that the API might change?

Yes, provisional is just our flag for "this API might change"

I think it incredibly unlikely that the blend-mode API would change. There might be reason to change the blend-group API, but that also seems unlikely to me.

If I were trying to add ArtistGroup to the API, I definitely would slap a giant "provisional" warning on that.

@story645

Copy link
Copy Markdown
Member

I think it incredibly unlikely that the blend-mode API would change.

I'm not sure we've ever actually changed our provisional API (subplot_mosaic was provisional for years), it's just a kind of emergency escape/hedge on mostly big changes.

Comment thread extern/agg24-svn/include/agg_pixfmt_rgba.h
@ayshih

ayshih commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

I have moved the fixes for contourf and pcolor/pcolormesh out of this PR to make this PR slightly more wieldy to review. The bugs are less apparent to users because it requires antialiasing to be turned on, which is not the default. I'll PR those fixes after this PR is merged.

I have retained the fix to Agg Gouraud shading because the bug is easier to be encountered by users and would be immediately seen in the blend-mode gallery.

Comment thread lib/matplotlib/artist.py Outdated
Comment thread lib/matplotlib/artist.py Outdated
Comment thread src/_backend_agg.h
Comment thread lib/matplotlib/backends/backend_agg.py Outdated
Comment thread lib/matplotlib/tests/test_backends_rendering.py Outdated
@QuLogic

QuLogic commented Aug 25, 2026

Copy link
Copy Markdown
Member

Also, do you want to squash merge this when complete?

@ayshih

ayshih commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Also, do you want to squash merge this when complete?

This PR feels too hefty to be a single commit. Once the PR is approved, my inclination is to squash the review-triggered commits back into the first ~10 commits in the appropriate places.

Comment thread galleries/users_explain/colors/blend_modes.py Outdated
Comment thread galleries/users_explain/colors/blend_modes.py Outdated
Comment thread extern/agg24-svn/include/agg_pixfmt_rgba.h

@story645 story645 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor clarification on knockout and a bit confused on blend modes but otherwise this is awesome and thanks for your patience. Also sorry for the delay in getting back to you.

Also you might wanna make a blending mode :mpl_type:

def _mpltype_role(name, rawtext, text, lineno, inliner, options=None, content=None):

Comment thread galleries/users_explain/colors/blend_groups.py Outdated
@ayshih

ayshih commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Also you might wanna make a blending mode :mpl_type:

Done!

This PR feels too hefty to be a single commit. Once the PR is approved, my inclination is to squash the review-triggered commits back into the first ~10 commits in the appropriate places.

I have now done this, so this PR is ready for (non-squashed) merging.

Blending and compositing groups of artists
==========================================

An advanced technique of blending artists (see :ref:`blend-modes`) is to use a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion, but since blend groups is an advanced topic depending on blend modes, I'd suggest making this a subsection of an overall "blend modes and groups" page with two subsections.

  • Artist blending and compositing:
    • per artist blending and compositing
    • blend groups to create layers of composition.

I'm not sure a reader who doesn't know these terms would know which of these to drill down into.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine as is b/c @ayshih structured it so the blend_modes page comes first in the order.

@story645

Copy link
Copy Markdown
Member

Congrats @ayshih!

Appveyor and wasm failures are unrelated. Discussed on call and decided that doc changes can happen in follow ups as there are arguments for both approaches.

@ayshih

ayshih commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews and merge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: diagonal lines in pcolormesh with Gouraud shading and transparency Alternative compositing methods

8 participants