Fix T64625: Eevee image textures with alpha have dark edges
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Fri, 7 Jun 2019 15:49:58 +0000 (17:49 +0200)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Fri, 7 Jun 2019 18:51:40 +0000 (20:51 +0200)
Now texture storage of images is defined by the alpha mode of the image. The
downside of this is that there can be artifacts near alpha edges where pixels
with zero alpha bleed in. It also adds more code complexity since image textures
are no longer all stored the same way.

This changes allows us to keep using sRGB texture formats, which have edge
darkening when stored with premultiplied alpha. Game engines seems to generally
do the same thing, and we want to be compatible with them.

19 files changed:
source/blender/draw/engines/gpencil/gpencil_draw_utils.c
source/blender/draw/engines/gpencil/shaders/gpencil_fill_frag.glsl
source/blender/draw/engines/gpencil/shaders/gpencil_point_frag.glsl
source/blender/draw/engines/gpencil/shaders/gpencil_stroke_frag.glsl
source/blender/draw/engines/workbench/shaders/workbench_common_lib.glsl
source/blender/draw/engines/workbench/shaders/workbench_forward_transparent_accum_frag.glsl
source/blender/draw/engines/workbench/shaders/workbench_prepass_frag.glsl
source/blender/draw/engines/workbench/workbench_materials.c
source/blender/draw/modes/object_mode.c
source/blender/draw/modes/paint_texture_mode.c
source/blender/draw/modes/shaders/object_empty_image_frag.glsl
source/blender/draw/modes/shaders/paint_texture_frag.glsl
source/blender/gpu/intern/gpu_draw.c
source/blender/gpu/shaders/gpu_shader_material.glsl
source/blender/imbuf/IMB_colormanagement.h
source/blender/imbuf/intern/colormanagement.c
source/blender/makesrna/intern/rna_image_api.c
source/blender/nodes/shader/nodes/node_shader_tex_environment.c
source/blender/nodes/shader/nodes/node_shader_tex_image.c

index cd35395..9babd8c 100644 (file)
@@ -467,6 +467,8 @@ static DRWShadingGroup *DRW_gpencil_shgroup_fill_create(GPENCIL_e_data *e_data,
     else {
       GPUTexture *texture = GPU_texture_from_blender(gp_style->ima, &iuser, GL_TEXTURE_2D);
       DRW_shgroup_uniform_texture(grp, "myTexture", texture);
+      DRW_shgroup_uniform_bool_copy(
+          grp, "myTexturePremultiplied", (image->alpha_mode == IMA_ALPHA_PREMUL));
 
       stl->shgroups[id].texture_clamp = gp_style->flag & GP_STYLE_COLOR_TEX_CLAMP ? 1 : 0;
       DRW_shgroup_uniform_int(grp, "texture_clamp", &stl->shgroups[id].texture_clamp, 1);
@@ -633,6 +635,8 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create(GPENCIL_e_data *e_data,
     else {
       GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D);
       DRW_shgroup_uniform_texture(grp, "myTexture", texture);
+      DRW_shgroup_uniform_bool_copy(
+          grp, "myTexturePremultiplied", (image->alpha_mode == IMA_ALPHA_PREMUL));
 
       BKE_image_release_ibuf(image, ibuf, NULL);
     }
@@ -789,6 +793,8 @@ static DRWShadingGroup *DRW_gpencil_shgroup_point_create(GPENCIL_e_data *e_data,
     else {
       GPUTexture *texture = GPU_texture_from_blender(gp_style->sima, &iuser, GL_TEXTURE_2D);
       DRW_shgroup_uniform_texture(grp, "myTexture", texture);
+      DRW_shgroup_uniform_bool_copy(
+          grp, "myTexturePremultiplied", (image->alpha_mode == IMA_ALPHA_PREMUL));
 
       BKE_image_release_ibuf(image, ibuf, NULL);
     }
index 192720a..1fdfd05 100644 (file)
@@ -19,6 +19,7 @@ uniform int drawmode;
 uniform float layer_opacity;
 
 uniform sampler2D myTexture;
+uniform bool myTexturePremultiplied;
 uniform int texture_clamp;
 
 uniform int viewport_xray;
@@ -99,11 +100,15 @@ float linearrgb_to_srgb(float c)
   }
 }
 
-vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+vec4 texture_read_as_srgb(sampler2D tex, bool premultiplied, vec2 co)
 {
   /* By convention image textures return scene linear colors, but
    * grease pencil still works in srgb. */
   vec4 color = texture(tex, co);
+  /* Unpremultiply if stored multiplied, since straight alpha is expected by shaders. */
+  if (premultiplied && !(color.a == 0.0 || color.a == 1.0)) {
+    color.rgb = color.rgb / color.a;
+  }
   color.r = linearrgb_to_srgb(color.r);
   color.g = linearrgb_to_srgb(color.g);
   color.b = linearrgb_to_srgb(color.b);
@@ -118,8 +123,10 @@ void main()
   vec2 rot_tex = (matrot_tex * (texCoord_interp - t_center)) + t_center + texture_offset;
   vec4 tmp_color;
   tmp_color = (texture_clamp == 0) ?
-                  texture_read_as_srgb(myTexture, rot_tex * texture_scale) :
-                  texture_read_as_srgb(myTexture, clamp(rot_tex * texture_scale, 0.0, 1.0));
+                  texture_read_as_srgb(
+                      myTexture, myTexturePremultiplied, rot_tex * texture_scale) :
+                  texture_read_as_srgb(
+                      myTexture, myTexturePremultiplied, clamp(rot_tex * texture_scale, 0.0, 1.0));
   vec4 text_color = vec4(tmp_color[0], tmp_color[1], tmp_color[2], tmp_color[3] * texture_opacity);
   vec4 chesscolor;
 
index 569a686..7fed42a 100644 (file)
@@ -1,6 +1,7 @@
 uniform int color_type;
 uniform int mode;
 uniform sampler2D myTexture;
+uniform bool myTexturePremultiplied;
 
 uniform float gradient_f;
 uniform vec2 gradient_s;
@@ -57,11 +58,15 @@ float linearrgb_to_srgb(float c)
   }
 }
 
-vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+vec4 texture_read_as_srgb(sampler2D tex, bool premultiplied, vec2 co)
 {
   /* By convention image textures return scene linear colors, but
    * grease pencil still works in srgb. */
   vec4 color = texture(tex, co);
+  /* Unpremultiply if stored multiplied, since straight alpha is expected by shaders. */
+  if (premultiplied && !(color.a == 0.0 || color.a == 1.0)) {
+    color.rgb = color.rgb / color.a;
+  }
   color.r = linearrgb_to_srgb(color.r);
   color.g = linearrgb_to_srgb(color.g);
   color.b = linearrgb_to_srgb(color.b);
@@ -86,15 +91,13 @@ void main()
     }
   }
 
-  vec4 tmp_color = texture_read_as_srgb(myTexture, mTexCoord);
-
   /* Solid */
   if ((color_type == GPENCIL_COLOR_SOLID) || (no_texture)) {
     fragColor = mColor;
   }
   /* texture */
   if ((color_type == GPENCIL_COLOR_TEXTURE) && (!no_texture)) {
-    vec4 text_color = texture_read_as_srgb(myTexture, mTexCoord);
+    vec4 text_color = texture_read_as_srgb(myTexture, myTexturePremultiplied, mTexCoord);
     if (mix_stroke_factor > 0.0) {
       fragColor.rgb = mix(text_color.rgb, colormix.rgb, mix_stroke_factor);
       fragColor.a = text_color.a;
@@ -108,7 +111,7 @@ void main()
   }
   /* pattern */
   if ((color_type == GPENCIL_COLOR_PATTERN) && (!no_texture)) {
-    vec4 text_color = texture_read_as_srgb(myTexture, mTexCoord);
+    vec4 text_color = texture_read_as_srgb(myTexture, myTexturePremultiplied, mTexCoord);
     fragColor = mColor;
     /* mult both alpha factor to use strength factor with color alpha limit */
     fragColor.a = min(text_color.a * mColor.a, mColor.a);
index 7d0ebe8..bc703d2 100644 (file)
@@ -1,5 +1,6 @@
 uniform int color_type;
 uniform sampler2D myTexture;
+uniform bool myTexturePremultiplied;
 
 uniform float gradient_f;
 
@@ -37,11 +38,15 @@ float linearrgb_to_srgb(float c)
   }
 }
 
-vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+vec4 texture_read_as_srgb(sampler2D tex, bool premultiplied, vec2 co)
 {
   /* By convention image textures return scene linear colors, but
    * grease pencil still works in srgb. */
   vec4 color = texture(tex, co);
+  /* Unpremultiply if stored multiplied, since straight alpha is expected by shaders. */
+  if (premultiplied && !(color.a == 0.0 || color.a == 1.0)) {
+    color.rgb = color.rgb / color.a;
+  }
   color.r = linearrgb_to_srgb(color.r);
   color.g = linearrgb_to_srgb(color.g);
   color.b = linearrgb_to_srgb(color.b);
@@ -68,10 +73,11 @@ void main()
   /* texture for endcaps */
   vec4 text_color;
   if (uvfac[1] == ENDCAP) {
-    text_color = texture_read_as_srgb(myTexture, vec2(mTexCoord.x, mTexCoord.y));
+    text_color = texture_read_as_srgb(
+        myTexture, myTexturePremultiplied, vec2(mTexCoord.x, mTexCoord.y));
   }
   else {
-    text_color = texture_read_as_srgb(myTexture, mTexCoord);
+    text_color = texture_read_as_srgb(myTexture, myTexturePremultiplied, mTexCoord);
   }
 
   /* texture */
index 96f8f6e..59a463f 100644 (file)
@@ -140,7 +140,10 @@ vec2 matcap_uv_compute(vec3 I, vec3 N, bool flipped)
   return matcap_uv * 0.496 + 0.5;
 }
 
-vec4 workbench_sample_texture(sampler2D image, vec2 coord, bool nearest_sampling)
+vec4 workbench_sample_texture(sampler2D image,
+                              vec2 coord,
+                              bool nearest_sampling,
+                              bool premultiplied)
 {
   vec2 tex_size = vec2(textureSize(image, 0).xy);
   /* TODO(fclem) We could do the same with sampler objects.
@@ -148,8 +151,8 @@ vec4 workbench_sample_texture(sampler2D image, vec2 coord, bool nearest_sampling
   vec2 uv = nearest_sampling ? (floor(coord * tex_size) + 0.5) / tex_size : coord;
   vec4 color = texture(image, uv);
 
-  /* Unpremultiply, ideally shaders would be added so this is not needed. */
-  if (!(color.a == 0.0 || color.a == 1.0)) {
+  /* Unpremultiply if stored multiplied, since straight alpha is expected by shaders. */
+  if (premultiplied && !(color.a == 0.0 || color.a == 1.0)) {
     color.rgb = color.rgb / color.a;
   }
 
index 51bce63..c78b218 100644 (file)
@@ -2,6 +2,7 @@
 uniform float ImageTransparencyCutoff = 0.1;
 uniform sampler2D image;
 uniform bool imageNearest;
+uniform bool imagePremultiplied;
 
 uniform float alpha = 0.5;
 uniform vec2 invertedViewportSize;
@@ -43,7 +44,7 @@ void main()
   vec4 diffuse_color;
 
 #if defined(V3D_SHADING_TEXTURE_COLOR)
-  diffuse_color = workbench_sample_texture(image, uv_interp, imageNearest);
+  diffuse_color = workbench_sample_texture(image, uv_interp, imageNearest, imagePremultiplied);
   if (diffuse_color.a < ImageTransparencyCutoff) {
     discard;
   }
index af9f1d1..c673b24 100644 (file)
@@ -7,6 +7,7 @@ uniform float materialRoughness;
 uniform sampler2D image;
 uniform float ImageTransparencyCutoff = 0.1;
 uniform bool imageNearest;
+uniform bool imagePremultiplied;
 
 #ifdef NORMAL_VIEWPORT_PASS_ENABLED
 in vec3 normal_viewport;
@@ -40,7 +41,7 @@ void main()
   vec4 color;
 
 #  if defined(V3D_SHADING_TEXTURE_COLOR)
-  color = workbench_sample_texture(image, uv_interp, imageNearest);
+  color = workbench_sample_texture(image, uv_interp, imageNearest, imagePremultiplied);
   if (color.a < ImageTransparencyCutoff) {
     discard;
   }
index 6ca55d4..d570fda 100644 (file)
@@ -296,6 +296,8 @@ void workbench_material_shgroup_uniform(WORKBENCH_PrivateData *wpd,
       V3D_SHADING_TEXTURE_COLOR) {
     GPUTexture *tex = GPU_texture_from_blender(material->ima, material->iuser, GL_TEXTURE_2D);
     DRW_shgroup_uniform_texture(grp, "image", tex);
+    DRW_shgroup_uniform_bool_copy(
+        grp, "imagePremultiplied", (material->ima->alpha_mode == IMA_ALPHA_PREMUL));
     DRW_shgroup_uniform_bool_copy(grp, "imageNearest", (interp == SHD_INTERP_CLOSEST));
   }
   else {
index 8dae160..bbf3649 100644 (file)
@@ -974,9 +974,10 @@ static void DRW_shgroup_empty_image(OBJECT_Shaders *sh_data,
 
   const bool use_alpha_blend = (ob->empty_image_flag & OB_EMPTY_IMAGE_USE_ALPHA_BLEND) != 0;
   GPUTexture *tex = NULL;
+  Image *ima = ob->data;
 
-  if (ob->data != NULL) {
-    tex = GPU_texture_from_blender(ob->data, ob->iuser, GL_TEXTURE_2D);
+  if (ima != NULL) {
+    tex = GPU_texture_from_blender(ima, ob->iuser, GL_TEXTURE_2D);
     if (tex) {
       size[0] = GPU_texture_width(tex);
       size[1] = GPU_texture_height(tex);
@@ -1023,6 +1024,8 @@ static void DRW_shgroup_empty_image(OBJECT_Shaders *sh_data,
     DRW_shgroup_uniform_float(grp, "size", &ob->empty_drawsize, 1);
     DRW_shgroup_uniform_vec2(grp, "offset", ob->ima_ofs, 1);
     DRW_shgroup_uniform_texture(grp, "image", tex);
+    DRW_shgroup_uniform_bool_copy(
+        grp, "imagePremultiplied", (ima->alpha_mode == IMA_ALPHA_PREMUL));
     DRW_shgroup_uniform_vec4(grp, "objectColor", ob->color, 1);
     DRW_shgroup_uniform_bool_copy(grp, "useAlphaTest", !use_alpha_blend);
     if (sh_cfg == GPU_SHADER_CFG_CLIPPED) {
index a31efcc..5f833e4 100644 (file)
@@ -206,6 +206,7 @@ static void PAINT_TEXTURE_engine_init(void *vedata)
 }
 
 static DRWShadingGroup *create_texture_paint_shading_group(PAINT_TEXTURE_PassList *psl,
+                                                           const Image *image,
                                                            const struct GPUTexture *texture,
                                                            const DRWContextState *draw_ctx,
                                                            const bool nearest_interp)
@@ -219,6 +220,8 @@ static DRWShadingGroup *create_texture_paint_shading_group(PAINT_TEXTURE_PassLis
   DRWShadingGroup *grp = DRW_shgroup_create(masking_enabled ? sh_data->image_mask : sh_data->image,
                                             psl->image_faces);
   DRW_shgroup_uniform_texture(grp, "image", texture);
+  DRW_shgroup_uniform_bool_copy(
+      grp, "imagePremultiplied", (image->alpha_mode == IMA_ALPHA_PREMUL));
   DRW_shgroup_uniform_float(grp, "alpha", &draw_ctx->v3d->overlay.texture_paint_mode_opacity, 1);
   DRW_shgroup_uniform_block(grp, "globalsBlock", G_draw.block_ubo);
   DRW_shgroup_uniform_bool_copy(grp, "nearestInterp", nearest_interp);
@@ -227,6 +230,8 @@ static DRWShadingGroup *create_texture_paint_shading_group(PAINT_TEXTURE_PassLis
     const bool masking_inverted = (imapaint->flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) > 0;
     GPUTexture *stencil = GPU_texture_from_blender(imapaint->stencil, NULL, GL_TEXTURE_2D);
     DRW_shgroup_uniform_texture(grp, "maskingImage", stencil);
+    DRW_shgroup_uniform_bool_copy(
+        grp, "maskingImagePremultiplied", (imapaint->stencil->alpha_mode == IMA_ALPHA_PREMUL));
     DRW_shgroup_uniform_vec3(grp, "maskingColor", imapaint->stencil_col, 1);
     DRW_shgroup_uniform_bool_copy(grp, "maskingInvertStencil", masking_inverted);
   }
@@ -283,7 +288,7 @@ static void PAINT_TEXTURE_cache_init(void *vedata)
 
         if (tex) {
           DRWShadingGroup *grp = create_texture_paint_shading_group(
-              psl, tex, draw_ctx, interp == SHD_INTERP_CLOSEST);
+              psl, ima, tex, draw_ctx, interp == SHD_INTERP_CLOSEST);
           stl->g_data->shgroup_image_array[i] = grp;
         }
         else {
@@ -297,7 +302,7 @@ static void PAINT_TEXTURE_cache_init(void *vedata)
 
       if (tex) {
         DRWShadingGroup *grp = create_texture_paint_shading_group(
-            psl, tex, draw_ctx, imapaint->interp == IMAGEPAINT_INTERP_CLOSEST);
+            psl, ima, tex, draw_ctx, imapaint->interp == IMAGEPAINT_INTERP_CLOSEST);
         stl->g_data->shgroup_image_array[0] = grp;
       }
       else {
index deb82a8..8822014 100644 (file)
@@ -9,6 +9,7 @@ out vec4 fragColor;
 
 #ifndef USE_WIRE
 uniform sampler2D image;
+uniform bool imagePremultiplied;
 #endif
 
 uniform int depthMode;
@@ -24,11 +25,15 @@ float linearrgb_to_srgb(float c)
   }
 }
 
-vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+vec4 texture_read_as_srgb(sampler2D tex, bool premultiplied, vec2 co)
 {
   /* By convention image textures return scene linear colors, but
    * overlays still assume srgb. */
   vec4 color = texture(tex, co);
+  /* Unpremultiply if stored multiplied, since straight alpha is expected by shaders. */
+  if (premultiplied && !(color.a == 0.0 || color.a == 1.0)) {
+    color.rgb = color.rgb / color.a;
+  }
   color.r = linearrgb_to_srgb(color.r);
   color.g = linearrgb_to_srgb(color.g);
   color.b = linearrgb_to_srgb(color.b);
@@ -40,7 +45,7 @@ void main()
 #ifdef USE_WIRE
   fragColor = finalColor;
 #else
-  vec4 tex_col = texture_read_as_srgb(image, texCoord_interp);
+  vec4 tex_col = texture_read_as_srgb(image, imagePremultiplied, texCoord_interp);
   fragColor = finalColor * tex_col;
 
   if (useAlphaTest) {
index 4a3c5cb..af7ea99 100644 (file)
@@ -7,11 +7,13 @@ in vec2 masking_uv_interp;
 out vec4 fragColor;
 
 uniform sampler2D image;
+uniform bool imagePremultiplied;
 uniform float alpha = 1.0;
 uniform bool nearestInterp;
 
 #ifdef TEXTURE_PAINT_MASK
 uniform sampler2D maskingImage;
+uniform bool maskingImagePremultiplied;
 uniform vec3 maskingColor;
 uniform bool maskingInvertStencil;
 #endif
@@ -26,11 +28,15 @@ float linearrgb_to_srgb(float c)
   }
 }
 
-vec4 texture_read_as_srgb(sampler2D tex, vec2 co)
+vec4 texture_read_as_srgb(sampler2D tex, bool premultiplied, vec2 co)
 {
   /* By convention image textures return scene linear colors, but
    * overlays still assume srgb. */
   vec4 color = texture(tex, co);
+  /* Unpremultiply if stored multiplied, since straight alpha is expected by shaders. */
+  if (premultiplied && !(color.a == 0.0 || color.a == 1.0)) {
+    color.rgb = color.rgb / color.a;
+  }
   color.r = linearrgb_to_srgb(color.r);
   color.g = linearrgb_to_srgb(color.g);
   color.b = linearrgb_to_srgb(color.b);
@@ -45,11 +51,12 @@ void main()
     uv = (floor(uv_interp * tex_size) + 0.5) / tex_size;
   }
 
-  vec4 color = texture_read_as_srgb(image, uv);
+  vec4 color = texture_read_as_srgb(image, imagePremultiplied, uv);
   color.a *= alpha;
 
 #ifdef TEXTURE_PAINT_MASK
-  vec4 mask = vec4(texture(maskingImage, masking_uv_interp).rgb, 1.0);
+  vec4 mask = vec4(
+      texture_read_as_srgb(maskingImage, maskingImagePremultiplied, masking_uv_interp).rgb, 1.0);
   if (maskingInvertStencil) {
     mask.rgb = 1.0 - mask.rgb;
   }
index 4830b99..d3d7b3b 100644 (file)
@@ -242,29 +242,31 @@ static uint gpu_texture_create_from_ibuf(Image *ima, ImBuf *ibuf, int textarget)
         return bindcode;
       }
 
-      IMB_colormanagement_imbuf_to_srgb_texture(
-          rect, 0, 0, ibuf->x, ibuf->y, ibuf, compress_as_srgb);
+      /* Texture storage of images is defined by the alpha mode of the image. The
+       * downside of this is that there can be artifacts near alpha edges. However,
+       * this allows us to use sRGB texture formats and preserves color values in
+       * zero alpha areas, and appears generally closer to what game engines that we
+       * want to be compatible with do. */
+      const bool store_premultiplied = (ima->alpha_mode == IMA_ALPHA_PREMUL);
+      IMB_colormanagement_imbuf_to_byte_texture(
+          rect, 0, 0, ibuf->x, ibuf->y, ibuf, compress_as_srgb, store_premultiplied);
     }
   }
-  else if (ibuf->channels != 4) {
+  else {
     /* Float image is already in scene linear colorspace or non-color data by
      * convention, no colorspace conversion needed. But we do require 4 channels
      * currently. */
-    rect_float = MEM_mallocN(sizeof(float) * 4 * ibuf->x * ibuf->y, __func__);
-    if (rect_float == NULL) {
-      return bindcode;
-    }
+    const bool store_premultiplied = (ima->alpha_mode != IMA_ALPHA_STRAIGHT);
+
+    if (ibuf->channels != 4 || !store_premultiplied) {
+      rect_float = MEM_mallocN(sizeof(float) * 4 * ibuf->x * ibuf->y, __func__);
+      if (rect_float == NULL) {
+        return bindcode;
+      }
 
-    IMB_buffer_float_from_float(rect_float,
-                                ibuf->rect_float,
-                                ibuf->channels,
-                                IB_PROFILE_LINEAR_RGB,
-                                IB_PROFILE_LINEAR_RGB,
-                                false,
-                                ibuf->x,
-                                ibuf->y,
-                                ibuf->x,
-                                ibuf->x);
+      IMB_colormanagement_imbuf_to_float_texture(
+          rect_float, 0, 0, ibuf->x, ibuf->y, ibuf, store_premultiplied);
+    }
   }
 
   /* Create OpenGL texture. */
@@ -348,7 +350,7 @@ static void gpu_texture_update_unscaled(
   glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
 }
 
-static void gpu_texture_update_from_ibuf(ImBuf *ibuf, int x, int y, int w, int h)
+static void gpu_texture_update_from_ibuf(Image *ima, ImBuf *ibuf, int x, int y, int w, int h)
 {
   /* Partial update of texture for texture painting. This is often much
    * quicker than fully updating the texture for high resolution images.
@@ -388,30 +390,27 @@ static void gpu_texture_update_from_ibuf(ImBuf *ibuf, int x, int y, int w, int h
 
       /* Convert to scene linear with sRGB compression, and premultiplied for
        * correct texture interpolation. */
-      IMB_colormanagement_imbuf_to_srgb_texture(rect, x, y, w, h, ibuf, compress_as_srgb);
+      const bool store_premultiplied = (ima->alpha_mode == IMA_ALPHA_PREMUL);
+      IMB_colormanagement_imbuf_to_byte_texture(
+          rect, x, y, w, h, ibuf, compress_as_srgb, store_premultiplied);
     }
   }
-  else if (ibuf->channels != 4 || scaled) {
+  else {
     /* Float pixels. */
-    rect_float = MEM_mallocN(sizeof(float) * 4 * x * y, __func__);
-    if (rect_float == NULL) {
-      return;
-    }
+    const bool store_premultiplied = (ima->alpha_mode != IMA_ALPHA_STRAIGHT);
 
-    tex_stride = w;
-    tex_offset = 0;
+    if (ibuf->channels != 4 || scaled || !store_premultiplied) {
+      rect_float = MEM_mallocN(sizeof(float) * 4 * x * y, __func__);
+      if (rect_float == NULL) {
+        return;
+      }
 
-    size_t ibuf_offset = (y * ibuf->x + x) * ibuf->channels;
-    IMB_buffer_float_from_float(rect_float,
-                                ibuf->rect_float + ibuf_offset,
-                                ibuf->channels,
-                                IB_PROFILE_LINEAR_RGB,
-                                IB_PROFILE_LINEAR_RGB,
-                                false,
-                                w,
-                                h,
-                                x,
-                                ibuf->x);
+      tex_stride = w;
+      tex_offset = 0;
+
+      IMB_colormanagement_imbuf_to_float_texture(
+          rect_float, x, y, w, h, ibuf, store_premultiplied);
+    }
   }
 
   if (scaled) {
@@ -825,7 +824,7 @@ void GPU_paint_update_image(Image *ima, ImageUser *iuser, int x, int y, int w, i
     /* Partial update of texture. */
     GPU_texture_bind(ima->gputexture[TEXTARGET_TEXTURE_2D], 0);
 
-    gpu_texture_update_from_ibuf(ibuf, x, y, w, h);
+    gpu_texture_update_from_ibuf(ima, ibuf, x, y, w, h);
 
     if (GPU_get_mipmap()) {
       glGenerateMipmap(GL_TEXTURE_2D);
index 4135151..1750e12 100644 (file)
@@ -2425,6 +2425,11 @@ void tex_color_alpha_clear(vec4 color, out vec4 result)
   result = vec4(color.rgb, 1.0);
 }
 
+void tex_color_alpha_premultiply(vec4 color, out vec4 result)
+{
+  result = vec4(color.rgb * color.a, 1.0);
+}
+
 void tex_color_alpha_unpremultiply(vec4 color, out vec4 result)
 {
   if (color.a == 0.0 || color.a == 1.0) {
index a27945c..b12339a 100644 (file)
@@ -129,13 +129,21 @@ void IMB_colormanagement_colorspace_to_scene_linear(float *buffer,
                                                     struct ColorSpace *colorspace,
                                                     bool predivide);
 
-void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *rect,
+void IMB_colormanagement_imbuf_to_byte_texture(unsigned char *out_buffer,
                                                const int x,
                                                const int y,
                                                const int width,
                                                const int height,
                                                const struct ImBuf *ibuf,
-                                               const bool compress_as_srgb);
+                                               const bool compress_as_srgb,
+                                               const bool store_premultiplied);
+void IMB_colormanagement_imbuf_to_float_texture(float *out_buffer,
+                                                const int offset_x,
+                                                const int offset_y,
+                                                const int width,
+                                                const int height,
+                                                const struct ImBuf *ibuf,
+                                                const bool store_premultiplied);
 
 void IMB_colormanagement_scene_linear_to_color_picking_v3(float pixel[3]);
 void IMB_colormanagement_color_picking_to_scene_linear_v3(float pixel[3]);
index 583a3be..bec2925 100644 (file)
@@ -2176,13 +2176,14 @@ void IMB_colormanagement_colorspace_to_scene_linear(float *buffer,
   }
 }
 
-void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *out_buffer,
+void IMB_colormanagement_imbuf_to_byte_texture(unsigned char *out_buffer,
                                                const int offset_x,
                                                const int offset_y,
                                                const int width,
                                                const int height,
                                                const struct ImBuf *ibuf,
-                                               const bool compress_as_srgb)
+                                               const bool compress_as_srgb,
+                                               const bool store_premultiplied)
 {
   /* Convert byte buffer for texture storage on the GPU. These have builtin
    * support for converting sRGB to linear, which allows us to store textures
@@ -2197,7 +2198,7 @@ void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *out_buffer,
 
   /* TODO(brecht): make this multithreaded, or at least process in batches. */
   const unsigned char *in_buffer = (unsigned char *)ibuf->rect;
-  const bool use_premultiply = IMB_alpha_affects_rgb(ibuf);
+  const bool use_premultiply = IMB_alpha_affects_rgb(ibuf) && store_premultiplied;
 
   for (int y = 0; y < height; y++) {
     const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
@@ -2239,6 +2240,58 @@ void IMB_colormanagement_imbuf_to_srgb_texture(unsigned char *out_buffer,
   }
 }
 
+void IMB_colormanagement_imbuf_to_float_texture(float *out_buffer,
+                                                const int offset_x,
+                                                const int offset_y,
+                                                const int width,
+                                                const int height,
+                                                const struct ImBuf *ibuf,
+                                                const bool store_premultiplied)
+{
+  /* Float texture are stored in scene linear color space, with premultiplied
+   * alpha depending on the image alpha mode. */
+  const float *in_buffer = ibuf->rect_float;
+  const int in_channels = ibuf->channels;
+  const bool use_unpremultiply = IMB_alpha_affects_rgb(ibuf) && !store_premultiplied;
+
+  for (int y = 0; y < height; y++) {
+    const size_t in_offset = (offset_y + y) * ibuf->x + offset_x;
+    const size_t out_offset = y * width;
+    const float *in = in_buffer + in_offset * 4;
+    float *out = out_buffer + out_offset * 4;
+
+    if (in_channels == 1) {
+      /* Copy single channel. */
+      for (int x = 0; x < width; x++, in += 1, out += 4) {
+        out[0] = in[0];
+        out[1] = in[0];
+        out[2] = in[0];
+        out[3] = in[0];
+      }
+    }
+    else if (in_channels == 3) {
+      /* Copy RGB. */
+      for (int x = 0; x < width; x++, in += 3, out += 4) {
+        out[0] = in[0];
+        out[1] = in[1];
+        out[2] = in[2];
+        out[3] = 1.0f;
+      }
+    }
+    else if (in_channels == 4) {
+      /* Copy or convert RGBA. */
+      if (use_unpremultiply) {
+        for (int x = 0; x < width; x++, in += 4, out += 4) {
+          premul_to_straight_v4_v4(out, in);
+        }
+      }
+      else {
+        memcpy(out, in, sizeof(float) * 4 * width);
+      }
+    }
+  }
+}
+
 /* Conversion between color picking role. Typically we would expect such a
  * requirements:
  * - It is approximately perceptually linear, so that the HSV numbers and
index a26b4c6..933dae7 100644 (file)
@@ -342,7 +342,7 @@ void RNA_api_image(StructRNA *srna)
       func,
       "Load the image into an OpenGL texture. On success, image.bindcode will contain the "
       "OpenGL texture bindcode. Colors read from the texture will be in scene linear color space "
-      "and have premultiplied alpha.");
+      "and have premultiplied or straight alpha matching the image alpha mode");
   RNA_def_function_flag(func, FUNC_USE_REPORTS);
   RNA_def_int(
       func, "frame", 0, 0, INT_MAX, "Frame", "Frame of image sequence or movie", 0, INT_MAX);
index 615f55e..bd8355e 100644 (file)
@@ -121,7 +121,20 @@ static int node_shader_gpu_tex_environment(GPUMaterial *mat,
   }
 
   if (out[0].hasoutput) {
-    GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
+    if (ELEM(ima->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED) ||
+        IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name)) {
+      /* Don't let alpha affect color output in these cases. */
+      GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
+    }
+    else {
+      /* Always output with premultiplied alpha. */
+      if (ima->alpha_mode == IMA_ALPHA_PREMUL) {
+        GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
+      }
+      else {
+        GPU_link(mat, "tex_color_alpha_premultiply", out[0].link, &out[0].link);
+      }
+    }
   }
 
   return true;
index 786386b..6f3614e 100644 (file)
@@ -180,16 +180,32 @@ static int node_shader_gpu_tex_image(GPUMaterial *mat,
   }
 
   if (out[0].hasoutput) {
-    /* When the alpha socket is used, unpremultiply alpha. This makes it so
-     * that if we blend the color with a transparent shader using alpha as
-     * a factor, we don't multiply alpha into the color twice. */
-    if (out[1].hasoutput &&
-        !(ELEM(ima->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED) ||
-          IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name))) {
-      GPU_link(mat, "tex_color_alpha_unpremultiply", out[0].link, &out[0].link);
+    if (ELEM(ima->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED) ||
+        IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name)) {
+      /* Don't let alpha affect color output in these cases. */
+      GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
     }
     else {
-      GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
+      /* Output premultiplied alpha depending on alpha socket usage. This makes
+       * it so that if we blend the color with a transparent shader using alpha as
+       * a factor, we don't multiply alpha into the color twice. And if we do
+       * not, then there will be no artifacts from zero alpha areas. */
+      if (ima->alpha_mode == IMA_ALPHA_PREMUL) {
+        if (out[1].hasoutput) {
+          GPU_link(mat, "tex_color_alpha_unpremultiply", out[0].link, &out[0].link);
+        }
+        else {
+          GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
+        }
+      }
+      else {
+        if (out[1].hasoutput) {
+          GPU_link(mat, "tex_color_alpha_clear", out[0].link, &out[0].link);
+        }
+        else {
+          GPU_link(mat, "tex_color_alpha_premultiply", out[0].link, &out[0].link);
+        }
+      }
     }
   }