Pre-multiply gradient colors the right way
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
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user