diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h
index 6f053877bbc1d74cb7153fe8d58ae8463d35fbf4..d68f535a14665cb7198d1faea570eb338ffb92cc 100644
--- a/include/libweston/libweston.h
+++ b/include/libweston/libweston.h
@@ -374,6 +374,7 @@ struct weston_output {
 	bool enabled; /**< is in the output_list, not pending list */
 	int scale;
 
+	struct weston_color_profile *color_profile;
 	struct weston_color_transform *from_sRGB_to_output;
 	struct weston_color_transform *from_sRGB_to_blend;
 	struct weston_color_transform *from_blend_to_output;
@@ -2082,6 +2083,10 @@ void
 weston_output_set_transform(struct weston_output *output,
 			    uint32_t transform);
 
+bool
+weston_output_set_color_profile(struct weston_output *output,
+				struct weston_color_profile *cprof);
+
 void
 weston_output_init(struct weston_output *output,
 		   struct weston_compositor *compositor,
diff --git a/libweston/color-lcms/color-lcms.c b/libweston/color-lcms/color-lcms.c
index 16e998126902885126a504f9271b6c69e4b45fca..18340a39316adf287c6414fa95c3d401ece4b3fb 100644
--- a/libweston/color-lcms/color-lcms.c
+++ b/libweston/color-lcms/color-lcms.c
@@ -57,6 +57,10 @@ cmlcms_get_surface_color_transform(struct weston_color_manager *cm_base,
 	};
 	struct cmlcms_color_transform *xform;
 
+	/* TODO: use output color profile */
+	if (output->color_profile)
+		return false;
+
 	xform = cmlcms_color_transform_get(cm, &param);
 	if (!xform)
 		return false;
@@ -82,6 +86,10 @@ cmlcms_get_output_color_transform(struct weston_color_manager *cm_base,
 	};
 	struct cmlcms_color_transform *xform;
 
+	/* TODO: use output color profile */
+	if (output->color_profile)
+		return false;
+
 	xform = cmlcms_color_transform_get(cm, &param);
 	if (!xform)
 		return false;
@@ -96,6 +104,11 @@ cmlcms_get_sRGB_to_output_color_transform(struct weston_color_manager *cm_base,
 					  struct weston_color_transform **xform_out)
 {
 	/* Assumes output color space is sRGB SDR */
+
+	/* TODO: use output color profile */
+	if (output->color_profile)
+		return false;
+
 	/* Identity transform */
 	*xform_out = NULL;
 
@@ -114,6 +127,10 @@ cmlcms_get_sRGB_to_blend_color_transform(struct weston_color_manager *cm_base,
 	};
 	struct cmlcms_color_transform *xform;
 
+	/* TODO: use output color profile */
+	if (output->color_profile)
+		return false;
+
 	xform = cmlcms_color_transform_get(cm, &param);
 	if (!xform)
 		return false;
diff --git a/libweston/color-noop.c b/libweston/color-noop.c
index 557797dcec08b244f7a53a47602f15f2a7f79570..30a0762b2be5415978a4c458e34080110aa078df 100644
--- a/libweston/color-noop.c
+++ b/libweston/color-noop.c
@@ -73,7 +73,7 @@ cmnoop_get_surface_color_transform(struct weston_color_manager *cm_base,
 				   struct weston_surface_color_transform *surf_xform)
 {
 	/* TODO: Assert surface has no colorspace set */
-	/* TODO: Assert output has no colorspace set */
+	assert(output->color_profile == NULL);
 
 	/* Identity transform */
 	surf_xform->transform = NULL;
@@ -87,7 +87,7 @@ cmnoop_get_output_color_transform(struct weston_color_manager *cm_base,
 				  struct weston_output *output,
 				  struct weston_color_transform **xform_out)
 {
-	/* TODO: Assert output has no colorspace set */
+	assert(output->color_profile == NULL);
 
 	/* Identity transform */
 	*xform_out = NULL;
@@ -100,7 +100,7 @@ cmnoop_get_sRGB_to_output_color_transform(struct weston_color_manager *cm_base,
 					  struct weston_output *output,
 					  struct weston_color_transform **xform_out)
 {
-	/* TODO: Assert output has no colorspace set */
+	assert(output->color_profile == NULL);
 
 	/* Identity transform */
 	*xform_out = NULL;
@@ -113,7 +113,7 @@ cmnoop_get_sRGB_to_blend_color_transform(struct weston_color_manager *cm_base,
 					 struct weston_output *output,
 					 struct weston_color_transform **xform_out)
 {
-	/* TODO: Assert output has no colorspace set */
+	assert(output->color_profile == NULL);
 
 	/* Identity transform */
 	*xform_out = NULL;
diff --git a/libweston/color.c b/libweston/color.c
index 90f332c5de990ca43c3e61ba690dc960811e94af..405e0965cdaf157e04cd6c39cf201e055ec8f2c4 100644
--- a/libweston/color.c
+++ b/libweston/color.c
@@ -181,6 +181,8 @@ void
 weston_surface_color_transform_fini(struct weston_surface_color_transform *surf_xform)
 {
 	weston_color_transform_unref(surf_xform->transform);
+	surf_xform->transform = NULL;
+	surf_xform->identity_pipeline = false;
 }
 
 /**
diff --git a/libweston/compositor.c b/libweston/compositor.c
index 11bb820c8b21c34128b25694efc9b932d9cbf480..5e0f9165b75e3488c89eb3a8688518f8fb59522d 100644
--- a/libweston/compositor.c
+++ b/libweston/compositor.c
@@ -6310,6 +6310,9 @@ weston_output_set_color_transforms(struct weston_output *output)
 	output->from_sRGB_to_output = sRGB_to_output;
 	output->from_sRGB_to_blend = sRGB_to_blend;
 
+	weston_log("Output '%s' using color profile: %s\n", output->name,
+		   weston_color_profile_get_description(output->color_profile));
+
 	return true;
 }
 
@@ -6497,6 +6500,62 @@ weston_output_set_transform(struct weston_output *output,
 	}
 }
 
+/** Set output's color profile
+ *
+ * \param output The output to change.
+ * \param cprof The color profile to set. Can be NULL for default sRGB profile.
+ * \return True on success, or false on failure.
+ *
+ * The color profile must have WESTON_COLOR_PROFILE_KIND_OUTPUT bit,
+ * or it must be NULL.
+ *
+ * Calling this function changes the color profile of the output. This causes
+ * all existing weston_color_transform objects related to this output via
+ * paint nodes to be unreferenced and later re-created on demand.
+ *
+ * This function may not be called from within weston_output_repaint().
+ *
+ * On failure, nothing is changed.
+ *
+ * \ingroup output
+ */
+WL_EXPORT bool
+weston_output_set_color_profile(struct weston_output *output,
+				struct weston_color_profile *cprof)
+{
+	struct weston_color_profile *old;
+	struct weston_paint_node *pnode;
+
+	if (cprof && (cprof->usage & WESTON_COLOR_PROFILE_KIND_OUTPUT) == 0) {
+		weston_log("Error setting color profile '%s' for output '%s': not an output profile\n",
+			   weston_color_profile_get_description(cprof),
+			   output->name);
+		return false;
+	}
+
+	old = output->color_profile;
+	output->color_profile = weston_color_profile_ref(cprof);
+
+	if (output->enabled) {
+		if (!weston_output_set_color_transforms(output)) {
+			/* Failed, roll back */
+			weston_color_profile_unref(output->color_profile);
+			output->color_profile = old;
+			return false;
+		}
+
+		/* Remove outdated cached color transformations */
+		wl_list_for_each(pnode, &output->paint_node_list, output_link) {
+			weston_surface_color_transform_fini(&pnode->surf_xform);
+			pnode->surf_xform_valid = false;
+		}
+	}
+
+	weston_color_profile_unref(old);
+
+	return true;
+}
+
 /** Initializes a weston_output object with enough data so
  ** an output can be configured.
  *
@@ -6852,6 +6911,8 @@ weston_output_release(struct weston_output *output)
 	if (output->enabled)
 		weston_compositor_remove_output(output);
 
+	weston_color_profile_unref(output->color_profile);
+
 	pixman_region32_fini(&output->region);
 	wl_list_remove(&output->link);