diff --git a/src/enc/near_lossless.c b/src/enc/near_lossless.c index 93aeaa0e..e6dd8535 100644 --- a/src/enc/near_lossless.c +++ b/src/enc/near_lossless.c @@ -16,8 +16,9 @@ #include -#include "./vp8enci.h" +#include "../dsp/lossless.h" #include "../utils/utils.h" +#include "./vp8enci.h" #ifdef WEBP_EXPERIMENTAL_FEATURES // 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); } -// Quantizes values {a, a+(1<= min && candidate <= max) { min_distance = distance; best_val = candidate; } @@ -54,12 +58,34 @@ static int FindClosestDiscretized(int a, int bits) { 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. -static uint32_t ClosestDiscretizedArgb(uint32_t a, int bits) { - return (FindClosestDiscretized(a >> 24, bits) << 24) | - (FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) | - (FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) | - (FindClosestDiscretized(a & 0xff, bits)); +static uint32_t ClosestDiscretizedArgb(uint32_t a, int bits, + uint32_t min, uint32_t max) { + return (FindClosestDiscretized(a >> 24, bits, min >> 24, max >> 24) << 24) | + (FindClosestDiscretized((a >> 16) & 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 @@ -102,12 +128,16 @@ static void NearLossless(int xsize, int ysize, uint32_t* argb, smooth_area = 0; } 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) 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)); // quality mapping 0..12 -> 5 // 13..100 -> 4..1 - const int limit_bits = 5 - (quality + 12) / 25; + const int limit_bits = QualityToLimitBits(quality); assert(argb != NULL); assert(limit_bits >= 0); 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); } WebPSafeFree(copy_buffer); -#endif +#endif // WEBP_EXPERIMENTAL_FEATURES 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 diff --git a/src/enc/vp8enci.h b/src/enc/vp8enci.h index ab19c077..d17aa1f6 100644 --- a/src/enc/vp8enci.h +++ b/src/enc/vp8enci.h @@ -572,7 +572,12 @@ int WebPPictureAllocYUVA(WebPPicture* const picture, int width, int height); // in near_lossless.c // Near lossless preprocessing in RGB color-space. 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 diff --git a/src/enc/vp8l.c b/src/enc/vp8l.c index dba31b5c..415627c7 100644 --- a/src/enc/vp8l.c +++ b/src/enc/vp8l.c @@ -1214,6 +1214,7 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, const int height = picture->height; VP8LEncoder* const enc = VP8LEncoderNew(config, picture); const size_t byte_position = VP8LBitWriterNumBytes(bw); + const int use_near_lossless = !enc->use_palette_ && config->near_lossless; int hdr_size = 0; int data_size = 0; @@ -1230,6 +1231,13 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, 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_) { err = EncodePalette(bw, enc); if (err != VP8_ENC_OK) goto Error; @@ -1256,8 +1264,39 @@ WebPEncodingError VP8LEncodeStream(const WebPConfig* const config, } 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); - 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_) { diff --git a/src/enc/webpenc.c b/src/enc/webpenc.c index 5b01e9ef..090f14c5 100644 --- a/src/enc/webpenc.c +++ b/src/enc/webpenc.c @@ -368,11 +368,6 @@ int WebPEncode(const WebPConfig* config, WebPPicture* pic) { } ok &= DeleteVP8Encoder(enc); // must always be called, even if !ok } else { - if (config->near_lossless > 0 && - !VP8ApplyNearLossless(pic->width, pic->height, - pic->argb, config->near_lossless)) { - return 0; - } // Make sure we have ARGB samples. if (pic->argb == NULL && !WebPPictureYUVAToARGB(pic)) { return 0;