diff --git a/cogl-color.h b/cogl-color.h index 0cbc479a4..73de891de 100644 --- a/cogl-color.h +++ b/cogl-color.h @@ -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__ */ diff --git a/cogl-material.h b/cogl-material.h index 497d7f852..786ff344a 100644 --- a/cogl-material.h +++ b/cogl-material.h @@ -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, * * * + * 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. diff --git a/cogl-texture.h b/cogl-texture.h index 15cadc879..e3a2264a1 100644 --- a/cogl-texture.h +++ b/cogl-texture.h @@ -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 diff --git a/cogl.h.in b/cogl.h.in index 9425a1267..c1cc10f29 100644 --- a/cogl.h.in +++ b/cogl.h.in @@ -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. diff --git a/common/cogl-bitmap-fallback.c b/common/cogl-bitmap-fallback.c index e118459e1..ddff6ea40 100644 --- a/common/cogl-bitmap-fallback.c +++ b/common/cogl-bitmap-fallback.c @@ -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; + } } } diff --git a/common/cogl-bitmap-pixbuf.c b/common/cogl-bitmap-pixbuf.c index f1e500534..1b75cd0ba 100644 --- a/common/cogl-bitmap-pixbuf.c +++ b/common/cogl-bitmap-pixbuf.c @@ -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 */ diff --git a/common/cogl-bitmap-private.h b/common/cogl-bitmap-private.h index f54961bd0..b6e425733 100644 --- a/common/cogl-bitmap-private.h +++ b/common/cogl-bitmap-private.h @@ -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, diff --git a/common/cogl-bitmap.c b/common/cogl-bitmap.c index f4c322abb..3dc7fd534 100644 --- a/common/cogl-bitmap.c +++ b/common/cogl-bitmap.c @@ -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 */ diff --git a/common/cogl-color.c b/common/cogl-color.c index 306005f2f..df338fcc4 100644 --- a/common/cogl-color.c +++ b/common/cogl-color.c @@ -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, diff --git a/common/cogl-material.c b/common/cogl-material.c index 831fa30f1..7e3935bff 100644 --- a/common/cogl-material.c +++ b/common/cogl-material.c @@ -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; diff --git a/common/cogl.c b/common/cogl.c index 336303917..5857db2d3 100644 --- a/common/cogl.c +++ b/common/cogl.c @@ -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); } diff --git a/gl/cogl-texture.c b/gl/cogl-texture.c index 3af78f9a0..877107d3c 100644 --- a/gl/cogl-texture.c +++ b/gl/cogl-texture.c @@ -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, diff --git a/gles/cogl-texture.c b/gles/cogl-texture.c index ae3e437b8..ab1653e07 100644 --- a/gles/cogl-texture.c +++ b/gles/cogl-texture.c @@ -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,