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);
/**
* 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
#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.
*
* 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)
*
* Since 1.0
@ -475,6 +480,11 @@ void cogl_material_set_alpha_test_function (CoglHandle material,
* </programlisting>
* </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
* blending is supported by the underlying driver/hardware. If there
* 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
* @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE
* @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
*
* 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
* @format: the #CoglPixelFormat the buffer is stored in in RAM
* @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
* scanlines in @data
* @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_far: Position along z-axes where full fogging should be applied
*
* Enables fogging. Fogging causes vertices that are further away from
* the eye to be rendered with a different color. The color is determined
* according to the chosen fog mode; at it's simplest the color is
* linearly interpolated so that vertices at @z_near are drawn fully
* with their original color and vertices at @z_far are drawn fully
* with @fog_color. Fogging will remain enabled until you call
* cogl_disable_fog().
* Enables fogging. Fogging causes vertices that are further away from the eye
* to be rendered with a different color. The color is determined according to
* the chosen fog mode; at it's simplest the color is linearly interpolated so
* that vertices at @z_near are drawn fully with their original color and
* vertices at @z_far are drawn fully with @fog_color. Fogging will remain
* enabled until you call 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,
CoglFogMode mode,
@ -455,8 +464,13 @@ void cogl_set_source (CoglHandle material);
* cogl_set_source_color:
* @color: a #CoglColor
*
* Sets the source color using normalized values for each component.
* This color will be used for any subsequent drawing operation.
* This is a convenience function for creating a solid fill source material
* 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()
* 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];
dst[0] = ((((gulong) src[0] >> 16) & 0xff) * 255 ) / alpha;
dst[1] = ((((gulong) src[1] >> 8) & 0xff) * 255 ) / alpha;
dst[2] = ((((gulong) src[2] >> 0) & 0xff) * 255 ) / alpha;
dst[0] = (src[0] * 255) / alpha;
dst[1] = (src[1] * 255) / alpha;
dst[2] = (src[2] * 255) / alpha;
dst[3] = alpha;
}
@ -175,11 +175,46 @@ _cogl_unpremult_alpha_first (const guchar *src, guchar *dst)
guchar alpha = src[0];
dst[0] = alpha;
dst[1] = ((((gulong) src[1] >> 16) & 0xff) * 255 ) / alpha;
dst[2] = ((((gulong) src[2] >> 8) & 0xff) * 255 ) / alpha;
dst[3] = ((((gulong) src[3] >> 0) & 0xff) * 255 ) / alpha;
dst[1] = (src[1] * 255) / alpha;
dst[2] = (src[2] * 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
_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);
}
gboolean
_cogl_bitmap_fallback_can_premult (CoglPixelFormat format)
{
return ((format & COGL_UNORDERED_MASK) == COGL_PIXEL_FORMAT_32);
}
gboolean
_cogl_bitmap_fallback_convert (const CoglBitmap *bmp,
CoglBitmap *dst_bmp,
@ -324,34 +365,86 @@ _cogl_bitmap_fallback_unpremult (const CoglBitmap *bmp,
* dst_bmp->height
* dst_bmp->rowstride);
/* FIXME: Optimize */
for (y = 0; y < bmp->height; y++)
{
src = (guchar*)bmp->data + y * bmp->rowstride;
dst = (guchar*)dst_bmp->data + y * dst_bmp->rowstride;
for (x = 0; x < bmp->width; x++)
{
/* FIXME: Would be nice to at least remove this inner
* branching, but not sure it can be done without
* rewriting of the whole loop */
if (bmp->format & COGL_AFIRST_BIT)
{
if (src[0] == 0)
_cogl_unpremult_alpha_0 (src, dst);
else
_cogl_unpremult_alpha_first (src, dst);
}
else
{
if (src[3] == 0)
_cogl_unpremult_alpha_0 (src, dst);
else
_cogl_unpremult_alpha_last (src, dst);
}
if (bmp->format & COGL_AFIRST_BIT)
{
for (x = 0; x < bmp->width; x++)
{
if (src[0] == 0)
_cogl_unpremult_alpha_0 (src, dst);
else
_cogl_unpremult_alpha_first (src, dst);
src += bpp;
dst += bpp;
}
}
else
{
for (x = 0; x < bmp->width; x++)
{
if (src[0] == 0)
_cogl_unpremult_alpha_0 (src, dst);
else
_cogl_unpremult_alpha_last (src, dst);
src += bpp;
dst += bpp;
}
}
}
src += bpp;
dst += bpp;
return TRUE;
}
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;
}
gboolean
_cogl_bitmap_can_premult (CoglPixelFormat format)
{
return FALSE;
}
gboolean
_cogl_bitmap_convert (const CoglBitmap *bmp,
CoglBitmap *dst_bmp,
@ -64,6 +70,13 @@ _cogl_bitmap_unpremult (const CoglBitmap *bmp,
return FALSE;
}
gboolean
_cogl_bitmap_premult (const CoglBitmap *bmp,
CoglBitmap *dst_bmp)
{
return FALSE;
}
#ifdef USE_QUARTZ
/* 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
_cogl_bitmap_fallback_can_unpremult (CoglPixelFormat format);
gboolean
_cogl_bitmap_can_premult (CoglPixelFormat format);
gboolean
_cogl_bitmap_fallback_can_premult (CoglPixelFormat format);
gboolean
_cogl_bitmap_convert (const CoglBitmap *bmp,
CoglBitmap *dst_bmp,
@ -69,6 +75,14 @@ gboolean
_cogl_bitmap_fallback_unpremult (const CoglBitmap *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
_cogl_bitmap_from_file (CoglBitmap *bmp,
const gchar *filename,

View File

@ -87,8 +87,8 @@ _cogl_bitmap_convert_and_premult (const CoglBitmap *bmp,
}
/* Do we need to unpremultiply */
if ((bmp->format & COGL_PREMULT_BIT) == 0 &&
(dst_format & COGL_PREMULT_BIT) > 0)
if ((bmp->format & COGL_PREMULT_BIT) > 0 &&
(dst_format & COGL_PREMULT_BIT) == 0)
{
/* Try unpremultiplying using imaging library */
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 */
if ((bmp->format & COGL_PREMULT_BIT) > 0 &&
(dst_format & COGL_PREMULT_BIT) == 0)
if ((bmp->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)
g_free (new_bmp.data);
return FALSE;
new_bmp = tmp_bmp;
new_bmp_owner = TRUE;
}
/* Output new bitmap info */

View File

@ -153,6 +153,14 @@ cogl_color_get_alpha (const CoglColor *color)
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
cogl_set_source_color4ub (guint8 red,
guint8 green,

View File

@ -109,7 +109,7 @@ cogl_material_new (void)
material->blend_constant[2] = 0;
material->blend_constant[3] = 0;
#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->flags |= COGL_MATERIAL_FLAG_DEFAULT_BLEND_FUNC;

View File

@ -250,12 +250,17 @@ cogl_get_backface_culling_enabled (void)
void
cogl_set_source_color (const CoglColor *color)
{
CoglColor premultiplied;
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
/* In case cogl_set_source_texture was previously used... */
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);
}

View File

@ -1085,18 +1085,12 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format,
GLenum glformat = 0;
GLenum gltype = 0;
/* No premultiplied formats accepted by GL
* (FIXME: latest hardware?) */
/* FIXME: check YUV support */
if (format & COGL_PREMULT_BIT)
format = (format & COGL_UNPREMULT_MASK);
/* Everything else accepted
* (FIXME: check YUV support) */
required_format = format;
/* Find GL equivalents */
switch (format)
switch (format & COGL_UNPREMULT_MASK)
{
case COGL_PIXEL_FORMAT_A_8:
glintformat = GL_ALPHA;
@ -1195,9 +1189,15 @@ _cogl_texture_bitmap_prepare (CoglTexture *tex,
CoglPixelFormat new_data_format;
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)
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 */
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 gltype = 0;
/* No premultiplied formats accepted by GL
* (FIXME: latest hardware?) */
/* FIXME: check YUV support */
if (format & COGL_PREMULT_BIT)
format = (format & COGL_UNPREMULT_MASK);
/* Everything else accepted
* (FIXME: check YUV support) */
required_format = format;
/* Find GL equivalents */
switch (format)
switch (format & COGL_UNPREMULT_MASK)
{
case COGL_PIXEL_FORMAT_A_8:
glintformat = GL_ALPHA;
@ -1226,6 +1220,7 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format,
glformat = GL_RGBA;
gltype = GL_UNSIGNED_BYTE;
required_format = COGL_PIXEL_FORMAT_RGBA_8888;
required_format |= (format & COGL_PREMULT_BIT);
break;
/* The following three types of channel ordering
@ -1270,9 +1265,15 @@ _cogl_texture_bitmap_prepare (CoglTexture *tex,
CoglPixelFormat new_data_format;
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)
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 */
new_data_format = _cogl_pixel_format_to_gl (internal_format,