OSDN Git Service

Pre-multiply gradient colors the right way
authorRomain Guy <romainguy@google.com>
Tue, 13 Dec 2016 02:21:32 +0000 (18:21 -0800)
committerRomain Guy <romainguy@google.com>
Tue, 13 Dec 2016 02:50:07 +0000 (18:50 -0800)
Alpha pre-multiplication must be done after applying the
opto-electronic transfer function when linear blending is
disabled. The correct way would be to pre-multiply before
gamma encoding but this leads to improper blending which
cannot be corrected without using sRGB frame buffers and
texture sampling.

Bug: 33010587
Test: cts-tradefed run singleCommand cts-dev --module CtsUiRenderingTestCases --test android.uirendering.cts.testclasses.GradientTests
Change-Id: I5f04bda4cb9f63674537aef5931621c14d601884

libs/hwui/FloatColor.h
libs/hwui/GradientCache.cpp
libs/hwui/ProgramCache.cpp
libs/hwui/SkiaShader.cpp

index 9df7338..d8afa35 100644 (file)
@@ -38,13 +38,13 @@ struct FloatColor {
     }
 
     // "color" is a gamma-encoded sRGB color
-    // After calling this method, the color is stored as a pre-multiplied linear color
-    // if linear blending is enabled.
-    void setSRGB(uint32_t color) {
+    // After calling this method, the color is stored as a linear color. The color
+    // is not pre-multiplied.
+    void setUnPreMultipliedSRGB(uint32_t color) {
         a = ((color >> 24) & 0xff) / 255.0f;
-        r = a * EOCF_sRGB(((color >> 16) & 0xff) / 255.0f);
-        g = a * EOCF_sRGB(((color >>  8) & 0xff) / 255.0f);
-        b = a * EOCF_sRGB(((color      ) & 0xff) / 255.0f);
+        r = EOCF_sRGB(((color >> 16) & 0xff) / 255.0f);
+        g = EOCF_sRGB(((color >>  8) & 0xff) / 255.0f);
+        b = EOCF_sRGB(((color      ) & 0xff) / 255.0f);
     }
 
     bool isNotBlack() {
index 0972ac1..1dad58f 100644 (file)
@@ -188,26 +188,28 @@ size_t GradientCache::sourceBytesPerPixel() const {
 void GradientCache::mixBytes(const FloatColor& start, const FloatColor& end,
         float amount, uint8_t*& dst) const {
     float oppAmount = 1.0f - amount;
-    *dst++ = uint8_t(OECF_sRGB(start.r * oppAmount + end.r * amount) * 255.0f);
-    *dst++ = uint8_t(OECF_sRGB(start.g * oppAmount + end.g * amount) * 255.0f);
-    *dst++ = uint8_t(OECF_sRGB(start.b * oppAmount + end.b * amount) * 255.0f);
-    *dst++ = uint8_t(         (start.a * oppAmount + end.a * amount) * 255.0f);
+    float a = start.a * oppAmount + end.a * amount;
+    *dst++ = uint8_t(a * OECF_sRGB((start.r * oppAmount + end.r * amount)) * 255.0f);
+    *dst++ = uint8_t(a * OECF_sRGB((start.g * oppAmount + end.g * amount)) * 255.0f);
+    *dst++ = uint8_t(a * OECF_sRGB((start.b * oppAmount + end.b * amount)) * 255.0f);
+    *dst++ = uint8_t(a * 255.0f);
 }
 
 void GradientCache::mixFloats(const FloatColor& start, const FloatColor& end,
         float amount, uint8_t*& dst) const {
     float oppAmount = 1.0f - amount;
+    float a = start.a * oppAmount + end.a * amount;
     float* d = (float*) dst;
 #ifdef ANDROID_ENABLE_LINEAR_BLENDING
-    *d++ = start.r * oppAmount + end.r * amount;
-    *d++ = start.g * oppAmount + end.g * amount;
-    *d++ = start.b * oppAmount + end.b * amount;
+    *d++ = a * (start.r * oppAmount + end.r * amount);
+    *d++ = a * (start.g * oppAmount + end.g * amount);
+    *d++ = a * (start.b * oppAmount + end.b * amount);
 #else
-    *d++ = OECF_sRGB(start.r * oppAmount + end.r * amount);
-    *d++ = OECF_sRGB(start.g * oppAmount + end.g * amount);
-    *d++ = OECF_sRGB(start.b * oppAmount + end.b * amount);
+    *d++ = a * OECF_sRGB(start.r * oppAmount + end.r * amount);
+    *d++ = a * OECF_sRGB(start.g * oppAmount + end.g * amount);
+    *d++ = a * OECF_sRGB(start.b * oppAmount + end.b * amount);
 #endif
-    *d++ = start.a * oppAmount + end.a * amount;
+    *d++ = a;
     dst += 4 * sizeof(float);
 }
 
@@ -217,16 +219,19 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions,
     uint8_t pixels[rowBytes * height];
 
     static ChannelMixer gMixers[] = {
-            &android::uirenderer::GradientCache::mixBytes,  // colors are stored gamma-encoded
-            &android::uirenderer::GradientCache::mixFloats, // colors are stored in linear
+            // colors are stored gamma-encoded
+            &android::uirenderer::GradientCache::mixBytes,
+            // colors are stored in linear (linear blending on)
+            // or gamma-encoded (linear blending off)
+            &android::uirenderer::GradientCache::mixFloats,
     };
     ChannelMixer mix = gMixers[mUseFloatTexture];
 
     FloatColor start;
-    start.setSRGB(colors[0]);
+    start.setUnPreMultipliedSRGB(colors[0]);
 
     FloatColor end;
-    end.setSRGB(colors[1]);
+    end.setUnPreMultipliedSRGB(colors[1]);
 
     int currentPos = 1;
     float startPos = positions[0];
@@ -241,7 +246,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions,
 
             currentPos++;
 
-            end.setSRGB(colors[currentPos]);
+            end.setUnPreMultipliedSRGB(colors[currentPos]);
             distance = positions[currentPos] - startPos;
         }
 
index 0c2309f..2688ba4 100644 (file)
@@ -175,10 +175,11 @@ const char* gFS_Gradient_Functions =
 const char* gFS_Gradient_Preamble[2] = {
         // Linear framebuffer
         "\nvec4 dither(const vec4 color) {\n"
-        "    return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);"
+        "    return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);\n"
         "}\n"
         "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n"
-        "    return pow(mix(a, b, v), vec4(vec3(1.0 / 2.2), 1.0));"
+        "    vec4 c = pow(mix(a, b, v), vec4(vec3(1.0 / 2.2), 1.0));\n"
+        "    return vec4(c.rgb * c.a, c.a);\n"
         "}\n",
         // sRGB framebuffer
         "\nvec4 dither(const vec4 color) {\n"
@@ -186,7 +187,8 @@ const char* gFS_Gradient_Preamble[2] = {
         "    return vec4(dithered * dithered, color.a);\n"
         "}\n"
         "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n"
-        "    return mix(a, b, v);"
+        "    vec4 c = mix(a, b, v);\n"
+        "    return vec4(c.rgb * c.a, c.a);\n"
         "}\n"
 };
 
index 34e6a06..0f6651b 100644 (file)
@@ -81,7 +81,7 @@ static void computeScreenSpaceMatrix(mat4& screenSpace, const SkMatrix& unitMatr
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// gradient shader matrix helpers
+// Gradient shader matrix helpers
 ///////////////////////////////////////////////////////////////////////////////
 
 static void toLinearUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) {
@@ -161,7 +161,7 @@ bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 mode
     gradInfo.fColorOffsets = &colorOffsets[0];
     shader.asAGradient(&gradInfo);
 
-    if (CC_UNLIKELY(!isSimpleGradient(gradInfo))) {
+    if (CC_UNLIKELY(!description->isSimpleGradient)) {
         outData->gradientSampler = (*textureUnit)++;
 
 #ifndef SK_SCALAR_IS_FLOAT
@@ -174,8 +174,8 @@ bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 mode
         outData->gradientSampler = 0;
         outData->gradientTexture = nullptr;
 
-        outData->startColor.setSRGB(gradInfo.fColors[0]);
-        outData->endColor.setSRGB(gradInfo.fColors[1]);
+        outData->startColor.setUnPreMultipliedSRGB(gradInfo.fColors[0]);
+        outData->endColor.setUnPreMultipliedSRGB(gradInfo.fColors[1]);
     }
 
     return true;