From e8ab6a825a86ed77f0b148645fa7ce3aa25ec216 Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 20 Jul 2016 12:55:05 -0700 Subject: [PATCH] VP8EstimateQuality(): roughty estimate webp bitstream quality factor This function returns a rough estimation of the quality factor used for compressing the WebP bitstream. Simple command line: 'webp_quality [-quiet] in.webp' should print the estimated quality factor. Change-Id: Ifba3e489461f5a587003ac9f08cc7556e9b24ac2 --- extras/Makefile.am | 10 +++- extras/extras.h | 8 +++ extras/quality_estimate.c | 121 ++++++++++++++++++++++++++++++++++++++ extras/webp_quality.c | 49 +++++++++++++++ makefile.unix | 21 +++++-- 5 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 extras/quality_estimate.c create mode 100644 extras/webp_quality.c diff --git a/extras/Makefile.am b/extras/Makefile.am index b2525ad2..a03d880b 100644 --- a/extras/Makefile.am +++ b/extras/Makefile.am @@ -5,15 +5,21 @@ noinst_HEADERS = noinst_HEADERS += ../src/webp/types.h libwebpextras_la_SOURCES = -libwebpextras_la_SOURCES += extras.c extras.h +libwebpextras_la_SOURCES += extras.c extras.h quality_estimate.c libwebpextras_la_CPPFLAGS = $(AM_CPPFLAGS) libwebpextras_la_LIBADD = ../src/libwebp.la -noinst_PROGRAMS = get_disto +noinst_PROGRAMS = get_disto webp_quality get_disto_SOURCES = get_disto.c get_disto_CPPFLAGS = $(AM_CPPFLAGS) get_disto_LDADD = ../imageio/libexample_util.la ../imageio/libimagedec.la get_disto_LDADD += ../src/libwebp.la get_disto_LDADD += $(PNG_LIBS) $(JPEG_LIBS) $(TIFF_LIBS) + +webp_quality_SOURCES = webp_quality.c +webp_quality_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) +webp_quality_LDADD = ../imageio/libexample_util.la ../imageio/libimagedec.la +webp_quality_LDADD += ./libwebpextras.la +webp_quality_LDADD += ../src/libwebp.la $(JPEG_LIBS) $(PNG_LIBS) $(TIFF_LIBS) diff --git a/extras/extras.h b/extras/extras.h index 0b687ff4..cc324e9f 100644 --- a/extras/extras.h +++ b/extras/extras.h @@ -44,6 +44,14 @@ WEBP_EXTERN(int) WebPImportRGB4444(const uint8_t* rgb4444, WebPPicture* pic); //------------------------------------------------------------------------------ +// Parse a bitstream, search for VP8 (lossy) header and report a +// rough estimation of the quality factor used for compressing the bitstream. +// Any error (invalid bitstream, lossless compression, incomplete header, etc.) +// will return a value of -1. +WEBP_EXTERN(int) VP8EstimateQuality(const uint8_t* const data, size_t size); + +//------------------------------------------------------------------------------ + #ifdef __cplusplus } // extern "C" #endif diff --git a/extras/quality_estimate.c b/extras/quality_estimate.c new file mode 100644 index 00000000..1466ce92 --- /dev/null +++ b/extras/quality_estimate.c @@ -0,0 +1,121 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// VP8EstimateQuality(): rough encoding quality estimate +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "./extras.h" + +#include + +//------------------------------------------------------------------------------ + +#define INVALID_BIT_POS (1ull << 63) + +// In most cases, we don't need to use a full arithmetic decoder, since +// all the header's bits are written using a uniform probability of 128. +// We can just parse the header as if it was bits (works in 99.999% cases). +static WEBP_INLINE uint32_t GetBit(const uint8_t* const data, size_t nb, + uint64_t max_size, uint64_t* const bit_pos) { + uint32_t val = 0; + if (*bit_pos + nb <= 8 * max_size) { + while (nb-- > 0) { + const uint64_t p = (*bit_pos)++; + const int bit = !!(data[p >> 3] & (128 >> ((p & 7)))); + val = (val << 1) | bit; + } + } else { + *bit_pos = INVALID_BIT_POS; + } + return val; +} + +#define GET_BIT(n) GetBit(data, (n), size, &bit_pos) +#define CONDITIONAL_SKIP(n) (GET_BIT(1) ? GET_BIT((n)) : 0) + +int VP8EstimateQuality(const uint8_t* const data, size_t size) { + size_t pos = 0; + uint64_t bit_pos; + uint64_t sig = 0x00; + int ok = 0; + int Q = -1; + + if (data == NULL) return -1; + + while (pos < size) { + sig = (sig >> 8) | ((uint64_t)data[pos++] << 40); + if ((sig >> 24) == 0x2a019dull) { + ok = 1; + break; + } + } + if (!ok) return -1; + if (pos + 4 > size) return -1; + + // Skip main Header + // width = (data[pos + 0] | (data[pos + 1] << 8)) & 0x3fff; + // height = (data[pos + 2] | (data[pos + 3] << 8)) & 0x3fff; + pos += 4; + bit_pos = pos * 8; + + GET_BIT(2); // color_space + clamp type + + // Segment header + if (GET_BIT(1)) { // use_segment_ + int s; + const int update_map = GET_BIT(1); + if (GET_BIT(1)) { // update data + const int absolute_delta = GET_BIT(1); + int q[4] = { 0, 0, 0, 0 }; + for (s = 0; s < 4; ++s) { + if (GET_BIT(1)) { + q[s] = GET_BIT(7); + if (GET_BIT(1)) q[s] = -q[s]; // sign + } + } + if (absolute_delta) Q = q[0]; // just use the first segment's quantizer + for (s = 0; s < 4; ++s) CONDITIONAL_SKIP(7); // filter strength + } + if (update_map) { + for (s = 0; s < 3; ++s) CONDITIONAL_SKIP(8); + } + } + // Filter header + GET_BIT(1 + 6 + 3); // simple + level + sharpness + if (GET_BIT(1)) { // use_lf_delta + if (GET_BIT(1)) { // update lf_delta? + int n; + for (n = 0; n < 4 + 4; ++n) CONDITIONAL_SKIP(6); + } + } + // num partitions + GET_BIT(2); + + // ParseQuant + { + const int base_q = GET_BIT(7); + /* dqy1_dc = */ CONDITIONAL_SKIP(5); + /* dqy2_dc = */ CONDITIONAL_SKIP(5); + /* dqy2_ac = */ CONDITIONAL_SKIP(5); + /* dquv_dc = */ CONDITIONAL_SKIP(5); + /* dquv_ac = */ CONDITIONAL_SKIP(5); + + if (Q < 0) Q = base_q; + } + if (bit_pos == INVALID_BIT_POS) return -1; + + // base mapping + Q = (127 - Q) * 100 / 127; + // correction for power-law behavior in low range + if (Q < 80) { + Q = (int)(pow(Q / 80., 1. / 0.38) * 80); + } + return Q; +} diff --git a/extras/webp_quality.c b/extras/webp_quality.c new file mode 100644 index 00000000..45d67085 --- /dev/null +++ b/extras/webp_quality.c @@ -0,0 +1,49 @@ +// Simple tool to roughly evaluate the quality encoding of a webp bitstream +// +// Result is a *rough* estimation of the quality. You should just consider +// the bucket it's in (q > 80? > 50? > 20?) and not take it for face value. +/* + gcc -o webp_quality webp_quality.c -O3 -I../ -L. -L../imageio \ + -lexample_util -limagedec -lwebpextras -lwebp -L/opt/local/lib \ + -lpng -lz -ljpeg -ltiff -lm -lpthread +*/ + +#include +#include +#include + +#include "./extras.h" +#include "../imageio/example_util.h" + +int main(int argc, const char *argv[]) { + int c; + int quiet = 0; + for (c = 1; c < argc; ++c) { + if (!strcmp(argv[c], "-quiet")) { + quiet = 1; + } else if (!strcmp(argv[c], "-help") || !strcmp(argv[c], "-h")) { + printf("webp_quality [-h][-quiet] webp_files...\n"); + return 1; + } else { + const char* const filename = argv[c]; + const uint8_t* data = NULL; + size_t data_size = 0; + int q; + const int ok = ExUtilReadFile(filename, &data, &data_size); + if (!ok) continue; + q = VP8EstimateQuality(data, data_size); + if (!quiet) printf("[%s] ", filename); + if (q < 0) { + fprintf(stderr, "Not a WebP file, or not a lossy WebP file.\n"); + } else { + if (!quiet) { + printf("Estimated quality factor: %d\n", q); + } else { + printf("%d\n", q); // just print the number + } + } + free((void*)data); + } + } + return 1; +} diff --git a/makefile.unix b/makefile.unix index bef01b2a..9db8227d 100644 --- a/makefile.unix +++ b/makefile.unix @@ -252,6 +252,7 @@ UTILS_ENC_OBJS = \ EXTRA_OBJS = \ extras/extras.o \ + extras/quality_estimate.o \ LIBWEBPDECODER_OBJS = $(DEC_OBJS) $(DSP_DEC_OBJS) $(UTILS_DEC_OBJS) LIBWEBP_OBJS = $(LIBWEBPDECODER_OBJS) $(ENC_OBJS) $(DSP_ENC_OBJS) \ @@ -311,17 +312,18 @@ OUT_LIBS += src/libwebp.a EXTRA_LIB = extras/libwebpextras.a OUT_EXAMPLES = examples/cwebp examples/dwebp EXTRA_EXAMPLES = examples/gif2webp examples/vwebp examples/webpmux \ - examples/anim_diff extras/get_disto + examples/anim_diff +OTHER_EXAMPLES = extras/get_disto extras/webp_quality OUTPUT = $(OUT_LIBS) $(OUT_EXAMPLES) ifeq ($(MAKECMDGOALS),clean) - OUTPUT += $(EXTRA_EXAMPLES) + OUTPUT += $(EXTRA_EXAMPLES) $(OTHER_EXAMPLES) OUTPUT += src/demux/libwebpdemux.a src/mux/libwebpmux.a $(EXTRA_LIB) OUTPUT += examples/libgifdec.a examples/libanim_util.a endif ex: $(OUT_EXAMPLES) -all: ex $(EXTRA_EXAMPLES) +all: ex $(EXTRA_EXAMPLES) $(OTHER_EXAMPLES) extras: $(EXTRA_LIB) $(EX_FORMAT_DEC_OBJS): %.o: %.h @@ -379,11 +381,18 @@ examples/webpmux: imageio/libexample_util.a src/mux/libwebpmux.a examples/webpmux: src/libwebpdecoder.a extras/get_disto: extras/get_disto.o -extras/get_disto: imageio/libimagedec.a imageio/libexampledec.a +extras/get_disto: imageio/libimagedec.a extras/get_disto: imageio/libexample_util.a -extras/get_disto: src/libwebp.a $(CWEBP_LIBS) +extras/get_disto: src/libwebp.a $(EXTRA_LIB) +extras/get_disto: EXTRA_LIBS += $(CWEBP_LIBS) -$(OUT_EXAMPLES) $(EXTRA_EXAMPLES): +extras/webp_quality: extras/webp_quality.o +extras/webp_quality: imageio/libimagedec.a +extras/webp_quality: imageio/libexample_util.a +extras/webp_quality: src/libwebp.a $(EXTRA_LIB) +extras/get_disto: EXTRA_LIBS += $(CWEBP_LIBS) + +$(OUT_EXAMPLES) $(EXTRA_EXAMPLES) $(OTHER_EXAMPLES): $(CC) -o $@ $^ $(LDFLAGS) dist: DESTDIR := dist