diff --git a/README b/README index 3b7a6764..61c0e903 100644 --- a/README +++ b/README @@ -112,6 +112,8 @@ options: -f ............... filter strength (0=off..100) -sharpness ....... filter sharpness (0:most .. 7:least sharp) -strong ................ use strong filter instead of simple. + -alpha_comp ...... set the transparency-compression + -noalpha ............... discard any transparency information. -pass ............ analysis pass number (1..10) -partitions ...... number of partitions to use (0..3) -crop .. crop picture with the given rectangle @@ -120,6 +122,8 @@ options: -short ................. condense printed message -quiet ................. don't print anything. + -version ............... print version number and exit. + -noasm ................. disable all assembly optimizations. -v ..................... verbose, e.g. print encoding/decoding times Experimental Options: diff --git a/configure.ac b/configure.ac index 62657e77..45b3a06e 100644 --- a/configure.ac +++ b/configure.ac @@ -8,6 +8,10 @@ AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=PATH], [pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig']) AC_SUBST([pkgconfigdir]) +dnl === Check libz is present + +AC_CHECK_LIB(z, gzsetparams, [AC_CHECK_HEADER(zlib.h,,)], [AC_MSG_ERROR(zlib library not found)]) + dnl === check for PNG support === PNG_INCLUDES="" diff --git a/examples/Makefile.am b/examples/Makefile.am index 7c5ec30a..0350b452 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -4,8 +4,8 @@ bin_PROGRAMS = dwebp cwebp dwebp_SOURCES = dwebp.c stopwatch.h dwebp_CPPFLAGS = $(AM_CPPFLAGS) $(PNG_INCLUDES) $(JPEG_INCLUDES) -dwebp_LDADD = ../src/libwebp.la $(PNG_LIBS) $(JPEG_LIBS) +dwebp_LDADD = ../src/libwebp.la $(PNG_LIBS) $(JPEG_LIBS) -lz cwebp_SOURCES = cwebp.c stopwatch.h cwebp_CPPFLAGS = $(AM_CPPFLAGS) $(PNG_INCLUDES) $(JPEG_INCLUDES) -cwebp_LDADD = ../src/libwebp.la $(PNG_LIBS) $(JPEG_LIBS) +cwebp_LDADD = ../src/libwebp.la $(PNG_LIBS) $(JPEG_LIBS) -lz diff --git a/examples/cwebp.c b/examples/cwebp.c index bd963159..38cccdf3 100644 --- a/examples/cwebp.c +++ b/examples/cwebp.c @@ -162,7 +162,8 @@ static HRESULT ReadPictureWithWIC(const char* filename, return hr; } -static int ReadPicture(const char* const filename, WebPPicture* const pic) { +static int ReadPicture(const char* const filename, WebPPicture* const pic, + int keep_alpha) { int ok; if (pic->width != 0 && pic->height != 0) { // If image size is specified, infer it as YUV format. @@ -282,10 +283,11 @@ static void PNGAPI error_function(png_structp png, png_const_charp dummy) { longjmp(png_jmpbuf(png), 1); } -static int ReadPNG(FILE* in_file, WebPPicture* const pic) { +static int ReadPNG(FILE* in_file, WebPPicture* const pic, int keep_alpha) { png_structp png; png_infop info; int color_type, bit_depth, interlaced; + int has_alpha; int num_passes; int p; int ok = 0; @@ -327,12 +329,16 @@ static int ReadPNG(FILE* in_file, WebPPicture* const pic) { if (png_get_valid(png, info, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png); } + has_alpha = !!(color_type & PNG_COLOR_MASK_ALPHA); + + if (!keep_alpha) { + png_set_strip_alpha(png); + has_alpha = 0; + } - // TODO(skal): Strip Alpha for now (till Alpha is supported). - png_set_strip_alpha(png); num_passes = png_set_interlace_handling(png); png_read_update_info(png, info); - stride = 3 * width * sizeof(*rgb); + stride = (has_alpha ? 4 : 3) * width * sizeof(*rgb); rgb = (uint8_t*)malloc(stride * height); if (rgb == NULL) goto Error; for (p = 0; p < num_passes; ++p) { @@ -346,14 +352,15 @@ static int ReadPNG(FILE* in_file, WebPPicture* const pic) { pic->width = width; pic->height = height; - ok = WebPPictureImportRGB(pic, rgb, stride); + ok = has_alpha ? WebPPictureImportRGBA(pic, rgb, stride) + : WebPPictureImportRGB(pic, rgb, stride); free(rgb); End: return ok; } #else -static int ReadPNG(FILE* in_file, WebPPicture* const pic) { +static int ReadPNG(FILE* in_file, WebPPicture* const pic, int keep_alpha) { printf("PNG support not compiled. Please install the libpng development " "package before building.\n"); return 0; @@ -385,7 +392,8 @@ static InputFileFormat GetImageType(FILE* in_file) { return format; } -static int ReadPicture(const char* const filename, WebPPicture* const pic) { +static int ReadPicture(const char* const filename, WebPPicture* const pic, + int keep_alpha) { int ok = 0; FILE* in_file = fopen(filename, "rb"); if (in_file == NULL) { @@ -397,7 +405,7 @@ static int ReadPicture(const char* const filename, WebPPicture* const pic) { // If no size specified, try to decode it as PNG/JPEG (as appropriate). const InputFileFormat format = GetImageType(in_file); if (format == PNG) { - ok = ReadPNG(in_file, pic); + ok = ReadPNG(in_file, pic, keep_alpha); } else if (format == JPEG) { ok = ReadJPEG(in_file, pic); } @@ -475,6 +483,10 @@ static void PrintExtraInfo(const WebPPicture* const pic, int short_output) { 100.f * stats->header_bytes[0] / stats->coded_size, stats->header_bytes[1], 100.f * stats->header_bytes[1] / stats->coded_size); + if (stats->alpha_data_size) { + fprintf(stderr, " transparency: %6d\n", + stats->alpha_data_size); + } fprintf(stderr, " Residuals bytes " "|segment 1|segment 2|segment 3" "|segment 4| total\n"); @@ -590,6 +602,8 @@ static void HelpLong(void) { printf(" -sharpness ....... " "filter sharpness (0:most .. 7:least sharp)\n"); printf(" -strong ................ use strong filter instead of simple.\n"); + printf(" -alpha_comp ...... set the transparency-compression\n"); + printf(" -noalpha ............... discard any transparency information.\n"); printf(" -pass ............ analysis pass number (1..10)\n"); printf(" -crop .. crop picture with the given rectangle\n"); printf(" -map ............. print map of extra info.\n"); @@ -618,12 +632,15 @@ int main(int argc, const char *argv[]) { int c; int short_output = 0; int quiet = 0; + int keep_alpha = 0; int crop = 0, crop_x = 0, crop_y = 0, crop_w = 0, crop_h = 0; WebPPicture picture; WebPConfig config; WebPAuxStats stats; Stopwatch stop_watch; - +#ifdef WEBP_EXPERIMENTAL_FEATURES + keep_alpha = 1; +#endif if (!WebPPictureInit(&picture) || !WebPConfigInit(&config)) { fprintf(stderr, "Error! Version mismatch!\n"); goto Error; @@ -675,6 +692,10 @@ int main(int argc, const char *argv[]) { config.preprocessing = strtol(argv[++c], NULL, 0); } else if (!strcmp(argv[c], "-segments") && c < argc - 1) { config.segments = strtol(argv[++c], NULL, 0); + } else if (!strcmp(argv[c], "-alpha_comp") && c < argc - 1) { + config.alpha_compression = strtol(argv[++c], NULL, 0); + } else if (!strcmp(argv[c], "-noalpha")) { + keep_alpha = 0; } else if (!strcmp(argv[c], "-map") && c < argc - 1) { picture.extra_info_type = strtol(argv[++c], NULL, 0); } else if (!strcmp(argv[c], "-crop") && c < argc - 4) { @@ -734,7 +755,7 @@ int main(int argc, const char *argv[]) { // Read the input if (verbose) StopwatchReadAndReset(&stop_watch); - if (!ReadPicture(in_file, &picture)) { + if (!ReadPicture(in_file, &picture, keep_alpha)) { fprintf(stderr, "Error! Cannot read input picture\n"); goto Error; } diff --git a/examples/dwebp.c b/examples/dwebp.c index 45673862..29c5d202 100644 --- a/examples/dwebp.c +++ b/examples/dwebp.c @@ -109,7 +109,8 @@ static HRESULT WriteUsingWIC(const char* out_file_name, REFGUID container_guid, } static int WritePNG(const char* out_file_name, unsigned char* rgb, int stride, - uint32_t width, uint32_t height) { + uint32_t width, uint32_t height, int has_alpha) { + assert(!has_alpha); // TODO(mikolaj) return SUCCEEDED(WriteUsingWIC(out_file_name, MAKE_REFGUID(GUID_ContainerFormatPng), rgb, stride, width, height)); @@ -122,7 +123,7 @@ static void PNGAPI error_function(png_structp png, png_const_charp dummy) { } static int WritePNG(FILE* out_file, unsigned char* rgb, int stride, - png_uint_32 width, png_uint_32 height) { + png_uint_32 width, png_uint_32 height, int has_alpha) { png_structp png; png_infop info; png_uint_32 y; @@ -142,7 +143,8 @@ static int WritePNG(FILE* out_file, unsigned char* rgb, int stride, return 0; } png_init_io(png, out_file); - png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB, + png_set_IHDR(png, info, width, height, 8, + has_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png, info); @@ -159,7 +161,7 @@ static int WritePNG(FILE* out_file, unsigned char* rgb, int stride, typedef uint32_t png_uint_32; static int WritePNG(FILE* out_file, unsigned char* rgb, int stride, - png_uint_32 width, png_uint_32 height) { + png_uint_32 width, png_uint_32 height, int has_alpha) { printf("PNG support not compiled. Please install the libpng development " "package before building.\n"); printf("You can run with -ppm flag to decode in PPM format.\n"); @@ -167,12 +169,28 @@ static int WritePNG(FILE* out_file, unsigned char* rgb, int stride, } #endif -static int WritePPM(FILE* fout, unsigned char* rgb, +static int WritePPM(FILE* fout, const unsigned char* rgb, uint32_t width, uint32_t height) { fprintf(fout, "P6\n%d %d\n255\n", width, height); return (fwrite(rgb, width * height, 3, fout) == 3); } +static int WriteAlphaPlane(FILE* fout, const unsigned char* rgba, + uint32_t width, uint32_t height) { + uint32_t y; + fprintf(fout, "P5\n%d %d\n255\n", width, height); + for (y = 0; y < height; ++y) { + const unsigned char* line = rgba + y * (width * 4); + uint32_t x; + for (x = 0; x < width; ++x) { + if (fputc(line[4 * x + 3], fout) == EOF) { + return 0; + } + } + } + return 1; +} + static int WritePGM(FILE* fout, unsigned char* y_plane, unsigned char *u, unsigned char* v, int y_stride, int uv_stride, @@ -202,6 +220,7 @@ typedef enum { PNG = 0, PPM, PGM, + ALPHA_PLANE_ONLY // this is for experimenting only } OutputFileFormat; static void Help(void) { @@ -222,6 +241,7 @@ int main(int argc, const char *argv[]) { const char *out_file = NULL; int width, height, stride, uv_stride; + int has_alpha = 0; uint8_t* out = NULL, *u = NULL, *v = NULL; OutputFileFormat format = PNG; Stopwatch stop_watch; @@ -232,6 +252,8 @@ int main(int argc, const char *argv[]) { return 0; } else if (!strcmp(argv[c], "-o") && c < argc - 1) { out_file = argv[++c]; + } else if (!strcmp(argv[c], "-alpha")) { + format = ALPHA_PLANE_ONLY; } else if (!strcmp(argv[c], "-ppm")) { format = PPM; } else if (!strcmp(argv[c], "-version")) { @@ -284,8 +306,12 @@ int main(int argc, const char *argv[]) { case PNG: #ifdef _WIN32 out = WebPDecodeBGR((const uint8_t*)data, data_size, &width, &height); + stride = 3 * width; + has_alpha = 0; #else - out = WebPDecodeRGB((const uint8_t*)data, data_size, &width, &height); + out = WebPDecodeRGBA((const uint8_t*)data, data_size, &width, &height); + stride = 4 * width; + has_alpha = 1; #endif break; case PPM: @@ -295,6 +321,9 @@ int main(int argc, const char *argv[]) { out = WebPDecodeYUV((const uint8_t*)data, data_size, &width, &height, &u, &v, &stride, &uv_stride); break; + case ALPHA_PLANE_ONLY: + out = WebPDecodeRGBA((const uint8_t*)data, data_size, &width, &height); + break; default: free(data); return -1; @@ -331,14 +360,16 @@ int main(int argc, const char *argv[]) { int ok = 1; if (format == PNG) { #ifdef _WIN32 - ok &= WritePNG(out_file, out, 3 * width, width, height); + ok &= WritePNG(out_file, out, stride, width, height, has_alpha); #else - ok &= WritePNG(fout, out, 3 * width, width, height); + ok &= WritePNG(fout, out, stride, width, height, has_alpha); #endif } else if (format == PPM) { ok &= WritePPM(fout, out, width, height); } else if (format == PGM) { ok &= WritePGM(fout, out, u, v, stride, uv_stride, width, height); + } else if (format == ALPHA_PLANE_ONLY) { + ok &= WriteAlphaPlane(fout, out, width, height); } if (fout) fclose(fout); diff --git a/makefile.unix b/makefile.unix index 8d7799cb..3f50b076 100644 --- a/makefile.unix +++ b/makefile.unix @@ -13,7 +13,7 @@ # These flag assume you have libpng and libjpeg installed. If not, either # follow below install instructions or just comment out the next lines. EXTRA_FLAGS= -DWEBP_HAVE_PNG -DWEBP_HAVE_JPEG -EXTRA_LIBS= -lpng -ljpeg +EXTRA_LIBS= -lpng -ljpeg -lz ifeq ("$(HOSTTYPE)", "intel-mac") EXTRA_FLAGS += -I/opt/local/include EXTRA_LIBS += -L/opt/local/lib @@ -46,13 +46,13 @@ CFLAGS = -O3 -DNDEBUG $(EXTRA_FLAGS) LDFLAGS = src/libwebp.a $(EXTRA_LIBS) -lm OBJS = src/enc/webpenc.o src/enc/bit_writer.o src/enc/syntax.o \ - src/enc/dsp.o src/enc/dsp_sse2.o \ + src/enc/dsp.o src/enc/dsp_sse2.o src/enc/alpha.o \ src/enc/tree.o src/enc/config.o src/enc/frame.o \ src/enc/quant.o src/enc/iterator.o src/enc/analysis.o \ src/enc/cost.o src/enc/picture.o src/enc/filter.o \ src/dec/bits.o src/dec/dsp.o src/dec/frame.o src/dec/webp.o \ src/dec/quant.o src/dec/tree.o src/dec/vp8.o src/dec/yuv.o \ - src/dec/idec.o + src/dec/idec.o src/dec/alpha.o HDRS = src/webp/encode.h src/enc/vp8enci.h src/enc/bit_writer.h \ src/enc/cost.h src/dec/bits.h src/dec/vp8i.h src/dec/yuv.h OUTPUT = examples/cwebp examples/dwebp src/libwebp.a diff --git a/src/Makefile.am b/src/Makefile.am index d408e318..4bbe7189 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,7 +6,7 @@ lib_LTLIBRARIES = libwebp.la libwebp_la_SOURCES = libwebp_la_LIBADD = dec/libwebpdecode.la \ enc/libwebpencode.la -libwebp_la_LDFLAGS = -version-info 0:0:0 +libwebp_la_LDFLAGS = -version-info 0:0:0 -lz libwebpinclude_HEADERS = webp/types.h webp/decode.h webp/decode_vp8.h \ webp/encode.h libwebpincludedir = $(includedir)/webp diff --git a/src/dec/Makefile.am b/src/dec/Makefile.am index 835141a3..fc36aafe 100644 --- a/src/dec/Makefile.am +++ b/src/dec/Makefile.am @@ -1,7 +1,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src libwebpdecode_la_SOURCES = bits.h vp8i.h yuv.h bits.c dsp.c frame.c \ - quant.c tree.c vp8.c webp.c yuv.c idec.c + quant.c tree.c vp8.c webp.c yuv.c idec.c alpha.c libwebpdecode_la_LDFLAGS = -version-info 0:0:0 libwebpdecodeinclude_HEADERS = ../webp/decode.h ../webp/decode_vp8.h ../webp/types.h libwebpdecodeincludedir = $(includedir)/webp diff --git a/src/dec/alpha.c b/src/dec/alpha.c new file mode 100644 index 00000000..585695e2 --- /dev/null +++ b/src/dec/alpha.c @@ -0,0 +1,69 @@ +// Copyright 2011 Google Inc. +// +// 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 decompression. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "vp8i.h" + +#ifdef WEBP_EXPERIMENTAL_FEATURES + +#include "zlib.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +//----------------------------------------------------------------------------- + +const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + int row, int num_rows) { + uint8_t* output = dec->alpha_plane_; + const int stride = dec->pic_hdr_.width_; + if (row < 0 || row + num_rows > dec->pic_hdr_.height_) { + return NULL; // sanity check + } + if (row == 0) { + // TODO(skal): for now, we just decompress everything during the first call. + // Later, we'll decode progressively, but we need to store the + // z_stream state. + const uint8_t* data = dec->alpha_data_; + size_t data_size = dec->alpha_data_size_; + const size_t output_size = stride * dec->pic_hdr_.height_; + int ret = Z_OK; + z_stream strm; + + memset(&strm, 0, sizeof(strm)); + if (inflateInit(&strm) != Z_OK) { + return 0; + } + strm.avail_in = data_size; + strm.next_in = (unsigned char*)data; + do { + strm.avail_out = output_size; + strm.next_out = output; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) { + break; + } + } while (strm.avail_out == 0); + + inflateEnd(&strm); + if (ret != Z_STREAM_END) { + return NULL; // error + } + } + return output + row * stride; +} + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif + +#endif // WEBP_EXPERIMENTAL_FEATURES diff --git a/src/dec/frame.c b/src/dec/frame.c index 44c63579..e61b0452 100644 --- a/src/dec/frame.c +++ b/src/dec/frame.c @@ -33,10 +33,12 @@ int VP8InitFrame(VP8Decoder* const dec, VP8Io* io) { const int coeffs_size = 384 * sizeof(*dec->coeffs_); const int cache_height = (16 + kFilterExtraRows[dec->filter_type_]) * 3 / 2; const int cache_size = top_size * cache_height; + const int alpha_size = + dec->alpha_data_ ? (dec->pic_hdr_.width_ * dec->pic_hdr_.height_) : 0; const int needed = intra_pred_mode_size + top_size + info_size + yuv_size + coeffs_size - + cache_size + ALIGN_MASK; + + cache_size + alpha_size + ALIGN_MASK; uint8_t* mem; if (needed > dec->mem_size_) { @@ -84,6 +86,10 @@ int VP8InitFrame(VP8Decoder* const dec, VP8Io* io) { } mem += cache_size; + // alpha plane + dec->alpha_plane_ = alpha_size ? (uint8_t*)mem : NULL; + mem += alpha_size; + // note: left-info is initialized once for all. memset(dec->mb_info_ - 1, 0, (mb_w + 1) * sizeof(*dec->mb_info_)); @@ -100,6 +106,7 @@ int VP8InitFrame(VP8Decoder* const dec, VP8Io* io) { io->y_stride = dec->cache_y_stride_; io->uv_stride = dec->cache_uv_stride_; io->fancy_upscaling = 0; // default + io->a = NULL; // Init critical function pointers and look-up tables. VP8DspInitTables(); @@ -250,6 +257,16 @@ int VP8FinishRow(VP8Decoder* const dec, VP8Io* io) { } io->mb_y = y_start; io->mb_h = y_end - y_start; + io->a = NULL; +#ifdef WEBP_EXPERIMENTAL_FEATURES + if (dec->alpha_data_) { + io->a = VP8DecompressAlphaRows(dec, y_start, y_end - y_start); + if (io->a == NULL) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "Could not decode alpha data."); + } + } +#endif if (!io->put(io)) { return 0; } diff --git a/src/dec/vp8.c b/src/dec/vp8.c index 43a0c354..ed706126 100644 --- a/src/dec/vp8.c +++ b/src/dec/vp8.c @@ -305,6 +305,10 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, "bad partition length"); } + + dec->alpha_data_ = NULL; + dec->alpha_data_size_ = 0; + br = &dec->br_; VP8InitBitReader(br, buf, buf + frm_hdr->partition_length_); buf += frm_hdr->partition_length_; @@ -323,6 +327,7 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, "cannot parse filter header"); } + status = ParsePartitions(dec, buf, buf_size); if (status != VP8_STATUS_OK) { return VP8SetError(dec, status, "cannot parse partitions"); @@ -368,6 +373,32 @@ int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { VP8ParseProba(br, dec); +#ifdef WEBP_EXPERIMENTAL_FEATURES + // Extensions + if (dec->pic_hdr_.colorspace_) { + const uint32_t EXT_SIZE = 4; + uint32_t ext_size; + uint8_t ext_bits; + const uint8_t* ext_bytes_end = buf - EXT_SIZE; + if (frm_hdr->partition_length_ <= EXT_SIZE) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "RIFF: Inconsistent extra information."); + } + ext_size = (ext_bytes_end[0] << 16) | (ext_bytes_end[1] << 8) + | (ext_bytes_end[2]); + ext_bits = ext_bytes_end[3]; + ext_bytes_end -= ext_size; + if (!(ext_bits & 0x01) || (ext_size + EXT_SIZE > frm_hdr->partition_length_)) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "RIFF: Inconsistent extra information."); + } + if (!!(ext_bits & 0x02)) { // has alpha data + dec->alpha_data_size_ = ext_size; + dec->alpha_data_ = ext_bytes_end; + } + } +#endif + // sanitized state dec->ready_ = 1; return 1; diff --git a/src/dec/vp8i.h b/src/dec/vp8i.h index b2ad9a33..91da59a6 100644 --- a/src/dec/vp8i.h +++ b/src/dec/vp8i.h @@ -246,6 +246,11 @@ struct VP8Decoder { // Filtering side-info int filter_type_; // 0=off, 1=simple, 2=complex uint8_t filter_levels_[NUM_MB_SEGMENTS]; // precalculated per-segment + + // extensions + const uint8_t* alpha_data_; // compressed alpha data (if present) + size_t alpha_data_size_; + uint8_t* alpha_plane_; // output }; //----------------------------------------------------------------------------- @@ -274,6 +279,10 @@ int VP8FinishRow(VP8Decoder* const dec, VP8Io* io); // Decode one macroblock. Returns false if there is not enough data. int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br); +// in alpha.c +const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + int row, int num_rows); + // in dsp.c typedef void (*VP8Idct)(const int16_t* coeffs, uint8_t* dst); extern VP8Idct VP8Transform; diff --git a/src/dec/webp.c b/src/dec/webp.c index 3bf6f55b..d8094264 100644 --- a/src/dec/webp.c +++ b/src/dec/webp.c @@ -139,10 +139,10 @@ static inline void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ } // All variants implemented. -UPSCALE_FUNC(UpscaleRgbLinePair, VP8YuvToRgb, 3) -UPSCALE_FUNC(UpscaleBgrLinePair, VP8YuvToBgr, 3) -UPSCALE_FUNC(UpscaleRgbaLinePair, VP8YuvToRgba, 4) -UPSCALE_FUNC(UpscaleBgraLinePair, VP8YuvToBgra, 4) +UPSCALE_FUNC(UpscaleRgbLinePair, VP8YuvToRgb, 3) +UPSCALE_FUNC(UpscaleBgrLinePair, VP8YuvToBgr, 3) +UPSCALE_FUNC(UpscaleRgbaLinePair, VP8YuvToRgb, 4) +UPSCALE_FUNC(UpscaleBgraLinePair, VP8YuvToBgr, 4) // Main driver function. static inline @@ -266,15 +266,42 @@ static int CustomPut(const VP8Io* io) { } else if (p->mode == MODE_BGR) { VP8YuvToBgr(y, u, v, dst + i * 3); } else if (p->mode == MODE_RGBA) { - VP8YuvToRgba(y, u, v, dst + i * 4); + VP8YuvToRgb(y, u, v, dst + i * 4); } else { - VP8YuvToBgra(y, u, v, dst + i * 4); + VP8YuvToBgr(y, u, v, dst + i * 4); } } dst += p->stride; } } } + + // Alpha handling + if (p->mode == MODE_RGBA || p->mode == MODE_BGRA) { + int i, j; + uint8_t* dst = p->output + io->mb_y * p->stride + 3; + const uint8_t* alpha = io->a; + const int has_alpha = (alpha != NULL); +#ifdef WEBP_EXPERIMENTAL_FEATURES + if (has_alpha) { + for (j = 0; j < mb_h; ++j) { + for (i = 0; i < w; ++i) { + dst[4 * i] = alpha[i]; + } + alpha += io->width; + dst += p->stride; + } + } +#endif + if (!has_alpha) { // fill-in with 0xFFs + for (j = 0; j < mb_h; ++j) { + for (i = 0; i < w; ++i) { + dst[4 * i] = 0xff; + } + dst += p->stride; + } + } + } return 1; } diff --git a/src/dec/yuv.h b/src/dec/yuv.h index 50e63f9b..1af0f089 100644 --- a/src/dec/yuv.h +++ b/src/dec/yuv.h @@ -36,11 +36,6 @@ inline static void VP8YuvToRgb(uint8_t y, uint8_t u, uint8_t v, rgb[2] = VP8kClip[y + b_off - YUV_RANGE_MIN]; } -inline static void VP8YuvToRgba(int y, int u, int v, uint8_t* const rgba) { - VP8YuvToRgb(y, u, v, rgba); - rgba[3] = 0xff; -} - inline static void VP8YuvToBgr(uint8_t y, uint8_t u, uint8_t v, uint8_t* const bgr) { const int r_off = VP8kVToR[v]; diff --git a/src/enc/Makefile.am b/src/enc/Makefile.am index 6cd0b2d3..34b6b4bf 100644 --- a/src/enc/Makefile.am +++ b/src/enc/Makefile.am @@ -3,7 +3,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src libwebpencode_la_SOURCES = analysis.c bit_writer.c bit_writer.h \ config.c cost.c cost.h dsp.c dsp_sse2.c filter.c \ frame.c iterator.c picture.c quant.c \ - syntax.c tree.c vp8enci.h webpenc.c + syntax.c tree.c vp8enci.h webpenc.c alpha.c libwebpencode_la_LDFLAGS = -version-info 0:0:0 -lm libwebpencodeinclude_HEADERS = ../webp/encode.h ../webp/types.h libwebpencodeincludedir = $(includedir)/webp diff --git a/src/enc/alpha.c b/src/enc/alpha.c new file mode 100644 index 00000000..dc764e59 --- /dev/null +++ b/src/enc/alpha.c @@ -0,0 +1,98 @@ +// Copyright 2011 Google Inc. +// +// 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 compression. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "vp8enci.h" + +#include "zlib.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +#ifdef WEBP_EXPERIMENTAL_FEATURES + +//----------------------------------------------------------------------------- + +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; + const int CHUNK_SIZE = 8192; + *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; + unsigned char chunk[CHUNK_SIZE]; + 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 */ + +int VP8EncProcessAlpha(VP8Encoder* enc) { + const WebPPicture* pic_ = enc->pic_; + enc->alpha_data_ = NULL; + enc->alpha_data_size_ = 0; +#ifdef WEBP_EXPERIMENTAL_FEATURES + if (pic_->a == NULL) { + return 1; + } + if (!CompressAlpha(pic_->a, pic_->width * pic_->height, + &enc->alpha_data_, &enc->alpha_data_size_, + enc->config_->alpha_compression)) { + return 0; + } +#endif /* WEBP_EXPERIMENTAL_FEATURES */ + return 1; +} + +void VP8EncDeleteAlpha(VP8Encoder* enc) { + free(enc->alpha_data_); + enc->alpha_data_ = NULL; + enc->alpha_data_size_ = 0; +} + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif diff --git a/src/enc/bit_writer.c b/src/enc/bit_writer.c index 3656a7e2..24bbd335 100644 --- a/src/enc/bit_writer.c +++ b/src/enc/bit_writer.c @@ -168,6 +168,16 @@ uint8_t* VP8BitWriterFinish(VP8BitWriter* const bw) { return bw->buf_; } +int VP8BitWriterAppend(VP8BitWriter* const bw, + const uint8_t* data, size_t size) { + assert(data); + if (bw->nb_bits_ != -8) return 0; // kFlush() must have been called + if (!BitWriterResize(bw, size)) return 0; + memcpy(bw->buf_ + bw->pos_, data, size); + bw->pos_ += size; + return 1; +} + //----------------------------------------------------------------------------- #if defined(__cplusplus) || defined(c_plusplus) diff --git a/src/enc/bit_writer.h b/src/enc/bit_writer.h index 3773c9cb..69e247a1 100644 --- a/src/enc/bit_writer.h +++ b/src/enc/bit_writer.h @@ -39,6 +39,8 @@ int VP8PutBit(VP8BitWriter* const bw, int bit, int prob); int VP8PutBitUniform(VP8BitWriter* const bw, int bit); void VP8PutValue(VP8BitWriter* const bw, int value, int nb_bits); void VP8PutSignedValue(VP8BitWriter* const bw, int value, int nb_bits); +int VP8BitWriterAppend(VP8BitWriter* const bw, + const uint8_t* data, size_t size); // return approximate write position (in bits) static inline uint64_t VP8BitWriterPos(const VP8BitWriter* const bw) { diff --git a/src/enc/config.c b/src/enc/config.c index 86ef5ce2..0a1ccbbe 100644 --- a/src/enc/config.c +++ b/src/enc/config.c @@ -41,6 +41,7 @@ int WebPConfigInitInternal(WebPConfig* const config, config->show_compressed = 0; config->preprocessing = 0; config->autofilter = 0; + config->alpha_compression = 0; // TODO(skal): tune. switch (preset) { @@ -105,6 +106,8 @@ int WebPValidateConfig(const WebPConfig* const config) { return 0; if (config->partitions < 0 || config->partitions > 3) return 0; + if (config->alpha_compression < 0) + return 0; return 1; } diff --git a/src/enc/picture.c b/src/enc/picture.c index 6c12ea48..03109eee 100644 --- a/src/enc/picture.c +++ b/src/enc/picture.c @@ -46,10 +46,30 @@ int WebPPictureAlloc(WebPPicture* const picture) { return 1; } +int WebPPictureAddAlphaPlane(WebPPicture* const picture) { + if (picture) { + const int width = picture->width; + const int height = picture->height; + const uint64_t a_size = (uint64_t)width * height; + // Security and validation checks + if (width <= 0 || height <= 0 || // check param error + a_size >= (1ULL << 40) || // check for reasonable global size + (size_t)a_size != a_size) { // check for overflow on 32bit + return 0; + } + free(picture->a); // erase previous buffer + picture->a = (uint8_t*)malloc((size_t)a_size); + return (picture->a != NULL); + } + return 1; +} + void WebPPictureFree(WebPPicture* const picture) { if (picture) { free(picture->y); picture->y = picture->u = picture->v = NULL; + free(picture->a); + picture->a = NULL; } } @@ -198,7 +218,7 @@ static inline int rgb_to_v(int r, int g, int b) { static int Import(WebPPicture* const picture, const uint8_t* const rgb, int rgb_stride, - int step, int swap) { + int step, int swap, int alpha_offset) { int x, y; const uint8_t* const r_ptr = rgb + (swap ? 2 : 0); const uint8_t* const g_ptr = rgb + 1; @@ -227,6 +247,17 @@ static int Import(WebPPicture* const picture, RGB_TO_UV(x, y, SUM1); } } + if (alpha_offset >= 0) { + if (!WebPPictureAddAlphaPlane(picture)) { + return 0; + } + for (y = 0; y < picture->height; ++y) { + for (x = 0; x < picture->width; ++x) { + picture->a[x + y * picture->width] = + rgb[step * x + y * rgb_stride + alpha_offset]; + } + } + } return 1; } #undef SUM4 @@ -238,25 +269,25 @@ static int Import(WebPPicture* const picture, int WebPPictureImportRGB(WebPPicture* const picture, const uint8_t* const rgb, int rgb_stride) { if (!WebPPictureAlloc(picture)) return 0; - return Import(picture, rgb, rgb_stride, 3, 0); + return Import(picture, rgb, rgb_stride, 3, 0, -1); } int WebPPictureImportBGR(WebPPicture* const picture, const uint8_t* const rgb, int rgb_stride) { if (!WebPPictureAlloc(picture)) return 0; - return Import(picture, rgb, rgb_stride, 3, 1); + return Import(picture, rgb, rgb_stride, 3, 1, -1); } int WebPPictureImportRGBA(WebPPicture* const picture, const uint8_t* const rgba, int rgba_stride) { if (!WebPPictureAlloc(picture)) return 0; - return Import(picture, rgba, rgba_stride, 4, 0); + return Import(picture, rgba, rgba_stride, 4, 0, 3); } int WebPPictureImportBGRA(WebPPicture* const picture, const uint8_t* const rgba, int rgba_stride) { if (!WebPPictureAlloc(picture)) return 0; - return Import(picture, rgba, rgba_stride, 4, 1); + return Import(picture, rgba, rgba_stride, 4, 1, 3); } //----------------------------------------------------------------------------- @@ -264,7 +295,7 @@ int WebPPictureImportBGRA(WebPPicture* const picture, typedef int (*Importer)(WebPPicture* const, const uint8_t* const, int); -static size_t Encode(const uint8_t* rgb, int width, int height, int stride, +static size_t Encode(const uint8_t* rgba, int width, int height, int stride, Importer import, float quality_factor, uint8_t** output) { size_t output_size = 0; WebPPicture pic; @@ -286,7 +317,7 @@ static size_t Encode(const uint8_t* rgb, int width, int height, int stride, wrt.size = &output_size; InitMemoryWriter(&wrt); - ok = import(&pic, rgb, stride) && WebPEncode(&config, &pic); + ok = import(&pic, rgba, stride) && WebPEncode(&config, &pic); WebPPictureFree(&pic); if (!ok) { free(*output); diff --git a/src/enc/syntax.c b/src/enc/syntax.c index a788f3c3..3ddcad88 100644 --- a/src/enc/syntax.c +++ b/src/enc/syntax.c @@ -155,14 +155,46 @@ static int EmitPartitionsSize(const VP8Encoder* const enc, //----------------------------------------------------------------------------- +static int WriteExtensions(VP8Encoder* const enc) { +#ifdef WEBP_EXPERIMENTAL_FEATURES + const int EXT_SIZE = 4; + VP8BitWriter* const bw = &enc->bw_; + uint8_t trailer[EXT_SIZE]; + uint32_t ext_size = 0; + uint8_t ext_bits = 0x01; + if (enc->alpha_data_size_) { + if (!VP8BitWriterAppend(bw, enc->alpha_data_, enc->alpha_data_size_)) { + return 0; + } + ext_size += enc->alpha_data_size_; + ext_bits |= 0x02; + } + trailer[0] = (ext_size >> 16) & 0xff; + trailer[1] = (ext_size >> 8) & 0xff; + trailer[2] = (ext_size >> 0) & 0xff; + trailer[EXT_SIZE - 1] = ext_bits; + if (!VP8BitWriterAppend(bw, trailer, EXT_SIZE)) { + return 0; + } +#endif + return 1; +} + +//----------------------------------------------------------------------------- + static size_t GeneratePartition0(VP8Encoder* const enc) { VP8BitWriter* const bw = &enc->bw_; const int mb_size = enc->mb_w_ * enc->mb_h_; uint64_t pos1, pos2, pos3; + const int need_extensions = (enc->alpha_data_size_ > 0); pos1 = VP8BitWriterPos(bw); VP8BitWriterInit(bw, mb_size * 7 / 8); // ~7 bits per macroblock +#ifdef WEBP_EXPERIMENTAL_FEATURES + VP8PutBitUniform(bw, need_extensions); // extensions +#else VP8PutBitUniform(bw, 0); // colorspace +#endif VP8PutBitUniform(bw, 0); // clamp type PutSegmentHeader(bw, enc); @@ -174,11 +206,17 @@ static size_t GeneratePartition0(VP8Encoder* const enc) { pos2 = VP8BitWriterPos(bw); VP8CodeIntraModes(enc); VP8BitWriterFinish(bw); + + if (need_extensions && !WriteExtensions(enc)) { + return 0; + } + pos3 = VP8BitWriterPos(bw); if (enc->pic_->stats) { enc->pic_->stats->header_bytes[0] = (int)((pos2 - pos1 + 7) >> 3); enc->pic_->stats->header_bytes[1] = (int)((pos3 - pos2 + 7) >> 3); + enc->pic_->stats->alpha_data_size = enc->alpha_data_size_; } return !bw->error_; } diff --git a/src/enc/vp8enci.h b/src/enc/vp8enci.h index c496568a..7e28351a 100644 --- a/src/enc/vp8enci.h +++ b/src/enc/vp8enci.h @@ -326,6 +326,10 @@ struct VP8Encoder { VP8BitWriter bw_; // part0 VP8BitWriter parts_[MAX_NUM_PARTITIONS]; // token partitions + // transparency blob + uint8_t* alpha_data_; + size_t alpha_data_size_; + // quantization info (one set of DC/AC dequant factor per segment) VP8SegmentInfo dqm_[NUM_MB_SEGMENTS]; int base_quant_; // nominal quantizer value. Only used @@ -414,6 +418,11 @@ void VP8SetSegmentParams(VP8Encoder* const enc, float quality); // Pick best modes and fills the levels. Returns true if skipped. int VP8Decimate(VP8EncIterator* const it, VP8ModeScore* const rd, int rd_opt); + // in alpha.c +// Compress transparency information into enc->alpha_data_. Return true if ok. +int VP8EncProcessAlpha(VP8Encoder* enc); +void VP8EncDeleteAlpha(VP8Encoder* enc); // delete compressed data + // in dsp.c // Transforms // VP8Idct: Does one of two inverse transforms. If do_two is set, the transforms diff --git a/src/enc/webpenc.c b/src/enc/webpenc.c index 59221d7f..2c3d2811 100644 --- a/src/enc/webpenc.c +++ b/src/enc/webpenc.c @@ -246,7 +246,12 @@ static VP8Encoder* InitEncoder(const WebPConfig* const config, } static void DeleteEncoder(VP8Encoder* enc) { - free(enc); + if (enc) { + if (enc->alpha_data_) { + VP8EncDeleteAlpha(enc); + } + free(enc); + } } //----------------------------------------------------------------------------- @@ -306,6 +311,7 @@ int WebPEncode(const WebPConfig* const config, WebPPicture* const pic) { ok = VP8EncAnalyze(enc) && VP8StatLoop(enc) && VP8EncLoop(enc) + && VP8EncProcessAlpha(enc) && VP8EncWrite(enc); StoreStats(enc); DeleteEncoder(enc); diff --git a/src/webp/decode.h b/src/webp/decode.h index 60047e37..6c63d54b 100644 --- a/src/webp/decode.h +++ b/src/webp/decode.h @@ -178,11 +178,10 @@ VP8StatusCode WebPIAppend(WebPIDecoder* const idec, const uint8_t* data, VP8StatusCode WebPIUpdate(WebPIDecoder* const idec, const uint8_t* data, uint32_t data_size); -// Returns the r/g/b/(a) image decoded so far. Returns NULL if output params -// are not initialized yet. The r/g/b/(a) output type corresponds to the mode -// specified in WebPINew()/WebPINewRGB(). *last_y is the index of last decoded -// row in raster scan order. Some pointers (*last_y, *width etc.) can be NULL if -// corresponding information is not needed. +// Returns the RGB image decoded so far. Returns NULL if output params are not +// initialized yet. *last_y is the index of last decoded row in raster scan +// order. Some pointers (*last_y, *width etc.) can be NULL if corresponding +// information is not needed. uint8_t* WebPIDecGetRGB(const WebPIDecoder* const idec, int *last_y, int* width, int* height, int* stride); diff --git a/src/webp/decode_vp8.h b/src/webp/decode_vp8.h index 153a4c54..63757233 100644 --- a/src/webp/decode_vp8.h +++ b/src/webp/decode_vp8.h @@ -18,7 +18,7 @@ extern "C" { #endif -#define WEBP_DECODER_ABI_VERSION 0x0001 +#define WEBP_DECODER_ABI_VERSION 0x0002 //----------------------------------------------------------------------------- // Lower-level API @@ -80,6 +80,9 @@ struct VP8Io { // of more visible blocking. Note that output will also be non-compliant // with the VP8 specifications. int bypass_filtering; + + // pointer to the alpha data (if present) corresponding to the rows + const uint8_t* a; }; // Internal, version-checked, entry point diff --git a/src/webp/encode.h b/src/webp/encode.h index e0cc5dc3..6828a4bf 100644 --- a/src/webp/encode.h +++ b/src/webp/encode.h @@ -20,7 +20,7 @@ extern "C" { #endif -#define WEBP_ENCODER_ABI_VERSION 0x0001 +#define WEBP_ENCODER_ABI_VERSION 0x0002 // Return the encoder's version number, packed in hexadecimal using 8bits for // each of major/minor/revision. E.g: v2.5.7 is 0x020507. @@ -32,7 +32,6 @@ int WebPGetEncoderVersion(void); // Returns the size of the compressed data (pointed to by *output), or 0 if // an error occurred. The compressed data must be released by the caller // using the call 'free(*output)'. -// Currently, alpha values are discarded. size_t WebPEncodeRGB(const uint8_t* rgb, int width, int height, int stride, float quality_factor, uint8_t** output); size_t WebPEncodeBGR(const uint8_t* bgr, int width, int height, int stride, @@ -66,6 +65,7 @@ typedef struct { int preprocessing; // preprocessing filter (0=none, 1=segment-smooth) int partitions; // log2(number of token partitions) in [0..3] // Default is set to 0 for easier progressive decoding. + int alpha_compression; // Algorithm for optimizing the alpha plane (0 = none) } WebPConfig; // Enumerate some predefined settings for WebPConfig, depending on the type @@ -120,6 +120,8 @@ typedef struct { int segment_size[4]; // number of macroblocks in each segments int segment_quant[4]; // quantizer values for each segments int segment_level[4]; // filtering strength for each segments [0..63] + + int alpha_data_size; // size of the transparency data } WebPAuxStats; // Signature for output function. Should return 1 if writing was successful. @@ -134,7 +136,7 @@ struct WebPPicture { int width, height; // dimensions. uint8_t *y, *u, *v; // pointers to luma/chroma planes. int y_stride, uv_stride; // luma/chroma strides. - uint8_t *a; // pointer to the alpha plane (unused for now). + uint8_t *a; // pointer to the width x height alpha plane // output WebPWriterFunction writer; // can be NULL @@ -173,6 +175,10 @@ static inline int WebPPictureInit(WebPPicture* const picture) { // Returns 0 in case of memory error. int WebPPictureAlloc(WebPPicture* const picture); +// This function will add storage for a transparency plane to a picture, using +// its width and depth. +int WebPPictureAddAlphaPlane(WebPPicture* const picture); + // Release memory allocated by WebPPictureAlloc() or WebPPictureImport*() // Note that this function does _not_ free the memory pointed to by 'picture'. void WebPPictureFree(WebPPicture* const picture); @@ -187,16 +193,17 @@ int WebPPictureCopy(const WebPPicture* const src, WebPPicture* const dst); int WebPPictureCrop(WebPPicture* const picture, int left, int top, int width, int height); -// Colorspace conversion function. Previous buffer will be free'd, if any. +// Colorspace conversion function to import RGB samples. +// Previous buffer will be free'd, if any. // *rgb buffer should have a size of at least height * rgb_stride. // Returns 0 in case of memory error. int WebPPictureImportRGB(WebPPicture* const picture, const uint8_t* const rgb, int rgb_stride); -// Same, but for RGBA buffer. Alpha information is ignored. +// Same, but for RGBA buffer int WebPPictureImportRGBA(WebPPicture* const picture, const uint8_t* const rgba, int rgba_stride); -// Variant of the above, but taking BGR input: +// Variant of the above, but taking BGR(A) input: int WebPPictureImportBGR(WebPPicture* const picture, const uint8_t* const bgr, int bgr_stride); int WebPPictureImportBGRA(WebPPicture* const picture, diff --git a/src/webp/types.h b/src/webp/types.h index 003c3f76..c3f67f6f 100644 --- a/src/webp/types.h +++ b/src/webp/types.h @@ -12,6 +12,10 @@ #ifndef WEBP_WEBP_TYPES_H_ #define WEBP_WEBP_TYPES_H_ +// This is for experimentation only! Bitstreams generated will surely +// be invalid, non-decodable ones! USE WITH CARE! +// #define WEBP_EXPERIMENTAL_FEATURES // activate alpha support, yuv444, etc. + #ifndef _MSC_VER #include #ifdef ANSI