mirror of
https://github.com/webmproject/libwebp.git
synced 2025-02-13 15:32:53 +01:00
Improved near-lossless mode.
Compared to previous mode it gives another 10-30% improvement in compression keeping comparable PSNR on corresponding quality settings. Still protected by the WEBP_EXPERIMENTAL_FEATURES flag. Change-Id: I4821815b9a508f4f38c98821acaddb74c73c60ac
This commit is contained in:
parent
0ce27e715e
commit
6c6736816c
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "./vp8enci.h"
|
#include "../dsp/lossless.h"
|
||||||
#include "../utils/utils.h"
|
#include "../utils/utils.h"
|
||||||
|
#include "./vp8enci.h"
|
||||||
|
|
||||||
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
||||||
// Computes quantized pixel value and distance from original value.
|
// Computes quantized pixel value and distance from original value.
|
||||||
@ -28,12 +29,13 @@ static void GetValAndDistance(int a, int initial, int bits,
|
|||||||
*distance = 2 * abs(a - *val);
|
*distance = 2 * abs(a - *val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quantizes values {a, a+(1<<bits), a-(1<<bits)} and returns the nearest one.
|
// Quantizes values {a, a+(1<<bits), a-(1<<bits)}, checks if in [min, max] range
|
||||||
static int FindClosestDiscretized(int a, int bits) {
|
// and returns the nearest one.
|
||||||
int best_val, min_distance, i;
|
static int FindClosestDiscretized(int a, int bits, int min, int max) {
|
||||||
GetValAndDistance(a, a, bits, &best_val, &min_distance);
|
int best_val = a, i;
|
||||||
|
int min_distance = 256;
|
||||||
|
|
||||||
for (i = -1; i <= 1; i += 2) {
|
for (i = -1; i <= 1; ++i) {
|
||||||
int val = a + i * (1 << bits);
|
int val = a + i * (1 << bits);
|
||||||
int candidate, distance;
|
int candidate, distance;
|
||||||
if (val < 0) {
|
if (val < 0) {
|
||||||
@ -42,11 +44,13 @@ static int FindClosestDiscretized(int a, int bits) {
|
|||||||
val = 255;
|
val = 255;
|
||||||
}
|
}
|
||||||
GetValAndDistance(a, val, bits, &candidate, &distance);
|
GetValAndDistance(a, val, bits, &candidate, &distance);
|
||||||
++distance;
|
if (i != 0) {
|
||||||
|
++distance;
|
||||||
|
}
|
||||||
// Smallest distance but favor i == 0 over i == -1 and i == 1
|
// Smallest distance but favor i == 0 over i == -1 and i == 1
|
||||||
// since that keeps the overall intensity more constant in the
|
// since that keeps the overall intensity more constant in the
|
||||||
// images.
|
// images.
|
||||||
if (distance < min_distance) {
|
if (distance < min_distance && candidate >= min && candidate <= max) {
|
||||||
min_distance = distance;
|
min_distance = distance;
|
||||||
best_val = candidate;
|
best_val = candidate;
|
||||||
}
|
}
|
||||||
@ -54,12 +58,34 @@ static int FindClosestDiscretized(int a, int bits) {
|
|||||||
return best_val;
|
return best_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discretizes value (actual - predicted) in the way that actual pixel value
|
||||||
|
// stays within error bounds.
|
||||||
|
static WEBP_INLINE uint32_t DiscretizedResidual(uint32_t actual,
|
||||||
|
uint32_t predicted,
|
||||||
|
int limit_bits) {
|
||||||
|
const uint32_t res = (actual - predicted) & 0xff;
|
||||||
|
uint32_t min, max;
|
||||||
|
if (actual < predicted) {
|
||||||
|
min = 256 - predicted;
|
||||||
|
max = 255;
|
||||||
|
} else {
|
||||||
|
min = 0;
|
||||||
|
max = 255 - predicted;
|
||||||
|
}
|
||||||
|
return FindClosestDiscretized(res, limit_bits, min, max);
|
||||||
|
}
|
||||||
|
|
||||||
// Applies FindClosestDiscretized to all channels of pixel.
|
// Applies FindClosestDiscretized to all channels of pixel.
|
||||||
static uint32_t ClosestDiscretizedArgb(uint32_t a, int bits) {
|
static uint32_t ClosestDiscretizedArgb(uint32_t a, int bits,
|
||||||
return (FindClosestDiscretized(a >> 24, bits) << 24) |
|
uint32_t min, uint32_t max) {
|
||||||
(FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) |
|
return (FindClosestDiscretized(a >> 24, bits, min >> 24, max >> 24) << 24) |
|
||||||
(FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) |
|
(FindClosestDiscretized((a >> 16) & 0xff, bits,
|
||||||
(FindClosestDiscretized(a & 0xff, bits));
|
(min >> 16) & 0xff,
|
||||||
|
(max >> 16) & 0xff) << 16) |
|
||||||
|
(FindClosestDiscretized((a >> 8) & 0xff, bits,
|
||||||
|
(min >> 8) & 0xff,
|
||||||
|
(max >> 8) & 0xff) << 8) |
|
||||||
|
(FindClosestDiscretized(a & 0xff, bits, min & 0xff, max & 0xff));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if distance between corresponding channel values of pixels a and b
|
// Checks if distance between corresponding channel values of pixels a and b
|
||||||
@ -102,12 +128,16 @@ static void NearLossless(int xsize, int ysize, uint32_t* argb,
|
|||||||
smooth_area = 0;
|
smooth_area = 0;
|
||||||
}
|
}
|
||||||
if (!smooth_area) {
|
if (!smooth_area) {
|
||||||
argb[ix] = ClosestDiscretizedArgb(argb[ix], limit_bits);
|
argb[ix] = ClosestDiscretizedArgb(argb[ix], limit_bits, 0, 0xffffffff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
static int QualityToLimitBits(int quality) {
|
||||||
|
return 5 - (quality + 12) / 25;
|
||||||
|
}
|
||||||
|
#endif // WEBP_EXPERIMENTAL_FEATURES
|
||||||
|
|
||||||
// TODO(akramarz): optimize memory to O(xsize)
|
// TODO(akramarz): optimize memory to O(xsize)
|
||||||
int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality) {
|
int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality) {
|
||||||
@ -122,7 +152,7 @@ int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality) {
|
|||||||
(uint32_t *)WebPSafeMalloc(xsize * ysize, sizeof(*copy_buffer));
|
(uint32_t *)WebPSafeMalloc(xsize * ysize, sizeof(*copy_buffer));
|
||||||
// quality mapping 0..12 -> 5
|
// quality mapping 0..12 -> 5
|
||||||
// 13..100 -> 4..1
|
// 13..100 -> 4..1
|
||||||
const int limit_bits = 5 - (quality + 12) / 25;
|
const int limit_bits = QualityToLimitBits(quality);
|
||||||
assert(argb != NULL);
|
assert(argb != NULL);
|
||||||
assert(limit_bits >= 0);
|
assert(limit_bits >= 0);
|
||||||
assert(limit_bits < 31);
|
assert(limit_bits < 31);
|
||||||
@ -133,6 +163,97 @@ int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality) {
|
|||||||
NearLossless(xsize, ysize, argb, i, copy_buffer);
|
NearLossless(xsize, ysize, argb, i, copy_buffer);
|
||||||
}
|
}
|
||||||
WebPSafeFree(copy_buffer);
|
WebPSafeFree(copy_buffer);
|
||||||
#endif
|
#endif // WEBP_EXPERIMENTAL_FEATURES
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
||||||
|
|
||||||
|
// In-place sum of each component with mod 256.
|
||||||
|
// This probably should go somewhere else (lossless.h?). This is just copy-paste
|
||||||
|
// from lossless.c.
|
||||||
|
static WEBP_INLINE void AddPixelsEq(uint32_t* a, uint32_t b) {
|
||||||
|
const uint32_t alpha_and_green = (*a & 0xff00ff00u) + (b & 0xff00ff00u);
|
||||||
|
const uint32_t red_and_blue = (*a & 0x00ff00ffu) + (b & 0x00ff00ffu);
|
||||||
|
*a = (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VP8ApplyNearLosslessPredict(int xsize, int ysize, int pred_bits,
|
||||||
|
const uint32_t* argb_orig,
|
||||||
|
uint32_t* argb, uint32_t* argb_scratch,
|
||||||
|
const uint32_t* const transform_data,
|
||||||
|
int quality, int subtract_green) {
|
||||||
|
const int tiles_per_row = VP8LSubSampleSize(xsize, pred_bits);
|
||||||
|
uint32_t* const upper_row = argb_scratch;
|
||||||
|
const int limit_bits = QualityToLimitBits(quality);
|
||||||
|
|
||||||
|
int y;
|
||||||
|
for (y = 0; y < ysize; ++y) {
|
||||||
|
int x;
|
||||||
|
uint32_t curr_pix = 0, prev_pix = 0;
|
||||||
|
for (x = 0; x < xsize; ++x) {
|
||||||
|
const int tile_idx = (y >> pred_bits) * tiles_per_row + (x >> pred_bits);
|
||||||
|
const int pred = (transform_data[tile_idx] >> 8) & 0xf;
|
||||||
|
const VP8LPredictorFunc pred_func = VP8LPredictors[pred];
|
||||||
|
uint32_t predict, rb_shift = 0, delta_g = 0;
|
||||||
|
if (y == 0) {
|
||||||
|
predict = (x == 0) ? ARGB_BLACK : prev_pix; // Left.
|
||||||
|
} else if (x == 0) {
|
||||||
|
predict = upper_row[x]; // Top.
|
||||||
|
} else {
|
||||||
|
predict = pred_func(prev_pix, upper_row + x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discretize all residuals keeping the original pixel values in error
|
||||||
|
// bounds.
|
||||||
|
curr_pix = argb_orig[x];
|
||||||
|
{
|
||||||
|
const uint32_t a = curr_pix >> 24;
|
||||||
|
const uint32_t a_pred = predict >> 24;
|
||||||
|
const uint32_t a_res = DiscretizedResidual(a, a_pred, limit_bits);
|
||||||
|
curr_pix = (curr_pix & 0x00ffffff) | a_res << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const uint32_t g = (curr_pix >> 8) & 0xff;
|
||||||
|
const uint32_t g_pred = (predict >> 8) & 0xff;
|
||||||
|
const uint32_t g_res = DiscretizedResidual(g, g_pred, limit_bits);
|
||||||
|
// In case subtract-green transform is used, we need to shift
|
||||||
|
// red and blue later.
|
||||||
|
if (subtract_green) {
|
||||||
|
delta_g = (g_pred + g_res - g) & 0xff;
|
||||||
|
rb_shift = g;
|
||||||
|
}
|
||||||
|
curr_pix = (curr_pix & 0xffff00ff) | (g_res << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const uint32_t r = ((curr_pix >> 16) + rb_shift) & 0xff;
|
||||||
|
const uint32_t r_pred = ((predict >> 16) + rb_shift + delta_g) & 0xff;
|
||||||
|
const uint32_t r_res = DiscretizedResidual(r, r_pred, limit_bits);
|
||||||
|
curr_pix = (curr_pix & 0xff00ffff) | (r_res << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const uint32_t b = (curr_pix + rb_shift) & 0xff;
|
||||||
|
const uint32_t b_pred = (predict + rb_shift + delta_g) & 0xff;
|
||||||
|
const uint32_t b_res = DiscretizedResidual(b, b_pred, limit_bits);
|
||||||
|
curr_pix = (curr_pix & 0xffffff00) | b_res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change pixel value.
|
||||||
|
argb[x] = curr_pix;
|
||||||
|
curr_pix = predict;
|
||||||
|
AddPixelsEq(&curr_pix, argb[x]);
|
||||||
|
// Copy previous pixel to upper row.
|
||||||
|
if(x > 0) {
|
||||||
|
upper_row[x - 1] = prev_pix;
|
||||||
|
}
|
||||||
|
prev_pix = curr_pix;
|
||||||
|
}
|
||||||
|
argb += xsize;
|
||||||
|
argb_orig += xsize;
|
||||||
|
upper_row[xsize - 1] = curr_pix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // WEBP_EXPERIMENTAL_FEATURES
|
||||||
|
@ -572,7 +572,12 @@ int WebPPictureAllocYUVA(WebPPicture* const picture, int width, int height);
|
|||||||
// in near_lossless.c
|
// in near_lossless.c
|
||||||
// Near lossless preprocessing in RGB color-space.
|
// Near lossless preprocessing in RGB color-space.
|
||||||
int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality);
|
int VP8ApplyNearLossless(int xsize, int ysize, uint32_t* argb, int quality);
|
||||||
|
// Near lossless adjustment for predictors.
|
||||||
|
void VP8ApplyNearLosslessPredict(int xsize, int ysize, int pred_bits,
|
||||||
|
const uint32_t* argb_orig,
|
||||||
|
uint32_t* argb, uint32_t* argb_scratch,
|
||||||
|
const uint32_t* const transform_data,
|
||||||
|
int quality, int subtract_green);
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -1214,6 +1214,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config,
|
|||||||
const int height = picture->height;
|
const int height = picture->height;
|
||||||
VP8LEncoder* const enc = VP8LEncoderNew(config, picture);
|
VP8LEncoder* const enc = VP8LEncoderNew(config, picture);
|
||||||
const size_t byte_position = VP8LBitWriterNumBytes(bw);
|
const size_t byte_position = VP8LBitWriterNumBytes(bw);
|
||||||
|
const int use_near_lossless = !enc->use_palette_ && config->near_lossless;
|
||||||
int hdr_size = 0;
|
int hdr_size = 0;
|
||||||
int data_size = 0;
|
int data_size = 0;
|
||||||
|
|
||||||
@ -1230,6 +1231,13 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config,
|
|||||||
goto Error;
|
goto Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no prediction transform just apply near-lossless preprocessing.
|
||||||
|
if (!enc->use_predict_ && use_near_lossless &&
|
||||||
|
!VP8ApplyNearLossless(width, height, picture->argb,
|
||||||
|
config->near_lossless)) {
|
||||||
|
goto Error;
|
||||||
|
}
|
||||||
|
|
||||||
if (enc->use_palette_) {
|
if (enc->use_palette_) {
|
||||||
err = EncodePalette(bw, enc);
|
err = EncodePalette(bw, enc);
|
||||||
if (err != VP8_ENC_OK) goto Error;
|
if (err != VP8_ENC_OK) goto Error;
|
||||||
@ -1256,8 +1264,39 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (enc->use_predict_) {
|
if (enc->use_predict_) {
|
||||||
|
uint32_t* copy_buffer = NULL;
|
||||||
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
||||||
|
if (use_near_lossless) {
|
||||||
|
// Copy image to temporary buffer.
|
||||||
|
int y;
|
||||||
|
copy_buffer = WebPSafeMalloc(height * enc->current_width_,
|
||||||
|
sizeof(*copy_buffer));
|
||||||
|
if (copy_buffer == NULL) {
|
||||||
|
err = VP8_ENC_ERROR_OUT_OF_MEMORY;
|
||||||
|
goto Error;
|
||||||
|
}
|
||||||
|
for (y = 0; y < height; ++y) {
|
||||||
|
memcpy(copy_buffer + y * enc->current_width_,
|
||||||
|
enc->argb_ + y * enc->current_width_,
|
||||||
|
enc->current_width_ * sizeof(*enc->argb_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // WEBP_EXPERIMENTAL_FEATURES
|
||||||
err = ApplyPredictFilter(enc, enc->current_width_, height, quality, bw);
|
err = ApplyPredictFilter(enc, enc->current_width_, height, quality, bw);
|
||||||
if (err != VP8_ENC_OK) goto Error;
|
if (err != VP8_ENC_OK) {
|
||||||
|
WebPSafeFree(copy_buffer);
|
||||||
|
goto Error;
|
||||||
|
}
|
||||||
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
||||||
|
if (use_near_lossless) {
|
||||||
|
VP8ApplyNearLosslessPredict(enc->current_width_, height,
|
||||||
|
enc->transform_bits_, copy_buffer, enc->argb_,
|
||||||
|
enc->argb_scratch_, enc->transform_data_,
|
||||||
|
config->near_lossless,
|
||||||
|
enc->use_subtract_green_);
|
||||||
|
WebPSafeFree(copy_buffer);
|
||||||
|
}
|
||||||
|
#endif // WEBP_EXPERIMENTAL_FEATURES
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enc->use_cross_color_) {
|
if (enc->use_cross_color_) {
|
||||||
|
@ -368,11 +368,6 @@ int WebPEncode(const WebPConfig* config, WebPPicture* pic) {
|
|||||||
}
|
}
|
||||||
ok &= DeleteVP8Encoder(enc); // must always be called, even if !ok
|
ok &= DeleteVP8Encoder(enc); // must always be called, even if !ok
|
||||||
} else {
|
} else {
|
||||||
if (config->near_lossless > 0 &&
|
|
||||||
!VP8ApplyNearLossless(pic->width, pic->height,
|
|
||||||
pic->argb, config->near_lossless)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Make sure we have ARGB samples.
|
// Make sure we have ARGB samples.
|
||||||
if (pic->argb == NULL && !WebPPictureYUVAToARGB(pic)) {
|
if (pic->argb == NULL && !WebPPictureYUVAToARGB(pic)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user