Merge tag 'v0.5.1'

libwebp-0.5.1
- 6/14/2016: version 0.5.1
  This is a binary compatible release.
  * miscellaneous bug fixes (issues #280, #289)
  * reverted alpha plane encoding with color cache for compatibility with
    libwebp 0.4.0->0.4.3 (issues #291, #298)
  * lossless encoding performance improvements
  * memory reduction in both lossless encoding and decoding
  * force mux output to be in the extended format (VP8X) when undefined chunks
    are present (issue #294)
  * gradle, cmake build support
  * workaround for compiler bug causing 64-bit decode failures on android
    devices using clang-3.8 in the r11c NDK
  * various WebPAnimEncoder improvements

* tag 'v0.5.1': (30 commits)
  update ChangeLog
  Clarify the expected 'config' lifespan in WebPIDecode()
  update ChangeLog
  Fix corner case in CostManagerInit.
  gif2webp: normalize the number of .'s in the help message
  vwebp: normalize the number of .'s in the help message
  cwebp: normalize the number of .'s in the help message
  fix rescaling bug: alpha plane wasn't filled with 0xff
  Improve lossless compression.
  'our bug tracker' -> 'the bug tracker'
  normalize the number of .'s in the help message
  pngdec,ReadFunc: throw an error on invalid read
  decode.h,WebPGetInfo: normalize function comment
  Inline GetResidual for speed.
  Speed-up uniform-region processing.
  free -> WebPSafeFree()
  DecodeImageData(): change the incorrect assert
  Fix a boundary case in BackwardReferencesHashChainDistanceOnly.
  Make sure to consider small distances in LZ77.
  add some asserts to delimit the perimeter of CostManager's operation
  ...

Change-Id: I44cee79fddd43527062ea9d83be67da42484ebfc
This commit is contained in:
James Zern
2016-07-06 19:30:36 -07:00
27 changed files with 396 additions and 123 deletions

View File

@ -38,7 +38,7 @@ libwebp_la_LIBADD += utils/libwebputils.la
# other than the ones listed on the command line, i.e., after linking, it will
# not have unresolved symbols. Some platforms (Windows among them) require all
# symbols in shared libraries to be resolved at library creation.
libwebp_la_LDFLAGS = -no-undefined -version-info 6:0:0
libwebp_la_LDFLAGS = -no-undefined -version-info 6:1:0
libwebpincludedir = $(includedir)/webp
pkgconfig_DATA = libwebp.pc
@ -50,7 +50,7 @@ if BUILD_LIBWEBPDECODER
libwebpdecoder_la_LIBADD += dsp/libwebpdspdecode.la
libwebpdecoder_la_LIBADD += utils/libwebputilsdecode.la
libwebpdecoder_la_LDFLAGS = -no-undefined -version-info 2:0:0
libwebpdecoder_la_LDFLAGS = -no-undefined -version-info 2:1:0
pkgconfig_DATA += libwebpdecoder.pc
endif

View File

@ -721,6 +721,9 @@ static void ProcessRows(VP8LDecoder* const dec, int row) {
const int num_rows = row - dec->last_row_;
assert(row <= dec->io_->crop_bottom);
// We can't process more than NUM_ARGB_CACHE_ROWS at a time (that's the size
// of argb_cache_), but we currently don't need more than that.
assert(num_rows <= NUM_ARGB_CACHE_ROWS);
if (num_rows > 0) { // Emit output.
VP8Io* const io = dec->io_;
uint8_t* rows_data = (uint8_t*)dec->argb_cache_;
@ -1055,7 +1058,7 @@ static int DecodeImageData(VP8LDecoder* const dec, uint32_t* const data,
const int mask = hdr->huffman_mask_;
const HTreeGroup* htree_group =
(src < src_last) ? GetHtreeGroupForPos(hdr, col, row) : NULL;
assert(src < src_end);
assert(dec->last_row_ < last_row);
assert(src_last <= src_end);
while (src < src_last) {
@ -1464,23 +1467,31 @@ static int AllocateInternalBuffers8b(VP8LDecoder* const dec) {
// Special row-processing that only stores the alpha data.
static void ExtractAlphaRows(VP8LDecoder* const dec, int last_row) {
const int num_rows = last_row - dec->last_row_;
const uint32_t* const in = dec->pixels_ + dec->width_ * dec->last_row_;
int cur_row = dec->last_row_;
int num_rows = last_row - cur_row;
const uint32_t* in = dec->pixels_ + dec->width_ * cur_row;
assert(last_row <= dec->io_->crop_bottom);
if (num_rows > 0) {
while (num_rows > 0) {
const int num_rows_to_process =
(num_rows > NUM_ARGB_CACHE_ROWS) ? NUM_ARGB_CACHE_ROWS : num_rows;
// Extract alpha (which is stored in the green plane).
ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io_->opaque;
uint8_t* const output = alph_dec->output_;
const int width = dec->io_->width; // the final width (!= dec->width_)
const int cache_pixs = width * num_rows;
uint8_t* dst = output + width * dec->last_row_;
const int cache_pixs = width * num_rows_to_process;
uint8_t* const dst = output + width * cur_row;
const uint32_t* const src = dec->argb_cache_;
int i;
ApplyInverseTransforms(dec, num_rows, in);
ApplyInverseTransforms(dec, num_rows_to_process, in);
for (i = 0; i < cache_pixs; ++i) dst[i] = (src[i] >> 8) & 0xff;
AlphaApplyFilter(alph_dec, dec->last_row_, last_row, dst, width);
AlphaApplyFilter(alph_dec,
cur_row, cur_row + num_rows_to_process, dst, width);
num_rows -= num_rows_to_process;
in += num_rows_to_process * dec->width_;
cur_row += num_rows_to_process;
}
assert(cur_row == last_row);
dec->last_row_ = dec->last_out_row_ = last_row;
}

View File

@ -684,14 +684,13 @@ static uint32_t NearLossless(uint32_t value, uint32_t predict,
// Returns the difference between the pixel and its prediction. In case of a
// lossy encoding, updates the source image to avoid propagating the deviation
// further to pixels which depend on the current pixel for their predictions.
static uint32_t GetResidual(int width, int height,
uint32_t* const upper_row,
uint32_t* const current_row,
const uint8_t* const max_diffs,
int mode, VP8LPredictorFunc pred_func,
int x, int y,
int max_quantization,
int exact, int used_subtract_green) {
static WEBP_INLINE uint32_t GetResidual(int width, int height,
uint32_t* const upper_row,
uint32_t* const current_row,
const uint8_t* const max_diffs,
int mode, VP8LPredictorFunc pred_func,
int x, int y, int max_quantization,
int exact, int used_subtract_green) {
const uint32_t predict = Predict(pred_func, x, y, current_row, upper_row);
uint32_t residual;
if (max_quantization == 1 || mode == 0 || y == 0 || y == height - 1 ||

View File

@ -245,6 +245,7 @@ int VP8LHashChainFill(VP8LHashChain* const p, int quality,
const uint32_t* const argb, int xsize, int ysize) {
const int size = xsize * ysize;
const int iter_max = GetMaxItersForQuality(quality);
const int iter_min = iter_max - quality / 10;
const uint32_t window_size = GetWindowSizeForHashChain(quality, xsize);
int pos;
uint32_t base_position;
@ -296,7 +297,11 @@ int VP8LHashChainFill(VP8LHashChain* const p, int quality,
if (best_length < curr_length) {
best_length = curr_length;
best_distance = base_position - pos;
if (curr_length >= length_max) {
// Stop if we have reached the maximum length. Otherwise, make sure
// we have executed a minimum number of iterations depending on the
// quality.
if ((best_length == MAX_LENGTH) ||
(curr_length >= length_max && iter < iter_min)) {
break;
}
}
@ -437,7 +442,6 @@ static int BackwardReferencesLz77(int xsize, int ysize,
int len = 0;
int j;
HashChainFindCopy(hash_chain, i, &offset, &len);
// MIN_LENGTH+1 is empirically better than MIN_LENGTH.
if (len > MIN_LENGTH + 1) {
const int len_ini = len;
int max_reach = 0;
@ -648,9 +652,8 @@ typedef struct {
CostCacheInterval* cache_intervals_;
size_t cache_intervals_size_;
double cost_cache_[MAX_LENGTH]; // Contains the GetLengthCost(cost_model, k).
// cost_cache_min_dist_[i] contains the index of the minimum in
// cost_cache_[1:i]. cost_cache_min_dist_[0] is unused.
uint16_t cost_cache_min_dist_[MAX_LENGTH];
double min_cost_cache_; // The minimum value in cost_cache_[1:].
double max_cost_cache_; // The maximum value in cost_cache_[1:].
float* costs_;
uint16_t* dist_array_;
// Most of the time, we only need few intervals -> use a free-list, to avoid
@ -660,6 +663,10 @@ typedef struct {
// These are regularly malloc'd remains. This list can't grow larger than than
// size COST_CACHE_INTERVAL_SIZE_MAX - COST_MANAGER_MAX_FREE_LIST, note.
CostInterval* recycled_intervals_;
// Buffer used in BackwardReferencesHashChainDistanceOnly to store the ends
// of the intervals that can have impacted the cost at a pixel.
int* interval_ends_;
int interval_ends_size_;
} CostManager;
static int IsCostCacheIntervalWritable(int start, int end) {
@ -708,6 +715,7 @@ static void CostManagerClear(CostManager* const manager) {
WebPSafeFree(manager->costs_);
WebPSafeFree(manager->cache_intervals_);
WebPSafeFree(manager->interval_ends_);
// Clear the interval lists.
DeleteIntervalList(manager, manager->head_);
@ -720,22 +728,6 @@ static void CostManagerClear(CostManager* const manager) {
CostManagerInitFreeList(manager);
}
static void CostManagerMinDistCacheInit(CostManager* const manager) {
int min_idx = 1;
double min = manager->cost_cache_[1];
int i;
// cost_cache_min_dist_[i] contains the index of the minimum in
// cost_cache_[1:i].
for (i = 1; i < MAX_LENGTH; ++i) {
manager->cost_cache_min_dist_[i] = min_idx;
if (manager->cost_cache_[i] < min) {
min_idx = i;
min = manager->cost_cache_[i];
}
}
}
static int CostManagerInit(CostManager* const manager,
uint16_t* const dist_array, int pix_count,
const CostModel* const cost_model) {
@ -745,6 +737,9 @@ static int CostManagerInit(CostManager* const manager,
// Empirically, differences between intervals is usually of more than 1.
const double min_cost_diff = 0.1;
manager->costs_ = NULL;
manager->cache_intervals_ = NULL;
manager->interval_ends_ = NULL;
manager->head_ = NULL;
manager->recycled_intervals_ = NULL;
manager->count_ = 0;
@ -761,10 +756,17 @@ static int CostManagerInit(CostManager* const manager,
min_cost_diff) {
++manager->cache_intervals_size_;
}
// Compute the minimum of cost_cache_.
if (i == 1) {
manager->min_cost_cache_ = manager->cost_cache_[1];
manager->max_cost_cache_ = manager->cost_cache_[1];
} else if (manager->cost_cache_[i] < manager->min_cost_cache_) {
manager->min_cost_cache_ = manager->cost_cache_[i];
} else if (manager->cost_cache_[i] > manager->max_cost_cache_) {
manager->max_cost_cache_ = manager->cost_cache_[i];
}
}
CostManagerMinDistCacheInit(manager);
// With the current cost models, we have 15 intervals, so we are safe by
// setting a maximum of COST_CACHE_INTERVAL_SIZE_MAX.
if (manager->cache_intervals_size_ > COST_CACHE_INTERVAL_SIZE_MAX) {
@ -789,7 +791,8 @@ static int CostManagerInit(CostManager* const manager,
// difference is found, a new interval is created and bounded.
for (i = 0; i < cost_cache_size; ++i) {
const double cost_val = manager->cost_cache_[i];
if (fabs(cost_val - cost_prev) > min_cost_diff && cur + 1 < end) {
if (i == 0 ||
(fabs(cost_val - cost_prev) > min_cost_diff && cur + 1 < end)) {
if (i > 1) {
const int is_writable =
IsCostCacheIntervalWritable(cur->start_, cur->end_);
@ -833,25 +836,67 @@ static int CostManagerInit(CostManager* const manager,
CostManagerClear(manager);
return 0;
}
// Set the initial costs_ high for every pixel as we wil lkeep the minimum.
// Set the initial costs_ high for every pixel as we will keep the minimum.
for (i = 0; i < pix_count; ++i) manager->costs_[i] = 1e38f;
// The cost at pixel is influenced by the cost intervals from previous pixels.
// Let us take the specific case where the offset is the same (which actually
// happens a lot in case of uniform regions).
// pixel i contributes to j>i a cost of: offset cost + cost_cache_[j-i]
// pixel i+1 contributes to j>i a cost of: 2*offset cost + cost_cache_[j-i-1]
// pixel i+2 contributes to j>i a cost of: 3*offset cost + cost_cache_[j-i-2]
// and so on.
// A pixel i influences the following length(j) < MAX_LENGTH pixels. What is
// the value of j such that pixel i + j cannot influence any of those pixels?
// This value is such that:
// max of cost_cache_ < j*offset cost + min of cost_cache_
// (pixel i + j 's cost cannot beat the worst cost given by pixel i).
// This value will be used to optimize the cost computation in
// BackwardReferencesHashChainDistanceOnly.
{
// The offset cost is computed in GetDistanceCost and has a minimum value of
// the minimum in cost_model->distance_. The case where the offset cost is 0
// will be dealt with differently later so we are only interested in the
// minimum non-zero offset cost.
double offset_cost_min = 0.;
int size;
for (i = 0; i < NUM_DISTANCE_CODES; ++i) {
if (cost_model->distance_[i] != 0) {
if (offset_cost_min == 0.) {
offset_cost_min = cost_model->distance_[i];
} else if (cost_model->distance_[i] < offset_cost_min) {
offset_cost_min = cost_model->distance_[i];
}
}
}
// In case all the cost_model->distance_ is 0, the next non-zero cost we
// can have is from the extra bit in GetDistanceCost, hence 1.
if (offset_cost_min < 1.) offset_cost_min = 1.;
size = 1 + (int)ceil((manager->max_cost_cache_ - manager->min_cost_cache_) /
offset_cost_min);
// Empirically, we usually end up with a value below 100.
if (size > MAX_LENGTH) size = MAX_LENGTH;
manager->interval_ends_ =
(int*)WebPSafeMalloc(size, sizeof(*manager->interval_ends_));
if (manager->interval_ends_ == NULL) {
CostManagerClear(manager);
return 0;
}
manager->interval_ends_size_ = size;
}
return 1;
}
// Given the distance_cost for pixel 'index', update the cost at pixel 'i' if it
// is smaller than the previously computed value.
// If index is negative, the distance is computed using
// manager->cost_cache_min_dist_[-index] instead.
static WEBP_INLINE void UpdateCost(CostManager* const manager, int i, int index,
double distance_cost) {
int k;
int k = i - index;
double cost_tmp;
if (index < 0) {
k = manager->cost_cache_min_dist_[-index];
} else {
k = i - index;
}
assert(k >= 0 && k < MAX_LENGTH);
cost_tmp = distance_cost + manager->cost_cache_[k];
if (manager->costs_[i] > cost_tmp) {
@ -1173,41 +1218,63 @@ static int BackwardReferencesHashChainDistanceOnly(
if (len >= MIN_LENGTH) {
const int code = DistanceToPlaneCode(xsize, offset);
const double offset_cost = GetDistanceCost(cost_model, code);
double distance_cost = prev_cost + offset_cost;
const int first_i = i;
int j_max = 0, interval_ends_index = 0;
const int is_offset_zero = (offset_cost == 0.);
PushInterval(cost_manager, distance_cost, i, len);
// The cost to get to pixel i+k is:
// prev_cost + best weight of path going from i to l.
// For the last cost, if the path were composed of several segments
// (like [i,j), [j,k), [k,l) ), its cost would be:
// offset cost + manager->cost_cache_[j-i] +
// offset cost + manager->cost_cache_[k-j] +
// offset cost + manager->cost_cache_[l-j]
// As the offset_cost is close to the values in cost_cache_ (both are the
// cost to store a number, whether it's an offset or a length), the best
// paths are therefore the ones involving one hop.
for (; i + 1 < pix_count - 1; ++i) {
int offset_next, len_next, diff;
if (!is_offset_zero) {
j_max = (int)ceil(
(cost_manager->max_cost_cache_ - cost_manager->min_cost_cache_) /
offset_cost);
if (j_max < 1) {
j_max = 1;
} else if (j_max > cost_manager->interval_ends_size_ - 1) {
// This could only happen in the case of MAX_LENGTH.
j_max = cost_manager->interval_ends_size_ - 1;
}
} // else j_max is unused anyway.
// Instead of considering all contributions from a pixel i by calling:
// PushInterval(cost_manager, prev_cost + offset_cost, i, len);
// we optimize these contributions in case offset_cost stays the same for
// consecutive pixels. This describes a set of pixels similar to a
// previous set (e.g. constant color regions).
for (; i < pix_count - 1; ++i) {
int offset_next, len_next;
prev_cost = cost_manager->costs_[i - 1];
distance_cost = prev_cost + offset_cost;
if (is_offset_zero) {
// No optimization can be made so we just push all of the
// contributions from i.
PushInterval(cost_manager, prev_cost, i, len);
} else {
// j_max is chosen as the smallest j such that:
// max of cost_cache_ < j*offset cost + min of cost_cache_
// Therefore, the pixel influenced by i-j_max, cannot be influenced
// by i. Only the costs after the end of what i contributed need to be
// updated. cost_manager->interval_ends_ is a circular buffer that
// stores those ends.
const double distance_cost = prev_cost + offset_cost;
int j = cost_manager->interval_ends_[interval_ends_index];
if (i - first_i <= j_max ||
!IsCostCacheIntervalWritable(j, i + len)) {
PushInterval(cost_manager, distance_cost, i, len);
} else {
for (; j < i + len; ++j) {
UpdateCost(cost_manager, j, i, distance_cost);
}
}
// Store the new end in the circular buffer.
assert(interval_ends_index < cost_manager->interval_ends_size_);
cost_manager->interval_ends_[interval_ends_index] = i + len;
if (++interval_ends_index > j_max) interval_ends_index = 0;
}
// Check whether i is the last pixel to consider, as it is handled
// differently.
if (i + 1 >= pix_count - 1) break;
HashChainFindCopy(hash_chain, i + 1, &offset_next, &len_next);
if (offset_next != offset) break;
diff = i - first_i;
if (diff > 0) {
assert(len > 1);
if (diff >= len) diff = len - 1;
// The best path going to pixel i is the one starting in a pixel
// within diff hence at the pixel:
// i - manager->cost_cache_min_dist_[diff].
UpdateCost(cost_manager, i, -diff, distance_cost);
}
len = len_next;
UpdateCostPerIndex(cost_manager, i);
AddSingleLiteralWithCostModel(argb + i, &hashers, cost_model, i,
@ -1215,7 +1282,6 @@ static int BackwardReferencesHashChainDistanceOnly(
cost_manager->costs_, dist_array);
}
// Submit the last pixel.
if (first_i != i) PushInterval(cost_manager, distance_cost, i, len);
UpdateCostPerIndex(cost_manager, i + 1);
// This if is for speedup only. It roughly doubles the speed, and
@ -1238,7 +1304,7 @@ static int BackwardReferencesHashChainDistanceOnly(
}
goto next_symbol;
}
if (len != MIN_LENGTH) {
if (len > MIN_LENGTH) {
int code_min_length;
double cost_total;
offset = HashChainFindOffset(hash_chain, i);

View File

@ -32,7 +32,7 @@ extern "C" {
// version numbers
#define ENC_MAJ_VERSION 0
#define ENC_MIN_VERSION 5
#define ENC_REV_VERSION 0
#define ENC_REV_VERSION 1
enum { MAX_LF_LEVELS = 64, // Maximum loop filter level
MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost

View File

@ -290,7 +290,7 @@ static int AnalyzeEntropy(const uint32_t* argb,
}
}
}
free(histo);
WebPSafeFree(histo);
return 1;
} else {
return 0;

View File

@ -13,6 +13,6 @@ libwebpmuxinclude_HEADERS += ../webp/mux_types.h
libwebpmuxinclude_HEADERS += ../webp/types.h
libwebpmux_la_LIBADD = ../libwebp.la
libwebpmux_la_LDFLAGS = -no-undefined -version-info 2:0:0
libwebpmux_la_LDFLAGS = -no-undefined -version-info 2:1:0
libwebpmuxincludedir = $(includedir)/webp
pkgconfig_DATA = libwebpmux.pc

View File

@ -28,7 +28,7 @@ extern "C" {
#define MUX_MAJ_VERSION 0
#define MUX_MIN_VERSION 3
#define MUX_REV_VERSION 0
#define MUX_REV_VERSION 1
// Chunk object.
typedef struct WebPChunk WebPChunk;

View File

@ -39,8 +39,8 @@ typedef struct WebPDecoderConfig WebPDecoderConfig;
WEBP_EXTERN(int) WebPGetDecoderVersion(void);
// Retrieve basic header information: width, height.
// This function will also validate the header and return 0 in
// case of formatting error.
// This function will also validate the header, returning true on success,
// false otherwise. '*width' and '*height' are only valid on successful return.
// Pointers 'width' and 'height' can be passed NULL if deemed irrelevant.
WEBP_EXTERN(int) WebPGetInfo(const uint8_t* data, size_t data_size,
int* width, int* height);
@ -471,16 +471,18 @@ static WEBP_INLINE int WebPInitDecoderConfig(WebPDecoderConfig* config) {
// parameter, in which case the features will be parsed and stored into
// config->input. Otherwise, 'data' can be NULL and no parsing will occur.
// Note that 'config' can be NULL too, in which case a default configuration
// is used.
// is used. If 'config' is not NULL, it must outlive the WebPIDecoder object
// as some references to its fields will be used. No internal copy of 'config'
// is made.
// The return WebPIDecoder object must always be deleted calling WebPIDelete().
// Returns NULL in case of error (and config->status will then reflect
// the error condition).
// the error condition, if available).
WEBP_EXTERN(WebPIDecoder*) WebPIDecode(const uint8_t* data, size_t data_size,
WebPDecoderConfig* config);
// Non-incremental version. This version decodes the full data at once, taking
// 'config' into account. Returns decoding status (which should be VP8_STATUS_OK
// if the decoding was successful).
// if the decoding was successful). Note that 'config' cannot be NULL.
WEBP_EXTERN(VP8StatusCode) WebPDecode(const uint8_t* data, size_t data_size,
WebPDecoderConfig* config);