Bug 1406 - Handling of premultiplication in clutter

Merge branch 'premultiplication'

[cogl-texture docs] Improves the documentation of the internal_format args
[test-premult] Adds a unit test for texture upload premultiplication semantics
[fog] Document that fogging only works with opaque or unmultipled colors
[test-blend-strings] Explicitly request RGBA_888 tex format for test textures
[premultiplication] Be more conservative with what data gets premultiplied
[bitmap] Fixes _cogl_bitmap_fallback_unpremult
[cogl-bitmap] Fix minor copy and paste error in _cogl_bitmap_fallback_premult
Avoid unnecesary unpremultiplication when saving to local data
Don't unpremultiply Cairo data
Default to a blend function that expects premultiplied colors
Implement premultiplication for CoglBitmap
Use correct texture format for pixmap textures and FBO's
Add cogl_color_premultiply()
This commit is contained in:
Robert Bragg 2009-06-11 15:16:27 +01:00
commit c52eda6715
13 changed files with 263 additions and 67 deletions

View File

@ -228,6 +228,18 @@ float cogl_color_get_blue (const CoglColor *color);
*/ */
float cogl_color_get_alpha (const CoglColor *color); float cogl_color_get_alpha (const CoglColor *color);
/**
* cogl_color_premultiply:
* @color: the color to premultiply
*
* Converts a non-premultiplied color to a pre-multiplied color. For
* example, semi-transparent red is (1.0, 0, 0, 0.5) when non-premultiplied
* and (0.5, 0, 0, 0.5) when premultiplied.
*
* Since: 1.0
*/
void cogl_color_premultiply (CoglColor *color);
G_END_DECLS G_END_DECLS
#endif /* __COGL_COLOR_H__ */ #endif /* __COGL_COLOR_H__ */

View File

@ -138,6 +138,11 @@ gboolean cogl_is_material (CoglHandle handle);
* *
* This is the basic color of the material, used when no lighting is enabled. * This is the basic color of the material, used when no lighting is enabled.
* *
* Note that if you don't add any layers to the material then the color
* will be blended unmodified with the destination; the default blend
* expects premultiplied colors: for example, use (0.5, 0.0, 0.0, 0.5) for
* semi-transparent red. See cogl_color_premultiply().
*
* The default value is (1.0, 1.0, 1.0, 1.0) * The default value is (1.0, 1.0, 1.0, 1.0)
* *
* Since 1.0 * Since 1.0
@ -475,6 +480,11 @@ void cogl_material_set_alpha_test_function (CoglHandle material,
* </programlisting> * </programlisting>
* </section> * </section>
* *
* The default blend string is:
* "RGBA = ADD (SRC_COLOR, DST_COLOR*(1-SRC_COLOR[A]))"
* That gives normal alpha-blending when the calculated color for the material
* is in premultiplied form.
*
* Returns: TRUE if the blend string was successfully parsed, and the described * Returns: TRUE if the blend string was successfully parsed, and the described
* blending is supported by the underlying driver/hardware. If there * blending is supported by the underlying driver/hardware. If there
* was an error, it returns FALSE. * was an error, it returns FALSE.

View File

@ -68,7 +68,13 @@ CoglHandle cogl_texture_new_with_size (guint width,
* @filename: the file to load * @filename: the file to load
* @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE * @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE
* @internal_format: the #CoglPixelFormat to use for the GPU storage of the * @internal_format: the #CoglPixelFormat to use for the GPU storage of the
* texture * texture. If COGL_PIXEL_FORMAT_ANY is given then a premultiplied
* format similar to the format of the source data will be used. The
* default blending equations of Cogl expect premultiplied color data;
* the main use of passing a non-premultiplied format here is if you
* have non-premultiplied source data and are going to adjust the blend
* mode (see cogl_material_set_blend()) or use the data for something
* other than straight blending.
* @error: return location for a #GError or %NULL * @error: return location for a #GError or %NULL
* *
* Creates a COGL texture from an image file. * Creates a COGL texture from an image file.
@ -90,7 +96,13 @@ CoglHandle cogl_texture_new_from_file (const gchar *filename,
* @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE * @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE
* @format: the #CoglPixelFormat the buffer is stored in in RAM * @format: the #CoglPixelFormat the buffer is stored in in RAM
* @internal_format: the #CoglPixelFormat that will be used for storing * @internal_format: the #CoglPixelFormat that will be used for storing
* the buffer on the GPU * the buffer on the GPU. If COGL_PIXEL_FORMAT_ANY is given then a
* premultiplied format similar to the format of the source data will
* be used. The default blending equations of Cogl expect premultiplied
* color data; the main use of passing a non-premultiplied format here
* is if you have non-premultiplied source data and are going to adjust
* the blend mode (see cogl_material_set_blend()) or use the data for
* something other than straight blending.
* @rowstride: the memory offset in bytes between the starts of * @rowstride: the memory offset in bytes between the starts of
* scanlines in @data * scanlines in @data
* @data: pointer the memory region where the source buffer resides * @data: pointer the memory region where the source buffer resides

View File

@ -386,13 +386,22 @@ gboolean cogl_get_backface_culling_enabled (void);
* @z_near: Position along z-axis where no fogging should be applied * @z_near: Position along z-axis where no fogging should be applied
* @z_far: Position along z-axes where full fogging should be applied * @z_far: Position along z-axes where full fogging should be applied
* *
* Enables fogging. Fogging causes vertices that are further away from * Enables fogging. Fogging causes vertices that are further away from the eye
* the eye to be rendered with a different color. The color is determined * to be rendered with a different color. The color is determined according to
* according to the chosen fog mode; at it's simplest the color is * the chosen fog mode; at it's simplest the color is linearly interpolated so
* linearly interpolated so that vertices at @z_near are drawn fully * that vertices at @z_near are drawn fully with their original color and
* with their original color and vertices at @z_far are drawn fully * vertices at @z_far are drawn fully with @fog_color. Fogging will remain
* with @fog_color. Fogging will remain enabled until you call * enabled until you call cogl_disable_fog().
* cogl_disable_fog(). *
* Note: The fogging functions only work correctly when primitives use
* unmultiplied alpha colors. By default Cogl will premultiply textures
* and cogl_set_source_color will premultiply colors, so unless you
* explicitly load your textures requesting an unmultiplied
* internal_format and use cogl_material_set_color you can only use
* fogging with fully opaque primitives.
*
* We can look to improve this in the future when we can depend on
* fragment shaders.
*/ */
void cogl_set_fog (const CoglColor *fog_color, void cogl_set_fog (const CoglColor *fog_color,
CoglFogMode mode, CoglFogMode mode,
@ -455,8 +464,13 @@ void cogl_set_source (CoglHandle material);
* cogl_set_source_color: * cogl_set_source_color:
* @color: a #CoglColor * @color: a #CoglColor
* *
* Sets the source color using normalized values for each component. * This is a convenience function for creating a solid fill source material
* This color will be used for any subsequent drawing operation. * from the given color. This color will be used for any subsequent drawing
* operation.
*
* The color will be premultiplied by Cogl, so the color should be
* non-premultiplied. For example: use (1.0, 0.0, 0.0, 0.5) for
* semi-transparent red.
* *
* See also cogl_set_source_color4ub() and cogl_set_source_color4f() * See also cogl_set_source_color4ub() and cogl_set_source_color4f()
* if you already have the color components. * if you already have the color components.

View File

@ -163,9 +163,9 @@ _cogl_unpremult_alpha_last (const guchar *src, guchar *dst)
{ {
guchar alpha = src[3]; guchar alpha = src[3];
dst[0] = ((((gulong) src[0] >> 16) & 0xff) * 255 ) / alpha; dst[0] = (src[0] * 255) / alpha;
dst[1] = ((((gulong) src[1] >> 8) & 0xff) * 255 ) / alpha; dst[1] = (src[1] * 255) / alpha;
dst[2] = ((((gulong) src[2] >> 0) & 0xff) * 255 ) / alpha; dst[2] = (src[2] * 255) / alpha;
dst[3] = alpha; dst[3] = alpha;
} }
@ -175,11 +175,46 @@ _cogl_unpremult_alpha_first (const guchar *src, guchar *dst)
guchar alpha = src[0]; guchar alpha = src[0];
dst[0] = alpha; dst[0] = alpha;
dst[1] = ((((gulong) src[1] >> 16) & 0xff) * 255 ) / alpha; dst[1] = (src[1] * 255) / alpha;
dst[2] = ((((gulong) src[2] >> 8) & 0xff) * 255 ) / alpha; dst[2] = (src[2] * 255) / alpha;
dst[3] = ((((gulong) src[3] >> 0) & 0xff) * 255 ) / alpha; dst[3] = (src[3] * 255) / alpha;
} }
/* No division form of floor((c*a + 128)/255) (I first encountered
* this in the RENDER implementation in the X server.) Being exact
* is important for a == 255 - we want to get exactly c.
*/
#define MULT(d,c,a,t) G_STMT_START { t = c * a + 128; d = ((t >> 8) + t) >> 8; } G_STMT_END
inline static void
_cogl_premult_alpha_last (const guchar *src, guchar *dst)
{
guchar alpha = src[3];
/* Using a separate temporary per component has given slightly better
* code generation with GCC in the past; it shouldn't do any worse in
* any case.
*/
guint t1, t2, t3;
MULT(dst[0], src[0], alpha, t1);
MULT(dst[1], src[1], alpha, t2);
MULT(dst[2], src[2], alpha, t3);
dst[3] = alpha;
}
inline static void
_cogl_premult_alpha_first (const guchar *src, guchar *dst)
{
guchar alpha = src[0];
guint t1, t2, t3;
dst[0] = alpha;
MULT(dst[1], src[1], alpha, t1);
MULT(dst[2], src[2], alpha, t2);
MULT(dst[3], src[3], alpha, t3);
}
#undef MULT
gboolean gboolean
_cogl_bitmap_fallback_can_convert (CoglPixelFormat src, CoglPixelFormat dst) _cogl_bitmap_fallback_can_convert (CoglPixelFormat src, CoglPixelFormat dst)
{ {
@ -211,6 +246,12 @@ _cogl_bitmap_fallback_can_unpremult (CoglPixelFormat format)
return ((format & COGL_UNORDERED_MASK) == COGL_PIXEL_FORMAT_32); return ((format & COGL_UNORDERED_MASK) == COGL_PIXEL_FORMAT_32);
} }
gboolean
_cogl_bitmap_fallback_can_premult (CoglPixelFormat format)
{
return ((format & COGL_UNORDERED_MASK) == COGL_PIXEL_FORMAT_32);
}
gboolean gboolean
_cogl_bitmap_fallback_convert (const CoglBitmap *bmp, _cogl_bitmap_fallback_convert (const CoglBitmap *bmp,
CoglBitmap *dst_bmp, CoglBitmap *dst_bmp,
@ -324,34 +365,86 @@ _cogl_bitmap_fallback_unpremult (const CoglBitmap *bmp,
* dst_bmp->height * dst_bmp->height
* dst_bmp->rowstride); * dst_bmp->rowstride);
/* FIXME: Optimize */
for (y = 0; y < bmp->height; y++) for (y = 0; y < bmp->height; y++)
{ {
src = (guchar*)bmp->data + y * bmp->rowstride; src = (guchar*)bmp->data + y * bmp->rowstride;
dst = (guchar*)dst_bmp->data + y * dst_bmp->rowstride; dst = (guchar*)dst_bmp->data + y * dst_bmp->rowstride;
for (x = 0; x < bmp->width; x++) if (bmp->format & COGL_AFIRST_BIT)
{ {
/* FIXME: Would be nice to at least remove this inner for (x = 0; x < bmp->width; x++)
* branching, but not sure it can be done without {
* rewriting of the whole loop */ if (src[0] == 0)
if (bmp->format & COGL_AFIRST_BIT) _cogl_unpremult_alpha_0 (src, dst);
{ else
if (src[0] == 0) _cogl_unpremult_alpha_first (src, dst);
_cogl_unpremult_alpha_0 (src, dst); src += bpp;
else dst += bpp;
_cogl_unpremult_alpha_first (src, dst); }
} }
else else
{ {
if (src[3] == 0) for (x = 0; x < bmp->width; x++)
_cogl_unpremult_alpha_0 (src, dst); {
else if (src[0] == 0)
_cogl_unpremult_alpha_last (src, dst); _cogl_unpremult_alpha_0 (src, dst);
} else
_cogl_unpremult_alpha_last (src, dst);
src += bpp;
dst += bpp;
}
}
}
src += bpp; return TRUE;
dst += bpp; }
gboolean
_cogl_bitmap_fallback_premult (const CoglBitmap *bmp,
CoglBitmap *dst_bmp)
{
guchar *src;
guchar *dst;
gint bpp;
gint x,y;
/* Make sure format supported for un-premultiplication */
if (!_cogl_bitmap_fallback_can_premult (bmp->format))
return FALSE;
bpp = _cogl_get_format_bpp (bmp->format);
/* Initialize destination bitmap */
*dst_bmp = *bmp;
dst_bmp->format |= COGL_PREMULT_BIT;
/* Allocate a new buffer to hold converted data */
dst_bmp->data = g_malloc (sizeof(guchar)
* dst_bmp->height
* dst_bmp->rowstride);
for (y = 0; y < bmp->height; y++)
{
src = (guchar*)bmp->data + y * bmp->rowstride;
dst = (guchar*)dst_bmp->data + y * dst_bmp->rowstride;
if (bmp->format & COGL_AFIRST_BIT)
{
for (x = 0; x < bmp->width; x++)
{
_cogl_premult_alpha_first (src, dst);
src += bpp;
dst += bpp;
}
}
else
{
for (x = 0; x < bmp->width; x++)
{
_cogl_premult_alpha_last (src, dst);
src += bpp;
dst += bpp;
}
} }
} }

View File

@ -49,6 +49,12 @@ _cogl_bitmap_can_unpremult (CoglPixelFormat format)
return FALSE; return FALSE;
} }
gboolean
_cogl_bitmap_can_premult (CoglPixelFormat format)
{
return FALSE;
}
gboolean gboolean
_cogl_bitmap_convert (const CoglBitmap *bmp, _cogl_bitmap_convert (const CoglBitmap *bmp,
CoglBitmap *dst_bmp, CoglBitmap *dst_bmp,
@ -64,6 +70,13 @@ _cogl_bitmap_unpremult (const CoglBitmap *bmp,
return FALSE; return FALSE;
} }
gboolean
_cogl_bitmap_premult (const CoglBitmap *bmp,
CoglBitmap *dst_bmp)
{
return FALSE;
}
#ifdef USE_QUARTZ #ifdef USE_QUARTZ
/* lacking GdkPixbuf and other useful GError domains, define one of our own */ /* lacking GdkPixbuf and other useful GError domains, define one of our own */

View File

@ -52,6 +52,12 @@ _cogl_bitmap_can_unpremult (CoglPixelFormat format);
gboolean gboolean
_cogl_bitmap_fallback_can_unpremult (CoglPixelFormat format); _cogl_bitmap_fallback_can_unpremult (CoglPixelFormat format);
gboolean
_cogl_bitmap_can_premult (CoglPixelFormat format);
gboolean
_cogl_bitmap_fallback_can_premult (CoglPixelFormat format);
gboolean gboolean
_cogl_bitmap_convert (const CoglBitmap *bmp, _cogl_bitmap_convert (const CoglBitmap *bmp,
CoglBitmap *dst_bmp, CoglBitmap *dst_bmp,
@ -69,6 +75,14 @@ gboolean
_cogl_bitmap_fallback_unpremult (const CoglBitmap *bmp, _cogl_bitmap_fallback_unpremult (const CoglBitmap *bmp,
CoglBitmap *dst_bmp); CoglBitmap *dst_bmp);
gboolean
_cogl_bitmap_premult (const CoglBitmap *bmp,
CoglBitmap *dst_bmp);
gboolean
_cogl_bitmap_fallback_premult (const CoglBitmap *bmp,
CoglBitmap *dst_bmp);
gboolean gboolean
_cogl_bitmap_from_file (CoglBitmap *bmp, _cogl_bitmap_from_file (CoglBitmap *bmp,
const gchar *filename, const gchar *filename,

View File

@ -87,8 +87,8 @@ _cogl_bitmap_convert_and_premult (const CoglBitmap *bmp,
} }
/* Do we need to unpremultiply */ /* Do we need to unpremultiply */
if ((bmp->format & COGL_PREMULT_BIT) == 0 && if ((bmp->format & COGL_PREMULT_BIT) > 0 &&
(dst_format & COGL_PREMULT_BIT) > 0) (dst_format & COGL_PREMULT_BIT) == 0)
{ {
/* Try unpremultiplying using imaging library */ /* Try unpremultiplying using imaging library */
if (!_cogl_bitmap_unpremult (&new_bmp, &tmp_bmp)) if (!_cogl_bitmap_unpremult (&new_bmp, &tmp_bmp))
@ -112,14 +112,28 @@ _cogl_bitmap_convert_and_premult (const CoglBitmap *bmp,
} }
/* Do we need to premultiply */ /* Do we need to premultiply */
if ((bmp->format & COGL_PREMULT_BIT) > 0 && if ((bmp->format & COGL_PREMULT_BIT) == 0 &&
(dst_format & COGL_PREMULT_BIT) == 0) (dst_format & COGL_PREMULT_BIT) > 0)
{ {
/* FIXME: implement premultiplication */ /* Try premultiplying using imaging library */
if (!_cogl_bitmap_premult (&new_bmp, &tmp_bmp))
{
/* ... or try fallback */
if (!_cogl_bitmap_fallback_premult (&new_bmp, &tmp_bmp))
{
if (new_bmp_owner)
g_free (new_bmp.data);
return FALSE;
}
}
/* Update bitmap with new data */
if (new_bmp_owner) if (new_bmp_owner)
g_free (new_bmp.data); g_free (new_bmp.data);
return FALSE; new_bmp = tmp_bmp;
new_bmp_owner = TRUE;
} }
/* Output new bitmap info */ /* Output new bitmap info */

View File

@ -153,6 +153,14 @@ cogl_color_get_alpha (const CoglColor *color)
return ((float) color->alpha / 255.0); return ((float) color->alpha / 255.0);
} }
void
cogl_color_premultiply (CoglColor *color)
{
color->red = (color->red * color->alpha + 128) / 255;
color->green = (color->green * color->alpha + 128) / 255;
color->blue = (color->blue * color->alpha + 128) / 255;
}
void void
cogl_set_source_color4ub (guint8 red, cogl_set_source_color4ub (guint8 red,
guint8 green, guint8 green,

View File

@ -109,7 +109,7 @@ cogl_material_new (void)
material->blend_constant[2] = 0; material->blend_constant[2] = 0;
material->blend_constant[3] = 0; material->blend_constant[3] = 0;
#endif #endif
material->blend_src_factor_rgb = GL_SRC_ALPHA; material->blend_src_factor_rgb = GL_ONE;
material->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA; material->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA;
material->flags |= COGL_MATERIAL_FLAG_DEFAULT_BLEND_FUNC; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_BLEND_FUNC;

View File

@ -250,12 +250,17 @@ cogl_get_backface_culling_enabled (void)
void void
cogl_set_source_color (const CoglColor *color) cogl_set_source_color (const CoglColor *color)
{ {
CoglColor premultiplied;
_COGL_GET_CONTEXT (ctx, NO_RETVAL); _COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* In case cogl_set_source_texture was previously used... */ /* In case cogl_set_source_texture was previously used... */
cogl_material_remove_layer (ctx->default_material, 0); cogl_material_remove_layer (ctx->default_material, 0);
cogl_material_set_color (ctx->default_material, color); premultiplied = *color;
cogl_color_premultiply (&premultiplied);
cogl_material_set_color (ctx->default_material, &premultiplied);
cogl_set_source (ctx->default_material); cogl_set_source (ctx->default_material);
} }

View File

@ -1085,18 +1085,12 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format,
GLenum glformat = 0; GLenum glformat = 0;
GLenum gltype = 0; GLenum gltype = 0;
/* No premultiplied formats accepted by GL /* FIXME: check YUV support */
* (FIXME: latest hardware?) */
if (format & COGL_PREMULT_BIT)
format = (format & COGL_UNPREMULT_MASK);
/* Everything else accepted
* (FIXME: check YUV support) */
required_format = format; required_format = format;
/* Find GL equivalents */ /* Find GL equivalents */
switch (format) switch (format & COGL_UNPREMULT_MASK)
{ {
case COGL_PIXEL_FORMAT_A_8: case COGL_PIXEL_FORMAT_A_8:
glintformat = GL_ALPHA; glintformat = GL_ALPHA;
@ -1195,9 +1189,15 @@ _cogl_texture_bitmap_prepare (CoglTexture *tex,
CoglPixelFormat new_data_format; CoglPixelFormat new_data_format;
gboolean success; gboolean success;
/* Was there any internal conversion requested? */ /* Was there any internal conversion requested?
* By default Cogl will use a premultiplied internal format. Later we will
* add control over this. */
if (internal_format == COGL_PIXEL_FORMAT_ANY) if (internal_format == COGL_PIXEL_FORMAT_ANY)
internal_format = tex->bitmap.format; {
if ((tex->bitmap.format & COGL_A_BIT) &&
tex->bitmap.format != COGL_PIXEL_FORMAT_A_8)
internal_format = tex->bitmap.format | COGL_PREMULT_BIT;
}
/* Find closest format accepted by GL */ /* Find closest format accepted by GL */
new_data_format = _cogl_pixel_format_to_gl (internal_format, new_data_format = _cogl_pixel_format_to_gl (internal_format,

View File

@ -1184,18 +1184,12 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format,
GLenum glformat = 0; GLenum glformat = 0;
GLenum gltype = 0; GLenum gltype = 0;
/* No premultiplied formats accepted by GL /* FIXME: check YUV support */
* (FIXME: latest hardware?) */
if (format & COGL_PREMULT_BIT)
format = (format & COGL_UNPREMULT_MASK);
/* Everything else accepted
* (FIXME: check YUV support) */
required_format = format; required_format = format;
/* Find GL equivalents */ /* Find GL equivalents */
switch (format) switch (format & COGL_UNPREMULT_MASK)
{ {
case COGL_PIXEL_FORMAT_A_8: case COGL_PIXEL_FORMAT_A_8:
glintformat = GL_ALPHA; glintformat = GL_ALPHA;
@ -1226,6 +1220,7 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format,
glformat = GL_RGBA; glformat = GL_RGBA;
gltype = GL_UNSIGNED_BYTE; gltype = GL_UNSIGNED_BYTE;
required_format = COGL_PIXEL_FORMAT_RGBA_8888; required_format = COGL_PIXEL_FORMAT_RGBA_8888;
required_format |= (format & COGL_PREMULT_BIT);
break; break;
/* The following three types of channel ordering /* The following three types of channel ordering
@ -1270,9 +1265,15 @@ _cogl_texture_bitmap_prepare (CoglTexture *tex,
CoglPixelFormat new_data_format; CoglPixelFormat new_data_format;
gboolean success; gboolean success;
/* Was there any internal conversion requested? */ /* Was there any internal conversion requested?
* By default Cogl will use a premultiplied internal format. Later we will
* add control over this. */
if (internal_format == COGL_PIXEL_FORMAT_ANY) if (internal_format == COGL_PIXEL_FORMAT_ANY)
internal_format = tex->bitmap.format; {
if ((tex->bitmap.format & COGL_A_BIT) &&
tex->bitmap.format != COGL_PIXEL_FORMAT_A_8)
internal_format = tex->bitmap.format | COGL_PREMULT_BIT;
}
/* Find closest format accepted by GL */ /* Find closest format accepted by GL */
new_data_format = _cogl_pixel_format_to_gl (internal_format, new_data_format = _cogl_pixel_format_to_gl (internal_format,