diff --git a/src/enc/Makefile.am b/src/enc/Makefile.am index 1e60c2bf..9d92cf24 100644 --- a/src/enc/Makefile.am +++ b/src/enc/Makefile.am @@ -6,7 +6,7 @@ libwebpencode_la_SOURCES = analysis.c config.c cost.c cost.h filter.c \ layer.c libwebpencode_la_LDFLAGS = -version-info 2:0:0 -lm libwebpencode_la_CPPFLAGS = $(USE_EXPERIMENTAL_CODE) -libwebpencodeinclude_HEADERS = ../webp/encode.h ../webp/types.h +libwebpencodeinclude_HEADERS = ../webp/encode.h ../webp/types.h ../webp/mux.h libwebpencodeincludedir = $(includedir)/webp noinst_LTLIBRARIES = libwebpencode.la diff --git a/src/enc/alpha.c b/src/enc/alpha.c index 2ea054d7..22a54219 100644 --- a/src/enc/alpha.c +++ b/src/enc/alpha.c @@ -12,92 +12,38 @@ #include #include #include "vp8enci.h" - -#ifdef WEBP_EXPERIMENTAL_FEATURES -#include "zlib.h" -#endif +#include "../utils/alpha.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif -#ifdef WEBP_EXPERIMENTAL_FEATURES - -#define CHUNK_SIZE 8192 - //------------------------------------------------------------------------------ -static int CompressAlpha(const uint8_t* data, size_t data_size, - uint8_t** output, size_t* output_size, - int algo) { - int ret = Z_OK; - z_stream strm; - unsigned char chunk[CHUNK_SIZE]; - - *output = NULL; - *output_size = 0; - memset(&strm, 0, sizeof(strm)); - if (deflateInit(&strm, algo ? Z_BEST_SPEED : Z_BEST_COMPRESSION) != Z_OK) { - return 0; - } - strm.next_in = (unsigned char*)data; - strm.avail_in = data_size; - do { - size_t size_out; - - strm.next_out = chunk; - strm.avail_out = CHUNK_SIZE; - ret = deflate(&strm, Z_FINISH); - if (ret == Z_STREAM_ERROR) { - break; - } - size_out = CHUNK_SIZE - strm.avail_out; - if (size_out) { - size_t new_size = *output_size + size_out; - uint8_t* new_output = realloc(*output, new_size); - if (new_output == NULL) { - ret = Z_MEM_ERROR; - break; - } - memcpy(new_output + *output_size, chunk, size_out); - *output_size = new_size; - *output = new_output; - } - } while (ret != Z_STREAM_END || strm.avail_out == 0); - - deflateEnd(&strm); - if (ret != Z_STREAM_END) { - free(*output); - output_size = 0; - return 0; - } - return 1; -} - -#endif /* WEBP_EXPERIMENTAL_FEATURES */ - void VP8EncInitAlpha(VP8Encoder* enc) { enc->has_alpha_ = (enc->pic_->a != NULL); enc->alpha_data_ = NULL; enc->alpha_data_size_ = 0; } -void VP8EncCodeAlphaBlock(VP8EncIterator* it) { - (void)it; - // Nothing for now. We just ZLIB-compress in the end. -} - int VP8EncFinishAlpha(VP8Encoder* enc) { if (enc->has_alpha_) { -#ifdef WEBP_EXPERIMENTAL_FEATURES + const WebPConfig* config = enc->config_; const WebPPicture* pic = enc->pic_; + uint8_t* tmp_data = NULL; + size_t tmp_size = 0; assert(pic->a); - if (!CompressAlpha(pic->a, pic->width * pic->height, - &enc->alpha_data_, &enc->alpha_data_size_, - enc->config_->alpha_compression)) { + if (!EncodeAlpha(pic->a, pic->width, pic->height, pic->a_stride, + config->alpha_quality, config->alpha_compression, + &tmp_data, &tmp_size)) { return 0; } -#endif + if (tmp_size != (uint32_t)tmp_size) { // Sanity check. + free(tmp_data); + return 0; + } + enc->alpha_data_size_ = (uint32_t)tmp_size; + enc->alpha_data_ = tmp_data; } return 1; } diff --git a/src/enc/config.c b/src/enc/config.c index 1a74f68a..53da8faa 100644 --- a/src/enc/config.c +++ b/src/enc/config.c @@ -41,8 +41,9 @@ int WebPConfigInitInternal(WebPConfig* const config, config->show_compressed = 0; config->preprocessing = 0; config->autofilter = 0; - config->alpha_compression = 0; config->partition_limit = 0; + config->alpha_compression = 1; + config->alpha_quality = 100; // TODO(skal): tune. switch (preset) { @@ -111,6 +112,8 @@ int WebPValidateConfig(const WebPConfig* const config) { return 0; if (config->alpha_compression < 0) return 0; + if (config->alpha_quality < 0 || config->alpha_quality > 100) + return 0; return 1; } diff --git a/src/enc/frame.c b/src/enc/frame.c index eebea73e..d573995c 100644 --- a/src/enc/frame.c +++ b/src/enc/frame.c @@ -588,9 +588,6 @@ int VP8EncLoop(VP8Encoder* const enc) { ResetAfterSkip(&it); } #ifdef WEBP_EXPERIMENTAL_FEATURES - if (enc->has_alpha_) { - VP8EncCodeAlphaBlock(&it); - } if (enc->use_layer_) { VP8EncCodeLayerBlock(&it); } diff --git a/src/enc/picture.c b/src/enc/picture.c index 854d90d6..306be491 100644 --- a/src/enc/picture.c +++ b/src/enc/picture.c @@ -147,11 +147,11 @@ int WebPPictureCopy(const WebPPicture* const src, WebPPicture* const dst) { dst->u, dst->uv_stride, HALVE(dst->width), HALVE(dst->height)); CopyPlane(src->v, src->uv_stride, dst->v, dst->uv_stride, HALVE(dst->width), HALVE(dst->height)); -#ifdef WEBP_EXPERIMENTAL_FEATURES if (dst->a != NULL) { CopyPlane(src->a, src->a_stride, dst->a, dst->a_stride, dst->width, dst->height); } +#ifdef WEBP_EXPERIMENTAL_FEATURES if (dst->u0 != NULL) { int uv0_width = src->width; if ((dst->colorspace & WEBP_CSP_UV_MASK) == WEBP_YUV422) { @@ -194,12 +194,12 @@ int WebPPictureCrop(WebPPicture* const pic, tmp.v, tmp.uv_stride, HALVE(width), HALVE(height)); } -#ifdef WEBP_EXPERIMENTAL_FEATURES if (tmp.a) { const int a_offset = top * pic->a_stride + left; CopyPlane(pic->a + a_offset, pic->a_stride, tmp.a, tmp.a_stride, width, height); } +#ifdef WEBP_EXPERIMENTAL_FEATURES if (tmp.u0) { int w = width; int l = left; @@ -347,11 +347,11 @@ int WebPPictureRescale(WebPPicture* const pic, int width, int height) { tmp.v, HALVE(width), HALVE(height), tmp.uv_stride, work); -#ifdef WEBP_EXPERIMENTAL_FEATURES if (tmp.a) { RescalePlane(pic->a, prev_width, prev_height, pic->a_stride, tmp.a, width, height, tmp.a_stride, work); } +#ifdef WEBP_EXPERIMENTAL_FEATURES if (tmp.u0) { int s = 1; if ((tmp.colorspace & WEBP_CSP_UV_MASK) == WEBP_YUV422) { @@ -547,7 +547,6 @@ static int Import(WebPPicture* const picture, } if (import_alpha) { -#ifdef WEBP_EXPERIMENTAL_FEATURES const uint8_t* const a_ptr = rgb + 3; assert(step >= 4); for (y = 0; y < height; ++y) { @@ -556,7 +555,6 @@ static int Import(WebPPicture* const picture, a_ptr[step * x + y * rgb_stride]; } } -#endif } return 1; } diff --git a/src/enc/syntax.c b/src/enc/syntax.c index f1190181..2dfeb842 100644 --- a/src/enc/syntax.c +++ b/src/enc/syntax.c @@ -12,24 +12,21 @@ #include #include -#include "vp8enci.h" +#include "./vp8enci.h" +#include "../dec/webpi.h" // For chunk-size constants. +#include "../webp/mux.h" // For 'ALPHA_FLAG' constant. #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif #define KSIGNATURE 0x9d012a -#define KHEADER_SIZE 10 -#define KRIFF_SIZE 20 -#define KSIZE_OFFSET (KRIFF_SIZE - 8) - #define MAX_PARTITION0_SIZE (1 << 19) // max size of mode partition #define MAX_PARTITION_SIZE (1 << 24) // max size for token partition -//------------------------------------------------------------------------------ -// Writers for header's various pieces (in order of appearance) -// Main keyframe header +//------------------------------------------------------------------------------ +// Helper functions static void PutLE32(uint8_t* const data, uint32_t val) { data[0] = (val >> 0) & 0xff; @@ -38,46 +35,162 @@ static void PutLE32(uint8_t* const data, uint32_t val) { data[3] = (val >> 24) & 0xff; } -static int PutHeader(int profile, size_t size0, size_t total_size, - WebPPicture* const pic) { - uint8_t buf[KHEADER_SIZE]; - uint8_t RIFF[KRIFF_SIZE] = { - 'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'E', 'B', 'P', 'V', 'P', '8', ' ' +static int IsVP8XNeeded(const VP8Encoder* const enc) { + return !!enc->has_alpha_; // Currently the only case when VP8X is needed. + // This could change in the future. +} + +static int PutPaddingByte(const WebPPicture* const pic) { + + const uint8_t pad_byte[1] = { 0 }; + return !!pic->writer(pad_byte, 1, pic); +} + +//------------------------------------------------------------------------------ +// Writers for header's various pieces (in order of appearance) + +static WebPEncodingError PutRIFFHeader(const VP8Encoder* const enc, + size_t riff_size) { + const WebPPicture* const pic = enc->pic_; + uint8_t riff[RIFF_HEADER_SIZE] = { + 'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'E', 'B', 'P' }; + PutLE32(riff + TAG_SIZE, riff_size); + if (!pic->writer(riff, sizeof(riff), pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + return VP8_ENC_OK; +} + +static WebPEncodingError PutVP8XHeader(const VP8Encoder* const enc) { + const WebPPicture* const pic = enc->pic_; + uint8_t vp8x[CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE] = { + 'V', 'P', '8', 'X' + }; + uint32_t flags = 0; + + assert(IsVP8XNeeded(enc)); + + if (enc->has_alpha_) { + flags |= ALPHA_FLAG; + } + + PutLE32(vp8x + TAG_SIZE, VP8X_CHUNK_SIZE); + PutLE32(vp8x + CHUNK_HEADER_SIZE, flags); + PutLE32(vp8x + CHUNK_HEADER_SIZE + 4 , pic->width); + PutLE32(vp8x + CHUNK_HEADER_SIZE + 8 , pic->height); + if(!pic->writer(vp8x, sizeof(vp8x), pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + return VP8_ENC_OK; +} + +static WebPEncodingError PutAlphaChunk(const VP8Encoder* const enc) { + const WebPPicture* const pic = enc->pic_; + uint8_t alpha_chunk_hdr[CHUNK_HEADER_SIZE] = { + 'A', 'L', 'P', 'H' + }; + + assert(enc->has_alpha_); + + // Alpha chunk header. + PutLE32(alpha_chunk_hdr + TAG_SIZE, enc->alpha_data_size_); + if (!pic->writer(alpha_chunk_hdr, sizeof(alpha_chunk_hdr), pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + + // Alpha chunk data. + if (!pic->writer(enc->alpha_data_, enc->alpha_data_size_, pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + + // Padding. + if ((enc->alpha_data_size_ & 1) && !PutPaddingByte(pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + return VP8_ENC_OK; +} + +static WebPEncodingError PutVP8Header(const WebPPicture* const pic, + size_t vp8_size) { + uint8_t vp8_chunk_hdr[CHUNK_HEADER_SIZE] = { + 'V', 'P', '8', ' ' + }; + PutLE32(vp8_chunk_hdr + TAG_SIZE, vp8_size); + if (!pic->writer(vp8_chunk_hdr, sizeof(vp8_chunk_hdr), pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + return VP8_ENC_OK; +} + +static WebPEncodingError PutVP8FrameHeader(const WebPPicture* const pic, + int profile, size_t size0) { + uint8_t vp8_frm_hdr[VP8_FRAME_HEADER_SIZE]; uint32_t bits; if (size0 >= MAX_PARTITION0_SIZE) { // partition #0 is too big to fit - return WebPEncodingSetError(pic, VP8_ENC_ERROR_PARTITION0_OVERFLOW); - } - - if (total_size > 0xfffffffeU - KRIFF_SIZE) { - return WebPEncodingSetError(pic, VP8_ENC_ERROR_FILE_TOO_BIG); - } - - PutLE32(RIFF + 4, (uint32_t)(total_size + KSIZE_OFFSET)); - PutLE32(RIFF + 16, (uint32_t)total_size); - if (!pic->writer(RIFF, sizeof(RIFF), pic)) { - return WebPEncodingSetError(pic, VP8_ENC_ERROR_BAD_WRITE); + return VP8_ENC_ERROR_PARTITION0_OVERFLOW; } bits = 0 // keyframe (1b) | (profile << 1) // profile (3b) | (1 << 4) // visible (1b) - | ((uint32_t)size0 << 5); // partition length (19b) - buf[0] = bits & 0xff; - buf[1] = (bits >> 8) & 0xff; - buf[2] = (bits >> 16) & 0xff; + | ((uint32_t)size0 << 5); // partition length (19b) + vp8_frm_hdr[0] = bits & 0xff; + vp8_frm_hdr[1] = (bits >> 8) & 0xff; + vp8_frm_hdr[2] = (bits >> 16) & 0xff; // signature - buf[3] = (KSIGNATURE >> 16) & 0xff; - buf[4] = (KSIGNATURE >> 8) & 0xff; - buf[5] = (KSIGNATURE >> 0) & 0xff; + vp8_frm_hdr[3] = (KSIGNATURE >> 16) & 0xff; + vp8_frm_hdr[4] = (KSIGNATURE >> 8) & 0xff; + vp8_frm_hdr[5] = (KSIGNATURE >> 0) & 0xff; // dimensions - buf[6] = pic->width & 0xff; - buf[7] = pic->width >> 8; - buf[8] = pic->height & 0xff; - buf[9] = pic->height >> 8; + vp8_frm_hdr[6] = pic->width & 0xff; + vp8_frm_hdr[7] = pic->width >> 8; + vp8_frm_hdr[8] = pic->height & 0xff; + vp8_frm_hdr[9] = pic->height >> 8; - return pic->writer(buf, sizeof(buf), pic); + if (!pic->writer(vp8_frm_hdr, sizeof(vp8_frm_hdr), pic)) { + return VP8_ENC_ERROR_BAD_WRITE; + } + return VP8_ENC_OK; +} + +// WebP Headers. +static int PutWebPHeaders(const VP8Encoder* const enc, size_t size0, + size_t vp8_size, size_t riff_size) { + WebPPicture* const pic = enc->pic_; + WebPEncodingError err = VP8_ENC_OK; + + // RIFF header. + err = PutRIFFHeader(enc, riff_size); + if (err != VP8_ENC_OK) goto Error; + + // VP8X. + if (IsVP8XNeeded(enc)) { + err = PutVP8XHeader(enc); + if (err != VP8_ENC_OK) goto Error; + } + + // Alpha. + if (enc->has_alpha_) { + err = PutAlphaChunk(enc); + if (err != VP8_ENC_OK) goto Error; + } + + // VP8 header. + err = PutVP8Header(pic, vp8_size); + if (err != VP8_ENC_OK) goto Error; + + // VP8 frame header. + err = PutVP8FrameHeader(pic, enc->profile_, size0); + if (err != VP8_ENC_OK) goto Error; + + // All OK. + return 1; + + // Error. + Error: + return WebPEncodingSetError(pic, err); } // Segmentation header @@ -186,14 +299,6 @@ static int WriteExtensions(VP8Encoder* const enc) { return WebPEncodingSetError(pic, VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY); } } - // Alpha (bytes 4..6) - PutLE24(buffer + 4, enc->alpha_data_size_); - if (enc->alpha_data_size_ > 0) { - assert(enc->has_alpha_); - if (!VP8BitWriterAppend(bw, enc->alpha_data_, enc->alpha_data_size_)) { - return WebPEncodingSetError(pic, VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY); - } - } buffer[KTRAILER_SIZE - 1] = 0x01; // marker if (!VP8BitWriterAppend(bw, buffer, KTRAILER_SIZE)) { @@ -211,7 +316,7 @@ static size_t GeneratePartition0(VP8Encoder* const enc) { const int mb_size = enc->mb_w_ * enc->mb_h_; uint64_t pos1, pos2, pos3; #ifdef WEBP_EXPERIMENTAL_FEATURES - const int need_extensions = enc->has_alpha_ || enc->use_layer_; + const int need_extensions = enc->use_layer_; #endif pos1 = VP8BitWriterPos(bw); @@ -254,25 +359,43 @@ int VP8EncWrite(VP8Encoder* const enc) { WebPPicture* const pic = enc->pic_; VP8BitWriter* const bw = &enc->bw_; int ok = 0; - size_t coded_size, pad; + size_t vp8_size, pad, riff_size; int p; // Partition #0 with header and partition sizes ok = !!GeneratePartition0(enc); - // Compute total size (for the RIFF header) - coded_size = KHEADER_SIZE + VP8BitWriterSize(bw) + 3 * (enc->num_parts_ - 1); + // Compute VP8 size + vp8_size = VP8_FRAME_HEADER_SIZE + + VP8BitWriterSize(bw) + + 3 * (enc->num_parts_ - 1); for (p = 0; p < enc->num_parts_; ++p) { - coded_size += VP8BitWriterSize(enc->parts_ + p); + vp8_size += VP8BitWriterSize(enc->parts_ + p); + } + pad = vp8_size & 1; + vp8_size += pad; + + // Computer RIFF size + // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size. + riff_size = TAG_SIZE + CHUNK_HEADER_SIZE + vp8_size; + if (IsVP8XNeeded(enc)) { // Add size for: VP8X header + data. + riff_size += CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; + } + if (enc->has_alpha_) { // Add size for: ALPH header + data. + const uint32_t padded_alpha_size = enc->alpha_data_size_ + + (enc->alpha_data_size_ & 1); + riff_size += CHUNK_HEADER_SIZE + padded_alpha_size; + } + // Sanity check. + if (riff_size > 0xfffffffeU) { + return WebPEncodingSetError(pic, VP8_ENC_ERROR_FILE_TOO_BIG); } - pad = coded_size & 1; - coded_size += pad; // Emit headers and partition #0 { const uint8_t* const part0 = VP8BitWriterBuf(bw); const size_t size0 = VP8BitWriterSize(bw); - ok = ok && PutHeader(enc->profile_, size0, coded_size, pic) + ok = ok && PutWebPHeaders(enc, size0, vp8_size, riff_size) && pic->writer(part0, size0, pic) && EmitPartitionsSize(enc, pic); free((void*)part0); @@ -289,11 +412,10 @@ int VP8EncWrite(VP8Encoder* const enc) { // Padding byte if (ok && pad) { - const uint8_t pad_byte[1] = { 0 }; - ok = pic->writer(pad_byte, 1, pic); + ok = PutPaddingByte(pic); } - enc->coded_size_ = (int)coded_size + KRIFF_SIZE; + enc->coded_size_ = (int)(CHUNK_HEADER_SIZE + riff_size); return ok; } diff --git a/src/enc/vp8enci.h b/src/enc/vp8enci.h index c66697c4..282d05f2 100644 --- a/src/enc/vp8enci.h +++ b/src/enc/vp8enci.h @@ -333,7 +333,7 @@ struct VP8Encoder { // transparency blob int has_alpha_; uint8_t* alpha_data_; // non-NULL if transparency is present - size_t alpha_data_size_; + uint32_t alpha_data_size_; // enhancement layer int use_layer_; @@ -436,7 +436,6 @@ int VP8Decimate(VP8EncIterator* const it, VP8ModeScore* const rd, int rd_opt); // in alpha.c void VP8EncInitAlpha(VP8Encoder* enc); // initialize alpha compression -void VP8EncCodeAlphaBlock(VP8EncIterator* it); // analyze or code a macroblock int VP8EncFinishAlpha(VP8Encoder* enc); // finalize compressed data void VP8EncDeleteAlpha(VP8Encoder* enc); // delete compressed data diff --git a/src/enc/webpenc.c b/src/enc/webpenc.c index 00c7d61c..48243e38 100644 --- a/src/enc/webpenc.c +++ b/src/enc/webpenc.c @@ -250,8 +250,8 @@ static VP8Encoder* InitEncoder(const WebPConfig* const config, ResetFilterHeader(enc); ResetBoundaryPredictions(enc); -#ifdef WEBP_EXPERIMENTAL_FEATURES VP8EncInitAlpha(enc); +#ifdef WEBP_EXPERIMENTAL_FEATURES VP8EncInitLayer(enc); #endif @@ -260,8 +260,8 @@ static VP8Encoder* InitEncoder(const WebPConfig* const config, static void DeleteEncoder(VP8Encoder* enc) { if (enc) { -#ifdef WEBP_EXPERIMENTAL_FEATURES VP8EncDeleteAlpha(enc); +#ifdef WEBP_EXPERIMENTAL_FEATURES VP8EncDeleteLayer(enc); #endif free(enc); @@ -335,8 +335,8 @@ int WebPEncode(const WebPConfig* const config, WebPPicture* const pic) { ok = VP8EncAnalyze(enc) && VP8StatLoop(enc) && VP8EncLoop(enc) -#ifdef WEBP_EXPERIMENTAL_FEATURES && VP8EncFinishAlpha(enc) +#ifdef WEBP_EXPERIMENTAL_FEATURES && VP8EncFinishLayer(enc) #endif && VP8EncWrite(enc); diff --git a/src/webp/encode.h b/src/webp/encode.h index d8ee03ee..e70f0b28 100644 --- a/src/webp/encode.h +++ b/src/webp/encode.h @@ -71,7 +71,11 @@ typedef struct { // Default is set to 0 for easier progressive decoding. int partition_limit; // quality degradation allowed to fit the 512k limit on // prediction modes coding (0=no degradation, 100=full) - int alpha_compression; // Algorithm for optimizing the alpha plane (0 = none) + int alpha_compression; // Algorithm for encoding the alpha plane (0 = none, + // 1 = Backward reference counts encoded with + // Arithmetic encoder). Default is 1. + int alpha_quality; // Between 0 (smallest size) and 100 (lossless). + // Default is 100. } WebPConfig; // Enumerate some predefined settings for WebPConfig, depending on the type