diff --git a/meson.build b/meson.build
index d4e69ba8a296a9f2febc3b19bc81fcd82062f031..3c1e5f9ac4be322b97081bb697c74c5937615609 100644
--- a/meson.build
+++ b/meson.build
@@ -2,7 +2,7 @@ project('libplacebo', ['c', 'cpp'],
   license: 'LGPL2.1+',
   default_options: ['c_std=c99', 'cpp_std=c++11', 'warning_level=2'],
   meson_version: '>=0.49',
-  version: '2.66.0',
+  version: '2.67.0',
 )
 
 # Version number
diff --git a/src/colorspace.c b/src/colorspace.c
index 2cd6b4808a247812056f488204d09779b96c8695..a605d67f4cc9808c04da0e9eea540c7d0c261d91 100644
--- a/src/colorspace.c
+++ b/src/colorspace.c
@@ -371,6 +371,9 @@ void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y)
 {
     *x = *y = 0;
 
+    // This is the majority of subsampled chroma content out there
+    loc = PL_DEF(loc, PL_CHROMA_TOP_LEFT);
+
     switch (loc) {
     case PL_CHROMA_LEFT:
     case PL_CHROMA_TOP_LEFT:
diff --git a/src/include/libplacebo/colorspace.h b/src/include/libplacebo/colorspace.h
index 6d8d0420c7192ed4660c7509d7448c9200707c65..4713993505becd1c9aa0a7d137b68ea1d6dc6fbf 100644
--- a/src/include/libplacebo/colorspace.h
+++ b/src/include/libplacebo/colorspace.h
@@ -342,6 +342,8 @@ enum pl_chroma_location {
 
 // Fills *x and *y with the offset in luma pixels corresponding to a given
 // chroma location.
+//
+// Note: PL_CHROMA_UNKNOWN defaults to PL_CHROMA_TOP_LEFT
 void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y);
 
 // Represents a single CIE xy coordinate (e.g. CIE Yxy with Y = 1.0)
diff --git a/src/include/libplacebo/renderer.h b/src/include/libplacebo/renderer.h
index b3ccf57ba3c69197b7629338c1b060d19b630e8b..7ecf98e310d56f4802ae2b6d46e2582808366bbc 100644
--- a/src/include/libplacebo/renderer.h
+++ b/src/include/libplacebo/renderer.h
@@ -376,6 +376,12 @@ struct pl_image {
     int width, height;
 };
 
+// Helper function to infer the chroma location offset for each plane in an
+// image. This is equivalent to calling `pl_chroma_location_offset` on all
+// subsampled planes' shift_x/shift_y variables.
+void pl_image_set_chroma_location(struct pl_image *image,
+                                  enum pl_chroma_location chroma_loc);
+
 // Represents the target of a rendering operation
 struct pl_render_target {
     // The framebuffer (or texture) we want to render to. Must have `renderable`
diff --git a/src/renderer.c b/src/renderer.c
index 8ec09569bccac7fab8fcb71dd68c2ea09967ff3f..ffa99334822eb8796389762c60faa3e0d7b8f0de 100644
--- a/src/renderer.c
+++ b/src/renderer.c
@@ -1681,6 +1681,48 @@ error:
     return false;
 }
 
+void pl_image_set_chroma_location(struct pl_image *image,
+                                  enum pl_chroma_location chroma_loc)
+{
+    // Re-use our internal helpers for this
+    struct plane_state planes[4];
+    struct plane_state *ref = NULL;
+
+    pl_assert(image->num_planes < PL_ARRAY_SIZE(planes));
+    for (int i = 0; i < image->num_planes; i++) {
+        struct plane_state *st = &planes[i];
+        *st = (struct plane_state) {
+            .plane = image->planes[i],
+            .img = { .repr = image->repr, },
+        };
+
+        st->type = detect_plane_type(st);
+        pl_assert(st->type >= 0);
+        switch (st->type) {
+        case PLANE_RGB:
+        case PLANE_LUMA:
+        case PLANE_XYZ:
+            ref = st;
+            break;
+        default: break;
+        }
+    }
+
+    if (!ref)
+        return; // no planes
+
+    int ref_w = ref->plane.texture->params.w,
+        ref_h = ref->plane.texture->params.h;
+
+    for (int i = 0; i < image->num_planes; i++) {
+        struct pl_plane *plane = &image->planes[i];
+        const struct pl_tex *tex = plane->texture;
+        bool subsampled = tex->params.w < ref_w || tex->params.h < ref_h;
+        if (subsampled)
+            pl_chroma_location_offset(chroma_loc, &plane->shift_x, &plane->shift_y);
+    }
+}
+
 void pl_render_target_from_swapchain(struct pl_render_target *out_target,
                                      const struct pl_swapchain_frame *frame)
 {