Path: blob/master/thirdparty/libwebp/src/enc/alpha_enc.c
21733 views
// Copyright 2011 Google Inc. All Rights Reserved.1//2// Use of this source code is governed by a BSD-style license3// that can be found in the COPYING file in the root of the source4// tree. An additional intellectual property rights grant can be found5// in the file PATENTS. All contributing project authors may6// be found in the AUTHORS file in the root of the source tree.7// -----------------------------------------------------------------------------8//9// Alpha-plane compression.10//11// Author: Skal ([email protected])1213#include <assert.h>14#include <stdlib.h>15#include <string.h>1617#include "src/dsp/dsp.h"18#include "src/webp/types.h"19#include "src/enc/vp8i_enc.h"20#include "src/utils/bit_writer_utils.h"21#include "src/utils/filters_utils.h"22#include "src/utils/quant_levels_utils.h"23#include "src/utils/thread_utils.h"24#include "src/utils/utils.h"25#include "src/webp/encode.h"26#include "src/webp/format_constants.h"2728// -----------------------------------------------------------------------------29// Encodes the given alpha data via specified compression method 'method'.30// The pre-processing (quantization) is performed if 'quality' is less than 100.31// For such cases, the encoding is lossy. The valid range is [0, 100] for32// 'quality' and [0, 1] for 'method':33// 'method = 0' - No compression;34// 'method = 1' - Use lossless coder on the alpha plane only35// 'filter' values [0, 4] correspond to prediction modes none, horizontal,36// vertical & gradient filters. The prediction mode 4 will try all the37// prediction modes 0 to 3 and pick the best one.38// 'effort_level': specifies how much effort must be spent to try and reduce39// the compressed output size. In range 0 (quick) to 6 (slow).40//41// 'output' corresponds to the buffer containing compressed alpha data.42// This buffer is allocated by this method and caller should call43// WebPSafeFree(*output) when done.44// 'output_size' corresponds to size of this compressed alpha buffer.45//46// Returns 1 on successfully encoding the alpha and47// 0 if either:48// invalid quality or method, or49// memory allocation for the compressed data fails.5051#include "src/enc/vp8li_enc.h"5253static int EncodeLossless(const uint8_t* const data, int width, int height,54int effort_level, // in [0..6] range55int use_quality_100, VP8LBitWriter* const bw,56WebPAuxStats* const stats) {57int ok = 0;58WebPConfig config;59WebPPicture picture;6061if (!WebPPictureInit(&picture)) return 0;62picture.width = width;63picture.height = height;64picture.use_argb = 1;65picture.stats = stats;66if (!WebPPictureAlloc(&picture)) return 0;6768// Transfer the alpha values to the green channel.69WebPDispatchAlphaToGreen(data, width, picture.width, picture.height,70picture.argb, picture.argb_stride);7172if (!WebPConfigInit(&config)) return 0;73config.lossless = 1;74// Enable exact, or it would alter RGB values of transparent alpha, which is75// normally OK but not here since we are not encoding the input image but an76// internal encoding-related image containing necessary exact information in77// RGB channels.78config.exact = 1;79config.method = effort_level; // impact is very small80// Set a low default quality for encoding alpha. Ensure that Alpha quality at81// lower methods (3 and below) is less than the threshold for triggering82// costly 'BackwardReferencesTraceBackwards'.83// If the alpha quality is set to 100 and the method to 6, allow for a high84// lossless quality to trigger the cruncher.85config.quality =86(use_quality_100 && effort_level == 6) ? 100 : 8.f * effort_level;87assert(config.quality >= 0 && config.quality <= 100.f);8889ok = VP8LEncodeStream(&config, &picture, bw);90WebPPictureFree(&picture);91ok = ok && !bw->error;92if (!ok) {93VP8LBitWriterWipeOut(bw);94return 0;95}96return 1;97}9899// -----------------------------------------------------------------------------100101// Small struct to hold the result of a filter mode compression attempt.102typedef struct {103size_t score;104VP8BitWriter bw;105WebPAuxStats stats;106} FilterTrial;107108// This function always returns an initialized 'bw' object, even upon error.109static int EncodeAlphaInternal(const uint8_t* const data, int width, int height,110int method, int filter, int reduce_levels,111int effort_level, // in [0..6] range112uint8_t* const tmp_alpha,113FilterTrial* result) {114int ok = 0;115const uint8_t* alpha_src;116WebPFilterFunc filter_func;117uint8_t header;118const size_t data_size = width * height;119const uint8_t* output = NULL;120size_t output_size = 0;121VP8LBitWriter tmp_bw;122123assert((uint64_t)data_size == (uint64_t)width * height); // as per spec124assert(filter >= 0 && filter < WEBP_FILTER_LAST);125assert(method >= ALPHA_NO_COMPRESSION);126assert(method <= ALPHA_LOSSLESS_COMPRESSION);127assert(sizeof(header) == ALPHA_HEADER_LEN);128129filter_func = WebPFilters[filter];130if (filter_func != NULL) {131filter_func(data, width, height, width, tmp_alpha);132alpha_src = tmp_alpha;133} else {134alpha_src = data;135}136137if (method != ALPHA_NO_COMPRESSION) {138ok = VP8LBitWriterInit(&tmp_bw, data_size >> 3);139ok = ok && EncodeLossless(alpha_src, width, height, effort_level,140!reduce_levels, &tmp_bw, &result->stats);141if (ok) {142output = VP8LBitWriterFinish(&tmp_bw);143if (tmp_bw.error) {144VP8LBitWriterWipeOut(&tmp_bw);145memset(&result->bw, 0, sizeof(result->bw));146return 0;147}148output_size = VP8LBitWriterNumBytes(&tmp_bw);149if (output_size > data_size) {150// compressed size is larger than source! Revert to uncompressed mode.151method = ALPHA_NO_COMPRESSION;152VP8LBitWriterWipeOut(&tmp_bw);153}154} else {155VP8LBitWriterWipeOut(&tmp_bw);156memset(&result->bw, 0, sizeof(result->bw));157return 0;158}159}160161if (method == ALPHA_NO_COMPRESSION) {162output = alpha_src;163output_size = data_size;164ok = 1;165}166167// Emit final result.168header = method | (filter << 2);169if (reduce_levels) header |= ALPHA_PREPROCESSED_LEVELS << 4;170171if (!VP8BitWriterInit(&result->bw, ALPHA_HEADER_LEN + output_size)) ok = 0;172ok = ok && VP8BitWriterAppend(&result->bw, &header, ALPHA_HEADER_LEN);173ok = ok && VP8BitWriterAppend(&result->bw, output, output_size);174175if (method != ALPHA_NO_COMPRESSION) {176VP8LBitWriterWipeOut(&tmp_bw);177}178ok = ok && !result->bw.error;179result->score = VP8BitWriterSize(&result->bw);180return ok;181}182183// -----------------------------------------------------------------------------184185static int GetNumColors(const uint8_t* data, int width, int height,186int stride) {187int j;188int colors = 0;189uint8_t color[256] = { 0 };190191for (j = 0; j < height; ++j) {192int i;193const uint8_t* const p = data + j * stride;194for (i = 0; i < width; ++i) {195color[p[i]] = 1;196}197}198for (j = 0; j < 256; ++j) {199if (color[j] > 0) ++colors;200}201return colors;202}203204#define FILTER_TRY_NONE (1 << WEBP_FILTER_NONE)205#define FILTER_TRY_ALL ((1 << WEBP_FILTER_LAST) - 1)206207// Given the input 'filter' option, return an OR'd bit-set of filters to try.208static uint32_t GetFilterMap(const uint8_t* alpha, int width, int height,209int filter, int effort_level) {210uint32_t bit_map = 0U;211if (filter == WEBP_FILTER_FAST) {212// Quick estimate of the best candidate.213int try_filter_none = (effort_level > 3);214const int kMinColorsForFilterNone = 16;215const int kMaxColorsForFilterNone = 192;216const int num_colors = GetNumColors(alpha, width, height, width);217// For low number of colors, NONE yields better compression.218filter = (num_colors <= kMinColorsForFilterNone)219? WEBP_FILTER_NONE220: WebPEstimateBestFilter(alpha, width, height, width);221bit_map |= 1 << filter;222// For large number of colors, try FILTER_NONE in addition to the best223// filter as well.224if (try_filter_none || num_colors > kMaxColorsForFilterNone) {225bit_map |= FILTER_TRY_NONE;226}227} else if (filter == WEBP_FILTER_NONE) {228bit_map = FILTER_TRY_NONE;229} else { // WEBP_FILTER_BEST -> try all230bit_map = FILTER_TRY_ALL;231}232return bit_map;233}234235static void InitFilterTrial(FilterTrial* const score) {236score->score = (size_t)~0U;237VP8BitWriterInit(&score->bw, 0);238}239240static int ApplyFiltersAndEncode(const uint8_t* alpha, int width, int height,241size_t data_size, int method, int filter,242int reduce_levels, int effort_level,243uint8_t** const output,244size_t* const output_size,245WebPAuxStats* const stats) {246int ok = 1;247FilterTrial best;248uint32_t try_map =249GetFilterMap(alpha, width, height, filter, effort_level);250InitFilterTrial(&best);251252if (try_map != FILTER_TRY_NONE) {253uint8_t* filtered_alpha = (uint8_t*)WebPSafeMalloc(1ULL, data_size);254if (filtered_alpha == NULL) return 0;255256for (filter = WEBP_FILTER_NONE; ok && try_map; ++filter, try_map >>= 1) {257if (try_map & 1) {258FilterTrial trial;259ok = EncodeAlphaInternal(alpha, width, height, method, filter,260reduce_levels, effort_level, filtered_alpha,261&trial);262if (ok && trial.score < best.score) {263VP8BitWriterWipeOut(&best.bw);264best = trial;265} else {266VP8BitWriterWipeOut(&trial.bw);267}268}269}270WebPSafeFree(filtered_alpha);271} else {272ok = EncodeAlphaInternal(alpha, width, height, method, WEBP_FILTER_NONE,273reduce_levels, effort_level, NULL, &best);274}275if (ok) {276#if !defined(WEBP_DISABLE_STATS)277if (stats != NULL) {278stats->lossless_features = best.stats.lossless_features;279stats->histogram_bits = best.stats.histogram_bits;280stats->transform_bits = best.stats.transform_bits;281stats->cross_color_transform_bits = best.stats.cross_color_transform_bits;282stats->cache_bits = best.stats.cache_bits;283stats->palette_size = best.stats.palette_size;284stats->lossless_size = best.stats.lossless_size;285stats->lossless_hdr_size = best.stats.lossless_hdr_size;286stats->lossless_data_size = best.stats.lossless_data_size;287}288#else289(void)stats;290#endif291*output_size = VP8BitWriterSize(&best.bw);292*output = VP8BitWriterBuf(&best.bw);293} else {294VP8BitWriterWipeOut(&best.bw);295}296return ok;297}298299static int EncodeAlpha(VP8Encoder* const enc,300int quality, int method, int filter,301int effort_level,302uint8_t** const output, size_t* const output_size) {303const WebPPicture* const pic = enc->pic;304const int width = pic->width;305const int height = pic->height;306307uint8_t* quant_alpha = NULL;308const size_t data_size = width * height;309uint64_t sse = 0;310int ok = 1;311const int reduce_levels = (quality < 100);312313// quick correctness checks314assert((uint64_t)data_size == (uint64_t)width * height); // as per spec315assert(enc != NULL && pic != NULL && pic->a != NULL);316assert(output != NULL && output_size != NULL);317assert(width > 0 && height > 0);318assert(pic->a_stride >= width);319assert(filter >= WEBP_FILTER_NONE && filter <= WEBP_FILTER_FAST);320321if (quality < 0 || quality > 100) {322return WebPEncodingSetError(pic, VP8_ENC_ERROR_INVALID_CONFIGURATION);323}324325if (method < ALPHA_NO_COMPRESSION || method > ALPHA_LOSSLESS_COMPRESSION) {326return WebPEncodingSetError(pic, VP8_ENC_ERROR_INVALID_CONFIGURATION);327}328329if (method == ALPHA_NO_COMPRESSION) {330// Don't filter, as filtering will make no impact on compressed size.331filter = WEBP_FILTER_NONE;332}333334quant_alpha = (uint8_t*)WebPSafeMalloc(1ULL, data_size);335if (quant_alpha == NULL) {336return WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY);337}338339// Extract alpha data (width x height) from raw_data (stride x height).340WebPCopyPlane(pic->a, pic->a_stride, quant_alpha, width, width, height);341342if (reduce_levels) { // No Quantization required for 'quality = 100'.343// 16 alpha levels gives quite a low MSE w.r.t original alpha plane hence344// mapped to moderate quality 70. Hence Quality:[0, 70] -> Levels:[2, 16]345// and Quality:]70, 100] -> Levels:]16, 256].346const int alpha_levels = (quality <= 70) ? (2 + quality / 5)347: (16 + (quality - 70) * 8);348ok = QuantizeLevels(quant_alpha, width, height, alpha_levels, &sse);349}350351if (ok) {352VP8FiltersInit();353ok = ApplyFiltersAndEncode(quant_alpha, width, height, data_size, method,354filter, reduce_levels, effort_level, output,355output_size, pic->stats);356if (!ok) {357WebPEncodingSetError(pic, VP8_ENC_ERROR_OUT_OF_MEMORY); // imprecise358}359#if !defined(WEBP_DISABLE_STATS)360if (pic->stats != NULL) { // need stats?361pic->stats->coded_size += (int)(*output_size);362enc->sse[3] = sse;363}364#endif365}366367WebPSafeFree(quant_alpha);368return ok;369}370371//------------------------------------------------------------------------------372// Main calls373374static int CompressAlphaJob(void* arg1, void* unused) {375VP8Encoder* const enc = (VP8Encoder*)arg1;376const WebPConfig* config = enc->config;377uint8_t* alpha_data = NULL;378size_t alpha_size = 0;379const int effort_level = config->method; // maps to [0..6]380const WEBP_FILTER_TYPE filter =381(config->alpha_filtering == 0) ? WEBP_FILTER_NONE :382(config->alpha_filtering == 1) ? WEBP_FILTER_FAST :383WEBP_FILTER_BEST;384if (!EncodeAlpha(enc, config->alpha_quality, config->alpha_compression,385filter, effort_level, &alpha_data, &alpha_size)) {386return 0;387}388if (alpha_size != (uint32_t)alpha_size) { // Soundness check.389WebPSafeFree(alpha_data);390return 0;391}392enc->alpha_data_size = (uint32_t)alpha_size;393enc->alpha_data = alpha_data;394(void)unused;395return 1;396}397398void VP8EncInitAlpha(VP8Encoder* const enc) {399WebPInitAlphaProcessing();400enc->has_alpha = WebPPictureHasTransparency(enc->pic);401enc->alpha_data = NULL;402enc->alpha_data_size = 0;403if (enc->thread_level > 0) {404WebPWorker* const worker = &enc->alpha_worker;405WebPGetWorkerInterface()->Init(worker);406worker->data1 = enc;407worker->data2 = NULL;408worker->hook = CompressAlphaJob;409}410}411412int VP8EncStartAlpha(VP8Encoder* const enc) {413if (enc->has_alpha) {414if (enc->thread_level > 0) {415WebPWorker* const worker = &enc->alpha_worker;416// Makes sure worker is good to go.417if (!WebPGetWorkerInterface()->Reset(worker)) {418return WebPEncodingSetError(enc->pic, VP8_ENC_ERROR_OUT_OF_MEMORY);419}420WebPGetWorkerInterface()->Launch(worker);421return 1;422} else {423return CompressAlphaJob(enc, NULL); // just do the job right away424}425}426return 1;427}428429int VP8EncFinishAlpha(VP8Encoder* const enc) {430if (enc->has_alpha) {431if (enc->thread_level > 0) {432WebPWorker* const worker = &enc->alpha_worker;433if (!WebPGetWorkerInterface()->Sync(worker)) return 0; // error434}435}436return WebPReportProgress(enc->pic, enc->percent + 20, &enc->percent);437}438439int VP8EncDeleteAlpha(VP8Encoder* const enc) {440int ok = 1;441if (enc->thread_level > 0) {442WebPWorker* const worker = &enc->alpha_worker;443// finish anything left in flight444ok = WebPGetWorkerInterface()->Sync(worker);445// still need to end the worker, even if !ok446WebPGetWorkerInterface()->End(worker);447}448WebPSafeFree(enc->alpha_data);449enc->alpha_data = NULL;450enc->alpha_data_size = 0;451enc->has_alpha = 0;452return ok;453}454455456