Add webpinfo tool.

A command line tool to print out the chunk level structure of
WebP files along with basic integrity checks.

BUG=webp:330

Change-Id: Ic69f646f649abb655b1854621d99afedeed158d7
This commit is contained in:
Pascal Massimino 2017-03-14 10:00:43 +01:00
parent ec5036e475
commit 274daf5415
8 changed files with 811 additions and 2 deletions

View File

@ -8,6 +8,7 @@ option(WEBP_BUILD_CWEBP "Build the cwebp command line tool." OFF)
option(WEBP_BUILD_DWEBP "Build the dwebp command line tool." OFF) option(WEBP_BUILD_DWEBP "Build the dwebp command line tool." OFF)
option(WEBP_BUILD_GIF2WEBP "Build the gif2webp conversion tool." OFF) option(WEBP_BUILD_GIF2WEBP "Build the gif2webp conversion tool." OFF)
option(WEBP_BUILD_IMG2WEBP "Build the img2webp animation tool." OFF) option(WEBP_BUILD_IMG2WEBP "Build the img2webp animation tool." OFF)
option(WEBP_BUILD_WEBPINFO "Build the webpinfo command line tool." OFF)
option(WEBP_BUILD_WEBP_JS "Emscripten build of webp.js." OFF) option(WEBP_BUILD_WEBP_JS "Emscripten build of webp.js." OFF)
option(WEBP_EXPERIMENTAL_FEATURES "Build with experimental features." OFF) option(WEBP_EXPERIMENTAL_FEATURES "Build with experimental features." OFF)
option(WEBP_ENABLE_SWAP_16BIT_CSP "Enable byte swap for 16 bit colorspaces." OFF) option(WEBP_ENABLE_SWAP_16BIT_CSP "Enable byte swap for 16 bit colorspaces." OFF)

View File

@ -348,6 +348,7 @@ OUT_EXAMPLES = $(DIRBIN)\cwebp.exe $(DIRBIN)\dwebp.exe
EXTRA_EXAMPLES = $(DIRBIN)\vwebp.exe $(DIRBIN)\webpmux.exe \ EXTRA_EXAMPLES = $(DIRBIN)\vwebp.exe $(DIRBIN)\webpmux.exe \
$(DIRBIN)\img2webp.exe $(DIRBIN)\get_disto.exe \ $(DIRBIN)\img2webp.exe $(DIRBIN)\get_disto.exe \
$(DIRBIN)\webp_quality.exe $(DIRBIN)\vwebp_sdl.exe \ $(DIRBIN)\webp_quality.exe $(DIRBIN)\vwebp_sdl.exe \
$(DIRBIN)\webpinfo.exe
ex: $(OUT_LIBS) $(OUT_EXAMPLES) ex: $(OUT_LIBS) $(OUT_EXAMPLES)
all: ex $(EXTRA_EXAMPLES) all: ex $(EXTRA_EXAMPLES)
@ -379,10 +380,12 @@ $(DIRBIN)\img2webp.exe: $(IMAGEIO_DEC_OBJS)
$(DIRBIN)\img2webp.exe: $(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS) $(LIBWEBP) $(DIRBIN)\img2webp.exe: $(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS) $(LIBWEBP)
$(DIRBIN)\get_disto.exe: $(DIROBJ)\extras\get_disto.obj $(DIRBIN)\get_disto.exe: $(DIROBJ)\extras\get_disto.obj
$(DIRBIN)\get_disto.exe: $(IMAGEIO_DEC_OBJS) $(IMAGEIO_UTIL_OBJS) $(LIBWEBP) $(DIRBIN)\get_disto.exe: $(IMAGEIO_DEC_OBJS) $(IMAGEIO_UTIL_OBJS) $(LIBWEBP)
$(DIRBIN)\webp_quality.exe: $(DIROBJ)\extras\webp_quality.obj $(DIRBIN)\webp_quality.exe: $(DIROBJ)\extras\webp_quality.obj
$(DIRBIN)\webp_quality.exe: $(IMAGEIO_UTIL_OBJS) $(DIRBIN)\webp_quality.exe: $(IMAGEIO_UTIL_OBJS)
$(DIRBIN)\webp_quality.exe: $(EXTRAS_OBJS) $(LIBWEBP) $(DIRBIN)\webp_quality.exe: $(EXTRAS_OBJS) $(LIBWEBP)
$(DIRBIN)\webpinfo.exe: $(DIROBJ)\examples\webpinfo.obj
$(DIRBIN)\webpinfo.exe: $(IMAGEIO_DEC_OBJS)
$(DIRBIN)\webpinfo.exe: $(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS) $(LIBWEBP)
$(OUT_EXAMPLES): $(EX_UTIL_OBJS) $(LIBWEBP) $(OUT_EXAMPLES): $(EX_UTIL_OBJS) $(LIBWEBP)
$(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS): $(OUTPUT_DIRS) $(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS): $(OUTPUT_DIRS)

View File

@ -392,6 +392,24 @@ model {
} }
} }
} }
webpinfo_example(NativeExecutableSpec) {
binaries {
all {
lib library: "example_util", linkage: "static"
lib library: "imageio_util", linkage: "static"
lib library: "webp"
}
}
sources {
c {
source {
srcDir "./examples"
include "webpinfo.c"
}
}
}
}
} }
tasks { tasks {
// Task to test all possible configurations. // Task to test all possible configurations.

View File

@ -599,6 +599,11 @@ if test "$enable_libwebpmux" = "yes"; then
fi fi
AM_CONDITIONAL([BUILD_IMG2WEBP], [test "${build_img2webp}" = "yes"]) AM_CONDITIONAL([BUILD_IMG2WEBP], [test "${build_img2webp}" = "yes"])
if test "$enable_libwebpmux" = "yes"; then
build_webpinfo=yes
fi
AM_CONDITIONAL([BUILD_WEBPINFO], [test "${build_webpinfo}" = "yes"])
dnl === check for WIC support === dnl === check for WIC support ===
AC_ARG_ENABLE([wic], AC_ARG_ENABLE([wic],
@ -756,6 +761,7 @@ gif2webp : ${build_gif2webp-no}
img2webp : ${build_img2webp-no} img2webp : ${build_img2webp-no}
webpmux : ${enable_libwebpmux-no} webpmux : ${enable_libwebpmux-no}
vwebp : ${build_vwebp-no} vwebp : ${build_vwebp-no}
webpinfo : ${build_webpinfo-no}
SDL support : ${sdl_support-no} SDL support : ${sdl_support-no}
vwebp_sdl : ${build_vwebp_sdl-no} vwebp_sdl : ${build_vwebp_sdl-no}
]) ])

View File

@ -80,3 +80,19 @@ LOCAL_STATIC_LIBRARIES := example_util imageio_util imagedec webpmux webp
LOCAL_MODULE := img2webp_example LOCAL_MODULE := img2webp_example
include $(BUILD_EXECUTABLE) include $(BUILD_EXECUTABLE)
################################################################################
# webpinfo
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
webpinfo.c \
LOCAL_CFLAGS := $(WEBP_CFLAGS)
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../src
LOCAL_STATIC_LIBRARIES := example_util imageio_util webp
LOCAL_MODULE := webpinfo_example
include $(BUILD_EXECUTABLE)

View File

@ -16,6 +16,9 @@ endif
if BUILD_VWEBP if BUILD_VWEBP
bin_PROGRAMS += vwebp bin_PROGRAMS += vwebp
endif endif
if BUILD_WEBPINFO
bin_PROGRAMS += webpinfo
endif
noinst_LTLIBRARIES = libexample_util.la noinst_LTLIBRARIES = libexample_util.la
@ -66,6 +69,11 @@ img2webp_LDADD += ../imageio/libimagedec.la
img2webp_LDADD += ../src/mux/libwebpmux.la ../src/libwebp.la img2webp_LDADD += ../src/mux/libwebpmux.la ../src/libwebp.la
img2webp_LDADD += $(PNG_LIBS) $(JPEG_LIBS) $(TIFF_LIBS) img2webp_LDADD += $(PNG_LIBS) $(JPEG_LIBS) $(TIFF_LIBS)
webpinfo_SOURCES = webpinfo.c
webpinfo_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE)
webpinfo_LDADD = libexample_util.la ../imageio/libimageio_util.la
webpinfo_LDADD += ../src/libwebp.la
if BUILD_LIBWEBPDECODER if BUILD_LIBWEBPDECODER
anim_diff_LDADD += ../src/libwebpdecoder.la anim_diff_LDADD += ../src/libwebpdecoder.la
vwebp_LDADD += ../src/libwebpdecoder.la vwebp_LDADD += ../src/libwebpdecoder.la

754
examples/webpinfo.c Normal file
View File

@ -0,0 +1,754 @@
// Copyright 2017 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.
// -----------------------------------------------------------------------------
//
// Command-line tool to print out the chunk level structure of WebP files
// along with basic integrity checks.
//
// Author: Hui Su (huisu@google.com)
#include <assert.h>
#include <stdio.h>
#ifdef HAVE_CONFIG_H
#include "webp/config.h"
#endif
#include "../imageio/imageio_util.h"
#include "webp/decode.h"
#include "webp/format_constants.h"
#include "webp/mux_types.h"
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
typedef enum {
WEBP_INFO_OK = 0,
WEBP_INFO_TRUNCATED_DATA,
WEBP_INFO_PARSE_ERROR,
WEBP_INFO_INVALID_PARAM,
WEBP_INFO_BITSTREAM_ERROR,
WEBP_INFO_MISSING_DATA,
WEBP_INFO_INVALID_COMMAND
} WebPInfoStatus;
typedef enum ChunkID {
CHUNK_VP8,
CHUNK_VP8L,
CHUNK_VP8X,
CHUNK_ALPHA,
CHUNK_ANIM,
CHUNK_ANMF,
CHUNK_ICCP,
CHUNK_EXIF,
CHUNK_XMP,
CHUNK_UNKNOWN,
CHUNK_TYPES = CHUNK_UNKNOWN
} ChunkID;
typedef struct {
size_t start_;
size_t end_;
const uint8_t* buf_;
} MemBuffer;
typedef struct {
size_t offset_;
size_t size_;
const uint8_t* payload_;
ChunkID id_;
} ChunkData;
typedef struct WebPInfo {
int canvas_width_;
int canvas_height_;
int loop_count_;
int num_frames_;
int chunk_counts_[CHUNK_TYPES];
int anmf_subchunk_counts_[3]; // 0 VP8; 1 VP8L; 2 ALPH.
uint32_t bgcolor_;
int feature_flags_;
int has_alpha_;
// Used for parsing ANMF chunks.
int frame_width_, frame_height_;
size_t anim_frame_data_size_;
int is_processing_anim_frame_, seen_alpha_subchunk_, seen_image_subchunk_;
// Print output control.
int quiet_, show_diagnosis_, show_summary_;
} WebPInfo;
static void WebPInfoInit(WebPInfo* const webp_info) {
memset(webp_info, 0, sizeof(*webp_info));
}
static const char kWebPChunkTags[CHUNK_TYPES][4] = {
{ 'V', 'P', '8', ' ' },
{ 'V', 'P', '8', 'L' },
{ 'V', 'P', '8', 'X' },
{ 'A', 'L', 'P', 'H' },
{ 'A', 'N', 'I', 'M' },
{ 'A', 'N', 'M', 'F' },
{ 'I', 'C', 'C', 'P' },
{ 'E', 'X', 'I', 'F' },
{ 'X', 'M', 'P', ' ' },
};
// -----------------------------------------------------------------------------
// Data reading.
static int GetLE16(const uint8_t* const data) {
return (data[0] << 0) | (data[1] << 8);
}
static int GetLE24(const uint8_t* const data) {
return GetLE16(data) | (data[2] << 16);
}
static uint32_t GetLE32(const uint8_t* const data) {
return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16);
}
static int ReadLE16(const uint8_t** data) {
const int val = GetLE16(*data);
*data += 2;
return val;
}
static int ReadLE24(const uint8_t** data) {
const int val = GetLE24(*data);
*data += 3;
return val;
}
static uint32_t ReadLE32(const uint8_t** data) {
const uint32_t val = GetLE32(*data);
*data += 4;
return val;
}
static int ReadFileToWebPData(const char* const filename,
WebPData* const webp_data) {
const uint8_t* data;
size_t size;
if (!ImgIoUtilReadFile(filename, &data, &size)) return 0;
webp_data->bytes = data;
webp_data->size = size;
return 1;
}
// -----------------------------------------------------------------------------
// MemBuffer object.
static void InitMemBuffer(MemBuffer* const mem, const WebPData* webp_data) {
mem->buf_ = webp_data->bytes;
mem->start_ = 0;
mem->end_ = webp_data->size;
}
static size_t MemDataSize(const MemBuffer* const mem) {
return (mem->end_ - mem->start_);
}
static const uint8_t* GetBuffer(MemBuffer* const mem) {
return mem->buf_ + mem->start_;
}
static void Skip(MemBuffer* const mem, size_t size) {
mem->start_ += size;
}
static uint32_t ReadMemBufLE32(MemBuffer* const mem) {
const uint8_t* const data = mem->buf_ + mem->start_;
const uint32_t val = GetLE32(data);
assert(MemDataSize(mem) >= 4);
Skip(mem, 4);
return val;
}
// -----------------------------------------------------------------------------
// Chunk parsing.
#define LOG_ERROR(MESSAGE) \
if (webp_info->show_diagnosis_) { \
fprintf(stderr, "Error: %s\n", MESSAGE); \
}
#define LOG_WARN(MESSAGE) \
if (webp_info->show_diagnosis_) { \
fprintf(stderr, "Warning: %s\n", MESSAGE); \
}
static WebPInfoStatus ParseRIFFHeader(const WebPInfo* const webp_info,
MemBuffer* const mem) {
const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE;
size_t riff_size;
if (MemDataSize(mem) < min_size) {
LOG_ERROR("Truncated data detected when parsing RIFF header.");
return WEBP_INFO_TRUNCATED_DATA;
}
if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) ||
memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) {
LOG_ERROR("Corrupted RIFF header.");
return WEBP_INFO_PARSE_ERROR;
}
riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE);
if (riff_size < CHUNK_HEADER_SIZE) {
LOG_ERROR("RIFF size is too small.");
return WEBP_INFO_PARSE_ERROR;
}
if (riff_size > MAX_CHUNK_PAYLOAD) {
LOG_ERROR("RIFF size is over limit.");
return WEBP_INFO_PARSE_ERROR;
}
riff_size += CHUNK_HEADER_SIZE;
if (!webp_info->quiet_) {
printf("RIFF HEADER:\n");
printf(" RIFF file size: %6d\n", (int)riff_size);
}
if (riff_size < mem->end_) {
LOG_WARN("RIFF size is smaller than the file size.");
mem->end_ = riff_size;
} else if (riff_size > mem->end_) {
LOG_ERROR("Truncated data detected when parsing RIFF payload.");
return WEBP_INFO_TRUNCATED_DATA;
}
Skip(mem, RIFF_HEADER_SIZE);
return WEBP_INFO_OK;
}
static WebPInfoStatus ParseChunk(const WebPInfo* const webp_info,
MemBuffer* const mem,
ChunkData* const chunk_data) {
memset(chunk_data, 0, sizeof(*chunk_data));
if (MemDataSize(mem) < CHUNK_HEADER_SIZE) {
LOG_ERROR("Truncated data detected when parsing chunk header.");
return WEBP_INFO_TRUNCATED_DATA;
} else {
const size_t chunk_start_offset = mem->start_;
const uint32_t fourcc = ReadMemBufLE32(mem);
const uint32_t payload_size = ReadMemBufLE32(mem);
const uint32_t payload_size_padded = payload_size + (payload_size & 1);
const size_t chunk_size = CHUNK_HEADER_SIZE + payload_size_padded;
int i;
if (payload_size > MAX_CHUNK_PAYLOAD) {
LOG_ERROR("Size of chunk payload is over limit.");
return WEBP_INFO_INVALID_PARAM;
}
if (payload_size_padded > MemDataSize(mem)){
LOG_ERROR("Truncated data detected when parsing chunk payload.");
return WEBP_INFO_TRUNCATED_DATA;
}
for (i = 0; i < CHUNK_TYPES; ++i) {
if (!memcmp(kWebPChunkTags[i], &fourcc, TAG_SIZE)) break;
}
chunk_data->offset_ = chunk_start_offset;
chunk_data->size_ = chunk_size;
chunk_data->id_ = (ChunkID)i;
chunk_data->payload_ = GetBuffer(mem);
if (chunk_data->id_ == CHUNK_ANMF) {
if (payload_size != payload_size_padded) {
LOG_ERROR("ANMF chunk size should always be even.");
return WEBP_INFO_PARSE_ERROR;
}
// There are sub-chunks to be parsed in an ANMF chunk.
Skip(mem, ANMF_CHUNK_SIZE);
} else {
Skip(mem, payload_size_padded);
}
return WEBP_INFO_OK;
}
}
// -----------------------------------------------------------------------------
// Chunk analysis.
static WebPInfoStatus ProcessVP8XChunk(const ChunkData* const chunk_data,
WebPInfo* const webp_info) {
const uint8_t* data = chunk_data->payload_;
if (webp_info->chunk_counts_[CHUNK_VP8] ||
webp_info->chunk_counts_[CHUNK_VP8L] ||
webp_info->chunk_counts_[CHUNK_VP8X]) {
LOG_ERROR("Already seen a VP8/VP8L/VP8X chunk when parsing VP8X chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (chunk_data->size_ != VP8X_CHUNK_SIZE + CHUNK_HEADER_SIZE) {
LOG_ERROR("Corrupted VP8X chunk.");
return WEBP_INFO_PARSE_ERROR;
}
++webp_info->chunk_counts_[CHUNK_VP8X];
webp_info->feature_flags_ = *data;
data += 4;
webp_info->canvas_width_ = 1 + ReadLE24(&data);
webp_info->canvas_height_ = 1 + ReadLE24(&data);
if (!webp_info->quiet_) {
printf(" ICCP %d\n Alpha %d\n EXIF %d\n XMP %d\n Animation %d\n",
(webp_info->feature_flags_ & ICCP_FLAG) != 0,
(webp_info->feature_flags_ & ALPHA_FLAG) != 0,
(webp_info->feature_flags_ & EXIF_FLAG) != 0,
(webp_info->feature_flags_ & XMP_FLAG) != 0,
(webp_info->feature_flags_ & ANIMATION_FLAG) != 0);
printf(" Canvas size %d x %d\n",
webp_info->canvas_width_, webp_info->canvas_height_);
}
if (webp_info->canvas_width_ > MAX_CANVAS_SIZE) {
LOG_WARN("Canvas width is out of range in VP8X chunk.");
}
if (webp_info->canvas_height_ > MAX_CANVAS_SIZE) {
LOG_WARN("Canvas height is out of range in VP8X chunk.");
}
if ((uint64_t)webp_info->canvas_width_ * webp_info->canvas_height_ >
MAX_IMAGE_AREA) {
LOG_WARN("Canvas area is out of range in VP8X chunk.");
}
return WEBP_INFO_OK;
}
static WebPInfoStatus ProcessANIMChunk(const ChunkData* const chunk_data,
WebPInfo* const webp_info) {
const uint8_t* data = chunk_data->payload_;
if (!webp_info->chunk_counts_[CHUNK_VP8X]) {
LOG_ERROR("ANIM chunk detected before VP8X chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (chunk_data->size_ != ANIM_CHUNK_SIZE + CHUNK_HEADER_SIZE) {
LOG_ERROR("Corrupted ANIM chunk.");
return WEBP_INFO_PARSE_ERROR;
}
webp_info->bgcolor_ = ReadLE32(&data);
webp_info->loop_count_ = ReadLE16(&data);
++webp_info->chunk_counts_[CHUNK_ANIM];
if (!webp_info->quiet_) {
printf(" Background color:(BGRA) %02x %02x %02x %02x\n",
(webp_info->bgcolor_ >> 24) & 0xff,
(webp_info->bgcolor_ >> 16) & 0xff,
(webp_info->bgcolor_ >> 8) & 0xff,
webp_info->bgcolor_ & 0xff);
printf(" Loop count : %d\n", webp_info->loop_count_);
}
if (webp_info->loop_count_ > MAX_LOOP_COUNT) {
LOG_WARN("Loop count is out of range in ANIM chunk.");
}
return WEBP_INFO_OK;
}
static WebPInfoStatus ProcessANMFChunk(const ChunkData* const chunk_data,
WebPInfo* const webp_info) {
const uint8_t* data = chunk_data->payload_;
int offset_x, offset_y, width, height, duration, blend, dispose, temp;
if (webp_info->is_processing_anim_frame_) {
LOG_ERROR("ANMF chunk detected within another ANMF chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (!webp_info->chunk_counts_[CHUNK_ANIM]) {
LOG_ERROR("ANMF chunk detected before ANIM chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (chunk_data->size_ <= CHUNK_HEADER_SIZE + ANMF_CHUNK_SIZE) {
LOG_ERROR("Truncated data detected when parsing ANMF chunk.");
return WEBP_INFO_TRUNCATED_DATA;
}
offset_x = 2 * ReadLE24(&data);
offset_y = 2 * ReadLE24(&data);
width = 1 + ReadLE24(&data);
height = 1 + ReadLE24(&data);
duration = ReadLE24(&data);
temp = *data;
dispose = temp & 1;
blend = (temp >> 1) & 1;
++webp_info->chunk_counts_[CHUNK_ANMF];
if (!webp_info->quiet_) {
printf(" Offset_X %d\n Offset_Y %d\n Width %d\n Height %d\n"
" Duration %d\n Dispose %d\n Blend %d\n",
offset_x, offset_y, width, height, duration, dispose, blend);
}
if (duration > MAX_DURATION) {
LOG_ERROR("Invalid duration parameter in ANMF chunk.");
return WEBP_INFO_INVALID_PARAM;
}
if (offset_x > MAX_POSITION_OFFSET || offset_y > MAX_POSITION_OFFSET) {
LOG_ERROR("Invalid offset parameters in ANMF chunk.");
return WEBP_INFO_INVALID_PARAM;
}
if ((uint64_t)offset_x + width > (uint64_t)webp_info->canvas_width_ ||
(uint64_t)offset_y + height > (uint64_t)webp_info->canvas_height_) {
LOG_ERROR("Frame exceeds canvas in ANMF chunk.");
return WEBP_INFO_INVALID_PARAM;
}
webp_info->is_processing_anim_frame_ = 1;
webp_info->seen_alpha_subchunk_ = 0;
webp_info->seen_image_subchunk_ = 0;
webp_info->frame_width_ = width;
webp_info->frame_height_ = height;
webp_info->anim_frame_data_size_ =
chunk_data->size_ - CHUNK_HEADER_SIZE - ANMF_CHUNK_SIZE;
return WEBP_INFO_OK;
}
static WebPInfoStatus ProcessImageChunk(const ChunkData* const chunk_data,
WebPInfo* const webp_info) {
const uint8_t* data = chunk_data->payload_ - CHUNK_HEADER_SIZE;
WebPBitstreamFeatures features;
const VP8StatusCode vp8_status =
WebPGetFeatures(data, chunk_data->size_, &features);
if (vp8_status != VP8_STATUS_OK) {
LOG_ERROR("VP8/VP8L bitstream error.");
return WEBP_INFO_BITSTREAM_ERROR;
}
if (!webp_info->quiet_) {
printf(" Width %d\n Height %d\n Alpha %d\n Animation %d\n Format %d\n",
features.width, features.height, features.has_alpha,
features.has_animation, features.format);
}
if (webp_info->is_processing_anim_frame_) {
++webp_info->anmf_subchunk_counts_[chunk_data->id_ == CHUNK_VP8 ? 0 : 1];
if (chunk_data->id_ == CHUNK_VP8L && webp_info->seen_alpha_subchunk_) {
LOG_ERROR("Both VP8L and ALPH sub-chunks are present in an ANMF chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (webp_info->frame_width_ != features.width ||
webp_info->frame_height_ != features.height) {
LOG_ERROR("Frame size in VP8/VP8L sub-chunk differs from ANMF header.");
return WEBP_INFO_PARSE_ERROR;
}
if (webp_info->seen_image_subchunk_) {
LOG_ERROR("Consecutive VP8/VP8L sub-chunks in an ANMF chunk.");
return WEBP_INFO_PARSE_ERROR;
} else {
webp_info->seen_image_subchunk_ = 1;
}
} else {
if (webp_info->chunk_counts_[CHUNK_VP8] ||
webp_info->chunk_counts_[CHUNK_VP8L]) {
LOG_ERROR("Multiple VP8/VP8L chunks detected.");
return WEBP_INFO_PARSE_ERROR;
}
if (chunk_data->id_ == CHUNK_VP8L &&
webp_info->chunk_counts_[CHUNK_ALPHA]) {
LOG_ERROR("Both VP8L and ALPH chunks are detected.");
return WEBP_INFO_PARSE_ERROR;
}
if (webp_info->chunk_counts_[CHUNK_ANIM] ||
webp_info->chunk_counts_[CHUNK_ANMF]) {
LOG_ERROR("VP8/VP8L chunk and ANIM/ANMF chunk are both detected.");
return WEBP_INFO_PARSE_ERROR;
}
if (webp_info->chunk_counts_[CHUNK_VP8X]) {
if (webp_info->canvas_width_ != features.width ||
webp_info->canvas_height_ != features.height) {
LOG_ERROR("Image size in VP8/VP8L chunk differs from VP8X chunk.");
return WEBP_INFO_PARSE_ERROR;
}
} else {
webp_info->canvas_width_ = features.width;
webp_info->canvas_height_ = features.height;
if (webp_info->canvas_width_ < 1 || webp_info->canvas_height_ < 1 ||
webp_info->canvas_width_ > MAX_CANVAS_SIZE ||
webp_info->canvas_height_ > MAX_CANVAS_SIZE ||
(uint64_t)webp_info->canvas_width_ * webp_info->canvas_height_ >
MAX_IMAGE_AREA) {
LOG_WARN("Invalid parameters in VP8/VP8L chunk.");
}
}
++webp_info->chunk_counts_[chunk_data->id_];
}
++webp_info->num_frames_;
webp_info->has_alpha_ |= features.has_alpha;
return WEBP_INFO_OK;
}
static WebPInfoStatus ProcessALPHChunk(const ChunkData* const chunk_data,
WebPInfo* const webp_info) {
(void)chunk_data;
if (webp_info->is_processing_anim_frame_) {
++webp_info->anmf_subchunk_counts_[2];
if (webp_info->seen_alpha_subchunk_) {
LOG_ERROR("Consecutive ALPH sub-chunks in an ANMF chunk.");
return WEBP_INFO_PARSE_ERROR;
} else {
webp_info->seen_alpha_subchunk_ = 1;
}
if (webp_info->seen_image_subchunk_) {
LOG_ERROR("ALPHA sub-chunk detected after VP8 sub-chunk "
"in an ANMF chunk.");
return WEBP_INFO_PARSE_ERROR;
}
} else {
if (webp_info->chunk_counts_[CHUNK_ANIM] ||
webp_info->chunk_counts_[CHUNK_ANMF]) {
LOG_ERROR("ALPHA chunk and ANIM/ANMF chunk are both detected.");
return WEBP_INFO_PARSE_ERROR;
}
if (!webp_info->chunk_counts_[CHUNK_VP8X]) {
LOG_ERROR("ALPHA chunk detected before VP8X chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (webp_info->chunk_counts_[CHUNK_VP8]) {
LOG_ERROR("ALPHA chunk detected after VP8 chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (webp_info->chunk_counts_[CHUNK_ALPHA]) {
LOG_ERROR("Multiple ALPHA chunks detected.");
return WEBP_INFO_PARSE_ERROR;
}
++webp_info->chunk_counts_[CHUNK_ALPHA];
}
webp_info->has_alpha_ = 1;
return WEBP_INFO_OK;
}
static WebPInfoStatus ProcessICCPChunk(const ChunkData* const chunk_data,
WebPInfo* const webp_info) {
(void)chunk_data;
if (!webp_info->chunk_counts_[CHUNK_VP8X]) {
LOG_ERROR("ICCP chunk detected before VP8X chunk.");
return WEBP_INFO_PARSE_ERROR;
}
if (webp_info->chunk_counts_[CHUNK_VP8] ||
webp_info->chunk_counts_[CHUNK_VP8L] ||
webp_info->chunk_counts_[CHUNK_ANIM]) {
LOG_ERROR("ICCP chunk detected after image data.");
return WEBP_INFO_PARSE_ERROR;
}
++webp_info->chunk_counts_[CHUNK_ICCP];
return WEBP_INFO_OK;
}
static WebPInfoStatus ProcessChunk(const ChunkData* const chunk_data,
WebPInfo* const webp_info) {
WebPInfoStatus status = WEBP_INFO_OK;
ChunkID id = chunk_data->id_;
if (chunk_data->id_ == CHUNK_UNKNOWN) {
char error_message[50];
snprintf(error_message, 50, "Unknown chunk at offset %6d, length %6d",
(int)chunk_data->offset_, (int)chunk_data->size_);
LOG_WARN(error_message);
} else {
if (!webp_info->quiet_) {
const char* tag = kWebPChunkTags[chunk_data->id_];
printf("Chunk %c%c%c%c at offset %6d, length %6d\n",
tag[0], tag[1], tag[2], tag[3], (int)chunk_data->offset_,
(int)chunk_data->size_);
}
}
switch (id) {
case CHUNK_VP8:
case CHUNK_VP8L:
status = ProcessImageChunk(chunk_data, webp_info);
break;
case CHUNK_VP8X:
status = ProcessVP8XChunk(chunk_data, webp_info);
break;
case CHUNK_ALPHA:
status = ProcessALPHChunk(chunk_data, webp_info);
break;
case CHUNK_ANIM:
status = ProcessANIMChunk(chunk_data, webp_info);
break;
case CHUNK_ANMF:
status = ProcessANMFChunk(chunk_data, webp_info);
break;
case CHUNK_ICCP:
status = ProcessICCPChunk(chunk_data, webp_info);
break;
case CHUNK_EXIF:
case CHUNK_XMP:
++webp_info->chunk_counts_[id];
break;
case CHUNK_UNKNOWN:
default:
break;
}
if (webp_info->is_processing_anim_frame_ && id != CHUNK_ANMF) {
if (webp_info->anim_frame_data_size_ == chunk_data->size_) {
if (!webp_info->seen_image_subchunk_) {
LOG_ERROR("No VP8/VP8L chunk detected in an ANMF chunk.");
return WEBP_INFO_PARSE_ERROR;
}
webp_info->is_processing_anim_frame_ = 0;
} else if (webp_info->anim_frame_data_size_ > chunk_data->size_) {
webp_info->anim_frame_data_size_ -= chunk_data->size_;
} else {
LOG_ERROR("Truncated data detected when parsing ANMF chunk.");
return WEBP_INFO_TRUNCATED_DATA;
}
}
return status;
}
static WebPInfoStatus Validate(const WebPInfo* const webp_info) {
if (webp_info->num_frames_ < 1) {
LOG_ERROR("No image/frame detected.");
return WEBP_INFO_MISSING_DATA;
}
if (webp_info->chunk_counts_[CHUNK_VP8X]) {
const int iccp = !!(webp_info->feature_flags_ & ICCP_FLAG);
const int exif = !!(webp_info->feature_flags_ & EXIF_FLAG);
const int xmp = !!(webp_info->feature_flags_ & XMP_FLAG);
const int animation = !!(webp_info->feature_flags_ & ANIMATION_FLAG);
if (iccp && !webp_info->chunk_counts_[CHUNK_ICCP]) {
LOG_ERROR("Missing ICCP chunk.");
return WEBP_INFO_MISSING_DATA;
}
if (exif && !webp_info->chunk_counts_[CHUNK_EXIF]) {
LOG_ERROR("Missing EXIF chunk.");
return WEBP_INFO_MISSING_DATA;
}
if (xmp && !webp_info->chunk_counts_[CHUNK_XMP]) {
LOG_ERROR("Missing XMP chunk.");
return WEBP_INFO_MISSING_DATA;
}
if (!iccp && webp_info->chunk_counts_[CHUNK_ICCP]) {
LOG_ERROR("Unexpected ICCP chunk detected.");
return WEBP_INFO_PARSE_ERROR;
}
if (!exif && webp_info->chunk_counts_[CHUNK_EXIF]) {
LOG_ERROR("Unexpected EXIF chunk detected.");
return WEBP_INFO_PARSE_ERROR;
}
if (!xmp && webp_info->chunk_counts_[CHUNK_XMP]) {
LOG_ERROR("Unexpected XMP chunk detected.");
return WEBP_INFO_PARSE_ERROR;
}
// Incomplete animation frame.
if (webp_info->is_processing_anim_frame_) return WEBP_INFO_MISSING_DATA;
if (!animation && webp_info->num_frames_ > 1) {
LOG_ERROR("More than 1 frame detected in non-animation file.");
return WEBP_INFO_PARSE_ERROR;
}
if (animation && (!webp_info->chunk_counts_[CHUNK_ANIM] ||
!webp_info->chunk_counts_[CHUNK_ANMF])) {
LOG_ERROR("No ANIM/ANMF chunk detected in animation file.");
return WEBP_INFO_PARSE_ERROR;
}
}
return WEBP_INFO_OK;
}
static void ShowSummary(const WebPInfo* const webp_info) {
int i;
printf("Summary:\n");
printf("Number of frames: %d\n", webp_info->num_frames_);
printf("Chunk type : VP8 VP8L VP8X ALPH ANIM ANMF(VP8 /VP8L/ALPH) ICCP "
"EXIF XMP\n");
printf("Chunk counts: ");
for (i = 0; i < CHUNK_TYPES; ++i) {
printf("%4d ", webp_info->chunk_counts_[i]);
if (i == CHUNK_ANMF) {
printf("%4d %4d %4d ",
webp_info->anmf_subchunk_counts_[0],
webp_info->anmf_subchunk_counts_[1],
webp_info->anmf_subchunk_counts_[2]);
}
}
printf("\n");
}
static WebPInfoStatus AnalyzeWebP(WebPInfo* const webp_info,
const WebPData* webp_data) {
ChunkData chunk_data;
MemBuffer mem_buffer;
WebPInfoStatus webp_info_status = WEBP_INFO_OK;
InitMemBuffer(&mem_buffer, webp_data);
webp_info_status = ParseRIFFHeader(webp_info, &mem_buffer);
if (webp_info_status != WEBP_INFO_OK) goto Error;
// Loop through all the chunks. Terminate immediately in case of error.
while (webp_info_status == WEBP_INFO_OK && MemDataSize(&mem_buffer) > 0) {
webp_info_status = ParseChunk(webp_info, &mem_buffer, &chunk_data);
if (webp_info_status != WEBP_INFO_OK) goto Error;
webp_info_status = ProcessChunk(&chunk_data, webp_info);
}
if (webp_info_status != WEBP_INFO_OK) goto Error;
if (webp_info->show_summary_) ShowSummary(webp_info);
// Final check.
webp_info_status = Validate(webp_info);
Error:
if (!webp_info->quiet_) {
if (webp_info_status == WEBP_INFO_OK) {
printf("No error detected.\n");
} else {
printf("Errors detected.\n");
}
}
return webp_info_status;
}
static void HelpShort(void) {
printf("Usage: webpinfo [options] in_files\n"
"Try -longhelp for an exhaustive list of options.\n");
}
static void HelpLong(void) {
printf("Usage: webpinfo [options] in_files\n"
"Note: there could be multiple input files;\n"
" options must come before input files.\n"
"Options:\n"
" -quiet ......... Do not show chunk parsing information.\n"
" -diag .......... Show parsing error diagnosis.\n"
" -summary ....... Show chunk stats summary.\n");
}
int main(int argc, const char* argv[]) {
int c, quiet = 0, show_diag = 0, show_summary = 0;
WebPInfoStatus webp_info_status = WEBP_INFO_OK;
WebPInfo webp_info;
if (argc == 1) {
HelpShort();
return WEBP_INFO_OK;
}
// Parse command-line input.
for (c = 1; c < argc; ++c) {
if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
HelpShort();
return WEBP_INFO_OK;
} else if (!strcmp(argv[c], "-H") || !strcmp(argv[c], "-longhelp")) {
HelpLong();
return WEBP_INFO_OK;
} else if (!strcmp(argv[c], "-quiet")) {
quiet = 1;
} else if (!strcmp(argv[c], "-diag")) {
show_diag = 1;
} else if (!strcmp(argv[c], "-summary")) {
show_summary = 1;
} else { // Assume the remaining are all input files.
break;
}
}
if (c == argc) {
HelpShort();
return WEBP_INFO_INVALID_COMMAND;
}
// Process input files one by one.
for (; c < argc; ++c) {
WebPData webp_data;
const char* in_file = NULL;
WebPInfoInit(&webp_info);
webp_info.quiet_ = quiet;
webp_info.show_diagnosis_ = show_diag;
webp_info.show_summary_ = show_summary;
in_file = argv[c];
if (in_file == NULL || !ReadFileToWebPData(in_file, &webp_data)) {
webp_info_status = WEBP_INFO_INVALID_COMMAND;
fprintf(stderr, "Failed to open input file %s.\n", in_file);
continue;
}
if (!webp_info.quiet_) printf("File %s.\n", in_file);
webp_info_status = AnalyzeWebP(&webp_info, &webp_data);
WebPDataClear(&webp_data);
}
return webp_info_status;
}

View File

@ -333,7 +333,7 @@ OUT_LIBS += src/libwebp.a
EXTRA_LIB = extras/libwebpextras.a EXTRA_LIB = extras/libwebpextras.a
OUT_EXAMPLES = examples/cwebp examples/dwebp OUT_EXAMPLES = examples/cwebp examples/dwebp
EXTRA_EXAMPLES = examples/gif2webp examples/vwebp examples/webpmux \ EXTRA_EXAMPLES = examples/gif2webp examples/vwebp examples/webpmux \
examples/anim_diff examples/img2webp examples/anim_diff examples/img2webp examples/webpinfo
OTHER_EXAMPLES = extras/get_disto extras/webp_quality extras/vwebp_sdl OTHER_EXAMPLES = extras/get_disto extras/webp_quality extras/vwebp_sdl
OUTPUT = $(OUT_LIBS) $(OUT_EXAMPLES) OUTPUT = $(OUT_LIBS) $(OUT_EXAMPLES)
@ -385,6 +385,7 @@ examples/gif2webp: examples/gif2webp.o $(GIFDEC_OBJS)
examples/vwebp: examples/vwebp.o examples/vwebp: examples/vwebp.o
examples/webpmux: examples/webpmux.o examples/webpmux: examples/webpmux.o
examples/img2webp: examples/img2webp.o examples/img2webp: examples/img2webp.o
examples/webpinfo: examples/webpinfo.o
examples/anim_diff: examples/libanim_util.a examples/libgifdec.a examples/anim_diff: examples/libanim_util.a examples/libgifdec.a
examples/anim_diff: src/demux/libwebpdemux.a examples/libexample_util.a examples/anim_diff: src/demux/libwebpdemux.a examples/libexample_util.a
@ -416,6 +417,8 @@ examples/img2webp: examples/libexample_util.a imageio/libimageio_util.a
examples/img2webp: imageio/libimagedec.a examples/img2webp: imageio/libimagedec.a
examples/img2webp: src/mux/libwebpmux.a src/libwebp.a examples/img2webp: src/mux/libwebpmux.a src/libwebp.a
examples/img2webp: EXTRA_LIBS += $(CWEBP_LIBS) examples/img2webp: EXTRA_LIBS += $(CWEBP_LIBS)
examples/webpinfo: examples/libexample_util.a imageio/libimageio_util.a
examples/webpinfo: src/libwebpdecoder.a
extras/get_disto: extras/get_disto.o extras/get_disto: extras/get_disto.o
extras/get_disto: imageio/libimagedec.a imageio/libimageio_util.a src/libwebp.a extras/get_disto: imageio/libimagedec.a imageio/libimageio_util.a src/libwebp.a