| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // This code is licensed under the same terms as WebM: |
| // Software License Agreement: http://www.webmproject.org/license/software/ |
| // Additional IP Rights Grant: http://www.webmproject.org/license/additional/ |
| // ----------------------------------------------------------------------------- |
| // |
| // Alpha plane encoding and decoding library. |
| // |
| // Author: vikasa@google.com (Vikas Arora) |
| |
| #include <string.h> // for memcpy() |
| #include "./alpha.h" |
| |
| #include "./bit_reader.h" |
| #include "./bit_writer.h" |
| #include "./filters.h" |
| #include "./tcoder.h" |
| |
| #if defined(__cplusplus) || defined(c_plusplus) |
| extern "C" { |
| #endif |
| |
| #define MAX_SYMBOLS 255 |
| #define ALPHA_HEADER_LEN 2 |
| |
| // ----------------------------------------------------------------------------- |
| // Zlib-like encoding using TCoder |
| |
| typedef struct { |
| int dist; // backward distance (=0 means: literal) |
| int literal; // literal value (if dist = 0) |
| int len; // length of matched string for non-literal |
| } Token; |
| |
| #define MIN_LEN 2 |
| #define DEFER_SKIP 1 // for deferred evaluation (0 = off) |
| |
| #define CACHED_COST(coder, c) ((cost_cache[(c)] == 0.) ? \ |
| (cost_cache[(c)] = lit_mode_cost + TCoderSymbolCost((coder), (c))) \ |
| : cost_cache[(c)]) |
| |
| // Record symbol |
| #define RECORD(TOKEN) { \ |
| TCoderEncode(coderd, (TOKEN)->dist, NULL); \ |
| if ((TOKEN)->dist == 0) { \ |
| TCoderEncode(coder, (TOKEN)->literal, NULL); \ |
| } else { \ |
| TCoderEncode(coderl, (TOKEN)->len - MIN_LEN, NULL); \ |
| } \ |
| } |
| |
| static int GetLongestMatch(const uint8_t* const data, |
| const uint8_t* const ref, int max_len) { |
| int n; |
| for (n = 0; (n < max_len) && (data[n] == ref[n]); ++n) { /* do nothing */ } |
| return n; |
| } |
| |
| static int EncodeZlibTCoder(const uint8_t* data, int width, int height, |
| VP8BitWriter* const bw) { |
| int ok = 0; |
| const int data_len = width * height; |
| const int MAX_DIST = 3 * width; |
| const int MAX_LEN = 2 * width; |
| Token* const msg = (Token*)malloc(data_len * sizeof(*msg)); |
| int num_tokens; |
| TCoder* const coder = TCoderNew(MAX_SYMBOLS); |
| TCoder* const coderd = TCoderNew(MAX_DIST); |
| TCoder* const coderl = TCoderNew(MAX_LEN - MIN_LEN); |
| |
| if (coder == NULL || coderd == NULL || coderl == NULL) { |
| goto End; |
| } |
| if (msg == NULL) { |
| goto End; |
| } |
| |
| { |
| int deferred_eval = 0; |
| int n = 0; |
| num_tokens = 0; |
| while (n < data_len) { |
| const double lit_mode_cost = TCoderSymbolCost(coderd, 0); |
| double cost_cache[MAX_SYMBOLS + 1] = { 0. }; |
| Token best; |
| int dist = 0; |
| double best_cost = CACHED_COST(coder, data[n]); |
| const int max_len = (MAX_LEN > data_len - n) ? data_len - n : MAX_LEN; |
| |
| best.dist = 0; |
| best.literal = data[n]; |
| best.len = 1; |
| for (dist = 1; dist <= MAX_DIST && dist <= n; ++dist) { |
| const int pos = n - dist; |
| const int min_len = best.len - 1; |
| int len; |
| |
| // Early out: we probe at two locations for a quick match check |
| if (data[pos] != data[n] || |
| data[pos + min_len] != data[n + min_len]) { |
| continue; |
| } |
| |
| len = GetLongestMatch(data + pos, data + n, max_len); |
| if (len >= MIN_LEN && len >= best.len) { |
| // This is the cost of the coding proposal |
| const double cost = TCoderSymbolCost(coderl, len - MIN_LEN) |
| + TCoderSymbolCost(coderd, dist); |
| // We're gaining an extra len-best.len coded message over the last |
| // known best. Compute how this would have cost if coded all literal. |
| // (TODO: we should fully re-evaluate at position best.len and not |
| // assume all is going be coded as literals. But it's at least an |
| // upper-bound (worst-case coding). Deferred evaluation used below |
| // partially addresses this. |
| double lit_cost = 0; |
| int i; |
| for (i = best.len; i < len; ++i) { |
| lit_cost += CACHED_COST(coder, data[n + i]); |
| } |
| // So, is it worth ? |
| if (best_cost + lit_cost >= cost) { |
| best_cost = cost; |
| best.len = len; |
| best.dist = dist; |
| } |
| } |
| if (len >= max_len) { |
| break; // No need to search further. We already got a max-long match |
| } |
| } |
| // Deferred evaluation: before finalizing a choice we try to find |
| // best cost at position n + 1 and see if we get a longer |
| // match then current best. If so, we transform the current match |
| // into a literal, go to position n + 1, and try again. |
| { |
| Token* cur = &msg[num_tokens]; |
| int forget = 0; |
| if (deferred_eval) { |
| --cur; |
| // If the next match isn't longer, keep previous match |
| if (best.len <= cur->len) { |
| deferred_eval = 0; |
| n += cur->len - DEFER_SKIP; |
| forget = 1; // forget the new match |
| RECORD(cur) |
| } else { // else transform previous match into a shorter one |
| cur->len = DEFER_SKIP; |
| if (DEFER_SKIP == 1) { |
| cur->dist = 0; // literal |
| } |
| // TODO(later): RECORD() macro should be changed to take an extra |
| // "is_final" param, so that we could write the bitstream at once. |
| RECORD(cur) |
| ++cur; |
| } |
| } |
| if (!forget) { |
| *cur = best; |
| ++num_tokens; |
| if (DEFER_SKIP > 0) { |
| deferred_eval = (cur->len > 2) && (cur->len < MAX_LEN / 2); |
| } |
| if (deferred_eval) { |
| // will probe at a later position before finalizing. |
| n += DEFER_SKIP; |
| } else { |
| // Keep the current choice. |
| n += cur->len; |
| RECORD(cur) |
| } |
| } |
| } |
| } |
| } |
| |
| // Final bitstream assembly. |
| { |
| int n; |
| TCoderInit(coder); |
| TCoderInit(coderd); |
| TCoderInit(coderl); |
| for (n = 0; n < num_tokens; ++n) { |
| const Token* const t = &msg[n]; |
| const int is_literal = (t->dist == 0); |
| TCoderEncode(coderd, t->dist, bw); |
| if (is_literal) { // literal |
| TCoderEncode(coder, t->literal, bw); |
| } else { |
| TCoderEncode(coderl, t->len - MIN_LEN, bw); |
| } |
| } |
| ok = 1; |
| } |
| |
| End: |
| if (coder) TCoderDelete(coder); |
| if (coderl) TCoderDelete(coderl); |
| if (coderd) TCoderDelete(coderd); |
| free(msg); |
| return ok && !bw->error_; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| static int EncodeAlphaInternal(const uint8_t* data, int width, int height, |
| int method, int filter, size_t data_size, |
| uint8_t* tmp_alpha, VP8BitWriter* const bw) { |
| int ok = 0; |
| const uint8_t* alpha_src; |
| WebPFilterFunc filter_func; |
| uint8_t header[ALPHA_HEADER_LEN]; |
| const size_t expected_size = (method == 0) ? |
| (ALPHA_HEADER_LEN + data_size) : (data_size >> 5); |
| header[0] = (filter << 4) | method; |
| header[1] = 0; // reserved byte for later use |
| VP8BitWriterInit(bw, expected_size); |
| VP8BitWriterAppend(bw, header, sizeof(header)); |
| |
| filter_func = WebPFilters[filter]; |
| if (filter_func) { |
| filter_func(data, width, height, 1, width, tmp_alpha); |
| alpha_src = tmp_alpha; |
| } else { |
| alpha_src = data; |
| } |
| |
| if (method == 0) { |
| ok = VP8BitWriterAppend(bw, alpha_src, width * height); |
| ok = ok && !bw->error_; |
| } else { |
| ok = EncodeZlibTCoder(alpha_src, width, height, bw); |
| VP8BitWriterFinish(bw); |
| } |
| return ok; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| // TODO(skal): move to dsp/ ? |
| 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; |
| } |
| } |
| |
| int EncodeAlpha(const uint8_t* data, int width, int height, int stride, |
| int quality, int method, int filter, |
| uint8_t** output, size_t* output_size) { |
| uint8_t* quant_alpha = NULL; |
| const size_t data_size = height * width; |
| int ok = 1; |
| |
| // quick sanity checks |
| assert(data != NULL && output != NULL && output_size != NULL); |
| assert(width > 0 && height > 0); |
| assert(stride >= width); |
| assert(filter >= WEBP_FILTER_NONE && filter <= WEBP_FILTER_FAST); |
| |
| if (quality < 0 || quality > 100) { |
| return 0; |
| } |
| |
| if (method < 0 || method > 1) { |
| return 0; |
| } |
| |
| quant_alpha = (uint8_t*)malloc(data_size); |
| if (quant_alpha == NULL) { |
| return 0; |
| } |
| |
| // Extract alpha data (width x height) from raw_data (stride x height). |
| CopyPlane(data, stride, quant_alpha, width, width, height); |
| |
| if (quality < 100) { // No Quantization required for 'quality = 100'. |
| // 16 alpha levels gives quite a low MSE w.r.t original alpha plane hence |
| // mapped to moderate quality 70. Hence Quality:[0, 70] -> Levels:[2, 16] |
| // and Quality:]70, 100] -> Levels:]16, 256]. |
| const int alpha_levels = (quality <= 70) ? (2 + quality / 5) |
| : (16 + (quality - 70) * 8); |
| ok = QuantizeLevels(quant_alpha, width, height, alpha_levels, NULL); |
| } |
| |
| if (ok) { |
| VP8BitWriter bw; |
| size_t best_score; |
| int test_filter; |
| uint8_t* filtered_alpha = NULL; |
| |
| // We always test WEBP_FILTER_NONE first. |
| ok = EncodeAlphaInternal(quant_alpha, width, height, method, |
| WEBP_FILTER_NONE, data_size, NULL, &bw); |
| if (!ok) { |
| VP8BitWriterWipeOut(&bw); |
| goto End; |
| } |
| best_score = VP8BitWriterSize(&bw); |
| |
| if (filter == WEBP_FILTER_FAST) { // Quick estimate of a second candidate? |
| filter = EstimateBestFilter(quant_alpha, width, height, width); |
| } |
| // Stop? |
| if (filter == WEBP_FILTER_NONE) { |
| goto Ok; |
| } |
| |
| filtered_alpha = (uint8_t*)malloc(data_size); |
| ok = (filtered_alpha != NULL); |
| if (!ok) { |
| goto End; |
| } |
| |
| // Try the other mode(s). |
| for (test_filter = WEBP_FILTER_HORIZONTAL; |
| ok && (test_filter <= WEBP_FILTER_GRADIENT); |
| ++test_filter) { |
| VP8BitWriter tmp_bw; |
| if (filter != WEBP_FILTER_BEST && test_filter != filter) { |
| continue; |
| } |
| |
| ok = EncodeAlphaInternal(quant_alpha, width, height, method, test_filter, |
| data_size, filtered_alpha, &tmp_bw); |
| if (ok) { |
| const size_t score = VP8BitWriterSize(&tmp_bw); |
| if (score < best_score) { |
| // swap bitwriter objects. |
| VP8BitWriter tmp = tmp_bw; |
| tmp_bw = bw; |
| bw = tmp; |
| best_score = score; |
| } |
| } else { |
| VP8BitWriterWipeOut(&bw); |
| } |
| VP8BitWriterWipeOut(&tmp_bw); |
| } |
| Ok: |
| if (ok) { |
| *output_size = VP8BitWriterSize(&bw); |
| *output = VP8BitWriterBuf(&bw); |
| } |
| free(filtered_alpha); |
| } |
| End: |
| free(quant_alpha); |
| return ok; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| // Alpha Decode. |
| |
| static int DecompressZlibTCoder(VP8BitReader* const br, int width, |
| uint8_t* output, size_t output_size) { |
| int ok = 0; |
| const int MAX_DIST = 3 * width; |
| const int MAX_LEN = 2 * width; |
| TCoder* const coder = TCoderNew(MAX_SYMBOLS); |
| TCoder* const coderd = TCoderNew(MAX_DIST); |
| TCoder* const coderl = TCoderNew(MAX_LEN - MIN_LEN); |
| |
| if (coder == NULL || coderd == NULL || coderl == NULL) { |
| goto End; |
| } |
| |
| { |
| size_t pos = 0; |
| assert(br != NULL); |
| while (pos < output_size && !br->eof_) { |
| const size_t dist = TCoderDecode(coderd, br); |
| if (dist == 0) { |
| output[pos] = TCoderDecode(coder, br); |
| ++pos; |
| } else { |
| const size_t len = MIN_LEN + TCoderDecode(coderl, br); |
| size_t k; |
| if (pos + len > output_size || pos < dist) goto End; |
| for (k = 0; k < len; ++k) { |
| output[pos + k] = output[pos + k - dist]; |
| } |
| pos += len; |
| } |
| } |
| ok = !br->eof_; |
| } |
| |
| End: |
| if (coder) TCoderDelete(coder); |
| if (coderl) TCoderDelete(coderl); |
| if (coderd) TCoderDelete(coderd); |
| return ok; |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| int DecodeAlpha(const uint8_t* data, size_t data_size, |
| int width, int height, int stride, |
| uint8_t* output) { |
| uint8_t* decoded_data = NULL; |
| const size_t decoded_size = height * width; |
| uint8_t* unfiltered_data = NULL; |
| WEBP_FILTER_TYPE filter; |
| int ok = 0; |
| int method; |
| |
| assert(width > 0 && height > 0 && stride >= width); |
| assert(data != NULL && output != NULL); |
| |
| if (data_size <= ALPHA_HEADER_LEN) { |
| return 0; |
| } |
| |
| method = data[0] & 0x0f; |
| filter = data[0] >> 4; |
| ok = (data[1] == 0); |
| if (method < 0 || method > 1 || |
| filter > WEBP_FILTER_GRADIENT || !ok) { |
| return 0; |
| } |
| |
| if (method == 0) { |
| ok = (data_size >= decoded_size); |
| decoded_data = (uint8_t*)data + ALPHA_HEADER_LEN; |
| } else if (method == 1) { |
| VP8BitReader br; |
| decoded_data = (uint8_t*)malloc(decoded_size); |
| if (decoded_data == NULL) { |
| return 0; |
| } |
| VP8InitBitReader(&br, data + ALPHA_HEADER_LEN, data + data_size); |
| ok = DecompressZlibTCoder(&br, width, decoded_data, decoded_size); |
| } |
| if (ok) { |
| WebPFilterFunc unfilter_func = WebPUnfilters[filter]; |
| if (unfilter_func) { |
| unfiltered_data = (uint8_t*)malloc(decoded_size); |
| if (unfiltered_data == NULL) { |
| if (method == 1) free(decoded_data); |
| return 0; |
| } |
| // TODO(vikas): Implement on-the-fly decoding & filter mechanism to decode |
| // and apply filter per image-row. |
| unfilter_func(decoded_data, width, height, 1, width, unfiltered_data); |
| // Construct raw_data (height x stride) from alpha data (height x width). |
| CopyPlane(unfiltered_data, width, output, stride, width, height); |
| free(unfiltered_data); |
| } else { |
| // Construct raw_data (height x stride) from alpha data (height x width). |
| CopyPlane(decoded_data, width, output, stride, width, height); |
| } |
| } |
| if (method == 1) { |
| free(decoded_data); |
| } |
| return ok; |
| } |
| |
| #if defined(__cplusplus) || defined(c_plusplus) |
| } // extern "C" |
| #endif |