diff --git a/node-graph/libraries/no-std-types/src/color/color_types.rs b/node-graph/libraries/no-std-types/src/color/color_types.rs index 13bbe379121..88ceea2b286 100644 --- a/node-graph/libraries/no-std-types/src/color/color_types.rs +++ b/node-graph/libraries/no-std-types/src/color/color_types.rs @@ -360,8 +360,8 @@ impl Pixel for Color { fn from_bytes(bytes: &[u8]) -> Self { // `Image` 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()) } fn byte_size() -> usize { 4 diff --git a/node-graph/libraries/raster-types/src/image.rs b/node-graph/libraries/raster-types/src/image.rs index 16d4d5955a7..4a41b8745e4 100644 --- a/node-graph/libraries/raster-types/src/image.rs +++ b/node-graph/libraries/raster-types/src/image.rs @@ -148,8 +148,8 @@ impl Image { .chunks_exact(4) .map(|v| { // `Image` pixels are stored linear-light with premultiplied alpha - 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(); Image { diff --git a/node-graph/libraries/wgpu-executor/src/texture_conversion.rs b/node-graph/libraries/wgpu-executor/src/texture_conversion.rs index 1411a57f6b3..cff7b4f17f9 100644 --- a/node-graph/libraries/wgpu-executor/src/texture_conversion.rs +++ b/node-graph/libraries/wgpu-executor/src/texture_conversion.rs @@ -8,14 +8,14 @@ use raster_types::Image; use raster_types::{CPU, GPU, Raster, Texture}; use wgpu::{Extent3d, TextureFormat}; -/// Uploads CPU image data to a GPU texture +/// Uploads CPU image data to a GPU texture as gamma sRGB with unassociated alpha, the convention for all GPU raster textures. fn upload_to_texture(executor: &WgpuExecutor, queue: &wgpu::Queue, image: &Raster) -> Texture { - let rgba8_data: Vec = image.data.iter().map(|x| (*x).into()).collect(); + let rgba8_data = image.to_flat_u8().0; let texture = executor.request_texture_with_format(glam::UVec2::new(image.width, image.height), TextureFormat::Rgba8UnormSrgb); queue.write_texture( texture.as_image_copy(), - bytemuck::cast_slice(rgba8_data.as_slice()), + &rgba8_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4 * image.width), @@ -116,8 +116,8 @@ impl RasterGpuToRasterCpuConverter { let row_slice = &view[start..start + row_bytes]; for px in row_slice.chunks_exact(4) { // `Image` pixels are stored linear-light with associated (premultiplied) alpha - let srgba = SRGBA8::new(px[0], px[1], px[2], px[3]); - cpu_data.push(Color::from(srgba).apply_opacity(px[3] as f32 / 255.)); + let color = Color::from(SRGBA8::new(px[0], px[1], px[2], px[3])); + cpu_data.push(color.map_rgb(|channel| channel * color.a())); } } diff --git a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs index 2d9adbac6a2..a77ba2389be 100644 --- a/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs +++ b/node-graph/node-macro/src/shader_nodes/per_pixel_adjust.rs @@ -149,7 +149,11 @@ impl PerPixelAdjustCodegen<'_> { .iter() .map(|Param { ident, param_type, item_wrapped, .. }| { let bare_value = match param_type { - ParamType::Image { .. } => quote!(Color::from_vec4(#ident.fetch_with(texel_coord, lod(0)))), + // Textures hold unassociated alpha but node functions take premultiplied `Color` + ParamType::Image { .. } => quote!({ + let texel = Color::from_vec4(#ident.fetch_with(texel_coord, lod(0))); + texel.map_rgb(|channel| channel * texel.a()) + }), ParamType::Uniform => quote!(uniform.#ident), }; if *item_wrapped { quote!(Item::new_from_element(#bare_value)) } else { bare_value } @@ -184,7 +188,7 @@ impl PerPixelAdjustCodegen<'_> { let uniform = ::read(*uniform); let texel_coord = frag_coord.xy().as_uvec2(); let color: Color = #fn_name(#context, #(#call_args),*)#unwrap_result; - *color_out = color.to_vec4(); + *color_out = color.to_unassociated_alpha().to_vec4(); } } }) diff --git a/node-graph/nodes/gstd/src/platform_application_io.rs b/node-graph/nodes/gstd/src/platform_application_io.rs index 5ed9eb366a2..4cc2bb100b1 100644 --- a/node-graph/nodes/gstd/src/platform_application_io.rs +++ b/node-graph/nodes/gstd/src/platform_application_io.rs @@ -174,9 +174,9 @@ fn decode_image(_: impl Ctx, data: Item) -> Item> { data: image .chunks(4) .map(|pixel| { - // Decoded bytes are unassociated gamma sRGB; premultiply in gamma then lift to linear - let a = pixel[3]; - Color::from_gamma_srgb_channels(pixel[0] * a, pixel[1] * a, pixel[2] * a, a) + // Decoded bytes are unassociated gamma sRGB, so lift to linear before premultiplying + let color = Color::from_gamma_srgb_channels(pixel[0], pixel[1], pixel[2], pixel[3]); + color.map_rgb(|channel| channel * color.a()) }) .collect(), width: image.width(), diff --git a/node-graph/nodes/raster/src/std_nodes.rs b/node-graph/nodes/raster/src/std_nodes.rs index 2662cfbfd32..a5d7e557e74 100644 --- a/node-graph/nodes/raster/src/std_nodes.rs +++ b/node-graph/nodes/raster/src/std_nodes.rs @@ -260,8 +260,8 @@ pub fn image<'a: 'n>(_: impl Ctx, resource: Item) -> Item> data: image .chunks(4) .map(|pixel| { - let alpha = pixel[3]; - Color::from_gamma_srgb_channels(pixel[0] * alpha, pixel[1] * alpha, pixel[2] * alpha, alpha) + let color = Color::from_gamma_srgb_channels(pixel[0], pixel[1], pixel[2], pixel[3]); + color.map_rgb(|channel| channel * color.a()) }) .collect(), width: image.width(),