add WebPBlendAlpha() function to blend colors against background

new option: -blend_alpha 0xrrggbb
also: don't force picture.use_argb value for lossless. Instead,
delay the YUVA<->ARGB conversion till WebPEncode() is called.
This make the blending more accurate when source is ARGB
and lossy compression is used (YUVA).
This has an effect on cropping/rescaling. E.g. for PNG, these
are now done in ARGB colorspace instead of YUV when lossy compression
is used.

Change-Id: I18571f1b1179881737a8dbd23ad0aa8cddae3c6b
This commit is contained in:
Pascal Massimino 2013-04-02 19:14:14 -07:00
parent df4a406d8d
commit e7d9548c9b
8 changed files with 121 additions and 6 deletions

4
README
View File

@ -170,6 +170,10 @@ options:
-alpha_filter <string> . predictive filtering for alpha plane. -alpha_filter <string> . predictive filtering for alpha plane.
One of: none, fast (default) or best. One of: none, fast (default) or best.
-alpha_cleanup ......... Clean RGB values in transparent area. -alpha_cleanup ......... Clean RGB values in transparent area.
-blend_alpha <hex> ..... Blend colors against background color
expressed as RGB values written in
hexadecimal, e.g. 0xc0e0d0 for red=0xc0
green=0xe0 and blue=0xd0.
-noalpha ............... discard any transparency information. -noalpha ............... discard any transparency information.
-lossless .............. Encode image losslessly. -lossless .............. Encode image losslessly.
-hint <string> ......... Specify image characteristics hint. -hint <string> ......... Specify image characteristics hint.

View File

@ -594,6 +594,10 @@ static void HelpLong(void) {
printf(" -alpha_filter <string> . predictive filtering for alpha plane.\n"); printf(" -alpha_filter <string> . predictive filtering for alpha plane.\n");
printf(" One of: none, fast (default) or best.\n"); printf(" One of: none, fast (default) or best.\n");
printf(" -alpha_cleanup ......... Clean RGB values in transparent area.\n"); printf(" -alpha_cleanup ......... Clean RGB values in transparent area.\n");
printf(" -blend_alpha <hex> ..... Blend colors against background color\n"
" expressed as RGB values written in\n"
" hexadecimal, e.g. 0xc0e0d0 for red=0xc0\n"
" green=0xe0 and blue=0xd0.\n");
printf(" -noalpha ............... discard any transparency information.\n"); printf(" -noalpha ............... discard any transparency information.\n");
printf(" -lossless .............. Encode image losslessly.\n"); printf(" -lossless .............. Encode image losslessly.\n");
printf(" -hint <string> ......... Specify image characteristics hint.\n"); printf(" -hint <string> ......... Specify image characteristics hint.\n");
@ -656,6 +660,8 @@ int main(int argc, const char *argv[]) {
int short_output = 0; int short_output = 0;
int quiet = 0; int quiet = 0;
int keep_alpha = 1; int keep_alpha = 1;
int blend_alpha = 0;
uint32_t background_color = 0xffffffu;
int crop = 0, crop_x = 0, crop_y = 0, crop_w = 0, crop_h = 0; int crop = 0, crop_x = 0, crop_y = 0, crop_w = 0, crop_h = 0;
int resize_w = 0, resize_h = 0; int resize_w = 0, resize_h = 0;
int show_progress = 0; int show_progress = 0;
@ -720,6 +726,10 @@ int main(int argc, const char *argv[]) {
config.alpha_compression = strtol(argv[++c], NULL, 0); config.alpha_compression = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-alpha_cleanup")) { } else if (!strcmp(argv[c], "-alpha_cleanup")) {
keep_alpha = keep_alpha ? 2 : 0; keep_alpha = keep_alpha ? 2 : 0;
} else if (!strcmp(argv[c], "-blend_alpha") && c < argc - 1) {
blend_alpha = 1;
background_color = strtol(argv[++c], NULL, 16); // <- parses '0x' prefix
background_color = background_color & 0x00ffffffu;
} else if (!strcmp(argv[c], "-alpha_filter") && c < argc - 1) { } else if (!strcmp(argv[c], "-alpha_filter") && c < argc - 1) {
++c; ++c;
if (!strcmp(argv[c], "none")) { if (!strcmp(argv[c], "none")) {
@ -736,7 +746,6 @@ int main(int argc, const char *argv[]) {
keep_alpha = 0; keep_alpha = 0;
} else if (!strcmp(argv[c], "-lossless")) { } else if (!strcmp(argv[c], "-lossless")) {
config.lossless = 1; config.lossless = 1;
picture.use_argb = 1;
} else if (!strcmp(argv[c], "-hint") && c < argc - 1) { } else if (!strcmp(argv[c], "-hint") && c < argc - 1) {
++c; ++c;
if (!strcmp(argv[c], "photo")) { if (!strcmp(argv[c], "photo")) {
@ -924,6 +933,11 @@ int main(int argc, const char *argv[]) {
goto Error; goto Error;
} }
picture.progress_hook = (show_progress && !quiet) ? ProgressReport : NULL; picture.progress_hook = (show_progress && !quiet) ? ProgressReport : NULL;
if (blend_alpha) {
WebPBlendAlpha(&picture, background_color);
}
if (keep_alpha == 2) { if (keep_alpha == 2) {
WebPCleanupTransparentArea(&picture); WebPCleanupTransparentArea(&picture);
} }

View File

@ -267,6 +267,7 @@ int ReadPNG(FILE* in_file, WebPPicture* const pic, int keep_alpha,
pic->width = width; pic->width = width;
pic->height = height; pic->height = height;
pic->use_argb = 1;
ok = has_alpha ? WebPPictureImportRGBA(pic, rgb, stride) ok = has_alpha ? WebPPictureImportRGBA(pic, rgb, stride)
: WebPPictureImportRGB(pic, rgb, stride); : WebPPictureImportRGB(pic, rgb, stride);
free(rgb); free(rgb);

View File

@ -98,6 +98,7 @@ int ReadTIFF(const char* const filename,
#ifdef __BIG_ENDIAN__ #ifdef __BIG_ENDIAN__
TIFFSwabArrayOfLong(raster, width * height); TIFFSwabArrayOfLong(raster, width * height);
#endif #endif
pic->use_argb = 1;
ok = keep_alpha ok = keep_alpha
? WebPPictureImportRGBA(pic, (const uint8_t*)raster, stride) ? WebPPictureImportRGBA(pic, (const uint8_t*)raster, stride)
: WebPPictureImportRGBX(pic, (const uint8_t*)raster, stride); : WebPPictureImportRGBX(pic, (const uint8_t*)raster, stride);

View File

@ -306,6 +306,7 @@ int ReadPictureWithWIC(const char* const filename,
int ok; int ok;
pic->width = width; pic->width = width;
pic->height = height; pic->height = height;
pic->use_argb = 1;
ok = importer->import(pic, rgb, stride); ok = importer->import(pic, rgb, stride);
if (!ok) hr = E_FAIL; if (!ok) hr = E_FAIL;
} }

View File

@ -1,5 +1,5 @@
.\" Hey, EMACS: -*- nroff -*- .\" Hey, EMACS: -*- nroff -*-
.TH CWEBP 1 "March 13, 2013" .TH CWEBP 1 "March 28, 2013"
.SH NAME .SH NAME
cwebp \- compress an image file to a WebP file cwebp \- compress an image file to a WebP file
.SH SYNOPSIS .SH SYNOPSIS
@ -187,6 +187,11 @@ no compression, 1 uses WebP lossless format for compression. The default is 1.
Modify unseen RGB values under fully transparent area, to help compressibility. Modify unseen RGB values under fully transparent area, to help compressibility.
The default is off. The default is off.
.TP .TP
.BI \-blend_alpha " int
This option blends the alpha channel (if present) with the source using the
background color specified in hexadecimal as 0xrrggbb. The alpha channel is
afterward reset to the opaque value 255.
.TP
.B \-noalpha .B \-noalpha
Using this option will discard the alpha channel. Using this option will discard the alpha channel.
.TP .TP

View File

@ -32,6 +32,10 @@ static const union {
} test_endian = { 0xff000000u }; } test_endian = { 0xff000000u };
#define ALPHA_IS_LAST (test_endian.bytes[3] == 0xff) #define ALPHA_IS_LAST (test_endian.bytes[3] == 0xff)
static WEBP_INLINE uint32_t MakeARGB32(int r, int g, int b) {
return (0xff000000u | (r << 16) | (g << 8) | b);
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// WebPPicture // WebPPicture
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -696,10 +700,7 @@ static int Import(WebPPicture* const picture,
for (x = 0; x < width; ++x) { for (x = 0; x < width; ++x) {
const int offset = step * x + y * rgb_stride; const int offset = step * x + y * rgb_stride;
const uint32_t argb = const uint32_t argb =
0xff000000u | MakeARGB32(r_ptr[offset], g_ptr[offset], b_ptr[offset]);
(r_ptr[offset] << 16) |
(g_ptr[offset] << 8) |
(b_ptr[offset]);
picture->argb[x + y * picture->argb_stride] = argb; picture->argb[x + y * picture->argb_stride] = argb;
} }
} }
@ -910,6 +911,89 @@ void WebPCleanupTransparentArea(WebPPicture* pic) {
#undef SIZE #undef SIZE
#undef SIZE2 #undef SIZE2
//------------------------------------------------------------------------------
// Blend color and remove transparency info
#define BLEND(V0, V1, ALPHA) \
((((V0) * (255 - (ALPHA)) + (V1) * (ALPHA)) * 0x101) >> 16)
#define BLEND_10BIT(V0, V1, ALPHA) \
((((V0) * (1020 - (ALPHA)) + (V1) * (ALPHA)) * 0x101) >> 18)
void WebPBlendAlpha(WebPPicture* pic, uint32_t background_rgb) {
const int red = (background_rgb >> 16) & 0xff;
const int green = (background_rgb >> 8) & 0xff;
const int blue = (background_rgb >> 0) & 0xff;
int x, y;
if (pic == NULL) return;
if (!pic->use_argb) {
const int uv_width = (pic->width >> 1); // omit last pixel during u/v loop
const int Y0 = VP8RGBToY(red, green, blue);
// VP8RGBToU/V expects the u/v values summed over four pixels
const int U0 = VP8RGBToU(4 * red, 4 * green, 4 * blue);
const int V0 = VP8RGBToV(4 * red, 4 * green, 4 * blue);
const int has_alpha = pic->colorspace & WEBP_CSP_ALPHA_BIT;
if (!has_alpha || pic->a == NULL) return; // nothing to do
for (y = 0; y < pic->height; ++y) {
// Luma blending
uint8_t* const y_ptr = pic->y + y * pic->y_stride;
uint8_t* const a_ptr = pic->a + y * pic->a_stride;
for (x = 0; x < pic->width; ++x) {
const int alpha = a_ptr[x];
if (alpha < 0xff) {
y_ptr[x] = BLEND(Y0, y_ptr[x], a_ptr[x]);
}
}
// Chroma blending every even line
if ((y & 1) == 0) {
uint8_t* const u = pic->u + (y >> 1) * pic->uv_stride;
uint8_t* const v = pic->v + (y >> 1) * pic->uv_stride;
uint8_t* const a_ptr2 =
(y + 1 == pic->height) ? a_ptr : a_ptr + pic->a_stride;
for (x = 0; x < uv_width; ++x) {
// Average four alpha values into a single blending weight.
// TODO(skal): might lead to visible contouring. Can we do better?
const int alpha =
a_ptr[2 * x + 0] + a_ptr[2 * x + 1] +
a_ptr2[2 * x + 0] + a_ptr2[2 * x + 1];
u[x] = BLEND_10BIT(U0, u[x], alpha);
v[x] = BLEND_10BIT(V0, v[x], alpha);
}
if (pic->width & 1) { // rightmost pixel
const int alpha = 2 * (a_ptr[2 * x + 0] + a_ptr2[2 * x + 0]);
u[x] = BLEND_10BIT(U0, u[x], alpha);
v[x] = BLEND_10BIT(V0, v[x], alpha);
}
}
memset(a_ptr, 0xff, pic->width);
}
} else {
uint32_t* argb = pic->argb;
const uint32_t background = MakeARGB32(red, green, blue);
for (y = 0; y < pic->height; ++y) {
for (x = 0; x < pic->width; ++x) {
const int alpha = (argb[x] >> 24) & 0xff;
if (alpha != 0xff) {
if (alpha > 0) {
int r = (argb[x] >> 16) & 0xff;
int g = (argb[x] >> 8) & 0xff;
int b = (argb[x] >> 0) & 0xff;
r = BLEND(red, r, alpha);
g = BLEND(green, g, alpha);
b = BLEND(blue, b, alpha);
argb[x] = MakeARGB32(r, g, b);
} else {
argb[x] = background;
}
}
}
argb += pic->argb_stride;
}
}
}
#undef BLEND
#undef BLEND_10BIT
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// local-min distortion // local-min distortion
// //

View File

@ -456,6 +456,11 @@ WEBP_EXTERN(void) WebPCleanupTransparentArea(WebPPicture* picture);
// alpha plane can be ignored altogether e.g.). // alpha plane can be ignored altogether e.g.).
WEBP_EXTERN(int) WebPPictureHasTransparency(const WebPPicture* picture); WEBP_EXTERN(int) WebPPictureHasTransparency(const WebPPicture* picture);
// Remove the transparency information (if present) by blending the color with
// the background color 'background_rgb' (specified as 24bit RGB triplet).
// After this call, all alpha values are reset to 0xff.
WEBP_EXTERN(void) WebPBlendAlpha(WebPPicture* pic, uint32_t background_rgb);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Main call // Main call