Skip to content

Fix translucent image colors by correcting alpha premultiplication in image decoding and GPU texture transfers - #4517

Closed
Keavon wants to merge 1 commit into
masterfrom
fix-image-premult
Closed

Fix translucent image colors by correcting alpha premultiplication in image decoding and GPU texture transfers#4517
Keavon wants to merge 1 commit into
masterfrom
fix-image-premult

Conversation

@Keavon

@Keavon Keavon commented Sep 11, 2026

Copy link
Copy Markdown
Member

Superseded by #4518.

@Keavon
Keavon requested a review from TrueDoctor as a code owner September 11, 2026 12:50

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 6 files

Confidence score: 4/5

  • node-graph/libraries/no-std-types/src/color/color_types.rs duplicates SRGBA8-to-premultiplied-Color conversion logic, so future color or alpha fixes could diverge across image decoding and GPU readback; extract and reuse a shared helper.
  • node-graph/libraries/raster-types/src/image.rs changes stored alpha semantics and could introduce a subtle color regression despite the fix; add a round-trip test using from_image_data with known alpha values.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="node-graph/libraries/no-std-types/src/color/color_types.rs">

<violation number="1" location="node-graph/libraries/no-std-types/src/color/color_types.rs:363">
P3: This change duplicates the SRGBA8-to-premultiplied-`Color` conversion already used by image-data decoding and GPU readback. Extract the conversion into a shared helper and call it from all paths so future color or alpha fixes cannot leave these implementations inconsistent.</violation>
</file>

<file name="node-graph/libraries/raster-types/src/image.rs">

<violation number="1" location="node-graph/libraries/raster-types/src/image.rs:152">
P3: This premultiplication fix changes stored alpha semantics (no longer alpha-squared) and is the kind of subtle color-space regression that can silently reappear. Add a round-trip test that runs from_image_data on a known semi-transparent RGBA pixel and asserts the stored Color has alpha == v[3]/255 with premultiplied linear RGB (e.g. white at alpha 128 → rgb ≈ 0.502, alpha ≈ 0.502), and that to_png()/to_flat_u8 recovers the original u8 values.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

// `Image<Color>` pixel convention is linear-light with associated (premultiplied) alpha.
let srgba = SRGBA8::new(bytes[0], bytes[1], bytes[2], bytes[3]);
Color::from(srgba).apply_opacity(bytes[3] as f32 / 255.)
let color = Color::from(SRGBA8::new(bytes[0], bytes[1], bytes[2], bytes[3]));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This change duplicates the SRGBA8-to-premultiplied-Color conversion already used by image-data decoding and GPU readback. Extract the conversion into a shared helper and call it from all paths so future color or alpha fixes cannot leave these implementations inconsistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/libraries/no-std-types/src/color/color_types.rs, line 363:

<comment>This change duplicates the SRGBA8-to-premultiplied-`Color` conversion already used by image-data decoding and GPU readback. Extract the conversion into a shared helper and call it from all paths so future color or alpha fixes cannot leave these implementations inconsistent.</comment>

<file context>
@@ -360,8 +360,8 @@ impl Pixel for Color {
 		// `Image<Color>` pixel convention is linear-light with associated (premultiplied) alpha.
-		let srgba = SRGBA8::new(bytes[0], bytes[1], bytes[2], bytes[3]);
-		Color::from(srgba).apply_opacity(bytes[3] as f32 / 255.)
+		let color = Color::from(SRGBA8::new(bytes[0], bytes[1], bytes[2], bytes[3]));
+		color.map_rgb(|channel| channel * color.a())
 	}
</file context>

let srgba = SRGBA8::new(v[0], v[1], v[2], v[3]);
Color::from(srgba).apply_opacity(v[3] as f32 / 255.)
let color = Color::from(SRGBA8::new(v[0], v[1], v[2], v[3]));
color.map_rgb(|channel| channel * color.a())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This premultiplication fix changes stored alpha semantics (no longer alpha-squared) and is the kind of subtle color-space regression that can silently reappear. Add a round-trip test that runs from_image_data on a known semi-transparent RGBA pixel and asserts the stored Color has alpha == v[3]/255 with premultiplied linear RGB (e.g. white at alpha 128 → rgb ≈ 0.502, alpha ≈ 0.502), and that to_png()/to_flat_u8 recovers the original u8 values.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/libraries/raster-types/src/image.rs, line 152:

<comment>This premultiplication fix changes stored alpha semantics (no longer alpha-squared) and is the kind of subtle color-space regression that can silently reappear. Add a round-trip test that runs from_image_data on a known semi-transparent RGBA pixel and asserts the stored Color has alpha == v[3]/255 with premultiplied linear RGB (e.g. white at alpha 128 → rgb ≈ 0.502, alpha ≈ 0.502), and that to_png()/to_flat_u8 recovers the original u8 values.</comment>

<file context>
@@ -148,8 +148,8 @@ impl Image<Color> {
-				let srgba = SRGBA8::new(v[0], v[1], v[2], v[3]);
-				Color::from(srgba).apply_opacity(v[3] as f32 / 255.)
+				let color = Color::from(SRGBA8::new(v[0], v[1], v[2], v[3]));
+				color.map_rgb(|channel| channel * color.a())
 			})
 			.collect();
</file context>

@Keavon

Keavon commented Sep 12, 2026

Copy link
Copy Markdown
Member Author

Superseded by #4518.

@Keavon Keavon closed this Sep 12, 2026
@Keavon
Keavon deleted the fix-image-premult branch September 12, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants