mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-26 13:48:21 +01:00
split monolithic picture.c into picture_{tools,psnr,rescale}.c
Change-Id: Ia5eb5496e4337e5bac8203872c5b014cad21c4f9
This commit is contained in:
parent
c76f07ecc2
commit
fbadb48026
@ -64,6 +64,9 @@ LOCAL_SRC_FILES := \
|
||||
src/enc/histogram.c \
|
||||
src/enc/iterator.c \
|
||||
src/enc/picture.c \
|
||||
src/enc/picture_psnr.c \
|
||||
src/enc/picture_tools.c \
|
||||
src/enc/picture_rescale.c \
|
||||
src/enc/quant.c \
|
||||
src/enc/syntax.c \
|
||||
src/enc/token.c \
|
||||
|
@ -218,6 +218,9 @@ ENC_OBJS = \
|
||||
$(DIROBJ)\enc\histogram.obj \
|
||||
$(DIROBJ)\enc\iterator.obj \
|
||||
$(DIROBJ)\enc\picture.obj \
|
||||
$(DIROBJ)\enc\picture_psnr.obj \
|
||||
$(DIROBJ)\enc\picture_tools.obj \
|
||||
$(DIROBJ)\enc\picture_rescale.obj \
|
||||
$(DIROBJ)\enc\quant.obj \
|
||||
$(DIROBJ)\enc\syntax.obj \
|
||||
$(DIROBJ)\enc\token.obj \
|
||||
|
@ -143,6 +143,9 @@ ENC_OBJS = \
|
||||
src/enc/histogram.o \
|
||||
src/enc/iterator.o \
|
||||
src/enc/picture.o \
|
||||
src/enc/picture_psnr.o \
|
||||
src/enc/picture_rescale.o \
|
||||
src/enc/picture_tools.o \
|
||||
src/enc/quant.o \
|
||||
src/enc/syntax.o \
|
||||
src/enc/token.o \
|
||||
|
@ -12,6 +12,9 @@ libwebpencode_la_SOURCES += frame.c
|
||||
libwebpencode_la_SOURCES += histogram.c
|
||||
libwebpencode_la_SOURCES += iterator.c
|
||||
libwebpencode_la_SOURCES += picture.c
|
||||
libwebpencode_la_SOURCES += picture_psnr.c
|
||||
libwebpencode_la_SOURCES += picture_rescale.c
|
||||
libwebpencode_la_SOURCES += picture_tools.c
|
||||
libwebpencode_la_SOURCES += quant.c
|
||||
libwebpencode_la_SOURCES += syntax.c
|
||||
libwebpencode_la_SOURCES += token.c
|
||||
|
@ -17,9 +17,7 @@
|
||||
|
||||
#include "./vp8enci.h"
|
||||
#include "../utils/random.h"
|
||||
#include "../utils/rescaler.h"
|
||||
#include "../utils/utils.h"
|
||||
#include "../dsp/dsp.h"
|
||||
#include "../dsp/yuv.h"
|
||||
|
||||
// Uncomment to disable gamma-compression during RGB->U/V averaging
|
||||
@ -141,8 +139,8 @@ static void PictureResetYUVA(WebPPicture* const picture) {
|
||||
|
||||
// Grab the 'specs' (writer, *opaque, width, height...) from 'src' and copy them
|
||||
// into 'dst'. Mark 'dst' as not owning any memory.
|
||||
static void WebPPictureGrabSpecs(const WebPPicture* const src,
|
||||
WebPPicture* const dst) {
|
||||
void WebPPictureGrabSpecs(const WebPPicture* const src,
|
||||
WebPPicture* const dst) {
|
||||
assert(src != NULL && dst != NULL);
|
||||
*dst = *src;
|
||||
PictureResetYUVA(dst);
|
||||
@ -176,260 +174,6 @@ void WebPPictureFree(WebPPicture* picture) {
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Picture copying
|
||||
|
||||
// Not worth moving to dsp/enc.c (only used here).
|
||||
static void CopyPlane(const uint8_t* src, int src_stride,
|
||||
uint8_t* dst, int dst_stride, int width, int height) {
|
||||
while (height-- > 0) {
|
||||
memcpy(dst, src, width);
|
||||
src += src_stride;
|
||||
dst += dst_stride;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust top-left corner to chroma sample position.
|
||||
static void SnapTopLeftPosition(const WebPPicture* const pic,
|
||||
int* const left, int* const top) {
|
||||
if (!pic->use_argb) {
|
||||
*left &= ~1;
|
||||
*top &= ~1;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust top-left corner and verify that the sub-rectangle is valid.
|
||||
static int AdjustAndCheckRectangle(const WebPPicture* const pic,
|
||||
int* const left, int* const top,
|
||||
int width, int height) {
|
||||
SnapTopLeftPosition(pic, left, top);
|
||||
if ((*left) < 0 || (*top) < 0) return 0;
|
||||
if (width <= 0 || height <= 0) return 0;
|
||||
if ((*left) + width > pic->width) return 0;
|
||||
if ((*top) + height > pic->height) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebPPictureCopy(const WebPPicture* src, WebPPicture* dst) {
|
||||
if (src == NULL || dst == NULL) return 0;
|
||||
if (src == dst) return 1;
|
||||
|
||||
WebPPictureGrabSpecs(src, dst);
|
||||
if (!WebPPictureAlloc(dst)) return 0;
|
||||
|
||||
if (!src->use_argb) {
|
||||
CopyPlane(src->y, src->y_stride,
|
||||
dst->y, dst->y_stride, dst->width, dst->height);
|
||||
CopyPlane(src->u, src->uv_stride,
|
||||
dst->u, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
|
||||
CopyPlane(src->v, src->uv_stride,
|
||||
dst->v, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
|
||||
if (dst->a != NULL) {
|
||||
CopyPlane(src->a, src->a_stride,
|
||||
dst->a, dst->a_stride, dst->width, dst->height);
|
||||
}
|
||||
} else {
|
||||
CopyPlane((const uint8_t*)src->argb, 4 * src->argb_stride,
|
||||
(uint8_t*)dst->argb, 4 * dst->argb_stride,
|
||||
4 * dst->width, dst->height);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebPPictureIsView(const WebPPicture* picture) {
|
||||
if (picture == NULL) return 0;
|
||||
if (picture->use_argb) {
|
||||
return (picture->memory_argb_ == NULL);
|
||||
}
|
||||
return (picture->memory_ == NULL);
|
||||
}
|
||||
|
||||
int WebPPictureView(const WebPPicture* src,
|
||||
int left, int top, int width, int height,
|
||||
WebPPicture* dst) {
|
||||
if (src == NULL || dst == NULL) return 0;
|
||||
|
||||
// verify rectangle position.
|
||||
if (!AdjustAndCheckRectangle(src, &left, &top, width, height)) return 0;
|
||||
|
||||
if (src != dst) { // beware of aliasing! We don't want to leak 'memory_'.
|
||||
WebPPictureGrabSpecs(src, dst);
|
||||
}
|
||||
dst->width = width;
|
||||
dst->height = height;
|
||||
if (!src->use_argb) {
|
||||
dst->y = src->y + top * src->y_stride + left;
|
||||
dst->u = src->u + (top >> 1) * src->uv_stride + (left >> 1);
|
||||
dst->v = src->v + (top >> 1) * src->uv_stride + (left >> 1);
|
||||
dst->y_stride = src->y_stride;
|
||||
dst->uv_stride = src->uv_stride;
|
||||
if (src->a != NULL) {
|
||||
dst->a = src->a + top * src->a_stride + left;
|
||||
dst->a_stride = src->a_stride;
|
||||
}
|
||||
} else {
|
||||
dst->argb = src->argb + top * src->argb_stride + left;
|
||||
dst->argb_stride = src->argb_stride;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Picture cropping
|
||||
|
||||
int WebPPictureCrop(WebPPicture* pic,
|
||||
int left, int top, int width, int height) {
|
||||
WebPPicture tmp;
|
||||
|
||||
if (pic == NULL) return 0;
|
||||
if (!AdjustAndCheckRectangle(pic, &left, &top, width, height)) return 0;
|
||||
|
||||
WebPPictureGrabSpecs(pic, &tmp);
|
||||
tmp.width = width;
|
||||
tmp.height = height;
|
||||
if (!WebPPictureAlloc(&tmp)) return 0;
|
||||
|
||||
if (!pic->use_argb) {
|
||||
const int y_offset = top * pic->y_stride + left;
|
||||
const int uv_offset = (top / 2) * pic->uv_stride + left / 2;
|
||||
CopyPlane(pic->y + y_offset, pic->y_stride,
|
||||
tmp.y, tmp.y_stride, width, height);
|
||||
CopyPlane(pic->u + uv_offset, pic->uv_stride,
|
||||
tmp.u, tmp.uv_stride, HALVE(width), HALVE(height));
|
||||
CopyPlane(pic->v + uv_offset, pic->uv_stride,
|
||||
tmp.v, tmp.uv_stride, HALVE(width), HALVE(height));
|
||||
|
||||
if (tmp.a != NULL) {
|
||||
const int a_offset = top * pic->a_stride + left;
|
||||
CopyPlane(pic->a + a_offset, pic->a_stride,
|
||||
tmp.a, tmp.a_stride, width, height);
|
||||
}
|
||||
} else {
|
||||
const uint8_t* const src =
|
||||
(const uint8_t*)(pic->argb + top * pic->argb_stride + left);
|
||||
CopyPlane(src, pic->argb_stride * 4,
|
||||
(uint8_t*)tmp.argb, tmp.argb_stride * 4,
|
||||
width * 4, height);
|
||||
}
|
||||
WebPPictureFree(pic);
|
||||
*pic = tmp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Simple picture rescaler
|
||||
|
||||
static void RescalePlane(const uint8_t* src,
|
||||
int src_width, int src_height, int src_stride,
|
||||
uint8_t* dst,
|
||||
int dst_width, int dst_height, int dst_stride,
|
||||
int32_t* const work,
|
||||
int num_channels) {
|
||||
WebPRescaler rescaler;
|
||||
int y = 0;
|
||||
WebPRescalerInit(&rescaler, src_width, src_height,
|
||||
dst, dst_width, dst_height, dst_stride,
|
||||
num_channels,
|
||||
src_width, dst_width,
|
||||
src_height, dst_height,
|
||||
work);
|
||||
memset(work, 0, 2 * dst_width * num_channels * sizeof(*work));
|
||||
while (y < src_height) {
|
||||
y += WebPRescalerImport(&rescaler, src_height - y,
|
||||
src + y * src_stride, src_stride);
|
||||
WebPRescalerExport(&rescaler);
|
||||
}
|
||||
}
|
||||
|
||||
static void AlphaMultiplyARGB(WebPPicture* const pic, int inverse) {
|
||||
assert(pic->argb != NULL);
|
||||
WebPMultARGBRows((uint8_t*)pic->argb, pic->argb_stride * sizeof(*pic->argb),
|
||||
pic->width, pic->height, inverse);
|
||||
}
|
||||
|
||||
static void AlphaMultiplyY(WebPPicture* const pic, int inverse) {
|
||||
if (pic->a != NULL) {
|
||||
WebPMultRows(pic->y, pic->y_stride, pic->a, pic->a_stride,
|
||||
pic->width, pic->height, inverse);
|
||||
}
|
||||
}
|
||||
|
||||
int WebPPictureRescale(WebPPicture* pic, int width, int height) {
|
||||
WebPPicture tmp;
|
||||
int prev_width, prev_height;
|
||||
int32_t* work;
|
||||
|
||||
if (pic == NULL) return 0;
|
||||
prev_width = pic->width;
|
||||
prev_height = pic->height;
|
||||
// if width is unspecified, scale original proportionally to height ratio.
|
||||
if (width == 0) {
|
||||
width = (prev_width * height + prev_height / 2) / prev_height;
|
||||
}
|
||||
// if height is unspecified, scale original proportionally to width ratio.
|
||||
if (height == 0) {
|
||||
height = (prev_height * width + prev_width / 2) / prev_width;
|
||||
}
|
||||
// Check if the overall dimensions still make sense.
|
||||
if (width <= 0 || height <= 0) return 0;
|
||||
|
||||
WebPPictureGrabSpecs(pic, &tmp);
|
||||
tmp.width = width;
|
||||
tmp.height = height;
|
||||
if (!WebPPictureAlloc(&tmp)) return 0;
|
||||
|
||||
if (!pic->use_argb) {
|
||||
work = (int32_t*)WebPSafeMalloc(2ULL * width, sizeof(*work));
|
||||
if (work == NULL) {
|
||||
WebPPictureFree(&tmp);
|
||||
return 0;
|
||||
}
|
||||
// If present, we need to rescale alpha first (for AlphaMultiplyY).
|
||||
if (pic->a != NULL) {
|
||||
WebPInitAlphaProcessing();
|
||||
RescalePlane(pic->a, prev_width, prev_height, pic->a_stride,
|
||||
tmp.a, width, height, tmp.a_stride, work, 1);
|
||||
}
|
||||
|
||||
// We take transparency into account on the luma plane only. That's not
|
||||
// totally exact blending, but still is a good approximation.
|
||||
AlphaMultiplyY(pic, 0);
|
||||
RescalePlane(pic->y, prev_width, prev_height, pic->y_stride,
|
||||
tmp.y, width, height, tmp.y_stride, work, 1);
|
||||
AlphaMultiplyY(&tmp, 1);
|
||||
|
||||
RescalePlane(pic->u,
|
||||
HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
|
||||
tmp.u,
|
||||
HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
|
||||
RescalePlane(pic->v,
|
||||
HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
|
||||
tmp.v,
|
||||
HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
|
||||
} else {
|
||||
work = (int32_t*)WebPSafeMalloc(2ULL * width * 4, sizeof(*work));
|
||||
if (work == NULL) {
|
||||
WebPPictureFree(&tmp);
|
||||
return 0;
|
||||
}
|
||||
// In order to correctly interpolate colors, we need to apply the alpha
|
||||
// weighting first (black-matting), scale the RGB values, and remove
|
||||
// the premultiplication afterward (while preserving the alpha channel).
|
||||
WebPInitAlphaProcessing();
|
||||
AlphaMultiplyARGB(pic, 0);
|
||||
RescalePlane((const uint8_t*)pic->argb, prev_width, prev_height,
|
||||
pic->argb_stride * 4,
|
||||
(uint8_t*)tmp.argb, width, height,
|
||||
tmp.argb_stride * 4,
|
||||
work, 4);
|
||||
AlphaMultiplyARGB(&tmp, 1);
|
||||
}
|
||||
WebPPictureFree(pic);
|
||||
WebPSafeFree(work);
|
||||
*pic = tmp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// WebPMemoryWriter: Write-to-memory
|
||||
|
||||
@ -734,6 +478,8 @@ static int Import(WebPPicture* const picture,
|
||||
#undef SUM1
|
||||
#undef RGB_TO_UV
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int WebPPictureImportRGB(WebPPicture* picture,
|
||||
const uint8_t* rgb, int rgb_stride) {
|
||||
return Import(picture, rgb, rgb_stride, 3, 0, 0);
|
||||
@ -859,326 +605,6 @@ int WebPPictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace) {
|
||||
return WebPPictureARGBToYUVADithered(picture, colorspace, 0.f);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helper: clean up fully transparent area to help compressibility.
|
||||
|
||||
#define SIZE 8
|
||||
#define SIZE2 (SIZE / 2)
|
||||
static int is_transparent_area(const uint8_t* ptr, int stride, int size) {
|
||||
int y, x;
|
||||
for (y = 0; y < size; ++y) {
|
||||
for (x = 0; x < size; ++x) {
|
||||
if (ptr[x]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ptr += stride;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_transparent_argb_area(const uint32_t* ptr, int stride, int size) {
|
||||
int y, x;
|
||||
for (y = 0; y < size; ++y) {
|
||||
for (x = 0; x < size; ++x) {
|
||||
if (ptr[x] & 0xff000000u) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ptr += stride;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void flatten(uint8_t* ptr, int v, int stride, int size) {
|
||||
int y;
|
||||
for (y = 0; y < size; ++y) {
|
||||
memset(ptr, v, size);
|
||||
ptr += stride;
|
||||
}
|
||||
}
|
||||
|
||||
static void flatten_argb(uint32_t* ptr, uint32_t v, int stride, int size) {
|
||||
int x, y;
|
||||
for (y = 0; y < size; ++y) {
|
||||
for (x = 0; x < size; ++x) ptr[x] = v;
|
||||
ptr += stride;
|
||||
}
|
||||
}
|
||||
|
||||
void WebPCleanupTransparentArea(WebPPicture* pic) {
|
||||
int x, y, w, h;
|
||||
if (pic == NULL) return;
|
||||
w = pic->width / SIZE;
|
||||
h = pic->height / SIZE;
|
||||
|
||||
// note: we ignore the left-overs on right/bottom
|
||||
if (pic->use_argb) {
|
||||
uint32_t argb_value = 0;
|
||||
for (y = 0; y < h; ++y) {
|
||||
int need_reset = 1;
|
||||
for (x = 0; x < w; ++x) {
|
||||
const int off = (y * pic->argb_stride + x) * SIZE;
|
||||
if (is_transparent_argb_area(pic->argb + off, pic->argb_stride, SIZE)) {
|
||||
if (need_reset) {
|
||||
argb_value = pic->argb[off];
|
||||
need_reset = 0;
|
||||
}
|
||||
flatten_argb(pic->argb + off, argb_value, pic->argb_stride, SIZE);
|
||||
} else {
|
||||
need_reset = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const uint8_t* const a_ptr = pic->a;
|
||||
int values[3] = { 0 };
|
||||
if (a_ptr == NULL) return; // nothing to do
|
||||
for (y = 0; y < h; ++y) {
|
||||
int need_reset = 1;
|
||||
for (x = 0; x < w; ++x) {
|
||||
const int off_a = (y * pic->a_stride + x) * SIZE;
|
||||
const int off_y = (y * pic->y_stride + x) * SIZE;
|
||||
const int off_uv = (y * pic->uv_stride + x) * SIZE2;
|
||||
if (is_transparent_area(a_ptr + off_a, pic->a_stride, SIZE)) {
|
||||
if (need_reset) {
|
||||
values[0] = pic->y[off_y];
|
||||
values[1] = pic->u[off_uv];
|
||||
values[2] = pic->v[off_uv];
|
||||
need_reset = 0;
|
||||
}
|
||||
flatten(pic->y + off_y, values[0], pic->y_stride, SIZE);
|
||||
flatten(pic->u + off_uv, values[1], pic->uv_stride, SIZE2);
|
||||
flatten(pic->v + off_uv, values[2], pic->uv_stride, SIZE2);
|
||||
} else {
|
||||
need_reset = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef SIZE
|
||||
#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;
|
||||
VP8Random rg;
|
||||
int x, y;
|
||||
if (pic == NULL) return;
|
||||
VP8InitRandom(&rg, 0.f);
|
||||
if (!pic->use_argb) {
|
||||
const int uv_width = (pic->width >> 1); // omit last pixel during u/v loop
|
||||
const int Y0 = RGBToY(red, green, blue, &rg);
|
||||
// VP8RGBToU/V expects the u/v values summed over four pixels
|
||||
const int U0 = RGBToU(4 * red, 4 * green, 4 * blue, &rg);
|
||||
const int V0 = RGBToV(4 * red, 4 * green, 4 * blue, &rg);
|
||||
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
|
||||
//
|
||||
// For every pixel in the *reference* picture, we search for the local best
|
||||
// match in the compressed image. This is not a symmetrical measure.
|
||||
|
||||
// search radius. Shouldn't be too large.
|
||||
#define RADIUS 2
|
||||
|
||||
static float AccumulateLSIM(const uint8_t* src, int src_stride,
|
||||
const uint8_t* ref, int ref_stride,
|
||||
int w, int h) {
|
||||
int x, y;
|
||||
double total_sse = 0.;
|
||||
for (y = 0; y < h; ++y) {
|
||||
const int y_0 = (y - RADIUS < 0) ? 0 : y - RADIUS;
|
||||
const int y_1 = (y + RADIUS + 1 >= h) ? h : y + RADIUS + 1;
|
||||
for (x = 0; x < w; ++x) {
|
||||
const int x_0 = (x - RADIUS < 0) ? 0 : x - RADIUS;
|
||||
const int x_1 = (x + RADIUS + 1 >= w) ? w : x + RADIUS + 1;
|
||||
double best_sse = 255. * 255.;
|
||||
const double value = (double)ref[y * ref_stride + x];
|
||||
int i, j;
|
||||
for (j = y_0; j < y_1; ++j) {
|
||||
const uint8_t* s = src + j * src_stride;
|
||||
for (i = x_0; i < x_1; ++i) {
|
||||
const double sse = (double)(s[i] - value) * (s[i] - value);
|
||||
if (sse < best_sse) best_sse = sse;
|
||||
}
|
||||
}
|
||||
total_sse += best_sse;
|
||||
}
|
||||
}
|
||||
return (float)total_sse;
|
||||
}
|
||||
#undef RADIUS
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Distortion
|
||||
|
||||
// Max value returned in case of exact similarity.
|
||||
static const double kMinDistortion_dB = 99.;
|
||||
static float GetPSNR(const double v) {
|
||||
return (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.))
|
||||
: kMinDistortion_dB);
|
||||
}
|
||||
|
||||
int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref,
|
||||
int type, float result[5]) {
|
||||
DistoStats stats[5];
|
||||
int has_alpha;
|
||||
int uv_w, uv_h;
|
||||
|
||||
if (src == NULL || ref == NULL ||
|
||||
src->width != ref->width || src->height != ref->height ||
|
||||
src->y == NULL || ref->y == NULL ||
|
||||
src->u == NULL || ref->u == NULL ||
|
||||
src->v == NULL || ref->v == NULL ||
|
||||
result == NULL) {
|
||||
return 0;
|
||||
}
|
||||
// TODO(skal): provide distortion for ARGB too.
|
||||
if (src->use_argb == 1 || src->use_argb != ref->use_argb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
has_alpha = !!(src->colorspace & WEBP_CSP_ALPHA_BIT);
|
||||
if (has_alpha != !!(ref->colorspace & WEBP_CSP_ALPHA_BIT) ||
|
||||
(has_alpha && (src->a == NULL || ref->a == NULL))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(stats, 0, sizeof(stats));
|
||||
|
||||
uv_w = HALVE(src->width);
|
||||
uv_h = HALVE(src->height);
|
||||
if (type >= 2) {
|
||||
float sse[4];
|
||||
sse[0] = AccumulateLSIM(src->y, src->y_stride,
|
||||
ref->y, ref->y_stride, src->width, src->height);
|
||||
sse[1] = AccumulateLSIM(src->u, src->uv_stride,
|
||||
ref->u, ref->uv_stride, uv_w, uv_h);
|
||||
sse[2] = AccumulateLSIM(src->v, src->uv_stride,
|
||||
ref->v, ref->uv_stride, uv_w, uv_h);
|
||||
sse[3] = has_alpha ? AccumulateLSIM(src->a, src->a_stride,
|
||||
ref->a, ref->a_stride,
|
||||
src->width, src->height)
|
||||
: 0.f;
|
||||
result[0] = GetPSNR(sse[0] / (src->width * src->height));
|
||||
result[1] = GetPSNR(sse[1] / (uv_w * uv_h));
|
||||
result[2] = GetPSNR(sse[2] / (uv_w * uv_h));
|
||||
result[3] = GetPSNR(sse[3] / (src->width * src->height));
|
||||
{
|
||||
double total_sse = sse[0] + sse[1] + sse[2];
|
||||
int total_pixels = src->width * src->height + 2 * uv_w * uv_h;
|
||||
if (has_alpha) {
|
||||
total_pixels += src->width * src->height;
|
||||
total_sse += sse[3];
|
||||
}
|
||||
result[4] = GetPSNR(total_sse / total_pixels);
|
||||
}
|
||||
} else {
|
||||
int c;
|
||||
VP8SSIMAccumulatePlane(src->y, src->y_stride,
|
||||
ref->y, ref->y_stride,
|
||||
src->width, src->height, &stats[0]);
|
||||
VP8SSIMAccumulatePlane(src->u, src->uv_stride,
|
||||
ref->u, ref->uv_stride,
|
||||
uv_w, uv_h, &stats[1]);
|
||||
VP8SSIMAccumulatePlane(src->v, src->uv_stride,
|
||||
ref->v, ref->uv_stride,
|
||||
uv_w, uv_h, &stats[2]);
|
||||
if (has_alpha) {
|
||||
VP8SSIMAccumulatePlane(src->a, src->a_stride,
|
||||
ref->a, ref->a_stride,
|
||||
src->width, src->height, &stats[3]);
|
||||
}
|
||||
for (c = 0; c <= 4; ++c) {
|
||||
if (type == 1) {
|
||||
const double v = VP8SSIMGet(&stats[c]);
|
||||
result[c] = (float)((v < 1.) ? -10.0 * log10(1. - v)
|
||||
: kMinDistortion_dB);
|
||||
} else {
|
||||
const double v = VP8SSIMGetSquaredError(&stats[c]);
|
||||
result[c] = GetPSNR(v);
|
||||
}
|
||||
// Accumulate forward
|
||||
if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Simplest high-level calls:
|
||||
|
||||
|
150
src/enc/picture_psnr.c
Normal file
150
src/enc/picture_psnr.c
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the COPYING file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// WebPPicture tools for measuring distortion
|
||||
//
|
||||
// Author: Skal (pascal.massimino@gmail.com)
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "./vp8enci.h"
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// local-min distortion
|
||||
//
|
||||
// For every pixel in the *reference* picture, we search for the local best
|
||||
// match in the compressed image. This is not a symmetrical measure.
|
||||
|
||||
#define RADIUS 2 // search radius. Shouldn't be too large.
|
||||
|
||||
static float AccumulateLSIM(const uint8_t* src, int src_stride,
|
||||
const uint8_t* ref, int ref_stride,
|
||||
int w, int h) {
|
||||
int x, y;
|
||||
double total_sse = 0.;
|
||||
for (y = 0; y < h; ++y) {
|
||||
const int y_0 = (y - RADIUS < 0) ? 0 : y - RADIUS;
|
||||
const int y_1 = (y + RADIUS + 1 >= h) ? h : y + RADIUS + 1;
|
||||
for (x = 0; x < w; ++x) {
|
||||
const int x_0 = (x - RADIUS < 0) ? 0 : x - RADIUS;
|
||||
const int x_1 = (x + RADIUS + 1 >= w) ? w : x + RADIUS + 1;
|
||||
double best_sse = 255. * 255.;
|
||||
const double value = (double)ref[y * ref_stride + x];
|
||||
int i, j;
|
||||
for (j = y_0; j < y_1; ++j) {
|
||||
const uint8_t* s = src + j * src_stride;
|
||||
for (i = x_0; i < x_1; ++i) {
|
||||
const double sse = (double)(s[i] - value) * (s[i] - value);
|
||||
if (sse < best_sse) best_sse = sse;
|
||||
}
|
||||
}
|
||||
total_sse += best_sse;
|
||||
}
|
||||
}
|
||||
return (float)total_sse;
|
||||
}
|
||||
#undef RADIUS
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Distortion
|
||||
|
||||
// Max value returned in case of exact similarity.
|
||||
static const double kMinDistortion_dB = 99.;
|
||||
static float GetPSNR(const double v) {
|
||||
return (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.))
|
||||
: kMinDistortion_dB);
|
||||
}
|
||||
|
||||
int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref,
|
||||
int type, float result[5]) {
|
||||
DistoStats stats[5];
|
||||
int has_alpha;
|
||||
int uv_w, uv_h;
|
||||
|
||||
if (src == NULL || ref == NULL ||
|
||||
src->width != ref->width || src->height != ref->height ||
|
||||
src->y == NULL || ref->y == NULL ||
|
||||
src->u == NULL || ref->u == NULL ||
|
||||
src->v == NULL || ref->v == NULL ||
|
||||
result == NULL) {
|
||||
return 0;
|
||||
}
|
||||
// TODO(skal): provide distortion for ARGB too.
|
||||
if (src->use_argb == 1 || src->use_argb != ref->use_argb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
has_alpha = !!(src->colorspace & WEBP_CSP_ALPHA_BIT);
|
||||
if (has_alpha != !!(ref->colorspace & WEBP_CSP_ALPHA_BIT) ||
|
||||
(has_alpha && (src->a == NULL || ref->a == NULL))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(stats, 0, sizeof(stats));
|
||||
|
||||
uv_w = (src->width + 1) >> 1;
|
||||
uv_h = (src->height + 1) >> 1;
|
||||
if (type >= 2) {
|
||||
float sse[4];
|
||||
sse[0] = AccumulateLSIM(src->y, src->y_stride,
|
||||
ref->y, ref->y_stride, src->width, src->height);
|
||||
sse[1] = AccumulateLSIM(src->u, src->uv_stride,
|
||||
ref->u, ref->uv_stride, uv_w, uv_h);
|
||||
sse[2] = AccumulateLSIM(src->v, src->uv_stride,
|
||||
ref->v, ref->uv_stride, uv_w, uv_h);
|
||||
sse[3] = has_alpha ? AccumulateLSIM(src->a, src->a_stride,
|
||||
ref->a, ref->a_stride,
|
||||
src->width, src->height)
|
||||
: 0.f;
|
||||
result[0] = GetPSNR(sse[0] / (src->width * src->height));
|
||||
result[1] = GetPSNR(sse[1] / (uv_w * uv_h));
|
||||
result[2] = GetPSNR(sse[2] / (uv_w * uv_h));
|
||||
result[3] = GetPSNR(sse[3] / (src->width * src->height));
|
||||
{
|
||||
double total_sse = sse[0] + sse[1] + sse[2];
|
||||
int total_pixels = src->width * src->height + 2 * uv_w * uv_h;
|
||||
if (has_alpha) {
|
||||
total_pixels += src->width * src->height;
|
||||
total_sse += sse[3];
|
||||
}
|
||||
result[4] = GetPSNR(total_sse / total_pixels);
|
||||
}
|
||||
} else {
|
||||
int c;
|
||||
VP8SSIMAccumulatePlane(src->y, src->y_stride,
|
||||
ref->y, ref->y_stride,
|
||||
src->width, src->height, &stats[0]);
|
||||
VP8SSIMAccumulatePlane(src->u, src->uv_stride,
|
||||
ref->u, ref->uv_stride,
|
||||
uv_w, uv_h, &stats[1]);
|
||||
VP8SSIMAccumulatePlane(src->v, src->uv_stride,
|
||||
ref->v, ref->uv_stride,
|
||||
uv_w, uv_h, &stats[2]);
|
||||
if (has_alpha) {
|
||||
VP8SSIMAccumulatePlane(src->a, src->a_stride,
|
||||
ref->a, ref->a_stride,
|
||||
src->width, src->height, &stats[3]);
|
||||
}
|
||||
for (c = 0; c <= 4; ++c) {
|
||||
if (type == 1) {
|
||||
const double v = VP8SSIMGet(&stats[c]);
|
||||
result[c] = (float)((v < 1.) ? -10.0 * log10(1. - v)
|
||||
: kMinDistortion_dB);
|
||||
} else {
|
||||
const double v = VP8SSIMGetSquaredError(&stats[c]);
|
||||
result[c] = GetPSNR(v);
|
||||
}
|
||||
// Accumulate forward
|
||||
if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
276
src/enc/picture_rescale.c
Normal file
276
src/enc/picture_rescale.c
Normal file
@ -0,0 +1,276 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the COPYING file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// WebPPicture tools: copy, crop, rescaling and view.
|
||||
//
|
||||
// Author: Skal (pascal.massimino@gmail.com)
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "./vp8enci.h"
|
||||
#include "../utils/rescaler.h"
|
||||
#include "../utils/utils.h"
|
||||
|
||||
#define HALVE(x) (((x) + 1) >> 1)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Picture copying
|
||||
|
||||
static void CopyPlane(const uint8_t* src, int src_stride,
|
||||
uint8_t* dst, int dst_stride, int width, int height) {
|
||||
while (height-- > 0) {
|
||||
memcpy(dst, src, width);
|
||||
src += src_stride;
|
||||
dst += dst_stride;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust top-left corner to chroma sample position.
|
||||
static void SnapTopLeftPosition(const WebPPicture* const pic,
|
||||
int* const left, int* const top) {
|
||||
if (!pic->use_argb) {
|
||||
*left &= ~1;
|
||||
*top &= ~1;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust top-left corner and verify that the sub-rectangle is valid.
|
||||
static int AdjustAndCheckRectangle(const WebPPicture* const pic,
|
||||
int* const left, int* const top,
|
||||
int width, int height) {
|
||||
SnapTopLeftPosition(pic, left, top);
|
||||
if ((*left) < 0 || (*top) < 0) return 0;
|
||||
if (width <= 0 || height <= 0) return 0;
|
||||
if ((*left) + width > pic->width) return 0;
|
||||
if ((*top) + height > pic->height) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebPPictureCopy(const WebPPicture* src, WebPPicture* dst) {
|
||||
if (src == NULL || dst == NULL) return 0;
|
||||
if (src == dst) return 1;
|
||||
|
||||
WebPPictureGrabSpecs(src, dst);
|
||||
if (!WebPPictureAlloc(dst)) return 0;
|
||||
|
||||
if (!src->use_argb) {
|
||||
CopyPlane(src->y, src->y_stride,
|
||||
dst->y, dst->y_stride, dst->width, dst->height);
|
||||
CopyPlane(src->u, src->uv_stride,
|
||||
dst->u, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
|
||||
CopyPlane(src->v, src->uv_stride,
|
||||
dst->v, dst->uv_stride, HALVE(dst->width), HALVE(dst->height));
|
||||
if (dst->a != NULL) {
|
||||
CopyPlane(src->a, src->a_stride,
|
||||
dst->a, dst->a_stride, dst->width, dst->height);
|
||||
}
|
||||
} else {
|
||||
CopyPlane((const uint8_t*)src->argb, 4 * src->argb_stride,
|
||||
(uint8_t*)dst->argb, 4 * dst->argb_stride,
|
||||
4 * dst->width, dst->height);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int WebPPictureIsView(const WebPPicture* picture) {
|
||||
if (picture == NULL) return 0;
|
||||
if (picture->use_argb) {
|
||||
return (picture->memory_argb_ == NULL);
|
||||
}
|
||||
return (picture->memory_ == NULL);
|
||||
}
|
||||
|
||||
int WebPPictureView(const WebPPicture* src,
|
||||
int left, int top, int width, int height,
|
||||
WebPPicture* dst) {
|
||||
if (src == NULL || dst == NULL) return 0;
|
||||
|
||||
// verify rectangle position.
|
||||
if (!AdjustAndCheckRectangle(src, &left, &top, width, height)) return 0;
|
||||
|
||||
if (src != dst) { // beware of aliasing! We don't want to leak 'memory_'.
|
||||
WebPPictureGrabSpecs(src, dst);
|
||||
}
|
||||
dst->width = width;
|
||||
dst->height = height;
|
||||
if (!src->use_argb) {
|
||||
dst->y = src->y + top * src->y_stride + left;
|
||||
dst->u = src->u + (top >> 1) * src->uv_stride + (left >> 1);
|
||||
dst->v = src->v + (top >> 1) * src->uv_stride + (left >> 1);
|
||||
dst->y_stride = src->y_stride;
|
||||
dst->uv_stride = src->uv_stride;
|
||||
if (src->a != NULL) {
|
||||
dst->a = src->a + top * src->a_stride + left;
|
||||
dst->a_stride = src->a_stride;
|
||||
}
|
||||
} else {
|
||||
dst->argb = src->argb + top * src->argb_stride + left;
|
||||
dst->argb_stride = src->argb_stride;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Picture cropping
|
||||
|
||||
int WebPPictureCrop(WebPPicture* pic,
|
||||
int left, int top, int width, int height) {
|
||||
WebPPicture tmp;
|
||||
|
||||
if (pic == NULL) return 0;
|
||||
if (!AdjustAndCheckRectangle(pic, &left, &top, width, height)) return 0;
|
||||
|
||||
WebPPictureGrabSpecs(pic, &tmp);
|
||||
tmp.width = width;
|
||||
tmp.height = height;
|
||||
if (!WebPPictureAlloc(&tmp)) return 0;
|
||||
|
||||
if (!pic->use_argb) {
|
||||
const int y_offset = top * pic->y_stride + left;
|
||||
const int uv_offset = (top / 2) * pic->uv_stride + left / 2;
|
||||
CopyPlane(pic->y + y_offset, pic->y_stride,
|
||||
tmp.y, tmp.y_stride, width, height);
|
||||
CopyPlane(pic->u + uv_offset, pic->uv_stride,
|
||||
tmp.u, tmp.uv_stride, HALVE(width), HALVE(height));
|
||||
CopyPlane(pic->v + uv_offset, pic->uv_stride,
|
||||
tmp.v, tmp.uv_stride, HALVE(width), HALVE(height));
|
||||
|
||||
if (tmp.a != NULL) {
|
||||
const int a_offset = top * pic->a_stride + left;
|
||||
CopyPlane(pic->a + a_offset, pic->a_stride,
|
||||
tmp.a, tmp.a_stride, width, height);
|
||||
}
|
||||
} else {
|
||||
const uint8_t* const src =
|
||||
(const uint8_t*)(pic->argb + top * pic->argb_stride + left);
|
||||
CopyPlane(src, pic->argb_stride * 4,
|
||||
(uint8_t*)tmp.argb, tmp.argb_stride * 4,
|
||||
width * 4, height);
|
||||
}
|
||||
WebPPictureFree(pic);
|
||||
*pic = tmp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Simple picture rescaler
|
||||
|
||||
static void RescalePlane(const uint8_t* src,
|
||||
int src_width, int src_height, int src_stride,
|
||||
uint8_t* dst,
|
||||
int dst_width, int dst_height, int dst_stride,
|
||||
int32_t* const work,
|
||||
int num_channels) {
|
||||
WebPRescaler rescaler;
|
||||
int y = 0;
|
||||
WebPRescalerInit(&rescaler, src_width, src_height,
|
||||
dst, dst_width, dst_height, dst_stride,
|
||||
num_channels,
|
||||
src_width, dst_width,
|
||||
src_height, dst_height,
|
||||
work);
|
||||
memset(work, 0, 2 * dst_width * num_channels * sizeof(*work));
|
||||
while (y < src_height) {
|
||||
y += WebPRescalerImport(&rescaler, src_height - y,
|
||||
src + y * src_stride, src_stride);
|
||||
WebPRescalerExport(&rescaler);
|
||||
}
|
||||
}
|
||||
|
||||
static void AlphaMultiplyARGB(WebPPicture* const pic, int inverse) {
|
||||
assert(pic->argb != NULL);
|
||||
WebPMultARGBRows((uint8_t*)pic->argb, pic->argb_stride * sizeof(*pic->argb),
|
||||
pic->width, pic->height, inverse);
|
||||
}
|
||||
|
||||
static void AlphaMultiplyY(WebPPicture* const pic, int inverse) {
|
||||
if (pic->a != NULL) {
|
||||
WebPMultRows(pic->y, pic->y_stride, pic->a, pic->a_stride,
|
||||
pic->width, pic->height, inverse);
|
||||
}
|
||||
}
|
||||
|
||||
int WebPPictureRescale(WebPPicture* pic, int width, int height) {
|
||||
WebPPicture tmp;
|
||||
int prev_width, prev_height;
|
||||
int32_t* work;
|
||||
|
||||
if (pic == NULL) return 0;
|
||||
prev_width = pic->width;
|
||||
prev_height = pic->height;
|
||||
// if width is unspecified, scale original proportionally to height ratio.
|
||||
if (width == 0) {
|
||||
width = (prev_width * height + prev_height / 2) / prev_height;
|
||||
}
|
||||
// if height is unspecified, scale original proportionally to width ratio.
|
||||
if (height == 0) {
|
||||
height = (prev_height * width + prev_width / 2) / prev_width;
|
||||
}
|
||||
// Check if the overall dimensions still make sense.
|
||||
if (width <= 0 || height <= 0) return 0;
|
||||
|
||||
WebPPictureGrabSpecs(pic, &tmp);
|
||||
tmp.width = width;
|
||||
tmp.height = height;
|
||||
if (!WebPPictureAlloc(&tmp)) return 0;
|
||||
|
||||
if (!pic->use_argb) {
|
||||
work = (int32_t*)WebPSafeMalloc(2ULL * width, sizeof(*work));
|
||||
if (work == NULL) {
|
||||
WebPPictureFree(&tmp);
|
||||
return 0;
|
||||
}
|
||||
// If present, we need to rescale alpha first (for AlphaMultiplyY).
|
||||
if (pic->a != NULL) {
|
||||
WebPInitAlphaProcessing();
|
||||
RescalePlane(pic->a, prev_width, prev_height, pic->a_stride,
|
||||
tmp.a, width, height, tmp.a_stride, work, 1);
|
||||
}
|
||||
|
||||
// We take transparency into account on the luma plane only. That's not
|
||||
// totally exact blending, but still is a good approximation.
|
||||
AlphaMultiplyY(pic, 0);
|
||||
RescalePlane(pic->y, prev_width, prev_height, pic->y_stride,
|
||||
tmp.y, width, height, tmp.y_stride, work, 1);
|
||||
AlphaMultiplyY(&tmp, 1);
|
||||
|
||||
RescalePlane(pic->u,
|
||||
HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
|
||||
tmp.u,
|
||||
HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
|
||||
RescalePlane(pic->v,
|
||||
HALVE(prev_width), HALVE(prev_height), pic->uv_stride,
|
||||
tmp.v,
|
||||
HALVE(width), HALVE(height), tmp.uv_stride, work, 1);
|
||||
} else {
|
||||
work = (int32_t*)WebPSafeMalloc(2ULL * width * 4, sizeof(*work));
|
||||
if (work == NULL) {
|
||||
WebPPictureFree(&tmp);
|
||||
return 0;
|
||||
}
|
||||
// In order to correctly interpolate colors, we need to apply the alpha
|
||||
// weighting first (black-matting), scale the RGB values, and remove
|
||||
// the premultiplication afterward (while preserving the alpha channel).
|
||||
WebPInitAlphaProcessing();
|
||||
AlphaMultiplyARGB(pic, 0);
|
||||
RescalePlane((const uint8_t*)pic->argb, prev_width, prev_height,
|
||||
pic->argb_stride * 4,
|
||||
(uint8_t*)tmp.argb, width, height,
|
||||
tmp.argb_stride * 4,
|
||||
work, 4);
|
||||
AlphaMultiplyARGB(&tmp, 1);
|
||||
}
|
||||
WebPPictureFree(pic);
|
||||
WebPSafeFree(work);
|
||||
*pic = tmp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
206
src/enc/picture_tools.c
Normal file
206
src/enc/picture_tools.c
Normal file
@ -0,0 +1,206 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the COPYING file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
// -----------------------------------------------------------------------------
|
||||
//
|
||||
// WebPPicture tools: alpha handling, etc.
|
||||
//
|
||||
// Author: Skal (pascal.massimino@gmail.com)
|
||||
|
||||
#include "./vp8enci.h"
|
||||
#include "../dsp/yuv.h"
|
||||
|
||||
static WEBP_INLINE uint32_t MakeARGB32(int r, int g, int b) {
|
||||
return (0xff000000u | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helper: clean up fully transparent area to help compressibility.
|
||||
|
||||
#define SIZE 8
|
||||
#define SIZE2 (SIZE / 2)
|
||||
static int is_transparent_area(const uint8_t* ptr, int stride, int size) {
|
||||
int y, x;
|
||||
for (y = 0; y < size; ++y) {
|
||||
for (x = 0; x < size; ++x) {
|
||||
if (ptr[x]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ptr += stride;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_transparent_argb_area(const uint32_t* ptr, int stride, int size) {
|
||||
int y, x;
|
||||
for (y = 0; y < size; ++y) {
|
||||
for (x = 0; x < size; ++x) {
|
||||
if (ptr[x] & 0xff000000u) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ptr += stride;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void flatten(uint8_t* ptr, int v, int stride, int size) {
|
||||
int y;
|
||||
for (y = 0; y < size; ++y) {
|
||||
memset(ptr, v, size);
|
||||
ptr += stride;
|
||||
}
|
||||
}
|
||||
|
||||
static void flatten_argb(uint32_t* ptr, uint32_t v, int stride, int size) {
|
||||
int x, y;
|
||||
for (y = 0; y < size; ++y) {
|
||||
for (x = 0; x < size; ++x) ptr[x] = v;
|
||||
ptr += stride;
|
||||
}
|
||||
}
|
||||
|
||||
void WebPCleanupTransparentArea(WebPPicture* pic) {
|
||||
int x, y, w, h;
|
||||
if (pic == NULL) return;
|
||||
w = pic->width / SIZE;
|
||||
h = pic->height / SIZE;
|
||||
|
||||
// note: we ignore the left-overs on right/bottom
|
||||
if (pic->use_argb) {
|
||||
uint32_t argb_value = 0;
|
||||
for (y = 0; y < h; ++y) {
|
||||
int need_reset = 1;
|
||||
for (x = 0; x < w; ++x) {
|
||||
const int off = (y * pic->argb_stride + x) * SIZE;
|
||||
if (is_transparent_argb_area(pic->argb + off, pic->argb_stride, SIZE)) {
|
||||
if (need_reset) {
|
||||
argb_value = pic->argb[off];
|
||||
need_reset = 0;
|
||||
}
|
||||
flatten_argb(pic->argb + off, argb_value, pic->argb_stride, SIZE);
|
||||
} else {
|
||||
need_reset = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const uint8_t* const a_ptr = pic->a;
|
||||
int values[3] = { 0 };
|
||||
if (a_ptr == NULL) return; // nothing to do
|
||||
for (y = 0; y < h; ++y) {
|
||||
int need_reset = 1;
|
||||
for (x = 0; x < w; ++x) {
|
||||
const int off_a = (y * pic->a_stride + x) * SIZE;
|
||||
const int off_y = (y * pic->y_stride + x) * SIZE;
|
||||
const int off_uv = (y * pic->uv_stride + x) * SIZE2;
|
||||
if (is_transparent_area(a_ptr + off_a, pic->a_stride, SIZE)) {
|
||||
if (need_reset) {
|
||||
values[0] = pic->y[off_y];
|
||||
values[1] = pic->u[off_uv];
|
||||
values[2] = pic->v[off_uv];
|
||||
need_reset = 0;
|
||||
}
|
||||
flatten(pic->y + off_y, values[0], pic->y_stride, SIZE);
|
||||
flatten(pic->u + off_uv, values[1], pic->uv_stride, SIZE2);
|
||||
flatten(pic->v + off_uv, values[2], pic->uv_stride, SIZE2);
|
||||
} else {
|
||||
need_reset = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef SIZE
|
||||
#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, 1 << (YUV_FIX - 1));
|
||||
// VP8RGBToU/V expects the u/v values summed over four pixels
|
||||
const int U0 = VP8RGBToU(4 * red, 4 * green, 4 * blue, 1 << (YUV_FIX + 1));
|
||||
const int V0 = VP8RGBToV(4 * red, 4 * green, 4 * blue, 1 << (YUV_FIX + 1));
|
||||
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
|
||||
|
||||
//------------------------------------------------------------------------------
|
@ -553,6 +553,13 @@ void VP8AdjustFilterStrength(VP8EncIterator* const it);
|
||||
// step of 'delta', given a sharpness parameter 'sharpness'.
|
||||
int VP8FilterStrengthFromDelta(int sharpness, int delta);
|
||||
|
||||
// misc utils for picture_*.c:
|
||||
|
||||
// Grab the 'specs' (writer, *opaque, width, height...) from 'src' and copy them
|
||||
// into 'dst'. Mark 'dst' as not owning any memory.
|
||||
void WebPPictureGrabSpecs(const WebPPicture* const src,
|
||||
WebPPicture* const dst);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
Loading…
Reference in New Issue
Block a user